@axhub/genie 0.2.11 → 0.2.12

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 (96) hide show
  1. package/dist/api-docs.html +2 -2
  2. package/dist/assets/App-Clb2COtW.js +274 -0
  3. package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
  4. package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
  5. package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
  6. package/dist/assets/{_basePickBy-BDnj7-0Z.js → _basePickBy-jUZsM51q.js} +1 -1
  7. package/dist/assets/{_baseUniq-Bl0JKOyl.js → _baseUniq-BXglE6_v.js} +1 -1
  8. package/dist/assets/{arc-DY-4Kev3.js → arc-D-oFCFBv.js} +1 -1
  9. package/dist/assets/{architectureDiagram-2XIMDMQ5-qw7crNVd.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-B9xg7ep3.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
  11. package/dist/assets/{c4Diagram-IC4MRINW-H9xp3ytb.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
  12. package/dist/assets/channel-BF4woPXX.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-B3EVDUxI.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-CGv945ef.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-uAT4CKWM.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-Cbvlpkf7.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-CcqIuGat.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
  18. package/dist/assets/{chunk-NQ4KR5QH-CgrcsRuX.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
  19. package/dist/assets/{chunk-QZHKN3VN-Cx0APOoV.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
  20. package/dist/assets/{chunk-WL4C6EOR-BbZirvBk.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
  21. package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
  23. package/dist/assets/clone-CqBvwCJW.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-CrvmGFLD.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
  25. package/dist/assets/{dagre-KLK3FWXG-C-W6VPjS.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
  26. package/dist/assets/{diagram-E7M64L7V-IP2q3bL0.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
  27. package/dist/assets/{diagram-IFDJBPK2-CQaL-XyV.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
  28. package/dist/assets/{diagram-P4PSJMXO-BxBLThfv.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
  29. package/dist/assets/{erDiagram-INFDFZHY-Dyl7bJTt.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
  30. package/dist/assets/{flowDiagram-PKNHOUZH-B7NFMgFK.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
  31. package/dist/assets/{ganttDiagram-A5KZAMGK-hReWSDu2.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-gVgcr0ST.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
  33. package/dist/assets/{graph-DNDiJhTn.js → graph-Dkeg9oys.js} +1 -1
  34. package/dist/assets/{highlighted-body-TPN3WLV5-DclLmTou.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
  35. package/dist/assets/index-DgGmiqsP.css +1 -0
  36. package/dist/assets/index-DvA901Vs.js +2 -0
  37. package/dist/assets/{infoDiagram-LFFYTUFH-CqQOOzDA.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-PHBUUO56-CZ0iLiHg.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
  39. package/dist/assets/{journeyDiagram-4ABVD52K-DdfYKfNh.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
  40. package/dist/assets/{kanban-definition-K7BYSVSG-C5Vf32u6.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
  41. package/dist/assets/{layout-rvTEu2KS.js → layout-Xr9Z2VGF.js} +1 -1
  42. package/dist/assets/{linear-CD9SiYze.js → linear-JBmzAJtl.js} +1 -1
  43. package/dist/assets/{mermaid-O7DHMXV3-OZ8qWWwa.js → mermaid-O7DHMXV3-fDuyNLKe.js} +230 -222
  44. package/dist/assets/{mindmap-definition-YRQLILUH-CQxrLNVc.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
  45. package/dist/assets/{pieDiagram-SKSYHLDU-XgAUByWg.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-337W2JSQ-CH16ls7G.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
  47. package/dist/assets/{requirementDiagram-Z7DCOOCP-B_kQO06L.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-WA2Y5GQK-ofe78CyS.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-2WXFIKYE-Ckbxwny6.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
  50. package/dist/assets/{stateDiagram-RAJIS63D-DNtzCk14.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
  51. package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
  52. package/dist/assets/{timeline-definition-YZTLITO2-zT6CklKt.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
  53. package/dist/assets/{treemap-KZPCXAKY-y0U2c3xG.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
  54. package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
  55. package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
  56. package/dist/assets/{vennDiagram-LZ73GAT5-xKj3SjYG.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
  57. package/dist/assets/{xychartDiagram-JWTSCODW-Da_qyEoX.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
  58. package/dist/index.html +4 -4
  59. package/package.json +25 -6
  60. package/scripts/refresh-acp-default-capabilities.mjs +160 -0
  61. package/server/acp-runtime/client.js +1137 -181
  62. package/server/acp-runtime/command-overrides.js +48 -0
  63. package/server/acp-runtime/index.js +576 -16
  64. package/server/acp-runtime/registry.js +6 -4
  65. package/server/acp-runtime/session-store.js +235 -92
  66. package/server/database/db.js +12 -3
  67. package/server/external-agent/ws.js +212 -11
  68. package/server/index.js +145 -52
  69. package/server/projects-watcher-config.js +4 -0
  70. package/server/projects.js +466 -125
  71. package/server/routes/cc-connect.js +5 -4
  72. package/server/routes/codex.js +24 -0
  73. package/server/routes/commands.js +144 -1
  74. package/server/routes/runs.js +641 -0
  75. package/server/routes/session-core.js +357 -109
  76. package/server/session-core/eventStore.js +0 -121
  77. package/server/session-core/providerAdapters.js +644 -163
  78. package/server/session-core/providerDiscovery.js +66 -38
  79. package/server/session-core/runRegistry.js +244 -0
  80. package/server/session-core/runtimeState.js +75 -3
  81. package/server/session-core/runtimeWriter.js +132 -10
  82. package/server/utils/codexImagePlayground.js +479 -0
  83. package/server/utils/localTerminal.js +56 -0
  84. package/server/utils/shellCommand.js +70 -0
  85. package/shared/acpCapabilities.js +393 -0
  86. package/shared/acpDefaultCapabilities.generated.json +141 -0
  87. package/shared/conversationEvents.js +425 -121
  88. package/dist/assets/App-VH1wNUHs.js +0 -259
  89. package/dist/assets/ReviewApp-D_9EN4TM.js +0 -1
  90. package/dist/assets/channel-CyNUnRfc.js +0 -1
  91. package/dist/assets/classDiagram-VBA2DB6C-DxBtyz2A.js +0 -1
  92. package/dist/assets/classDiagram-v2-RAHNMMFH-DxBtyz2A.js +0 -1
  93. package/dist/assets/clone-C341l3d0.js +0 -1
  94. package/dist/assets/index-DBkz_W_P.css +0 -1
  95. package/dist/assets/index-DdRyoXKh.js +0 -2
  96. package/dist/assets/stateDiagram-v2-FVOUBMTO-B3VPhiE1.js +0 -1
@@ -1,3 +1,11 @@
1
+ import {
2
+ buildAcpCapabilities,
3
+ normalizeAcpAvailableCommands,
4
+ normalizeAcpConfigOptions,
5
+ normalizeAcpModeState,
6
+ normalizeAcpTokenUsage
7
+ } from './acpCapabilities.js';
8
+
1
9
  export const SUPPORTED_PROVIDERS = ['claude', 'codex', 'gemini', 'opencode'];
2
10
 
3
11
  export const SESSION_STATES = [
@@ -6,6 +14,7 @@ export const SESSION_STATES = [
6
14
  'idle',
7
15
  'streaming',
8
16
  'awaiting_approval',
17
+ 'awaiting_input',
9
18
  'completed',
10
19
  'aborted',
11
20
  'errored'
@@ -32,6 +41,8 @@ export const CONVERSATION_EVENT_KINDS = {
32
41
  USAGE_UPDATE: 'usage_update',
33
42
  APPROVAL_REQUEST: 'approval_request',
34
43
  APPROVAL_RESOLVED: 'approval_resolved',
44
+ ELICITATION_REQUEST: 'elicitation_request',
45
+ ELICITATION_RESOLVED: 'elicitation_resolved',
35
46
  ARTIFACT_CREATED: 'artifact_created',
36
47
  SESSION_STATE_CHANGED: 'session_state_changed',
37
48
  SYSTEM_NOTICE: 'system_notice',
@@ -75,6 +86,43 @@ export function createConversationEvent({
75
86
  };
76
87
  }
77
88
 
89
+ export function createUserMessageConversationEvent({
90
+ provider = 'claude',
91
+ sessionId = null,
92
+ timestamp,
93
+ text = '',
94
+ contentBlocks = [],
95
+ clientRequestId = null,
96
+ eventId = null,
97
+ extensions = {},
98
+ rawRef = null
99
+ } = {}) {
100
+ const normalizedClientRequestId = typeof clientRequestId === 'string' && clientRequestId.trim()
101
+ ? clientRequestId.trim()
102
+ : null;
103
+
104
+ return createConversationEvent({
105
+ kind: CONVERSATION_EVENT_KINDS.USER_MESSAGE,
106
+ provider,
107
+ sessionId,
108
+ timestamp,
109
+ eventId,
110
+ payload: {
111
+ text: String(text || ''),
112
+ contentBlocks: normalizeMessageContentBlocks(contentBlocks)
113
+ },
114
+ extensions: {
115
+ runtimeSource: 'acp',
116
+ ...(normalizedClientRequestId ? { clientRequestId: normalizedClientRequestId } : {}),
117
+ ...extensions
118
+ },
119
+ rawRef: rawRef || {
120
+ runtime: 'acp',
121
+ sourceType: 'acp-user-prompt'
122
+ }
123
+ });
124
+ }
125
+
78
126
  export function isConversationEvent(value) {
79
127
  return !!(
80
128
  value &&
@@ -208,11 +256,23 @@ function hasAcpProtocolTags(value) {
208
256
  return /<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/i.test(String(value || ''));
209
257
  }
210
258
 
211
- export function stripAssistantProtocolTags(text) {
259
+ function stripAssistantThoughtProtocolPrelude(value) {
260
+ const text = String(value || '');
261
+ const markerPattern = /\[\s*Thought\s*:\s*true\s*\]/gi;
262
+ let markerEnd = -1;
263
+ let match;
264
+ while ((match = markerPattern.exec(text)) !== null) {
265
+ markerEnd = markerPattern.lastIndex;
266
+ }
267
+ return markerEnd >= 0 ? text.slice(markerEnd) : value;
268
+ }
269
+
270
+ export function stripAssistantProtocolTags(text, options = {}) {
212
271
  if (typeof text !== 'string') {
213
272
  return text;
214
273
  }
215
274
 
275
+ const shouldTrim = options.trim !== false;
216
276
  const visibleUserMessage = extractProtocolUserMessage(text);
217
277
  if (visibleUserMessage != null) {
218
278
  return visibleUserMessage;
@@ -230,7 +290,9 @@ export function stripAssistantProtocolTags(text) {
230
290
  result = result.replace(/["']?\s*\}\s*\}\s*$/g, '');
231
291
  }
232
292
 
233
- return result.trim();
293
+ result = stripAssistantThoughtProtocolPrelude(result);
294
+
295
+ return shouldTrim ? result.trim() : result;
234
296
  }
235
297
 
236
298
  function extractVisibleUserMessage(value) {
@@ -331,6 +393,153 @@ function getTimelineMessageClientRequestId(message) {
331
393
  : null;
332
394
  }
333
395
 
396
+ function getConversationEventMessageId(event, payload = {}) {
397
+ const rawValue = payload?.messageId || event?.extensions?.messageId || null;
398
+ return typeof rawValue === 'string' && rawValue.trim()
399
+ ? rawValue.trim()
400
+ : null;
401
+ }
402
+
403
+ function isReplayConversationEvent(event) {
404
+ const sourceType = String(event?.rawRef?.sourceType || '').trim().toLowerCase();
405
+ return Boolean(event?.extensions?.runtimeReplay) || sourceType === 'acp-session-loaded-replay';
406
+ }
407
+
408
+ function isUserEchoConversationEvent(event) {
409
+ const sourceType = String(event?.rawRef?.sourceType || '').trim().toLowerCase();
410
+ return sourceType === 'acp-user-message-chunk' || isReplayConversationEvent(event);
411
+ }
412
+
413
+ function getAppliedConversationEventIds(message) {
414
+ const ids = new Set();
415
+ if (typeof message?.eventId === 'string' && message.eventId.trim()) {
416
+ ids.add(message.eventId.trim());
417
+ }
418
+ if (Array.isArray(message?.appliedConversationEventIds)) {
419
+ message.appliedConversationEventIds.forEach((eventId) => {
420
+ if (typeof eventId === 'string' && eventId.trim()) {
421
+ ids.add(eventId.trim());
422
+ }
423
+ });
424
+ }
425
+ return ids;
426
+ }
427
+
428
+ function hasAppliedConversationEvent(messages, event) {
429
+ const eventId = typeof event?.eventId === 'string' && event.eventId.trim()
430
+ ? event.eventId.trim()
431
+ : null;
432
+ if (!eventId) {
433
+ return false;
434
+ }
435
+ return messages.some((message) => getAppliedConversationEventIds(message).has(eventId));
436
+ }
437
+
438
+ function markConversationEventApplied(message, event) {
439
+ const eventId = typeof event?.eventId === 'string' && event.eventId.trim()
440
+ ? event.eventId.trim()
441
+ : null;
442
+ if (!eventId || !message || typeof message !== 'object') {
443
+ return isReplayConversationEvent(event) && message && typeof message === 'object'
444
+ ? { ...message, runtimeReplay: true }
445
+ : message;
446
+ }
447
+
448
+ const existingIds = getAppliedConversationEventIds(message);
449
+ if (existingIds.has(eventId)) {
450
+ return isReplayConversationEvent(event) && message.runtimeReplay !== true
451
+ ? { ...message, runtimeReplay: true }
452
+ : message;
453
+ }
454
+
455
+ return {
456
+ ...message,
457
+ ...(isReplayConversationEvent(event) ? { runtimeReplay: true } : {}),
458
+ appliedConversationEventIds: [
459
+ ...(Array.isArray(message.appliedConversationEventIds) ? message.appliedConversationEventIds : []),
460
+ eventId
461
+ ]
462
+ };
463
+ }
464
+
465
+ function findCompletedTextMessageIndex(messages, { messageId, isThinking = false } = {}) {
466
+ if (!messageId) {
467
+ return -1;
468
+ }
469
+
470
+ return findLatestTimelineMessageIndex(messages, (message) => (
471
+ message?.eventMessageId === messageId &&
472
+ !message.isToolUse &&
473
+ !!message.isThinking === !!isThinking &&
474
+ message.isStreaming === false
475
+ ));
476
+ }
477
+
478
+ function isAssistantTextTimelineMessage(message, { isThinking = false } = {}) {
479
+ return (
480
+ String(message?.type || '').trim().toLowerCase() === 'assistant' &&
481
+ !message.isToolUse &&
482
+ !!message.isThinking === !!isThinking
483
+ );
484
+ }
485
+
486
+ function getTimelineMessageText(message) {
487
+ return String(message?.content || message?.reasoning || '').trim();
488
+ }
489
+
490
+ function getTimelineTextSignature(message, { isThinking = false } = {}) {
491
+ if (!isAssistantTextTimelineMessage(message, { isThinking })) {
492
+ return null;
493
+ }
494
+
495
+ const text = getTimelineMessageText(message);
496
+ if (!text) {
497
+ return null;
498
+ }
499
+
500
+ return `${isThinking ? 'reasoning' : 'answer'}\u0000${text}`;
501
+ }
502
+
503
+ function removeDuplicateReplayTextMessages(messages, { isThinking = false } = {}) {
504
+ const seenSignatures = new Set();
505
+ const completedTexts = [];
506
+ const nextMessages = [];
507
+
508
+ for (const message of messages) {
509
+ const isTextMessage = isAssistantTextTimelineMessage(message, { isThinking });
510
+ const text = isTextMessage ? getTimelineMessageText(message) : '';
511
+ const signature = getTimelineTextSignature(message, { isThinking });
512
+ if (message?.runtimeReplay === true && isTextMessage) {
513
+ const replayIsCoveredByCompletedMessage = completedTexts.some((completedText) => (
514
+ !text || completedText === text || completedText.includes(text)
515
+ ));
516
+ if ((signature && seenSignatures.has(signature)) || replayIsCoveredByCompletedMessage) {
517
+ continue;
518
+ }
519
+ }
520
+ if (signature) {
521
+ seenSignatures.add(signature);
522
+ }
523
+ if (isTextMessage && message?.isStreaming === false && text) {
524
+ completedTexts.push(text);
525
+ }
526
+ nextMessages.push(message);
527
+ }
528
+
529
+ return nextMessages;
530
+ }
531
+
532
+ function shouldDedupeReplayTextEvent(event) {
533
+ return isReplayConversationEvent(event) && (
534
+ event.kind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START ||
535
+ event.kind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA ||
536
+ event.kind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END ||
537
+ event.kind === CONVERSATION_EVENT_KINDS.REASONING_START ||
538
+ event.kind === CONVERSATION_EVENT_KINDS.REASONING_DELTA ||
539
+ event.kind === CONVERSATION_EVENT_KINDS.REASONING_END
540
+ );
541
+ }
542
+
334
543
  function buildComparableUserMessageContentBlocks(message = {}) {
335
544
  const imageBlocks = Array.isArray(message?.images)
336
545
  ? message.images.map((image) => ({
@@ -349,22 +558,36 @@ function buildComparableUserMessageContentBlocks(message = {}) {
349
558
 
350
559
  function findMatchingOptimisticUserMessageIndex(messages, event, payload = {}) {
351
560
  const clientRequestId = getConversationEventClientRequestId(event);
352
- if (!clientRequestId) {
561
+ if (!clientRequestId && !isUserEchoConversationEvent(event)) {
353
562
  return -1;
354
563
  }
355
564
 
356
565
  const expectedContentBlocks = JSON.stringify(normalizeMessageContentBlocks(payload.contentBlocks));
566
+ const rawText = typeof payload.text === 'string' ? payload.text : '';
567
+ const visibleText = rawText ? extractVisibleUserMessage(rawText) : '';
568
+ const expectedText = visibleText || rawText;
357
569
 
358
570
  return findLatestTimelineMessageIndex(messages, (message) => {
359
571
  if (String(message?.type || '').trim().toLowerCase() !== 'user') {
360
572
  return false;
361
573
  }
362
574
 
363
- if (getTimelineMessageClientRequestId(message) !== clientRequestId) {
575
+ const messageClientRequestId = getTimelineMessageClientRequestId(message);
576
+ if (clientRequestId) {
577
+ if (messageClientRequestId !== clientRequestId) {
578
+ return false;
579
+ }
580
+ } else if (!messageClientRequestId) {
364
581
  return false;
582
+ } else {
583
+ const eventProvider = String(event?.provider || '').trim().toLowerCase();
584
+ const messageProvider = String(message?.provider || message?.__provider || '').trim().toLowerCase();
585
+ if (eventProvider && messageProvider && eventProvider !== messageProvider) {
586
+ return false;
587
+ }
365
588
  }
366
589
 
367
- if (String(message?.content || '') !== String(payload.text || '')) {
590
+ if (String(message?.content || '') !== String(expectedText || '')) {
368
591
  return false;
369
592
  }
370
593
 
@@ -373,70 +596,6 @@ function findMatchingOptimisticUserMessageIndex(messages, event, payload = {}) {
373
596
  });
374
597
  }
375
598
 
376
- function normalizeAcpModeState(value) {
377
- if (!value || typeof value !== 'object') {
378
- return null;
379
- }
380
-
381
- const availableModes = Array.isArray(value.availableModes)
382
- ? value.availableModes
383
- .filter((mode) => mode && typeof mode === 'object' && String(mode.id || '').trim())
384
- .map((mode) => ({
385
- id: String(mode.id).trim(),
386
- name: String(mode.name || mode.id || '').trim() || String(mode.id).trim(),
387
- description: mode.description == null ? null : String(mode.description)
388
- }))
389
- : [];
390
- const currentModeId = typeof value.currentModeId === 'string' && value.currentModeId.trim()
391
- ? value.currentModeId.trim()
392
- : null;
393
-
394
- if (!availableModes.length && !currentModeId) {
395
- return null;
396
- }
397
-
398
- return {
399
- availableModes,
400
- currentModeId: currentModeId || availableModes[0]?.id || null
401
- };
402
- }
403
-
404
- function normalizeAcpAvailableCommands(value) {
405
- const commands = Array.isArray(value?.availableCommands)
406
- ? value.availableCommands
407
- : Array.isArray(value)
408
- ? value
409
- : [];
410
-
411
- return commands
412
- .filter((command) => command && typeof command === 'object' && String(command.name || '').trim())
413
- .map((command) => ({
414
- name: String(command.name).trim(),
415
- description: command.description == null ? '' : String(command.description),
416
- input: command.input && typeof command.input === 'object' ? cloneJsonValue(command.input) : null,
417
- source: 'acp',
418
- namespace: 'acp',
419
- type: 'acp'
420
- }));
421
- }
422
-
423
- function normalizeAcpConfigOptions(value) {
424
- const configOptions = Array.isArray(value?.configOptions)
425
- ? value.configOptions
426
- : Array.isArray(value)
427
- ? value
428
- : [];
429
-
430
- return configOptions
431
- .filter((option) => option && typeof option === 'object' && String(option.key || '').trim())
432
- .map((option) => ({
433
- key: String(option.key).trim(),
434
- description: typeof option.description === 'string' ? option.description : '',
435
- value: cloneJsonValue(option.value),
436
- schema: option.schema && typeof option.schema === 'object' ? cloneJsonValue(option.schema) : null
437
- }));
438
- }
439
-
440
599
  function normalizeAcpSessionInfo(value) {
441
600
  if (!value || typeof value !== 'object') {
442
601
  return null;
@@ -459,32 +618,6 @@ function normalizeAcpSessionInfo(value) {
459
618
  };
460
619
  }
461
620
 
462
- function normalizeAcpTokenUsage(value) {
463
- if (!value || typeof value !== 'object') {
464
- return null;
465
- }
466
-
467
- const used = Number(value.used);
468
- const total = Number(value.size);
469
- if (!Number.isFinite(used) || used < 0 || !Number.isFinite(total) || total < 0) {
470
- return null;
471
- }
472
-
473
- const safeUsed = Math.round(used);
474
- const safeTotal = Math.round(total);
475
- const percentage = safeTotal > 0
476
- ? Math.min(100, Math.max(0, Math.round((safeUsed / safeTotal) * 100)))
477
- : 0;
478
-
479
- return {
480
- used: safeUsed,
481
- total: safeTotal,
482
- percentage,
483
- remaining: Math.max(0, safeTotal - safeUsed),
484
- cost: value.cost && typeof value.cost === 'object' ? cloneJsonValue(value.cost) : null
485
- };
486
- }
487
-
488
621
  function mergeToolCallSnapshot(existingSnapshot = null, payload = {}) {
489
622
  const nextSnapshot = existingSnapshot && typeof existingSnapshot === 'object'
490
623
  ? { ...existingSnapshot }
@@ -555,6 +688,20 @@ function appendDeltaText(existing = '', delta = '') {
555
688
  return `${existing}${delta}`;
556
689
  }
557
690
 
691
+ function closeOpenStreamingTextMessages(messages) {
692
+ for (let index = 0; index < messages.length; index += 1) {
693
+ const message = messages[index];
694
+ if (!message || message.isStreaming !== true || message.isToolUse) {
695
+ continue;
696
+ }
697
+
698
+ messages[index] = {
699
+ ...message,
700
+ isStreaming: false
701
+ };
702
+ }
703
+ }
704
+
558
705
  function ensureStreamingTextMessage(messages, {
559
706
  messageId,
560
707
  timestamp,
@@ -562,10 +709,30 @@ function ensureStreamingTextMessage(messages, {
562
709
  isThinking = false,
563
710
  kind = 'assistant'
564
711
  }) {
565
- const index = findLatestTimelineMessageIndex(messages, (message) => (
712
+ const hasRoleBoundaryAfter = (candidateIndex) => {
713
+ const targetRole = String(kind || 'assistant').trim().toLowerCase();
714
+ for (let index = candidateIndex + 1; index < messages.length; index += 1) {
715
+ const messageRole = String(messages[index]?.type || messages[index]?.role || '').trim().toLowerCase();
716
+ if (messageRole && messageRole !== targetRole) {
717
+ return true;
718
+ }
719
+ }
720
+ return false;
721
+ };
722
+
723
+ const index = findLatestTimelineMessageIndex(messages, (message, messageIndex) => (
566
724
  message?.eventMessageId === messageId &&
567
725
  !message.isToolUse &&
568
- !!message.isThinking === !!isThinking
726
+ !!message.isThinking === !!isThinking &&
727
+ !hasRoleBoundaryAfter(messageIndex) &&
728
+ (
729
+ message.isStreaming !== false ||
730
+ (
731
+ !String(message?.content || '').trim() &&
732
+ Array.isArray(message?.contentBlocks) &&
733
+ message.contentBlocks.length > 0
734
+ )
735
+ )
569
736
  ));
570
737
 
571
738
  if (index >= 0) {
@@ -776,6 +943,7 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
776
943
  export function applyConversationEventToTimelineMessages(messages = [], event, sessionProvider = 'claude') {
777
944
  const nextMessages = Array.isArray(messages) ? [...messages] : [];
778
945
  if (!isConversationEvent(event)) return nextMessages;
946
+ if (hasAppliedConversationEvent(nextMessages, event)) return nextMessages;
779
947
 
780
948
  const provider = event.provider || sessionProvider;
781
949
  const timestamp = event.timestamp || new Date().toISOString();
@@ -783,6 +951,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
783
951
 
784
952
  switch (event.kind) {
785
953
  case CONVERSATION_EVENT_KINDS.USER_MESSAGE: {
954
+ closeOpenStreamingTextMessages(nextMessages);
786
955
  const clientRequestId = getConversationEventClientRequestId(event);
787
956
  const optimisticIndex = findMatchingOptimisticUserMessageIndex(nextMessages, event, payload);
788
957
  const normalizedContentBlocks = normalizeMessageContentBlocks(payload.contentBlocks);
@@ -799,7 +968,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
799
968
  ? normalizedContentBlocks.filter((block) => block?.type !== 'image')
800
969
  : normalizedContentBlocks;
801
970
 
802
- nextMessages[optimisticIndex] = {
971
+ nextMessages[optimisticIndex] = markConversationEventApplied({
803
972
  ...existingMessage,
804
973
  type: 'user',
805
974
  content: visibleText || existingMessage.content || '',
@@ -809,46 +978,65 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
809
978
  clientRequestId: clientRequestId || existingMessage.clientRequestId || null,
810
979
  eventId: event.eventId || existingMessage.eventId,
811
980
  id: existingMessage.id || event.eventId || existingMessage.id
812
- };
981
+ }, event);
813
982
  break;
814
983
  }
815
984
 
816
- nextMessages.push({
985
+ nextMessages.push(markConversationEventApplied({
817
986
  type: 'user',
818
987
  content: visibleText,
819
988
  timestamp,
820
989
  provider,
821
990
  contentBlocks: normalizedContentBlocks,
822
- clientRequestId
823
- });
991
+ clientRequestId,
992
+ eventId: event.eventId
993
+ }, event));
824
994
  break;
825
995
  }
826
996
 
827
997
  case CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START: {
828
- const messageId = payload.messageId || event.extensions?.messageId || createEventId('assistant_text');
829
- ensureStreamingTextMessage(nextMessages, { messageId, timestamp, provider, isThinking: false });
998
+ const messageId = getConversationEventMessageId(event, payload) || createEventId('assistant_text');
999
+ const completedReplayIndex = isReplayConversationEvent(event)
1000
+ ? findCompletedTextMessageIndex(nextMessages, { messageId, isThinking: false })
1001
+ : -1;
1002
+ if (completedReplayIndex >= 0) {
1003
+ nextMessages[completedReplayIndex] = markConversationEventApplied(nextMessages[completedReplayIndex], event);
1004
+ break;
1005
+ }
1006
+ const { index } = ensureStreamingTextMessage(nextMessages, { messageId, timestamp, provider, isThinking: false });
1007
+ nextMessages[index] = markConversationEventApplied(nextMessages[index], event);
830
1008
  break;
831
1009
  }
832
1010
 
833
1011
  case CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA: {
834
- const messageId = payload.messageId || event.extensions?.messageId || createEventId('assistant_text');
1012
+ const messageId = getConversationEventMessageId(event, payload) || createEventId('assistant_text');
1013
+ const completedReplayIndex = isReplayConversationEvent(event)
1014
+ ? findCompletedTextMessageIndex(nextMessages, { messageId, isThinking: false })
1015
+ : -1;
1016
+ if (completedReplayIndex >= 0) {
1017
+ nextMessages[completedReplayIndex] = markConversationEventApplied(nextMessages[completedReplayIndex], event);
1018
+ break;
1019
+ }
835
1020
  const { index } = ensureStreamingTextMessage(nextMessages, { messageId, timestamp, provider, isThinking: false });
836
1021
  const target = { ...nextMessages[index] };
837
- target.content = appendDeltaText(target.content || '', payload.text || '');
1022
+ target.content = stripAssistantProtocolTags(
1023
+ appendDeltaText(target.content || '', payload.text || ''),
1024
+ { trim: false }
1025
+ );
838
1026
  target.timestamp = target.timestamp || timestamp;
839
1027
  target.provider = target.provider || provider;
840
- nextMessages[index] = target;
1028
+ nextMessages[index] = markConversationEventApplied(target, event);
841
1029
  break;
842
1030
  }
843
1031
 
844
1032
  case CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END: {
845
- const messageId = payload.messageId || event.extensions?.messageId;
1033
+ const messageId = getConversationEventMessageId(event, payload);
846
1034
  if (!messageId) break;
847
1035
  const index = findLatestTimelineMessageIndex(nextMessages, (message) => (
848
1036
  message?.eventMessageId === messageId && !message.isToolUse && !message.isThinking
849
1037
  ));
850
1038
  if (index >= 0) {
851
- nextMessages[index] = { ...nextMessages[index], isStreaming: false };
1039
+ nextMessages[index] = markConversationEventApplied({ ...nextMessages[index], isStreaming: false }, event);
852
1040
  }
853
1041
  break;
854
1042
  }
@@ -874,31 +1062,46 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
874
1062
  }
875
1063
 
876
1064
  case CONVERSATION_EVENT_KINDS.REASONING_START: {
877
- const messageId = payload.messageId || event.extensions?.messageId || createEventId('reasoning');
878
- ensureStreamingTextMessage(nextMessages, { messageId, timestamp, provider, isThinking: true });
1065
+ const messageId = getConversationEventMessageId(event, payload) || createEventId('reasoning');
1066
+ const completedReplayIndex = isReplayConversationEvent(event)
1067
+ ? findCompletedTextMessageIndex(nextMessages, { messageId, isThinking: true })
1068
+ : -1;
1069
+ if (completedReplayIndex >= 0) {
1070
+ nextMessages[completedReplayIndex] = markConversationEventApplied(nextMessages[completedReplayIndex], event);
1071
+ break;
1072
+ }
1073
+ const { index } = ensureStreamingTextMessage(nextMessages, { messageId, timestamp, provider, isThinking: true });
1074
+ nextMessages[index] = markConversationEventApplied(nextMessages[index], event);
879
1075
  break;
880
1076
  }
881
1077
 
882
1078
  case CONVERSATION_EVENT_KINDS.REASONING_DELTA: {
883
- const messageId = payload.messageId || event.extensions?.messageId || createEventId('reasoning');
1079
+ const messageId = getConversationEventMessageId(event, payload) || createEventId('reasoning');
1080
+ const completedReplayIndex = isReplayConversationEvent(event)
1081
+ ? findCompletedTextMessageIndex(nextMessages, { messageId, isThinking: true })
1082
+ : -1;
1083
+ if (completedReplayIndex >= 0) {
1084
+ nextMessages[completedReplayIndex] = markConversationEventApplied(nextMessages[completedReplayIndex], event);
1085
+ break;
1086
+ }
884
1087
  const { index } = ensureStreamingTextMessage(nextMessages, { messageId, timestamp, provider, isThinking: true });
885
1088
  const target = { ...nextMessages[index] };
886
1089
  target.content = appendDeltaText(target.content || '', payload.text || '');
887
1090
  target.timestamp = target.timestamp || timestamp;
888
1091
  target.provider = target.provider || provider;
889
1092
  target.isThinking = true;
890
- nextMessages[index] = target;
1093
+ nextMessages[index] = markConversationEventApplied(target, event);
891
1094
  break;
892
1095
  }
893
1096
 
894
1097
  case CONVERSATION_EVENT_KINDS.REASONING_END: {
895
- const messageId = payload.messageId || event.extensions?.messageId;
1098
+ const messageId = getConversationEventMessageId(event, payload);
896
1099
  if (!messageId) break;
897
1100
  const index = findLatestTimelineMessageIndex(nextMessages, (message) => (
898
1101
  message?.eventMessageId === messageId && !!message.isThinking
899
1102
  ));
900
1103
  if (index >= 0) {
901
- nextMessages[index] = { ...nextMessages[index], isStreaming: false, isThinking: true };
1104
+ nextMessages[index] = markConversationEventApplied({ ...nextMessages[index], isStreaming: false, isThinking: true }, event);
902
1105
  }
903
1106
  break;
904
1107
  }
@@ -1069,28 +1272,79 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
1069
1272
  break;
1070
1273
  }
1071
1274
 
1275
+ if (shouldDedupeReplayTextEvent(event)) {
1276
+ return removeDuplicateReplayTextMessages(nextMessages, {
1277
+ isThinking: (
1278
+ event.kind === CONVERSATION_EVENT_KINDS.REASONING_START ||
1279
+ event.kind === CONVERSATION_EVENT_KINDS.REASONING_DELTA ||
1280
+ event.kind === CONVERSATION_EVENT_KINDS.REASONING_END
1281
+ )
1282
+ });
1283
+ }
1284
+
1072
1285
  return nextMessages;
1073
1286
  }
1074
1287
 
1288
+ function normalizeConversationTimelineEntries(entries = [], sessionProvider = 'claude') {
1289
+ const normalizedEvents = [];
1290
+ let legacyEntries = [];
1291
+
1292
+ const flushLegacyEntries = () => {
1293
+ if (legacyEntries.length === 0) {
1294
+ return;
1295
+ }
1296
+
1297
+ normalizedEvents.push(...normalizeLegacyHistoryEntries(legacyEntries, sessionProvider));
1298
+ legacyEntries = [];
1299
+ };
1300
+
1301
+ for (const entry of entries) {
1302
+ if (isConversationEvent(entry)) {
1303
+ flushLegacyEntries();
1304
+ normalizedEvents.push(entry);
1305
+ } else {
1306
+ legacyEntries.push(entry);
1307
+ }
1308
+ }
1309
+
1310
+ flushLegacyEntries();
1311
+ return normalizedEvents;
1312
+ }
1313
+
1075
1314
  export function conversationEventsToTimelineMessages(events = [], sessionProvider = 'claude') {
1076
1315
  if (!Array.isArray(events)) return [];
1077
- if (events.length > 0 && !isConversationEvent(events[0])) {
1078
- return conversationEventsToTimelineMessages(normalizeLegacyHistoryEntries(events, sessionProvider), sessionProvider);
1079
- }
1316
+ const normalizedEvents = events.every((event) => isConversationEvent(event))
1317
+ ? events
1318
+ : normalizeConversationTimelineEntries(events, sessionProvider);
1080
1319
 
1081
- return events.reduce(
1320
+ return normalizedEvents.reduce(
1082
1321
  (messages, event) => applyConversationEventToTimelineMessages(messages, event, sessionProvider),
1083
1322
  []
1084
1323
  );
1085
1324
  }
1086
1325
 
1326
+ function mergeAcpConfigOptions(previousOptions = [], nextOptions = []) {
1327
+ const mergedByKey = new Map();
1328
+
1329
+ for (const option of normalizeAcpConfigOptions(previousOptions)) {
1330
+ mergedByKey.set(option.key, option);
1331
+ }
1332
+
1333
+ for (const option of normalizeAcpConfigOptions(nextOptions)) {
1334
+ mergedByKey.set(option.key, option);
1335
+ }
1336
+
1337
+ return Array.from(mergedByKey.values());
1338
+ }
1339
+
1087
1340
  export function extractAcpSessionMetadataFromConversationEvents(events = []) {
1088
1341
  const metadata = {
1089
1342
  modeState: null,
1090
1343
  availableCommands: [],
1091
1344
  configOptions: [],
1092
1345
  sessionInfo: null,
1093
- tokenUsage: null
1346
+ tokenUsage: null,
1347
+ capabilitySnapshot: null
1094
1348
  };
1095
1349
 
1096
1350
  for (const event of Array.isArray(events) ? events : []) {
@@ -1117,7 +1371,7 @@ export function extractAcpSessionMetadataFromConversationEvents(events = []) {
1117
1371
  }
1118
1372
 
1119
1373
  if (event.kind === CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE) {
1120
- metadata.configOptions = normalizeAcpConfigOptions(event.payload || {});
1374
+ metadata.configOptions = mergeAcpConfigOptions(metadata.configOptions, event.payload || {});
1121
1375
  }
1122
1376
 
1123
1377
  if (event.kind === CONVERSATION_EVENT_KINDS.SESSION_INFO_UPDATE) {
@@ -1138,6 +1392,14 @@ export function extractAcpSessionMetadataFromConversationEvents(events = []) {
1138
1392
  }
1139
1393
  }
1140
1394
 
1395
+ metadata.capabilitySnapshot = buildAcpCapabilities({
1396
+ configOptions: metadata.configOptions,
1397
+ modeState: metadata.modeState,
1398
+ tokenUsage: metadata.tokenUsage,
1399
+ availableCommands: metadata.availableCommands,
1400
+ source: 'history'
1401
+ });
1402
+
1141
1403
  return metadata;
1142
1404
  }
1143
1405
 
@@ -1241,6 +1503,48 @@ export function normalizeRealtimePayloadToConversationEvents(payload, fallbackPr
1241
1503
  );
1242
1504
  }
1243
1505
 
1506
+ const initialAvailableCommands = normalizeAcpAvailableCommands(payload.availableCommands || []);
1507
+ if (initialAvailableCommands.length > 0) {
1508
+ events.push(
1509
+ createConversationEvent({
1510
+ kind: CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE,
1511
+ provider,
1512
+ sessionId,
1513
+ timestamp,
1514
+ payload: { availableCommands: initialAvailableCommands },
1515
+ rawRef
1516
+ })
1517
+ );
1518
+ }
1519
+
1520
+ const initialConfigOptions = normalizeAcpConfigOptions(payload.configOptions || []);
1521
+ if (initialConfigOptions.length > 0) {
1522
+ events.push(
1523
+ createConversationEvent({
1524
+ kind: CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE,
1525
+ provider,
1526
+ sessionId,
1527
+ timestamp,
1528
+ payload: { configOptions: initialConfigOptions },
1529
+ rawRef
1530
+ })
1531
+ );
1532
+ }
1533
+
1534
+ const initialTokenUsage = normalizeAcpTokenUsage(payload.tokenUsage || null);
1535
+ if (initialTokenUsage) {
1536
+ events.push(
1537
+ createConversationEvent({
1538
+ kind: CONVERSATION_EVENT_KINDS.USAGE_UPDATE,
1539
+ provider,
1540
+ sessionId,
1541
+ timestamp,
1542
+ payload: initialTokenUsage,
1543
+ rawRef
1544
+ })
1545
+ );
1546
+ }
1547
+
1244
1548
  return events;
1245
1549
  }
1246
1550