@qduc/term2 0.32.2 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/dist/build-info.json +2 -2
  2. package/dist/components/menu/ModelSelectionMenu.d.ts.map +1 -1
  3. package/dist/components/menu/ModelSelectionMenu.js +12 -14
  4. package/dist/components/menu/ModelSelectionMenu.js.map +1 -1
  5. package/dist/lib/agent-chat-service.d.ts.map +1 -1
  6. package/dist/lib/agent-chat-service.js +2 -6
  7. package/dist/lib/agent-chat-service.js.map +1 -1
  8. package/dist/lib/agent-client.d.ts +2 -1
  9. package/dist/lib/agent-client.d.ts.map +1 -1
  10. package/dist/lib/agent-client.js +71 -84
  11. package/dist/lib/agent-client.js.map +1 -1
  12. package/dist/lib/agent-factory.d.ts.map +1 -1
  13. package/dist/lib/agent-factory.js +17 -19
  14. package/dist/lib/agent-factory.js.map +1 -1
  15. package/dist/prompts/tool-surface-guidance.js +1 -1
  16. package/dist/prompts/tool-surface-guidance.js.map +1 -1
  17. package/dist/prompts/tool-surface-guidance.ts +1 -1
  18. package/dist/providers/ai-sdk-anthropic.provider.d.ts.map +1 -1
  19. package/dist/providers/ai-sdk-anthropic.provider.js +16 -2
  20. package/dist/providers/ai-sdk-anthropic.provider.js.map +1 -1
  21. package/dist/providers/ai-sdk-google.provider.d.ts.map +1 -1
  22. package/dist/providers/ai-sdk-google.provider.js +16 -2
  23. package/dist/providers/ai-sdk-google.provider.js.map +1 -1
  24. package/dist/providers/ai-sdk-openrouter.provider.d.ts.map +1 -1
  25. package/dist/providers/ai-sdk-openrouter.provider.js +16 -1
  26. package/dist/providers/ai-sdk-openrouter.provider.js.map +1 -1
  27. package/dist/providers/ai-sdk-provider-settings.d.ts +2 -0
  28. package/dist/providers/ai-sdk-provider-settings.d.ts.map +1 -1
  29. package/dist/providers/ai-sdk-provider-settings.js +5 -0
  30. package/dist/providers/ai-sdk-provider-settings.js.map +1 -1
  31. package/dist/providers/ai-sdk-streamed-model.d.ts +3 -3
  32. package/dist/providers/ai-sdk-streamed-model.d.ts.map +1 -1
  33. package/dist/providers/ai-sdk-streamed-model.js +5 -69
  34. package/dist/providers/ai-sdk-streamed-model.js.map +1 -1
  35. package/dist/providers/anthropic-chat-caching.d.ts +5 -0
  36. package/dist/providers/anthropic-chat-caching.d.ts.map +1 -0
  37. package/dist/providers/{common/openai-compatible-messages.js → anthropic-chat-caching.js} +19 -1
  38. package/dist/providers/anthropic-chat-caching.js.map +1 -0
  39. package/dist/providers/codex.provider.js +2 -0
  40. package/dist/providers/codex.provider.js.map +1 -1
  41. package/dist/providers/custom-provider-runtime.d.ts +18 -0
  42. package/dist/providers/custom-provider-runtime.d.ts.map +1 -0
  43. package/dist/providers/custom-provider-runtime.js +26 -0
  44. package/dist/providers/custom-provider-runtime.js.map +1 -0
  45. package/dist/providers/llama-cpp.provider.d.ts +3 -0
  46. package/dist/providers/llama-cpp.provider.d.ts.map +1 -1
  47. package/dist/providers/llama-cpp.provider.js +17 -0
  48. package/dist/providers/llama-cpp.provider.js.map +1 -1
  49. package/dist/providers/neuralwatt.provider.d.ts +6 -0
  50. package/dist/providers/neuralwatt.provider.d.ts.map +1 -0
  51. package/dist/providers/neuralwatt.provider.js +8 -0
  52. package/dist/providers/neuralwatt.provider.js.map +1 -0
  53. package/dist/providers/openai-compatible-lazy.d.ts.map +1 -1
  54. package/dist/providers/openai-compatible-lazy.js +2 -0
  55. package/dist/providers/openai-compatible-lazy.js.map +1 -1
  56. package/dist/providers/openai-compatible-middleware.d.ts +4 -9
  57. package/dist/providers/openai-compatible-middleware.d.ts.map +1 -1
  58. package/dist/providers/openai-compatible-middleware.js +25 -48
  59. package/dist/providers/openai-compatible-middleware.js.map +1 -1
  60. package/dist/providers/openai-compatible-response-normalizer.d.ts +1 -1
  61. package/dist/providers/openai-compatible-response-normalizer.d.ts.map +1 -1
  62. package/dist/providers/openai-compatible-response-normalizer.js +6 -6
  63. package/dist/providers/openai-compatible-response-normalizer.js.map +1 -1
  64. package/dist/providers/openai-compatible.provider.d.ts +2 -32
  65. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  66. package/dist/providers/openai-compatible.provider.js +18 -144
  67. package/dist/providers/openai-compatible.provider.js.map +1 -1
  68. package/dist/providers/openai.provider.d.ts.map +1 -1
  69. package/dist/providers/openai.provider.js +1 -0
  70. package/dist/providers/openai.provider.js.map +1 -1
  71. package/dist/providers/opencode-middleware.d.ts +9 -0
  72. package/dist/providers/opencode-middleware.d.ts.map +1 -0
  73. package/dist/providers/opencode-middleware.js +15 -0
  74. package/dist/providers/opencode-middleware.js.map +1 -0
  75. package/dist/providers/opencode-model-provider.d.ts +19 -0
  76. package/dist/providers/opencode-model-provider.d.ts.map +1 -0
  77. package/dist/providers/opencode-model-provider.js +128 -0
  78. package/dist/providers/opencode-model-provider.js.map +1 -0
  79. package/dist/providers/opencode.provider.d.ts +2 -0
  80. package/dist/providers/opencode.provider.d.ts.map +1 -1
  81. package/dist/providers/opencode.provider.js +4 -0
  82. package/dist/providers/opencode.provider.js.map +1 -1
  83. package/dist/providers/openrouter-decision-models.d.ts +7 -0
  84. package/dist/providers/openrouter-decision-models.d.ts.map +1 -0
  85. package/dist/providers/openrouter-decision-models.js +31 -0
  86. package/dist/providers/openrouter-decision-models.js.map +1 -0
  87. package/dist/providers/openrouter.provider.js +2 -1
  88. package/dist/providers/openrouter.provider.js.map +1 -1
  89. package/dist/providers/orcarouter-models.d.ts +12 -0
  90. package/dist/providers/orcarouter-models.d.ts.map +1 -0
  91. package/dist/providers/orcarouter-models.js +34 -0
  92. package/dist/providers/orcarouter-models.js.map +1 -0
  93. package/dist/providers/registry.d.ts +7 -0
  94. package/dist/providers/registry.d.ts.map +1 -1
  95. package/dist/providers/registry.js.map +1 -1
  96. package/dist/services/agent-runtime/application-run-loop.d.ts +20 -1
  97. package/dist/services/agent-runtime/application-run-loop.d.ts.map +1 -1
  98. package/dist/services/agent-runtime/application-run-loop.js +871 -759
  99. package/dist/services/agent-runtime/application-run-loop.js.map +1 -1
  100. package/dist/services/agent-runtime/tool-call-execution.d.ts +1 -1
  101. package/dist/services/agent-runtime/tool-call-execution.d.ts.map +1 -1
  102. package/dist/services/agent-runtime/tool-call-execution.js +6 -3
  103. package/dist/services/agent-runtime/tool-call-execution.js.map +1 -1
  104. package/dist/services/approval/approval-flow-coordinator.d.ts +4 -2
  105. package/dist/services/approval/approval-flow-coordinator.d.ts.map +1 -1
  106. package/dist/services/approval/approval-flow-coordinator.js +2 -1
  107. package/dist/services/approval/approval-flow-coordinator.js.map +1 -1
  108. package/dist/services/approval/approval-state.d.ts +0 -13
  109. package/dist/services/approval/approval-state.d.ts.map +1 -1
  110. package/dist/services/approval/approval-state.js +0 -5
  111. package/dist/services/approval/approval-state.js.map +1 -1
  112. package/dist/services/approval/background-subagent-approval-controller.d.ts.map +1 -1
  113. package/dist/services/approval/background-subagent-approval-controller.js +0 -2
  114. package/dist/services/approval/background-subagent-approval-controller.js.map +1 -1
  115. package/dist/services/approval/nested-approval-owner.d.ts.map +1 -1
  116. package/dist/services/approval/nested-approval-owner.js +5 -1
  117. package/dist/services/approval/nested-approval-owner.js.map +1 -1
  118. package/dist/services/approval/shell-auto-approval-resolver.d.ts +2 -2
  119. package/dist/services/approval/shell-auto-approval-resolver.d.ts.map +1 -1
  120. package/dist/services/approval/shell-auto-approval-resolver.js +2 -2
  121. package/dist/services/approval/shell-auto-approval-resolver.js.map +1 -1
  122. package/dist/services/approval/tool-approval-batch-coordinator.d.ts +2 -2
  123. package/dist/services/approval/tool-approval-batch-coordinator.d.ts.map +1 -1
  124. package/dist/services/approval/tool-approval-batch-coordinator.js +4 -4
  125. package/dist/services/approval/tool-approval-batch-coordinator.js.map +1 -1
  126. package/dist/services/conversation/conversation-adapter.d.ts +17 -47
  127. package/dist/services/conversation/conversation-adapter.d.ts.map +1 -1
  128. package/dist/services/conversation/conversation-adapter.js +114 -88
  129. package/dist/services/conversation/conversation-adapter.js.map +1 -1
  130. package/dist/services/conversation/conversation-orchestrator.d.ts.map +1 -1
  131. package/dist/services/conversation/conversation-orchestrator.js +48 -121
  132. package/dist/services/conversation/conversation-orchestrator.js.map +1 -1
  133. package/dist/services/conversation/conversation-record-stream.d.ts +21 -0
  134. package/dist/services/conversation/conversation-record-stream.d.ts.map +1 -0
  135. package/dist/services/conversation/conversation-record-stream.js +186 -0
  136. package/dist/services/conversation/conversation-record-stream.js.map +1 -0
  137. package/dist/services/conversation/conversation-record.d.ts +160 -0
  138. package/dist/services/conversation/conversation-record.d.ts.map +1 -0
  139. package/dist/services/conversation/conversation-record.js +371 -0
  140. package/dist/services/conversation/conversation-record.js.map +1 -0
  141. package/dist/services/conversation/conversation-result-builder.d.ts +0 -4
  142. package/dist/services/conversation/conversation-result-builder.d.ts.map +1 -1
  143. package/dist/services/conversation/conversation-result-builder.js +0 -10
  144. package/dist/services/conversation/conversation-result-builder.js.map +1 -1
  145. package/dist/services/conversation/conversation-service.d.ts +2 -1
  146. package/dist/services/conversation/conversation-service.d.ts.map +1 -1
  147. package/dist/services/conversation/conversation-service.js +3 -0
  148. package/dist/services/conversation/conversation-service.js.map +1 -1
  149. package/dist/services/conversation-agent-client.d.ts +17 -7
  150. package/dist/services/conversation-agent-client.d.ts.map +1 -1
  151. package/dist/services/logging/conversation-logger.d.ts +1 -7
  152. package/dist/services/logging/conversation-logger.d.ts.map +1 -1
  153. package/dist/services/logging/conversation-logger.js +0 -18
  154. package/dist/services/logging/conversation-logger.js.map +1 -1
  155. package/dist/services/model-service.d.ts +2 -6
  156. package/dist/services/model-service.d.ts.map +1 -1
  157. package/dist/services/model-service.js +8 -21
  158. package/dist/services/model-service.js.map +1 -1
  159. package/dist/services/models/decision-model-listing.d.ts +2 -12
  160. package/dist/services/models/decision-model-listing.d.ts.map +1 -1
  161. package/dist/services/models/decision-model-listing.js +2 -29
  162. package/dist/services/models/decision-model-listing.js.map +1 -1
  163. package/dist/services/provider-continuity.d.ts +26 -0
  164. package/dist/services/provider-continuity.d.ts.map +1 -1
  165. package/dist/services/provider-continuity.js +47 -0
  166. package/dist/services/provider-continuity.js.map +1 -1
  167. package/dist/services/retry/recovery-executor.d.ts +3 -4
  168. package/dist/services/retry/recovery-executor.d.ts.map +1 -1
  169. package/dist/services/retry/recovery-executor.js +13 -75
  170. package/dist/services/retry/recovery-executor.js.map +1 -1
  171. package/dist/services/retry/retry-contracts.d.ts +2 -10
  172. package/dist/services/retry/retry-contracts.d.ts.map +1 -1
  173. package/dist/services/session/continuation-plan-applier.d.ts +5 -9
  174. package/dist/services/session/continuation-plan-applier.d.ts.map +1 -1
  175. package/dist/services/session/continuation-plan-applier.js +9 -43
  176. package/dist/services/session/continuation-plan-applier.js.map +1 -1
  177. package/dist/services/session/continuation-recovery-handler.d.ts +4 -6
  178. package/dist/services/session/continuation-recovery-handler.d.ts.map +1 -1
  179. package/dist/services/session/continuation-recovery-handler.js +3 -15
  180. package/dist/services/session/continuation-recovery-handler.js.map +1 -1
  181. package/dist/services/session/initial-input-preparer.d.ts +2 -2
  182. package/dist/services/session/initial-input-preparer.d.ts.map +1 -1
  183. package/dist/services/session/initial-input-preparer.js +3 -3
  184. package/dist/services/session/initial-input-preparer.js.map +1 -1
  185. package/dist/services/session/initial-turn-recovery-handler.d.ts +2 -4
  186. package/dist/services/session/initial-turn-recovery-handler.d.ts.map +1 -1
  187. package/dist/services/session/initial-turn-recovery-handler.js +4 -5
  188. package/dist/services/session/initial-turn-recovery-handler.js.map +1 -1
  189. package/dist/services/session/live-run.d.ts +9 -3
  190. package/dist/services/session/live-run.d.ts.map +1 -1
  191. package/dist/services/session/live-run.js +19 -4
  192. package/dist/services/session/live-run.js.map +1 -1
  193. package/dist/services/session/session-composition.d.ts +8 -12
  194. package/dist/services/session/session-composition.d.ts.map +1 -1
  195. package/dist/services/session/session-composition.js +41 -87
  196. package/dist/services/session/session-composition.js.map +1 -1
  197. package/dist/services/session/session-input-planner.d.ts +2 -2
  198. package/dist/services/session/session-input-planner.d.ts.map +1 -1
  199. package/dist/services/session/session-input-planner.js +6 -13
  200. package/dist/services/session/session-input-planner.js.map +1 -1
  201. package/dist/services/session/session-lifecycle.d.ts +73 -23
  202. package/dist/services/session/session-lifecycle.d.ts.map +1 -1
  203. package/dist/services/session/session-lifecycle.js +127 -45
  204. package/dist/services/session/session-lifecycle.js.map +1 -1
  205. package/dist/services/session/session-stream-processor.d.ts +4 -30
  206. package/dist/services/session/session-stream-processor.d.ts.map +1 -1
  207. package/dist/services/session/session-stream-processor.js +11 -300
  208. package/dist/services/session/session-stream-processor.js.map +1 -1
  209. package/dist/services/session/session-tool-tracker.d.ts +9 -144
  210. package/dist/services/session/session-tool-tracker.d.ts.map +1 -1
  211. package/dist/services/session/session-tool-tracker.js +17 -251
  212. package/dist/services/session/session-tool-tracker.js.map +1 -1
  213. package/dist/services/session/{turn-workflow.d.ts → session-turn-adapter.d.ts} +10 -43
  214. package/dist/services/session/session-turn-adapter.d.ts.map +1 -0
  215. package/dist/services/session/session-turn-adapter.js +734 -0
  216. package/dist/services/session/session-turn-adapter.js.map +1 -0
  217. package/dist/services/session/turn-attempt-factory.d.ts +2 -11
  218. package/dist/services/session/turn-attempt-factory.d.ts.map +1 -1
  219. package/dist/services/session/turn-attempt-factory.js +4 -12
  220. package/dist/services/session/turn-attempt-factory.js.map +1 -1
  221. package/dist/services/session/turn-attempt.d.ts +0 -3
  222. package/dist/services/session/turn-attempt.d.ts.map +1 -1
  223. package/dist/services/session/turn-attempt.js +0 -5
  224. package/dist/services/session/turn-attempt.js.map +1 -1
  225. package/dist/services/session/turn-coordinator.d.ts +3 -3
  226. package/dist/services/session/turn-coordinator.d.ts.map +1 -1
  227. package/dist/services/session/turn-coordinator.js +31 -26
  228. package/dist/services/session/turn-coordinator.js.map +1 -1
  229. package/dist/services/session/turn-projection-state.d.ts +45 -0
  230. package/dist/services/session/turn-projection-state.d.ts.map +1 -0
  231. package/dist/services/session/turn-projection-state.js +61 -0
  232. package/dist/services/session/turn-projection-state.js.map +1 -0
  233. package/dist/services/subagents/tool-policy.d.ts +5 -5
  234. package/dist/services/subagents/tool-policy.d.ts.map +1 -1
  235. package/dist/services/subagents/tool-policy.js.map +1 -1
  236. package/dist/tools/system/run-code/run-code-runtime.d.ts.map +1 -1
  237. package/dist/tools/system/run-code/run-code-runtime.js +90 -19
  238. package/dist/tools/system/run-code/run-code-runtime.js.map +1 -1
  239. package/dist/tools/system/shell.d.ts +2 -2
  240. package/dist/tools/system/shell.d.ts.map +1 -1
  241. package/dist/tools/system/shell.js +1 -0
  242. package/dist/tools/system/shell.js.map +1 -1
  243. package/dist/tools/tool-names.d.ts +1 -1
  244. package/dist/tools/tool-names.d.ts.map +1 -1
  245. package/dist/utils/ai/token-usage.js +9 -8
  246. package/dist/utils/ai/token-usage.js.map +1 -1
  247. package/package.json +1 -1
  248. package/dist/providers/anthropic-middleware.d.ts +0 -25
  249. package/dist/providers/anthropic-middleware.d.ts.map +0 -1
  250. package/dist/providers/anthropic-middleware.js +0 -32
  251. package/dist/providers/anthropic-middleware.js.map +0 -1
  252. package/dist/providers/common/openai-compatible-messages.d.ts +0 -2
  253. package/dist/providers/common/openai-compatible-messages.d.ts.map +0 -1
  254. package/dist/providers/common/openai-compatible-messages.js.map +0 -1
  255. package/dist/services/session/continuation-call-id-resolver.d.ts +0 -29
  256. package/dist/services/session/continuation-call-id-resolver.d.ts.map +0 -1
  257. package/dist/services/session/continuation-call-id-resolver.js +0 -69
  258. package/dist/services/session/continuation-call-id-resolver.js.map +0 -1
  259. package/dist/services/session/continuation-state.d.ts +0 -69
  260. package/dist/services/session/continuation-state.d.ts.map +0 -1
  261. package/dist/services/session/continuation-state.js +0 -78
  262. package/dist/services/session/continuation-state.js.map +0 -1
  263. package/dist/services/session/session-manager.d.ts +0 -87
  264. package/dist/services/session/session-manager.d.ts.map +0 -1
  265. package/dist/services/session/session-manager.js +0 -146
  266. package/dist/services/session/session-manager.js.map +0 -1
  267. package/dist/services/session/turn-workflow.d.ts.map +0 -1
  268. package/dist/services/session/turn-workflow.js +0 -1055
  269. package/dist/services/session/turn-workflow.js.map +0 -1
