@axhub/genie 0.2.8 → 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 (83) 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/ReviewApp-C9K--AQE.js +1 -0
  5. package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DR_8uFCo.js} +1 -1
  6. package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-D0njlQ_7.js} +1 -1
  7. package/dist/assets/{arc-BBmKEN-S.js → arc-CKlr_Rec.js} +1 -1
  8. package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-BmO_uLUH.js} +1 -1
  9. package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +1 -1
  10. package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-C67kFoXx.js} +1 -1
  11. package/dist/assets/channel-V3MBjKys.js +1 -0
  12. package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
  13. package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
  15. package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
  16. package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
  17. package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-5UAE0Vg-.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
  19. package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DjDPvUUP.js} +1 -1
  20. package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
  21. package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
  22. package/dist/assets/clone-BbMGfZwt.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-D-60XrkJ.js} +1 -1
  24. package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
  25. package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-BueeqoYm.js} +1 -1
  26. package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
  27. package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
  28. package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-D0oVnO-x.js} +1 -1
  29. package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +1 -1
  30. package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BwhbbgCP.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
  32. package/dist/assets/{graph-D11wiwHo.js → graph-DzKos-N0.js} +1 -1
  33. package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-CKDMgz3X.js} +1 -1
  34. package/dist/assets/{index-DFxzgWoO.js → index-DiQlHzGj.js} +2 -2
  35. package/dist/assets/index-Drat2nB9.css +1 -0
  36. package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-CtihxDxl.js} +1 -1
  38. package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-Du00J8_d.js} +1 -1
  39. package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +1 -1
  40. package/dist/assets/{layout-BLUNf-PJ.js → layout-B80Sityu.js} +1 -1
  41. package/dist/assets/{linear-DukIV_Xv.js → linear-sRQLOf5H.js} +1 -1
  42. package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-CBuVs4eJ.js} +6 -6
  43. package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-C5IL_xi-.js} +1 -1
  44. package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-COfUcLWt.js} +1 -1
  46. package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DSb-CJ5B.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-C2VpkMwA.js} +1 -1
  49. package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
  51. package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-Dx1hP5lg.js} +1 -1
  52. package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-CkLOdYCZ.js} +1 -1
  53. package/dist/assets/{vendor-codemirror-Dz7_EqNA.js → vendor-codemirror-BxPY6emf.js} +1 -1
  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-D6KWcnln.js} +1 -1
  56. package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-6fh6qmzN.js} +1 -1
  57. package/dist/index.html +5 -5
  58. package/package.json +3 -3
  59. package/server/acp-runtime/client.js +9 -2
  60. package/server/acp-runtime/session-store.js +4 -4
  61. package/server/cli.js +23 -2
  62. package/server/external-agent/service.js +24 -6
  63. package/server/external-agent/ws.js +63 -3
  64. package/server/index.js +34 -5
  65. package/server/projects.js +536 -161
  66. package/server/routes/session-core.js +149 -86
  67. package/server/session-core/eventStore.js +45 -18
  68. package/server/session-core/providerAdapters.js +50 -13
  69. package/server/session-core/runtimeState.js +8 -0
  70. package/shared/conversationEvents.js +78 -14
  71. package/dist/assets/App-CTKZtqB1.js +0 -460
  72. package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
  73. package/dist/assets/channel-1oJBvF-0.js +0 -1
  74. package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
  75. package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
  76. package/dist/assets/clone-CinxIlEu.js +0 -1
  77. package/dist/assets/index-YCFGDVKw.css +0 -1
  78. package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
  79. package/server/_legacy-providers/README.md +0 -30
  80. package/server/_legacy-providers/claude-sdk.js +0 -956
  81. package/server/_legacy-providers/gemini-cli.js +0 -368
  82. package/server/_legacy-providers/openai-codex.js +0 -705
  83. package/server/_legacy-providers/opencode-cli.js +0 -674
