@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,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the extracted turn spawning pipeline (claude-turn-starter.ts).
|
|
3
|
+
* Covers the key branches of startTurnForChat without touching agent.ts internals.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, test, expect, mock } from "bun:test"
|
|
6
|
+
import { startTurnForChat, type StartTurnDeps, type StartTurnForChatArgs } from "./claude-turn-starter"
|
|
7
|
+
import { OAuthPoolUnavailableError } from "./oauth-errors"
|
|
8
|
+
import type { ActiveTurn, ClaudeSessionState } from "./claude-session-state"
|
|
9
|
+
import type { HarnessTurn } from "./harness-types"
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Helpers
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
function makeFakeTurn(): HarnessTurn {
|
|
16
|
+
return {
|
|
17
|
+
provider: "codex",
|
|
18
|
+
stream: { async *[Symbol.asyncIterator]() {} },
|
|
19
|
+
interrupt: async () => {},
|
|
20
|
+
close: () => {},
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeFakeChatRecord(overrides: Record<string, unknown> = {}) {
|
|
25
|
+
return {
|
|
26
|
+
id: "chat-1",
|
|
27
|
+
projectId: "proj-1",
|
|
28
|
+
provider: "codex" as const,
|
|
29
|
+
title: "New Chat",
|
|
30
|
+
sessionTokensByProvider: {},
|
|
31
|
+
pendingForkSessionToken: null,
|
|
32
|
+
...overrides,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeFakeProjectRecord() {
|
|
37
|
+
return {
|
|
38
|
+
id: "proj-1",
|
|
39
|
+
localPath: "/tmp/project",
|
|
40
|
+
title: "Test Project",
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function makeDeps(overrides: Partial<StartTurnDeps> = {}): StartTurnDeps {
|
|
45
|
+
const activeTurns = new Map<string, ActiveTurn>()
|
|
46
|
+
const claudeSessions = new Map<string, ClaudeSessionState>()
|
|
47
|
+
const drainingStreams = new Map<string, { turn: HarnessTurn }>()
|
|
48
|
+
const mentionedSubagentIdsByChat = new Map<string, string[]>()
|
|
49
|
+
|
|
50
|
+
const chat = makeFakeChatRecord()
|
|
51
|
+
const project = makeFakeProjectRecord()
|
|
52
|
+
|
|
53
|
+
const fakeTurn = makeFakeTurn()
|
|
54
|
+
|
|
55
|
+
const deps: StartTurnDeps = {
|
|
56
|
+
activeTurns,
|
|
57
|
+
claudeSessions,
|
|
58
|
+
drainingStreams,
|
|
59
|
+
mentionedSubagentIdsByChat,
|
|
60
|
+
|
|
61
|
+
store: {
|
|
62
|
+
requireChat: mock(() => chat),
|
|
63
|
+
getMessages: mock(() => []),
|
|
64
|
+
getProject: mock(() => project),
|
|
65
|
+
appendMessage: mock(async () => {}),
|
|
66
|
+
setChatProvider: mock(async () => {}),
|
|
67
|
+
setPlanMode: mock(async () => {}),
|
|
68
|
+
renameChat: mock(async () => {}),
|
|
69
|
+
recordTurnStarted: mock(async () => {}),
|
|
70
|
+
recordTurnFailed: mock(async () => {}),
|
|
71
|
+
setPendingForkSessionToken: mock(async () => {}),
|
|
72
|
+
} as unknown as StartTurnDeps["store"],
|
|
73
|
+
|
|
74
|
+
codexManager: {
|
|
75
|
+
startSession: mock(async () => null),
|
|
76
|
+
startTurn: mock(async () => fakeTurn),
|
|
77
|
+
} as unknown as StartTurnDeps["codexManager"],
|
|
78
|
+
|
|
79
|
+
subagentOrchestrator: {
|
|
80
|
+
clearChatCancellation: mock(() => {}),
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
clearDrainingStream: mock(() => {}),
|
|
84
|
+
emitStateChange: mock(() => {}),
|
|
85
|
+
resolveClaudeDriverPreference: mock(() => "sdk" as const),
|
|
86
|
+
getSubagents: mock(() => []),
|
|
87
|
+
getAppSettingsSnapshot: mock(() => ({ globalPromptAppend: undefined })),
|
|
88
|
+
generateTitleInBackground: mock(async () => {}),
|
|
89
|
+
recreateActiveTurnFromSession: mock(() => undefined),
|
|
90
|
+
startClaudeTurn: mock(async () => fakeTurn),
|
|
91
|
+
findLastUserMessageId: mock(() => null),
|
|
92
|
+
runTurn: mock(() => {}),
|
|
93
|
+
|
|
94
|
+
...overrides,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return deps
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function makeArgs(overrides: Partial<StartTurnForChatArgs> = {}): StartTurnForChatArgs {
|
|
101
|
+
return {
|
|
102
|
+
chatId: "chat-1",
|
|
103
|
+
provider: "codex",
|
|
104
|
+
content: "hello world",
|
|
105
|
+
attachments: [],
|
|
106
|
+
model: "gpt-4o",
|
|
107
|
+
planMode: false,
|
|
108
|
+
appendUserPrompt: true,
|
|
109
|
+
...overrides,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Tests
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
describe("startTurnForChat", () => {
|
|
118
|
+
test("1. closes a draining stream before starting a new turn", async () => {
|
|
119
|
+
const deps = makeDeps()
|
|
120
|
+
const closeFn = mock(() => {})
|
|
121
|
+
deps.drainingStreams.set("chat-1", { turn: { ...makeFakeTurn(), close: closeFn } })
|
|
122
|
+
|
|
123
|
+
await startTurnForChat(deps, makeArgs())
|
|
124
|
+
|
|
125
|
+
expect(closeFn).toHaveBeenCalledTimes(1)
|
|
126
|
+
expect(deps.clearDrainingStream as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("2. throws when chat is already running (activeTurns has the chatId)", async () => {
|
|
130
|
+
const deps = makeDeps()
|
|
131
|
+
// Pre-populate activeTurns to simulate an in-flight turn
|
|
132
|
+
deps.activeTurns.set("chat-1", {} as ActiveTurn)
|
|
133
|
+
|
|
134
|
+
await expect(startTurnForChat(deps, makeArgs())).rejects.toThrow("Chat is already running")
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("3. clears cancellation on subagentOrchestrator at the start", async () => {
|
|
138
|
+
const deps = makeDeps()
|
|
139
|
+
await startTurnForChat(deps, makeArgs())
|
|
140
|
+
expect((deps.subagentOrchestrator.clearChatCancellation as ReturnType<typeof mock>)).toHaveBeenCalledWith("chat-1")
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test("4. appends user_prompt entry when appendUserPrompt is true", async () => {
|
|
144
|
+
const deps = makeDeps()
|
|
145
|
+
await startTurnForChat(deps, makeArgs({ appendUserPrompt: true }))
|
|
146
|
+
expect(deps.store.appendMessage as ReturnType<typeof mock>).toHaveBeenCalled()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test("5. does NOT append user_prompt entry when appendUserPrompt is false", async () => {
|
|
150
|
+
const deps = makeDeps()
|
|
151
|
+
await startTurnForChat(deps, makeArgs({ appendUserPrompt: false }))
|
|
152
|
+
// appendMessage may be called for other things (account_info etc), but
|
|
153
|
+
// we can check that user_prompt was not the kind persisted.
|
|
154
|
+
const appendCalls = (deps.store.appendMessage as ReturnType<typeof mock>).mock.calls
|
|
155
|
+
const userPromptCalls = appendCalls.filter(
|
|
156
|
+
(call: unknown[]) => (call[1] as { kind?: string })?.kind === "user_prompt"
|
|
157
|
+
)
|
|
158
|
+
expect(userPromptCalls).toHaveLength(0)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test("6. calls recordTurnStarted with correct fields", async () => {
|
|
162
|
+
const deps = makeDeps()
|
|
163
|
+
await startTurnForChat(deps, makeArgs({ provider: "codex", model: "gpt-4o", planMode: true }))
|
|
164
|
+
expect(deps.store.recordTurnStarted as ReturnType<typeof mock>).toHaveBeenCalledWith(
|
|
165
|
+
"chat-1",
|
|
166
|
+
expect.objectContaining({ provider: "codex", model: "gpt-4o", planMode: true })
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test("7. swallows OAuthPoolUnavailableError and persists a result error entry", async () => {
|
|
171
|
+
const oauthError = new OAuthPoolUnavailableError("pool is full")
|
|
172
|
+
const deps = makeDeps({
|
|
173
|
+
startClaudeTurn: mock(async () => { throw oauthError }),
|
|
174
|
+
// Make it a claude provider so startClaudeTurn gets called
|
|
175
|
+
})
|
|
176
|
+
// Use claude provider so isClaudeSdkProvider returns true → startClaudeTurn called
|
|
177
|
+
const args = makeArgs({ provider: "claude", model: "claude-opus-4-5" })
|
|
178
|
+
|
|
179
|
+
// Should NOT throw (swallowed)
|
|
180
|
+
await expect(startTurnForChat(deps, args)).resolves.toBeUndefined()
|
|
181
|
+
|
|
182
|
+
// Should persist the error as a result entry
|
|
183
|
+
const appendCalls = (deps.store.appendMessage as ReturnType<typeof mock>).mock.calls
|
|
184
|
+
const resultEntries = appendCalls.filter(
|
|
185
|
+
(call: unknown[]) => {
|
|
186
|
+
const entry = call[1] as { kind?: string; isError?: boolean }
|
|
187
|
+
return entry?.kind === "result" && entry?.isError === true
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
expect(resultEntries.length).toBeGreaterThan(0)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test("8. rethrows non-OAuth errors after cleanup (recordTurnFailed, emitStateChange)", async () => {
|
|
194
|
+
const boom = new Error("unexpected failure")
|
|
195
|
+
const deps = makeDeps({
|
|
196
|
+
startClaudeTurn: mock(async () => { throw boom }),
|
|
197
|
+
})
|
|
198
|
+
const args = makeArgs({ provider: "claude", model: "claude-opus-4-5" })
|
|
199
|
+
|
|
200
|
+
await expect(startTurnForChat(deps, args)).rejects.toThrow("unexpected failure")
|
|
201
|
+
|
|
202
|
+
// Cleanup should still run
|
|
203
|
+
expect(deps.store.recordTurnFailed as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1", "unexpected failure")
|
|
204
|
+
expect(deps.emitStateChange as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1", { immediate: true })
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test("9. routes to codexManager.startTurn for non-claude providers", async () => {
|
|
208
|
+
const deps = makeDeps()
|
|
209
|
+
await startTurnForChat(deps, makeArgs({ provider: "codex", model: "gpt-4o" }))
|
|
210
|
+
expect(deps.codexManager.startTurn as ReturnType<typeof mock>).toHaveBeenCalledTimes(1)
|
|
211
|
+
expect(deps.startClaudeTurn as ReturnType<typeof mock>).not.toHaveBeenCalled()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test("10. routes to startClaudeTurn for claude provider", async () => {
|
|
215
|
+
const deps = makeDeps()
|
|
216
|
+
const fakeTurn = makeFakeTurn()
|
|
217
|
+
// The real startClaudeTurn populates claudeSessions as a side effect;
|
|
218
|
+
// our mock must do the same so the SDK-session prompt-send path can proceed.
|
|
219
|
+
deps.startClaudeTurn = mock(async () => {
|
|
220
|
+
deps.claudeSessions.set("chat-1", {
|
|
221
|
+
id: "sess-1",
|
|
222
|
+
chatId: "chat-1",
|
|
223
|
+
session: { sendPrompt: mock(async () => {}), getAccountInfo: undefined },
|
|
224
|
+
localPath: "/tmp/project",
|
|
225
|
+
additionalDirectories: [],
|
|
226
|
+
model: "claude-opus-4-5",
|
|
227
|
+
effort: undefined,
|
|
228
|
+
planMode: false,
|
|
229
|
+
sessionToken: null,
|
|
230
|
+
accountInfoLoaded: false,
|
|
231
|
+
nextPromptSeq: 0,
|
|
232
|
+
pendingPromptSeqs: [],
|
|
233
|
+
activeTokenId: null,
|
|
234
|
+
oauthKeyMasked: null,
|
|
235
|
+
oauthLabel: null,
|
|
236
|
+
openrouterKeyMasked: null,
|
|
237
|
+
openrouterModel: null,
|
|
238
|
+
lastUsedAt: Date.now(),
|
|
239
|
+
backgroundTaskIds: new Set<string>(),
|
|
240
|
+
backgroundTaskDeadlineAt: 0,
|
|
241
|
+
loopArmedAtSpawn: false,
|
|
242
|
+
cancelledResultPending: 0,
|
|
243
|
+
suppressSessionTokenPersist: false,
|
|
244
|
+
} as unknown as ClaudeSessionState)
|
|
245
|
+
return fakeTurn
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
await startTurnForChat(deps, makeArgs({ provider: "claude", model: "claude-opus-4-5" }))
|
|
249
|
+
expect(deps.startClaudeTurn as ReturnType<typeof mock>).toHaveBeenCalledTimes(1)
|
|
250
|
+
expect(deps.codexManager.startTurn as ReturnType<typeof mock>).not.toHaveBeenCalled()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test("11. registers ActiveTurn in activeTurns map after turn starts", async () => {
|
|
254
|
+
const deps = makeDeps()
|
|
255
|
+
await startTurnForChat(deps, makeArgs({ provider: "codex" }))
|
|
256
|
+
expect(deps.activeTurns.has("chat-1")).toBe(true)
|
|
257
|
+
const active = deps.activeTurns.get("chat-1")
|
|
258
|
+
expect(active?.provider).toBe("codex")
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test("12. calls runTurn for Codex (non-SDK-session) provider", async () => {
|
|
262
|
+
const deps = makeDeps()
|
|
263
|
+
await startTurnForChat(deps, makeArgs({ provider: "codex" }))
|
|
264
|
+
expect(deps.runTurn as ReturnType<typeof mock>).toHaveBeenCalledTimes(1)
|
|
265
|
+
})
|
|
266
|
+
})
|