@cuongtran001/kanna 1.2.0 → 1.3.1

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 (196) hide show
  1. package/dist/client/assets/{arc-BLk1CZKB.js → arc-DXtN3EWJ.js} +1 -1
  2. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B01ZQYte.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
  3. package/dist/client/assets/{blockDiagram-GPEHLZMM-diL6a3cq.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
  4. package/dist/client/assets/{c4Diagram-AAUBKEIU-BY1verL8.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
  5. package/dist/client/assets/channel-BEYTrUAm.js +1 -0
  6. package/dist/client/assets/{chunk-2J33WTMH-psdZuajw.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
  7. package/dist/client/assets/{chunk-4BX2VUAB-CrwQVBc5.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
  8. package/dist/client/assets/{chunk-55IACEB6-9-aj4jKO.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
  9. package/dist/client/assets/{chunk-727SXJPM-1FNK9_nB.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
  10. package/dist/client/assets/{chunk-AQP2D5EJ-DFhvd_Ek.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-B7MLrDa7.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-D4Df05QE.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
  13. package/dist/client/assets/{chunk-QZHKN3VN-Iq17Dznm.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
  14. package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
  15. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
  16. package/dist/client/assets/{cose-bilkent-S5V4N54A-BmaZhtXy.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
  17. package/dist/client/assets/{dagre-BM42HDAG-BCmpfnfP.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
  18. package/dist/client/assets/{diagram-2AECGRRQ-BIEcKowS.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
  19. package/dist/client/assets/{diagram-5GNKFQAL-CRBABBfv.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
  20. package/dist/client/assets/{diagram-KO2AKTUF-BRxUk16N.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
  21. package/dist/client/assets/{diagram-LMA3HP47-DPBs4xl1.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
  22. package/dist/client/assets/{diagram-OG6HWLK6-BE9rgVv8.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
  23. package/dist/client/assets/{erDiagram-TEJ5UH35-D3vgA1xJ.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
  24. package/dist/client/assets/{flowDiagram-I6XJVG4X-Cgu-8XXC.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
  25. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CgjXVyTR.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
  26. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-zD5D8B-i.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
  27. package/dist/client/assets/{index-DBTdhMbJ.js → index-BqUY1Bm4.js} +1 -1
  28. package/dist/client/assets/index-CLsOWXff.css +32 -0
  29. package/dist/client/assets/{index-zQLLeAfl.js → index-TU1k6ov6.js} +253 -243
  30. package/dist/client/assets/{infoDiagram-5YYISTIA-BaFHqT4u.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
  31. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-2yAC1br2.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-JHISSGLW-DacQ-ND5.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-UN3LZRKU-Qlbwm3z4.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
  34. package/dist/client/assets/{linear-DeafqpgO.js → linear-ByKHtsIx.js} +1 -1
  35. package/dist/client/assets/{mermaid.core-KYiCPJDj.js → mermaid.core-Dl_f2KLc.js} +4 -4
  36. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B1V-T6d3.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-4H26LBE5-DOF9OFRu.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-lH1mB7ko.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BaTsJ_sS.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Pfxib5iI.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQMEKkBC.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-AJRCARHV-CuldNnzp.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
  44. package/dist/client/assets/{timeline-definition-PNZ67QCA-DP7u0YAg.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
  45. package/dist/client/assets/{vennDiagram-CIIHVFJN-QlrjpNeQ.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
  46. package/dist/client/assets/{wardley-L42UT6IY-BqdJ_sAM.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
  47. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Wi3kaQ5X.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
  48. package/dist/client/assets/{xychartDiagram-2RQKCTM6-D-KXMyCG.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
  49. package/dist/client/index.html +2 -2
  50. package/package.json +1 -1
  51. package/src/server/agent-coordinator-types.ts +158 -0
  52. package/src/server/agent-coordinator.ts +919 -0
  53. package/src/server/agent-deps-builders.ts +425 -0
  54. package/src/server/agent.oauth-rotation.test.ts +101 -0
  55. package/src/server/agent.test.ts +24 -8
  56. package/src/server/agent.ts +35 -4963
  57. package/src/server/auto-continue/read-model.test.ts +2 -2
  58. package/src/server/auto-continue/read-model.ts +4 -1
  59. package/src/server/claude-autocontinue-commands.test.ts +494 -0
  60. package/src/server/claude-autocontinue-commands.ts +256 -0
  61. package/src/server/claude-cancel-handler.test.ts +472 -0
  62. package/src/server/claude-cancel-handler.ts +241 -0
  63. package/src/server/claude-chat-management.test.ts +395 -0
  64. package/src/server/claude-chat-management.ts +251 -0
  65. package/src/server/claude-harness-stream.test.ts +273 -0
  66. package/src/server/claude-harness-stream.ts +158 -0
  67. package/src/server/claude-loop-orch-commands.test.ts +579 -0
  68. package/src/server/claude-loop-orch-commands.ts +466 -0
  69. package/src/server/claude-message-normalizer.test.ts +213 -0
  70. package/src/server/claude-message-normalizer.ts +436 -0
  71. package/src/server/claude-prompt-helpers.test.ts +248 -0
  72. package/src/server/claude-prompt-helpers.ts +139 -0
  73. package/src/server/claude-sdk-queue.test.ts +169 -0
  74. package/src/server/claude-sdk-queue.ts +82 -0
  75. package/src/server/claude-send-command.test.ts +414 -0
  76. package/src/server/claude-send-command.ts +420 -0
  77. package/src/server/claude-session-config-helpers.test.ts +279 -0
  78. package/src/server/claude-session-config-helpers.ts +151 -0
  79. package/src/server/claude-session-config.test.ts +250 -0
  80. package/src/server/claude-session-config.ts +159 -0
  81. package/src/server/claude-session-error-handler.test.ts +498 -0
  82. package/src/server/claude-session-error-handler.ts +361 -0
  83. package/src/server/claude-session-lifecycle.test.ts +669 -0
  84. package/src/server/claude-session-lifecycle.ts +283 -0
  85. package/src/server/claude-session-rebuild.test.ts +224 -0
  86. package/src/server/claude-session-rebuild.ts +101 -0
  87. package/src/server/claude-session-runner.test.ts +523 -0
  88. package/src/server/claude-session-runner.ts +521 -0
  89. package/src/server/claude-session-spawner.test.ts +401 -0
  90. package/src/server/claude-session-spawner.ts +401 -0
  91. package/src/server/claude-session-start.test.ts +205 -0
  92. package/src/server/claude-session-start.ts +277 -0
  93. package/src/server/claude-session-state-queries.test.ts +344 -0
  94. package/src/server/claude-session-state-queries.ts +160 -0
  95. package/src/server/claude-session-state.ts +104 -0
  96. package/src/server/claude-slash-commands.test.ts +277 -0
  97. package/src/server/claude-slash-commands.ts +234 -0
  98. package/src/server/claude-spawn-helpers.test.ts +221 -0
  99. package/src/server/claude-spawn-helpers.ts +179 -0
  100. package/src/server/claude-steer-log.test.ts +243 -0
  101. package/src/server/claude-steer-log.ts +58 -0
  102. package/src/server/claude-subagent-tool-response.test.ts +213 -0
  103. package/src/server/claude-subagent-tool-response.ts +165 -0
  104. package/src/server/claude-subagent-wiring.test.ts +263 -0
  105. package/src/server/claude-subagent-wiring.ts +324 -0
  106. package/src/server/claude-tool-respond.test.ts +321 -0
  107. package/src/server/claude-tool-respond.ts +142 -0
  108. package/src/server/claude-turn-runner.test.ts +310 -0
  109. package/src/server/claude-turn-runner.ts +228 -0
  110. package/src/server/claude-turn-starter.test.ts +266 -0
  111. package/src/server/claude-turn-starter.ts +566 -0
  112. package/src/server/claude-usage-math.test.ts +169 -0
  113. package/src/server/claude-usage-math.ts +132 -0
  114. package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
  115. package/src/server/diff-store-branch-ops.adapter.ts +602 -0
  116. package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
  117. package/src/server/diff-store-commit-ops.adapter.ts +258 -0
  118. package/src/server/diff-store-errors.ts +85 -0
  119. package/src/server/diff-store-file-ops.adapter.ts +212 -0
  120. package/src/server/diff-store-git-branch.adapter.ts +686 -0
  121. package/src/server/diff-store-io.adapter.ts +27 -0
  122. package/src/server/diff-store-parse.test.ts +212 -0
  123. package/src/server/diff-store-parse.ts +122 -0
  124. package/src/server/diff-store-state.ts +82 -0
  125. package/src/server/diff-store.ts +76 -1854
  126. package/src/server/event-store-apply.test.ts +280 -0
  127. package/src/server/event-store-apply.ts +132 -0
  128. package/src/server/event-store-chat-lifecycle.test.ts +408 -0
  129. package/src/server/event-store-chat-lifecycle.ts +441 -0
  130. package/src/server/event-store-entity-write.ts +356 -0
  131. package/src/server/event-store-helpers.test.ts +230 -0
  132. package/src/server/event-store-helpers.ts +235 -0
  133. package/src/server/event-store-init.ts +276 -0
  134. package/src/server/event-store-messages.adapter.test.ts +365 -0
  135. package/src/server/event-store-messages.adapter.ts +213 -0
  136. package/src/server/event-store-orch-subscription.ts +124 -0
  137. package/src/server/event-store-orch.test.ts +462 -0
  138. package/src/server/event-store-orch.ts +312 -0
  139. package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
  140. package/src/server/event-store-peripheral-events.adapter.ts +105 -0
  141. package/src/server/event-store-snapshot.test.ts +597 -0
  142. package/src/server/event-store-snapshot.ts +658 -0
  143. package/src/server/event-store-subagent.test.ts +408 -0
  144. package/src/server/event-store-subagent.ts +198 -0
  145. package/src/server/event-store-tool-requests.test.ts +210 -0
  146. package/src/server/event-store-tool-requests.ts +131 -0
  147. package/src/server/event-store-transcript-write.adapter.ts +285 -0
  148. package/src/server/event-store-write-ops.test.ts +554 -0
  149. package/src/server/event-store-write-ops.ts +529 -0
  150. package/src/server/event-store.test.ts +35 -0
  151. package/src/server/event-store.ts +251 -2284
  152. package/src/server/events.ts +3 -0
  153. package/src/server/harness-types.ts +15 -1
  154. package/src/server/oauth-errors.ts +19 -0
  155. package/src/server/read-models.ts +26 -1
  156. package/src/server/subagent-orchestrator.ts +3 -0
  157. package/src/server/ws-router-agent-ctrl.test.ts +187 -0
  158. package/src/server/ws-router-agent-ctrl.ts +137 -0
  159. package/src/server/ws-router-broadcast.ts +488 -0
  160. package/src/server/ws-router-chat.test.ts +461 -0
  161. package/src/server/ws-router-chat.ts +279 -0
  162. package/src/server/ws-router-defaults.test.ts +283 -0
  163. package/src/server/ws-router-defaults.ts +434 -0
  164. package/src/server/ws-router-diff.test.ts +167 -0
  165. package/src/server/ws-router-diff.ts +250 -0
  166. package/src/server/ws-router-envelope.ts +329 -0
  167. package/src/server/ws-router-misc.test.ts +422 -0
  168. package/src/server/ws-router-misc.ts +265 -0
  169. package/src/server/ws-router-orch.test.ts +190 -0
  170. package/src/server/ws-router-orch.ts +93 -0
  171. package/src/server/ws-router-project.test.ts +403 -0
  172. package/src/server/ws-router-project.ts +256 -0
  173. package/src/server/ws-router-push.test.ts +193 -0
  174. package/src/server/ws-router-push.ts +120 -0
  175. package/src/server/ws-router-settings.test.ts +173 -0
  176. package/src/server/ws-router-settings.ts +416 -0
  177. package/src/server/ws-router-skills.test.ts +194 -0
  178. package/src/server/ws-router-skills.ts +204 -0
  179. package/src/server/ws-router-utils.ts +224 -0
  180. package/src/server/ws-router.ts +339 -2254
  181. package/src/shared/app-settings-types.ts +365 -0
  182. package/src/shared/core-types.ts +27 -0
  183. package/src/shared/git-diff-types.ts +165 -0
  184. package/src/shared/loop-progress.test.ts +106 -0
  185. package/src/shared/loop-progress.ts +88 -0
  186. package/src/shared/mcp-types.ts +105 -0
  187. package/src/shared/provider-model-types.ts +437 -0
  188. package/src/shared/subagent-types.ts +198 -0
  189. package/src/shared/tool-call-types.ts +314 -0
  190. package/src/shared/transcript-types.ts +270 -0
  191. package/src/shared/types.ts +27 -1668
  192. package/dist/client/assets/channel-BMkhjiLI.js +0 -1
  193. package/dist/client/assets/classDiagram-4FO5ZUOK-C5Kg1XOD.js +0 -1
  194. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5Kg1XOD.js +0 -1
  195. package/dist/client/assets/index-H6C-f2Vp.css +0 -32
  196. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ca2U11af.js +0 -1
@@ -0,0 +1,919 @@
1
+ import { type SetupLoopHandlerResult } from "./kanna-mcp"
2
+ import {
3
+ findLastUserMessageId as findLastUserMessageIdFn,
4
+ recreateActiveTurnFromSession as recreateActiveTurnFromSessionFn,
5
+ type SessionRebuildDeps,
6
+ } from "./claude-session-rebuild"
7
+ import type { LoopSetupInput } from "./loop-template"
8
+ import type {
9
+ AgentProvider,
10
+ ChatAttachment,
11
+ LlmProviderSnapshot,
12
+ McpOAuthState,
13
+ McpServerConfig,
14
+ PendingToolSnapshot,
15
+ QueuedChatMessage,
16
+ SlashCommand,
17
+ Subagent,
18
+ } from "../shared/types"
19
+ import type { ClientCommand } from "../shared/protocol"
20
+ import { EventStore } from "./event-store"
21
+ import type { AnalyticsReporter } from "./analytics"
22
+ import { NoopAnalyticsReporter } from "./analytics"
23
+ import { CodexAppServerManager } from "./codex-app-server"
24
+ import { type GenerateChatTitleResult, generateTitleForChatDetailed } from "./generate-title"
25
+ import type { ClaudeSessionHandle, HarnessTurn } from "./harness-types"
26
+ import { startClaudeSession } from "./claude-session-start"
27
+ import { readLlmProviderSnapshot } from "./llm-provider"
28
+ import { type ClaudeDriverPreference } from "../shared/types"
29
+ import type { AutoContinueEvent } from "./auto-continue/events"
30
+ import { ClaudeLimitDetector, CodexLimitDetector, type LimitDetection, type LimitDetector } from "./auto-continue/limit-detector"
31
+ import { ClaudeAuthErrorDetector, type AuthErrorDetection } from "./auto-continue/auth-error-detector"
32
+ import type { ScheduleManager } from "./auto-continue/schedule-manager"
33
+ import type { LoopState } from "./auto-continue/read-model"
34
+ import type { TunnelGateway } from "./cloudflare-tunnel/gateway"
35
+ import { OAuthTokenPool } from "./oauth-pool/oauth-token-pool"
36
+ import { SubagentOrchestrator, type BackgroundRunOutcome, type ProviderRunStart } from "./subagent-orchestrator"
37
+ import {
38
+ buildSubagentProviderRunForChat as buildSubagentProviderRunForChatFn,
39
+ type SubagentWiringDeps,
40
+ type BuildSubagentProviderRunForChatArgs,
41
+ } from "./claude-subagent-wiring"
42
+ import { OrchestrationQueue, type WorkerResult, type WorkerSpawnArgs } from "./orchestration-queue"
43
+ import { createOrchWorktreeOps } from "./orchestration-worktree.adapter"
44
+ import { runCommandInWorktree } from "./orchestration-exec-io.adapter"
45
+ import type { OrchRunDetail, OrchRunInput } from "../shared/orchestration-types"
46
+ import type { ToolCallbackService } from "./tool-callback"
47
+ import type { ChatPermissionPolicy } from "../shared/permission-policy"
48
+ import { POLICY_DEFAULT } from "../shared/permission-policy"
49
+ import { startClaudeSessionPTY, type StartClaudeSessionPtyArgs } from "./claude-pty/driver"
50
+ import {
51
+ type ClaudeSessionConfigHelpersDeps,
52
+ resolveClaudeDriverPreference as resolveClaudeDriverPreferenceFn,
53
+ getEnabledCustomMcpServers as getEnabledCustomMcpServersFn,
54
+ buildOAuthBearers as buildOAuthBearersFn,
55
+ resolveChatPolicy as resolveChatPolicyFn,
56
+ killPtyInstance as killPtyInstanceFn,
57
+ } from "./claude-session-config-helpers"
58
+ import type { AnyValue } from "../shared/errors"
59
+ import { positiveIntegerFromEnv } from "./claude-prompt-helpers"
60
+ import {
61
+ type SendMessageOptions,
62
+ type SendToStartingProfile,
63
+ } from "./claude-steer-log"
64
+ import type { ActiveTurn, ClaudeSessionState } from "./claude-session-state"
65
+ import { runClaudeSession as runClaudeSessionLoop, type RunClaudeSessionDeps } from "./claude-session-runner"
66
+ import {
67
+ startTurnForChat as startTurnForChatFn,
68
+ type StartTurnDeps,
69
+ } from "./claude-turn-starter"
70
+ import { runTurn as runTurnFn, type RunTurnDeps } from "./claude-turn-runner"
71
+ import { spawnClaudeTurn, type SpawnClaudeTurnArgs, type SpawnClaudeTurnDeps } from "./claude-session-spawner"
72
+ import {
73
+ resolveClaudeIdleMs as resolveClaudeIdleMsFn,
74
+ hasLiveWorkflow as hasLiveWorkflowFn,
75
+ hasPendingBackgroundTask as hasPendingBackgroundTaskFn,
76
+ closeClaudeSession as closeClaudeSessionFn,
77
+ maybeRegisterSdkWorkflowsDir as maybeRegisterSdkWorkflowsDirFn,
78
+ enforceClaudeSessionBudget as enforceClaudeSessionBudgetFn,
79
+ buildPoolUnavailableMessage as buildPoolUnavailableMessageFn,
80
+ type SessionLifecycleDeps,
81
+ } from "./claude-session-lifecycle"
82
+ import {
83
+ handleLimitError as handleLimitErrorFn,
84
+ handleLimitDetection as handleLimitDetectionFn,
85
+ handleAuthFailure as handleAuthFailureFn,
86
+ type SessionErrorHandlerDeps,
87
+ type TokenRotationDedupeEntry,
88
+ } from "./claude-session-error-handler"
89
+ import {
90
+ resolveAutoResumeFor as resolveAutoResumeForFn,
91
+ emitAutoContinueEvent as emitAutoContinueEventFn,
92
+ fireAutoContinue as fireAutoContinueFn,
93
+ acceptAutoContinue as acceptAutoContinueFn,
94
+ rescheduleAutoContinue as rescheduleAutoContinueFn,
95
+ cancelAutoContinue as cancelAutoContinueFn,
96
+ type AutoContinueCommandDeps,
97
+ } from "./claude-autocontinue-commands"
98
+ import {
99
+ buildOrchWorker as buildOrchWorkerFn,
100
+ runOrchestration as runOrchestrationFn,
101
+ cancelOrchRun as cancelOrchRunFn,
102
+ getOrchRunDetail as getOrchRunDetailFn,
103
+ deliverSubagentToMain as deliverSubagentToMainFn,
104
+ setupLoop as setupLoopFn,
105
+ isLoopArmed as isLoopArmedFn,
106
+ stopLoop as stopLoopFn,
107
+ listLiveSchedules as listLiveSchedulesFn,
108
+ type LoopOrchCommandDeps,
109
+ } from "./claude-loop-orch-commands"
110
+ import {
111
+ cancelChat as cancelChatFn,
112
+ type CancelHandlerDeps,
113
+ } from "./claude-cancel-handler"
114
+ import {
115
+ stopDraining as stopDrainingFn,
116
+ closeChat as closeChatFn,
117
+ steer as steerFn,
118
+ dequeue as dequeueFn,
119
+ forkChat as forkChatFn,
120
+ generateTitleInBackground as generateTitleInBackgroundFn,
121
+ type ChatManagementDeps,
122
+ } from "./claude-chat-management"
123
+ import {
124
+ respondTool as respondToolFn,
125
+ type ToolRespondDeps,
126
+ } from "./claude-tool-respond"
127
+ import {
128
+ sendCommand as sendCommandFn,
129
+ enqueueMessage as enqueueMessageFn,
130
+ dequeueAndStartQueuedMessage as dequeueAndStartQueuedMessageFn,
131
+ maybeStartNextQueuedMessage as maybeStartNextQueuedMessageFn,
132
+ type SendCommandDeps,
133
+ } from "./claude-send-command"
134
+ import {
135
+ ensureSlashCommandsLoaded as ensureSlashCommandsLoadedFn,
136
+ mergeLocalCatalog as mergeLocalCatalogFn,
137
+ type SlashCommandsDeps,
138
+ } from "./claude-slash-commands"
139
+ import {
140
+ subagentPendingKey as subagentPendingKeyFn,
141
+ rejectPendingResolversForChat as rejectPendingResolversForChatFn,
142
+ rejectPendingResolversForRun as rejectPendingResolversForRunFn,
143
+ respondSubagentTool as respondSubagentToolFn,
144
+ cancelSubagentRun as cancelSubagentRunFn,
145
+ type SubagentToolResponseDeps,
146
+ } from "./claude-subagent-tool-response"
147
+ import {
148
+ getActiveStatuses as getActiveStatusesFn,
149
+ getWaitStartedAtByChatId as getWaitStartedAtByChatIdFn,
150
+ getPendingTool as getPendingToolFn,
151
+ getDrainingChatIds as getDrainingChatIdsFn,
152
+ getSlashCommandsLoadingChatIds as getSlashCommandsLoadingChatIdsFn,
153
+ getClaudeSessionStates as getClaudeSessionStatesFn,
154
+ sweepIdleClaudeSessions as sweepIdleClaudeSessionsFn,
155
+ type SessionStateQueryDeps,
156
+ } from "./claude-session-state-queries"
157
+ import * as agentDepsBuilders from "./agent-deps-builders"
158
+ import type {
159
+ AgentCoordinatorArgs,
160
+ ClaudeSessionLifecycleOptions,
161
+ } from "./agent-coordinator-types"
162
+
163
+ const DEFAULT_CLAUDE_SESSION_IDLE_MS = 10 * 60 * 1000
164
+ const DEFAULT_CLAUDE_SESSION_MAX_RESIDENT = 4
165
+ const DEFAULT_CLAUDE_SESSION_SWEEP_INTERVAL_MS = 60 * 1000
166
+ // Keep a PTY session warm up to 30 min while a background Bash task is pending —
167
+ // comfortably longer than the 10-min idle window and typical CI durations.
168
+ const DEFAULT_PTY_BACKGROUND_TASK_MAX_MS = 30 * 60 * 1000
169
+ // OpenRouter-only watchdog: a stalled upstream leaves the SDK stream open but
170
+ // silent after the session-token handshake, so the runClaudeSession for-await
171
+ // never ends and the existing fail-close never fires. Abort if no transcript
172
+ // entry arrives within this window. system_init is the SDK init echo (precedes
173
+ // model inference), so 2 min is generous; env-tunable per deployment.
174
+ const DEFAULT_OPENROUTER_FIRST_ENTRY_TIMEOUT_MS = 2 * 60 * 1000
175
+
176
+ // Thrown by Claude spawn paths when the OAuth pool has tokens but every one
177
+ // is currently unusable (rate-limited, errored, disabled, or reserved by
178
+ // another chat). `startTurnForChat` catches this and persists `message` as a
179
+ // `result` transcript entry instead of letting it surface as an ephemeral
180
+ // commandError that gets wiped by the next chat snapshot tick.
181
+ // Moved to oauth-errors.ts to avoid a circular import with claude-turn-starter.ts.
182
+ export class AgentCoordinator {
183
+ // Fields accessed by agent-deps-builders.ts are intentionally non-private
184
+ // (readonly instead of private readonly) to allow the external builder
185
+ // functions to read them without bypassing TypeScript's type system.
186
+ // `onStateChange` and `claudeSessionSweepTimer` are truly internal and stay private.
187
+ readonly store: EventStore
188
+ private readonly onStateChange: (chatId?: string, options?: { immediate?: boolean }) => void
189
+ readonly analytics: AnalyticsReporter
190
+ readonly codexManager: CodexAppServerManager
191
+ readonly generateTitle: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
192
+ readonly startClaudeSessionFn: NonNullable<AgentCoordinatorArgs["startClaudeSession"]>
193
+ readonly startClaudeSessionPTYFn: (args: StartClaudeSessionPtyArgs) => Promise<ClaudeSessionHandle>
194
+ reportBackgroundError: ((message: string) => void) | null = null
195
+ readonly activeTurns = new Map<string, ActiveTurn>()
196
+ readonly drainingStreams = new Map<string, { turn: HarnessTurn }>()
197
+ readonly claudeSessions = new Map<string, ClaudeSessionState>()
198
+ readonly mentionedSubagentIdsByChat = new Map<string, string[]>()
199
+ readonly slashCommandsInFlight = new Set<string>()
200
+ readonly claudeLimitDetector: LimitDetector
201
+ readonly codexLimitDetector: LimitDetector
202
+ readonly claudeAuthErrorDetector: ClaudeAuthErrorDetector
203
+ readonly scheduleManager: ScheduleManager | null
204
+ readonly getAutoResumePreference: () => boolean
205
+ readonly getSubagents: () => Subagent[]
206
+ readonly getAppSettingsSnapshot: NonNullable<AgentCoordinatorArgs["getAppSettingsSnapshot"]>
207
+ private readonly subagentOrchestrator: SubagentOrchestrator
208
+ /** Public accessor for tests + the `delegate_subagent` MCP tool wiring. */
209
+ getSubagentOrchestrator(): SubagentOrchestrator {
210
+ return this.subagentOrchestrator
211
+ }
212
+ private readonly orchestrationQueue: OrchestrationQueue
213
+ /** Public accessor for tests + the `orch_*` MCP tool + ws-router wiring. */
214
+ getOrchestrationQueue(): OrchestrationQueue {
215
+ return this.orchestrationQueue
216
+ }
217
+ readonly throwOnClaudeSessionStart: boolean
218
+ readonly autoResumeByChat = new Map<string, boolean>()
219
+ readonly openrouterFirstEntryTimeoutMs: number
220
+ // Per-tokenId rotation dedupe state. When a shared OAuth token throws
221
+ // limit/auth-error against N chats simultaneously, only the first chat
222
+ // pays the cost of marking the pool + picking a fresh target; subsequent
223
+ // chats within TOKEN_ROTATION_DEDUPE_WINDOW_MS reuse the dedupe slot to
224
+ // stagger their respawns by TOKEN_ROTATION_HERD_STAGGER_MS each.
225
+ readonly tokenRotationDedupe = new Map<string, TokenRotationDedupeEntry>()
226
+ // Per-chat circuit breaker for proactive `/compact` injection lives in the
227
+ // persisted ChatRecord (`compactFailureCount`): increments on every compact
228
+ // attempt that fails (turn errored / cancelled) and resets on success.
229
+ // After MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES, skip further proactive
230
+ // compacts on this chat so doomed sessions don't hammer the API on every
231
+ // turn (mirrors claude-code's autoCompact circuit breaker). Persisting it
232
+ // means a server restart cannot reset a doomed chat's breaker to 0.
233
+ readonly tunnelGateway: TunnelGateway | null
234
+ readonly oauthPool: OAuthTokenPool | null
235
+ readonly toolCallback: ToolCallbackService | null
236
+ readonly chatPolicy: ChatPermissionPolicy
237
+ readonly claudeSessionLifecycle: ClaudeSessionLifecycleOptions
238
+ private readonly claudeSessionSweepTimer: ReturnType<typeof setInterval> | null
239
+ readonly claudePtyRegistry: import("./claude-pty/pid-registry.adapter").ClaudePtyRegistry | null
240
+ readonly ptyInstanceRegistry: import("./claude-pty/pty-instance-registry").PtyInstanceRegistry | null
241
+ readonly workflowRegistry: import("./workflow-registry").WorkflowRegistry | null
242
+ readonly subagentTranscriptRegistry: import("./subagent-transcript-registry").SubagentTranscriptRegistry | null
243
+ readonly localCatalog: import("./local-catalog").LocalCatalogService | null
244
+ readonly readLlmProvider: () => Promise<LlmProviderSnapshot>
245
+ readonly listOpenRouterModelsFn: (() => Promise<import("../shared/types").OpenRouterModel[]>) | null
246
+ readonly persistOAuthStateFn: ((id: string, oauth: McpOAuthState) => void) | null
247
+ readonly subagentPendingResolvers = new Map<
248
+ string,
249
+ { resolve: (v: AnyValue) => void; reject: (e: Error) => void }
250
+ >()
251
+
252
+ constructor(args: AgentCoordinatorArgs) {
253
+ this.store = args.store
254
+ this.onStateChange = args.onStateChange
255
+ this.analytics = args.analytics ?? NoopAnalyticsReporter
256
+ this.codexManager = args.codexManager ?? new CodexAppServerManager()
257
+ this.generateTitle = args.generateTitle ?? generateTitleForChatDetailed
258
+ this.startClaudeSessionFn = args.startClaudeSession ?? startClaudeSession
259
+ this.startClaudeSessionPTYFn = args.startClaudeSessionPTY ?? startClaudeSessionPTY
260
+ this.claudeLimitDetector = args.claudeLimitDetector ?? new ClaudeLimitDetector()
261
+ this.codexLimitDetector = args.codexLimitDetector ?? new CodexLimitDetector()
262
+ this.claudeAuthErrorDetector = new ClaudeAuthErrorDetector()
263
+ this.scheduleManager = args.scheduleManager ?? null
264
+ this.getAutoResumePreference = args.getAutoResumePreference ?? (() => false)
265
+ this.openrouterFirstEntryTimeoutMs =
266
+ args.openrouterFirstEntryTimeoutMs ?? DEFAULT_OPENROUTER_FIRST_ENTRY_TIMEOUT_MS
267
+ this.getSubagents = args.getSubagents ?? (() => [])
268
+ this.getAppSettingsSnapshot = args.getAppSettingsSnapshot ?? (() => ({}))
269
+ this.readLlmProvider = args.readLlmProvider ?? readLlmProviderSnapshot
270
+ this.listOpenRouterModelsFn = args.listOpenRouterModels ?? null
271
+ this.persistOAuthStateFn = args.persistOAuthState ?? null
272
+ this.subagentOrchestrator = new SubagentOrchestrator({
273
+ store: this.store,
274
+ appSettings: { getSnapshot: () => ({ subagents: this.getSubagents() }) },
275
+ startProviderRun: (a) => this.buildSubagentProviderRunForChat({
276
+ subagent: a.subagent,
277
+ chatId: a.chatId,
278
+ primer: a.primer,
279
+ userInstruction: a.userInstruction,
280
+ runId: a.runId,
281
+ abortSignal: a.abortSignal,
282
+ depth: a.depth,
283
+ ancestorSubagentIds: a.ancestorSubagentIds,
284
+ parentUserMessageId: a.parentUserMessageId,
285
+ }),
286
+ onRunTerminal: (chatId, runId) => {
287
+ this.rejectPendingResolversForRun(chatId, runId)
288
+ // failRun appended the terminal event synchronously before invoking
289
+ // this hook, so the store already has the new state. Emit now so
290
+ // multi-subagent fan-outs do not have to wait for Promise.all.
291
+ this.emitStateChange(chatId)
292
+ },
293
+ onRunProgress: (chatId) => {
294
+ // Run start + every persisted subagent entry. Without this the
295
+ // client only gets a snapshot at terminal, so a delegated run
296
+ // renders blank until it finishes (delegate_subagent blocks the
297
+ // main turn, which itself emits nothing meanwhile). ws-router
298
+ // coalesces (16ms) and signature-dedups, so per-entry fan-out is
299
+ // cheap.
300
+ this.emitStateChange(chatId)
301
+ },
302
+ onBackgroundRunComplete: (chatId, runId, outcome) => {
303
+ void this.deliverSubagentToMain(chatId, runId, outcome)
304
+ },
305
+ maxLive: positiveIntegerFromEnv(process.env.KANNA_SUBAGENT_MAX_LIVE, 0) || undefined,
306
+ liveIdleTimeoutMs: positiveIntegerFromEnv(process.env.KANNA_SUBAGENT_IDLE_TIMEOUT_MS, 0) || undefined,
307
+ // Stall/idle watchdog window. Precedence: app setting > env > orchestrator
308
+ // default. The orchestrator reads this once at construction; a settings
309
+ // change takes effect on next server start (acceptable — restart-scoped).
310
+ runTimeoutMs: (this.getAppSettingsSnapshot().subagentRuntime?.runTimeoutMs
311
+ ?? positiveIntegerFromEnv(process.env.KANNA_SUBAGENT_RUN_TIMEOUT_MS, 0))
312
+ || undefined,
313
+ })
314
+ this.orchestrationQueue = new OrchestrationQueue({
315
+ store: this.store,
316
+ worktrees: createOrchWorktreeOps(),
317
+ startWorker: (a) => this.buildOrchWorker(a),
318
+ runVerify: runCommandInWorktree,
319
+ runInit: runCommandInWorktree,
320
+ })
321
+ this.throwOnClaudeSessionStart = args.throwOnClaudeSessionStart ?? false
322
+ this.tunnelGateway = args.tunnelGateway ?? null
323
+ this.oauthPool = args.oauthPool ?? null
324
+ this.toolCallback = args.toolCallback ?? null
325
+ this.chatPolicy = args.chatPolicy ?? POLICY_DEFAULT
326
+ this.claudeSessionLifecycle = {
327
+ idleMs: args.claudeSessionLifecycle?.idleMs
328
+ ?? positiveIntegerFromEnv(process.env.KANNA_CLAUDE_SESSION_IDLE_MS, DEFAULT_CLAUDE_SESSION_IDLE_MS),
329
+ maxResidentSessions: args.claudeSessionLifecycle?.maxResidentSessions
330
+ ?? positiveIntegerFromEnv(process.env.KANNA_CLAUDE_SESSION_MAX_RESIDENT, DEFAULT_CLAUDE_SESSION_MAX_RESIDENT),
331
+ sweepIntervalMs: args.claudeSessionLifecycle?.sweepIntervalMs
332
+ ?? positiveIntegerFromEnv(process.env.KANNA_CLAUDE_SESSION_SWEEP_INTERVAL_MS, DEFAULT_CLAUDE_SESSION_SWEEP_INTERVAL_MS),
333
+ backgroundTaskMaxMs: args.claudeSessionLifecycle?.backgroundTaskMaxMs
334
+ ?? positiveIntegerFromEnv(process.env.KANNA_PTY_BACKGROUND_TASK_MAX_MS, DEFAULT_PTY_BACKGROUND_TASK_MAX_MS),
335
+ }
336
+ this.claudeSessionSweepTimer = this.claudeSessionLifecycle.sweepIntervalMs > 0
337
+ ? setInterval(() => { this.sweepIdleClaudeSessions() }, this.claudeSessionLifecycle.sweepIntervalMs)
338
+ : null
339
+ this.claudeSessionSweepTimer?.unref?.()
340
+ this.claudePtyRegistry = args.claudePtyRegistry ?? null
341
+ this.ptyInstanceRegistry = args.ptyInstanceRegistry ?? null
342
+ this.workflowRegistry = args.workflowRegistry ?? null
343
+ this.subagentTranscriptRegistry = args.subagentTranscriptRegistry ?? null
344
+ this.localCatalog = args.localCatalog ?? null
345
+ }
346
+
347
+ setBackgroundErrorReporter(report: ((message: string) => void) | null) {
348
+ this.reportBackgroundError = report
349
+ }
350
+
351
+ dispose() {
352
+ if (this.claudeSessionSweepTimer) clearInterval(this.claudeSessionSweepTimer)
353
+ for (const [chatId, session] of [...this.claudeSessions.entries()]) {
354
+ this.closeClaudeSession(chatId, session)
355
+ }
356
+ }
357
+
358
+ getActiveStatuses() {
359
+ return getActiveStatusesFn(this.buildSessionStateQueryDeps())
360
+ }
361
+
362
+ getWaitStartedAtByChatId(): Map<string, number> {
363
+ return getWaitStartedAtByChatIdFn(this.buildSessionStateQueryDeps())
364
+ }
365
+
366
+ getPendingTool(chatId: string): PendingToolSnapshot | null {
367
+ return getPendingToolFn(this.buildSessionStateQueryDeps(), chatId)
368
+ }
369
+
370
+ getDrainingChatIds(): Set<string> {
371
+ return getDrainingChatIdsFn(this.buildSessionStateQueryDeps())
372
+ }
373
+
374
+ getSlashCommandsLoadingChatIds(): Set<string> {
375
+ return getSlashCommandsLoadingChatIdsFn(this.buildSessionStateQueryDeps())
376
+ }
377
+
378
+ /**
379
+ * Snapshot of live claude PTY session states per chat. Used by the
380
+ * sidebar badge selector. Chats not present are implicitly `cold`.
381
+ */
382
+ getClaudeSessionStates(): Map<string, "warming" | "active" | "idle"> {
383
+ return getClaudeSessionStatesFn(this.buildSessionStateQueryDeps())
384
+ }
385
+
386
+ get toolCallbackService(): ToolCallbackService | null {
387
+ return this.toolCallback
388
+ }
389
+
390
+ /** @internal used by agent-deps-builders.ts */
391
+ emitStateChange(chatId?: string, options?: { immediate?: boolean }) {
392
+ this.onStateChange(chatId, options)
393
+ }
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // Session config helpers deps builder
397
+ // ---------------------------------------------------------------------------
398
+
399
+ private buildClaudeSessionConfigHelpersDeps(): ClaudeSessionConfigHelpersDeps {
400
+ return agentDepsBuilders.buildClaudeSessionConfigHelpersDeps(this)
401
+ }
402
+
403
+ /** @internal used by agent-deps-builders.ts */
404
+ resolveClaudeDriverPreference(): ClaudeDriverPreference {
405
+ return resolveClaudeDriverPreferenceFn(this.buildClaudeSessionConfigHelpersDeps())
406
+ }
407
+
408
+ /** @internal used by agent-deps-builders.ts */
409
+ getEnabledCustomMcpServers(): readonly McpServerConfig[] {
410
+ return getEnabledCustomMcpServersFn(this.buildClaudeSessionConfigHelpersDeps())
411
+ }
412
+
413
+ /** @internal used by agent-deps-builders.ts */
414
+ async buildOAuthBearers(servers: readonly McpServerConfig[]): Promise<Map<string, string>> {
415
+ return buildOAuthBearersFn(this.buildClaudeSessionConfigHelpersDeps(), servers)
416
+ }
417
+
418
+ /** @internal used by agent-deps-builders.ts */
419
+ resolveChatPolicy(chatId: string): ChatPermissionPolicy {
420
+ return resolveChatPolicyFn(this.buildClaudeSessionConfigHelpersDeps(), chatId)
421
+ }
422
+
423
+ // ---------------------------------------------------------------------------
424
+ // Session lifecycle deps builder — wires this.* refs into SessionLifecycleDeps
425
+ // ---------------------------------------------------------------------------
426
+
427
+ private buildSessionLifecycleDeps(): SessionLifecycleDeps {
428
+ return agentDepsBuilders.buildSessionLifecycleDeps(this)
429
+ }
430
+
431
+ private buildSessionErrorHandlerDeps(): SessionErrorHandlerDeps {
432
+ return agentDepsBuilders.buildSessionErrorHandlerDeps(this)
433
+ }
434
+
435
+ // ---------------------------------------------------------------------------
436
+ // Auto-continue command deps builder — wires this.* refs into AutoContinueCommandDeps
437
+ // ---------------------------------------------------------------------------
438
+
439
+ private buildAutoContinueCommandDeps(): AutoContinueCommandDeps {
440
+ return agentDepsBuilders.buildAutoContinueCommandDeps(this)
441
+ }
442
+
443
+ private buildLoopOrchCommandDeps(): LoopOrchCommandDeps {
444
+ return agentDepsBuilders.buildLoopOrchCommandDeps(this)
445
+ }
446
+
447
+ // ---------------------------------------------------------------------------
448
+ // Cancel handler deps builder — wires this.* refs into CancelHandlerDeps
449
+ // ---------------------------------------------------------------------------
450
+
451
+ private buildCancelHandlerDeps(): CancelHandlerDeps {
452
+ return agentDepsBuilders.buildCancelHandlerDeps(this)
453
+ }
454
+
455
+ // ---------------------------------------------------------------------------
456
+ // Chat management deps builder — wires this.* refs into ChatManagementDeps
457
+ // ---------------------------------------------------------------------------
458
+
459
+ private buildChatManagementDeps(): ChatManagementDeps {
460
+ return agentDepsBuilders.buildChatManagementDeps(this)
461
+ }
462
+
463
+ // ---------------------------------------------------------------------------
464
+ // Send command deps builder — wires this.* refs into SendCommandDeps
465
+ // ---------------------------------------------------------------------------
466
+
467
+ private buildSendCommandDeps(): SendCommandDeps {
468
+ return agentDepsBuilders.buildSendCommandDeps(this)
469
+ }
470
+
471
+ // ---------------------------------------------------------------------------
472
+ // Subagent wiring deps builder — wires this.* refs into SubagentWiringDeps
473
+ // ---------------------------------------------------------------------------
474
+
475
+ private buildSubagentWiringDeps(): SubagentWiringDeps {
476
+ return agentDepsBuilders.buildSubagentWiringDeps(this)
477
+ }
478
+
479
+ private buildSlashCommandsDeps(): SlashCommandsDeps {
480
+ return agentDepsBuilders.buildSlashCommandsDeps(this)
481
+ }
482
+
483
+ /** @internal used by agent-deps-builders.ts */
484
+ resolveClaudeIdleMs(): number {
485
+ return resolveClaudeIdleMsFn(this.buildSessionLifecycleDeps())
486
+ }
487
+
488
+ /**
489
+ * True when the chat is hosting an in-flight background Workflow. A live
490
+ * workflow runs inside the warm PTY claude process but registers no
491
+ * activeTurn, pendingPromptSeq, or lastUsedAt bump, so without this signal
492
+ * the idle reaper / budget enforcer would tear the process down mid-run and
493
+ * abort the workflow.
494
+ *
495
+ * Liveness comes from the registry's live-run-dir probe, NOT the terminal
496
+ * `wf_<runId>.json` sidecar: Claude only flushes that sidecar at/near
497
+ * termination, so a sidecar-only check is blind for the entire run (the
498
+ * window the guard must cover). `hasActiveRun` reads the live
499
+ * `subagents/workflows/wf_*` transcript dirs (written from second one) and
500
+ * requires activity within one idle window so a stalled/crashed run still
501
+ * eventually reaps.
502
+ * @internal used by agent-deps-builders.ts
503
+ */
504
+ hasLiveWorkflow(chatId: string): boolean {
505
+ return hasLiveWorkflowFn(this.buildSessionLifecycleDeps(), chatId)
506
+ }
507
+
508
+ /** @internal used by agent-deps-builders.ts */
509
+ resolveBackgroundTaskMaxMs(): number {
510
+ return this.claudeSessionLifecycle.backgroundTaskMaxMs
511
+ }
512
+
513
+ /**
514
+ * True while the session has at least one Claude-Code background Bash task
515
+ * that has not yet settled. Primary gate is set size > 0: settle events
516
+ * (task_notification) remove their id from the set, so the guard clears the
517
+ * moment the last task reports. The deadline is a zombie backstop only —
518
+ * it fires when a settle notification is genuinely lost (SDK crash / dropped
519
+ * message) and is reset on every launch and settle, so it never expires
520
+ * during normal execution regardless of task duration.
521
+ * @internal used by agent-deps-builders.ts
522
+ */
523
+ hasPendingBackgroundTask(session: ClaudeSessionState, now: number): boolean {
524
+ return hasPendingBackgroundTaskFn(session, now)
525
+ }
526
+
527
+ /**
528
+ * Tear down a Claude session and (by default) release the OAuth-pool
529
+ * reservation owned by the chat.
530
+ *
531
+ * `keepReservation: true` — used by rate-limit / auth-error rotation
532
+ * paths that have ALREADY claimed a fresh token via `pickActive(chatId)`
533
+ * before calling close. Without this flag, `release(chatId)` would
534
+ * scan reservedBy for `owner === chatId` and drop the *new* token the
535
+ * rotation just claimed, leaking the rotation's reservation (audit #9d).
536
+ * @internal used by agent-deps-builders.ts
537
+ */
538
+ closeClaudeSession(
539
+ chatId: string,
540
+ session: ClaudeSessionState,
541
+ opts?: { keepReservation?: boolean },
542
+ ): void {
543
+ closeClaudeSessionFn(this.buildSessionLifecycleDeps(), chatId, session, opts)
544
+ }
545
+
546
+ /**
547
+ * Register the workflow disk-watch dir for an SDK session once the session
548
+ * token is known. No-op if the registry is absent, already registered, or
549
+ * the driver preference is PTY (the PTY driver registers from its own
550
+ * resolved transcript path in driver.ts cleanup and must not be double-fired).
551
+ * @internal used by agent-deps-builders.ts
552
+ */
553
+ maybeRegisterSdkWorkflowsDir(session: ClaudeSessionState): void {
554
+ maybeRegisterSdkWorkflowsDirFn(this.buildSessionLifecycleDeps(), session)
555
+ }
556
+
557
+ private sweepIdleClaudeSessions(now = Date.now()): void {
558
+ sweepIdleClaudeSessionsFn(this.buildSessionStateQueryDeps(), now)
559
+ }
560
+
561
+ /** @internal used by agent-deps-builders.ts */
562
+ enforceClaudeSessionBudget(protectedChatId?: string): void {
563
+ enforceClaudeSessionBudgetFn(this.buildSessionLifecycleDeps(), protectedChatId)
564
+ }
565
+
566
+ /**
567
+ * Format a refusal message when `pickActive(chatId)` returned null but the
568
+ * pool has tokens. Names the offending tokens so the user knows which
569
+ * chat to close or which token to add a quota to, instead of seeing the
570
+ * generic "all tokens unavailable" line that doesn't say what's holding
571
+ * them. `scopeSuffix` lets the subagent path tag its variant.
572
+ * @internal used by agent-deps-builders.ts
573
+ */
574
+ buildPoolUnavailableMessage(reservedFor: string, scopeSuffix: string): string {
575
+ return buildPoolUnavailableMessageFn(this.buildSessionLifecycleDeps(), reservedFor, scopeSuffix)
576
+ }
577
+
578
+ private buildSubagentToolResponseDeps(): SubagentToolResponseDeps {
579
+ return agentDepsBuilders.buildSubagentToolResponseDeps(this)
580
+ }
581
+
582
+ private buildToolRespondDeps(): ToolRespondDeps {
583
+ return agentDepsBuilders.buildToolRespondDeps(this)
584
+ }
585
+
586
+ private buildSessionStateQueryDeps(): SessionStateQueryDeps {
587
+ return agentDepsBuilders.buildSessionStateQueryDeps(this)
588
+ }
589
+
590
+ /** @internal used by agent-deps-builders.ts */
591
+ subagentPendingKey(chatId: string, runId: string, toolUseId: string): string {
592
+ return subagentPendingKeyFn(chatId, runId, toolUseId)
593
+ }
594
+
595
+ /** @internal used by agent-deps-builders.ts */
596
+ rejectPendingResolversForChat(chatId: string): void {
597
+ rejectPendingResolversForChatFn({ subagentPendingResolvers: this.subagentPendingResolvers }, chatId)
598
+ }
599
+
600
+ private rejectPendingResolversForRun(chatId: string, runId: string): void {
601
+ rejectPendingResolversForRunFn({ subagentPendingResolvers: this.subagentPendingResolvers }, chatId, runId)
602
+ }
603
+
604
+ getActiveTurnProfile(chatId: string): SendToStartingProfile | null {
605
+ const active = this.activeTurns.get(chatId)
606
+ if (!active?.clientTraceId || active.profilingStartedAt === undefined) {
607
+ return null
608
+ }
609
+
610
+ return {
611
+ traceId: active.clientTraceId,
612
+ startedAt: active.profilingStartedAt,
613
+ }
614
+ }
615
+
616
+ /** @internal used by agent-deps-builders.ts */
617
+ clearDrainingStream(chatId: string): void {
618
+ this.drainingStreams.delete(chatId)
619
+ }
620
+
621
+ async stopDraining(chatId: string) {
622
+ return stopDrainingFn(this.buildChatManagementDeps(), chatId)
623
+ }
624
+
625
+ async ensureSlashCommandsLoaded(chatId: string): Promise<void> {
626
+ return ensureSlashCommandsLoadedFn(this.buildSlashCommandsDeps(), chatId)
627
+ }
628
+
629
+ /** @internal used by agent-deps-builders.ts via buildRunClaudeSessionDeps / buildSpawnClaudeTurnDeps */
630
+ mergeLocalCatalog(commands: SlashCommand[], cwd: string): SlashCommand[] {
631
+ return mergeLocalCatalogFn(this.buildSlashCommandsDeps(), commands, cwd)
632
+ }
633
+
634
+ async closeChat(chatId: string) {
635
+ return closeChatFn(this.buildChatManagementDeps(), chatId)
636
+ }
637
+
638
+ /** @internal Delegates to enqueueMessageFn — see claude-send-command.ts. */
639
+ async enqueueMessage(chatId: string, content: string, attachments: ChatAttachment[], options?: SendMessageOptions) {
640
+ return enqueueMessageFn(this.buildSendCommandDeps(), chatId, content, attachments, options)
641
+ }
642
+
643
+ /** @internal Delegates to dequeueAndStartQueuedMessageFn — see claude-send-command.ts. */
644
+ async dequeueAndStartQueuedMessage(chatId: string, queuedMessage: QueuedChatMessage, options?: { steered?: boolean }) {
645
+ return dequeueAndStartQueuedMessageFn(this.buildSendCommandDeps(), chatId, queuedMessage, options)
646
+ }
647
+
648
+ /** @internal Delegates to maybeStartNextQueuedMessageFn — see claude-send-command.ts. */
649
+ async maybeStartNextQueuedMessage(chatId: string) {
650
+ return maybeStartNextQueuedMessageFn(this.buildSendCommandDeps(), chatId)
651
+ }
652
+
653
+ private buildStartTurnDeps(): StartTurnDeps {
654
+ return agentDepsBuilders.buildStartTurnDeps(this)
655
+ }
656
+
657
+ /** @internal used by agent-deps-builders.ts (via RunTurnDeps + SendCommandDeps). */
658
+ async startTurnForChat(args: {
659
+ chatId: string
660
+ provider: AgentProvider
661
+ content: string
662
+ attachments: ChatAttachment[]
663
+ model: string
664
+ effort?: string
665
+ serviceTier?: "fast"
666
+ planMode: boolean
667
+ appendUserPrompt: boolean
668
+ steered?: boolean
669
+ autoContinue?: { scheduleId: string }
670
+ userClearedContext?: boolean
671
+ profile?: SendToStartingProfile | null
672
+ }) {
673
+ return startTurnForChatFn(this.buildStartTurnDeps(), args)
674
+ }
675
+
676
+
677
+ private buildSessionRebuildDeps(): SessionRebuildDeps {
678
+ return agentDepsBuilders.buildSessionRebuildDeps(this)
679
+ }
680
+
681
+ /** @internal used by agent-deps-builders.ts via buildStartTurnDeps */
682
+ recreateActiveTurnFromSession(args: {
683
+ chatId: string
684
+ provider: AgentProvider
685
+ model: string
686
+ effort?: string
687
+ serviceTier?: "fast"
688
+ planMode: boolean
689
+ clientTraceId?: string
690
+ }): ActiveTurn | undefined {
691
+ return recreateActiveTurnFromSessionFn(this.buildSessionRebuildDeps(), args)
692
+ }
693
+
694
+ /** @internal used by agent-deps-builders.ts via buildStartTurnDeps */
695
+ findLastUserMessageId(chatId: string): string | null {
696
+ return findLastUserMessageIdFn(this.buildSessionRebuildDeps(), chatId)
697
+ }
698
+
699
+ private buildSpawnClaudeTurnDeps(): SpawnClaudeTurnDeps {
700
+ return agentDepsBuilders.buildSpawnClaudeTurnDeps(this)
701
+ }
702
+
703
+ /** @internal used by agent-deps-builders.ts via buildStartTurnDeps */
704
+ startClaudeTurn(args: SpawnClaudeTurnArgs): Promise<HarnessTurn> {
705
+ return spawnClaudeTurn(this.buildSpawnClaudeTurnDeps(), args)
706
+ }
707
+
708
+ /** Delegates to sendCommandFn — see claude-send-command.ts. */
709
+ async send(command: Extract<ClientCommand, { type: "chat.send" }>) {
710
+ return sendCommandFn(this.buildSendCommandDeps(), command)
711
+ }
712
+
713
+ /** @internal used by agent-deps-builders.ts via buildLoopOrchCommandDeps; Delegates to buildSubagentProviderRunForChatFn. */
714
+ buildSubagentProviderRunForChat(args: BuildSubagentProviderRunForChatArgs): ProviderRunStart {
715
+ return buildSubagentProviderRunForChatFn(this.buildSubagentWiringDeps(), args)
716
+ }
717
+
718
+ /**
719
+ * StartWorker adapter for the OrchestrationQueue: spawn the run's configured
720
+ * worker subagent against the task worktree (`spawn.cwd`) with the phase
721
+ * prompt. Origin chat + subagent are read from the persisted run config so
722
+ * this resolves identically on a fresh run and after a restart.
723
+ */
724
+ /** Delegates to buildOrchWorkerFn — see claude-loop-orch-commands.ts. */
725
+ private async buildOrchWorker(spawn: WorkerSpawnArgs): Promise<WorkerResult> {
726
+ return buildOrchWorkerFn(this.buildLoopOrchCommandDeps(), spawn)
727
+ }
728
+
729
+ /**
730
+ * User-callable entry point (MCP `orch_run` + ws `orch.run`). Validates the
731
+ * task list into the fixed linear config, then starts the run. Returns the
732
+ * runId or the flat validation error list — never a partial run.
733
+ * Delegates to runOrchestrationFn — see claude-loop-orch-commands.ts.
734
+ */
735
+ async runOrchestration(
736
+ chatId: string,
737
+ input: OrchRunInput,
738
+ ): Promise<{ ok: true; runId: string } | { ok: false; errors: string[] }> {
739
+ return runOrchestrationFn(this.buildLoopOrchCommandDeps(), chatId, input)
740
+ }
741
+
742
+ /** Cancel a run (MCP `orch_cancel_run` + ws `orch.cancelRun`). Delegates to cancelOrchRunFn. */
743
+ async cancelOrchRun(runId: string): Promise<void> {
744
+ return cancelOrchRunFn(this.buildLoopOrchCommandDeps(), runId)
745
+ }
746
+
747
+ /** Canonical run detail DTO (MCP `orch_run_status` + ws `orch.getRun`). Delegates to getOrchRunDetailFn. */
748
+ getOrchRunDetail(runId: string): OrchRunDetail | null {
749
+ return getOrchRunDetailFn(this.buildLoopOrchCommandDeps(), runId)
750
+ }
751
+
752
+ async enqueue(command: Extract<ClientCommand, { type: "message.enqueue" }>) {
753
+ if (typeof command.autoResumeOnRateLimit === "boolean") {
754
+ this.autoResumeByChat.set(command.chatId, command.autoResumeOnRateLimit)
755
+ }
756
+ this.analytics.track("message_sent")
757
+ const queuedMessage = await this.enqueueMessage(command.chatId, command.content, command.attachments ?? [], {
758
+ provider: command.provider,
759
+ model: command.model,
760
+ modelOptions: command.modelOptions,
761
+ planMode: command.planMode,
762
+ })
763
+ return { queuedMessageId: queuedMessage.id }
764
+ }
765
+
766
+ async steer(command: Extract<ClientCommand, { type: "message.steer" }>) {
767
+ return steerFn(this.buildChatManagementDeps(), command)
768
+ }
769
+
770
+ async dequeue(command: Extract<ClientCommand, { type: "message.dequeue" }>) {
771
+ return dequeueFn(this.buildChatManagementDeps(), command)
772
+ }
773
+
774
+ async forkChat(chatId: string) {
775
+ return forkChatFn(this.buildChatManagementDeps(), chatId)
776
+ }
777
+
778
+ private buildRunClaudeSessionDeps(): RunClaudeSessionDeps {
779
+ return agentDepsBuilders.buildRunClaudeSessionDeps(this)
780
+ }
781
+
782
+ /** @internal used by agent-deps-builders.ts via buildSpawnClaudeTurnDeps */
783
+ async runClaudeSession(session: ClaudeSessionState) {
784
+ return runClaudeSessionLoop(this.buildRunClaudeSessionDeps(), session)
785
+ }
786
+
787
+ /** @internal used by agent-deps-builders.ts via buildChatManagementDeps */
788
+ async generateTitleInBackground(chatId: string, messageContent: string, cwd: string, expectedCurrentTitle: string) {
789
+ return generateTitleInBackgroundFn(this.buildChatManagementDeps(), chatId, messageContent, cwd, expectedCurrentTitle)
790
+ }
791
+
792
+ private buildRunTurnDeps(): RunTurnDeps {
793
+ return agentDepsBuilders.buildRunTurnDeps(this)
794
+ }
795
+
796
+ /** @internal used by agent-deps-builders.ts via buildStartTurnDeps; Delegates to runTurnFn. */
797
+ async runTurn(active: ActiveTurn): Promise<void> {
798
+ return runTurnFn(this.buildRunTurnDeps(), active)
799
+ }
800
+
801
+ /** @internal used by agent-deps-builders.ts via buildSessionErrorHandlerDeps; Delegates to resolveAutoResumeForFn. */
802
+ resolveAutoResumeFor(chatId: string): boolean {
803
+ return resolveAutoResumeForFn(this.buildAutoContinueCommandDeps(), chatId)
804
+ }
805
+
806
+ /** @internal used by agent-deps-builders.ts via buildSessionErrorHandlerDeps + buildLoopOrchCommandDeps; Delegates to emitAutoContinueEventFn. */
807
+ async emitAutoContinueEvent(event: AutoContinueEvent): Promise<void> {
808
+ return emitAutoContinueEventFn(this.buildAutoContinueCommandDeps(), event)
809
+ }
810
+
811
+ /** @internal used by agent-deps-builders.ts via buildRunClaudeSessionDeps + buildRunTurnDeps; Delegates to handleLimitErrorFn. */
812
+ async handleLimitError(chatId: string, detector: LimitDetector, error: AnyValue): Promise<boolean> {
813
+ return handleLimitErrorFn(this.buildSessionErrorHandlerDeps(), chatId, detector, error)
814
+ }
815
+
816
+ /** @internal used by agent-deps-builders.ts via buildRunClaudeSessionDeps; Delegates to handleLimitDetectionFn. */
817
+ async handleLimitDetection(chatId: string, detection: LimitDetection): Promise<boolean> {
818
+ return handleLimitDetectionFn(this.buildSessionErrorHandlerDeps(), chatId, detection)
819
+ }
820
+
821
+ /** @internal used by agent-deps-builders.ts via buildRunClaudeSessionDeps; Delegates to handleAuthFailureFn. */
822
+ async handleAuthFailure(
823
+ session: ClaudeSessionState,
824
+ detection: AuthErrorDetection,
825
+ ): Promise<boolean> {
826
+ return handleAuthFailureFn(this.buildSessionErrorHandlerDeps(), session, detection)
827
+ }
828
+
829
+ /** Delegates to fireAutoContinueFn — see claude-autocontinue-commands.ts. */
830
+ async fireAutoContinue(chatId: string, scheduleId: string) {
831
+ return fireAutoContinueFn(this.buildAutoContinueCommandDeps(), chatId, scheduleId)
832
+ }
833
+
834
+ /** Delegates to acceptAutoContinueFn — see claude-autocontinue-commands.ts. */
835
+ async acceptAutoContinue(chatId: string, scheduleId: string, scheduledAt: number): Promise<void> {
836
+ return acceptAutoContinueFn(this.buildAutoContinueCommandDeps(), chatId, scheduleId, scheduledAt)
837
+ }
838
+
839
+ /** Delegates to rescheduleAutoContinueFn — see claude-autocontinue-commands.ts. */
840
+ async rescheduleAutoContinue(chatId: string, scheduleId: string, scheduledAt: number): Promise<void> {
841
+ return rescheduleAutoContinueFn(this.buildAutoContinueCommandDeps(), chatId, scheduleId, scheduledAt)
842
+ }
843
+
844
+ /** Delegates to cancelAutoContinueFn — see claude-autocontinue-commands.ts. */
845
+ async cancelAutoContinue(chatId: string, scheduleId: string, reason: "user" | "chat_deleted"): Promise<void> {
846
+ return cancelAutoContinueFn(this.buildAutoContinueCommandDeps(), chatId, scheduleId, reason)
847
+ }
848
+
849
+ /**
850
+ * Deliver a finished `run_in_background` subagent's result back into the
851
+ * main chat as a fresh turn AND clear the main-agent's Claude session so the
852
+ * next turn starts with a fresh context window. Wired as the orchestrator's
853
+ * `onBackgroundRunComplete` hook. Delegates to deliverSubagentToMainFn —
854
+ * see claude-loop-orch-commands.ts.
855
+ */
856
+ private async deliverSubagentToMain(
857
+ chatId: string,
858
+ runId: string,
859
+ outcome: BackgroundRunOutcome,
860
+ ): Promise<void> {
861
+ return deliverSubagentToMainFn(this.buildLoopOrchCommandDeps(), chatId, runId, outcome)
862
+ }
863
+
864
+ /**
865
+ * Arm an autonomous loop on the main chat. Validates the loop spec, ensures
866
+ * the tracking file exists (writes a skeleton if absent), then /clears the
867
+ * main-agent Claude session and enqueues the templated recurring prompt so
868
+ * the next turn starts the loop. Backs `mcp__kanna__setup_loop`. Delegates
869
+ * to setupLoopFn — see claude-loop-orch-commands.ts.
870
+ */
871
+ async setupLoop(args: {
872
+ chatId: string
873
+ input: LoopSetupInput
874
+ }): Promise<SetupLoopHandlerResult> {
875
+ return setupLoopFn(this.buildLoopOrchCommandDeps(), args)
876
+ }
877
+
878
+ /** Current armed-loop state for a chat, or null. Delegates to isLoopArmedFn — see claude-loop-orch-commands.ts. */
879
+ isLoopArmed(chatId: string): LoopState | null {
880
+ return isLoopArmedFn(this.buildLoopOrchCommandDeps(), chatId)
881
+ }
882
+
883
+ /**
884
+ * Disarm an armed loop (restores tools + stops prompt re-injection). Backs
885
+ * the `stop_loop` MCP tool (called by the model on GOAL MET) and the
886
+ * user-send takeover path. No-op when no loop is armed. Delegates to
887
+ * stopLoopFn — see claude-loop-orch-commands.ts.
888
+ */
889
+ async stopLoop(chatId: string, reason: "goal_met" | "user_send" | "chat_deleted"): Promise<void> {
890
+ return stopLoopFn(this.buildLoopOrchCommandDeps(), chatId, reason)
891
+ }
892
+
893
+ /** Delegates to listLiveSchedulesFn — see claude-loop-orch-commands.ts. */
894
+ listLiveSchedules(chatId: string): string[] {
895
+ return listLiveSchedulesFn(this.buildLoopOrchCommandDeps(), chatId)
896
+ }
897
+
898
+ async killPtyInstance(chatId: string): Promise<void> {
899
+ return killPtyInstanceFn(this.buildClaudeSessionConfigHelpersDeps(), chatId)
900
+ }
901
+
902
+ async cancel(chatId: string, options?: { hideInterrupted?: boolean; skipQueueDrain?: boolean }) {
903
+ return cancelChatFn(this.buildCancelHandlerDeps(), chatId, options)
904
+ }
905
+
906
+ async respondTool(command: Extract<ClientCommand, { type: "chat.respondTool" }>) {
907
+ return respondToolFn(this.buildToolRespondDeps(), command)
908
+ }
909
+
910
+ async respondSubagentTool(command: Extract<ClientCommand, { type: "chat.respondSubagentTool" }>) {
911
+ return respondSubagentToolFn(this.buildSubagentToolResponseDeps(), command)
912
+ }
913
+
914
+ async cancelSubagentRun(
915
+ command: Extract<ClientCommand, { type: "chat.cancelSubagentRun" }>,
916
+ ) {
917
+ cancelSubagentRunFn(this.buildSubagentToolResponseDeps(), command)
918
+ }
919
+ }