2026-01-14 17:24:00 -08:00
import {
buildBootstrapContextFiles ,
resolveBootstrapMaxChars ,
} from "../../agents/pi-embedded-helpers.js" ;
2026-01-15 01:06:19 +00:00
import { createClawdbotCodingTools } from "../../agents/pi-tools.js" ;
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js" ;
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js" ;
2026-01-16 03:45:03 +00:00
import { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js" ;
2026-01-15 01:06:19 +00:00
import { buildAgentSystemPrompt } from "../../agents/system-prompt.js" ;
import { buildSystemPromptReport } from "../../agents/system-prompt-report.js" ;
import { buildToolSummaryMap } from "../../agents/tool-summaries.js" ;
2026-01-18 05:31:04 +00:00
import { resolveBootstrapFilesForRun } from "../../agents/bootstrap-files.js" ;
2026-01-15 01:06:19 +00:00
import type { SessionSystemPromptReport } from "../../config/sessions/types.js" ;
2026-01-16 03:45:03 +00:00
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js" ;
2026-01-15 01:06:19 +00:00
import type { ReplyPayload } from "../types.js" ;
import type { HandleCommandsParams } from "./commands-types.js" ;
function estimateTokensFromChars ( chars : number ) : number {
return Math . ceil ( Math . max ( 0 , chars ) / 4 ) ;
}
function formatInt ( n : number ) : string {
return new Intl . NumberFormat ( "en-US" ) . format ( n ) ;
}
function formatCharsAndTokens ( chars : number ) : string {
return ` ${ formatInt ( chars ) } chars (~ ${ formatInt ( estimateTokensFromChars ( chars ) ) } tok) ` ;
}
function parseContextArgs ( commandBodyNormalized : string ) : string {
if ( commandBodyNormalized === "/context" ) return "" ;
if ( commandBodyNormalized . startsWith ( "/context " ) ) return commandBodyNormalized . slice ( 8 ) . trim ( ) ;
return "" ;
}
function formatListTop (
entries : Array < { name : string ; value : number } > ,
cap : number ,
) : { lines : string [ ] ; omitted : number } {
const sorted = [ . . . entries ] . sort ( ( a , b ) = > b . value - a . value ) ;
const top = sorted . slice ( 0 , cap ) ;
const omitted = Math . max ( 0 , sorted . length - top . length ) ;
const lines = top . map ( ( e ) = > ` - ${ e . name } : ${ formatCharsAndTokens ( e . value ) } ` ) ;
return { lines , omitted } ;
}
async function resolveContextReport (
params : HandleCommandsParams ,
) : Promise < SessionSystemPromptReport > {
const existing = params . sessionEntry ? . systemPromptReport ;
if ( existing && existing . source === "run" ) return existing ;
const workspaceDir = params . workspaceDir ;
const bootstrapMaxChars = resolveBootstrapMaxChars ( params . cfg ) ;
2026-01-18 05:31:04 +00:00
const hookAdjustedBootstrapFiles = await resolveBootstrapFilesForRun ( {
2026-01-18 05:24:47 +00:00
workspaceDir ,
config : params.cfg ,
sessionKey : params.sessionKey ,
sessionId : params.sessionEntry?.sessionId ,
} ) ;
const injectedFiles = buildBootstrapContextFiles ( hookAdjustedBootstrapFiles , {
2026-01-15 01:06:19 +00:00
maxChars : bootstrapMaxChars ,
} ) ;
const skillsSnapshot = ( ( ) = > {
try {
2026-01-16 03:45:03 +00:00
return buildWorkspaceSkillSnapshot ( workspaceDir , {
config : params.cfg ,
eligibility : { remote : getRemoteSkillEligibility ( ) } ,
snapshotVersion : getSkillsSnapshotVersion ( workspaceDir ) ,
} ) ;
2026-01-15 01:06:19 +00:00
} catch {
return { prompt : "" , skills : [ ] , resolvedSkills : [ ] } ;
}
} ) ( ) ;
const skillsPrompt = skillsSnapshot . prompt ? ? "" ;
const sandboxRuntime = resolveSandboxRuntimeStatus ( {
cfg : params.cfg ,
sessionKey : params.ctx.SessionKey ? ? params . sessionKey ,
} ) ;
const tools = ( ( ) = > {
try {
return createClawdbotCodingTools ( {
config : params.cfg ,
workspaceDir ,
sessionKey : params.sessionKey ,
messageProvider : params.command.channel ,
modelProvider : params.provider ,
modelId : params.model ,
} ) ;
} catch {
return [ ] ;
}
} ) ( ) ;
const toolSummaries = buildToolSummaryMap ( tools ) ;
const toolNames = tools . map ( ( t ) = > t . name ) ;
const runtimeInfo = {
host : "unknown" ,
os : "unknown" ,
arch : "unknown" ,
node : process.version ,
model : ` ${ params . provider } / ${ params . model } ` ,
} ;
const sandboxInfo = sandboxRuntime . sandboxed
? {
enabled : true ,
workspaceDir ,
workspaceAccess : "rw" as const ,
elevated : {
allowed : params.elevated.allowed ,
defaultLevel : params.resolvedElevatedLevel === "off" ? ( "off" as const ) : ( "on" as const ) ,
} ,
}
: { enabled : false } ;
const systemPrompt = buildAgentSystemPrompt ( {
workspaceDir ,
defaultThinkLevel : params.resolvedThinkLevel ,
reasoningLevel : params.resolvedReasoningLevel ,
extraSystemPrompt : undefined ,
ownerNumbers : undefined ,
reasoningTagHint : false ,
toolNames ,
toolSummaries ,
modelAliasLines : [ ] ,
userTimezone : "" ,
userTime : "" ,
contextFiles : injectedFiles ,
skillsPrompt ,
heartbeatPrompt : undefined ,
runtimeInfo ,
sandboxInfo ,
} ) ;
return buildSystemPromptReport ( {
source : "estimate" ,
generatedAt : Date.now ( ) ,
sessionId : params.sessionEntry?.sessionId ,
sessionKey : params.sessionKey ,
provider : params.provider ,
model : params.model ,
workspaceDir ,
bootstrapMaxChars ,
sandbox : { mode : sandboxRuntime.mode , sandboxed : sandboxRuntime.sandboxed } ,
systemPrompt ,
2026-01-18 05:24:47 +00:00
bootstrapFiles : hookAdjustedBootstrapFiles ,
2026-01-15 01:06:19 +00:00
injectedFiles ,
skillsPrompt ,
tools ,
} ) ;
}
export async function buildContextReply ( params : HandleCommandsParams ) : Promise < ReplyPayload > {
const args = parseContextArgs ( params . command . commandBodyNormalized ) ;
const sub = args . split ( /\s+/ ) . filter ( Boolean ) [ 0 ] ? . toLowerCase ( ) ? ? "" ;
if ( ! sub || sub === "help" ) {
return {
text : [
"🧠 /context" ,
"" ,
"What counts as context (high-level), plus a breakdown mode." ,
"" ,
"Try:" ,
"- /context list (short breakdown)" ,
"- /context detail (per-file + per-tool + per-skill + system prompt size)" ,
"- /context json (same, machine-readable)" ,
"" ,
"Inline shortcut = a command token inside a normal message (e.g. “hey /status”). It runs immediately (allowlisted senders only) and is stripped before the model sees the remaining text." ,
] . join ( "\n" ) ,
} ;
}
const report = await resolveContextReport ( params ) ;
const session = {
totalTokens : params.sessionEntry?.totalTokens ? ? null ,
inputTokens : params.sessionEntry?.inputTokens ? ? null ,
outputTokens : params.sessionEntry?.outputTokens ? ? null ,
contextTokens : params.contextTokens ? ? null ,
} as const ;
if ( sub === "json" ) {
return { text : JSON.stringify ( { report , session } , null , 2 ) } ;
}
if ( sub !== "list" && sub !== "show" && sub !== "detail" && sub !== "deep" ) {
return {
text : [
"Unknown /context mode." ,
"Use: /context, /context list, /context detail, or /context json" ,
] . join ( "\n" ) ,
} ;
}
const fileLines = report . injectedWorkspaceFiles . map ( ( f ) = > {
const status = f . missing ? "MISSING" : f . truncated ? "TRUNCATED" : "OK" ;
const raw = f . missing ? "0" : formatCharsAndTokens ( f . rawChars ) ;
const injected = f . missing ? "0" : formatCharsAndTokens ( f . injectedChars ) ;
return ` - ${ f . name } : ${ status } | raw ${ raw } | injected ${ injected } ` ;
} ) ;
const sandboxLine = ` Sandbox: mode= ${ report . sandbox ? . mode ? ? "unknown" } sandboxed= ${ report . sandbox ? . sandboxed ? ? false } ` ;
const toolSchemaLine = ` Tool schemas (JSON): ${ formatCharsAndTokens ( report . tools . schemaChars ) } (counts toward context; not shown as text) ` ;
const toolListLine = ` Tool list (system prompt text): ${ formatCharsAndTokens ( report . tools . listChars ) } ` ;
const skillNameSet = new Set ( report . skills . entries . map ( ( s ) = > s . name ) ) ;
const skillNames = Array . from ( skillNameSet ) ;
const toolNames = report . tools . entries . map ( ( t ) = > t . name ) ;
const formatNameList = ( names : string [ ] , cap : number ) = >
2026-01-14 17:24:00 -08:00
names . length <= cap
? names . join ( ", " )
: ` ${ names . slice ( 0 , cap ) . join ( ", " ) } , … (+ ${ names . length - cap } more) ` ;
2026-01-15 01:06:19 +00:00
const skillsLine = ` Skills list (system prompt text): ${ formatCharsAndTokens ( report . skills . promptChars ) } ( ${ skillNameSet . size } skills) ` ;
const skillsNamesLine = skillNameSet . size
? ` Skills: ${ formatNameList ( skillNames , 20 ) } `
: "Skills: (none)" ;
const toolsNamesLine = toolNames . length
? ` Tools: ${ formatNameList ( toolNames , 30 ) } `
: "Tools: (none)" ;
const systemPromptLine = ` System prompt ( ${ report . source } ): ${ formatCharsAndTokens ( report . systemPrompt . chars ) } (Project Context ${ formatCharsAndTokens ( report . systemPrompt . projectContextChars ) } ) ` ;
const workspaceLabel = report . workspaceDir ? ? params . workspaceDir ;
const bootstrapMaxLabel =
2026-01-14 17:24:00 -08:00
typeof report . bootstrapMaxChars === "number"
? ` ${ formatInt ( report . bootstrapMaxChars ) } chars `
: "? chars" ;
2026-01-15 01:06:19 +00:00
const totalsLine =
session . totalTokens != null
? ` Session tokens (cached): ${ formatInt ( session . totalTokens ) } total / ctx= ${ session . contextTokens ? ? "?" } `
: ` Session tokens (cached): unknown / ctx= ${ session . contextTokens ? ? "?" } ` ;
if ( sub === "detail" || sub === "deep" ) {
const perSkill = formatListTop (
report . skills . entries . map ( ( s ) = > ( { name : s.name , value : s.blockChars } ) ) ,
30 ,
) ;
const perToolSchema = formatListTop (
report . tools . entries . map ( ( t ) = > ( { name : t.name , value : t.schemaChars } ) ) ,
30 ,
) ;
const perToolSummary = formatListTop (
report . tools . entries . map ( ( t ) = > ( { name : t.name , value : t.summaryChars } ) ) ,
30 ,
) ;
const toolPropsLines = report . tools . entries
. filter ( ( t ) = > t . propertiesCount != null )
. sort ( ( a , b ) = > ( b . propertiesCount ? ? 0 ) - ( a . propertiesCount ? ? 0 ) )
. slice ( 0 , 30 )
. map ( ( t ) = > ` - ${ t . name } : ${ t . propertiesCount } params ` ) ;
return {
text : [
"🧠 Context breakdown (detailed)" ,
` Workspace: ${ workspaceLabel } ` ,
` Bootstrap max/file: ${ bootstrapMaxLabel } ` ,
sandboxLine ,
systemPromptLine ,
"" ,
"Injected workspace files:" ,
. . . fileLines ,
"" ,
skillsLine ,
skillsNamesLine ,
. . . ( perSkill . lines . length ? [ "Top skills (prompt entry size):" , . . . perSkill . lines ] : [ ] ) ,
. . . ( perSkill . omitted ? [ ` … (+ ${ perSkill . omitted } more skills) ` ] : [ ] ) ,
"" ,
toolListLine ,
toolSchemaLine ,
toolsNamesLine ,
"Top tools (schema size):" ,
. . . perToolSchema . lines ,
. . . ( perToolSchema . omitted ? [ ` … (+ ${ perToolSchema . omitted } more tools) ` ] : [ ] ) ,
"" ,
"Top tools (summary text size):" ,
. . . perToolSummary . lines ,
. . . ( perToolSummary . omitted ? [ ` … (+ ${ perToolSummary . omitted } more tools) ` ] : [ ] ) ,
. . . ( toolPropsLines . length ? [ "" , "Tools (param count):" , . . . toolPropsLines ] : [ ] ) ,
"" ,
totalsLine ,
"" ,
"Inline shortcut: a command token inside normal text (e.g. “hey /status”) that runs immediately (allowlisted senders only) and is stripped before the model sees the remaining message." ,
]
. filter ( Boolean )
. join ( "\n" ) ,
} ;
}
return {
text : [
"🧠 Context breakdown" ,
` Workspace: ${ workspaceLabel } ` ,
` Bootstrap max/file: ${ bootstrapMaxLabel } ` ,
sandboxLine ,
systemPromptLine ,
"" ,
"Injected workspace files:" ,
. . . fileLines ,
"" ,
skillsLine ,
skillsNamesLine ,
toolListLine ,
toolSchemaLine ,
toolsNamesLine ,
"" ,
totalsLine ,
"" ,
"Inline shortcut: a command token inside normal text (e.g. “hey /status”) that runs immediately (allowlisted senders only) and is stripped before the model sees the remaining message." ,
] . join ( "\n" ) ,
} ;
}