@axhub/genie 0.2.7 → 0.2.9

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 (131) hide show
  1. package/LICENSE +21 -675
  2. package/dist/api-docs.html +2 -2
  3. package/dist/assets/App-GBcTeeUS.js +460 -0
  4. package/dist/assets/App-qxJ8_QYu.css +32 -0
  5. package/dist/assets/ReviewApp-C9K--AQE.js +1 -0
  6. package/dist/assets/{_basePickBy-C19AekOu.js → _basePickBy-DR_8uFCo.js} +1 -1
  7. package/dist/assets/{_baseUniq-JsnevLw_.js → _baseUniq-D0njlQ_7.js} +1 -1
  8. package/dist/assets/{arc-BLpcuBlf.js → arc-CKlr_Rec.js} +1 -1
  9. package/dist/assets/architectureDiagram-2XIMDMQ5-BmO_uLUH.js +36 -0
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-DQBLwsUS.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +3 -3
  11. package/dist/assets/c4Diagram-IC4MRINW-C67kFoXx.js +10 -0
  12. package/dist/assets/channel-V3MBjKys.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-De63kbgc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DtTDDdM9.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-DHuwd8tw.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-BgytFtmO.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-nZdp86aN.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
  18. package/dist/assets/chunk-NQ4KR5QH-5UAE0Vg-.js +220 -0
  19. package/dist/assets/{chunk-QZHKN3VN-DvUQ3mnO.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
  20. package/dist/assets/chunk-WL4C6EOR-DjDPvUUP.js +189 -0
  21. package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
  23. package/dist/assets/clone-BbMGfZwt.js +1 -0
  24. package/dist/assets/cose-bilkent-S5V4N54A-D-60XrkJ.js +1 -0
  25. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +331 -0
  26. package/dist/assets/{dagre-KLK3FWXG-CHYIvW47.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
  27. package/dist/assets/diagram-E7M64L7V-BueeqoYm.js +24 -0
  28. package/dist/assets/{diagram-IFDJBPK2-Dzsiln_C.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
  29. package/dist/assets/{diagram-P4PSJMXO-DKnGbUpE.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
  30. package/dist/assets/erDiagram-INFDFZHY-D0oVnO-x.js +70 -0
  31. package/dist/assets/{flowDiagram-PKNHOUZH-BAZ2-jKp.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +4 -4
  32. package/dist/assets/ganttDiagram-A5KZAMGK-BwhbbgCP.js +292 -0
  33. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BflpyjGy.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
  34. package/dist/assets/{graph-suelaXFh.js → graph-DzKos-N0.js} +1 -1
  35. package/dist/assets/highlighted-body-TPN3WLV5-CKDMgz3X.js +1 -0
  36. package/dist/assets/index-DiQlHzGj.js +2 -0
  37. package/dist/assets/index-Drat2nB9.css +1 -0
  38. package/dist/assets/{infoDiagram-LFFYTUFH-pfD1FA3p.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
  39. package/dist/assets/ishikawaDiagram-PHBUUO56-CtihxDxl.js +70 -0
  40. package/dist/assets/journeyDiagram-4ABVD52K-Du00J8_d.js +139 -0
  41. package/dist/assets/{kanban-definition-K7BYSVSG-FWinmur1.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +5 -5
  42. package/dist/assets/{layout-vcz43XvZ.js → layout-B80Sityu.js} +1 -1
  43. package/dist/assets/{linear-le4gc0vx.js → linear-sRQLOf5H.js} +1 -1
  44. package/dist/assets/mermaid-O7DHMXV3-CBuVs4eJ.js +1038 -0
  45. package/dist/assets/mindmap-definition-YRQLILUH-C5IL_xi-.js +68 -0
  46. package/dist/assets/{pieDiagram-SKSYHLDU-C7PKDh3b.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +2 -2
  47. package/dist/assets/quadrantDiagram-337W2JSQ-COfUcLWt.js +7 -0
  48. package/dist/assets/requirementDiagram-Z7DCOOCP-DSb-CJ5B.js +73 -0
  49. package/dist/assets/{sankeyDiagram-WA2Y5GQK-4gulcOP4.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +3 -3
  50. package/dist/assets/sequenceDiagram-2WXFIKYE-C2VpkMwA.js +145 -0
  51. package/dist/assets/{stateDiagram-RAJIS63D-CB4Vl7qM.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
  52. package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
  53. package/dist/assets/timeline-definition-YZTLITO2-Dx1hP5lg.js +61 -0
  54. package/dist/assets/{treemap-KZPCXAKY-DZSEE6Hz.js → treemap-KZPCXAKY-CkLOdYCZ.js} +58 -58
  55. package/dist/assets/vendor-codemirror-BxPY6emf.js +39 -0
  56. package/dist/assets/vendor-react-xmA_f8ig.js +59 -0
  57. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  58. package/dist/assets/{vennDiagram-LZ73GAT5-8E_G06fI.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +4 -4
  59. package/dist/assets/xychartDiagram-JWTSCODW-6fh6qmzN.js +7 -0
  60. package/dist/index.html +5 -5
  61. package/package.json +36 -35
  62. package/server/acp-runtime/client.js +91 -17
  63. package/server/acp-runtime/index.js +5 -16
  64. package/server/acp-runtime/session-store.js +4 -4
  65. package/server/channels/runtime/AgentRuntimeAdapter.js +1 -10
  66. package/server/claude-sdk.js +1 -3
  67. package/server/cli.js +159 -2
  68. package/server/external-agent/service.js +24 -6
  69. package/server/external-agent/ws.js +63 -3
  70. package/server/gemini-cli.js +1 -3
  71. package/server/index.js +120 -19
  72. package/server/openai-codex.js +1 -3
  73. package/server/opencode-cli.js +1 -3
  74. package/server/projects.js +654 -236
  75. package/server/routes/cc-connect.js +1131 -0
  76. package/server/routes/cli-auth.js +1 -73
  77. package/server/routes/commands.js +4 -9
  78. package/server/routes/projects.js +45 -24
  79. package/server/routes/session-core.js +149 -86
  80. package/server/session-core/eventStore.js +45 -18
  81. package/server/session-core/providerAdapters.js +50 -13
  82. package/server/session-core/providerDiscovery.js +8 -3
  83. package/server/session-core/runtimeState.js +8 -0
  84. package/server/utils/ccConnectManager.js +390 -0
  85. package/server/utils/ccConnectState.js +575 -0
  86. package/server/utils/resolveCommandPath.js +71 -0
  87. package/server/utils/workspaceRoots.js +154 -0
  88. package/shared/conversationEvents.js +78 -14
  89. package/dist/assets/App-BWSqiXAT.js +0 -220
  90. package/dist/assets/App-DrlLKa8f.css +0 -1
  91. package/dist/assets/ReviewApp-nz3mbArg.js +0 -1
  92. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +0 -36
  93. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +0 -10
  94. package/dist/assets/channel-DkFNxV_H.js +0 -1
  95. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +0 -220
  96. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +0 -189
  97. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +0 -1
  98. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +0 -1
  99. package/dist/assets/clone-C0lCEIEO.js +0 -1
  100. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +0 -1
  101. package/dist/assets/cytoscape.esm-5J0xJHOV.js +0 -321
  102. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +0 -24
  103. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +0 -70
  104. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +0 -292
  105. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +0 -1
  106. package/dist/assets/index-B01NxbUv.css +0 -1
  107. package/dist/assets/index-DW5pGgQ_.js +0 -2
  108. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +0 -70
  109. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +0 -139
  110. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +0 -870
  111. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +0 -68
  112. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +0 -7
  113. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +0 -73
  114. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +0 -145
  115. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +0 -1
  116. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +0 -61
  117. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +0 -31
  118. package/dist/assets/vendor-react-CP4yFTs7.js +0 -8
  119. package/dist/assets/vendor-xterm-DfcmCpbH.js +0 -66
  120. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +0 -7
  121. package/server/_legacy-providers/README.md +0 -30
  122. package/server/_legacy-providers/claude-sdk.js +0 -956
  123. package/server/_legacy-providers/gemini-cli.js +0 -368
  124. package/server/_legacy-providers/openai-codex.js +0 -705
  125. package/server/_legacy-providers/opencode-cli.js +0 -674
  126. package/server/acp-runtime/client.test.js +0 -688
  127. package/server/acp-runtime/session-store.test.js +0 -89
  128. package/server/cli.test.js +0 -76
  129. package/server/external-agent/service.test.js +0 -53
  130. package/server/external-agent/ws.test.js +0 -289
  131. package/shared/conversationEvents.test.js +0 -403
@@ -1,403 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import {
5
- applyConversationEventToTimelineMessages,
6
- CONVERSATION_EVENT_KINDS,
7
- createConversationEvent,
8
- extractAcpSessionMetadataFromConversationEvents,
9
- normalizeLegacyHistoryEntries,
10
- normalizeRealtimePayloadToConversationEvents
11
- } from './conversationEvents.js';
12
-
13
- test('createConversationEvent returns normalized event payload', () => {
14
- const event = createConversationEvent({
15
- kind: CONVERSATION_EVENT_KINDS.USER_MESSAGE,
16
- provider: 'claude',
17
- sessionId: 'session-1',
18
- payload: {
19
- text: 'hello'
20
- }
21
- });
22
-
23
- assert.equal(event.kind, CONVERSATION_EVENT_KINDS.USER_MESSAGE);
24
- assert.equal(event.provider, 'claude');
25
- assert.equal(event.sessionId, 'session-1');
26
- assert.equal(event.payload.text, 'hello');
27
- assert.match(event.eventId, /^user_message:/);
28
- });
29
-
30
- test('applyConversationEventToTimelineMessages builds assistant and tool messages', () => {
31
- const events = [
32
- createConversationEvent({
33
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START,
34
- provider: 'claude',
35
- sessionId: 'session-2',
36
- payload: { messageId: 'assistant:1' }
37
- }),
38
- createConversationEvent({
39
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA,
40
- provider: 'claude',
41
- sessionId: 'session-2',
42
- payload: { messageId: 'assistant:1', text: 'Hello from ACP.' }
43
- }),
44
- createConversationEvent({
45
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END,
46
- provider: 'claude',
47
- sessionId: 'session-2',
48
- payload: { messageId: 'assistant:1' }
49
- }),
50
- createConversationEvent({
51
- kind: CONVERSATION_EVENT_KINDS.TOOL_CALL_START,
52
- provider: 'claude',
53
- sessionId: 'session-2',
54
- payload: {
55
- toolCallId: 'tool:1',
56
- toolName: 'WriteFile',
57
- title: 'Write file',
58
- kind: 'edit',
59
- status: 'in_progress'
60
- }
61
- }),
62
- createConversationEvent({
63
- kind: CONVERSATION_EVENT_KINDS.TOOL_CALL_INPUT,
64
- provider: 'claude',
65
- sessionId: 'session-2',
66
- payload: {
67
- toolCallId: 'tool:1',
68
- input: { path: 'README.md' },
69
- rawInput: { path: 'README.md' },
70
- locations: [{ path: 'README.md', line: 1 }]
71
- }
72
- }),
73
- createConversationEvent({
74
- kind: CONVERSATION_EVENT_KINDS.TOOL_CALL_END,
75
- provider: 'claude',
76
- sessionId: 'session-2',
77
- payload: {
78
- toolCallId: 'tool:1',
79
- toolName: 'WriteFile',
80
- status: 'completed'
81
- }
82
- }),
83
- createConversationEvent({
84
- kind: CONVERSATION_EVENT_KINDS.TOOL_RESULT,
85
- provider: 'claude',
86
- sessionId: 'session-2',
87
- payload: {
88
- toolCallId: 'tool:1',
89
- content: 'updated README',
90
- contentBlocks: [
91
- {
92
- type: 'diff',
93
- path: 'README.md',
94
- oldText: 'old',
95
- newText: 'new'
96
- }
97
- ],
98
- isError: false
99
- }
100
- })
101
- ];
102
-
103
- const messages = events.reduce(
104
- (timeline, event) => applyConversationEventToTimelineMessages(timeline, event, 'claude'),
105
- []
106
- );
107
-
108
- assert.equal(messages.length, 2);
109
- assert.equal(messages[0].content, 'Hello from ACP.');
110
- assert.equal(messages[0].isStreaming, false);
111
- assert.equal(messages[1].isToolUse, true);
112
- assert.equal(messages[1].toolName, 'WriteFile');
113
- assert.equal(messages[1].toolResult.content, 'updated README');
114
- assert.equal(messages[1].acpToolCall.kind, 'edit');
115
- assert.equal(messages[1].acpToolCall.status, 'completed');
116
- assert.equal(messages[1].acpToolCall.locations[0].path, 'README.md');
117
- assert.equal(messages[1].acpToolCall.content[0].type, 'diff');
118
- });
119
-
120
- test('applyConversationEventToTimelineMessages marks tool call end even without a tool result', () => {
121
- const events = [
122
- createConversationEvent({
123
- kind: CONVERSATION_EVENT_KINDS.TOOL_CALL_START,
124
- provider: 'codex',
125
- sessionId: 'session-tool-end',
126
- timestamp: '2026-03-30T09:10:34.879Z',
127
- payload: {
128
- toolCallId: 'tool:web:1',
129
- toolName: 'Searching the Web'
130
- }
131
- }),
132
- createConversationEvent({
133
- kind: CONVERSATION_EVENT_KINDS.TOOL_CALL_INPUT,
134
- provider: 'codex',
135
- sessionId: 'session-tool-end',
136
- timestamp: '2026-03-30T09:10:35.725Z',
137
- payload: {
138
- toolCallId: 'tool:web:1',
139
- toolName: 'Searching for: weather: Guangzhou',
140
- input: {
141
- query: 'weather: Guangzhou'
142
- }
143
- }
144
- }),
145
- createConversationEvent({
146
- kind: CONVERSATION_EVENT_KINDS.TOOL_CALL_END,
147
- provider: 'codex',
148
- sessionId: 'session-tool-end',
149
- timestamp: '2026-03-30T09:10:36.142Z',
150
- payload: {
151
- toolCallId: 'tool:web:1',
152
- toolName: 'Searching for: weather: Guangzhou'
153
- }
154
- })
155
- ];
156
-
157
- const messages = events.reduce(
158
- (timeline, event) => applyConversationEventToTimelineMessages(timeline, event, 'codex'),
159
- []
160
- );
161
-
162
- assert.equal(messages.length, 1);
163
- assert.equal(messages[0].isToolUse, true);
164
- assert.equal(messages[0].toolResult, undefined);
165
- assert.equal(messages[0].toolEndedAt, '2026-03-30T09:10:36.142Z');
166
- });
167
-
168
- test('applyConversationEventToTimelineMessages merges echoed user events into optimistic messages', () => {
169
- const optimisticMessage = {
170
- type: 'user',
171
- content: '广州天气怎么样?',
172
- timestamp: '2026-03-30T09:00:00.000Z',
173
- provider: 'codex',
174
- clientRequestId: 'req-user-1',
175
- contentBlocks: [
176
- {
177
- type: 'resource_link',
178
- uri: '/tmp/weather.md',
179
- name: 'weather.md'
180
- }
181
- ]
182
- };
183
-
184
- const echoedEvent = createConversationEvent({
185
- kind: CONVERSATION_EVENT_KINDS.USER_MESSAGE,
186
- provider: 'codex',
187
- sessionId: 'session-user',
188
- timestamp: '2026-03-30T09:00:01.000Z',
189
- payload: {
190
- text: '广州天气怎么样?',
191
- contentBlocks: [
192
- {
193
- type: 'resource_link',
194
- uri: '/tmp/weather.md',
195
- name: 'weather.md'
196
- }
197
- ]
198
- },
199
- extensions: {
200
- clientRequestId: 'req-user-1'
201
- }
202
- });
203
-
204
- const messages = applyConversationEventToTimelineMessages([optimisticMessage], echoedEvent, 'codex');
205
-
206
- assert.equal(messages.length, 1);
207
- assert.equal(messages[0].content, '广州天气怎么样?');
208
- assert.equal(messages[0].clientRequestId, 'req-user-1');
209
- assert.equal(messages[0].eventId, echoedEvent.eventId);
210
- assert.equal(messages[0].provider, 'codex');
211
- });
212
-
213
- test('applyConversationEventToTimelineMessages replaces ACP plan entries in-place', () => {
214
- const firstPlan = createConversationEvent({
215
- kind: CONVERSATION_EVENT_KINDS.PLAN_UPDATE,
216
- provider: 'claude',
217
- sessionId: 'session-plan',
218
- payload: {
219
- entries: [
220
- { content: 'Read files', status: 'completed', priority: 'medium' },
221
- { content: 'Implement UI', status: 'in_progress', priority: 'high' }
222
- ],
223
- message: 'Implementation plan updated'
224
- }
225
- });
226
-
227
- const secondPlan = createConversationEvent({
228
- kind: CONVERSATION_EVENT_KINDS.PLAN_UPDATE,
229
- provider: 'claude',
230
- sessionId: 'session-plan',
231
- payload: {
232
- entries: [
233
- { content: 'Read files', status: 'completed', priority: 'medium' },
234
- { content: 'Implement UI', status: 'completed', priority: 'high' }
235
- ],
236
- message: 'Implementation plan updated'
237
- }
238
- });
239
-
240
- let messages = applyConversationEventToTimelineMessages([], firstPlan, 'claude');
241
- messages = applyConversationEventToTimelineMessages(messages, secondPlan, 'claude');
242
-
243
- assert.equal(messages.length, 1);
244
- assert.equal(messages[0].isPlanUpdate, true);
245
- assert.equal(messages[0].planEntries.length, 2);
246
- assert.equal(messages[0].planEntries[1].status, 'completed');
247
- });
248
-
249
- test('applyConversationEventToTimelineMessages keeps assistant content blocks on the same message', () => {
250
- const events = [
251
- createConversationEvent({
252
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_CONTENT_BLOCK,
253
- provider: 'claude',
254
- sessionId: 'session-content',
255
- payload: {
256
- messageId: 'assistant:content:1',
257
- contentBlock: {
258
- type: 'resource_link',
259
- uri: '/tmp/spec.md',
260
- name: 'spec.md'
261
- }
262
- }
263
- }),
264
- createConversationEvent({
265
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START,
266
- provider: 'claude',
267
- sessionId: 'session-content',
268
- payload: {
269
- messageId: 'assistant:content:1'
270
- }
271
- }),
272
- createConversationEvent({
273
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA,
274
- provider: 'claude',
275
- sessionId: 'session-content',
276
- payload: {
277
- messageId: 'assistant:content:1',
278
- text: 'Attached the spec for review.'
279
- }
280
- }),
281
- createConversationEvent({
282
- kind: CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END,
283
- provider: 'claude',
284
- sessionId: 'session-content',
285
- payload: {
286
- messageId: 'assistant:content:1'
287
- }
288
- })
289
- ];
290
-
291
- const messages = events.reduce(
292
- (timeline, event) => applyConversationEventToTimelineMessages(timeline, event, 'claude'),
293
- []
294
- );
295
-
296
- assert.equal(messages.length, 1);
297
- assert.equal(messages[0].content, 'Attached the spec for review.');
298
- assert.equal(messages[0].contentBlocks.length, 1);
299
- assert.equal(messages[0].contentBlocks[0].type, 'resource_link');
300
- });
301
-
302
- test('extractAcpSessionMetadataFromConversationEvents keeps latest modes and commands', () => {
303
- const metadata = extractAcpSessionMetadataFromConversationEvents([
304
- createConversationEvent({
305
- kind: CONVERSATION_EVENT_KINDS.MODE_UPDATE,
306
- provider: 'claude',
307
- sessionId: 'session-meta',
308
- payload: {
309
- availableModes: [
310
- { id: 'ask', name: 'Ask', description: 'Answer questions' },
311
- { id: 'code', name: 'Code', description: 'Edit files' }
312
- ],
313
- currentModeId: 'ask'
314
- }
315
- }),
316
- createConversationEvent({
317
- kind: CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE,
318
- provider: 'claude',
319
- sessionId: 'session-meta',
320
- payload: {
321
- availableCommands: [
322
- { name: 'create_plan', description: 'Create a plan' }
323
- ]
324
- }
325
- }),
326
- createConversationEvent({
327
- kind: CONVERSATION_EVENT_KINDS.MODE_UPDATE,
328
- provider: 'claude',
329
- sessionId: 'session-meta',
330
- payload: {
331
- currentModeId: 'code'
332
- }
333
- })
334
- ]);
335
-
336
- assert.equal(metadata.modeState.currentModeId, 'code');
337
- assert.equal(metadata.modeState.availableModes.length, 2);
338
- assert.equal(metadata.availableCommands[0].name, 'create_plan');
339
- assert.equal(metadata.availableCommands[0].source, 'acp');
340
- });
341
-
342
- test('normalizeLegacyHistoryEntries keeps user and assistant content across providers', () => {
343
- const rawMessages = [
344
- {
345
- timestamp: '2026-03-30T10:00:00.000Z',
346
- message: {
347
- role: 'user',
348
- content: 'Please summarize the diff.'
349
- }
350
- },
351
- {
352
- timestamp: '2026-03-30T10:00:01.000Z',
353
- message: {
354
- role: 'assistant',
355
- content: 'Summary ready.'
356
- }
357
- }
358
- ];
359
-
360
- for (const provider of ['claude', 'codex', 'gemini', 'opencode']) {
361
- const events = normalizeLegacyHistoryEntries(rawMessages, provider, `session-${provider}`);
362
- assert.equal(events[0].kind, CONVERSATION_EVENT_KINDS.USER_MESSAGE);
363
- assert.equal(events[0].payload.text, 'Please summarize the diff.');
364
- assert.equal(events[1].kind, CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START);
365
- assert.equal(events[2].kind, CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA);
366
- assert.equal(events[2].payload.text, 'Summary ready.');
367
- assert.equal(events[3].kind, CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END);
368
- }
369
- });
370
-
371
- test('normalizeRealtimePayloadToConversationEvents maps realtime payloads into conversation events', () => {
372
- const queuedEvents = normalizeRealtimePayloadToConversationEvents({
373
- type: 'session-created',
374
- provider: 'claude',
375
- sessionId: 'session-3',
376
- modes: {
377
- availableModes: [
378
- { id: 'ask', name: 'Ask' }
379
- ],
380
- currentModeId: 'ask'
381
- }
382
- }, 'claude');
383
- assert.equal(queuedEvents[0].kind, CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED);
384
- assert.equal(queuedEvents[0].payload.state, 'queued');
385
- assert.equal(queuedEvents[1].kind, CONVERSATION_EVENT_KINDS.MODE_UPDATE);
386
- assert.equal(queuedEvents[1].payload.currentModeId, 'ask');
387
-
388
- const deltaEvents = normalizeRealtimePayloadToConversationEvents({
389
- type: 'claude-response',
390
- provider: 'claude',
391
- sessionId: 'session-3',
392
- data: {
393
- type: 'content_block_delta',
394
- delta: {
395
- text: 'stream chunk'
396
- }
397
- }
398
- }, 'claude');
399
-
400
- assert.equal(deltaEvents[0].kind, CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START);
401
- assert.equal(deltaEvents[1].kind, CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA);
402
- assert.equal(deltaEvents[1].payload.text, 'stream chunk');
403
- });