@@ -1,1055 +0,0 @@
1
- import { isCommittedOutputEvent } from '../conversation/conversation-events.js';
2
- import { TurnAttempt } from './turn-attempt.js';
3
- import { getMethod, getCallIdFromObject, getToolInfoFromInterruption } from '../interruption-info.js';
4
- import { issueInputSurgeApproval } from '../input-surge-approval.js';
5
- import { ShellAutoApprovalDecisionPolicy } from '../approval/approval-decision-policy.js';
6
- import { describeError } from '../../utils/error-helpers.js';
7
- import { classifyProviderFailure, isClassifiedCancellation } from '../retry/provider-failure-classification.js';
8
- import { ContinuationState } from './continuation-state.js';
9
- import { ToolApprovalBatchCoordinator } from '../approval/tool-approval-batch-coordinator.js';
10
- import { createApprovalRequiredTerminal, buildConversationResult, } from '../conversation/conversation-result-builder.js';
11
- import { selectAgentStreamItems } from '../agent-stream.js';
12
- import { extractCommandMessages, ToolCallMarkerStore } from '../../utils/streaming/extract-command-messages.js';
13
- import { collectKnownToolCallIds, resolveAbortedApprovalCallIds, resolveResponseCycleCallIds, } from './continuation-call-id-resolver.js';
14
- import { LiveRun } from './live-run.js';
15
- import { extractFinalizationSnapshot } from '../stream-snapshot.js';
16
- import { lastOpenAICompaction } from './session-stream-processor.js';
17
- import { contextCompactionFailureCategory } from '../../providers/openai-responses-model.js';
18
- import { resolvePromptCacheKey, resolveSessionId } from './session-identity.js';
19
- export class TurnWorkflow {
20
- deps;
21
- /**
22
- * Graph registry pinned for the active turn. Re-resolved from the client at
23
- * each fresh-turn boundary so post-rebuild turns follow the rebuilt graph,
24
- * while in-flight turns and pending-approval continuations keep the graph
25
- * that produced their interruptions and tools.
26
- */
27
- #activePolicyRegistry;
28
- #batchCoordinator;
29
- #toolCallMarkers;
30
- #liveAttemptOwners = new WeakSet();
31
- #liveRun = null;
32
- #nextLiveRunId = 0;
33
- #hookTurnId;
34
- /**
35
- * The recovery envelope of the initial attempt that started the current
36
- * logical turn, if one is active. Continuation attempts (tool-call
37
- * follow-ups, and standalone approval-resolution continuations) reuse it so
38
- * the whole turn shares one 90s/3-attempt/1-replay budget rather than each
39
- * continuation getting its own unbounded allowance.
40
- */
41
- #activeRecoveryBudget;
42
- /**
43
- * Set once `context_compaction_failed` has been yielded for the current failure
44
- * episode. Recovery replays the same request-boundary failure through both the
45
- * initial and continuation catch sites, and without this each replay appends
46
- * another identical "Context compaction failed" system message. Cleared at the
47
- * next turn boundary (openTurn) and whenever a turn returns an outcome, so a
48
- * genuinely separate episode still surfaces.
49
- */
50
- #compactionFailureNotified = false;
51
- /** Pull the client's current graph registry when it has rotated (rebuild). */
52
- #syncPolicyRegistry() {
53
- const current = this.deps.agentClient?.getApprovalPolicyRegistry?.();
54
- if (current && current !== this.#activePolicyRegistry) {
55
- this.#activePolicyRegistry = current;
56
- this.#batchCoordinator.updatePolicyRegistry(current);
57
- }
58
- }
59
- constructor(deps) {
60
- this.deps = deps;
61
- this.#activePolicyRegistry = deps.approvalPolicyRegistry;
62
- this.#toolCallMarkers = deps.toolCallMarkers ?? new ToolCallMarkerStore();
63
- this.#batchCoordinator =
64
- deps.batchCoordinator ??
65
- new ToolApprovalBatchCoordinator({
66
- approvalFlow: deps.approvalFlow,
67
- planApplier: deps.planApplier,
68
- shellAutoApproval: deps.shellAutoApproval,
69
- logger: deps.logger,
70
- sessionId: deps.sessionId,
71
- sessionAccess: deps.sessionAccess,
72
- policyRegistry: deps.approvalPolicyRegistry,
73
- isCurrent: (token) => deps.generationGuard.isCurrent(token),
74
- hookLifecycle: deps.hookLifecycle,
75
- hookEvents: deps.hookEvents,
76
- });
77
- }
78
- /**
79
- * Tell the run loop where this turn begins and ends, so a message steered
80
- * into it survives the gaps between its streams — the preparation before the
81
- * first request, and the backoff before a retry restarts one.
82
- */
83
- openTurn() {
84
- this.#compactionFailureNotified = false;
85
- this.deps.agentClient.openTurn?.();
86
- }
87
- closeTurn() {
88
- this.deps.agentClient.closeTurn?.();
89
- }
90
- /**
91
- * Yields at most one `context_compaction_failed` per failure episode. Returns
92
- * undefined (and emits nothing) when the same episode already notified, so
93
- * recovery replays of one request-boundary failure render a single UI message.
94
- */
95
- #compactionFailureEvent(category) {
96
- if (this.#compactionFailureNotified)
97
- return undefined;
98
- this.#compactionFailureNotified = true;
99
- return {
100
- type: 'context_compaction_failed',
101
- provider: 'openai',
102
- sessionId: resolveSessionId(this.deps.sessionId),
103
- errorCategory: category,
104
- durationMs: 0,
105
- };
106
- }
107
- steer(items, options) {
108
- return this.deps.agentClient.steer?.(items, options) ?? Promise.resolve('released');
109
- }
110
- /** Drop a still-waiting steer. False when it was already admitted. */
111
- retractSteer(id) {
112
- return this.deps.agentClient.retractSteer?.(id) ?? false;
113
- }
114
- /** Replace a waiting steer's items in place, keeping its position. */
115
- editSteer(id, items) {
116
- return this.deps.agentClient.editSteer?.(id, items) ?? false;
117
- }
118
- /** Internal composition seam for correlating physical tool calls to a turn. */
119
- setHookTurnId(turnId) {
120
- this.#hookTurnId = turnId;
121
- }
122
- async *executeInitial(attemptOrInput, options = {}) {
123
- // Fresh-turn boundary: follow a mid-session rebuild. Continuations
124
- // deliberately do not re-sync — a pending approval resolves against the
125
- // graph that produced its interruption.
126
- this.#syncPolicyRegistry();
127
- try {
128
- const outcome = yield* this.#executeInitialBody(attemptOrInput, options);
129
- // A returned outcome ends the failure episode even when the turn itself
130
- // failed; only a thrown error keeps the episode (and the suppression) alive.
131
- this.#compactionFailureNotified = false;
132
- return outcome;
133
- }
134
- catch (error) {
135
- const failure = contextCompactionFailureCategory(error);
136
- if (failure) {
137
- const event = this.#compactionFailureEvent(failure);
138
- if (event)
139
- yield event;
140
- }
141
- throw error;
142
- }
143
- }
144
- async *#executeInitialBody(attemptOrInput, options = {}) {
145
- let currentInput = attemptOrInput;
146
- let currentOptions = options;
147
- while (true) {
148
- const initialOutcome = yield* this.executeInitialAttempt(currentInput, currentOptions);
149
- if (initialOutcome.kind !== 'abort_resolution_required' && initialOutcome.kind !== 'auto_approval_required') {
150
- return initialOutcome;
151
- }
152
- const generation = initialOutcome.generation;
153
- let driveResult;
154
- if (initialOutcome.kind === 'abort_resolution_required') {
155
- driveResult = yield* this.executeContinuationAttempt({
156
- kind: 'abort_resolution',
157
- abortedContext: initialOutcome.abortedContext,
158
- userText: initialOutcome.userText,
159
- generation,
160
- }, new ShellAutoApprovalDecisionPolicy(this.deps.shellAutoApproval));
161
- }
162
- else {
163
- driveResult = yield* this.executeContinuationAttempt({
164
- kind: 'approval_decision',
165
- answer: 'y',
166
- generation,
167
- }, new ShellAutoApprovalDecisionPolicy(this.deps.shellAutoApproval));
168
- }
169
- if (driveResult.kind !== 'fresh_start_required') {
170
- return driveResult;
171
- }
172
- currentInput = { text: '' };
173
- currentOptions = {
174
- skipUserMessage: true,
175
- retries: driveResult.retryCounts,
176
- delayMs: driveResult.delayMs,
177
- useStandardServiceTier: driveResult.useStandardServiceTier,
178
- disableChainingForAttempt: driveResult.disableChainingForAttempt,
179
- token: generation,
180
- replayFromHistory: true,
181
- };
182
- }
183
- }
184
- async *executeContinuation(init, policy) {
185
- try {
186
- const outcome = yield* this.#executeContinuationBodyImpl(init, policy);
187
- // A returned outcome ends the failure episode (see executeInitial).
188
- this.#compactionFailureNotified = false;
189
- return outcome;
190
- }
191
- catch (error) {
192
- const failure = contextCompactionFailureCategory(error);
193
- if (failure) {
194
- const event = this.#compactionFailureEvent(failure);
195
- if (event)
196
- yield event;
197
- }
198
- throw error;
199
- }
200
- }
201
- async *#executeContinuationBodyImpl(init, policy) {
202
- if (this.#liveRun) {
203
- return yield* this.#continuePostExecuteRun();
204
- }
205
- const driveResult = yield* this.executeContinuationAttempt(init, policy);
206
- if (driveResult.kind !== 'fresh_start_required') {
207
- return driveResult;
208
- }
209
- return yield* this.#replayFromFreshStart(init.generation, driveResult);
210
- }
211
- /** Re-observe an already-running post-execute stream without involving SDK approvals. */
212
- async *continuePostExecute() {
213
- return yield* this.#continuePostExecuteRun();
214
- }
215
- #freshStartReplayOptions(generation, result) {
216
- return {
217
- skipUserMessage: true,
218
- retries: result.retryCounts,
219
- delayMs: result.delayMs,
220
- useStandardServiceTier: result.useStandardServiceTier,
221
- disableChainingForAttempt: result.disableChainingForAttempt,
222
- token: generation,
223
- replayFromHistory: true,
224
- };
225
- }
226
- async *#replayFromFreshStart(generation, result) {
227
- return yield* this.executeInitial({ text: '' }, this.#freshStartReplayOptions(generation, result));
228
- }
229
- async *executeInitialAttempt(attemptOrInput, options = {}) {
230
- let attempt;
231
- if (attemptOrInput instanceof TurnAttempt) {
232
- attempt = attemptOrInput;
233
- }
234
- else {
235
- const creation = this.deps.attemptFactory.create(attemptOrInput, options);
236
- if (creation.kind === 'stale') {
237
- return { kind: 'stale' };
238
- }
239
- attempt = creation.attempt;
240
- }
241
- // Continuation attempts driven from within this same logical turn (tool
242
- // approvals, abort resolution) reuse this budget instead of getting their
243
- // own unbounded one; see #activeRecoveryBudget.
244
- this.#activeRecoveryBudget = attempt.recoveryBudget;
245
- let skipUser = options.skipUserMessage ?? false;
246
- let currentResumeState = options.resumeState;
247
- let currentResumePreviousResponseId = options.resumePreviousResponseId;
248
- let currentDisableChainingForAttempt = options.disableChainingForAttempt === true;
249
- let currentAbortedContext = options.abortedContext ?? null;
250
- const initialCounts = attempt.retryCounts;
251
- if (options.replayFromHistory ||
252
- !skipUser ||
253
- initialCounts.modelRetryCount > 0 ||
254
- initialCounts.serviceTierFallbackCount > 0 ||
255
- initialCounts.transientRetryCount > 0 ||
256
- initialCounts.transportDowngradeCount > 0) {
257
- this.deps.turnAccumulator.resetPersistedTurnState();
258
- }
259
- try {
260
- if (options.delayMs && options.delayMs > 0) {
261
- await this.#waitBeforeRetry(attempt, options.delayMs);
262
- }
263
- if (!this.deps.generationGuard.isCurrent(attempt.token)) {
264
- return { kind: 'stale' };
265
- }
266
- if (options.useStandardServiceTier) {
267
- getMethod(this.deps.agentClient, 'useStandardServiceTierForNextRequest')?.call(this.deps.agentClient);
268
- }
269
- this.deps.toolTracker.ledger.beginTurn();
270
- this.deps.journal.resetForNewTurn();
271
- while (true) {
272
- // 1. Check generation token validity
273
- if (currentAbortedContext) {
274
- const tokenVal = currentAbortedContext.token ?? 0;
275
- if (!this.deps.generationGuard.isCurrent(tokenVal)) {
276
- return { kind: 'stale' };
277
- }
278
- }
279
- else {
280
- if (!this.deps.generationGuard.isCurrent(attempt.token)) {
281
- return { kind: 'stale' };
282
- }
283
- }
284
- // 2. Handle aborted-approval resolution
285
- if (currentAbortedContext) {
286
- // Preserve the follow-up prompt as a normal user turn so the next
287
- // turn can steer the conversation naturally after an ESC abort.
288
- const preparation = this.deps.inputPreparer.prepare(attempt, skipUser, {
289
- inputSurgeApproval: issueInputSurgeApproval(attempt.submittedTurn),
290
- replayFromHistory: options.replayFromHistory,
291
- });
292
- if (preparation.kind === 'blocked') {
293
- yield preparation.event;
294
- return { kind: 'failed' };
295
- }
296
- this.deps.logger.debug('Resolving aborted approval with fake execution', {
297
- message: attempt.turn.text,
298
- });
299
- return {
300
- kind: 'abort_resolution_required',
301
- abortedContext: currentAbortedContext,
302
- userText: attempt.turn.text,
303
- generation: attempt.token,
304
- };
305
- }
306
- const preparation = this.deps.inputPreparer.prepare(attempt, skipUser, options);
307
- if (preparation.kind === 'blocked') {
308
- yield preparation.event;
309
- return { kind: 'failed' };
310
- }
311
- try {
312
- const cycleResult = yield* this.#executeInitialStreamCycle(attempt, {
313
- resumeState: currentResumeState,
314
- resumePreviousResponseId: currentResumePreviousResponseId,
315
- disableChainingForAttempt: currentDisableChainingForAttempt,
316
- });
317
- currentDisableChainingForAttempt = false;
318
- if (cycleResult.kind === 'stale') {
319
- return { kind: 'stale' };
320
- }
321
- if (cycleResult.kind === 'post_execute_approval_required') {
322
- return { kind: 'approval_required', terminal: this.#postExecuteApprovalTerminal(cycleResult.entries) };
323
- }
324
- const { outcome } = cycleResult;
325
- attempt.advanceRetry({ ...attempt.retryCounts, transientRetryCount: 0 });
326
- if (outcome.kind === 'response') {
327
- return { kind: 'response', terminal: outcome.result };
328
- }
329
- if (outcome.kind === 'auto_approve') {
330
- if (outcome.advisory?.source === 'llm') {
331
- this.#toolCallMarkers.markToolCallAsLlmAutoApproved(outcome.callId);
332
- }
333
- this.deps.logger.debug('Shell command auto-approved by LLM', {
334
- eventType: 'approval.auto_approved',
335
- category: 'approval',
336
- phase: 'approval',
337
- sessionId: resolveSessionId(this.deps.sessionId),
338
- traceId: this.deps.logger.getCorrelationId(),
339
- callId: outcome.callId,
340
- command: outcome.argumentsText,
341
- model: outcome.advisory?.model,
342
- reasoning: outcome.advisory?.reasoning,
343
- });
344
- return {
345
- kind: 'auto_approval_required',
346
- generation: attempt.token,
347
- callId: outcome.callId,
348
- command: outcome.argumentsText,
349
- };
350
- }
351
- if (outcome.result.approval.callId) {
352
- this.deps.toolTracker.recordFunctionCall({
353
- type: 'function_call',
354
- callId: outcome.result.approval.callId,
355
- name: outcome.result.approval.toolName,
356
- arguments: outcome.result.approval.argumentsText,
357
- });
358
- }
359
- await this.#emitApprovalRequested(outcome.result.approval);
360
- this.deps.logger.debug('Tool approval required', {
361
- eventType: 'approval.required',
362
- category: 'approval',
363
- phase: 'approval',
364
- sessionId: resolveSessionId(this.deps.sessionId),
365
- traceId: this.deps.logger.getCorrelationId(),
366
- toolName: outcome.result.approval.toolName,
367
- });
368
- return { kind: 'approval_required', terminal: outcome.result };
369
- }
370
- catch (error) {
371
- const handled = yield* this.deps.recoveryHandler.handle({
372
- error,
373
- attempt,
374
- stream: attempt.stream,
375
- });
376
- if (handled.kind === 'run') {
377
- skipUser = handled.instruction.skipUserMessage;
378
- currentResumeState = handled.instruction.resumeState;
379
- currentResumePreviousResponseId = handled.instruction.resumePreviousResponseId;
380
- currentDisableChainingForAttempt = handled.instruction.disableChainingForAttempt === true;
381
- currentAbortedContext = null;
382
- if (handled.delayMs && handled.delayMs > 0) {
383
- await this.#waitBeforeRetry(attempt, handled.delayMs);
384
- }
385
- if (handled.useStandardServiceTier) {
386
- getMethod(this.deps.agentClient, 'useStandardServiceTierForNextRequest')?.call(this.deps.agentClient);
387
- }
388
- continue;
389
- }
390
- else if (handled.kind === 'stale') {
391
- return { kind: 'stale' };
392
- }
393
- else {
394
- throw error;
395
- }
396
- }
397
- }
398
- }
399
- finally {
400
- // A post-execute gate hands this attempt to its live consumer. That task
401
- // owns terminal cleanup; closing here would detach abort handling while
402
- // the SDK is still waiting at the tool boundary.
403
- if (!this.#liveAttemptOwners.has(attempt))
404
- attempt.close();
405
- }
406
- }
407
- async *#executeInitialStreamCycle(attempt, options) {
408
- // Establish ownership before asking the client for a stream. Some clients
409
- // can start consuming synchronously while constructing their stream.
410
- const runId = `${this.deps.sessionId}:live:${++this.#nextLiveRunId}`;
411
- this.deps.setActivePostExecuteRunId?.(runId);
412
- let stream;
413
- try {
414
- stream = await this.#startInitialStream(attempt, options);
415
- }
416
- catch (error) {
417
- this.deps.setActivePostExecuteRunId?.(null);
418
- throw error;
419
- }
420
- attempt.attachStream(stream);
421
- const liveRun = new LiveRun(runId, this.deps.postExecutePending, async (emit) => {
422
- try {
423
- return await this.#consumeInitialStream(stream, attempt, emit);
424
- }
425
- finally {
426
- this.#liveAttemptOwners.delete(attempt);
427
- attempt.close();
428
- }
429
- });
430
- this.#liveAttemptOwners.add(attempt);
431
- this.#liveRun = liveRun;
432
- return yield* this.#drainLiveRun(liveRun);
433
- }
434
- async *#drainLiveRun(liveRun) {
435
- while (true) {
436
- let state;
437
- try {
438
- state = await liveRun.next();
439
- }
440
- catch (error) {
441
- if (this.#liveRun === liveRun) {
442
- this.#liveRun = null;
443
- this.deps.setActivePostExecuteRunId?.(null);
444
- }
445
- throw error;
446
- }
447
- if (state.kind === 'event') {
448
- yield state.event;
449
- continue;
450
- }
451
- if (state.kind === 'post_execute_approval_required')
452
- return state;
453
- if (state.kind === 'cancelled') {
454
- if (this.#liveRun === liveRun) {
455
- this.#liveRun = null;
456
- this.deps.setActivePostExecuteRunId?.(null);
457
- }
458
- return { kind: 'stale' };
459
- }
460
- if (this.#liveRun === liveRun) {
461
- this.#liveRun = null;
462
- this.deps.setActivePostExecuteRunId?.(null);
463
- }
464
- return state.result;
465
- }
466
- }
467
- async #consumeInitialStream(stream, attempt, emit) {
468
- const processor = this.deps.streamProcessor.process(stream, {
469
- gen: attempt.token,
470
- source: 'startStream',
471
- preserveExistingToolArgs: false,
472
- });
473
- let next = await processor.next();
474
- while (!next.done) {
475
- if (isCommittedOutputEvent(next.value))
476
- attempt.markModelEventSeen();
477
- emit(next.value);
478
- next = await processor.next();
479
- }
480
- const accumulated = next.value;
481
- const finalized = this.deps.streamProcessor.finalize(stream, attempt.token, attempt.inputMode, 'startStream');
482
- if (finalized.kind === 'stale') {
483
- return { kind: 'stale' };
484
- }
485
- for (const event of this.#contextCompactionCompletedEvent({
486
- stream,
487
- inputTokensBefore: accumulated.latestUsage?.prompt_tokens,
488
- durationMs: accumulated.lastContextCompactionDurationMs,
489
- })) {
490
- emit(event);
491
- }
492
- const outcome = await buildConversationResult({
493
- result: stream,
494
- finalOutputOverride: accumulated.finalOutput || undefined,
495
- reasoningOutputOverride: accumulated.reasoningOutput || undefined,
496
- emittedCommandIds: accumulated.emittedCommandIds,
497
- usage: accumulated.latestUsage,
498
- toolCallArgumentsById: this.deps.toolTracker.argumentsById,
499
- turnItems: this.deps.turnAccumulator.getTurnItems(),
500
- token: attempt.token,
501
- inputMode: attempt.inputMode,
502
- }, {
503
- approvalFlow: this.deps.approvalFlow,
504
- shellAutoApproval: this.deps.shellAutoApproval,
505
- logger: this.deps.logger,
506
- sessionId: resolveSessionId(this.deps.sessionId),
507
- sessionAccess: this.deps.sessionAccess,
508
- approvalPolicyRegistry: this.#activePolicyRegistry,
509
- toolCallMarkers: this.#toolCallMarkers,
510
- });
511
- this.deps.inputPlanner.recordSuccess(attempt.inputMode === 'delta' ? attempt.streamInput : this.deps.conversationStore.getHistory(), attempt.inputMode === 'delta'
512
- ? { kind: attempt.inputMode }
513
- : { kind: attempt.inputMode, previousInput: attempt.streamInput });
514
- return { kind: 'completed', outcome, generation: attempt.token };
515
- }
516
- /**
517
- * The compaction completion notice.
518
- *
519
- * `started` is *not* emitted here: it is surfaced live by the stream processor from the
520
- * provider's own `output_item.added` frame, which is the only moment that corresponds to
521
- * compaction actually beginning. This site owns only the completion, because the token
522
- * count it reports comes from `usage`, which the provider does not send until the response
523
- * completes — after the compaction frames have already gone by.
524
- *
525
- * `durationMs` likewise comes from the frames (parked on the accumulator), not from a
526
- * clock read here: a `Date.now()` captured at this call site is taken after the stream has
527
- * finished, so it would always yield ~0.
528
- */
529
- #contextCompactionCompletedEvent(args) {
530
- const compaction = lastOpenAICompaction(extractFinalizationSnapshot(args.stream).output);
531
- if (!compaction)
532
- return [];
533
- return [
534
- {
535
- type: 'context_compaction_completed',
536
- provider: compaction.provider,
537
- sessionId: resolveSessionId(this.deps.sessionId),
538
- ...(args.inputTokensBefore !== undefined ? { inputTokensBefore: args.inputTokensBefore } : {}),
539
- // The provider reports no post-compaction size: usage.input_tokens is the
540
- // compaction pass's own input, i.e. the pre-compaction total.
541
- inputTokensAfter: undefined,
542
- durationMs: args.durationMs ?? 0,
543
- },
544
- ];
545
- }
546
- async *#continuePostExecuteRun() {
547
- const liveRun = this.#liveRun;
548
- if (!liveRun) {
549
- throw new Error('No live post-execute run active to continue.');
550
- }
551
- try {
552
- const result = yield* this.#drainLiveRun(liveRun);
553
- if (result.kind === 'stale')
554
- return { kind: 'stale' };
555
- if (result.kind === 'post_execute_approval_required') {
556
- return { kind: 'approval_required', terminal: this.#postExecuteApprovalTerminal(result.entries) };
557
- }
558
- const { outcome, generation } = result;
559
- if (outcome.kind === 'response')
560
- return { kind: 'response', terminal: outcome.result };
561
- if (outcome.kind === 'approval_required')
562
- return { kind: 'approval_required', terminal: outcome.result };
563
- if (outcome.kind === 'auto_approve') {
564
- // Initial-path parity (`turn-workflow.test.ts:685`): the resumed live
565
- // run ended on a shell interruption the auto-approval policy already
566
- // approved, so settle exactly as the initial path settles an
567
- // `auto_approve` stream result — drive the auto-approval continuation
568
- // instead of re-prompting a decision the policy has made. The
569
- // generation travels with the live run so the init-less
570
- // `continuePostExecute` observer path uses the same token.
571
- if (outcome.advisory?.source === 'llm') {
572
- this.#toolCallMarkers.markToolCallAsLlmAutoApproved(outcome.callId);
573
- }
574
- this.deps.logger.debug('Shell command auto-approved by LLM', {
575
- eventType: 'approval.auto_approved',
576
- category: 'approval',
577
- phase: 'approval',
578
- sessionId: resolveSessionId(this.deps.sessionId),
579
- traceId: this.deps.logger.getCorrelationId(),
580
- callId: outcome.callId,
581
- command: outcome.argumentsText,
582
- model: outcome.advisory?.model,
583
- reasoning: outcome.advisory?.reasoning,
584
- });
585
- const driveResult = yield* this.executeContinuationAttempt({ kind: 'approval_decision', answer: 'y', generation }, new ShellAutoApprovalDecisionPolicy(this.deps.shellAutoApproval));
586
- if (driveResult.kind !== 'fresh_start_required') {
587
- return driveResult;
588
- }
589
- return yield* this.#replayFromFreshStart(generation, driveResult);
590
- }
591
- throw new Error('Post-execute live run finished without a terminal outcome.');
592
- }
593
- catch (error) {
594
- this.#liveRun = null;
595
- this.deps.setActivePostExecuteRunId?.(null);
596
- throw error;
597
- }
598
- }
599
- #postExecuteApprovalTerminal(entries) {
600
- const entry = entries[0];
601
- const snapshot = this.deps.postExecutePending.snapshot();
602
- return createApprovalRequiredTerminal({
603
- agentName: 'Agent',
604
- toolName: entry.toolName,
605
- argumentsText: entry.argumentsText,
606
- deniedRead: entry.deniedRead,
607
- rawInterruption: { type: 'post_execute', runId: entry.runId, id: entry.id },
608
- callId: entry.toolCallId,
609
- postExecute: {
610
- kind: 'post_execute',
611
- sessionId: snapshot.sessionId,
612
- epoch: snapshot.epoch,
613
- revision: snapshot.revision,
614
- ids: [entry.id],
615
- },
616
- });
617
- }
618
- async #startInitialStream(attempt, options) {
619
- if (options.resumeState && typeof this.deps.agentClient.continueRunStream === 'function') {
620
- const resumeOptions = {
621
- recoveryBudget: attempt.recoveryBudget,
622
- previousResponseId: options.resumePreviousResponseId ?? this.deps.providerContinuity.previousResponseId,
623
- sessionId: resolveSessionId(this.deps.sessionId),
624
- providerHistorySnapshot: this.deps.conversationStore.getProviderHistorySnapshot(),
625
- hookTurnId: this.#hookTurnId,
626
- };
627
- Object.defineProperty(resumeOptions, 'providerContinuityLineage', {
628
- value: this.deps.providerContinuity.lineage,
629
- enumerable: this.deps.providerContinuity.lineage !== 0,
630
- });
631
- return (await this.deps.agentClient.continueRunStream(options.resumeState, resumeOptions));
632
- }
633
- const legacyPreviousResponseId = attempt.inputMode === 'delta' ? this.deps.providerContinuity.previousResponseId : null;
634
- const selectedPreviousResponseId = legacyPreviousResponseId;
635
- const sessionId = resolveSessionId(this.deps.sessionId);
636
- const promptCacheKey = resolvePromptCacheKey(this.deps.sessionId);
637
- const startOptions = {
638
- recoveryBudget: attempt.recoveryBudget,
639
- previousResponseId: options.disableChainingForAttempt ? undefined : selectedPreviousResponseId,
640
- sessionId,
641
- ...(promptCacheKey !== sessionId ? { promptCacheKey } : {}),
642
- providerHistorySnapshot: attempt.providerHistorySnapshot,
643
- ...(this.#hookTurnId ? { hookTurnId: this.#hookTurnId } : {}),
644
- ...(options.disableChainingForAttempt ? { disableChainingForAttempt: true } : {}),
645
- };
646
- Object.defineProperty(startOptions, 'providerContinuityLineage', {
647
- value: this.deps.providerContinuity.lineage,
648
- enumerable: this.deps.providerContinuity.lineage !== 0,
649
- });
650
- return (await this.deps.agentClient.startStream(attempt.streamInput, startOptions));
651
- }
652
- async #waitBeforeRetry(attempt, delayMs) {
653
- if (attempt.signal?.aborted)
654
- throw Object.assign(new Error('Operation aborted'), { name: 'AbortError' });
655
- if (!attempt.signal) {
656
- await new Promise((resolve) => setTimeout(resolve, delayMs));
657
- return;
658
- }
659
- await new Promise((resolve, reject) => {
660
- const onAbort = () => {
661
- clearTimeout(timer);
662
- attempt.signal?.removeEventListener('abort', onAbort);
663
- reject(Object.assign(new Error('Operation aborted'), { name: 'AbortError' }));
664
- };
665
- const timer = setTimeout(() => {
666
- attempt.signal?.removeEventListener('abort', onAbort);
667
- resolve();
668
- }, delayMs);
669
- attempt.signal.addEventListener('abort', onAbort, { once: true });
670
- });
671
- }
672
- async *executeContinuationAttempt(init, policy) {
673
- const activePolicy = policy ?? new ShellAutoApprovalDecisionPolicy(this.deps.shellAutoApproval);
674
- if (!this.deps.generationGuard.isCurrent(init.generation)) {
675
- return { kind: 'stale' };
676
- }
677
- const prepared = this.deps.planApplier.prepareInit(init);
678
- const state = new ContinuationState(init.generation, this.#activeRecoveryBudget);
679
- state.initializeFrom(prepared, this.#activeCallIdsForInit(init, prepared));
680
- try {
681
- yield* this.deps.planApplier.applyInitialSetup(prepared, state);
682
- while (true) {
683
- if (!this.deps.generationGuard.isCurrent(state.token)) {
684
- return { kind: 'stale' };
685
- }
686
- try {
687
- const batchDecision = yield* this.#stagePendingParallelApprovals(state, activePolicy);
688
- if (batchDecision.kind === 'stale') {
689
- return { kind: 'stale' };
690
- }
691
- if (batchDecision.kind === 'approval_required') {
692
- return { kind: 'approval_required', terminal: batchDecision.terminal };
693
- }
694
- const previousInputForSurge = state.inputMode === 'full_history' ? this.deps.conversationStore.getHistory() : undefined;
695
- const cycleResult = yield* this.#executeContinuationStreamCycle(state, init.kind === 'approval_decision' && init.stopAfterApprovalResolution === true);
696
- if (cycleResult.kind === 'stale') {
697
- return { kind: 'stale' };
698
- }
699
- const { outcome, nextCumulativeMessages, nextCumulativeUsage, nextCumulativeTurnItems, mergedEmittedIds } = cycleResult;
700
- state.setRetryCounts({ ...state.retryCounts, transientRetryCount: 0 });
701
- state.setCumulativeUsage(nextCumulativeUsage);
702
- state.setCumulativeCommandMessages(nextCumulativeMessages);
703
- state.setCumulativeTurnItems(nextCumulativeTurnItems);
704
- if (outcome.kind === 'response') {
705
- this.#recordSuccess(state, previousInputForSurge);
706
- return { kind: 'response', terminal: this.#buildResponse(outcome.result, nextCumulativeUsage) };
707
- }
708
- const approvalResult = await this.#handleApprovalOutcome(outcome, state, activePolicy, nextCumulativeUsage, previousInputForSurge);
709
- if (approvalResult.action === 'return') {
710
- return approvalResult.result;
711
- }
712
- if (approvalResult.action === 'loop') {
713
- yield* this.deps.planApplier.applyNextPlan(approvalResult.nextPlan, state, mergedEmittedIds, approvalResult.isApproved);
714
- state.currentCallIds = resolveResponseCycleCallIds({
715
- interruptionCallIds: this.#interruptionCallIds(approvalResult.nextPlan.pendingApprovalContext.state, approvalResult.nextPlan.pendingApprovalContext.interruption),
716
- completedResultCallIds: this.deps.toolTracker.completedResultCallIdsForCurrentTurn(),
717
- fallbackCallIds: state.currentCallIds,
718
- conversationHistory: this.deps.conversationStore.getHistory(),
719
- });
720
- }
721
- continue;
722
- }
723
- catch (error) {
724
- const recovery = yield* this.#handleContinuationRecovery(error, state);
725
- if (recovery.kind === 'terminated') {
726
- throw error;
727
- }
728
- if (recovery.kind === 'stale') {
729
- return { kind: 'stale' };
730
- }
731
- if (recovery.kind === 'fresh_start') {
732
- return {
733
- kind: 'fresh_start_required',
734
- retryCounts: recovery.retryCounts,
735
- ...(recovery.delayMs !== undefined ? { delayMs: recovery.delayMs } : {}),
736
- ...(recovery.useStandardServiceTier ? { useStandardServiceTier: true } : {}),
737
- ...(recovery.disableChainingForAttempt ? { disableChainingForAttempt: true } : {}),
738
- };
739
- }
740
- // recovery.kind === 'resume' -> continue loop
741
- }
742
- }
743
- }
744
- catch (error) {
745
- const classification = classifyProviderFailure(error);
746
- const cancelled = isClassifiedCancellation(error);
747
- const log = cancelled ? this.deps.logger.debug : this.deps.logger.error;
748
- log.call(this.deps.logger, cancelled ? 'Conversation stream aborted during continuation' : 'Conversation stream error during continuation', {
749
- eventType: cancelled ? 'stream.aborted' : 'stream.failed',
750
- category: 'stream',
751
- phase: 'abort',
752
- sessionId: resolveSessionId(this.deps.sessionId),
753
- traceId: this.deps.logger.getCorrelationId(),
754
- errorMessage: describeError(error),
755
- errorKind: classification.errorKind,
756
- stack: error instanceof Error ? error.stack : undefined,
757
- });
758
- if (!cancelled) {
759
- yield {
760
- type: 'error',
761
- message: describeError(error),
762
- ...(error && typeof error === 'object' && 'code' in error && typeof error.code === 'string'
763
- ? { code: error.code }
764
- : {}),
765
- ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
766
- };
767
- }
768
- throw error;
769
- }
770
- }
771
- async *#stagePendingParallelApprovals(state, activePolicy) {
772
- const result = yield* this.#batchCoordinator.stageBatch({
773
- state,
774
- policy: activePolicy,
775
- token: state.token,
776
- });
777
- if (result.kind === 'stale') {
778
- return { kind: 'stale' };
779
- }
780
- if (result.kind === 'ready') {
781
- const pending = this.deps.approvalFlow.getPending?.();
782
- state.currentCallIds = resolveResponseCycleCallIds({
783
- interruptionCallIds: this.#interruptionCallIds(state.currentState, pending?.interruption),
784
- completedResultCallIds: this.deps.toolTracker.completedResultCallIdsForCurrentTurn(),
785
- fallbackCallIds: state.currentCallIds,
786
- conversationHistory: this.deps.conversationStore.getHistory(),
787
- preserveFallback: true,
788
- });
789
- }
790
- return result;
791
- }
792
- async *#handleContinuationRecovery(error, state) {
793
- const recoveryIterator = this.deps.continuationRecoveryHandler.handle({ error, state });
794
- let recoveryNext = await recoveryIterator.next();
795
- while (!recoveryNext.done) {
796
- yield recoveryNext.value;
797
- recoveryNext = await recoveryIterator.next();
798
- }
799
- return recoveryNext.value;
800
- }
801
- #buildResponse(result, usage) {
802
- return {
803
- type: 'response',
804
- commandMessages: result.commandMessages ?? [],
805
- finalText: result.finalText,
806
- ...(result.reasoningText ? { reasoningText: result.reasoningText } : {}),
807
- ...(usage && Object.keys(usage).length > 0 ? { usage } : {}),
808
- // Cost records are run-cumulative, so the continuation cycle must carry
809
- // forward what the collector observed; dropping them here silently
810
- // unprices every turn that used a tool or paused for approval.
811
- ...(result.costRecords?.length ? { costRecords: result.costRecords } : {}),
812
- turnItems: result.turnItems,
813
- };
814
- }
815
- async #handleApprovalOutcome(outcome, state, activePolicy, nextCumulativeUsage, previousInputForSurge) {
816
- const { kind } = outcome;
817
- if (kind === 'auto_approve') {
818
- if (outcome.advisory?.source === 'llm') {
819
- this.#toolCallMarkers.markToolCallAsLlmAutoApproved(outcome.callId);
820
- }
821
- this.deps.logger.debug('Shell command auto-approved by LLM', {
822
- eventType: 'approval.auto_approved',
823
- category: 'approval',
824
- phase: 'approval',
825
- sessionId: resolveSessionId(this.deps.sessionId),
826
- traceId: this.deps.logger.getCorrelationId(),
827
- callId: outcome.callId,
828
- command: outcome.argumentsText,
829
- model: outcome.advisory?.model,
830
- reasoning: outcome.advisory?.reasoning,
831
- });
832
- const nextPlan = this.deps.approvalFlow.prepareContinuation('y', undefined, 'policy');
833
- if (!nextPlan) {
834
- const approvalFallback = this.#createApprovalRequiredFromAutoApprove(outcome, nextCumulativeUsage);
835
- return { action: 'return', result: { kind: 'approval_required', terminal: approvalFallback } };
836
- }
837
- return { action: 'loop', nextPlan, isApproved: true };
838
- }
839
- const approvalContext = {
840
- toolName: outcome.result.approval.toolName,
841
- argumentsText: outcome.result.approval.argumentsText,
842
- callId: outcome.result.approval.callId,
843
- llmAdvisory: outcome.result.approval.llmAdvisory,
844
- };
845
- const decision = await activePolicy.decide(approvalContext);
846
- if (decision === 'prompt') {
847
- this.deps.planApplier.recordPendingApproval(approvalContext);
848
- if (outcome.result.approval.callId) {
849
- this.deps.logger.debug('Tool approval required', {
850
- eventType: 'approval.required',
851
- category: 'approval',
852
- phase: 'approval',
853
- sessionId: resolveSessionId(this.deps.sessionId),
854
- traceId: this.deps.logger.getCorrelationId(),
855
- toolName: outcome.result.approval.toolName,
856
- });
857
- }
858
- this.#recordSuccess(state, previousInputForSurge);
859
- const resultWithUsage = {
860
- ...outcome.result,
861
- ...(nextCumulativeUsage && Object.keys(nextCumulativeUsage).length > 0 ? { usage: nextCumulativeUsage } : {}),
862
- };
863
- return { action: 'return', result: { kind: 'approval_required', terminal: resultWithUsage } };
864
- }
865
- if (decision === 'approve') {
866
- this.deps.logger.debug('Shell command auto-approved by policy', {
867
- eventType: 'approval.auto_approved',
868
- category: 'approval',
869
- phase: 'approval',
870
- sessionId: resolveSessionId(this.deps.sessionId),
871
- traceId: this.deps.logger.getCorrelationId(),
872
- callId: approvalContext.callId,
873
- command: approvalContext.argumentsText,
874
- });
875
- }
876
- const answer = decision === 'approve' ? 'y' : 'n';
877
- const nextPlan = this.deps.approvalFlow.prepareContinuation(answer, undefined, decision === 'approve' || decision === 'reject' ? 'policy' : 'user');
878
- if (!nextPlan) {
879
- return { action: 'return', result: { kind: 'approval_required', terminal: outcome.result } };
880
- }
881
- return { action: 'loop', nextPlan, isApproved: answer === 'y' };
882
- }
883
- async *#executeContinuationStreamCycle(state, stopAfterApprovalResolution = false) {
884
- const sessionId = resolveSessionId(this.deps.sessionId);
885
- const promptCacheKey = resolvePromptCacheKey(this.deps.sessionId);
886
- const continuationOptions = {
887
- recoveryBudget: state.recoveryBudget,
888
- previousResponseId: state.currentResumePreviousResponseId ?? this.deps.providerContinuity.previousResponseId,
889
- sessionId,
890
- ...(promptCacheKey !== sessionId ? { promptCacheKey } : {}),
891
- toolResultCallIds: state.currentCallIds,
892
- knownToolCallIds: collectKnownToolCallIds(this.deps.conversationStore.getHistory(), this.deps.toolTracker.activeCallIdsForCurrentTurn()),
893
- providerHistorySnapshot: this.deps.conversationStore.getProviderHistorySnapshot(),
894
- hookTurnId: this.#hookTurnId,
895
- ...(stopAfterApprovalResolution ? { stopAfterApprovalResolution: true } : {}),
896
- };
897
- Object.defineProperty(continuationOptions, 'providerContinuityLineage', {
898
- value: this.deps.providerContinuity.lineage,
899
- enumerable: this.deps.providerContinuity.lineage !== 0,
900
- });
901
- const stream = (await this.deps.agentClient.continueRunStream(state.currentState, continuationOptions));
902
- state.setLastStream(stream);
903
- const allEmittedIds = new Set([...state.previouslyEmittedIds]);
904
- const acc = yield* this.deps.streamProcessor.process(stream, {
905
- gen: state.token,
906
- source: state.source,
907
- preserveExistingToolArgs: true,
908
- previouslyEmittedCommandIds: allEmittedIds,
909
- });
910
- const finalizeResult = this.deps.streamProcessor.finalize(stream, state.token, state.inputMode, state.source);
911
- if (finalizeResult.kind === 'stale') {
912
- return { kind: 'stale' };
913
- }
914
- for (const event of this.#contextCompactionCompletedEvent({
915
- stream,
916
- inputTokensBefore: acc.latestUsage?.prompt_tokens,
917
- durationMs: acc.lastContextCompactionDurationMs,
918
- })) {
919
- yield event;
920
- }
921
- const mergedEmittedIds = new Set([...allEmittedIds, ...acc.emittedCommandIds]);
922
- const streamMessages = extractCommandMessages(selectAgentStreamItems(stream), this.#toolCallMarkers);
923
- const filteredMessages = streamMessages.filter((msg) => !state.previouslyEmittedIds.has(msg.id));
924
- const nextCumulativeMessages = [...state.cumulativeCommandMessages, ...filteredMessages];
925
- const nextCumulativeUsage = acc.latestUsage ?? state.cumulativeUsage;
926
- const nextCumulativeTurnItems = this.deps.turnAccumulator.getTurnItems();
927
- const outcome = await buildConversationResult({
928
- result: stream,
929
- finalOutputOverride: acc.finalOutput || undefined,
930
- reasoningOutputOverride: acc.reasoningOutput || undefined,
931
- emittedCommandIds: mergedEmittedIds,
932
- usage: acc.latestUsage,
933
- toolCallArgumentsById: this.deps.toolTracker.argumentsById,
934
- turnItems: nextCumulativeTurnItems,
935
- token: state.token,
936
- inputMode: state.inputMode,
937
- cumulativeUsage: nextCumulativeUsage,
938
- cumulativeCommandMessages: nextCumulativeMessages,
939
- cumulativeTurnItems: nextCumulativeTurnItems,
940
- }, {
941
- approvalFlow: this.deps.approvalFlow,
942
- shellAutoApproval: this.deps.shellAutoApproval,
943
- logger: this.deps.logger,
944
- sessionId: resolveSessionId(this.deps.sessionId),
945
- sessionAccess: this.deps.sessionAccess,
946
- approvalPolicyRegistry: this.#activePolicyRegistry,
947
- toolCallMarkers: this.#toolCallMarkers,
948
- });
949
- return {
950
- kind: 'completed',
951
- outcome,
952
- nextCumulativeMessages,
953
- nextCumulativeUsage,
954
- nextCumulativeTurnItems,
955
- mergedEmittedIds,
956
- };
957
- }
958
- #activeCallIdsForInit(init, prepared) {
959
- if (init.kind === 'abort_resolution') {
960
- return resolveAbortedApprovalCallIds({
961
- interruptionCallIds: this.#interruptionCallIds(init.abortedContext.state, init.abortedContext.interruption),
962
- completedResultCallIds: this.deps.toolTracker.completedResultCallIdsForCurrentTurn(),
963
- });
964
- }
965
- return resolveResponseCycleCallIds({
966
- interruptionCallIds: this.#interruptionCallIds(prepared.state, prepared.interruption),
967
- completedResultCallIds: this.deps.toolTracker.completedResultCallIdsForCurrentTurn(),
968
- fallbackCallIds: this.deps.toolTracker.activeCallIdsForCurrentTurn(),
969
- conversationHistory: this.deps.conversationStore.getHistory(),
970
- });
971
- }
972
- #interruptionCallIds(state, primaryInterruption) {
973
- const callIds = new Set();
974
- const interruptions = getMethod(state, 'getInterruptions')?.();
975
- if (Array.isArray(interruptions)) {
976
- for (const interruption of interruptions) {
977
- const callId = getCallIdFromObject(interruption);
978
- if (callId) {
979
- callIds.add(callId);
980
- }
981
- }
982
- }
983
- const primaryCallId = getCallIdFromObject(primaryInterruption);
984
- if (primaryCallId) {
985
- callIds.add(primaryCallId);
986
- }
987
- return [...callIds];
988
- }
989
- #recordSuccess(state, previousInputForSurge) {
990
- this.deps.inputPlanner.recordSuccess(state.inputMode === 'delta' ? state.lastStream : this.deps.conversationStore.getHistory(), state.inputMode === 'delta'
991
- ? { kind: state.inputMode }
992
- : { kind: state.inputMode, previousInput: previousInputForSurge });
993
- }
994
- abortLiveRun() {
995
- const liveRun = this.#liveRun;
996
- this.#liveRun = null;
997
- liveRun?.cancel();
998
- this.deps.setActivePostExecuteRunId?.(null);
999
- return (liveRun?.completion.then(() => undefined, () => undefined) ?? Promise.resolve());
1000
- }
1001
- get activePostExecuteRunId() {
1002
- return this.#liveRun?.runId ?? null;
1003
- }
1004
- #createApprovalRequiredFromAutoApprove(outcome, usage) {
1005
- const pending = this.deps.approvalFlow.getPending();
1006
- if (pending) {
1007
- const { toolName, argumentsText } = getToolInfoFromInterruption(pending.interruption);
1008
- const agent = pending.interruption && typeof pending.interruption === 'object'
1009
- ? pending.interruption.agent
1010
- : undefined;
1011
- const agentName = agent && typeof agent === 'object' ? agent.name : 'Agent';
1012
- const callId = getCallIdFromObject(pending.interruption);
1013
- return createApprovalRequiredTerminal({
1014
- agentName: typeof agentName === 'string' ? agentName : 'Agent',
1015
- toolName: toolName ?? 'Unknown Tool',
1016
- argumentsText,
1017
- rawInterruption: pending.interruption,
1018
- callId: callId ? String(callId) : undefined,
1019
- llmAdvisory: outcome.advisory,
1020
- usage,
1021
- });
1022
- }
1023
- return createApprovalRequiredTerminal({
1024
- agentName: 'Agent',
1025
- toolName: 'Unknown Tool',
1026
- argumentsText: outcome.argumentsText,
1027
- rawInterruption: undefined,
1028
- callId: outcome.callId,
1029
- llmAdvisory: outcome.advisory,
1030
- usage,
1031
- });
1032
- }
1033
- async #emitApprovalRequested(approval) {
1034
- if (!this.deps.hookLifecycle || !this.deps.hookEvents)
1035
- return;
1036
- let normalizedArguments = approval.argumentsText;
1037
- try {
1038
- normalizedArguments = JSON.parse(approval.argumentsText);
1039
- }
1040
- catch {
1041
- // Preserve a bounded opaque representation for malformed model input.
1042
- }
1043
- await this.deps.hookLifecycle.emit(this.deps.hookEvents.create('approval.requested', {
1044
- toolName: approval.toolName,
1045
- normalizedArguments: this.deps.hookEvents.includeToolArguments
1046
- ? normalizedArguments
1047
- : typeof normalizedArguments === 'string'
1048
- ? normalizedArguments.slice(0, 500)
1049
- : JSON.stringify(normalizedArguments).slice(0, 500),
1050
- approvalKind: approval.toolName === 'ask_user' ? 'ask_user' : 'tool',
1051
- proposedDecision: 'approve',
1052
- }, { toolCallId: approval.callId }));
1053
- }
1054
- }
1055
- //# sourceMappingURL=turn-workflow.js.map