@cuongtran001/kanna 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{arc-CAysfmX5.js → arc-BHj370W7.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-1G88q1PY.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-D0L3Co5v.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-CQtERl3H.js} +1 -1
- package/dist/client/assets/channel-BP9IMEvd.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-DxOwt09f.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-Wry66y9R.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-D6p-Hi4x.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-CRQZt23N.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-BzgTKOM9.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CKfD-91C.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-a71B27wc.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.js → chunk-QZHKN3VN-vv9gJwx0.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK--W6Wt7FF.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2--W6Wt7FF.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-YgpMurXf.js → cose-bilkent-S5V4N54A-Cu-dCCaI.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-D8coOWLK.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-NIwZ65jE.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-d46aE_le.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-xdm2UxWc.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-hDbLwC5K.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-MHUGG7ki.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-Ro6n9X70.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-DOZqvRMu.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-Bn2NXYIy.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DK44bfbC.js} +1 -1
- package/dist/client/assets/{index-C0U2kwuB.js → index-CWJpHyQy.js} +115 -115
- package/dist/client/assets/{index-CLsOWXff.css → index-Cyzd4zZx.css} +1 -1
- package/dist/client/assets/{index-CbSY9HfS.js → index-DU56d4oC.js} +1 -1
- package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-C2tW_Dwj.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-C__4Z1wz.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-Bbn5EawR.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-RgS5IRWl.js} +1 -1
- package/dist/client/assets/{linear-DLR6miy3.js → linear-DfI0qBO_.js} +1 -1
- package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-BiN10VHc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-BQhibhh3.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-BEfLRn7a.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-ba641Y2x.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-BGtl-tVO.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-DyziaSFr.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BfYBAYga.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.js → stateDiagram-AJRCARHV-BfUfmLYA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdGcgYEq.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-ByWRdpQX.js → timeline-definition-PNZ67QCA-T6z7dEFl.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-BAwQbFFd.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-DF1cRKsP.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BRtAZ8za.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-Brb-q76h.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.test.ts +22 -6
- package/src/server/agent.ts +35 -4987
- package/src/server/chat-op-log.test.ts +63 -0
- package/src/server/chat-op-log.ts +71 -0
- package/src/server/chat-ops-diff.test.ts +120 -0
- package/src/server/chat-ops-diff.ts +97 -0
- package/src/server/chat-ops-parity.test.ts +140 -0
- 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 +532 -0
- package/src/server/event-store-messages.adapter.ts +455 -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 +291 -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 +75 -0
- package/src/server/event-store.ts +257 -2285
- package/src/server/events.ts +1 -1
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/storage/backend.ts +7 -0
- package/src/server/storage/fs-storage.adapter.ts +21 -1
- package/src/server/storage/in-memory-storage.ts +12 -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 +560 -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 +360 -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 +238 -0
- package/src/server/ws-router.test.ts +90 -0
- package/src/server/ws-router.ts +340 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/chat-ops.test.ts +138 -0
- package/src/shared/chat-ops.ts +71 -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/protocol.ts +3 -2
- 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 +31 -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,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ws-router-chat.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for the chat lifecycle WS command handlers.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, mock, test } from "bun:test"
|
|
7
|
+
import type {
|
|
8
|
+
ChatAgentDep,
|
|
9
|
+
ChatAnalyticsDep,
|
|
10
|
+
ChatCommandDeps,
|
|
11
|
+
ChatStoreDep,
|
|
12
|
+
ChatToolCallbackServiceDep,
|
|
13
|
+
} from "./ws-router-chat"
|
|
14
|
+
import { handleChatCommand } from "./ws-router-chat"
|
|
15
|
+
import type { ClientCommand, ServerEnvelope } from "../shared/protocol"
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helpers
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function makeStore(overrides: Partial<ChatStoreDep> = {}): ChatStoreDep {
|
|
22
|
+
return {
|
|
23
|
+
createChat: mock(async () => ({ id: "chat-1" })),
|
|
24
|
+
renameChat: mock(async () => {}),
|
|
25
|
+
archiveChat: mock(async () => {}),
|
|
26
|
+
unarchiveChat: mock(async () => {}),
|
|
27
|
+
deleteChat: mock(async () => {}),
|
|
28
|
+
setChatReadState: mock(async () => {}),
|
|
29
|
+
setChatPolicyOverride: mock(async () => {}),
|
|
30
|
+
getChat: mock(() => ({ id: "chat-1" })),
|
|
31
|
+
getMessagesPageBefore: mock(() => ({ messages: [], hasOlder: false, olderCursor: null })),
|
|
32
|
+
getToolRequest: mock(() => ({ chatId: "chat-1" })),
|
|
33
|
+
...overrides,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeToolCallbackService(
|
|
38
|
+
overrides: Partial<ChatToolCallbackServiceDep> = {},
|
|
39
|
+
): ChatToolCallbackServiceDep {
|
|
40
|
+
return {
|
|
41
|
+
cancelAllForChat: mock(async () => {}),
|
|
42
|
+
answer: mock(async () => {}),
|
|
43
|
+
...overrides,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeAgent(overrides: Partial<ChatAgentDep> = {}): ChatAgentDep {
|
|
48
|
+
return {
|
|
49
|
+
send: mock(async () => ({ chatId: "chat-1" })),
|
|
50
|
+
forkChat: mock(async () => ({ chatId: "forked-1" })),
|
|
51
|
+
cancel: mock(async () => {}),
|
|
52
|
+
cancelAutoContinue: mock(async () => {}),
|
|
53
|
+
listLiveSchedules: mock(() => []),
|
|
54
|
+
closeChat: mock(async () => {}),
|
|
55
|
+
stopDraining: mock(async () => {}),
|
|
56
|
+
respondTool: mock(async () => {}),
|
|
57
|
+
respondSubagentTool: mock(async () => {}),
|
|
58
|
+
cancelSubagentRun: mock(async () => {}),
|
|
59
|
+
getActiveTurnProfile: mock(() => null),
|
|
60
|
+
toolCallbackService: makeToolCallbackService(),
|
|
61
|
+
...overrides,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function makeAnalytics(): ChatAnalyticsDep & { events: string[] } {
|
|
66
|
+
const events: string[] = []
|
|
67
|
+
return { events, track: (e) => { events.push(e) } }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface TestDeps extends ChatCommandDeps {
|
|
71
|
+
sent: ServerEnvelope[]
|
|
72
|
+
sidebarBroadcasts: number
|
|
73
|
+
chatSidebarBroadcasts: string[]
|
|
74
|
+
allBroadcasts: number
|
|
75
|
+
draftProtectionCalls: string[][]
|
|
76
|
+
logCalls: Array<{ stage: string }>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function makeDeps(
|
|
80
|
+
opts: {
|
|
81
|
+
storeOverrides?: Partial<ChatStoreDep>
|
|
82
|
+
agentOverrides?: Partial<ChatAgentDep>
|
|
83
|
+
} = {},
|
|
84
|
+
): TestDeps {
|
|
85
|
+
const sent: ServerEnvelope[] = []
|
|
86
|
+
let sidebarBroadcasts = 0
|
|
87
|
+
const chatSidebarBroadcasts: string[] = []
|
|
88
|
+
let allBroadcasts = 0
|
|
89
|
+
const draftProtectionCalls: string[][] = []
|
|
90
|
+
const logCalls: Array<{ stage: string }> = []
|
|
91
|
+
const analytics = makeAnalytics()
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
store: makeStore(opts.storeOverrides),
|
|
95
|
+
agent: makeAgent(opts.agentOverrides),
|
|
96
|
+
analytics,
|
|
97
|
+
setDraftProtection: (chatIds) => { draftProtectionCalls.push(chatIds) },
|
|
98
|
+
logSendProfilingFn: (_traceId, _startedAt, stage) => { logCalls.push({ stage }) },
|
|
99
|
+
send: (envelope) => { sent.push(envelope); return JSON.stringify(envelope).length },
|
|
100
|
+
broadcastChatAndSidebar: async (chatId) => { chatSidebarBroadcasts.push(chatId) },
|
|
101
|
+
broadcastSidebar: async () => { sidebarBroadcasts++ },
|
|
102
|
+
broadcastAll: async () => { allBroadcasts++ },
|
|
103
|
+
sent,
|
|
104
|
+
get sidebarBroadcasts() { return sidebarBroadcasts },
|
|
105
|
+
chatSidebarBroadcasts,
|
|
106
|
+
get allBroadcasts() { return allBroadcasts },
|
|
107
|
+
draftProtectionCalls,
|
|
108
|
+
logCalls,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Tests
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
describe("handleChatCommand", () => {
|
|
117
|
+
// -------------------------------------------------------------------------
|
|
118
|
+
// Unknown / out-of-scope
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
test("returns false for a non-chat command", async () => {
|
|
122
|
+
const deps = makeDeps()
|
|
123
|
+
const handled = await handleChatCommand(
|
|
124
|
+
deps,
|
|
125
|
+
{ type: "system.ping" } as unknown as ClientCommand,
|
|
126
|
+
"r0",
|
|
127
|
+
)
|
|
128
|
+
expect(handled).toBe(false)
|
|
129
|
+
expect(deps.sent).toHaveLength(0)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
// chat.create
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
test("chat.create — creates chat, acks, tracks analytics, broadcasts chat+sidebar", async () => {
|
|
137
|
+
const deps = makeDeps()
|
|
138
|
+
const cmd: ClientCommand = { type: "chat.create", projectId: "proj-1" } as ClientCommand
|
|
139
|
+
const handled = await handleChatCommand(deps, cmd, "r1")
|
|
140
|
+
expect(handled).toBe(true)
|
|
141
|
+
expect(deps.store.createChat as ReturnType<typeof mock>).toHaveBeenCalledWith("proj-1", expect.any(Object))
|
|
142
|
+
const ack = deps.sent[0] as { type: string; result: { chatId: string } }
|
|
143
|
+
expect(ack.result.chatId).toBe("chat-1")
|
|
144
|
+
expect((deps.analytics as ReturnType<typeof makeAnalytics>).events).toContain("chat_created")
|
|
145
|
+
expect(deps.chatSidebarBroadcasts).toContain("chat-1")
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
// chat.fork
|
|
150
|
+
// -------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
test("chat.fork — forks chat, acks with result, broadcasts sidebar", async () => {
|
|
153
|
+
const deps = makeDeps()
|
|
154
|
+
const handled = await handleChatCommand(
|
|
155
|
+
deps,
|
|
156
|
+
{ type: "chat.fork", chatId: "chat-1" },
|
|
157
|
+
"r2",
|
|
158
|
+
)
|
|
159
|
+
expect(handled).toBe(true)
|
|
160
|
+
expect(deps.agent.forkChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
161
|
+
expect(deps.sidebarBroadcasts).toBe(1)
|
|
162
|
+
expect(deps.sent[0]).toMatchObject({ type: "ack", id: "r2" })
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
// chat.rename
|
|
167
|
+
// -------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
test("chat.rename — renames, acks, broadcasts chat+sidebar", async () => {
|
|
170
|
+
const deps = makeDeps()
|
|
171
|
+
const handled = await handleChatCommand(
|
|
172
|
+
deps,
|
|
173
|
+
{ type: "chat.rename", chatId: "chat-1", title: "New Title" },
|
|
174
|
+
"r3",
|
|
175
|
+
)
|
|
176
|
+
expect(handled).toBe(true)
|
|
177
|
+
expect(deps.store.renameChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1", "New Title")
|
|
178
|
+
expect(deps.chatSidebarBroadcasts).toContain("chat-1")
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// chat.archive / chat.unarchive
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
test("chat.archive — archives, acks, broadcasts sidebar only", async () => {
|
|
186
|
+
const deps = makeDeps()
|
|
187
|
+
const handled = await handleChatCommand(
|
|
188
|
+
deps,
|
|
189
|
+
{ type: "chat.archive", chatId: "chat-1" },
|
|
190
|
+
"r4",
|
|
191
|
+
)
|
|
192
|
+
expect(handled).toBe(true)
|
|
193
|
+
expect(deps.store.archiveChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
194
|
+
expect(deps.sidebarBroadcasts).toBe(1)
|
|
195
|
+
expect(deps.chatSidebarBroadcasts).toHaveLength(0)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// -------------------------------------------------------------------------
|
|
199
|
+
// chat.delete
|
|
200
|
+
// -------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
test("chat.delete — cancels, closes schedules, closes chat, cancels tool callbacks, deletes, tracks analytics", async () => {
|
|
203
|
+
const scheduleId = "sched-1"
|
|
204
|
+
const cancelAutoContinue = mock(async () => {})
|
|
205
|
+
const cancelAllForChat = mock(async () => {})
|
|
206
|
+
const deleteChat = mock(async () => {})
|
|
207
|
+
const deps = makeDeps({
|
|
208
|
+
agentOverrides: {
|
|
209
|
+
listLiveSchedules: mock(() => [scheduleId]),
|
|
210
|
+
cancelAutoContinue,
|
|
211
|
+
toolCallbackService: makeToolCallbackService({ cancelAllForChat }),
|
|
212
|
+
},
|
|
213
|
+
storeOverrides: { deleteChat },
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const handled = await handleChatCommand(
|
|
217
|
+
deps,
|
|
218
|
+
{ type: "chat.delete", chatId: "chat-1" },
|
|
219
|
+
"r5",
|
|
220
|
+
)
|
|
221
|
+
expect(handled).toBe(true)
|
|
222
|
+
expect(deps.agent.cancel as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
223
|
+
expect(cancelAutoContinue as ReturnType<typeof mock>).toHaveBeenCalledWith(
|
|
224
|
+
"chat-1",
|
|
225
|
+
scheduleId,
|
|
226
|
+
"chat_deleted",
|
|
227
|
+
)
|
|
228
|
+
expect(deps.agent.closeChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
229
|
+
expect(cancelAllForChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1", "chat_deleted")
|
|
230
|
+
expect(deleteChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
231
|
+
expect((deps.analytics as ReturnType<typeof makeAnalytics>).events).toContain("chat_deleted")
|
|
232
|
+
expect(deps.sidebarBroadcasts).toBe(1)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// -------------------------------------------------------------------------
|
|
236
|
+
// chat.setDraftProtection
|
|
237
|
+
// -------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
test("chat.setDraftProtection — sets protection, acks, broadcasts all", async () => {
|
|
240
|
+
const deps = makeDeps()
|
|
241
|
+
const handled = await handleChatCommand(
|
|
242
|
+
deps,
|
|
243
|
+
{ type: "chat.setDraftProtection", chatIds: ["chat-a", "chat-b"] },
|
|
244
|
+
"r6",
|
|
245
|
+
)
|
|
246
|
+
expect(handled).toBe(true)
|
|
247
|
+
expect(deps.draftProtectionCalls).toHaveLength(1)
|
|
248
|
+
expect(deps.draftProtectionCalls[0]).toEqual(["chat-a", "chat-b"])
|
|
249
|
+
expect(deps.allBroadcasts).toBe(1)
|
|
250
|
+
expect(deps.sent[0]).toMatchObject({ type: "ack", id: "r6" })
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
// -------------------------------------------------------------------------
|
|
254
|
+
// chat.send
|
|
255
|
+
// -------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
test("chat.send — calls agent.send, acks with result, calls profiling hooks", async () => {
|
|
258
|
+
const sendResult = { chatId: "chat-sent" }
|
|
259
|
+
const profile = { traceId: "trace-1", startedAt: 100 }
|
|
260
|
+
const agentSend = mock(async () => sendResult)
|
|
261
|
+
const getActiveTurnProfile = mock(() => profile)
|
|
262
|
+
const deps = makeDeps({
|
|
263
|
+
agentOverrides: { send: agentSend, getActiveTurnProfile },
|
|
264
|
+
})
|
|
265
|
+
const cmd: ClientCommand = {
|
|
266
|
+
type: "chat.send",
|
|
267
|
+
content: "hello",
|
|
268
|
+
clientTraceId: "trace-1",
|
|
269
|
+
} as unknown as ClientCommand
|
|
270
|
+
const handled = await handleChatCommand(deps, cmd, "r7")
|
|
271
|
+
expect(handled).toBe(true)
|
|
272
|
+
expect(agentSend as ReturnType<typeof mock>).toHaveBeenCalledWith(cmd)
|
|
273
|
+
const ack = deps.sent[0] as { type: string; result: typeof sendResult }
|
|
274
|
+
expect(ack.result).toEqual(sendResult)
|
|
275
|
+
// profiling hooks should have been called twice (ack + ack_completed)
|
|
276
|
+
expect(deps.logCalls.some((c) => c.stage === "ws.chat_send_ack")).toBe(true)
|
|
277
|
+
expect(deps.logCalls.some((c) => c.stage === "ws.chat_send_ack_completed")).toBe(true)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// -------------------------------------------------------------------------
|
|
281
|
+
// chat.cancel
|
|
282
|
+
// -------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
test("chat.cancel — cancels agent, cancels tool callbacks, acks", async () => {
|
|
285
|
+
const cancelAllForChat = mock(async () => {})
|
|
286
|
+
const deps = makeDeps({
|
|
287
|
+
agentOverrides: {
|
|
288
|
+
toolCallbackService: makeToolCallbackService({ cancelAllForChat }),
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
const handled = await handleChatCommand(
|
|
292
|
+
deps,
|
|
293
|
+
{ type: "chat.cancel", chatId: "chat-1" },
|
|
294
|
+
"r8",
|
|
295
|
+
)
|
|
296
|
+
expect(handled).toBe(true)
|
|
297
|
+
expect(deps.agent.cancel as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
298
|
+
expect(cancelAllForChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1", "chat_cancelled")
|
|
299
|
+
expect(deps.sent[0]).toMatchObject({ type: "ack", id: "r8" })
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test("chat.cancel — no toolCallbackService — still acks", async () => {
|
|
303
|
+
const deps = makeDeps({
|
|
304
|
+
agentOverrides: { toolCallbackService: null },
|
|
305
|
+
})
|
|
306
|
+
const handled = await handleChatCommand(
|
|
307
|
+
deps,
|
|
308
|
+
{ type: "chat.cancel", chatId: "chat-2" },
|
|
309
|
+
"r9",
|
|
310
|
+
)
|
|
311
|
+
expect(handled).toBe(true)
|
|
312
|
+
expect(deps.sent[0]).toMatchObject({ type: "ack" })
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// -------------------------------------------------------------------------
|
|
316
|
+
// chat.loadHistory
|
|
317
|
+
// -------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
test("chat.loadHistory — returns messages page", async () => {
|
|
320
|
+
const page: import("../shared/types").ChatHistoryPage = { messages: [] as import("../shared/types").TranscriptEntry[], hasOlder: false, olderCursor: null }
|
|
321
|
+
const deps = makeDeps({
|
|
322
|
+
storeOverrides: {
|
|
323
|
+
getMessagesPageBefore: mock(() => page),
|
|
324
|
+
getChat: mock(() => ({ id: "chat-1" })),
|
|
325
|
+
},
|
|
326
|
+
})
|
|
327
|
+
const handled = await handleChatCommand(
|
|
328
|
+
deps,
|
|
329
|
+
{ type: "chat.loadHistory", chatId: "chat-1", beforeCursor: "cursor-0", limit: 50 },
|
|
330
|
+
"r10",
|
|
331
|
+
)
|
|
332
|
+
expect(handled).toBe(true)
|
|
333
|
+
const ack = deps.sent[0] as { type: string; result: typeof page }
|
|
334
|
+
expect(ack.result).toEqual(page)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test("chat.loadHistory — chat not found — throws", async () => {
|
|
338
|
+
const deps = makeDeps({
|
|
339
|
+
storeOverrides: { getChat: mock(() => undefined) },
|
|
340
|
+
})
|
|
341
|
+
await expect(
|
|
342
|
+
handleChatCommand(
|
|
343
|
+
deps,
|
|
344
|
+
{ type: "chat.loadHistory", chatId: "ghost", beforeCursor: "", limit: 50 },
|
|
345
|
+
"r11",
|
|
346
|
+
),
|
|
347
|
+
).rejects.toThrow("Chat not found")
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// -------------------------------------------------------------------------
|
|
351
|
+
// chat.toolRequestAnswer
|
|
352
|
+
// -------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
test("chat.toolRequestAnswer — valid decision — answers, acks, broadcasts chat+sidebar", async () => {
|
|
355
|
+
const answer = mock(async () => {})
|
|
356
|
+
const deps = makeDeps({
|
|
357
|
+
agentOverrides: {
|
|
358
|
+
toolCallbackService: makeToolCallbackService({ answer }),
|
|
359
|
+
},
|
|
360
|
+
storeOverrides: {
|
|
361
|
+
getToolRequest: mock(() => ({ chatId: "chat-1" })),
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
const handled = await handleChatCommand(
|
|
365
|
+
deps,
|
|
366
|
+
{
|
|
367
|
+
type: "chat.toolRequestAnswer",
|
|
368
|
+
chatId: "chat-1",
|
|
369
|
+
toolRequestId: "req-1",
|
|
370
|
+
decision: { kind: "allow" },
|
|
371
|
+
} as unknown as ClientCommand,
|
|
372
|
+
"r12",
|
|
373
|
+
)
|
|
374
|
+
expect(handled).toBe(true)
|
|
375
|
+
expect(answer as ReturnType<typeof mock>).toHaveBeenCalledWith("req-1", { kind: "allow" })
|
|
376
|
+
expect(deps.chatSidebarBroadcasts).toContain("chat-1")
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test("chat.toolRequestAnswer — no toolCallbackService — throws", async () => {
|
|
380
|
+
const deps = makeDeps({ agentOverrides: { toolCallbackService: null } })
|
|
381
|
+
await expect(
|
|
382
|
+
handleChatCommand(
|
|
383
|
+
deps,
|
|
384
|
+
{
|
|
385
|
+
type: "chat.toolRequestAnswer",
|
|
386
|
+
chatId: "chat-1",
|
|
387
|
+
toolRequestId: "req-1",
|
|
388
|
+
decision: { kind: "allow" },
|
|
389
|
+
} as unknown as ClientCommand,
|
|
390
|
+
"r13",
|
|
391
|
+
),
|
|
392
|
+
).rejects.toThrow("tool callback service unavailable")
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
test("chat.toolRequestAnswer — invalid decision kind — throws", async () => {
|
|
396
|
+
const deps = makeDeps()
|
|
397
|
+
await expect(
|
|
398
|
+
handleChatCommand(
|
|
399
|
+
deps,
|
|
400
|
+
{
|
|
401
|
+
type: "chat.toolRequestAnswer",
|
|
402
|
+
chatId: "chat-1",
|
|
403
|
+
toolRequestId: "req-1",
|
|
404
|
+
decision: { kind: "INVALID" },
|
|
405
|
+
} as unknown as ClientCommand,
|
|
406
|
+
"r14",
|
|
407
|
+
),
|
|
408
|
+
).rejects.toThrow("Invalid tool request decision kind")
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
test("chat.toolRequestAnswer — tool request belongs to different chat — throws", async () => {
|
|
412
|
+
const deps = makeDeps({
|
|
413
|
+
storeOverrides: {
|
|
414
|
+
getToolRequest: mock(() => ({ chatId: "different-chat" })),
|
|
415
|
+
},
|
|
416
|
+
})
|
|
417
|
+
await expect(
|
|
418
|
+
handleChatCommand(
|
|
419
|
+
deps,
|
|
420
|
+
{
|
|
421
|
+
type: "chat.toolRequestAnswer",
|
|
422
|
+
chatId: "chat-1",
|
|
423
|
+
toolRequestId: "req-1",
|
|
424
|
+
decision: { kind: "allow" },
|
|
425
|
+
} as unknown as ClientCommand,
|
|
426
|
+
"r15",
|
|
427
|
+
),
|
|
428
|
+
).rejects.toThrow("Tool request does not belong to this chat")
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
// -------------------------------------------------------------------------
|
|
432
|
+
// chat.respondTool / chat.respondSubagentTool / chat.cancelSubagentRun
|
|
433
|
+
// -------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
test("chat.respondTool — calls respondTool, acks", async () => {
|
|
436
|
+
const respondTool = mock(async () => {})
|
|
437
|
+
const deps = makeDeps({ agentOverrides: { respondTool } })
|
|
438
|
+
const cmd: ClientCommand = {
|
|
439
|
+
type: "chat.respondTool",
|
|
440
|
+
chatId: "chat-1",
|
|
441
|
+
toolUseId: "t1",
|
|
442
|
+
result: "ok",
|
|
443
|
+
} as unknown as ClientCommand
|
|
444
|
+
const handled = await handleChatCommand(deps, cmd, "r16")
|
|
445
|
+
expect(handled).toBe(true)
|
|
446
|
+
expect(respondTool as ReturnType<typeof mock>).toHaveBeenCalledWith(cmd)
|
|
447
|
+
expect(deps.sent[0]).toMatchObject({ type: "ack", id: "r16" })
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
test("chat.stopDraining — stops draining, acks", async () => {
|
|
451
|
+
const stopDraining = mock(async () => {})
|
|
452
|
+
const deps = makeDeps({ agentOverrides: { stopDraining } })
|
|
453
|
+
const handled = await handleChatCommand(
|
|
454
|
+
deps,
|
|
455
|
+
{ type: "chat.stopDraining", chatId: "chat-1" },
|
|
456
|
+
"r17",
|
|
457
|
+
)
|
|
458
|
+
expect(handled).toBe(true)
|
|
459
|
+
expect(stopDraining as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-1")
|
|
460
|
+
})
|
|
461
|
+
})
|