@ponythewhite/base-context 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/base-context-runtime/pyproject.toml +1 -1
- package/dist/base-context-runtime/uv.lock +1 -1
- package/dist/build-info.json +1 -1
- package/dist/bundle/amazon-bedrock.js +3 -3
- package/dist/bundle/{anthropic-5PSCZEER.js → anthropic-BJVPUMPB.js} +2 -2
- package/dist/bundle/{azure-openai-responses-AYTLJR33.js → azure-openai-responses-ERRMAXK6.js} +4 -5
- package/dist/bundle/{bundled-modules-PEFT72QN.js → bundled-modules-WCGPZI2H.js} +5 -5
- package/dist/bundle/{chunk-D3KOF57W.js → chunk-25VYRAC6.js} +14 -1
- package/dist/bundle/{chunk-2IPDSTDR.js → chunk-AEMRBMI2.js} +9 -9
- package/dist/bundle/{chunk-Q3MBSBEB.js → chunk-FOGKLC7V.js} +1 -1
- package/dist/bundle/{chunk-JZHGOOFM.js → chunk-IAKHCQA2.js} +118 -22
- package/dist/bundle/{chunk-KT2BNEPE.js → chunk-MUFZODP5.js} +1 -1
- package/dist/bundle/{chunk-QKIFBA7N.js → chunk-Q6UAHLII.js} +2 -2
- package/dist/bundle/{chunk-TOOVREMP.js → chunk-YGV3Y4AD.js} +515 -107
- package/dist/bundle/{cli-main-BRXGYTGK.js → cli-main-CTYDQHFE.js} +4 -4
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/{google-HTMCUJ7X.js → google-4GA4YMUX.js} +2 -2
- package/dist/bundle/{google-vertex-ZQO65AXF.js → google-vertex-7AMALD7D.js} +2 -2
- package/dist/bundle/{main-POUXGEL6.js → main-GVGK354K.js} +5 -5
- package/dist/bundle/{mistral-SB7LU56G.js → mistral-74BXMNRM.js} +1 -1
- package/dist/bundle/{openai-codex-responses-5CEW4VN3.js → openai-codex-responses-COXNC4IH.js} +2 -2
- package/dist/bundle/{openai-completions-BAZJQSS2.js → openai-completions-BCSA5Q6S.js} +2 -2
- package/dist/bundle/{openai-responses-VHOE427P.js → openai-responses-ONCIZSJS.js} +3 -3
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +362 -103
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +9 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +29 -3
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context-tree.d.ts +13 -10
- package/dist/core/context-tree.d.ts.map +1 -1
- package/dist/core/context-tree.js +49 -3
- package/dist/core/context-tree.js.map +1 -1
- package/dist/core/inference-coordinator.d.ts +1 -1
- package/dist/core/inference-coordinator.d.ts.map +1 -1
- package/dist/core/inference-coordinator.js +18 -5
- package/dist/core/inference-coordinator.js.map +1 -1
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +2 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/refinement/refinement.d.ts +2 -0
- package/dist/core/refinement/refinement.d.ts.map +1 -1
- package/dist/core/refinement/refinement.js +10 -1
- package/dist/core/refinement/refinement.js.map +1 -1
- package/dist/core/request-usage.d.ts +34 -0
- package/dist/core/request-usage.d.ts.map +1 -0
- package/dist/core/request-usage.js +111 -0
- package/dist/core/request-usage.js.map +1 -0
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +7 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +4 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
- package/dist/modes/agent-connection/daemon-agent-connection.js +8 -1
- package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
- package/dist/modes/daemon/daemon-protocol.d.ts +13 -3
- package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-protocol.js +10 -2
- package/dist/modes/daemon/daemon-protocol.js.map +1 -1
- package/dist/modes/daemon/daemon-session-summarizer.d.ts +3 -0
- package/dist/modes/daemon/daemon-session-summarizer.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-session-summarizer.js +71 -32
- package/dist/modes/daemon/daemon-session-summarizer.js.map +1 -1
- package/dist/modes/interactive/components/context-tree-format.d.ts.map +1 -1
- package/dist/modes/interactive/components/context-tree-format.js +49 -0
- package/dist/modes/interactive/components/context-tree-format.js.map +1 -1
- package/docs/compaction.md +27 -3
- package/docs/context-management.md +17 -1
- package/docs/rlm.md +1 -1
- package/docs/settings.md +12 -0
- package/docs/usage.md +24 -1
- package/package.json +4 -4
|
@@ -2,6 +2,8 @@ import { completeInference } from "../../core/inference-coordinator.js";
|
|
|
2
2
|
const SWEEP_INTERVAL_MS = 25_000;
|
|
3
3
|
// Collapse a tool-use loop's rapid turn_end bursts into one summarization.
|
|
4
4
|
const SETTLE_DEBOUNCE_MS = 2_000;
|
|
5
|
+
// Keep rapid tool/stream updates local; periodically refresh the generated recap.
|
|
6
|
+
const MIN_REFRESH_INTERVAL_MS = 60_000;
|
|
5
7
|
const SUMMARY_CONTEXT_MESSAGES = 8;
|
|
6
8
|
const SUMMARY_MAX_CHARS_PER_MESSAGE = 600;
|
|
7
9
|
// Generous so a chatty model still closes the tags before truncation.
|
|
@@ -37,7 +39,7 @@ function messageText(content) {
|
|
|
37
39
|
if (type === "text" && typeof block.text === "string") {
|
|
38
40
|
parts.push(block.text);
|
|
39
41
|
}
|
|
40
|
-
else if (type === "tool_use" || type === "toolUse") {
|
|
42
|
+
else if (type === "toolCall" || type === "tool_use" || type === "toolUse") {
|
|
41
43
|
const name = block.name;
|
|
42
44
|
if (typeof name === "string") {
|
|
43
45
|
tools.push(name);
|
|
@@ -110,7 +112,7 @@ export function parseAgentStatusResponse(text, isWorking) {
|
|
|
110
112
|
}
|
|
111
113
|
/** Use the session's selected model, or keep the local status when unavailable. */
|
|
112
114
|
export async function generateAgentStatus(params) {
|
|
113
|
-
const { registry, messages, isWorking, signal, requests, model: selectedModel } = params;
|
|
115
|
+
const { registry, messages, isWorking, sessionId, signal, requests, model: selectedModel } = params;
|
|
114
116
|
if (messages.length === 0) {
|
|
115
117
|
return undefined;
|
|
116
118
|
}
|
|
@@ -134,7 +136,13 @@ export async function generateAgentStatus(params) {
|
|
|
134
136
|
timestamp,
|
|
135
137
|
},
|
|
136
138
|
],
|
|
137
|
-
}, {
|
|
139
|
+
}, {
|
|
140
|
+
maxTokens: SUMMARY_MAX_TOKENS,
|
|
141
|
+
apiKey: auth.apiKey,
|
|
142
|
+
headers: auth.headers,
|
|
143
|
+
signal,
|
|
144
|
+
sessionId: sessionId ? `daemon-status:${sessionId}` : undefined,
|
|
145
|
+
}, { purpose: "native-control", purposeDetail: "daemon-status" });
|
|
138
146
|
if (response.stopReason === "error") {
|
|
139
147
|
return undefined;
|
|
140
148
|
}
|
|
@@ -181,6 +189,7 @@ export class DaemonSessionSummarizer {
|
|
|
181
189
|
debounceTimers = new Map();
|
|
182
190
|
// Controller per in-flight summary so a closing session can abort its write.
|
|
183
191
|
inFlight = new Map();
|
|
192
|
+
refreshes = new Map();
|
|
184
193
|
tasks = new Map();
|
|
185
194
|
forgotten = new Set();
|
|
186
195
|
stopped = false;
|
|
@@ -232,6 +241,7 @@ export class DaemonSessionSummarizer {
|
|
|
232
241
|
this.debounceTimers.delete(activeSessionId);
|
|
233
242
|
}
|
|
234
243
|
this.inFlight.get(activeSessionId)?.abort();
|
|
244
|
+
this.refreshes.delete(activeSessionId);
|
|
235
245
|
this.rerunRequested.delete(activeSessionId);
|
|
236
246
|
await this.tasks.get(activeSessionId);
|
|
237
247
|
}
|
|
@@ -248,6 +258,9 @@ export class DaemonSessionSummarizer {
|
|
|
248
258
|
}
|
|
249
259
|
/** Called when a session finishes a turn; debounce until the agent settles. */
|
|
250
260
|
notifyActivity(state) {
|
|
261
|
+
this.schedule(state, SETTLE_DEBOUNCE_MS);
|
|
262
|
+
}
|
|
263
|
+
schedule(state, delay) {
|
|
251
264
|
const id = state.activeSessionId;
|
|
252
265
|
if (this.stopped || this.forgotten.has(id))
|
|
253
266
|
return;
|
|
@@ -258,7 +271,7 @@ export class DaemonSessionSummarizer {
|
|
|
258
271
|
const timer = setTimeout(() => {
|
|
259
272
|
this.debounceTimers.delete(id);
|
|
260
273
|
this.summarize(state);
|
|
261
|
-
},
|
|
274
|
+
}, delay);
|
|
262
275
|
timer.unref?.();
|
|
263
276
|
this.debounceTimers.set(id, timer);
|
|
264
277
|
}
|
|
@@ -294,48 +307,71 @@ export class DaemonSessionSummarizer {
|
|
|
294
307
|
const messageCount = messages.length;
|
|
295
308
|
const isWorking = isSessionWorking(state);
|
|
296
309
|
const previous = state.summaryState;
|
|
297
|
-
// Idle sessions with a current verdict need no refresh unless a terminal
|
|
298
|
-
// error must replace a previously fabricated verdict. Working sessions
|
|
299
|
-
// always refresh so the recap keeps up with the in-progress turn.
|
|
300
|
-
const contentUnchanged = previous?.basedOnMessageCount === messageCount;
|
|
301
310
|
const owesIdleVerdict = !isWorking && previous?.taskState === undefined;
|
|
302
|
-
// A blank recap means the model call hasn't succeeded yet (e.g. the
|
|
303
|
-
// needs_input fallback fired on a transient failure); keep retrying until a
|
|
304
|
-
// real summary lands so the recap isn't left permanently empty.
|
|
305
311
|
const owesSummary = !isWorking && !previous?.summary;
|
|
306
312
|
const turnError = !isWorking ? terminalTurnError(messages) : undefined;
|
|
307
|
-
|
|
308
|
-
|
|
313
|
+
// Compare exactly what the classifier sees, not message counts or hidden
|
|
314
|
+
// thinking/tool output beyond the bounded prompt.
|
|
315
|
+
const contextMessages = () => {
|
|
316
|
+
const streaming = isWorking ? session.state.streamingMessage : undefined;
|
|
317
|
+
return streaming ? [...session.messages, streaming] : session.messages;
|
|
318
|
+
};
|
|
319
|
+
const input = buildStatusContext(contextMessages(), isWorking);
|
|
320
|
+
const model = session.model;
|
|
321
|
+
const isCurrentSource = () => state.runtime.session === session &&
|
|
322
|
+
session.sessionManager === sessionManager &&
|
|
323
|
+
sessionManager.getSessionId() === sessionId &&
|
|
324
|
+
sessionManager.getSessionFile() === sessionFile &&
|
|
325
|
+
session.model === model;
|
|
326
|
+
let refresh = this.refreshes.get(id);
|
|
327
|
+
if (!refresh?.isCurrentSource()) {
|
|
328
|
+
refresh = { isCurrentSource };
|
|
329
|
+
this.refreshes.set(id, refresh);
|
|
330
|
+
}
|
|
331
|
+
const finalTransition = !isWorking && refresh.wasWorking === true;
|
|
332
|
+
refresh.wasWorking = isWorking;
|
|
333
|
+
const reuse = turnError === undefined && refresh.input === input && previous?.summary ? previous : undefined;
|
|
334
|
+
if ((reuse ||
|
|
335
|
+
(turnError !== undefined && previous?.summary === turnError && previous.taskState === "needs_input")) &&
|
|
336
|
+
previous?.basedOnMessageCount === messageCount) {
|
|
309
337
|
return;
|
|
310
338
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
339
|
+
if (!reuse && turnError === undefined) {
|
|
340
|
+
// A final working -> idle verdict must not wait behind the live recap.
|
|
341
|
+
const remaining = MIN_REFRESH_INTERVAL_MS - (Date.now() - (refresh.attemptedAt ?? -Infinity));
|
|
342
|
+
if (!finalTransition && remaining > 0) {
|
|
343
|
+
this.schedule(state, remaining);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
refresh.attemptedAt = Date.now();
|
|
347
|
+
}
|
|
314
348
|
const controller = new AbortController();
|
|
315
349
|
const isCurrent = () => !controller.signal.aborted &&
|
|
316
|
-
|
|
317
|
-
session.sessionManager === sessionManager &&
|
|
318
|
-
sessionManager.getSessionId() === sessionId &&
|
|
319
|
-
sessionManager.getSessionFile() === sessionFile &&
|
|
350
|
+
isCurrentSource() &&
|
|
320
351
|
isSessionWorking(state) === isWorking &&
|
|
321
|
-
session.messages.length === messageCount
|
|
352
|
+
session.messages.length === messageCount &&
|
|
353
|
+
buildStatusContext(contextMessages(), isWorking) === input &&
|
|
354
|
+
(isWorking || terminalTurnError(session.messages) === turnError);
|
|
322
355
|
this.inFlight.set(id, controller);
|
|
323
356
|
let requests;
|
|
324
357
|
let status;
|
|
358
|
+
let completedInput;
|
|
325
359
|
let changed = false;
|
|
326
360
|
try {
|
|
327
|
-
if (turnError === undefined)
|
|
361
|
+
if (!reuse && turnError === undefined)
|
|
328
362
|
requests = this.getRequests?.(session);
|
|
329
|
-
const generated =
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
363
|
+
const generated = reuse ??
|
|
364
|
+
(turnError !== undefined
|
|
365
|
+
? { summary: turnError, taskState: "needs_input" }
|
|
366
|
+
: await this.generate({
|
|
367
|
+
registry: session.modelRegistry,
|
|
368
|
+
model,
|
|
369
|
+
messages: contextMessages(),
|
|
370
|
+
isWorking,
|
|
371
|
+
sessionId,
|
|
372
|
+
signal: controller.signal,
|
|
373
|
+
requests,
|
|
374
|
+
}));
|
|
339
375
|
// An unused capture otherwise counts this idle subject as working.
|
|
340
376
|
await requests?.dispose();
|
|
341
377
|
// A failed classification on an idle session would spin at "working"
|
|
@@ -353,6 +389,8 @@ export class DaemonSessionSummarizer {
|
|
|
353
389
|
if (!isCurrent()) {
|
|
354
390
|
return;
|
|
355
391
|
}
|
|
392
|
+
if (generated && turnError === undefined)
|
|
393
|
+
completedInput = input;
|
|
356
394
|
// A working refresh carries no verdict; keep the prior one at the same
|
|
357
395
|
// message count so a still-valid needs_input isn't dropped.
|
|
358
396
|
const taskState = result.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);
|
|
@@ -383,6 +421,7 @@ export class DaemonSessionSummarizer {
|
|
|
383
421
|
}
|
|
384
422
|
}
|
|
385
423
|
if (status && isCurrent()) {
|
|
424
|
+
refresh.input = completedInput;
|
|
386
425
|
state.summaryState = status;
|
|
387
426
|
if (changed)
|
|
388
427
|
this.onStatusChanged?.(state);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-session-summarizer.js","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-session-summarizer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@ponythewhite/base-context-agent\";\nimport type { Api, Model } from \"@ponythewhite/base-context-ai\";\nimport { completeInference, type InferenceCoordinator } from \"../../core/inference-coordinator.js\";\nimport type { ModelRegistry } from \"../../core/model-registry.js\";\nimport type { AgentStatus, AgentTaskState } from \"../../core/session-manager.js\";\nimport type { ActiveSessionState } from \"./active-session-state.js\";\n\nconst SWEEP_INTERVAL_MS = 25_000;\n// Collapse a tool-use loop's rapid turn_end bursts into one summarization.\nconst SETTLE_DEBOUNCE_MS = 2_000;\n\nconst SUMMARY_CONTEXT_MESSAGES = 8;\nconst SUMMARY_MAX_CHARS_PER_MESSAGE = 600;\n// Generous so a chatty model still closes the tags before truncation.\nconst SUMMARY_MAX_TOKENS = 400;\n\nexport const AGENT_STATUS_SYSTEM_PROMPT = `You generate a status line for an AI coding agent dashboard. You are given the recent conversation between a user and the agent, plus whether the agent is currently working or idle.\n\nOutput ONLY these two tags, nothing before, between, or after. Do not think out loud, explain, or count words.\n<recap>a present-tense clause, at most 12 words, saying what the agent is doing or just did, no trailing period</recap>\n<status>one of NEEDS_INPUT, COMPLETED</status>\n\nSTATUS meaning:\n- COMPLETED: the agent finished its turn AND the user's request is fully done with nothing left.\n- NEEDS_INPUT: the agent finished its turn but the task is not fully done — it asked a question, hit a blocker, or needs more prompting.\nWhen you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.\n\nExample:\n<recap>Refactoring the auth middleware and updating its tests</recap>\n<status>NEEDS_INPUT</status>`;\n\nexport interface AgentStatusResult {\n\tsummary: string;\n\ttaskState?: AgentTaskState;\n}\n\nfunction messageText(content: unknown): { text: string; tools: string[] } {\n\tif (typeof content === \"string\") {\n\t\treturn { text: content, tools: [] };\n\t}\n\tif (!Array.isArray(content)) {\n\t\treturn { text: \"\", tools: [] };\n\t}\n\tconst parts: string[] = [];\n\tconst tools: string[] = [];\n\tfor (const block of content) {\n\t\tif (typeof block !== \"object\" || block === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = (block as { type?: unknown }).type;\n\t\tif (type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t} else if (type === \"tool_use\" || type === \"toolUse\") {\n\t\t\tconst name = (block as { name?: unknown }).name;\n\t\t\tif (typeof name === \"string\") {\n\t\t\t\ttools.push(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn { text: parts.join(\"\\n\"), tools };\n}\n\nfunction clamp(text: string, max: number): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\treturn normalized.length > max ? `${normalized.slice(0, max)}…` : normalized;\n}\n\n/** Serialize the trailing messages into a compact prompt body (tool calls by name only). */\nexport function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string {\n\tconst recent = messages.slice(-SUMMARY_CONTEXT_MESSAGES);\n\tconst lines: string[] = [];\n\tfor (const message of recent) {\n\t\tconst role = message.role;\n\t\tif (role !== \"user\" && role !== \"assistant\" && role !== \"toolResult\" && role !== \"custom\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { text, tools } = messageText(message.content);\n\t\tconst body = clamp(text, SUMMARY_MAX_CHARS_PER_MESSAGE);\n\t\tconst toolNote = tools.length > 0 ? `[tools: ${[...new Set(tools)].join(\", \")}]` : \"\";\n\t\tconst rendered = [body, toolNote].filter((part) => part.length > 0).join(\" \");\n\t\tif (rendered) {\n\t\t\tlines.push(`${role}: ${rendered}`);\n\t\t}\n\t}\n\tconst state = isWorking ? \"working\" : \"idle (finished its turn)\";\n\treturn `<agent-state>${state}</agent-state>\\n<conversation>\\n${lines.join(\"\\n\")}\\n</conversation>`;\n}\n\n// Cuts a word-counting trailer the model sometimes appends, e.g.\n// `Sending X. That's 5 words? Count: X(1)... = 6 words.`. Kept to structural\n// counting markers so plain words (\"Waiting for CI\") survive.\nconst REASONING_TRAILER = /\\s*(?:[\"”]\\s*)?(?:\\bthat['’]?s\\s+\\d+\\s*words?\\b|\\bcount\\s*:|\\(\\d+\\)|=\\s*\\d+\\s*words?\\b).*/i;\nconst COUNTING_ARTIFACT = /\\(\\d+\\)|=\\s*\\d+\\s*words?\\b/i;\nconst MAX_RECAP_WORDS = 16;\n\nfunction cleanRecap(raw: string): string | undefined {\n\tconst value = raw\n\t\t.trim()\n\t\t.replace(REASONING_TRAILER, \"\")\n\t\t.replace(/^[\"“']+|[\"”']+$/g, \"\")\n\t\t.replace(/[.\\s]+$/, \"\")\n\t\t.trim();\n\tif (!value || value.startsWith(\"<\") || /present-tense|12 words/i.test(value)) {\n\t\treturn undefined;\n\t}\n\tif (COUNTING_ARTIFACT.test(value) || value.split(/\\s+/).length > MAX_RECAP_WORDS) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\n/** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */\nexport function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined {\n\t// Normalize unicode angle-bracket lookalikes (‹ › < >) so a tag written with them still parses.\n\tconst cleaned = text.replace(/[‹<]/g, \"<\").replace(/[›>]/g, \">\");\n\n\tconst recapMatch = [...cleaned.matchAll(/<recap>([\\s\\S]*?)<\\/recap>/gi)].at(-1);\n\tconst summary = recapMatch ? cleanRecap(recapMatch[1]!) : undefined;\n\tif (!summary) {\n\t\treturn undefined;\n\t}\n\tif (isWorking) {\n\t\treturn { summary };\n\t}\n\tconst statusMatch = [...cleaned.matchAll(/<status>\\s*([a-z_]+)\\s*<\\/status>/gi)].at(-1);\n\tconst status = statusMatch ? statusMatch[1]!.toUpperCase() : undefined;\n\tconst taskState: AgentTaskState = status === \"COMPLETED\" ? \"completed\" : \"needs_input\";\n\treturn { summary, taskState };\n}\n\nexport interface GenerateAgentStatusParams {\n\tregistry: ModelRegistry;\n\tmodel?: Model<Api>;\n\tmessages: readonly AgentMessage[];\n\tisWorking: boolean;\n\tsignal?: AbortSignal;\n\trequests?: InferenceCoordinator;\n}\n\n/** Use the session's selected model, or keep the local status when unavailable. */\nexport async function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined> {\n\tconst { registry, messages, isWorking, signal, requests, model: selectedModel } = params;\n\tif (messages.length === 0) {\n\t\treturn undefined;\n\t}\n\tif (!selectedModel) {\n\t\treturn undefined;\n\t}\n\tconst model = { ...selectedModel, cost: { ...selectedModel.cost } };\n\tconst subject = buildStatusContext(messages, isWorking);\n\tconst timestamp = Date.now();\n\tconst auth = await registry.getApiKeyAndHeaders(model);\n\tif (!auth.ok || !auth.apiKey) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst response = await completeInference(\n\t\t\trequests,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: AGENT_STATUS_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: subject }],\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ maxTokens: SUMMARY_MAX_TOKENS, apiKey: auth.apiKey, headers: auth.headers, signal },\n\t\t\t{ purpose: \"native-control\", purposeDetail: \"daemon-status\" },\n\t\t);\n\t\tif (response.stopReason === \"error\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst textContent = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseAgentStatusResponse(textContent, isWorking);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isSessionWorking(state: ActiveSessionState): boolean {\n\tconst session = state.runtime.session;\n\treturn session.isSessionActive;\n}\n\n/** Use the last assistant turn's real failure instead of asking a classifier to invent completed work. */\nfunction terminalTurnError(messages: readonly AgentMessage[]): string | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tif (message.stopReason !== \"error\") return undefined;\n\t\tconst detail = message.errorMessage?.trim();\n\t\treturn detail ? `Model request failed: ${clamp(detail, 160)}` : \"Model request failed\";\n\t}\n\treturn undefined;\n}\n\n/**\n * Background status summarization for daemon-hosted sessions, top-level and\n * subagents alike. A periodic sweep refreshes working sessions; debounced\n * turn-end activity drives the idle verdict. Status lives in memory; settled\n * idle verdicts are persisted.\n */\nexport class DaemonSessionSummarizer {\n\tprivate interval: ReturnType<typeof setInterval> | undefined;\n\tprivate readonly debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\t// Controller per in-flight summary so a closing session can abort its write.\n\tprivate readonly inFlight = new Map<string, AbortController>();\n\tprivate readonly tasks = new Map<string, Promise<void>>();\n\tprivate readonly forgotten = new Set<string>();\n\tprivate stopped = false;\n\t// Sessions requested while one was running; get one more pass on completion.\n\tprivate readonly rerunRequested = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly listSessions: () => readonly ActiveSessionState[],\n\t\tprivate readonly onStatusChanged?: (state: ActiveSessionState) => void,\n\t\t// Injectable for tests.\n\t\tprivate readonly generate: (\n\t\t\tparams: GenerateAgentStatusParams,\n\t\t) => Promise<AgentStatusResult | undefined> = generateAgentStatus,\n\t\tprivate readonly getRequests?: (session: ActiveSessionState[\"runtime\"][\"session\"]) => InferenceCoordinator,\n\t\tprivate readonly onError: (state: ActiveSessionState, error: unknown) => void = (_state, error) =>\n\t\t\tconsole.error(error),\n\t) {}\n\n\tstart(): void {\n\t\tif (this.interval) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stopped = false;\n\t\tthis.interval = setInterval(() => {\n\t\t\tfor (const state of this.listSessions()) {\n\t\t\t\tthis.summarize(state);\n\t\t\t}\n\t\t}, SWEEP_INTERVAL_MS);\n\t\tthis.interval.unref?.();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tthis.stopped = true;\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = undefined;\n\t\t}\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\t\tfor (const controller of this.inFlight.values()) {\n\t\t\tcontroller.abort();\n\t\t}\n\t\tthis.rerunRequested.clear();\n\t\tawait Promise.all(this.tasks.values());\n\t}\n\n\t/** Cancel new work and drain any admitted status write before closing. */\n\tasync forget(activeSessionId: string): Promise<void> {\n\t\tthis.forgotten.add(activeSessionId);\n\t\tconst timer = this.debounceTimers.get(activeSessionId);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.debounceTimers.delete(activeSessionId);\n\t\t}\n\t\tthis.inFlight.get(activeSessionId)?.abort();\n\t\tthis.rerunRequested.delete(activeSessionId);\n\t\tawait this.tasks.get(activeSessionId);\n\t}\n\n\t/** Seed in-memory status from the persisted entry when a session is added. */\n\tseed(state: ActiveSessionState): void {\n\t\tthis.forgotten.delete(state.activeSessionId);\n\t\tif (state.summaryState) {\n\t\t\treturn;\n\t\t}\n\t\tconst persisted = state.runtime.session.sessionManager.getLatestAgentStatus();\n\t\tif (persisted) {\n\t\t\tstate.summaryState = persisted;\n\t\t}\n\t}\n\n\t/** Called when a session finishes a turn; debounce until the agent settles. */\n\tnotifyActivity(state: ActiveSessionState): void {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return;\n\t\tconst existing = this.debounceTimers.get(id);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.debounceTimers.delete(id);\n\t\t\tthis.summarize(state);\n\t\t}, SETTLE_DEBOUNCE_MS);\n\t\ttimer.unref?.();\n\t\tthis.debounceTimers.set(id, timer);\n\t}\n\n\tprivate summarize(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return Promise.resolve();\n\t\tconst existing = this.tasks.get(id);\n\t\tif (existing) {\n\t\t\tthis.rerunRequested.add(id);\n\t\t\treturn existing;\n\t\t}\n\t\tconst task = this.runSummary(state)\n\t\t\t.catch((error) => this.onError(state, error))\n\t\t\t.finally(() => {\n\t\t\t\tthis.tasks.delete(id);\n\t\t\t\tif (this.rerunRequested.delete(id)) this.notifyActivity(state);\n\t\t\t});\n\t\tthis.tasks.set(id, task);\n\t\treturn task;\n\t}\n\n\tprivate async runSummary(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tconst session = state.runtime.session;\n\t\tconst sessionManager = session.sessionManager;\n\t\tconst sessionId = sessionManager.getSessionId();\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst messages = session.messages;\n\t\tif (messages.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst messageCount = messages.length;\n\t\tconst isWorking = isSessionWorking(state);\n\t\tconst previous = state.summaryState;\n\t\t// Idle sessions with a current verdict need no refresh unless a terminal\n\t\t// error must replace a previously fabricated verdict. Working sessions\n\t\t// always refresh so the recap keeps up with the in-progress turn.\n\t\tconst contentUnchanged = previous?.basedOnMessageCount === messageCount;\n\t\tconst owesIdleVerdict = !isWorking && previous?.taskState === undefined;\n\t\t// A blank recap means the model call hasn't succeeded yet (e.g. the\n\t\t// needs_input fallback fired on a transient failure); keep retrying until a\n\t\t// real summary lands so the recap isn't left permanently empty.\n\t\tconst owesSummary = !isWorking && !previous?.summary;\n\t\tconst turnError = !isWorking ? terminalTurnError(messages) : undefined;\n\t\tconst owesErrorVerdict =\n\t\t\tturnError !== undefined && (previous?.taskState !== \"needs_input\" || previous?.summary !== turnError);\n\t\tif (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary && !owesErrorVerdict) {\n\t\t\treturn;\n\t\t}\n\t\t// Include the in-progress message so a long streaming turn gets a live recap.\n\t\tconst streaming = isWorking ? session.state.streamingMessage : undefined;\n\t\tconst contextMessages = streaming ? [...messages, streaming] : messages;\n\n\t\tconst controller = new AbortController();\n\t\tconst isCurrent = () =>\n\t\t\t!controller.signal.aborted &&\n\t\t\tstate.runtime.session === session &&\n\t\t\tsession.sessionManager === sessionManager &&\n\t\t\tsessionManager.getSessionId() === sessionId &&\n\t\t\tsessionManager.getSessionFile() === sessionFile &&\n\t\t\tisSessionWorking(state) === isWorking &&\n\t\t\tsession.messages.length === messageCount;\n\t\tthis.inFlight.set(id, controller);\n\t\tlet requests: InferenceCoordinator | undefined;\n\t\tlet status: AgentStatus | undefined;\n\t\tlet changed = false;\n\t\ttry {\n\t\t\tif (turnError === undefined) requests = this.getRequests?.(session);\n\t\t\tconst generated: AgentStatusResult | undefined =\n\t\t\t\tturnError !== undefined\n\t\t\t\t\t? { summary: turnError, taskState: \"needs_input\" }\n\t\t\t\t\t: await this.generate({\n\t\t\t\t\t\t\tregistry: session.modelRegistry,\n\t\t\t\t\t\t\tmodel: session.model,\n\t\t\t\t\t\t\tmessages: contextMessages,\n\t\t\t\t\t\t\tisWorking,\n\t\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t});\n\t\t\t// An unused capture otherwise counts this idle subject as working.\n\t\t\tawait requests?.dispose();\n\t\t\t// A failed classification on an idle session would spin at \"working\"\n\t\t\t// forever (the activity axis holds unjudged idle sessions there), so\n\t\t\t// settle it to needs_input.\n\t\t\tconst result =\n\t\t\t\tgenerated ??\n\t\t\t\t(!isWorking && (owesIdleVerdict || owesSummary)\n\t\t\t\t\t? { summary: previous?.summary ?? \"\", taskState: \"needs_input\" as const }\n\t\t\t\t\t: undefined);\n\t\t\tif (!result) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Discard if the session closed, was swapped, or moved to a new turn\n\t\t\t// during the async call — never write a verdict for stale state.\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A working refresh carries no verdict; keep the prior one at the same\n\t\t\t// message count so a still-valid needs_input isn't dropped.\n\t\t\tconst taskState =\n\t\t\t\tresult.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);\n\t\t\tstatus = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\ttaskState,\n\t\t\t\tbasedOnMessageCount: messageCount,\n\t\t\t};\n\t\t\t// An idle settle refreshes the verdict's currency, which drives the roster's\n\t\t\t// activity axis: it must publish even when the verdict text is unchanged.\n\t\t\tchanged =\n\t\t\t\tprevious?.summary !== status.summary ||\n\t\t\t\tprevious?.taskState !== status.taskState ||\n\t\t\t\t(!isWorking && previous?.basedOnMessageCount !== status.basedOnMessageCount);\n\t\t\t// Persist only settled idle verdicts, never mid-stream.\n\t\t\tif (!isWorking) {\n\t\t\t\tawait sessionManager.appendAgentStatus(status);\n\t\t\t\tif (!isCurrent()) return;\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait requests?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.inFlight.delete(id);\n\t\t\t}\n\t\t}\n\t\tif (status && isCurrent()) {\n\t\t\tstate.summaryState = status;\n\t\t\tif (changed) this.onStatusChanged?.(state);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"daemon-session-summarizer.js","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-session-summarizer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@ponythewhite/base-context-agent\";\nimport type { Api, Model } from \"@ponythewhite/base-context-ai\";\nimport { completeInference, type InferenceCoordinator } from \"../../core/inference-coordinator.js\";\nimport type { ModelRegistry } from \"../../core/model-registry.js\";\nimport type { AgentStatus, AgentTaskState } from \"../../core/session-manager.js\";\nimport type { ActiveSessionState } from \"./active-session-state.js\";\n\nconst SWEEP_INTERVAL_MS = 25_000;\n// Collapse a tool-use loop's rapid turn_end bursts into one summarization.\nconst SETTLE_DEBOUNCE_MS = 2_000;\n// Keep rapid tool/stream updates local; periodically refresh the generated recap.\nconst MIN_REFRESH_INTERVAL_MS = 60_000;\n\nconst SUMMARY_CONTEXT_MESSAGES = 8;\nconst SUMMARY_MAX_CHARS_PER_MESSAGE = 600;\n// Generous so a chatty model still closes the tags before truncation.\nconst SUMMARY_MAX_TOKENS = 400;\n\nexport const AGENT_STATUS_SYSTEM_PROMPT = `You generate a status line for an AI coding agent dashboard. You are given the recent conversation between a user and the agent, plus whether the agent is currently working or idle.\n\nOutput ONLY these two tags, nothing before, between, or after. Do not think out loud, explain, or count words.\n<recap>a present-tense clause, at most 12 words, saying what the agent is doing or just did, no trailing period</recap>\n<status>one of NEEDS_INPUT, COMPLETED</status>\n\nSTATUS meaning:\n- COMPLETED: the agent finished its turn AND the user's request is fully done with nothing left.\n- NEEDS_INPUT: the agent finished its turn but the task is not fully done — it asked a question, hit a blocker, or needs more prompting.\nWhen you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.\n\nExample:\n<recap>Refactoring the auth middleware and updating its tests</recap>\n<status>NEEDS_INPUT</status>`;\n\nexport interface AgentStatusResult {\n\tsummary: string;\n\ttaskState?: AgentTaskState;\n}\n\nfunction messageText(content: unknown): { text: string; tools: string[] } {\n\tif (typeof content === \"string\") {\n\t\treturn { text: content, tools: [] };\n\t}\n\tif (!Array.isArray(content)) {\n\t\treturn { text: \"\", tools: [] };\n\t}\n\tconst parts: string[] = [];\n\tconst tools: string[] = [];\n\tfor (const block of content) {\n\t\tif (typeof block !== \"object\" || block === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = (block as { type?: unknown }).type;\n\t\tif (type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t} else if (type === \"toolCall\" || type === \"tool_use\" || type === \"toolUse\") {\n\t\t\tconst name = (block as { name?: unknown }).name;\n\t\t\tif (typeof name === \"string\") {\n\t\t\t\ttools.push(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn { text: parts.join(\"\\n\"), tools };\n}\n\nfunction clamp(text: string, max: number): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\treturn normalized.length > max ? `${normalized.slice(0, max)}…` : normalized;\n}\n\n/** Serialize the trailing messages into a compact prompt body (tool calls by name only). */\nexport function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string {\n\tconst recent = messages.slice(-SUMMARY_CONTEXT_MESSAGES);\n\tconst lines: string[] = [];\n\tfor (const message of recent) {\n\t\tconst role = message.role;\n\t\tif (role !== \"user\" && role !== \"assistant\" && role !== \"toolResult\" && role !== \"custom\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { text, tools } = messageText(message.content);\n\t\tconst body = clamp(text, SUMMARY_MAX_CHARS_PER_MESSAGE);\n\t\tconst toolNote = tools.length > 0 ? `[tools: ${[...new Set(tools)].join(\", \")}]` : \"\";\n\t\tconst rendered = [body, toolNote].filter((part) => part.length > 0).join(\" \");\n\t\tif (rendered) {\n\t\t\tlines.push(`${role}: ${rendered}`);\n\t\t}\n\t}\n\tconst state = isWorking ? \"working\" : \"idle (finished its turn)\";\n\treturn `<agent-state>${state}</agent-state>\\n<conversation>\\n${lines.join(\"\\n\")}\\n</conversation>`;\n}\n\n// Cuts a word-counting trailer the model sometimes appends, e.g.\n// `Sending X. That's 5 words? Count: X(1)... = 6 words.`. Kept to structural\n// counting markers so plain words (\"Waiting for CI\") survive.\nconst REASONING_TRAILER = /\\s*(?:[\"”]\\s*)?(?:\\bthat['’]?s\\s+\\d+\\s*words?\\b|\\bcount\\s*:|\\(\\d+\\)|=\\s*\\d+\\s*words?\\b).*/i;\nconst COUNTING_ARTIFACT = /\\(\\d+\\)|=\\s*\\d+\\s*words?\\b/i;\nconst MAX_RECAP_WORDS = 16;\n\nfunction cleanRecap(raw: string): string | undefined {\n\tconst value = raw\n\t\t.trim()\n\t\t.replace(REASONING_TRAILER, \"\")\n\t\t.replace(/^[\"“']+|[\"”']+$/g, \"\")\n\t\t.replace(/[.\\s]+$/, \"\")\n\t\t.trim();\n\tif (!value || value.startsWith(\"<\") || /present-tense|12 words/i.test(value)) {\n\t\treturn undefined;\n\t}\n\tif (COUNTING_ARTIFACT.test(value) || value.split(/\\s+/).length > MAX_RECAP_WORDS) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\n/** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */\nexport function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined {\n\t// Normalize unicode angle-bracket lookalikes (‹ › < >) so a tag written with them still parses.\n\tconst cleaned = text.replace(/[‹<]/g, \"<\").replace(/[›>]/g, \">\");\n\n\tconst recapMatch = [...cleaned.matchAll(/<recap>([\\s\\S]*?)<\\/recap>/gi)].at(-1);\n\tconst summary = recapMatch ? cleanRecap(recapMatch[1]!) : undefined;\n\tif (!summary) {\n\t\treturn undefined;\n\t}\n\tif (isWorking) {\n\t\treturn { summary };\n\t}\n\tconst statusMatch = [...cleaned.matchAll(/<status>\\s*([a-z_]+)\\s*<\\/status>/gi)].at(-1);\n\tconst status = statusMatch ? statusMatch[1]!.toUpperCase() : undefined;\n\tconst taskState: AgentTaskState = status === \"COMPLETED\" ? \"completed\" : \"needs_input\";\n\treturn { summary, taskState };\n}\n\nexport interface GenerateAgentStatusParams {\n\tregistry: ModelRegistry;\n\tmodel?: Model<Api>;\n\tmessages: readonly AgentMessage[];\n\tisWorking: boolean;\n\tsessionId?: string;\n\tsignal?: AbortSignal;\n\trequests?: InferenceCoordinator;\n}\n\n/** Use the session's selected model, or keep the local status when unavailable. */\nexport async function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined> {\n\tconst { registry, messages, isWorking, sessionId, signal, requests, model: selectedModel } = params;\n\tif (messages.length === 0) {\n\t\treturn undefined;\n\t}\n\tif (!selectedModel) {\n\t\treturn undefined;\n\t}\n\tconst model = { ...selectedModel, cost: { ...selectedModel.cost } };\n\tconst subject = buildStatusContext(messages, isWorking);\n\tconst timestamp = Date.now();\n\tconst auth = await registry.getApiKeyAndHeaders(model);\n\tif (!auth.ok || !auth.apiKey) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst response = await completeInference(\n\t\t\trequests,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: AGENT_STATUS_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: subject }],\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tmaxTokens: SUMMARY_MAX_TOKENS,\n\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\theaders: auth.headers,\n\t\t\t\tsignal,\n\t\t\t\tsessionId: sessionId ? `daemon-status:${sessionId}` : undefined,\n\t\t\t},\n\t\t\t{ purpose: \"native-control\", purposeDetail: \"daemon-status\" },\n\t\t);\n\t\tif (response.stopReason === \"error\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst textContent = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseAgentStatusResponse(textContent, isWorking);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isSessionWorking(state: ActiveSessionState): boolean {\n\tconst session = state.runtime.session;\n\treturn session.isSessionActive;\n}\n\n/** Use the last assistant turn's real failure instead of asking a classifier to invent completed work. */\nfunction terminalTurnError(messages: readonly AgentMessage[]): string | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tif (message.stopReason !== \"error\") return undefined;\n\t\tconst detail = message.errorMessage?.trim();\n\t\treturn detail ? `Model request failed: ${clamp(detail, 160)}` : \"Model request failed\";\n\t}\n\treturn undefined;\n}\n\n/**\n * Background status summarization for daemon-hosted sessions, top-level and\n * subagents alike. A periodic sweep refreshes working sessions; debounced\n * turn-end activity drives the idle verdict. Status lives in memory; settled\n * idle verdicts are persisted.\n */\nexport class DaemonSessionSummarizer {\n\tprivate interval: ReturnType<typeof setInterval> | undefined;\n\tprivate readonly debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\t// Controller per in-flight summary so a closing session can abort its write.\n\tprivate readonly inFlight = new Map<string, AbortController>();\n\tprivate readonly refreshes = new Map<\n\t\tstring,\n\t\t{ isCurrentSource: () => boolean; input?: string; attemptedAt?: number; wasWorking?: boolean }\n\t>();\n\tprivate readonly tasks = new Map<string, Promise<void>>();\n\tprivate readonly forgotten = new Set<string>();\n\tprivate stopped = false;\n\t// Sessions requested while one was running; get one more pass on completion.\n\tprivate readonly rerunRequested = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly listSessions: () => readonly ActiveSessionState[],\n\t\tprivate readonly onStatusChanged?: (state: ActiveSessionState) => void,\n\t\t// Injectable for tests.\n\t\tprivate readonly generate: (\n\t\t\tparams: GenerateAgentStatusParams,\n\t\t) => Promise<AgentStatusResult | undefined> = generateAgentStatus,\n\t\tprivate readonly getRequests?: (session: ActiveSessionState[\"runtime\"][\"session\"]) => InferenceCoordinator,\n\t\tprivate readonly onError: (state: ActiveSessionState, error: unknown) => void = (_state, error) =>\n\t\t\tconsole.error(error),\n\t) {}\n\n\tstart(): void {\n\t\tif (this.interval) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stopped = false;\n\t\tthis.interval = setInterval(() => {\n\t\t\tfor (const state of this.listSessions()) {\n\t\t\t\tthis.summarize(state);\n\t\t\t}\n\t\t}, SWEEP_INTERVAL_MS);\n\t\tthis.interval.unref?.();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tthis.stopped = true;\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = undefined;\n\t\t}\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\t\tfor (const controller of this.inFlight.values()) {\n\t\t\tcontroller.abort();\n\t\t}\n\t\tthis.rerunRequested.clear();\n\t\tawait Promise.all(this.tasks.values());\n\t}\n\n\t/** Cancel new work and drain any admitted status write before closing. */\n\tasync forget(activeSessionId: string): Promise<void> {\n\t\tthis.forgotten.add(activeSessionId);\n\t\tconst timer = this.debounceTimers.get(activeSessionId);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.debounceTimers.delete(activeSessionId);\n\t\t}\n\t\tthis.inFlight.get(activeSessionId)?.abort();\n\t\tthis.refreshes.delete(activeSessionId);\n\t\tthis.rerunRequested.delete(activeSessionId);\n\t\tawait this.tasks.get(activeSessionId);\n\t}\n\n\t/** Seed in-memory status from the persisted entry when a session is added. */\n\tseed(state: ActiveSessionState): void {\n\t\tthis.forgotten.delete(state.activeSessionId);\n\t\tif (state.summaryState) {\n\t\t\treturn;\n\t\t}\n\t\tconst persisted = state.runtime.session.sessionManager.getLatestAgentStatus();\n\t\tif (persisted) {\n\t\t\tstate.summaryState = persisted;\n\t\t}\n\t}\n\n\t/** Called when a session finishes a turn; debounce until the agent settles. */\n\tnotifyActivity(state: ActiveSessionState): void {\n\t\tthis.schedule(state, SETTLE_DEBOUNCE_MS);\n\t}\n\n\tprivate schedule(state: ActiveSessionState, delay: number): void {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return;\n\t\tconst existing = this.debounceTimers.get(id);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.debounceTimers.delete(id);\n\t\t\tthis.summarize(state);\n\t\t}, delay);\n\t\ttimer.unref?.();\n\t\tthis.debounceTimers.set(id, timer);\n\t}\n\n\tprivate summarize(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return Promise.resolve();\n\t\tconst existing = this.tasks.get(id);\n\t\tif (existing) {\n\t\t\tthis.rerunRequested.add(id);\n\t\t\treturn existing;\n\t\t}\n\t\tconst task = this.runSummary(state)\n\t\t\t.catch((error) => this.onError(state, error))\n\t\t\t.finally(() => {\n\t\t\t\tthis.tasks.delete(id);\n\t\t\t\tif (this.rerunRequested.delete(id)) this.notifyActivity(state);\n\t\t\t});\n\t\tthis.tasks.set(id, task);\n\t\treturn task;\n\t}\n\n\tprivate async runSummary(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tconst session = state.runtime.session;\n\t\tconst sessionManager = session.sessionManager;\n\t\tconst sessionId = sessionManager.getSessionId();\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst messages = session.messages;\n\t\tif (messages.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst messageCount = messages.length;\n\t\tconst isWorking = isSessionWorking(state);\n\t\tconst previous = state.summaryState;\n\t\tconst owesIdleVerdict = !isWorking && previous?.taskState === undefined;\n\t\tconst owesSummary = !isWorking && !previous?.summary;\n\t\tconst turnError = !isWorking ? terminalTurnError(messages) : undefined;\n\t\t// Compare exactly what the classifier sees, not message counts or hidden\n\t\t// thinking/tool output beyond the bounded prompt.\n\t\tconst contextMessages = () => {\n\t\t\tconst streaming = isWorking ? session.state.streamingMessage : undefined;\n\t\t\treturn streaming ? [...session.messages, streaming] : session.messages;\n\t\t};\n\t\tconst input = buildStatusContext(contextMessages(), isWorking);\n\t\tconst model = session.model;\n\t\tconst isCurrentSource = () =>\n\t\t\tstate.runtime.session === session &&\n\t\t\tsession.sessionManager === sessionManager &&\n\t\t\tsessionManager.getSessionId() === sessionId &&\n\t\t\tsessionManager.getSessionFile() === sessionFile &&\n\t\t\tsession.model === model;\n\t\tlet refresh = this.refreshes.get(id);\n\t\tif (!refresh?.isCurrentSource()) {\n\t\t\trefresh = { isCurrentSource };\n\t\t\tthis.refreshes.set(id, refresh);\n\t\t}\n\t\tconst finalTransition = !isWorking && refresh.wasWorking === true;\n\t\trefresh.wasWorking = isWorking;\n\t\tconst reuse = turnError === undefined && refresh.input === input && previous?.summary ? previous : undefined;\n\t\tif (\n\t\t\t(reuse ||\n\t\t\t\t(turnError !== undefined && previous?.summary === turnError && previous.taskState === \"needs_input\")) &&\n\t\t\tprevious?.basedOnMessageCount === messageCount\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tif (!reuse && turnError === undefined) {\n\t\t\t// A final working -> idle verdict must not wait behind the live recap.\n\t\t\tconst remaining = MIN_REFRESH_INTERVAL_MS - (Date.now() - (refresh.attemptedAt ?? -Infinity));\n\t\t\tif (!finalTransition && remaining > 0) {\n\t\t\t\tthis.schedule(state, remaining);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\trefresh.attemptedAt = Date.now();\n\t\t}\n\n\t\tconst controller = new AbortController();\n\t\tconst isCurrent = () =>\n\t\t\t!controller.signal.aborted &&\n\t\t\tisCurrentSource() &&\n\t\t\tisSessionWorking(state) === isWorking &&\n\t\t\tsession.messages.length === messageCount &&\n\t\t\tbuildStatusContext(contextMessages(), isWorking) === input &&\n\t\t\t(isWorking || terminalTurnError(session.messages) === turnError);\n\t\tthis.inFlight.set(id, controller);\n\t\tlet requests: InferenceCoordinator | undefined;\n\t\tlet status: AgentStatus | undefined;\n\t\tlet completedInput: string | undefined;\n\t\tlet changed = false;\n\t\ttry {\n\t\t\tif (!reuse && turnError === undefined) requests = this.getRequests?.(session);\n\t\t\tconst generated: AgentStatusResult | undefined =\n\t\t\t\treuse ??\n\t\t\t\t(turnError !== undefined\n\t\t\t\t\t? { summary: turnError, taskState: \"needs_input\" }\n\t\t\t\t\t: await this.generate({\n\t\t\t\t\t\t\tregistry: session.modelRegistry,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tmessages: contextMessages(),\n\t\t\t\t\t\t\tisWorking,\n\t\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t}));\n\t\t\t// An unused capture otherwise counts this idle subject as working.\n\t\t\tawait requests?.dispose();\n\t\t\t// A failed classification on an idle session would spin at \"working\"\n\t\t\t// forever (the activity axis holds unjudged idle sessions there), so\n\t\t\t// settle it to needs_input.\n\t\t\tconst result =\n\t\t\t\tgenerated ??\n\t\t\t\t(!isWorking && (owesIdleVerdict || owesSummary)\n\t\t\t\t\t? { summary: previous?.summary ?? \"\", taskState: \"needs_input\" as const }\n\t\t\t\t\t: undefined);\n\t\t\tif (!result) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Discard if the session closed, was swapped, or moved to a new turn\n\t\t\t// during the async call — never write a verdict for stale state.\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (generated && turnError === undefined) completedInput = input;\n\t\t\t// A working refresh carries no verdict; keep the prior one at the same\n\t\t\t// message count so a still-valid needs_input isn't dropped.\n\t\t\tconst taskState =\n\t\t\t\tresult.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);\n\t\t\tstatus = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\ttaskState,\n\t\t\t\tbasedOnMessageCount: messageCount,\n\t\t\t};\n\t\t\t// An idle settle refreshes the verdict's currency, which drives the roster's\n\t\t\t// activity axis: it must publish even when the verdict text is unchanged.\n\t\t\tchanged =\n\t\t\t\tprevious?.summary !== status.summary ||\n\t\t\t\tprevious?.taskState !== status.taskState ||\n\t\t\t\t(!isWorking && previous?.basedOnMessageCount !== status.basedOnMessageCount);\n\t\t\t// Persist only settled idle verdicts, never mid-stream.\n\t\t\tif (!isWorking) {\n\t\t\t\tawait sessionManager.appendAgentStatus(status);\n\t\t\t\tif (!isCurrent()) return;\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait requests?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.inFlight.delete(id);\n\t\t\t}\n\t\t}\n\t\tif (status && isCurrent()) {\n\t\t\trefresh.input = completedInput;\n\t\t\tstate.summaryState = status;\n\t\t\tif (changed) this.onStatusChanged?.(state);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-tree-format.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/context-tree-format.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAwGrE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAwF9E","sourcesContent":["import type { Usage } from \"@ponythewhite/base-context-ai\";\nimport { truncateToWidth, visibleWidth } from \"@ponythewhite/base-context-tui\";\nimport type { ContextTreeNode } from \"../../../core/context-tree.js\";\nimport type { ContextUsage } from \"../../../core/extensions/index.js\";\nimport { addAssistantUsage, emptyUsage } from \"../../../core/usage.js\";\nimport { formatTokenCount } from \"../agent-activity.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst CONTEXT_BAR_WIDTH = 10;\nconst MIN_LABEL_WIDTH = 16;\n\ninterface ContextTreeRow {\n\tnode: ContextTreeNode;\n\t/** Tree-drawing prefix, e.g. \"│ └─ \". */\n\tprefix: string;\n}\n\nfunction statusIcon(status: ContextTreeNode[\"status\"]): string {\n\tswitch (status) {\n\t\tcase \"active\":\n\t\t\treturn theme.fg(\"accent\", \"●\");\n\t\tcase \"queued\":\n\t\t\treturn theme.fg(\"dim\", \"◇\");\n\t\tcase \"running\":\n\t\t\treturn theme.fg(\"accent\", \"◆\");\n\t\tcase \"done\":\n\t\t\treturn theme.fg(\"success\", \"✓\");\n\t\tcase \"error\":\n\t\tcase \"cancelled\":\n\t\t\treturn theme.fg(\"error\", \"✗\");\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\treturn _exhaustive;\n\t\t}\n\t}\n}\n\nfunction flattenContextTree(root: ContextTreeNode): ContextTreeRow[] {\n\tconst rows: ContextTreeRow[] = [{ node: root, prefix: \"\" }];\n\tconst walk = (children: readonly ContextTreeNode[], ancestors: string): void => {\n\t\tfor (const [index, child] of children.entries()) {\n\t\t\tconst isLast = index === children.length - 1;\n\t\t\trows.push({ node: child, prefix: `${ancestors}${isLast ? \"└─ \" : \"├─ \"}` });\n\t\t\twalk(child.children, `${ancestors}${isLast ? \" \" : \"│ \"}`);\n\t\t}\n\t};\n\twalk(root.children, \"\");\n\treturn rows;\n}\n\n/** Spend-relevant token count, matching the \"Total\" line of /usage. */\nfunction spentTokens(usage: Usage): number {\n\treturn usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction formatCost(cost: number): string {\n\treturn `$${cost.toFixed(2)}`;\n}\n\nfunction formatContextColumn(contextUsage: ContextUsage | undefined, withBar: boolean): string {\n\tif (!contextUsage) {\n\t\treturn theme.fg(\"dim\", \"-\");\n\t}\n\tif (contextUsage.tokens === null || contextUsage.percent === null) {\n\t\treturn theme.fg(\"dim\", \"unknown after compaction\");\n\t}\n\tconst percent = Math.round(contextUsage.percent);\n\tconst detail = `${formatTokenCount(contextUsage.tokens)}/${formatTokenCount(contextUsage.contextWindow)}`;\n\tconst text = `${percent}% ${theme.fg(\"dim\", `(${detail})`)}`;\n\tif (!withBar) {\n\t\treturn text;\n\t}\n\tconst filled = Math.max(\n\t\t0,\n\t\tMath.min(CONTEXT_BAR_WIDTH, Math.round((contextUsage.percent / 100) * CONTEXT_BAR_WIDTH)),\n\t);\n\tconst barColor = contextUsage.percent >= 80 ? \"warning\" : \"accent\";\n\tconst bar = theme.fg(barColor, \"▓\".repeat(filled)) + theme.fg(\"dim\", \"░\".repeat(CONTEXT_BAR_WIDTH - filled));\n\treturn `${bar} ${text}`;\n}\n\nfunction padEndAnsi(text: string, width: number): string {\n\treturn text + \" \".repeat(Math.max(0, width - visibleWidth(text)));\n}\n\nfunction padStartAnsi(text: string, width: number): string {\n\treturn \" \".repeat(Math.max(0, width - visibleWidth(text))) + text;\n}\n\nfunction countNodes(root: ContextTreeNode): number {\n\treturn 1 + root.children.reduce((sum, child) => sum + countNodes(child), 0);\n}\n\n/** Own usage summed over the whole tree: exact even while children are mid-run. */\nfunction sumOwnUsage(root: ContextTreeNode): Usage {\n\tconst total = emptyUsage();\n\tconst walk = (node: ContextTreeNode): void => {\n\t\taddAssistantUsage(total, node.ownUsage);\n\t\tfor (const child of node.children) {\n\t\t\twalk(child);\n\t\t}\n\t};\n\twalk(root);\n\treturn total;\n}\n\n/**\n * Render the /context overview: a tree with one row per agent showing its own\n * tokens and cost (descendants excluded, so columns add up) plus per-agent\n * context-window utilization, followed by grand totals.\n */\nexport function formatContextTree(root: ContextTreeNode, width: number): string {\n\tconst rows = flattenContextTree(root);\n\n\tconst tokenCells = rows.map((row) => formatTokenCount(spentTokens(row.node.ownUsage)));\n\tconst costCells = rows.map((row) => formatCost(row.node.ownUsage.cost.total));\n\tconst tokenHeader = \"tokens\";\n\tconst costHeader = \"cost\";\n\tconst contextHeader = \"context\";\n\tconst tokenWidth = Math.max(tokenHeader.length, ...tokenCells.map((cell) => cell.length));\n\tconst costWidth = Math.max(costHeader.length, ...costCells.map((cell) => cell.length));\n\tconst labelWidth = Math.max(\n\t\tMIN_LABEL_WIDTH,\n\t\tMath.min(\n\t\t\tMath.max(...rows.map((row) => row.prefix.length + 2 + visibleWidth(row.node.label))),\n\t\t\twidth - tokenWidth - costWidth - 28,\n\t\t),\n\t);\n\n\tconst lines: string[] = [];\n\tlines.push(theme.bold(\"Context\"));\n\tlines.push(\"\");\n\tif (root.model) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Model:\")} ${root.model.provider}/${root.model.id}`);\n\t\tlines.push(\"\");\n\t}\n\n\tlines.push(\n\t\ttheme.fg(\n\t\t\t\"dim\",\n\t\t\t`${\" \".repeat(2)}${padEndAnsi(\"agent\", labelWidth)} ${padStartAnsi(tokenHeader, tokenWidth)} ${padStartAnsi(costHeader, costWidth)} ${contextHeader}`,\n\t\t),\n\t);\n\n\tfor (const [index, row] of rows.entries()) {\n\t\tconst labelSpace = Math.max(1, labelWidth - row.prefix.length - 2);\n\t\tconst label = truncateToWidth(row.node.label, labelSpace, \"...\");\n\t\tconst labelCell = padEndAnsi(\n\t\t\t`${theme.fg(\"dim\", row.prefix)}${statusIcon(row.node.status)} ${label}`,\n\t\t\tlabelWidth + 2,\n\t\t);\n\t\tconst tokenCell = padStartAnsi(tokenCells[index], tokenWidth);\n\t\tconst costCell = padStartAnsi(theme.fg(\"dim\", costCells[index]), costWidth);\n\t\tconst contextCell = formatContextColumn(row.node.contextUsage, row.node.id === \"root\");\n\t\tlines.push(`${labelCell} ${tokenCell} ${costCell} ${contextCell}`);\n\t}\n\n\tconst totals = sumOwnUsage(root);\n\tconst agentCount = countNodes(root);\n\tlines.push(\"\");\n\tlines.push(\n\t\t`${theme.fg(\"dim\", \"Total:\")} ${formatTokenCount(spentTokens(totals))} tokens ${theme.fg(\"dim\", \"·\")} ${formatCost(\n\t\t\ttotals.cost.total,\n\t\t)}${agentCount > 1 ? theme.fg(\"dim\", ` across ${agentCount} agents`) : \"\"}`,\n\t);\n\n\tlines.push(\"\");\n\tlines.push(theme.bold(\"Tokens\"));\n\tlines.push(`${theme.fg(\"dim\", \"Input:\")} ${totals.input.toLocaleString()}`);\n\tlines.push(`${theme.fg(\"dim\", \"Output:\")} ${totals.output.toLocaleString()}`);\n\tif (totals.cacheRead > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Read:\")} ${totals.cacheRead.toLocaleString()}`);\n\t}\n\tif (totals.cacheWrite > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Write:\")} ${totals.cacheWrite.toLocaleString()}`);\n\t}\n\tlines.push(`${theme.fg(\"dim\", \"Total:\")} ${spentTokens(totals).toLocaleString()}`);\n\n\tif (totals.cost.total > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Cost\"));\n\t\tlines.push(`${theme.fg(\"dim\", \"Total:\")} $${totals.cost.total.toFixed(4)}`);\n\t}\n\n\tconst rootContext = root.contextUsage;\n\tif (rootContext) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Context\"));\n\t\tif (rootContext.tokens === null || rootContext.percent === null) {\n\t\t\tlines.push(`${theme.fg(\"dim\", \"Current:\")} unknown after compaction`);\n\t\t} else {\n\t\t\tconst percent = `${Math.round(rootContext.percent * 10) / 10}%`;\n\t\t\tlines.push(\n\t\t\t\t`${theme.fg(\"dim\", \"Current:\")} ${rootContext.tokens.toLocaleString()} / ${rootContext.contextWindow.toLocaleString()} (${percent})`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-tree-format.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/context-tree-format.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AA2JrE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA6F9E","sourcesContent":["import type { Usage } from \"@ponythewhite/base-context-ai\";\nimport { truncateToWidth, visibleWidth } from \"@ponythewhite/base-context-tui\";\nimport type { ContextTreeNode } from \"../../../core/context-tree.js\";\nimport type { ContextUsage } from \"../../../core/extensions/index.js\";\nimport { addRequestUsageTotals, emptyRequestUsageTotals } from \"../../../core/request-usage.js\";\nimport { addAssistantUsage, emptyUsage } from \"../../../core/usage.js\";\nimport { formatTokenCount } from \"../agent-activity.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst CONTEXT_BAR_WIDTH = 10;\nconst MIN_LABEL_WIDTH = 16;\n\ninterface ContextTreeRow {\n\tnode: ContextTreeNode;\n\t/** Tree-drawing prefix, e.g. \"│ └─ \". */\n\tprefix: string;\n}\n\nfunction statusIcon(status: ContextTreeNode[\"status\"]): string {\n\tswitch (status) {\n\t\tcase \"active\":\n\t\t\treturn theme.fg(\"accent\", \"●\");\n\t\tcase \"queued\":\n\t\t\treturn theme.fg(\"dim\", \"◇\");\n\t\tcase \"running\":\n\t\t\treturn theme.fg(\"accent\", \"◆\");\n\t\tcase \"done\":\n\t\t\treturn theme.fg(\"success\", \"✓\");\n\t\tcase \"error\":\n\t\tcase \"cancelled\":\n\t\t\treturn theme.fg(\"error\", \"✗\");\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\treturn _exhaustive;\n\t\t}\n\t}\n}\n\nfunction flattenContextTree(root: ContextTreeNode): ContextTreeRow[] {\n\tconst rows: ContextTreeRow[] = [{ node: root, prefix: \"\" }];\n\tconst walk = (children: readonly ContextTreeNode[], ancestors: string): void => {\n\t\tfor (const [index, child] of children.entries()) {\n\t\t\tconst isLast = index === children.length - 1;\n\t\t\trows.push({ node: child, prefix: `${ancestors}${isLast ? \"└─ \" : \"├─ \"}` });\n\t\t\twalk(child.children, `${ancestors}${isLast ? \" \" : \"│ \"}`);\n\t\t}\n\t};\n\twalk(root.children, \"\");\n\treturn rows;\n}\n\n/** Spend-relevant token count, matching the \"Total\" line of /usage. */\nfunction spentTokens(usage: Usage): number {\n\treturn usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction formatCost(cost: number): string {\n\treturn `$${cost.toFixed(2)}`;\n}\n\nfunction formatContextColumn(contextUsage: ContextUsage | undefined, withBar: boolean): string {\n\tif (!contextUsage) {\n\t\treturn theme.fg(\"dim\", \"-\");\n\t}\n\tif (contextUsage.tokens === null || contextUsage.percent === null) {\n\t\treturn theme.fg(\"dim\", \"unknown after compaction\");\n\t}\n\tconst percent = Math.round(contextUsage.percent);\n\tconst detail = `${formatTokenCount(contextUsage.tokens)}/${formatTokenCount(contextUsage.contextWindow)}`;\n\tconst text = `${percent}% ${theme.fg(\"dim\", `(${detail})`)}`;\n\tif (!withBar) {\n\t\treturn text;\n\t}\n\tconst filled = Math.max(\n\t\t0,\n\t\tMath.min(CONTEXT_BAR_WIDTH, Math.round((contextUsage.percent / 100) * CONTEXT_BAR_WIDTH)),\n\t);\n\tconst barColor = contextUsage.percent >= 80 ? \"warning\" : \"accent\";\n\tconst bar = theme.fg(barColor, \"▓\".repeat(filled)) + theme.fg(\"dim\", \"░\".repeat(CONTEXT_BAR_WIDTH - filled));\n\treturn `${bar} ${text}`;\n}\n\nfunction padEndAnsi(text: string, width: number): string {\n\treturn text + \" \".repeat(Math.max(0, width - visibleWidth(text)));\n}\n\nfunction padStartAnsi(text: string, width: number): string {\n\treturn \" \".repeat(Math.max(0, width - visibleWidth(text))) + text;\n}\n\nfunction countNodes(root: ContextTreeNode): number {\n\treturn 1 + root.children.reduce((sum, child) => sum + countNodes(child), 0);\n}\n\n/** Own usage summed over the whole tree: exact even while children are mid-run. */\nfunction sumOwnUsage(root: ContextTreeNode): Usage {\n\tconst total = emptyUsage();\n\tconst walk = (node: ContextTreeNode): void => {\n\t\taddAssistantUsage(total, node.ownUsage);\n\t\tfor (const child of node.children) {\n\t\t\twalk(child);\n\t\t}\n\t};\n\twalk(root);\n\treturn total;\n}\n\nconst GOAL_SCOPE =\n\t\"Goal budget: root successful main uncached input + output only; excludes cache, children and auxiliary requests\";\n\nfunction formatReceiptContextTree(root: ContextTreeNode, width: number): string {\n\tconst rows = flattenContextTree(root);\n\tconst total = emptyRequestUsageTotals();\n\tconst seen = new Set<string>();\n\tlet missingNodes = 0;\n\tconst lines = [theme.bold(\"Context\"), \"\", \"Generation receipts: captured sources, known usage only\", \"\"];\n\tfor (const { node, prefix } of rows) {\n\t\tconst usage = node.ownRequestUsage;\n\t\tconst label = truncateToWidth(`${prefix}${node.label}`, Math.max(16, width - 45), \"...\");\n\t\tif (!usage) {\n\t\t\tmissingNodes++;\n\t\t\tlines.push(`${statusIcon(node.status)} ${label}: request usage unknown`);\n\t\t} else if (seen.has(usage.sessionId)) {\n\t\t\tlines.push(`${statusIcon(node.status)} ${label}: shared source (counted once)`);\n\t\t} else {\n\t\t\tseen.add(usage.sessionId);\n\t\t\taddRequestUsageTotals(total, usage.total);\n\t\t\tlines.push(\n\t\t\t\t`${statusIcon(node.status)} ${label}: ${formatTokenCount(usage.total.processedTokens)} tokens · ${formatCost(usage.total.catalogEstimateUsd)} est.`,\n\t\t\t);\n\t\t\tfor (const [purpose, bucket] of Object.entries(usage.byPurpose)) {\n\t\t\t\tlines.push(\n\t\t\t\t\t` ${purpose}: ${bucket.processedTokens.toLocaleString()} tokens · $${bucket.catalogEstimateUsd.toFixed(4)} est. · ${bucket.settledAttempts} settled`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (node.contextUsage) lines.push(` Context: ${formatContextColumn(node.contextUsage, node.id === \"root\")}`);\n\t}\n\tlines.push(\"\", theme.bold(\"Captured family — known generation usage\"));\n\tlines.push(`Sessions with receipts: ${seen.size}; request usage unavailable: ${missingNodes}`);\n\tlines.push(`Uncached input: ${total.input.toLocaleString()}`);\n\tlines.push(`Input total (including cache): ${total.inputTotal.toLocaleString()}`);\n\tlines.push(`Cache read: ${total.cacheRead.toLocaleString()}; cache write: ${total.cacheWrite.toLocaleString()}`);\n\tlines.push(`Output: ${total.output.toLocaleString()}`);\n\tlines.push(`Processed total: ${total.processedTokens.toLocaleString()}`);\n\tlines.push(`Catalog estimate (unvalidated rates, not an invoice): $${total.catalogEstimateUsd.toFixed(4)}`);\n\tlines.push(\n\t\t`Unknown usage: ${total.missingUsageAttempts} missing, ${total.partialUsageAttempts} partial, ${total.unsettledAttempts} unsettled attempts; ${total.unpricedAttempts} settled attempts unpriced`,\n\t);\n\tlines.push(\n\t\t\"Missing fields are not zero. Totals exclude unavailable sessions and non-generation token-count requests.\",\n\t);\n\tlines.push(\"Captured sources are not a guarantee of complete historical family usage.\");\n\tlines.push(\"\", GOAL_SCOPE);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Render the /context overview: a tree with one row per agent showing its own\n * tokens and cost (descendants excluded, so columns add up) plus per-agent\n * context-window utilization, followed by grand totals.\n */\nexport function formatContextTree(root: ContextTreeNode, width: number): string {\n\tconst rows = flattenContextTree(root);\n\tif (rows.some(({ node }) => node.ownRequestUsage)) return formatReceiptContextTree(root, width);\n\n\tconst tokenCells = rows.map((row) => formatTokenCount(spentTokens(row.node.ownUsage)));\n\tconst costCells = rows.map((row) => formatCost(row.node.ownUsage.cost.total));\n\tconst tokenHeader = \"tokens\";\n\tconst costHeader = \"cost\";\n\tconst contextHeader = \"context\";\n\tconst tokenWidth = Math.max(tokenHeader.length, ...tokenCells.map((cell) => cell.length));\n\tconst costWidth = Math.max(costHeader.length, ...costCells.map((cell) => cell.length));\n\tconst labelWidth = Math.max(\n\t\tMIN_LABEL_WIDTH,\n\t\tMath.min(\n\t\t\tMath.max(...rows.map((row) => row.prefix.length + 2 + visibleWidth(row.node.label))),\n\t\t\twidth - tokenWidth - costWidth - 28,\n\t\t),\n\t);\n\n\tconst lines: string[] = [];\n\tlines.push(theme.bold(\"Context\"));\n\tlines.push(\"\");\n\tlines.push(\"Legacy conversation projection — request receipts unavailable; auxiliary usage may be missing\");\n\tlines.push(\"Costs below are catalog estimates, not invoices\");\n\tlines.push(\"\");\n\tif (root.model) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Model:\")} ${root.model.provider}/${root.model.id}`);\n\t\tlines.push(\"\");\n\t}\n\n\tlines.push(\n\t\ttheme.fg(\n\t\t\t\"dim\",\n\t\t\t`${\" \".repeat(2)}${padEndAnsi(\"agent\", labelWidth)} ${padStartAnsi(tokenHeader, tokenWidth)} ${padStartAnsi(costHeader, costWidth)} ${contextHeader}`,\n\t\t),\n\t);\n\n\tfor (const [index, row] of rows.entries()) {\n\t\tconst labelSpace = Math.max(1, labelWidth - row.prefix.length - 2);\n\t\tconst label = truncateToWidth(row.node.label, labelSpace, \"...\");\n\t\tconst labelCell = padEndAnsi(\n\t\t\t`${theme.fg(\"dim\", row.prefix)}${statusIcon(row.node.status)} ${label}`,\n\t\t\tlabelWidth + 2,\n\t\t);\n\t\tconst tokenCell = padStartAnsi(tokenCells[index], tokenWidth);\n\t\tconst costCell = padStartAnsi(theme.fg(\"dim\", costCells[index]), costWidth);\n\t\tconst contextCell = formatContextColumn(row.node.contextUsage, row.node.id === \"root\");\n\t\tlines.push(`${labelCell} ${tokenCell} ${costCell} ${contextCell}`);\n\t}\n\n\tconst totals = sumOwnUsage(root);\n\tconst agentCount = countNodes(root);\n\tlines.push(\"\");\n\tlines.push(\n\t\t`${theme.fg(\"dim\", \"Total:\")} ${formatTokenCount(spentTokens(totals))} tokens ${theme.fg(\"dim\", \"·\")} ${formatCost(\n\t\t\ttotals.cost.total,\n\t\t)}${agentCount > 1 ? theme.fg(\"dim\", ` across ${agentCount} agents`) : \"\"}`,\n\t);\n\n\tlines.push(\"\");\n\tlines.push(theme.bold(\"Tokens\"));\n\tlines.push(`${theme.fg(\"dim\", \"Input:\")} ${totals.input.toLocaleString()}`);\n\tlines.push(`${theme.fg(\"dim\", \"Output:\")} ${totals.output.toLocaleString()}`);\n\tif (totals.cacheRead > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Read:\")} ${totals.cacheRead.toLocaleString()}`);\n\t}\n\tif (totals.cacheWrite > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Write:\")} ${totals.cacheWrite.toLocaleString()}`);\n\t}\n\tlines.push(`${theme.fg(\"dim\", \"Total:\")} ${spentTokens(totals).toLocaleString()}`);\n\n\tif (totals.cost.total > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Cost\"));\n\t\tlines.push(`${theme.fg(\"dim\", \"Total:\")} $${totals.cost.total.toFixed(4)}`);\n\t}\n\n\tconst rootContext = root.contextUsage;\n\tif (rootContext) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Context\"));\n\t\tif (rootContext.tokens === null || rootContext.percent === null) {\n\t\t\tlines.push(`${theme.fg(\"dim\", \"Current:\")} unknown after compaction`);\n\t\t} else {\n\t\t\tconst percent = `${Math.round(rootContext.percent * 10) / 10}%`;\n\t\t\tlines.push(\n\t\t\t\t`${theme.fg(\"dim\", \"Current:\")} ${rootContext.tokens.toLocaleString()} / ${rootContext.contextWindow.toLocaleString()} (${percent})`,\n\t\t\t);\n\t\t}\n\t}\n\n\tlines.push(\"\", GOAL_SCOPE);\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { truncateToWidth, visibleWidth } from "@ponythewhite/base-context-tui";
|
|
2
|
+
import { addRequestUsageTotals, emptyRequestUsageTotals } from "../../../core/request-usage.js";
|
|
2
3
|
import { addAssistantUsage, emptyUsage } from "../../../core/usage.js";
|
|
3
4
|
import { formatTokenCount } from "../agent-activity.js";
|
|
4
5
|
import { theme } from "../theme/theme.js";
|
|
@@ -81,6 +82,48 @@ function sumOwnUsage(root) {
|
|
|
81
82
|
walk(root);
|
|
82
83
|
return total;
|
|
83
84
|
}
|
|
85
|
+
const GOAL_SCOPE = "Goal budget: root successful main uncached input + output only; excludes cache, children and auxiliary requests";
|
|
86
|
+
function formatReceiptContextTree(root, width) {
|
|
87
|
+
const rows = flattenContextTree(root);
|
|
88
|
+
const total = emptyRequestUsageTotals();
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
let missingNodes = 0;
|
|
91
|
+
const lines = [theme.bold("Context"), "", "Generation receipts: captured sources, known usage only", ""];
|
|
92
|
+
for (const { node, prefix } of rows) {
|
|
93
|
+
const usage = node.ownRequestUsage;
|
|
94
|
+
const label = truncateToWidth(`${prefix}${node.label}`, Math.max(16, width - 45), "...");
|
|
95
|
+
if (!usage) {
|
|
96
|
+
missingNodes++;
|
|
97
|
+
lines.push(`${statusIcon(node.status)} ${label}: request usage unknown`);
|
|
98
|
+
}
|
|
99
|
+
else if (seen.has(usage.sessionId)) {
|
|
100
|
+
lines.push(`${statusIcon(node.status)} ${label}: shared source (counted once)`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
seen.add(usage.sessionId);
|
|
104
|
+
addRequestUsageTotals(total, usage.total);
|
|
105
|
+
lines.push(`${statusIcon(node.status)} ${label}: ${formatTokenCount(usage.total.processedTokens)} tokens · ${formatCost(usage.total.catalogEstimateUsd)} est.`);
|
|
106
|
+
for (const [purpose, bucket] of Object.entries(usage.byPurpose)) {
|
|
107
|
+
lines.push(` ${purpose}: ${bucket.processedTokens.toLocaleString()} tokens · $${bucket.catalogEstimateUsd.toFixed(4)} est. · ${bucket.settledAttempts} settled`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (node.contextUsage)
|
|
111
|
+
lines.push(` Context: ${formatContextColumn(node.contextUsage, node.id === "root")}`);
|
|
112
|
+
}
|
|
113
|
+
lines.push("", theme.bold("Captured family — known generation usage"));
|
|
114
|
+
lines.push(`Sessions with receipts: ${seen.size}; request usage unavailable: ${missingNodes}`);
|
|
115
|
+
lines.push(`Uncached input: ${total.input.toLocaleString()}`);
|
|
116
|
+
lines.push(`Input total (including cache): ${total.inputTotal.toLocaleString()}`);
|
|
117
|
+
lines.push(`Cache read: ${total.cacheRead.toLocaleString()}; cache write: ${total.cacheWrite.toLocaleString()}`);
|
|
118
|
+
lines.push(`Output: ${total.output.toLocaleString()}`);
|
|
119
|
+
lines.push(`Processed total: ${total.processedTokens.toLocaleString()}`);
|
|
120
|
+
lines.push(`Catalog estimate (unvalidated rates, not an invoice): $${total.catalogEstimateUsd.toFixed(4)}`);
|
|
121
|
+
lines.push(`Unknown usage: ${total.missingUsageAttempts} missing, ${total.partialUsageAttempts} partial, ${total.unsettledAttempts} unsettled attempts; ${total.unpricedAttempts} settled attempts unpriced`);
|
|
122
|
+
lines.push("Missing fields are not zero. Totals exclude unavailable sessions and non-generation token-count requests.");
|
|
123
|
+
lines.push("Captured sources are not a guarantee of complete historical family usage.");
|
|
124
|
+
lines.push("", GOAL_SCOPE);
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
}
|
|
84
127
|
/**
|
|
85
128
|
* Render the /context overview: a tree with one row per agent showing its own
|
|
86
129
|
* tokens and cost (descendants excluded, so columns add up) plus per-agent
|
|
@@ -88,6 +131,8 @@ function sumOwnUsage(root) {
|
|
|
88
131
|
*/
|
|
89
132
|
export function formatContextTree(root, width) {
|
|
90
133
|
const rows = flattenContextTree(root);
|
|
134
|
+
if (rows.some(({ node }) => node.ownRequestUsage))
|
|
135
|
+
return formatReceiptContextTree(root, width);
|
|
91
136
|
const tokenCells = rows.map((row) => formatTokenCount(spentTokens(row.node.ownUsage)));
|
|
92
137
|
const costCells = rows.map((row) => formatCost(row.node.ownUsage.cost.total));
|
|
93
138
|
const tokenHeader = "tokens";
|
|
@@ -99,6 +144,9 @@ export function formatContextTree(root, width) {
|
|
|
99
144
|
const lines = [];
|
|
100
145
|
lines.push(theme.bold("Context"));
|
|
101
146
|
lines.push("");
|
|
147
|
+
lines.push("Legacy conversation projection — request receipts unavailable; auxiliary usage may be missing");
|
|
148
|
+
lines.push("Costs below are catalog estimates, not invoices");
|
|
149
|
+
lines.push("");
|
|
102
150
|
if (root.model) {
|
|
103
151
|
lines.push(`${theme.fg("dim", "Model:")} ${root.model.provider}/${root.model.id}`);
|
|
104
152
|
lines.push("");
|
|
@@ -145,6 +193,7 @@ export function formatContextTree(root, width) {
|
|
|
145
193
|
lines.push(`${theme.fg("dim", "Current:")} ${rootContext.tokens.toLocaleString()} / ${rootContext.contextWindow.toLocaleString()} (${percent})`);
|
|
146
194
|
}
|
|
147
195
|
}
|
|
196
|
+
lines.push("", GOAL_SCOPE);
|
|
148
197
|
return lines.join("\n");
|
|
149
198
|
}
|
|
150
199
|
//# sourceMappingURL=context-tree-format.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-tree-format.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/context-tree-format.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Usage } from \"@ponythewhite/base-context-ai\";\nimport { truncateToWidth, visibleWidth } from \"@ponythewhite/base-context-tui\";\nimport type { ContextTreeNode } from \"../../../core/context-tree.js\";\nimport type { ContextUsage } from \"../../../core/extensions/index.js\";\nimport { addAssistantUsage, emptyUsage } from \"../../../core/usage.js\";\nimport { formatTokenCount } from \"../agent-activity.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst CONTEXT_BAR_WIDTH = 10;\nconst MIN_LABEL_WIDTH = 16;\n\ninterface ContextTreeRow {\n\tnode: ContextTreeNode;\n\t/** Tree-drawing prefix, e.g. \"│ └─ \". */\n\tprefix: string;\n}\n\nfunction statusIcon(status: ContextTreeNode[\"status\"]): string {\n\tswitch (status) {\n\t\tcase \"active\":\n\t\t\treturn theme.fg(\"accent\", \"●\");\n\t\tcase \"queued\":\n\t\t\treturn theme.fg(\"dim\", \"◇\");\n\t\tcase \"running\":\n\t\t\treturn theme.fg(\"accent\", \"◆\");\n\t\tcase \"done\":\n\t\t\treturn theme.fg(\"success\", \"✓\");\n\t\tcase \"error\":\n\t\tcase \"cancelled\":\n\t\t\treturn theme.fg(\"error\", \"✗\");\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\treturn _exhaustive;\n\t\t}\n\t}\n}\n\nfunction flattenContextTree(root: ContextTreeNode): ContextTreeRow[] {\n\tconst rows: ContextTreeRow[] = [{ node: root, prefix: \"\" }];\n\tconst walk = (children: readonly ContextTreeNode[], ancestors: string): void => {\n\t\tfor (const [index, child] of children.entries()) {\n\t\t\tconst isLast = index === children.length - 1;\n\t\t\trows.push({ node: child, prefix: `${ancestors}${isLast ? \"└─ \" : \"├─ \"}` });\n\t\t\twalk(child.children, `${ancestors}${isLast ? \" \" : \"│ \"}`);\n\t\t}\n\t};\n\twalk(root.children, \"\");\n\treturn rows;\n}\n\n/** Spend-relevant token count, matching the \"Total\" line of /usage. */\nfunction spentTokens(usage: Usage): number {\n\treturn usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction formatCost(cost: number): string {\n\treturn `$${cost.toFixed(2)}`;\n}\n\nfunction formatContextColumn(contextUsage: ContextUsage | undefined, withBar: boolean): string {\n\tif (!contextUsage) {\n\t\treturn theme.fg(\"dim\", \"-\");\n\t}\n\tif (contextUsage.tokens === null || contextUsage.percent === null) {\n\t\treturn theme.fg(\"dim\", \"unknown after compaction\");\n\t}\n\tconst percent = Math.round(contextUsage.percent);\n\tconst detail = `${formatTokenCount(contextUsage.tokens)}/${formatTokenCount(contextUsage.contextWindow)}`;\n\tconst text = `${percent}% ${theme.fg(\"dim\", `(${detail})`)}`;\n\tif (!withBar) {\n\t\treturn text;\n\t}\n\tconst filled = Math.max(\n\t\t0,\n\t\tMath.min(CONTEXT_BAR_WIDTH, Math.round((contextUsage.percent / 100) * CONTEXT_BAR_WIDTH)),\n\t);\n\tconst barColor = contextUsage.percent >= 80 ? \"warning\" : \"accent\";\n\tconst bar = theme.fg(barColor, \"▓\".repeat(filled)) + theme.fg(\"dim\", \"░\".repeat(CONTEXT_BAR_WIDTH - filled));\n\treturn `${bar} ${text}`;\n}\n\nfunction padEndAnsi(text: string, width: number): string {\n\treturn text + \" \".repeat(Math.max(0, width - visibleWidth(text)));\n}\n\nfunction padStartAnsi(text: string, width: number): string {\n\treturn \" \".repeat(Math.max(0, width - visibleWidth(text))) + text;\n}\n\nfunction countNodes(root: ContextTreeNode): number {\n\treturn 1 + root.children.reduce((sum, child) => sum + countNodes(child), 0);\n}\n\n/** Own usage summed over the whole tree: exact even while children are mid-run. */\nfunction sumOwnUsage(root: ContextTreeNode): Usage {\n\tconst total = emptyUsage();\n\tconst walk = (node: ContextTreeNode): void => {\n\t\taddAssistantUsage(total, node.ownUsage);\n\t\tfor (const child of node.children) {\n\t\t\twalk(child);\n\t\t}\n\t};\n\twalk(root);\n\treturn total;\n}\n\n/**\n * Render the /context overview: a tree with one row per agent showing its own\n * tokens and cost (descendants excluded, so columns add up) plus per-agent\n * context-window utilization, followed by grand totals.\n */\nexport function formatContextTree(root: ContextTreeNode, width: number): string {\n\tconst rows = flattenContextTree(root);\n\n\tconst tokenCells = rows.map((row) => formatTokenCount(spentTokens(row.node.ownUsage)));\n\tconst costCells = rows.map((row) => formatCost(row.node.ownUsage.cost.total));\n\tconst tokenHeader = \"tokens\";\n\tconst costHeader = \"cost\";\n\tconst contextHeader = \"context\";\n\tconst tokenWidth = Math.max(tokenHeader.length, ...tokenCells.map((cell) => cell.length));\n\tconst costWidth = Math.max(costHeader.length, ...costCells.map((cell) => cell.length));\n\tconst labelWidth = Math.max(\n\t\tMIN_LABEL_WIDTH,\n\t\tMath.min(\n\t\t\tMath.max(...rows.map((row) => row.prefix.length + 2 + visibleWidth(row.node.label))),\n\t\t\twidth - tokenWidth - costWidth - 28,\n\t\t),\n\t);\n\n\tconst lines: string[] = [];\n\tlines.push(theme.bold(\"Context\"));\n\tlines.push(\"\");\n\tif (root.model) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Model:\")} ${root.model.provider}/${root.model.id}`);\n\t\tlines.push(\"\");\n\t}\n\n\tlines.push(\n\t\ttheme.fg(\n\t\t\t\"dim\",\n\t\t\t`${\" \".repeat(2)}${padEndAnsi(\"agent\", labelWidth)} ${padStartAnsi(tokenHeader, tokenWidth)} ${padStartAnsi(costHeader, costWidth)} ${contextHeader}`,\n\t\t),\n\t);\n\n\tfor (const [index, row] of rows.entries()) {\n\t\tconst labelSpace = Math.max(1, labelWidth - row.prefix.length - 2);\n\t\tconst label = truncateToWidth(row.node.label, labelSpace, \"...\");\n\t\tconst labelCell = padEndAnsi(\n\t\t\t`${theme.fg(\"dim\", row.prefix)}${statusIcon(row.node.status)} ${label}`,\n\t\t\tlabelWidth + 2,\n\t\t);\n\t\tconst tokenCell = padStartAnsi(tokenCells[index], tokenWidth);\n\t\tconst costCell = padStartAnsi(theme.fg(\"dim\", costCells[index]), costWidth);\n\t\tconst contextCell = formatContextColumn(row.node.contextUsage, row.node.id === \"root\");\n\t\tlines.push(`${labelCell} ${tokenCell} ${costCell} ${contextCell}`);\n\t}\n\n\tconst totals = sumOwnUsage(root);\n\tconst agentCount = countNodes(root);\n\tlines.push(\"\");\n\tlines.push(\n\t\t`${theme.fg(\"dim\", \"Total:\")} ${formatTokenCount(spentTokens(totals))} tokens ${theme.fg(\"dim\", \"·\")} ${formatCost(\n\t\t\ttotals.cost.total,\n\t\t)}${agentCount > 1 ? theme.fg(\"dim\", ` across ${agentCount} agents`) : \"\"}`,\n\t);\n\n\tlines.push(\"\");\n\tlines.push(theme.bold(\"Tokens\"));\n\tlines.push(`${theme.fg(\"dim\", \"Input:\")} ${totals.input.toLocaleString()}`);\n\tlines.push(`${theme.fg(\"dim\", \"Output:\")} ${totals.output.toLocaleString()}`);\n\tif (totals.cacheRead > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Read:\")} ${totals.cacheRead.toLocaleString()}`);\n\t}\n\tif (totals.cacheWrite > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Write:\")} ${totals.cacheWrite.toLocaleString()}`);\n\t}\n\tlines.push(`${theme.fg(\"dim\", \"Total:\")} ${spentTokens(totals).toLocaleString()}`);\n\n\tif (totals.cost.total > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Cost\"));\n\t\tlines.push(`${theme.fg(\"dim\", \"Total:\")} $${totals.cost.total.toFixed(4)}`);\n\t}\n\n\tconst rootContext = root.contextUsage;\n\tif (rootContext) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Context\"));\n\t\tif (rootContext.tokens === null || rootContext.percent === null) {\n\t\t\tlines.push(`${theme.fg(\"dim\", \"Current:\")} unknown after compaction`);\n\t\t} else {\n\t\t\tconst percent = `${Math.round(rootContext.percent * 10) / 10}%`;\n\t\t\tlines.push(\n\t\t\t\t`${theme.fg(\"dim\", \"Current:\")} ${rootContext.tokens.toLocaleString()} / ${rootContext.contextWindow.toLocaleString()} (${percent})`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-tree-format.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/context-tree-format.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Usage } from \"@ponythewhite/base-context-ai\";\nimport { truncateToWidth, visibleWidth } from \"@ponythewhite/base-context-tui\";\nimport type { ContextTreeNode } from \"../../../core/context-tree.js\";\nimport type { ContextUsage } from \"../../../core/extensions/index.js\";\nimport { addRequestUsageTotals, emptyRequestUsageTotals } from \"../../../core/request-usage.js\";\nimport { addAssistantUsage, emptyUsage } from \"../../../core/usage.js\";\nimport { formatTokenCount } from \"../agent-activity.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst CONTEXT_BAR_WIDTH = 10;\nconst MIN_LABEL_WIDTH = 16;\n\ninterface ContextTreeRow {\n\tnode: ContextTreeNode;\n\t/** Tree-drawing prefix, e.g. \"│ └─ \". */\n\tprefix: string;\n}\n\nfunction statusIcon(status: ContextTreeNode[\"status\"]): string {\n\tswitch (status) {\n\t\tcase \"active\":\n\t\t\treturn theme.fg(\"accent\", \"●\");\n\t\tcase \"queued\":\n\t\t\treturn theme.fg(\"dim\", \"◇\");\n\t\tcase \"running\":\n\t\t\treturn theme.fg(\"accent\", \"◆\");\n\t\tcase \"done\":\n\t\t\treturn theme.fg(\"success\", \"✓\");\n\t\tcase \"error\":\n\t\tcase \"cancelled\":\n\t\t\treturn theme.fg(\"error\", \"✗\");\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\treturn _exhaustive;\n\t\t}\n\t}\n}\n\nfunction flattenContextTree(root: ContextTreeNode): ContextTreeRow[] {\n\tconst rows: ContextTreeRow[] = [{ node: root, prefix: \"\" }];\n\tconst walk = (children: readonly ContextTreeNode[], ancestors: string): void => {\n\t\tfor (const [index, child] of children.entries()) {\n\t\t\tconst isLast = index === children.length - 1;\n\t\t\trows.push({ node: child, prefix: `${ancestors}${isLast ? \"└─ \" : \"├─ \"}` });\n\t\t\twalk(child.children, `${ancestors}${isLast ? \" \" : \"│ \"}`);\n\t\t}\n\t};\n\twalk(root.children, \"\");\n\treturn rows;\n}\n\n/** Spend-relevant token count, matching the \"Total\" line of /usage. */\nfunction spentTokens(usage: Usage): number {\n\treturn usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction formatCost(cost: number): string {\n\treturn `$${cost.toFixed(2)}`;\n}\n\nfunction formatContextColumn(contextUsage: ContextUsage | undefined, withBar: boolean): string {\n\tif (!contextUsage) {\n\t\treturn theme.fg(\"dim\", \"-\");\n\t}\n\tif (contextUsage.tokens === null || contextUsage.percent === null) {\n\t\treturn theme.fg(\"dim\", \"unknown after compaction\");\n\t}\n\tconst percent = Math.round(contextUsage.percent);\n\tconst detail = `${formatTokenCount(contextUsage.tokens)}/${formatTokenCount(contextUsage.contextWindow)}`;\n\tconst text = `${percent}% ${theme.fg(\"dim\", `(${detail})`)}`;\n\tif (!withBar) {\n\t\treturn text;\n\t}\n\tconst filled = Math.max(\n\t\t0,\n\t\tMath.min(CONTEXT_BAR_WIDTH, Math.round((contextUsage.percent / 100) * CONTEXT_BAR_WIDTH)),\n\t);\n\tconst barColor = contextUsage.percent >= 80 ? \"warning\" : \"accent\";\n\tconst bar = theme.fg(barColor, \"▓\".repeat(filled)) + theme.fg(\"dim\", \"░\".repeat(CONTEXT_BAR_WIDTH - filled));\n\treturn `${bar} ${text}`;\n}\n\nfunction padEndAnsi(text: string, width: number): string {\n\treturn text + \" \".repeat(Math.max(0, width - visibleWidth(text)));\n}\n\nfunction padStartAnsi(text: string, width: number): string {\n\treturn \" \".repeat(Math.max(0, width - visibleWidth(text))) + text;\n}\n\nfunction countNodes(root: ContextTreeNode): number {\n\treturn 1 + root.children.reduce((sum, child) => sum + countNodes(child), 0);\n}\n\n/** Own usage summed over the whole tree: exact even while children are mid-run. */\nfunction sumOwnUsage(root: ContextTreeNode): Usage {\n\tconst total = emptyUsage();\n\tconst walk = (node: ContextTreeNode): void => {\n\t\taddAssistantUsage(total, node.ownUsage);\n\t\tfor (const child of node.children) {\n\t\t\twalk(child);\n\t\t}\n\t};\n\twalk(root);\n\treturn total;\n}\n\nconst GOAL_SCOPE =\n\t\"Goal budget: root successful main uncached input + output only; excludes cache, children and auxiliary requests\";\n\nfunction formatReceiptContextTree(root: ContextTreeNode, width: number): string {\n\tconst rows = flattenContextTree(root);\n\tconst total = emptyRequestUsageTotals();\n\tconst seen = new Set<string>();\n\tlet missingNodes = 0;\n\tconst lines = [theme.bold(\"Context\"), \"\", \"Generation receipts: captured sources, known usage only\", \"\"];\n\tfor (const { node, prefix } of rows) {\n\t\tconst usage = node.ownRequestUsage;\n\t\tconst label = truncateToWidth(`${prefix}${node.label}`, Math.max(16, width - 45), \"...\");\n\t\tif (!usage) {\n\t\t\tmissingNodes++;\n\t\t\tlines.push(`${statusIcon(node.status)} ${label}: request usage unknown`);\n\t\t} else if (seen.has(usage.sessionId)) {\n\t\t\tlines.push(`${statusIcon(node.status)} ${label}: shared source (counted once)`);\n\t\t} else {\n\t\t\tseen.add(usage.sessionId);\n\t\t\taddRequestUsageTotals(total, usage.total);\n\t\t\tlines.push(\n\t\t\t\t`${statusIcon(node.status)} ${label}: ${formatTokenCount(usage.total.processedTokens)} tokens · ${formatCost(usage.total.catalogEstimateUsd)} est.`,\n\t\t\t);\n\t\t\tfor (const [purpose, bucket] of Object.entries(usage.byPurpose)) {\n\t\t\t\tlines.push(\n\t\t\t\t\t` ${purpose}: ${bucket.processedTokens.toLocaleString()} tokens · $${bucket.catalogEstimateUsd.toFixed(4)} est. · ${bucket.settledAttempts} settled`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (node.contextUsage) lines.push(` Context: ${formatContextColumn(node.contextUsage, node.id === \"root\")}`);\n\t}\n\tlines.push(\"\", theme.bold(\"Captured family — known generation usage\"));\n\tlines.push(`Sessions with receipts: ${seen.size}; request usage unavailable: ${missingNodes}`);\n\tlines.push(`Uncached input: ${total.input.toLocaleString()}`);\n\tlines.push(`Input total (including cache): ${total.inputTotal.toLocaleString()}`);\n\tlines.push(`Cache read: ${total.cacheRead.toLocaleString()}; cache write: ${total.cacheWrite.toLocaleString()}`);\n\tlines.push(`Output: ${total.output.toLocaleString()}`);\n\tlines.push(`Processed total: ${total.processedTokens.toLocaleString()}`);\n\tlines.push(`Catalog estimate (unvalidated rates, not an invoice): $${total.catalogEstimateUsd.toFixed(4)}`);\n\tlines.push(\n\t\t`Unknown usage: ${total.missingUsageAttempts} missing, ${total.partialUsageAttempts} partial, ${total.unsettledAttempts} unsettled attempts; ${total.unpricedAttempts} settled attempts unpriced`,\n\t);\n\tlines.push(\n\t\t\"Missing fields are not zero. Totals exclude unavailable sessions and non-generation token-count requests.\",\n\t);\n\tlines.push(\"Captured sources are not a guarantee of complete historical family usage.\");\n\tlines.push(\"\", GOAL_SCOPE);\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Render the /context overview: a tree with one row per agent showing its own\n * tokens and cost (descendants excluded, so columns add up) plus per-agent\n * context-window utilization, followed by grand totals.\n */\nexport function formatContextTree(root: ContextTreeNode, width: number): string {\n\tconst rows = flattenContextTree(root);\n\tif (rows.some(({ node }) => node.ownRequestUsage)) return formatReceiptContextTree(root, width);\n\n\tconst tokenCells = rows.map((row) => formatTokenCount(spentTokens(row.node.ownUsage)));\n\tconst costCells = rows.map((row) => formatCost(row.node.ownUsage.cost.total));\n\tconst tokenHeader = \"tokens\";\n\tconst costHeader = \"cost\";\n\tconst contextHeader = \"context\";\n\tconst tokenWidth = Math.max(tokenHeader.length, ...tokenCells.map((cell) => cell.length));\n\tconst costWidth = Math.max(costHeader.length, ...costCells.map((cell) => cell.length));\n\tconst labelWidth = Math.max(\n\t\tMIN_LABEL_WIDTH,\n\t\tMath.min(\n\t\t\tMath.max(...rows.map((row) => row.prefix.length + 2 + visibleWidth(row.node.label))),\n\t\t\twidth - tokenWidth - costWidth - 28,\n\t\t),\n\t);\n\n\tconst lines: string[] = [];\n\tlines.push(theme.bold(\"Context\"));\n\tlines.push(\"\");\n\tlines.push(\"Legacy conversation projection — request receipts unavailable; auxiliary usage may be missing\");\n\tlines.push(\"Costs below are catalog estimates, not invoices\");\n\tlines.push(\"\");\n\tif (root.model) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Model:\")} ${root.model.provider}/${root.model.id}`);\n\t\tlines.push(\"\");\n\t}\n\n\tlines.push(\n\t\ttheme.fg(\n\t\t\t\"dim\",\n\t\t\t`${\" \".repeat(2)}${padEndAnsi(\"agent\", labelWidth)} ${padStartAnsi(tokenHeader, tokenWidth)} ${padStartAnsi(costHeader, costWidth)} ${contextHeader}`,\n\t\t),\n\t);\n\n\tfor (const [index, row] of rows.entries()) {\n\t\tconst labelSpace = Math.max(1, labelWidth - row.prefix.length - 2);\n\t\tconst label = truncateToWidth(row.node.label, labelSpace, \"...\");\n\t\tconst labelCell = padEndAnsi(\n\t\t\t`${theme.fg(\"dim\", row.prefix)}${statusIcon(row.node.status)} ${label}`,\n\t\t\tlabelWidth + 2,\n\t\t);\n\t\tconst tokenCell = padStartAnsi(tokenCells[index], tokenWidth);\n\t\tconst costCell = padStartAnsi(theme.fg(\"dim\", costCells[index]), costWidth);\n\t\tconst contextCell = formatContextColumn(row.node.contextUsage, row.node.id === \"root\");\n\t\tlines.push(`${labelCell} ${tokenCell} ${costCell} ${contextCell}`);\n\t}\n\n\tconst totals = sumOwnUsage(root);\n\tconst agentCount = countNodes(root);\n\tlines.push(\"\");\n\tlines.push(\n\t\t`${theme.fg(\"dim\", \"Total:\")} ${formatTokenCount(spentTokens(totals))} tokens ${theme.fg(\"dim\", \"·\")} ${formatCost(\n\t\t\ttotals.cost.total,\n\t\t)}${agentCount > 1 ? theme.fg(\"dim\", ` across ${agentCount} agents`) : \"\"}`,\n\t);\n\n\tlines.push(\"\");\n\tlines.push(theme.bold(\"Tokens\"));\n\tlines.push(`${theme.fg(\"dim\", \"Input:\")} ${totals.input.toLocaleString()}`);\n\tlines.push(`${theme.fg(\"dim\", \"Output:\")} ${totals.output.toLocaleString()}`);\n\tif (totals.cacheRead > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Read:\")} ${totals.cacheRead.toLocaleString()}`);\n\t}\n\tif (totals.cacheWrite > 0) {\n\t\tlines.push(`${theme.fg(\"dim\", \"Cache Write:\")} ${totals.cacheWrite.toLocaleString()}`);\n\t}\n\tlines.push(`${theme.fg(\"dim\", \"Total:\")} ${spentTokens(totals).toLocaleString()}`);\n\n\tif (totals.cost.total > 0) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Cost\"));\n\t\tlines.push(`${theme.fg(\"dim\", \"Total:\")} $${totals.cost.total.toFixed(4)}`);\n\t}\n\n\tconst rootContext = root.contextUsage;\n\tif (rootContext) {\n\t\tlines.push(\"\");\n\t\tlines.push(theme.bold(\"Context\"));\n\t\tif (rootContext.tokens === null || rootContext.percent === null) {\n\t\t\tlines.push(`${theme.fg(\"dim\", \"Current:\")} unknown after compaction`);\n\t\t} else {\n\t\t\tconst percent = `${Math.round(rootContext.percent * 10) / 10}%`;\n\t\t\tlines.push(\n\t\t\t\t`${theme.fg(\"dim\", \"Current:\")} ${rootContext.tokens.toLocaleString()} / ${rootContext.contextWindow.toLocaleString()} (${percent})`,\n\t\t\t);\n\t\t}\n\t}\n\n\tlines.push(\"\", GOAL_SCOPE);\n\treturn lines.join(\"\\n\");\n}\n"]}
|