open-multi-agent-kit 0.90.7 → 0.90.9
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 +52 -0
- package/README.md +17 -10
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/doctor-provider-cli.d.ts +33 -0
- package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
- package/dist/commands/doctor-provider-cli.js +215 -0
- package/dist/commands/doctor-provider-cli.js.map +1 -0
- package/dist/commands/doctor-provider-transport.d.ts +35 -0
- package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
- package/dist/commands/doctor-provider-transport.js +185 -0
- package/dist/commands/doctor-provider-transport.js.map +1 -0
- package/dist/commands/doctor-provider.d.ts +121 -7
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +1295 -102
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/session-doctor-cli.d.ts +43 -0
- package/dist/commands/session-doctor-cli.d.ts.map +1 -0
- package/dist/commands/session-doctor-cli.js +494 -0
- package/dist/commands/session-doctor-cli.js.map +1 -0
- package/dist/commands/verify-ci.d.ts +21 -0
- package/dist/commands/verify-ci.d.ts.map +1 -0
- package/dist/commands/verify-ci.js +66 -0
- package/dist/commands/verify-ci.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +3 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +116 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1015 -71
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tool-settings.d.ts +35 -0
- package/dist/core/agent-tool-settings.d.ts.map +1 -0
- package/dist/core/agent-tool-settings.js +155 -0
- package/dist/core/agent-tool-settings.js.map +1 -0
- package/dist/core/atomic-session-file.d.ts +14 -0
- package/dist/core/atomic-session-file.d.ts.map +1 -0
- package/dist/core/atomic-session-file.js +104 -0
- package/dist/core/atomic-session-file.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +6 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +12 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/hysteresis.d.ts +27 -0
- package/dist/core/compaction/hysteresis.d.ts.map +1 -0
- package/dist/core/compaction/hysteresis.js +84 -0
- package/dist/core/compaction/hysteresis.js.map +1 -0
- package/dist/core/compaction/index.d.ts +2 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +2 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts +172 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -0
- package/dist/core/compaction/transaction.js +824 -0
- package/dist/core/compaction/transaction.js.map +1 -0
- package/dist/core/context-budget-governor-v2.d.ts +2 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +2 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-headroom.d.ts.map +1 -1
- package/dist/core/context-budget-headroom.js +5 -1
- package/dist/core/context-budget-headroom.js.map +1 -1
- package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
- package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
- package/dist/core/context-budget-reserved-tokens.js +188 -0
- package/dist/core/context-budget-reserved-tokens.js.map +1 -0
- package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
- package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
- package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
- package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +6 -0
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.d.ts +8 -2
- package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.js +28 -5
- package/dist/core/context-budget-v2-cache-provider.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 +1 -0
- 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 +1 -0
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
- package/dist/core/context-budget-v2-selection.js +9 -0
- package/dist/core/context-budget-v2-selection.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +4 -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/durable-file-directory.d.ts +4 -0
- package/dist/core/durable-file-directory.d.ts.map +1 -0
- package/dist/core/durable-file-directory.js +76 -0
- package/dist/core/durable-file-directory.js.map +1 -0
- package/dist/core/durable-file-identity.d.ts +26 -0
- package/dist/core/durable-file-identity.d.ts.map +1 -0
- package/dist/core/durable-file-identity.js +118 -0
- package/dist/core/durable-file-identity.js.map +1 -0
- package/dist/core/durable-file-io.d.ts +29 -0
- package/dist/core/durable-file-io.d.ts.map +1 -0
- package/dist/core/durable-file-io.js +179 -0
- package/dist/core/durable-file-io.js.map +1 -0
- package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
- package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
- package/dist/core/durable-file-lock-cleanup.js +39 -0
- package/dist/core/durable-file-lock-cleanup.js.map +1 -0
- package/dist/core/durable-file-lock-owner.d.ts +18 -0
- package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
- package/dist/core/durable-file-lock-owner.js +78 -0
- package/dist/core/durable-file-lock-owner.js.map +1 -0
- package/dist/core/durable-file-lock-storage.d.ts +35 -0
- package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
- package/dist/core/durable-file-lock-storage.js +263 -0
- package/dist/core/durable-file-lock-storage.js.map +1 -0
- package/dist/core/durable-file-mode.d.ts +8 -0
- package/dist/core/durable-file-mode.d.ts.map +1 -0
- package/dist/core/durable-file-mode.js +41 -0
- package/dist/core/durable-file-mode.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +12 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +50 -37
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/loop-gates.d.ts.map +1 -1
- package/dist/core/loop-gates.js +25 -1
- package/dist/core/loop-gates.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/pi-package-intake.d.ts +1 -0
- package/dist/core/pi-package-intake.d.ts.map +1 -1
- package/dist/core/pi-package-intake.js +7 -0
- package/dist/core/pi-package-intake.js.map +1 -1
- package/dist/core/redaction.d.ts +8 -0
- package/dist/core/redaction.d.ts.map +1 -0
- package/dist/core/redaction.js +31 -0
- package/dist/core/redaction.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +36 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/run-journal-store.d.ts +53 -0
- package/dist/core/run-journal-store.d.ts.map +1 -0
- package/dist/core/run-journal-store.js +278 -0
- package/dist/core/run-journal-store.js.map +1 -0
- package/dist/core/run-journal.d.ts +172 -0
- package/dist/core/run-journal.d.ts.map +1 -0
- package/dist/core/run-journal.js +1053 -0
- package/dist/core/run-journal.js.map +1 -0
- package/dist/core/sdk.d.ts +7 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +94 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-doctor-plan.d.ts +128 -0
- package/dist/core/session-doctor-plan.d.ts.map +1 -0
- package/dist/core/session-doctor-plan.js +833 -0
- package/dist/core/session-doctor-plan.js.map +1 -0
- package/dist/core/session-file-compaction.d.ts +6 -0
- package/dist/core/session-file-compaction.d.ts.map +1 -0
- package/dist/core/session-file-compaction.js +169 -0
- package/dist/core/session-file-compaction.js.map +1 -0
- package/dist/core/session-file-head.d.ts +10 -0
- package/dist/core/session-file-head.d.ts.map +1 -0
- package/dist/core/session-file-head.js +78 -0
- package/dist/core/session-file-head.js.map +1 -0
- package/dist/core/session-file-persistence.d.ts +30 -0
- package/dist/core/session-file-persistence.d.ts.map +1 -0
- package/dist/core/session-file-persistence.js +106 -0
- package/dist/core/session-file-persistence.js.map +1 -0
- package/dist/core/session-integrity.d.ts +43 -0
- package/dist/core/session-integrity.d.ts.map +1 -0
- package/dist/core/session-integrity.js +420 -0
- package/dist/core/session-integrity.js.map +1 -0
- package/dist/core/session-manager.d.ts +24 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +242 -125
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-owner-lease.d.ts +22 -0
- package/dist/core/session-owner-lease.d.ts.map +1 -0
- package/dist/core/session-owner-lease.js +176 -0
- package/dist/core/session-owner-lease.js.map +1 -0
- package/dist/core/session-path-policy.d.ts +132 -0
- package/dist/core/session-path-policy.d.ts.map +1 -0
- package/dist/core/session-path-policy.js +780 -0
- package/dist/core/session-path-policy.js.map +1 -0
- package/dist/core/session-repair-plan.d.ts +49 -0
- package/dist/core/session-repair-plan.d.ts.map +1 -0
- package/dist/core/session-repair-plan.js +202 -0
- package/dist/core/session-repair-plan.js.map +1 -0
- package/dist/core/session-termination.d.ts +100 -0
- package/dist/core/session-termination.d.ts.map +1 -0
- package/dist/core/session-termination.js +403 -0
- package/dist/core/session-termination.js.map +1 -0
- package/dist/core/settings-manager.d.ts +32 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +52 -5
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/verified-bash-adapter.d.ts +36 -0
- package/dist/core/verified-bash-adapter.d.ts.map +1 -0
- package/dist/core/verified-bash-adapter.js +90 -0
- package/dist/core/verified-bash-adapter.js.map +1 -0
- package/dist/guardrails/command-redaction.d.ts +32 -0
- package/dist/guardrails/command-redaction.d.ts.map +1 -0
- package/dist/guardrails/command-redaction.js +776 -0
- package/dist/guardrails/command-redaction.js.map +1 -0
- package/dist/guardrails/evidence-attestation.d.ts +31 -0
- package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
- package/dist/guardrails/evidence-attestation.js +178 -0
- package/dist/guardrails/evidence-attestation.js.map +1 -0
- package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
- package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
- package/dist/guardrails/evidence-receipt-store.js +446 -0
- package/dist/guardrails/evidence-receipt-store.js.map +1 -0
- package/dist/guardrails/evidence-receipt.d.ts +58 -0
- package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
- package/dist/guardrails/evidence-receipt.js +389 -0
- package/dist/guardrails/evidence-receipt.js.map +1 -0
- package/dist/guardrails/evidence-system.d.ts +40 -9
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +379 -83
- package/dist/guardrails/evidence-system.js.map +1 -1
- package/dist/guardrails/replay-ledger-io.d.ts +6 -0
- package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-io.js +42 -0
- package/dist/guardrails/replay-ledger-io.js.map +1 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
- package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
- package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-lock.js +135 -0
- package/dist/guardrails/replay-ledger-lock.js.map +1 -0
- package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
- package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
- package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
- package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
- package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-quarantine.js +19 -0
- package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
- package/dist/guardrails/replay-ledger-store.d.ts +32 -0
- package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-store.js +211 -0
- package/dist/guardrails/replay-ledger-store.js.map +1 -0
- package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
- package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
- package/dist/guardrails/shell-command-name-glob.js +233 -0
- package/dist/guardrails/shell-command-name-glob.js.map +1 -0
- package/dist/guardrails/shell-command-option.d.ts +7 -0
- package/dist/guardrails/shell-command-option.d.ts.map +1 -0
- package/dist/guardrails/shell-command-option.js +24 -0
- package/dist/guardrails/shell-command-option.js.map +1 -0
- package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
- package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
- package/dist/guardrails/shell-command-tokenizer.js +145 -0
- package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
- package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
- package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
- package/dist/guardrails/shell-command-wrapper.js +42 -0
- package/dist/guardrails/shell-command-wrapper.js.map +1 -0
- package/dist/guardrails/verified-executor.d.ts +65 -0
- package/dist/guardrails/verified-executor.d.ts.map +1 -0
- package/dist/guardrails/verified-executor.js +171 -0
- package/dist/guardrails/verified-executor.js.map +1 -0
- package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
- package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
- package/dist/guardrails/workspace-fingerprint.js +634 -0
- package/dist/guardrails/workspace-fingerprint.js.map +1 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +12 -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 +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +59 -9
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +43 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +23 -5
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/types/evidence.d.ts +215 -1
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/dist/verify-ci.d.ts +3 -0
- package/dist/verify-ci.d.ts.map +1 -0
- package/dist/verify-ci.js +30 -0
- package/dist/verify-ci.js.map +1 -0
- package/docs/compaction.md +44 -4
- package/docs/extensions.md +2 -0
- package/docs/providers.md +6 -0
- package/docs/sdk.md +238 -1
- package/docs/settings.md +67 -4
- package/docs/usage.md +38 -0
- package/examples/extensions/correctness-wall/README.md +6 -6
- package/examples/extensions/correctness-wall/index.ts +37 -11
- package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
- 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/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +4 -4
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -12,19 +12,30 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Modes use this class and add their own I/O layer on top.
|
|
14
14
|
*/
|
|
15
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
15
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
17
|
import { basename, dirname } from "node:path";
|
|
18
|
+
import { isDeepStrictEqual } from "node:util";
|
|
19
|
+
import { repairTranscriptIntegrity, } from "omk-agent-core";
|
|
17
20
|
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "omk-ai";
|
|
18
21
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
19
22
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
20
23
|
import { resolvePath } from "../utils/paths.js";
|
|
24
|
+
import { sanitizeBinaryOutput } from "../utils/shell.js";
|
|
21
25
|
import { sleep } from "../utils/sleep.js";
|
|
26
|
+
import { applyCategoryTimeoutDefaults, resolveAgentToolSettings, resolveToolTimeoutCategory, } from "./agent-tool-settings.js";
|
|
22
27
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
23
28
|
import { parseBangInvocation } from "./bang-skill-invocation.js";
|
|
24
29
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
25
30
|
import { classifyShellCommand } from "./command-safety.js";
|
|
26
|
-
import {
|
|
31
|
+
import { getCompactionHeadroomThreshold } from "./compaction/compaction.js";
|
|
32
|
+
import { createCompactionHysteresisConfig, createCompactionHysteresisState, stepCompactionHysteresis, } from "./compaction/hysteresis.js";
|
|
33
|
+
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, generateBranchSummary, prepareCompaction, resolveCompactionModel, } from "./compaction/index.js";
|
|
27
34
|
import { compactionEmitWillRetry } from "./compaction/resume-policy.js";
|
|
35
|
+
import { createCompactionEnvelope, createCompactionSourceIdentity, createCompactionTransaction, decideCompactionCommit, evaluateCompactionBarrier, validateCompactionEnvelope, } from "./compaction/transaction.js";
|
|
36
|
+
import { estimateToolResultReserve, } from "./context-budget-reserved-tokens.js";
|
|
37
|
+
import { applyContextCacheInvalidation, createContextCacheInvalidationSnapshot, } from "./context-budget-v2-cache-invalidation.js";
|
|
38
|
+
import { createMemoryContextBudgetCacheProviderV2 } from "./context-budget-v2-cache-provider.js";
|
|
28
39
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
29
40
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
30
41
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
@@ -34,12 +45,17 @@ import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
|
34
45
|
import { assertTextChatModelForCompletion } from "./grok-harness.js";
|
|
35
46
|
import { grokPlaybookAppendForProvider } from "./grok-playbook.js";
|
|
36
47
|
import { assertLoadoutAccess, decideLoadoutAccess } from "./loadout-access-policy.js";
|
|
48
|
+
import { findExactModelReferenceMatch } from "./model-resolver.js";
|
|
37
49
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
38
50
|
import { getBiasStepsForCell, getDefaultRouterBiasSnapshotPath, parseRouterBiasSnapshot, } from "./reasoning-router-bias.js";
|
|
39
51
|
import { classifyTaskV4, resolveThinkingLevelV4WithUncertainty } from "./reasoning-router-v4.js";
|
|
52
|
+
import { redactSensitiveText } from "./redaction.js";
|
|
40
53
|
import { appendRouterFeedbackRecord, } from "./router-feedback-collector.js";
|
|
54
|
+
import { RunJournalStore } from "./run-journal-store.js";
|
|
41
55
|
import { detectSandboxBackend } from "./sandbox/backend.js";
|
|
56
|
+
import { inspectSessionIntegrity } from "./session-integrity.js";
|
|
42
57
|
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
58
|
+
import { classifySessionTermination, } from "./session-termination.js";
|
|
43
59
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
44
60
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
45
61
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
@@ -71,6 +87,23 @@ function mergePromptActiveSkillNames(first, second) {
|
|
|
71
87
|
}
|
|
72
88
|
return names;
|
|
73
89
|
}
|
|
90
|
+
const PENDING_TOOL_RESULT_TOKENS = {
|
|
91
|
+
text: 1024,
|
|
92
|
+
image: 4096,
|
|
93
|
+
"large-output": 16_384,
|
|
94
|
+
};
|
|
95
|
+
const TEXT_RESULT_TOOLS = new Set(["edit", "find", "grep", "ls", "read", "write"]);
|
|
96
|
+
const IMAGE_PATH_PATTERN = /\.(?:avif|gif|jpe?g|png|webp)$/iu;
|
|
97
|
+
function pendingToolResultClass(name, args) {
|
|
98
|
+
if (name === "read" &&
|
|
99
|
+
typeof args === "object" &&
|
|
100
|
+
args !== null &&
|
|
101
|
+
typeof Reflect.get(args, "path") === "string" &&
|
|
102
|
+
IMAGE_PATH_PATTERN.test(Reflect.get(args, "path"))) {
|
|
103
|
+
return "image";
|
|
104
|
+
}
|
|
105
|
+
return TEXT_RESULT_TOOLS.has(name) ? "text" : "large-output";
|
|
106
|
+
}
|
|
74
107
|
// ============================================================================
|
|
75
108
|
// Constants
|
|
76
109
|
// ============================================================================
|
|
@@ -96,6 +129,102 @@ function buildSandboxDeniedBashResult(reason) {
|
|
|
96
129
|
truncated: false,
|
|
97
130
|
};
|
|
98
131
|
}
|
|
132
|
+
function snapshotContractError(reason) {
|
|
133
|
+
return new Error(`Finalized message replacement must be a plain serializable snapshot: ${reason}`);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Clone a finalized message replacement into the JSON-compatible snapshot
|
|
137
|
+
* persisted by SessionManager. Undefined remains allowed as ordinary optional
|
|
138
|
+
* message data: JSON omits object properties with undefined values and writes
|
|
139
|
+
* undefined array elements as null.
|
|
140
|
+
*/
|
|
141
|
+
function clonePlainSnapshot(value, ancestors = new WeakSet(), copies = new WeakMap()) {
|
|
142
|
+
if (value === null)
|
|
143
|
+
return value;
|
|
144
|
+
switch (typeof value) {
|
|
145
|
+
case "string":
|
|
146
|
+
case "boolean":
|
|
147
|
+
case "undefined":
|
|
148
|
+
return value;
|
|
149
|
+
case "number":
|
|
150
|
+
if (!Number.isFinite(value)) {
|
|
151
|
+
throw snapshotContractError("non-finite number values are not allowed");
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
case "bigint":
|
|
155
|
+
throw snapshotContractError("bigint values are not allowed");
|
|
156
|
+
case "function":
|
|
157
|
+
case "symbol":
|
|
158
|
+
throw snapshotContractError(`${typeof value} values are not allowed`);
|
|
159
|
+
case "object":
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
throw snapshotContractError(`${typeof value} values are not allowed`);
|
|
163
|
+
}
|
|
164
|
+
if (ancestors.has(value)) {
|
|
165
|
+
throw snapshotContractError("cyclic values are not allowed");
|
|
166
|
+
}
|
|
167
|
+
if (copies.has(value)) {
|
|
168
|
+
return copies.get(value);
|
|
169
|
+
}
|
|
170
|
+
ancestors.add(value);
|
|
171
|
+
try {
|
|
172
|
+
if (Array.isArray(value)) {
|
|
173
|
+
const copy = new Array(value.length);
|
|
174
|
+
copies.set(value, copy);
|
|
175
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
176
|
+
if (key === "length")
|
|
177
|
+
continue;
|
|
178
|
+
if (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
|
|
179
|
+
throw snapshotContractError("arrays may only contain indexed values");
|
|
180
|
+
}
|
|
181
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
182
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
183
|
+
throw snapshotContractError("accessor or non-enumerable properties are not allowed");
|
|
184
|
+
}
|
|
185
|
+
copy[Number(key)] = clonePlainSnapshot(descriptor.value, ancestors, copies);
|
|
186
|
+
}
|
|
187
|
+
return copy;
|
|
188
|
+
}
|
|
189
|
+
const prototype = Object.getPrototypeOf(value);
|
|
190
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
191
|
+
throw snapshotContractError("non-plain objects are not allowed");
|
|
192
|
+
}
|
|
193
|
+
const copy = Object.create(prototype);
|
|
194
|
+
copies.set(value, copy);
|
|
195
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
196
|
+
if (typeof key !== "string") {
|
|
197
|
+
throw snapshotContractError("symbol-keyed properties are not allowed");
|
|
198
|
+
}
|
|
199
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
200
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
201
|
+
throw snapshotContractError("accessor or non-enumerable properties are not allowed");
|
|
202
|
+
}
|
|
203
|
+
Object.defineProperty(copy, key, {
|
|
204
|
+
value: clonePlainSnapshot(descriptor.value, ancestors, copies),
|
|
205
|
+
enumerable: true,
|
|
206
|
+
configurable: true,
|
|
207
|
+
writable: true,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return copy;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
ancestors.delete(value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function freezeSnapshot(value, seen = new WeakSet()) {
|
|
217
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
218
|
+
return value;
|
|
219
|
+
seen.add(value);
|
|
220
|
+
for (const child of Object.values(value)) {
|
|
221
|
+
freezeSnapshot(child, seen);
|
|
222
|
+
}
|
|
223
|
+
return Object.freeze(value);
|
|
224
|
+
}
|
|
225
|
+
function createImmutableMessageSnapshot(message) {
|
|
226
|
+
return freezeSnapshot(clonePlainSnapshot(message));
|
|
227
|
+
}
|
|
99
228
|
// ============================================================================
|
|
100
229
|
// AgentSession Class
|
|
101
230
|
// ============================================================================
|
|
@@ -128,6 +257,7 @@ export class AgentSession {
|
|
|
128
257
|
// Event subscription state
|
|
129
258
|
_unsubscribeAgent;
|
|
130
259
|
_eventListeners = [];
|
|
260
|
+
_messageEndReplacements = new WeakMap();
|
|
131
261
|
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
132
262
|
_steeringMessages = [];
|
|
133
263
|
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
@@ -138,6 +268,8 @@ export class AgentSession {
|
|
|
138
268
|
_compactionAbortController = undefined;
|
|
139
269
|
_autoCompactionAbortController = undefined;
|
|
140
270
|
_overflowRecoveryAttempted = false;
|
|
271
|
+
_thresholdCompactionEmergency = false;
|
|
272
|
+
_compactionHysteresisState = createCompactionHysteresisState();
|
|
141
273
|
// Branch summarization state
|
|
142
274
|
_branchSummaryAbortController = undefined;
|
|
143
275
|
// Retry state
|
|
@@ -169,8 +301,24 @@ export class AgentSession {
|
|
|
169
301
|
_extensionShutdownHandler;
|
|
170
302
|
_extensionErrorListener;
|
|
171
303
|
_extensionErrorUnsubscriber;
|
|
304
|
+
// Required durable run/audit chain, persisted next to the session file.
|
|
305
|
+
_runJournalStore;
|
|
306
|
+
_activeRunId = null;
|
|
307
|
+
_pendingRuntimeTerminationCause;
|
|
308
|
+
_activeRunToolTermination;
|
|
309
|
+
_lastTermination;
|
|
310
|
+
_userAbortRequested = false;
|
|
311
|
+
_replayLedger;
|
|
312
|
+
_replayGoalId;
|
|
313
|
+
_replayLaneId;
|
|
314
|
+
_transcriptRepair;
|
|
315
|
+
_sessionRiskLevel = "normal";
|
|
316
|
+
_workspaceMutationCount = 0;
|
|
172
317
|
// Model registry for API key resolution
|
|
173
318
|
_modelRegistry;
|
|
319
|
+
/** Lazy, in-memory only; never shared across sessions or persisted. */
|
|
320
|
+
_contextBudgetCacheProvider;
|
|
321
|
+
_contextCacheInvalidationSnapshot;
|
|
174
322
|
// Tool registry for extension getTools/setTools
|
|
175
323
|
_toolRegistry = new Map();
|
|
176
324
|
_toolDefinitions = new Map();
|
|
@@ -188,6 +336,13 @@ export class AgentSession {
|
|
|
188
336
|
this._customTools = config.customTools ?? [];
|
|
189
337
|
this._cwd = config.cwd;
|
|
190
338
|
this._modelRegistry = config.modelRegistry;
|
|
339
|
+
const initialModelId = this._contextCacheModelId(this.agent.state.model);
|
|
340
|
+
this._contextCacheInvalidationSnapshot = createContextCacheInvalidationSnapshot({
|
|
341
|
+
forkId: this.sessionManager.getSessionId(),
|
|
342
|
+
worktreeFingerprint: this._contextCacheWorktreeFingerprint(),
|
|
343
|
+
activeModelId: initialModelId,
|
|
344
|
+
compactionModelId: initialModelId,
|
|
345
|
+
});
|
|
191
346
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
|
192
347
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
193
348
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
|
@@ -196,10 +351,34 @@ export class AgentSession {
|
|
|
196
351
|
this._loadoutAccessPolicy = config.loadoutAccessPolicy;
|
|
197
352
|
this._configuredBashSandboxPreflight = config.bashSandboxPreflight;
|
|
198
353
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
354
|
+
this._transcriptRepair = config.transcriptRepair;
|
|
355
|
+
this._replayLedger = config.replayLedger;
|
|
356
|
+
this._replayGoalId = config.replayGoalId ?? config.replayLedger?.getLedger().goalId;
|
|
357
|
+
this._replayLaneId = config.replayLaneId;
|
|
358
|
+
if (this._replayLedger && this._replayGoalId !== this._replayLedger.getLedger().goalId) {
|
|
359
|
+
throw new Error("AgentSession replayGoalId does not match the replay ledger goal id");
|
|
360
|
+
}
|
|
361
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
362
|
+
this._runJournalStore = RunJournalStore.open({
|
|
363
|
+
...(sessionFile ? { journalPath: `${sessionFile}.runjournal` } : {}),
|
|
364
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
365
|
+
});
|
|
366
|
+
const startupTerminal = this._runJournalStore.records.at(-1);
|
|
367
|
+
if (startupTerminal?.event === "run_recovered") {
|
|
368
|
+
this._lastTermination = startupTerminal.termination;
|
|
369
|
+
}
|
|
199
370
|
// Always subscribe to agent events for internal handling
|
|
200
371
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
201
372
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
202
373
|
this._installAgentToolHooks();
|
|
374
|
+
// Durable transcript-repair audit for a repair applied on open/resume.
|
|
375
|
+
if (this._transcriptRepair) {
|
|
376
|
+
this._invalidateContextBudgetCache({ type: "transcriptRepair" });
|
|
377
|
+
this._appendRunJournalAudit("transcript_repaired", {
|
|
378
|
+
insertedToolCallIds: [...this._transcriptRepair.insertedToolCallIds],
|
|
379
|
+
reason: this._transcriptRepair.reason,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
203
382
|
this._buildRuntime({
|
|
204
383
|
activeToolNames: this._initialActiveToolNames,
|
|
205
384
|
includeAllExtensionTools: true,
|
|
@@ -209,6 +388,347 @@ export class AgentSession {
|
|
|
209
388
|
get modelRegistry() {
|
|
210
389
|
return this._modelRegistry;
|
|
211
390
|
}
|
|
391
|
+
/** Transcript repair applied while this session was opened/resumed, if any. */
|
|
392
|
+
get transcriptRepair() {
|
|
393
|
+
return this._transcriptRepair;
|
|
394
|
+
}
|
|
395
|
+
/** Durable lifecycle and audit records appended by this session's run journal. */
|
|
396
|
+
get runJournalRecords() {
|
|
397
|
+
return this._runJournalStore.records;
|
|
398
|
+
}
|
|
399
|
+
/** Most recently observed or inferred termination for this session. */
|
|
400
|
+
get lastTermination() {
|
|
401
|
+
return this._lastTermination;
|
|
402
|
+
}
|
|
403
|
+
/** Exact trailing journal fragment quarantine performed during startup, if any. */
|
|
404
|
+
get runJournalQuarantineReport() {
|
|
405
|
+
return this._runJournalStore.quarantineReport;
|
|
406
|
+
}
|
|
407
|
+
/** Elevated once a late-settling potentially-writing tool may have mutated the workspace. */
|
|
408
|
+
get sessionRiskLevel() {
|
|
409
|
+
return this._sessionRiskLevel;
|
|
410
|
+
}
|
|
411
|
+
/** Monotonic count of workspace mutation/invalidation signals emitted by this session. */
|
|
412
|
+
get workspaceMutationCount() {
|
|
413
|
+
return this._workspaceMutationCount;
|
|
414
|
+
}
|
|
415
|
+
get contextCacheInvalidationSnapshot() {
|
|
416
|
+
return this._contextCacheInvalidationSnapshot;
|
|
417
|
+
}
|
|
418
|
+
_contextCacheModelId(model) {
|
|
419
|
+
return model
|
|
420
|
+
? `model-${createHash("sha256").update(`${model.provider}\0${model.id}`, "utf8").digest("hex")}`
|
|
421
|
+
: "unknown";
|
|
422
|
+
}
|
|
423
|
+
_contextCacheWorktreeFingerprint() {
|
|
424
|
+
return createHash("sha256").update(`${this._cwd}\0${this._workspaceMutationCount}`, "utf8").digest("hex");
|
|
425
|
+
}
|
|
426
|
+
_invalidateContextBudgetCache(event) {
|
|
427
|
+
const result = applyContextCacheInvalidation(this._contextCacheInvalidationSnapshot, event);
|
|
428
|
+
this._contextCacheInvalidationSnapshot = result.snapshot;
|
|
429
|
+
if (result.status === "overflow") {
|
|
430
|
+
this._contextBudgetCacheProvider = undefined;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
this._contextBudgetCacheProvider?.setInvalidationSnapshot?.(result.snapshot);
|
|
434
|
+
}
|
|
435
|
+
_recordEvidenceReceiptInvalidation(customType) {
|
|
436
|
+
if (customType === "evidence_receipt" || customType === "evidence-receipt") {
|
|
437
|
+
this._invalidateContextBudgetCache({ type: "evidenceReceipt" });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
_sessionRevision() {
|
|
441
|
+
return this.sessionManager.getEntries().length;
|
|
442
|
+
}
|
|
443
|
+
_appendReplayEvent(type, payload) {
|
|
444
|
+
if (!this._replayLedger || !this._replayGoalId)
|
|
445
|
+
return;
|
|
446
|
+
this._replayLedger.append({
|
|
447
|
+
type,
|
|
448
|
+
goalId: this._replayGoalId,
|
|
449
|
+
...(this._replayLaneId ? { laneId: this._replayLaneId } : {}),
|
|
450
|
+
payload,
|
|
451
|
+
});
|
|
452
|
+
this._replayLedger.persist();
|
|
453
|
+
}
|
|
454
|
+
/** Append one required durable audit record. Persistence failures propagate. */
|
|
455
|
+
_appendRunJournalAudit(event, details) {
|
|
456
|
+
this._runJournalStore.audit({
|
|
457
|
+
event,
|
|
458
|
+
details,
|
|
459
|
+
sessionRevision: this._sessionRevision(),
|
|
460
|
+
timestamp: new Date().toISOString(),
|
|
461
|
+
});
|
|
462
|
+
this._appendReplayEvent(event, details);
|
|
463
|
+
}
|
|
464
|
+
_terminationMessage(value, fallback) {
|
|
465
|
+
const redacted = redactSensitiveText(value?.trim() || fallback)
|
|
466
|
+
.replace(/\0/g, "")
|
|
467
|
+
.slice(0, 512);
|
|
468
|
+
return redacted || fallback;
|
|
469
|
+
}
|
|
470
|
+
_providerFailureCause(message) {
|
|
471
|
+
const text = message.errorMessage ?? "";
|
|
472
|
+
if (isContextOverflow(message, this.model?.contextWindow ?? 0)) {
|
|
473
|
+
return { area: "provider", code: "context_overflow" };
|
|
474
|
+
}
|
|
475
|
+
if (/auth|unauthori[sz]ed|forbidden|invalid.?api.?key|no api key|401|403|\/login/i.test(text)) {
|
|
476
|
+
return { area: "provider", code: "auth" };
|
|
477
|
+
}
|
|
478
|
+
if (/rate.?limit|too many requests|429|quota|available balance|billing/i.test(text)) {
|
|
479
|
+
return { area: "provider", code: "rate_limit" };
|
|
480
|
+
}
|
|
481
|
+
if (/tool.+timed? out|tool.+timeout/i.test(text))
|
|
482
|
+
return { area: "tool", code: "timeout" };
|
|
483
|
+
if (/tool/i.test(text))
|
|
484
|
+
return { area: "tool", code: "fatal" };
|
|
485
|
+
if (/network|fetch failed|connection|socket|websocket|timed? out|timeout|dns|econn/i.test(text)) {
|
|
486
|
+
return { area: "provider", code: "network" };
|
|
487
|
+
}
|
|
488
|
+
return { area: "provider", code: "protocol" };
|
|
489
|
+
}
|
|
490
|
+
_classifyPreflightCause(message) {
|
|
491
|
+
if (!this.model || /no model|model selected|model is required/i.test(message)) {
|
|
492
|
+
return { area: "configuration", code: "invalid" };
|
|
493
|
+
}
|
|
494
|
+
if (/auth|api key|unauthori[sz]ed|forbidden|401|403|\/login/i.test(message)) {
|
|
495
|
+
return { area: "provider", code: "auth" };
|
|
496
|
+
}
|
|
497
|
+
if (/context.+overflow|context window|too many tokens/i.test(message)) {
|
|
498
|
+
return { area: "provider", code: "context_overflow" };
|
|
499
|
+
}
|
|
500
|
+
if (/duplicate.?result/i.test(message))
|
|
501
|
+
return { area: "transcript", code: "duplicate_result" };
|
|
502
|
+
if (/orphan.?result/i.test(message))
|
|
503
|
+
return { area: "transcript", code: "orphan_result" };
|
|
504
|
+
if (/duplicate.?call/i.test(message))
|
|
505
|
+
return { area: "transcript", code: "duplicate_call_id" };
|
|
506
|
+
if (/transcript|missing.?result/i.test(message))
|
|
507
|
+
return { area: "transcript", code: "missing_result" };
|
|
508
|
+
if (/compaction/i.test(message))
|
|
509
|
+
return { area: "compaction", code: "failed" };
|
|
510
|
+
if (/fsync/i.test(message))
|
|
511
|
+
return { area: "persistence", code: "fsync_failed" };
|
|
512
|
+
if (/lock/i.test(message))
|
|
513
|
+
return { area: "persistence", code: "lock_failed" };
|
|
514
|
+
if (/append|persist|write/i.test(message))
|
|
515
|
+
return { area: "persistence", code: "append_failed" };
|
|
516
|
+
if (/tool/i.test(message))
|
|
517
|
+
return { area: "tool", code: "fatal" };
|
|
518
|
+
return { area: "internal", code: "unclassified" };
|
|
519
|
+
}
|
|
520
|
+
_classifyRunTermination(runId, event) {
|
|
521
|
+
const timestamp = new Date().toISOString();
|
|
522
|
+
const assistant = [...event.messages]
|
|
523
|
+
.reverse()
|
|
524
|
+
.find((message) => message.role === "assistant");
|
|
525
|
+
let cause;
|
|
526
|
+
let message;
|
|
527
|
+
let sideEffects = this._sessionRiskLevel === "elevated" ? "possible" : "none";
|
|
528
|
+
let toolCallId;
|
|
529
|
+
let toolName;
|
|
530
|
+
if (this._activeRunToolTermination) {
|
|
531
|
+
cause = { area: "tool", code: "timeout" };
|
|
532
|
+
message = `Tool ${this._activeRunToolTermination.toolName} timed out.`;
|
|
533
|
+
toolCallId = this._activeRunToolTermination.toolCallId;
|
|
534
|
+
toolName = this._activeRunToolTermination.toolName;
|
|
535
|
+
sideEffects = this._activeRunToolTermination.executionStarted ? "possible" : sideEffects;
|
|
536
|
+
}
|
|
537
|
+
else if (!assistant) {
|
|
538
|
+
cause = { area: "internal", code: "unclassified" };
|
|
539
|
+
message = "Agent run ended without an assistant result.";
|
|
540
|
+
}
|
|
541
|
+
else if (assistant.stopReason === "aborted") {
|
|
542
|
+
cause = this._userAbortRequested ? { area: "user", code: "abort" } : { area: "provider", code: "abort" };
|
|
543
|
+
message = this._terminationMessage(assistant.errorMessage, this._userAbortRequested ? "The user aborted the run." : "The provider aborted the run.");
|
|
544
|
+
}
|
|
545
|
+
else if (assistant.stopReason === "error") {
|
|
546
|
+
cause = this._providerFailureCause(assistant);
|
|
547
|
+
message = this._terminationMessage(assistant.errorMessage, "The provider request failed.");
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
cause = { area: "completed" };
|
|
551
|
+
message = "Run completed.";
|
|
552
|
+
}
|
|
553
|
+
return classifySessionTermination({
|
|
554
|
+
sessionId: this.sessionId,
|
|
555
|
+
runId,
|
|
556
|
+
timestamp,
|
|
557
|
+
source: "observed",
|
|
558
|
+
message,
|
|
559
|
+
cause,
|
|
560
|
+
sideEffects,
|
|
561
|
+
...(assistant?.provider
|
|
562
|
+
? { provider: assistant.provider }
|
|
563
|
+
: this.model
|
|
564
|
+
? { provider: this.model.provider }
|
|
565
|
+
: {}),
|
|
566
|
+
...(assistant?.model ? { model: assistant.model } : this.model ? { model: this.model.id } : {}),
|
|
567
|
+
...(toolCallId ? { toolCallId } : {}),
|
|
568
|
+
...(toolName ? { toolName } : {}),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
_publishTermination(termination) {
|
|
572
|
+
this._lastTermination = termination;
|
|
573
|
+
this._emit({ type: "session_termination", termination });
|
|
574
|
+
}
|
|
575
|
+
_runtimeFailureCause(error) {
|
|
576
|
+
if (this._pendingRuntimeTerminationCause)
|
|
577
|
+
return this._pendingRuntimeTerminationCause;
|
|
578
|
+
const code = typeof error === "object" && error !== null && "code" in error ? Reflect.get(error, "code") : undefined;
|
|
579
|
+
if (typeof code === "string" &&
|
|
580
|
+
new Set(["EACCES", "EDQUOT", "EFBIG", "EIO", "EISDIR", "EMFILE", "ENFILE", "ENOSPC", "EPERM", "EROFS"]).has(code)) {
|
|
581
|
+
return { area: "persistence", code: "append_failed" };
|
|
582
|
+
}
|
|
583
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
+
if (/compaction.+stale|session changed during compaction/i.test(message)) {
|
|
585
|
+
return { area: "compaction", code: "stale" };
|
|
586
|
+
}
|
|
587
|
+
if (/compaction/i.test(message))
|
|
588
|
+
return { area: "compaction", code: "failed" };
|
|
589
|
+
return { area: "internal", code: "unclassified" };
|
|
590
|
+
}
|
|
591
|
+
_publishRuntimeFailure(error) {
|
|
592
|
+
const cause = this._runtimeFailureCause(error);
|
|
593
|
+
const runId = this._activeRunId ?? `runtime-${randomUUID()}`;
|
|
594
|
+
const timestamp = new Date().toISOString();
|
|
595
|
+
const message = cause.area === "persistence"
|
|
596
|
+
? "A required runtime persistence operation failed."
|
|
597
|
+
: cause.area === "compaction"
|
|
598
|
+
? "Runtime compaction failed."
|
|
599
|
+
: "The AgentSession runtime failed before completing the run.";
|
|
600
|
+
let termination = classifySessionTermination({
|
|
601
|
+
sessionId: this.sessionId,
|
|
602
|
+
runId,
|
|
603
|
+
timestamp,
|
|
604
|
+
source: "observed",
|
|
605
|
+
message,
|
|
606
|
+
cause,
|
|
607
|
+
sideEffects: this._activeRunId === null ? "none" : "possible",
|
|
608
|
+
...(this.model ? { provider: this.model.provider, model: this.model.id } : {}),
|
|
609
|
+
});
|
|
610
|
+
if (this._activeRunId !== null && this._runJournalStore.openRunId === this._activeRunId) {
|
|
611
|
+
try {
|
|
612
|
+
this._runJournalStore.finish({
|
|
613
|
+
termination,
|
|
614
|
+
sessionRevision: this._sessionRevision(),
|
|
615
|
+
timestamp,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
termination = classifySessionTermination({
|
|
620
|
+
sessionId: this.sessionId,
|
|
621
|
+
runId,
|
|
622
|
+
timestamp,
|
|
623
|
+
source: "observed",
|
|
624
|
+
message: "The run journal could not persist the runtime termination.",
|
|
625
|
+
cause: { area: "persistence", code: "append_failed" },
|
|
626
|
+
sideEffects: "possible",
|
|
627
|
+
...(this.model ? { provider: this.model.provider, model: this.model.id } : {}),
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
this._activeRunId = null;
|
|
632
|
+
this._activeRunToolTermination = undefined;
|
|
633
|
+
this._pendingRuntimeTerminationCause = undefined;
|
|
634
|
+
this._userAbortRequested = false;
|
|
635
|
+
this._publishTermination(termination);
|
|
636
|
+
}
|
|
637
|
+
_handleRunLifecycleEvent(event) {
|
|
638
|
+
if (event.type === "agent_start") {
|
|
639
|
+
if (this._activeRunId !== null)
|
|
640
|
+
throw new Error("run journal already has an active AgentSession run");
|
|
641
|
+
const runId = randomUUID();
|
|
642
|
+
this._activeRunId = runId;
|
|
643
|
+
this._activeRunToolTermination = undefined;
|
|
644
|
+
this._pendingRuntimeTerminationCause = undefined;
|
|
645
|
+
this._userAbortRequested = false;
|
|
646
|
+
try {
|
|
647
|
+
this._runJournalStore.start({
|
|
648
|
+
runId,
|
|
649
|
+
sessionRevision: this._sessionRevision(),
|
|
650
|
+
timestamp: new Date().toISOString(),
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
this._pendingRuntimeTerminationCause = { area: "persistence", code: "append_failed" };
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (event.type !== "agent_end")
|
|
660
|
+
return;
|
|
661
|
+
if (this._activeRunId === null)
|
|
662
|
+
throw new Error("run journal received agent_end without run_started");
|
|
663
|
+
const termination = this._classifyRunTermination(this._activeRunId, event);
|
|
664
|
+
try {
|
|
665
|
+
this._runJournalStore.finish({
|
|
666
|
+
termination,
|
|
667
|
+
sessionRevision: this._sessionRevision(),
|
|
668
|
+
timestamp: termination.timestamp,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
this._pendingRuntimeTerminationCause = { area: "persistence", code: "append_failed" };
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
this._activeRunId = null;
|
|
676
|
+
this._activeRunToolTermination = undefined;
|
|
677
|
+
this._pendingRuntimeTerminationCause = undefined;
|
|
678
|
+
this._userAbortRequested = false;
|
|
679
|
+
this._publishTermination(termination);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Handle tool timeout / late-settlement audit signals (ALG004-A/B). A late
|
|
683
|
+
* settlement of a potentially-writing tool raises session risk and emits a
|
|
684
|
+
* workspace mutation/invalidation signal for evidence freshness consumers.
|
|
685
|
+
*/
|
|
686
|
+
_handleToolAuditEvent(event) {
|
|
687
|
+
if (event.type === "tool_execution_end") {
|
|
688
|
+
this._invalidateContextBudgetCache({ type: "toolResultDisposition" });
|
|
689
|
+
const envelope = event.result?.details?.omk;
|
|
690
|
+
if (envelope && envelope.schema === "tool-result/v2" && envelope.disposition === "timeout") {
|
|
691
|
+
const timeout = {
|
|
692
|
+
toolCallId: event.toolCallId,
|
|
693
|
+
toolName: event.toolName,
|
|
694
|
+
...(typeof envelope.timeoutMs === "number" ? { timeoutMs: envelope.timeoutMs } : {}),
|
|
695
|
+
executionStarted: envelope.executionStarted === true,
|
|
696
|
+
};
|
|
697
|
+
this._activeRunToolTermination = timeout;
|
|
698
|
+
this._appendRunJournalAudit("tool_timeout", timeout);
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (event.type !== "tool_execution_late_settlement") {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
// Fail closed: anything not classified as a read-category tool may write.
|
|
706
|
+
const potentiallyWriting = resolveToolTimeoutCategory(event.toolName) !== "read";
|
|
707
|
+
this._appendRunJournalAudit("tool_late_settlement", {
|
|
708
|
+
toolCallId: event.toolCallId,
|
|
709
|
+
toolName: event.toolName,
|
|
710
|
+
disposition: event.disposition,
|
|
711
|
+
outcome: event.outcome,
|
|
712
|
+
...(potentiallyWriting ? { sessionRisk: "elevated" } : {}),
|
|
713
|
+
});
|
|
714
|
+
if (potentiallyWriting) {
|
|
715
|
+
this._sessionRiskLevel = "elevated";
|
|
716
|
+
this._workspaceMutationCount += 1;
|
|
717
|
+
this._invalidateContextBudgetCache({
|
|
718
|
+
type: "worktreeFingerprint",
|
|
719
|
+
value: this._contextCacheWorktreeFingerprint(),
|
|
720
|
+
});
|
|
721
|
+
const payload = { root: this._cwd, paths: [] };
|
|
722
|
+
this._appendReplayEvent("workspace_mutation", payload);
|
|
723
|
+
this._emit({
|
|
724
|
+
type: "workspace_mutation",
|
|
725
|
+
source: "tool_late_settlement",
|
|
726
|
+
toolCallId: event.toolCallId,
|
|
727
|
+
toolName: event.toolName,
|
|
728
|
+
payload,
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
212
732
|
_getBashSandboxPreflight(override) {
|
|
213
733
|
const preflight = override ?? this._configuredBashSandboxPreflight;
|
|
214
734
|
if (!preflight || preflight.policy.mode === "off" || preflight.backend) {
|
|
@@ -319,6 +839,8 @@ export class AgentSession {
|
|
|
319
839
|
_lastAssistantMessage = undefined;
|
|
320
840
|
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
321
841
|
_handleAgentEvent = async (event) => {
|
|
842
|
+
// Required run lifecycle persistence executes before extension/user listeners.
|
|
843
|
+
this._handleRunLifecycleEvent(event);
|
|
322
844
|
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
323
845
|
// This ensures the UI sees the updated queue state
|
|
324
846
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
@@ -341,28 +863,37 @@ export class AgentSession {
|
|
|
341
863
|
}
|
|
342
864
|
}
|
|
343
865
|
}
|
|
866
|
+
// Durable tool timeout / late-settlement audits (ALG004-A/B).
|
|
867
|
+
this._handleToolAuditEvent(event);
|
|
344
868
|
// Emit to extensions first
|
|
345
869
|
await this._emitExtensionEvent(event);
|
|
870
|
+
const replacement = event.type === "message_end" ? this._messageEndReplacements.get(event) : undefined;
|
|
871
|
+
if (event.type === "message_end") {
|
|
872
|
+
this._messageEndReplacements.delete(event);
|
|
873
|
+
}
|
|
874
|
+
const finalizedEvent = replacement === undefined ? event : Object.freeze({ ...event, message: replacement });
|
|
346
875
|
// Notify all listeners
|
|
347
|
-
this._emit(
|
|
876
|
+
this._emit(finalizedEvent.type === "agent_end"
|
|
877
|
+
? { ...finalizedEvent, willRetry: this._willRetryAfterAgentEnd(finalizedEvent) }
|
|
878
|
+
: finalizedEvent);
|
|
348
879
|
// Handle session persistence
|
|
349
|
-
if (
|
|
880
|
+
if (finalizedEvent.type === "message_end") {
|
|
350
881
|
// Check if this is a custom message from extensions
|
|
351
|
-
if (
|
|
882
|
+
if (finalizedEvent.message.role === "custom") {
|
|
352
883
|
// Persist as CustomMessageEntry
|
|
353
|
-
this.sessionManager.appendCustomMessageEntry(
|
|
884
|
+
this.sessionManager.appendCustomMessageEntry(finalizedEvent.message.customType, finalizedEvent.message.content, finalizedEvent.message.display, finalizedEvent.message.details);
|
|
354
885
|
}
|
|
355
|
-
else if (
|
|
356
|
-
|
|
357
|
-
|
|
886
|
+
else if (finalizedEvent.message.role === "user" ||
|
|
887
|
+
finalizedEvent.message.role === "assistant" ||
|
|
888
|
+
finalizedEvent.message.role === "toolResult") {
|
|
358
889
|
// Regular LLM message - persist as SessionMessageEntry
|
|
359
|
-
this.sessionManager.appendMessage(
|
|
890
|
+
this.sessionManager.appendMessage(finalizedEvent.message);
|
|
360
891
|
}
|
|
361
892
|
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
362
893
|
// Track assistant message for auto-compaction (checked on agent_end)
|
|
363
|
-
if (
|
|
364
|
-
this._lastAssistantMessage =
|
|
365
|
-
const assistantMsg =
|
|
894
|
+
if (finalizedEvent.message.role === "assistant") {
|
|
895
|
+
this._lastAssistantMessage = finalizedEvent.message;
|
|
896
|
+
const assistantMsg = finalizedEvent.message;
|
|
366
897
|
if (assistantMsg.stopReason !== "error") {
|
|
367
898
|
this._overflowRecoveryAttempted = false;
|
|
368
899
|
}
|
|
@@ -413,19 +944,19 @@ export class AgentSession {
|
|
|
413
944
|
}
|
|
414
945
|
return undefined;
|
|
415
946
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
947
|
+
_replaceFinalizedMessage(target, replacement) {
|
|
948
|
+
const messages = this.agent.state.messages;
|
|
949
|
+
const matchingIndexes = messages.flatMap((message, index) => (isDeepStrictEqual(message, target) ? [index] : []));
|
|
950
|
+
if (matchingIndexes.length !== 1) {
|
|
951
|
+
throw new Error(matchingIndexes.length === 0
|
|
952
|
+
? "Finalized message was not found in agent state"
|
|
953
|
+
: "Finalized message is ambiguous in agent state");
|
|
954
|
+
}
|
|
955
|
+
const finalizedReplacement = createImmutableMessageSnapshot(replacement);
|
|
956
|
+
const nextMessages = messages.slice();
|
|
957
|
+
nextMessages[matchingIndexes[0]] = finalizedReplacement;
|
|
958
|
+
this.agent.state.messages = nextMessages;
|
|
959
|
+
return finalizedReplacement;
|
|
429
960
|
}
|
|
430
961
|
/** Emit extension events based on agent events */
|
|
431
962
|
async _emitExtensionEvent(event) {
|
|
@@ -476,7 +1007,8 @@ export class AgentSession {
|
|
|
476
1007
|
};
|
|
477
1008
|
const replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);
|
|
478
1009
|
if (replacement) {
|
|
479
|
-
this.
|
|
1010
|
+
const finalizedReplacement = this._replaceFinalizedMessage(event.message, replacement);
|
|
1011
|
+
this._messageEndReplacements.set(event, finalizedReplacement);
|
|
480
1012
|
}
|
|
481
1013
|
}
|
|
482
1014
|
else if (event.type === "tool_execution_start") {
|
|
@@ -643,10 +1175,28 @@ export class AgentSession {
|
|
|
643
1175
|
}
|
|
644
1176
|
}
|
|
645
1177
|
this.agent.state.tools = tools;
|
|
1178
|
+
this._applyToolTimeoutCategoryDefaults(validToolNames);
|
|
646
1179
|
// Rebuild base system prompt with new tool set
|
|
647
1180
|
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
|
|
648
1181
|
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
649
1182
|
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Recompute the Agent's per-name timeout map for the active tool set:
|
|
1185
|
+
* explicit user/settings entries always win; active tools without an entry
|
|
1186
|
+
* receive their §6.3 category default (ALG004-C); uncategorized tools fall
|
|
1187
|
+
* through to the global `toolTimeoutMs`.
|
|
1188
|
+
*/
|
|
1189
|
+
_applyToolTimeoutCategoryDefaults(activeToolNames) {
|
|
1190
|
+
try {
|
|
1191
|
+
const resolved = resolveAgentToolSettings(this.settingsManager);
|
|
1192
|
+
this.agent.toolTimeouts = applyCategoryTimeoutDefaults(activeToolNames, resolved.toolTimeouts);
|
|
1193
|
+
}
|
|
1194
|
+
catch {
|
|
1195
|
+
// Invalid settings fail closed at session creation; keep current
|
|
1196
|
+
// explicit entries and still fill category defaults for active tools.
|
|
1197
|
+
this.agent.toolTimeouts = applyCategoryTimeoutDefaults(activeToolNames, this.agent.toolTimeouts ?? {});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
650
1200
|
/** Whether compaction or branch summarization is currently running */
|
|
651
1201
|
get isCompacting() {
|
|
652
1202
|
return (this._autoCompactionAbortController !== undefined ||
|
|
@@ -744,7 +1294,11 @@ export class AgentSession {
|
|
|
744
1294
|
return undefined;
|
|
745
1295
|
}
|
|
746
1296
|
_getContextBudgetOptions() {
|
|
747
|
-
|
|
1297
|
+
const contextGovernorOverride = process.env.OMK_CONTEXT_GOVERNOR;
|
|
1298
|
+
if (contextGovernorOverride === "0") {
|
|
1299
|
+
return undefined;
|
|
1300
|
+
}
|
|
1301
|
+
if (contextGovernorOverride !== "1" && !this.settingsManager.getContextBudgetEnabled()) {
|
|
748
1302
|
return undefined;
|
|
749
1303
|
}
|
|
750
1304
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
@@ -782,6 +1336,9 @@ export class AgentSession {
|
|
|
782
1336
|
responseReserveTokens =
|
|
783
1337
|
parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ?? LEGACY_RESPONSE_RESERVE_TOKENS;
|
|
784
1338
|
}
|
|
1339
|
+
const cacheProvider = this._contextBudgetCacheProvider ?? createMemoryContextBudgetCacheProviderV2("session");
|
|
1340
|
+
cacheProvider.setInvalidationSnapshot?.(this._contextCacheInvalidationSnapshot);
|
|
1341
|
+
this._contextBudgetCacheProvider = cacheProvider;
|
|
785
1342
|
// Enforce floor
|
|
786
1343
|
if (maxPromptTokens < MIN_PROMPT_TOKENS) {
|
|
787
1344
|
maxPromptTokens = MIN_PROMPT_TOKENS;
|
|
@@ -797,6 +1354,7 @@ export class AgentSession {
|
|
|
797
1354
|
tokenizerMode: parseTokenizerModeEnv(process.env.OMK_CONTEXT_GOVERNOR_TOKENIZER),
|
|
798
1355
|
activeSkillNames: parseCommaSeparatedEnv(process.env.OMK_CONTEXT_GOVERNOR_ACTIVE_SKILLS),
|
|
799
1356
|
queryContext: this._extractCurrentQuery(),
|
|
1357
|
+
cacheProvider,
|
|
800
1358
|
};
|
|
801
1359
|
}
|
|
802
1360
|
/**
|
|
@@ -859,6 +1417,10 @@ export class AgentSession {
|
|
|
859
1417
|
await this.agent.continue();
|
|
860
1418
|
}
|
|
861
1419
|
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
this._publishRuntimeFailure(error);
|
|
1422
|
+
throw error;
|
|
1423
|
+
}
|
|
862
1424
|
finally {
|
|
863
1425
|
this._flushPendingBashMessages();
|
|
864
1426
|
}
|
|
@@ -900,7 +1462,7 @@ export class AgentSession {
|
|
|
900
1462
|
async prompt(text, options) {
|
|
901
1463
|
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
902
1464
|
const preflightResult = options?.preflightResult;
|
|
903
|
-
let currentText = text;
|
|
1465
|
+
let currentText = redactSensitiveText(text);
|
|
904
1466
|
let promptActiveSkillNames = [...(options?.activeSkillNames ?? [])];
|
|
905
1467
|
let promptActiveSkillSource = options?.activeSkillSource;
|
|
906
1468
|
let isBangSkillInvocation = false;
|
|
@@ -948,6 +1510,7 @@ export class AgentSession {
|
|
|
948
1510
|
expandedText = this._expandSkillCommand(expandedText);
|
|
949
1511
|
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
950
1512
|
}
|
|
1513
|
+
expandedText = redactSensitiveText(expandedText);
|
|
951
1514
|
// If streaming, queue via steer() or followUp() based on option
|
|
952
1515
|
if (this.isStreaming) {
|
|
953
1516
|
if (!options?.streamingBehavior) {
|
|
@@ -1047,6 +1610,19 @@ export class AgentSession {
|
|
|
1047
1610
|
}
|
|
1048
1611
|
catch (error) {
|
|
1049
1612
|
preflightResult?.(false);
|
|
1613
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
1614
|
+
const cause = this._classifyPreflightCause(rawMessage);
|
|
1615
|
+
const timestamp = new Date().toISOString();
|
|
1616
|
+
this._publishTermination(classifySessionTermination({
|
|
1617
|
+
sessionId: this.sessionId,
|
|
1618
|
+
runId: `preflight-${randomUUID()}`,
|
|
1619
|
+
timestamp,
|
|
1620
|
+
source: "observed",
|
|
1621
|
+
message: this._terminationMessage(rawMessage, "Prompt preflight failed."),
|
|
1622
|
+
cause,
|
|
1623
|
+
sideEffects: "none",
|
|
1624
|
+
...(this.model ? { provider: this.model.provider, model: this.model.id } : {}),
|
|
1625
|
+
}));
|
|
1050
1626
|
throw error;
|
|
1051
1627
|
}
|
|
1052
1628
|
if (!messages) {
|
|
@@ -1121,14 +1697,15 @@ export class AgentSession {
|
|
|
1121
1697
|
* @throws Error if text is an extension command
|
|
1122
1698
|
*/
|
|
1123
1699
|
async steer(text, images) {
|
|
1700
|
+
const sanitizedText = redactSensitiveText(text);
|
|
1124
1701
|
// Check for extension commands (cannot be queued)
|
|
1125
|
-
if (
|
|
1126
|
-
this._throwIfExtensionCommand(
|
|
1702
|
+
if (sanitizedText.startsWith("/")) {
|
|
1703
|
+
this._throwIfExtensionCommand(sanitizedText);
|
|
1127
1704
|
}
|
|
1128
1705
|
// Expand skill commands and prompt templates
|
|
1129
|
-
let expandedText = this._expandSkillCommand(
|
|
1706
|
+
let expandedText = this._expandSkillCommand(sanitizedText);
|
|
1130
1707
|
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
1131
|
-
await this._queueSteer(expandedText, images);
|
|
1708
|
+
await this._queueSteer(redactSensitiveText(expandedText), images);
|
|
1132
1709
|
}
|
|
1133
1710
|
/**
|
|
1134
1711
|
* Queue a follow-up message to be processed after the agent finishes.
|
|
@@ -1138,19 +1715,21 @@ export class AgentSession {
|
|
|
1138
1715
|
* @throws Error if text is an extension command
|
|
1139
1716
|
*/
|
|
1140
1717
|
async followUp(text, images) {
|
|
1718
|
+
const sanitizedText = redactSensitiveText(text);
|
|
1141
1719
|
// Check for extension commands (cannot be queued)
|
|
1142
|
-
if (
|
|
1143
|
-
this._throwIfExtensionCommand(
|
|
1720
|
+
if (sanitizedText.startsWith("/")) {
|
|
1721
|
+
this._throwIfExtensionCommand(sanitizedText);
|
|
1144
1722
|
}
|
|
1145
1723
|
// Expand skill commands and prompt templates
|
|
1146
|
-
let expandedText = this._expandSkillCommand(
|
|
1724
|
+
let expandedText = this._expandSkillCommand(sanitizedText);
|
|
1147
1725
|
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
1148
|
-
await this._queueFollowUp(expandedText, images);
|
|
1726
|
+
await this._queueFollowUp(redactSensitiveText(expandedText), images);
|
|
1149
1727
|
}
|
|
1150
1728
|
/**
|
|
1151
1729
|
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
1152
1730
|
*/
|
|
1153
1731
|
async _queueSteer(text, images) {
|
|
1732
|
+
this._invalidateContextBudgetCache({ type: "userSteering" });
|
|
1154
1733
|
this._steeringMessages.push(text);
|
|
1155
1734
|
this._emitQueueUpdate();
|
|
1156
1735
|
const content = [{ type: "text", text }];
|
|
@@ -1211,6 +1790,7 @@ export class AgentSession {
|
|
|
1211
1790
|
details: message.details,
|
|
1212
1791
|
timestamp: Date.now(),
|
|
1213
1792
|
};
|
|
1793
|
+
this._recordEvidenceReceiptInvalidation(message.customType);
|
|
1214
1794
|
if (options?.deliverAs === "nextTurn") {
|
|
1215
1795
|
this._pendingNextTurnMessages.push(appMessage);
|
|
1216
1796
|
}
|
|
@@ -1302,16 +1882,43 @@ export class AgentSession {
|
|
|
1302
1882
|
* Abort current operation and wait for agent to become idle.
|
|
1303
1883
|
*/
|
|
1304
1884
|
async abort() {
|
|
1885
|
+
this._userAbortRequested = this.isStreaming || this.isRetrying;
|
|
1305
1886
|
this.abortRetry();
|
|
1306
1887
|
this.agent.abort();
|
|
1307
1888
|
await this.agent.waitForIdle();
|
|
1308
1889
|
}
|
|
1890
|
+
/** Record an observed process signal before a mode begins shutdown. */
|
|
1891
|
+
recordProcessSignal(signal) {
|
|
1892
|
+
const timestamp = new Date().toISOString();
|
|
1893
|
+
const runId = this._activeRunId ?? `signal-${randomUUID()}`;
|
|
1894
|
+
const termination = classifySessionTermination({
|
|
1895
|
+
sessionId: this.sessionId,
|
|
1896
|
+
runId,
|
|
1897
|
+
timestamp,
|
|
1898
|
+
source: "observed",
|
|
1899
|
+
message: `Process received ${signal}.`,
|
|
1900
|
+
cause: { area: "process", code: "signal", signal },
|
|
1901
|
+
sideEffects: this._activeRunId === null ? "none" : "possible",
|
|
1902
|
+
...(this.model ? { provider: this.model.provider, model: this.model.id } : {}),
|
|
1903
|
+
});
|
|
1904
|
+
if (this._activeRunId !== null) {
|
|
1905
|
+
this._runJournalStore.finish({
|
|
1906
|
+
termination,
|
|
1907
|
+
sessionRevision: this._sessionRevision(),
|
|
1908
|
+
timestamp,
|
|
1909
|
+
});
|
|
1910
|
+
this._activeRunId = null;
|
|
1911
|
+
}
|
|
1912
|
+
this._publishTermination(termination);
|
|
1913
|
+
return termination;
|
|
1914
|
+
}
|
|
1309
1915
|
// =========================================================================
|
|
1310
1916
|
// Model Management
|
|
1311
1917
|
// =========================================================================
|
|
1312
1918
|
async _emitModelSelect(nextModel, previousModel, source) {
|
|
1313
1919
|
if (modelsAreEqual(previousModel, nextModel))
|
|
1314
1920
|
return;
|
|
1921
|
+
this._invalidateContextBudgetCache({ type: "activeModelId", value: this._contextCacheModelId(nextModel) });
|
|
1315
1922
|
await this._extensionRunner.emit({
|
|
1316
1923
|
type: "model_select",
|
|
1317
1924
|
model: nextModel,
|
|
@@ -1649,6 +2256,331 @@ export class AgentSession {
|
|
|
1649
2256
|
// =========================================================================
|
|
1650
2257
|
// Compaction
|
|
1651
2258
|
// =========================================================================
|
|
2259
|
+
_captureCompactionState() {
|
|
2260
|
+
return this.sessionManager.withCompactionCommitLock(() => this._captureCompactionStateLocked());
|
|
2261
|
+
}
|
|
2262
|
+
_captureCompactionStateLocked() {
|
|
2263
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
2264
|
+
const bytes = sessionFile && existsSync(sessionFile)
|
|
2265
|
+
? new Uint8Array(readFileSync(sessionFile))
|
|
2266
|
+
: new TextEncoder().encode(`${[this.sessionManager.getHeader(), ...this.sessionManager.getEntries()]
|
|
2267
|
+
.filter((entry) => entry !== null)
|
|
2268
|
+
.map((entry) => JSON.stringify(entry))
|
|
2269
|
+
.join("\n")}\n`);
|
|
2270
|
+
const report = inspectSessionIntegrity(bytes, { activeLeafId: this.sessionManager.getLeafId() });
|
|
2271
|
+
const branchEntries = report.activeBranch;
|
|
2272
|
+
let latestCompactionIndex = -1;
|
|
2273
|
+
for (let index = branchEntries.length - 1; index >= 0; index -= 1) {
|
|
2274
|
+
if (branchEntries[index]?.type === "compaction") {
|
|
2275
|
+
latestCompactionIndex = index;
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
const latestCompaction = branchEntries[latestCompactionIndex];
|
|
2280
|
+
const firstKeptIndex = latestCompaction?.type === "compaction"
|
|
2281
|
+
? branchEntries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId)
|
|
2282
|
+
: -1;
|
|
2283
|
+
const sourceEntries = branchEntries.slice(latestCompactionIndex < 0 ? 0 : firstKeptIndex < 0 ? latestCompactionIndex : firstKeptIndex);
|
|
2284
|
+
const firstEntry = sourceEntries[0];
|
|
2285
|
+
const lastEntry = sourceEntries.at(-1);
|
|
2286
|
+
if (!firstEntry || !lastEntry || report.activeLeafId === null) {
|
|
2287
|
+
const barrier = evaluateCompactionBarrier(report, [...this.agent.state.pendingToolCalls]);
|
|
2288
|
+
if (barrier.status !== "ready")
|
|
2289
|
+
throw this._barrierError(barrier);
|
|
2290
|
+
throw new Error("Nothing to compact: the active session branch is empty");
|
|
2291
|
+
}
|
|
2292
|
+
const revision = this.sessionManager.getDurableHeadToken();
|
|
2293
|
+
const source = createCompactionSourceIdentity({
|
|
2294
|
+
sessionId: revision.sessionId,
|
|
2295
|
+
entryIds: sourceEntries.map((entry) => entry.id),
|
|
2296
|
+
firstEntryId: firstEntry.id,
|
|
2297
|
+
lastEntryId: lastEntry.id,
|
|
2298
|
+
sourceSha256: createHash("sha256")
|
|
2299
|
+
.update(sourceEntries.map((entry) => JSON.stringify(entry)).join("\n"), "utf8")
|
|
2300
|
+
.digest("hex"),
|
|
2301
|
+
activeLeafId: report.activeLeafId,
|
|
2302
|
+
messageCount: report.activeMessages.length,
|
|
2303
|
+
});
|
|
2304
|
+
return { report, branchEntries, revision, source };
|
|
2305
|
+
}
|
|
2306
|
+
_captureCompactionProvenance(capture) {
|
|
2307
|
+
let latestIntent = "Continue the current session";
|
|
2308
|
+
for (let index = capture.report.activeMessages.length - 1; index >= 0; index -= 1) {
|
|
2309
|
+
const message = capture.report.activeMessages[index];
|
|
2310
|
+
if (message?.role !== "user")
|
|
2311
|
+
continue;
|
|
2312
|
+
const candidate = sanitizeBinaryOutput(redactSensitiveText(this._getUserMessageText(message)).trim()).slice(0, 16_384);
|
|
2313
|
+
if (candidate.length > 0)
|
|
2314
|
+
latestIntent = candidate;
|
|
2315
|
+
break;
|
|
2316
|
+
}
|
|
2317
|
+
const modelHistory = capture.branchEntries
|
|
2318
|
+
.flatMap((entry) => {
|
|
2319
|
+
if (entry.type === "model_change") {
|
|
2320
|
+
return [{ entryId: entry.id, provider: entry.provider, modelId: entry.modelId }];
|
|
2321
|
+
}
|
|
2322
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2323
|
+
return [{ entryId: entry.id, provider: entry.message.provider, modelId: entry.message.model }];
|
|
2324
|
+
}
|
|
2325
|
+
return [];
|
|
2326
|
+
})
|
|
2327
|
+
.slice(-256);
|
|
2328
|
+
const customEntryIds = (customType) => capture.branchEntries
|
|
2329
|
+
.filter((entry) => entry.type === "custom" && entry.customType === customType)
|
|
2330
|
+
.map((entry) => entry.id);
|
|
2331
|
+
return {
|
|
2332
|
+
latestIntent,
|
|
2333
|
+
openTasks: [],
|
|
2334
|
+
laneIds: customEntryIds("lane"),
|
|
2335
|
+
acceptancePredicateIds: customEntryIds("acceptance_predicate"),
|
|
2336
|
+
evidenceReceiptIds: customEntryIds("evidence_receipt"),
|
|
2337
|
+
blockerReasons: [],
|
|
2338
|
+
repairEventIds: [
|
|
2339
|
+
...customEntryIds("transcript_repaired"),
|
|
2340
|
+
...customEntryIds("compaction_transcript_repaired"),
|
|
2341
|
+
],
|
|
2342
|
+
branch: null,
|
|
2343
|
+
worktree: this._cwd,
|
|
2344
|
+
modelHistory,
|
|
2345
|
+
nextAction: latestIntent,
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
_barrierError(barrier) {
|
|
2349
|
+
if (barrier.status === "defer") {
|
|
2350
|
+
return new Error(`Compaction deferred until the transcript closes (${barrier.reason})`);
|
|
2351
|
+
}
|
|
2352
|
+
return new Error(`Compaction failed closed on transcript integrity (${barrier.reason}). Run the session doctor before retrying.`);
|
|
2353
|
+
}
|
|
2354
|
+
_evaluateCompactionBarrier(capture, includeMissingTailAsPending, excludedPendingIds = new Set()) {
|
|
2355
|
+
const pending = new Set([...this.agent.state.pendingToolCalls].filter((toolCallId) => !excludedPendingIds.has(toolCallId)));
|
|
2356
|
+
if (includeMissingTailAsPending) {
|
|
2357
|
+
for (const issue of capture.report.transcript?.issues ?? []) {
|
|
2358
|
+
if (issue.kind === "missing_result")
|
|
2359
|
+
pending.add(issue.toolCallId);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return evaluateCompactionBarrier(capture.report, [...pending]);
|
|
2363
|
+
}
|
|
2364
|
+
_repairEmergencyCompactionTail(capture) {
|
|
2365
|
+
const barrier = this._evaluateCompactionBarrier(capture, true);
|
|
2366
|
+
if (barrier.status !== "defer" || barrier.reason !== "missing_active_tail_results") {
|
|
2367
|
+
if (barrier.status !== "ready")
|
|
2368
|
+
throw this._barrierError(barrier);
|
|
2369
|
+
return { capture, repairedToolCallIds: new Set() };
|
|
2370
|
+
}
|
|
2371
|
+
const repairedMessages = repairTranscriptIntegrity([...capture.report.activeMessages], "Tool result missing; synthesized to close an emergency compaction barrier");
|
|
2372
|
+
const inserted = repairedMessages.slice(capture.report.activeMessages.length);
|
|
2373
|
+
const repairedToolCallIds = new Set();
|
|
2374
|
+
for (const message of inserted) {
|
|
2375
|
+
if (message.role !== "toolResult") {
|
|
2376
|
+
throw new Error("Emergency compaction repair produced a non-tool result");
|
|
2377
|
+
}
|
|
2378
|
+
const toolResult = message;
|
|
2379
|
+
repairedToolCallIds.add(toolResult.toolCallId);
|
|
2380
|
+
this.sessionManager.appendMessage(toolResult);
|
|
2381
|
+
}
|
|
2382
|
+
this.sessionManager.appendCustomEntry("compaction_transcript_repaired", {
|
|
2383
|
+
insertedToolCallIds: [...repairedToolCallIds],
|
|
2384
|
+
reason: "emergency_compaction",
|
|
2385
|
+
});
|
|
2386
|
+
this._invalidateContextBudgetCache({ type: "transcriptRepair" });
|
|
2387
|
+
const closedCapture = this._captureCompactionState();
|
|
2388
|
+
const closedBarrier = this._evaluateCompactionBarrier(closedCapture, false, repairedToolCallIds);
|
|
2389
|
+
if (closedBarrier.status !== "ready")
|
|
2390
|
+
throw this._barrierError(closedBarrier);
|
|
2391
|
+
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
2392
|
+
return { capture: closedCapture, repairedToolCallIds };
|
|
2393
|
+
}
|
|
2394
|
+
_priorCommittedCompactionSourceDigests() {
|
|
2395
|
+
const digests = [];
|
|
2396
|
+
for (const entry of this.sessionManager.getEntries()) {
|
|
2397
|
+
if (entry.type !== "compaction" || typeof entry.details !== "object" || entry.details === null)
|
|
2398
|
+
continue;
|
|
2399
|
+
if (!Object.hasOwn(entry.details, "compactionEnvelope"))
|
|
2400
|
+
continue;
|
|
2401
|
+
const envelope = validateCompactionEnvelope(Reflect.get(entry.details, "compactionEnvelope"));
|
|
2402
|
+
if (envelope.summary !== entry.summary) {
|
|
2403
|
+
throw new Error(`Compaction entry ${entry.id} has invalid provenance. Run the session doctor.`);
|
|
2404
|
+
}
|
|
2405
|
+
digests.push(envelope.source.sourceSha256);
|
|
2406
|
+
}
|
|
2407
|
+
return digests;
|
|
2408
|
+
}
|
|
2409
|
+
_beginCompactionTransaction(compactionModel, emergency) {
|
|
2410
|
+
let capture = this._captureCompactionState();
|
|
2411
|
+
if (emergency) {
|
|
2412
|
+
capture = this._repairEmergencyCompactionTail(capture).capture;
|
|
2413
|
+
}
|
|
2414
|
+
else {
|
|
2415
|
+
const barrier = this._evaluateCompactionBarrier(capture, false);
|
|
2416
|
+
if (barrier.status !== "ready")
|
|
2417
|
+
throw this._barrierError(barrier);
|
|
2418
|
+
}
|
|
2419
|
+
const transaction = createCompactionTransaction({
|
|
2420
|
+
transactionId: randomUUID(),
|
|
2421
|
+
baseRevision: capture.revision,
|
|
2422
|
+
source: capture.source,
|
|
2423
|
+
createdAt: new Date().toISOString(),
|
|
2424
|
+
model: { provider: compactionModel.provider, id: compactionModel.id },
|
|
2425
|
+
preserved: this._captureCompactionProvenance(capture),
|
|
2426
|
+
});
|
|
2427
|
+
if (this._priorCommittedCompactionSourceDigests().includes(transaction.source.sourceSha256)) {
|
|
2428
|
+
throw new Error("This exact compaction source was already compacted");
|
|
2429
|
+
}
|
|
2430
|
+
return { capture, transaction };
|
|
2431
|
+
}
|
|
2432
|
+
_detailsWithCompactionEnvelope(details, envelope) {
|
|
2433
|
+
if (typeof details === "object" && details !== null && !Array.isArray(details)) {
|
|
2434
|
+
return { ...details, compactionEnvelope: envelope };
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
compactionEnvelope: envelope,
|
|
2438
|
+
...(details === undefined ? {} : { resultDetails: details }),
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
_commitCompaction(begun, result, fromExtension) {
|
|
2442
|
+
if (!begun.transaction.source.entryIds.includes(result.firstKeptEntryId)) {
|
|
2443
|
+
throw new Error("Compaction first-kept entry is outside the captured source");
|
|
2444
|
+
}
|
|
2445
|
+
const committed = this.sessionManager.withCompactionCommitLock(() => {
|
|
2446
|
+
const current = this._captureCompactionState();
|
|
2447
|
+
const barrier = this._evaluateCompactionBarrier(current, false);
|
|
2448
|
+
const decision = decideCompactionCommit({
|
|
2449
|
+
transaction: begun.transaction,
|
|
2450
|
+
currentRevision: current.revision,
|
|
2451
|
+
currentSource: current.source,
|
|
2452
|
+
barrier,
|
|
2453
|
+
priorCommittedSourceDigests: this._priorCommittedCompactionSourceDigests(),
|
|
2454
|
+
});
|
|
2455
|
+
switch (decision.decision) {
|
|
2456
|
+
case "duplicate":
|
|
2457
|
+
throw new Error("This exact compaction source was already compacted");
|
|
2458
|
+
case "stale":
|
|
2459
|
+
throw new Error(`Session changed during compaction (${decision.reason}); generated summary was discarded`);
|
|
2460
|
+
case "defer":
|
|
2461
|
+
case "fail_closed":
|
|
2462
|
+
throw this._barrierError(barrier);
|
|
2463
|
+
case "commit": {
|
|
2464
|
+
const envelope = createCompactionEnvelope({
|
|
2465
|
+
transaction: begun.transaction,
|
|
2466
|
+
decision,
|
|
2467
|
+
summary: result.summary,
|
|
2468
|
+
summarySha256: createHash("sha256").update(result.summary, "utf8").digest("hex"),
|
|
2469
|
+
});
|
|
2470
|
+
const entryId = this.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, this._detailsWithCompactionEnvelope(result.details, envelope), fromExtension);
|
|
2471
|
+
const entry = this.sessionManager.getEntry(entryId);
|
|
2472
|
+
if (!entry || entry.type !== "compaction") {
|
|
2473
|
+
throw new Error("Compaction commit did not produce a compaction entry");
|
|
2474
|
+
}
|
|
2475
|
+
return { entry, envelope };
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
});
|
|
2479
|
+
this._recordCompactionCommitForHysteresis();
|
|
2480
|
+
return committed;
|
|
2481
|
+
}
|
|
2482
|
+
_pendingToolResultReserve(settings) {
|
|
2483
|
+
const pendingIds = this.agent.state.pendingToolCalls;
|
|
2484
|
+
const calls = new Map();
|
|
2485
|
+
const streaming = this.agent.state.streamingMessage;
|
|
2486
|
+
const messages = streaming ? [...this.agent.state.messages, streaming] : this.agent.state.messages;
|
|
2487
|
+
for (const message of messages) {
|
|
2488
|
+
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
2489
|
+
continue;
|
|
2490
|
+
for (const part of message.content) {
|
|
2491
|
+
if (part.type === "toolCall" && pendingIds.has(part.id)) {
|
|
2492
|
+
calls.set(part.id, { name: part.name, args: part.arguments });
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
const counts = { text: 0, image: 0, "large-output": 0 };
|
|
2497
|
+
for (const id of pendingIds) {
|
|
2498
|
+
const call = calls.get(id);
|
|
2499
|
+
const resultClass = call ? pendingToolResultClass(call.name, call.args) : "large-output";
|
|
2500
|
+
counts[resultClass] += 1;
|
|
2501
|
+
}
|
|
2502
|
+
const requests = [];
|
|
2503
|
+
for (const resultClass of ["text", "image", "large-output"]) {
|
|
2504
|
+
if (counts[resultClass] > 0) {
|
|
2505
|
+
requests.push({
|
|
2506
|
+
class: resultClass,
|
|
2507
|
+
count: counts[resultClass],
|
|
2508
|
+
tokensPerResult: PENDING_TOOL_RESULT_TOKENS[resultClass],
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const configured = settings.reservedToolResultTokens ?? 0;
|
|
2513
|
+
requests.push({ class: "large-output", count: 1, tokensPerResult: configured });
|
|
2514
|
+
return estimateToolResultReserve(requests);
|
|
2515
|
+
}
|
|
2516
|
+
_compactionHysteresisConfig(contextWindow, settings) {
|
|
2517
|
+
const threshold = getCompactionHeadroomThreshold(contextWindow, {
|
|
2518
|
+
...settings,
|
|
2519
|
+
reservedToolResultTokens: this._pendingToolResultReserve(settings),
|
|
2520
|
+
});
|
|
2521
|
+
if (!threshold)
|
|
2522
|
+
return undefined;
|
|
2523
|
+
const triggerRatio = Math.min(1, Math.max(1 / Math.floor(contextWindow), threshold.triggerTokens / contextWindow));
|
|
2524
|
+
const configuredRearm = settings.rearmRatio ?? triggerRatio * 0.75;
|
|
2525
|
+
const rearmRatio = Math.min(configuredRearm, triggerRatio * 0.999);
|
|
2526
|
+
const emergencyRatio = Math.max(triggerRatio, settings.emergencyRatio ?? 0.98);
|
|
2527
|
+
return createCompactionHysteresisConfig({ rearmRatio, triggerRatio, emergencyRatio });
|
|
2528
|
+
}
|
|
2529
|
+
_runtimeCompactionDecision(contextTokens, contextWindow, settings) {
|
|
2530
|
+
if (!Number.isFinite(contextTokens) || contextTokens < 0)
|
|
2531
|
+
return { compact: false, emergency: false };
|
|
2532
|
+
const config = this._compactionHysteresisConfig(contextWindow, settings);
|
|
2533
|
+
if (!config)
|
|
2534
|
+
return { compact: false, emergency: false };
|
|
2535
|
+
const result = stepCompactionHysteresis({
|
|
2536
|
+
config,
|
|
2537
|
+
state: this._compactionHysteresisState,
|
|
2538
|
+
ratio: Math.min(1, contextTokens / contextWindow),
|
|
2539
|
+
});
|
|
2540
|
+
this._compactionHysteresisState = result.nextState;
|
|
2541
|
+
return { compact: result.action === "compact", emergency: result.reason === "emergency_threshold_reached" };
|
|
2542
|
+
}
|
|
2543
|
+
async _runThresholdCompaction(emergency) {
|
|
2544
|
+
this._thresholdCompactionEmergency = emergency;
|
|
2545
|
+
try {
|
|
2546
|
+
return await this._runAutoCompaction("threshold", false);
|
|
2547
|
+
}
|
|
2548
|
+
finally {
|
|
2549
|
+
this._thresholdCompactionEmergency = false;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
_recordCompactionCommitForHysteresis() {
|
|
2553
|
+
const contextWindow = this.model?.contextWindow ?? 0;
|
|
2554
|
+
const config = this._compactionHysteresisConfig(contextWindow, this.settingsManager.getCompactionSettings());
|
|
2555
|
+
if (!config)
|
|
2556
|
+
return;
|
|
2557
|
+
this._compactionHysteresisState = stepCompactionHysteresis({
|
|
2558
|
+
config,
|
|
2559
|
+
state: this._compactionHysteresisState,
|
|
2560
|
+
ratio: 0,
|
|
2561
|
+
outcome: "commit",
|
|
2562
|
+
}).nextState;
|
|
2563
|
+
}
|
|
2564
|
+
_resolveCompactionModel(sessionModel) {
|
|
2565
|
+
const configuredModel = this.settingsManager.getCompactionModel();
|
|
2566
|
+
const availableModels = this._modelRegistry.getAvailable();
|
|
2567
|
+
let model;
|
|
2568
|
+
if (configuredModel) {
|
|
2569
|
+
const configured = findExactModelReferenceMatch(configuredModel, availableModels);
|
|
2570
|
+
if (!configured) {
|
|
2571
|
+
throw new Error(`Configured compaction model "${configuredModel}" is unavailable or unauthenticated.`);
|
|
2572
|
+
}
|
|
2573
|
+
model = configured;
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
model = resolveCompactionModel(sessionModel, availableModels);
|
|
2577
|
+
}
|
|
2578
|
+
this._invalidateContextBudgetCache({
|
|
2579
|
+
type: "compactionModelId",
|
|
2580
|
+
value: this._contextCacheModelId(model),
|
|
2581
|
+
});
|
|
2582
|
+
return model;
|
|
2583
|
+
}
|
|
1652
2584
|
/**
|
|
1653
2585
|
* Manually compact the session context.
|
|
1654
2586
|
* Aborts current agent operation first.
|
|
@@ -1659,14 +2591,16 @@ export class AgentSession {
|
|
|
1659
2591
|
await this.abort();
|
|
1660
2592
|
this._compactionAbortController = new AbortController();
|
|
1661
2593
|
this._emit({ type: "compaction_start", reason: "manual" });
|
|
2594
|
+
let committedCompaction = false;
|
|
1662
2595
|
try {
|
|
1663
2596
|
if (!this.model) {
|
|
1664
2597
|
throw new Error(formatNoModelSelectedMessage());
|
|
1665
2598
|
}
|
|
1666
|
-
const compactionModel =
|
|
2599
|
+
const compactionModel = this._resolveCompactionModel(this.model);
|
|
1667
2600
|
const { apiKey, headers } = await this._getCompactionRequestAuth(compactionModel);
|
|
1668
|
-
const pathEntries = this.sessionManager.getBranch();
|
|
1669
2601
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2602
|
+
const begun = this._beginCompactionTransaction(compactionModel, false);
|
|
2603
|
+
const pathEntries = [...begun.capture.branchEntries];
|
|
1670
2604
|
const preparation = prepareCompaction(pathEntries, settings);
|
|
1671
2605
|
if (!preparation) {
|
|
1672
2606
|
// Check why we can't compact
|
|
@@ -1716,25 +2650,22 @@ export class AgentSession {
|
|
|
1716
2650
|
if (this._compactionAbortController.signal.aborted) {
|
|
1717
2651
|
throw new Error("Compaction cancelled");
|
|
1718
2652
|
}
|
|
1719
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1720
|
-
const newEntries = this.sessionManager.getEntries();
|
|
1721
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1722
|
-
this.agent.state.messages = sessionContext.messages;
|
|
1723
|
-
// Get the saved compaction entry for the extension event
|
|
1724
|
-
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1725
|
-
if (this._extensionRunner && savedCompactionEntry) {
|
|
1726
|
-
await this._extensionRunner.emit({
|
|
1727
|
-
type: "session_compact",
|
|
1728
|
-
compactionEntry: savedCompactionEntry,
|
|
1729
|
-
fromExtension,
|
|
1730
|
-
});
|
|
1731
|
-
}
|
|
1732
2653
|
const compactionResult = {
|
|
1733
2654
|
summary,
|
|
1734
2655
|
firstKeptEntryId,
|
|
1735
2656
|
tokensBefore,
|
|
1736
2657
|
details,
|
|
1737
2658
|
};
|
|
2659
|
+
const committed = this._commitCompaction(begun, compactionResult, fromExtension);
|
|
2660
|
+
committedCompaction = true;
|
|
2661
|
+
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
2662
|
+
if (this._extensionRunner) {
|
|
2663
|
+
await this._extensionRunner.emit({
|
|
2664
|
+
type: "session_compact",
|
|
2665
|
+
compactionEntry: committed.entry,
|
|
2666
|
+
fromExtension,
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
1738
2669
|
this._emit({
|
|
1739
2670
|
type: "compaction_end",
|
|
1740
2671
|
reason: "manual",
|
|
@@ -1747,6 +2678,18 @@ export class AgentSession {
|
|
|
1747
2678
|
catch (error) {
|
|
1748
2679
|
const message = error instanceof Error ? error.message : String(error);
|
|
1749
2680
|
const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
|
|
2681
|
+
const stale = /stale|session changed during compaction|already compacted/i.test(message);
|
|
2682
|
+
const timestamp = new Date().toISOString();
|
|
2683
|
+
this._publishTermination(classifySessionTermination({
|
|
2684
|
+
sessionId: this.sessionId,
|
|
2685
|
+
runId: `compaction-${randomUUID()}`,
|
|
2686
|
+
timestamp,
|
|
2687
|
+
source: "observed",
|
|
2688
|
+
message: this._terminationMessage(message, "Manual compaction failed."),
|
|
2689
|
+
cause: { area: "compaction", code: aborted ? "aborted" : stale ? "stale" : "failed" },
|
|
2690
|
+
sideEffects: committedCompaction ? "confirmed" : "none",
|
|
2691
|
+
...(this.model ? { provider: this.model.provider, model: this.model.id } : {}),
|
|
2692
|
+
}));
|
|
1750
2693
|
this._emit({
|
|
1751
2694
|
type: "compaction_end",
|
|
1752
2695
|
reason: "manual",
|
|
@@ -1792,8 +2735,9 @@ export class AgentSession {
|
|
|
1792
2735
|
return false;
|
|
1793
2736
|
}
|
|
1794
2737
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
2738
|
+
const decision = this._runtimeCompactionDecision(estimate.tokens, contextWindow, settings);
|
|
2739
|
+
if (decision.compact) {
|
|
2740
|
+
return await this._runThresholdCompaction(decision.emergency);
|
|
1797
2741
|
}
|
|
1798
2742
|
return false;
|
|
1799
2743
|
}
|
|
@@ -1874,15 +2818,16 @@ export class AgentSession {
|
|
|
1874
2818
|
else {
|
|
1875
2819
|
contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
1876
2820
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
2821
|
+
const decision = this._runtimeCompactionDecision(contextTokens, contextWindow, settings);
|
|
2822
|
+
if (decision.compact) {
|
|
2823
|
+
return await this._runThresholdCompaction(decision.emergency);
|
|
1879
2824
|
}
|
|
1880
2825
|
return false;
|
|
1881
2826
|
}
|
|
1882
2827
|
/**
|
|
1883
2828
|
* Internal: Run auto-compaction with events.
|
|
1884
2829
|
*/
|
|
1885
|
-
async _runAutoCompaction(reason, willRetry) {
|
|
2830
|
+
async _runAutoCompaction(reason, willRetry, emergency = reason === "overflow" || this._thresholdCompactionEmergency) {
|
|
1886
2831
|
const settings = this.settingsManager.getCompactionSettings();
|
|
1887
2832
|
this._emit({ type: "compaction_start", reason });
|
|
1888
2833
|
this._autoCompactionAbortController = new AbortController();
|
|
@@ -1897,7 +2842,7 @@ export class AgentSession {
|
|
|
1897
2842
|
});
|
|
1898
2843
|
return false;
|
|
1899
2844
|
}
|
|
1900
|
-
const compactionModel =
|
|
2845
|
+
const compactionModel = this._resolveCompactionModel(this.model);
|
|
1901
2846
|
let apiKey;
|
|
1902
2847
|
let headers;
|
|
1903
2848
|
if (this.agent.streamFn === streamSimple) {
|
|
@@ -1921,7 +2866,8 @@ export class AgentSession {
|
|
|
1921
2866
|
else {
|
|
1922
2867
|
({ apiKey, headers } = await this._getCompactionRequestAuth(compactionModel));
|
|
1923
2868
|
}
|
|
1924
|
-
const
|
|
2869
|
+
const begun = this._beginCompactionTransaction(compactionModel, emergency);
|
|
2870
|
+
const pathEntries = [...begun.capture.branchEntries];
|
|
1925
2871
|
const preparation = prepareCompaction(pathEntries, settings);
|
|
1926
2872
|
if (!preparation) {
|
|
1927
2873
|
this._emit({
|
|
@@ -1987,25 +2933,21 @@ export class AgentSession {
|
|
|
1987
2933
|
});
|
|
1988
2934
|
return false;
|
|
1989
2935
|
}
|
|
1990
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1991
|
-
const newEntries = this.sessionManager.getEntries();
|
|
1992
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1993
|
-
this.agent.state.messages = sessionContext.messages;
|
|
1994
|
-
// Get the saved compaction entry for the extension event
|
|
1995
|
-
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1996
|
-
if (this._extensionRunner && savedCompactionEntry) {
|
|
1997
|
-
await this._extensionRunner.emit({
|
|
1998
|
-
type: "session_compact",
|
|
1999
|
-
compactionEntry: savedCompactionEntry,
|
|
2000
|
-
fromExtension,
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
2936
|
const result = {
|
|
2004
2937
|
summary,
|
|
2005
2938
|
firstKeptEntryId,
|
|
2006
2939
|
tokensBefore,
|
|
2007
2940
|
details,
|
|
2008
2941
|
};
|
|
2942
|
+
const committed = this._commitCompaction(begun, result, fromExtension);
|
|
2943
|
+
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
2944
|
+
if (this._extensionRunner) {
|
|
2945
|
+
await this._extensionRunner.emit({
|
|
2946
|
+
type: "session_compact",
|
|
2947
|
+
compactionEntry: committed.entry,
|
|
2948
|
+
fromExtension,
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2009
2951
|
const emitWillRetry = compactionEmitWillRetry(willRetry, this.agent.hasQueuedMessages());
|
|
2010
2952
|
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry: emitWillRetry });
|
|
2011
2953
|
if (willRetry) {
|
|
@@ -2173,6 +3115,7 @@ export class AgentSession {
|
|
|
2173
3115
|
},
|
|
2174
3116
|
appendEntry: (customType, data) => {
|
|
2175
3117
|
this.sessionManager.appendCustomEntry(customType, data);
|
|
3118
|
+
this._recordEvidenceReceiptInvalidation(customType);
|
|
2176
3119
|
},
|
|
2177
3120
|
setSessionName: (name) => {
|
|
2178
3121
|
this.setSessionName(name);
|
|
@@ -2391,6 +3334,7 @@ export class AgentSession {
|
|
|
2391
3334
|
const previousFlagValues = this._extensionRunner.getFlagValues();
|
|
2392
3335
|
await emitSessionShutdownEvent(this._extensionRunner, { type: "session_shutdown", reason: "reload" });
|
|
2393
3336
|
await this.settingsManager.reload();
|
|
3337
|
+
this._invalidateContextBudgetCache({ type: "settings" });
|
|
2394
3338
|
resetApiProviders();
|
|
2395
3339
|
await this._resourceLoader.reload();
|
|
2396
3340
|
this._buildRuntime({
|