import { resolveSessionAgentIds } from "../../agents/agent-scope.js"; import { resolveBootstrapMaxChars } from "../../agents/pi-embedded-helpers.js"; import { createClawdbotCodingTools } from "../../agents/pi-tools.js"; import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js"; import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js"; import { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js"; import { buildAgentSystemPrompt } from "../../agents/system-prompt.js"; import { buildSystemPromptReport } from "../../agents/system-prompt-report.js"; import { buildSystemPromptParams } from "../../agents/system-prompt-params.js"; import { resolveDefaultModelForAgent } from "../../agents/model-selection.js"; import { buildToolSummaryMap } from "../../agents/tool-summaries.js"; import { resolveBootstrapContextForRun } from "../../agents/bootstrap-files.js"; import type { SessionSystemPromptReport } from "../../config/sessions/types.js"; import { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; 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 { const existing = params.sessionEntry?.systemPromptReport; if (existing && existing.source === "run") return existing; const workspaceDir = params.workspaceDir; const bootstrapMaxChars = resolveBootstrapMaxChars(params.cfg); const { bootstrapFiles, contextFiles: injectedFiles } = await resolveBootstrapContextForRun({ workspaceDir, config: params.cfg, sessionKey: params.sessionKey, sessionId: params.sessionEntry?.sessionId, }); const skillsSnapshot = (() => { try { return buildWorkspaceSkillSnapshot(workspaceDir, { config: params.cfg, eligibility: { remote: getRemoteSkillEligibility() }, snapshotVersion: getSkillsSnapshotVersion(workspaceDir), }); } 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, groupId: params.sessionEntry?.groupId ?? undefined, groupChannel: params.sessionEntry?.groupChannel ?? undefined, groupSpace: params.sessionEntry?.space ?? undefined, spawnedBy: params.sessionEntry?.spawnedBy ?? undefined, modelProvider: params.provider, modelId: params.model, }); } catch { return []; } })(); const toolSummaries = buildToolSummaryMap(tools); const toolNames = tools.map((t) => t.name); const { sessionAgentId } = resolveSessionAgentIds({ sessionKey: params.sessionKey, config: params.cfg, }); const defaultModelRef = resolveDefaultModelForAgent({ cfg: params.cfg, agentId: sessionAgentId, }); const defaultModelLabel = `${defaultModelRef.provider}/${defaultModelRef.model}`; const { runtimeInfo, userTimezone, userTime, userTimeFormat } = buildSystemPromptParams({ config: params.cfg, agentId: sessionAgentId, workspaceDir, cwd: process.cwd(), runtime: { host: "unknown", os: "unknown", arch: "unknown", node: process.version, model: `${params.provider}/${params.model}`, defaultModel: defaultModelLabel, }, }); const sandboxInfo = sandboxRuntime.sandboxed ? { enabled: true, workspaceDir, workspaceAccess: "rw" as const, elevated: { allowed: params.elevated.allowed, defaultLevel: (params.resolvedElevatedLevel ?? "off") as "on" | "off" | "ask" | "full", }, } : { enabled: false }; const systemPrompt = buildAgentSystemPrompt({ workspaceDir, defaultThinkLevel: params.resolvedThinkLevel, reasoningLevel: params.resolvedReasoningLevel, extraSystemPrompt: undefined, ownerNumbers: undefined, reasoningTagHint: false, toolNames, toolSummaries, modelAliasLines: [], userTimezone, userTime, userTimeFormat, 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, bootstrapFiles, injectedFiles, skillsPrompt, tools, }); } export async function buildContextReply(params: HandleCommandsParams): Promise { 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) => names.length <= cap ? names.join(", ") : `${names.slice(0, cap).join(", ")}, … (+${names.length - cap} more)`; 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 = typeof report.bootstrapMaxChars === "number" ? `${formatInt(report.bootstrapMaxChars)} chars` : "? chars"; 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"), }; }