@axhub/genie 0.2.8 → 0.2.10

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 (106) hide show
  1. package/LICENSE +21 -675
  2. package/dist/api-docs.html +2 -2
  3. package/dist/assets/App-CYCCsgwf.js +264 -0
  4. package/dist/assets/ReviewApp-0srHIXwb.js +1 -0
  5. package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DVVb07UV.js} +1 -1
  6. package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-BtbziL5G.js} +1 -1
  7. package/dist/assets/{arc-BBmKEN-S.js → arc-BsCC8yBD.js} +1 -1
  8. package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
  9. package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
  10. package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
  11. package/dist/assets/channel-BMhScXFe.js +1 -0
  12. package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
  13. package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
  15. package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
  16. package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
  17. package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
  19. package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
  20. package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
  21. package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
  22. package/dist/assets/clone-BPqOt4r3.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
  24. package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
  25. package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
  26. package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
  27. package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
  28. package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
  29. package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
  30. package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
  32. package/dist/assets/{graph-D11wiwHo.js → graph-CeJCMjan.js} +1 -1
  33. package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
  34. package/dist/assets/index-C514cLyb.js +2 -0
  35. package/dist/assets/index-h1DBl_g3.css +1 -0
  36. package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
  38. package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
  39. package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
  40. package/dist/assets/{layout-BLUNf-PJ.js → layout-CI2RM-v6.js} +1 -1
  41. package/dist/assets/{linear-DukIV_Xv.js → linear-DE7bISck.js} +1 -1
  42. package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
  43. package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
  44. package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
  46. package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
  49. package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
  51. package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
  52. package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
  53. package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
  54. package/dist/assets/{vendor-react-Cpt6D04s.js → vendor-react-xmA_f8ig.js} +1 -1
  55. package/dist/assets/{vennDiagram-LZ73GAT5-D96ZI6Mg.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
  56. package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
  57. package/dist/index.html +5 -5
  58. package/package.json +8 -7
  59. package/server/acp-runtime/client.js +129 -16
  60. package/server/acp-runtime/index.js +54 -0
  61. package/server/acp-runtime/registry.js +2 -2
  62. package/server/acp-runtime/session-store.js +79 -5
  63. package/server/cli.js +55 -10
  64. package/server/database/db.js +20 -0
  65. package/server/external-agent/service.js +24 -6
  66. package/server/external-agent/ws.js +540 -27
  67. package/server/index.js +112 -151
  68. package/server/lan-access/core.js +79 -0
  69. package/server/lan-access/state.js +102 -0
  70. package/server/middleware/auth.js +57 -14
  71. package/server/projects.js +930 -667
  72. package/server/routes/auth.js +24 -4
  73. package/server/routes/cli-auth.js +21 -25
  74. package/server/routes/codex.js +84 -298
  75. package/server/routes/commands.js +322 -407
  76. package/server/routes/lan-access.js +231 -0
  77. package/server/routes/projects.js +154 -158
  78. package/server/routes/session-core.js +160 -91
  79. package/server/routes/settings.js +113 -99
  80. package/server/session-core/eventStore.js +60 -20
  81. package/server/session-core/providerAdapters.js +75 -38
  82. package/server/session-core/runtimeState.js +8 -0
  83. package/server/session-core/sessionListMerge.js +47 -0
  84. package/shared/conversationEvents.js +174 -15
  85. package/shared/modelConstants.js +79 -99
  86. package/dist/assets/App-CTKZtqB1.js +0 -460
  87. package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
  88. package/dist/assets/channel-1oJBvF-0.js +0 -1
  89. package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
  90. package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
  91. package/dist/assets/clone-CinxIlEu.js +0 -1
  92. package/dist/assets/index-DFxzgWoO.js +0 -2
  93. package/dist/assets/index-YCFGDVKw.css +0 -1
  94. package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
  95. package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
  96. package/server/_legacy-providers/README.md +0 -30
  97. package/server/_legacy-providers/claude-sdk.js +0 -956
  98. package/server/_legacy-providers/gemini-cli.js +0 -368
  99. package/server/_legacy-providers/openai-codex.js +0 -705
  100. package/server/_legacy-providers/opencode-cli.js +0 -674
  101. package/server/routes/git.js +0 -1110
  102. package/server/routes/mcp-utils.js +0 -48
  103. package/server/routes/mcp.js +0 -536
  104. package/server/routes/taskmaster.js +0 -1963
  105. package/server/utils/mcp-detector.js +0 -198
  106. package/server/utils/taskmaster-websocket.js +0 -129
@@ -1,37 +1,61 @@
1
+ import fsSync from 'fs';
1
2
  import fs from 'fs/promises';
2
3
  import os from 'os';
3
4
  import path from 'path';
5
+ import readline from 'readline';
4
6
 
5
7
  import {
6
8
  CONVERSATION_EVENT_KINDS,
7
9
  isConversationEvent
8
10
  } from '../../shared/conversationEvents.js';
9
11
 
10
- const MIRRORED_EVENT_STORE_ROOT = path.join(os.homedir(), '.axhub-genie', 'session-events');
12
+ function getMirroredEventStoreRoot() {
13
+ if (process.env.AXHUB_GENIE_SESSION_EVENTS_ROOT) {
14
+ return path.resolve(process.env.AXHUB_GENIE_SESSION_EVENTS_ROOT);
15
+ }
16
+
17
+ return path.join(os.homedir(), '.axhub-genie', 'session-events');
18
+ }
11
19
  const PERSISTED_EVENT_KINDS = new Set([
20
+ CONVERSATION_EVENT_KINDS.USER_MESSAGE,
21
+ CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START,
22
+ CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA,
23
+ CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END,
12
24
  CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED,
13
25
  CONVERSATION_EVENT_KINDS.ERROR,
14
26
  CONVERSATION_EVENT_KINDS.APPROVAL_REQUEST,
15
27
  CONVERSATION_EVENT_KINDS.APPROVAL_RESOLVED,
16
- CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE
28
+ CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE,
29
+ CONVERSATION_EVENT_KINDS.MODE_UPDATE,
30
+ CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE,
31
+ CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE,
32
+ CONVERSATION_EVENT_KINDS.SESSION_INFO_UPDATE,
33
+ CONVERSATION_EVENT_KINDS.USAGE_UPDATE,
34
+ CONVERSATION_EVENT_KINDS.PLAN_UPDATE,
35
+ CONVERSATION_EVENT_KINDS.ARTIFACT_CREATED
17
36
  ]);
18
37
 
19
38
  function getSessionEventFilePath(provider, sessionId) {
20
- return path.join(MIRRORED_EVENT_STORE_ROOT, String(provider || 'claude'), `${sessionId}.jsonl`);
39
+ return path.join(getMirroredEventStoreRoot(), String(provider || 'claude'), `${sessionId}.jsonl`);
21
40
  }
22
41
 
23
42
  function normalizePersistedEvents(events = []) {
24
43
  return events.filter((event) => (
25
44
  isConversationEvent(event) &&
26
45
  event.sessionId &&
27
- (
28
- event.extensions?.runtimeSource === 'acp' ||
29
- event.rawRef?.runtime === 'acp' ||
30
- PERSISTED_EVENT_KINDS.has(event.kind)
31
- )
46
+ PERSISTED_EVENT_KINDS.has(event.kind)
32
47
  ));
33
48
  }
34
49
 
50
+ function extractSerializedEventKind(line) {
51
+ if (typeof line !== 'string' || !line) {
52
+ return null;
53
+ }
54
+
55
+ const match = line.match(/"kind"\s*:\s*"([^"]+)"/);
56
+ return match?.[1] || null;
57
+ }
58
+
35
59
  function sortObjectKeys(value) {
36
60
  if (Array.isArray(value)) {
37
61
  return value.map(sortObjectKeys);
@@ -91,19 +115,35 @@ export async function readMirroredConversationEvents(provider, sessionId) {
91
115
  const filePath = getSessionEventFilePath(provider, sessionId);
92
116
 
93
117
  try {
94
- const content = await fs.readFile(filePath, 'utf8');
95
- return content
96
- .split('\n')
97
- .map((line) => line.trim())
98
- .filter(Boolean)
99
- .map((line) => {
100
- try {
101
- return JSON.parse(line);
102
- } catch {
103
- return null;
118
+ const fileStream = fsSync.createReadStream(filePath, { encoding: 'utf8' });
119
+ const rl = readline.createInterface({
120
+ input: fileStream,
121
+ crlfDelay: Infinity
122
+ });
123
+ const events = [];
124
+
125
+ for await (const rawLine of rl) {
126
+ const line = rawLine.trim();
127
+ if (!line) {
128
+ continue;
129
+ }
130
+
131
+ const kind = extractSerializedEventKind(line);
132
+ if (!kind || !PERSISTED_EVENT_KINDS.has(kind)) {
133
+ continue;
134
+ }
135
+
136
+ try {
137
+ const event = JSON.parse(line);
138
+ if (isConversationEvent(event) && PERSISTED_EVENT_KINDS.has(event.kind)) {
139
+ events.push(event);
104
140
  }
105
- })
106
- .filter(isConversationEvent);
141
+ } catch {
142
+ // Skip malformed lines and oversized legacy transcript events.
143
+ }
144
+ }
145
+
146
+ return events;
107
147
  } catch (error) {
108
148
  if (error?.code === 'ENOENT') {
109
149
  return [];
@@ -14,12 +14,26 @@ import {
14
14
  findAcpSessionRecord,
15
15
  listAcpSessions
16
16
  } from '../acp-runtime/session-store.js';
17
+ import { mergeSessionLists } from './sessionListMerge.js';
17
18
 
18
19
  async function flattenLegacyMessages(result) {
19
20
  if (Array.isArray(result)) return result;
20
21
  return Array.isArray(result?.messages) ? result.messages : [];
21
22
  }
22
23
 
24
+ function hasReplayableTranscriptContent(events = []) {
25
+ return (Array.isArray(events) ? events : []).some((event) => (
26
+ event?.kind === 'user_message' ||
27
+ event?.kind === 'assistant_text_delta' ||
28
+ event?.kind === 'assistant_content_block' ||
29
+ event?.kind === 'reasoning_delta' ||
30
+ event?.kind === 'tool_call_start' ||
31
+ event?.kind === 'tool_result' ||
32
+ event?.kind === 'plan_update' ||
33
+ event?.kind === 'system_notice'
34
+ ));
35
+ }
36
+
23
37
  async function normalizeLegacyLoadResult(result, provider, sessionId) {
24
38
  const messages = await flattenLegacyMessages(result);
25
39
  const legacyEvents = normalizeLegacyHistoryEntries(messages, provider, sessionId);
@@ -37,43 +51,46 @@ async function normalizeLegacyLoadResult(result, provider, sessionId) {
37
51
  };
38
52
  }
39
53
 
40
- function mergeSessionLists(legacySessions = [], acpSessions = []) {
41
- const merged = new Map();
42
-
43
- legacySessions.forEach((session) => {
44
- if (session?.id) {
45
- merged.set(session.id, session);
46
- }
47
- });
48
-
49
- acpSessions.forEach((session) => {
50
- if (session?.id) {
51
- merged.set(session.id, session);
52
- }
53
- });
54
-
55
- return Array.from(merged.values()).sort((left, right) => {
56
- const leftTime = new Date(left?.lastActivity || left?.updatedAt || left?.createdAt || 0).getTime();
57
- const rightTime = new Date(right?.lastActivity || right?.updatedAt || right?.createdAt || 0).getTime();
58
- return rightTime - leftTime;
59
- });
54
+ function createEventLoadResult(events = [], source = 'acp') {
55
+ const normalizedEvents = Array.isArray(events) ? events : [];
56
+ return {
57
+ events: normalizedEvents,
58
+ total: normalizedEvents.length,
59
+ hasMore: false,
60
+ offset: 0,
61
+ limit: null,
62
+ source
63
+ };
60
64
  }
61
65
 
62
- async function loadAcpEvents(provider, sessionId) {
66
+ async function loadAcpEvents(provider, sessionId, nativeHistoryLoader = null) {
63
67
  const record = await findAcpSessionRecord(sessionId, provider);
64
68
  if (!record) {
65
69
  return null;
66
70
  }
67
71
 
68
- const events = await readMirroredConversationEvents(provider, sessionId);
69
- return {
70
- events,
71
- total: events.length,
72
- hasMore: false,
73
- offset: 0,
74
- limit: null,
75
- source: 'acp'
76
- };
72
+ const mirroredEvents = await readMirroredConversationEvents(provider, sessionId);
73
+ if (hasReplayableTranscriptContent(mirroredEvents)) {
74
+ return createEventLoadResult(mirroredEvents, 'acp');
75
+ }
76
+
77
+ if (typeof nativeHistoryLoader === 'function') {
78
+ try {
79
+ const nativeResult = await nativeHistoryLoader();
80
+ const normalizedResult = await normalizeLegacyLoadResult(nativeResult, provider, sessionId);
81
+ if (Array.isArray(normalizedResult)) {
82
+ return createEventLoadResult(normalizedResult, 'acp');
83
+ }
84
+ return {
85
+ ...normalizedResult,
86
+ source: 'acp'
87
+ };
88
+ } catch {
89
+ // Fall back to the mirrored event store when native history is unavailable.
90
+ }
91
+ }
92
+
93
+ return createEventLoadResult(mirroredEvents, 'acp');
77
94
  }
78
95
 
79
96
  const PROVIDER_ADAPTERS = {
@@ -85,11 +102,16 @@ const PROVIDER_ADAPTERS = {
85
102
  ]);
86
103
  return mergeSessionLists(
87
104
  (result?.sessions || []).map((session) => ({ ...session, provider: 'claude', source: 'legacy' })),
88
- acpSessions
105
+ acpSessions,
106
+ { fallbackProvider: 'claude' }
89
107
  );
90
108
  },
91
109
  async loadEvents({ projectName, sessionId, limit = null, offset = 0 }) {
92
- const acpResult = await loadAcpEvents('claude', sessionId);
110
+ const acpResult = await loadAcpEvents(
111
+ 'claude',
112
+ sessionId,
113
+ () => getSessionMessages(projectName, sessionId, limit, offset),
114
+ );
93
115
  if (acpResult) {
94
116
  return acpResult;
95
117
  }
@@ -106,11 +128,16 @@ const PROVIDER_ADAPTERS = {
106
128
  ]);
107
129
  return mergeSessionLists(
108
130
  sessions.map((session) => ({ ...session, provider: 'codex', source: 'legacy' })),
109
- acpSessions
131
+ acpSessions,
132
+ { fallbackProvider: 'codex' }
110
133
  );
111
134
  },
112
135
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
113
- const acpResult = await loadAcpEvents('codex', sessionId);
136
+ const acpResult = await loadAcpEvents(
137
+ 'codex',
138
+ sessionId,
139
+ () => getCodexSessionMessages(sessionId, limit, offset),
140
+ );
114
141
  if (acpResult) {
115
142
  return acpResult;
116
143
  }
@@ -127,11 +154,16 @@ const PROVIDER_ADAPTERS = {
127
154
  ]);
128
155
  return mergeSessionLists(
129
156
  sessions.map((session) => ({ ...session, provider: 'gemini', source: 'legacy' })),
130
- acpSessions
157
+ acpSessions,
158
+ { fallbackProvider: 'gemini' }
131
159
  );
132
160
  },
133
161
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
134
- const acpResult = await loadAcpEvents('gemini', sessionId);
162
+ const acpResult = await loadAcpEvents(
163
+ 'gemini',
164
+ sessionId,
165
+ () => getGeminiSessionMessages(sessionId, limit, offset),
166
+ );
135
167
  if (acpResult) {
136
168
  return acpResult;
137
169
  }
@@ -148,11 +180,16 @@ const PROVIDER_ADAPTERS = {
148
180
  ]);
149
181
  return mergeSessionLists(
150
182
  sessions.map((session) => ({ ...session, provider: 'opencode', source: 'legacy' })),
151
- acpSessions
183
+ acpSessions,
184
+ { fallbackProvider: 'opencode' }
152
185
  );
153
186
  },
154
187
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
155
- const acpResult = await loadAcpEvents('opencode', sessionId);
188
+ const acpResult = await loadAcpEvents(
189
+ 'opencode',
190
+ sessionId,
191
+ () => getOpencodeSessionMessages(sessionId, limit, offset),
192
+ );
156
193
  if (acpResult) {
157
194
  return acpResult;
158
195
  }
@@ -266,6 +266,14 @@ function createRuntimeSignature(snapshot) {
266
266
  });
267
267
  }
