@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
@@ -118,6 +118,151 @@ function normalizePositiveInteger(value) {
118
118
  return next;
119
119
  }
120
120
 
121
+ function readContextCurrentFilePath(context) {
122
+ if (!isPlainObject(context)) {
123
+ return null;
124
+ }
125
+
126
+ if (typeof context.currentFile === 'string') {
127
+ return normalizeNonEmptyString(context.currentFile);
128
+ }
129
+
130
+ if (isPlainObject(context.currentFile)) {
131
+ return normalizeNonEmptyString(context.currentFile.path);
132
+ }
133
+
134
+ if (isPlainObject(context.extensions)) {
135
+ return normalizeNonEmptyString(context.extensions.targetPath);
136
+ }
137
+
138
+ return null;
139
+ }
140
+
141
+ function normalizePathValue(value) {
142
+ const normalized = normalizeNonEmptyString(value);
143
+ if (!normalized) {
144
+ return null;
145
+ }
146
+ return normalized
147
+ .replace(/\\/g, '/')
148
+ .replace(/^\.\//, '');
149
+ }
150
+
151
+ function pathsReferToSameFile(left, right) {
152
+ const normalizedLeft = normalizePathValue(left);
153
+ const normalizedRight = normalizePathValue(right);
154
+ if (!normalizedLeft || !normalizedRight) {
155
+ return false;
156
+ }
157
+ if (normalizedLeft === normalizedRight) {
158
+ return true;
159
+ }
160
+ return (
161
+ normalizedLeft.endsWith(`/${normalizedRight}`)
162
+ || normalizedRight.endsWith(`/${normalizedLeft}`)
163
+ );
164
+ }
165
+
166
+ function readContextCurrentFilePathFromPageUrl(pageUrl) {
167
+ const normalizedPageUrl = normalizeNonEmptyString(pageUrl);
168
+ if (!normalizedPageUrl) {
169
+ return null;
170
+ }
171
+
172
+ try {
173
+ const url = new URL(normalizedPageUrl, 'http://localhost');
174
+ const targetPath = normalizePathValue(url.searchParams.get('targetPath'));
175
+ if (targetPath) {
176
+ return targetPath;
177
+ }
178
+
179
+ const rawContext = normalizeNonEmptyString(url.searchParams.get('context'));
180
+ if (!rawContext) {
181
+ return null;
182
+ }
183
+
184
+ return readContextCurrentFilePath(JSON.parse(rawContext));
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+
190
+ function getIntegrationConnectionRecency(meta) {
191
+ return Math.max(Date.parse(meta?.lastSeenAt || '') || 0, Date.parse(meta?.connectedAt || '') || 0);
192
+ }
193
+
194
+ function getIntegrationConnectionIdentityKey(meta) {
195
+ if (!meta?.role || !meta?.channel || !meta?.clientId) {
196
+ return null;
197
+ }
198
+ return `${meta.role}::${meta.channel}::${meta.clientId}`;
199
+ }
200
+
201
+ function getIntegrationPageIdentityKey(meta) {
202
+ const pageUrl = normalizeNonEmptyString(meta?.pageUrl);
203
+ if (!meta?.role || !meta?.channel || !pageUrl) {
204
+ return null;
205
+ }
206
+ return `${meta.role}::${meta.channel}::${pageUrl}`;
207
+ }
208
+
209
+ function pickMostRecentIntegrationTarget(targets) {
210
+ return targets.slice().sort((left, right) => {
211
+ return getIntegrationConnectionRecency(right) - getIntegrationConnectionRecency(left);
212
+ })[0] || null;
213
+ }
214
+
215
+ function dedupeIntegrationConnectionsByIdentity(connections) {
216
+ const deduped = new Map();
217
+
218
+ for (const meta of connections) {
219
+ const key = getIntegrationConnectionIdentityKey(meta);
220
+ if (!key) {
221
+ continue;
222
+ }
223
+
224
+ const previous = deduped.get(key);
225
+ if (!previous || getIntegrationConnectionRecency(meta) >= getIntegrationConnectionRecency(previous)) {
226
+ deduped.set(key, meta);
227
+ }
228
+ }
229
+
230
+ return Array.from(deduped.values());
231
+ }
232
+
233
+ function collapseDuplicateFrontendPagesByPageUrl(connections) {
234
+ const deduped = new Map();
235
+ const withoutPageUrl = [];
236
+
237
+ for (const meta of connections) {
238
+ const key = getIntegrationPageIdentityKey(meta);
239
+ if (!key) {
240
+ withoutPageUrl.push(meta);
241
+ continue;
242
+ }
243
+
244
+ const previous = deduped.get(key);
245
+ if (!previous || getIntegrationConnectionRecency(meta) >= getIntegrationConnectionRecency(previous)) {
246
+ deduped.set(key, meta);
247
+ }
248
+ }
249
+
250
+ return [...deduped.values(), ...withoutPageUrl];
251
+ }
252
+
253
+ function pickContextMatchedTarget(targets, context) {
254
+ const currentFilePath = readContextCurrentFilePath(context);
255
+ if (!currentFilePath) {
256
+ return null;
257
+ }
258
+
259
+ const matches = targets.filter((target) => (
260
+ pathsReferToSameFile(readContextCurrentFilePathFromPageUrl(target.pageUrl), currentFilePath)
261
+ ));
262
+
263
+ return matches.length > 0 ? pickMostRecentIntegrationTarget(matches) : null;
264
+ }
265
+
121
266
  function buildSessionActivitySubscriptionKey({ requestId = null, provider = null, sessionId = null } = {}) {
122
267
  if (requestId) {
123
268
  return `request:${requestId}`;
@@ -359,7 +504,7 @@ function buildSessionActivityFromConversationEvent({
359
504
  id: event.eventId,
360
505
  timestamp: event.timestamp,
361
506
  kind: 'tool',
362
- text: `调用工具:${toolName}`,
507
+ text: toolName,
363
508
  requestId,
364
509
  sessionId: resolvedSessionId,
365
510
  provider: resolvedProvider,
@@ -726,8 +871,33 @@ function touchIntegrationConnection(ws) {
726
871
  return updated;
727
872
  }
728
873
 
874
+ function removeDuplicateIntegrationIdentityConnections(nextMeta, currentWs) {
875
+ if (nextMeta?.role !== INTEGRATION_ROLE_FRONTEND_PAGE) {
876
+ return;
877
+ }
878
+
879
+ const nextKey = getIntegrationConnectionIdentityKey(nextMeta);
880
+ if (!nextKey) {
881
+ return;
882
+ }
883
+
884
+ for (const [candidateWs, candidateMeta] of integrationConnections.entries()) {
885
+ if (candidateWs === currentWs) {
886
+ continue;
887
+ }
888
+
889
+ if (getIntegrationConnectionIdentityKey(candidateMeta) !== nextKey) {
890
+ continue;
891
+ }
892
+
893
+ integrationConnections.delete(candidateWs);
894
+ removePendingIntegrationRequestsForWs(candidateWs);
895
+ removePendingIntegrationPingsForWs(candidateWs);
896
+ }
897
+ }
898
+
729
899
  function listIntegrationConnections({ role = null, channel = null, clientId = null } = {}) {
730
- return Array.from(integrationConnections.values()).filter((meta) => {
900
+ const matchingConnections = Array.from(integrationConnections.values()).filter((meta) => {
731
901
  if (role && meta.role !== role) {
732
902
  return false;
733
903
  }
@@ -742,6 +912,12 @@ function listIntegrationConnections({ role = null, channel = null, clientId = nu
742
912
 
743
913
  return true;
744
914
  });
915
+
916
+ if (role !== INTEGRATION_ROLE_FRONTEND_PAGE) {
917
+ return matchingConnections;
918
+ }
919
+
920
+ return dedupeIntegrationConnectionsByIdentity(matchingConnections);
745
921
  }
746
922
 
747
923
  function buildEditorClientSummary(meta) {
@@ -977,6 +1153,7 @@ function handleIntegrationConnect(ws, data) {
977
1153
  };
978
1154
 
979
1155
  const previous = integrationConnections.get(ws) || null;
1156
+ removeDuplicateIntegrationIdentityConnections(nextMeta, ws);
980
1157
  integrationConnections.set(ws, nextMeta);
981
1158
 
982
1159
  sendJson(ws, {
@@ -1197,7 +1374,7 @@ function getTargetFrontendConnections(channel, targetClientId) {
1197
1374
  });
1198
1375
  }
1199
1376
 
1200
- function resolveForwardTargets(originWs, { channel, targetClientId }) {
1377
+ function resolveForwardTargets(originWs, { channel, targetClientId, context, requiredCapability = null }) {
1201
1378
  const explicitTargetId = normalizeNonEmptyString(targetClientId);
1202
1379
  if (explicitTargetId) {
1203
1380
  return {
@@ -1207,7 +1384,15 @@ function resolveForwardTargets(originWs, { channel, targetClientId }) {
1207
1384
  };
1208
1385
  }
1209
1386
 
1210
- const channelTargets = getTargetFrontendConnections(channel, null);
1387
+ const allChannelTargets = getTargetFrontendConnections(channel, null);
1388
+ const channelTargets = requiredCapability
1389
+ ? allChannelTargets.filter((target) => target.capabilities?.includes(requiredCapability))
1390
+ : allChannelTargets.filter((target) => (
1391
+ !Array.isArray(target.capabilities)
1392
+ || target.capabilities.length === 0
1393
+ || target.capabilities.includes('context.update')
1394
+ || target.capabilities.includes('prompt.update')
1395
+ ));
1211
1396
  if (channelTargets.length === 0) {
1212
1397
  return {
1213
1398
  targets: [],
@@ -1219,17 +1404,18 @@ function resolveForwardTargets(originWs, { channel, targetClientId }) {
1219
1404
  };
1220
1405
  }
1221
1406
 
1222
- if (channelTargets.length === 1) {
1407
+ const pageTargets = collapseDuplicateFrontendPagesByPageUrl(channelTargets);
1408
+ if (pageTargets.length === 1) {
1223
1409
  return {
1224
- targets: channelTargets,
1225
- resolvedTargetClientId: channelTargets[0].clientId,
1410
+ targets: pageTargets,
1411
+ resolvedTargetClientId: pageTargets[0].clientId,
1226
1412
  error: null
1227
1413
  };
1228
1414
  }
1229
1415
 
1230
1416
  const originMeta = integrationConnections.get(originWs) || null;
1231
1417
  const sessionMatchedTargets = originMeta?.sessionId
1232
- ? channelTargets.filter((target) => target.sessionId && target.sessionId === originMeta.sessionId)
1418
+ ? pageTargets.filter((target) => target.sessionId && target.sessionId === originMeta.sessionId)
1233
1419
  : [];
1234
1420
  if (sessionMatchedTargets.length === 1) {
1235
1421
  return {
@@ -1240,7 +1426,7 @@ function resolveForwardTargets(originWs, { channel, targetClientId }) {
1240
1426
  }
1241
1427
 
1242
1428
  const pageMatchedTargets = originMeta?.pageUrl
1243
- ? channelTargets.filter((target) => target.pageUrl && target.pageUrl === originMeta.pageUrl)
1429
+ ? pageTargets.filter((target) => target.pageUrl && target.pageUrl === originMeta.pageUrl)
1244
1430
  : [];
1245
1431
  if (pageMatchedTargets.length === 1) {
1246
1432
  return {
@@ -1250,6 +1436,15 @@ function resolveForwardTargets(originWs, { channel, targetClientId }) {
1250
1436
  };
1251
1437
  }
1252
1438
 
1439
+ const contextMatchedTarget = pickContextMatchedTarget(pageTargets, context);
1440
+ if (contextMatchedTarget) {
1441
+ return {
1442
+ targets: [contextMatchedTarget],
1443
+ resolvedTargetClientId: contextMatchedTarget.clientId,
1444
+ error: null
1445
+ };
1446
+ }
1447
+
1253
1448
  return {
1254
1449
  targets: [],
1255
1450
  resolvedTargetClientId: null,
@@ -1499,7 +1694,13 @@ function forwardIntegrationMessage(ws, data, { validator, type, awaitResult = fa
1499
1694
  }
1500
1695
 
1501
1696
  const { channel, targetClientId } = validation.payload;
1502
- const resolution = resolveForwardTargets(ws, { channel, targetClientId });
1697
+ const requiredCapability = INTEGRATION_EDITOR_CAPABILITIES[type] || null;
1698
+ const resolution = resolveForwardTargets(ws, {
1699
+ channel,
1700
+ targetClientId,
1701
+ context: validation.payload.context,
1702
+ requiredCapability
1703
+ });
1503
1704
  if (resolution.error) {
1504
1705
  sendIntegrationError(ws, {
1505
1706
  requestId,
@@ -1519,7 +1720,6 @@ function forwardIntegrationMessage(ws, data, { validator, type, awaitResult = fa
1519
1720
  return;
1520
1721
  }
1521
1722
 
1522
- const requiredCapability = INTEGRATION_EDITOR_CAPABILITIES[type] || null;
1523
1723
  if (requiredCapability) {
1524
1724
  const unsupportedTarget = targets.find((target) => !target.capabilities?.includes(requiredCapability));
1525
1725
  if (unsupportedTarget) {
@@ -2014,6 +2214,7 @@ export function handleExternalAgentWebSocketConnection(ws, request) {
2014
2214
  });
2015
2215
 
2016
2216
  ws.on('close', () => {
2217
+ removeSessionStateSubscriptionsForWs(ws);
2017
2218
  removeSessionActivitySubscriptionsForWs(ws);
2018
2219
  unregisterIntegrationConnection(ws);
2019
2220
  });
package/server/index.js CHANGED
@@ -96,14 +96,16 @@ async function getPty() {
96
96
  import fetch from 'node-fetch';
97
97
  import mime from 'mime-types';
98
98
 
99
- import { getProjects, getProjectsList, getProjectDetails, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache, clearProviderSessionLookupCaches, getGeminiSessionMessages } from './projects.js';
99
+ import { getProjects, getProjectsList, getProjectDetails, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache, clearProviderSessionLookupCaches, getGeminiSessionMessages, deleteGeminiSession } from './projects.js';
100
100
  import {
101
101
  executeAgentPrompt,
102
102
  getActiveAgentSessions,
103
103
  isAgentSessionActive,
104
+ resolveAgentElicitation,
104
105
  resolveAgentPermission,
105
106
  setAgentSessionMode
106
107
  } from './acp-runtime/index.js';
108
+ import { mergeAgentCommandOverrides } from './acp-runtime/command-overrides.js';
107
109
  import authRoutes from './routes/auth.js';
108
110
  import commandsRoutes from './routes/commands.js';
109
111
  import settingsRoutes from './routes/settings.js';
@@ -128,8 +130,11 @@ import userRoutes from './routes/user.js';
128
130
  import codexRoutes from './routes/codex.js';
129
131
  import opencodeRoutes from './routes/opencode.js';
130
132
  import sessionCoreRoutes from './routes/session-core.js';
133
+ import runsRoutes from './routes/runs.js';
131
134
  import { parseCodexTokenCountInfo } from './utils/codexTokenUsage.js';
132
135
  import { getConfiguredDefaultProjectPath, resolveWorkingDirectory } from './utils/defaultWorkingDirectory.js';
136
+ import { buildShellCommand } from './utils/shellCommand.js';
137
+ import { openLocalTerminal } from './utils/localTerminal.js';
133
138
  import { initializeDatabase, userDb } from './database/db.js';
134
139
  import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
135
140
  import { IS_PLATFORM } from './constants/config.js';
@@ -138,6 +143,8 @@ import { SessionEventMirrorWriter } from './session-core/runtimeWriter.js';
138
143
  import { abortAgentSessionWithWriter } from './session-core/abortSession.js';
139
144
  import { authenticateExternalWebSocketRequest } from './external-agent/auth.js';
140
145
  import { handleExternalAgentWebSocketConnection } from './external-agent/ws.js';
146
+ import { runRegistry } from './session-core/runRegistry.js';
147
+ import { shouldStartProjectsWatcher } from './projects-watcher-config.js';
141
148
 
142
149
  // File system watcher for projects folder
143
150
  let projectsWatcher = null;
@@ -520,6 +527,40 @@ app.use('/api/user', authenticateToken, userRoutes);
520
527
  app.use('/api/codex', authenticateToken, codexRoutes);
521
528
  app.use('/api/opencode', authenticateToken, opencodeRoutes);
522
529
  app.use('/api/session-core', authenticateToken, sessionCoreRoutes);
530
+ app.use('/api', authenticateToken, runsRoutes);
531
+
532
+ app.post('/api/local-terminal/session', authenticateToken, async (req, res) => {
533
+ try {
534
+ const provider = req.body?.provider || 'claude';
535
+ const sessionId = typeof req.body?.sessionId === 'string' ? req.body.sessionId.trim() : '';
536
+ const projectPath = resolveWorkingDirectory({ projectPath: req.body?.projectPath });
537
+
538
+ if (!sessionId) {
539
+ return res.status(400).json({ error: 'sessionId is required' });
540
+ }
541
+
542
+ const command = buildShellCommand({
543
+ projectPath,
544
+ provider,
545
+ sessionId,
546
+ hasSession: true,
547
+ platform: os.platform()
548
+ });
549
+ const terminal = openLocalTerminal({ command });
550
+
551
+ res.json({
552
+ success: true,
553
+ provider,
554
+ sessionId,
555
+ projectPath,
556
+ command,
557
+ terminal
558
+ });
559
+ } catch (error) {
560
+ console.error('[ERROR] Failed to open local terminal:', error);
561
+ res.status(500).json({ error: error.message });
562
+ }
563
+ });
523
564
 
524
565
  // Agent API Routes (uses API key authentication)
525
566
  app.use('/api/agent', agentRoutes);
@@ -651,6 +692,10 @@ app.get('/api/projects', authenticateToken, async (req, res) => {
651
692
 
652
693
  app.get('/api/projects/list', authenticateToken, async (req, res) => {
653
694
  try {
695
+ if (String(req.query.force || '').toLowerCase() === 'true') {
696
+ clearProjectDirectoryCache();
697
+ clearProviderSessionLookupCaches();
698
+ }
654
699
  const projects = await getProjectsList(broadcastProgress);
655
700
  res.json(projects);
656
701
  } catch (error) {
@@ -719,6 +764,16 @@ app.get('/api/gemini/sessions/:sessionId/messages', authenticateToken, async (re
719
764
  }
720
765
  });
721
766
 
767
+ app.delete('/api/gemini/sessions/:sessionId', authenticateToken, async (req, res) => {
768
+ try {
769
+ const { sessionId } = req.params;
770
+ await deleteGeminiSession(sessionId);
771
+ res.json({ success: true });
772
+ } catch (error) {
773
+ res.status(500).json({ error: error.message });
774
+ }
775
+ });
776
+
722
777
  // Rename project endpoint
723
778
  app.put('/api/projects/:projectName/rename', authenticateToken, async (req, res) => {
724
779
  try {
@@ -1151,21 +1206,89 @@ function handleChatConnection(ws) {
1151
1206
 
1152
1207
  if (agentCommandTypes.includes(data.type)) {
1153
1208
  const agentKey = data.type.replace(/-command$/, '');
1209
+ const clientRequestId = data.clientRequestId || data.options?.clientRequestId || null;
1154
1210
  console.log(`[DEBUG] ${agentKey} message:`, data.command || '[Continue/Resume]');
1155
1211
  console.log('📁 Project:', data.options?.projectPath || data.options?.cwd || 'Unknown');
1156
1212
  console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
1157
1213
  console.log('🤖 Model:', data.options?.model || 'default');
1158
1214
 
1159
- await executeAgentPrompt({
1160
- agentKey,
1161
- command: data.command,
1162
- options: {
1163
- ...(data.options || {}),
1164
- clientRequestId: data.clientRequestId || data.options?.clientRequestId || null
1165
- },
1166
- writer: new SessionEventMirrorWriter(baseWriter, agentKey)
1215
+ const run = runRegistry.createRun({
1216
+ provider: agentKey,
1217
+ sessionId: data.sessionId || data.options?.sessionId || null,
1218
+ clientRequestId
1219
+ });
1220
+ let sessionWriter = null;
1221
+ const emitRunState = (status, event = {}) => {
1222
+ const resolvedSessionId = event.sessionId ||
1223
+ sessionWriter?.getSessionId?.() ||
1224
+ data.sessionId ||
1225
+ data.options?.sessionId ||
1226
+ null;
1227
+ const updatedRun = runRegistry.updateRun(run.runId, {
1228
+ status,
1229
+ sessionId: resolvedSessionId,
1230
+ clientRequestId
1231
+ }) || runRegistry.getRun(run.runId);
1232
+ const runEvent = runRegistry.appendRunEvent(run.runId, {
1233
+ type: 'run-state',
1234
+ provider: agentKey,
1235
+ sessionId: updatedRun?.sessionId || resolvedSessionId,
1236
+ clientRequestId,
1237
+ event: {
1238
+ status,
1239
+ ...event
1240
+ }
1241
+ });
1242
+ baseWriter.send({
1243
+ type: 'run-state',
1244
+ provider: agentKey,
1245
+ sessionId: updatedRun?.sessionId || resolvedSessionId,
1246
+ clientRequestId,
1247
+ runId: run.runId,
1248
+ runEventSeq: runEvent?.seq || updatedRun?.lastEventSeq || null,
1249
+ run: updatedRun,
1250
+ event: {
1251
+ status,
1252
+ ...event
1253
+ }
1254
+ });
1255
+ };
1256
+
1257
+ sessionWriter = new SessionEventMirrorWriter(baseWriter, agentKey, {
1258
+ runRegistry,
1259
+ runId: run.runId,
1260
+ clientRequestId
1167
1261
  });
1168
- } else if (data.type === 'abort-session') {
1262
+ emitRunState('queued', { reason: 'accepted' });
1263
+
1264
+ try {
1265
+ emitRunState('running', { reason: 'execution_started' });
1266
+ const agentCommandOverrides = mergeAgentCommandOverrides(
1267
+ process.env.AXHUB_ACP_COMMAND_OVERRIDES,
1268
+ data.options?.agentCommandOverrides
1269
+ );
1270
+ const result = await executeAgentPrompt({
1271
+ agentKey,
1272
+ command: data.command,
1273
+ options: {
1274
+ ...(data.options || {}),
1275
+ ...(Object.keys(agentCommandOverrides).length > 0 ? { agentCommandOverrides } : {}),
1276
+ clientRequestId
1277
+ },
1278
+ writer: sessionWriter
1279
+ });
1280
+ emitRunState('succeeded', {
1281
+ sessionId: result?.sessionId || sessionWriter.getSessionId?.() || null,
1282
+ stopReason: result?.stopReason || null
1283
+ });
1284
+ } catch (error) {
1285
+ emitRunState('failed', {
1286
+ sessionId: sessionWriter.getSessionId?.() || data.options?.sessionId || null,
1287
+ error: error.message
1288
+ });
1289
+ throw error;
1290
+ }
1291
+ } else if (data.type === 'abort-session') {
1169
1292
  console.log('[DEBUG] Abort session request:', data.sessionId);
1170
1293
  const provider = typeof data.provider === 'string' && data.provider.trim()
1171
1294
  ? data.provider.trim().toLowerCase()
@@ -1203,6 +1326,15 @@ function handleChatConnection(ws) {
1203
1326
  rememberEntry: data.rememberEntry
1204
1327
  });
1205
1328
  }
1329
+ } else if (data.type === 'elicitation-response') {
1330
+ if (data.requestId) {
1331
+ resolveAgentElicitation(data.requestId, {
1332
+ action: data.action,
1333
+ content: data.content && typeof data.content === 'object' && !Array.isArray(data.content)
1334
+ ? data.content
1335
+ : {}
1336
+ });
1337
+ }
1206
1338
  } else if (data.type === 'check-session-status') {
1207
1339
  // Check if a specific session is currently processing
1208
1340
  const provider = data.provider || 'claude';
@@ -1340,47 +1472,14 @@ function handleShellConnection(ws) {
1340
1472
  }));
1341
1473
 
1342
1474
  try {
1343
- // Prepare the shell command adapted to the platform and provider
1344
- let shellCommand;
1345
- if (isPlainShell) {
1346
- // Plain shell mode - just run the initial command in the project directory
1347
- if (os.platform() === 'win32') {
1348
- shellCommand = `Set-Location -Path "${projectPath}"; ${initialCommand}`;
1349
- } else {
1350
- shellCommand = `cd "${projectPath}" && ${initialCommand}`;
1351
- }
1352
- } else if (provider === 'gemini') {
1353
- if (os.platform() === 'win32') {
1354
- if (hasSession && sessionId) {
1355
- shellCommand = `Set-Location -Path "${projectPath}"; npx -y @google/gemini-cli --resume ${sessionId}`;
1356
- } else {
1357
- shellCommand = `Set-Location -Path "${projectPath}"; npx -y @google/gemini-cli`;
1358
- }
1359
- } else {
1360
- if (hasSession && sessionId) {
1361
- shellCommand = `cd "${projectPath}" && npx -y @google/gemini-cli --resume ${sessionId}`;
1362
- } else {
1363
- shellCommand = `cd "${projectPath}" && npx -y @google/gemini-cli`;
1364
- }
1365
- }
1366
- } else {
1367
- // Use claude command (default) or initialCommand if provided
1368
- const command = initialCommand || 'claude';
1369
- if (os.platform() === 'win32') {
1370
- if (hasSession && sessionId) {
1371
- // Try to resume session, but with fallback to new session if it fails
1372
- shellCommand = `Set-Location -Path "${projectPath}"; claude --resume ${sessionId}; if ($LASTEXITCODE -ne 0) { claude }`;
1373
- } else {
1374
- shellCommand = `Set-Location -Path "${projectPath}"; ${command}`;
1375
- }
1376
- } else {
1377
- if (hasSession && sessionId) {
1378
- shellCommand = `cd "${projectPath}" && claude --resume ${sessionId} || claude`;
1379
- } else {
1380
- shellCommand = `cd "${projectPath}" && ${command}`;
1381
- }
1382
- }
1383
- }
1475
+ const shellCommand = buildShellCommand({
1476
+ projectPath,
1477
+ provider,
1478
+ sessionId,
1479
+ hasSession,
1480
+ initialCommand,
1481
+ isPlainShell
1482
+ });
1384
1483
 
1385
1484
  console.log('🔧 Executing shell command:', shellCommand);
1386
1485
 
@@ -2185,8 +2284,12 @@ async function startServer() {
2185
2284
 
2186
2285
  writeRuntimeStatusFile(PORT, isProduction);
2187
2286
 
2188
- // Start watching the projects folder for changes
2189
- await setupProjectsWatcher();
2287
+ if (shouldStartProjectsWatcher()) {
2288
+ // Opt-in only: provider session files are high-churn and expensive to poll.
2289
+ await setupProjectsWatcher();
2290
+ } else {
2291
+ console.log(`${c.info('[INFO]')} Project/session file watcher: disabled; use manual refresh`);
2292
+ }
2190
2293
  });
2191
2294
  } catch (error) {
2192
2295
  console.error('[ERROR] Failed to start server:', error);
@@ -0,0 +1,4 @@
1
+ export function shouldStartProjectsWatcher(env = process.env) {
2
+ const rawValue = String(env.AXHUB_GENIE_ENABLE_PROJECTS_WATCHER || '').trim().toLowerCase();
3
+ return rawValue === 'true' || rawValue === '1';
4
+ }