@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.
Files changed (200) hide show
  1. package/dist/client/assets/{arc-CAysfmX5.js → arc-BHj370W7.js} +1 -1
  2. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-1G88q1PY.js} +1 -1
  3. package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-D0L3Co5v.js} +1 -1
  4. package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-CQtERl3H.js} +1 -1
  5. package/dist/client/assets/channel-BP9IMEvd.js +1 -0
  6. package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-DxOwt09f.js} +1 -1
  7. package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-Wry66y9R.js} +1 -1
  8. package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-D6p-Hi4x.js} +1 -1
  9. package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-CRQZt23N.js} +1 -1
  10. package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-BzgTKOM9.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CKfD-91C.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-a71B27wc.js} +1 -1
  13. package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.js → chunk-QZHKN3VN-vv9gJwx0.js} +1 -1
  14. package/dist/client/assets/classDiagram-4FO5ZUOK--W6Wt7FF.js +1 -0
  15. package/dist/client/assets/classDiagram-v2-Q7XG4LA2--W6Wt7FF.js +1 -0
  16. package/dist/client/assets/{cose-bilkent-S5V4N54A-YgpMurXf.js → cose-bilkent-S5V4N54A-Cu-dCCaI.js} +1 -1
  17. package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-D8coOWLK.js} +1 -1
  18. package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-NIwZ65jE.js} +1 -1
  19. package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-d46aE_le.js} +1 -1
  20. package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-xdm2UxWc.js} +1 -1
  21. package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-hDbLwC5K.js} +1 -1
  22. package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-MHUGG7ki.js} +1 -1
  23. package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-Ro6n9X70.js} +1 -1
  24. package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-DOZqvRMu.js} +1 -1
  25. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-Bn2NXYIy.js} +1 -1
  26. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DK44bfbC.js} +1 -1
  27. package/dist/client/assets/{index-C0U2kwuB.js → index-CWJpHyQy.js} +115 -115
  28. package/dist/client/assets/{index-CLsOWXff.css → index-Cyzd4zZx.css} +1 -1
  29. package/dist/client/assets/{index-CbSY9HfS.js → index-DU56d4oC.js} +1 -1
  30. package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-C2tW_Dwj.js} +1 -1
  31. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-C__4Z1wz.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-Bbn5EawR.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-RgS5IRWl.js} +1 -1
  34. package/dist/client/assets/{linear-DLR6miy3.js → linear-DfI0qBO_.js} +1 -1
  35. package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-BiN10VHc.js} +4 -4
  36. package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-BQhibhh3.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-BEfLRn7a.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-ba641Y2x.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-BGtl-tVO.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-DyziaSFr.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BfYBAYga.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.js → stateDiagram-AJRCARHV-BfUfmLYA.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdGcgYEq.js +1 -0
  44. package/dist/client/assets/{timeline-definition-PNZ67QCA-ByWRdpQX.js → timeline-definition-PNZ67QCA-T6z7dEFl.js} +1 -1
  45. package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-BAwQbFFd.js} +1 -1
  46. package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-DF1cRKsP.js} +1 -1
  47. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BRtAZ8za.js} +1 -1
  48. package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-Brb-q76h.js} +1 -1
  49. package/dist/client/index.html +2 -2
  50. package/package.json +1 -1
  51. package/src/server/agent-coordinator-types.ts +158 -0
  52. package/src/server/agent-coordinator.ts +919 -0
  53. package/src/server/agent-deps-builders.ts +425 -0
  54. package/src/server/agent.test.ts +22 -6
  55. package/src/server/agent.ts +35 -4987
  56. package/src/server/chat-op-log.test.ts +63 -0
  57. package/src/server/chat-op-log.ts +71 -0
  58. package/src/server/chat-ops-diff.test.ts +120 -0
  59. package/src/server/chat-ops-diff.ts +97 -0
  60. package/src/server/chat-ops-parity.test.ts +140 -0
  61. package/src/server/claude-autocontinue-commands.test.ts +494 -0
  62. package/src/server/claude-autocontinue-commands.ts +256 -0
  63. package/src/server/claude-cancel-handler.test.ts +472 -0
  64. package/src/server/claude-cancel-handler.ts +241 -0
  65. package/src/server/claude-chat-management.test.ts +395 -0
  66. package/src/server/claude-chat-management.ts +251 -0
  67. package/src/server/claude-harness-stream.test.ts +273 -0
  68. package/src/server/claude-harness-stream.ts +158 -0
  69. package/src/server/claude-loop-orch-commands.test.ts +579 -0
  70. package/src/server/claude-loop-orch-commands.ts +466 -0
  71. package/src/server/claude-message-normalizer.test.ts +213 -0
  72. package/src/server/claude-message-normalizer.ts +436 -0
  73. package/src/server/claude-prompt-helpers.test.ts +248 -0
  74. package/src/server/claude-prompt-helpers.ts +139 -0
  75. package/src/server/claude-sdk-queue.test.ts +169 -0
  76. package/src/server/claude-sdk-queue.ts +82 -0
  77. package/src/server/claude-send-command.test.ts +414 -0
  78. package/src/server/claude-send-command.ts +420 -0
  79. package/src/server/claude-session-config-helpers.test.ts +279 -0
  80. package/src/server/claude-session-config-helpers.ts +151 -0
  81. package/src/server/claude-session-config.test.ts +250 -0
  82. package/src/server/claude-session-config.ts +159 -0
  83. package/src/server/claude-session-error-handler.test.ts +498 -0
  84. package/src/server/claude-session-error-handler.ts +361 -0
  85. package/src/server/claude-session-lifecycle.test.ts +669 -0
  86. package/src/server/claude-session-lifecycle.ts +283 -0
  87. package/src/server/claude-session-rebuild.test.ts +224 -0
  88. package/src/server/claude-session-rebuild.ts +101 -0
  89. package/src/server/claude-session-runner.test.ts +523 -0
  90. package/src/server/claude-session-runner.ts +521 -0
  91. package/src/server/claude-session-spawner.test.ts +401 -0
  92. package/src/server/claude-session-spawner.ts +401 -0
  93. package/src/server/claude-session-start.test.ts +205 -0
  94. package/src/server/claude-session-start.ts +277 -0
  95. package/src/server/claude-session-state-queries.test.ts +344 -0
  96. package/src/server/claude-session-state-queries.ts +160 -0
  97. package/src/server/claude-session-state.ts +104 -0
  98. package/src/server/claude-slash-commands.test.ts +277 -0
  99. package/src/server/claude-slash-commands.ts +234 -0
  100. package/src/server/claude-spawn-helpers.test.ts +221 -0
  101. package/src/server/claude-spawn-helpers.ts +179 -0
  102. package/src/server/claude-steer-log.test.ts +243 -0
  103. package/src/server/claude-steer-log.ts +58 -0
  104. package/src/server/claude-subagent-tool-response.test.ts +213 -0
  105. package/src/server/claude-subagent-tool-response.ts +165 -0
  106. package/src/server/claude-subagent-wiring.test.ts +263 -0
  107. package/src/server/claude-subagent-wiring.ts +324 -0
  108. package/src/server/claude-tool-respond.test.ts +321 -0
  109. package/src/server/claude-tool-respond.ts +142 -0
  110. package/src/server/claude-turn-runner.test.ts +310 -0
  111. package/src/server/claude-turn-runner.ts +228 -0
  112. package/src/server/claude-turn-starter.test.ts +266 -0
  113. package/src/server/claude-turn-starter.ts +566 -0
  114. package/src/server/claude-usage-math.test.ts +169 -0
  115. package/src/server/claude-usage-math.ts +132 -0
  116. package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
  117. package/src/server/diff-store-branch-ops.adapter.ts +602 -0
  118. package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
  119. package/src/server/diff-store-commit-ops.adapter.ts +258 -0
  120. package/src/server/diff-store-errors.ts +85 -0
  121. package/src/server/diff-store-file-ops.adapter.ts +212 -0
  122. package/src/server/diff-store-git-branch.adapter.ts +686 -0
  123. package/src/server/diff-store-io.adapter.ts +27 -0
  124. package/src/server/diff-store-parse.test.ts +212 -0
  125. package/src/server/diff-store-parse.ts +122 -0
  126. package/src/server/diff-store-state.ts +82 -0
  127. package/src/server/diff-store.ts +76 -1854
  128. package/src/server/event-store-apply.test.ts +280 -0
  129. package/src/server/event-store-apply.ts +132 -0
  130. package/src/server/event-store-chat-lifecycle.test.ts +408 -0
  131. package/src/server/event-store-chat-lifecycle.ts +441 -0
  132. package/src/server/event-store-entity-write.ts +356 -0
  133. package/src/server/event-store-helpers.test.ts +230 -0
  134. package/src/server/event-store-helpers.ts +235 -0
  135. package/src/server/event-store-init.ts +276 -0
  136. package/src/server/event-store-messages.adapter.test.ts +532 -0
  137. package/src/server/event-store-messages.adapter.ts +455 -0
  138. package/src/server/event-store-orch-subscription.ts +124 -0
  139. package/src/server/event-store-orch.test.ts +462 -0
  140. package/src/server/event-store-orch.ts +312 -0
  141. package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
  142. package/src/server/event-store-peripheral-events.adapter.ts +105 -0
  143. package/src/server/event-store-snapshot.test.ts +597 -0
  144. package/src/server/event-store-snapshot.ts +658 -0
  145. package/src/server/event-store-subagent.test.ts +408 -0
  146. package/src/server/event-store-subagent.ts +198 -0
  147. package/src/server/event-store-tool-requests.test.ts +210 -0
  148. package/src/server/event-store-tool-requests.ts +131 -0
  149. package/src/server/event-store-transcript-write.adapter.ts +291 -0
  150. package/src/server/event-store-write-ops.test.ts +554 -0
  151. package/src/server/event-store-write-ops.ts +529 -0
  152. package/src/server/event-store.test.ts +75 -0
  153. package/src/server/event-store.ts +257 -2285
  154. package/src/server/events.ts +1 -1
  155. package/src/server/harness-types.ts +15 -1
  156. package/src/server/oauth-errors.ts +19 -0
  157. package/src/server/storage/backend.ts +7 -0
  158. package/src/server/storage/fs-storage.adapter.ts +21 -1
  159. package/src/server/storage/in-memory-storage.ts +12 -0
  160. package/src/server/ws-router-agent-ctrl.test.ts +187 -0
  161. package/src/server/ws-router-agent-ctrl.ts +137 -0
  162. package/src/server/ws-router-broadcast.ts +560 -0
  163. package/src/server/ws-router-chat.test.ts +461 -0
  164. package/src/server/ws-router-chat.ts +279 -0
  165. package/src/server/ws-router-defaults.test.ts +283 -0
  166. package/src/server/ws-router-defaults.ts +434 -0
  167. package/src/server/ws-router-diff.test.ts +167 -0
  168. package/src/server/ws-router-diff.ts +250 -0
  169. package/src/server/ws-router-envelope.ts +360 -0
  170. package/src/server/ws-router-misc.test.ts +422 -0
  171. package/src/server/ws-router-misc.ts +265 -0
  172. package/src/server/ws-router-orch.test.ts +190 -0
  173. package/src/server/ws-router-orch.ts +93 -0
  174. package/src/server/ws-router-project.test.ts +403 -0
  175. package/src/server/ws-router-project.ts +256 -0
  176. package/src/server/ws-router-push.test.ts +193 -0
  177. package/src/server/ws-router-push.ts +120 -0
  178. package/src/server/ws-router-settings.test.ts +173 -0
  179. package/src/server/ws-router-settings.ts +416 -0
  180. package/src/server/ws-router-skills.test.ts +194 -0
  181. package/src/server/ws-router-skills.ts +204 -0
  182. package/src/server/ws-router-utils.ts +238 -0
  183. package/src/server/ws-router.test.ts +90 -0
  184. package/src/server/ws-router.ts +340 -2254
  185. package/src/shared/app-settings-types.ts +365 -0
  186. package/src/shared/chat-ops.test.ts +138 -0
  187. package/src/shared/chat-ops.ts +71 -0
  188. package/src/shared/core-types.ts +27 -0
  189. package/src/shared/git-diff-types.ts +165 -0
  190. package/src/shared/mcp-types.ts +105 -0
  191. package/src/shared/protocol.ts +3 -2
  192. package/src/shared/provider-model-types.ts +437 -0
  193. package/src/shared/subagent-types.ts +198 -0
  194. package/src/shared/tool-call-types.ts +314 -0
  195. package/src/shared/transcript-types.ts +270 -0
  196. package/src/shared/types.ts +31 -1711
  197. package/dist/client/assets/channel-KQhRwDzJ.js +0 -1
  198. package/dist/client/assets/classDiagram-4FO5ZUOK-ChxT1GNq.js +0 -1
  199. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ChxT1GNq.js +0 -1
  200. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D4X-ZnLV.js +0 -1
