@axhub/genie 0.2.5 → 0.2.7

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 (139) hide show
  1. package/dist/assets/App-BWSqiXAT.js +220 -0
  2. package/dist/assets/App-DrlLKa8f.css +1 -0
  3. package/dist/assets/ReviewApp-nz3mbArg.js +1 -0
  4. package/dist/assets/{_basePickBy-CFRQvihx.js → _basePickBy-C19AekOu.js} +1 -1
  5. package/dist/assets/{_baseUniq-Dhh8nCvs.js → _baseUniq-JsnevLw_.js} +1 -1
  6. package/dist/assets/{arc-DQ0v3dU4.js → arc-BLpcuBlf.js} +1 -1
  7. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +36 -0
  8. package/dist/assets/{blockDiagram-WCTKOSBZ-Bbxhj5KC.js → blockDiagram-WCTKOSBZ-DQBLwsUS.js} +3 -3
  9. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +10 -0
  10. package/dist/assets/channel-DkFNxV_H.js +1 -0
  11. package/dist/assets/{chunk-4BX2VUAB-DlvtrM0q.js → chunk-4BX2VUAB-De63kbgc.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-DJUSHyTa.js → chunk-55IACEB6-DtTDDdM9.js} +1 -1
  13. package/dist/assets/{chunk-FMBD7UC4-C6Ch-htf.js → chunk-FMBD7UC4-DHuwd8tw.js} +1 -1
  14. package/dist/assets/{chunk-JSJVCQXG-DzQIht58.js → chunk-JSJVCQXG-BgytFtmO.js} +1 -1
  15. package/dist/assets/{chunk-KX2RTZJC-C05jARMH.js → chunk-KX2RTZJC-nZdp86aN.js} +1 -1
  16. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +220 -0
  17. package/dist/assets/{chunk-QZHKN3VN-jxti9HTX.js → chunk-QZHKN3VN-DvUQ3mnO.js} +1 -1
  18. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +189 -0
  19. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +1 -0
  20. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +1 -0
  21. package/dist/assets/clone-C0lCEIEO.js +1 -0
  22. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +1 -0
  23. package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
  24. package/dist/assets/{dagre-KLK3FWXG-DJ3dNSYk.js → dagre-KLK3FWXG-CHYIvW47.js} +1 -1
  25. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +24 -0
  26. package/dist/assets/{diagram-IFDJBPK2-Da6K4aP-.js → diagram-IFDJBPK2-Dzsiln_C.js} +1 -1
  27. package/dist/assets/{diagram-P4PSJMXO-vZZKB92A.js → diagram-P4PSJMXO-DKnGbUpE.js} +1 -1
  28. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +70 -0
  29. package/dist/assets/{flowDiagram-PKNHOUZH-DUV13pHi.js → flowDiagram-PKNHOUZH-BAZ2-jKp.js} +4 -4
  30. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +292 -0
  31. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BZ5gW69I.js → gitGraphDiagram-K3NZZRJ6-BflpyjGy.js} +1 -1
  32. package/dist/assets/{graph-BbvHswRd.js → graph-suelaXFh.js} +1 -1
  33. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +1 -0
  34. package/dist/assets/index-B01NxbUv.css +1 -0
  35. package/dist/assets/index-DW5pGgQ_.js +2 -0
  36. package/dist/assets/{infoDiagram-LFFYTUFH-8auUIPKW.js → infoDiagram-LFFYTUFH-pfD1FA3p.js} +1 -1
  37. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +70 -0
  38. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +139 -0
  39. package/dist/assets/{kanban-definition-K7BYSVSG-Bappd2YO.js → kanban-definition-K7BYSVSG-FWinmur1.js} +5 -5
  40. package/dist/assets/{layout-BmbfFZKy.js → layout-vcz43XvZ.js} +1 -1
  41. package/dist/assets/{linear-WZnF-PT6.js → linear-le4gc0vx.js} +1 -1
  42. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +870 -0
  43. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +68 -0
  44. package/dist/assets/{pieDiagram-SKSYHLDU-uxjlAy1t.js → pieDiagram-SKSYHLDU-C7PKDh3b.js} +2 -2
  45. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +7 -0
  46. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +73 -0
  47. package/dist/assets/{sankeyDiagram-WA2Y5GQK-2-FHHM-R.js → sankeyDiagram-WA2Y5GQK-4gulcOP4.js} +3 -3
  48. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +145 -0
  49. package/dist/assets/{stateDiagram-RAJIS63D-DoW8U53H.js → stateDiagram-RAJIS63D-CB4Vl7qM.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +1 -0
  51. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +61 -0
  52. package/dist/assets/{treemap-KZPCXAKY-ajdAP-72.js → treemap-KZPCXAKY-DZSEE6Hz.js} +58 -58
  53. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +31 -0
  54. package/dist/assets/vendor-react-CP4yFTs7.js +8 -0
  55. package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
  56. package/dist/assets/{vennDiagram-LZ73GAT5-C9If0AT0.js → vennDiagram-LZ73GAT5-8E_G06fI.js} +4 -4
  57. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +7 -0
  58. package/dist/favicon.png +0 -0
  59. package/dist/icons/icon-128x128.png +0 -0
  60. package/dist/icons/icon-144x144.png +0 -0
  61. package/dist/icons/icon-152x152.png +0 -0
  62. package/dist/icons/icon-192x192.png +0 -0
  63. package/dist/icons/icon-384x384.png +0 -0
  64. package/dist/icons/icon-512x512.png +0 -0
  65. package/dist/icons/icon-72x72.png +0 -0
  66. package/dist/icons/icon-96x96.png +0 -0
  67. package/dist/index.html +4 -5
  68. package/dist/logo-128.png +0 -0
  69. package/dist/logo-256.png +0 -0
  70. package/dist/logo-32.png +0 -0
  71. package/dist/logo-512.png +0 -0
  72. package/dist/logo-64.png +0 -0
  73. package/package.json +2 -1
  74. package/server/_legacy-providers/README.md +30 -0
  75. package/server/_legacy-providers/claude-sdk.js +956 -0
  76. package/server/_legacy-providers/gemini-cli.js +368 -0
  77. package/server/_legacy-providers/openai-codex.js +705 -0
  78. package/server/_legacy-providers/opencode-cli.js +674 -0
  79. package/server/acp-runtime/client.js +1805 -0
  80. package/server/acp-runtime/client.test.js +688 -0
  81. package/server/acp-runtime/index.js +419 -0
  82. package/server/acp-runtime/registry.js +45 -0
  83. package/server/acp-runtime/session-store.js +254 -0
  84. package/server/acp-runtime/session-store.test.js +89 -0
  85. package/server/channels/runtime/AgentRuntimeAdapter.js +21 -70
  86. package/server/claude-sdk.js +24 -944
  87. package/server/cli.js +11 -5
  88. package/server/external-agent/service.js +77 -63
  89. package/server/gemini-cli.js +23 -360
  90. package/server/index.js +54 -46
  91. package/server/openai-codex.js +24 -698
  92. package/server/opencode-cli.js +70 -640
  93. package/server/routes/agent.js +2 -0
  94. package/server/routes/codex.js +5 -5
  95. package/server/routes/git.js +3 -20
  96. package/server/routes/mcp.js +18 -34
  97. package/server/routes/session-core.js +44 -10
  98. package/server/session-core/abortSession.js +2 -18
  99. package/server/session-core/eventStore.js +5 -1
  100. package/server/session-core/providerAdapters.js +98 -10
  101. package/server/session-core/providerDiscovery.js +2 -2
  102. package/server/session-core/runtimeState.js +16 -17
  103. package/server/session-core/runtimeWriter.js +19 -12
  104. package/server/utils/codexPath.js +3 -1
  105. package/server/utils/spawnCommand.js +7 -0
  106. package/shared/conversationEvents.js +347 -10
  107. package/shared/conversationEvents.test.js +403 -0
  108. package/dist/assets/App-BxazfNJn.js +0 -484
  109. package/dist/assets/App-qxJ8_QYu.css +0 -32
  110. package/dist/assets/ReviewApp-CsqTAlGU.js +0 -1
  111. package/dist/assets/architectureDiagram-2XIMDMQ5-DmUHdvQH.js +0 -36
  112. package/dist/assets/c4Diagram-IC4MRINW-BOivDlQU.js +0 -10
  113. package/dist/assets/channel-Cj8xVD0X.js +0 -1
  114. package/dist/assets/chunk-NQ4KR5QH-Ci-n7jfu.js +0 -220
  115. package/dist/assets/chunk-WL4C6EOR-C559Mk71.js +0 -189
  116. package/dist/assets/classDiagram-VBA2DB6C-CI2zklxw.js +0 -1
  117. package/dist/assets/classDiagram-v2-RAHNMMFH-CI2zklxw.js +0 -1
  118. package/dist/assets/clone-BEVqubrI.js +0 -1
  119. package/dist/assets/cose-bilkent-S5V4N54A-DNO9ncXL.js +0 -1
  120. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +0 -331
  121. package/dist/assets/diagram-E7M64L7V-Ba_LGLun.js +0 -24
  122. package/dist/assets/erDiagram-INFDFZHY-Csb8dFdP.js +0 -70
  123. package/dist/assets/ganttDiagram-A5KZAMGK-B5Kv9Wfz.js +0 -292
  124. package/dist/assets/highlighted-body-TPN3WLV5-DZJajMGm.js +0 -1
  125. package/dist/assets/index-BFX9lxRB.css +0 -1
  126. package/dist/assets/index-BiErUGrv.js +0 -2
  127. package/dist/assets/ishikawaDiagram-PHBUUO56-JmsNlo2I.js +0 -70
  128. package/dist/assets/journeyDiagram-4ABVD52K-Cuudv7Vv.js +0 -139
  129. package/dist/assets/mermaid-O7DHMXV3-D-2fQRvw.js +0 -988
  130. package/dist/assets/mindmap-definition-YRQLILUH-BQHnzzud.js +0 -68
  131. package/dist/assets/quadrantDiagram-337W2JSQ-DpwZU-f_.js +0 -7
  132. package/dist/assets/requirementDiagram-Z7DCOOCP-C_9ClOWm.js +0 -73
  133. package/dist/assets/sequenceDiagram-2WXFIKYE-egns-0XI.js +0 -145
  134. package/dist/assets/stateDiagram-v2-FVOUBMTO-BoFZZ4Ds.js +0 -1
  135. package/dist/assets/timeline-definition-YZTLITO2-chPa8ppH.js +0 -61
  136. package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
  137. package/dist/assets/vendor-react-Cpt6D04s.js +0 -59
  138. package/dist/assets/vendor-xterm-DfaPXD3y.js +0 -66
  139. package/dist/assets/xychartDiagram-JWTSCODW-DD42U6Or.js +0 -7
@@ -0,0 +1,419 @@
1
+ import { resolveWorkingDirectory } from '../utils/defaultWorkingDirectory.js';
2
+ import {
3
+ CLAUDE_MODELS,
4
+ CODEX_MODELS,
5
+ GEMINI_MODELS,
6
+ OPENCODE_MODELS
7
+ } from '../../shared/modelConstants.js';
8
+ import { AcpClient } from './client.js';
9
+ import {
10
+ findAcpSessionRecord,
11
+ touchAcpSessionRecord
12
+ } from './session-store.js';
13
+
14
+ const activeSessionClients = new Map();
15
+ const pendingSessionClientLoads = new Map();
16
+ const DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS = parseInt(process.env.ACP_SESSION_IDLE_TIMEOUT_MS, 10) || (5 * 60 * 1000);
17
+
18
+ function normalizeProvider(value) {
19
+ return String(value || '').trim().toLowerCase();
20
+ }
21
+
22
+ function createActiveSessionKey(provider, sessionId) {
23
+ const normalizedProvider = normalizeProvider(provider);
24
+ const normalizedSessionId = String(sessionId || '').trim();
25
+ if (!normalizedProvider || !normalizedSessionId) {
26
+ return null;
27
+ }
28
+ return `${normalizedProvider}:${normalizedSessionId}`;
29
+ }
30
+
31
+ function registerActiveClient(provider, sessionId, client) {
32
+ const key = createActiveSessionKey(provider, sessionId);
33
+ if (!key) {
34
+ return null;
35
+ }
36
+
37
+ const existing = activeSessionClients.get(key);
38
+ if (existing?.idleTimer) {
39
+ clearTimeout(existing.idleTimer);
40
+ }
41
+
42
+ const entry = existing && existing.client === client
43
+ ? existing
44
+ : {
45
+ client,
46
+ idleTimer: null,
47
+ lastUsedAt: null
48
+ };
49
+
50
+ entry.client = client;
51
+ entry.lastUsedAt = Date.now();
52
+ entry.idleTimer = null;
53
+ activeSessionClients.set(key, entry);
54
+ return entry;
55
+ }
56
+
57
+ function resolveDefaultModel(agentKey, requestedModel) {
58
+ if (requestedModel) {
59
+ return requestedModel;
60
+ }
61
+
62
+ if (agentKey === 'claude') return CLAUDE_MODELS.DEFAULT;
63
+ if (agentKey === 'codex') return CODEX_MODELS.DEFAULT;
64
+ if (agentKey === 'gemini') return GEMINI_MODELS.DEFAULT;
65
+ if (agentKey === 'opencode') return OPENCODE_MODELS.DEFAULT;
66
+ return null;
67
+ }
68
+
69
+ function unregisterActiveClient(provider, sessionId, client = null) {
70
+ const key = createActiveSessionKey(provider, sessionId);
71
+ if (!key) {
72
+ return null;
73
+ }
74
+
75
+ const entry = activeSessionClients.get(key);
76
+ if (!entry) {
77
+ return null;
78
+ }
79
+
80
+ if (client && entry.client !== client) {
81
+ return null;
82
+ }
83
+
84
+ if (entry.idleTimer) {
85
+ clearTimeout(entry.idleTimer);
86
+ }
87
+
88
+ activeSessionClients.delete(key);
89
+ return entry.client;
90
+ }
91
+
92
+ function getActiveClient(provider, sessionId) {
93
+ const key = createActiveSessionKey(provider, sessionId);
94
+ return key ? activeSessionClients.get(key)?.client || null : null;
95
+ }
96
+
97
+ async function disposeActiveClient(provider, sessionId, client = null) {
98
+ const resolvedClient = unregisterActiveClient(provider, sessionId, client) || client;
99
+ if (resolvedClient) {
100
+ await resolvedClient.close().catch(() => {});
101
+ }
102
+ }
103
+
104
+ function scheduleActiveClientCleanup(provider, sessionId, metadata = {}) {
105
+ if (DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS <= 0) {
106
+ return;
107
+ }
108
+
109
+ const key = createActiveSessionKey(provider, sessionId);
110
+ const entry = key ? activeSessionClients.get(key) : null;
111
+ if (!entry) {
112
+ return;
113
+ }
114
+
115
+ if (entry.idleTimer) {
116
+ clearTimeout(entry.idleTimer);
117
+ }
118
+
119
+ entry.lastUsedAt = Date.now();
120
+ entry.idleTimer = setTimeout(() => {
121
+ void (async () => {
122
+ const activeClient = unregisterActiveClient(provider, sessionId, entry.client);
123
+ if (!activeClient) {
124
+ return;
125
+ }
126
+
127
+ await touchAcpSessionRecord({
128
+ ...metadata,
129
+ provider,
130
+ sessionId,
131
+ lastActivity: new Date().toISOString(),
132
+ isClosed: false
133
+ }).catch(() => {});
134
+
135
+ await activeClient.close().catch(() => {});
136
+ })();
137
+ }, DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS);
138
+
139
+ entry.idleTimer.unref?.();
140
+ }
141
+
142
+ async function resolveProjectPath(options = {}) {
143
+ const explicitProjectPath = resolveWorkingDirectory({
144
+ cwd: options.cwd,
145
+ projectPath: options.projectPath
146
+ });
147
+
148
+ if (explicitProjectPath) {
149
+ return explicitProjectPath;
150
+ }
151
+
152
+ if (options.sessionId) {
153
+ const record = await findAcpSessionRecord(options.sessionId, options.provider || options.agentKey || null);
154
+ if (record?.projectPath) {
155
+ return record.projectPath;
156
+ }
157
+ }
158
+
159
+ return process.cwd();
160
+ }
161
+
162
+ function createClient({ normalizedAgent, writer, projectPath, options = {} }) {
163
+ return new AcpClient({
164
+ agentKey: normalizedAgent,
165
+ writer,
166
+ projectPath,
167
+ agentCommandOverrides: options.agentCommandOverrides,
168
+ model: resolveDefaultModel(normalizedAgent, options.model),
169
+ permissionMode: options.permissionMode || 'default'
170
+ });
171
+ }
172
+
173
+ async function createOrResumeClient({ normalizedAgent, options = {}, projectPath, writer }) {
174
+ const requestedSessionId = typeof options.sessionId === 'string' ? options.sessionId.trim() : '';
175
+ const activeClient = requestedSessionId ? getActiveClient(normalizedAgent, requestedSessionId) : null;
176
+ if (activeClient) {
177
+ registerActiveClient(normalizedAgent, requestedSessionId, activeClient);
178
+ return activeClient;
179
+ }
180
+
181
+ const pendingKey = requestedSessionId ? createActiveSessionKey(normalizedAgent, requestedSessionId) : null;
182
+ if (pendingKey && pendingSessionClientLoads.has(pendingKey)) {
183
+ return pendingSessionClientLoads.get(pendingKey);
184
+ }
185
+
186
+ const clientPromise = (async () => {
187
+ const client = createClient({
188
+ normalizedAgent,
189
+ writer,
190
+ projectPath,
191
+ options
192
+ });
193
+
194
+ try {
195
+ await client.start();
196
+
197
+ if (requestedSessionId) {
198
+ try {
199
+ await client.loadSession(requestedSessionId);
200
+ } catch (error) {
201
+ console.warn(`[ACP:${normalizedAgent}] Unable to resume session ${requestedSessionId}: ${error.message}`);
202
+ await client.createSession();
203
+ client.emitSystemNotice(`Unable to resume session ${requestedSessionId}; started a new session instead.`);
204
+ }
205
+ } else {
206
+ await client.createSession();
207
+ }
208
+
209
+ registerActiveClient(normalizedAgent, client.sessionId, client);
210
+ writer?.setSessionId?.(client.sessionId);
211
+ return client;
212
+ } catch (error) {
213
+ await client.close().catch(() => {});
214
+ throw error;
215
+ }
216
+ })();
217
+
218
+ if (pendingKey) {
219
+ pendingSessionClientLoads.set(pendingKey, clientPromise);
220
+ }
221
+
222
+ try {
223
+ return await clientPromise;
224
+ } finally {
225
+ if (pendingKey) {
226
+ pendingSessionClientLoads.delete(pendingKey);
227
+ }
228
+ }
229
+ }
230
+
231
+ export async function executeAgentPrompt({ agentKey, command, options = {}, writer }) {
232
+ const normalizedAgent = normalizeProvider(agentKey || options.provider);
233
+ const projectPath = await resolveProjectPath({
234
+ ...options,
235
+ agentKey: normalizedAgent,
236
+ provider: normalizedAgent
237
+ });
238
+ const resolvedModel = resolveDefaultModel(normalizedAgent, options.model);
239
+ const agentCommand = options.agentCommand || null;
240
+ let sessionRecord = null;
241
+ let client = null;
242
+
243
+ try {
244
+ client = await createOrResumeClient({
245
+ normalizedAgent,
246
+ options,
247
+ projectPath,
248
+ writer
249
+ });
250
+
251
+ const effectiveProjectPath = client.projectPath || projectPath;
252
+ const response = await client.runExclusive(async () => {
253
+ registerActiveClient(normalizedAgent, client.sessionId, client);
254
+ await client.configureForTurn({
255
+ writer,
256
+ permissionMode: options.permissionMode || 'default',
257
+ model: resolvedModel
258
+ });
259
+ writer?.setSessionId?.(client.sessionId);
260
+
261
+ sessionRecord = await touchAcpSessionRecord({
262
+ provider: normalizedAgent,
263
+ sessionId: client.sessionId,
264
+ projectPath: effectiveProjectPath,
265
+ agentCommand,
266
+ model: resolvedModel,
267
+ lastActivity: new Date().toISOString(),
268
+ lastPromptAt: new Date().toISOString(),
269
+ isClosed: false
270
+ });
271
+
272
+ return client.sendPrompt(command, {
273
+ images: options.images || [],
274
+ resourceLinks: options.resourceLinks || [],
275
+ clientRequestId: options.clientRequestId || null
276
+ });
277
+ });
278
+
279
+ await touchAcpSessionRecord({
280
+ provider: normalizedAgent,
281
+ sessionId: client.sessionId,
282
+ projectPath: effectiveProjectPath,
283
+ agentCommand,
284
+ model: resolvedModel,
285
+ lastActivity: new Date().toISOString(),
286
+ lastPromptAt: new Date().toISOString(),
287
+ isClosed: false
288
+ });
289
+
290
+ scheduleActiveClientCleanup(normalizedAgent, client.sessionId, {
291
+ projectPath: effectiveProjectPath,
292
+ agentCommand,
293
+ model: resolvedModel
294
+ });
295
+
296
+ return {
297
+ sessionId: client.sessionId,
298
+ provider: normalizedAgent,
299
+ runtime: 'acp',
300
+ stopReason: response?.stopReason || null,
301
+ source: sessionRecord?.source || 'acp'
302
+ };
303
+ } catch (error) {
304
+ if (client?.sessionId && !client.hasReportedPromptFatalError) {
305
+ writer?.send?.({
306
+ type: 'error',
307
+ provider: normalizedAgent,
308
+ sessionId: client.sessionId,
309
+ runtime: 'acp',
310
+ error: error.message
311
+ });
312
+ }
313
+
314
+ if (client?.sessionId) {
315
+ await disposeActiveClient(normalizedAgent, client.sessionId, client);
316
+ } else if (client) {
317
+ await client.close().catch(() => {});
318
+ }
319
+
320
+ throw error;
321
+ }
322
+ }
323
+
324
+ export async function abortAgentSession(agentKey, sessionId) {
325
+ const normalizedAgent = normalizeProvider(agentKey);
326
+ const client = getActiveClient(normalizedAgent, sessionId);
327
+
328
+ if (!client) {
329
+ return false;
330
+ }
331
+
332
+ const cancelled = await client.cancel(sessionId);
333
+ if (cancelled) {
334
+ await touchAcpSessionRecord({
335
+ provider: normalizedAgent,
336
+ sessionId,
337
+ lastActivity: new Date().toISOString()
338
+ }).catch(() => {});
339
+ }
340
+ return cancelled;
341
+ }
342
+
343
+ export async function setAgentSessionMode(agentKey, sessionId, modeId, { writer = null } = {}) {
344
+ const normalizedAgent = normalizeProvider(agentKey);
345
+ const normalizedSessionId = String(sessionId || '').trim();
346
+ const normalizedModeId = String(modeId || '').trim();
347
+
348
+ if (!normalizedAgent || !normalizedSessionId || !normalizedModeId) {
349
+ return false;
350
+ }
351
+
352
+ let client = getActiveClient(normalizedAgent, normalizedSessionId);
353
+ if (!client) {
354
+ const projectPath = await resolveProjectPath({
355
+ provider: normalizedAgent,
356
+ sessionId: normalizedSessionId
357
+ });
358
+
359
+ client = await createOrResumeClient({
360
+ normalizedAgent,
361
+ options: {
362
+ sessionId: normalizedSessionId
363
+ },
364
+ projectPath,
365
+ writer
366
+ });
367
+ }
368
+
369
+ const changed = await client.runExclusive(async () => {
370
+ registerActiveClient(normalizedAgent, normalizedSessionId, client);
371
+ if (writer) {
372
+ client.attachWriter(writer);
373
+ writer.setSessionId?.(normalizedSessionId);
374
+ }
375
+ return client.setMode(normalizedModeId);
376
+ });
377
+
378
+ if (changed) {
379
+ await touchAcpSessionRecord({
380
+ provider: normalizedAgent,
381
+ sessionId: normalizedSessionId,
382
+ projectPath: client.projectPath || null,
383
+ model: client.model || null,
384
+ lastActivity: new Date().toISOString(),
385
+ isClosed: false
386
+ }).catch(() => {});
387
+
388
+ scheduleActiveClientCleanup(normalizedAgent, normalizedSessionId, {
389
+ projectPath: client.projectPath || null,
390
+ model: client.model || null
391
+ });
392
+ }
393
+
394
+ return changed;
395
+ }
396
+
397
+ export function isAgentSessionActive(agentKey, sessionId) {
398
+ const key = createActiveSessionKey(agentKey, sessionId);
399
+ return key ? activeSessionClients.has(key) : false;
400
+ }
401
+
402
+ export function getActiveAgentSessions(agentKey = null) {
403
+ const normalizedAgent = agentKey ? normalizeProvider(agentKey) : null;
404
+ const sessions = [];
405
+
406
+ for (const key of activeSessionClients.keys()) {
407
+ const [provider, sessionId] = key.split(':');
408
+ if (normalizedAgent && provider !== normalizedAgent) {
409
+ continue;
410
+ }
411
+ sessions.push(sessionId);
412
+ }
413
+
414
+ return sessions;
415
+ }
416
+
417
+ export function resolveAgentPermission(requestId, decision = {}) {
418
+ return AcpClient.resolvePermissionRequest(requestId, decision);
419
+ }
@@ -0,0 +1,45 @@
1
+ const ACP_ADAPTER_PACKAGE_RANGES = {
2
+ claude: '^0.24.2',
3
+ codex: '^0.10.0'
4
+ };
5
+
6
+ export const AGENT_REGISTRY = {
7
+ claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
8
+ codex: `npx @zed-industries/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
9
+ gemini: 'gemini --acp',
10
+ opencode: 'npx -y opencode-ai acp'
11
+ };
12
+
13
+ const AGENT_ALIASES = {
14
+ openai: 'codex'
15
+ };
16
+
17
+ export function normalizeAgentName(value) {
18
+ const normalized = String(value || '').trim().toLowerCase();
19
+ return AGENT_ALIASES[normalized] || normalized;
20
+ }
21
+
22
+ export function mergeAgentRegistry(overrides = {}) {
23
+ const merged = { ...AGENT_REGISTRY };
24
+
25
+ Object.entries(overrides || {}).forEach(([agentName, command]) => {
26
+ const normalizedAgent = normalizeAgentName(agentName);
27
+ const normalizedCommand = typeof command === 'string' ? command.trim() : '';
28
+ if (!normalizedAgent || !normalizedCommand) {
29
+ return;
30
+ }
31
+ merged[normalizedAgent] = normalizedCommand;
32
+ });
33
+
34
+ return merged;
35
+ }
36
+
37
+ export function resolveAgentCommand(agentKey, overrides = {}) {
38
+ const normalizedAgent = normalizeAgentName(agentKey);
39
+ const registry = mergeAgentRegistry(overrides);
40
+ return registry[normalizedAgent] || registry[AGENT_ALIASES[normalizedAgent] || normalizedAgent] || '';
41
+ }
42
+
43
+ export function listBuiltInAgents(overrides = {}) {
44
+ return Object.keys(mergeAgentRegistry(overrides)).sort();
45
+ }
@@ -0,0 +1,254 @@
1
+ import fs from 'fs/promises';
2
+ import os from 'os';
3
+ import path from 'path';
4
+
5
+ function parsePositiveInteger(value, fallback) {
6
+ const parsed = parseInt(value, 10);
7
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
8
+ }
9
+
10
+ const ACP_SESSION_ROOT = process.env.AXHUB_GENIE_ACP_SESSION_ROOT
11
+ ? path.resolve(process.env.AXHUB_GENIE_ACP_SESSION_ROOT)
12
+ : path.join(os.homedir(), '.axhub-genie', 'acp-sessions');
13
+ const DEFAULT_SESSION_GC_MAX_AGE_DAYS = parsePositiveInteger(process.env.ACP_SESSION_GC_MAX_AGE_DAYS, 30);
14
+
15
+ function normalizeProvider(value) {
16
+ return String(value || '').trim().toLowerCase();
17
+ }
18
+
19
+ function normalizeSessionId(value) {
20
+ const normalized = String(value || '').trim();
21
+ return normalized || null;
22
+ }
23
+
24
+ function getProviderDir(provider) {
25
+ return path.join(ACP_SESSION_ROOT, normalizeProvider(provider));
26
+ }
27
+
28
+ function getSessionFilePath(provider, sessionId) {
29
+ return path.join(getProviderDir(provider), `${encodeURIComponent(sessionId)}.json`);
30
+ }
31
+
32
+ function sortSessionsByActivity(sessions = []) {
33
+ return sessions.sort((left, right) => {
34
+ const leftTime = new Date(left.lastActivity || left.updatedAt || left.createdAt || 0).getTime();
35
+ const rightTime = new Date(right.lastActivity || right.updatedAt || right.createdAt || 0).getTime();
36
+ return rightTime - leftTime;
37
+ });
38
+ }
39
+
40
+ async function ensureProviderDir(provider) {
41
+ await fs.mkdir(getProviderDir(provider), { recursive: true });
42
+ }
43
+
44
+ async function listProviderSessionFiles(provider) {
45
+ try {
46
+ const entries = await fs.readdir(getProviderDir(provider), { withFileTypes: true });
47
+ return entries
48
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
49
+ .map((entry) => path.join(getProviderDir(provider), entry.name));
50
+ } catch (error) {
51
+ if (error?.code === 'ENOENT') {
52
+ return [];
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ async function readJsonFile(filePath) {
59
+ try {
60
+ const content = await fs.readFile(filePath, 'utf8');
61
+ return JSON.parse(content);
62
+ } catch (error) {
63
+ if (error?.code === 'ENOENT') {
64
+ return null;
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ function createSessionSummary(record) {
71
+ if (!record || typeof record !== 'object') {
72
+ return null;
73
+ }
74
+
75
+ return {
76
+ id: record.sessionId,
77
+ sessionId: record.sessionId,
78
+ provider: record.provider,
79
+ source: 'acp',
80
+ runtime: 'acp',
81
+ projectPath: record.projectPath || null,
82
+ cwd: record.projectPath || null,
83
+ title: record.title || record.sessionId,
84
+ summary: record.summary || null,
85
+ model: record.model || null,
86
+ createdAt: record.createdAt || null,
87
+ updatedAt: record.updatedAt || null,
88
+ lastActivity: record.lastActivity || record.updatedAt || record.createdAt || null,
89
+ lastPromptAt: record.lastPromptAt || null,
90
+ closedAt: record.closedAt || null,
91
+ isClosed: Boolean(record.isClosed)
92
+ };
93
+ }
94
+
95
+ function getSessionActivityTime(record) {
96
+ return new Date(
97
+ record?.closedAt
98
+ || record?.lastActivity
99
+ || record?.updatedAt
100
+ || record?.createdAt
101
+ || 0
102
+ ).getTime();
103
+ }
104
+
105
+ export async function readAcpSessionRecord(provider, sessionId) {
106
+ const normalizedProvider = normalizeProvider(provider);
107
+ const normalizedSessionId = normalizeSessionId(sessionId);
108
+ if (!normalizedProvider || !normalizedSessionId) {
109
+ return null;
110
+ }
111
+
112
+ const record = await readJsonFile(getSessionFilePath(normalizedProvider, normalizedSessionId));
113
+ return record && typeof record === 'object' ? record : null;
114
+ }
115
+
116
+ export async function writeAcpSessionRecord(record) {
117
+ const normalizedProvider = normalizeProvider(record?.provider);
118
+ const normalizedSessionId = normalizeSessionId(record?.sessionId);
119
+
120
+ if (!normalizedProvider || !normalizedSessionId) {
121
+ throw new Error('provider and sessionId are required to persist an ACP session');
122
+ }
123
+
124
+ const now = new Date().toISOString();
125
+ const existing = await readAcpSessionRecord(normalizedProvider, normalizedSessionId);
126
+ const nextRecord = {
127
+ source: 'acp',
128
+ runtime: 'acp',
129
+ provider: normalizedProvider,
130
+ sessionId: normalizedSessionId,
131
+ createdAt: existing?.createdAt || record.createdAt || now,
132
+ updatedAt: now,
133
+ lastActivity: record.lastActivity || now,
134
+ projectPath: record.projectPath || existing?.projectPath || null,
135
+ agentCommand: record.agentCommand || existing?.agentCommand || null,
136
+ model: record.model || existing?.model || null,
137
+ summary: record.summary || existing?.summary || null,
138
+ title: record.title || existing?.title || null,
139
+ lastPromptAt: record.lastPromptAt || existing?.lastPromptAt || null,
140
+ closedAt: record.closedAt || existing?.closedAt || null,
141
+ isClosed: record.isClosed ?? existing?.isClosed ?? false
142
+ };
143
+
144
+ await ensureProviderDir(normalizedProvider);
145
+ const filePath = getSessionFilePath(normalizedProvider, normalizedSessionId);
146
+ await fs.writeFile(filePath, `${JSON.stringify(nextRecord, null, 2)}\n`, 'utf8');
147
+
148
+ return nextRecord;
149
+ }
150
+
151
+ export async function touchAcpSessionRecord(record) {
152
+ return writeAcpSessionRecord(record);
153
+ }
154
+
155
+ export async function closeAcpSessionRecord(provider, sessionId, updates = {}) {
156
+ const now = new Date().toISOString();
157
+ return writeAcpSessionRecord({
158
+ ...updates,
159
+ provider,
160
+ sessionId,
161
+ isClosed: true,
162
+ closedAt: updates.closedAt || now,
163
+ lastActivity: updates.lastActivity || now
164
+ });
165
+ }
166
+
167
+ export async function gcOldAcpSessions({ provider = null, maxAgeDays = DEFAULT_SESSION_GC_MAX_AGE_DAYS, includeOpen = false } = {}) {
168
+ const normalizedMaxAgeDays = Number(maxAgeDays);
169
+ if (!Number.isFinite(normalizedMaxAgeDays) || normalizedMaxAgeDays <= 0) {
170
+ return { removed: 0 };
171
+ }
172
+
173
+ const providers = provider ? [normalizeProvider(provider)] : ['claude', 'codex', 'gemini', 'opencode'];
174
+ const cutoffTime = Date.now() - (normalizedMaxAgeDays * 24 * 60 * 60 * 1000);
175
+ let removed = 0;
176
+
177
+ for (const currentProvider of providers) {
178
+ if (!currentProvider) {
179
+ continue;
180
+ }
181
+
182
+ const filePaths = await listProviderSessionFiles(currentProvider);
183
+ for (const filePath of filePaths) {
184
+ const record = await readJsonFile(filePath);
185
+ if (!record || typeof record !== 'object') {
186
+ continue;
187
+ }
188
+
189
+ if (!includeOpen && !record.isClosed) {
190
+ continue;
191
+ }
192
+
193
+ const activityTime = getSessionActivityTime(record);
194
+ if (!Number.isFinite(activityTime) || activityTime >= cutoffTime) {
195
+ continue;
196
+ }
197
+
198
+ await fs.rm(filePath, { force: true });
199
+ removed += 1;
200
+ }
201
+ }
202
+
203
+ return { removed };
204
+ }
205
+
206
+ export async function listAcpSessions({ provider = null, projectPath = null } = {}) {
207
+ await gcOldAcpSessions({ provider }).catch(() => {});
208
+ const providers = provider ? [normalizeProvider(provider)] : ['claude', 'codex', 'gemini', 'opencode'];
209
+ const sessions = [];
210
+
211
+ for (const currentProvider of providers) {
212
+ if (!currentProvider) {
213
+ continue;
214
+ }
215
+
216
+ const filePaths = await listProviderSessionFiles(currentProvider);
217
+ for (const filePath of filePaths) {
218
+ const record = await readJsonFile(filePath);
219
+ const summary = createSessionSummary(record);
220
+ if (!summary) {
221
+ continue;
222
+ }
223
+
224
+ if (projectPath && summary.projectPath !== projectPath) {
225
+ continue;
226
+ }
227
+
228
+ sessions.push(summary);
229
+ }
230
+ }
231
+
232
+ return sortSessionsByActivity(sessions);
233
+ }
234
+
235
+ export async function findAcpSessionRecord(sessionId, provider = null) {
236
+ const normalizedSessionId = normalizeSessionId(sessionId);
237
+ if (!normalizedSessionId) {
238
+ return null;
239
+ }
240
+
241
+ if (provider) {
242
+ return readAcpSessionRecord(provider, normalizedSessionId);
243
+ }
244
+
245
+ const providers = ['claude', 'codex', 'gemini', 'opencode'];
246
+ for (const currentProvider of providers) {
247
+ const record = await readAcpSessionRecord(currentProvider, normalizedSessionId);
248
+ if (record) {
249
+ return record;
250
+ }
251
+ }
252
+
253
+ return null;
254
+ }