@axhub/genie 0.2.9 → 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.
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-CYCCsgwf.js +264 -0
- package/dist/assets/{ReviewApp-C9K--AQE.js → ReviewApp-0srHIXwb.js} +1 -1
- package/dist/assets/{_basePickBy-DR_8uFCo.js → _basePickBy-DVVb07UV.js} +1 -1
- package/dist/assets/{_baseUniq-D0njlQ_7.js → _baseUniq-BtbziL5G.js} +1 -1
- package/dist/assets/{arc-CKlr_Rec.js → arc-BsCC8yBD.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-BmO_uLUH.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-DhAeO-56.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-C67kFoXx.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
- package/dist/assets/channel-BMhScXFe.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-mLLagvJi.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Lx-hOjlM.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-Bt-XmVUV.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-Cya6gaDV.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-Bd7Ig6tF.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-5UAE0Vg-.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BAxZ8m7w.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-DjDPvUUP.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
- package/dist/assets/clone-BPqOt4r3.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-D-60XrkJ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-bqu3ZS4K.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-BueeqoYm.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-D4fDv2E7.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-WqipY3fN.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-D0oVnO-x.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-DzbGyxrr.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-BwhbbgCP.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
- package/dist/assets/{graph-DzKos-N0.js → graph-CeJCMjan.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-CKDMgz3X.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
- package/dist/assets/index-C514cLyb.js +2 -0
- package/dist/assets/index-h1DBl_g3.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BFicZbTf.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-CtihxDxl.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-Du00J8_d.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-BJi9S0iQ.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
- package/dist/assets/{layout-B80Sityu.js → layout-CI2RM-v6.js} +1 -1
- package/dist/assets/{linear-sRQLOf5H.js → linear-DE7bISck.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-CBuVs4eJ.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-C5IL_xi-.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-CeTwlJ8z.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-COfUcLWt.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-DSb-CJ5B.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-8jtuVb45.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-C2VpkMwA.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-fmwMqxxc.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-Dx1hP5lg.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-CkLOdYCZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
- package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
- package/dist/assets/{vennDiagram-LZ73GAT5-D6KWcnln.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-6fh6qmzN.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +6 -5
- package/server/acp-runtime/client.js +120 -14
- package/server/acp-runtime/index.js +54 -0
- package/server/acp-runtime/registry.js +2 -2
- package/server/acp-runtime/session-store.js +75 -1
- package/server/cli.js +32 -8
- package/server/database/db.js +20 -0
- package/server/external-agent/ws.js +477 -24
- package/server/index.js +78 -146
- package/server/lan-access/core.js +79 -0
- package/server/lan-access/state.js +102 -0
- package/server/middleware/auth.js +57 -14
- package/server/projects.js +423 -535
- package/server/routes/auth.js +24 -4
- package/server/routes/cli-auth.js +21 -25
- package/server/routes/codex.js +84 -298
- package/server/routes/commands.js +322 -407
- package/server/routes/lan-access.js +231 -0
- package/server/routes/projects.js +154 -158
- package/server/routes/session-core.js +13 -7
- package/server/routes/settings.js +113 -99
- package/server/session-core/eventStore.js +15 -2
- package/server/session-core/providerAdapters.js +28 -28
- package/server/session-core/sessionListMerge.js +47 -0
- package/shared/conversationEvents.js +96 -1
- package/shared/modelConstants.js +79 -99
- package/dist/assets/App-GBcTeeUS.js +0 -460
- package/dist/assets/channel-V3MBjKys.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +0 -1
- package/dist/assets/clone-BbMGfZwt.js +0 -1
- package/dist/assets/index-DiQlHzGj.js +0 -2
- package/dist/assets/index-Drat2nB9.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +0 -1
- package/dist/assets/vendor-codemirror-BxPY6emf.js +0 -39
- package/server/routes/git.js +0 -1110
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -536
- package/server/routes/taskmaster.js +0 -1963
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
|
@@ -2,40 +2,50 @@ import express from 'express';
|
|
|
2
2
|
import { apiKeysDb, credentialsDb } from '../database/db.js';
|
|
3
3
|
|
|
4
4
|
const router = express.Router();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
const DISALLOWED_CREDENTIAL_TYPES = new Set(['github_token']);
|
|
6
|
+
|
|
7
|
+
function parseNumericId(rawValue) {
|
|
8
|
+
const parsed = Number.parseInt(String(rawValue || ''), 10);
|
|
9
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sanitizeApiKeyRecord(record) {
|
|
13
|
+
const visiblePrefix = String(record?.api_key || '').slice(0, 10);
|
|
14
|
+
return {
|
|
15
|
+
...record,
|
|
16
|
+
api_key: visiblePrefix ? `${visiblePrefix}...` : ''
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function badRequest(res, error) {
|
|
21
|
+
return res.status(400).json({ error });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function notFound(res, error) {
|
|
25
|
+
return res.status(404).json({ error });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
router.get('/api-keys', (req, res) => {
|
|
12
29
|
try {
|
|
13
|
-
const apiKeys = apiKeysDb.getApiKeys(req.user.id);
|
|
14
|
-
|
|
15
|
-
const sanitizedKeys = apiKeys.map(key => ({
|
|
16
|
-
...key,
|
|
17
|
-
api_key: key.api_key.substring(0, 10) + '...'
|
|
18
|
-
}));
|
|
19
|
-
res.json({ apiKeys: sanitizedKeys });
|
|
30
|
+
const apiKeys = apiKeysDb.getApiKeys(req.user.id).map(sanitizeApiKeyRecord);
|
|
31
|
+
res.json({ apiKeys });
|
|
20
32
|
} catch (error) {
|
|
21
33
|
console.error('Error fetching API keys:', error);
|
|
22
34
|
res.status(500).json({ error: 'Failed to fetch API keys' });
|
|
23
35
|
}
|
|
24
36
|
});
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!keyName || !keyName.trim()) {
|
|
32
|
-
return res.status(400).json({ error: 'Key name is required' });
|
|
33
|
-
}
|
|
38
|
+
router.post('/api-keys', (req, res) => {
|
|
39
|
+
const keyName = String(req.body?.keyName || '').trim();
|
|
40
|
+
if (!keyName) {
|
|
41
|
+
return badRequest(res, 'Key name is required');
|
|
42
|
+
}
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
try {
|
|
45
|
+
const apiKey = apiKeysDb.createApiKey(req.user.id, keyName);
|
|
36
46
|
res.json({
|
|
37
47
|
success: true,
|
|
38
|
-
apiKey
|
|
48
|
+
apiKey
|
|
39
49
|
});
|
|
40
50
|
} catch (error) {
|
|
41
51
|
console.error('Error creating API key:', error);
|
|
@@ -43,56 +53,54 @@ router.post('/api-keys', async (req, res) => {
|
|
|
43
53
|
}
|
|
44
54
|
});
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
router.delete('/api-keys/:keyId', (req, res) => {
|
|
57
|
+
const keyId = parseNumericId(req.params.keyId);
|
|
58
|
+
if (keyId === null) {
|
|
59
|
+
return badRequest(res, 'Invalid API key id');
|
|
60
|
+
}
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
res
|
|
62
|
+
try {
|
|
63
|
+
const removed = apiKeysDb.deleteApiKey(req.user.id, keyId);
|
|
64
|
+
if (!removed) {
|
|
65
|
+
return notFound(res, 'API key not found');
|
|
56
66
|
}
|
|
67
|
+
|
|
68
|
+
res.json({ success: true });
|
|
57
69
|
} catch (error) {
|
|
58
70
|
console.error('Error deleting API key:', error);
|
|
59
71
|
res.status(500).json({ error: 'Failed to delete API key' });
|
|
60
72
|
}
|
|
61
73
|
});
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const { keyId } = req.params;
|
|
67
|
-
const { isActive } = req.body;
|
|
75
|
+
router.patch('/api-keys/:keyId/toggle', (req, res) => {
|
|
76
|
+
const keyId = parseNumericId(req.params.keyId);
|
|
77
|
+
const isActive = req.body?.isActive;
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
if (keyId === null) {
|
|
80
|
+
return badRequest(res, 'Invalid API key id');
|
|
81
|
+
}
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
if (typeof isActive !== 'boolean') {
|
|
84
|
+
return badRequest(res, 'isActive must be a boolean');
|
|
85
|
+
}
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
res
|
|
87
|
+
try {
|
|
88
|
+
const updated = apiKeysDb.toggleApiKey(req.user.id, keyId, isActive);
|
|
89
|
+
if (!updated) {
|
|
90
|
+
return notFound(res, 'API key not found');
|
|
79
91
|
}
|
|
92
|
+
|
|
93
|
+
res.json({ success: true });
|
|
80
94
|
} catch (error) {
|
|
81
95
|
console.error('Error toggling API key:', error);
|
|
82
96
|
res.status(500).json({ error: 'Failed to toggle API key' });
|
|
83
97
|
}
|
|
84
98
|
});
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
// Generic Credentials Management
|
|
88
|
-
// ===============================
|
|
89
|
-
|
|
90
|
-
// Get all credentials for the authenticated user (optionally filtered by type)
|
|
91
|
-
router.get('/credentials', async (req, res) => {
|
|
100
|
+
router.get('/credentials', (req, res) => {
|
|
92
101
|
try {
|
|
93
|
-
const
|
|
94
|
-
const credentials = credentialsDb.getCredentials(req.user.id,
|
|
95
|
-
// Don't send the actual credential values for security
|
|
102
|
+
const credentialType = typeof req.query.type === 'string' ? req.query.type.trim() : null;
|
|
103
|
+
const credentials = credentialsDb.getCredentials(req.user.id, credentialType || null);
|
|
96
104
|
res.json({ credentials });
|
|
97
105
|
} catch (error) {
|
|
98
106
|
console.error('Error fetching credentials:', error);
|
|
@@ -100,38 +108,40 @@ router.get('/credentials', async (req, res) => {
|
|
|
100
108
|
}
|
|
101
109
|
});
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
router.post('/credentials', (req, res) => {
|
|
112
|
+
const credentialName = String(req.body?.credentialName || '').trim();
|
|
113
|
+
const credentialType = String(req.body?.credentialType || '').trim();
|
|
114
|
+
const credentialValue = String(req.body?.credentialValue || '').trim();
|
|
115
|
+
const description = String(req.body?.description || '').trim();
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
if (!credentialName) {
|
|
118
|
+
return badRequest(res, 'Credential name is required');
|
|
119
|
+
}
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
if (!credentialType) {
|
|
122
|
+
return badRequest(res, 'Credential type is required');
|
|
123
|
+
}
|
|
115
124
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
if (!credentialValue) {
|
|
126
|
+
return badRequest(res, 'Credential value is required');
|
|
127
|
+
}
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
if (DISALLOWED_CREDENTIAL_TYPES.has(credentialType.toLowerCase())) {
|
|
130
|
+
return badRequest(res, 'GitHub tokens are no longer supported.');
|
|
131
|
+
}
|
|
123
132
|
|
|
124
|
-
|
|
133
|
+
try {
|
|
134
|
+
const credential = credentialsDb.createCredential(
|
|
125
135
|
req.user.id,
|
|
126
|
-
credentialName
|
|
127
|
-
credentialType
|
|
128
|
-
credentialValue
|
|
129
|
-
description
|
|
136
|
+
credentialName,
|
|
137
|
+
credentialType,
|
|
138
|
+
credentialValue,
|
|
139
|
+
description || null
|
|
130
140
|
);
|
|
131
141
|
|
|
132
142
|
res.json({
|
|
133
143
|
success: true,
|
|
134
|
-
credential
|
|
144
|
+
credential
|
|
135
145
|
});
|
|
136
146
|
} catch (error) {
|
|
137
147
|
console.error('Error creating credential:', error);
|
|
@@ -139,40 +149,44 @@ router.post('/credentials', async (req, res) => {
|
|
|
139
149
|
}
|
|
140
150
|
});
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
router.delete('/credentials/:credentialId', (req, res) => {
|
|
153
|
+
const credentialId = parseNumericId(req.params.credentialId);
|
|
154
|
+
if (credentialId === null) {
|
|
155
|
+
return badRequest(res, 'Invalid credential id');
|
|
156
|
+
}
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
res
|
|
158
|
+
try {
|
|
159
|
+
const removed = credentialsDb.deleteCredential(req.user.id, credentialId);
|
|
160
|
+
if (!removed) {
|
|
161
|
+
return notFound(res, 'Credential not found');
|
|
152
162
|
}
|
|
163
|
+
|
|
164
|
+
res.json({ success: true });
|
|
153
165
|
} catch (error) {
|
|
154
166
|
console.error('Error deleting credential:', error);
|
|
155
167
|
res.status(500).json({ error: 'Failed to delete credential' });
|
|
156
168
|
}
|
|
157
169
|
});
|
|
158
170
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const { credentialId } = req.params;
|
|
163
|
-
const { isActive } = req.body;
|
|
171
|
+
router.patch('/credentials/:credentialId/toggle', (req, res) => {
|
|
172
|
+
const credentialId = parseNumericId(req.params.credentialId);
|
|
173
|
+
const isActive = req.body?.isActive;
|
|
164
174
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
if (credentialId === null) {
|
|
176
|
+
return badRequest(res, 'Invalid credential id');
|
|
177
|
+
}
|
|
168
178
|
|
|
169
|
-
|
|
179
|
+
if (typeof isActive !== 'boolean') {
|
|
180
|
+
return badRequest(res, 'isActive must be a boolean');
|
|
181
|
+
}
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
res
|
|
183
|
+
try {
|
|
184
|
+
const updated = credentialsDb.toggleCredential(req.user.id, credentialId, isActive);
|
|
185
|
+
if (!updated) {
|
|
186
|
+
return notFound(res, 'Credential not found');
|
|
175
187
|
}
|
|
188
|
+
|
|
189
|
+
res.json({ success: true });
|
|
176
190
|
} catch (error) {
|
|
177
191
|
console.error('Error toggling credential:', error);
|
|
178
192
|
res.status(500).json({ error: 'Failed to toggle credential' });
|
|
@@ -9,8 +9,18 @@ import {
|
|
|
9
9
|
isConversationEvent
|
|
10
10
|
} from '../../shared/conversationEvents.js';
|
|
11
11
|
|
|
12
|
-
|
|
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
|
+
}
|
|
13
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,
|
|
14
24
|
CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED,
|
|
15
25
|
CONVERSATION_EVENT_KINDS.ERROR,
|
|
16
26
|
CONVERSATION_EVENT_KINDS.APPROVAL_REQUEST,
|
|
@@ -18,12 +28,15 @@ const PERSISTED_EVENT_KINDS = new Set([
|
|
|
18
28
|
CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE,
|
|
19
29
|
CONVERSATION_EVENT_KINDS.MODE_UPDATE,
|
|
20
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,
|
|
21
34
|
CONVERSATION_EVENT_KINDS.PLAN_UPDATE,
|
|
22
35
|
CONVERSATION_EVENT_KINDS.ARTIFACT_CREATED
|
|
23
36
|
]);
|
|
24
37
|
|
|
25
38
|
function getSessionEventFilePath(provider, sessionId) {
|
|
26
|
-
return path.join(
|
|
39
|
+
return path.join(getMirroredEventStoreRoot(), String(provider || 'claude'), `${sessionId}.jsonl`);
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
function normalizePersistedEvents(events = []) {
|
|
@@ -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);
|
|
@@ -49,34 +63,17 @@ function createEventLoadResult(events = [], source = 'acp') {
|
|
|
49
63
|
};
|
|
50
64
|
}
|
|
51
65
|
|
|
52
|
-
function mergeSessionLists(legacySessions = [], acpSessions = []) {
|
|
53
|
-
const merged = new Map();
|
|
54
|
-
|
|
55
|
-
legacySessions.forEach((session) => {
|
|
56
|
-
if (session?.id) {
|
|
57
|
-
merged.set(session.id, session);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
acpSessions.forEach((session) => {
|
|
62
|
-
if (session?.id) {
|
|
63
|
-
merged.set(session.id, session);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return Array.from(merged.values()).sort((left, right) => {
|
|
68
|
-
const leftTime = new Date(left?.lastActivity || left?.updatedAt || left?.createdAt || 0).getTime();
|
|
69
|
-
const rightTime = new Date(right?.lastActivity || right?.updatedAt || right?.createdAt || 0).getTime();
|
|
70
|
-
return rightTime - leftTime;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
66
|
async function loadAcpEvents(provider, sessionId, nativeHistoryLoader = null) {
|
|
75
67
|
const record = await findAcpSessionRecord(sessionId, provider);
|
|
76
68
|
if (!record) {
|
|
77
69
|
return null;
|
|
78
70
|
}
|
|
79
71
|
|
|
72
|
+
const mirroredEvents = await readMirroredConversationEvents(provider, sessionId);
|
|
73
|
+
if (hasReplayableTranscriptContent(mirroredEvents)) {
|
|
74
|
+
return createEventLoadResult(mirroredEvents, 'acp');
|
|
75
|
+
}
|
|
76
|
+
|
|
80
77
|
if (typeof nativeHistoryLoader === 'function') {
|
|
81
78
|
try {
|
|
82
79
|
const nativeResult = await nativeHistoryLoader();
|
|
@@ -93,8 +90,7 @@ async function loadAcpEvents(provider, sessionId, nativeHistoryLoader = null) {
|
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
return createEventLoadResult(events, 'acp');
|
|
93
|
+
return createEventLoadResult(mirroredEvents, 'acp');
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
const PROVIDER_ADAPTERS = {
|
|
@@ -106,7 +102,8 @@ const PROVIDER_ADAPTERS = {
|
|
|
106
102
|
]);
|
|
107
103
|
return mergeSessionLists(
|
|
108
104
|
(result?.sessions || []).map((session) => ({ ...session, provider: 'claude', source: 'legacy' })),
|
|
109
|
-
acpSessions
|
|
105
|
+
acpSessions,
|
|
106
|
+
{ fallbackProvider: 'claude' }
|
|
110
107
|
);
|
|
111
108
|
},
|
|
112
109
|
async loadEvents({ projectName, sessionId, limit = null, offset = 0 }) {
|
|
@@ -131,7 +128,8 @@ const PROVIDER_ADAPTERS = {
|
|
|
131
128
|
]);
|
|
132
129
|
return mergeSessionLists(
|
|
133
130
|
sessions.map((session) => ({ ...session, provider: 'codex', source: 'legacy' })),
|
|
134
|
-
acpSessions
|
|
131
|
+
acpSessions,
|
|
132
|
+
{ fallbackProvider: 'codex' }
|
|
135
133
|
);
|
|
136
134
|
},
|
|
137
135
|
async loadEvents({ sessionId, limit = null, offset = 0 }) {
|
|
@@ -156,7 +154,8 @@ const PROVIDER_ADAPTERS = {
|
|
|
156
154
|
]);
|
|
157
155
|
return mergeSessionLists(
|
|
158
156
|
sessions.map((session) => ({ ...session, provider: 'gemini', source: 'legacy' })),
|
|
159
|
-
acpSessions
|
|
157
|
+
acpSessions,
|
|
158
|
+
{ fallbackProvider: 'gemini' }
|
|
160
159
|
);
|
|
161
160
|
},
|
|
162
161
|
async loadEvents({ sessionId, limit = null, offset = 0 }) {
|
|
@@ -181,7 +180,8 @@ const PROVIDER_ADAPTERS = {
|
|
|
181
180
|
]);
|
|
182
181
|
return mergeSessionLists(
|
|
183
182
|
sessions.map((session) => ({ ...session, provider: 'opencode', source: 'legacy' })),
|
|
184
|
-
acpSessions
|
|
183
|
+
acpSessions,
|
|
184
|
+
{ fallbackProvider: 'opencode' }
|
|
185
185
|
);
|
|
186
186
|
},
|
|
187
187
|
async loadEvents({ sessionId, limit = null, offset = 0 }) {
|
|
@@ -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',
|
|
@@ -417,6 +420,71 @@ function normalizeAcpAvailableCommands(value) {
|
|
|
417
420
|
}));
|
|
418
421
|
}
|
|
419
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
|
+
|
|
420
488
|
function mergeToolCallSnapshot(existingSnapshot = null, payload = {}) {
|
|
421
489
|
const nextSnapshot = existingSnapshot && typeof existingSnapshot === 'object'
|
|
422
490
|
? { ...existingSnapshot }
|
|
@@ -993,6 +1061,9 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
|
|
|
993
1061
|
case CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED:
|
|
994
1062
|
case CONVERSATION_EVENT_KINDS.MODE_UPDATE:
|
|
995
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:
|
|
996
1067
|
case CONVERSATION_EVENT_KINDS.ARTIFACT_CREATED:
|
|
997
1068
|
default:
|
|
998
1069
|
break;
|
|
@@ -1016,7 +1087,10 @@ export function conversationEventsToTimelineMessages(events = [], sessionProvide
|
|
|
1016
1087
|
export function extractAcpSessionMetadataFromConversationEvents(events = []) {
|
|
1017
1088
|
const metadata = {
|
|
1018
1089
|
modeState: null,
|
|
1019
|
-
availableCommands: []
|
|
1090
|
+
availableCommands: [],
|
|
1091
|
+
configOptions: [],
|
|
1092
|
+
sessionInfo: null,
|
|
1093
|
+
tokenUsage: null
|
|
1020
1094
|
};
|
|
1021
1095
|
|
|
1022
1096
|
for (const event of Array.isArray(events) ? events : []) {
|
|
@@ -1041,6 +1115,27 @@ export function extractAcpSessionMetadataFromConversationEvents(events = []) {
|
|
|
1041
1115
|
if (event.kind === CONVERSATION_EVENT_KINDS.AVAILABLE_COMMANDS_UPDATE) {
|
|
1042
1116
|
metadata.availableCommands = normalizeAcpAvailableCommands(event.payload || {});
|
|
1043
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
|
+
}
|
|
1044
1139
|
}
|
|
1045
1140
|
|
|
1046
1141
|
return metadata;
|