@axhub/genie 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +21 -675
  2. package/dist/api-docs.html +2 -2
  3. package/dist/assets/App-GBcTeeUS.js +460 -0
  4. package/dist/assets/App-qxJ8_QYu.css +32 -0
  5. package/dist/assets/ReviewApp-C9K--AQE.js +1 -0
  6. package/dist/assets/{_basePickBy-C19AekOu.js → _basePickBy-DR_8uFCo.js} +1 -1
  7. package/dist/assets/{_baseUniq-JsnevLw_.js → _baseUniq-D0njlQ_7.js} +1 -1
  8. package/dist/assets/{arc-BLpcuBlf.js → arc-CKlr_Rec.js} +1 -1
  9. package/dist/assets/architectureDiagram-2XIMDMQ5-BmO_uLUH.js +36 -0
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-DQBLwsUS.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +3 -3
  11. package/dist/assets/c4Diagram-IC4MRINW-C67kFoXx.js +10 -0
  12. package/dist/assets/channel-V3MBjKys.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-De63kbgc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DtTDDdM9.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-DHuwd8tw.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-BgytFtmO.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-nZdp86aN.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
  18. package/dist/assets/chunk-NQ4KR5QH-5UAE0Vg-.js +220 -0
  19. package/dist/assets/{chunk-QZHKN3VN-DvUQ3mnO.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
  20. package/dist/assets/chunk-WL4C6EOR-DjDPvUUP.js +189 -0
  21. package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
  23. package/dist/assets/clone-BbMGfZwt.js +1 -0
  24. package/dist/assets/cose-bilkent-S5V4N54A-D-60XrkJ.js +1 -0
  25. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +331 -0
  26. package/dist/assets/{dagre-KLK3FWXG-CHYIvW47.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
  27. package/dist/assets/diagram-E7M64L7V-BueeqoYm.js +24 -0
  28. package/dist/assets/{diagram-IFDJBPK2-Dzsiln_C.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
  29. package/dist/assets/{diagram-P4PSJMXO-DKnGbUpE.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
  30. package/dist/assets/erDiagram-INFDFZHY-D0oVnO-x.js +70 -0
  31. package/dist/assets/{flowDiagram-PKNHOUZH-BAZ2-jKp.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +4 -4
  32. package/dist/assets/ganttDiagram-A5KZAMGK-BwhbbgCP.js +292 -0
  33. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BflpyjGy.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
  34. package/dist/assets/{graph-suelaXFh.js → graph-DzKos-N0.js} +1 -1
  35. package/dist/assets/highlighted-body-TPN3WLV5-CKDMgz3X.js +1 -0
  36. package/dist/assets/index-DiQlHzGj.js +2 -0
  37. package/dist/assets/index-Drat2nB9.css +1 -0
  38. package/dist/assets/{infoDiagram-LFFYTUFH-pfD1FA3p.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
  39. package/dist/assets/ishikawaDiagram-PHBUUO56-CtihxDxl.js +70 -0
  40. package/dist/assets/journeyDiagram-4ABVD52K-Du00J8_d.js +139 -0
  41. package/dist/assets/{kanban-definition-K7BYSVSG-FWinmur1.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +5 -5
  42. package/dist/assets/{layout-vcz43XvZ.js → layout-B80Sityu.js} +1 -1
  43. package/dist/assets/{linear-le4gc0vx.js → linear-sRQLOf5H.js} +1 -1
  44. package/dist/assets/mermaid-O7DHMXV3-CBuVs4eJ.js +1038 -0
  45. package/dist/assets/mindmap-definition-YRQLILUH-C5IL_xi-.js +68 -0
  46. package/dist/assets/{pieDiagram-SKSYHLDU-C7PKDh3b.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +2 -2
  47. package/dist/assets/quadrantDiagram-337W2JSQ-COfUcLWt.js +7 -0
  48. package/dist/assets/requirementDiagram-Z7DCOOCP-DSb-CJ5B.js +73 -0
  49. package/dist/assets/{sankeyDiagram-WA2Y5GQK-4gulcOP4.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +3 -3
  50. package/dist/assets/sequenceDiagram-2WXFIKYE-C2VpkMwA.js +145 -0
  51. package/dist/assets/{stateDiagram-RAJIS63D-CB4Vl7qM.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
  52. package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
  53. package/dist/assets/timeline-definition-YZTLITO2-Dx1hP5lg.js +61 -0
  54. package/dist/assets/{treemap-KZPCXAKY-DZSEE6Hz.js → treemap-KZPCXAKY-CkLOdYCZ.js} +58 -58
  55. package/dist/assets/vendor-codemirror-BxPY6emf.js +39 -0
  56. package/dist/assets/vendor-react-xmA_f8ig.js +59 -0
  57. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  58. package/dist/assets/{vennDiagram-LZ73GAT5-8E_G06fI.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +4 -4
  59. package/dist/assets/xychartDiagram-JWTSCODW-6fh6qmzN.js +7 -0
  60. package/dist/index.html +5 -5
  61. package/package.json +36 -35
  62. package/server/acp-runtime/client.js +91 -17
  63. package/server/acp-runtime/index.js +5 -16
  64. package/server/acp-runtime/session-store.js +4 -4
  65. package/server/channels/runtime/AgentRuntimeAdapter.js +1 -10
  66. package/server/claude-sdk.js +1 -3
  67. package/server/cli.js +159 -2
  68. package/server/external-agent/service.js +24 -6
  69. package/server/external-agent/ws.js +63 -3
  70. package/server/gemini-cli.js +1 -3
  71. package/server/index.js +120 -19
  72. package/server/openai-codex.js +1 -3
  73. package/server/opencode-cli.js +1 -3
  74. package/server/projects.js +654 -236
  75. package/server/routes/cc-connect.js +1131 -0
  76. package/server/routes/cli-auth.js +1 -73
  77. package/server/routes/commands.js +4 -9
  78. package/server/routes/projects.js +45 -24
  79. package/server/routes/session-core.js +149 -86
  80. package/server/session-core/eventStore.js +45 -18
  81. package/server/session-core/providerAdapters.js +50 -13
  82. package/server/session-core/providerDiscovery.js +8 -3
  83. package/server/session-core/runtimeState.js +8 -0
  84. package/server/utils/ccConnectManager.js +390 -0
  85. package/server/utils/ccConnectState.js +575 -0
  86. package/server/utils/resolveCommandPath.js +71 -0
  87. package/server/utils/workspaceRoots.js +154 -0
  88. package/shared/conversationEvents.js +78 -14
  89. package/dist/assets/App-BWSqiXAT.js +0 -220
  90. package/dist/assets/App-DrlLKa8f.css +0 -1
  91. package/dist/assets/ReviewApp-nz3mbArg.js +0 -1
  92. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +0 -36
  93. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +0 -10
  94. package/dist/assets/channel-DkFNxV_H.js +0 -1
  95. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +0 -220
  96. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +0 -189
  97. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +0 -1
  98. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +0 -1
  99. package/dist/assets/clone-C0lCEIEO.js +0 -1
  100. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +0 -1
  101. package/dist/assets/cytoscape.esm-5J0xJHOV.js +0 -321
  102. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +0 -24
  103. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +0 -70
  104. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +0 -292
  105. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +0 -1
  106. package/dist/assets/index-B01NxbUv.css +0 -1
  107. package/dist/assets/index-DW5pGgQ_.js +0 -2
  108. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +0 -70
  109. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +0 -139
  110. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +0 -870
  111. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +0 -68
  112. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +0 -7
  113. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +0 -73
  114. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +0 -145
  115. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +0 -1
  116. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +0 -61
  117. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +0 -31
  118. package/dist/assets/vendor-react-CP4yFTs7.js +0 -8
  119. package/dist/assets/vendor-xterm-DfcmCpbH.js +0 -66
  120. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +0 -7
  121. package/server/_legacy-providers/README.md +0 -30
  122. package/server/_legacy-providers/claude-sdk.js +0 -956
  123. package/server/_legacy-providers/gemini-cli.js +0 -368
  124. package/server/_legacy-providers/openai-codex.js +0 -705
  125. package/server/_legacy-providers/opencode-cli.js +0 -674
  126. package/server/acp-runtime/client.test.js +0 -688
  127. package/server/acp-runtime/session-store.test.js +0 -89
  128. package/server/cli.test.js +0 -76
  129. package/server/external-agent/service.test.js +0 -53
  130. package/server/external-agent/ws.test.js +0 -289
  131. package/shared/conversationEvents.test.js +0 -403
@@ -12,6 +12,7 @@ import {
12
12
  import {
13
13
  buildSessionRuntimeSnapshot,
14
14
  createSessionRuntimeSubscriptionKey,
15
+ seedSessionRuntimeSubscriptionSnapshot,
15
16
  subscribeToSessionRuntimeStateChanges
16
17
  } from '../session-core/runtimeState.js';
17
18
  import { SUPPORTED_PROVIDERS } from '../../shared/conversationEvents.js';
@@ -24,6 +25,7 @@ const INTEGRATION_PING_TIMEOUT_MS = 1500;
24
25
  const INTEGRATION_EDITOR_REQUEST_TIMEOUT_MS = 15000;
25
26
  const INTEGRATION_EDITOR_TYPE_PREFIX = 'integration.editor.';
26
27
  const INTEGRATION_EDITOR_RESULT_SUFFIX = '.result';
28
+ const VALID_EDITING_STATES = Object.freeze(['editing', 'idle', 'completed', 'error']);
27
29
  const INTEGRATION_EDITOR_CAPABILITIES = Object.freeze({
28
30
  'integration.editor.snapshot.get': 'editor.snapshot',
29
31
  'integration.editor.nodes.list': 'editor.nodes.list',
@@ -38,6 +40,35 @@ const pendingIntegrationPings = new Map();
38
40
  const sessionStateSubscriptionsByWs = new Map();
39
41
  const sessionStateSubscribersByKey = new Map();
40
42
 
43
+ // Editing state cache for resilience across connection drops
44
+ const recentEditingStates = new Map();
45
+ const EDITING_STATE_TTL_MS = 30 * 60 * 1000;
46
+ const EDITING_STATE_CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
47
+
48
+ function cacheEditingState(channel, targetClientId, elementKey, state, taskRef) {
49
+ const key = `${channel}::${targetClientId}::${elementKey}`;
50
+ recentEditingStates.set(key, {
51
+ channel,
52
+ targetClientId,
53
+ elementKey,
54
+ state,
55
+ taskRef: taskRef || null,
56
+ updatedAt: Date.now(),
57
+ });
58
+ }
59
+
60
+ function cleanupExpiredEditingStates() {
61
+ const now = Date.now();
62
+ for (const [key, entry] of recentEditingStates.entries()) {
63
+ if (now - entry.updatedAt > EDITING_STATE_TTL_MS) {
64
+ recentEditingStates.delete(key);
65
+ }
66
+ }
67
+ }
68
+
69
+ const editingStateCleanupTimer = setInterval(cleanupExpiredEditingStates, EDITING_STATE_CLEANUP_INTERVAL_MS);
70
+ editingStateCleanupTimer.unref?.();
71
+
41
72
  function sendJson(ws, payload) {
42
73
  if (ws.readyState === WebSocket.OPEN) {
43
74
  ws.send(JSON.stringify(payload));
@@ -258,6 +289,7 @@ async function handleAgentStateSubscribe(ws, data) {
258
289
  }
259
290
 
260
291
  const subscriptionKey = createSessionRuntimeSubscriptionKey(normalized.provider, normalized.sessionId);
292
+ seedSessionRuntimeSubscriptionSnapshot(subscriptionKey, snapshot);
261
293
  addSessionStateSubscription(ws, subscriptionKey);
262
294
 
263
295
  sendJson(ws, {
@@ -369,6 +401,7 @@ function buildEditorClientSummary(meta) {
369
401
  return {
370
402
  channel: meta.channel,
371
403
  clientId: meta.clientId,
404
+ source: meta.source || null,
372
405
  sessionId: meta.sessionId || null,
373
406
  pageUrl: meta.pageUrl || null,
374
407
  lastSeenAt: meta.lastSeenAt || null,
@@ -565,6 +598,7 @@ function validateIntegrationConnectPayload(payload) {
565
598
  role,
566
599
  channel,
567
600
  clientId,
601
+ source: normalizeNonEmptyString(payload.source) || null,
568
602
  pageUrl: normalizeNonEmptyString(payload.pageUrl) || null,
569
603
  sessionId: normalizeNonEmptyString(payload.sessionId) || null,
570
604
  capabilities: Array.isArray(payload.capabilities)
@@ -948,8 +982,12 @@ function validateEditorEditingSetPayload(payload) {
948
982
  }
949
983
 
950
984
  const state = normalizeNonEmptyString(payload.state);
951
- if (state !== 'editing' && state !== 'idle') {
952
- return { ok: false, code: 'INVALID_PAYLOAD', message: 'state must be editing or idle' };
985
+ if (!VALID_EDITING_STATES.includes(state)) {
986
+ return {
987
+ ok: false,
988
+ code: 'INVALID_PAYLOAD',
989
+ message: `state must be one of: ${VALID_EDITING_STATES.join(', ')}`
990
+ };
953
991
  }
954
992
 
955
993
  return validateEditorTargetPayload(payload, {
@@ -1137,6 +1175,24 @@ async function forwardIntegrationResponseToOrigin(data) {
1137
1175
  outgoingData?.type === 'integration.error'
1138
1176
  || isEditorResultMessageType(outgoingData?.type)
1139
1177
  || (outgoingData?.type === 'integration.ack' && pendingRequest.awaitResult !== true);
1178
+
1179
+ // Cache successful editing state changes for resilience
1180
+ if (
1181
+ outgoingData?.type === 'integration.editor.editing.result'
1182
+ && isPlainObject(outgoingData?.payload)
1183
+ && outgoingData.payload.applied === true
1184
+ && pendingRequest.requestPayload
1185
+ ) {
1186
+ const rp = pendingRequest.requestPayload;
1187
+ cacheEditingState(
1188
+ rp.channel,
1189
+ rp.targetClientId,
1190
+ outgoingData.payload.elementKey || rp.elementKey,
1191
+ outgoingData.payload.state,
1192
+ outgoingData.payload.taskRef || rp.taskRef,
1193
+ );
1194
+ }
1195
+
1140
1196
  if (isTerminal) {
1141
1197
  clearPendingIntegrationRequest(requestId);
1142
1198
  }
@@ -1173,7 +1229,11 @@ class ExternalAgentWebSocketWriter {
1173
1229
  requestId: this.requestId,
1174
1230
  provider: payload.provider || this.provider,
1175
1231
  sessionId,
1176
- ...buildSessionNavigation(sessionId)
1232
+ ...buildSessionNavigation({
1233
+ provider: payload.provider || this.provider,
1234
+ sessionId,
1235
+ projectPath: payload.projectPath || null
1236
+ })
1177
1237
  });
1178
1238
  return;
1179
1239
  }
@@ -4,15 +4,13 @@ import {
4
4
  getActiveAgentSessions,
5
5
  isAgentSessionActive
6
6
  } from './acp-runtime/index.js';
7
- import { GEMINI_MODELS } from '../shared/modelConstants.js';
8
7
 
9
8
  export async function queryGemini(command, options = {}, writer) {
10
9
  return executeAgentPrompt({
11
10
  agentKey: 'gemini',
12
11
  command,
13
12
  options: {
14
- ...options,
15
- model: options.model || GEMINI_MODELS.DEFAULT
13
+ ...options
16
14
  },
17
15
  writer
18
16
  });
package/server/index.js CHANGED
@@ -96,7 +96,7 @@ async function getPty() {
96
96
  import fetch from 'node-fetch';
97
97
  import mime from 'mime-types';
98
98
 
99
- import { getProjects, 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 } from './projects.js';
100
100
  import {
101
101
  executeAgentPrompt,
102
102
  getActiveAgentSessions,
@@ -113,8 +113,20 @@ import commandsRoutes from './routes/commands.js';
113
113
  import settingsRoutes from './routes/settings.js';
114
114
  import channelsRoutes from './routes/channels.js';
115
115
  import agentRoutes from './routes/agent.js';
116
- import projectsRoutes, { WORKSPACES_ROOT, validateWorkspacePath } from './routes/projects.js';
116
+ import projectsRoutes, {
117
+ DEFAULT_WORKSPACES_ROOT,
118
+ HAS_WORKSPACES_ROOT_RESTRICTION,
119
+ WORKSPACES_ROOTS,
120
+ validateWorkspacePath
121
+ } from './routes/projects.js';
122
+ import {
123
+ UNIX_WORKSPACE_ROOTS,
124
+ buildWorkspaceRootSuggestions,
125
+ expandWorkspacePath,
126
+ shouldUseVirtualWorkspaceRoots
127
+ } from './utils/workspaceRoots.js';
117
128
  import cliAuthRoutes, { detectProviderInstallationStatus } from './routes/cli-auth.js';
129
+ import ccConnectRoutes from './routes/cc-connect.js';
118
130
  import userRoutes from './routes/user.js';
119
131
  import codexRoutes from './routes/codex.js';
120
132
  import opencodeRoutes from './routes/opencode.js';
@@ -138,6 +150,7 @@ const UPDATE_PACKAGE_NAME = process.env.UPDATE_PACKAGE_NAME || packageInfo.id ||
138
150
  const VERSION_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
139
151
  let cachedSystemVersion = null;
140
152
  let cachedSystemVersionExpiresAt = 0;
153
+ const WINDOWS_DRIVES_ROOT = '__windows_drives__';
141
154
  const RUNTIME_STATUS_PATH = (() => {
142
155
  if (process.env.AXHUB_GENIE_STATUS_PATH) {
143
156
  return process.env.AXHUB_GENIE_STATUS_PATH;
@@ -320,8 +333,8 @@ async function setupProjectsWatcher() {
320
333
  clearProjectDirectoryCache();
321
334
  clearProviderSessionLookupCaches();
322
335
 
323
- // Get updated projects list
324
- const updatedProjects = await getProjects(broadcastProgress);
336
+ // Get updated lightweight projects list
337
+ const updatedProjects = await getProjectsList(broadcastProgress);
325
338
 
326
339
  // Notify all connected clients about the project changes
327
340
  const changedProvider = filePath.startsWith(codexSessionsPath)
@@ -330,14 +343,21 @@ async function setupProjectsWatcher() {
330
343
  const changedFileRoot = changedProvider === 'codex'
331
344
  ? codexSessionsPath
332
345
  : claudeProjectsPath;
346
+ const normalizedRelativePath = path.relative(changedFileRoot, filePath);
347
+ const changedPathSegments = normalizedRelativePath.split(path.sep).filter(Boolean);
348
+ const affectedProjectName = changedProvider === 'claude'
349
+ ? (changedPathSegments[0] || null)
350
+ : null;
333
351
  const updateMessage = JSON.stringify({
334
352
  type: 'projects_updated',
335
353
  projects: updatedProjects,
336
354
  timestamp: new Date().toISOString(),
337
355
  changeType: eventType,
338
- changedFile: path.relative(changedFileRoot, filePath),
356
+ changedFile: normalizedRelativePath,
339
357
  changedProvider,
340
- changedSessionId: extractSessionIdFromChangedFile(filePath)
358
+ changedSessionId: extractSessionIdFromChangedFile(filePath),
359
+ affectedProjectName,
360
+ requiresDetailsRefresh: Boolean(affectedProjectName)
341
361
  });
342
362
 
343
363
  connectedClients.forEach(client => {
@@ -504,6 +524,7 @@ app.use('/api/channels', authenticateToken, channelsRoutes);
504
524
 
505
525
  // CLI Authentication API Routes (protected)
506
526
  app.use('/api/cli', authenticateToken, cliAuthRoutes);
527
+ app.use('/api/cc-connect', authenticateToken, ccConnectRoutes);
507
528
 
508
529
  // User API Routes (protected)
509
530
  app.use('/api/user', authenticateToken, userRoutes);
@@ -633,6 +654,7 @@ app.post('/api/system/update', authenticateToken, async (req, res) => {
633
654
 
634
655
  app.get('/api/projects', authenticateToken, async (req, res) => {
635
656
  try {
657
+ res.setHeader('Deprecation', 'true');
636
658
  const projects = await getProjects(broadcastProgress);
637
659
  res.json(projects);
638
660
  } catch (error) {
@@ -640,6 +662,27 @@ app.get('/api/projects', authenticateToken, async (req, res) => {
640
662
  }
641
663
  });
642
664
 
665
+ app.get('/api/projects/list', authenticateToken, async (req, res) => {
666
+ try {
667
+ const projects = await getProjectsList(broadcastProgress);
668
+ res.json(projects);
669
+ } catch (error) {
670
+ res.status(500).json({ error: error.message });
671
+ }
672
+ });
673
+
674
+ app.get('/api/projects/:projectName/details', authenticateToken, async (req, res) => {
675
+ try {
676
+ const project = await getProjectDetails(req.params.projectName);
677
+ res.json(project);
678
+ } catch (error) {
679
+ if (/Project not found/i.test(error.message)) {
680
+ return res.status(404).json({ error: error.message });
681
+ }
682
+ res.status(500).json({ error: error.message });
683
+ }
684
+ });
685
+
643
686
  app.get('/api/projects/:projectName/sessions', authenticateToken, async (req, res) => {
644
687
  try {
645
688
  const { limit = 5, offset = 0 } = req.query;
@@ -743,16 +786,31 @@ app.post('/api/projects/create', authenticateToken, async (req, res) => {
743
786
  }
744
787
  });
745
788
 
746
- const expandWorkspacePath = (inputPath) => {
747
- if (!inputPath) return inputPath;
748
- if (inputPath === '~') {
749
- return WORKSPACES_ROOT;
750
- }
751
- if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
752
- return path.join(WORKSPACES_ROOT, inputPath.slice(2));
753
- }
754
- return inputPath;
755
- };
789
+ async function listWindowsDrives() {
790
+ const checks = await Promise.all(
791
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(async (letter) => {
792
+ const drivePath = `${letter}:\\`;
793
+ try {
794
+ await fsPromises.access(drivePath, fs.constants.R_OK);
795
+ const stats = await fsPromises.stat(drivePath);
796
+ if (!stats.isDirectory()) {
797
+ return null;
798
+ }
799
+
800
+ return {
801
+ path: drivePath,
802
+ name: drivePath,
803
+ type: 'directory',
804
+ isDrive: true
805
+ };
806
+ } catch (error) {
807
+ return null;
808
+ }
809
+ })
810
+ );
811
+
812
+ return checks.filter(Boolean);
813
+ }
756
814
 
757
815
  // Browse filesystem endpoint for project suggestions - uses existing getFileTree
758
816
  app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
@@ -760,9 +818,47 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
760
818
  const { path: dirPath } = req.query;
761
819
 
762
820
  console.log('[API] Browse filesystem request for path:', dirPath);
763
- console.log('[API] WORKSPACES_ROOT is:', WORKSPACES_ROOT);
821
+ console.log('[API] WORKSPACES_ROOTS are:', WORKSPACES_ROOTS.length > 0 ? WORKSPACES_ROOTS : '(unrestricted)');
822
+
823
+ if (process.platform === 'win32' && dirPath === WINDOWS_DRIVES_ROOT) {
824
+ const suggestions = await listWindowsDrives();
825
+ return res.json({
826
+ path: WINDOWS_DRIVES_ROOT,
827
+ displayPath: 'This PC',
828
+ suggestions,
829
+ platform: process.platform,
830
+ virtualRoot: 'windows-drives',
831
+ hasWorkspaceRootRestriction: HAS_WORKSPACES_ROOT_RESTRICTION,
832
+ allowedRoots: WORKSPACES_ROOTS
833
+ });
834
+ }
835
+
836
+ if (shouldUseVirtualWorkspaceRoots({ dirPath })) {
837
+ const suggestions = buildWorkspaceRootSuggestions({
838
+ allowedWorkspaceRoots: WORKSPACES_ROOTS,
839
+ homeDir: DEFAULT_WORKSPACES_ROOT,
840
+ platform: process.platform
841
+ }).filter((entry) => {
842
+ try {
843
+ return fs.existsSync(entry.path);
844
+ } catch (error) {
845
+ return false;
846
+ }
847
+ });
848
+
849
+ return res.json({
850
+ path: UNIX_WORKSPACE_ROOTS,
851
+ displayPath: 'Allowed workspace roots',
852
+ suggestions,
853
+ platform: process.platform,
854
+ virtualRoot: 'workspace-roots',
855
+ hasWorkspaceRootRestriction: HAS_WORKSPACES_ROOT_RESTRICTION,
856
+ allowedRoots: WORKSPACES_ROOTS
857
+ });
858
+ }
859
+
764
860
  // Default to home directory if no path provided
765
- const defaultRoot = WORKSPACES_ROOT;
861
+ const defaultRoot = DEFAULT_WORKSPACES_ROOT;
766
862
  let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
767
863
 
768
864
  // Resolve and normalize the path
@@ -826,7 +922,12 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
826
922
 
827
923
  res.json({
828
924
  path: resolvedPath,
829
- suggestions: suggestions
925
+ displayPath: resolvedPath,
926
+ suggestions: suggestions,
927
+ platform: process.platform,
928
+ virtualRoot: null,
929
+ hasWorkspaceRootRestriction: HAS_WORKSPACES_ROOT_RESTRICTION,
930
+ allowedRoots: WORKSPACES_ROOTS
830
931
  });
831
932
 
832
933
  } catch (error) {
@@ -4,15 +4,13 @@ import {
4
4
  getActiveAgentSessions,
5
5
  isAgentSessionActive
6
6
  } from './acp-runtime/index.js';
7
- import { CODEX_MODELS } from '../shared/modelConstants.js';
8
7
 
9
8
  export async function queryCodex(command, options = {}, writer) {
10
9
  return executeAgentPrompt({
11
10
  agentKey: 'codex',
12
11
  command,
13
12
  options: {
14
- ...options,
15
- model: options.model || CODEX_MODELS.DEFAULT
13
+ ...options
16
14
  },
17
15
  writer
18
16
  });
@@ -4,7 +4,6 @@ import {
4
4
  getActiveAgentSessions,
5
5
  isAgentSessionActive
6
6
  } from './acp-runtime/index.js';
7
- import { OPENCODE_MODELS } from '../shared/modelConstants.js';
8
7
  import { spawnCommand } from './utils/spawnCommand.js';
9
8
  import { resolveWorkingDirectory } from './utils/defaultWorkingDirectory.js';
10
9
 
@@ -84,8 +83,7 @@ export async function queryOpencode(command, options = {}, writer) {
84
83
  agentKey: 'opencode',
85
84
  command,
86
85
  options: {
87
- ...options,
88
- model: options.model || OPENCODE_MODELS.DEFAULT
86
+ ...options
89
87
  },
90
88
  writer
91
89
  });