@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.
Files changed (186) hide show
  1. package/dist/client/assets/{arc-CAysfmX5.js → arc-DXtN3EWJ.js} +1 -1
  2. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
  3. package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
  4. package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
  5. package/dist/client/assets/channel-BEYTrUAm.js +1 -0
  6. package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
  7. package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
  8. package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
  9. package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
  10. package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
  13. package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
  14. package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
  15. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
  16. package/dist/client/assets/{cose-bilkent-S5V4N54A-YgpMurXf.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
  17. package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
  18. package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
  19. package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
  20. package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
  21. package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
  22. package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
  23. package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
  24. package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
  25. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
  26. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
  27. package/dist/client/assets/{index-CbSY9HfS.js → index-BqUY1Bm4.js} +1 -1
  28. package/dist/client/assets/{index-C0U2kwuB.js → index-TU1k6ov6.js} +5 -5
  29. package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
  30. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
  31. package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
  32. package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
  33. package/dist/client/assets/{linear-DLR6miy3.js → linear-ByKHtsIx.js} +1 -1
  34. package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-Dl_f2KLc.js} +4 -4
  35. package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
  36. package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
  37. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
  38. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
  39. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
  40. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
  41. package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
  42. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
  43. package/dist/client/assets/{timeline-definition-PNZ67QCA-ByWRdpQX.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
  44. package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
  45. package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
  46. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
  47. package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
  48. package/dist/client/index.html +1 -1
  49. package/package.json +1 -1
  50. package/src/server/agent-coordinator-types.ts +158 -0
  51. package/src/server/agent-coordinator.ts +919 -0
  52. package/src/server/agent-deps-builders.ts +425 -0
  53. package/src/server/agent.test.ts +22 -6
  54. package/src/server/agent.ts +35 -4987
  55. package/src/server/claude-autocontinue-commands.test.ts +494 -0
  56. package/src/server/claude-autocontinue-commands.ts +256 -0
  57. package/src/server/claude-cancel-handler.test.ts +472 -0
  58. package/src/server/claude-cancel-handler.ts +241 -0
  59. package/src/server/claude-chat-management.test.ts +395 -0
  60. package/src/server/claude-chat-management.ts +251 -0
  61. package/src/server/claude-harness-stream.test.ts +273 -0
  62. package/src/server/claude-harness-stream.ts +158 -0
  63. package/src/server/claude-loop-orch-commands.test.ts +579 -0
  64. package/src/server/claude-loop-orch-commands.ts +466 -0
  65. package/src/server/claude-message-normalizer.test.ts +213 -0
  66. package/src/server/claude-message-normalizer.ts +436 -0
  67. package/src/server/claude-prompt-helpers.test.ts +248 -0
  68. package/src/server/claude-prompt-helpers.ts +139 -0
  69. package/src/server/claude-sdk-queue.test.ts +169 -0
  70. package/src/server/claude-sdk-queue.ts +82 -0
  71. package/src/server/claude-send-command.test.ts +414 -0
  72. package/src/server/claude-send-command.ts +420 -0
  73. package/src/server/claude-session-config-helpers.test.ts +279 -0
  74. package/src/server/claude-session-config-helpers.ts +151 -0
  75. package/src/server/claude-session-config.test.ts +250 -0
  76. package/src/server/claude-session-config.ts +159 -0
  77. package/src/server/claude-session-error-handler.test.ts +498 -0
  78. package/src/server/claude-session-error-handler.ts +361 -0
  79. package/src/server/claude-session-lifecycle.test.ts +669 -0
  80. package/src/server/claude-session-lifecycle.ts +283 -0
  81. package/src/server/claude-session-rebuild.test.ts +224 -0
  82. package/src/server/claude-session-rebuild.ts +101 -0
  83. package/src/server/claude-session-runner.test.ts +523 -0
  84. package/src/server/claude-session-runner.ts +521 -0
  85. package/src/server/claude-session-spawner.test.ts +401 -0
  86. package/src/server/claude-session-spawner.ts +401 -0
  87. package/src/server/claude-session-start.test.ts +205 -0
  88. package/src/server/claude-session-start.ts +277 -0
  89. package/src/server/claude-session-state-queries.test.ts +344 -0
  90. package/src/server/claude-session-state-queries.ts +160 -0
  91. package/src/server/claude-session-state.ts +104 -0
  92. package/src/server/claude-slash-commands.test.ts +277 -0
  93. package/src/server/claude-slash-commands.ts +234 -0
  94. package/src/server/claude-spawn-helpers.test.ts +221 -0
  95. package/src/server/claude-spawn-helpers.ts +179 -0
  96. package/src/server/claude-steer-log.test.ts +243 -0
  97. package/src/server/claude-steer-log.ts +58 -0
  98. package/src/server/claude-subagent-tool-response.test.ts +213 -0
  99. package/src/server/claude-subagent-tool-response.ts +165 -0
  100. package/src/server/claude-subagent-wiring.test.ts +263 -0
  101. package/src/server/claude-subagent-wiring.ts +324 -0
  102. package/src/server/claude-tool-respond.test.ts +321 -0
  103. package/src/server/claude-tool-respond.ts +142 -0
  104. package/src/server/claude-turn-runner.test.ts +310 -0
  105. package/src/server/claude-turn-runner.ts +228 -0
  106. package/src/server/claude-turn-starter.test.ts +266 -0
  107. package/src/server/claude-turn-starter.ts +566 -0
  108. package/src/server/claude-usage-math.test.ts +169 -0
  109. package/src/server/claude-usage-math.ts +132 -0
  110. package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
  111. package/src/server/diff-store-branch-ops.adapter.ts +602 -0
  112. package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
  113. package/src/server/diff-store-commit-ops.adapter.ts +258 -0
  114. package/src/server/diff-store-errors.ts +85 -0
  115. package/src/server/diff-store-file-ops.adapter.ts +212 -0
  116. package/src/server/diff-store-git-branch.adapter.ts +686 -0
  117. package/src/server/diff-store-io.adapter.ts +27 -0
  118. package/src/server/diff-store-parse.test.ts +212 -0
  119. package/src/server/diff-store-parse.ts +122 -0
  120. package/src/server/diff-store-state.ts +82 -0
  121. package/src/server/diff-store.ts +76 -1854
  122. package/src/server/event-store-apply.test.ts +280 -0
  123. package/src/server/event-store-apply.ts +132 -0
  124. package/src/server/event-store-chat-lifecycle.test.ts +408 -0
  125. package/src/server/event-store-chat-lifecycle.ts +441 -0
  126. package/src/server/event-store-entity-write.ts +356 -0
  127. package/src/server/event-store-helpers.test.ts +230 -0
  128. package/src/server/event-store-helpers.ts +235 -0
  129. package/src/server/event-store-init.ts +276 -0
  130. package/src/server/event-store-messages.adapter.test.ts +365 -0
  131. package/src/server/event-store-messages.adapter.ts +213 -0
  132. package/src/server/event-store-orch-subscription.ts +124 -0
  133. package/src/server/event-store-orch.test.ts +462 -0
  134. package/src/server/event-store-orch.ts +312 -0
  135. package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
  136. package/src/server/event-store-peripheral-events.adapter.ts +105 -0
  137. package/src/server/event-store-snapshot.test.ts +597 -0
  138. package/src/server/event-store-snapshot.ts +658 -0
  139. package/src/server/event-store-subagent.test.ts +408 -0
  140. package/src/server/event-store-subagent.ts +198 -0
  141. package/src/server/event-store-tool-requests.test.ts +210 -0
  142. package/src/server/event-store-tool-requests.ts +131 -0
  143. package/src/server/event-store-transcript-write.adapter.ts +285 -0
  144. package/src/server/event-store-write-ops.test.ts +554 -0
  145. package/src/server/event-store-write-ops.ts +529 -0
  146. package/src/server/event-store.test.ts +35 -0
  147. package/src/server/event-store.ts +251 -2285
  148. package/src/server/harness-types.ts +15 -1
  149. package/src/server/oauth-errors.ts +19 -0
  150. package/src/server/ws-router-agent-ctrl.test.ts +187 -0
  151. package/src/server/ws-router-agent-ctrl.ts +137 -0
  152. package/src/server/ws-router-broadcast.ts +488 -0
  153. package/src/server/ws-router-chat.test.ts +461 -0
  154. package/src/server/ws-router-chat.ts +279 -0
  155. package/src/server/ws-router-defaults.test.ts +283 -0
  156. package/src/server/ws-router-defaults.ts +434 -0
  157. package/src/server/ws-router-diff.test.ts +167 -0
  158. package/src/server/ws-router-diff.ts +250 -0
  159. package/src/server/ws-router-envelope.ts +329 -0
  160. package/src/server/ws-router-misc.test.ts +422 -0
  161. package/src/server/ws-router-misc.ts +265 -0
  162. package/src/server/ws-router-orch.test.ts +190 -0
  163. package/src/server/ws-router-orch.ts +93 -0
  164. package/src/server/ws-router-project.test.ts +403 -0
  165. package/src/server/ws-router-project.ts +256 -0
  166. package/src/server/ws-router-push.test.ts +193 -0
  167. package/src/server/ws-router-push.ts +120 -0
  168. package/src/server/ws-router-settings.test.ts +173 -0
  169. package/src/server/ws-router-settings.ts +416 -0
  170. package/src/server/ws-router-skills.test.ts +194 -0
  171. package/src/server/ws-router-skills.ts +204 -0
  172. package/src/server/ws-router-utils.ts +224 -0
  173. package/src/server/ws-router.ts +339 -2254
  174. package/src/shared/app-settings-types.ts +365 -0
  175. package/src/shared/core-types.ts +27 -0
  176. package/src/shared/git-diff-types.ts +165 -0
  177. package/src/shared/mcp-types.ts +105 -0
  178. package/src/shared/provider-model-types.ts +437 -0
  179. package/src/shared/subagent-types.ts +198 -0
  180. package/src/shared/tool-call-types.ts +314 -0
  181. package/src/shared/transcript-types.ts +270 -0
  182. package/src/shared/types.ts +25 -1711
  183. package/dist/client/assets/channel-KQhRwDzJ.js +0 -1
  184. package/dist/client/assets/classDiagram-4FO5ZUOK-ChxT1GNq.js +0 -1
  185. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ChxT1GNq.js +0 -1
  186. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D4X-ZnLV.js +0 -1
@@ -0,0 +1,250 @@
1
+ /**
2
+ * ws-router-diff.ts
3
+ *
4
+ * WS command handlers for all diff/git operations (chat.refreshDiffs,
5
+ * chat.initGit, chat.getGitHubPublishInfo, chat.checkGitHubRepoAvailability,
6
+ * chat.publishToGitHub, chat.listBranches, chat.previewMergeBranch,
7
+ * chat.mergeBranch, chat.checkoutBranch, chat.syncBranch, chat.createBranch,
8
+ * chat.generateCommitMessage, chat.commitDiffs, chat.discardDiffFile,
9
+ * chat.ignoreDiffFile) extracted from ws-router.ts.
10
+ *
11
+ * All 15 handlers delegate exclusively to the injected DiffStoreDep and follow
12
+ * the same pattern — look up the project, call the store, ack, broadcast if
13
+ * the snapshot changed. No closure dependencies on createWsRouter locals.
14
+ */
15
+ import { PROTOCOL_VERSION } from "../shared/types"
16
+ import type { ClientCommand, ServerEnvelope } from "../shared/protocol"
17
+ import type { DiffStore } from "./diff-store"
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Dep interface (duck-typed; avoids circular imports with ws-router.ts)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** The subset of DiffStore methods consumed by diff/git WS commands. */
24
+ export type DiffStoreDep = Pick<
25
+ DiffStore,
26
+ | "refreshSnapshot"
27
+ | "initializeGit"
28
+ | "getGitHubPublishInfo"
29
+ | "checkGitHubRepoAvailability"
30
+ | "publishToGitHub"
31
+ | "listBranches"
32
+ | "previewMergeBranch"
33
+ | "mergeBranch"
34
+ | "checkoutBranch"
35
+ | "syncBranch"
36
+ | "createBranch"
37
+ | "generateCommitMessage"
38
+ | "commitFiles"
39
+ | "discardFile"
40
+ | "ignoreFile"
41
+ >
42
+
43
+ export interface DiffCommandDeps {
44
+ /** Resolved DiffStore (or its no-op fallback). */
45
+ resolvedDiffStore: DiffStoreDep
46
+ /** Resolve the project for a given chatId — throws if not found. */
47
+ resolveChatProject: (chatId: string) => { project: { id: string; localPath: string } }
48
+ /** Pre-bound to the current WebSocket; called to send an ack envelope. */
49
+ send: (envelope: ServerEnvelope) => void
50
+ /** Called after any operation that may have changed the diff snapshot. */
51
+ broadcastSnapshots: () => void
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Command dispatcher
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Handle one diff/git WS command.
60
+ *
61
+ * Returns `true` when the command was handled (caller should `return`).
62
+ * Returns `false` when the command type is outside this module's scope.
63
+ */
64
+ export async function handleDiffCommand(
65
+ deps: DiffCommandDeps,
66
+ command: ClientCommand,
67
+ id: string,
68
+ ): Promise<boolean> {
69
+ const { resolvedDiffStore, resolveChatProject, send, broadcastSnapshots } = deps
70
+
71
+ switch (command.type) {
72
+ case "chat.refreshDiffs": {
73
+ const { project } = resolveChatProject(command.chatId)
74
+ const changed = await resolvedDiffStore.refreshSnapshot(project.id, project.localPath)
75
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
76
+ if (changed) {
77
+ void broadcastSnapshots()
78
+ }
79
+ return true
80
+ }
81
+ case "chat.initGit": {
82
+ const { project } = resolveChatProject(command.chatId)
83
+ const result = await resolvedDiffStore.initializeGit({
84
+ projectId: project.id,
85
+ projectPath: project.localPath,
86
+ })
87
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
88
+ if (result.snapshotChanged) {
89
+ void broadcastSnapshots()
90
+ }
91
+ return true
92
+ }
93
+ case "chat.getGitHubPublishInfo": {
94
+ const { project } = resolveChatProject(command.chatId)
95
+ const result = await resolvedDiffStore.getGitHubPublishInfo({
96
+ projectPath: project.localPath,
97
+ })
98
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
99
+ return true
100
+ }
101
+ case "chat.checkGitHubRepoAvailability": {
102
+ const result = await resolvedDiffStore.checkGitHubRepoAvailability({
103
+ owner: command.owner,
104
+ name: command.name,
105
+ })
106
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
107
+ return true
108
+ }
109
+ case "chat.publishToGitHub": {
110
+ const { project } = resolveChatProject(command.chatId)
111
+ const result = await resolvedDiffStore.publishToGitHub({
112
+ projectId: project.id,
113
+ projectPath: project.localPath,
114
+ owner: command.owner,
115
+ name: command.name,
116
+ visibility: command.visibility,
117
+ description: command.description,
118
+ })
119
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
120
+ if (result.snapshotChanged) {
121
+ void broadcastSnapshots()
122
+ }
123
+ return true
124
+ }
125
+ case "chat.listBranches": {
126
+ const { project } = resolveChatProject(command.chatId)
127
+ const result = await resolvedDiffStore.listBranches({
128
+ projectPath: project.localPath,
129
+ })
130
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
131
+ return true
132
+ }
133
+ case "chat.previewMergeBranch": {
134
+ const { project } = resolveChatProject(command.chatId)
135
+ const result = await resolvedDiffStore.previewMergeBranch({
136
+ projectPath: project.localPath,
137
+ branch: command.branch,
138
+ })
139
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
140
+ return true
141
+ }
142
+ case "chat.mergeBranch": {
143
+ const { project } = resolveChatProject(command.chatId)
144
+ const result = await resolvedDiffStore.mergeBranch({
145
+ projectId: project.id,
146
+ projectPath: project.localPath,
147
+ branch: command.branch,
148
+ })
149
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
150
+ if (result.snapshotChanged) {
151
+ void broadcastSnapshots()
152
+ }
153
+ return true
154
+ }
155
+ case "chat.checkoutBranch": {
156
+ const { project } = resolveChatProject(command.chatId)
157
+ const result = await resolvedDiffStore.checkoutBranch({
158
+ projectId: project.id,
159
+ projectPath: project.localPath,
160
+ branch: command.branch,
161
+ bringChanges: command.bringChanges,
162
+ })
163
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
164
+ if (result.snapshotChanged) {
165
+ void broadcastSnapshots()
166
+ }
167
+ return true
168
+ }
169
+ case "chat.syncBranch": {
170
+ const { project } = resolveChatProject(command.chatId)
171
+ const result = await resolvedDiffStore.syncBranch({
172
+ projectId: project.id,
173
+ projectPath: project.localPath,
174
+ action: command.action,
175
+ })
176
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
177
+ if (result.snapshotChanged) {
178
+ void broadcastSnapshots()
179
+ }
180
+ return true
181
+ }
182
+ case "chat.createBranch": {
183
+ const { project } = resolveChatProject(command.chatId)
184
+ const result = await resolvedDiffStore.createBranch({
185
+ projectId: project.id,
186
+ projectPath: project.localPath,
187
+ name: command.name,
188
+ baseBranchName: command.baseBranchName,
189
+ })
190
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
191
+ if (result.snapshotChanged) {
192
+ void broadcastSnapshots()
193
+ }
194
+ return true
195
+ }
196
+ case "chat.generateCommitMessage": {
197
+ const { project } = resolveChatProject(command.chatId)
198
+ const result = await resolvedDiffStore.generateCommitMessage({
199
+ projectPath: project.localPath,
200
+ paths: command.paths,
201
+ })
202
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
203
+ return true
204
+ }
205
+ case "chat.commitDiffs": {
206
+ const { project } = resolveChatProject(command.chatId)
207
+ const result = await resolvedDiffStore.commitFiles({
208
+ projectId: project.id,
209
+ projectPath: project.localPath,
210
+ paths: command.paths,
211
+ summary: command.summary,
212
+ description: command.description,
213
+ mode: command.mode,
214
+ })
215
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
216
+ if (result.snapshotChanged) {
217
+ void broadcastSnapshots()
218
+ }
219
+ return true
220
+ }
221
+ case "chat.discardDiffFile": {
222
+ const { project } = resolveChatProject(command.chatId)
223
+ const result = await resolvedDiffStore.discardFile({
224
+ projectId: project.id,
225
+ projectPath: project.localPath,
226
+ path: command.path,
227
+ })
228
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
229
+ if (result.snapshotChanged) {
230
+ void broadcastSnapshots()
231
+ }
232
+ return true
233
+ }
234
+ case "chat.ignoreDiffFile": {
235
+ const { project } = resolveChatProject(command.chatId)
236
+ const result = await resolvedDiffStore.ignoreFile({
237
+ projectId: project.id,
238
+ projectPath: project.localPath,
239
+ path: command.path,
240
+ })
241
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
242
+ if (result.snapshotChanged) {
243
+ void broadcastSnapshots()
244
+ }
245
+ return true
246
+ }
247
+ default:
248
+ return false
249
+ }
250
+ }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * ws-router-envelope.ts
3
+ *
4
+ * Snapshot-envelope computation for the WebSocket router.
5
+ * Extracted from ws-router.ts to reduce its size.
6
+ *
7
+ * `createEnvelopeBuilder(deps)` returns `{ createEnvelope, getSidebarSnapshotCacheEntry }`
8
+ * whose closures capture the stable dep objects — no shared mutable state lives here.
9
+ */
10
+ import os from "node:os"
11
+ import { log } from "../shared/log"
12
+ import { PROTOCOL_VERSION } from "../shared/types"
13
+ import type { ServerEnvelope, SubscriptionTopic } from "../shared/protocol"
14
+ import type { ServerWebSocket } from "bun"
15
+ import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } from "./read-models"
16
+ import { toOrchRunSummary } from "./orchestration-input"
17
+ import type { EventStore } from "./event-store"
18
+ import type { AgentCoordinator } from "./agent"
19
+ import type { TerminalManager } from "./terminal-manager"
20
+ import type { KeybindingsManager } from "./keybindings"
21
+ import type { PtyInstanceRegistry } from "./claude-pty/pty-instance-registry"
22
+ import type { WorkflowRegistry } from "./workflow-registry"
23
+ import type { UpdateManager } from "./update-manager"
24
+ import type { PushManager } from "./push/push-manager"
25
+ import type { DiffStore } from "./diff-store"
26
+ import type { DiscoveredProject } from "./discovery.adapter"
27
+ import {
28
+ getSidebarProjectOrder,
29
+ isSendToStartingProfilingEnabled,
30
+ } from "./ws-router-utils"
31
+ import type { ClientState, SnapshotComputationCache } from "./ws-router-utils"
32
+ import type { ResolvedAppSettings } from "./ws-router-defaults"
33
+
34
+ const DEFAULT_CHAT_RECENT_LIMIT = 200
35
+
36
+ // ── Deps type ─────────────────────────────────────────────────────────────────
37
+
38
+ export interface EnvelopeDeps {
39
+ store: EventStore
40
+ agent: AgentCoordinator
41
+ resolvedAppSettings: ResolvedAppSettings
42
+ keybindings: KeybindingsManager
43
+ resolvedDiffStore: Pick<DiffStore,
44
+ "getProjectSnapshot" | "refreshSnapshot" | "initializeGit" | "getGitHubPublishInfo" |
45
+ "checkGitHubRepoAvailability" | "publishToGitHub" | "listBranches" | "previewMergeBranch" |
46
+ "mergeBranch" | "syncBranch" | "checkoutBranch" | "createBranch" | "generateCommitMessage" |
47
+ "commitFiles" | "discardFile" | "ignoreFile" | "readPatch">
48
+ ptyInstances?: PtyInstanceRegistry
49
+ workflowRegistry?: WorkflowRegistry
50
+ machineDisplayName: string
51
+ updateManager: UpdateManager | null
52
+ getDiscoveredProjects: () => DiscoveredProject[]
53
+ terminals: TerminalManager
54
+ pushManager: PushManager
55
+ }
56
+
57
+ // ── Internal: sidebar cache entry ─────────────────────────────────────────────
58
+
59
+ function buildSidebarSnapshotCacheEntry(
60
+ deps: EnvelopeDeps,
61
+ cache?: SnapshotComputationCache
62
+ ): NonNullable<SnapshotComputationCache["sidebar"]> {
63
+ if (cache?.sidebar) {
64
+ return cache.sidebar
65
+ }
66
+
67
+ const { store, agent, pushManager } = deps
68
+ const startedAt = performance.now()
69
+ const data = deriveSidebarData(store.state, agent.getActiveStatuses(), {
70
+ sidebarProjectOrder: getSidebarProjectOrder(store),
71
+ drainingChatIds: agent.getDrainingChatIds(),
72
+ claudeSessionStates: agent.getClaudeSessionStates?.(),
73
+ })
74
+ const observed = data.projectGroups.flatMap((group) =>
75
+ group.chats.map((chat) => ({
76
+ chatId: chat.chatId,
77
+ projectLocalPath: group.localPath,
78
+ projectTitle: group.localPath.split("/").filter(Boolean).pop() ?? group.localPath,
79
+ chatTitle: chat.title,
80
+ status: chat.status,
81
+ }))
82
+ )
83
+ void pushManager.observeStatuses(observed).catch((error) => {
84
+ log.warn("[kanna/push] observeStatuses failed", { error })
85
+ })
86
+ if (isSendToStartingProfilingEnabled()) {
87
+ const totalChats = data.projectGroups.reduce((count, group) => count + group.chats.length, 0)
88
+ log.info("[kanna/send->starting][server]", JSON.stringify({
89
+ stage: "ws.sidebar_snapshot_built",
90
+ elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
91
+ projectGroupCount: data.projectGroups.length,
92
+ chatCount: totalChats,
93
+ totalChatCount: store.state.chatsById.size,
94
+ totalProjectCount: store.state.projectsById.size,
95
+ }))
96
+ }
97
+
98
+ const sidebar = {
99
+ data,
100
+ signature: JSON.stringify({
101
+ type: "sidebar" as const,
102
+ data,
103
+ }),
104
+ }
105
+
106
+ if (cache) {
107
+ cache.sidebar = sidebar
108
+ }
109
+
110
+ return sidebar
111
+ }
112
+
113
+ // ── Exported factory ──────────────────────────────────────────────────────────
114
+
115
+ export interface EnvelopeBuilder {
116
+ /**
117
+ * Returns a cached sidebar entry (computes it once per cache object).
118
+ * Exposed so callers can read the pre-computed signature for dedup.
119
+ */
120
+ getSidebarSnapshotCacheEntry(cache?: SnapshotComputationCache): NonNullable<SnapshotComputationCache["sidebar"]>
121
+ /**
122
+ * Build the full ServerEnvelope for a given subscription topic.
123
+ */
124
+ createEnvelope(
125
+ id: string,
126
+ topic: SubscriptionTopic,
127
+ cache?: SnapshotComputationCache,
128
+ connection?: ServerWebSocket<ClientState>,
129
+ ): ServerEnvelope
130
+ }
131
+
132
+ export function createEnvelopeBuilder(deps: EnvelopeDeps): EnvelopeBuilder {
133
+ const {
134
+ store,
135
+ agent,
136
+ resolvedAppSettings,
137
+ keybindings,
138
+ resolvedDiffStore,
139
+ ptyInstances,
140
+ workflowRegistry,
141
+ machineDisplayName,
142
+ updateManager,
143
+ getDiscoveredProjects,
144
+ terminals,
145
+ pushManager,
146
+ } = deps
147
+
148
+ function getSidebarSnapshotCacheEntry(cache?: SnapshotComputationCache) {
149
+ return buildSidebarSnapshotCacheEntry(deps, cache)
150
+ }
151
+
152
+ function createEnvelope(
153
+ id: string,
154
+ topic: SubscriptionTopic,
155
+ cache?: SnapshotComputationCache,
156
+ connection?: ServerWebSocket<ClientState>,
157
+ ): ServerEnvelope {
158
+ if (topic.type === "sidebar") {
159
+ const sidebar = getSidebarSnapshotCacheEntry(cache)
160
+ return {
161
+ v: PROTOCOL_VERSION,
162
+ type: "snapshot",
163
+ id,
164
+ snapshot: {
165
+ type: "sidebar",
166
+ data: sidebar.data,
167
+ },
168
+ }
169
+ }
170
+
171
+ if (topic.type === "local-projects") {
172
+ const discoveredProjects = getDiscoveredProjects()
173
+ const data = deriveLocalProjectsSnapshot(store.state, discoveredProjects, machineDisplayName, os.homedir())
174
+
175
+ return {
176
+ v: PROTOCOL_VERSION,
177
+ type: "snapshot",
178
+ id,
179
+ snapshot: {
180
+ type: "local-projects",
181
+ data,
182
+ },
183
+ }
184
+ }
185
+
186
+ if (topic.type === "keybindings") {
187
+ return {
188
+ v: PROTOCOL_VERSION,
189
+ type: "snapshot",
190
+ id,
191
+ snapshot: {
192
+ type: "keybindings",
193
+ data: keybindings.getSnapshot(),
194
+ },
195
+ }
196
+ }
197
+
198
+ if (topic.type === "app-settings") {
199
+ return {
200
+ v: PROTOCOL_VERSION,
201
+ type: "snapshot",
202
+ id,
203
+ snapshot: {
204
+ type: "app-settings",
205
+ data: resolvedAppSettings.getSnapshot(),
206
+ },
207
+ }
208
+ }
209
+
210
+ if (topic.type === "push-config") {
211
+ return {
212
+ v: PROTOCOL_VERSION,
213
+ type: "snapshot",
214
+ id,
215
+ snapshot: {
216
+ type: "push-config",
217
+ data: pushManager.getConfigSnapshot(connection?.data.pushDeviceId ?? null),
218
+ },
219
+ }
220
+ }
221
+
222
+ if (topic.type === "update") {
223
+ return {
224
+ v: PROTOCOL_VERSION,
225
+ type: "snapshot",
226
+ id,
227
+ snapshot: {
228
+ type: "update",
229
+ data: updateManager?.getSnapshot() ?? {
230
+ currentVersion: "unknown",
231
+ latestVersion: null,
232
+ status: "idle",
233
+ updateAvailable: false,
234
+ lastCheckedAt: null,
235
+ error: null,
236
+ installAction: "restart",
237
+ reloadRequestedAt: null,
238
+ },
239
+ },
240
+ }
241
+ }
242
+
243
+ if (topic.type === "terminal") {
244
+ return {
245
+ v: PROTOCOL_VERSION,
246
+ type: "snapshot",
247
+ id,
248
+ snapshot: {
249
+ type: "terminal",
250
+ data: terminals.getSnapshot(topic.terminalId),
251
+ },
252
+ }
253
+ }
254
+
255
+ if (topic.type === "project-git") {
256
+ return {
257
+ v: PROTOCOL_VERSION,
258
+ type: "snapshot",
259
+ id,
260
+ snapshot: {
261
+ type: "project-git",
262
+ data: store.getProject(topic.projectId)
263
+ ? resolvedDiffStore.getProjectSnapshot(topic.projectId)
264
+ : null,
265
+ },
266
+ }
267
+ }
268
+
269
+ if (topic.type === "pty-instances") {
270
+ return {
271
+ v: PROTOCOL_VERSION,
272
+ type: "snapshot",
273
+ id,
274
+ snapshot: {
275
+ type: "pty-instances",
276
+ data: { instances: ptyInstances?.snapshot() ?? [] },
277
+ },
278
+ }
279
+ }
280
+
281
+ if (topic.type === "workflows") {
282
+ return {
283
+ v: PROTOCOL_VERSION,
284
+ type: "snapshot",
285
+ id,
286
+ snapshot: {
287
+ type: "workflows",
288
+ data: { chatId: topic.chatId, runs: workflowRegistry?.snapshot(topic.chatId) ?? [] },
289
+ },
290
+ }
291
+ }
292
+
293
+ if (topic.type === "orch-runs") {
294
+ return {
295
+ v: PROTOCOL_VERSION,
296
+ type: "snapshot",
297
+ id,
298
+ snapshot: {
299
+ type: "orch-runs",
300
+ data: { runs: store.getOrchRuns().map(toOrchRunSummary) },
301
+ },
302
+ }
303
+ }
304
+
305
+ return {
306
+ v: PROTOCOL_VERSION,
307
+ type: "snapshot",
308
+ id,
309
+ snapshot: {
310
+ type: "chat",
311
+ data: deriveChatSnapshot(
312
+ store.state,
313
+ agent.getActiveStatuses(),
314
+ agent.getDrainingChatIds(),
315
+ agent.getSlashCommandsLoadingChatIds(),
316
+ topic.chatId,
317
+ (chatId) => store.getRecentChatHistory(chatId, topic.recentLimit ?? DEFAULT_CHAT_RECENT_LIMIT),
318
+ (chatId) => store.getTunnelEvents(chatId),
319
+ agent.getWaitStartedAtByChatId(),
320
+ Date.now(),
321
+ agent.getClaudeSessionStates?.() ?? new Map(),
322
+ resolvedAppSettings.getSnapshot().customModels ?? [],
323
+ ),
324
+ },
325
+ }
326
+ }
327
+
328
+ return { getSidebarSnapshotCacheEntry, createEnvelope }
329
+ }