@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.
- package/LICENSE +21 -675
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-GBcTeeUS.js +460 -0
- package/dist/assets/ReviewApp-C9K--AQE.js +1 -0
- package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DR_8uFCo.js} +1 -1
- package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-D0njlQ_7.js} +1 -1
- package/dist/assets/{arc-BBmKEN-S.js → arc-CKlr_Rec.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-BmO_uLUH.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-C67kFoXx.js} +1 -1
- package/dist/assets/channel-V3MBjKys.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-5UAE0Vg-.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DjDPvUUP.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
- package/dist/assets/clone-BbMGfZwt.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-D-60XrkJ.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-BueeqoYm.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-D0oVnO-x.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BwhbbgCP.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
- package/dist/assets/{graph-D11wiwHo.js → graph-DzKos-N0.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-CKDMgz3X.js} +1 -1
- package/dist/assets/{index-DFxzgWoO.js → index-DiQlHzGj.js} +2 -2
- package/dist/assets/index-Drat2nB9.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-CtihxDxl.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-Du00J8_d.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +1 -1
- package/dist/assets/{layout-BLUNf-PJ.js → layout-B80Sityu.js} +1 -1
- package/dist/assets/{linear-DukIV_Xv.js → linear-sRQLOf5H.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-CBuVs4eJ.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-C5IL_xi-.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-COfUcLWt.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DSb-CJ5B.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-C2VpkMwA.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-Dx1hP5lg.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-CkLOdYCZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-Dz7_EqNA.js → vendor-codemirror-BxPY6emf.js} +1 -1
- package/dist/assets/{vendor-react-Cpt6D04s.js → vendor-react-xmA_f8ig.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-D96ZI6Mg.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-6fh6qmzN.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +3 -3
- package/server/acp-runtime/client.js +9 -2
- package/server/acp-runtime/session-store.js +4 -4
- package/server/cli.js +23 -2
- package/server/external-agent/service.js +24 -6
- package/server/external-agent/ws.js +63 -3
- package/server/index.js +34 -5
- package/server/projects.js +536 -161
- package/server/routes/session-core.js +149 -86
- package/server/session-core/eventStore.js +45 -18
- package/server/session-core/providerAdapters.js +50 -13
- package/server/session-core/runtimeState.js +8 -0
- package/shared/conversationEvents.js +78 -14
- package/dist/assets/App-CTKZtqB1.js +0 -460
- package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
- package/dist/assets/channel-1oJBvF-0.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
- package/dist/assets/clone-CinxIlEu.js +0 -1
- package/dist/assets/index-YCFGDVKw.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
- package/server/_legacy-providers/README.md +0 -30
- package/server/_legacy-providers/claude-sdk.js +0 -956
- package/server/_legacy-providers/gemini-cli.js +0 -368
- package/server/_legacy-providers/openai-codex.js +0 -705
- package/server/_legacy-providers/opencode-cli.js +0 -674
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import
|
|
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
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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.
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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
|
|
206
|
-
|
|
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)
|
|
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'
|
|
617
|
-
|
|
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
|
|
637
|
-
|
|
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:
|
|
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:
|
|
750
|
+
content: visibleText,
|
|
687
751
|
timestamp,
|
|
688
752
|
provider,
|
|
689
753
|
contentBlocks: normalizedContentBlocks,
|