@@ -1,8 +1,17 @@
1
1
  import express from 'express';
2
- import { getProjects } from '../projects.js';
2
+ import crypto from 'node:crypto';
3
+ import path from 'path';
4
+ import {
5
+ getClaudeSessionMetadata,
6
+ getCodexSessionMetadata,
7
+ getGeminiSessionMetadata,
8
+ getOpencodeSessionMetadata,
9
+ getProjects,
10
+ getProjectsList
11
+ } from '../projects.js';
3
12
  import { discoverAllProviders, discoverProvider } from '../session-core/providerDiscovery.js';
4
13
  import { getProviderAdapter } from '../session-core/providerAdapters.js';
5
- import { listAcpSessions } from '../acp-runtime/session-store.js';
14
+ import { findAcpSessionRecord, listAcpSessions } from '../acp-runtime/session-store.js';
6
15
 
7
16
  const router = express.Router();
8
17
 
@@ -29,6 +38,127 @@ async function flattenProjectSessions(project) {
29
38
  .sort((a, b) => new Date(b.lastActivity || b.updated_at || b.createdAt || 0) - new Date(a.lastActivity || a.updated_at || a.createdAt || 0));
30
39
  }
31
40
 
41
+ async function resolveProviderSessionRoute({
42
+ provider,
43
+ sessionId,
44
+ projectList = null
45
+ }) {
46
+ const normalizedProvider = String(provider || '').trim().toLowerCase();
47
+ const normalizedSessionId = String(sessionId || '').trim();
48
+
49
+ if (!normalizedProvider || !normalizedSessionId) {
50
+ return null;
51
+ }
52
+
53
+ const projects = Array.isArray(projectList) ? projectList : await getProjectsList();
54
+ const normalizeComparableProjectPath = (projectPath) => {
55
+ if (typeof projectPath !== 'string' || !projectPath.trim()) {
56
+ return '';
57
+ }
58
+
59
+ const withoutWindowsLongPathPrefix = projectPath.startsWith('\\\\?\\')
60
+ ? projectPath.slice(4)
61
+ : projectPath;
62
+
63
+ return path.normalize(withoutWindowsLongPathPrefix);
64
+ };
65
+
66
+ const findProjectByPath = (projectPath) => {
67
+ const normalizedProjectPath = normalizeComparableProjectPath(projectPath);
68
+ if (!normalizedProjectPath) {
69
+ return null;
70
+ }
71
+
72
+ return projects.find((project) => {
73
+ const candidatePath = project.fullPath || project.path || '';
74
+ return normalizeComparableProjectPath(candidatePath) === normalizedProjectPath;
75
+ }) || null;
76
+ };
77
+
78
+ const acpRecord = await findAcpSessionRecord(normalizedSessionId, normalizedProvider);
79
+ const acpProject = findProjectByPath(acpRecord?.projectPath || null);
80
+
81
+ let matchedProject = acpProject;
82
+ let resolvedSource = acpRecord ? 'acp' : 'legacy';
83
+
84
+ if (!matchedProject) {
85
+ switch (normalizedProvider) {
86
+ case 'claude': {
87
+ const metadata = await getClaudeSessionMetadata(normalizedSessionId);
88
+ matchedProject = findProjectByPath(metadata?.cwd || null);
89
+ break;
90
+ }
91
+ case 'codex': {
92
+ const metadata = await getCodexSessionMetadata(normalizedSessionId);
93
+ matchedProject = findProjectByPath(metadata?.cwd || null);
94
+ break;
95
+ }
96
+ case 'gemini': {
97
+ const metadata = await getGeminiSessionMetadata(normalizedSessionId);
98
+ const projectHash = String(metadata?.projectHash || '').trim();
99
+
100
+ if (projectHash) {
101
+ matchedProject = projects.find((project) => {
102
+ const projectPath = normalizeComparableProjectPath(project.fullPath || project.path || '');
103
+ if (!projectPath) {
104
+ return false;
105
+ }
106
+
107
+ const candidateHash = crypto.createHash('sha256').update(projectPath).digest('hex');
108
+ return candidateHash === projectHash;
109
+ }) || null;
110
+ }
111
+ break;
112
+ }
113
+ case 'opencode': {
114
+ const metadata = await getOpencodeSessionMetadata(normalizedSessionId);
115
+ matchedProject = findProjectByPath(metadata?.cwd || null);
116
+ break;
117
+ }
118
+ default:
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (!matchedProject) {
124
+ return null;
125
+ }
126
+
127
+ const adapter = getProviderAdapter(normalizedProvider);
128
+ let sessions = [];
129
+
130
+ if (normalizedProvider === 'claude') {
131
+ sessions = await adapter.listSessions({
132
+ projectName: matchedProject.name,
133
+ projectPath: matchedProject.fullPath || matchedProject.path,
134
+ limit: 1000,
135
+ offset: 0
136
+ });
137
+ } else {
138
+ sessions = await adapter.listSessions({
139
+ projectPath: matchedProject.fullPath || matchedProject.path,
140
+ limit: 0
141
+ });
142
+ }
143
+
144
+ const matchedSession = (Array.isArray(sessions) ? sessions : []).find((session) => session.id === normalizedSessionId);
145
+ if (!matchedSession) {
146
+ return null;
147
+ }
148
+
149
+ return {
150
+ provider: normalizedProvider,
151
+ source: matchedSession.source || resolvedSource,
152
+ session: matchedSession,
153
+ project: {
154
+ name: matchedProject.name,
155
+ fullPath: matchedProject.fullPath || matchedProject.path,
156
+ path: matchedProject.path,
157
+ displayName: matchedProject.displayName || matchedProject.name
158
+ }
159
+ };
160
+ }
161
+
32
162
  router.get('/providers', async (req, res) => {
33
163
  try {
34
164
  const providers = await discoverAllProviders({ projectPath: req.query.projectPath });
@@ -68,98 +198,29 @@ router.get('/projects/:projectName/history-index', async (req, res) => {
68
198
  }
69
199
  });
70
200
 
71
- router.get('/sessions/:sessionId/resolve', async (req, res) => {
201
+ router.get('/sessions/:provider/:sessionId/resolve', async (req, res) => {
72
202
  try {
73
- const projects = await getProjects();
203
+ const requestedProvider = String(req.params.provider || '').trim().toLowerCase();
74
204
  const requestedSessionId = String(req.params.sessionId || '').trim();
75
- const providerHint = typeof req.query.provider === 'string' ? req.query.provider.trim().toLowerCase() : '';
76
-
77
- if (!requestedSessionId) {
78
- return res.status(400).json({ success: false, error: 'Session id is required' });
79
- }
80
205
 
81
- const providerOrder = [
82
- providerHint,
83
- 'codex',
84
- 'claude',
85
- 'gemini',
86
- 'opencode'
87
- ].filter((provider, index, all) => provider && all.indexOf(provider) === index);
88
-
89
- let directProjectMatch = null;
90
- let directMatchSessions = [];
91
-
92
- for (const project of projects) {
93
- const flattened = await flattenProjectSessions(project);
94
- if (flattened.some((session) => session.id === requestedSessionId)) {
95
- directProjectMatch = project;
96
- directMatchSessions = flattened;
97
- break;
98
- }
206
+ if (!requestedProvider || !requestedSessionId) {
207
+ return res.status(400).json({ success: false, error: 'provider and session id are required' });
99
208
  }
100
209
 
101
- if (directProjectMatch) {
102
- const matchedSession = directMatchSessions.find((session) => session.id === requestedSessionId);
103
-
104
- return res.json({
105
- success: true,
106
- found: true,
107
- provider: matchedSession.provider,
108
- source: matchedSession.source || 'legacy',
109
- session: matchedSession,
110
- project: {
111
- name: directProjectMatch.name,
112
- fullPath: directProjectMatch.fullPath || directProjectMatch.path,
113
- path: directProjectMatch.path,
114
- displayName: directProjectMatch.displayName || directProjectMatch.name
115
- }
116
- });
117
- }
118
-
119
- for (const provider of providerOrder) {
120
- const adapter = getProviderAdapter(provider);
121
-
122
- for (const project of projects) {
123
- let sessions = [];
124
-
125
- try {
126
- if (provider === 'claude') {
127
- const result = await adapter.listSessions({
128
- projectName: project.name,
129
- projectPath: project.fullPath || project.path,
130
- limit: 1000,
131
- offset: 0
132
- });
133
- sessions = Array.isArray(result) ? result : [];
134
- } else {
135
- sessions = await adapter.listSessions({ projectPath: project.fullPath || project.path, limit: 0 });
136
- }
137
- } catch (error) {
138
- continue;
139
- }
140
-
141
- const matchedSession = sessions.find((session) => session.id === requestedSessionId);
142
- if (!matchedSession) {
143
- continue;
144
- }
210
+ const result = await resolveProviderSessionRoute({
211
+ provider: requestedProvider,
212
+ sessionId: requestedSessionId
213
+ });
145
214
 
146
- return res.json({
147
- success: true,
148
- found: true,
149
- provider,
150
- source: matchedSession.source || 'legacy',
151
- session: matchedSession,
152
- project: {
153
- name: project.name,
154
- fullPath: project.fullPath || project.path,
155
- path: project.path,
156
- displayName: project.displayName || project.name
157
- }
158
- });
159
- }
215
+ if (!result) {
216
+ return res.status(404).json({ success: true, found: false });
160
217
  }
161
218
 
162
- res.status(404).json({ success: true, found: false });
219
+ res.json({
220
+ success: true,
221
+ found: true,
222
+ ...result
223
+ });
163
224
  } catch (error) {
164
225
  res.status(500).json({ success: false, error: error.message });
165
226
  }
@@ -208,4 +269,6 @@ router.get('/sessions/:provider/:sessionId/events', async (req, res) => {
208
269
  }
209
270
  });
210
271
 
272
+ export { resolveProviderSessionRoute };
273
+
211
274
  export default router;
@@ -1,6 +1,8 @@
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,
@@ -13,7 +15,11 @@ const PERSISTED_EVENT_KINDS = new Set([
13
15
  CONVERSATION_EVENT_KINDS.ERROR,
14
16
  CONVERSATION_EVENT_KINDS.APPROVAL_REQUEST,
15
17
  CONVERSATION_EVENT_KINDS.APPROVAL_RESOLVED,
16
- CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE
18
+ CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE,
19
+ CONVERSATION_EVENT_KINDS.MODE_UPDATE,
20
+ CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE,
21
+ CONVERSATION_EVENT_KINDS.PLAN_UPDATE,
22
+ CONVERSATION_EVENT_KINDS.ARTIFACT_CREATED
17
23
  ]);
18
24
 
19
25
  function getSessionEventFilePath(provider, sessionId) {
@@ -24,14 +30,19 @@ function normalizePersistedEvents(events = []) {
24
30
  return events.filter((event) => (
25
31
  isConversationEvent(event) &&
26
32
  event.sessionId &&
27
- (
28
- event.extensions?.runtimeSource === 'acp' ||
29
- event.rawRef?.runtime === 'acp' ||
30
- PERSISTED_EVENT_KINDS.has(event.kind)
31
- )
33
+ PERSISTED_EVENT_KINDS.has(event.kind)
32
34
  ));
33
35
  }
34
36
 
37
+ function extractSerializedEventKind(line) {
38
+ if (typeof line !== 'string' || !line) {
39
+ return null;
40
+ }
41
+
42
+ const match = line.match(/"kind"\s*:\s*"([^"]+)"/);
43
+ return match?.[1] || null;
44
+ }
45
+
35
46
  function sortObjectKeys(value) {
36
47
  if (Array.isArray(value)) {
37
48
  return value.map(sortObjectKeys);
@@ -91,19 +102,35 @@ export async function readMirroredConversationEvents(provider, sessionId) {
91
102
  const filePath = getSessionEventFilePath(provider, sessionId);
92
103
 
93
104
  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;
105
+ const fileStream = fsSync.createReadStream(filePath, { encoding: 'utf8' });
106
+ const rl = readline.createInterface({
107
+ input: fileStream,
108
+ crlfDelay: Infinity
109
+ });
110
+ const events = [];
111
+
112
+ for await (const rawLine of rl) {
113
+ const line = rawLine.trim();
114
+ if (!line) {
115
+ continue;
116
+ }
117
+
118
+ const kind = extractSerializedEventKind(line);
119
+ if (!kind || !PERSISTED_EVENT_KINDS.has(kind)) {
120
+ continue;
121
+ }
122
+
123
+ try {
124
+ const event = JSON.parse(line);
125
+ if (isConversationEvent(event) && PERSISTED_EVENT_KINDS.has(event.kind)) {
126
+ events.push(event);
104
127
  }
105
- })
106
- .filter(isConversationEvent);
128
+ } catch {
129
+ // Skip malformed lines and oversized legacy transcript events.
130
+ }
131
+ }
132
+
133
+ return events;
107
134
  } catch (error) {
108
135
  if (error?.code === 'ENOENT') {
109
136
  return [];
@@ -37,6 +37,18 @@ async function normalizeLegacyLoadResult(result, provider, sessionId) {
37
37
  };
38
38
  }
39
39
 
40
+ function createEventLoadResult(events = [], source = 'acp') {
41
+ const normalizedEvents = Array.isArray(events) ? events : [];
42
+ return {
43
+ events: normalizedEvents,
44
+ total: normalizedEvents.length,
45
+ hasMore: false,
46
+ offset: 0,
47
+ limit: null,
48
+ source
49
+ };
50
+ }
51
+
40
52
  function mergeSessionLists(legacySessions = [], acpSessions = []) {
41
53
  const merged = new Map();
42
54
 
@@ -59,21 +71,30 @@ function mergeSessionLists(legacySessions = [], acpSessions = []) {
59
71
  });
60
72
  }
61
73
 
62
- async function loadAcpEvents(provider, sessionId) {
74
+ async function loadAcpEvents(provider, sessionId, nativeHistoryLoader = null) {
63
75
  const record = await findAcpSessionRecord(sessionId, provider);
64
76
  if (!record) {
65
77
  return null;
66
78
  }
67
79
 
80
+ if (typeof nativeHistoryLoader === 'function') {
81
+ try {
82
+ const nativeResult = await nativeHistoryLoader();
83
+ const normalizedResult = await normalizeLegacyLoadResult(nativeResult, provider, sessionId);
84
+ if (Array.isArray(normalizedResult)) {
85
+ return createEventLoadResult(normalizedResult, 'acp');
86
+ }
87
+ return {
88
+ ...normalizedResult,
89
+ source: 'acp'
90
+ };
91
+ } catch {
92
+ // Fall back to the mirrored event store when native history is unavailable.
93
+ }
94
+ }
95
+
68
96
  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
- };
97
+ return createEventLoadResult(events, 'acp');
77
98
  }
78
99
 
79
100
  const PROVIDER_ADAPTERS = {
@@ -89,7 +110,11 @@ const PROVIDER_ADAPTERS = {
89
110
  );
90
111
  },
91
112
  async loadEvents({ projectName, sessionId, limit = null, offset = 0 }) {
92
- const acpResult = await loadAcpEvents('claude', sessionId);
113
+ const acpResult = await loadAcpEvents(
114
+ 'claude',
115
+ sessionId,
116
+ () => getSessionMessages(projectName, sessionId, limit, offset),
117
+ );
93
118
  if (acpResult) {
94
119
  return acpResult;
95
120
  }
@@ -110,7 +135,11 @@ const PROVIDER_ADAPTERS = {
110
135
  );
111
136
  },
112
137
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
113
- const acpResult = await loadAcpEvents('codex', sessionId);
138
+ const acpResult = await loadAcpEvents(
139
+ 'codex',
140
+ sessionId,
141
+ () => getCodexSessionMessages(sessionId, limit, offset),
142
+ );
114
143
  if (acpResult) {
115
144
  return acpResult;
116
145
  }
@@ -131,7 +160,11 @@ const PROVIDER_ADAPTERS = {
131
160
  );
132
161
  },
133
162
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
134
- const acpResult = await loadAcpEvents('gemini', sessionId);
163
+ const acpResult = await loadAcpEvents(
164
+ 'gemini',
165
+ sessionId,
166
+ () => getGeminiSessionMessages(sessionId, limit, offset),
167
+ );
135
168
  if (acpResult) {
136
169
  return acpResult;
137
170
  }
@@ -152,7 +185,11 @@ const PROVIDER_ADAPTERS = {
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);
@@ -185,11 +185,61 @@ function createTextEndEvent({
185
185
  ];
186
186
  }
187
187
 
188
+ function extractProtocolUserMessage(value) {
189
+ const text = String(value || '').trim();
190
+ if (!text) {
191
+ return null;
192
+ }
193
+
194
+ const dynamicContextMatch = text.match(
195
+ /<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>\s*<user_message(?:\s[^>]*)?>\s*([\s\S]*?)\s*<\/user_message>/i
196
+ );
197
+ if (!dynamicContextMatch) {
198
+ return null;
199
+ }
200
+
201
+ return String(dynamicContextMatch[1] || '').trim();
202
+ }
203
+
204
+ function hasAcpProtocolTags(value) {
205
+ return /<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/i.test(String(value || ''));
206
+ }
207
+
208
+ export function stripAssistantProtocolTags(text) {
209
+ if (typeof text !== 'string') {
210
+ return text;
211
+ }
212
+
213
+ const visibleUserMessage = extractProtocolUserMessage(text);
214
+ if (visibleUserMessage != null) {
215
+ return visibleUserMessage;
216
+ }
217
+
218
+ const hadProtocolTags = hasAcpProtocolTags(text);
219
+ let result = text;
220
+
221
+ result = result.replace(/<subagent_notification(?:\s[^>]*)?>[\s\S]*?<\/subagent_notification>/gi, '');
222
+ result = result.replace(/<environment_context(?:\s[^>]*)?>[\s\S]*?<\/environment_context>/gi, '');
223
+ result = result.replace(/<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>/gi, '');
224
+ result = result.replace(/<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/gi, '');
225
+
226
+ if (hadProtocolTags) {
227
+ result = result.replace(/["']?\s*\}\s*\}\s*$/g, '');
228
+ }
229
+
230
+ return result.trim();
231
+ }
232
+
188
233
  function extractVisibleUserMessage(value) {
189
234
  const text = String(value || '').trim();
190
235
  if (!text) return '';
191
236
 
192
- if (text.startsWith('# AGENTS.md instructions for ') || text.includes('<environment_context>')) {
237
+ if (
238
+ text.startsWith('# AGENTS.md instructions for ') ||
239
+ text.includes('<environment_context>') ||
240
+ text.startsWith('<subagent_notification>') ||
241
+ text.startsWith('</subagent_notification>')
242
+ ) {
193
243
  return '';
194
244
  }
195
245
 
@@ -202,11 +252,9 @@ function extractVisibleUserMessage(value) {
202
252
  return '';
203
253
  }
204
254
 
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();
255
+ const protocolUserMessage = extractProtocolUserMessage(text);
256
+ if (protocolUserMessage != null) {
257
+ return protocolUserMessage;
210
258
  }
211
259
 
212
260
  return text;
@@ -222,12 +270,16 @@ function isHiddenSystemContextContent(value) {
222
270
  text.startsWith('[Dynamic Context]') ||
223
271
  text.startsWith('[Hidden Context]') ||
224
272
  text.startsWith('# AGENTS.md instructions for ') ||
225
- text.startsWith('[DYNAMIC CONTEXT V1]')
273
+ text.startsWith('[DYNAMIC CONTEXT V1]') ||
274
+ text.startsWith('<subagent_notification>') ||
275
+ text.startsWith('</subagent_notification>')
226
276
  ) {
227
277
  return true;
228
278
  }
229
279
 
230
- return /^<dynamic_context(?:\s|>)/i.test(text) || text.includes('<environment_context>');
280
+ return /^<dynamic_context(?:\s|>)/i.test(text)
281
+ || text.includes('<environment_context>')
282
+ || /<subagent_notification(?:\s|>)/i.test(text);
231
283
  }
232
284
 
233
285
  function stringifyToolContent(value) {
@@ -613,8 +665,11 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
613
665
  if (msg?.message?.role === 'assistant' && msg?.message?.content) {
614
666
  if (Array.isArray(msg.message.content)) {
615
667
  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 }));
668
+ if (part?.type === 'text') {
669
+ const cleanText = stripAssistantProtocolTags(part.text);
670
+ if (cleanText && !isHiddenSystemContextContent(cleanText)) {
671
+ events.push(...createTextSpanEvents({ text: cleanText, provider, sessionId, timestamp, rawRef }));
672
+ }
618
673
  } else if (part?.type === 'tool_use') {
619
674
  const toolCallId = part.id || createEventId('tool');
620
675
  const result = toolResults.get(part.id);
@@ -633,8 +688,11 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
633
688
  }
634
689
  }
635
690
  }
636
- } else if (!isHiddenSystemContextContent(msg.message.content)) {
637
- events.push(...createTextSpanEvents({ text: msg.message.content, provider, sessionId, timestamp, rawRef }));
691
+ } else {
692
+ const cleanText = stripAssistantProtocolTags(msg.message.content);
693
+ if (cleanText && !isHiddenSystemContextContent(cleanText)) {
694
+ events.push(...createTextSpanEvents({ text: cleanText, provider, sessionId, timestamp, rawRef }));
695
+ }
638
696
  }
639
697
  continue;
640
698
  }
@@ -660,6 +718,12 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
660
718
  const clientRequestId = getConversationEventClientRequestId(event);
661
719
  const optimisticIndex = findMatchingOptimisticUserMessageIndex(nextMessages, event, payload);
662
720
  const normalizedContentBlocks = normalizeMessageContentBlocks(payload.contentBlocks);
721
+ const rawText = typeof payload.text === 'string' ? payload.text : '';
722
+ const visibleText = rawText ? extractVisibleUserMessage(rawText) : '';
723
+
724
+ if (rawText.trim() && !visibleText && normalizedContentBlocks.length === 0) {
725
+ break;
726
+ }
663
727
 
664
728
  if (optimisticIndex >= 0) {
665
729
  const existingMessage = nextMessages[optimisticIndex] || {};
@@ -670,7 +734,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
670
734
  nextMessages[optimisticIndex] = {
671
735
  ...existingMessage,
672
736
  type: 'user',
673
- content: payload.text || existingMessage.content || '',
737
+ content: visibleText || existingMessage.content || '',
674
738
  timestamp: existingMessage.timestamp || timestamp,
675
739
  provider,
676
740
  contentBlocks: nextContentBlocks,
@@ -683,7 +747,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
683
747
 
684
748
  nextMessages.push({
685
749
  type: 'user',
686
- content: payload.text || '',
750
+ content: visibleText,
687
751
  timestamp,
688
752
  provider,
689
753
  contentBlocks: normalizedContentBlocks,