@oh-my-pi/pi-coding-agent 16.0.10 → 16.1.0
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 +57 -0
- package/dist/cli.js +3344 -3371
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +52 -0
- package/dist/types/commit/agentic/agent.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +14 -8
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/modes/components/__tests__/skill-message.test.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +6 -1
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +39 -0
- package/dist/types/modes/components/assistant-message.d.ts +8 -0
- package/dist/types/modes/components/cache-invalidation-marker.d.ts +34 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +42 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +14 -1
- package/dist/types/modes/components/index.d.ts +0 -1
- package/dist/types/modes/components/message-frame.d.ts +6 -4
- package/dist/types/modes/controllers/command-controller.d.ts +3 -2
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/theme/theme.d.ts +7 -1
- package/dist/types/modes/types.d.ts +9 -2
- package/dist/types/registry/agent-registry.d.ts +10 -3
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +20 -1
- package/dist/types/session/compact-modes.d.ts +60 -0
- package/dist/types/session/session-context.d.ts +7 -0
- package/dist/types/session/session-dump-format.d.ts +1 -0
- package/dist/types/session/streaming-output.d.ts +0 -2
- package/dist/types/session/tool-choice-queue.d.ts +14 -0
- package/dist/types/system-prompt.d.ts +3 -3
- package/dist/types/tools/__tests__/json-tree.test.d.ts +1 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/resolve.d.ts +15 -5
- package/package.json +12 -12
- package/src/advisor/index.ts +1 -0
- package/src/advisor/transcript-recorder.ts +136 -0
- package/src/cli/stats-cli.ts +2 -11
- package/src/collab/host.ts +25 -13
- package/src/commit/agentic/agent.ts +2 -1
- package/src/commit/agentic/tools/git-file-diff.ts +2 -2
- package/src/commit/changelog/index.ts +1 -1
- package/src/commit/map-reduce/map-phase.ts +1 -1
- package/src/commit/map-reduce/utils.ts +1 -1
- package/src/config/settings-schema.ts +16 -9
- package/src/config/settings.ts +0 -6
- package/src/debug/log-viewer.ts +4 -4
- package/src/debug/raw-sse.ts +4 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/renderer.ts +9 -9
- package/src/eval/js/tool-bridge.ts +3 -2
- package/src/eval/py/prelude.py +3 -2
- package/src/export/html/tool-views.generated.js +28 -28
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/history-protocol.ts +8 -3
- package/src/irc/bus.ts +8 -0
- package/src/lsp/index.ts +2 -2
- package/src/lsp/render.ts +7 -7
- package/src/main.ts +4 -1
- package/src/modes/acp/acp-agent.ts +63 -0
- package/src/modes/components/__tests__/skill-message.test.ts +92 -0
- package/src/modes/components/agent-dashboard.ts +1 -1
- package/src/modes/components/agent-hub.ts +97 -920
- package/src/modes/components/agent-transcript-viewer.ts +461 -0
- package/src/modes/components/assistant-message.ts +21 -0
- package/src/modes/components/cache-invalidation-marker.ts +84 -0
- package/src/modes/components/chat-transcript-builder.ts +476 -0
- package/src/modes/components/compaction-summary-message.ts +29 -1
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/diff.ts +12 -35
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +5 -5
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/index.ts +0 -1
- package/src/modes/components/message-frame.ts +10 -6
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/overlay-box.ts +10 -9
- package/src/modes/components/skill-message.ts +39 -19
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +12 -2
- package/src/modes/controllers/event-controller.ts +15 -1
- package/src/modes/controllers/input-controller.ts +8 -1
- package/src/modes/controllers/selector-controller.ts +11 -1
- package/src/modes/interactive-mode.ts +13 -3
- package/src/modes/theme/theme.ts +14 -0
- package/src/modes/types.ts +9 -2
- package/src/modes/utils/ui-helpers.ts +20 -2
- package/src/prompts/steering/user-interjection.md +3 -4
- package/src/prompts/tools/read.md +1 -1
- package/src/registry/agent-registry.ts +13 -4
- package/src/sdk.ts +9 -7
- package/src/session/agent-session.ts +182 -16
- package/src/session/compact-modes.ts +105 -0
- package/src/session/messages.ts +7 -9
- package/src/session/session-context.ts +54 -7
- package/src/session/session-dump-format.ts +4 -2
- package/src/session/session-history-format.ts +1 -1
- package/src/session/snapcompact-inline.ts +2 -2
- package/src/session/streaming-output.ts +5 -5
- package/src/session/tool-choice-queue.ts +59 -0
- package/src/slash-commands/builtin-registry.ts +16 -4
- package/src/system-prompt.ts +10 -9
- package/src/task/executor.ts +1 -1
- package/src/task/output-manager.ts +5 -0
- package/src/tools/__tests__/json-tree.test.ts +35 -0
- package/src/tools/approval.ts +1 -1
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/bash.ts +0 -1
- package/src/tools/browser.ts +0 -1
- package/src/tools/eval.ts +1 -1
- package/src/tools/gh.ts +1 -1
- package/src/tools/index.ts +4 -0
- package/src/tools/irc.ts +1 -1
- package/src/tools/json-tree.ts +22 -5
- package/src/tools/read.ts +5 -6
- package/src/tools/resolve.ts +66 -41
- package/src/tui/output-block.ts +9 -9
- package/src/web/scrapers/firefox-addons.ts +1 -1
- package/src/web/scrapers/github.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/metacpan.ts +2 -2
- package/src/web/scrapers/nvd.ts +2 -2
- package/src/web/scrapers/ollama.ts +1 -1
- package/src/web/scrapers/opencorporates.ts +1 -1
- package/src/web/scrapers/pub-dev.ts +1 -1
- package/src/web/scrapers/repology.ts +1 -1
- package/src/web/scrapers/sourcegraph.ts +1 -1
- package/src/web/scrapers/terraform.ts +6 -6
- package/src/web/scrapers/wikidata.ts +2 -2
- package/src/workspace-tree.ts +1 -1
- package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
- package/src/modes/components/branch-summary-message.ts +0 -46
|
@@ -9,9 +9,10 @@ import { createAdvisorMessageCard } from "../../modes/components/advisor-message
|
|
|
9
9
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
10
10
|
import { createBackgroundTanDispatchBlock } from "../../modes/components/background-tan-message";
|
|
11
11
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
12
|
-
import {
|
|
12
|
+
import { detectCacheInvalidation } from "../../modes/components/cache-invalidation-marker";
|
|
13
13
|
import { CollabPromptMessageComponent } from "../../modes/components/collab-prompt-message";
|
|
14
14
|
import {
|
|
15
|
+
BranchSummaryMessageComponent,
|
|
15
16
|
CompactionSummaryMessageComponent,
|
|
16
17
|
createHandoffSummaryMessageComponent,
|
|
17
18
|
} from "../../modes/components/compaction-summary-message";
|
|
@@ -358,6 +359,9 @@ export class UiHelpers {
|
|
|
358
359
|
): void {
|
|
359
360
|
// Preserved: message_start handler owns this lifecycle (see #783)
|
|
360
361
|
this.ctx.pendingTools.clear();
|
|
362
|
+
// Reseed the cache-invalidation baseline: this rebuild re-derives every
|
|
363
|
+
// turn's marker from usage, and the last turn becomes the live baseline.
|
|
364
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
361
365
|
|
|
362
366
|
if (options.updateFooter) {
|
|
363
367
|
this.ctx.statusLine.invalidate();
|
|
@@ -399,13 +403,27 @@ export class UiHelpers {
|
|
|
399
403
|
// updateResult armed.
|
|
400
404
|
previous.seal();
|
|
401
405
|
};
|
|
402
|
-
|
|
406
|
+
const messages = sessionContext.messages;
|
|
407
|
+
const count = messages.length;
|
|
408
|
+
for (let i = 0; i < count; i++) {
|
|
409
|
+
const message = messages[i]!;
|
|
403
410
|
if (message.role !== "toolResult") flushPendingUsage();
|
|
404
411
|
// Assistant messages need special handling for tool calls
|
|
405
412
|
if (message.role === "assistant") {
|
|
406
413
|
this.ctx.addMessageToChat(message);
|
|
407
414
|
const lastChild = this.ctx.chatContainer.children[this.ctx.chatContainer.children.length - 1];
|
|
408
415
|
const assistantComponent = lastChild instanceof AssistantMessageComponent ? lastChild : undefined;
|
|
416
|
+
if (assistantComponent) {
|
|
417
|
+
const usage = message.usage;
|
|
418
|
+
const explained = sessionContext.cacheMissExplainedAt?.[i] ?? false;
|
|
419
|
+
if (this.ctx.settings.get("display.cacheMissMarker") && !explained) {
|
|
420
|
+
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
|
|
421
|
+
if (invalidation) assistantComponent.setCacheInvalidation(invalidation);
|
|
422
|
+
}
|
|
423
|
+
if (usage.cacheRead + usage.cacheWrite + usage.input > 0) {
|
|
424
|
+
this.ctx.lastAssistantUsage = usage;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
409
427
|
const hasVisibleAssistantContent = message.content.some(
|
|
410
428
|
content =>
|
|
411
429
|
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<user_interjection>
|
|
2
|
-
The user sent this message while you were working
|
|
3
|
-
priority and supersedes
|
|
4
|
-
|
|
5
|
-
now.
|
|
2
|
+
The user sent this message as an interjection while you were working. It takes
|
|
3
|
+
priority and supersedes earlier instructions wherever they conflict — re-read it
|
|
4
|
+
and make sure your current work reflects their intent.
|
|
6
5
|
|
|
7
6
|
<message>
|
|
8
7
|
{{message}}
|
|
@@ -33,7 +33,7 @@ Append `:<sel>` to `path`; bare path = default mode.
|
|
|
33
33
|
- File with explicit selector → lines prefixed with numbers: `41|def alpha():`.
|
|
34
34
|
{{/if}}
|
|
35
35
|
{{/if}}
|
|
36
|
-
- Parseable code without selector → **structural summary**: declarations kept,
|
|
36
|
+
- Parseable code without selector → **structural summary**: declarations kept, body elided with `…`. The footer shows the recovery selector. Re-issue ONLY the ranges you need via the multi-range selector.
|
|
37
37
|
|
|
38
38
|
# Documents & Notebooks
|
|
39
39
|
|
|
@@ -22,7 +22,13 @@ export const MAIN_AGENT_ID = "Main";
|
|
|
22
22
|
* - `aborted`: hard-killed, terminal.
|
|
23
23
|
*/
|
|
24
24
|
export type AgentStatus = "running" | "idle" | "parked" | "aborted";
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* - `main`/`sub`: the user-facing agent tree (driving agent + task subagents).
|
|
27
|
+
* - `advisor`: a passive review transcript persisted like a subagent for usage
|
|
28
|
+
* attribution and Agent Hub observability, but never a peer — hidden from
|
|
29
|
+
* agent-facing rosters (`irc`, `history://`) and not messageable/revivable.
|
|
30
|
+
*/
|
|
31
|
+
export type AgentKind = "main" | "sub" | "advisor";
|
|
26
32
|
|
|
27
33
|
export interface AgentRef {
|
|
28
34
|
id: string;
|
|
@@ -157,11 +163,14 @@ export class AgentRegistry {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
/**
|
|
160
|
-
* Returns every alive agent (running | idle) except the caller.
|
|
161
|
-
*
|
|
166
|
+
* Returns every alive agent (running | idle) except the caller. Advisor refs
|
|
167
|
+
* are observability-only transcripts, never peers, so they are excluded.
|
|
168
|
+
* Flat namespace: every other agent is visible.
|
|
162
169
|
*/
|
|
163
170
|
listVisibleTo(id: string): AgentRef[] {
|
|
164
|
-
return this.list().filter(
|
|
171
|
+
return this.list().filter(
|
|
172
|
+
ref => ref.id !== id && ref.kind !== "advisor" && (ref.status === "running" || ref.status === "idle"),
|
|
173
|
+
);
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
onChange(listener: RegistryListener): () => void {
|
package/src/sdk.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
type AgentTelemetryConfig,
|
|
6
6
|
type AgentTool,
|
|
7
7
|
AppendOnlyContextManager,
|
|
8
|
-
INTENT_FIELD,
|
|
9
8
|
type ThinkingLevel,
|
|
10
9
|
} from "@oh-my-pi/pi-agent-core";
|
|
11
10
|
import {
|
|
@@ -35,6 +34,7 @@ import {
|
|
|
35
34
|
prompt,
|
|
36
35
|
Snowflake,
|
|
37
36
|
} from "@oh-my-pi/pi-utils";
|
|
37
|
+
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
38
38
|
import { ADVISOR_READONLY_TOOL_NAMES, discoverWatchdogFiles } from "./advisor";
|
|
39
39
|
import { type AsyncJob, AsyncJobManager } from "./async";
|
|
40
40
|
import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
|
|
@@ -838,7 +838,7 @@ export interface BuildSystemPromptOptions {
|
|
|
838
838
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
839
839
|
cwd?: string;
|
|
840
840
|
appendPrompt?: string;
|
|
841
|
-
|
|
841
|
+
inlineToolDescriptors?: boolean;
|
|
842
842
|
}
|
|
843
843
|
|
|
844
844
|
/**
|
|
@@ -853,7 +853,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
853
853
|
skills: options.skills,
|
|
854
854
|
contextFiles: options.contextFiles,
|
|
855
855
|
appendSystemPrompt: options.appendPrompt,
|
|
856
|
-
|
|
856
|
+
inlineToolDescriptors: options.inlineToolDescriptors,
|
|
857
857
|
});
|
|
858
858
|
}
|
|
859
859
|
|
|
@@ -2130,7 +2130,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2130
2130
|
emitEvent: event => cursorEventEmitter?.(event),
|
|
2131
2131
|
});
|
|
2132
2132
|
|
|
2133
|
-
const
|
|
2133
|
+
const inlineToolDescriptors = settings.get("inlineToolDescriptors");
|
|
2134
2134
|
const eagerTasks = settings.get("task.eager") !== "default";
|
|
2135
2135
|
const eagerTasksAlways = settings.get("task.eager") === "always";
|
|
2136
2136
|
const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
|
|
@@ -2198,7 +2198,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2198
2198
|
}
|
|
2199
2199
|
appendPrompt = parts.join("\n\n");
|
|
2200
2200
|
}
|
|
2201
|
-
// Owned/in-band tool
|
|
2201
|
+
// Owned/in-band tool dialects (non-native) require the catalog as `# Tool:`
|
|
2202
2202
|
// sections; native tool calling lets the compact name list suffice.
|
|
2203
2203
|
const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2204
2204
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
@@ -2211,7 +2211,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2211
2211
|
alwaysApplyRules,
|
|
2212
2212
|
skillsSettings: settings.getGroup("skills"),
|
|
2213
2213
|
appendSystemPrompt: appendPrompt,
|
|
2214
|
-
|
|
2214
|
+
inlineToolDescriptors,
|
|
2215
2215
|
nativeTools,
|
|
2216
2216
|
intentField,
|
|
2217
2217
|
mcpDiscoveryMode: hasDiscoverableTools,
|
|
@@ -2536,9 +2536,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2536
2536
|
return result;
|
|
2537
2537
|
},
|
|
2538
2538
|
intentTracing: !!intentField,
|
|
2539
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
2539
2540
|
dialect: resolveDialect(settings.get("tools.format"), model),
|
|
2540
2541
|
abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
|
|
2541
|
-
getToolChoice: () => session?.
|
|
2542
|
+
getToolChoice: () => session?.nextToolChoiceDirective(),
|
|
2542
2543
|
telemetry: options.telemetry,
|
|
2543
2544
|
appendOnlyContext: model
|
|
2544
2545
|
? shouldEnableAppendOnlyContext(settings.get("provider.appendOnlyContext"), model)
|
|
@@ -2606,6 +2607,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2606
2607
|
session = new AgentSession({
|
|
2607
2608
|
advisorWatchdogPrompt,
|
|
2608
2609
|
agent,
|
|
2610
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
2609
2611
|
thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
|
|
2610
2612
|
sessionManager,
|
|
2611
2613
|
settings,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
countTokens,
|
|
36
36
|
resolveTelemetry,
|
|
37
37
|
ThinkingLevel,
|
|
38
|
+
type ToolChoiceDirective,
|
|
38
39
|
} from "@oh-my-pi/pi-agent-core";
|
|
39
40
|
import {
|
|
40
41
|
AGGRESSIVE_SHAKE_CONFIG,
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
type ShakeRegion,
|
|
63
64
|
type SummaryOptions,
|
|
64
65
|
shouldCompact,
|
|
66
|
+
shouldUseOpenAiRemoteCompaction,
|
|
65
67
|
} from "@oh-my-pi/pi-agent-core/compaction";
|
|
66
68
|
import {
|
|
67
69
|
DEFAULT_PRUNE_CONFIG,
|
|
@@ -101,6 +103,7 @@ import {
|
|
|
101
103
|
resolveServiceTier,
|
|
102
104
|
streamSimple,
|
|
103
105
|
} from "@oh-my-pi/pi-ai";
|
|
106
|
+
import { stripToolDescriptions } from "@oh-my-pi/pi-ai/utils/schema";
|
|
104
107
|
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
105
108
|
import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
106
109
|
import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
@@ -126,6 +129,7 @@ import {
|
|
|
126
129
|
type AdvisorNote,
|
|
127
130
|
AdvisorRuntime,
|
|
128
131
|
type AdvisorSeverity,
|
|
132
|
+
AdvisorTranscriptRecorder,
|
|
129
133
|
formatAdvisorBatchContent,
|
|
130
134
|
isAdvisorInterruptImmuneTurnActive,
|
|
131
135
|
isInterruptingSeverity,
|
|
@@ -258,6 +262,7 @@ import type { CheckpointState } from "../tools/checkpoint";
|
|
|
258
262
|
import { outputMeta, wrapToolWithMetaNotice } from "../tools/output-meta";
|
|
259
263
|
import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils";
|
|
260
264
|
import { isAutoQaEnabled } from "../tools/report-tool-issue";
|
|
265
|
+
import { buildResolveReminderMessage } from "../tools/resolve";
|
|
261
266
|
import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo";
|
|
262
267
|
import { ToolAbortError, ToolError } from "../tools/tool-errors";
|
|
263
268
|
import { clampTimeout } from "../tools/tool-timeouts";
|
|
@@ -277,6 +282,7 @@ import {
|
|
|
277
282
|
shouldEvaluateCodexAutoRedeem,
|
|
278
283
|
shouldPromptCodexAutoRedeem,
|
|
279
284
|
} from "./codex-auto-reset";
|
|
285
|
+
import { findCompactMode } from "./compact-modes";
|
|
280
286
|
import {
|
|
281
287
|
type BashExecutionMessage,
|
|
282
288
|
type CustomMessage,
|
|
@@ -364,6 +370,23 @@ const COMPACTION_CHECK_CONTINUATION: CompactionCheckResult = {
|
|
|
364
370
|
deferredHandoff: false,
|
|
365
371
|
continuationScheduled: true,
|
|
366
372
|
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Per-turn prune cache window. A tool result whose all-message suffix exceeds
|
|
376
|
+
* this is in the warm, already-sent prompt-cache prefix: re-writing it costs the
|
|
377
|
+
* cacheWrite premium on the whole suffix. Per-turn passes only reclaim inside
|
|
378
|
+
* this tail (matches the supersede pass's default `suffixTokenLimit`); deeper
|
|
379
|
+
* stale/age victims are left to compaction/shake, which rebuild the cache anyway.
|
|
380
|
+
*/
|
|
381
|
+
const PRUNE_CACHE_WARM_SUFFIX_TOKENS = 8_000;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Idle gap after which the supersede pass may flush the whole sent region (the
|
|
385
|
+
* provider cache is cold, so re-writing it is free). MUST exceed the maximum
|
|
386
|
+
* Anthropic prompt-cache TTL — "long" retention (the OAuth default) is 1h — or a
|
|
387
|
+
* still-warm prefix is busted by the flush. 90 min leaves margin over the 1h TTL.
|
|
388
|
+
*/
|
|
389
|
+
const PRUNE_IDLE_FLUSH_MS = 90 * 60_000;
|
|
367
390
|
export type CommandMetadataChangedListener = () => void | Promise<void>;
|
|
368
391
|
export type AsyncJobSnapshotItem = Pick<AsyncJob, "id" | "type" | "status" | "label" | "startTime">;
|
|
369
392
|
|
|
@@ -513,6 +536,12 @@ export interface AgentSessionConfig {
|
|
|
513
536
|
advisorReadOnlyTools?: AgentTool[];
|
|
514
537
|
/** Preloaded watchdog prompt content for the advisor. */
|
|
515
538
|
advisorWatchdogPrompt?: string;
|
|
539
|
+
/**
|
|
540
|
+
* Strip tool descriptions from provider-bound tool specs on side requests
|
|
541
|
+
* (handoff). Must match the session-start value used to build the system
|
|
542
|
+
* prompt so inline descriptors are not also sent through provider schemas.
|
|
543
|
+
*/
|
|
544
|
+
pruneToolDescriptions?: boolean;
|
|
516
545
|
/**
|
|
517
546
|
* Disconnect this session's OWNED MCP manager on dispose. Provided only when
|
|
518
547
|
* the session created the manager (top-level sessions); subagents reuse a
|
|
@@ -1110,6 +1139,13 @@ export class AgentSession {
|
|
|
1110
1139
|
#advisorReadOnlyTools?: AgentTool[];
|
|
1111
1140
|
#advisorWatchdogPrompt?: string;
|
|
1112
1141
|
#advisorYieldQueueUnsubscribe?: () => void;
|
|
1142
|
+
/** Persists the advisor agent's turns to `<session>/__advisor.jsonl` for stats
|
|
1143
|
+
* attribution and Agent Hub observability. Undefined when no advisor is active. */
|
|
1144
|
+
#advisorTranscriptRecorder?: AdvisorTranscriptRecorder;
|
|
1145
|
+
/** Unsubscribe for the advisor agent's event stream feeding the recorder. */
|
|
1146
|
+
#advisorAgentUnsubscribe?: () => void;
|
|
1147
|
+
/** Latest advisor-recorder close, awaited by dispose() so the final turn lands on disk. */
|
|
1148
|
+
#advisorRecorderClosed: Promise<void> = Promise.resolve();
|
|
1113
1149
|
#goalTurnCounter = 0;
|
|
1114
1150
|
#planReferenceSent = false;
|
|
1115
1151
|
#planReferencePath = "local://PLAN.md";
|
|
@@ -1295,6 +1331,8 @@ export class AgentSession {
|
|
|
1295
1331
|
// unchanged — otherwise a mid-turn estimate would survive into idle.
|
|
1296
1332
|
#contextUsageRevision = 0;
|
|
1297
1333
|
#obfuscator: SecretObfuscator | undefined;
|
|
1334
|
+
/** Session-start value of `inlineToolDescriptors`; drives handoff tool pruning. */
|
|
1335
|
+
#pruneToolDescriptions = false;
|
|
1298
1336
|
#checkpointState: CheckpointState | undefined = undefined;
|
|
1299
1337
|
#pendingRewindReport: string | undefined = undefined;
|
|
1300
1338
|
#lastSuccessfulYieldToolCallId: string | undefined = undefined;
|
|
@@ -1503,6 +1541,7 @@ export class AgentSession {
|
|
|
1503
1541
|
this.#modelRegistry = config.modelRegistry;
|
|
1504
1542
|
this.#advisorReadOnlyTools = config.advisorReadOnlyTools;
|
|
1505
1543
|
this.#advisorWatchdogPrompt = config.advisorWatchdogPrompt;
|
|
1544
|
+
this.#pruneToolDescriptions = config.pruneToolDescriptions === true;
|
|
1506
1545
|
this.#validateRetryFallbackChains();
|
|
1507
1546
|
this.#toolRegistry = config.toolRegistry ?? new Map();
|
|
1508
1547
|
this.#requestedToolNames = config.requestedToolNames;
|
|
@@ -1708,7 +1747,13 @@ export class AgentSession {
|
|
|
1708
1747
|
* so none of them inject into the new conversation.
|
|
1709
1748
|
*/
|
|
1710
1749
|
#resetAdvisorSessionState(): void {
|
|
1750
|
+
// Mute the recorder across the re-prime: AdvisorRuntime.reset() aborts the advisor
|
|
1751
|
+
// loop, and that abort can emit an `aborted` message_end we must not attribute to
|
|
1752
|
+
// either session's transcript. Detach, reset, then re-attach the live agent's feed.
|
|
1753
|
+
this.#advisorAgentUnsubscribe?.();
|
|
1754
|
+
this.#advisorAgentUnsubscribe = undefined;
|
|
1711
1755
|
this.#advisorRuntime?.reset();
|
|
1756
|
+
this.#attachAdvisorRecorderFeed();
|
|
1712
1757
|
this.#advisorPrimaryTurnsCompleted = 0;
|
|
1713
1758
|
this.#advisorInterruptImmuneTurnStart = undefined;
|
|
1714
1759
|
this.#advisorAutoResumeSuppressed = false;
|
|
@@ -1841,6 +1886,18 @@ export class AgentSession {
|
|
|
1841
1886
|
};
|
|
1842
1887
|
|
|
1843
1888
|
this.#advisorAgent = advisorAgent;
|
|
1889
|
+
// Persist the advisor's turns to `<session>/__advisor.jsonl` (resolved lazily
|
|
1890
|
+
// so it follows session switches) so its model usage is attributed in stats
|
|
1891
|
+
// and its transcript shows in the Agent Hub — without registering it as a peer.
|
|
1892
|
+
const recorder = new AdvisorTranscriptRecorder(
|
|
1893
|
+
() => this.sessionManager.getSessionFile(),
|
|
1894
|
+
() => this.sessionManager.getCwd(),
|
|
1895
|
+
// On the advisor on→off→on toggle, wait for the prior recorder's close so
|
|
1896
|
+
// two SessionManagers never hold the same __advisor.jsonl at once.
|
|
1897
|
+
this.#advisorRecorderClosed,
|
|
1898
|
+
);
|
|
1899
|
+
this.#advisorTranscriptRecorder = recorder;
|
|
1900
|
+
this.#attachAdvisorRecorderFeed();
|
|
1844
1901
|
this.#advisorRuntime = new AdvisorRuntime(advisorAgentFacade, {
|
|
1845
1902
|
snapshotMessages: () => this.agent.state.messages,
|
|
1846
1903
|
enqueueAdvice,
|
|
@@ -1871,10 +1928,21 @@ export class AgentSession {
|
|
|
1871
1928
|
}
|
|
1872
1929
|
|
|
1873
1930
|
#stopAdvisorRuntime(): void {
|
|
1931
|
+
// Detach the recorder feed BEFORE aborting the advisor agent: dispose() aborts
|
|
1932
|
+
// the loop, and an abort emits a final `message_end` we must not enqueue against
|
|
1933
|
+
// a closing recorder (it would reopen and resurrect an already-released file).
|
|
1934
|
+
this.#advisorAgentUnsubscribe?.();
|
|
1935
|
+
this.#advisorAgentUnsubscribe = undefined;
|
|
1874
1936
|
if (this.#advisorRuntime) {
|
|
1875
1937
|
this.#advisorRuntime.dispose();
|
|
1876
1938
|
this.#advisorRuntime = undefined;
|
|
1877
1939
|
}
|
|
1940
|
+
if (this.#advisorTranscriptRecorder) {
|
|
1941
|
+
// Capture the close so dispose()/`/drop` can await the queued open+append+close —
|
|
1942
|
+
// the last advisor turn would otherwise be lost on a fast process exit.
|
|
1943
|
+
this.#advisorRecorderClosed = this.#advisorTranscriptRecorder.close();
|
|
1944
|
+
this.#advisorTranscriptRecorder = undefined;
|
|
1945
|
+
}
|
|
1878
1946
|
if (this.#advisorAgent) {
|
|
1879
1947
|
this.#advisorAgent = undefined;
|
|
1880
1948
|
}
|
|
@@ -1882,6 +1950,18 @@ export class AgentSession {
|
|
|
1882
1950
|
this.#advisorYieldQueueUnsubscribe = undefined;
|
|
1883
1951
|
}
|
|
1884
1952
|
|
|
1953
|
+
/** Subscribe the advisor agent's finalized messages into the transcript recorder.
|
|
1954
|
+
* Idempotent-by-replacement: callers detach the prior feed first. Kept separate
|
|
1955
|
+
* so the re-prime path can mute the feed across an abort-driven reset. */
|
|
1956
|
+
#attachAdvisorRecorderFeed(): void {
|
|
1957
|
+
const agent = this.#advisorAgent;
|
|
1958
|
+
const recorder = this.#advisorTranscriptRecorder;
|
|
1959
|
+
if (!agent || !recorder) return;
|
|
1960
|
+
this.#advisorAgentUnsubscribe = agent.subscribe(event => {
|
|
1961
|
+
if (event.type === "message_end") recorder.record(event.message);
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1885
1965
|
async #promoteAdvisorContextModel(currentModel: Model): Promise<boolean> {
|
|
1886
1966
|
const promotionSettings = this.settings.getGroup("contextPromotion");
|
|
1887
1967
|
if (!promotionSettings.enabled) return false;
|
|
@@ -2073,6 +2153,36 @@ export class AgentSession {
|
|
|
2073
2153
|
return undefined;
|
|
2074
2154
|
}
|
|
2075
2155
|
|
|
2156
|
+
/**
|
|
2157
|
+
* The per-turn tool-choice directive for the agent loop's `getToolChoice`. Priority:
|
|
2158
|
+
* 1. a HARD forced choice from the queue (genuine forces: user-force, eager-todo, …) —
|
|
2159
|
+
* consuming, unchanged from `nextToolChoice`;
|
|
2160
|
+
* 2. else, when a non-forcing preview is pending, a {@link SoftToolRequirement} — a
|
|
2161
|
+
* PEEK (advances/pops nothing), so the agent-loop injects the reminder once per head
|
|
2162
|
+
* and escalates to a forced `resolve` only if the model declines. A compliant turn
|
|
2163
|
+
* pays ZERO tool_choice change (no prompt-cache messages-cache invalidation);
|
|
2164
|
+
* 3. else undefined.
|
|
2165
|
+
*/
|
|
2166
|
+
nextToolChoiceDirective(): ToolChoiceDirective | undefined {
|
|
2167
|
+
const hard = this.nextToolChoice();
|
|
2168
|
+
if (hard !== undefined) return hard;
|
|
2169
|
+
const head = this.#toolChoiceQueue.peekPendingHead();
|
|
2170
|
+
if (head !== undefined) {
|
|
2171
|
+
return {
|
|
2172
|
+
soft: true,
|
|
2173
|
+
id: head.id,
|
|
2174
|
+
toolName: "resolve",
|
|
2175
|
+
reminder: [buildResolveReminderMessage(head.sourceToolName)],
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
return undefined;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/** Peek the head non-forcing pending preview invoker, for the `resolve` tool's dispatch. */
|
|
2182
|
+
peekPendingInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined {
|
|
2183
|
+
return this.#toolChoiceQueue.peekPendingInvoker();
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2076
2186
|
/**
|
|
2077
2187
|
* Force the next model call to target a specific active tool, then terminate
|
|
2078
2188
|
* the agent loop. Pushes a two-step sequence [forced, "none"] so the model
|
|
@@ -4060,6 +4170,9 @@ export class AgentSession {
|
|
|
4060
4170
|
await shutdownTinyTitleClient();
|
|
4061
4171
|
this.#releasePowerAssertion();
|
|
4062
4172
|
await this.sessionManager.close();
|
|
4173
|
+
// beginDispose() stopped the advisor and captured its recorder close; await
|
|
4174
|
+
// it so the final advisor turn is flushed before the process may exit.
|
|
4175
|
+
await this.#advisorRecorderClosed;
|
|
4063
4176
|
this.#closeAllProviderSessions("dispose");
|
|
4064
4177
|
// Disconnect the MCP manager this session OWNS so its stdio servers are
|
|
4065
4178
|
// not orphaned at exit. Best-effort: a failure here must never throw out
|
|
@@ -4797,7 +4910,7 @@ export class AgentSession {
|
|
|
4797
4910
|
* cache per-tool strings without preserving this property.
|
|
4798
4911
|
*
|
|
4799
4912
|
* Inputs NOT covered: tool input schemas; memory instructions read from disk;
|
|
4800
|
-
* and SDK-init-time closure constants in `sdk.ts` (`
|
|
4913
|
+
* and SDK-init-time closure constants in `sdk.ts` (`inlineToolDescriptors`,
|
|
4801
4914
|
* `eagerTasks`, `intentField`, `mcpDiscoveryEnabled`, `secretsEnabled`). The
|
|
4802
4915
|
* closure-captured ones cannot change at runtime regardless of skip behavior.
|
|
4803
4916
|
* For everything else, callers must explicitly call `refreshBaseSystemPrompt()`
|
|
@@ -6597,6 +6710,14 @@ export class AgentSession {
|
|
|
6597
6710
|
this.#closeAllProviderSessions("new session");
|
|
6598
6711
|
this.agent.reset();
|
|
6599
6712
|
if (options?.drop && previousSessionFile) {
|
|
6713
|
+
// Detach the advisor recorder feed and drain its writer BEFORE deleting the
|
|
6714
|
+
// old artifacts dir: `await this.abort()` only stops the primary, so a still-
|
|
6715
|
+
// running advisor turn could otherwise finish, emit `message_end`, and recreate
|
|
6716
|
+
// `<old>/__advisor.jsonl`. #resetAdvisorSessionState (after newSession) re-primes
|
|
6717
|
+
// the advisor and re-attaches the feed at the new session's path.
|
|
6718
|
+
this.#advisorAgentUnsubscribe?.();
|
|
6719
|
+
this.#advisorAgentUnsubscribe = undefined;
|
|
6720
|
+
if (this.#advisorTranscriptRecorder) await this.#advisorTranscriptRecorder.close();
|
|
6600
6721
|
try {
|
|
6601
6722
|
await this.sessionManager.dropSession(previousSessionFile);
|
|
6602
6723
|
} catch (err) {
|
|
@@ -7237,11 +7358,16 @@ export class AgentSession {
|
|
|
7237
7358
|
|
|
7238
7359
|
async #pruneToolOutputs(): Promise<{ prunedCount: number; tokensSaved: number } | undefined> {
|
|
7239
7360
|
const branchEntries = this.sessionManager.getBranch();
|
|
7361
|
+
const keepBoundaryId = getLatestCompactionEntry(branchEntries)?.firstKeptEntryId;
|
|
7240
7362
|
const result = pruneToolOutputs(
|
|
7241
7363
|
branchEntries,
|
|
7242
7364
|
this.#withPlanProtection({
|
|
7243
7365
|
...DEFAULT_PRUNE_CONFIG,
|
|
7244
7366
|
pruneUseless: this.settings.getGroup("compaction").dropUseless,
|
|
7367
|
+
// Cache-stable boundary: never re-write the warm, already-sent prefix
|
|
7368
|
+
// (deep stale/age victims) or summarized-away entries every turn.
|
|
7369
|
+
keepBoundaryId,
|
|
7370
|
+
cacheWarmSuffixTokens: PRUNE_CACHE_WARM_SUFFIX_TOKENS,
|
|
7245
7371
|
}),
|
|
7246
7372
|
);
|
|
7247
7373
|
if (result.prunedCount === 0) {
|
|
@@ -7269,12 +7395,17 @@ export class AgentSession {
|
|
|
7269
7395
|
const { supersedeReads, dropUseless } = this.settings.getGroup("compaction");
|
|
7270
7396
|
if (!supersedeReads && !dropUseless) return undefined;
|
|
7271
7397
|
const branchEntries = this.sessionManager.getBranch();
|
|
7398
|
+
const keepBoundaryId = getLatestCompactionEntry(branchEntries)?.firstKeptEntryId;
|
|
7272
7399
|
const result = pruneSupersededToolResults(
|
|
7273
7400
|
branchEntries,
|
|
7274
7401
|
this.#withPlanProtection({
|
|
7275
7402
|
supersedeKey: supersedeReads ? readToolSupersedeKey : undefined,
|
|
7276
7403
|
pruneUseless: dropUseless,
|
|
7277
7404
|
protectedTools: [...DEFAULT_PRUNE_CONFIG.protectedTools],
|
|
7405
|
+
// Never re-write summarized-away entries; only flush the whole sent
|
|
7406
|
+
// region once the cache is genuinely cold (idle exceeds the 1h TTL).
|
|
7407
|
+
keepBoundaryId,
|
|
7408
|
+
idleFlushMs: PRUNE_IDLE_FLUSH_MS,
|
|
7278
7409
|
}),
|
|
7279
7410
|
);
|
|
7280
7411
|
if (result.prunedCount === 0) {
|
|
@@ -7358,8 +7489,14 @@ export class AgentSession {
|
|
|
7358
7489
|
return { mode, toolResultsDropped: 0, blocksDropped: 0, imagesDropped: removed, tokensFreed: 0 };
|
|
7359
7490
|
}
|
|
7360
7491
|
|
|
7361
|
-
const
|
|
7362
|
-
const
|
|
7492
|
+
const branchEntries = this.sessionManager.getBranch();
|
|
7493
|
+
const config = this.#withPlanProtection({
|
|
7494
|
+
...(opts.config ?? AGGRESSIVE_SHAKE_CONFIG),
|
|
7495
|
+
// Skip entries summarized away by the latest compaction — shaking them
|
|
7496
|
+
// only churns persisted history with no prompt/cache effect.
|
|
7497
|
+
keepBoundaryId: getLatestCompactionEntry(branchEntries)?.firstKeptEntryId,
|
|
7498
|
+
});
|
|
7499
|
+
const regions = collectShakeRegions(branchEntries, config);
|
|
7363
7500
|
if (regions.length === 0) {
|
|
7364
7501
|
return { mode, toolResultsDropped: 0, blocksDropped: 0, tokensFreed: 0 };
|
|
7365
7502
|
}
|
|
@@ -7433,6 +7570,15 @@ export class AgentSession {
|
|
|
7433
7570
|
if (this.#compactionAbortController) {
|
|
7434
7571
|
throw new Error("Compaction already in progress");
|
|
7435
7572
|
}
|
|
7573
|
+
// Resolve the `/compact <mode>` subcommand up front so input validation
|
|
7574
|
+
// runs before we disconnect/abort the active agent operation below.
|
|
7575
|
+
const compactMode = options?.mode ? findCompactMode(options.mode) : undefined;
|
|
7576
|
+
// Modes that produce no LLM summary (snapcompact) have nothing to focus.
|
|
7577
|
+
// Reject focus text loudly so programmatic callers don't silently lose
|
|
7578
|
+
// instructions (the slash path pre-validates via parseCompactArgs).
|
|
7579
|
+
if (compactMode?.rejectsFocus && customInstructions) {
|
|
7580
|
+
throw new Error(`/compact ${compactMode.name} does not take focus instructions.`);
|
|
7581
|
+
}
|
|
7436
7582
|
this.#disconnectFromAgent();
|
|
7437
7583
|
await this.abort({ goalReason: "internal" });
|
|
7438
7584
|
const compactionAbortController = new AbortController();
|
|
@@ -7444,8 +7590,26 @@ export class AgentSession {
|
|
|
7444
7590
|
}
|
|
7445
7591
|
|
|
7446
7592
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
7593
|
+
// The `/compact <mode>` override (resolved above) replaces the configured
|
|
7594
|
+
// strategy/remote flags for this one invocation. Merged before
|
|
7595
|
+
// prepareCompaction so the remote gating (preparation.settings.
|
|
7596
|
+
// remoteEnabled/endpoint) and the snapcompact decision below both see it.
|
|
7597
|
+
const effectiveSettings = compactMode
|
|
7598
|
+
? { ...compactionSettings, ...compactMode.overrides }
|
|
7599
|
+
: compactionSettings;
|
|
7600
|
+
if (compactMode?.requiresRemote) {
|
|
7601
|
+
const remoteReady =
|
|
7602
|
+
Boolean(effectiveSettings.remoteEndpoint) || shouldUseOpenAiRemoteCompaction(this.model);
|
|
7603
|
+
if (!remoteReady) {
|
|
7604
|
+
this.emitNotice(
|
|
7605
|
+
"warning",
|
|
7606
|
+
`remote compaction is unavailable for ${this.model.id} (no remote endpoint configured) — using a local summary instead`,
|
|
7607
|
+
"compaction",
|
|
7608
|
+
);
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7447
7611
|
const pathEntries = this.sessionManager.getBranch();
|
|
7448
|
-
const preparation = prepareCompaction(pathEntries,
|
|
7612
|
+
const preparation = prepareCompaction(pathEntries, effectiveSettings);
|
|
7449
7613
|
if (!preparation) {
|
|
7450
7614
|
// Check why we can't compact
|
|
7451
7615
|
const lastEntry = pathEntries[pathEntries.length - 1];
|
|
@@ -7484,7 +7648,7 @@ export class AgentSession {
|
|
|
7484
7648
|
// directed LLM summary; a text-only model cannot read the frames back —
|
|
7485
7649
|
// both take the summarizer path (the latter loudly).
|
|
7486
7650
|
const wantsSnapcompact =
|
|
7487
|
-
compactionPrep.kind !== "fromHook" &&
|
|
7651
|
+
compactionPrep.kind !== "fromHook" && effectiveSettings.strategy === "snapcompact" && !customInstructions;
|
|
7488
7652
|
const snapcompactReady = wantsSnapcompact && this.model.input.includes("image");
|
|
7489
7653
|
if (wantsSnapcompact && !snapcompactReady) {
|
|
7490
7654
|
this.emitNotice(
|
|
@@ -7509,14 +7673,11 @@ export class AgentSession {
|
|
|
7509
7673
|
convertToLlm,
|
|
7510
7674
|
model: this.model,
|
|
7511
7675
|
shape: snapcompact.resolveShape(this.model, this.settings.get("snapcompact.shape")),
|
|
7512
|
-
// Providers with hard image caps (OpenRouter: 8) silently drop
|
|
7513
|
-
// frames past the cap — keep the archive within budget.
|
|
7514
|
-
maxFrames: snapcompact.providerFrameBudget(this.model?.provider),
|
|
7515
7676
|
});
|
|
7516
7677
|
const ctxWindow = this.model?.contextWindow ?? 0;
|
|
7517
7678
|
const budget =
|
|
7518
7679
|
ctxWindow > 0
|
|
7519
|
-
? ctxWindow - effectiveReserveTokens(ctxWindow,
|
|
7680
|
+
? ctxWindow - effectiveReserveTokens(ctxWindow, effectiveSettings)
|
|
7520
7681
|
: Number.POSITIVE_INFINITY;
|
|
7521
7682
|
if (this.#projectSnapcompactContextTokens(preparation, snapcompactResult) > budget) {
|
|
7522
7683
|
logger.warn("Snapcompact still overflows the window; falling back to an LLM summary", {
|
|
@@ -7760,7 +7921,10 @@ export class AgentSession {
|
|
|
7760
7921
|
this.#modelRegistry.resolver(model, this.sessionId),
|
|
7761
7922
|
{
|
|
7762
7923
|
systemPrompt: this.#obfuscateForProvider(this.#baseSystemPrompt),
|
|
7763
|
-
tools: obfuscateProviderTools(
|
|
7924
|
+
tools: obfuscateProviderTools(
|
|
7925
|
+
this.#obfuscator,
|
|
7926
|
+
this.#pruneToolDescriptions ? stripToolDescriptions(this.agent.state.tools) : this.agent.state.tools,
|
|
7927
|
+
),
|
|
7764
7928
|
customInstructions: this.#obfuscateTextForProvider(customInstructions),
|
|
7765
7929
|
convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
|
|
7766
7930
|
initiatorOverride: "agent",
|
|
@@ -9065,14 +9229,15 @@ export class AgentSession {
|
|
|
9065
9229
|
*/
|
|
9066
9230
|
#projectSnapcompactContextTokens(preparation: CompactionPreparation, result: snapcompact.CompactionResult): number {
|
|
9067
9231
|
const archive = snapcompact.getPreservedArchive(result.preserveData);
|
|
9068
|
-
const
|
|
9232
|
+
const blocks = archive ? snapcompact.historyBlocks(archive) : undefined;
|
|
9069
9233
|
const summaryMessage = createCompactionSummaryMessage(
|
|
9070
9234
|
result.summary,
|
|
9071
9235
|
result.tokensBefore,
|
|
9072
9236
|
new Date().toISOString(),
|
|
9073
9237
|
result.shortSummary,
|
|
9074
9238
|
undefined,
|
|
9075
|
-
|
|
9239
|
+
undefined,
|
|
9240
|
+
blocks,
|
|
9076
9241
|
);
|
|
9077
9242
|
let tokens = computeNonMessageTokens(this) + estimateTokens(summaryMessage);
|
|
9078
9243
|
for (const message of preparation.recentMessages) {
|
|
@@ -9300,15 +9465,15 @@ export class AgentSession {
|
|
|
9300
9465
|
let details: unknown;
|
|
9301
9466
|
|
|
9302
9467
|
// Snapcompact runs locally first; if its frame archive plus the kept
|
|
9303
|
-
// history still overflows the model window (frames
|
|
9304
|
-
//
|
|
9305
|
-
// far cheaper — downgrade to context-full and take the
|
|
9468
|
+
// history still overflows the model window (frames default to
|
|
9469
|
+
// MAX_FRAMES_DEFAULT and cost ~FRAME_TOKEN_ESTIMATE each), an LLM
|
|
9470
|
+
// summary is far cheaper — downgrade to context-full and take the
|
|
9471
|
+
// summarizer path.
|
|
9306
9472
|
let snapcompactResult: snapcompact.CompactionResult | undefined;
|
|
9307
9473
|
if (action === "snapcompact" && compactionPrep.kind !== "fromHook") {
|
|
9308
9474
|
snapcompactResult = await snapcompact.compact(preparation, {
|
|
9309
9475
|
convertToLlm,
|
|
9310
9476
|
model: this.model,
|
|
9311
|
-
maxFrames: snapcompact.providerFrameBudget(this.model?.provider),
|
|
9312
9477
|
});
|
|
9313
9478
|
const ctxWindow = this.model?.contextWindow ?? 0;
|
|
9314
9479
|
const budget =
|
|
@@ -12147,6 +12312,7 @@ export class AgentSession {
|
|
|
12147
12312
|
model: this.agent.state.model,
|
|
12148
12313
|
thinkingLevel: this.#thinkingLevel,
|
|
12149
12314
|
tools: this.agent.state.tools,
|
|
12315
|
+
inlineToolDescriptors: this.#pruneToolDescriptions,
|
|
12150
12316
|
});
|
|
12151
12317
|
}
|
|
12152
12318
|
|