open-multi-agent-kit 0.96.1 → 0.96.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 +41 -0
- package/README.md +12 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/resource-doctor-cli.d.ts +22 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
- package/dist/commands/resource-doctor-cli.js +98 -0
- package/dist/commands/resource-doctor-cli.js.map +1 -0
- package/dist/core/agent-session.d.ts +37 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +289 -5
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/completion-sound.d.ts +109 -0
- package/dist/core/completion-sound.d.ts.map +1 -0
- package/dist/core/completion-sound.js +165 -0
- package/dist/core/completion-sound.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +2 -2
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/host-resource-probe.d.ts +63 -0
- package/dist/core/host-resource-probe.d.ts.map +1 -0
- package/dist/core/host-resource-probe.js +126 -0
- package/dist/core/host-resource-probe.js.map +1 -0
- package/dist/core/host-resource-snapshot.d.ts +54 -0
- package/dist/core/host-resource-snapshot.d.ts.map +1 -0
- package/dist/core/host-resource-snapshot.js +139 -0
- package/dist/core/host-resource-snapshot.js.map +1 -0
- package/dist/core/prompt-settlement.d.ts +81 -0
- package/dist/core/prompt-settlement.d.ts.map +1 -0
- package/dist/core/prompt-settlement.js +84 -0
- package/dist/core/prompt-settlement.js.map +1 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +1 -1
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +7 -0
- package/dist/core/provider-retry.d.ts.map +1 -1
- package/dist/core/provider-retry.js +24 -0
- package/dist/core/provider-retry.js.map +1 -1
- package/dist/core/provider-usage.d.ts +15 -2
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +110 -2
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +1 -1
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/resource-admission-config.d.ts +55 -0
- package/dist/core/resource-admission-config.d.ts.map +1 -0
- package/dist/core/resource-admission-config.js +97 -0
- package/dist/core/resource-admission-config.js.map +1 -0
- package/dist/core/resource-admission.d.ts +81 -0
- package/dist/core/resource-admission.d.ts.map +1 -0
- package/dist/core/resource-admission.js +128 -0
- package/dist/core/resource-admission.js.map +1 -0
- package/dist/core/resource-governor-format.d.ts +50 -0
- package/dist/core/resource-governor-format.d.ts.map +1 -0
- package/dist/core/resource-governor-format.js +113 -0
- package/dist/core/resource-governor-format.js.map +1 -0
- package/dist/core/resource-governor-settings.d.ts +56 -0
- package/dist/core/resource-governor-settings.d.ts.map +1 -0
- package/dist/core/resource-governor-settings.js +89 -0
- package/dist/core/resource-governor-settings.js.map +1 -0
- package/dist/core/resource-observation-journal.d.ts +74 -0
- package/dist/core/resource-observation-journal.d.ts.map +1 -0
- package/dist/core/resource-observation-journal.js +198 -0
- package/dist/core/resource-observation-journal.js.map +1 -0
- package/dist/core/resource-safety-gate.d.ts +54 -0
- package/dist/core/resource-safety-gate.d.ts.map +1 -0
- package/dist/core/resource-safety-gate.js +51 -0
- package/dist/core/resource-safety-gate.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +13 -1
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/run-resource-lease.d.ts +73 -0
- package/dist/core/run-resource-lease.d.ts.map +1 -0
- package/dist/core/run-resource-lease.js +78 -0
- package/dist/core/run-resource-lease.js.map +1 -0
- package/dist/core/session-bash-service.d.ts +12 -0
- package/dist/core/session-bash-service.d.ts.map +1 -1
- package/dist/core/session-bash-service.js +14 -0
- package/dist/core/session-bash-service.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +2 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -1
- package/dist/core/session-system-prompt.js +2 -0
- package/dist/core/session-system-prompt.js.map +1 -1
- package/dist/core/session-termination.d.ts +7 -2
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +29 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-lane-launcher.d.ts +64 -0
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
- package/dist/core/subagent-lane-launcher.js +107 -0
- package/dist/core/subagent-lane-launcher.js.map +1 -0
- package/dist/core/system-cpu-sampler.d.ts +45 -0
- package/dist/core/system-cpu-sampler.d.ts.map +1 -0
- package/dist/core/system-cpu-sampler.js +95 -0
- package/dist/core/system-cpu-sampler.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -8
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +32 -0
- package/dist/core/workload-classifier.d.ts.map +1 -0
- package/dist/core/workload-classifier.js +66 -0
- package/dist/core/workload-classifier.js.map +1 -0
- package/dist/core/workload-family-matcher.d.ts +3 -0
- package/dist/core/workload-family-matcher.d.ts.map +1 -0
- package/dist/core/workload-family-matcher.js +126 -0
- package/dist/core/workload-family-matcher.js.map +1 -0
- package/dist/core/workload-permit-pool.d.ts +78 -0
- package/dist/core/workload-permit-pool.d.ts.map +1 -0
- package/dist/core/workload-permit-pool.js +138 -0
- package/dist/core/workload-permit-pool.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +68 -0
- package/dist/core/workload-shard-executor.d.ts.map +1 -0
- package/dist/core/workload-shard-executor.js +156 -0
- package/dist/core/workload-shard-executor.js.map +1 -0
- package/dist/core/workload-shard-plan.d.ts +81 -0
- package/dist/core/workload-shard-plan.d.ts.map +1 -0
- package/dist/core/workload-shard-plan.js +183 -0
- package/dist/core/workload-shard-plan.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts +11 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -0
- package/dist/core/workload-shard-runner.js +98 -0
- package/dist/core/workload-shard-runner.js.map +1 -0
- package/dist/core/workload-shard-store.d.ts +44 -0
- package/dist/core/workload-shard-store.d.ts.map +1 -0
- package/dist/core/workload-shard-store.js +189 -0
- package/dist/core/workload-shard-store.js.map +1 -0
- package/dist/core/workload-sharders.d.ts +47 -0
- package/dist/core/workload-sharders.d.ts.map +1 -0
- package/dist/core/workload-sharders.js +162 -0
- package/dist/core/workload-sharders.js.map +1 -0
- package/dist/core/workload-shell-scan.d.ts +12 -0
- package/dist/core/workload-shell-scan.d.ts.map +1 -0
- package/dist/core/workload-shell-scan.js +79 -0
- package/dist/core/workload-shell-scan.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +81 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/providers.md +1 -1
- package/docs/sdk.md +4 -0
- package/docs/settings.md +64 -2
- package/docs/skills.md +16 -0
- 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/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
- package/examples/extensions/subagent/domain-profiles.ts +0 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +7 -9
|
@@ -24,6 +24,12 @@ export interface ExecuteBashOptions {
|
|
|
24
24
|
/** Trusted internal/test override for local bash sandboxing. */
|
|
25
25
|
sandboxPolicy?: BashSandboxPreflight;
|
|
26
26
|
}
|
|
27
|
+
/** Resource gate grant (roadmap §11/M3): §11.3 blocked payload, permit release, or ungated. */
|
|
28
|
+
export interface BashResourcePermitGrant {
|
|
29
|
+
/** §11.3 structured block JSON; when set the command must not spawn. */
|
|
30
|
+
readonly blocked?: string;
|
|
31
|
+
readonly release?: () => void;
|
|
32
|
+
}
|
|
27
33
|
export interface SessionBashServiceDeps {
|
|
28
34
|
readonly settings: SettingsManager;
|
|
29
35
|
readonly runtime: SessionBashRuntime;
|
|
@@ -37,6 +43,12 @@ export interface SessionBashServiceDeps {
|
|
|
37
43
|
readonly isStreaming: () => boolean;
|
|
38
44
|
readonly pushMessage: (message: BashExecutionMessage) => void;
|
|
39
45
|
readonly appendMessage: (message: BashExecutionMessage) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Optional resource governor gate, invoked AFTER command safety (§9.4
|
|
48
|
+
* order) and before any shell spawns. The abort signal cancels a queued
|
|
49
|
+
* permit wait. Implementations must never throw.
|
|
50
|
+
*/
|
|
51
|
+
readonly acquireResourcePermit?: (command: string, signal: AbortSignal) => Promise<BashResourcePermitGrant>;
|
|
40
52
|
}
|
|
41
53
|
export declare class SessionBashService {
|
|
42
54
|
private abortController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-bash-service.d.ts","sourceRoot":"","sources":["../../src/core/session-bash-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,oBAAoB,CAAC;AAOhF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,oBAAoB,EAA6B,MAAM,iBAAiB,CAAC;AAE5G,MAAM,WAAW,kBAAkB;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8FAA8F;IAC9F,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gEAAgE;IAChE,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACrC;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,uBAAuB,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9F,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAChE;AAUD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,YAAY,IAAI,EAAE,sBAAsB,EAEvC;IAEK,WAAW,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,UAAU,CAAC,CAoFrB;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAoBtG;IAED,mCAAmC;IACnC,SAAS,IAAI,IAAI,CAEhB;IAED,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,IAAI,sBAAsB,IAAI,OAAO,CAEpC;IAED,+EAA+E;IAC/E,YAAY,IAAI,IAAI,CAOnB;CACD","sourcesContent":["/**\n * Session bash service: command execution, result recording, abort, and the\n * streaming-deferral queue. Extracted from AgentSession so the session class\n * coordinates lifecycle while this owns the bash surface end-to-end.\n *\n * Ordering contract (preserved from the session implementation):\n * - results recorded while the agent is streaming are queued and flushed on\n * turn end (`flushPending`) so tool_use/tool_result ordering never breaks;\n * - `recordBashResult` is idempotent-safe to call from executeBash and from\n * extensions that run bash themselves.\n */\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport { classifyShellCommand } from \"./command-safety.ts\";\nimport {\n\tbuildBlockedBashResult,\n\tevaluateCommandGate,\n\tisCommandSafetyAssumeYesEnabled,\n} from \"./extensions/builtin/command-safety-gate.ts\";\nimport type { LoadoutAccessPolicy } from \"./loadout-access-policy.ts\";\nimport { assertLoadoutAccess, decideLoadoutAccess } from \"./loadout-access-policy.ts\";\nimport type { BashExecutionMessage } from \"./messages.ts\";\nimport type { SessionBashRuntime } from \"./session-bash-runtime.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, type BashSandboxPreflight, createLocalBashOperations } from \"./tools/bash.ts\";\n\nexport interface ExecuteBashOptions {\n\texcludeFromContext?: boolean;\n\t/** Optional identifier echoed in `bash_execution_update` events (RPC request correlation). */\n\tid?: string;\n\toperations?: BashOperations;\n\tsafetyGate?: \"headless\";\n\t/** Trusted internal/test override for local bash sandboxing. */\n\tsandboxPolicy?: BashSandboxPreflight;\n}\n\nexport interface SessionBashServiceDeps {\n\treadonly settings: SettingsManager;\n\treadonly runtime: SessionBashRuntime;\n\treadonly loadoutPolicy: LoadoutAccessPolicy | undefined;\n\treadonly getCwd: () => string;\n\treadonly emit: (event: { type: \"bash_execution_update\"; id?: string; delta: string }) => void;\n\treadonly isStreaming: () => boolean;\n\treadonly pushMessage: (message: BashExecutionMessage) => void;\n\treadonly appendMessage: (message: BashExecutionMessage) => void;\n}\n\nfunction isSandboxDeniedError(error: unknown): error is Error {\n\treturn error instanceof Error && error.message.startsWith(\"sandbox: shell denied\");\n}\n\nfunction buildSandboxDeniedBashResult(reason: string): BashResult {\n\treturn { output: reason, exitCode: 1, cancelled: false, truncated: false };\n}\n\nexport class SessionBashService {\n\tprivate abortController: AbortController | undefined;\n\tprivate pendingBashMessages: BashExecutionMessage[] = [];\n\tprivate readonly deps: SessionBashServiceDeps;\n\n\tconstructor(deps: SessionBashServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: ExecuteBashOptions,\n\t): Promise<BashResult> {\n\t\tconst { settings, runtime, loadoutPolicy } = this.deps;\n\t\tconst prefix = settings.getShellCommandPrefix();\n\t\tconst shellPath = settings.getShellPath();\n\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\t\tif (loadoutPolicy) {\n\t\t\tassertLoadoutAccess((request) => decideLoadoutAccess(loadoutPolicy, request), {\n\t\t\t\toperation: \"execute\",\n\t\t\t\ttoolName: \"bash\",\n\t\t\t\tcommand: resolvedCommand,\n\t\t\t});\n\t\t}\n\n\t\t// Non-negotiable safety floor for headless callers (RPC bash): hard-deny\n\t\t// block-tier commands before any shell is spawned.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst floorVerdict = classifyShellCommand(command);\n\t\t\tif (floorVerdict.risk === \"block\") {\n\t\t\t\tthrow new Error(`OMK §0.1 safety floor blocked bash: [${floorVerdict.rule}] ${floorVerdict.reason}`);\n\t\t\t}\n\t\t}\n\n\t\t// Command-safety parity for non-interactive callers (RPC bash). Interactive\n\t\t// `!`/`!!` bash is gated earlier through the user_bash extension event, which\n\t\t// keeps its prompt-based approval semantics, so it does not pass safetyGate and\n\t\t// is never double-prompted here. confirm/block-tier verdicts deny headlessly;\n\t\t// the EFFECTIVE command (after the shell command prefix) is classified.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst decision = await evaluateCommandGate(resolvedCommand, {\n\t\t\t\thasUI: false,\n\t\t\t\theadlessConfirmPolicy: isCommandSafetyAssumeYesEnabled() ? \"allow\" : \"deny\",\n\t\t\t});\n\t\t\tif (decision?.deny) {\n\t\t\t\tconst blocked = buildBlockedBashResult(decision.reason);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t}\n\n\t\tthis.abortController = new AbortController();\n\n\t\ttry {\n\t\t\ttry {\n\t\t\t\tconst operations =\n\t\t\t\t\toptions?.operations ??\n\t\t\t\t\tcreateLocalBashOperations({\n\t\t\t\t\t\tshellPath,\n\t\t\t\t\t\tsandboxPolicy: runtime.sandboxPreflight(options?.sandboxPolicy),\n\t\t\t\t\t});\n\t\t\t\tconst cwd = this.deps.getCwd();\n\t\t\t\tconst onChunkWrapped = (delta: string) => {\n\t\t\t\t\tonChunk?.(delta);\n\t\t\t\t\tthis.deps.emit({ type: \"bash_execution_update\", id: options?.id, delta });\n\t\t\t\t};\n\n\t\t\t\t// Default-on verified path when a session ledger exists; OMK_VERIFIED_BASH=0 rolls back.\n\t\t\t\tconst verified = await runtime.executeVerified({\n\t\t\t\t\tresolvedCommand,\n\t\t\t\t\tcwd,\n\t\t\t\t\tshellPath,\n\t\t\t\t\toperations,\n\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t});\n\t\t\t\tconst result: BashResult =\n\t\t\t\t\tverified ??\n\t\t\t\t\t(await executeBashWithOperations(resolvedCommand, cwd, operations, {\n\t\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\t}));\n\n\t\t\t\tthis.recordBashResult(command, result, options);\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isSandboxDeniedError(error)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tconst blocked = buildSandboxDeniedBashResult(error.message);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.abortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\tthis.pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/** Cancel running bash command. */\n\tabortBash(): void {\n\t\tthis.abortController?.abort();\n\t}\n\n\tget isBashRunning(): boolean {\n\t\treturn this.abortController !== undefined;\n\t}\n\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this.pendingBashMessages.length > 0;\n\t}\n\n\t/** Flush queued results after the agent turn completes (ordering contract). */\n\tflushPending(): void {\n\t\tif (this.pendingBashMessages.length === 0) return;\n\t\tfor (const bashMessage of this.pendingBashMessages) {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t\tthis.pendingBashMessages = [];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-bash-service.d.ts","sourceRoot":"","sources":["../../src/core/session-bash-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,oBAAoB,CAAC;AAOhF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,oBAAoB,EAA6B,MAAM,iBAAiB,CAAC;AAE5G,MAAM,WAAW,kBAAkB;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8FAA8F;IAC9F,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gEAAgE;IAChE,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACrC;AAED,iGAA+F;AAC/F,MAAM,WAAW,uBAAuB;IACvC,yEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,uBAAuB,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9F,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChE;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC5G;AAUD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,YAAY,IAAI,EAAE,sBAAsB,EAEvC;IAEK,WAAW,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,UAAU,CAAC,CAkGrB;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAoBtG;IAED,mCAAmC;IACnC,SAAS,IAAI,IAAI,CAEhB;IAED,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,IAAI,sBAAsB,IAAI,OAAO,CAEpC;IAED,+EAA+E;IAC/E,YAAY,IAAI,IAAI,CAOnB;CACD","sourcesContent":["/**\n * Session bash service: command execution, result recording, abort, and the\n * streaming-deferral queue. Extracted from AgentSession so the session class\n * coordinates lifecycle while this owns the bash surface end-to-end.\n *\n * Ordering contract (preserved from the session implementation):\n * - results recorded while the agent is streaming are queued and flushed on\n * turn end (`flushPending`) so tool_use/tool_result ordering never breaks;\n * - `recordBashResult` is idempotent-safe to call from executeBash and from\n * extensions that run bash themselves.\n */\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport { classifyShellCommand } from \"./command-safety.ts\";\nimport {\n\tbuildBlockedBashResult,\n\tevaluateCommandGate,\n\tisCommandSafetyAssumeYesEnabled,\n} from \"./extensions/builtin/command-safety-gate.ts\";\nimport type { LoadoutAccessPolicy } from \"./loadout-access-policy.ts\";\nimport { assertLoadoutAccess, decideLoadoutAccess } from \"./loadout-access-policy.ts\";\nimport type { BashExecutionMessage } from \"./messages.ts\";\nimport type { SessionBashRuntime } from \"./session-bash-runtime.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, type BashSandboxPreflight, createLocalBashOperations } from \"./tools/bash.ts\";\n\nexport interface ExecuteBashOptions {\n\texcludeFromContext?: boolean;\n\t/** Optional identifier echoed in `bash_execution_update` events (RPC request correlation). */\n\tid?: string;\n\toperations?: BashOperations;\n\tsafetyGate?: \"headless\";\n\t/** Trusted internal/test override for local bash sandboxing. */\n\tsandboxPolicy?: BashSandboxPreflight;\n}\n\n/** Resource gate grant (roadmap §11/M3): §11.3 blocked payload, permit release, or ungated. */\nexport interface BashResourcePermitGrant {\n\t/** §11.3 structured block JSON; when set the command must not spawn. */\n\treadonly blocked?: string;\n\treadonly release?: () => void;\n}\n\nexport interface SessionBashServiceDeps {\n\treadonly settings: SettingsManager;\n\treadonly runtime: SessionBashRuntime;\n\treadonly loadoutPolicy: LoadoutAccessPolicy | undefined;\n\treadonly getCwd: () => string;\n\treadonly emit: (event: { type: \"bash_execution_update\"; id?: string; delta: string }) => void;\n\treadonly isStreaming: () => boolean;\n\treadonly pushMessage: (message: BashExecutionMessage) => void;\n\treadonly appendMessage: (message: BashExecutionMessage) => void;\n\t/**\n\t * Optional resource governor gate, invoked AFTER command safety (§9.4\n\t * order) and before any shell spawns. The abort signal cancels a queued\n\t * permit wait. Implementations must never throw.\n\t */\n\treadonly acquireResourcePermit?: (command: string, signal: AbortSignal) => Promise<BashResourcePermitGrant>;\n}\n\nfunction isSandboxDeniedError(error: unknown): error is Error {\n\treturn error instanceof Error && error.message.startsWith(\"sandbox: shell denied\");\n}\n\nfunction buildSandboxDeniedBashResult(reason: string): BashResult {\n\treturn { output: reason, exitCode: 1, cancelled: false, truncated: false };\n}\n\nexport class SessionBashService {\n\tprivate abortController: AbortController | undefined;\n\tprivate pendingBashMessages: BashExecutionMessage[] = [];\n\tprivate readonly deps: SessionBashServiceDeps;\n\n\tconstructor(deps: SessionBashServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: ExecuteBashOptions,\n\t): Promise<BashResult> {\n\t\tconst { settings, runtime, loadoutPolicy } = this.deps;\n\t\tconst prefix = settings.getShellCommandPrefix();\n\t\tconst shellPath = settings.getShellPath();\n\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\t\tif (loadoutPolicy) {\n\t\t\tassertLoadoutAccess((request) => decideLoadoutAccess(loadoutPolicy, request), {\n\t\t\t\toperation: \"execute\",\n\t\t\t\ttoolName: \"bash\",\n\t\t\t\tcommand: resolvedCommand,\n\t\t\t});\n\t\t}\n\n\t\t// Non-negotiable safety floor for headless callers (RPC bash): hard-deny\n\t\t// block-tier commands before any shell is spawned.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst floorVerdict = classifyShellCommand(command);\n\t\t\tif (floorVerdict.risk === \"block\") {\n\t\t\t\tthrow new Error(`OMK §0.1 safety floor blocked bash: [${floorVerdict.rule}] ${floorVerdict.reason}`);\n\t\t\t}\n\t\t}\n\n\t\t// Command-safety parity for non-interactive callers (RPC bash). Interactive\n\t\t// `!`/`!!` bash is gated earlier through the user_bash extension event, which\n\t\t// keeps its prompt-based approval semantics, so it does not pass safetyGate and\n\t\t// is never double-prompted here. confirm/block-tier verdicts deny headlessly;\n\t\t// the EFFECTIVE command (after the shell command prefix) is classified.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst decision = await evaluateCommandGate(resolvedCommand, {\n\t\t\t\thasUI: false,\n\t\t\t\theadlessConfirmPolicy: isCommandSafetyAssumeYesEnabled() ? \"allow\" : \"deny\",\n\t\t\t});\n\t\t\tif (decision?.deny) {\n\t\t\t\tconst blocked = buildBlockedBashResult(decision.reason);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t}\n\n\t\tthis.abortController = new AbortController();\n\t\tlet releaseResourcePermit: (() => void) | undefined;\n\n\t\ttry {\n\t\t\t// Resource safety gate (§9.4: command safety above stays authoritative;\n\t\t\t// pressure can only throttle or defer an already-allowed command).\n\t\t\tif (this.deps.acquireResourcePermit) {\n\t\t\t\tconst grant = await this.deps.acquireResourcePermit(resolvedCommand, this.abortController.signal);\n\t\t\t\tif (grant.blocked !== undefined) {\n\t\t\t\t\tconst blocked = buildBlockedBashResult(grant.blocked);\n\t\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\t\treturn blocked;\n\t\t\t\t}\n\t\t\t\treleaseResourcePermit = grant.release;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst operations =\n\t\t\t\t\toptions?.operations ??\n\t\t\t\t\tcreateLocalBashOperations({\n\t\t\t\t\t\tshellPath,\n\t\t\t\t\t\tsandboxPolicy: runtime.sandboxPreflight(options?.sandboxPolicy),\n\t\t\t\t\t});\n\t\t\t\tconst cwd = this.deps.getCwd();\n\t\t\t\tconst onChunkWrapped = (delta: string) => {\n\t\t\t\t\tonChunk?.(delta);\n\t\t\t\t\tthis.deps.emit({ type: \"bash_execution_update\", id: options?.id, delta });\n\t\t\t\t};\n\n\t\t\t\t// Default-on verified path when a session ledger exists; OMK_VERIFIED_BASH=0 rolls back.\n\t\t\t\tconst verified = await runtime.executeVerified({\n\t\t\t\t\tresolvedCommand,\n\t\t\t\t\tcwd,\n\t\t\t\t\tshellPath,\n\t\t\t\t\toperations,\n\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t});\n\t\t\t\tconst result: BashResult =\n\t\t\t\t\tverified ??\n\t\t\t\t\t(await executeBashWithOperations(resolvedCommand, cwd, operations, {\n\t\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\t}));\n\n\t\t\t\tthis.recordBashResult(command, result, options);\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isSandboxDeniedError(error)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tconst blocked = buildSandboxDeniedBashResult(error.message);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t} finally {\n\t\t\t// §10.3 exactly-once: the pool treats a repeated release as a no-op.\n\t\t\treleaseResourcePermit?.();\n\t\t\tthis.abortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\tthis.pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/** Cancel running bash command. */\n\tabortBash(): void {\n\t\tthis.abortController?.abort();\n\t}\n\n\tget isBashRunning(): boolean {\n\t\treturn this.abortController !== undefined;\n\t}\n\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this.pendingBashMessages.length > 0;\n\t}\n\n\t/** Flush queued results after the agent turn completes (ordering contract). */\n\tflushPending(): void {\n\t\tif (this.pendingBashMessages.length === 0) return;\n\t\tfor (const bashMessage of this.pendingBashMessages) {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t\tthis.pendingBashMessages = [];\n\t}\n}\n"]}
|
|
@@ -64,7 +64,19 @@ export class SessionBashService {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
this.abortController = new AbortController();
|
|
67
|
+
let releaseResourcePermit;
|
|
67
68
|
try {
|
|
69
|
+
// Resource safety gate (§9.4: command safety above stays authoritative;
|
|
70
|
+
// pressure can only throttle or defer an already-allowed command).
|
|
71
|
+
if (this.deps.acquireResourcePermit) {
|
|
72
|
+
const grant = await this.deps.acquireResourcePermit(resolvedCommand, this.abortController.signal);
|
|
73
|
+
if (grant.blocked !== undefined) {
|
|
74
|
+
const blocked = buildBlockedBashResult(grant.blocked);
|
|
75
|
+
this.recordBashResult(command, blocked, options);
|
|
76
|
+
return blocked;
|
|
77
|
+
}
|
|
78
|
+
releaseResourcePermit = grant.release;
|
|
79
|
+
}
|
|
68
80
|
try {
|
|
69
81
|
const operations = options?.operations ??
|
|
70
82
|
createLocalBashOperations({
|
|
@@ -103,6 +115,8 @@ export class SessionBashService {
|
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
finally {
|
|
118
|
+
// §10.3 exactly-once: the pool treats a repeated release as a no-op.
|
|
119
|
+
releaseResourcePermit?.();
|
|
106
120
|
this.abortController = undefined;
|
|
107
121
|
}
|
|
108
122
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-bash-service.js","sourceRoot":"","sources":["../../src/core/session-bash-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session bash service: command execution, result recording, abort, and the\n * streaming-deferral queue. Extracted from AgentSession so the session class\n * coordinates lifecycle while this owns the bash surface end-to-end.\n *\n * Ordering contract (preserved from the session implementation):\n * - results recorded while the agent is streaming are queued and flushed on\n * turn end (`flushPending`) so tool_use/tool_result ordering never breaks;\n * - `recordBashResult` is idempotent-safe to call from executeBash and from\n * extensions that run bash themselves.\n */\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport { classifyShellCommand } from \"./command-safety.ts\";\nimport {\n\tbuildBlockedBashResult,\n\tevaluateCommandGate,\n\tisCommandSafetyAssumeYesEnabled,\n} from \"./extensions/builtin/command-safety-gate.ts\";\nimport type { LoadoutAccessPolicy } from \"./loadout-access-policy.ts\";\nimport { assertLoadoutAccess, decideLoadoutAccess } from \"./loadout-access-policy.ts\";\nimport type { BashExecutionMessage } from \"./messages.ts\";\nimport type { SessionBashRuntime } from \"./session-bash-runtime.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, type BashSandboxPreflight, createLocalBashOperations } from \"./tools/bash.ts\";\n\nexport interface ExecuteBashOptions {\n\texcludeFromContext?: boolean;\n\t/** Optional identifier echoed in `bash_execution_update` events (RPC request correlation). */\n\tid?: string;\n\toperations?: BashOperations;\n\tsafetyGate?: \"headless\";\n\t/** Trusted internal/test override for local bash sandboxing. */\n\tsandboxPolicy?: BashSandboxPreflight;\n}\n\nexport interface SessionBashServiceDeps {\n\treadonly settings: SettingsManager;\n\treadonly runtime: SessionBashRuntime;\n\treadonly loadoutPolicy: LoadoutAccessPolicy | undefined;\n\treadonly getCwd: () => string;\n\treadonly emit: (event: { type: \"bash_execution_update\"; id?: string; delta: string }) => void;\n\treadonly isStreaming: () => boolean;\n\treadonly pushMessage: (message: BashExecutionMessage) => void;\n\treadonly appendMessage: (message: BashExecutionMessage) => void;\n}\n\nfunction isSandboxDeniedError(error: unknown): error is Error {\n\treturn error instanceof Error && error.message.startsWith(\"sandbox: shell denied\");\n}\n\nfunction buildSandboxDeniedBashResult(reason: string): BashResult {\n\treturn { output: reason, exitCode: 1, cancelled: false, truncated: false };\n}\n\nexport class SessionBashService {\n\tprivate abortController: AbortController | undefined;\n\tprivate pendingBashMessages: BashExecutionMessage[] = [];\n\tprivate readonly deps: SessionBashServiceDeps;\n\n\tconstructor(deps: SessionBashServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: ExecuteBashOptions,\n\t): Promise<BashResult> {\n\t\tconst { settings, runtime, loadoutPolicy } = this.deps;\n\t\tconst prefix = settings.getShellCommandPrefix();\n\t\tconst shellPath = settings.getShellPath();\n\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\t\tif (loadoutPolicy) {\n\t\t\tassertLoadoutAccess((request) => decideLoadoutAccess(loadoutPolicy, request), {\n\t\t\t\toperation: \"execute\",\n\t\t\t\ttoolName: \"bash\",\n\t\t\t\tcommand: resolvedCommand,\n\t\t\t});\n\t\t}\n\n\t\t// Non-negotiable safety floor for headless callers (RPC bash): hard-deny\n\t\t// block-tier commands before any shell is spawned.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst floorVerdict = classifyShellCommand(command);\n\t\t\tif (floorVerdict.risk === \"block\") {\n\t\t\t\tthrow new Error(`OMK §0.1 safety floor blocked bash: [${floorVerdict.rule}] ${floorVerdict.reason}`);\n\t\t\t}\n\t\t}\n\n\t\t// Command-safety parity for non-interactive callers (RPC bash). Interactive\n\t\t// `!`/`!!` bash is gated earlier through the user_bash extension event, which\n\t\t// keeps its prompt-based approval semantics, so it does not pass safetyGate and\n\t\t// is never double-prompted here. confirm/block-tier verdicts deny headlessly;\n\t\t// the EFFECTIVE command (after the shell command prefix) is classified.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst decision = await evaluateCommandGate(resolvedCommand, {\n\t\t\t\thasUI: false,\n\t\t\t\theadlessConfirmPolicy: isCommandSafetyAssumeYesEnabled() ? \"allow\" : \"deny\",\n\t\t\t});\n\t\t\tif (decision?.deny) {\n\t\t\t\tconst blocked = buildBlockedBashResult(decision.reason);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t}\n\n\t\tthis.abortController = new AbortController();\n\n\t\ttry {\n\t\t\ttry {\n\t\t\t\tconst operations =\n\t\t\t\t\toptions?.operations ??\n\t\t\t\t\tcreateLocalBashOperations({\n\t\t\t\t\t\tshellPath,\n\t\t\t\t\t\tsandboxPolicy: runtime.sandboxPreflight(options?.sandboxPolicy),\n\t\t\t\t\t});\n\t\t\t\tconst cwd = this.deps.getCwd();\n\t\t\t\tconst onChunkWrapped = (delta: string) => {\n\t\t\t\t\tonChunk?.(delta);\n\t\t\t\t\tthis.deps.emit({ type: \"bash_execution_update\", id: options?.id, delta });\n\t\t\t\t};\n\n\t\t\t\t// Default-on verified path when a session ledger exists; OMK_VERIFIED_BASH=0 rolls back.\n\t\t\t\tconst verified = await runtime.executeVerified({\n\t\t\t\t\tresolvedCommand,\n\t\t\t\t\tcwd,\n\t\t\t\t\tshellPath,\n\t\t\t\t\toperations,\n\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t});\n\t\t\t\tconst result: BashResult =\n\t\t\t\t\tverified ??\n\t\t\t\t\t(await executeBashWithOperations(resolvedCommand, cwd, operations, {\n\t\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\t}));\n\n\t\t\t\tthis.recordBashResult(command, result, options);\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isSandboxDeniedError(error)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tconst blocked = buildSandboxDeniedBashResult(error.message);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.abortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\tthis.pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/** Cancel running bash command. */\n\tabortBash(): void {\n\t\tthis.abortController?.abort();\n\t}\n\n\tget isBashRunning(): boolean {\n\t\treturn this.abortController !== undefined;\n\t}\n\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this.pendingBashMessages.length > 0;\n\t}\n\n\t/** Flush queued results after the agent turn completes (ordering contract). */\n\tflushPending(): void {\n\t\tif (this.pendingBashMessages.length === 0) return;\n\t\tfor (const bashMessage of this.pendingBashMessages) {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t\tthis.pendingBashMessages = [];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-bash-service.js","sourceRoot":"","sources":["../../src/core/session-bash-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session bash service: command execution, result recording, abort, and the\n * streaming-deferral queue. Extracted from AgentSession so the session class\n * coordinates lifecycle while this owns the bash surface end-to-end.\n *\n * Ordering contract (preserved from the session implementation):\n * - results recorded while the agent is streaming are queued and flushed on\n * turn end (`flushPending`) so tool_use/tool_result ordering never breaks;\n * - `recordBashResult` is idempotent-safe to call from executeBash and from\n * extensions that run bash themselves.\n */\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport { classifyShellCommand } from \"./command-safety.ts\";\nimport {\n\tbuildBlockedBashResult,\n\tevaluateCommandGate,\n\tisCommandSafetyAssumeYesEnabled,\n} from \"./extensions/builtin/command-safety-gate.ts\";\nimport type { LoadoutAccessPolicy } from \"./loadout-access-policy.ts\";\nimport { assertLoadoutAccess, decideLoadoutAccess } from \"./loadout-access-policy.ts\";\nimport type { BashExecutionMessage } from \"./messages.ts\";\nimport type { SessionBashRuntime } from \"./session-bash-runtime.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, type BashSandboxPreflight, createLocalBashOperations } from \"./tools/bash.ts\";\n\nexport interface ExecuteBashOptions {\n\texcludeFromContext?: boolean;\n\t/** Optional identifier echoed in `bash_execution_update` events (RPC request correlation). */\n\tid?: string;\n\toperations?: BashOperations;\n\tsafetyGate?: \"headless\";\n\t/** Trusted internal/test override for local bash sandboxing. */\n\tsandboxPolicy?: BashSandboxPreflight;\n}\n\n/** Resource gate grant (roadmap §11/M3): §11.3 blocked payload, permit release, or ungated. */\nexport interface BashResourcePermitGrant {\n\t/** §11.3 structured block JSON; when set the command must not spawn. */\n\treadonly blocked?: string;\n\treadonly release?: () => void;\n}\n\nexport interface SessionBashServiceDeps {\n\treadonly settings: SettingsManager;\n\treadonly runtime: SessionBashRuntime;\n\treadonly loadoutPolicy: LoadoutAccessPolicy | undefined;\n\treadonly getCwd: () => string;\n\treadonly emit: (event: { type: \"bash_execution_update\"; id?: string; delta: string }) => void;\n\treadonly isStreaming: () => boolean;\n\treadonly pushMessage: (message: BashExecutionMessage) => void;\n\treadonly appendMessage: (message: BashExecutionMessage) => void;\n\t/**\n\t * Optional resource governor gate, invoked AFTER command safety (§9.4\n\t * order) and before any shell spawns. The abort signal cancels a queued\n\t * permit wait. Implementations must never throw.\n\t */\n\treadonly acquireResourcePermit?: (command: string, signal: AbortSignal) => Promise<BashResourcePermitGrant>;\n}\n\nfunction isSandboxDeniedError(error: unknown): error is Error {\n\treturn error instanceof Error && error.message.startsWith(\"sandbox: shell denied\");\n}\n\nfunction buildSandboxDeniedBashResult(reason: string): BashResult {\n\treturn { output: reason, exitCode: 1, cancelled: false, truncated: false };\n}\n\nexport class SessionBashService {\n\tprivate abortController: AbortController | undefined;\n\tprivate pendingBashMessages: BashExecutionMessage[] = [];\n\tprivate readonly deps: SessionBashServiceDeps;\n\n\tconstructor(deps: SessionBashServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: ExecuteBashOptions,\n\t): Promise<BashResult> {\n\t\tconst { settings, runtime, loadoutPolicy } = this.deps;\n\t\tconst prefix = settings.getShellCommandPrefix();\n\t\tconst shellPath = settings.getShellPath();\n\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\t\tif (loadoutPolicy) {\n\t\t\tassertLoadoutAccess((request) => decideLoadoutAccess(loadoutPolicy, request), {\n\t\t\t\toperation: \"execute\",\n\t\t\t\ttoolName: \"bash\",\n\t\t\t\tcommand: resolvedCommand,\n\t\t\t});\n\t\t}\n\n\t\t// Non-negotiable safety floor for headless callers (RPC bash): hard-deny\n\t\t// block-tier commands before any shell is spawned.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst floorVerdict = classifyShellCommand(command);\n\t\t\tif (floorVerdict.risk === \"block\") {\n\t\t\t\tthrow new Error(`OMK §0.1 safety floor blocked bash: [${floorVerdict.rule}] ${floorVerdict.reason}`);\n\t\t\t}\n\t\t}\n\n\t\t// Command-safety parity for non-interactive callers (RPC bash). Interactive\n\t\t// `!`/`!!` bash is gated earlier through the user_bash extension event, which\n\t\t// keeps its prompt-based approval semantics, so it does not pass safetyGate and\n\t\t// is never double-prompted here. confirm/block-tier verdicts deny headlessly;\n\t\t// the EFFECTIVE command (after the shell command prefix) is classified.\n\t\tif (options?.safetyGate === \"headless\") {\n\t\t\tconst decision = await evaluateCommandGate(resolvedCommand, {\n\t\t\t\thasUI: false,\n\t\t\t\theadlessConfirmPolicy: isCommandSafetyAssumeYesEnabled() ? \"allow\" : \"deny\",\n\t\t\t});\n\t\t\tif (decision?.deny) {\n\t\t\t\tconst blocked = buildBlockedBashResult(decision.reason);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t}\n\n\t\tthis.abortController = new AbortController();\n\t\tlet releaseResourcePermit: (() => void) | undefined;\n\n\t\ttry {\n\t\t\t// Resource safety gate (§9.4: command safety above stays authoritative;\n\t\t\t// pressure can only throttle or defer an already-allowed command).\n\t\t\tif (this.deps.acquireResourcePermit) {\n\t\t\t\tconst grant = await this.deps.acquireResourcePermit(resolvedCommand, this.abortController.signal);\n\t\t\t\tif (grant.blocked !== undefined) {\n\t\t\t\t\tconst blocked = buildBlockedBashResult(grant.blocked);\n\t\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\t\treturn blocked;\n\t\t\t\t}\n\t\t\t\treleaseResourcePermit = grant.release;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst operations =\n\t\t\t\t\toptions?.operations ??\n\t\t\t\t\tcreateLocalBashOperations({\n\t\t\t\t\t\tshellPath,\n\t\t\t\t\t\tsandboxPolicy: runtime.sandboxPreflight(options?.sandboxPolicy),\n\t\t\t\t\t});\n\t\t\t\tconst cwd = this.deps.getCwd();\n\t\t\t\tconst onChunkWrapped = (delta: string) => {\n\t\t\t\t\tonChunk?.(delta);\n\t\t\t\t\tthis.deps.emit({ type: \"bash_execution_update\", id: options?.id, delta });\n\t\t\t\t};\n\n\t\t\t\t// Default-on verified path when a session ledger exists; OMK_VERIFIED_BASH=0 rolls back.\n\t\t\t\tconst verified = await runtime.executeVerified({\n\t\t\t\t\tresolvedCommand,\n\t\t\t\t\tcwd,\n\t\t\t\t\tshellPath,\n\t\t\t\t\toperations,\n\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t});\n\t\t\t\tconst result: BashResult =\n\t\t\t\t\tverified ??\n\t\t\t\t\t(await executeBashWithOperations(resolvedCommand, cwd, operations, {\n\t\t\t\t\t\tonChunk: onChunkWrapped,\n\t\t\t\t\t\tsignal: this.abortController.signal,\n\t\t\t\t\t}));\n\n\t\t\t\tthis.recordBashResult(command, result, options);\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isSandboxDeniedError(error)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tconst blocked = buildSandboxDeniedBashResult(error.message);\n\t\t\t\tthis.recordBashResult(command, blocked, options);\n\t\t\t\treturn blocked;\n\t\t\t}\n\t\t} finally {\n\t\t\t// §10.3 exactly-once: the pool treats a repeated release as a no-op.\n\t\t\treleaseResourcePermit?.();\n\t\t\tthis.abortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\tthis.pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/** Cancel running bash command. */\n\tabortBash(): void {\n\t\tthis.abortController?.abort();\n\t}\n\n\tget isBashRunning(): boolean {\n\t\treturn this.abortController !== undefined;\n\t}\n\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this.pendingBashMessages.length > 0;\n\t}\n\n\t/** Flush queued results after the agent turn completes (ordering contract). */\n\tflushPending(): void {\n\t\tif (this.pendingBashMessages.length === 0) return;\n\t\tfor (const bashMessage of this.pendingBashMessages) {\n\t\t\tthis.deps.pushMessage(bashMessage);\n\t\t\tthis.deps.appendMessage(bashMessage);\n\t\t}\n\t\tthis.pendingBashMessages = [];\n\t}\n}\n"]}
|
|
@@ -11,6 +11,8 @@ export interface SessionSystemPromptInput {
|
|
|
11
11
|
readonly appendSystemPrompt?: readonly string[];
|
|
12
12
|
readonly providerAppend?: string;
|
|
13
13
|
readonly skills: readonly Skill[];
|
|
14
|
+
readonly activeSkillNames?: readonly string[];
|
|
15
|
+
readonly activeSkillSource?: string;
|
|
14
16
|
readonly contextFiles: readonly ContextFile[];
|
|
15
17
|
readonly contextBudget?: BuildSystemPromptOptions["contextBudget"];
|
|
16
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/session-system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,wBAAwB,EAAiD,MAAM,oBAAoB,CAAC;AAElH,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC,eAAe,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,wBAAwB,GAAG,2BAA2B,
|
|
1
|
+
{"version":3,"file":"session-system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/session-system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,wBAAwB,EAAiD,MAAM,oBAAoB,CAAC;AAElH,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC,eAAe,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,wBAAwB,GAAG,2BAA2B,CA8BxG","sourcesContent":["import type { ContextFile } from \"./resource-loader.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { type BuildSystemPromptOptions, type BuiltSystemPrompt, buildSystemPromptPlan } from \"./system-prompt.ts\";\n\nexport interface SessionSystemPromptInput {\n\treadonly cwd: string;\n\treadonly toolNames: readonly string[];\n\treadonly hasTool: (name: string) => boolean;\n\treadonly toolPromptSnippets: ReadonlyMap<string, string>;\n\treadonly toolPromptGuidelines: ReadonlyMap<string, readonly string[]>;\n\treadonly customPrompt?: string;\n\treadonly appendSystemPrompt?: readonly string[];\n\treadonly providerAppend?: string;\n\treadonly skills: readonly Skill[];\n\treadonly activeSkillNames?: readonly string[];\n\treadonly activeSkillSource?: string;\n\treadonly contextFiles: readonly ContextFile[];\n\treadonly contextBudget?: BuildSystemPromptOptions[\"contextBudget\"];\n}\n\nexport interface SessionSystemPromptAssembly {\n\treadonly options: BuildSystemPromptOptions;\n\treadonly prompt: string;\n\treadonly cacheBoundary: number;\n}\n\n/**\n * Pure assembly of the session system-prompt plan: tool filtering, snippet and\n * guideline collection, and append-section joining. Callers resolve side effects\n * (resource loader reads, provider playbook files) and pass them in.\n */\nexport function assembleSessionSystemPrompt(input: SessionSystemPromptInput): SessionSystemPromptAssembly {\n\tconst validToolNames = input.toolNames.filter((name) => input.hasTool(name));\n\tconst toolSnippets: Record<string, string> = {};\n\tconst promptGuidelines: string[] = [];\n\tfor (const name of validToolNames) {\n\t\tconst snippet = input.toolPromptSnippets.get(name);\n\t\tif (snippet) toolSnippets[name] = snippet;\n\t\tconst toolGuidelines = input.toolPromptGuidelines.get(name);\n\t\tif (toolGuidelines) promptGuidelines.push(...toolGuidelines);\n\t}\n\n\tconst appendParts = [...(input.appendSystemPrompt ?? [])];\n\tif (input.providerAppend) appendParts.push(input.providerAppend);\n\tconst appendSystemPrompt = appendParts.length > 0 ? appendParts.join(\"\\n\\n\") : undefined;\n\n\tconst options: BuildSystemPromptOptions = {\n\t\tcwd: input.cwd,\n\t\tskills: [...input.skills],\n\t\tactiveSkillNames: input.activeSkillNames ? [...input.activeSkillNames] : undefined,\n\t\tactiveSkillSource: input.activeSkillSource,\n\t\tcontextFiles: [...input.contextFiles],\n\t\tcustomPrompt: input.customPrompt,\n\t\tappendSystemPrompt,\n\t\tselectedTools: validToolNames,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tcontextBudget: input.contextBudget,\n\t};\n\tconst built: BuiltSystemPrompt = buildSystemPromptPlan(options);\n\treturn { options, prompt: built.prompt, cacheBoundary: built.cacheBoundary };\n}\n"]}
|
|
@@ -23,6 +23,8 @@ export function assembleSessionSystemPrompt(input) {
|
|
|
23
23
|
const options = {
|
|
24
24
|
cwd: input.cwd,
|
|
25
25
|
skills: [...input.skills],
|
|
26
|
+
activeSkillNames: input.activeSkillNames ? [...input.activeSkillNames] : undefined,
|
|
27
|
+
activeSkillSource: input.activeSkillSource,
|
|
26
28
|
contextFiles: [...input.contextFiles],
|
|
27
29
|
customPrompt: input.customPrompt,
|
|
28
30
|
appendSystemPrompt,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-system-prompt.js","sourceRoot":"","sources":["../../src/core/session-system-prompt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyD,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-system-prompt.js","sourceRoot":"","sources":["../../src/core/session-system-prompt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyD,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAwBlH;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,KAA+B,EAA+B;IACzG,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7E,MAAM,YAAY,GAA2B,EAAE,CAAC;IAChD,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,OAAO;YAAE,YAAY,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;QAC1C,MAAM,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,cAAc;YAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,cAAc;QAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACjE,MAAM,kBAAkB,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEzF,MAAM,OAAO,GAA6B;QACzC,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QACzB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;QAClF,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,YAAY,EAAE,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC;QACrC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,kBAAkB;QAClB,aAAa,EAAE,cAAc;QAC7B,YAAY;QACZ,gBAAgB;QAChB,aAAa,EAAE,KAAK,CAAC,aAAa;KAClC,CAAC;IACF,MAAM,KAAK,GAAsB,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;AAAA,CAC7E","sourcesContent":["import type { ContextFile } from \"./resource-loader.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { type BuildSystemPromptOptions, type BuiltSystemPrompt, buildSystemPromptPlan } from \"./system-prompt.ts\";\n\nexport interface SessionSystemPromptInput {\n\treadonly cwd: string;\n\treadonly toolNames: readonly string[];\n\treadonly hasTool: (name: string) => boolean;\n\treadonly toolPromptSnippets: ReadonlyMap<string, string>;\n\treadonly toolPromptGuidelines: ReadonlyMap<string, readonly string[]>;\n\treadonly customPrompt?: string;\n\treadonly appendSystemPrompt?: readonly string[];\n\treadonly providerAppend?: string;\n\treadonly skills: readonly Skill[];\n\treadonly activeSkillNames?: readonly string[];\n\treadonly activeSkillSource?: string;\n\treadonly contextFiles: readonly ContextFile[];\n\treadonly contextBudget?: BuildSystemPromptOptions[\"contextBudget\"];\n}\n\nexport interface SessionSystemPromptAssembly {\n\treadonly options: BuildSystemPromptOptions;\n\treadonly prompt: string;\n\treadonly cacheBoundary: number;\n}\n\n/**\n * Pure assembly of the session system-prompt plan: tool filtering, snippet and\n * guideline collection, and append-section joining. Callers resolve side effects\n * (resource loader reads, provider playbook files) and pass them in.\n */\nexport function assembleSessionSystemPrompt(input: SessionSystemPromptInput): SessionSystemPromptAssembly {\n\tconst validToolNames = input.toolNames.filter((name) => input.hasTool(name));\n\tconst toolSnippets: Record<string, string> = {};\n\tconst promptGuidelines: string[] = [];\n\tfor (const name of validToolNames) {\n\t\tconst snippet = input.toolPromptSnippets.get(name);\n\t\tif (snippet) toolSnippets[name] = snippet;\n\t\tconst toolGuidelines = input.toolPromptGuidelines.get(name);\n\t\tif (toolGuidelines) promptGuidelines.push(...toolGuidelines);\n\t}\n\n\tconst appendParts = [...(input.appendSystemPrompt ?? [])];\n\tif (input.providerAppend) appendParts.push(input.providerAppend);\n\tconst appendSystemPrompt = appendParts.length > 0 ? appendParts.join(\"\\n\\n\") : undefined;\n\n\tconst options: BuildSystemPromptOptions = {\n\t\tcwd: input.cwd,\n\t\tskills: [...input.skills],\n\t\tactiveSkillNames: input.activeSkillNames ? [...input.activeSkillNames] : undefined,\n\t\tactiveSkillSource: input.activeSkillSource,\n\t\tcontextFiles: [...input.contextFiles],\n\t\tcustomPrompt: input.customPrompt,\n\t\tappendSystemPrompt,\n\t\tselectedTools: validToolNames,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tcontextBudget: input.contextBudget,\n\t};\n\tconst built: BuiltSystemPrompt = buildSystemPromptPlan(options);\n\treturn { options, prompt: built.prompt, cacheBoundary: built.cacheBoundary };\n}\n"]}
|
|
@@ -5,7 +5,7 @@ export declare const MAX_SESSION_TERMINATION_MESSAGE_LENGTH = 512;
|
|
|
5
5
|
* runtime bounds from this tuple so a newly added kind can never crash
|
|
6
6
|
* journaling with "termination kind is out of bounds" again.
|
|
7
7
|
*/
|
|
8
|
-
export declare const SESSION_TERMINATION_KIND_VALUES: readonly ["completed", "user_abort", "provider_abort", "provider_auth", "provider_rate_limit", "provider_network", "provider_protocol", "provider_refusal", "context_overflow", "transcript_invalid", "tool_timeout", "tool_fatal", "compaction", "persistence", "process_signal", "process_crash", "configuration", "internal_error"];
|
|
8
|
+
export declare const SESSION_TERMINATION_KIND_VALUES: readonly ["completed", "user_abort", "provider_abort", "provider_auth", "provider_rate_limit", "provider_network", "provider_protocol", "provider_refusal", "context_overflow", "transcript_invalid", "tool_timeout", "tool_fatal", "compaction", "persistence", "process_signal", "process_crash", "configuration", "internal_error", "resource_pressure"];
|
|
9
9
|
export type SessionTerminationKind = (typeof SESSION_TERMINATION_KIND_VALUES)[number];
|
|
10
10
|
export type SessionTerminationPhase = "completed" | "control" | "preflight" | "provider" | "tool" | "compaction" | "persistence" | "process" | "resume";
|
|
11
11
|
export type SessionTerminationSource = "observed" | "inferred_on_resume";
|
|
@@ -13,10 +13,12 @@ export type SessionSideEffects = "none" | "possible" | "confirmed";
|
|
|
13
13
|
export type SessionProcessSignal = "SIGINT" | "SIGTERM" | "SIGHUP" | "SIGQUIT";
|
|
14
14
|
export type ProviderTerminationCauseCode = "abort" | "auth" | "rate_limit" | "network" | "protocol" | "refusal" | "context_overflow";
|
|
15
15
|
export type ToolTerminationCauseCode = "timeout" | "fatal";
|
|
16
|
+
/** §15.4 resource-aware runtime roadmap: host-pressure termination causes. */
|
|
17
|
+
export type ResourceTerminationCauseCode = "memory" | "disk" | "cpu" | "heap" | "probe_unavailable" | "queue_overflow";
|
|
16
18
|
export type CompactionTerminationCauseCode = "aborted" | "failed" | "stale";
|
|
17
19
|
export type PersistenceTerminationCauseCode = "read_failed" | "append_failed" | "replace_failed" | "fsync_failed" | "lock_failed";
|
|
18
20
|
export type TranscriptTerminationCauseCode = "missing_result" | "duplicate_result" | "orphan_result" | "duplicate_call_id" | "interleaved_non_result" | "invalid_jsonl" | "invalid_tree" | "unsupported_version" | "trailing_fragment";
|
|
19
|
-
export type SessionTerminationCauseCode = "session.completed" | "session.user_abort" | `provider.${ProviderTerminationCauseCode}` | `tool.${ToolTerminationCauseCode}` | `compaction.${CompactionTerminationCauseCode}` | `persistence.${PersistenceTerminationCauseCode}` | "process.signal" | "process.crash" | `transcript.${TranscriptTerminationCauseCode}` | "configuration.invalid" | "internal.unclassified"
|
|
21
|
+
export type SessionTerminationCauseCode = "session.completed" | "session.user_abort" | `provider.${ProviderTerminationCauseCode}` | `tool.${ToolTerminationCauseCode}` | `compaction.${CompactionTerminationCauseCode}` | `persistence.${PersistenceTerminationCauseCode}` | "process.signal" | "process.crash" | `transcript.${TranscriptTerminationCauseCode}` | "configuration.invalid" | "internal.unclassified" | `resource.${ResourceTerminationCauseCode}`;
|
|
20
22
|
export type SessionTerminationCause = {
|
|
21
23
|
readonly area: "completed";
|
|
22
24
|
} | {
|
|
@@ -50,6 +52,9 @@ export type SessionTerminationCause = {
|
|
|
50
52
|
} | {
|
|
51
53
|
readonly area: "internal";
|
|
52
54
|
readonly code: "unclassified";
|
|
55
|
+
} | {
|
|
56
|
+
readonly area: "resource";
|
|
57
|
+
readonly code: ResourceTerminationCauseCode;
|
|
53
58
|
};
|
|
54
59
|
export interface ClassifySessionTerminationInput {
|
|
55
60
|
readonly sessionId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-termination.d.ts","sourceRoot":"","sources":["../../src/core/session-termination.ts"],"names":[],"mappings":"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","sourcesContent":["export const SESSION_TERMINATION_SCHEMA_VERSION = 1 as const;\nexport const MAX_SESSION_TERMINATION_MESSAGE_LENGTH = 512;\n\n/**\n * Canonical list of termination kinds. The run-journal validator derives its\n * runtime bounds from this tuple so a newly added kind can never crash\n * journaling with \"termination kind is out of bounds\" again.\n */\nexport const SESSION_TERMINATION_KIND_VALUES = [\n\t\"completed\",\n\t\"user_abort\",\n\t\"provider_abort\",\n\t\"provider_auth\",\n\t\"provider_rate_limit\",\n\t\"provider_network\",\n\t\"provider_protocol\",\n\t\"provider_refusal\",\n\t\"context_overflow\",\n\t\"transcript_invalid\",\n\t\"tool_timeout\",\n\t\"tool_fatal\",\n\t\"compaction\",\n\t\"persistence\",\n\t\"process_signal\",\n\t\"process_crash\",\n\t\"configuration\",\n\t\"internal_error\",\n] as const;\n\nexport type SessionTerminationKind = (typeof SESSION_TERMINATION_KIND_VALUES)[number];\n\nexport type SessionTerminationPhase =\n\t| \"completed\"\n\t| \"control\"\n\t| \"preflight\"\n\t| \"provider\"\n\t| \"tool\"\n\t| \"compaction\"\n\t| \"persistence\"\n\t| \"process\"\n\t| \"resume\";\n\nexport type SessionTerminationSource = \"observed\" | \"inferred_on_resume\";\nexport type SessionSideEffects = \"none\" | \"possible\" | \"confirmed\";\nexport type SessionProcessSignal = \"SIGINT\" | \"SIGTERM\" | \"SIGHUP\" | \"SIGQUIT\";\n\nexport type ProviderTerminationCauseCode =\n\t| \"abort\"\n\t| \"auth\"\n\t| \"rate_limit\"\n\t| \"network\"\n\t| \"protocol\"\n\t| \"refusal\"\n\t| \"context_overflow\";\nexport type ToolTerminationCauseCode = \"timeout\" | \"fatal\";\nexport type CompactionTerminationCauseCode = \"aborted\" | \"failed\" | \"stale\";\nexport type PersistenceTerminationCauseCode =\n\t| \"read_failed\"\n\t| \"append_failed\"\n\t| \"replace_failed\"\n\t| \"fsync_failed\"\n\t| \"lock_failed\";\nexport type TranscriptTerminationCauseCode =\n\t| \"missing_result\"\n\t| \"duplicate_result\"\n\t| \"orphan_result\"\n\t| \"duplicate_call_id\"\n\t| \"interleaved_non_result\"\n\t| \"invalid_jsonl\"\n\t| \"invalid_tree\"\n\t| \"unsupported_version\"\n\t| \"trailing_fragment\";\nexport type SessionTerminationCauseCode =\n\t| \"session.completed\"\n\t| \"session.user_abort\"\n\t| `provider.${ProviderTerminationCauseCode}`\n\t| `tool.${ToolTerminationCauseCode}`\n\t| `compaction.${CompactionTerminationCauseCode}`\n\t| `persistence.${PersistenceTerminationCauseCode}`\n\t| \"process.signal\"\n\t| \"process.crash\"\n\t| `transcript.${TranscriptTerminationCauseCode}`\n\t| \"configuration.invalid\"\n\t| \"internal.unclassified\";\n\nexport type SessionTerminationCause =\n\t| { readonly area: \"completed\" }\n\t| { readonly area: \"user\"; readonly code: \"abort\" }\n\t| { readonly area: \"provider\"; readonly code: ProviderTerminationCauseCode }\n\t| { readonly area: \"tool\"; readonly code: ToolTerminationCauseCode }\n\t| { readonly area: \"compaction\"; readonly code: CompactionTerminationCauseCode }\n\t| { readonly area: \"persistence\"; readonly code: PersistenceTerminationCauseCode }\n\t| { readonly area: \"process\"; readonly code: \"signal\"; readonly signal: SessionProcessSignal }\n\t| { readonly area: \"process\"; readonly code: \"crash\" }\n\t| { readonly area: \"transcript\"; readonly code: TranscriptTerminationCauseCode }\n\t| { readonly area: \"configuration\"; readonly code: \"invalid\" }\n\t| { readonly area: \"internal\"; readonly code: \"unclassified\" };\n\nexport interface ClassifySessionTerminationInput {\n\treadonly sessionId: string;\n\treadonly runId: string;\n\t/** Deterministic caller-provided ISO-8601 timestamp. */\n\treadonly timestamp: string;\n\treadonly source: SessionTerminationSource;\n\t/** Caller-supplied, pre-redacted diagnostic text. */\n\treadonly message: string;\n\treadonly cause: SessionTerminationCause;\n\treadonly sideEffects: SessionSideEffects;\n\treadonly retryAfterMs?: number;\n\treadonly provider?: string;\n\treadonly model?: string;\n\treadonly toolCallId?: string;\n\treadonly toolName?: string;\n}\n\nexport interface SessionTermination {\n\treadonly schemaVersion: typeof SESSION_TERMINATION_SCHEMA_VERSION;\n\treadonly sessionId: string;\n\treadonly runId: string;\n\treadonly kind: SessionTerminationKind;\n\treadonly phase: SessionTerminationPhase;\n\treadonly source: SessionTerminationSource;\n\treadonly message: string;\n\treadonly causeCode: SessionTerminationCauseCode;\n\t/** Stable operator guidance suitable for print, JSON, RPC, and TUI surfaces. */\n\treadonly nextAction: string;\n\treadonly retryable: boolean;\n\treadonly safeToAutoRetry: boolean;\n\treadonly sideEffects: SessionSideEffects;\n\treadonly timestamp: string;\n\treadonly retryAfterMs?: number;\n\treadonly provider?: string;\n\treadonly model?: string;\n\treadonly toolCallId?: string;\n\treadonly toolName?: string;\n\treadonly processSignal?: SessionProcessSignal;\n\treadonly transcriptIssue?: TranscriptTerminationCauseCode;\n}\n\nexport class SessionTerminationError extends Error {\n\treadonly termination: SessionTermination;\n\n\tconstructor(message: string, termination: SessionTermination) {\n\t\tsuper(message);\n\t\tthis.name = \"SessionTerminationError\";\n\t\tthis.termination = termination;\n\t}\n}\n\ninterface Classification {\n\treadonly kind: SessionTerminationKind;\n\treadonly phase: SessionTerminationPhase;\n\treadonly causeCode: SessionTerminationCauseCode;\n\treadonly retryable: boolean;\n\treadonly processSignal?: SessionProcessSignal;\n\treadonly transcriptIssue?: TranscriptTerminationCauseCode;\n}\n\nconst CREDENTIAL_SHAPES = [\n\t/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,\n\t/\\b(?:sk-[A-Za-z0-9_-]{16,}|github_pat_[A-Za-z0-9_]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|xox[baprs]-[A-Za-z0-9-]{16,}|AKIA[A-Z0-9]{16})\\b/,\n\t/\\bBearer\\s+[A-Za-z0-9._~+/-]{8,}={0,2}\\b/i,\n\t/\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/,\n\t/(?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]+[_-])*(?:authorization|api[-_ ]?key|access[-_ ]?token|token|password|secret(?:[-_ ]?key)?|private[-_ ]?key|cookie)[\"']?\\s*[:=]/i,\n] as const;\n\nconst TOP_LEVEL_KEYS = new Set([\n\t\"sessionId\",\n\t\"runId\",\n\t\"timestamp\",\n\t\"source\",\n\t\"message\",\n\t\"cause\",\n\t\"sideEffects\",\n\t\"retryAfterMs\",\n\t\"provider\",\n\t\"model\",\n\t\"toolCallId\",\n\t\"toolName\",\n]);\nconst PROVIDER_CAUSE_CODES = new Set<ProviderTerminationCauseCode>([\n\t\"abort\",\n\t\"auth\",\n\t\"rate_limit\",\n\t\"network\",\n\t\"protocol\",\n\t\"refusal\",\n\t\"context_overflow\",\n]);\nconst TOOL_CAUSE_CODES = new Set<ToolTerminationCauseCode>([\"timeout\", \"fatal\"]);\nconst COMPACTION_CAUSE_CODES = new Set<CompactionTerminationCauseCode>([\"aborted\", \"failed\", \"stale\"]);\nconst PERSISTENCE_CAUSE_CODES = new Set<PersistenceTerminationCauseCode>([\n\t\"read_failed\",\n\t\"append_failed\",\n\t\"replace_failed\",\n\t\"fsync_failed\",\n\t\"lock_failed\",\n]);\nconst TRANSCRIPT_CAUSE_CODES = new Set<TranscriptTerminationCauseCode>([\n\t\"missing_result\",\n\t\"duplicate_result\",\n\t\"orphan_result\",\n\t\"duplicate_call_id\",\n\t\"interleaved_non_result\",\n\t\"invalid_jsonl\",\n\t\"invalid_tree\",\n\t\"unsupported_version\",\n\t\"trailing_fragment\",\n]);\nconst PROCESS_SIGNALS = new Set<SessionProcessSignal>([\"SIGINT\", \"SIGTERM\", \"SIGHUP\", \"SIGQUIT\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n\tconst allowed = new Set(keys);\n\treturn Object.keys(value).every((key) => allowed.has(key));\n}\n\nfunction assertCause(cause: unknown): asserts cause is SessionTerminationCause {\n\tif (!isRecord(cause) || typeof cause.area !== \"string\") {\n\t\tthrow new TypeError(\"cause must be a bounded structured termination cause\");\n\t}\n\tconst code = cause.code;\n\tlet valid = false;\n\tswitch (cause.area) {\n\t\tcase \"completed\":\n\t\t\tvalid = hasOnlyKeys(cause, [\"area\"]);\n\t\t\tbreak;\n\t\tcase \"user\":\n\t\t\tvalid = code === \"abort\" && hasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"provider\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tPROVIDER_CAUSE_CODES.has(code as ProviderTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"tool\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tTOOL_CAUSE_CODES.has(code as ToolTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"compaction\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tCOMPACTION_CAUSE_CODES.has(code as CompactionTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"persistence\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tPERSISTENCE_CAUSE_CODES.has(code as PersistenceTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"process\":\n\t\t\tvalid =\n\t\t\t\t(code === \"crash\" && hasOnlyKeys(cause, [\"area\", \"code\"])) ||\n\t\t\t\t(code === \"signal\" &&\n\t\t\t\t\ttypeof cause.signal === \"string\" &&\n\t\t\t\t\tPROCESS_SIGNALS.has(cause.signal as SessionProcessSignal) &&\n\t\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\", \"signal\"]));\n\t\t\tbreak;\n\t\tcase \"transcript\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tTRANSCRIPT_CAUSE_CODES.has(code as TranscriptTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"configuration\":\n\t\t\tvalid = code === \"invalid\" && hasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"internal\":\n\t\t\tvalid = code === \"unclassified\" && hasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t}\n\tif (!valid) throw new TypeError(\"cause must be a bounded structured termination cause\");\n}\n\nfunction assertClassifierInput(input: unknown): asserts input is ClassifySessionTerminationInput {\n\tif (!isRecord(input) || Object.keys(input).some((key) => !TOP_LEVEL_KEYS.has(key))) {\n\t\tthrow new TypeError(\"input must contain only bounded structured termination fields\");\n\t}\n\tif (\n\t\ttypeof input.sessionId !== \"string\" ||\n\t\ttypeof input.runId !== \"string\" ||\n\t\ttypeof input.timestamp !== \"string\" ||\n\t\ttypeof input.message !== \"string\" ||\n\t\t(input.source !== \"observed\" && input.source !== \"inferred_on_resume\") ||\n\t\t(input.sideEffects !== \"none\" && input.sideEffects !== \"possible\" && input.sideEffects !== \"confirmed\") ||\n\t\t(input.retryAfterMs !== undefined && typeof input.retryAfterMs !== \"number\") ||\n\t\t(input.provider !== undefined && typeof input.provider !== \"string\") ||\n\t\t(input.model !== undefined && typeof input.model !== \"string\") ||\n\t\t(input.toolCallId !== undefined && typeof input.toolCallId !== \"string\") ||\n\t\t(input.toolName !== undefined && typeof input.toolName !== \"string\")\n\t) {\n\t\tthrow new TypeError(\"input must contain only bounded structured termination fields\");\n\t}\n\tassertCause(input.cause);\n}\n\nfunction assertIdentifier(value: string, name: string, maxLength: number): void {\n\tif (value.length === 0 || value.length > maxLength || /[\\u0000-\\u001f\\u007f]/.test(value)) {\n\t\tthrow new TypeError(`${name} must be non-empty bounded text without C0 or DEL control characters`);\n\t}\n}\n\n/** Validate text at the termination trust boundary without attempting redaction. */\nexport function assertPreRedactedTerminationMessage(message: string): void {\n\tif (message.length === 0 || message.length > MAX_SESSION_TERMINATION_MESSAGE_LENGTH) {\n\t\tthrow new TypeError(\n\t\t\t`message must contain 1-${MAX_SESSION_TERMINATION_MESSAGE_LENGTH} characters of pre-redacted text`,\n\t\t);\n\t}\n\tif (message.includes(\"\\0\")) {\n\t\tthrow new TypeError(\"message must not contain NUL\");\n\t}\n\tif (CREDENTIAL_SHAPES.some((pattern) => pattern.test(message))) {\n\t\tthrow new TypeError(\"message contains a credential-shaped literal; redact it before classification\");\n\t}\n}\n\nfunction assertTimestamp(timestamp: string): void {\n\tconst parsed = new Date(timestamp);\n\tif (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== timestamp) {\n\t\tthrow new TypeError(\"timestamp must be a canonical ISO-8601 UTC instant\");\n\t}\n}\n\nfunction classifyProvider(code: ProviderTerminationCauseCode): Classification {\n\tswitch (code) {\n\t\tcase \"abort\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_abort\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.abort\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"auth\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_auth\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.auth\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"rate_limit\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_rate_limit\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.rate_limit\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"network\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_network\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.network\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"protocol\":\n\t\t\t// Many \"protocol\" stops are sticky transcript shape bugs (orphan tool_call_id)\n\t\t\t// that succeed after transform-messages sanitizes and the turn is retried.\n\t\t\t// Mark retryable so auto-retry can heal; safeToAutoRetry stays false (needs sanitize path).\n\t\t\treturn {\n\t\t\t\tkind: \"provider_protocol\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.protocol\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"context_overflow\":\n\t\t\treturn {\n\t\t\t\tkind: \"context_overflow\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.context_overflow\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"refusal\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_refusal\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.refusal\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t}\n}\n\nfunction classifyCause(cause: SessionTerminationCause, source: SessionTerminationSource): Classification {\n\tswitch (cause.area) {\n\t\tcase \"completed\":\n\t\t\treturn {\n\t\t\t\tkind: \"completed\",\n\t\t\t\tphase: \"completed\",\n\t\t\t\tcauseCode: \"session.completed\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"user\":\n\t\t\treturn {\n\t\t\t\tkind: \"user_abort\",\n\t\t\t\tphase: \"control\",\n\t\t\t\tcauseCode: \"session.user_abort\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"provider\":\n\t\t\treturn classifyProvider(cause.code);\n\t\tcase \"tool\":\n\t\t\treturn cause.code === \"timeout\"\n\t\t\t\t? { kind: \"tool_timeout\", phase: \"tool\", causeCode: \"tool.timeout\", retryable: true }\n\t\t\t\t: { kind: \"tool_fatal\", phase: \"tool\", causeCode: \"tool.fatal\", retryable: false };\n\t\tcase \"compaction\":\n\t\t\treturn {\n\t\t\t\tkind: \"compaction\",\n\t\t\t\tphase: \"compaction\",\n\t\t\t\tcauseCode: `compaction.${cause.code}`,\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"persistence\":\n\t\t\treturn {\n\t\t\t\tkind: \"persistence\",\n\t\t\t\tphase: \"persistence\",\n\t\t\t\tcauseCode: `persistence.${cause.code}`,\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"process\":\n\t\t\tif (cause.code === \"signal\") {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"process_signal\",\n\t\t\t\t\tphase: \"process\",\n\t\t\t\t\tcauseCode: \"process.signal\",\n\t\t\t\t\tretryable: false,\n\t\t\t\t\tprocessSignal: cause.signal,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tkind: \"process_crash\",\n\t\t\t\tphase: \"resume\",\n\t\t\t\tcauseCode: \"process.crash\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"transcript\":\n\t\t\treturn {\n\t\t\t\tkind: \"transcript_invalid\",\n\t\t\t\tphase: source === \"inferred_on_resume\" ? \"resume\" : \"preflight\",\n\t\t\t\tcauseCode: `transcript.${cause.code}`,\n\t\t\t\tretryable: false,\n\t\t\t\ttranscriptIssue: cause.code,\n\t\t\t};\n\t\tcase \"configuration\":\n\t\t\treturn {\n\t\t\t\tkind: \"configuration\",\n\t\t\t\tphase: \"preflight\",\n\t\t\t\tcauseCode: \"configuration.invalid\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"internal\":\n\t\t\treturn {\n\t\t\t\tkind: \"internal_error\",\n\t\t\t\tphase: \"preflight\",\n\t\t\t\tcauseCode: \"internal.unclassified\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t}\n}\n\nfunction isSafeToAutoRetry(classification: Classification, input: ClassifySessionTerminationInput): boolean {\n\t// provider_refusal: Fable/Claude often false-positive on benign turns. Safe to auto-retry once\n\t// when no tool side effects occurred (same class of recovery as rate_limit/network).\n\treturn (\n\t\tclassification.retryable &&\n\t\tinput.source === \"observed\" &&\n\t\tinput.sideEffects === \"none\" &&\n\t\t(classification.kind === \"provider_rate_limit\" ||\n\t\t\tclassification.kind === \"provider_network\" ||\n\t\t\tclassification.kind === \"provider_refusal\")\n\t);\n}\n\nfunction nextActionFor(classification: Classification, input: ClassifySessionTerminationInput): string {\n\tswitch (classification.kind) {\n\t\tcase \"completed\":\n\t\t\treturn \"No recovery action is required; continue with the next prompt.\";\n\t\tcase \"user_abort\":\n\t\t\treturn \"Review possible partial side effects, then retry only if intended.\";\n\t\tcase \"provider_abort\":\n\t\t\treturn \"Confirm provider availability, then retry the request.\";\n\t\tcase \"provider_auth\":\n\t\t\treturn `Run /login${input.provider ? ` ${input.provider}` : \"\"} or configure valid credentials, then retry.`;\n\t\tcase \"provider_rate_limit\":\n\t\t\treturn \"Wait for the provider retry window or choose another model, then retry.\";\n\t\tcase \"provider_network\":\n\t\t\treturn \"Check network and provider connectivity, then retry.\";\n\t\tcase \"provider_protocol\":\n\t\t\treturn \"Often an orphan tool_call_id / sticky transcript after a dropped error turn — auto-retry after sanitize, or /new session if it persists.\";\n\t\tcase \"provider_refusal\":\n\t\t\treturn \"Model declined this turn (content/safety stop). Usually a false positive on Fable/Claude — auto-retry once, or switch model (k3/qwen3.8-max/grok-4.5/deepseek) / rephrase as a pure coding task.\";\n\t\tcase \"context_overflow\":\n\t\t\treturn \"Compact or reduce context, or switch to a larger-context model.\";\n\t\tcase \"tool_timeout\":\n\t\t\treturn \"Inspect possible tool side effects and increase the tool timeout only if safe.\";\n\t\tcase \"tool_fatal\":\n\t\t\treturn \"Inspect the failed tool result and repair its configuration before retrying.\";\n\t\tcase \"compaction\":\n\t\t\tswitch (classification.causeCode) {\n\t\t\t\tcase \"compaction.aborted\":\n\t\t\t\t\treturn \"Retry /compact if cancellation was unintended; avoid Escape while compaction is running.\";\n\t\t\t\tcase \"compaction.stale\":\n\t\t\t\t\treturn \"Retry /compact after pending session changes settle.\";\n\t\t\t\tdefault:\n\t\t\t\t\treturn \"Resolve the reported compaction error, then retry /compact.\";\n\t\t\t}\n\t\tcase \"persistence\":\n\t\t\treturn `Run omk session doctor --session ${input.sessionId} before resuming.`;\n\t\tcase \"process_signal\":\n\t\t\treturn \"Review possible partial side effects before resuming the session.\";\n\t\tcase \"process_crash\":\n\t\t\treturn `Run omk session doctor --session ${input.sessionId} and review partial side effects.`;\n\t\tcase \"transcript_invalid\":\n\t\t\treturn `Run omk session doctor --session ${input.sessionId}; do not resume until integrity passes.`;\n\t\tcase \"configuration\":\n\t\t\treturn \"Correct the provider, model, tool, or session configuration and retry.\";\n\t\tcase \"internal_error\":\n\t\t\treturn `Inspect run ${input.runId} diagnostics and the run journal before retrying.`;\n\t}\n}\n\n/** Concise actionable rendering shared by non-JSON and TUI surfaces. */\nexport function formatSessionTermination(termination: SessionTermination): string {\n\tconst route =\n\t\ttermination.provider || termination.model\n\t\t\t? `${termination.provider ?? \"unknown\"}/${termination.model ?? \"unknown\"}`\n\t\t\t: \"n/a\";\n\treturn [\n\t\t`kind=${termination.kind}`,\n\t\t`provider/model=${route}`,\n\t\t`retryable=${termination.retryable}`,\n\t\t`cause=${termination.causeCode}`,\n\t\t`message=${termination.message}`,\n\t\t`run=${termination.runId}`,\n\t\t`next=${termination.nextAction}`,\n\t].join(\" \");\n}\n\n/**\n * Classify a bounded, structured termination cause. This intentionally accepts\n * no raw Error value, stack, provider body, or arbitrary cause code.\n */\nexport function classifySessionTermination(input: ClassifySessionTerminationInput): SessionTermination {\n\tassertClassifierInput(input);\n\tassertIdentifier(input.sessionId, \"sessionId\", 128);\n\tassertIdentifier(input.runId, \"runId\", 128);\n\tassertTimestamp(input.timestamp);\n\tassertPreRedactedTerminationMessage(input.message);\n\tif (input.provider !== undefined) assertIdentifier(input.provider, \"provider\", 128);\n\tif (input.model !== undefined) assertIdentifier(input.model, \"model\", 256);\n\tif (input.toolCallId !== undefined) assertIdentifier(input.toolCallId, \"toolCallId\", 512);\n\tif (input.toolName !== undefined) assertIdentifier(input.toolName, \"toolName\", 128);\n\tif (input.retryAfterMs !== undefined && (!Number.isSafeInteger(input.retryAfterMs) || input.retryAfterMs < 0)) {\n\t\tthrow new TypeError(\"retryAfterMs must be a non-negative safe integer\");\n\t}\n\tif (input.cause.area === \"process\" && input.cause.code === \"crash\" && input.source !== \"inferred_on_resume\") {\n\t\tthrow new TypeError(\"process crashes may only be classified as inferred_on_resume\");\n\t}\n\tif (input.cause.area === \"process\" && input.cause.code === \"signal\" && input.source !== \"observed\") {\n\t\tthrow new TypeError(\"process signals must be observed\");\n\t}\n\n\tconst classification = classifyCause(input.cause, input.source);\n\treturn Object.freeze({\n\t\tschemaVersion: SESSION_TERMINATION_SCHEMA_VERSION,\n\t\tsessionId: input.sessionId,\n\t\trunId: input.runId,\n\t\tkind: classification.kind,\n\t\tphase: classification.phase,\n\t\tsource: input.source,\n\t\tmessage: input.message,\n\t\tcauseCode: classification.causeCode,\n\t\tnextAction: nextActionFor(classification, input),\n\t\tretryable: classification.retryable,\n\t\tsafeToAutoRetry: isSafeToAutoRetry(classification, input),\n\t\tsideEffects: input.sideEffects,\n\t\ttimestamp: input.timestamp,\n\t\t...(input.retryAfterMs === undefined ? {} : { retryAfterMs: input.retryAfterMs }),\n\t\t...(input.provider === undefined ? {} : { provider: input.provider }),\n\t\t...(input.model === undefined ? {} : { model: input.model }),\n\t\t...(input.toolCallId === undefined ? {} : { toolCallId: input.toolCallId }),\n\t\t...(input.toolName === undefined ? {} : { toolName: input.toolName }),\n\t\t...(classification.processSignal === undefined ? {} : { processSignal: classification.processSignal }),\n\t\t...(classification.transcriptIssue === undefined ? {} : { transcriptIssue: classification.transcriptIssue }),\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-termination.d.ts","sourceRoot":"","sources":["../../src/core/session-termination.ts"],"names":[],"mappings":"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","sourcesContent":["export const SESSION_TERMINATION_SCHEMA_VERSION = 1 as const;\nexport const MAX_SESSION_TERMINATION_MESSAGE_LENGTH = 512;\n\n/**\n * Canonical list of termination kinds. The run-journal validator derives its\n * runtime bounds from this tuple so a newly added kind can never crash\n * journaling with \"termination kind is out of bounds\" again.\n */\nexport const SESSION_TERMINATION_KIND_VALUES = [\n\t\"completed\",\n\t\"user_abort\",\n\t\"provider_abort\",\n\t\"provider_auth\",\n\t\"provider_rate_limit\",\n\t\"provider_network\",\n\t\"provider_protocol\",\n\t\"provider_refusal\",\n\t\"context_overflow\",\n\t\"transcript_invalid\",\n\t\"tool_timeout\",\n\t\"tool_fatal\",\n\t\"compaction\",\n\t\"persistence\",\n\t\"process_signal\",\n\t\"process_crash\",\n\t\"configuration\",\n\t\"internal_error\",\n\t\"resource_pressure\",\n] as const;\n\nexport type SessionTerminationKind = (typeof SESSION_TERMINATION_KIND_VALUES)[number];\n\nexport type SessionTerminationPhase =\n\t| \"completed\"\n\t| \"control\"\n\t| \"preflight\"\n\t| \"provider\"\n\t| \"tool\"\n\t| \"compaction\"\n\t| \"persistence\"\n\t| \"process\"\n\t| \"resume\";\n\nexport type SessionTerminationSource = \"observed\" | \"inferred_on_resume\";\nexport type SessionSideEffects = \"none\" | \"possible\" | \"confirmed\";\nexport type SessionProcessSignal = \"SIGINT\" | \"SIGTERM\" | \"SIGHUP\" | \"SIGQUIT\";\n\nexport type ProviderTerminationCauseCode =\n\t| \"abort\"\n\t| \"auth\"\n\t| \"rate_limit\"\n\t| \"network\"\n\t| \"protocol\"\n\t| \"refusal\"\n\t| \"context_overflow\";\nexport type ToolTerminationCauseCode = \"timeout\" | \"fatal\";\n/** §15.4 resource-aware runtime roadmap: host-pressure termination causes. */\nexport type ResourceTerminationCauseCode = \"memory\" | \"disk\" | \"cpu\" | \"heap\" | \"probe_unavailable\" | \"queue_overflow\";\nexport type CompactionTerminationCauseCode = \"aborted\" | \"failed\" | \"stale\";\nexport type PersistenceTerminationCauseCode =\n\t| \"read_failed\"\n\t| \"append_failed\"\n\t| \"replace_failed\"\n\t| \"fsync_failed\"\n\t| \"lock_failed\";\nexport type TranscriptTerminationCauseCode =\n\t| \"missing_result\"\n\t| \"duplicate_result\"\n\t| \"orphan_result\"\n\t| \"duplicate_call_id\"\n\t| \"interleaved_non_result\"\n\t| \"invalid_jsonl\"\n\t| \"invalid_tree\"\n\t| \"unsupported_version\"\n\t| \"trailing_fragment\";\nexport type SessionTerminationCauseCode =\n\t| \"session.completed\"\n\t| \"session.user_abort\"\n\t| `provider.${ProviderTerminationCauseCode}`\n\t| `tool.${ToolTerminationCauseCode}`\n\t| `compaction.${CompactionTerminationCauseCode}`\n\t| `persistence.${PersistenceTerminationCauseCode}`\n\t| \"process.signal\"\n\t| \"process.crash\"\n\t| `transcript.${TranscriptTerminationCauseCode}`\n\t| \"configuration.invalid\"\n\t| \"internal.unclassified\"\n\t| `resource.${ResourceTerminationCauseCode}`;\n\nexport type SessionTerminationCause =\n\t| { readonly area: \"completed\" }\n\t| { readonly area: \"user\"; readonly code: \"abort\" }\n\t| { readonly area: \"provider\"; readonly code: ProviderTerminationCauseCode }\n\t| { readonly area: \"tool\"; readonly code: ToolTerminationCauseCode }\n\t| { readonly area: \"compaction\"; readonly code: CompactionTerminationCauseCode }\n\t| { readonly area: \"persistence\"; readonly code: PersistenceTerminationCauseCode }\n\t| { readonly area: \"process\"; readonly code: \"signal\"; readonly signal: SessionProcessSignal }\n\t| { readonly area: \"process\"; readonly code: \"crash\" }\n\t| { readonly area: \"transcript\"; readonly code: TranscriptTerminationCauseCode }\n\t| { readonly area: \"configuration\"; readonly code: \"invalid\" }\n\t| { readonly area: \"internal\"; readonly code: \"unclassified\" }\n\t| { readonly area: \"resource\"; readonly code: ResourceTerminationCauseCode };\n\nexport interface ClassifySessionTerminationInput {\n\treadonly sessionId: string;\n\treadonly runId: string;\n\t/** Deterministic caller-provided ISO-8601 timestamp. */\n\treadonly timestamp: string;\n\treadonly source: SessionTerminationSource;\n\t/** Caller-supplied, pre-redacted diagnostic text. */\n\treadonly message: string;\n\treadonly cause: SessionTerminationCause;\n\treadonly sideEffects: SessionSideEffects;\n\treadonly retryAfterMs?: number;\n\treadonly provider?: string;\n\treadonly model?: string;\n\treadonly toolCallId?: string;\n\treadonly toolName?: string;\n}\n\nexport interface SessionTermination {\n\treadonly schemaVersion: typeof SESSION_TERMINATION_SCHEMA_VERSION;\n\treadonly sessionId: string;\n\treadonly runId: string;\n\treadonly kind: SessionTerminationKind;\n\treadonly phase: SessionTerminationPhase;\n\treadonly source: SessionTerminationSource;\n\treadonly message: string;\n\treadonly causeCode: SessionTerminationCauseCode;\n\t/** Stable operator guidance suitable for print, JSON, RPC, and TUI surfaces. */\n\treadonly nextAction: string;\n\treadonly retryable: boolean;\n\treadonly safeToAutoRetry: boolean;\n\treadonly sideEffects: SessionSideEffects;\n\treadonly timestamp: string;\n\treadonly retryAfterMs?: number;\n\treadonly provider?: string;\n\treadonly model?: string;\n\treadonly toolCallId?: string;\n\treadonly toolName?: string;\n\treadonly processSignal?: SessionProcessSignal;\n\treadonly transcriptIssue?: TranscriptTerminationCauseCode;\n}\n\nexport class SessionTerminationError extends Error {\n\treadonly termination: SessionTermination;\n\n\tconstructor(message: string, termination: SessionTermination) {\n\t\tsuper(message);\n\t\tthis.name = \"SessionTerminationError\";\n\t\tthis.termination = termination;\n\t}\n}\n\ninterface Classification {\n\treadonly kind: SessionTerminationKind;\n\treadonly phase: SessionTerminationPhase;\n\treadonly causeCode: SessionTerminationCauseCode;\n\treadonly retryable: boolean;\n\treadonly processSignal?: SessionProcessSignal;\n\treadonly transcriptIssue?: TranscriptTerminationCauseCode;\n}\n\nconst CREDENTIAL_SHAPES = [\n\t/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,\n\t/\\b(?:sk-[A-Za-z0-9_-]{16,}|github_pat_[A-Za-z0-9_]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|xox[baprs]-[A-Za-z0-9-]{16,}|AKIA[A-Z0-9]{16})\\b/,\n\t/\\bBearer\\s+[A-Za-z0-9._~+/-]{8,}={0,2}\\b/i,\n\t/\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/,\n\t/(?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]+[_-])*(?:authorization|api[-_ ]?key|access[-_ ]?token|token|password|secret(?:[-_ ]?key)?|private[-_ ]?key|cookie)[\"']?\\s*[:=]/i,\n] as const;\n\nconst TOP_LEVEL_KEYS = new Set([\n\t\"sessionId\",\n\t\"runId\",\n\t\"timestamp\",\n\t\"source\",\n\t\"message\",\n\t\"cause\",\n\t\"sideEffects\",\n\t\"retryAfterMs\",\n\t\"provider\",\n\t\"model\",\n\t\"toolCallId\",\n\t\"toolName\",\n]);\nconst PROVIDER_CAUSE_CODES = new Set<ProviderTerminationCauseCode>([\n\t\"abort\",\n\t\"auth\",\n\t\"rate_limit\",\n\t\"network\",\n\t\"protocol\",\n\t\"refusal\",\n\t\"context_overflow\",\n]);\nconst TOOL_CAUSE_CODES = new Set<ToolTerminationCauseCode>([\"timeout\", \"fatal\"]);\nconst RESOURCE_CAUSE_CODES = new Set<ResourceTerminationCauseCode>([\n\t\"memory\",\n\t\"disk\",\n\t\"cpu\",\n\t\"heap\",\n\t\"probe_unavailable\",\n\t\"queue_overflow\",\n]);\nconst COMPACTION_CAUSE_CODES = new Set<CompactionTerminationCauseCode>([\"aborted\", \"failed\", \"stale\"]);\nconst PERSISTENCE_CAUSE_CODES = new Set<PersistenceTerminationCauseCode>([\n\t\"read_failed\",\n\t\"append_failed\",\n\t\"replace_failed\",\n\t\"fsync_failed\",\n\t\"lock_failed\",\n]);\nconst TRANSCRIPT_CAUSE_CODES = new Set<TranscriptTerminationCauseCode>([\n\t\"missing_result\",\n\t\"duplicate_result\",\n\t\"orphan_result\",\n\t\"duplicate_call_id\",\n\t\"interleaved_non_result\",\n\t\"invalid_jsonl\",\n\t\"invalid_tree\",\n\t\"unsupported_version\",\n\t\"trailing_fragment\",\n]);\nconst PROCESS_SIGNALS = new Set<SessionProcessSignal>([\"SIGINT\", \"SIGTERM\", \"SIGHUP\", \"SIGQUIT\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n\tconst allowed = new Set(keys);\n\treturn Object.keys(value).every((key) => allowed.has(key));\n}\n\nfunction assertCause(cause: unknown): asserts cause is SessionTerminationCause {\n\tif (!isRecord(cause) || typeof cause.area !== \"string\") {\n\t\tthrow new TypeError(\"cause must be a bounded structured termination cause\");\n\t}\n\tconst code = cause.code;\n\tlet valid = false;\n\tswitch (cause.area) {\n\t\tcase \"completed\":\n\t\t\tvalid = hasOnlyKeys(cause, [\"area\"]);\n\t\t\tbreak;\n\t\tcase \"user\":\n\t\t\tvalid = code === \"abort\" && hasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"provider\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tPROVIDER_CAUSE_CODES.has(code as ProviderTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"tool\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tTOOL_CAUSE_CODES.has(code as ToolTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"compaction\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tCOMPACTION_CAUSE_CODES.has(code as CompactionTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"persistence\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tPERSISTENCE_CAUSE_CODES.has(code as PersistenceTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"process\":\n\t\t\tvalid =\n\t\t\t\t(code === \"crash\" && hasOnlyKeys(cause, [\"area\", \"code\"])) ||\n\t\t\t\t(code === \"signal\" &&\n\t\t\t\t\ttypeof cause.signal === \"string\" &&\n\t\t\t\t\tPROCESS_SIGNALS.has(cause.signal as SessionProcessSignal) &&\n\t\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\", \"signal\"]));\n\t\t\tbreak;\n\t\tcase \"transcript\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tTRANSCRIPT_CAUSE_CODES.has(code as TranscriptTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"configuration\":\n\t\t\tvalid = code === \"invalid\" && hasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"internal\":\n\t\t\tvalid = code === \"unclassified\" && hasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t\tcase \"resource\":\n\t\t\tvalid =\n\t\t\t\ttypeof code === \"string\" &&\n\t\t\t\tRESOURCE_CAUSE_CODES.has(code as ResourceTerminationCauseCode) &&\n\t\t\t\thasOnlyKeys(cause, [\"area\", \"code\"]);\n\t\t\tbreak;\n\t}\n\tif (!valid) throw new TypeError(\"cause must be a bounded structured termination cause\");\n}\n\nfunction assertClassifierInput(input: unknown): asserts input is ClassifySessionTerminationInput {\n\tif (!isRecord(input) || Object.keys(input).some((key) => !TOP_LEVEL_KEYS.has(key))) {\n\t\tthrow new TypeError(\"input must contain only bounded structured termination fields\");\n\t}\n\tif (\n\t\ttypeof input.sessionId !== \"string\" ||\n\t\ttypeof input.runId !== \"string\" ||\n\t\ttypeof input.timestamp !== \"string\" ||\n\t\ttypeof input.message !== \"string\" ||\n\t\t(input.source !== \"observed\" && input.source !== \"inferred_on_resume\") ||\n\t\t(input.sideEffects !== \"none\" && input.sideEffects !== \"possible\" && input.sideEffects !== \"confirmed\") ||\n\t\t(input.retryAfterMs !== undefined && typeof input.retryAfterMs !== \"number\") ||\n\t\t(input.provider !== undefined && typeof input.provider !== \"string\") ||\n\t\t(input.model !== undefined && typeof input.model !== \"string\") ||\n\t\t(input.toolCallId !== undefined && typeof input.toolCallId !== \"string\") ||\n\t\t(input.toolName !== undefined && typeof input.toolName !== \"string\")\n\t) {\n\t\tthrow new TypeError(\"input must contain only bounded structured termination fields\");\n\t}\n\tassertCause(input.cause);\n}\n\nfunction assertIdentifier(value: string, name: string, maxLength: number): void {\n\tif (value.length === 0 || value.length > maxLength || /[\\u0000-\\u001f\\u007f]/.test(value)) {\n\t\tthrow new TypeError(`${name} must be non-empty bounded text without C0 or DEL control characters`);\n\t}\n}\n\n/** Validate text at the termination trust boundary without attempting redaction. */\nexport function assertPreRedactedTerminationMessage(message: string): void {\n\tif (message.length === 0 || message.length > MAX_SESSION_TERMINATION_MESSAGE_LENGTH) {\n\t\tthrow new TypeError(\n\t\t\t`message must contain 1-${MAX_SESSION_TERMINATION_MESSAGE_LENGTH} characters of pre-redacted text`,\n\t\t);\n\t}\n\tif (message.includes(\"\\0\")) {\n\t\tthrow new TypeError(\"message must not contain NUL\");\n\t}\n\tif (CREDENTIAL_SHAPES.some((pattern) => pattern.test(message))) {\n\t\tthrow new TypeError(\"message contains a credential-shaped literal; redact it before classification\");\n\t}\n}\n\nfunction assertTimestamp(timestamp: string): void {\n\tconst parsed = new Date(timestamp);\n\tif (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== timestamp) {\n\t\tthrow new TypeError(\"timestamp must be a canonical ISO-8601 UTC instant\");\n\t}\n}\n\nfunction classifyProvider(code: ProviderTerminationCauseCode): Classification {\n\tswitch (code) {\n\t\tcase \"abort\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_abort\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.abort\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"auth\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_auth\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.auth\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"rate_limit\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_rate_limit\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.rate_limit\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"network\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_network\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.network\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"protocol\":\n\t\t\t// Many \"protocol\" stops are sticky transcript shape bugs (orphan tool_call_id)\n\t\t\t// that succeed after transform-messages sanitizes and the turn is retried.\n\t\t\t// Mark retryable so auto-retry can heal; safeToAutoRetry stays false (needs sanitize path).\n\t\t\treturn {\n\t\t\t\tkind: \"provider_protocol\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.protocol\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"context_overflow\":\n\t\t\treturn {\n\t\t\t\tkind: \"context_overflow\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.context_overflow\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"refusal\":\n\t\t\treturn {\n\t\t\t\tkind: \"provider_refusal\",\n\t\t\t\tphase: \"provider\",\n\t\t\t\tcauseCode: \"provider.refusal\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t}\n}\n\nfunction classifyCause(cause: SessionTerminationCause, source: SessionTerminationSource): Classification {\n\tswitch (cause.area) {\n\t\tcase \"completed\":\n\t\t\treturn {\n\t\t\t\tkind: \"completed\",\n\t\t\t\tphase: \"completed\",\n\t\t\t\tcauseCode: \"session.completed\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"user\":\n\t\t\treturn {\n\t\t\t\tkind: \"user_abort\",\n\t\t\t\tphase: \"control\",\n\t\t\t\tcauseCode: \"session.user_abort\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"provider\":\n\t\t\treturn classifyProvider(cause.code);\n\t\tcase \"tool\":\n\t\t\treturn cause.code === \"timeout\"\n\t\t\t\t? { kind: \"tool_timeout\", phase: \"tool\", causeCode: \"tool.timeout\", retryable: true }\n\t\t\t\t: { kind: \"tool_fatal\", phase: \"tool\", causeCode: \"tool.fatal\", retryable: false };\n\t\tcase \"compaction\":\n\t\t\treturn {\n\t\t\t\tkind: \"compaction\",\n\t\t\t\tphase: \"compaction\",\n\t\t\t\tcauseCode: `compaction.${cause.code}`,\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"persistence\":\n\t\t\treturn {\n\t\t\t\tkind: \"persistence\",\n\t\t\t\tphase: \"persistence\",\n\t\t\t\tcauseCode: `persistence.${cause.code}`,\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"process\":\n\t\t\tif (cause.code === \"signal\") {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"process_signal\",\n\t\t\t\t\tphase: \"process\",\n\t\t\t\t\tcauseCode: \"process.signal\",\n\t\t\t\t\tretryable: false,\n\t\t\t\t\tprocessSignal: cause.signal,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tkind: \"process_crash\",\n\t\t\t\tphase: \"resume\",\n\t\t\t\tcauseCode: \"process.crash\",\n\t\t\t\tretryable: true,\n\t\t\t};\n\t\tcase \"transcript\":\n\t\t\treturn {\n\t\t\t\tkind: \"transcript_invalid\",\n\t\t\t\tphase: source === \"inferred_on_resume\" ? \"resume\" : \"preflight\",\n\t\t\t\tcauseCode: `transcript.${cause.code}`,\n\t\t\t\tretryable: false,\n\t\t\t\ttranscriptIssue: cause.code,\n\t\t\t};\n\t\tcase \"configuration\":\n\t\t\treturn {\n\t\t\t\tkind: \"configuration\",\n\t\t\t\tphase: \"preflight\",\n\t\t\t\tcauseCode: \"configuration.invalid\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"internal\":\n\t\t\treturn {\n\t\t\t\tkind: \"internal_error\",\n\t\t\t\tphase: \"preflight\",\n\t\t\t\tcauseCode: \"internal.unclassified\",\n\t\t\t\tretryable: false,\n\t\t\t};\n\t\tcase \"resource\":\n\t\t\t// §15.4: every resource cause is retryable; queue overflow surfaces at\n\t\t\t// the tool boundary, everything else at preflight admission.\n\t\t\treturn {\n\t\t\t\tkind: \"resource_pressure\",\n\t\t\t\tphase: cause.code === \"queue_overflow\" ? \"tool\" : \"preflight\",\n\t\t\t\tcauseCode: `resource.${cause.code}`,\n\t\t\t\tretryable: true,\n\t\t\t};\n\t}\n}\n\nfunction isSafeToAutoRetry(classification: Classification, input: ClassifySessionTerminationInput): boolean {\n\t// provider_refusal: Fable/Claude often false-positive on benign turns. Safe to auto-retry once\n\t// when no tool side effects occurred (same class of recovery as rate_limit/network).\n\treturn (\n\t\tclassification.retryable &&\n\t\tinput.source === \"observed\" &&\n\t\tinput.sideEffects === \"none\" &&\n\t\t(classification.kind === \"provider_rate_limit\" ||\n\t\t\tclassification.kind === \"provider_network\" ||\n\t\t\tclassification.kind === \"provider_refusal\" ||\n\t\t\t// §15.4 mapping: only cpu pressure is safe to auto-retry (with delay).\n\t\t\tclassification.causeCode === \"resource.cpu\")\n\t);\n}\n\nfunction nextActionFor(classification: Classification, input: ClassifySessionTerminationInput): string {\n\tswitch (classification.kind) {\n\t\tcase \"completed\":\n\t\t\treturn \"No recovery action is required; continue with the next prompt.\";\n\t\tcase \"user_abort\":\n\t\t\treturn \"Review possible partial side effects, then retry only if intended.\";\n\t\tcase \"resource_pressure\":\n\t\t\treturn \"Host resources are constrained; reduce load or wait for recovery, then retry.\";\n\t\tcase \"provider_abort\":\n\t\t\treturn \"Confirm provider availability, then retry the request.\";\n\t\tcase \"provider_auth\":\n\t\t\treturn `Run /login${input.provider ? ` ${input.provider}` : \"\"} or configure valid credentials, then retry.`;\n\t\tcase \"provider_rate_limit\":\n\t\t\treturn \"Wait for the provider retry window or choose another model, then retry.\";\n\t\tcase \"provider_network\":\n\t\t\treturn \"Check network and provider connectivity, then retry.\";\n\t\tcase \"provider_protocol\":\n\t\t\treturn \"Often an orphan tool_call_id / sticky transcript after a dropped error turn — auto-retry after sanitize, or /new session if it persists.\";\n\t\tcase \"provider_refusal\":\n\t\t\treturn \"Model declined this turn (content/safety stop). Usually a false positive on Fable/Claude — auto-retry once, or switch model (k3/qwen3.8-max/grok-4.5/deepseek) / rephrase as a pure coding task.\";\n\t\tcase \"context_overflow\":\n\t\t\treturn \"Compact or reduce context, or switch to a larger-context model.\";\n\t\tcase \"tool_timeout\":\n\t\t\treturn \"Inspect possible tool side effects and increase the tool timeout only if safe.\";\n\t\tcase \"tool_fatal\":\n\t\t\treturn \"Inspect the failed tool result and repair its configuration before retrying.\";\n\t\tcase \"compaction\":\n\t\t\tswitch (classification.causeCode) {\n\t\t\t\tcase \"compaction.aborted\":\n\t\t\t\t\treturn \"Retry /compact if cancellation was unintended; avoid Escape while compaction is running.\";\n\t\t\t\tcase \"compaction.stale\":\n\t\t\t\t\treturn \"Retry /compact after pending session changes settle.\";\n\t\t\t\tdefault:\n\t\t\t\t\treturn \"Resolve the reported compaction error, then retry /compact.\";\n\t\t\t}\n\t\tcase \"persistence\":\n\t\t\treturn `Run omk session doctor --session ${input.sessionId} before resuming.`;\n\t\tcase \"process_signal\":\n\t\t\treturn \"Review possible partial side effects before resuming the session.\";\n\t\tcase \"process_crash\":\n\t\t\treturn `Run omk session doctor --session ${input.sessionId} and review partial side effects.`;\n\t\tcase \"transcript_invalid\":\n\t\t\treturn `Run omk session doctor --session ${input.sessionId}; do not resume until integrity passes.`;\n\t\tcase \"configuration\":\n\t\t\treturn \"Correct the provider, model, tool, or session configuration and retry.\";\n\t\tcase \"internal_error\":\n\t\t\treturn `Inspect run ${input.runId} diagnostics and the run journal before retrying.`;\n\t}\n}\n\n/** Concise actionable rendering shared by non-JSON and TUI surfaces. */\nexport function formatSessionTermination(termination: SessionTermination): string {\n\tconst route =\n\t\ttermination.provider || termination.model\n\t\t\t? `${termination.provider ?? \"unknown\"}/${termination.model ?? \"unknown\"}`\n\t\t\t: \"n/a\";\n\treturn [\n\t\t`kind=${termination.kind}`,\n\t\t`provider/model=${route}`,\n\t\t`retryable=${termination.retryable}`,\n\t\t`cause=${termination.causeCode}`,\n\t\t`message=${termination.message}`,\n\t\t`run=${termination.runId}`,\n\t\t`next=${termination.nextAction}`,\n\t].join(\" \");\n}\n\n/**\n * Classify a bounded, structured termination cause. This intentionally accepts\n * no raw Error value, stack, provider body, or arbitrary cause code.\n */\nexport function classifySessionTermination(input: ClassifySessionTerminationInput): SessionTermination {\n\tassertClassifierInput(input);\n\tassertIdentifier(input.sessionId, \"sessionId\", 128);\n\tassertIdentifier(input.runId, \"runId\", 128);\n\tassertTimestamp(input.timestamp);\n\tassertPreRedactedTerminationMessage(input.message);\n\tif (input.provider !== undefined) assertIdentifier(input.provider, \"provider\", 128);\n\tif (input.model !== undefined) assertIdentifier(input.model, \"model\", 256);\n\tif (input.toolCallId !== undefined) assertIdentifier(input.toolCallId, \"toolCallId\", 512);\n\tif (input.toolName !== undefined) assertIdentifier(input.toolName, \"toolName\", 128);\n\tif (input.retryAfterMs !== undefined && (!Number.isSafeInteger(input.retryAfterMs) || input.retryAfterMs < 0)) {\n\t\tthrow new TypeError(\"retryAfterMs must be a non-negative safe integer\");\n\t}\n\tif (input.cause.area === \"process\" && input.cause.code === \"crash\" && input.source !== \"inferred_on_resume\") {\n\t\tthrow new TypeError(\"process crashes may only be classified as inferred_on_resume\");\n\t}\n\tif (input.cause.area === \"process\" && input.cause.code === \"signal\" && input.source !== \"observed\") {\n\t\tthrow new TypeError(\"process signals must be observed\");\n\t}\n\n\tconst classification = classifyCause(input.cause, input.source);\n\treturn Object.freeze({\n\t\tschemaVersion: SESSION_TERMINATION_SCHEMA_VERSION,\n\t\tsessionId: input.sessionId,\n\t\trunId: input.runId,\n\t\tkind: classification.kind,\n\t\tphase: classification.phase,\n\t\tsource: input.source,\n\t\tmessage: input.message,\n\t\tcauseCode: classification.causeCode,\n\t\tnextAction: nextActionFor(classification, input),\n\t\tretryable: classification.retryable,\n\t\tsafeToAutoRetry: isSafeToAutoRetry(classification, input),\n\t\tsideEffects: input.sideEffects,\n\t\ttimestamp: input.timestamp,\n\t\t...(input.retryAfterMs === undefined ? {} : { retryAfterMs: input.retryAfterMs }),\n\t\t...(input.provider === undefined ? {} : { provider: input.provider }),\n\t\t...(input.model === undefined ? {} : { model: input.model }),\n\t\t...(input.toolCallId === undefined ? {} : { toolCallId: input.toolCallId }),\n\t\t...(input.toolName === undefined ? {} : { toolName: input.toolName }),\n\t\t...(classification.processSignal === undefined ? {} : { processSignal: classification.processSignal }),\n\t\t...(classification.transcriptIssue === undefined ? {} : { transcriptIssue: classification.transcriptIssue }),\n\t});\n}\n"]}
|
|
@@ -24,6 +24,7 @@ export const SESSION_TERMINATION_KIND_VALUES = [
|
|
|
24
24
|
"process_crash",
|
|
25
25
|
"configuration",
|
|
26
26
|
"internal_error",
|
|
27
|
+
"resource_pressure",
|
|
27
28
|
];
|
|
28
29
|
export class SessionTerminationError extends Error {
|
|
29
30
|
termination;
|
|
@@ -64,6 +65,14 @@ const PROVIDER_CAUSE_CODES = new Set([
|
|
|
64
65
|
"context_overflow",
|
|
65
66
|
]);
|
|
66
67
|
const TOOL_CAUSE_CODES = new Set(["timeout", "fatal"]);
|
|
68
|
+
const RESOURCE_CAUSE_CODES = new Set([
|
|
69
|
+
"memory",
|
|
70
|
+
"disk",
|
|
71
|
+
"cpu",
|
|
72
|
+
"heap",
|
|
73
|
+
"probe_unavailable",
|
|
74
|
+
"queue_overflow",
|
|
75
|
+
]);
|
|
67
76
|
const COMPACTION_CAUSE_CODES = new Set(["aborted", "failed", "stale"]);
|
|
68
77
|
const PERSISTENCE_CAUSE_CODES = new Set([
|
|
69
78
|
"read_failed",
|
|
@@ -148,6 +157,12 @@ function assertCause(cause) {
|
|
|
148
157
|
case "internal":
|
|
149
158
|
valid = code === "unclassified" && hasOnlyKeys(cause, ["area", "code"]);
|
|
150
159
|
break;
|
|
160
|
+
case "resource":
|
|
161
|
+
valid =
|
|
162
|
+
typeof code === "string" &&
|
|
163
|
+
RESOURCE_CAUSE_CODES.has(code) &&
|
|
164
|
+
hasOnlyKeys(cause, ["area", "code"]);
|
|
165
|
+
break;
|
|
151
166
|
}
|
|
152
167
|
if (!valid)
|
|
153
168
|
throw new TypeError("cause must be a bounded structured termination cause");
|
|
@@ -324,6 +339,15 @@ function classifyCause(cause, source) {
|
|
|
324
339
|
causeCode: "internal.unclassified",
|
|
325
340
|
retryable: false,
|
|
326
341
|
};
|
|
342
|
+
case "resource":
|
|
343
|
+
// §15.4: every resource cause is retryable; queue overflow surfaces at
|
|
344
|
+
// the tool boundary, everything else at preflight admission.
|
|
345
|
+
return {
|
|
346
|
+
kind: "resource_pressure",
|
|
347
|
+
phase: cause.code === "queue_overflow" ? "tool" : "preflight",
|
|
348
|
+
causeCode: `resource.${cause.code}`,
|
|
349
|
+
retryable: true,
|
|
350
|
+
};
|
|
327
351
|
}
|
|
328
352
|
}
|
|
329
353
|
function isSafeToAutoRetry(classification, input) {
|
|
@@ -334,7 +358,9 @@ function isSafeToAutoRetry(classification, input) {
|
|
|
334
358
|
input.sideEffects === "none" &&
|
|
335
359
|
(classification.kind === "provider_rate_limit" ||
|
|
336
360
|
classification.kind === "provider_network" ||
|
|
337
|
-
classification.kind === "provider_refusal"
|
|
361
|
+
classification.kind === "provider_refusal" ||
|
|
362
|
+
// §15.4 mapping: only cpu pressure is safe to auto-retry (with delay).
|
|
363
|
+
classification.causeCode === "resource.cpu"));
|
|
338
364
|
}
|
|
339
365
|
function nextActionFor(classification, input) {
|
|
340
366
|
switch (classification.kind) {
|
|
@@ -342,6 +368,8 @@ function nextActionFor(classification, input) {
|
|
|
342
368
|
return "No recovery action is required; continue with the next prompt.";
|
|
343
369
|
case "user_abort":
|
|
344
370
|
return "Review possible partial side effects, then retry only if intended.";
|
|
371
|
+
case "resource_pressure":
|
|
372
|
+
return "Host resources are constrained; reduce load or wait for recovery, then retry.";
|
|
345
373
|
case "provider_abort":
|
|
346
374
|
return "Confirm provider availability, then retry the request.";
|
|
347
375
|
case "provider_auth":
|