@@ -0,0 +1,280 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { STORE_VERSION } from "../shared/types"
3
+ import type { ToolRequest } from "../shared/permission-policy"
4
+ import { AUTO_CONTINUE_EVENT_VERSION } from "./auto-continue/events"
5
+ import type { AutoContinueEvent } from "./auto-continue/events"
6
+ import { createEmptyState } from "./events"
7
+ import type { TranscriptEntry } from "../shared/types"
8
+ import { applyStoreEvent } from "./event-store-apply"
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+
14
+ const TS = 1_700_000_000_000
15
+
16
+ function makeState() {
17
+ return createEmptyState()
18
+ }
19
+
20
+ function makeBaseChat() {
21
+ return {
22
+ id: "chat-1",
23
+ projectId: "proj-1",
24
+ title: "C",
25
+ createdAt: TS,
26
+ updatedAt: TS,
27
+ unread: false,
28
+ provider: null as null,
29
+ planMode: false,
30
+ sessionTokensByProvider: {} as Record<string, string | null>,
31
+ sourceHash: null as null,
32
+ pendingForkSessionToken: null as null,
33
+ hasMessages: false,
34
+ lastTurnOutcome: null as null,
35
+ }
36
+ }
37
+
38
+ function makeToolRequest(): ToolRequest {
39
+ return {
40
+ id: "req-1",
41
+ chatId: "chat-1",
42
+ sessionId: "sess-1",
43
+ toolUseId: "tool-use-1",
44
+ toolName: "AskUserQuestion",
45
+ arguments: {},
46
+ canonicalArgsHash: "hash",
47
+ policyVerdict: "ask",
48
+ status: "pending",
49
+ createdAt: TS,
50
+ expiresAt: TS + 600_000,
51
+ }
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Project events
56
+ // ---------------------------------------------------------------------------
57
+
58
+ describe("applyStoreEvent — project events", () => {
59
+ test("project_opened populates projectsById and projectIdsByPath", () => {
60
+ const state = makeState()
61
+ applyStoreEvent(
62
+ {
63
+ v: STORE_VERSION,
64
+ type: "project_opened",
65
+ timestamp: TS,
66
+ projectId: "proj-1",
67
+ localPath: "/tmp/repo",
68
+ title: "Repo",
69
+ },
70
+ state,
71
+ new Map(),
72
+ new Map(),
73
+ )
74
+ expect(state.projectsById.has("proj-1")).toBe(true)
75
+ })
76
+
77
+ test("project_removed soft-deletes", () => {
78
+ const state = makeState()
79
+ state.projectsById.set("proj-1", {
80
+ id: "proj-1", localPath: "/tmp/repo", title: "R", createdAt: TS, updatedAt: TS,
81
+ })
82
+ state.projectIdsByPath.set("/tmp/repo", "proj-1")
83
+ applyStoreEvent(
84
+ { v: STORE_VERSION, type: "project_removed", timestamp: TS + 1, projectId: "proj-1" },
85
+ state,
86
+ new Map(),
87
+ new Map(),
88
+ )
89
+ expect(state.projectsById.get("proj-1")?.deletedAt).toBeDefined()
90
+ })
91
+ })
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Chat lifecycle events
95
+ // ---------------------------------------------------------------------------
96
+
97
+ describe("applyStoreEvent — chat events", () => {
98
+ test("chat_created populates chatsById and chatTimingsByChatId", () => {
99
+ const state = makeState()
100
+ applyStoreEvent(
101
+ {
102
+ v: STORE_VERSION,
103
+ type: "chat_created",
104
+ timestamp: TS,
105
+ chatId: "chat-1",
106
+ projectId: "proj-1",
107
+ title: "New Chat",
108
+ },
109
+ state,
110
+ new Map(),
111
+ new Map(),
112
+ )
113
+ expect(state.chatsById.has("chat-1")).toBe(true)
114
+ expect(state.chatTimingsByChatId.has("chat-1")).toBe(true)
115
+ })
116
+
117
+ test("chat_renamed updates title", () => {
118
+ const state = makeState()
119
+ state.chatsById.set("chat-1", makeBaseChat())
120
+ applyStoreEvent(
121
+ { v: STORE_VERSION, type: "chat_renamed", timestamp: TS + 1, chatId: "chat-1", title: "New" },
122
+ state,
123
+ new Map(),
124
+ new Map(),
125
+ )
126
+ expect(state.chatsById.get("chat-1")?.title).toBe("New")
127
+ })
128
+
129
+ test("chat_provider_set updates provider in chatsById and replayChatProvider", () => {
130
+ const state = makeState()
131
+ state.chatsById.set("chat-1", makeBaseChat())
132
+ const replay = new Map<string, "claude" | null>([["chat-1", null]])
133
+ applyStoreEvent(
134
+ { v: STORE_VERSION, type: "chat_provider_set", timestamp: TS + 1, chatId: "chat-1", provider: "claude" },
135
+ state,
136
+ new Map(),
137
+ replay,
138
+ )
139
+ expect(state.chatsById.get("chat-1")?.provider).toBe("claude")
140
+ expect(replay.get("chat-1")).toBe("claude")
141
+ })
142
+ })
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // message_appended
146
+ // ---------------------------------------------------------------------------
147
+
148
+ describe("applyStoreEvent — message_appended", () => {
149
+ test("appends to legacyMessagesByChatId and marks hasMessages", () => {
150
+ const state = makeState()
151
+ state.chatsById.set("chat-1", makeBaseChat())
152
+ const legacy = new Map<string, TranscriptEntry[]>()
153
+ applyStoreEvent(
154
+ {
155
+ v: STORE_VERSION,
156
+ type: "message_appended",
157
+ timestamp: TS,
158
+ chatId: "chat-1",
159
+ entry: { kind: "user_prompt", content: "hello", createdAt: TS, _id: "entry-1" } as TranscriptEntry,
160
+ },
161
+ state,
162
+ legacy,
163
+ new Map(),
164
+ )
165
+ expect(legacy.get("chat-1")?.length).toBe(1)
166
+ expect(state.chatsById.get("chat-1")?.hasMessages).toBe(true)
167
+ })
168
+
169
+ test("multiple appends accumulate entries", () => {
170
+ const state = makeState()
171
+ state.chatsById.set("chat-1", makeBaseChat())
172
+ const legacy = new Map<string, TranscriptEntry[]>()
173
+ for (let i = 0; i < 3; i++) {
174
+ applyStoreEvent(
175
+ {
176
+ v: STORE_VERSION,
177
+ type: "message_appended",
178
+ timestamp: TS + i,
179
+ chatId: "chat-1",
180
+ entry: {
181
+ kind: "user_prompt",
182
+ content: `msg-${i}`,
183
+ createdAt: TS + i,
184
+ _id: `entry-${i}`,
185
+ } as TranscriptEntry,
186
+ },
187
+ state,
188
+ legacy,
189
+ new Map(),
190
+ )
191
+ }
192
+ expect(legacy.get("chat-1")?.length).toBe(3)
193
+ })
194
+ })
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // Stack events
198
+ // ---------------------------------------------------------------------------
199
+
200
+ describe("applyStoreEvent — stack events", () => {
201
+ test("stack_added creates a stack", () => {
202
+ const state = makeState()
203
+ applyStoreEvent(
204
+ {
205
+ v: STORE_VERSION,
206
+ type: "stack_added",
207
+ timestamp: TS,
208
+ stackId: "stack-1",
209
+ title: "My Stack",
210
+ projectIds: ["proj-1", "proj-2"],
211
+ },
212
+ state,
213
+ new Map(),
214
+ new Map(),
215
+ )
216
+ expect(state.stacksById.has("stack-1")).toBe(true)
217
+ const stack = state.stacksById.get("stack-1")
218
+ if (stack) {
219
+ expect(stack.title).toBe("My Stack")
220
+ }
221
+ })
222
+
223
+ test("stack_removed soft-deletes", () => {
224
+ const state = makeState()
225
+ state.stacksById.set("stack-1", {
226
+ id: "stack-1", title: "S", projectIds: ["p1", "p2"], createdAt: TS, updatedAt: TS,
227
+ })
228
+ applyStoreEvent(
229
+ { v: STORE_VERSION, type: "stack_removed", timestamp: TS + 1, stackId: "stack-1" },
230
+ state,
231
+ new Map(),
232
+ new Map(),
233
+ )
234
+ expect(state.stacksById.get("stack-1")?.deletedAt).toBeDefined()
235
+ })
236
+ })
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // Auto-continue events (kind-based dispatch)
240
+ // ---------------------------------------------------------------------------
241
+
242
+ describe("applyStoreEvent — auto-continue events", () => {
243
+ test("auto_continue_accepted is routed to autoContinueEventsByChatId", () => {
244
+ const state = makeState()
245
+ const event: AutoContinueEvent = {
246
+ v: AUTO_CONTINUE_EVENT_VERSION,
247
+ kind: "auto_continue_accepted",
248
+ chatId: "chat-1",
249
+ scheduleId: "sched-1",
250
+ timestamp: TS,
251
+ scheduledAt: TS,
252
+ tz: "UTC",
253
+ source: "subagent_background",
254
+ resetAt: TS + 60_000,
255
+ detectedAt: TS - 1_000,
256
+ prompt: "do next thing",
257
+ }
258
+ applyStoreEvent(event, state, new Map(), new Map())
259
+ expect(state.autoContinueEventsByChatId.has("chat-1")).toBe(true)
260
+ expect(state.autoContinueEventsByChatId.get("chat-1")?.length).toBe(1)
261
+ })
262
+ })
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Tool-request events
266
+ // ---------------------------------------------------------------------------
267
+
268
+ describe("applyStoreEvent — tool request events", () => {
269
+ test("tool_request_put adds to toolRequestsById", () => {
270
+ const state = makeState()
271
+ const req = makeToolRequest()
272
+ applyStoreEvent(
273
+ { v: STORE_VERSION, type: "tool_request_put", timestamp: TS, request: req },
274
+ state,
275
+ new Map(),
276
+ new Map(),
277
+ )
278
+ expect(state.toolRequestsById.has("req-1")).toBe(true)
279
+ })
280
+ })
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Pure event dispatch — applies any StoreEvent to the in-memory state.
3
+ *
4
+ * Extracted from EventStore.applyEvent() to reduce event-store.ts file size.
5
+ * All functions are pure state mutations — no IO, no side effects
6
+ * (side-effect seal: IO lives in *.adapter.ts files).
7
+ */
8
+
9
+ import type { AgentProvider, TranscriptEntry } from "../shared/types"
10
+ import type { AutoContinueEvent } from "./auto-continue/events"
11
+ import type { StoreEvent, StoreState } from "./events"
12
+ import {
13
+ applyChatLifecycleEvent,
14
+ applyChatMessageMetadata,
15
+ applyAutoContinueToState,
16
+ applyProjectEvent,
17
+ applyStackEvent,
18
+ } from "./event-store-chat-lifecycle"
19
+ import { applySubagentEvent } from "./event-store-subagent"
20
+ import { applyToolRequestEvent } from "./event-store-tool-requests"
21
+ import { applyOrchEvent } from "./event-store-orch"
22
+
23
+ function isAutoContinueEvent(event: StoreEvent): event is AutoContinueEvent {
24
+ return "kind" in event
25
+ }
26
+
27
+ /**
28
+ * Apply a single StoreEvent to the full in-memory state.
29
+ *
30
+ * @param event - the event to apply
31
+ * @param state - full StoreState (mutated in-place)
32
+ * @param legacyMessagesByChatId - legacy transcript store (mutated for `message_appended`)
33
+ * @param replayChatProvider - provider-replay tracking map
34
+ */
35
+ export function applyStoreEvent(
36
+ event: StoreEvent,
37
+ state: StoreState,
38
+ legacyMessagesByChatId: Map<string, TranscriptEntry[]>,
39
+ replayChatProvider: Map<string, AgentProvider | null>,
40
+ ): void {
41
+ if (isAutoContinueEvent(event)) {
42
+ applyAutoContinueToState(state.autoContinueEventsByChatId, event)
43
+ return
44
+ }
45
+ const e: Exclude<StoreEvent, AutoContinueEvent> = event
46
+ switch (e.type) {
47
+ case "project_opened":
48
+ case "project_removed":
49
+ case "sidebar_project_order_set":
50
+ case "project_star_set": {
51
+ applyProjectEvent(state, e)
52
+ break
53
+ }
54
+ case "chat_created":
55
+ case "chat_renamed":
56
+ case "chat_deleted":
57
+ case "chat_archived":
58
+ case "chat_unarchived":
59
+ case "chat_provider_set":
60
+ case "chat_plan_mode_set":
61
+ case "chat_read_state_set":
62
+ case "chat_source_hash_set":
63
+ case "chat_policy_override_set":
64
+ case "chat_compact_failures_set": {
65
+ applyChatLifecycleEvent(state, replayChatProvider, e)
66
+ break
67
+ }
68
+ case "message_appended": {
69
+ applyChatMessageMetadata(state.chatsById, e.chatId, e.entry)
70
+ const existing = legacyMessagesByChatId.get(e.chatId) ?? []
71
+ existing.push({ ...e.entry })
72
+ legacyMessagesByChatId.set(e.chatId, existing)
73
+ break
74
+ }
75
+ case "queued_message_enqueued":
76
+ case "queued_message_removed":
77
+ case "turn_started":
78
+ case "turn_finished":
79
+ case "turn_failed":
80
+ case "turn_cancelled":
81
+ case "session_token_set":
82
+ case "session_commands_loaded":
83
+ case "pending_fork_session_token_set": {
84
+ applyChatLifecycleEvent(state, replayChatProvider, e)
85
+ break
86
+ }
87
+ case "stack_added":
88
+ case "stack_removed":
89
+ case "stack_renamed":
90
+ case "stack_project_added":
91
+ case "stack_project_removed": {
92
+ applyStackEvent(state.stacksById, e)
93
+ break
94
+ }
95
+ case "subagent_run_started":
96
+ case "subagent_message_delta":
97
+ case "subagent_entry_appended":
98
+ case "subagent_run_completed":
99
+ case "subagent_run_failed":
100
+ case "subagent_run_cancelled":
101
+ case "subagent_tool_pending":
102
+ case "subagent_tool_resolved": {
103
+ applySubagentEvent(state.subagentRunsByChatId, e)
104
+ break
105
+ }
106
+ case "tool_request_put":
107
+ case "tool_request_resolved": {
108
+ applyToolRequestEvent(state.toolRequestsById, e)
109
+ break
110
+ }
111
+ case "orch_run_created":
112
+ case "orch_worktree_provisioned":
113
+ case "orch_worktree_init_started":
114
+ case "orch_worktree_init_completed":
115
+ case "orch_task_claimed":
116
+ case "orch_phase_started":
117
+ case "orch_phase_completed":
118
+ case "orch_gate_opened":
119
+ case "orch_gate_resolved":
120
+ case "orch_scope_overlap_flagged":
121
+ case "orch_config_warning":
122
+ case "orch_verify_started":
123
+ case "orch_verify_completed":
124
+ case "orch_task_committed":
125
+ case "orch_task_failed":
126
+ case "orch_task_requeued":
127
+ case "orch_run_completed":
128
+ case "orch_run_cancelled":
129
+ applyOrchEvent(state.orchRunsById, e)
130
+ break
131
+ }
132
+ }