open-multi-agent-kit 0.95.0 → 0.95.2
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 +33 -0
- package/README.md +1 -1
- package/dist/approvals/approval-receipt-store.d.ts +18 -0
- package/dist/approvals/approval-receipt-store.d.ts.map +1 -0
- package/dist/approvals/approval-receipt-store.js +169 -0
- package/dist/approvals/approval-receipt-store.js.map +1 -0
- package/dist/approvals/approval-receipt.d.ts +43 -0
- package/dist/approvals/approval-receipt.d.ts.map +1 -0
- package/dist/approvals/approval-receipt.js +208 -0
- package/dist/approvals/approval-receipt.js.map +1 -0
- package/dist/approvals/approval-verifier.d.ts +17 -0
- package/dist/approvals/approval-verifier.d.ts.map +1 -0
- package/dist/approvals/approval-verifier.js +23 -0
- package/dist/approvals/approval-verifier.js.map +1 -0
- package/dist/approvals/plannotator-approval-bridge.d.ts +58 -0
- package/dist/approvals/plannotator-approval-bridge.d.ts.map +1 -0
- package/dist/approvals/plannotator-approval-bridge.js +169 -0
- package/dist/approvals/plannotator-approval-bridge.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/package-doctor-cli.d.ts +13 -0
- package/dist/commands/package-doctor-cli.d.ts.map +1 -0
- package/dist/commands/package-doctor-cli.js +80 -0
- package/dist/commands/package-doctor-cli.js.map +1 -0
- package/dist/commands/router-feedback-cli.d.ts +11 -0
- package/dist/commands/router-feedback-cli.d.ts.map +1 -0
- package/dist/commands/router-feedback-cli.js +99 -0
- package/dist/commands/router-feedback-cli.js.map +1 -0
- package/dist/commands/stats-cli.d.ts +20 -0
- package/dist/commands/stats-cli.d.ts.map +1 -0
- package/dist/commands/stats-cli.js +99 -0
- package/dist/commands/stats-cli.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +4 -1
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +97 -29
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +583 -199
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +8 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts +9 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +17 -2
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/context-budget-governor-v2.d.ts +1 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +1 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt.js +18 -2
- package/dist/core/context-budget-system-prompt.js.map +1 -1
- package/dist/core/context-budget-v2-cache-disk.d.ts +109 -0
- package/dist/core/context-budget-v2-cache-disk.d.ts.map +1 -0
- package/dist/core/context-budget-v2-cache-disk.js +343 -0
- package/dist/core/context-budget-v2-cache-disk.js.map +1 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts +22 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +4 -4
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-cache.d.ts +1 -0
- package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache.js +1 -0
- package/dist/core/context-budget-v2-cache.js.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.js +39 -5
- package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +17 -14
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +1 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +45 -22
- package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +553 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/headroom.d.ts.map +1 -1
- package/dist/core/headroom.js +7 -2
- package/dist/core/headroom.js.map +1 -1
- package/dist/core/mcp/client.d.ts +105 -0
- package/dist/core/mcp/client.d.ts.map +1 -0
- package/dist/core/mcp/client.js +198 -0
- package/dist/core/mcp/client.js.map +1 -0
- package/dist/core/mcp/config.d.ts +23 -0
- package/dist/core/mcp/config.d.ts.map +1 -0
- package/dist/core/mcp/config.js +89 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/index.d.ts +15 -0
- package/dist/core/mcp/index.d.ts.map +1 -0
- package/dist/core/mcp/index.js +15 -0
- package/dist/core/mcp/index.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +84 -0
- package/dist/core/mcp/manager.d.ts.map +1 -0
- package/dist/core/mcp/manager.js +173 -0
- package/dist/core/mcp/manager.js.map +1 -0
- package/dist/core/mcp/protocol.d.ts +59 -0
- package/dist/core/mcp/protocol.d.ts.map +1 -0
- package/dist/core/mcp/protocol.js +103 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/stdio-transport.d.ts +63 -0
- package/dist/core/mcp/stdio-transport.d.ts.map +1 -0
- package/dist/core/mcp/stdio-transport.js +132 -0
- package/dist/core/mcp/stdio-transport.js.map +1 -0
- package/dist/core/mcp/tools.d.ts +47 -0
- package/dist/core/mcp/tools.d.ts.map +1 -0
- package/dist/core/mcp/tools.js +102 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/package-archive.d.ts +3 -0
- package/dist/core/package-archive.d.ts.map +1 -0
- package/dist/core/package-archive.js +161 -0
- package/dist/core/package-archive.js.map +1 -0
- package/dist/core/package-doctor-source-scan.d.ts +13 -0
- package/dist/core/package-doctor-source-scan.d.ts.map +1 -0
- package/dist/core/package-doctor-source-scan.js +124 -0
- package/dist/core/package-doctor-source-scan.js.map +1 -0
- package/dist/core/package-doctor.d.ts +41 -0
- package/dist/core/package-doctor.d.ts.map +1 -0
- package/dist/core/package-doctor.js +145 -0
- package/dist/core/package-doctor.js.map +1 -0
- package/dist/core/package-manager.d.ts +10 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +175 -42
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manifest.d.ts +26 -0
- package/dist/core/package-manifest.d.ts.map +1 -0
- package/dist/core/package-manifest.js +85 -0
- package/dist/core/package-manifest.js.map +1 -0
- package/dist/core/pi-compat.d.ts +5 -0
- package/dist/core/pi-compat.d.ts.map +1 -0
- package/dist/core/pi-compat.js +18 -0
- package/dist/core/pi-compat.js.map +1 -0
- package/dist/core/reasoning-router-bias.d.ts +10 -14
- package/dist/core/reasoning-router-bias.d.ts.map +1 -1
- package/dist/core/reasoning-router-bias.js +22 -15
- package/dist/core/reasoning-router-bias.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts +9 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +20 -5
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/redaction.d.ts +2 -5
- package/dist/core/redaction.d.ts.map +1 -1
- package/dist/core/redaction.js +14 -1
- package/dist/core/redaction.js.map +1 -1
- package/dist/core/repository-learning-scope.d.ts +10 -0
- package/dist/core/repository-learning-scope.d.ts.map +1 -0
- package/dist/core/repository-learning-scope.js +42 -0
- package/dist/core/repository-learning-scope.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +16 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/router-feedback-collector.d.ts +2 -1
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js +2 -2
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/runtime-provenance.d.ts +54 -0
- package/dist/core/runtime-provenance.d.ts.map +1 -0
- package/dist/core/runtime-provenance.js +115 -0
- package/dist/core/runtime-provenance.js.map +1 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +39 -21
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/default-policy.d.ts +4 -5
- package/dist/core/sandbox/default-policy.d.ts.map +1 -1
- package/dist/core/sandbox/default-policy.js +9 -11
- package/dist/core/sandbox/default-policy.js.map +1 -1
- package/dist/core/session-bash-runtime.d.ts +4 -4
- package/dist/core/session-bash-runtime.d.ts.map +1 -1
- package/dist/core/session-bash-runtime.js +6 -5
- package/dist/core/session-bash-runtime.js.map +1 -1
- package/dist/core/session-compaction-service.d.ts.map +1 -1
- package/dist/core/session-compaction-service.js +7 -7
- package/dist/core/session-compaction-service.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +47 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +9 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +10 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +15 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/turn-metrics.d.ts +135 -0
- package/dist/core/turn-metrics.d.ts.map +1 -0
- package/dist/core/turn-metrics.js +261 -0
- package/dist/core/turn-metrics.js.map +1 -0
- package/dist/core/verified-bash-runtime.d.ts.map +1 -1
- package/dist/core/verified-bash-runtime.js +10 -4
- package/dist/core/verified-bash-runtime.js.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.d.ts +7 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.js +39 -0
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +18 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js +2 -2
- package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +40 -12
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts +14 -2
- package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.js +106 -19
- package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +104 -17
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +1 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/compaction.md +25 -3
- package/docs/containerization.md +14 -2
- package/docs/docs.json +8 -0
- package/docs/environment-variables.md +1 -1
- package/docs/extensions.md +75 -16
- package/docs/mcp.md +97 -0
- package/docs/metrics.md +87 -0
- package/docs/packages.md +25 -4
- package/docs/providers.md +1 -1
- package/docs/sdk.md +25 -4
- package/docs/settings.md +8 -7
- package/docs/usage.md +13 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/plannotator-approval-bridge.ts +83 -0
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +21 -11
- package/examples/extensions/subagent/adaptive-agent-runtime.ts +9 -3
- package/examples/extensions/subagent/agents/planner.md +1 -3
- package/examples/extensions/subagent/agents/reviewer.md +1 -3
- package/examples/extensions/subagent/agents/scout.md +1 -3
- package/examples/extensions/subagent/agents/worker.md +0 -1
- package/examples/extensions/subagent/agents.test.ts +9 -0
- package/examples/extensions/subagent/agents.ts +11 -4
- package/examples/extensions/subagent/deadline-budget.test.ts +32 -0
- package/examples/extensions/subagent/deadline-budget.ts +25 -1
- package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +10 -0
- package/examples/extensions/subagent/index.ts +234 -55
- package/examples/extensions/subagent/subagent-execution-policy.test.ts +27 -0
- package/examples/extensions/subagent/subagent-extension-smoke.test.ts +167 -2
- package/examples/extensions/subagent/workflow-graph.test.ts +80 -0
- package/examples/extensions/subagent/workflow-graph.ts +89 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +253 -439
- package/package.json +8 -8
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { createHash, randomUUID } from "node:crypto";
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { basename, dirname } from "node:path";
|
|
17
|
+
import { basename, dirname, join as joinPath } from "node:path";
|
|
18
18
|
import { isDeepStrictEqual } from "node:util";
|
|
19
|
-
import {
|
|
19
|
+
import { getVisionRouteModel, isVisionRouteModel } from "omk-agent-core";
|
|
20
|
+
import { clampThinkingLevel, cleanupSessionResources, deriveContextPromptCacheKey, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "omk-ai";
|
|
21
|
+
import { APP_NAME, VERSION } from "../config.js";
|
|
20
22
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
21
23
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
22
24
|
import { resolvePath } from "../utils/paths.js";
|
|
@@ -31,6 +33,7 @@ import { createCompactionHysteresisConfig, createCompactionHysteresisState, step
|
|
|
31
33
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, generateBranchSummary, prepareCompaction, resolveCompactionModel, } from "./compaction/index.js";
|
|
32
34
|
import { compactionEmitWillRetry } from "./compaction/resume-policy.js";
|
|
33
35
|
import { estimateToolResultReserve, } from "./context-budget-reserved-tokens.js";
|
|
36
|
+
import { createDiskContextBudgetCacheProviderV2, DiskContextBudgetCacheProviderV2, } from "./context-budget-v2-cache-disk.js";
|
|
34
37
|
import { applyContextCacheInvalidation, createContextCacheInvalidationSnapshot, } from "./context-budget-v2-cache-invalidation.js";
|
|
35
38
|
import { createMemoryContextBudgetCacheProviderV2 } from "./context-budget-v2-cache-provider.js";
|
|
36
39
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
@@ -41,21 +44,47 @@ import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
|
41
44
|
import { assertTextChatModelForCompletion } from "./grok-harness.js";
|
|
42
45
|
import { grokPlaybookAppendForProvider } from "./grok-playbook.js";
|
|
43
46
|
import { decideLoadoutAccess } from "./loadout-access-policy.js";
|
|
47
|
+
import { loadMcpServerConfigs } from "./mcp/config.js";
|
|
48
|
+
import { McpManager } from "./mcp/manager.js";
|
|
49
|
+
import { buildRuntimeProvenance } from "./runtime-provenance.js";
|
|
50
|
+
import { TurnMetricsSink } from "./turn-metrics.js";
|
|
51
|
+
/** Values that disable a session-level feature through its environment variable. */
|
|
52
|
+
const DISABLED_ENV_VALUES = new Set(["0", "false", "off", "disable", "disabled"]);
|
|
53
|
+
function isDisabledEnvValue(value) {
|
|
54
|
+
return value !== undefined && DISABLED_ENV_VALUES.has(value.trim().toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
/** Extract the first text block of a tool result, for failure classification only. */
|
|
57
|
+
function firstTextContent(result) {
|
|
58
|
+
const content = result?.content;
|
|
59
|
+
if (!Array.isArray(content))
|
|
60
|
+
return undefined;
|
|
61
|
+
for (const block of content) {
|
|
62
|
+
if (block && typeof block === "object" && block.type === "text") {
|
|
63
|
+
const text = block.text;
|
|
64
|
+
if (typeof text === "string")
|
|
65
|
+
return text;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
import { redactCredentialShapedContent } from "./compaction/transaction.js";
|
|
44
71
|
import { findExactModelReferenceMatch } from "./model-resolver.js";
|
|
45
72
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
46
73
|
import { isContentSafetyStopMessage, isStickySafetyModel, isTransientProviderErrorMessage, pickFailoverCandidate, resolveProviderResilience, stickySafetyBlockMessage, } from "./provider-resilience.js";
|
|
47
|
-
import { getBiasStepsForCell,
|
|
48
|
-
import { classifyTaskV4, resolveThinkingLevelV4WithUncertainty } from "./reasoning-router-v4.js";
|
|
49
|
-
import { redactSensitiveText } from "./redaction.js";
|
|
50
|
-
import {
|
|
74
|
+
import { getBiasStepsForCell, parseRouterBiasSnapshot } from "./reasoning-router-bias.js";
|
|
75
|
+
import { classifyTaskV4, deriveRouterFeedbackFeaturesV4, resolveThinkingLevelV4WithUncertainty, TASK_CLASS_THINKING_LEVELS_V4, } from "./reasoning-router-v4.js";
|
|
76
|
+
import { redactSensitiveText, redactSensitiveTextForced } from "./redaction.js";
|
|
77
|
+
import { getRepositoryRouterLearningPaths } from "./repository-learning-scope.js";
|
|
78
|
+
import { appendRouterFeedbackRecord, ROUTER_FEEDBACK_LEVELS, } from "./router-feedback-collector.js";
|
|
51
79
|
import { RunJournalStore } from "./run-journal-store.js";
|
|
52
80
|
import { SessionBashRuntime } from "./session-bash-runtime.js";
|
|
53
81
|
import { SessionBashService } from "./session-bash-service.js";
|
|
54
82
|
import { SessionCompactionService } from "./session-compaction-service.js";
|
|
55
83
|
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
84
|
+
import { acquireSessionOwnerLeaseSync } from "./session-owner-lease.js";
|
|
56
85
|
import { classifySessionTermination, } from "./session-termination.js";
|
|
57
86
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
58
|
-
import {
|
|
87
|
+
import { buildSystemPromptPlan } from "./system-prompt.js";
|
|
59
88
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
60
89
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
61
90
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
@@ -110,7 +139,13 @@ function pendingToolResultClass(name, args) {
|
|
|
110
139
|
/** Standard thinking levels */
|
|
111
140
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
112
141
|
function normalizeToolNames(toolNames) {
|
|
113
|
-
return [...new Set(toolNames.map((name) => name.trim()).filter((name) => name !== ""))].sort()
|
|
142
|
+
return [...new Set(toolNames.map((name) => name.trim()).filter((name) => name !== ""))].sort((left, right) => {
|
|
143
|
+
if (left < right)
|
|
144
|
+
return -1;
|
|
145
|
+
if (left > right)
|
|
146
|
+
return 1;
|
|
147
|
+
return 0;
|
|
148
|
+
});
|
|
114
149
|
}
|
|
115
150
|
function toolNameSetsEqual(left, right) {
|
|
116
151
|
const normalizedLeft = normalizeToolNames(left);
|
|
@@ -159,7 +194,8 @@ function clonePlainSnapshot(value, ancestors = new WeakSet(), copies = new WeakM
|
|
|
159
194
|
ancestors.add(value);
|
|
160
195
|
try {
|
|
161
196
|
if (Array.isArray(value)) {
|
|
162
|
-
const copy =
|
|
197
|
+
const copy = [];
|
|
198
|
+
copy.length = value.length;
|
|
163
199
|
copies.set(value, copy);
|
|
164
200
|
for (const key of Reflect.ownKeys(value)) {
|
|
165
201
|
if (key === "length")
|
|
@@ -243,6 +279,7 @@ export class AgentSession {
|
|
|
243
279
|
*/
|
|
244
280
|
_reasoningRouterBiasSnapshot = null;
|
|
245
281
|
_reasoningRouterBiasSnapshotLoaded = false;
|
|
282
|
+
_lastAutoRouterDecision;
|
|
246
283
|
/**
|
|
247
284
|
* AdaptOrch advisory bridge (default-off, global-only opt-in via
|
|
248
285
|
* `adaptorchBridge.enabled` in ~/.omk/agent/settings.json). Lazily
|
|
@@ -285,7 +322,12 @@ export class AgentSession {
|
|
|
285
322
|
_resourceLoader;
|
|
286
323
|
_customTools;
|
|
287
324
|
_baseToolDefinitions = new Map();
|
|
325
|
+
_mcpManager;
|
|
326
|
+
_mcpToolNames = new Set();
|
|
327
|
+
_turnMetricsSink;
|
|
328
|
+
_turnMetricsState;
|
|
288
329
|
_cwd;
|
|
330
|
+
_repositoryRouterLearningPaths;
|
|
289
331
|
_extensionRunnerRef;
|
|
290
332
|
_initialActiveToolNames;
|
|
291
333
|
_allowedToolNames;
|
|
@@ -302,6 +344,7 @@ export class AgentSession {
|
|
|
302
344
|
_extensionErrorUnsubscriber;
|
|
303
345
|
// Required durable run/audit chain, persisted next to the session file.
|
|
304
346
|
_runJournalStore;
|
|
347
|
+
_ownedSessionOwnerLease;
|
|
305
348
|
_activeRunId = null;
|
|
306
349
|
_pendingRuntimeTerminationCause;
|
|
307
350
|
_activeRunToolTermination;
|
|
@@ -326,7 +369,13 @@ export class AgentSession {
|
|
|
326
369
|
_toolPromptGuidelines = new Map();
|
|
327
370
|
// Base system prompt (without extension appends) - used to apply fresh appends each turn
|
|
328
371
|
_baseSystemPrompt = "";
|
|
372
|
+
_baseSystemPromptCacheBoundary;
|
|
329
373
|
_baseSystemPromptOptions;
|
|
374
|
+
_promptCacheKey;
|
|
375
|
+
_promptCacheKeyChanges = 0;
|
|
376
|
+
_promptCacheBoundaryBypasses = 0;
|
|
377
|
+
_promptCacheStablePrefixCharacters = 0;
|
|
378
|
+
_promptCacheLastBreakReason;
|
|
330
379
|
constructor(config) {
|
|
331
380
|
this.agent = config.agent;
|
|
332
381
|
this.sessionManager = config.sessionManager;
|
|
@@ -389,31 +438,55 @@ export class AgentSession {
|
|
|
389
438
|
recordCommit: () => this._recordCompactionCommitForHysteresis(),
|
|
390
439
|
});
|
|
391
440
|
const sessionFile = this.sessionManager.getSessionFile();
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
441
|
+
let ownerLease = this.sessionManager.getOwnerLease();
|
|
442
|
+
try {
|
|
443
|
+
if (sessionFile && !ownerLease) {
|
|
444
|
+
ownerLease = acquireSessionOwnerLeaseSync(sessionFile);
|
|
445
|
+
this._ownedSessionOwnerLease = ownerLease;
|
|
446
|
+
this.sessionManager.setOwnerLease(ownerLease);
|
|
447
|
+
}
|
|
448
|
+
this._runJournalStore = RunJournalStore.open({
|
|
449
|
+
...(sessionFile ? { journalPath: `${sessionFile}.runjournal` } : {}),
|
|
450
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
451
|
+
...(ownerLease ? { ownerLease } : {}),
|
|
452
|
+
});
|
|
453
|
+
const startupRecords = this._runJournalStore.records;
|
|
454
|
+
const startupTerminal = startupRecords[startupRecords.length - 1];
|
|
455
|
+
if (startupTerminal?.event === "run_recovered") {
|
|
456
|
+
this._lastTermination = startupTerminal.termination;
|
|
457
|
+
}
|
|
458
|
+
// Always subscribe to agent events for internal handling
|
|
459
|
+
// (session persistence, extensions, auto-compaction, retry logic)
|
|
460
|
+
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
461
|
+
this._installAgentToolHooks();
|
|
462
|
+
// Durable transcript-repair audit for a repair applied on open/resume.
|
|
463
|
+
if (this._transcriptRepair) {
|
|
464
|
+
this._invalidateContextBudgetCache({ type: "transcriptRepair" });
|
|
465
|
+
this._appendRunJournalAudit("transcript_repaired", {
|
|
466
|
+
insertedToolCallIds: [...this._transcriptRepair.insertedToolCallIds],
|
|
467
|
+
reason: this._transcriptRepair.reason,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
this._buildRuntime({
|
|
471
|
+
activeToolNames: this._initialActiveToolNames,
|
|
472
|
+
includeAllExtensionTools: true,
|
|
411
473
|
});
|
|
412
474
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
475
|
+
catch (error) {
|
|
476
|
+
const ownedLease = this._ownedSessionOwnerLease;
|
|
477
|
+
if (ownedLease) {
|
|
478
|
+
try {
|
|
479
|
+
ownedLease.release();
|
|
480
|
+
if (this.sessionManager.getOwnerLease() === ownedLease)
|
|
481
|
+
this.sessionManager.setOwnerLease(undefined);
|
|
482
|
+
this._ownedSessionOwnerLease = undefined;
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
throw new Error("AgentSession initialization cleanup failed");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
417
490
|
}
|
|
418
491
|
/** Model registry for API key resolution and model discovery */
|
|
419
492
|
get modelRegistry() {
|
|
@@ -564,9 +637,14 @@ export class AgentSession {
|
|
|
564
637
|
}
|
|
565
638
|
_classifyRunTermination(runId, event) {
|
|
566
639
|
const timestamp = new Date().toISOString();
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
640
|
+
let assistant;
|
|
641
|
+
for (let index = event.messages.length - 1; index >= 0; index -= 1) {
|
|
642
|
+
const message = event.messages[index];
|
|
643
|
+
if (message?.role === "assistant") {
|
|
644
|
+
assistant = message;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
570
648
|
let cause;
|
|
571
649
|
let message;
|
|
572
650
|
let sideEffects = this._sessionRiskLevel === "elevated" ? "possible" : "none";
|
|
@@ -595,6 +673,12 @@ export class AgentSession {
|
|
|
595
673
|
cause = { area: "completed" };
|
|
596
674
|
message = "Run completed.";
|
|
597
675
|
}
|
|
676
|
+
let provider = this.model?.provider;
|
|
677
|
+
if (assistant?.provider)
|
|
678
|
+
provider = assistant.provider;
|
|
679
|
+
let model = this.model?.id;
|
|
680
|
+
if (assistant?.model)
|
|
681
|
+
model = assistant.model;
|
|
598
682
|
return classifySessionTermination({
|
|
599
683
|
sessionId: this.sessionId,
|
|
600
684
|
runId,
|
|
@@ -603,12 +687,8 @@ export class AgentSession {
|
|
|
603
687
|
message,
|
|
604
688
|
cause,
|
|
605
689
|
sideEffects,
|
|
606
|
-
...(
|
|
607
|
-
|
|
608
|
-
: this.model
|
|
609
|
-
? { provider: this.model.provider }
|
|
610
|
-
: {}),
|
|
611
|
-
...(assistant?.model ? { model: assistant.model } : this.model ? { model: this.model.id } : {}),
|
|
690
|
+
...(provider ? { provider } : {}),
|
|
691
|
+
...(model ? { model } : {}),
|
|
612
692
|
...(toolCallId ? { toolCallId } : {}),
|
|
613
693
|
...(toolName ? { toolName } : {}),
|
|
614
694
|
});
|
|
@@ -637,11 +717,13 @@ export class AgentSession {
|
|
|
637
717
|
const cause = this._runtimeFailureCause(error);
|
|
638
718
|
const runId = this._activeRunId ?? `runtime-${randomUUID()}`;
|
|
639
719
|
const timestamp = new Date().toISOString();
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
720
|
+
let message = "The AgentSession runtime failed before completing the run.";
|
|
721
|
+
if (cause.area === "persistence") {
|
|
722
|
+
message = "A required runtime persistence operation failed.";
|
|
723
|
+
}
|
|
724
|
+
else if (cause.area === "compaction") {
|
|
725
|
+
message = "Runtime compaction failed.";
|
|
726
|
+
}
|
|
645
727
|
let termination = classifySessionTermination({
|
|
646
728
|
sessionId: this.sessionId,
|
|
647
729
|
runId,
|
|
@@ -993,9 +1075,13 @@ export class AgentSession {
|
|
|
993
1075
|
? "Finalized message was not found in agent state"
|
|
994
1076
|
: "Finalized message is ambiguous in agent state");
|
|
995
1077
|
}
|
|
1078
|
+
const matchingIndex = matchingIndexes[0];
|
|
1079
|
+
if (matchingIndex === undefined) {
|
|
1080
|
+
throw new Error("Finalized message was not found in agent state");
|
|
1081
|
+
}
|
|
996
1082
|
const finalizedReplacement = createImmutableMessageSnapshot(replacement);
|
|
997
|
-
const nextMessages = messages
|
|
998
|
-
nextMessages[
|
|
1083
|
+
const nextMessages = [...messages];
|
|
1084
|
+
nextMessages[matchingIndex] = finalizedReplacement;
|
|
999
1085
|
this.agent.state.messages = nextMessages;
|
|
1000
1086
|
return finalizedReplacement;
|
|
1001
1087
|
}
|
|
@@ -1009,6 +1095,13 @@ export class AgentSession {
|
|
|
1009
1095
|
await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
|
|
1010
1096
|
}
|
|
1011
1097
|
else if (event.type === "turn_start") {
|
|
1098
|
+
this._turnMetricsState = {
|
|
1099
|
+
startedAtEpochMs: Date.now(),
|
|
1100
|
+
toolCalls: [],
|
|
1101
|
+
toolStarts: new Map(),
|
|
1102
|
+
requestedModel: this.model ? `${this.model.provider}/${this.model.id}` : null,
|
|
1103
|
+
requestedThinking: this.thinkingMode === "auto" ? "auto" : this.thinkingLevel,
|
|
1104
|
+
};
|
|
1012
1105
|
const extensionEvent = {
|
|
1013
1106
|
type: "turn_start",
|
|
1014
1107
|
turnIndex: this._turnIndex,
|
|
@@ -1017,6 +1110,7 @@ export class AgentSession {
|
|
|
1017
1110
|
await this._extensionRunner.emit(extensionEvent);
|
|
1018
1111
|
}
|
|
1019
1112
|
else if (event.type === "turn_end") {
|
|
1113
|
+
this._recordTurnMetrics(event.message);
|
|
1020
1114
|
const extensionEvent = {
|
|
1021
1115
|
type: "turn_end",
|
|
1022
1116
|
turnIndex: this._turnIndex,
|
|
@@ -1053,6 +1147,7 @@ export class AgentSession {
|
|
|
1053
1147
|
}
|
|
1054
1148
|
}
|
|
1055
1149
|
else if (event.type === "tool_execution_start") {
|
|
1150
|
+
this._turnMetricsState?.toolStarts.set(event.toolCallId, Date.now());
|
|
1056
1151
|
const extensionEvent = {
|
|
1057
1152
|
type: "tool_execution_start",
|
|
1058
1153
|
toolCallId: event.toolCallId,
|
|
@@ -1072,6 +1167,7 @@ export class AgentSession {
|
|
|
1072
1167
|
await this._extensionRunner.emit(extensionEvent);
|
|
1073
1168
|
}
|
|
1074
1169
|
else if (event.type === "tool_execution_end") {
|
|
1170
|
+
this._recordToolMetric(event.toolCallId, event.toolName, event.isError === true, event.result);
|
|
1075
1171
|
const extensionEvent = {
|
|
1076
1172
|
type: "tool_execution_end",
|
|
1077
1173
|
toolCallId: event.toolCallId,
|
|
@@ -1082,6 +1178,81 @@ export class AgentSession {
|
|
|
1082
1178
|
await this._extensionRunner.emit(extensionEvent);
|
|
1083
1179
|
}
|
|
1084
1180
|
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Turn metrics sink for this session, created on first use.
|
|
1183
|
+
*
|
|
1184
|
+
* Writes to `<cwd>/.omk/metrics/`. Set `OMK_TURN_METRICS=0` to disable, or
|
|
1185
|
+
* `OMK_TURN_METRICS_DIR` to relocate. Returns `undefined` when disabled, so
|
|
1186
|
+
* every call site is a no-op rather than a branch.
|
|
1187
|
+
*/
|
|
1188
|
+
_metricsSink() {
|
|
1189
|
+
if (isDisabledEnvValue(process.env.OMK_TURN_METRICS))
|
|
1190
|
+
return undefined;
|
|
1191
|
+
if (!this._turnMetricsSink) {
|
|
1192
|
+
const dir = process.env.OMK_TURN_METRICS_DIR ?? joinPath(this._cwd, ".omk", "metrics");
|
|
1193
|
+
this._turnMetricsSink = new TurnMetricsSink({ dir });
|
|
1194
|
+
}
|
|
1195
|
+
return this._turnMetricsSink;
|
|
1196
|
+
}
|
|
1197
|
+
_recordToolMetric(toolCallId, toolName, isError, result) {
|
|
1198
|
+
const state = this._turnMetricsState;
|
|
1199
|
+
if (!state)
|
|
1200
|
+
return;
|
|
1201
|
+
const startedAt = state.toolStarts.get(toolCallId);
|
|
1202
|
+
state.toolStarts.delete(toolCallId);
|
|
1203
|
+
state.toolCalls.push({
|
|
1204
|
+
name: toolName,
|
|
1205
|
+
durationMs: startedAt === undefined ? 0 : Date.now() - startedAt,
|
|
1206
|
+
ok: !isError,
|
|
1207
|
+
...(isError ? { error: firstTextContent(result) } : {}),
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
/** Persist one turn. Metrics are advisory: a failure here never affects the turn. */
|
|
1211
|
+
_recordTurnMetrics(message) {
|
|
1212
|
+
const state = this._turnMetricsState;
|
|
1213
|
+
this._turnMetricsState = undefined;
|
|
1214
|
+
if (!state)
|
|
1215
|
+
return;
|
|
1216
|
+
const sink = this._metricsSink();
|
|
1217
|
+
if (!sink)
|
|
1218
|
+
return;
|
|
1219
|
+
const assistant = message.role === "assistant" ? message : undefined;
|
|
1220
|
+
const usage = assistant?.usage;
|
|
1221
|
+
const selectedModel = this.model ? `${this.model.provider}/${this.model.id}` : null;
|
|
1222
|
+
const runtimeProvenance = buildRuntimeProvenance({
|
|
1223
|
+
requestedModel: state.requestedModel,
|
|
1224
|
+
selectedModel,
|
|
1225
|
+
responseModel: assistant?.responseModel ?? (assistant ? `${assistant.provider}/${assistant.model}` : null),
|
|
1226
|
+
requestedThinking: state.requestedThinking,
|
|
1227
|
+
effectiveThinking: this.thinkingLevel,
|
|
1228
|
+
source: "session",
|
|
1229
|
+
});
|
|
1230
|
+
try {
|
|
1231
|
+
sink.record({
|
|
1232
|
+
sessionId: this.sessionId,
|
|
1233
|
+
turnIndex: this._turnIndex,
|
|
1234
|
+
provider: this.model?.provider,
|
|
1235
|
+
model: this.model?.id,
|
|
1236
|
+
startedAtEpochMs: state.startedAtEpochMs,
|
|
1237
|
+
endedAtEpochMs: Date.now(),
|
|
1238
|
+
stopReason: assistant?.stopReason,
|
|
1239
|
+
runtimeProvenance,
|
|
1240
|
+
usage: usage
|
|
1241
|
+
? {
|
|
1242
|
+
input: usage.input,
|
|
1243
|
+
output: usage.output,
|
|
1244
|
+
cacheRead: usage.cacheRead,
|
|
1245
|
+
cacheWrite: usage.cacheWrite,
|
|
1246
|
+
costUsd: usage.cost.total,
|
|
1247
|
+
}
|
|
1248
|
+
: undefined,
|
|
1249
|
+
toolCalls: state.toolCalls,
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
catch {
|
|
1253
|
+
// Never let observability break a turn.
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1085
1256
|
/**
|
|
1086
1257
|
* Subscribe to agent events.
|
|
1087
1258
|
* Session persistence is handled internally (saves messages on message_end).
|
|
@@ -1123,19 +1294,59 @@ export class AgentSession {
|
|
|
1123
1294
|
*/
|
|
1124
1295
|
dispose() {
|
|
1125
1296
|
try {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1297
|
+
try {
|
|
1298
|
+
this.abortRetry();
|
|
1299
|
+
this.abortCompaction();
|
|
1300
|
+
this.abortBranchSummary();
|
|
1301
|
+
this.abortBash();
|
|
1302
|
+
this.agent.abort();
|
|
1303
|
+
}
|
|
1304
|
+
catch {
|
|
1305
|
+
// Dispose must succeed even if an abort hook throws.
|
|
1306
|
+
}
|
|
1307
|
+
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured extension API or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
|
|
1308
|
+
this._disconnectFromAgent();
|
|
1309
|
+
this._eventListeners = [];
|
|
1310
|
+
if (this._contextBudgetCacheProvider instanceof DiskContextBudgetCacheProviderV2) {
|
|
1311
|
+
// Persist whatever the session rendered; a failed flush is never fatal.
|
|
1312
|
+
this._contextBudgetCacheProvider.close();
|
|
1313
|
+
}
|
|
1314
|
+
this._mcpManager?.close();
|
|
1315
|
+
this._mcpManager = undefined;
|
|
1316
|
+
cleanupSessionResources(this.sessionId);
|
|
1317
|
+
}
|
|
1318
|
+
finally {
|
|
1319
|
+
const ownerLease = this._ownedSessionOwnerLease;
|
|
1320
|
+
if (ownerLease) {
|
|
1321
|
+
ownerLease.release();
|
|
1322
|
+
if (this.sessionManager.getOwnerLease() === ownerLease)
|
|
1323
|
+
this.sessionManager.setOwnerLease(undefined);
|
|
1324
|
+
this._ownedSessionOwnerLease = undefined;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Build the context-budget cache provider for this session.
|
|
1330
|
+
*
|
|
1331
|
+
* Defaults to a workspace-layer provider persisted under `<cwd>/.omk/cache`,
|
|
1332
|
+
* so a second session reuses representations the first one already rendered
|
|
1333
|
+
* (representation keys are content-addressed; see
|
|
1334
|
+
* `context-budget-v2-cache-keys.ts`). Set
|
|
1335
|
+
* `OMK_CONTEXT_GOVERNOR_CACHE=memory` to opt out, or
|
|
1336
|
+
* `OMK_CONTEXT_GOVERNOR_CACHE_DIR` to relocate the snapshot.
|
|
1337
|
+
*/
|
|
1338
|
+
_createContextBudgetCacheProvider() {
|
|
1339
|
+
if (process.env.OMK_CONTEXT_GOVERNOR_CACHE === "memory") {
|
|
1340
|
+
return createMemoryContextBudgetCacheProviderV2("session");
|
|
1341
|
+
}
|
|
1342
|
+
const dir = process.env.OMK_CONTEXT_GOVERNOR_CACHE_DIR ?? joinPath(this._cwd, ".omk", "cache", "context-budget-v2");
|
|
1343
|
+
try {
|
|
1344
|
+
return createDiskContextBudgetCacheProviderV2({ dir, layer: "workspace" });
|
|
1131
1345
|
}
|
|
1132
1346
|
catch {
|
|
1133
|
-
//
|
|
1347
|
+
// An unusable cache directory must not take the session down.
|
|
1348
|
+
return createMemoryContextBudgetCacheProviderV2("session");
|
|
1134
1349
|
}
|
|
1135
|
-
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured extension API or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
|
|
1136
|
-
this._disconnectFromAgent();
|
|
1137
|
-
this._eventListeners = [];
|
|
1138
|
-
cleanupSessionResources(this.sessionId);
|
|
1139
1350
|
}
|
|
1140
1351
|
// =========================================================================
|
|
1141
1352
|
// Read-only State Access
|
|
@@ -1220,6 +1431,9 @@ export class AgentSession {
|
|
|
1220
1431
|
// Rebuild base system prompt with new tool set
|
|
1221
1432
|
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
|
|
1222
1433
|
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
1434
|
+
this.agent.state.systemPromptCacheBoundary = this._baseSystemPromptCacheBoundary;
|
|
1435
|
+
this.agent.state.systemPromptCacheBoundaryBypass = false;
|
|
1436
|
+
this._recordPromptCachePlan("tool-set");
|
|
1223
1437
|
}
|
|
1224
1438
|
/**
|
|
1225
1439
|
* Recompute the Agent's per-name timeout map for the active tool set:
|
|
@@ -1316,7 +1530,7 @@ export class AgentSession {
|
|
|
1316
1530
|
const recent = messages.slice(-3);
|
|
1317
1531
|
for (let i = recent.length - 1; i >= 0; i--) {
|
|
1318
1532
|
const msg = recent[i];
|
|
1319
|
-
if (!("role" in msg) || msg.role !== "user") {
|
|
1533
|
+
if (!msg || !("role" in msg) || msg.role !== "user") {
|
|
1320
1534
|
continue;
|
|
1321
1535
|
}
|
|
1322
1536
|
const content = msg.content;
|
|
@@ -1325,9 +1539,7 @@ export class AgentSession {
|
|
|
1325
1539
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
1326
1540
|
}
|
|
1327
1541
|
if (Array.isArray(content)) {
|
|
1328
|
-
const textParts = content
|
|
1329
|
-
.filter((c) => "type" in c && c.type === "text")
|
|
1330
|
-
.map((c) => c.text);
|
|
1542
|
+
const textParts = content.flatMap((item) => (item.type === "text" ? [item.text] : []));
|
|
1331
1543
|
const joined = textParts.join("\n").trim();
|
|
1332
1544
|
return joined.length > 0 ? joined : undefined;
|
|
1333
1545
|
}
|
|
@@ -1377,7 +1589,7 @@ export class AgentSession {
|
|
|
1377
1589
|
responseReserveTokens =
|
|
1378
1590
|
parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ?? LEGACY_RESPONSE_RESERVE_TOKENS;
|
|
1379
1591
|
}
|
|
1380
|
-
const cacheProvider = this._contextBudgetCacheProvider ??
|
|
1592
|
+
const cacheProvider = this._contextBudgetCacheProvider ?? this._createContextBudgetCacheProvider();
|
|
1381
1593
|
cacheProvider.setInvalidationSnapshot?.(this._contextCacheInvalidationSnapshot);
|
|
1382
1594
|
this._contextBudgetCacheProvider = cacheProvider;
|
|
1383
1595
|
// Enforce floor
|
|
@@ -1411,6 +1623,32 @@ export class AgentSession {
|
|
|
1411
1623
|
const ratio = overrideRatio ?? RESPONSE_RESERVE_RATIO;
|
|
1412
1624
|
return Math.max(Math.floor(contextWindow * ratio), LEGACY_RESPONSE_RESERVE_TOKENS);
|
|
1413
1625
|
}
|
|
1626
|
+
_recordPromptCachePlan(reason) {
|
|
1627
|
+
const boundary = this.agent.state.systemPromptCacheBoundary;
|
|
1628
|
+
const scope = this.model ? `${this.model.provider}/${this.model.id}` : "unknown";
|
|
1629
|
+
const nextKey = deriveContextPromptCacheKey({
|
|
1630
|
+
systemPrompt: this.agent.state.systemPrompt,
|
|
1631
|
+
systemPromptCacheBoundary: boundary,
|
|
1632
|
+
systemPromptCacheBoundaryBypass: this.agent.state.systemPromptCacheBoundaryBypass,
|
|
1633
|
+
messages: [],
|
|
1634
|
+
tools: this.agent.state.tools,
|
|
1635
|
+
}, scope);
|
|
1636
|
+
if (!nextKey) {
|
|
1637
|
+
this._promptCacheBoundaryBypasses += 1;
|
|
1638
|
+
this._promptCacheStablePrefixCharacters = 0;
|
|
1639
|
+
if (this._promptCacheKey !== undefined) {
|
|
1640
|
+
this._promptCacheLastBreakReason = reason;
|
|
1641
|
+
}
|
|
1642
|
+
this._promptCacheKey = undefined;
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (this._promptCacheKey !== undefined && this._promptCacheKey !== nextKey) {
|
|
1646
|
+
this._promptCacheKeyChanges += 1;
|
|
1647
|
+
this._promptCacheLastBreakReason = reason;
|
|
1648
|
+
}
|
|
1649
|
+
this._promptCacheKey = nextKey;
|
|
1650
|
+
this._promptCacheStablePrefixCharacters = boundary ?? 0;
|
|
1651
|
+
}
|
|
1414
1652
|
_rebuildSystemPrompt(toolNames) {
|
|
1415
1653
|
const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
|
|
1416
1654
|
const toolSnippets = {};
|
|
@@ -1446,7 +1684,9 @@ export class AgentSession {
|
|
|
1446
1684
|
promptGuidelines,
|
|
1447
1685
|
contextBudget: this._getContextBudgetOptions(),
|
|
1448
1686
|
};
|
|
1449
|
-
|
|
1687
|
+
const built = buildSystemPromptPlan(this._baseSystemPromptOptions);
|
|
1688
|
+
this._baseSystemPromptCacheBoundary = built.cacheBoundary;
|
|
1689
|
+
return built.prompt;
|
|
1450
1690
|
}
|
|
1451
1691
|
// =========================================================================
|
|
1452
1692
|
// Prompting
|
|
@@ -1625,9 +1865,9 @@ export class AgentSession {
|
|
|
1625
1865
|
...(promptActiveSkillNames.length > 0 ? { activeSkillNames: promptActiveSkillNames } : {}),
|
|
1626
1866
|
...(promptActiveSkillSource ? { activeSkillSource: promptActiveSkillSource } : {}),
|
|
1627
1867
|
};
|
|
1628
|
-
const turnSystemPrompt =
|
|
1868
|
+
const turnSystemPrompt = buildSystemPromptPlan(turnSystemPromptOptions);
|
|
1629
1869
|
// Emit before_agent_start extension event
|
|
1630
|
-
const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, turnSystemPrompt, turnSystemPromptOptions);
|
|
1870
|
+
const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, turnSystemPrompt.prompt, turnSystemPromptOptions);
|
|
1631
1871
|
// Add all custom messages from extensions
|
|
1632
1872
|
if (result?.messages) {
|
|
1633
1873
|
for (const msg of result.messages) {
|
|
@@ -1641,14 +1881,22 @@ export class AgentSession {
|
|
|
1641
1881
|
});
|
|
1642
1882
|
}
|
|
1643
1883
|
}
|
|
1644
|
-
// Apply extension-modified system prompt, or reset to
|
|
1884
|
+
// Apply extension-modified system prompt, or reset to the turn plan.
|
|
1645
1885
|
if (result?.systemPrompt) {
|
|
1886
|
+
const preservesCacheBoundary = result.systemPrompt === turnSystemPrompt.prompt;
|
|
1646
1887
|
this.agent.state.systemPrompt = result.systemPrompt;
|
|
1888
|
+
this.agent.state.systemPromptCacheBoundary = preservesCacheBoundary
|
|
1889
|
+
? turnSystemPrompt.cacheBoundary
|
|
1890
|
+
: undefined;
|
|
1891
|
+
this.agent.state.systemPromptCacheBoundaryBypass = !preservesCacheBoundary;
|
|
1647
1892
|
}
|
|
1648
1893
|
else {
|
|
1649
|
-
// Ensure we're using the
|
|
1650
|
-
this.agent.state.systemPrompt = turnSystemPrompt;
|
|
1894
|
+
// Ensure we're using the turn prompt (in case the previous turn had modifications).
|
|
1895
|
+
this.agent.state.systemPrompt = turnSystemPrompt.prompt;
|
|
1896
|
+
this.agent.state.systemPromptCacheBoundary = turnSystemPrompt.cacheBoundary;
|
|
1897
|
+
this.agent.state.systemPromptCacheBoundaryBypass = false;
|
|
1651
1898
|
}
|
|
1899
|
+
this._recordPromptCachePlan(result?.systemPrompt ? "extension-override" : "turn-plan");
|
|
1652
1900
|
await this._checkProjectedCompaction(messages);
|
|
1653
1901
|
}
|
|
1654
1902
|
catch (error) {
|
|
@@ -2080,6 +2328,18 @@ export class AgentSession {
|
|
|
2080
2328
|
});
|
|
2081
2329
|
}
|
|
2082
2330
|
}
|
|
2331
|
+
/** Apply an explicit user choice and retain it as bounded router feedback. */
|
|
2332
|
+
setUserThinkingLevel(level) {
|
|
2333
|
+
const overridesAuto = this._thinkingMode === "auto";
|
|
2334
|
+
this.setThinkingMode("manual");
|
|
2335
|
+
this.setThinkingLevel(level);
|
|
2336
|
+
if (overridesAuto) {
|
|
2337
|
+
this._recordUserThinkingOverride(this.thinkingLevel);
|
|
2338
|
+
}
|
|
2339
|
+
else {
|
|
2340
|
+
this._lastAutoRouterDecision = undefined;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2083
2343
|
/**
|
|
2084
2344
|
* Set the thinking mode. "auto" resolves a level per turn via the reasoning
|
|
2085
2345
|
* router; "manual" keeps the explicitly selected level. The mode persists for
|
|
@@ -2107,21 +2367,20 @@ export class AgentSession {
|
|
|
2107
2367
|
* context-pressure bucket, routed through the confidence-bearing v4 classifier
|
|
2108
2368
|
* and its uncertainty-aware resolver. No
|
|
2109
2369
|
* `laneType` applies to the main session (always "none"/`undefined`);
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
* adaptorch-bridge.ts). `bias` stays `0` unless BOTH hold: (a) the global,
|
|
2370
|
+
* the default-off Adaptorch bridge is wired but its current transport returns
|
|
2371
|
+
* no advisory hint. `bias` stays `0` unless BOTH hold: (a) the global,
|
|
2113
2372
|
* owner-only `reasoningRouterLearning.enabled` setting is `true` (default
|
|
2114
2373
|
* off; a project-scope `.omk/settings.json` value for this key is never
|
|
2115
2374
|
* consulted -- see settings-manager.ts), and (b) a compiled
|
|
2116
2375
|
* `RouterBiasSnapshot` was found and passed strict validation at the
|
|
2117
|
-
* configured
|
|
2118
|
-
*
|
|
2119
|
-
* enabled,
|
|
2120
|
-
* path/diff/session/model/provider/tool/hook
|
|
2121
|
-
* router-feedback-collector.ts's exact ten-key schema) is
|
|
2122
|
-
*
|
|
2123
|
-
*
|
|
2124
|
-
*
|
|
2376
|
+
* configured path or opaque repository/worktree-scoped default, loaded and
|
|
2377
|
+
* cached ("pinned") at most once per session (see
|
|
2378
|
+
* `_getReasoningRouterBiasSnapshot`). When learning is enabled, one bounded
|
|
2379
|
+
* "accepted" record (no raw prompt/path/diff/session/model/provider/tool/hook
|
|
2380
|
+
* content; see router-feedback-collector.ts's exact ten-key schema) is
|
|
2381
|
+
* appended after every v4 auto-turn. A later explicit user level records one
|
|
2382
|
+
* bounded `s1-override` direction for the same cell. Both remain inert until
|
|
2383
|
+
* a future, separate offline compile step. The
|
|
2125
2384
|
* resolver's own confidence-band/fallback-reason escalation (see
|
|
2126
2385
|
* reasoning-router-v4.ts) still applies on top of the base+lane+bias
|
|
2127
2386
|
* target, so a low-confidence or fallback-decided verdict can still only
|
|
@@ -2129,17 +2388,17 @@ export class AgentSession {
|
|
|
2129
2388
|
*/
|
|
2130
2389
|
_applyAutoThinkingLevelV4(promptText) {
|
|
2131
2390
|
const availableLevels = this.getAvailableThinkingLevels();
|
|
2391
|
+
const features = deriveRouterFeedbackFeaturesV4(promptText);
|
|
2132
2392
|
const verdict = classifyTaskV4({
|
|
2133
2393
|
prompt: promptText,
|
|
2134
2394
|
history: this._taskClassHistory,
|
|
2135
2395
|
pressureBucket: this._computePressureBucket(),
|
|
2136
|
-
});
|
|
2396
|
+
}, undefined, features);
|
|
2137
2397
|
this._taskClassHistory.unshift(verdict.taskClass);
|
|
2138
2398
|
if (this._taskClassHistory.length > 8)
|
|
2139
2399
|
this._taskClassHistory.length = 8;
|
|
2140
2400
|
const learningEnabled = this.settingsManager.getReasoningRouterLearningEnabled();
|
|
2141
2401
|
const snapshot = learningEnabled ? this._getReasoningRouterBiasSnapshot() : null;
|
|
2142
|
-
const features = this._deriveRouterFeedbackFeatures(promptText);
|
|
2143
2402
|
const bias = snapshot === null
|
|
2144
2403
|
? 0
|
|
2145
2404
|
: getBiasStepsForCell(snapshot, {
|
|
@@ -2150,22 +2409,23 @@ export class AgentSession {
|
|
|
2150
2409
|
hadDiff: features.hadDiff,
|
|
2151
2410
|
});
|
|
2152
2411
|
const resolved = resolveThinkingLevelV4WithUncertainty(verdict, availableLevels, undefined, bias, this._getAdaptorchHint(verdict, features));
|
|
2412
|
+
this._lastAutoRouterDecision = undefined;
|
|
2153
2413
|
if (learningEnabled && resolved !== "off") {
|
|
2154
|
-
const
|
|
2414
|
+
const decision = {
|
|
2155
2415
|
routerVersion: "v4",
|
|
2156
2416
|
laneType: "none",
|
|
2157
2417
|
predictedClass: verdict.taskClass,
|
|
2158
2418
|
resolvedLevel: resolved,
|
|
2159
|
-
acceptedLevel: resolved,
|
|
2160
|
-
signal: "s2-accept",
|
|
2161
|
-
outcome: "accepted",
|
|
2162
2419
|
lenBucket: features.lenBucket,
|
|
2163
2420
|
hadFence: features.hadFence,
|
|
2164
2421
|
hadDiff: features.hadDiff,
|
|
2165
2422
|
};
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2423
|
+
this._lastAutoRouterDecision = decision;
|
|
2424
|
+
this._appendRouterFeedback({
|
|
2425
|
+
...decision,
|
|
2426
|
+
acceptedLevel: resolved,
|
|
2427
|
+
signal: "s2-accept",
|
|
2428
|
+
outcome: "accepted",
|
|
2169
2429
|
});
|
|
2170
2430
|
}
|
|
2171
2431
|
const previousLevel = this.agent.state.thinkingLevel;
|
|
@@ -2175,21 +2435,44 @@ export class AgentSession {
|
|
|
2175
2435
|
this.sessionManager.appendThinkingLevelChange(resolved);
|
|
2176
2436
|
this._emit({ type: "thinking_level_changed", level: resolved });
|
|
2177
2437
|
}
|
|
2438
|
+
_getRepositoryRouterLearningPaths() {
|
|
2439
|
+
if (this._repositoryRouterLearningPaths === undefined) {
|
|
2440
|
+
this._repositoryRouterLearningPaths = getRepositoryRouterLearningPaths(this._cwd);
|
|
2441
|
+
}
|
|
2442
|
+
return this._repositoryRouterLearningPaths;
|
|
2443
|
+
}
|
|
2444
|
+
_appendRouterFeedback(record) {
|
|
2445
|
+
appendRouterFeedbackRecord(record, {
|
|
2446
|
+
enabled: this.settingsManager.getReasoningRouterLearningEnabled(),
|
|
2447
|
+
ledgerPath: this.settingsManager.getReasoningRouterLearningFeedbackLedgerPath() ??
|
|
2448
|
+
this._getRepositoryRouterLearningPaths().ledgerPath,
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
_recordUserThinkingOverride(acceptedLevel) {
|
|
2452
|
+
const decision = this._lastAutoRouterDecision;
|
|
2453
|
+
this._lastAutoRouterDecision = undefined;
|
|
2454
|
+
if (!this.settingsManager.getReasoningRouterLearningEnabled() || !decision || acceptedLevel === "off")
|
|
2455
|
+
return;
|
|
2456
|
+
const resolvedIndex = ROUTER_FEEDBACK_LEVELS.indexOf(decision.resolvedLevel);
|
|
2457
|
+
const acceptedIndex = ROUTER_FEEDBACK_LEVELS.indexOf(acceptedLevel);
|
|
2458
|
+
const outcome = acceptedIndex > resolvedIndex ? "up" : acceptedIndex < resolvedIndex ? "down" : "same";
|
|
2459
|
+
this._appendRouterFeedback({
|
|
2460
|
+
...decision,
|
|
2461
|
+
acceptedLevel,
|
|
2462
|
+
signal: "s1-override",
|
|
2463
|
+
outcome,
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2178
2466
|
/**
|
|
2179
|
-
*
|
|
2180
|
-
*
|
|
2181
|
-
* session ("pinned"): the first call attempts the read and caches whatever
|
|
2182
|
-
* it finds (including a `null` miss/failure); every later call in the same
|
|
2183
|
-
* session reuses that cached result without touching disk again. Returns
|
|
2184
|
-
* `null` when no file exists at the configured/default path, the file
|
|
2185
|
-
* cannot be read, or its contents fail `parseRouterBiasSnapshot`'s schema
|
|
2186
|
-
* validation -- never throws.
|
|
2467
|
+
* Strictly load the compiled bias snapshot once per session. The first call
|
|
2468
|
+
* pins either a valid snapshot or a null miss; later calls never touch disk.
|
|
2187
2469
|
*/
|
|
2188
2470
|
_getReasoningRouterBiasSnapshot() {
|
|
2189
2471
|
if (this._reasoningRouterBiasSnapshotLoaded)
|
|
2190
2472
|
return this._reasoningRouterBiasSnapshot;
|
|
2191
2473
|
this._reasoningRouterBiasSnapshotLoaded = true;
|
|
2192
|
-
const path = this.settingsManager.getReasoningRouterLearningBiasSnapshotPath() ??
|
|
2474
|
+
const path = this.settingsManager.getReasoningRouterLearningBiasSnapshotPath() ??
|
|
2475
|
+
this._getRepositoryRouterLearningPaths().biasSnapshotPath;
|
|
2193
2476
|
try {
|
|
2194
2477
|
if (existsSync(path)) {
|
|
2195
2478
|
this._reasoningRouterBiasSnapshot = parseRouterBiasSnapshot(readFileSync(path, "utf-8"));
|
|
@@ -2200,29 +2483,6 @@ export class AgentSession {
|
|
|
2200
2483
|
}
|
|
2201
2484
|
return this._reasoningRouterBiasSnapshot;
|
|
2202
2485
|
}
|
|
2203
|
-
/**
|
|
2204
|
-
* Locally derives the same three bounded, privacy-safe feedback-ledger
|
|
2205
|
-
* features (`lenBucket`, `hadFence`, `hadDiff`) the v4 classifier computes
|
|
2206
|
-
* internally (reasoning-router-v4.ts keeps those helpers file-private, so
|
|
2207
|
-
* they cannot be imported here). Mirrors reasoning-router-v4.ts's
|
|
2208
|
-
* length-bucket, `hasCodeFence`, and `hasDiffMarkers` helpers. Operates on the same trimmed prompt text
|
|
2209
|
-
* the classifier scores; never returns raw prompt content or its exact
|
|
2210
|
-
* length, only the clamped [0,7] bucket and two booleans.
|
|
2211
|
-
*/
|
|
2212
|
-
_deriveRouterFeedbackFeatures(promptText) {
|
|
2213
|
-
const trimmed = promptText.trim();
|
|
2214
|
-
let lenBucket = 0;
|
|
2215
|
-
let remaining = trimmed.length + 1;
|
|
2216
|
-
while (remaining > 1 && lenBucket < 7) {
|
|
2217
|
-
remaining >>= 1;
|
|
2218
|
-
lenBucket++;
|
|
2219
|
-
}
|
|
2220
|
-
const hadFence = trimmed.includes("```");
|
|
2221
|
-
const hadDiff = /^@@[^\n]*@@/m.test(trimmed) ||
|
|
2222
|
-
/^diff --git /m.test(trimmed) ||
|
|
2223
|
-
(/^\+(?!\+)/m.test(trimmed) && /^-(?!-)/m.test(trimmed));
|
|
2224
|
-
return { lenBucket: lenBucket, hadFence, hadDiff };
|
|
2225
|
-
}
|
|
2226
2486
|
/**
|
|
2227
2487
|
* Context-pressure band 0..3 from the projected token estimate over the
|
|
2228
2488
|
* model context window (reuses estimateProjectedContextTokens). 0..<0.5,
|
|
@@ -2298,20 +2558,7 @@ export class AgentSession {
|
|
|
2298
2558
|
// Map confidenceBand -> numeric confidence for the resolver's threshold gate
|
|
2299
2559
|
const confidenceMap = { low: 0.5, medium: 0.75, high: 0.95 };
|
|
2300
2560
|
const confidence = confidenceMap[hint.confidenceBand] ?? 0.5;
|
|
2301
|
-
|
|
2302
|
-
const levelMap = {
|
|
2303
|
-
trivial: "minimal",
|
|
2304
|
-
"simple-edit": "low",
|
|
2305
|
-
"code-gen": "medium",
|
|
2306
|
-
debug: "high",
|
|
2307
|
-
refactor: "high",
|
|
2308
|
-
review: "high",
|
|
2309
|
-
plan: "xhigh",
|
|
2310
|
-
};
|
|
2311
|
-
const level = levelMap[hint.taskClass];
|
|
2312
|
-
if (level === undefined)
|
|
2313
|
-
return null;
|
|
2314
|
-
return { level, confidence };
|
|
2561
|
+
return { level: TASK_CLASS_THINKING_LEVELS_V4[hint.taskClass], confidence };
|
|
2315
2562
|
}
|
|
2316
2563
|
/**
|
|
2317
2564
|
* Cycle to next thinking level.
|
|
@@ -2324,7 +2571,12 @@ export class AgentSession {
|
|
|
2324
2571
|
const currentIndex = levels.indexOf(this.thinkingLevel);
|
|
2325
2572
|
const nextIndex = (currentIndex + 1) % levels.length;
|
|
2326
2573
|
const nextLevel = levels[nextIndex];
|
|
2574
|
+
const overridesAuto = this._thinkingMode === "auto";
|
|
2327
2575
|
this.setThinkingLevel(nextLevel);
|
|
2576
|
+
if (overridesAuto) {
|
|
2577
|
+
this.setThinkingMode("manual");
|
|
2578
|
+
this._recordUserThinkingOverride(nextLevel);
|
|
2579
|
+
}
|
|
2328
2580
|
return nextLevel;
|
|
2329
2581
|
}
|
|
2330
2582
|
/**
|
|
@@ -2340,7 +2592,7 @@ export class AgentSession {
|
|
|
2340
2592
|
* Check if current model supports thinking/reasoning.
|
|
2341
2593
|
*/
|
|
2342
2594
|
supportsThinking() {
|
|
2343
|
-
return
|
|
2595
|
+
return Boolean(this.model?.reasoning);
|
|
2344
2596
|
}
|
|
2345
2597
|
_getThinkingLevelForModelSwitch(explicitLevel) {
|
|
2346
2598
|
if (explicitLevel !== undefined) {
|
|
@@ -2490,8 +2742,8 @@ export class AgentSession {
|
|
|
2490
2742
|
* @param customInstructions Optional instructions for the compaction summary
|
|
2491
2743
|
*/
|
|
2492
2744
|
async compact(customInstructions) {
|
|
2493
|
-
this._disconnectFromAgent();
|
|
2494
2745
|
await this.abort();
|
|
2746
|
+
this._disconnectFromAgent();
|
|
2495
2747
|
this._compactionAbortController = new AbortController();
|
|
2496
2748
|
this._emit({ type: "compaction_start", reason: "manual" });
|
|
2497
2749
|
let committedCompaction = false;
|
|
@@ -2554,7 +2806,7 @@ export class AgentSession {
|
|
|
2554
2806
|
throw new Error("Compaction cancelled");
|
|
2555
2807
|
}
|
|
2556
2808
|
const compactionResult = {
|
|
2557
|
-
summary:
|
|
2809
|
+
summary: redactCredentialShapedContent(redactSensitiveTextForced(summary)),
|
|
2558
2810
|
firstKeptEntryId,
|
|
2559
2811
|
tokensBefore,
|
|
2560
2812
|
details,
|
|
@@ -2582,6 +2834,11 @@ export class AgentSession {
|
|
|
2582
2834
|
const message = error instanceof Error ? error.message : String(error);
|
|
2583
2835
|
const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
|
|
2584
2836
|
const stale = /stale|session changed during compaction|already compacted/i.test(message);
|
|
2837
|
+
let compactionCode = "failed";
|
|
2838
|
+
if (aborted)
|
|
2839
|
+
compactionCode = "aborted";
|
|
2840
|
+
else if (stale)
|
|
2841
|
+
compactionCode = "stale";
|
|
2585
2842
|
const timestamp = new Date().toISOString();
|
|
2586
2843
|
this._publishTermination(classifySessionTermination({
|
|
2587
2844
|
sessionId: this.sessionId,
|
|
@@ -2589,7 +2846,7 @@ export class AgentSession {
|
|
|
2589
2846
|
timestamp,
|
|
2590
2847
|
source: "observed",
|
|
2591
2848
|
message: this._terminationMessage(message, "Manual compaction failed."),
|
|
2592
|
-
cause: { area: "compaction", code:
|
|
2849
|
+
cause: { area: "compaction", code: compactionCode },
|
|
2593
2850
|
sideEffects: committedCompaction ? "confirmed" : "none",
|
|
2594
2851
|
...(this.model ? { provider: this.model.provider, model: this.model.id } : {}),
|
|
2595
2852
|
}));
|
|
@@ -2621,11 +2878,46 @@ export class AgentSession {
|
|
|
2621
2878
|
abortBranchSummary() {
|
|
2622
2879
|
this._branchSummaryAbortController?.abort();
|
|
2623
2880
|
}
|
|
2881
|
+
/** True when the transcript carries image content that forces a vision-route turn. */
|
|
2882
|
+
_transcriptHasImages(messages) {
|
|
2883
|
+
return messages.some((m) => {
|
|
2884
|
+
const content = m.content;
|
|
2885
|
+
if (!Array.isArray(content))
|
|
2886
|
+
return false;
|
|
2887
|
+
return content.some((part) => {
|
|
2888
|
+
return typeof part === "object" && part !== null && part.type === "image";
|
|
2889
|
+
});
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Effective context window for the upcoming turn.
|
|
2894
|
+
* Image-bearing turns with a text-only session model are auto-routed to the
|
|
2895
|
+
* vision model (gpt-5.6-luna, 400K) — that window is the real limit. Text-only
|
|
2896
|
+
* turns keep the session model's (typically much larger) window.
|
|
2897
|
+
*/
|
|
2898
|
+
_effectiveTurnContextWindow(pendingMessages, sessionWindow) {
|
|
2899
|
+
// Vision routing keys off the full transcript, not just the pending turn:
|
|
2900
|
+
// images retained in history keep every subsequent request on the vision model.
|
|
2901
|
+
const hasImages = this._transcriptHasImages(this.agent.state.messages) || this._transcriptHasImages(pendingMessages);
|
|
2902
|
+
const model = this.model;
|
|
2903
|
+
if (!hasImages || !model || (model.input ?? []).includes("image")) {
|
|
2904
|
+
return sessionWindow;
|
|
2905
|
+
}
|
|
2906
|
+
return Math.min(sessionWindow, getVisionRouteModel(model).contextWindow);
|
|
2907
|
+
}
|
|
2624
2908
|
async _checkProjectedCompaction(pendingMessages) {
|
|
2625
2909
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2626
2910
|
if (!settings.enabled || pendingMessages.length === 0)
|
|
2627
2911
|
return false;
|
|
2628
|
-
const
|
|
2912
|
+
const sessionWindow = this.model?.contextWindow ?? 0;
|
|
2913
|
+
if (sessionWindow <= 0)
|
|
2914
|
+
return false;
|
|
2915
|
+
// Image-bearing turns are served by the auto-routed vision model, not the
|
|
2916
|
+
// session model. Its window (not the session model's) is the real limit the
|
|
2917
|
+
// provider enforces, so threshold compaction must use it or the vision
|
|
2918
|
+
// request overflows (context_length_exceeded) before deepseek-based
|
|
2919
|
+
// compaction would fire.
|
|
2920
|
+
const contextWindow = this._effectiveTurnContextWindow(pendingMessages, sessionWindow);
|
|
2629
2921
|
if (contextWindow <= 0)
|
|
2630
2922
|
return false;
|
|
2631
2923
|
const messages = [...this.agent.state.messages, ...pendingMessages];
|
|
@@ -2640,7 +2932,7 @@ export class AgentSession {
|
|
|
2640
2932
|
}
|
|
2641
2933
|
const decision = this._runtimeCompactionDecision(estimate.tokens, contextWindow, settings);
|
|
2642
2934
|
if (decision.compact) {
|
|
2643
|
-
return
|
|
2935
|
+
return this._runThresholdCompaction(decision.emergency);
|
|
2644
2936
|
}
|
|
2645
2937
|
return false;
|
|
2646
2938
|
}
|
|
@@ -2668,6 +2960,13 @@ export class AgentSession {
|
|
|
2668
2960
|
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
2669
2961
|
// shouldn't trigger compaction for the new model.
|
|
2670
2962
|
const sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
|
2963
|
+
// Image-bearing turns are auto-routed to a vision-capable model (gpt-5.6-luna)
|
|
2964
|
+
// while the session model stays text-only (deepseek). An overflow from that
|
|
2965
|
+
// routed model must still count as the session's own overflow: compaction is
|
|
2966
|
+
// the fix, and the raw context_length_exceeded error must never reach the user.
|
|
2967
|
+
const visionRouteOverflow = this.model != null &&
|
|
2968
|
+
!(this.model.input ?? []).includes("image") &&
|
|
2969
|
+
isVisionRouteModel({ provider: assistantMessage.provider, id: assistantMessage.model });
|
|
2671
2970
|
// Skip compaction checks if this assistant message is older than the latest
|
|
2672
2971
|
// compaction boundary. This prevents a stale pre-compaction usage/error
|
|
2673
2972
|
// from retriggering compaction on the first prompt after compaction.
|
|
@@ -2677,7 +2976,7 @@ export class AgentSession {
|
|
|
2677
2976
|
return false;
|
|
2678
2977
|
}
|
|
2679
2978
|
// Case 1: Overflow - LLM returned context overflow error
|
|
2680
|
-
if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
|
|
2979
|
+
if ((sameModel || visionRouteOverflow) && isContextOverflow(assistantMessage, contextWindow)) {
|
|
2681
2980
|
if (this._overflowRecoveryAttempted) {
|
|
2682
2981
|
this._emit({
|
|
2683
2982
|
type: "compaction_end",
|
|
@@ -2696,7 +2995,7 @@ export class AgentSession {
|
|
|
2696
2995
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2697
2996
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2698
2997
|
}
|
|
2699
|
-
return
|
|
2998
|
+
return this._runAutoCompaction("overflow", true);
|
|
2700
2999
|
}
|
|
2701
3000
|
// Case 2: Threshold - context is getting large
|
|
2702
3001
|
// For error messages (no usage data), estimate from last successful response.
|
|
@@ -2723,7 +3022,7 @@ export class AgentSession {
|
|
|
2723
3022
|
}
|
|
2724
3023
|
const decision = this._runtimeCompactionDecision(contextTokens, contextWindow, settings);
|
|
2725
3024
|
if (decision.compact) {
|
|
2726
|
-
return
|
|
3025
|
+
return this._runThresholdCompaction(decision.emergency);
|
|
2727
3026
|
}
|
|
2728
3027
|
return false;
|
|
2729
3028
|
}
|
|
@@ -2837,7 +3136,7 @@ export class AgentSession {
|
|
|
2837
3136
|
return false;
|
|
2838
3137
|
}
|
|
2839
3138
|
const result = {
|
|
2840
|
-
summary:
|
|
3139
|
+
summary: redactCredentialShapedContent(redactSensitiveTextForced(summary)),
|
|
2841
3140
|
firstKeptEntryId,
|
|
2842
3141
|
tokensBefore,
|
|
2843
3142
|
details,
|
|
@@ -2953,6 +3252,9 @@ export class AgentSession {
|
|
|
2953
3252
|
this._resourceLoader.extendResources(extensionPaths);
|
|
2954
3253
|
this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
|
|
2955
3254
|
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
3255
|
+
this.agent.state.systemPromptCacheBoundary = this._baseSystemPromptCacheBoundary;
|
|
3256
|
+
this.agent.state.systemPromptCacheBoundaryBypass = false;
|
|
3257
|
+
this._recordPromptCachePlan("resource-reload");
|
|
2956
3258
|
}
|
|
2957
3259
|
buildExtensionResourcePaths(entries) {
|
|
2958
3260
|
return entries.map((entry) => {
|
|
@@ -3108,6 +3410,54 @@ export class AgentSession {
|
|
|
3108
3410
|
},
|
|
3109
3411
|
});
|
|
3110
3412
|
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Connect configured MCP servers and register their tools for this session.
|
|
3415
|
+
*
|
|
3416
|
+
* Connection is lazy by construction: nothing is spawned until this is called,
|
|
3417
|
+
* and a server that fails to start is reported in the returned status instead
|
|
3418
|
+
* of taking the session down. Calling it twice replaces the previously
|
|
3419
|
+
* attached MCP tools rather than duplicating them.
|
|
3420
|
+
*
|
|
3421
|
+
* Returns per-server status so a caller can surface failures; an empty
|
|
3422
|
+
* configuration returns an empty array without spawning anything.
|
|
3423
|
+
*/
|
|
3424
|
+
async attachMcpServers(options) {
|
|
3425
|
+
const servers = options?.servers ?? loadMcpServerConfigs(this._cwd);
|
|
3426
|
+
this._mcpManager?.close();
|
|
3427
|
+
this._mcpManager = undefined;
|
|
3428
|
+
this._customTools = this._customTools.filter((definition) => !this._mcpToolNames.has(definition.name));
|
|
3429
|
+
this._mcpToolNames = new Set();
|
|
3430
|
+
if (servers.length === 0) {
|
|
3431
|
+
this._refreshToolRegistry();
|
|
3432
|
+
return [];
|
|
3433
|
+
}
|
|
3434
|
+
const manager = new McpManager({
|
|
3435
|
+
servers,
|
|
3436
|
+
cwd: this._cwd,
|
|
3437
|
+
clientInfo: { name: APP_NAME, version: VERSION },
|
|
3438
|
+
callTimeoutMs: options?.callTimeoutMs,
|
|
3439
|
+
});
|
|
3440
|
+
this._mcpManager = manager;
|
|
3441
|
+
const definitions = await manager.listToolDefinitions();
|
|
3442
|
+
// A builtin always wins a name collision; MCP must never shadow `bash`.
|
|
3443
|
+
const usable = definitions.filter((definition) => !this._baseToolDefinitions.has(definition.name));
|
|
3444
|
+
this._mcpToolNames = new Set(usable.map((definition) => definition.name));
|
|
3445
|
+
this._customTools = [...this._customTools, ...usable];
|
|
3446
|
+
this._refreshToolRegistry();
|
|
3447
|
+
return manager.status();
|
|
3448
|
+
}
|
|
3449
|
+
/** Status of MCP servers attached to this session. Empty when none were attached. */
|
|
3450
|
+
mcpServerStatus() {
|
|
3451
|
+
return this._mcpManager?.status() ?? [];
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Ping attached MCP servers and return fresh status, so callers (e.g. the
|
|
3455
|
+
* status rail) can keep displaying real connectivity instead of the last
|
|
3456
|
+
* handshake result. Resolves to [] when no manager is attached.
|
|
3457
|
+
*/
|
|
3458
|
+
mcpCheckHealth(options) {
|
|
3459
|
+
return this._mcpManager?.checkHealth(options) ?? Promise.resolve([]);
|
|
3460
|
+
}
|
|
3111
3461
|
_refreshToolRegistry(options) {
|
|
3112
3462
|
const previousRegistryNames = new Set(this._toolRegistry.keys());
|
|
3113
3463
|
const previousActiveToolNames = this.getActiveToolNames();
|
|
@@ -3128,15 +3478,17 @@ export class AgentSession {
|
|
|
3128
3478
|
throw new Error(`loadout extension tool shadows builtin: ${shadowedBuiltins.join(", ")}`);
|
|
3129
3479
|
}
|
|
3130
3480
|
}
|
|
3131
|
-
const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3481
|
+
const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries()).flatMap(([name, definition]) => isAllowedTool(name)
|
|
3482
|
+
? [
|
|
3483
|
+
[
|
|
3484
|
+
name,
|
|
3485
|
+
{
|
|
3486
|
+
definition,
|
|
3487
|
+
sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: "builtin" }),
|
|
3488
|
+
},
|
|
3489
|
+
],
|
|
3490
|
+
]
|
|
3491
|
+
: []));
|
|
3140
3492
|
for (const tool of allCustomTools) {
|
|
3141
3493
|
definitionRegistry.set(tool.definition.name, {
|
|
3142
3494
|
definition: tool.definition,
|
|
@@ -3144,26 +3496,29 @@ export class AgentSession {
|
|
|
3144
3496
|
});
|
|
3145
3497
|
}
|
|
3146
3498
|
this._toolDefinitions = definitionRegistry;
|
|
3147
|
-
this._toolPromptSnippets = new Map(Array.from(definitionRegistry.values())
|
|
3148
|
-
.map(({ definition }) => {
|
|
3499
|
+
this._toolPromptSnippets = new Map(Array.from(definitionRegistry.values()).flatMap(({ definition }) => {
|
|
3149
3500
|
const snippet = this._normalizePromptSnippet(definition.promptSnippet);
|
|
3150
|
-
return snippet ? [definition.name, snippet] :
|
|
3151
|
-
})
|
|
3152
|
-
|
|
3153
|
-
this._toolPromptGuidelines = new Map(Array.from(definitionRegistry.values())
|
|
3154
|
-
.map(({ definition }) => {
|
|
3501
|
+
return snippet ? [[definition.name, snippet]] : [];
|
|
3502
|
+
}));
|
|
3503
|
+
this._toolPromptGuidelines = new Map(Array.from(definitionRegistry.values()).flatMap(({ definition }) => {
|
|
3155
3504
|
const guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);
|
|
3156
|
-
return guidelines.length > 0 ? [definition.name, guidelines] :
|
|
3157
|
-
})
|
|
3158
|
-
|
|
3505
|
+
return guidelines.length > 0 ? [[definition.name, guidelines]] : [];
|
|
3506
|
+
}));
|
|
3507
|
+
// Tools contributed by extensions the harness itself loads (`<builtin:*>`),
|
|
3508
|
+
// as opposed to user/project/SDK extensions.
|
|
3509
|
+
const builtinExtensionToolNames = new Set(allCustomTools.flatMap((tool) => (tool.sourceInfo.path.startsWith("<builtin:") ? [tool.definition.name] : [])));
|
|
3159
3510
|
const runner = this._extensionRunner;
|
|
3160
3511
|
const wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);
|
|
3161
|
-
const wrappedBuiltInTools = wrapRegisteredTools(Array.from(this._baseToolDefinitions.values())
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3512
|
+
const wrappedBuiltInTools = wrapRegisteredTools(Array.from(this._baseToolDefinitions.values()).flatMap((definition) => isAllowedTool(definition.name)
|
|
3513
|
+
? [
|
|
3514
|
+
{
|
|
3515
|
+
definition,
|
|
3516
|
+
sourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, {
|
|
3517
|
+
source: "builtin",
|
|
3518
|
+
}),
|
|
3519
|
+
},
|
|
3520
|
+
]
|
|
3521
|
+
: []), runner);
|
|
3167
3522
|
const toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));
|
|
3168
3523
|
for (const tool of wrappedExtensionTools) {
|
|
3169
3524
|
toolRegistry.set(tool.name, tool);
|
|
@@ -3186,7 +3541,14 @@ export class AgentSession {
|
|
|
3186
3541
|
}
|
|
3187
3542
|
}
|
|
3188
3543
|
else if (options?.includeAllExtensionTools) {
|
|
3544
|
+
// "Keep extension tools on" is about the *user's* extensions. Tools shipped
|
|
3545
|
+
// by a built-in extension (e.g. update_todo) are part of the harness's own
|
|
3546
|
+
// tool surface, so they follow the built-in policy: when the caller asked
|
|
3547
|
+
// for no built-in tools, they stay off too.
|
|
3548
|
+
const builtinToolsDisabled = options.activeToolNames?.length === 0;
|
|
3189
3549
|
for (const tool of wrappedExtensionTools) {
|
|
3550
|
+
if (builtinToolsDisabled && builtinExtensionToolNames.has(tool.name))
|
|
3551
|
+
continue;
|
|
3190
3552
|
nextActiveToolNames.push(tool.name);
|
|
3191
3553
|
}
|
|
3192
3554
|
}
|
|
@@ -3217,12 +3579,14 @@ export class AgentSession {
|
|
|
3217
3579
|
canWritePath: (path) => loadoutAccessGuard({ operation: "write", toolName: "write", path }).allowed,
|
|
3218
3580
|
}
|
|
3219
3581
|
: {};
|
|
3220
|
-
const baseToolDefinitions =
|
|
3221
|
-
|
|
3222
|
-
name,
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3582
|
+
const baseToolDefinitions = (() => {
|
|
3583
|
+
if (this._baseToolsOverride) {
|
|
3584
|
+
return Object.fromEntries(Object.entries(this._baseToolsOverride).map(([name, tool]) => [
|
|
3585
|
+
name,
|
|
3586
|
+
createToolDefinitionFromAgentTool(tool),
|
|
3587
|
+
]));
|
|
3588
|
+
}
|
|
3589
|
+
return createAllToolDefinitions(this._cwd, {
|
|
3226
3590
|
read: { autoResizeImages, ...loadoutReadOptions },
|
|
3227
3591
|
bash: {
|
|
3228
3592
|
commandPrefix: shellCommandPrefix,
|
|
@@ -3255,6 +3619,7 @@ export class AgentSession {
|
|
|
3255
3619
|
edit: loadoutWriteOptions,
|
|
3256
3620
|
write: loadoutWriteOptions,
|
|
3257
3621
|
});
|
|
3622
|
+
})();
|
|
3258
3623
|
this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [name, tool]));
|
|
3259
3624
|
const extensionsResult = this._resourceLoader.getExtensions();
|
|
3260
3625
|
if (options.flagValues) {
|
|
@@ -3363,7 +3728,9 @@ export class AgentSession {
|
|
|
3363
3728
|
return;
|
|
3364
3729
|
const pick = pickFailoverCandidate(resilience.failoverCandidates, { provider: current.provider, id: current.id }, (c) => {
|
|
3365
3730
|
const next = this._modelRegistry.find(c.provider, c.id);
|
|
3366
|
-
|
|
3731
|
+
if (!next)
|
|
3732
|
+
return false;
|
|
3733
|
+
return this._modelRegistry.hasConfiguredAuth(next);
|
|
3367
3734
|
});
|
|
3368
3735
|
if (!pick) {
|
|
3369
3736
|
throw new Error(stickySafetyBlockMessage(current.id, current.provider));
|
|
@@ -3398,7 +3765,9 @@ export class AgentSession {
|
|
|
3398
3765
|
if (this._refusedModels.has(`${c.provider}/${c.id}`))
|
|
3399
3766
|
return false;
|
|
3400
3767
|
const next = this._modelRegistry.find(c.provider, c.id);
|
|
3401
|
-
|
|
3768
|
+
if (!next)
|
|
3769
|
+
return false;
|
|
3770
|
+
return this._modelRegistry.hasConfiguredAuth(next);
|
|
3402
3771
|
});
|
|
3403
3772
|
if (!pick)
|
|
3404
3773
|
return undefined;
|
|
@@ -3623,6 +3992,8 @@ export class AgentSession {
|
|
|
3623
3992
|
let summaryDetails;
|
|
3624
3993
|
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
|
|
3625
3994
|
const model = this.model;
|
|
3995
|
+
if (!model)
|
|
3996
|
+
throw new Error("No model available for summarization");
|
|
3626
3997
|
const { apiKey, headers } = await this._getRequiredRequestAuth(model);
|
|
3627
3998
|
const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
|
|
3628
3999
|
const result = await generateBranchSummary(entriesToSummarize, {
|
|
@@ -3664,13 +4035,18 @@ export class AgentSession {
|
|
|
3664
4035
|
else if (targetEntry.type === "custom_message") {
|
|
3665
4036
|
// Custom message: leaf = parent (null if root), text goes to editor
|
|
3666
4037
|
newLeafId = targetEntry.parentId;
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
4038
|
+
if (typeof targetEntry.content === "string") {
|
|
4039
|
+
editorText = targetEntry.content;
|
|
4040
|
+
}
|
|
4041
|
+
else {
|
|
4042
|
+
editorText = targetEntry.content
|
|
4043
|
+
.flatMap((item) => {
|
|
4044
|
+
if (item.type === "text")
|
|
4045
|
+
return [item.text];
|
|
4046
|
+
return [];
|
|
4047
|
+
})
|
|
4048
|
+
.join("");
|
|
4049
|
+
}
|
|
3674
4050
|
}
|
|
3675
4051
|
else {
|
|
3676
4052
|
// Non-user message: leaf = selected node
|
|
@@ -3741,8 +4117,7 @@ export class AgentSession {
|
|
|
3741
4117
|
return content;
|
|
3742
4118
|
if (Array.isArray(content)) {
|
|
3743
4119
|
return content
|
|
3744
|
-
.
|
|
3745
|
-
.map((c) => c.text)
|
|
4120
|
+
.flatMap((item) => (item.type === "text" && typeof item.text === "string" ? [item.text] : []))
|
|
3746
4121
|
.join("");
|
|
3747
4122
|
}
|
|
3748
4123
|
return "";
|
|
@@ -3772,6 +4147,7 @@ export class AgentSession {
|
|
|
3772
4147
|
totalCost += assistantMsg.usage.cost.total;
|
|
3773
4148
|
}
|
|
3774
4149
|
}
|
|
4150
|
+
const providerEligibleInputTokens = totalInput + totalCacheRead + totalCacheWrite;
|
|
3775
4151
|
return {
|
|
3776
4152
|
sessionFile: this.sessionFile,
|
|
3777
4153
|
sessionId: this.sessionId,
|
|
@@ -3787,6 +4163,14 @@ export class AgentSession {
|
|
|
3787
4163
|
cacheWrite: totalCacheWrite,
|
|
3788
4164
|
total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
|
|
3789
4165
|
},
|
|
4166
|
+
promptCache: {
|
|
4167
|
+
providerEligibleInputTokens,
|
|
4168
|
+
providerHitRate: providerEligibleInputTokens > 0 ? totalCacheRead / providerEligibleInputTokens : 0,
|
|
4169
|
+
keyChanges: this._promptCacheKeyChanges,
|
|
4170
|
+
boundaryBypasses: this._promptCacheBoundaryBypasses,
|
|
4171
|
+
stablePrefixCharacters: this._promptCacheStablePrefixCharacters,
|
|
4172
|
+
...(this._promptCacheLastBreakReason ? { lastBreakReason: this._promptCacheLastBreakReason } : {}),
|
|
4173
|
+
},
|
|
3790
4174
|
cost: totalCost,
|
|
3791
4175
|
contextUsage: this.getContextUsage(),
|
|
3792
4176
|
};
|
|
@@ -3845,7 +4229,7 @@ export class AgentSession {
|
|
|
3845
4229
|
theme,
|
|
3846
4230
|
cwd: this.sessionManager.getCwd(),
|
|
3847
4231
|
});
|
|
3848
|
-
return
|
|
4232
|
+
return exportSessionToHtml(this.sessionManager, this.state, {
|
|
3849
4233
|
outputPath,
|
|
3850
4234
|
themeName,
|
|
3851
4235
|
toolRenderer,
|
|
@@ -3891,18 +4275,18 @@ export class AgentSession {
|
|
|
3891
4275
|
* @returns Text content, or undefined if no assistant message exists
|
|
3892
4276
|
*/
|
|
3893
4277
|
getLastAssistantText() {
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
.
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
const msg = m;
|
|
4278
|
+
let lastAssistant;
|
|
4279
|
+
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
4280
|
+
const message = this.messages[index];
|
|
4281
|
+
if (message?.role !== "assistant")
|
|
4282
|
+
continue;
|
|
4283
|
+
const assistant = message;
|
|
3901
4284
|
// Skip aborted messages with no content
|
|
3902
|
-
if (
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
4285
|
+
if (assistant.stopReason === "aborted" && assistant.content.length === 0)
|
|
4286
|
+
continue;
|
|
4287
|
+
lastAssistant = assistant;
|
|
4288
|
+
break;
|
|
4289
|
+
}
|
|
3906
4290
|
if (!lastAssistant)
|
|
3907
4291
|
return undefined;
|
|
3908
4292
|
let text = "";
|