@cuongtran001/kanna 1.3.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-CAysfmX5.js → arc-DXtN3EWJ.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
- package/dist/client/assets/channel-BEYTrUAm.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.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-YgpMurXf.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
- package/dist/client/assets/{index-CbSY9HfS.js → index-BqUY1Bm4.js} +1 -1
- package/dist/client/assets/{index-C0U2kwuB.js → index-TU1k6ov6.js} +5 -5
- package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
- package/dist/client/assets/{linear-DLR6miy3.js → linear-ByKHtsIx.js} +1 -1
- package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-Dl_f2KLc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.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-ByWRdpQX.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
- package/dist/client/index.html +1 -1
- 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.test.ts +22 -6
- package/src/server/agent.ts +35 -4987
- 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 -2285
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -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/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 +25 -1711
- package/dist/client/assets/channel-KQhRwDzJ.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-ChxT1GNq.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ChxT1GNq.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D4X-ZnLV.js +0 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colocated unit tests for claude-subagent-wiring.ts.
|
|
3
|
+
*
|
|
4
|
+
* Tests cover the two extracted functions without a real AgentCoordinator:
|
|
5
|
+
* - buildClaudeSubagentStarter — PTY vs SDK dispatch logic
|
|
6
|
+
* - buildSubagentProviderRunForChat — ProviderRunStart construction
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test"
|
|
10
|
+
import {
|
|
11
|
+
buildClaudeSubagentStarter,
|
|
12
|
+
buildSubagentProviderRunForChat,
|
|
13
|
+
type SubagentWiringDeps,
|
|
14
|
+
type BuildSubagentProviderRunForChatArgs,
|
|
15
|
+
} from "./claude-subagent-wiring"
|
|
16
|
+
import type { ProviderRunStart } from "./subagent-orchestrator"
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Minimal stubs — only what the functions call
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const NOOP_PROMISE = () => Promise.resolve(null as unknown as never)
|
|
23
|
+
|
|
24
|
+
function makeDeps(overrides: Partial<SubagentWiringDeps> = {}): SubagentWiringDeps {
|
|
25
|
+
const pendingResolvers = new Map<
|
|
26
|
+
string,
|
|
27
|
+
{ resolve: (v: unknown) => void; reject: (e: Error) => void }
|
|
28
|
+
>()
|
|
29
|
+
|
|
30
|
+
const fakeSession = { interrupt: () => {}, getAccountInfo: async () => null, close: () => {}, getSupportedCommands: async () => [], setModel: async () => {}, setPermissionMode: async () => {}, sendPrompt: NOOP_PROMISE, pushChannelPrompt: NOOP_PROMISE }
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
store: {
|
|
34
|
+
requireChat: (chatId) => ({
|
|
35
|
+
id: chatId,
|
|
36
|
+
projectId: "proj-1",
|
|
37
|
+
provider: "claude",
|
|
38
|
+
model: "claude-opus-4-5",
|
|
39
|
+
effort: undefined,
|
|
40
|
+
serviceTier: undefined,
|
|
41
|
+
planMode: false,
|
|
42
|
+
title: "Test chat",
|
|
43
|
+
createdAt: 0,
|
|
44
|
+
updatedAt: 0,
|
|
45
|
+
tokenCount: 0,
|
|
46
|
+
compactFailureCount: 0,
|
|
47
|
+
policyOverride: undefined,
|
|
48
|
+
stackProjectIds: [],
|
|
49
|
+
} as never),
|
|
50
|
+
getProject: (_id) => ({
|
|
51
|
+
id: "proj-1",
|
|
52
|
+
localPath: "/tmp/test-project",
|
|
53
|
+
title: "Test Project",
|
|
54
|
+
createdAt: 0,
|
|
55
|
+
updatedAt: 0,
|
|
56
|
+
} as never),
|
|
57
|
+
appendSubagentEvent: async () => {},
|
|
58
|
+
},
|
|
59
|
+
startClaudeSessionFn: async (_a) => fakeSession as never,
|
|
60
|
+
startClaudeSessionPTYFn: async (_a) => fakeSession as never,
|
|
61
|
+
toolCallback: null,
|
|
62
|
+
tunnelGateway: null,
|
|
63
|
+
claudePtyRegistry: null,
|
|
64
|
+
ptyInstanceRegistry: null,
|
|
65
|
+
workflowRegistry: null,
|
|
66
|
+
subagentOrchestrator: {
|
|
67
|
+
notifySubagentToolPending: () => {},
|
|
68
|
+
} as never,
|
|
69
|
+
codexManager: {} as never,
|
|
70
|
+
oauthPool: null,
|
|
71
|
+
subagentPendingResolvers: pendingResolvers as never,
|
|
72
|
+
realpath: (p) => p,
|
|
73
|
+
resolveClaudeDriverPreference: () => "sdk",
|
|
74
|
+
getEnabledCustomMcpServers: () => [],
|
|
75
|
+
buildOAuthBearers: async () => new Map(),
|
|
76
|
+
resolveChatPolicy: () => ({ mode: "acceptEdits" } as never),
|
|
77
|
+
emitStateChange: () => {},
|
|
78
|
+
buildPoolUnavailableMessage: () => "no token available",
|
|
79
|
+
getAppSettingsSnapshot: () => ({}),
|
|
80
|
+
readLlmProvider: async () => ({ provider: "claude", apiKey: null } as never),
|
|
81
|
+
subagentPendingKey: (chatId, runId, toolUseId) => `${chatId}::${runId}::${toolUseId}`,
|
|
82
|
+
...overrides,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const BASE_SUBAGENT = {
|
|
87
|
+
id: "subagent-1",
|
|
88
|
+
name: "Test Agent",
|
|
89
|
+
description: "A test agent",
|
|
90
|
+
provider: "claude" as const,
|
|
91
|
+
model: "claude-opus-4-5",
|
|
92
|
+
modelOptions: { reasoningEffort: "low" as const, contextWindow: "200k" as const },
|
|
93
|
+
systemPrompt: "You are a test agent",
|
|
94
|
+
contextScope: "full-transcript" as const,
|
|
95
|
+
triggerMode: "manual" as const,
|
|
96
|
+
workingDir: undefined,
|
|
97
|
+
allowedPaths: undefined,
|
|
98
|
+
maxTurns: undefined,
|
|
99
|
+
updatedAt: 0,
|
|
100
|
+
createdAt: 0,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const BASE_ARGS: BuildSubagentProviderRunForChatArgs = {
|
|
104
|
+
subagent: BASE_SUBAGENT,
|
|
105
|
+
chatId: "chat-1",
|
|
106
|
+
primer: null,
|
|
107
|
+
userInstruction: "do something",
|
|
108
|
+
runId: "run-1",
|
|
109
|
+
abortSignal: new AbortController().signal,
|
|
110
|
+
depth: 0,
|
|
111
|
+
ancestorSubagentIds: [],
|
|
112
|
+
parentUserMessageId: "msg-1",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// buildClaudeSubagentStarter
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
describe("buildClaudeSubagentStarter", () => {
|
|
120
|
+
test("returns a function", () => {
|
|
121
|
+
const starter = buildClaudeSubagentStarter(makeDeps())
|
|
122
|
+
expect(typeof starter).toBe("function")
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test("SDK preference — calls startClaudeSessionFn with merged mcpServers", async () => {
|
|
126
|
+
let capturedArgs: unknown = null
|
|
127
|
+
const deps = makeDeps({
|
|
128
|
+
resolveClaudeDriverPreference: () => "sdk",
|
|
129
|
+
startClaudeSessionFn: async (a) => {
|
|
130
|
+
capturedArgs = a
|
|
131
|
+
return {} as never
|
|
132
|
+
},
|
|
133
|
+
getEnabledCustomMcpServers: () => [{ id: "s1", name: "server1", transport: "stdio", command: "npx", args: [], env: {}, disabled: false }] as never,
|
|
134
|
+
buildOAuthBearers: async () => new Map([["s1", "bearer-token"]]),
|
|
135
|
+
})
|
|
136
|
+
const starter = buildClaudeSubagentStarter(deps)
|
|
137
|
+
await starter({
|
|
138
|
+
projectId: "proj",
|
|
139
|
+
localPath: "/tmp/x",
|
|
140
|
+
model: "claude-opus-4-5",
|
|
141
|
+
effort: undefined,
|
|
142
|
+
planMode: false,
|
|
143
|
+
sessionToken: null,
|
|
144
|
+
forkSession: false,
|
|
145
|
+
oauthToken: null,
|
|
146
|
+
onToolRequest: async () => null,
|
|
147
|
+
} as never)
|
|
148
|
+
|
|
149
|
+
expect(capturedArgs).not.toBeNull()
|
|
150
|
+
const args = capturedArgs as Record<string, unknown>
|
|
151
|
+
expect(Array.isArray(args.customMcpServers)).toBe(true)
|
|
152
|
+
expect((args.customMcpServers as unknown[]).length).toBe(1)
|
|
153
|
+
expect(args.oauthBearers).toBeInstanceOf(Map)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test("PTY preference — calls startClaudeSessionPTYFn with oneShot: true", async () => {
|
|
157
|
+
let ptyCalled = false
|
|
158
|
+
let capturedPtyArgs: unknown = null
|
|
159
|
+
const deps = makeDeps({
|
|
160
|
+
resolveClaudeDriverPreference: () => "pty",
|
|
161
|
+
startClaudeSessionPTYFn: async (a) => {
|
|
162
|
+
ptyCalled = true
|
|
163
|
+
capturedPtyArgs = a
|
|
164
|
+
return {} as never
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
const starter = buildClaudeSubagentStarter(deps)
|
|
168
|
+
await starter({
|
|
169
|
+
chatId: "chat-x",
|
|
170
|
+
projectId: "proj",
|
|
171
|
+
localPath: "/tmp/x",
|
|
172
|
+
model: "claude-opus-4-5",
|
|
173
|
+
effort: undefined,
|
|
174
|
+
planMode: false,
|
|
175
|
+
sessionToken: null,
|
|
176
|
+
forkSession: false,
|
|
177
|
+
oauthToken: null,
|
|
178
|
+
onToolRequest: async () => null,
|
|
179
|
+
} as never)
|
|
180
|
+
|
|
181
|
+
expect(ptyCalled).toBe(true)
|
|
182
|
+
const ptyArgs = capturedPtyArgs as Record<string, unknown>
|
|
183
|
+
expect(ptyArgs.oneShot).toBe(true)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// buildSubagentProviderRunForChat
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
describe("buildSubagentProviderRunForChat", () => {
|
|
192
|
+
test("returns a ProviderRunStart with a start function", () => {
|
|
193
|
+
const result: ProviderRunStart = buildSubagentProviderRunForChat(makeDeps(), BASE_ARGS)
|
|
194
|
+
expect(typeof result.start).toBe("function")
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test("throws when project is not found", () => {
|
|
198
|
+
const deps = makeDeps({
|
|
199
|
+
store: {
|
|
200
|
+
requireChat: (_chatId) => ({ id: _chatId, projectId: "proj-missing" } as never),
|
|
201
|
+
getProject: (_id) => undefined,
|
|
202
|
+
appendSubagentEvent: async () => {},
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
expect(() => buildSubagentProviderRunForChat(deps, BASE_ARGS)).toThrow(
|
|
206
|
+
"Project proj-missing not found for chat chat-1",
|
|
207
|
+
)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test("cwdOverride suppresses restriction and additionalDirectories", () => {
|
|
211
|
+
const capturedStartArgs: unknown[] = []
|
|
212
|
+
const deps = makeDeps({
|
|
213
|
+
startClaudeSessionFn: async (a) => {
|
|
214
|
+
capturedStartArgs.push(a)
|
|
215
|
+
return {} as never
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
const args: BuildSubagentProviderRunForChatArgs = {
|
|
219
|
+
...BASE_ARGS,
|
|
220
|
+
cwdOverride: "/tmp/worktree",
|
|
221
|
+
}
|
|
222
|
+
const result = buildSubagentProviderRunForChat(deps, args)
|
|
223
|
+
// The ProviderRunStart is constructed — just verify it's valid
|
|
224
|
+
expect(typeof result.start).toBe("function")
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test("delegation context increments depth and chains ancestorSubagentIds", () => {
|
|
228
|
+
// We verify the ProviderRunStart is created correctly with depth 0+1=1
|
|
229
|
+
// by checking the function doesn't throw with proper args
|
|
230
|
+
const args: BuildSubagentProviderRunForChatArgs = {
|
|
231
|
+
...BASE_ARGS,
|
|
232
|
+
depth: 0,
|
|
233
|
+
ancestorSubagentIds: [],
|
|
234
|
+
}
|
|
235
|
+
const result = buildSubagentProviderRunForChat(makeDeps(), args)
|
|
236
|
+
expect(typeof result.start).toBe("function")
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test("subagentPendingKey is called when onToolRequest receives ask_user_question", async () => {
|
|
240
|
+
let keyBuilt: string | null = null
|
|
241
|
+
const deps = makeDeps({
|
|
242
|
+
subagentPendingKey: (chatId, runId, toolUseId) => {
|
|
243
|
+
keyBuilt = `${chatId}::${runId}::${toolUseId}`
|
|
244
|
+
return keyBuilt
|
|
245
|
+
},
|
|
246
|
+
store: {
|
|
247
|
+
requireChat: (_chatId) =>
|
|
248
|
+
({ id: _chatId, projectId: "proj-1", stackProjectIds: [] } as never),
|
|
249
|
+
getProject: (_id) =>
|
|
250
|
+
({ id: "proj-1", localPath: "/tmp/test-project", title: "Test" } as never),
|
|
251
|
+
appendSubagentEvent: async () => {},
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// We can't easily invoke onToolRequest without running start(), but we can
|
|
256
|
+
// confirm the ProviderRunStart builds without error, which exercises the
|
|
257
|
+
// closure that captures subagentPendingKey.
|
|
258
|
+
const result = buildSubagentProviderRunForChat(deps, BASE_ARGS)
|
|
259
|
+
expect(typeof result.start).toBe("function")
|
|
260
|
+
// The key builder will be called lazily when onToolRequest fires during a run.
|
|
261
|
+
expect(keyBuilt).toBeNull() // not yet called
|
|
262
|
+
})
|
|
263
|
+
})
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent provider-run wiring — standalone extraction from AgentCoordinator.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - buildClaudeSubagentStarter — builds the startClaudeSession callback for
|
|
6
|
+
* subagent runs: PTY/SDK dispatch, OAuth, MCP config, pool wiring.
|
|
7
|
+
* - buildSubagentProviderRunForChat — constructs the full ProviderRunStart
|
|
8
|
+
* bundle for a subagent run bound to a specific chat.
|
|
9
|
+
*
|
|
10
|
+
* Side-effect seal: this module contains NO direct IO (no node:fs, no HTTP
|
|
11
|
+
* calls, no Bun primitives). Every effectful operation is injected through
|
|
12
|
+
* SubagentWiringDeps so the module remains testable without real drivers.
|
|
13
|
+
* The `realpath` function is injected as a dep (RealpathFn) rather than
|
|
14
|
+
* imported from `paths-fs.adapter` directly.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { AnyValue } from "../shared/errors"
|
|
18
|
+
import type {
|
|
19
|
+
ClaudeDriverPreference,
|
|
20
|
+
LlmProviderSnapshot,
|
|
21
|
+
McpServerConfig,
|
|
22
|
+
Subagent,
|
|
23
|
+
} from "../shared/types"
|
|
24
|
+
import type { HarnessToolRequest } from "./harness-types"
|
|
25
|
+
import type { ClaudeSessionHandle } from "./harness-types"
|
|
26
|
+
import type { KannaMcpDelegationContext } from "./kanna-mcp"
|
|
27
|
+
import type { ChatRecord, ProjectRecord, SubagentRunEvent } from "./events"
|
|
28
|
+
import type { ProviderRunStart, SubagentOrchestrator } from "./subagent-orchestrator"
|
|
29
|
+
import type { BuildSubagentProviderRunArgs } from "./subagent-provider-run"
|
|
30
|
+
import { buildSubagentProviderRun } from "./subagent-provider-run"
|
|
31
|
+
import type { StartClaudeSessionPtyArgs } from "./claude-pty/driver"
|
|
32
|
+
import type { ChatPermissionPolicy } from "../shared/permission-policy"
|
|
33
|
+
import type { ToolCallbackService } from "./tool-callback"
|
|
34
|
+
import type { TunnelGateway } from "./cloudflare-tunnel/gateway"
|
|
35
|
+
import type { ClaudePtyRegistry } from "./claude-pty/pid-registry.adapter"
|
|
36
|
+
import type { PtyInstanceRegistry } from "./claude-pty/pty-instance-registry"
|
|
37
|
+
import type { WorkflowRegistry } from "./workflow-registry"
|
|
38
|
+
import type { CodexAppServerManager } from "./codex-app-server"
|
|
39
|
+
import type { RealpathFn } from "./paths"
|
|
40
|
+
import { resolveSubagentRoots } from "./paths"
|
|
41
|
+
import { resolveSpawnPaths, resolveStackProjects } from "./claude-session-config"
|
|
42
|
+
import { openrouterAuthReady } from "./provider-catalog"
|
|
43
|
+
import { OAuthPoolUnavailableError } from "./oauth-errors"
|
|
44
|
+
// Type-only import — no IO is pulled in from the SDK session start module.
|
|
45
|
+
import type { startClaudeSession as StartClaudeSessionFn } from "./claude-session-start"
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Structural sub-interfaces — only the operations this module calls.
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/** Subset of EventStore used by subagent wiring. */
|
|
52
|
+
interface SubagentWiringStore {
|
|
53
|
+
requireChat(chatId: string): ChatRecord
|
|
54
|
+
getProject(id: string): ProjectRecord | null | undefined
|
|
55
|
+
appendSubagentEvent(event: SubagentRunEvent): Promise<void>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Subset of OAuthTokenPool used by subagent wiring. */
|
|
59
|
+
interface SubagentWiringOAuthPool {
|
|
60
|
+
hasUsable(reservedFor?: string): boolean
|
|
61
|
+
pickActive(chatId: string): { id: string; token: string; label: string } | null | undefined
|
|
62
|
+
markUsed(tokenId: string): void
|
|
63
|
+
hasAnyToken(): boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Dependency bundle
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* All AgentCoordinator fields and methods accessed by the subagent wiring
|
|
72
|
+
* functions. Passed as a single deps object so both functions stay testable
|
|
73
|
+
* without a real coordinator.
|
|
74
|
+
*/
|
|
75
|
+
export interface SubagentWiringDeps {
|
|
76
|
+
// Store (structural subset — no concrete EventStore import)
|
|
77
|
+
store: SubagentWiringStore
|
|
78
|
+
|
|
79
|
+
// Session-start function references (injected so tests can stub them)
|
|
80
|
+
startClaudeSessionFn: typeof StartClaudeSessionFn
|
|
81
|
+
startClaudeSessionPTYFn: (args: StartClaudeSessionPtyArgs) => Promise<ClaudeSessionHandle>
|
|
82
|
+
|
|
83
|
+
// Opaque state passed through to the start functions
|
|
84
|
+
toolCallback: ToolCallbackService | null
|
|
85
|
+
tunnelGateway: TunnelGateway | null
|
|
86
|
+
claudePtyRegistry: ClaudePtyRegistry | null
|
|
87
|
+
ptyInstanceRegistry: PtyInstanceRegistry | null
|
|
88
|
+
workflowRegistry: WorkflowRegistry | null
|
|
89
|
+
subagentOrchestrator: SubagentOrchestrator
|
|
90
|
+
codexManager: CodexAppServerManager
|
|
91
|
+
oauthPool: SubagentWiringOAuthPool | null
|
|
92
|
+
|
|
93
|
+
// Per-run pending resolver registry (shared with the coordinator's cancel paths)
|
|
94
|
+
subagentPendingResolvers: Map<string, { resolve: (v: AnyValue) => void; reject: (e: Error) => void }>
|
|
95
|
+
|
|
96
|
+
// IO injection — realpath wraps realpathSync but is IO; inject via deps
|
|
97
|
+
realpath: RealpathFn
|
|
98
|
+
|
|
99
|
+
// Method references for private AgentCoordinator helpers
|
|
100
|
+
resolveClaudeDriverPreference: () => ClaudeDriverPreference
|
|
101
|
+
getEnabledCustomMcpServers: () => readonly McpServerConfig[]
|
|
102
|
+
buildOAuthBearers: (servers: readonly McpServerConfig[]) => Promise<Map<string, string>>
|
|
103
|
+
resolveChatPolicy: (chatId: string) => ChatPermissionPolicy
|
|
104
|
+
emitStateChange: (chatId: string) => void
|
|
105
|
+
buildPoolUnavailableMessage: (reservedFor: string, scopeSuffix: string) => string
|
|
106
|
+
getAppSettingsSnapshot: () => {
|
|
107
|
+
globalPromptAppend?: string
|
|
108
|
+
claudeAuth?: { authenticated?: boolean } | null
|
|
109
|
+
}
|
|
110
|
+
readLlmProvider: () => Promise<LlmProviderSnapshot>
|
|
111
|
+
subagentPendingKey: (chatId: string, runId: string, toolUseId: string) => string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Exported args type for buildSubagentProviderRunForChat
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
export interface BuildSubagentProviderRunForChatArgs {
|
|
119
|
+
subagent: Subagent
|
|
120
|
+
chatId: string
|
|
121
|
+
primer: string | null
|
|
122
|
+
userInstruction: string | null
|
|
123
|
+
runId: string
|
|
124
|
+
abortSignal: AbortSignal
|
|
125
|
+
depth: number
|
|
126
|
+
ancestorSubagentIds: string[]
|
|
127
|
+
parentUserMessageId: string
|
|
128
|
+
/**
|
|
129
|
+
* Orchestration workers run in an isolated git worktree, not the chat cwd.
|
|
130
|
+
* When set, this overrides the resolved cwd, disables workingDir/allowedPaths
|
|
131
|
+
* restriction, and drops additional dirs / stack labelling (the worktree is
|
|
132
|
+
* a self-contained checkout).
|
|
133
|
+
*/
|
|
134
|
+
cwdOverride?: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Core functions
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* D6 — subagent Claude starter. When `KANNA_CLAUDE_DRIVER=pty` the subagent
|
|
143
|
+
* turn runs through the PTY driver (subscription billing) instead of always
|
|
144
|
+
* falling back to the SDK (API billing). Adapts the SDK-shaped
|
|
145
|
+
* `startClaudeSession` arg to `StartClaudeSessionPtyArgs`, injecting the
|
|
146
|
+
* coordinator-owned preflight / toolCallback / tunnel / policy context and
|
|
147
|
+
* `oneShot: true` so the REPL closes after the single subagent turn.
|
|
148
|
+
*
|
|
149
|
+
* Extracted from AgentCoordinator.buildClaudeSubagentStarter.
|
|
150
|
+
*/
|
|
151
|
+
export function buildClaudeSubagentStarter(
|
|
152
|
+
deps: SubagentWiringDeps,
|
|
153
|
+
): NonNullable<BuildSubagentProviderRunArgs["startClaudeSession"]> {
|
|
154
|
+
return async (a) => {
|
|
155
|
+
const enabledMcpServers = deps.getEnabledCustomMcpServers()
|
|
156
|
+
const oauthBearers = await deps.buildOAuthBearers(enabledMcpServers)
|
|
157
|
+
if (deps.resolveClaudeDriverPreference() === "pty") {
|
|
158
|
+
return deps.startClaudeSessionPTYFn({
|
|
159
|
+
chatId: a.chatId ?? "",
|
|
160
|
+
projectId: a.projectId,
|
|
161
|
+
localPath: a.localPath,
|
|
162
|
+
model: a.model,
|
|
163
|
+
effort: a.effort,
|
|
164
|
+
planMode: a.planMode,
|
|
165
|
+
sessionToken: a.sessionToken,
|
|
166
|
+
forkSession: a.forkSession,
|
|
167
|
+
oauthToken: a.oauthToken,
|
|
168
|
+
additionalDirectories: a.additionalDirectories,
|
|
169
|
+
onToolRequest: a.onToolRequest,
|
|
170
|
+
systemPromptOverride: a.systemPromptOverride,
|
|
171
|
+
initialPrompt: a.initialPrompt,
|
|
172
|
+
subagentOrchestrator: a.subagentOrchestrator,
|
|
173
|
+
delegationContext: a.delegationContext,
|
|
174
|
+
toolCallback: deps.toolCallback ?? undefined,
|
|
175
|
+
tunnelGateway: deps.tunnelGateway,
|
|
176
|
+
chatPolicy: a.chatId ? deps.resolveChatPolicy(a.chatId) : undefined,
|
|
177
|
+
oneShot: true,
|
|
178
|
+
ptyRegistry: deps.claudePtyRegistry ?? undefined,
|
|
179
|
+
ptyInstanceRegistry: deps.ptyInstanceRegistry ?? undefined,
|
|
180
|
+
workflowRegistry: deps.workflowRegistry ?? undefined,
|
|
181
|
+
customMcpServers: enabledMcpServers,
|
|
182
|
+
oauthBearers,
|
|
183
|
+
restrictedAllowedPaths: a.restrictedAllowedPaths,
|
|
184
|
+
keepAlive: a.keepAlive,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
return deps.startClaudeSessionFn({ ...a, customMcpServers: enabledMcpServers, oauthBearers })
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Constructs the full ProviderRunStart bundle for a subagent run bound to a
|
|
193
|
+
* specific chat. Resolves the cwd, builds the delegation context, wires up the
|
|
194
|
+
* interactive tool-request forwarder, and delegates to `buildSubagentProviderRun`.
|
|
195
|
+
*
|
|
196
|
+
* Extracted from AgentCoordinator.buildSubagentProviderRunForChat.
|
|
197
|
+
*/
|
|
198
|
+
export function buildSubagentProviderRunForChat(
|
|
199
|
+
deps: SubagentWiringDeps,
|
|
200
|
+
args: BuildSubagentProviderRunForChatArgs,
|
|
201
|
+
): ProviderRunStart {
|
|
202
|
+
const chat = deps.store.requireChat(args.chatId)
|
|
203
|
+
const project = deps.store.getProject(chat.projectId)
|
|
204
|
+
if (!project) throw new Error(`Project ${chat.projectId} not found for chat ${args.chatId}`)
|
|
205
|
+
const spawn = resolveSpawnPaths(chat, project.localPath)
|
|
206
|
+
const restriction =
|
|
207
|
+
args.cwdOverride === undefined &&
|
|
208
|
+
(args.subagent.workingDir !== undefined || args.subagent.allowedPaths !== undefined)
|
|
209
|
+
? resolveSubagentRoots(
|
|
210
|
+
spawn.cwd,
|
|
211
|
+
args.subagent.workingDir,
|
|
212
|
+
args.subagent.allowedPaths,
|
|
213
|
+
deps.realpath,
|
|
214
|
+
)
|
|
215
|
+
: null
|
|
216
|
+
|
|
217
|
+
const onToolRequest = async (request: HarnessToolRequest): Promise<AnyValue> => {
|
|
218
|
+
if (
|
|
219
|
+
request.tool.toolKind !== "ask_user_question" &&
|
|
220
|
+
request.tool.toolKind !== "exit_plan_mode"
|
|
221
|
+
) {
|
|
222
|
+
// Non-interactive tools (bash, read, write, ...) — SDK handles
|
|
223
|
+
// them via canUseTool wrapper. No forwarding needed.
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
const toolUseId = request.tool.toolId
|
|
227
|
+
const key = deps.subagentPendingKey(args.chatId, args.runId, toolUseId)
|
|
228
|
+
await deps.store.appendSubagentEvent({
|
|
229
|
+
v: 3,
|
|
230
|
+
type: "subagent_tool_pending",
|
|
231
|
+
timestamp: Date.now(),
|
|
232
|
+
chatId: args.chatId,
|
|
233
|
+
runId: args.runId,
|
|
234
|
+
toolUseId,
|
|
235
|
+
toolKind: request.tool.toolKind,
|
|
236
|
+
input: request.tool.input,
|
|
237
|
+
})
|
|
238
|
+
deps.emitStateChange(args.chatId)
|
|
239
|
+
deps.subagentOrchestrator.notifySubagentToolPending(args.runId)
|
|
240
|
+
return await new Promise<AnyValue>((resolve, reject) => {
|
|
241
|
+
// Defensive: if `canUseTool` somehow fires twice for the same
|
|
242
|
+
// (chatId, runId, toolUseId) — e.g. SDK retry — reject the previous
|
|
243
|
+
// resolver before overwriting so its Promise doesn't leak.
|
|
244
|
+
const existing = deps.subagentPendingResolvers.get(key)
|
|
245
|
+
if (existing) {
|
|
246
|
+
existing.reject(new Error("superseded by retry"))
|
|
247
|
+
}
|
|
248
|
+
deps.subagentPendingResolvers.set(key, { resolve, reject })
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const delegationContext: KannaMcpDelegationContext = {
|
|
253
|
+
parentSubagentId: args.subagent.id,
|
|
254
|
+
parentRunId: args.runId,
|
|
255
|
+
ancestorSubagentIds: [...args.ancestorSubagentIds, args.subagent.id],
|
|
256
|
+
depth: args.depth + 1,
|
|
257
|
+
// For sub-spawn-sub, the parent_user_message_id stays anchored to the
|
|
258
|
+
// chat turn that started the whole chain — that's the attribution the
|
|
259
|
+
// run_started events use, and the orchestrator's depth/cycle checks
|
|
260
|
+
// protect against runaway chains.
|
|
261
|
+
getParentUserMessageId: () => args.parentUserMessageId,
|
|
262
|
+
// Subagents cannot inherit the user's @-mention authority: manual-trigger
|
|
263
|
+
// gates are enforced only at the top-level turn where the user typed the mention.
|
|
264
|
+
getMentionedSubagentIds: () => [],
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return buildSubagentProviderRun({
|
|
268
|
+
subagent: args.subagent,
|
|
269
|
+
chatId: args.chatId,
|
|
270
|
+
primer: args.primer,
|
|
271
|
+
userInstruction: args.userInstruction,
|
|
272
|
+
runId: args.runId,
|
|
273
|
+
abortSignal: args.abortSignal,
|
|
274
|
+
cwd: args.cwdOverride ?? restriction?.cwd ?? spawn.cwd,
|
|
275
|
+
additionalDirectories: args.cwdOverride ? [] : spawn.additionalDirectories,
|
|
276
|
+
// Only label stack projects for unrestricted runs — a path-restricted
|
|
277
|
+
// subagent cannot reach every root, so listing them all would mislead.
|
|
278
|
+
stackProjects:
|
|
279
|
+
args.cwdOverride || restriction
|
|
280
|
+
? []
|
|
281
|
+
: resolveStackProjects(chat, (id) => deps.store.getProject(id)?.title),
|
|
282
|
+
allowedPaths: restriction?.allowedPaths,
|
|
283
|
+
projectId: project.id,
|
|
284
|
+
startClaudeSession: buildClaudeSubagentStarter(deps),
|
|
285
|
+
// PTY claude has no native maxTurns (interactive CLI) — the orchestrator
|
|
286
|
+
// applies a host-side tool-call-count backstop for PTY + Codex runs.
|
|
287
|
+
claudeDriverIsPty: deps.resolveClaudeDriverPreference() === "pty",
|
|
288
|
+
subagentOrchestrator: deps.subagentOrchestrator,
|
|
289
|
+
delegationContext,
|
|
290
|
+
codexManager: deps.codexManager,
|
|
291
|
+
onToolRequest,
|
|
292
|
+
globalPromptAppend: deps.getAppSettingsSnapshot().globalPromptAppend,
|
|
293
|
+
authReady: async (provider) => {
|
|
294
|
+
if (provider === "openrouter") {
|
|
295
|
+
return openrouterAuthReady(await deps.readLlmProvider())
|
|
296
|
+
}
|
|
297
|
+
if (provider === "claude") {
|
|
298
|
+
const settings = deps.getAppSettingsSnapshot()
|
|
299
|
+
// Pass parent chat id so a token already reserved by the parent
|
|
300
|
+
// counts as usable. Subagent runs are sequential under the parent
|
|
301
|
+
// (parent's turn is paused), so sharing the parent's reservation
|
|
302
|
+
// is correct — see oauth-token-pool isEligible.
|
|
303
|
+
return Boolean(
|
|
304
|
+
settings.claudeAuth?.authenticated || deps.oauthPool?.hasUsable(args.chatId),
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
return true
|
|
308
|
+
},
|
|
309
|
+
pickOauthToken: () => {
|
|
310
|
+
// Subagent inherits the parent chat's reservation by re-picking under
|
|
311
|
+
// the same chatId. pickActive treats the parent's reservation as
|
|
312
|
+
// owned-by-self (drops + re-binds to chatId), so the lifecycle stays
|
|
313
|
+
// bound to the parent's close path — no separate subagent release.
|
|
314
|
+
const picked = deps.oauthPool?.pickActive(args.chatId) ?? null
|
|
315
|
+
if (deps.oauthPool && deps.oauthPool.hasAnyToken() && !picked) {
|
|
316
|
+
throw new OAuthPoolUnavailableError(
|
|
317
|
+
deps.buildPoolUnavailableMessage(args.chatId, " for subagent run"),
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
if (picked) deps.oauthPool!.markUsed(picked.id)
|
|
321
|
+
return picked?.token ?? null
|
|
322
|
+
},
|
|
323
|
+
})
|
|
324
|
+
}
|