@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,422 @@
1
+ /**
2
+ * ws-router-misc.test.ts
3
+ *
4
+ * Unit tests for handleMiscCommand (terminal, message, stack, share groups).
5
+ */
6
+
7
+ import { describe, expect, mock, test } from "bun:test"
8
+ import type {
9
+ MiscAgentDep,
10
+ MiscAnalyticsDep,
11
+ MiscCommandDeps,
12
+ MiscSessionShareDep,
13
+ MiscStoreDep,
14
+ MiscTerminalsDep,
15
+ } from "./ws-router-misc"
16
+ import { handleMiscCommand } from "./ws-router-misc"
17
+ import type { ClientCommand, ServerEnvelope } from "../shared/protocol"
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function makeStore(overrides: Partial<MiscStoreDep> = {}): MiscStoreDep {
24
+ return {
25
+ getProject: mock(() => null),
26
+ createStack: mock(async () => ({ id: "stack-1" })),
27
+ renameStack: mock(async () => {}),
28
+ removeStack: mock(async () => {}),
29
+ addProjectToStack: mock(async () => {}),
30
+ removeProjectFromStack: mock(async () => {}),
31
+ ...overrides,
32
+ }
33
+ }
34
+
35
+ function makeTerminals(overrides: Partial<MiscTerminalsDep> = {}): MiscTerminalsDep {
36
+ return {
37
+ createTerminal: mock(() => ({
38
+ terminalId: "term-1",
39
+ title: "bash",
40
+ cwd: "/tmp",
41
+ shell: "/bin/bash",
42
+ cols: 80,
43
+ rows: 24,
44
+ scrollback: 1000,
45
+ serializedState: "",
46
+ status: "running" as const,
47
+ exitCode: null,
48
+ })),
49
+ write: mock(() => {}),
50
+ resize: mock(() => {}),
51
+ close: mock(() => {}),
52
+ ...overrides,
53
+ }
54
+ }
55
+
56
+ function makeAgent(overrides: Partial<MiscAgentDep> = {}): MiscAgentDep {
57
+ return {
58
+ enqueue: mock(async () => ({ queuedMessageId: "msg-1" })),
59
+ steer: mock(async () => {}),
60
+ dequeue: mock(async () => {}),
61
+ ...overrides,
62
+ }
63
+ }
64
+
65
+ function makeSessionShare(overrides: Partial<MiscSessionShareDep> = {}): MiscSessionShareDep {
66
+ return {
67
+ mintToken: mock(async () => ({
68
+ ok: true as const,
69
+ data: {
70
+ summary: {
71
+ tokenId: "tok-1",
72
+ chatId: "chat-1",
73
+ createdAt: 0,
74
+ expiresAt: 0,
75
+ url: "https://example.com/share/tok-1",
76
+ revoked: false,
77
+ },
78
+ },
79
+ })),
80
+ revokeToken: mock(async () => ({ ok: true as const, data: { tokenId: "tok-1" } })),
81
+ listSharesForChat: mock(() => []),
82
+ ...overrides,
83
+ }
84
+ }
85
+
86
+ interface TestDeps extends MiscCommandDeps {
87
+ sent: unknown[]
88
+ sideBroadcasts: number
89
+ chatBroadcasts: string[]
90
+ pushSnapshots: string[]
91
+ worktreeCallPaths: string[]
92
+ }
93
+
94
+ function makeDeps(opts: {
95
+ storeOverrides?: Partial<MiscStoreDep>
96
+ terminalsOverrides?: Partial<MiscTerminalsDep>
97
+ agentOverrides?: Partial<MiscAgentDep>
98
+ sessionShare?: MiscSessionShareDep | null
99
+ analyticsOverrides?: Partial<MiscAnalyticsDep>
100
+ worktreeResult?: unknown[]
101
+ originHost?: string
102
+ } = {}): TestDeps {
103
+ const sent: unknown[] = []
104
+ const sideBroadcasts: number[] = []
105
+ const chatBroadcasts: string[] = []
106
+ const pushSnapshots: string[] = []
107
+ const worktreeCallPaths: string[] = []
108
+
109
+ return {
110
+ store: makeStore(opts.storeOverrides),
111
+ terminals: makeTerminals(opts.terminalsOverrides),
112
+ agent: makeAgent(opts.agentOverrides),
113
+ sessionShare: opts.sessionShare !== undefined ? opts.sessionShare : null,
114
+ analytics: { track: mock(() => {}) },
115
+ listWorktrees: async (path) => {
116
+ worktreeCallPaths.push(path)
117
+ return (opts.worktreeResult ?? []) as import("../shared/types").GitWorktree[]
118
+ },
119
+ getOriginHost: () => opts.originHost ?? "http://localhost:3000",
120
+ send: (envelope: ServerEnvelope) => { sent.push(envelope) },
121
+ broadcastSidebar: async () => { sideBroadcasts.push(1) },
122
+ broadcastChatAndSidebar: async (chatId) => { chatBroadcasts.push(chatId) },
123
+ pushTerminalSnapshot: (terminalId) => { pushSnapshots.push(terminalId) },
124
+ sent,
125
+ get sideBroadcasts() { return sideBroadcasts.length },
126
+ chatBroadcasts,
127
+ pushSnapshots,
128
+ worktreeCallPaths,
129
+ }
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Unknown command
134
+ // ---------------------------------------------------------------------------
135
+
136
+ describe("handleMiscCommand", () => {
137
+ test("returns false for an unrelated command type", async () => {
138
+ const deps = makeDeps()
139
+ const handled = await handleMiscCommand(
140
+ deps,
141
+ { type: "settings.readAppSettings" } as unknown as ClientCommand,
142
+ "r0",
143
+ )
144
+ expect(handled).toBe(false)
145
+ expect(deps.sent).toHaveLength(0)
146
+ })
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // message.*
150
+ // ---------------------------------------------------------------------------
151
+
152
+ test("message.enqueue — calls agent.enqueue, acks with result, broadcasts chat+sidebar", async () => {
153
+ const deps = makeDeps({ agentOverrides: { enqueue: mock(async () => ({ queuedMessageId: "q-1" })) } })
154
+ const cmd: ClientCommand = {
155
+ type: "message.enqueue",
156
+ chatId: "chat-1",
157
+ content: "Hello",
158
+ attachments: [],
159
+ }
160
+ const handled = await handleMiscCommand(deps, cmd, "r1")
161
+ expect(handled).toBe(true)
162
+ expect(deps.agent.enqueue as ReturnType<typeof mock>).toHaveBeenCalledWith(cmd)
163
+ const ack = deps.sent[0] as { type: string; result: unknown }
164
+ expect(ack.type).toBe("ack")
165
+ expect(ack.result).toEqual({ queuedMessageId: "q-1" })
166
+ expect(deps.chatBroadcasts).toContain("chat-1")
167
+ })
168
+
169
+ test("message.steer — calls agent.steer, acks, broadcasts chat+sidebar", async () => {
170
+ const deps = makeDeps()
171
+ const cmd: ClientCommand = {
172
+ type: "message.steer",
173
+ chatId: "chat-2",
174
+ queuedMessageId: "q-2",
175
+ }
176
+ const handled = await handleMiscCommand(deps, cmd, "r2")
177
+ expect(handled).toBe(true)
178
+ expect(deps.agent.steer as ReturnType<typeof mock>).toHaveBeenCalledWith(cmd)
179
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
180
+ expect(deps.chatBroadcasts).toContain("chat-2")
181
+ })
182
+
183
+ test("message.dequeue — calls agent.dequeue, acks, broadcasts chat+sidebar", async () => {
184
+ const deps = makeDeps()
185
+ const cmd: ClientCommand = {
186
+ type: "message.dequeue",
187
+ chatId: "chat-3",
188
+ queuedMessageId: "q-3",
189
+ }
190
+ const handled = await handleMiscCommand(deps, cmd, "r3")
191
+ expect(handled).toBe(true)
192
+ expect(deps.agent.dequeue as ReturnType<typeof mock>).toHaveBeenCalledWith(cmd)
193
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
194
+ expect(deps.chatBroadcasts).toContain("chat-3")
195
+ })
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // terminal.*
199
+ // ---------------------------------------------------------------------------
200
+
201
+ test("terminal.create — calls createTerminal with project path, acks with snapshot", async () => {
202
+ const deps = makeDeps({
203
+ storeOverrides: {
204
+ getProject: mock(() => ({ localPath: "/home/user/project" })),
205
+ },
206
+ })
207
+ const cmd: ClientCommand = {
208
+ type: "terminal.create",
209
+ projectId: "proj-1",
210
+ terminalId: "term-1",
211
+ cols: 120,
212
+ rows: 40,
213
+ scrollback: 5000,
214
+ }
215
+ const handled = await handleMiscCommand(deps, cmd, "r4")
216
+ expect(handled).toBe(true)
217
+ const createFn = deps.terminals.createTerminal as ReturnType<typeof mock>
218
+ expect(createFn).toHaveBeenCalledWith({
219
+ projectPath: "/home/user/project",
220
+ terminalId: "term-1",
221
+ cols: 120,
222
+ rows: 40,
223
+ scrollback: 5000,
224
+ })
225
+ const ack = deps.sent[0] as { type: string; result: unknown }
226
+ expect(ack.type).toBe("ack")
227
+ expect(ack.result).toBeDefined()
228
+ })
229
+
230
+ test("terminal.create — throws when project not found", async () => {
231
+ const deps = makeDeps({ storeOverrides: { getProject: mock(() => null) } })
232
+ const cmd: ClientCommand = {
233
+ type: "terminal.create",
234
+ projectId: "missing",
235
+ terminalId: "term-x",
236
+ cols: 80,
237
+ rows: 24,
238
+ scrollback: 1000,
239
+ }
240
+ await expect(handleMiscCommand(deps, cmd, "r5")).rejects.toThrow("Project not found")
241
+ })
242
+
243
+ test("terminal.input — calls terminals.write, acks", async () => {
244
+ const deps = makeDeps()
245
+ const cmd: ClientCommand = { type: "terminal.input", terminalId: "term-1", data: "ls\r" }
246
+ const handled = await handleMiscCommand(deps, cmd, "r6")
247
+ expect(handled).toBe(true)
248
+ expect(deps.terminals.write as ReturnType<typeof mock>).toHaveBeenCalledWith("term-1", "ls\r")
249
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
250
+ })
251
+
252
+ test("terminal.resize — calls terminals.resize, acks", async () => {
253
+ const deps = makeDeps()
254
+ const cmd: ClientCommand = { type: "terminal.resize", terminalId: "term-1", cols: 200, rows: 50 }
255
+ const handled = await handleMiscCommand(deps, cmd, "r7")
256
+ expect(handled).toBe(true)
257
+ expect(deps.terminals.resize as ReturnType<typeof mock>).toHaveBeenCalledWith("term-1", 200, 50)
258
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
259
+ })
260
+
261
+ test("terminal.close — calls terminals.close, acks, then pushes terminal snapshot", async () => {
262
+ const deps = makeDeps()
263
+ const cmd: ClientCommand = { type: "terminal.close", terminalId: "term-2" }
264
+ const handled = await handleMiscCommand(deps, cmd, "r8")
265
+ expect(handled).toBe(true)
266
+ expect(deps.terminals.close as ReturnType<typeof mock>).toHaveBeenCalledWith("term-2")
267
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
268
+ expect(deps.pushSnapshots).toContain("term-2")
269
+ })
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // stack.*
273
+ // ---------------------------------------------------------------------------
274
+
275
+ test("stack.create — creates stack, acks with stackId, tracks analytics, broadcasts sidebar", async () => {
276
+ const deps = makeDeps({
277
+ storeOverrides: { createStack: mock(async () => ({ id: "stack-99" })) },
278
+ })
279
+ const cmd: ClientCommand = { type: "stack.create", title: "My Stack", projectIds: ["p1", "p2"] }
280
+ const handled = await handleMiscCommand(deps, cmd, "r9")
281
+ expect(handled).toBe(true)
282
+ expect(deps.store.createStack as ReturnType<typeof mock>).toHaveBeenCalledWith("My Stack", ["p1", "p2"])
283
+ const ack = deps.sent[0] as { type: string; result: { stackId: string } }
284
+ expect(ack.result).toEqual({ stackId: "stack-99" })
285
+ expect(deps.analytics.track as ReturnType<typeof mock>).toHaveBeenCalledWith("stack_created")
286
+ expect(deps.sideBroadcasts).toBeGreaterThan(0)
287
+ })
288
+
289
+ test("stack.rename — renames, acks, broadcasts sidebar", async () => {
290
+ const deps = makeDeps()
291
+ const cmd: ClientCommand = { type: "stack.rename", stackId: "s-1", title: "New Title" }
292
+ const handled = await handleMiscCommand(deps, cmd, "r10")
293
+ expect(handled).toBe(true)
294
+ expect(deps.store.renameStack as ReturnType<typeof mock>).toHaveBeenCalledWith("s-1", "New Title")
295
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
296
+ expect(deps.sideBroadcasts).toBeGreaterThan(0)
297
+ })
298
+
299
+ test("stack.remove — removes, acks, broadcasts sidebar", async () => {
300
+ const deps = makeDeps()
301
+ const cmd: ClientCommand = { type: "stack.remove", stackId: "s-2" }
302
+ const handled = await handleMiscCommand(deps, cmd, "r11")
303
+ expect(handled).toBe(true)
304
+ expect(deps.store.removeStack as ReturnType<typeof mock>).toHaveBeenCalledWith("s-2")
305
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
306
+ expect(deps.sideBroadcasts).toBeGreaterThan(0)
307
+ })
308
+
309
+ test("stack.addProject — adds project, acks, broadcasts sidebar", async () => {
310
+ const deps = makeDeps()
311
+ const cmd: ClientCommand = { type: "stack.addProject", stackId: "s-3", projectId: "proj-5" }
312
+ const handled = await handleMiscCommand(deps, cmd, "r12")
313
+ expect(handled).toBe(true)
314
+ expect(deps.store.addProjectToStack as ReturnType<typeof mock>).toHaveBeenCalledWith("s-3", "proj-5")
315
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
316
+ expect(deps.sideBroadcasts).toBeGreaterThan(0)
317
+ })
318
+
319
+ test("stack.removeProject — removes project, acks, broadcasts sidebar", async () => {
320
+ const deps = makeDeps()
321
+ const cmd: ClientCommand = { type: "stack.removeProject", stackId: "s-4", projectId: "proj-6" }
322
+ const handled = await handleMiscCommand(deps, cmd, "r13")
323
+ expect(handled).toBe(true)
324
+ expect(deps.store.removeProjectFromStack as ReturnType<typeof mock>).toHaveBeenCalledWith("s-4", "proj-6")
325
+ expect((deps.sent[0] as { type: string }).type).toBe("ack")
326
+ })
327
+
328
+ test("stack.listWorktrees — lists worktrees for project path, acks with result", async () => {
329
+ const fakeWorktrees = [{ branch: "main", path: "/repo" }]
330
+ const deps = makeDeps({
331
+ storeOverrides: { getProject: mock(() => ({ localPath: "/my/repo" })) },
332
+ worktreeResult: fakeWorktrees,
333
+ })
334
+ const cmd: ClientCommand = { type: "stack.listWorktrees", projectId: "proj-w" }
335
+ const handled = await handleMiscCommand(deps, cmd, "r14")
336
+ expect(handled).toBe(true)
337
+ expect(deps.worktreeCallPaths).toContain("/my/repo")
338
+ const ack = deps.sent[0] as { type: string; result: { worktrees: unknown[] } }
339
+ expect(ack.result.worktrees).toEqual(fakeWorktrees)
340
+ })
341
+
342
+ test("stack.listWorktrees — throws when project not found", async () => {
343
+ const deps = makeDeps({ storeOverrides: { getProject: mock(() => null) } })
344
+ const cmd: ClientCommand = { type: "stack.listWorktrees", projectId: "missing" }
345
+ await expect(handleMiscCommand(deps, cmd, "r15")).rejects.toThrow("Project not found")
346
+ })
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // share.*
350
+ // ---------------------------------------------------------------------------
351
+
352
+ test("share.mint — mints token, acks with ok result when service available", async () => {
353
+ const shareService = makeSessionShare()
354
+ const deps = makeDeps({ sessionShare: shareService })
355
+ const cmd: ClientCommand = {
356
+ type: "share.mint",
357
+ payload: { chatId: "chat-share-1", ttlHours: 3600 },
358
+ }
359
+ const handled = await handleMiscCommand(deps, cmd, "r16")
360
+ expect(handled).toBe(true)
361
+ expect(shareService.mintToken as ReturnType<typeof mock>).toHaveBeenCalledWith(
362
+ { chatId: "chat-share-1", ttlHours: 3600 },
363
+ "http://localhost:3000",
364
+ )
365
+ const ack = deps.sent[0] as { type: string; result: { ok: boolean; kind?: string } }
366
+ expect(ack.result.ok).toBe(true)
367
+ expect(ack.result.kind).toBe("mint")
368
+ })
369
+
370
+ test("share.mint — acks with unavailable error when no sessionShare", async () => {
371
+ const deps = makeDeps({ sessionShare: null })
372
+ const cmd: ClientCommand = { type: "share.mint", payload: { chatId: "c-1", ttlHours: 60 } }
373
+ const handled = await handleMiscCommand(deps, cmd, "r17")
374
+ expect(handled).toBe(true)
375
+ const ack = deps.sent[0] as { type: string; result: { ok: boolean; error?: unknown } }
376
+ expect(ack.result.ok).toBe(false)
377
+ expect(ack.result.error).toBeDefined()
378
+ })
379
+
380
+ test("share.revoke — revokes token, acks with ok result when service available", async () => {
381
+ const shareService = makeSessionShare()
382
+ const deps = makeDeps({ sessionShare: shareService })
383
+ const cmd: ClientCommand = { type: "share.revoke", payload: { tokenId: "tok-1" } }
384
+ const handled = await handleMiscCommand(deps, cmd, "r18")
385
+ expect(handled).toBe(true)
386
+ const ack = deps.sent[0] as { type: string; result: { ok: boolean; kind?: string } }
387
+ expect(ack.result.ok).toBe(true)
388
+ expect(ack.result.kind).toBe("revoke")
389
+ })
390
+
391
+ test("share.revoke — acks with not_found error when no sessionShare", async () => {
392
+ const deps = makeDeps({ sessionShare: null })
393
+ const cmd: ClientCommand = { type: "share.revoke", payload: { tokenId: "tok-x" } }
394
+ const handled = await handleMiscCommand(deps, cmd, "r19")
395
+ expect(handled).toBe(true)
396
+ const ack = deps.sent[0] as { type: string; result: { ok: boolean } }
397
+ expect(ack.result.ok).toBe(false)
398
+ })
399
+
400
+ test("share.list — lists shares for chat when service available", async () => {
401
+ const shareService = makeSessionShare({ listSharesForChat: mock(() => [{ tokenId: "t-1", chatId: "chat-s", createdAt: 0, expiresAt: 0, url: "https://u", revoked: false }]) })
402
+ const deps = makeDeps({ sessionShare: shareService, originHost: "https://myapp.com" })
403
+ const cmd: ClientCommand = { type: "share.list", payload: { chatId: "chat-s" } }
404
+ const handled = await handleMiscCommand(deps, cmd, "r20")
405
+ expect(handled).toBe(true)
406
+ expect(shareService.listSharesForChat as ReturnType<typeof mock>).toHaveBeenCalledWith("chat-s", "https://myapp.com")
407
+ const ack = deps.sent[0] as { type: string; result: { ok: boolean; kind: string; data: { shares: unknown[] } } }
408
+ expect(ack.result.ok).toBe(true)
409
+ expect(ack.result.kind).toBe("list")
410
+ expect(ack.result.data.shares).toHaveLength(1)
411
+ })
412
+
413
+ test("share.list — acks with empty shares when no sessionShare", async () => {
414
+ const deps = makeDeps({ sessionShare: null })
415
+ const cmd: ClientCommand = { type: "share.list", payload: { chatId: "chat-empty" } }
416
+ const handled = await handleMiscCommand(deps, cmd, "r21")
417
+ expect(handled).toBe(true)
418
+ const ack = deps.sent[0] as { type: string; result: { ok: boolean; data: { shares: unknown[] } } }
419
+ expect(ack.result.ok).toBe(true)
420
+ expect(ack.result.data.shares).toHaveLength(0)
421
+ })
422
+ })
@@ -0,0 +1,265 @@
1
+ /**
2
+ * ws-router-misc.ts
3
+ *
4
+ * WS command handlers for terminal, message-queue, stack, and share operations:
5
+ * terminal.create, terminal.input, terminal.resize, terminal.close
6
+ * message.enqueue, message.steer, message.dequeue
7
+ * stack.create, stack.rename, stack.remove, stack.addProject,
8
+ * stack.removeProject, stack.listWorktrees
9
+ * share.mint, share.revoke, share.list
10
+ *
11
+ * Extracted from ws-router.ts.
12
+ */
13
+
14
+ import { PROTOCOL_VERSION } from "../shared/types"
15
+ import type { GitWorktree } from "../shared/types"
16
+ import type { ClientCommand, ServerEnvelope } from "../shared/protocol"
17
+ import type { TerminalSnapshot } from "../shared/protocol"
18
+ import type { ShareCommandResult } from "../shared/session-share/protocol"
19
+ import type { MintRequest, MintResponse, RevokeRequest, ShareError, ShareSummary } from "../shared/session-share/types"
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Dep interfaces (duck-typed; avoids circular imports with ws-router.ts)
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /** The subset of EventStore methods consumed by misc WS commands. */
26
+ export interface MiscStoreDep {
27
+ getProject(id: string): { localPath: string } | undefined | null
28
+ createStack(title: string, projectIds: string[]): Promise<{ id: string }>
29
+ renameStack(stackId: string, title: string): Promise<void>
30
+ removeStack(stackId: string): Promise<void>
31
+ addProjectToStack(stackId: string, projectId: string): Promise<void>
32
+ removeProjectFromStack(stackId: string, projectId: string): Promise<void>
33
+ }
34
+
35
+ /** The subset of TerminalManager methods consumed by misc WS commands. */
36
+ export interface MiscTerminalsDep {
37
+ createTerminal(args: {
38
+ projectPath: string
39
+ terminalId: string
40
+ cols: number
41
+ rows: number
42
+ scrollback: number
43
+ }): TerminalSnapshot
44
+ write(terminalId: string, data: string): void
45
+ resize(terminalId: string, cols: number, rows: number): void
46
+ close(terminalId: string): void
47
+ }
48
+
49
+ /** The subset of AgentCoordinator methods consumed by misc WS commands. */
50
+ export interface MiscAgentDep {
51
+ enqueue(command: Extract<ClientCommand, { type: "message.enqueue" }>): Promise<{ queuedMessageId: string }>
52
+ steer(command: Extract<ClientCommand, { type: "message.steer" }>): Promise<void>
53
+ dequeue(command: Extract<ClientCommand, { type: "message.dequeue" }>): Promise<void>
54
+ }
55
+
56
+ /** The subset of AnalyticsReporter consumed by misc WS commands. */
57
+ export interface MiscAnalyticsDep {
58
+ track(event: string, props?: Record<string, unknown>): void
59
+ }
60
+
61
+ /** Shared result wrapper used by SessionShareService methods. */
62
+ export type ShareResult<T> = { ok: true; data: T } | { ok: false; error: ShareError }
63
+
64
+ /** The subset of SessionShareService consumed by misc WS commands. */
65
+ export interface MiscSessionShareDep {
66
+ mintToken(req: MintRequest, baseUrl: string): Promise<ShareResult<MintResponse>>
67
+ revokeToken(req: RevokeRequest): Promise<ShareResult<{ tokenId: string }>>
68
+ listSharesForChat(chatId: string, baseUrl: string): ShareSummary[]
69
+ }
70
+
71
+ export interface MiscCommandDeps {
72
+ /** EventStore methods for project/stack operations. */
73
+ store: MiscStoreDep
74
+ /** TerminalManager for terminal lifecycle. */
75
+ terminals: MiscTerminalsDep
76
+ /** AgentCoordinator for message-queue operations. */
77
+ agent: MiscAgentDep
78
+ /** Optional session-share service (feature-gated). */
79
+ sessionShare?: MiscSessionShareDep | null
80
+ /** Analytics reporter. */
81
+ analytics: MiscAnalyticsDep
82
+ /**
83
+ * List the worktrees for a given repo root.
84
+ * Injected to avoid a direct dependency on the IO adapter.
85
+ */
86
+ listWorktrees(repoPath: string): Promise<GitWorktree[]>
87
+ /** Return the origin host for the current WebSocket connection. */
88
+ getOriginHost(): string
89
+ /** Pre-bound to the current WebSocket; called to send an ack envelope. */
90
+ send(envelope: ServerEnvelope): void
91
+ /** Broadcast sidebar snapshot to all connected clients. */
92
+ broadcastSidebar(): Promise<void>
93
+ /** Broadcast chat + sidebar snapshots to all connected clients. */
94
+ broadcastChatAndSidebar(chatId: string): Promise<void>
95
+ /**
96
+ * Push a fresh terminal snapshot to subscribers of the given terminal.
97
+ * Called after terminal.close to reflect the updated status.
98
+ */
99
+ pushTerminalSnapshot(terminalId: string): void
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Command dispatcher
104
+ // ---------------------------------------------------------------------------
105
+
106
+ /**
107
+ * Handle one misc WS command (terminal/message/stack/share group).
108
+ *
109
+ * Returns `true` when the command was handled (caller should `return`).
110
+ * Returns `false` when the command type is outside this module's scope.
111
+ */
112
+ export async function handleMiscCommand(
113
+ deps: MiscCommandDeps,
114
+ command: ClientCommand,
115
+ id: string,
116
+ ): Promise<boolean> {
117
+ const { store, terminals, agent, sessionShare, analytics, send, broadcastSidebar, broadcastChatAndSidebar, pushTerminalSnapshot, listWorktrees, getOriginHost } = deps
118
+
119
+ switch (command.type) {
120
+ // ------------------------------------------------------------------
121
+ // message.*
122
+ // ------------------------------------------------------------------
123
+ case "message.enqueue": {
124
+ const result = await agent.enqueue(command)
125
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
126
+ await broadcastChatAndSidebar(command.chatId)
127
+ return true
128
+ }
129
+ case "message.steer": {
130
+ await agent.steer(command)
131
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
132
+ await broadcastChatAndSidebar(command.chatId)
133
+ return true
134
+ }
135
+ case "message.dequeue": {
136
+ await agent.dequeue(command)
137
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
138
+ await broadcastChatAndSidebar(command.chatId)
139
+ return true
140
+ }
141
+
142
+ // ------------------------------------------------------------------
143
+ // terminal.*
144
+ // ------------------------------------------------------------------
145
+ case "terminal.create": {
146
+ const project = store.getProject(command.projectId)
147
+ if (!project) {
148
+ throw new Error("Project not found")
149
+ }
150
+ const snapshot = terminals.createTerminal({
151
+ projectPath: project.localPath,
152
+ terminalId: command.terminalId,
153
+ cols: command.cols,
154
+ rows: command.rows,
155
+ scrollback: command.scrollback,
156
+ })
157
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
158
+ return true
159
+ }
160
+ case "terminal.input": {
161
+ terminals.write(command.terminalId, command.data)
162
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
163
+ return true
164
+ }
165
+ case "terminal.resize": {
166
+ terminals.resize(command.terminalId, command.cols, command.rows)
167
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
168
+ return true
169
+ }
170
+ case "terminal.close": {
171
+ terminals.close(command.terminalId)
172
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
173
+ pushTerminalSnapshot(command.terminalId)
174
+ return true
175
+ }
176
+
177
+ // ------------------------------------------------------------------
178
+ // stack.*
179
+ // ------------------------------------------------------------------
180
+ case "stack.create": {
181
+ const stack = await store.createStack(command.title, command.projectIds)
182
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: { stackId: stack.id } })
183
+ analytics.track("stack_created")
184
+ await broadcastSidebar()
185
+ return true
186
+ }
187
+ case "stack.rename": {
188
+ await store.renameStack(command.stackId, command.title)
189
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
190
+ await broadcastSidebar()
191
+ return true
192
+ }
193
+ case "stack.remove": {
194
+ await store.removeStack(command.stackId)
195
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
196
+ await broadcastSidebar()
197
+ return true
198
+ }
199
+ case "stack.addProject": {
200
+ await store.addProjectToStack(command.stackId, command.projectId)
201
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
202
+ await broadcastSidebar()
203
+ return true
204
+ }
205
+ case "stack.removeProject": {
206
+ await store.removeProjectFromStack(command.stackId, command.projectId)
207
+ send({ v: PROTOCOL_VERSION, type: "ack", id })
208
+ await broadcastSidebar()
209
+ return true
210
+ }
211
+ case "stack.listWorktrees": {
212
+ const project = store.getProject(command.projectId)
213
+ if (!project) {
214
+ throw new Error("Project not found")
215
+ }
216
+ const worktrees = await listWorktrees(project.localPath)
217
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: { worktrees } })
218
+ return true
219
+ }
220
+
221
+ // ------------------------------------------------------------------
222
+ // share.*
223
+ // ------------------------------------------------------------------
224
+ case "share.mint": {
225
+ if (!sessionShare) {
226
+ const noSvcResult: ShareCommandResult = { ok: false, error: { kind: "snapshot_write_failed", message: "session-share service unavailable" } }
227
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: noSvcResult })
228
+ return true
229
+ }
230
+ const r = await sessionShare.mintToken(command.payload, getOriginHost())
231
+ const result: ShareCommandResult = r.ok
232
+ ? { ok: true, kind: "mint", data: r.data }
233
+ : { ok: false, error: r.error }
234
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
235
+ return true
236
+ }
237
+ case "share.revoke": {
238
+ if (!sessionShare) {
239
+ const noSvcResult: ShareCommandResult = { ok: false, error: { kind: "not_found" } }
240
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: noSvcResult })
241
+ return true
242
+ }
243
+ const r = await sessionShare.revokeToken(command.payload)
244
+ const result: ShareCommandResult = r.ok
245
+ ? { ok: true, kind: "revoke", data: r.data }
246
+ : { ok: false, error: r.error }
247
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result })
248
+ return true
249
+ }
250
+ case "share.list": {
251
+ if (!sessionShare) {
252
+ const emptyResult: ShareCommandResult = { ok: true, kind: "list", data: { shares: [] } }
253
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: emptyResult })
254
+ return true
255
+ }
256
+ const shares = sessionShare.listSharesForChat(command.payload.chatId, getOriginHost())
257
+ const listResult: ShareCommandResult = { ok: true, kind: "list", data: { shares } }
258
+ send({ v: PROTOCOL_VERSION, type: "ack", id, result: listResult })
259
+ return true
260
+ }
261
+
262
+ default:
263
+ return false
264
+ }
265
+ }