@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.
- package/dist/client/assets/{arc-BLk1CZKB.js → arc-DXtN3EWJ.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-B01ZQYte.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-diL6a3cq.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BY1verL8.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
- package/dist/client/assets/channel-BEYTrUAm.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-psdZuajw.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-CrwQVBc5.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-9-aj4jKO.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-1FNK9_nB.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DFhvd_Ek.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-B7MLrDa7.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-D4Df05QE.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-Iq17Dznm.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-BmaZhtXy.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-BCmpfnfP.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BIEcKowS.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CRBABBfv.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BRxUk16N.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DPBs4xl1.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BE9rgVv8.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-D3vgA1xJ.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Cgu-8XXC.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CgjXVyTR.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-zD5D8B-i.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
- package/dist/client/assets/{index-DBTdhMbJ.js → index-BqUY1Bm4.js} +1 -1
- package/dist/client/assets/index-CLsOWXff.css +32 -0
- package/dist/client/assets/{index-zQLLeAfl.js → index-TU1k6ov6.js} +253 -243
- package/dist/client/assets/{infoDiagram-5YYISTIA-BaFHqT4u.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-2yAC1br2.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DacQ-ND5.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Qlbwm3z4.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
- package/dist/client/assets/{linear-DeafqpgO.js → linear-ByKHtsIx.js} +1 -1
- package/dist/client/assets/{mermaid.core-KYiCPJDj.js → mermaid.core-Dl_f2KLc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-B1V-T6d3.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DOF9OFRu.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-lH1mB7ko.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BaTsJ_sS.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Pfxib5iI.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQMEKkBC.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CuldNnzp.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DP7u0YAg.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-QlrjpNeQ.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-BqdJ_sAM.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Wi3kaQ5X.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-D-KXMyCG.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent-coordinator-types.ts +158 -0
- package/src/server/agent-coordinator.ts +919 -0
- package/src/server/agent-deps-builders.ts +425 -0
- package/src/server/agent.oauth-rotation.test.ts +101 -0
- package/src/server/agent.test.ts +24 -8
- package/src/server/agent.ts +35 -4963
- package/src/server/auto-continue/read-model.test.ts +2 -2
- package/src/server/auto-continue/read-model.ts +4 -1
- package/src/server/claude-autocontinue-commands.test.ts +494 -0
- package/src/server/claude-autocontinue-commands.ts +256 -0
- package/src/server/claude-cancel-handler.test.ts +472 -0
- package/src/server/claude-cancel-handler.ts +241 -0
- package/src/server/claude-chat-management.test.ts +395 -0
- package/src/server/claude-chat-management.ts +251 -0
- package/src/server/claude-harness-stream.test.ts +273 -0
- package/src/server/claude-harness-stream.ts +158 -0
- package/src/server/claude-loop-orch-commands.test.ts +579 -0
- package/src/server/claude-loop-orch-commands.ts +466 -0
- package/src/server/claude-message-normalizer.test.ts +213 -0
- package/src/server/claude-message-normalizer.ts +436 -0
- package/src/server/claude-prompt-helpers.test.ts +248 -0
- package/src/server/claude-prompt-helpers.ts +139 -0
- package/src/server/claude-sdk-queue.test.ts +169 -0
- package/src/server/claude-sdk-queue.ts +82 -0
- package/src/server/claude-send-command.test.ts +414 -0
- package/src/server/claude-send-command.ts +420 -0
- package/src/server/claude-session-config-helpers.test.ts +279 -0
- package/src/server/claude-session-config-helpers.ts +151 -0
- package/src/server/claude-session-config.test.ts +250 -0
- package/src/server/claude-session-config.ts +159 -0
- package/src/server/claude-session-error-handler.test.ts +498 -0
- package/src/server/claude-session-error-handler.ts +361 -0
- package/src/server/claude-session-lifecycle.test.ts +669 -0
- package/src/server/claude-session-lifecycle.ts +283 -0
- package/src/server/claude-session-rebuild.test.ts +224 -0
- package/src/server/claude-session-rebuild.ts +101 -0
- package/src/server/claude-session-runner.test.ts +523 -0
- package/src/server/claude-session-runner.ts +521 -0
- package/src/server/claude-session-spawner.test.ts +401 -0
- package/src/server/claude-session-spawner.ts +401 -0
- package/src/server/claude-session-start.test.ts +205 -0
- package/src/server/claude-session-start.ts +277 -0
- package/src/server/claude-session-state-queries.test.ts +344 -0
- package/src/server/claude-session-state-queries.ts +160 -0
- package/src/server/claude-session-state.ts +104 -0
- package/src/server/claude-slash-commands.test.ts +277 -0
- package/src/server/claude-slash-commands.ts +234 -0
- package/src/server/claude-spawn-helpers.test.ts +221 -0
- package/src/server/claude-spawn-helpers.ts +179 -0
- package/src/server/claude-steer-log.test.ts +243 -0
- package/src/server/claude-steer-log.ts +58 -0
- package/src/server/claude-subagent-tool-response.test.ts +213 -0
- package/src/server/claude-subagent-tool-response.ts +165 -0
- package/src/server/claude-subagent-wiring.test.ts +263 -0
- package/src/server/claude-subagent-wiring.ts +324 -0
- package/src/server/claude-tool-respond.test.ts +321 -0
- package/src/server/claude-tool-respond.ts +142 -0
- package/src/server/claude-turn-runner.test.ts +310 -0
- package/src/server/claude-turn-runner.ts +228 -0
- package/src/server/claude-turn-starter.test.ts +266 -0
- package/src/server/claude-turn-starter.ts +566 -0
- package/src/server/claude-usage-math.test.ts +169 -0
- package/src/server/claude-usage-math.ts +132 -0
- package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
- package/src/server/diff-store-branch-ops.adapter.ts +602 -0
- package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
- package/src/server/diff-store-commit-ops.adapter.ts +258 -0
- package/src/server/diff-store-errors.ts +85 -0
- package/src/server/diff-store-file-ops.adapter.ts +212 -0
- package/src/server/diff-store-git-branch.adapter.ts +686 -0
- package/src/server/diff-store-io.adapter.ts +27 -0
- package/src/server/diff-store-parse.test.ts +212 -0
- package/src/server/diff-store-parse.ts +122 -0
- package/src/server/diff-store-state.ts +82 -0
- package/src/server/diff-store.ts +76 -1854
- package/src/server/event-store-apply.test.ts +280 -0
- package/src/server/event-store-apply.ts +132 -0
- package/src/server/event-store-chat-lifecycle.test.ts +408 -0
- package/src/server/event-store-chat-lifecycle.ts +441 -0
- package/src/server/event-store-entity-write.ts +356 -0
- package/src/server/event-store-helpers.test.ts +230 -0
- package/src/server/event-store-helpers.ts +235 -0
- package/src/server/event-store-init.ts +276 -0
- package/src/server/event-store-messages.adapter.test.ts +365 -0
- package/src/server/event-store-messages.adapter.ts +213 -0
- package/src/server/event-store-orch-subscription.ts +124 -0
- package/src/server/event-store-orch.test.ts +462 -0
- package/src/server/event-store-orch.ts +312 -0
- package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
- package/src/server/event-store-peripheral-events.adapter.ts +105 -0
- package/src/server/event-store-snapshot.test.ts +597 -0
- package/src/server/event-store-snapshot.ts +658 -0
- package/src/server/event-store-subagent.test.ts +408 -0
- package/src/server/event-store-subagent.ts +198 -0
- package/src/server/event-store-tool-requests.test.ts +210 -0
- package/src/server/event-store-tool-requests.ts +131 -0
- package/src/server/event-store-transcript-write.adapter.ts +285 -0
- package/src/server/event-store-write-ops.test.ts +554 -0
- package/src/server/event-store-write-ops.ts +529 -0
- package/src/server/event-store.test.ts +35 -0
- package/src/server/event-store.ts +251 -2284
- package/src/server/events.ts +3 -0
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/read-models.ts +26 -1
- package/src/server/subagent-orchestrator.ts +3 -0
- package/src/server/ws-router-agent-ctrl.test.ts +187 -0
- package/src/server/ws-router-agent-ctrl.ts +137 -0
- package/src/server/ws-router-broadcast.ts +488 -0
- package/src/server/ws-router-chat.test.ts +461 -0
- package/src/server/ws-router-chat.ts +279 -0
- package/src/server/ws-router-defaults.test.ts +283 -0
- package/src/server/ws-router-defaults.ts +434 -0
- package/src/server/ws-router-diff.test.ts +167 -0
- package/src/server/ws-router-diff.ts +250 -0
- package/src/server/ws-router-envelope.ts +329 -0
- package/src/server/ws-router-misc.test.ts +422 -0
- package/src/server/ws-router-misc.ts +265 -0
- package/src/server/ws-router-orch.test.ts +190 -0
- package/src/server/ws-router-orch.ts +93 -0
- package/src/server/ws-router-project.test.ts +403 -0
- package/src/server/ws-router-project.ts +256 -0
- package/src/server/ws-router-push.test.ts +193 -0
- package/src/server/ws-router-push.ts +120 -0
- package/src/server/ws-router-settings.test.ts +173 -0
- package/src/server/ws-router-settings.ts +416 -0
- package/src/server/ws-router-skills.test.ts +194 -0
- package/src/server/ws-router-skills.ts +204 -0
- package/src/server/ws-router-utils.ts +224 -0
- package/src/server/ws-router.ts +339 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/core-types.ts +27 -0
- package/src/shared/git-diff-types.ts +165 -0
- package/src/shared/loop-progress.test.ts +106 -0
- package/src/shared/loop-progress.ts +88 -0
- package/src/shared/mcp-types.ts +105 -0
- package/src/shared/provider-model-types.ts +437 -0
- package/src/shared/subagent-types.ts +198 -0
- package/src/shared/tool-call-types.ts +314 -0
- package/src/shared/transcript-types.ts +270 -0
- package/src/shared/types.ts +27 -1668
- package/dist/client/assets/channel-BMkhjiLI.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C5Kg1XOD.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5Kg1XOD.js +0 -1
- package/dist/client/assets/index-H6C-f2Vp.css +0 -32
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ca2U11af.js +0 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder functions for all 18 *Deps objects used by AgentCoordinator.
|
|
3
|
+
*
|
|
4
|
+
* Each function takes the coordinator instance and returns the typed *Deps
|
|
5
|
+
* object that the corresponding functional module expects. Using
|
|
6
|
+
* `import type { AgentCoordinator }` avoids a circular runtime dependency
|
|
7
|
+
* (the import is erased at compile time).
|
|
8
|
+
*
|
|
9
|
+
* **Visibility requirement:** All AgentCoordinator fields and methods accessed
|
|
10
|
+
* here must be declared `readonly` (not `private readonly`) so TypeScript
|
|
11
|
+
* allows external access.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { AgentCoordinator } from "./agent-coordinator"
|
|
15
|
+
import { ensureFreshMcpToken } from "./mcp-oauth.adapter"
|
|
16
|
+
import { realpathAdapter } from "./paths-fs.adapter"
|
|
17
|
+
import { ensureTrackingFile } from "./loop-template-io.adapter"
|
|
18
|
+
import { homedir } from "node:os"
|
|
19
|
+
import { providerUsesSdkSession } from "../shared/types"
|
|
20
|
+
import { isClaudeSdkProvider } from "./provider-catalog"
|
|
21
|
+
|
|
22
|
+
import type { ClaudeSessionConfigHelpersDeps } from "./claude-session-config-helpers"
|
|
23
|
+
import type { SessionLifecycleDeps } from "./claude-session-lifecycle"
|
|
24
|
+
import type { SessionErrorHandlerDeps } from "./claude-session-error-handler"
|
|
25
|
+
import type { AutoContinueCommandDeps } from "./claude-autocontinue-commands"
|
|
26
|
+
import type { LoopOrchCommandDeps } from "./claude-loop-orch-commands"
|
|
27
|
+
import type { CancelHandlerDeps } from "./claude-cancel-handler"
|
|
28
|
+
import type { ChatManagementDeps } from "./claude-chat-management"
|
|
29
|
+
import type { SendCommandDeps } from "./claude-send-command"
|
|
30
|
+
import type { SubagentWiringDeps } from "./claude-subagent-wiring"
|
|
31
|
+
import type { SlashCommandsDeps } from "./claude-slash-commands"
|
|
32
|
+
import type { SubagentToolResponseDeps } from "./claude-subagent-tool-response"
|
|
33
|
+
import type { ToolRespondDeps } from "./claude-tool-respond"
|
|
34
|
+
import type { SessionStateQueryDeps } from "./claude-session-state-queries"
|
|
35
|
+
import type { StartTurnDeps } from "./claude-turn-starter"
|
|
36
|
+
import type { SessionRebuildDeps } from "./claude-session-rebuild"
|
|
37
|
+
import type { SpawnClaudeTurnDeps } from "./claude-session-spawner"
|
|
38
|
+
import type { RunClaudeSessionDeps } from "./claude-session-runner"
|
|
39
|
+
import type { RunTurnDeps } from "./claude-turn-runner"
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// 1. Session config helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
export function buildClaudeSessionConfigHelpersDeps(agent: AgentCoordinator): ClaudeSessionConfigHelpersDeps {
|
|
46
|
+
return {
|
|
47
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
48
|
+
chatPolicy: agent.chatPolicy,
|
|
49
|
+
store: agent.store,
|
|
50
|
+
ptyInstanceRegistry: agent.ptyInstanceRegistry,
|
|
51
|
+
ensureFreshToken: (server, opts) => ensureFreshMcpToken(server, opts),
|
|
52
|
+
persistOAuthState: agent.persistOAuthStateFn,
|
|
53
|
+
killProcessTree: async (pid) => {
|
|
54
|
+
const { killProcessTree } = await import("./claude-pty/pid-registry.adapter")
|
|
55
|
+
await killProcessTree(pid)
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// 2. Session lifecycle
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
export function buildSessionLifecycleDeps(agent: AgentCoordinator): SessionLifecycleDeps {
|
|
65
|
+
return {
|
|
66
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
67
|
+
defaultIdleMs: agent.claudeSessionLifecycle.idleMs,
|
|
68
|
+
defaultMaxResidentSessions: agent.claudeSessionLifecycle.maxResidentSessions,
|
|
69
|
+
claudeSessions: agent.claudeSessions,
|
|
70
|
+
activeTurns: agent.activeTurns,
|
|
71
|
+
oauthPool: agent.oauthPool,
|
|
72
|
+
workflowRegistry: agent.workflowRegistry,
|
|
73
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
74
|
+
emitStateChange: (chatId: string) => { agent.emitStateChange(chatId) },
|
|
75
|
+
store: agent.store,
|
|
76
|
+
homeDir: homedir(),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// 3. Session error handler
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export function buildSessionErrorHandlerDeps(agent: AgentCoordinator): SessionErrorHandlerDeps {
|
|
85
|
+
return {
|
|
86
|
+
tokenRotationDedupe: agent.tokenRotationDedupe,
|
|
87
|
+
claudeSessions: agent.claudeSessions,
|
|
88
|
+
activeTurns: agent.activeTurns,
|
|
89
|
+
oauthPool: agent.oauthPool,
|
|
90
|
+
store: agent.store,
|
|
91
|
+
resolveAutoResumeFor: (chatId: string) => agent.resolveAutoResumeFor(chatId),
|
|
92
|
+
emitAutoContinueEvent: (event) => agent.emitAutoContinueEvent(event),
|
|
93
|
+
closeClaudeSession: (chatId, session, opts?) =>
|
|
94
|
+
agent.closeClaudeSession(chatId, session, opts),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// 4. Auto-continue commands
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
export function buildAutoContinueCommandDeps(agent: AgentCoordinator): AutoContinueCommandDeps {
|
|
103
|
+
return {
|
|
104
|
+
autoResumeByChat: agent.autoResumeByChat,
|
|
105
|
+
getAutoResumePreference: () => agent.getAutoResumePreference(),
|
|
106
|
+
store: agent.store,
|
|
107
|
+
scheduleManager: agent.scheduleManager,
|
|
108
|
+
emitStateChange: (chatId: string) => { agent.emitStateChange(chatId) },
|
|
109
|
+
enqueueMessage: (chatId, content, attachments, options) =>
|
|
110
|
+
agent.enqueueMessage(chatId, content, attachments, options),
|
|
111
|
+
maybeStartNextQueuedMessage: (chatId) => agent.maybeStartNextQueuedMessage(chatId),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// 5. Loop / orch commands
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
export function buildLoopOrchCommandDeps(agent: AgentCoordinator): LoopOrchCommandDeps {
|
|
120
|
+
return {
|
|
121
|
+
store: agent.store,
|
|
122
|
+
orchestrationQueue: agent.getOrchestrationQueue(),
|
|
123
|
+
claudeSessions: agent.claudeSessions,
|
|
124
|
+
activeTurns: agent.activeTurns,
|
|
125
|
+
getSubagents: () => agent.getSubagents(),
|
|
126
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
127
|
+
buildSubagentProviderRunForChat: (args) => agent.buildSubagentProviderRunForChat(args),
|
|
128
|
+
closeClaudeSession: (chatId, session) => agent.closeClaudeSession(chatId, session),
|
|
129
|
+
emitAutoContinueEvent: (event) => agent.emitAutoContinueEvent(event),
|
|
130
|
+
ensureTrackingFile,
|
|
131
|
+
isLoopArmed: (chatId) => agent.isLoopArmed(chatId),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// 6. Cancel handler
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
export function buildCancelHandlerDeps(agent: AgentCoordinator): CancelHandlerDeps {
|
|
140
|
+
return {
|
|
141
|
+
drainingStreams: agent.drainingStreams,
|
|
142
|
+
rejectPendingResolversForChat: (chatId) => agent.rejectPendingResolversForChat(chatId),
|
|
143
|
+
cancelChatInOrchestrator: (chatId) => agent.getSubagentOrchestrator().cancelChat(chatId),
|
|
144
|
+
activeTurns: agent.activeTurns,
|
|
145
|
+
store: agent.store,
|
|
146
|
+
claudeSessions: agent.claudeSessions,
|
|
147
|
+
emitStateChange: (chatId) => agent.emitStateChange(chatId),
|
|
148
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
149
|
+
closeClaudeSession: (chatId, session) => agent.closeClaudeSession(chatId, session),
|
|
150
|
+
maybeStartNextQueuedMessage: async (chatId) => { await agent.maybeStartNextQueuedMessage(chatId) },
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// 7. Chat management
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
export function buildChatManagementDeps(agent: AgentCoordinator): ChatManagementDeps {
|
|
159
|
+
return {
|
|
160
|
+
activeTurns: agent.activeTurns,
|
|
161
|
+
drainingStreams: agent.drainingStreams,
|
|
162
|
+
claudeSessions: agent.claudeSessions,
|
|
163
|
+
autoResumeByChat: agent.autoResumeByChat,
|
|
164
|
+
store: agent.store,
|
|
165
|
+
analytics: agent.analytics,
|
|
166
|
+
cancel: (chatId, options) => agent.cancel(chatId, options),
|
|
167
|
+
closeClaudeSession: (chatId, session, opts) => agent.closeClaudeSession(chatId, session, opts),
|
|
168
|
+
emitStateChange: (chatId) => agent.emitStateChange(chatId),
|
|
169
|
+
generateTitle: (messageContent, cwd) => agent.generateTitle(messageContent, cwd),
|
|
170
|
+
reportBackgroundError: agent.reportBackgroundError,
|
|
171
|
+
dequeueAndStartQueuedMessage: (chatId, queuedMessage, options) =>
|
|
172
|
+
agent.dequeueAndStartQueuedMessage(chatId, queuedMessage, options),
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// 8. Send command
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
export function buildSendCommandDeps(agent: AgentCoordinator): SendCommandDeps {
|
|
181
|
+
return {
|
|
182
|
+
store: agent.store,
|
|
183
|
+
activeTurns: agent.activeTurns,
|
|
184
|
+
claudeSessions: agent.claudeSessions,
|
|
185
|
+
autoResumeByChat: agent.autoResumeByChat,
|
|
186
|
+
analytics: agent.analytics,
|
|
187
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
188
|
+
stopLoop: (chatId, reason) => agent.stopLoop(chatId, reason),
|
|
189
|
+
emitStateChange: (chatId) => agent.emitStateChange(chatId),
|
|
190
|
+
startTurnForChat: (args) => agent.startTurnForChat(args),
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// 9. Subagent wiring
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
export function buildSubagentWiringDeps(agent: AgentCoordinator): SubagentWiringDeps {
|
|
199
|
+
return {
|
|
200
|
+
store: agent.store,
|
|
201
|
+
startClaudeSessionFn: agent.startClaudeSessionFn,
|
|
202
|
+
startClaudeSessionPTYFn: agent.startClaudeSessionPTYFn,
|
|
203
|
+
toolCallback: agent.toolCallback,
|
|
204
|
+
tunnelGateway: agent.tunnelGateway,
|
|
205
|
+
claudePtyRegistry: agent.claudePtyRegistry,
|
|
206
|
+
ptyInstanceRegistry: agent.ptyInstanceRegistry,
|
|
207
|
+
workflowRegistry: agent.workflowRegistry,
|
|
208
|
+
subagentOrchestrator: agent.getSubagentOrchestrator(),
|
|
209
|
+
codexManager: agent.codexManager,
|
|
210
|
+
oauthPool: agent.oauthPool,
|
|
211
|
+
subagentPendingResolvers: agent.subagentPendingResolvers,
|
|
212
|
+
realpath: realpathAdapter,
|
|
213
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
214
|
+
getEnabledCustomMcpServers: () => agent.getEnabledCustomMcpServers(),
|
|
215
|
+
buildOAuthBearers: (servers) => agent.buildOAuthBearers(servers),
|
|
216
|
+
resolveChatPolicy: (chatId) => agent.resolveChatPolicy(chatId),
|
|
217
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
218
|
+
buildPoolUnavailableMessage: (reservedFor, scopeSuffix) =>
|
|
219
|
+
agent.buildPoolUnavailableMessage(reservedFor, scopeSuffix),
|
|
220
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
221
|
+
readLlmProvider: () => agent.readLlmProvider(),
|
|
222
|
+
subagentPendingKey: (chatId, runId, toolUseId) =>
|
|
223
|
+
agent.subagentPendingKey(chatId, runId, toolUseId),
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// 10. Slash commands
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
export function buildSlashCommandsDeps(agent: AgentCoordinator): SlashCommandsDeps {
|
|
232
|
+
return {
|
|
233
|
+
store: agent.store,
|
|
234
|
+
claudeSessions: agent.claudeSessions,
|
|
235
|
+
oauthPool: agent.oauthPool,
|
|
236
|
+
slashCommandsInFlight: agent.slashCommandsInFlight,
|
|
237
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
238
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
239
|
+
startClaudeSessionPTY: agent.startClaudeSessionPTYFn,
|
|
240
|
+
startClaudeSessionSDK: agent.startClaudeSessionFn,
|
|
241
|
+
getSubagents: () => agent.getSubagents(),
|
|
242
|
+
getGlobalPromptAppend: () => agent.getAppSettingsSnapshot().globalPromptAppend,
|
|
243
|
+
getEnabledCustomMcpServers: () => agent.getEnabledCustomMcpServers(),
|
|
244
|
+
claudePtyRegistry: agent.claudePtyRegistry,
|
|
245
|
+
ptyInstanceRegistry: agent.ptyInstanceRegistry,
|
|
246
|
+
workflowRegistry: agent.workflowRegistry,
|
|
247
|
+
subagentTranscriptRegistry: agent.subagentTranscriptRegistry,
|
|
248
|
+
localCatalog: agent.localCatalog,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// 11. Subagent tool response
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
export function buildSubagentToolResponseDeps(agent: AgentCoordinator): SubagentToolResponseDeps {
|
|
257
|
+
return {
|
|
258
|
+
subagentPendingResolvers: agent.subagentPendingResolvers,
|
|
259
|
+
store: agent.store,
|
|
260
|
+
subagentOrchestrator: agent.getSubagentOrchestrator(),
|
|
261
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// 12. Tool respond
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
export function buildToolRespondDeps(agent: AgentCoordinator): ToolRespondDeps {
|
|
270
|
+
return {
|
|
271
|
+
activeTurns: agent.activeTurns,
|
|
272
|
+
store: agent.store,
|
|
273
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// 13. Session state queries
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
export function buildSessionStateQueryDeps(agent: AgentCoordinator): SessionStateQueryDeps {
|
|
282
|
+
return {
|
|
283
|
+
activeTurns: agent.activeTurns,
|
|
284
|
+
claudeSessions: agent.claudeSessions,
|
|
285
|
+
drainingStreams: agent.drainingStreams,
|
|
286
|
+
slashCommandsInFlight: agent.slashCommandsInFlight,
|
|
287
|
+
isClaudeSdkProvider: (provider) => isClaudeSdkProvider(provider),
|
|
288
|
+
hasPendingBackgroundTask: (session, now) => agent.hasPendingBackgroundTask(session, now),
|
|
289
|
+
resolveClaudeIdleMs: () => agent.resolveClaudeIdleMs(),
|
|
290
|
+
hasLiveWorkflow: (chatId) => agent.hasLiveWorkflow(chatId),
|
|
291
|
+
closeClaudeSession: (chatId, session) => { agent.closeClaudeSession(chatId, session) },
|
|
292
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// 14. Start turn
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
export function buildStartTurnDeps(agent: AgentCoordinator): StartTurnDeps {
|
|
301
|
+
return {
|
|
302
|
+
activeTurns: agent.activeTurns,
|
|
303
|
+
claudeSessions: agent.claudeSessions,
|
|
304
|
+
drainingStreams: agent.drainingStreams,
|
|
305
|
+
mentionedSubagentIdsByChat: agent.mentionedSubagentIdsByChat,
|
|
306
|
+
store: agent.store,
|
|
307
|
+
codexManager: agent.codexManager,
|
|
308
|
+
subagentOrchestrator: agent.getSubagentOrchestrator(),
|
|
309
|
+
clearDrainingStream: (chatId) => agent.clearDrainingStream(chatId),
|
|
310
|
+
emitStateChange: (chatId, opts) => agent.emitStateChange(chatId, opts),
|
|
311
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
312
|
+
getSubagents: () => agent.getSubagents(),
|
|
313
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
314
|
+
generateTitleInBackground: (chatId, content, localPath, optimisticTitle) =>
|
|
315
|
+
agent.generateTitleInBackground(chatId, content, localPath, optimisticTitle),
|
|
316
|
+
recreateActiveTurnFromSession: (args) => agent.recreateActiveTurnFromSession(args),
|
|
317
|
+
startClaudeTurn: (args) => agent.startClaudeTurn(args),
|
|
318
|
+
findLastUserMessageId: (chatId) => agent.findLastUserMessageId(chatId),
|
|
319
|
+
runTurn: (active) => agent.runTurn(active),
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// 15. Session rebuild
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
export function buildSessionRebuildDeps(agent: AgentCoordinator): SessionRebuildDeps {
|
|
328
|
+
return {
|
|
329
|
+
claudeSessions: agent.claudeSessions,
|
|
330
|
+
activeTurns: agent.activeTurns,
|
|
331
|
+
providerUsesSdkSession: (provider) => providerUsesSdkSession(provider),
|
|
332
|
+
getMessages: (chatId) => agent.store.getMessages(chatId),
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// 16. Spawn Claude turn
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
export function buildSpawnClaudeTurnDeps(agent: AgentCoordinator): SpawnClaudeTurnDeps {
|
|
341
|
+
return {
|
|
342
|
+
claudeSessions: agent.claudeSessions,
|
|
343
|
+
activeTurns: agent.activeTurns,
|
|
344
|
+
mentionedSubagentIdsByChat: agent.mentionedSubagentIdsByChat,
|
|
345
|
+
oauthPool: agent.oauthPool,
|
|
346
|
+
store: agent.store,
|
|
347
|
+
startClaudeSessionFn: agent.startClaudeSessionFn,
|
|
348
|
+
startClaudeSessionPTYFn: agent.startClaudeSessionPTYFn,
|
|
349
|
+
subagentOrchestrator: agent.getSubagentOrchestrator(),
|
|
350
|
+
toolCallback: agent.toolCallback,
|
|
351
|
+
tunnelGateway: agent.tunnelGateway,
|
|
352
|
+
claudePtyRegistry: agent.claudePtyRegistry,
|
|
353
|
+
ptyInstanceRegistry: agent.ptyInstanceRegistry,
|
|
354
|
+
workflowRegistry: agent.workflowRegistry,
|
|
355
|
+
subagentTranscriptRegistry: agent.subagentTranscriptRegistry,
|
|
356
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
357
|
+
isLoopArmed: (chatId) => agent.isLoopArmed(chatId),
|
|
358
|
+
closeClaudeSession: (chatId, session) => agent.closeClaudeSession(chatId, session),
|
|
359
|
+
enforceClaudeSessionBudget: (protectedChatId?) => agent.enforceClaudeSessionBudget(protectedChatId),
|
|
360
|
+
readLlmProvider: () => agent.readLlmProvider(),
|
|
361
|
+
buildPoolUnavailableMessage: (reservedFor, scopeSuffix) =>
|
|
362
|
+
agent.buildPoolUnavailableMessage(reservedFor, scopeSuffix),
|
|
363
|
+
listOpenRouterModelsFn: agent.listOpenRouterModelsFn,
|
|
364
|
+
getSubagents: () => agent.getSubagents(),
|
|
365
|
+
getAppSettingsSnapshot: () => agent.getAppSettingsSnapshot(),
|
|
366
|
+
getEnabledCustomMcpServers: () => agent.getEnabledCustomMcpServers(),
|
|
367
|
+
buildOAuthBearers: (servers) => agent.buildOAuthBearers(servers),
|
|
368
|
+
setupLoop: (chatId, input) => agent.setupLoop({ chatId, input }),
|
|
369
|
+
stopLoop: (chatId, reason) => agent.stopLoop(chatId, reason),
|
|
370
|
+
runOrchestration: (chatId, input) => agent.runOrchestration(chatId, input),
|
|
371
|
+
cancelOrchRun: (runId) => agent.cancelOrchRun(runId),
|
|
372
|
+
getOrchRunDetail: (runId) => agent.getOrchRunDetail(runId),
|
|
373
|
+
resolveChatPolicy: (chatId) => agent.resolveChatPolicy(chatId),
|
|
374
|
+
runClaudeSession: (session) => { void agent.runClaudeSession(session) },
|
|
375
|
+
mergeLocalCatalog: (commands, cwd) => agent.mergeLocalCatalog(commands, cwd),
|
|
376
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// 17. Run Claude session
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
export function buildRunClaudeSessionDeps(agent: AgentCoordinator): RunClaudeSessionDeps {
|
|
385
|
+
return {
|
|
386
|
+
openrouterFirstEntryTimeoutMs: agent.openrouterFirstEntryTimeoutMs,
|
|
387
|
+
claudeSessions: agent.claudeSessions,
|
|
388
|
+
activeTurns: agent.activeTurns,
|
|
389
|
+
oauthPool: agent.oauthPool,
|
|
390
|
+
claudeLimitDetector: agent.claudeLimitDetector,
|
|
391
|
+
claudeAuthErrorDetector: agent.claudeAuthErrorDetector,
|
|
392
|
+
throwOnClaudeSessionStart: agent.throwOnClaudeSessionStart,
|
|
393
|
+
store: agent.store,
|
|
394
|
+
emitStateChange: (chatId?) => { agent.emitStateChange(chatId) },
|
|
395
|
+
handleLimitDetection: (chatId, detection) => agent.handleLimitDetection(chatId, detection),
|
|
396
|
+
maybeRegisterSdkWorkflowsDir: (session) => { agent.maybeRegisterSdkWorkflowsDir(session) },
|
|
397
|
+
getSubagents: () => agent.getSubagents(),
|
|
398
|
+
resolveBackgroundTaskMaxMs: () => agent.resolveBackgroundTaskMaxMs(),
|
|
399
|
+
mergeLocalCatalog: (commands, cwd) => agent.mergeLocalCatalog(commands, cwd),
|
|
400
|
+
handleLimitError: (chatId, detector, error) => agent.handleLimitError(chatId, detector, error),
|
|
401
|
+
handleAuthFailure: (session, detection) => agent.handleAuthFailure(session, detection),
|
|
402
|
+
closeClaudeSession: (chatId, session) => { agent.closeClaudeSession(chatId, session) },
|
|
403
|
+
maybeStartNextQueuedMessage: (chatId) => agent.maybeStartNextQueuedMessage(chatId),
|
|
404
|
+
resolveClaudeDriverPreference: () => agent.resolveClaudeDriverPreference(),
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// 18. Run turn
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
export function buildRunTurnDeps(agent: AgentCoordinator): RunTurnDeps {
|
|
413
|
+
return {
|
|
414
|
+
store: agent.store,
|
|
415
|
+
activeTurns: agent.activeTurns,
|
|
416
|
+
drainingStreams: agent.drainingStreams,
|
|
417
|
+
oauthPool: agent.oauthPool,
|
|
418
|
+
codexLimitDetector: agent.codexLimitDetector,
|
|
419
|
+
handleLimitError: (chatId, detector, error) => agent.handleLimitError(chatId, detector, error),
|
|
420
|
+
emitStateChange: (chatId) => { agent.emitStateChange(chatId) },
|
|
421
|
+
clearDrainingStream: (chatId) => { agent.clearDrainingStream(chatId) },
|
|
422
|
+
startTurnForChat: (args) => agent.startTurnForChat(args),
|
|
423
|
+
maybeStartNextQueuedMessage: (chatId) => agent.maybeStartNextQueuedMessage(chatId),
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -928,4 +928,105 @@ describe("AgentCoordinator OAuth rotation", () => {
|
|
|
928
928
|
},
|
|
929
929
|
10_000,
|
|
930
930
|
)
|
|
931
|
+
|
|
932
|
+
test(
|
|
933
|
+
"errored rate-limit RESULT with no matching active turn still schedules a resume (loop silent-death regression)",
|
|
934
|
+
async () => {
|
|
935
|
+
// Regression for the 9h autonomous-loop stall: a synthetic 429 `result`
|
|
936
|
+
// arriving when the pending prompt-seq queue is already drained (observed
|
|
937
|
+
// on an auto-continue wake turn) fails the
|
|
938
|
+
// `completedClaudePromptSeq === active.claudePromptSeq` gate, so the entire
|
|
939
|
+
// error-handling block — INCLUDING limit detection — was skipped and the
|
|
940
|
+
// loop died with NO resume schedule (no proposal, no accept). It only
|
|
941
|
+
// recovered when the user manually typed "resume" hours later. A
|
|
942
|
+
// recognizable rate-limit result must schedule a resume even when the seq
|
|
943
|
+
// gate misses. Idempotent: handleLimitDetection dedupes on a live schedule.
|
|
944
|
+
let tokens: OAuthTokenEntry[] = [makeToken("a")] // single token → no rotation target
|
|
945
|
+
const pool = new OAuthTokenPool(
|
|
946
|
+
() => tokens,
|
|
947
|
+
(id, patch) => {
|
|
948
|
+
tokens = tokens.map((t) => (t.id === id ? { ...t, ...patch } : t))
|
|
949
|
+
},
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
const events = new AsyncEventQueue<HarnessEvent>()
|
|
953
|
+
const store = createFakeStore()
|
|
954
|
+
const coordinator = new AgentCoordinator({
|
|
955
|
+
store: store as never,
|
|
956
|
+
onStateChange: () => {},
|
|
957
|
+
oauthPool: pool,
|
|
958
|
+
// auto-resume preference defaults to OFF → the resume is a *proposal*,
|
|
959
|
+
// respecting the user's setting (not a silent forced accept).
|
|
960
|
+
startClaudeSession: async () => ({
|
|
961
|
+
provider: "claude",
|
|
962
|
+
stream: events,
|
|
963
|
+
getAccountInfo: async () => null,
|
|
964
|
+
interrupt: async () => {},
|
|
965
|
+
close: () => {},
|
|
966
|
+
setModel: async () => {},
|
|
967
|
+
setPermissionMode: async () => {},
|
|
968
|
+
getSupportedCommands: async () => [],
|
|
969
|
+
sendPrompt: async () => {
|
|
970
|
+
// Turn 1 finalizes normally, draining the pending prompt-seq…
|
|
971
|
+
events.push({
|
|
972
|
+
type: "transcript",
|
|
973
|
+
entry: {
|
|
974
|
+
_id: "result-ok",
|
|
975
|
+
createdAt: Date.now(),
|
|
976
|
+
kind: "result",
|
|
977
|
+
subtype: "success",
|
|
978
|
+
isError: false,
|
|
979
|
+
durationMs: 10,
|
|
980
|
+
result: "",
|
|
981
|
+
} as never,
|
|
982
|
+
})
|
|
983
|
+
// …then a synthetic 429 result arrives with the seq queue empty and
|
|
984
|
+
// no active turn — the gate misses. Pre-fix this was silently dropped.
|
|
985
|
+
events.push({
|
|
986
|
+
type: "transcript",
|
|
987
|
+
entry: {
|
|
988
|
+
_id: "result-429",
|
|
989
|
+
createdAt: Date.now(),
|
|
990
|
+
kind: "result",
|
|
991
|
+
subtype: "error",
|
|
992
|
+
isError: true,
|
|
993
|
+
durationMs: 0,
|
|
994
|
+
result: "You've hit your limit · resets 4:10am (Asia/Saigon)",
|
|
995
|
+
} as never,
|
|
996
|
+
})
|
|
997
|
+
events.close()
|
|
998
|
+
},
|
|
999
|
+
}),
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
await coordinator.send({
|
|
1003
|
+
type: "chat.send",
|
|
1004
|
+
chatId: "chat-1",
|
|
1005
|
+
provider: "claude",
|
|
1006
|
+
content: "test",
|
|
1007
|
+
model: "claude-opus-4-7",
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
await waitFor(
|
|
1011
|
+
() =>
|
|
1012
|
+
store
|
|
1013
|
+
.getAutoContinueEvents("chat-1")
|
|
1014
|
+
.some(
|
|
1015
|
+
(e) => e.kind === "auto_continue_proposed" || e.kind === "auto_continue_accepted",
|
|
1016
|
+
),
|
|
1017
|
+
4000,
|
|
1018
|
+
"unmatched rate-limit result schedules a resume instead of silent death",
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
const schedule = store
|
|
1022
|
+
.getAutoContinueEvents("chat-1")
|
|
1023
|
+
.find(
|
|
1024
|
+
(e) => e.kind === "auto_continue_proposed" || e.kind === "auto_continue_accepted",
|
|
1025
|
+
)
|
|
1026
|
+
expect(schedule).toBeDefined()
|
|
1027
|
+
// Single-token pool → no rotation; auto-resume off → a proposal (respects setting).
|
|
1028
|
+
expect(schedule!.kind).toBe("auto_continue_proposed")
|
|
1029
|
+
},
|
|
1030
|
+
10_000,
|
|
1031
|
+
)
|
|
931
1032
|
})
|
package/src/server/agent.test.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type { ChatPermissionPolicy } from "../shared/permission-policy"
|
|
|
23
23
|
import { POLICY_DEFAULT } from "../shared/permission-policy"
|
|
24
24
|
import type { HarnessTurn } from "./harness-types"
|
|
25
25
|
import type { ChatAttachment, McpServerConfig, SlashCommand, TranscriptEntry } from "../shared/types"
|
|
26
|
+
import { AUTO_CONTINUE_EVENT_VERSION } from "./auto-continue/events"
|
|
26
27
|
import type { AutoContinueEvent } from "./auto-continue/events"
|
|
27
28
|
import type { WorkflowRegistry } from "./workflow-registry"
|
|
28
29
|
import { AsyncEventQueue } from "./test-helpers/async-event-queue"
|
|
@@ -1213,13 +1214,19 @@ describe("AgentCoordinator codex integration", () => {
|
|
|
1213
1214
|
content: "run something with a background task",
|
|
1214
1215
|
})
|
|
1215
1216
|
|
|
1216
|
-
// Wait for the
|
|
1217
|
-
|
|
1217
|
+
// Wait for the active turn to be removed — this happens synchronously
|
|
1218
|
+
// right after recordTurnFinished resolves, which is itself after
|
|
1219
|
+
// appendMessage(result). Using this as the waitFor condition avoids a
|
|
1220
|
+
// microtask-gap race: waiting for messages.some("result") fires one await
|
|
1221
|
+
// before activeTurns.delete, so the assertion would see the turn still set.
|
|
1222
|
+
await waitFor(() => !coordinator.getActiveStatuses().has("chat-1"))
|
|
1218
1223
|
|
|
1219
1224
|
// The active turn should be removed even though the stream is still open.
|
|
1220
1225
|
// This is the key assertion: the UI should show idle (not "Running...")
|
|
1221
1226
|
// so the user can send new messages without hitting stop.
|
|
1222
1227
|
expect(coordinator.getActiveStatuses().has("chat-1")).toBe(false)
|
|
1228
|
+
// Result must have been appended (that's what triggers the removal).
|
|
1229
|
+
expect(store.messages.some((entry) => entry.kind === "result")).toBe(true)
|
|
1223
1230
|
expect(store.turnFinishedCount).toBe(1)
|
|
1224
1231
|
|
|
1225
1232
|
// The stream is still open, so it should be draining
|
|
@@ -3600,7 +3607,7 @@ describe("AgentCoordinator claude integration", () => {
|
|
|
3600
3607
|
|
|
3601
3608
|
// Pin the armed state to the test toggle — the send() takeover path would
|
|
3602
3609
|
// otherwise disarm before the spawn check ever sees an armed loop.
|
|
3603
|
-
coordinator.isLoopArmed = () => (armed ? { subagentId: "sa-1", prompt: "loop prompt" } : null)
|
|
3610
|
+
coordinator.isLoopArmed = () => (armed ? { subagentId: "sa-1", prompt: "loop prompt", armedAt: 0 } : null)
|
|
3604
3611
|
|
|
3605
3612
|
const sendTurn = async (content: string, expectedFinished: number) => {
|
|
3606
3613
|
await coordinator.send({
|
|
@@ -3681,7 +3688,7 @@ describe("AgentCoordinator claude integration", () => {
|
|
|
3681
3688
|
},
|
|
3682
3689
|
})
|
|
3683
3690
|
|
|
3684
|
-
coordinator.isLoopArmed = () => (armed ? { subagentId: "sa-1", prompt: "loop prompt" } : null)
|
|
3691
|
+
coordinator.isLoopArmed = () => (armed ? { subagentId: "sa-1", prompt: "loop prompt", armedAt: 0 } : null)
|
|
3685
3692
|
|
|
3686
3693
|
const sendTurn = async (content: string, expectedFinished: number) => {
|
|
3687
3694
|
await coordinator.send({
|
|
@@ -4369,12 +4376,21 @@ describe("AgentCoordinator.deliverSubagentToMain (notification-driven /clear)",
|
|
|
4369
4376
|
|
|
4370
4377
|
test("armed loop: notification omits <result> (PROGRESS.md is the contract) and the full loop prompt follows", async () => {
|
|
4371
4378
|
const store = createFakeStore()
|
|
4379
|
+
// Pre-arm the loop via store events so isLoopArmed() returns the armed state.
|
|
4380
|
+
store.autoContinueEvents.push({
|
|
4381
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
4382
|
+
timestamp: 0,
|
|
4383
|
+
chatId: "chat-1",
|
|
4384
|
+
scheduleId: "loop-schedule-1",
|
|
4385
|
+
kind: "loop_armed",
|
|
4386
|
+
subagentId: "sa-loop",
|
|
4387
|
+
prompt: "LOOP DISCIPLINE PROMPT",
|
|
4388
|
+
})
|
|
4372
4389
|
const coordinator = new AgentCoordinator({
|
|
4373
4390
|
store: store as never,
|
|
4374
4391
|
onStateChange: () => {},
|
|
4375
4392
|
startClaudeSession: async () => { throw new Error("not needed") },
|
|
4376
4393
|
})
|
|
4377
|
-
coordinator.isLoopArmed = () => ({ subagentId: "sa-loop", prompt: "LOOP DISCIPLINE PROMPT" })
|
|
4378
4394
|
|
|
4379
4395
|
await (coordinator as unknown as { deliverSubagentToMain: DeliverFn }).deliverSubagentToMain(
|
|
4380
4396
|
"chat-1",
|
|
@@ -4382,9 +4398,9 @@ describe("AgentCoordinator.deliverSubagentToMain (notification-driven /clear)",
|
|
|
4382
4398
|
{ status: "completed", runId: "run-loop", text: "iteration output that must not leak" },
|
|
4383
4399
|
)
|
|
4384
4400
|
|
|
4385
|
-
const
|
|
4386
|
-
expect(
|
|
4387
|
-
const ev =
|
|
4401
|
+
const acEvents = store.getAutoContinueEvents("chat-1").filter((e) => e.kind === "auto_continue_accepted")
|
|
4402
|
+
expect(acEvents).toHaveLength(1)
|
|
4403
|
+
const ev = acEvents[0]
|
|
4388
4404
|
expect(ev.kind).toBe("auto_continue_accepted")
|
|
4389
4405
|
if (ev.kind === "auto_continue_accepted") {
|
|
4390
4406
|
expect(ev.prompt).toContain("<task-notification>")
|