268
268
 
269
+ export function seedSessionRuntimeSubscriptionSnapshot(subscriptionKey, snapshot) {
270
+ if (!subscriptionKey || !snapshot) {
271
+ return;
272
+ }
273
+
274
+ lastBroadcastSignatures.set(subscriptionKey, createRuntimeSignature(snapshot));
275
+ }
276
+
269
277
  export function createSessionRuntimeSubscriptionKey(provider, sessionId) {
270
278
  const normalizedProvider = normalizeProvider(provider);
271
279
  const normalizedSessionId = normalizeNonEmptyString(sessionId);
@@ -0,0 +1,47 @@
1
+ function getSessionActivityTime(session) {
2
+ return new Date(
3
+ session?.lastActivity
4
+ || session?.updatedAt
5
+ || session?.updated_at
6
+ || session?.createdAt
7
+ || session?.created_at
8
+ || 0
9
+ ).getTime();
10
+ }
11
+
12
+ function buildSessionMergeKey(session, fallbackProvider = '') {
13
+ const provider = String(session?.provider || session?.__provider || fallbackProvider || '').trim().toLowerCase();
14
+ const sessionId = String(session?.id || session?.sessionId || '').trim();
15
+
16
+ if (!sessionId) {
17
+ return null;
18
+ }
19
+
20
+ return `${provider}:${sessionId}`;
21
+ }
22
+
23
+ export function mergeSessionLists(legacySessions = [], acpSessions = [], options = {}) {
24
+ const { fallbackProvider = '' } = options;
25
+ const merged = new Map();
26
+
27
+ for (const session of legacySessions) {
28
+ const mergeKey = buildSessionMergeKey(session, fallbackProvider);
29
+ if (!mergeKey) {
30
+ continue;
31
+ }
32
+
33
+ merged.set(mergeKey, { ...session });
34
+ }
35
+
36
+ for (const session of acpSessions) {
37
+ const mergeKey = buildSessionMergeKey(session, fallbackProvider);
38
+ if (!mergeKey) {
39
+ continue;
40
+ }
41
+
42
+ const existing = merged.get(mergeKey) || {};
43
+ merged.set(mergeKey, { ...existing, ...session });
44
+ }
45
+
46
+ return Array.from(merged.values()).sort((left, right) => getSessionActivityTime(right) - getSessionActivityTime(left));
47
+ }
@@ -27,6 +27,9 @@ export const CONVERSATION_EVENT_KINDS = {
27
27
  PLAN_UPDATE: 'plan_update',
28
28
  MODE_UPDATE: 'mode_update',
29
29
  AVAILABLE_COMMANDS_UPDATE: 'available_commands_update',
30
+ CONFIG_OPTION_UPDATE: 'config_option_update',
31
+ SESSION_INFO_UPDATE: 'session_info_update',
32
+ USAGE_UPDATE: 'usage_update',
30
33
  APPROVAL_REQUEST: 'approval_request',
31
34
  APPROVAL_RESOLVED: 'approval_resolved',
32
35
  ARTIFACT_CREATED: 'artifact_created',
@@ -185,11 +188,61 @@ function createTextEndEvent({
185
188
  ];
186
189
  }
187
190
 
191
+ function extractProtocolUserMessage(value) {
192
+ const text = String(value || '').trim();
193
+ if (!text) {
194
+ return null;
195
+ }
196
+
197
+ const dynamicContextMatch = text.match(
198
+ /<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>\s*<user_message(?:\s[^>]*)?>\s*([\s\S]*?)\s*<\/user_message>/i
199
+ );
200
+ if (!dynamicContextMatch) {
201
+ return null;
202
+ }
203
+
204
+ return String(dynamicContextMatch[1] || '').trim();
205
+ }
206
+
207
+ function hasAcpProtocolTags(value) {
208
+ return /<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/i.test(String(value || ''));
209
+ }
210
+
211
+ export function stripAssistantProtocolTags(text) {
212
+ if (typeof text !== 'string') {
213
+ return text;
214
+ }
215
+
216
+ const visibleUserMessage = extractProtocolUserMessage(text);
217
+ if (visibleUserMessage != null) {
218
+ return visibleUserMessage;
219
+ }
220
+
221
+ const hadProtocolTags = hasAcpProtocolTags(text);
222
+ let result = text;
223
+
224
+ result = result.replace(/<subagent_notification(?:\s[^>]*)?>[\s\S]*?<\/subagent_notification>/gi, '');
225
+ result = result.replace(/<environment_context(?:\s[^>]*)?>[\s\S]*?<\/environment_context>/gi, '');
226
+ result = result.replace(/<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>/gi, '');
227
+ result = result.replace(/<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/gi, '');
228
+
229
+ if (hadProtocolTags) {
230
+ result = result.replace(/["']?\s*\}\s*\}\s*$/g, '');
231
+ }
232
+
233
+ return result.trim();
234
+ }
235
+
188
236
  function extractVisibleUserMessage(value) {
189
237
  const text = String(value || '').trim();
190
238
  if (!text) return '';
191
239
 
192
- if (text.startsWith('# AGENTS.md instructions for ') || text.includes('<environment_context>')) {
240
+ if (
241
+ text.startsWith('# AGENTS.md instructions for ') ||
242
+ text.includes('<environment_context>') ||
243
+ text.startsWith('<subagent_notification>') ||
244
+ text.startsWith('</subagent_notification>')
245
+ ) {
193
246
  return '';
194
247
  }
195
248
 
@@ -202,11 +255,9 @@ function extractVisibleUserMessage(value) {
202
255
  return '';
203
256
  }
204
257
 
205
- const dynamicContextMatch = text.match(
206
- /^<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>\s*<user_message>\s*([\s\S]*?)\s*<\/user_message>$/i
207
- );
208
- if (dynamicContextMatch) {
209
- return String(dynamicContextMatch[1] || '').trim();
258
+ const protocolUserMessage = extractProtocolUserMessage(text);
259
+ if (protocolUserMessage != null) {
260
+ return protocolUserMessage;
210
261
  }
211
262
 
212
263
  return text;
@@ -222,12 +273,16 @@ function isHiddenSystemContextContent(value) {
222
273
  text.startsWith('[Dynamic Context]') ||
223
274
  text.startsWith('[Hidden Context]') ||
224
275
  text.startsWith('# AGENTS.md instructions for ') ||
225
- text.startsWith('[DYNAMIC CONTEXT V1]')
276
+ text.startsWith('[DYNAMIC CONTEXT V1]') ||
277
+ text.startsWith('<subagent_notification>') ||
278
+ text.startsWith('</subagent_notification>')
226
279
  ) {
227
280
  return true;
228
281
  }
229
282
 
230
- return /^<dynamic_context(?:\s|>)/i.test(text) || text.includes('<environment_context>');
283
+ return /^<dynamic_context(?:\s|>)/i.test(text)
284
+ || text.includes('<environment_context>')
285
+ || /<subagent_notification(?:\s|>)/i.test(text);
231
286
  }
232
287
 
233
288
  function stringifyToolContent(value) {
@@ -365,6 +420,71 @@ function normalizeAcpAvailableCommands(value) {
365
420
  }));
366
421
  }
367
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
+ function normalizeAcpSessionInfo(value) {
441
+ if (!value || typeof value !== 'object') {
442
+ return null;
443
+ }
444
+
445
+ const title = Object.prototype.hasOwnProperty.call(value, 'title')
446
+ ? (value.title == null ? null : String(value.title))
447
+ : undefined;
448
+ const updatedAt = Object.prototype.hasOwnProperty.call(value, 'updatedAt')
449
+ ? (value.updatedAt == null ? null : String(value.updatedAt))
450
+ : undefined;
451
+
452
+ if (title === undefined && updatedAt === undefined) {
453
+ return null;
454
+ }
455
+
456
+ return {
457
+ ...(title !== undefined ? { title } : {}),
458
+ ...(updatedAt !== undefined ? { updatedAt } : {})
459
+ };
460
+ }
461
+
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
+
368
488
  function mergeToolCallSnapshot(existingSnapshot = null, payload = {}) {
369
489
  const nextSnapshot = existingSnapshot && typeof existingSnapshot === 'object'
370
490
  ? { ...existingSnapshot }
@@ -613,8 +733,11 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
613
733
  if (msg?.message?.role === 'assistant' && msg?.message?.content) {
614
734
  if (Array.isArray(msg.message.content)) {
615
735
  for (const part of msg.message.content) {
616
- if (part?.type === 'text' && !isHiddenSystemContextContent(part.text)) {
617
- events.push(...createTextSpanEvents({ text: part.text, provider, sessionId, timestamp, rawRef }));
736
+ if (part?.type === 'text') {
737
+ const cleanText = stripAssistantProtocolTags(part.text);
738
+ if (cleanText && !isHiddenSystemContextContent(cleanText)) {
739
+ events.push(...createTextSpanEvents({ text: cleanText, provider, sessionId, timestamp, rawRef }));
740
+ }
618
741
  } else if (part?.type === 'tool_use') {
619
742
  const toolCallId = part.id || createEventId('tool');
620
743
  const result = toolResults.get(part.id);
@@ -633,8 +756,11 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
633
756
  }
634
757
  }
635
758
  }
636
- } else if (!isHiddenSystemContextContent(msg.message.content)) {
637
- events.push(...createTextSpanEvents({ text: msg.message.content, provider, sessionId, timestamp, rawRef }));
759
+ } else {
760
+ const cleanText = stripAssistantProtocolTags(msg.message.content);
761
+ if (cleanText && !isHiddenSystemContextContent(cleanText)) {
762
+ events.push(...createTextSpanEvents({ text: cleanText, provider, sessionId, timestamp, rawRef }));
763
+ }
638
764
  }
639
765
  continue;
640
766
  }
@@ -660,6 +786,12 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
660
786
  const clientRequestId = getConversationEventClientRequestId(event);
661
787
  const optimisticIndex = findMatchingOptimisticUserMessageIndex(nextMessages, event, payload);
662
788
  const normalizedContentBlocks = normalizeMessageContentBlocks(payload.contentBlocks);
789
+ const rawText = typeof payload.text === 'string' ? payload.text : '';
790
+ const visibleText = rawText ? extractVisibleUserMessage(rawText) : '';
791
+
792
+ if (rawText.trim() && !visibleText && normalizedContentBlocks.length === 0) {
793
+ break;
794
+ }
663
795
 
664
796
  if (optimisticIndex >= 0) {
665
797
  const existingMessage = nextMessages[optimisticIndex] || {};
@@ -670,7 +802,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
670
802
  nextMessages[optimisticIndex] = {
671
803
  ...existingMessage,
672
804
  type: 'user',
673
- content: payload.text || existingMessage.content || '',
805
+ content: visibleText || existingMessage.content || '',
674
806
  timestamp: existingMessage.timestamp || timestamp,
675
807
  provider,
676
808
  contentBlocks: nextContentBlocks,
@@ -683,7 +815,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
683
815
 
684
816
  nextMessages.push({
685
817
  type: 'user',
686
- content: payload.text || '',
818
+ content: visibleText,
687
819
  timestamp,
688
820
  provider,
689
821
  contentBlocks: normalizedContentBlocks,
@@ -929,6 +1061,9 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
929
1061
  case CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED:
930
1062
  case CONVERSATION_EVENT_KINDS.MODE_UPDATE:
931
1063
  case CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE:
1064
+ case CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE:
1065
+ case CONVERSATION_EVENT_KINDS.SESSION_INFO_UPDATE:
1066
+ case CONVERSATION_EVENT_KINDS.USAGE_UPDATE:
932
1067
  case CONVERSATION_EVENT_KINDS.ARTIFACT_CREATED:
933
1068
  default:
934
1069
  break;
@@ -952,7 +1087,10 @@ export function conversationEventsToTimelineMessages(events = [], sessionProvide
952
1087
  export function extractAcpSessionMetadataFromConversationEvents(events = []) {
953
1088
  const metadata = {
954
1089
  modeState: null,
955
- availableCommands: []
1090
+ availableCommands: [],
1091
+ configOptions: [],
1092
+ sessionInfo: null,
1093
+ tokenUsage: null
956
1094
  };
957
1095
 
958
1096
  for (const event of Array.isArray(events) ? events : []) {
@@ -977,6 +1115,27 @@ export function extractAcpSessionMetadataFromConversationEvents(events = []) {
977
1115
  if (event.kind === CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE) {
978
1116
  metadata.availableCommands = normalizeAcpAvailableCommands(event.payload || {});
979
1117
  }
1118
+
1119
+ if (event.kind === CONVERSATION_EVENT_KINDS.CONFIG_OPTION_UPDATE) {
1120
+ metadata.configOptions = normalizeAcpConfigOptions(event.payload || {});
1121
+ }
1122
+
1123
+ if (event.kind === CONVERSATION_EVENT_KINDS.SESSION_INFO_UPDATE) {
1124
+ const nextSessionInfo = normalizeAcpSessionInfo(event.payload || {});
1125
+ if (nextSessionInfo) {
1126
+ metadata.sessionInfo = {
1127
+ ...(metadata.sessionInfo && typeof metadata.sessionInfo === 'object' ? metadata.sessionInfo : {}),
1128
+ ...nextSessionInfo
1129
+ };
1130
+ }
1131
+ }
1132
+
1133
+ if (event.kind === CONVERSATION_EVENT_KINDS.USAGE_UPDATE) {
1134
+ const nextTokenUsage = normalizeAcpTokenUsage(event.payload || {});
1135
+ if (nextTokenUsage) {
1136
+ metadata.tokenUsage = nextTokenUsage;
1137
+ }
1138
+ }
980
1139
  }
981
1140
 
982
1141
  return metadata;