@axhub/genie 0.2.10 → 0.2.12

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 (96) hide show
  1. package/dist/api-docs.html +2 -2
  2. package/dist/assets/App-Clb2COtW.js +274 -0
  3. package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
  4. package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
  5. package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
  6. package/dist/assets/{_basePickBy-DVVb07UV.js → _basePickBy-jUZsM51q.js} +1 -1
  7. package/dist/assets/{_baseUniq-BtbziL5G.js → _baseUniq-BXglE6_v.js} +1 -1
  8. package/dist/assets/{arc-BsCC8yBD.js → arc-D-oFCFBv.js} +1 -1
  9. package/dist/assets/{architectureDiagram-2XIMDMQ5-woFp6eNI.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-ya8VAc2k.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
  11. package/dist/assets/{c4Diagram-IC4MRINW-CY1dZmIZ.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
  12. package/dist/assets/channel-BF4woPXX.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-CR1lAd74.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-CP98WcFC.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-D9c7ijAB.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-DQAGYOn-.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-BbTXiDq7.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
  18. package/dist/assets/{chunk-NQ4KR5QH-BI6AX0dr.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
  19. package/dist/assets/{chunk-QZHKN3VN-DB3V2Ifo.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
  20. package/dist/assets/{chunk-WL4C6EOR-DhzTthv6.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
  21. package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
  23. package/dist/assets/clone-CqBvwCJW.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-BQ09ZE2j.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
  25. package/dist/assets/{dagre-KLK3FWXG-Dc2ueD_R.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
  26. package/dist/assets/{diagram-E7M64L7V-DP-LsQoL.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
  27. package/dist/assets/{diagram-IFDJBPK2-Cg6r42cB.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
  28. package/dist/assets/{diagram-P4PSJMXO-aHsfoUZE.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
  29. package/dist/assets/{erDiagram-INFDFZHY-qBXJ4aAz.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
  30. package/dist/assets/{flowDiagram-PKNHOUZH-D_13emJM.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
  31. package/dist/assets/{ganttDiagram-A5KZAMGK-BvIcOLwz.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
  33. package/dist/assets/{graph-CeJCMjan.js → graph-Dkeg9oys.js} +1 -1
  34. package/dist/assets/{highlighted-body-TPN3WLV5-B_novwSz.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
  35. package/dist/assets/index-DgGmiqsP.css +1 -0
  36. package/dist/assets/index-DvA901Vs.js +2 -0
  37. package/dist/assets/{infoDiagram-LFFYTUFH-lOxAqb3m.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-PHBUUO56-DIr-51gj.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
  39. package/dist/assets/{journeyDiagram-4ABVD52K-CYcIW0ZU.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
  40. package/dist/assets/{kanban-definition-K7BYSVSG-C1ZK616a.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
  41. package/dist/assets/{layout-CI2RM-v6.js → layout-Xr9Z2VGF.js} +1 -1
  42. package/dist/assets/{linear-DE7bISck.js → linear-JBmzAJtl.js} +1 -1
  43. package/dist/assets/{mermaid-O7DHMXV3-XxAJo8EK.js → mermaid-O7DHMXV3-fDuyNLKe.js} +238 -220
  44. package/dist/assets/{mindmap-definition-YRQLILUH-Dz6EFjmn.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
  45. package/dist/assets/{pieDiagram-SKSYHLDU-DPpEzUed.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-337W2JSQ-xdoXNet7.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
  47. package/dist/assets/{requirementDiagram-Z7DCOOCP-DUq8H3CL.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-WA2Y5GQK-CmqEUxRu.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-2WXFIKYE-DhtXRNiH.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
  50. package/dist/assets/{stateDiagram-RAJIS63D-Dj0HOlbN.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
  51. package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
  52. package/dist/assets/{timeline-definition-YZTLITO2-DUuJzZB5.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
  53. package/dist/assets/{treemap-KZPCXAKY-DpYBQ0qr.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
  54. package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
  55. package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
  56. package/dist/assets/{vennDiagram-LZ73GAT5-DpePUyOd.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
  57. package/dist/assets/{xychartDiagram-JWTSCODW-Cfp1I4_U.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
  58. package/dist/index.html +4 -4
  59. package/package.json +25 -6
  60. package/scripts/refresh-acp-default-capabilities.mjs +160 -0
  61. package/server/acp-runtime/client.js +1137 -181
  62. package/server/acp-runtime/command-overrides.js +48 -0
  63. package/server/acp-runtime/index.js +576 -16
  64. package/server/acp-runtime/registry.js +6 -4
  65. package/server/acp-runtime/session-store.js +235 -92
  66. package/server/database/db.js +12 -3
  67. package/server/external-agent/ws.js +212 -11
  68. package/server/index.js +156 -53
  69. package/server/projects-watcher-config.js +4 -0
  70. package/server/projects.js +485 -125
  71. package/server/routes/cc-connect.js +5 -4
  72. package/server/routes/codex.js +24 -0
  73. package/server/routes/commands.js +166 -10
  74. package/server/routes/runs.js +641 -0
  75. package/server/routes/session-core.js +357 -109
  76. package/server/session-core/eventStore.js +0 -121
  77. package/server/session-core/providerAdapters.js +644 -163
  78. package/server/session-core/providerDiscovery.js +66 -38
  79. package/server/session-core/runRegistry.js +244 -0
  80. package/server/session-core/runtimeState.js +75 -3
  81. package/server/session-core/runtimeWriter.js +132 -10
  82. package/server/utils/codexImagePlayground.js +479 -0
  83. package/server/utils/localTerminal.js +56 -0
  84. package/server/utils/shellCommand.js +70 -0
  85. package/shared/acpCapabilities.js +393 -0
  86. package/shared/acpDefaultCapabilities.generated.json +141 -0
  87. package/shared/conversationEvents.js +425 -121
  88. package/dist/assets/App-CYCCsgwf.js +0 -264
  89. package/dist/assets/ReviewApp-0srHIXwb.js +0 -1
  90. package/dist/assets/channel-BMhScXFe.js +0 -1
  91. package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +0 -1
  92. package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +0 -1
  93. package/dist/assets/clone-BPqOt4r3.js +0 -1
  94. package/dist/assets/index-C514cLyb.js +0 -2
  95. package/dist/assets/index-h1DBl_g3.css +0 -1
  96. package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +0 -1
@@ -1,13 +1,26 @@
1
1
  import { resolveWorkingDirectory } from '../utils/defaultWorkingDirectory.js';
2
2
  import { AcpClient } from './client.js';
3
+ import fs from 'fs/promises';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import {
7
+ buildAcpCapabilities,
8
+ mergeAcpCapabilitySnapshots
9
+ } from '../../shared/acpCapabilities.js';
3
10
  import {
4
11
  findAcpSessionRecord,
5
12
  touchAcpSessionRecord
6
13
  } from './session-store.js';
14
+ import { mergeAgentCommandOverrides } from './command-overrides.js';
7
15
 
8
16
  const activeSessionClients = new Map();
17
+ const warmProjectClients = new Map();
9
18
  const pendingSessionClientLoads = new Map();
10
- const DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS = parseInt(process.env.ACP_SESSION_IDLE_TIMEOUT_MS, 10) || (5 * 60 * 1000);
19
+ const capabilityProbeSnapshotCache = new Map();
20
+ const capabilityProbeInFlight = new Map();
21
+ const DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS = parseInt(process.env.ACP_SESSION_IDLE_TIMEOUT_MS, 10) || (15 * 60 * 1000);
22
+ const DEFAULT_CAPABILITY_PROBE_SETTLE_MS = parseInt(process.env.ACP_CAPABILITY_PROBE_SETTLE_MS, 10) || 120;
23
+ const CAPABILITY_PROBE_POLL_MS = 10;
11
24
 
12
25
  function normalizeProvider(value) {
13
26
  return String(value || '').trim().toLowerCase();
@@ -22,7 +35,172 @@ function createActiveSessionKey(provider, sessionId) {
22
35
  return `${normalizedProvider}:${normalizedSessionId}`;
23
36
  }
24
37
 
38
+ function createWarmProjectKey(provider, projectPath) {
39
+ const normalizedProvider = normalizeProvider(provider);
40
+ const normalizedProjectPath = String(projectPath || '').trim();
41
+ if (!normalizedProvider || !normalizedProjectPath) {
42
+ return null;
43
+ }
44
+ return `${normalizedProvider}:${normalizedProjectPath}`;
45
+ }
46
+
47
+ function createCapabilityProbeCacheKey(provider, projectPath) {
48
+ const normalizedProvider = normalizeProvider(provider);
49
+ const normalizedProjectPath = String(projectPath || '').trim();
50
+ if (!normalizedProvider) {
51
+ return null;
52
+ }
53
+
54
+ return `${normalizedProvider}:${normalizedProjectPath || 'global'}`;
55
+ }
56
+
57
+ function cloneJsonValue(value) {
58
+ if (value === undefined) {
59
+ return undefined;
60
+ }
61
+
62
+ return JSON.parse(JSON.stringify(value));
63
+ }
64
+
65
+ function isForceRefreshOption(options = {}) {
66
+ return options.force === true || String(options.force || '').toLowerCase() === 'true';
67
+ }
68
+
69
+ function readCapabilityProbeCache(provider, projectPath) {
70
+ const key = createCapabilityProbeCacheKey(provider, projectPath);
71
+ if (!key) {
72
+ return null;
73
+ }
74
+
75
+ const cached = capabilityProbeSnapshotCache.get(key);
76
+ if (!cached?.snapshot) {
77
+ return null;
78
+ }
79
+
80
+ const updatedAtMs = Date.parse(cached.snapshot.updatedAt || '');
81
+ const ttl = Number(cached.snapshot.ttl || 0);
82
+ if (!Number.isFinite(updatedAtMs) || !Number.isFinite(ttl) || ttl <= 0) {
83
+ capabilityProbeSnapshotCache.delete(key);
84
+ return null;
85
+ }
86
+
87
+ if (Date.now() - updatedAtMs >= ttl) {
88
+ capabilityProbeSnapshotCache.delete(key);
89
+ return null;
90
+ }
91
+
92
+ return cloneJsonValue(cached.snapshot);
93
+ }
94
+
95
+ function writeCapabilityProbeCache(provider, projectPath, snapshot) {
96
+ const key = createCapabilityProbeCacheKey(provider, projectPath);
97
+ if (!key || !snapshot) {
98
+ return null;
99
+ }
100
+
101
+ const cloned = cloneJsonValue(snapshot);
102
+ capabilityProbeSnapshotCache.set(key, {
103
+ snapshot: cloned
104
+ });
105
+ return cloneJsonValue(cloned);
106
+ }
107
+
108
+ function deleteCapabilityProbeCache(provider, projectPath) {
109
+ const key = createCapabilityProbeCacheKey(provider, projectPath);
110
+ if (key) {
111
+ capabilityProbeSnapshotCache.delete(key);
112
+ }
113
+ }
114
+
115
+ function hasConfiguredAgentCommandOverride(provider, options = {}) {
116
+ const overrides = mergeAgentCommandOverrides(
117
+ process.env.AXHUB_ACP_COMMAND_OVERRIDES,
118
+ options.agentCommandOverrides
119
+ );
120
+ return Boolean(overrides[normalizeProvider(provider)]);
121
+ }
122
+
123
+ async function hasGeminiOauthCredentials() {
124
+ const geminiDir = path.join(os.homedir(), '.gemini');
125
+
126
+ try {
127
+ const content = await fs.readFile(path.join(geminiDir, 'oauth_creds.json'), 'utf8');
128
+ const credentials = JSON.parse(content);
129
+ if (credentials?.access_token || credentials?.refresh_token || credentials?.token) {
130
+ return true;
131
+ }
132
+ } catch {
133
+ }
134
+
135
+ try {
136
+ const content = await fs.readFile(path.join(geminiDir, 'google_accounts.json'), 'utf8');
137
+ const accounts = JSON.parse(content);
138
+ return typeof accounts?.active === 'string' && accounts.active.trim().length > 0;
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+
144
+ export function shouldSkipGeminiAutomaticAcpStartup(provider, options = {}) {
145
+ if (normalizeProvider(provider) !== 'gemini') {
146
+ return false;
147
+ }
148
+
149
+ return !hasConfiguredAgentCommandOverride(provider, options);
150
+ }
151
+
152
+ async function shouldBlockGeminiPromptStartup(provider, options = {}) {
153
+ if (normalizeProvider(provider) !== 'gemini') {
154
+ return false;
155
+ }
156
+
157
+ if (hasConfiguredAgentCommandOverride(provider, options)) {
158
+ return false;
159
+ }
160
+
161
+ return !(await hasGeminiOauthCredentials());
162
+ }
163
+
164
+ function buildSkippedGeminiCapabilityProbeSnapshot(projectPath) {
165
+ const snapshot = buildAcpCapabilities({
166
+ provider: 'gemini',
167
+ source: 'auth-unavailable'
168
+ });
169
+ snapshot.metadata = {
170
+ ...(snapshot.metadata || {}),
171
+ probeSkipped: true,
172
+ authenticated: false,
173
+ authError: 'Skipped automatic Gemini ACP startup to avoid repeated OAuth browser launches.'
174
+ };
175
+ writeCapabilityProbeCache('gemini', projectPath, snapshot);
176
+ return snapshot;
177
+ }
178
+
179
+ function unregisterWarmProjectClient(provider, projectPath, client = null) {
180
+ const key = createWarmProjectKey(provider, projectPath);
181
+ if (!key) {
182
+ return null;
183
+ }
184
+
185
+ const entry = warmProjectClients.get(key);
186
+ if (!entry) {
187
+ return null;
188
+ }
189
+
190
+ if (client && entry.client !== client) {
191
+ return null;
192
+ }
193
+
194
+ if (entry.idleTimer) {
195
+ clearTimeout(entry.idleTimer);
196
+ }
197
+
198
+ warmProjectClients.delete(key);
199
+ return entry.client;
200
+ }
201
+
25
202
  function registerActiveClient(provider, sessionId, client) {
203
+ const normalizedProvider = normalizeProvider(provider);
26
204
  const key = createActiveSessionKey(provider, sessionId);
27
205
  if (!key) {
28
206
  return null;
@@ -45,6 +223,7 @@ function registerActiveClient(provider, sessionId, client) {
45
223
  entry.lastUsedAt = Date.now();
46
224
  entry.idleTimer = null;
47
225
  activeSessionClients.set(key, entry);
226
+ unregisterWarmProjectClient(normalizedProvider, client?.projectPath, client);
48
227
  return entry;
49
228
  }
50
229
 
@@ -55,6 +234,78 @@ export function resolveDefaultModel(agentKey, requestedModel) {
55
234
  return normalizedRequestedModel || null;
56
235
  }
57
236
 
237
+ export function createCapabilityProbeWriter(provider) {
238
+ let snapshot = buildAcpCapabilities({ provider, source: 'probe' });
239
+ let sessionId = null;
240
+
241
+ return {
242
+ send(payload) {
243
+ if (!payload || typeof payload !== 'object') {
244
+ return;
245
+ }
246
+
247
+ if (typeof payload.sessionId === 'string' && payload.sessionId.trim()) {
248
+ sessionId = payload.sessionId.trim();
249
+ }
250
+
251
+ if (payload.capabilitySnapshot) {
252
+ snapshot = mergeAcpCapabilitySnapshots(snapshot, payload.capabilitySnapshot);
253
+ }
254
+
255
+ if (payload.type === 'session-created') {
256
+ snapshot = mergeAcpCapabilitySnapshots(snapshot, buildAcpCapabilities({
257
+ configOptions: payload.configOptions,
258
+ modeState: payload.modes,
259
+ tokenUsage: payload.tokenUsage,
260
+ availableCommands: payload.availableCommands,
261
+ provider: payload.provider || provider,
262
+ source: 'probe'
263
+ }));
264
+ }
265
+
266
+ if (payload.type === 'conversation-event' && payload.event) {
267
+ const event = payload.event;
268
+ if (event.kind === 'mode_update') {
269
+ snapshot = mergeAcpCapabilitySnapshots(snapshot, buildAcpCapabilities({
270
+ modeState: event.payload,
271
+ provider: event.provider || provider,
272
+ source: 'probe'
273
+ }));
274
+ } else if (event.kind === 'config_option_update') {
275
+ snapshot = mergeAcpCapabilitySnapshots(snapshot, buildAcpCapabilities({
276
+ configOptions: event.payload?.configOptions,
277
+ provider: event.provider || provider,
278
+ source: 'probe'
279
+ }));
280
+ } else if (event.kind === 'usage_update') {
281
+ snapshot = mergeAcpCapabilitySnapshots(snapshot, buildAcpCapabilities({
282
+ tokenUsage: event.payload,
283
+ provider: event.provider || provider,
284
+ source: 'probe'
285
+ }));
286
+ } else if (event.kind === 'available_commands_update') {
287
+ snapshot = mergeAcpCapabilitySnapshots(snapshot, buildAcpCapabilities({
288
+ availableCommands: event.payload?.availableCommands,
289
+ provider: event.provider || provider,
290
+ source: 'probe'
291
+ }));
292
+ }
293
+ }
294
+ },
295
+ setSessionId(nextSessionId) {
296
+ if (typeof nextSessionId === 'string' && nextSessionId.trim()) {
297
+ sessionId = nextSessionId.trim();
298
+ }
299
+ },
300
+ getSessionId() {
301
+ return sessionId;
302
+ },
303
+ getSnapshot() {
304
+ return snapshot;
305
+ }
306
+ };
307
+ }
308
+
58
309
  function truncateSessionSummary(text, maxLength = 50) {
59
310
  const normalizedText = typeof text === 'string' ? text.trim() : '';
60
311
  if (!normalizedText) {
@@ -104,6 +355,38 @@ export function deriveSessionSummaryFromPrompt(command) {
104
355
  return truncateSessionSummary(extractVisibleUserPrompt(command));
105
356
  }
106
357
 
358
+ function hasProbeAvailableCommands(snapshot) {
359
+ return Array.isArray(snapshot?.metadata?.availableCommands) && snapshot.metadata.availableCommands.length > 0;
360
+ }
361
+
362
+ function resolveCapabilityProbeSettleMs(options = {}) {
363
+ const explicit = Number(options.probeSettleMs ?? options.capabilityProbeSettleMs);
364
+ if (Number.isFinite(explicit) && explicit >= 0) {
365
+ return explicit;
366
+ }
367
+
368
+ return DEFAULT_CAPABILITY_PROBE_SETTLE_MS;
369
+ }
370
+
371
+ function delay(ms) {
372
+ return new Promise((resolve) => setTimeout(resolve, ms));
373
+ }
374
+
375
+ async function waitForCapabilityProbeSettle(writer, options = {}) {
376
+ const settleMs = resolveCapabilityProbeSettleMs(options);
377
+ if (settleMs <= 0 || hasProbeAvailableCommands(writer.getSnapshot())) {
378
+ return;
379
+ }
380
+
381
+ const deadline = Date.now() + settleMs;
382
+ while (Date.now() < deadline) {
383
+ await delay(Math.min(CAPABILITY_PROBE_POLL_MS, Math.max(0, deadline - Date.now())));
384
+ if (hasProbeAvailableCommands(writer.getSnapshot())) {
385
+ return;
386
+ }
387
+ }
388
+ }
389
+
107
390
  function unregisterActiveClient(provider, sessionId, client = null) {
108
391
  const key = createActiveSessionKey(provider, sessionId);
109
392
  if (!key) {
@@ -132,6 +415,26 @@ function getActiveClient(provider, sessionId) {
132
415
  return key ? activeSessionClients.get(key)?.client || null : null;
133
416
  }
134
417
 
418
+ function takeWarmProjectClient(provider, projectPath) {
419
+ const key = createWarmProjectKey(provider, projectPath);
420
+ if (!key) {
421
+ return null;
422
+ }
423
+
424
+ const entry = warmProjectClients.get(key);
425
+ if (!entry) {
426
+ return null;
427
+ }
428
+
429
+ if (entry.idleTimer) {
430
+ clearTimeout(entry.idleTimer);
431
+ }
432
+
433
+ warmProjectClients.delete(key);
434
+ entry.lastUsedAt = Date.now();
435
+ return entry.client;
436
+ }
437
+
135
438
  async function disposeActiveClient(provider, sessionId, client = null) {
136
439
  const resolvedClient = unregisterActiveClient(provider, sessionId, client) || client;
137
440
  if (resolvedClient) {
@@ -139,6 +442,22 @@ async function disposeActiveClient(provider, sessionId, client = null) {
139
442
  }
140
443
  }
141
444
 
445
+ export async function disposeManagedAgentClient({ provider, sessionId = null, projectPath = null, client = null } = {}) {
446
+ let resolvedClient = null;
447
+ if (sessionId) {
448
+ resolvedClient = unregisterActiveClient(provider, sessionId, client);
449
+ }
450
+
451
+ if (projectPath) {
452
+ resolvedClient = unregisterWarmProjectClient(provider, projectPath, client) || resolvedClient;
453
+ }
454
+
455
+ resolvedClient = resolvedClient || client;
456
+ if (resolvedClient) {
457
+ await resolvedClient.dispose().catch(() => {});
458
+ }
459
+ }
460
+
142
461
  function scheduleActiveClientCleanup(provider, sessionId, metadata = {}) {
143
462
  if (DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS <= 0) {
144
463
  return;
@@ -177,6 +496,130 @@ function scheduleActiveClientCleanup(provider, sessionId, metadata = {}) {
177
496
  entry.idleTimer.unref?.();
178
497
  }
179
498
 
499
+ function scheduleWarmProjectClientCleanup(provider, projectPath) {
500
+ if (DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS <= 0) {
501
+ return;
502
+ }
503
+
504
+ const key = createWarmProjectKey(provider, projectPath);
505
+ const entry = key ? warmProjectClients.get(key) : null;
506
+ if (!entry) {
507
+ return;
508
+ }
509
+
510
+ if (entry.idleTimer) {
511
+ clearTimeout(entry.idleTimer);
512
+ }
513
+
514
+ entry.lastUsedAt = Date.now();
515
+ entry.idleTimer = setTimeout(() => {
516
+ void (async () => {
517
+ const warmClient = unregisterWarmProjectClient(provider, projectPath, entry.client);
518
+ if (!warmClient) {
519
+ return;
520
+ }
521
+
522
+ await warmClient.dispose().catch(() => {});
523
+ })();
524
+ }, DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS);
525
+
526
+ entry.idleTimer.unref?.();
527
+ }
528
+
529
+ export function hasReusableCapabilitySnapshot(snapshot) {
530
+ if (!snapshot || typeof snapshot !== 'object') {
531
+ return false;
532
+ }
533
+
534
+ const capabilities = snapshot.capabilities && typeof snapshot.capabilities === 'object'
535
+ ? snapshot.capabilities
536
+ : {};
537
+ const hasCapability = Object.values(capabilities).some(Boolean);
538
+ const hasCommands = Array.isArray(snapshot.metadata?.availableCommands) && snapshot.metadata.availableCommands.length > 0;
539
+ const hasTokenUsage = Boolean(snapshot.metadata?.tokenUsage);
540
+
541
+ return hasCapability || hasCommands || hasTokenUsage;
542
+ }
543
+
544
+ export function retainProjectAgentClient({ provider, projectPath, client } = {}) {
545
+ const normalizedProvider = normalizeProvider(provider);
546
+ const key = createWarmProjectKey(normalizedProvider, projectPath);
547
+ if (!key || !client) {
548
+ return false;
549
+ }
550
+
551
+ const existing = warmProjectClients.get(key);
552
+ if (existing?.idleTimer) {
553
+ clearTimeout(existing.idleTimer);
554
+ }
555
+
556
+ warmProjectClients.set(key, {
557
+ client,
558
+ idleTimer: null,
559
+ lastUsedAt: Date.now()
560
+ });
561
+ scheduleWarmProjectClientCleanup(normalizedProvider, projectPath);
562
+ return true;
563
+ }
564
+
565
+ export function retainSessionAgentClient({ provider, sessionId, client, metadata = {} } = {}) {
566
+ const normalizedProvider = normalizeProvider(provider);
567
+ const normalizedSessionId = String(sessionId || client?.sessionId || '').trim();
568
+ if (!normalizedProvider || !normalizedSessionId || !client) {
569
+ return false;
570
+ }
571
+
572
+ registerActiveClient(normalizedProvider, normalizedSessionId, client);
573
+ scheduleActiveClientCleanup(normalizedProvider, normalizedSessionId, {
574
+ projectPath: client.projectPath || metadata.projectPath || null,
575
+ model: client.model || metadata.model || null,
576
+ agentCommand: metadata.agentCommand || null
577
+ });
578
+ return true;
579
+ }
580
+
581
+ export async function getOrStartAgentClient({ agentKey, provider, writer, projectPath, options = {}, sessionId = null } = {}) {
582
+ const normalizedAgent = normalizeProvider(agentKey || provider || options.provider);
583
+ const normalizedSessionId = String(sessionId || options.sessionId || '').trim();
584
+
585
+ if (normalizedSessionId) {
586
+ const activeClient = getActiveClient(normalizedAgent, normalizedSessionId);
587
+ if (activeClient) {
588
+ registerActiveClient(normalizedAgent, normalizedSessionId, activeClient);
589
+ if (writer) {
590
+ activeClient.attachWriter(writer);
591
+ }
592
+ return {
593
+ client: activeClient,
594
+ reused: 'session'
595
+ };
596
+ }
597
+ }
598
+
599
+ const warmClient = takeWarmProjectClient(normalizedAgent, projectPath);
600
+ if (warmClient) {
601
+ if (writer) {
602
+ warmClient.attachWriter(writer);
603
+ }
604
+ return {
605
+ client: warmClient,
606
+ reused: 'project'
607
+ };
608
+ }
609
+
610
+ const client = createClient({
611
+ normalizedAgent,
612
+ writer,
613
+ projectPath,
614
+ options
615
+ });
616
+ await client.start();
617
+ return {
618
+ client,
619
+ reused: false
620
+ };
621
+ }
622
+
180
623
  async function resolveProjectPath(options = {}) {
181
624
  const explicitProjectPath = resolveWorkingDirectory({
182
625
  cwd: options.cwd,
@@ -202,9 +645,17 @@ function createClient({ normalizedAgent, writer, projectPath, options = {} }) {
202
645
  agentKey: normalizedAgent,
203
646
  writer,
204
647
  projectPath,
205
- agentCommandOverrides: options.agentCommandOverrides,
648
+ agentCommandOverrides: mergeAgentCommandOverrides(
649
+ process.env.AXHUB_ACP_COMMAND_OVERRIDES,
650
+ options.agentCommandOverrides
651
+ ),
206
652
  model: resolveDefaultModel(normalizedAgent, options.model),
207
- permissionMode: options.permissionMode || 'default'
653
+ permissionMode: options.permissionMode || 'default',
654
+ modeId: options.modeId || options.mode || null,
655
+ thoughtLevel: options.thoughtLevel || options.sessionConfigOptions?.thought_level || null,
656
+ startTimeoutMs: options.startTimeoutMs,
657
+ sessionTimeoutMs: options.sessionTimeoutMs,
658
+ promptTimeoutMs: options.promptTimeoutMs
208
659
  });
209
660
  }
210
661
 
@@ -222,21 +673,29 @@ async function createOrResumeClient({ normalizedAgent, options = {}, projectPath
222
673
  }
223
674
 
224
675
  const clientPromise = (async () => {
225
- const client = createClient({
226
- normalizedAgent,
676
+ const { client } = await getOrStartAgentClient({
677
+ agentKey: normalizedAgent,
227
678
  writer,
228
679
  projectPath,
229
- options
680
+ options,
681
+ sessionId: requestedSessionId
230
682
  });
231
683
 
232
684
  try {
233
- await client.start();
234
-
235
685
  if (requestedSessionId) {
236
686
  try {
237
- await client.loadSession(requestedSessionId);
238
- } catch (error) {
239
- console.warn(`[ACP:${normalizedAgent}] Unable to resume session ${requestedSessionId}: ${error.message}`);
687
+ if (client.canResumeSession()) {
688
+ try {
689
+ await client.resumeSession(requestedSessionId);
690
+ } catch (error) {
691
+ console.warn(`[ACP:${normalizedAgent}] Unable to resume session ${requestedSessionId} with session/resume: ${error.message}`);
692
+ await client.loadSession(requestedSessionId);
693
+ }
694
+ } else {
695
+ await client.loadSession(requestedSessionId);
696
+ }
697
+ } catch (loadError) {
698
+ console.warn(`[ACP:${normalizedAgent}] Unable to resume session ${requestedSessionId}: ${loadError.message}`);
240
699
  await client.createSession();
241
700
  client.emitSystemNotice(`Unable to resume session ${requestedSessionId}; started a new session instead.`);
242
701
  }
@@ -248,7 +707,12 @@ async function createOrResumeClient({ normalizedAgent, options = {}, projectPath
248
707
  writer?.setSessionId?.(client.sessionId);
249
708
  return client;
250
709
  } catch (error) {
251
- await client.close().catch(() => {});
710
+ await disposeManagedAgentClient({
711
+ provider: normalizedAgent,
712
+ sessionId: client.sessionId || requestedSessionId || null,
713
+ projectPath,
714
+ client
715
+ });
252
716
  throw error;
253
717
  }
254
718
  })();
@@ -273,9 +737,16 @@ export async function executeAgentPrompt({ agentKey, command, options = {}, writ
273
737
  agentKey: normalizedAgent,
274
738
  provider: normalizedAgent
275
739
  });
740
+ if (await shouldBlockGeminiPromptStartup(normalizedAgent, options)) {
741
+ throw new Error('Gemini CLI is not authenticated. Configure a Gemini ACP command override or sign in before sending Gemini prompts.');
742
+ }
743
+
276
744
  const resolvedModel = resolveDefaultModel(normalizedAgent, options.model);
277
745
  const agentCommand = options.agentCommand || null;
278
746
  const sessionSummary = deriveSessionSummaryFromPrompt(command);
747
+ const clientRequestId = typeof options.clientRequestId === 'string' && options.clientRequestId.trim()
748
+ ? options.clientRequestId.trim()
749
+ : null;
279
750
  let sessionRecord = null;
280
751
  let client = null;
281
752
 
@@ -293,7 +764,9 @@ export async function executeAgentPrompt({ agentKey, command, options = {}, writ
293
764
  await client.configureForTurn({
294
765
  writer,
295
766
  permissionMode: options.permissionMode || 'default',
296
- model: resolvedModel
767
+ model: resolvedModel,
768
+ modeId: options.modeId || options.mode || null,
769
+ thoughtLevel: options.thoughtLevel || options.sessionConfigOptions?.thought_level || null
297
770
  });
298
771
  writer?.setSessionId?.(client.sessionId);
299
772
 
@@ -313,7 +786,7 @@ export async function executeAgentPrompt({ agentKey, command, options = {}, writ
313
786
  return client.sendPrompt(command, {
314
787
  images: options.images || [],
315
788
  resourceLinks: options.resourceLinks || [],
316
- clientRequestId: options.clientRequestId || null
789
+ clientRequestId
317
790
  });
318
791
  });
319
792
 
@@ -364,6 +837,88 @@ export async function executeAgentPrompt({ agentKey, command, options = {}, writ
364
837
  }
365
838
  }
366
839
 
840
+ export async function probeAgentCapabilities(agentKey, options = {}) {
841
+ const normalizedAgent = normalizeProvider(agentKey || options.provider);
842
+ const projectPath = await resolveProjectPath({
843
+ ...options,
844
+ agentKey: normalizedAgent,
845
+ provider: normalizedAgent
846
+ });
847
+ const force = isForceRefreshOption(options);
848
+ if (shouldSkipGeminiAutomaticAcpStartup(normalizedAgent, options)) {
849
+ return buildSkippedGeminiCapabilityProbeSnapshot(projectPath);
850
+ }
851
+
852
+ if (!force) {
853
+ const cachedSnapshot = readCapabilityProbeCache(normalizedAgent, projectPath);
854
+ if (cachedSnapshot) {
855
+ if (normalizedAgent === 'gemini' && cachedSnapshot.source === 'auth-unavailable') {
856
+ deleteCapabilityProbeCache(normalizedAgent, projectPath);
857
+ } else {
858
+ return cachedSnapshot;
859
+ }
860
+ }
861
+
862
+ const cacheKey = createCapabilityProbeCacheKey(normalizedAgent, projectPath);
863
+ if (cacheKey && capabilityProbeInFlight.has(cacheKey)) {
864
+ return capabilityProbeInFlight.get(cacheKey);
865
+ }
866
+ }
867
+
868
+ const cacheKey = createCapabilityProbeCacheKey(normalizedAgent, projectPath);
869
+ const probePromise = (async () => {
870
+ const writer = createCapabilityProbeWriter(normalizedAgent);
871
+ const { client } = await getOrStartAgentClient({
872
+ agentKey: normalizedAgent,
873
+ writer,
874
+ projectPath,
875
+ options
876
+ });
877
+
878
+ try {
879
+ await client.createSession();
880
+ await waitForCapabilityProbeSettle(writer, options);
881
+ const snapshot = writer.getSnapshot();
882
+ if (hasReusableCapabilitySnapshot(snapshot)) {
883
+ writeCapabilityProbeCache(normalizedAgent, projectPath, snapshot);
884
+ retainProjectAgentClient({
885
+ provider: normalizedAgent,
886
+ projectPath,
887
+ client
888
+ });
889
+ } else {
890
+ await client.close().catch(() => {});
891
+ }
892
+ return snapshot;
893
+ } catch (error) {
894
+ await disposeManagedAgentClient({
895
+ provider: normalizedAgent,
896
+ sessionId: client.sessionId || null,
897
+ projectPath,
898
+ client
899
+ });
900
+ throw error;
901
+ }
902
+ })();
903
+
904
+ if (!force && cacheKey) {
905
+ capabilityProbeInFlight.set(cacheKey, probePromise);
906
+ }
907
+
908
+ try {
909
+ return await probePromise;
910
+ } finally {
911
+ if (cacheKey) {
912
+ capabilityProbeInFlight.delete(cacheKey);
913
+ }
914
+ }
915
+ }
916
+
917
+ export function resetCapabilityProbeCacheForTests() {
918
+ capabilityProbeSnapshotCache.clear();
919
+ capabilityProbeInFlight.clear();
920
+ }
921
+
367
922
  export async function abortAgentSession(agentKey, sessionId) {
368
923
  const normalizedAgent = normalizeProvider(agentKey);
369
924
  const client = getActiveClient(normalizedAgent, sessionId);
@@ -372,7 +927,7 @@ export async function abortAgentSession(agentKey, sessionId) {
372
927
  return false;
373
928
  }
374
929
 
375
- const cancelled = await client.cancel(sessionId);
930
+ const cancelled = await client.cancel(sessionId, { disposeOnTimeout: true });
376
931
  if (cancelled) {
377
932
  await touchAcpSessionRecord({
378
933
  provider: normalizedAgent,
@@ -439,7 +994,8 @@ export async function setAgentSessionMode(agentKey, sessionId, modeId, { writer
439
994
 
440
995
  export function isAgentSessionActive(agentKey, sessionId) {
441
996
  const key = createActiveSessionKey(agentKey, sessionId);
442
- return key ? activeSessionClients.has(key) : false;
997
+ const client = key ? activeSessionClients.get(key)?.client : null;
998
+ return Boolean(client?.isPromptInFlight);
443
999
  }
444
1000
 
445
1001
  export function getActiveAgentSessions(agentKey = null) {
@@ -460,3 +1016,7 @@ export function getActiveAgentSessions(agentKey = null) {
460
1016
  export function resolveAgentPermission(requestId, decision = {}) {
461
1017
  return AcpClient.resolvePermissionRequest(requestId, decision);
462
1018
  }
1019
+
1020
+ export function resolveAgentElicitation(requestId, decision = {}) {
1021
+ return AcpClient.resolveElicitationRequest(requestId, decision);
1022
+ }