@axhub/genie 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-CTKZtqB1.js +460 -0
- package/dist/assets/{ReviewApp-BEicSBzW.js → ReviewApp-DM6BNAzR.js} +1 -1
- package/dist/assets/{_basePickBy-DkiHsp3X.js → _basePickBy-CqJbRZ9y.js} +1 -1
- package/dist/assets/{_baseUniq-7ElXb2sX.js → _baseUniq-BS8YH8jO.js} +1 -1
- package/dist/assets/{arc-CEsS3MdK.js → arc-BBmKEN-S.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-BubZ7T3U.js → architectureDiagram-2XIMDMQ5-N5lcb82R.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-Cza6M6Ht.js → blockDiagram-WCTKOSBZ-DTMwHuLn.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-jhjtOQ12.js → c4Diagram-IC4MRINW-BTKlkXI9.js} +1 -1
- package/dist/assets/channel-1oJBvF-0.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB--HkodwbY.js → chunk-4BX2VUAB-DUdoTxAc.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CyBuez4e.js → chunk-55IACEB6-Bm_92xe4.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CuzG4iAl.js → chunk-FMBD7UC4-CGW0g62g.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-BNi8S861.js → chunk-JSJVCQXG-DYkTH3w1.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-D817O-GT.js → chunk-KX2RTZJC-C9oTlISU.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-DyujyOvx.js → chunk-NQ4KR5QH-CM50ygWP.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-VMEn-zxh.js → chunk-QZHKN3VN-7dzpYeNJ.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-CQHHFLvx.js → chunk-WL4C6EOR-Cm9nQrsr.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +1 -0
- package/dist/assets/clone-CinxIlEu.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-qykDd54p.js → cose-bilkent-S5V4N54A-Ccp_p0JZ.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-Bqp7DjEa.js → dagre-KLK3FWXG-fBwTLUp9.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-BKtx468K.js → diagram-E7M64L7V-CeNVmFUp.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2--fHfW6V2.js → diagram-IFDJBPK2-CtavyLGa.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-D1kQI5RB.js → diagram-P4PSJMXO-CpQTjQwc.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-DT9YzdNw.js → erDiagram-INFDFZHY-B8R5vwhd.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-DWeNr4yg.js → flowDiagram-PKNHOUZH-BvkVVwIQ.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK--IgwcUhI.js → ganttDiagram-A5KZAMGK-DOu3hSNa.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-B5a8UWjN.js → gitGraphDiagram-K3NZZRJ6-C7zT67YE.js} +1 -1
- package/dist/assets/{graph-Cw1rYoD9.js → graph-D11wiwHo.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-BCxJHuqY.js → highlighted-body-TPN3WLV5-Babpthg-.js} +1 -1
- package/dist/assets/index-DFxzgWoO.js +2 -0
- package/dist/assets/index-YCFGDVKw.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-D2u70rhN.js → infoDiagram-LFFYTUFH-BmA7IpQG.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-Cl8yrezU.js → ishikawaDiagram-PHBUUO56-BEquZd3E.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-ddP0AMU9.js → journeyDiagram-4ABVD52K-BfemGz7f.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-DbVt0v29.js → kanban-definition-K7BYSVSG-CWja3mln.js} +1 -1
- package/dist/assets/{layout-W_tRx4UV.js → layout-BLUNf-PJ.js} +1 -1
- package/dist/assets/{linear-CcMb2ay-.js → linear-DukIV_Xv.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-BBJqt8pT.js → mermaid-O7DHMXV3-SgtM28qI.js} +265 -215
- package/dist/assets/{mindmap-definition-YRQLILUH-BGhZa7Na.js → mindmap-definition-YRQLILUH-4UjqXITU.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-CDyJaACv.js → pieDiagram-SKSYHLDU-8AxqJd0M.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-BSYuqf0Q.js → quadrantDiagram-337W2JSQ-D60m8V8r.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-Cfi9YX9H.js → requirementDiagram-Z7DCOOCP-zqh9jBVf.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-Di1ShaMF.js → sankeyDiagram-WA2Y5GQK-CDZILTLI.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-CYTTG38e.js → sequenceDiagram-2WXFIKYE-7BReFd0L.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-CVZYMqyW.js → stateDiagram-RAJIS63D-HPTVdIG4.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-B1sdb5mK.js → timeline-definition-YZTLITO2-CTVllFgr.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-CGG4gx3C.js → treemap-KZPCXAKY-BtyxboJZ.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-Dds37L2k.js → vennDiagram-LZ73GAT5-D96ZI6Mg.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-C8QKSyRR.js → xychartDiagram-JWTSCODW-eRk-39YO.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +35 -33
- package/server/_legacy-providers/README.md +30 -0
- package/server/_legacy-providers/claude-sdk.js +956 -0
- package/server/_legacy-providers/gemini-cli.js +368 -0
- package/server/_legacy-providers/openai-codex.js +705 -0
- package/server/_legacy-providers/opencode-cli.js +674 -0
- package/server/acp-runtime/client.js +1872 -0
- package/server/acp-runtime/index.js +408 -0
- package/server/acp-runtime/registry.js +45 -0
- package/server/acp-runtime/session-store.js +254 -0
- package/server/channels/runtime/AgentRuntimeAdapter.js +22 -80
- package/server/claude-sdk.js +24 -946
- package/server/cli.js +140 -2
- package/server/external-agent/service.js +52 -63
- package/server/gemini-cli.js +21 -360
- package/server/index.js +133 -58
- package/server/openai-codex.js +19 -695
- package/server/opencode-cli.js +68 -640
- package/server/projects.js +128 -85
- package/server/routes/agent.js +2 -0
- package/server/routes/cc-connect.js +1131 -0
- package/server/routes/cli-auth.js +1 -73
- package/server/routes/commands.js +4 -9
- package/server/routes/git.js +3 -20
- package/server/routes/projects.js +45 -24
- package/server/routes/session-core.js +44 -10
- package/server/session-core/abortSession.js +2 -18
- package/server/session-core/eventStore.js +5 -1
- package/server/session-core/providerAdapters.js +98 -10
- package/server/session-core/providerDiscovery.js +8 -3
- package/server/session-core/runtimeState.js +16 -17
- package/server/session-core/runtimeWriter.js +19 -12
- package/server/utils/ccConnectManager.js +390 -0
- package/server/utils/ccConnectState.js +575 -0
- package/server/utils/resolveCommandPath.js +71 -0
- package/server/utils/workspaceRoots.js +154 -0
- package/shared/conversationEvents.js +347 -10
- package/dist/assets/App-CYTE30Cf.js +0 -484
- package/dist/assets/channel-RmqTALN0.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-wvVV1ggz.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-wvVV1ggz.js +0 -1
- package/dist/assets/clone-oT5aWXpf.js +0 -1
- package/dist/assets/index-CBuAXA5S.js +0 -2
- package/dist/assets/index-CyLWKyxy.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-Bbl0b4-i.js +0 -1
- package/server/cli.test.js +0 -76
- package/server/external-agent/service.test.js +0 -53
- package/server/external-agent/ws.test.js +0 -289
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { constants as fsConstants } from 'fs';
|
|
3
2
|
import fs from 'fs/promises';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import os from 'os';
|
|
6
5
|
import { listOpencodeModels } from '../opencode-cli.js';
|
|
6
|
+
import { resolveCommandPath } from '../utils/resolveCommandPath.js';
|
|
7
7
|
|
|
8
8
|
const router = express.Router();
|
|
9
9
|
const INSTALLATION_CACHE_TTL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -22,78 +22,6 @@ const PROVIDER_INSTALL_HINTS = {
|
|
|
22
22
|
const installationStatusCache = new Map(); // provider -> status
|
|
23
23
|
const installationStatusInFlight = new Map(); // provider -> Promise
|
|
24
24
|
|
|
25
|
-
function getLocatorCommand() {
|
|
26
|
-
return process.platform === 'win32' ? 'where' : 'which';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function isRunnableCommand(candidatePath) {
|
|
30
|
-
try {
|
|
31
|
-
if (process.platform === 'win32') {
|
|
32
|
-
await fs.access(candidatePath, fsConstants.F_OK);
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
await fs.access(candidatePath, fsConstants.X_OK);
|
|
37
|
-
return true;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function resolveCommandPath(command) {
|
|
44
|
-
const normalizedCommand = String(command || '').trim();
|
|
45
|
-
if (!normalizedCommand) {
|
|
46
|
-
return {
|
|
47
|
-
found: false,
|
|
48
|
-
resolvedPath: null,
|
|
49
|
-
reason: 'Command name is empty'
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const isDirectPath = normalizedCommand.includes(path.sep) || (process.platform === 'win32' && normalizedCommand.includes('/'));
|
|
54
|
-
const pathEntries = isDirectPath
|
|
55
|
-
? ['']
|
|
56
|
-
: String(process.env.PATH || '')
|
|
57
|
-
.split(path.delimiter)
|
|
58
|
-
.map((entry) => entry.trim())
|
|
59
|
-
.filter(Boolean);
|
|
60
|
-
|
|
61
|
-
const windowsExtensions = process.platform === 'win32'
|
|
62
|
-
? String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
|
|
63
|
-
.split(';')
|
|
64
|
-
.map((ext) => ext.trim())
|
|
65
|
-
.filter(Boolean)
|
|
66
|
-
: [''];
|
|
67
|
-
|
|
68
|
-
const candidatePaths = [];
|
|
69
|
-
for (const baseDir of pathEntries) {
|
|
70
|
-
const baseCandidate = isDirectPath ? normalizedCommand : path.join(baseDir, normalizedCommand);
|
|
71
|
-
candidatePaths.push(baseCandidate);
|
|
72
|
-
|
|
73
|
-
if (process.platform === 'win32' && !path.extname(baseCandidate)) {
|
|
74
|
-
for (const ext of windowsExtensions) {
|
|
75
|
-
candidatePaths.push(`${baseCandidate}${ext}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const candidatePath of candidatePaths) {
|
|
81
|
-
if (await isRunnableCommand(candidatePath)) {
|
|
82
|
-
return {
|
|
83
|
-
found: true,
|
|
84
|
-
resolvedPath: candidatePath,
|
|
85
|
-
reason: null
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
found: false,
|
|
92
|
-
resolvedPath: null,
|
|
93
|
-
reason: `${normalizedCommand} not found in PATH`
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
25
|
function buildInstallationStatus(provider, command, installed, resolvedPath, reason, cacheHit = false) {
|
|
98
26
|
return {
|
|
99
27
|
success: true,
|
|
@@ -199,13 +199,8 @@ Custom commands can be created in:
|
|
|
199
199
|
}, {});
|
|
200
200
|
|
|
201
201
|
const currentProvider = context?.provider || 'claude';
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
codex: CODEX_MODELS.DEFAULT,
|
|
205
|
-
gemini: GEMINI_MODELS.DEFAULT,
|
|
206
|
-
opencode: OPENCODE_MODELS.DEFAULT
|
|
207
|
-
};
|
|
208
|
-
const currentModel = context?.model || providerDefaults[currentProvider] || CLAUDE_MODELS.DEFAULT;
|
|
202
|
+
const discoveredCurrentModel = providerDiscovery.find((item) => item.provider === currentProvider)?.currentModel || null;
|
|
203
|
+
const currentModel = context?.model || discoveredCurrentModel || null;
|
|
209
204
|
const opencodeDiscovery = providerDiscovery.find((item) => item.provider === 'opencode') || null;
|
|
210
205
|
|
|
211
206
|
return {
|
|
@@ -220,7 +215,7 @@ Custom commands can be created in:
|
|
|
220
215
|
opencodeDiscovery,
|
|
221
216
|
message: args.length > 0
|
|
222
217
|
? `Switching to model: ${args[0]}`
|
|
223
|
-
: `Current model: ${currentModel}`
|
|
218
|
+
: (currentModel ? `Current model: ${currentModel}` : 'Current model is managed by the active provider configuration.')
|
|
224
219
|
}
|
|
225
220
|
};
|
|
226
221
|
},
|
|
@@ -254,7 +249,7 @@ Custom commands can be created in:
|
|
|
254
249
|
packageName,
|
|
255
250
|
uptime: uptimeFormatted,
|
|
256
251
|
uptimeSeconds: Math.floor(uptime),
|
|
257
|
-
model: context?.model ||
|
|
252
|
+
model: context?.model || null,
|
|
258
253
|
provider: context?.provider || 'claude',
|
|
259
254
|
nodeVersion: process.version,
|
|
260
255
|
platform: process.platform
|
package/server/routes/git.js
CHANGED
|
@@ -610,26 +610,9 @@ Generate the commit message:`;
|
|
|
610
610
|
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
611
611
|
console.log('🔍 Writer received message type:', parsed.type);
|
|
612
612
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (parsed.type === '
|
|
616
|
-
const message = parsed.data.message || parsed.data;
|
|
617
|
-
console.log('📦 Claude response message:', JSON.stringify(message, null, 2).substring(0, 500));
|
|
618
|
-
if (message.content && Array.isArray(message.content)) {
|
|
619
|
-
// Extract text from content array
|
|
620
|
-
for (const item of message.content) {
|
|
621
|
-
if (item.type === 'text' && item.text) {
|
|
622
|
-
console.log('✅ Extracted text chunk:', item.text.substring(0, 100));
|
|
623
|
-
responseText += item.text;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
else if (parsed.type === 'claude-response' && parsed.data?.type === 'content_block_delta' && parsed.data?.delta?.text) {
|
|
629
|
-
responseText += parsed.data.delta.text;
|
|
630
|
-
}
|
|
631
|
-
// Also handle direct text messages
|
|
632
|
-
else if (parsed.type === 'text' && parsed.text) {
|
|
613
|
+
if (parsed.type === 'conversation-event' && parsed.event?.kind === 'assistant_text_delta' && parsed.event?.payload?.text) {
|
|
614
|
+
responseText += parsed.event.payload.text;
|
|
615
|
+
} else if (parsed.type === 'text' && parsed.text) {
|
|
633
616
|
console.log('✅ Direct text:', parsed.text.substring(0, 100));
|
|
634
617
|
responseText += parsed.text;
|
|
635
618
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
4
|
import { addProjectManually } from '../projects.js';
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_WORKSPACES_ROOT,
|
|
7
|
+
HAS_WORKSPACES_ROOT_RESTRICTION,
|
|
8
|
+
WORKSPACES_ROOTS,
|
|
9
|
+
formatAllowedWorkspaceRoots,
|
|
10
|
+
isPathWithinAllowedRoots,
|
|
11
|
+
resolveAllowedWorkspaceRoots
|
|
12
|
+
} from '../utils/workspaceRoots.js';
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_WORKSPACES_ROOT,
|
|
16
|
+
HAS_WORKSPACES_ROOT_RESTRICTION,
|
|
17
|
+
WORKSPACES_ROOTS
|
|
18
|
+
} from '../utils/workspaceRoots.js';
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
|
|
20
|
+
const router = express.Router();
|
|
11
21
|
|
|
12
22
|
// System-critical paths that should never be used as workspace directories
|
|
13
23
|
export const FORBIDDEN_PATHS = [
|
|
@@ -42,8 +52,14 @@ export const FORBIDDEN_PATHS = [
|
|
|
42
52
|
* @param {string} requestedPath - The path to validate
|
|
43
53
|
* @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
|
|
44
54
|
*/
|
|
45
|
-
export async function validateWorkspacePath(requestedPath) {
|
|
55
|
+
export async function validateWorkspacePath(requestedPath, options = {}) {
|
|
46
56
|
try {
|
|
57
|
+
const {
|
|
58
|
+
fsImpl = fs,
|
|
59
|
+
hasWorkspaceRootRestriction = HAS_WORKSPACES_ROOT_RESTRICTION,
|
|
60
|
+
allowedWorkspaceRoots = WORKSPACES_ROOTS
|
|
61
|
+
} = options;
|
|
62
|
+
|
|
47
63
|
// Resolve to absolute path
|
|
48
64
|
let absolutePath = path.resolve(requestedPath);
|
|
49
65
|
|
|
@@ -79,14 +95,14 @@ export async function validateWorkspacePath(requestedPath) {
|
|
|
79
95
|
let realPath;
|
|
80
96
|
try {
|
|
81
97
|
// Check if path exists to resolve real path
|
|
82
|
-
await
|
|
83
|
-
realPath = await
|
|
98
|
+
await fsImpl.access(absolutePath);
|
|
99
|
+
realPath = await fsImpl.realpath(absolutePath);
|
|
84
100
|
} catch (error) {
|
|
85
101
|
if (error.code === 'ENOENT') {
|
|
86
102
|
// Path doesn't exist yet - check parent directory
|
|
87
103
|
let parentPath = path.dirname(absolutePath);
|
|
88
104
|
try {
|
|
89
|
-
const parentRealPath = await
|
|
105
|
+
const parentRealPath = await fsImpl.realpath(parentPath);
|
|
90
106
|
|
|
91
107
|
// Reconstruct the full path with real parent
|
|
92
108
|
realPath = path.join(parentRealPath, path.basename(absolutePath));
|
|
@@ -104,31 +120,36 @@ export async function validateWorkspacePath(requestedPath) {
|
|
|
104
120
|
}
|
|
105
121
|
}
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
if (hasWorkspaceRootRestriction && allowedWorkspaceRoots.length > 0) {
|
|
124
|
+
const resolvedWorkspaceRoots = await resolveAllowedWorkspaceRoots({
|
|
125
|
+
fsImpl,
|
|
126
|
+
allowedWorkspaceRoots
|
|
127
|
+
});
|
|
109
128
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
129
|
+
if (!isPathWithinAllowedRoots(realPath, resolvedWorkspaceRoots)) {
|
|
130
|
+
return {
|
|
131
|
+
valid: false,
|
|
132
|
+
error: `Workspace path must be within one of the allowed workspace roots: ${formatAllowedWorkspaceRoots(allowedWorkspaceRoots)}`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
// Additional symlink check for existing paths
|
|
120
138
|
try {
|
|
121
|
-
await
|
|
122
|
-
const stats = await
|
|
139
|
+
await fsImpl.access(absolutePath);
|
|
140
|
+
const stats = await fsImpl.lstat(absolutePath);
|
|
123
141
|
|
|
124
|
-
if (stats.isSymbolicLink()) {
|
|
142
|
+
if (stats.isSymbolicLink() && hasWorkspaceRootRestriction && allowedWorkspaceRoots.length > 0) {
|
|
143
|
+
const resolvedWorkspaceRoots = await resolveAllowedWorkspaceRoots({
|
|
144
|
+
fsImpl,
|
|
145
|
+
allowedWorkspaceRoots
|
|
146
|
+
});
|
|
125
147
|
// Verify symlink target is also within allowed root
|
|
126
|
-
const linkTarget = await
|
|
148
|
+
const linkTarget = await fsImpl.readlink(absolutePath);
|
|
127
149
|
const resolvedTarget = path.resolve(path.dirname(absolutePath), linkTarget);
|
|
128
|
-
const realTarget = await
|
|
150
|
+
const realTarget = await fsImpl.realpath(resolvedTarget);
|
|
129
151
|
|
|
130
|
-
if (!realTarget
|
|
131
|
-
realTarget !== resolvedWorkspaceRoot) {
|
|
152
|
+
if (!isPathWithinAllowedRoots(realTarget, resolvedWorkspaceRoots)) {
|
|
132
153
|
return {
|
|
133
154
|
valid: false,
|
|
134
155
|
error: 'Symlink target is outside the allowed workspace root'
|
|
@@ -2,10 +2,17 @@ import express from 'express';
|
|
|
2
2
|
import { getProjects } from '../projects.js';
|
|
3
3
|
import { discoverAllProviders, discoverProvider } from '../session-core/providerDiscovery.js';
|
|
4
4
|
import { getProviderAdapter } from '../session-core/providerAdapters.js';
|
|
5
|
+
import { listAcpSessions } from '../acp-runtime/session-store.js';
|
|
5
6
|
|
|
6
7
|
const router = express.Router();
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
router.use((req, res, next) => {
|
|
10
|
+
res.setHeader('X-Runtime-Engine', 'acp');
|
|
11
|
+
next();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
async function flattenProjectSessions(project) {
|
|
15
|
+
const projectPath = project.fullPath || project.path || null;
|
|
9
16
|
const groups = [
|
|
10
17
|
{ provider: 'claude', items: project.sessions || [] },
|
|
11
18
|
{ provider: 'codex', items: project.codexSessions || [] },
|
|
@@ -13,7 +20,12 @@ function flattenProjectSessions(project) {
|
|
|
13
20
|
{ provider: 'opencode', items: project.opencodeSessions || [] }
|
|
14
21
|
];
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
const acpSessions = await listAcpSessions({ projectPath });
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
...groups.flatMap(({ provider, items }) => items.map((item) => ({ ...item, provider, __provider: provider, source: item?.source || 'legacy' }))),
|
|
27
|
+
...acpSessions.map((item) => ({ ...item, provider: item.provider, __provider: item.provider, source: 'acp' }))
|
|
28
|
+
]
|
|
17
29
|
.sort((a, b) => new Date(b.lastActivity || b.updated_at || b.createdAt || 0) - new Date(a.lastActivity || a.updated_at || a.createdAt || 0));
|
|
18
30
|
}
|
|
19
31
|
|
|
@@ -42,7 +54,15 @@ router.get('/projects/:projectName/history-index', async (req, res) => {
|
|
|
42
54
|
if (!project) {
|
|
43
55
|
return res.status(404).json({ success: false, error: 'Project not found' });
|
|
44
56
|
}
|
|
45
|
-
res.json({
|
|
57
|
+
res.json({
|
|
58
|
+
success: true,
|
|
59
|
+
project: {
|
|
60
|
+
name: project.name,
|
|
61
|
+
fullPath: project.fullPath || project.path,
|
|
62
|
+
displayName: project.displayName || project.name
|
|
63
|
+
},
|
|
64
|
+
sessions: await flattenProjectSessions(project)
|
|
65
|
+
});
|
|
46
66
|
} catch (error) {
|
|
47
67
|
res.status(500).json({ success: false, error: error.message });
|
|
48
68
|
}
|
|
@@ -66,19 +86,26 @@ router.get('/sessions/:sessionId/resolve', async (req, res) => {
|
|
|
66
86
|
'opencode'
|
|
67
87
|
].filter((provider, index, all) => provider && all.indexOf(provider) === index);
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
let directProjectMatch = null;
|
|
90
|
+
let directMatchSessions = [];
|
|
91
|
+
|
|
92
|
+
for (const project of projects) {
|
|
93
|
+
const flattened = await flattenProjectSessions(project);
|
|
94
|
+
if (flattened.some((session) => session.id === requestedSessionId)) {
|
|
95
|
+
directProjectMatch = project;
|
|
96
|
+
directMatchSessions = flattened;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
73
100
|
|
|
74
101
|
if (directProjectMatch) {
|
|
75
|
-
const
|
|
76
|
-
const matchedSession = flattened.find((session) => session.id === requestedSessionId);
|
|
102
|
+
const matchedSession = directMatchSessions.find((session) => session.id === requestedSessionId);
|
|
77
103
|
|
|
78
104
|
return res.json({
|
|
79
105
|
success: true,
|
|
80
106
|
found: true,
|
|
81
107
|
provider: matchedSession.provider,
|
|
108
|
+
source: matchedSession.source || 'legacy',
|
|
82
109
|
session: matchedSession,
|
|
83
110
|
project: {
|
|
84
111
|
name: directProjectMatch.name,
|
|
@@ -97,7 +124,12 @@ router.get('/sessions/:sessionId/resolve', async (req, res) => {
|
|
|
97
124
|
|
|
98
125
|
try {
|
|
99
126
|
if (provider === 'claude') {
|
|
100
|
-
const result = await adapter.listSessions({
|
|
127
|
+
const result = await adapter.listSessions({
|
|
128
|
+
projectName: project.name,
|
|
129
|
+
projectPath: project.fullPath || project.path,
|
|
130
|
+
limit: 1000,
|
|
131
|
+
offset: 0
|
|
132
|
+
});
|
|
101
133
|
sessions = Array.isArray(result) ? result : [];
|
|
102
134
|
} else {
|
|
103
135
|
sessions = await adapter.listSessions({ projectPath: project.fullPath || project.path, limit: 0 });
|
|
@@ -115,6 +147,7 @@ router.get('/sessions/:sessionId/resolve', async (req, res) => {
|
|
|
115
147
|
success: true,
|
|
116
148
|
found: true,
|
|
117
149
|
provider,
|
|
150
|
+
source: matchedSession.source || 'legacy',
|
|
118
151
|
session: matchedSession,
|
|
119
152
|
project: {
|
|
120
153
|
name: project.name,
|
|
@@ -157,6 +190,7 @@ router.get('/sessions/:provider/:sessionId/events', async (req, res) => {
|
|
|
157
190
|
success: true,
|
|
158
191
|
provider: req.params.provider,
|
|
159
192
|
sessionId: req.params.sessionId,
|
|
193
|
+
source: result?.source || 'legacy',
|
|
160
194
|
events,
|
|
161
195
|
total,
|
|
162
196
|
hasMore,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { abortCodexSession } from '../openai-codex.js';
|
|
3
|
-
import { abortGeminiSession } from '../gemini-cli.js';
|
|
4
|
-
import { abortOpencodeSession } from '../opencode-cli.js';
|
|
1
|
+
import { abortAgentSession as abortAcpAgentSession } from '../acp-runtime/index.js';
|
|
5
2
|
|
|
6
3
|
export const ABORTABLE_AGENT_PROVIDERS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
7
4
|
|
|
@@ -11,20 +8,7 @@ export function isAbortableAgentProvider(provider) {
|
|
|
11
8
|
|
|
12
9
|
export async function abortAgentSession(provider, sessionId) {
|
|
13
10
|
const normalizedProvider = String(provider || 'claude').trim().toLowerCase();
|
|
14
|
-
|
|
15
|
-
if (normalizedProvider === 'codex') {
|
|
16
|
-
return abortCodexSession(sessionId);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (normalizedProvider === 'gemini') {
|
|
20
|
-
return abortGeminiSession(sessionId);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (normalizedProvider === 'opencode') {
|
|
24
|
-
return abortOpencodeSession(sessionId);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return abortClaudeSDKSession(sessionId);
|
|
11
|
+
return abortAcpAgentSession(normalizedProvider, sessionId);
|
|
28
12
|
}
|
|
29
13
|
|
|
30
14
|
export async function abortAgentSessionWithWriter({ provider = 'claude', sessionId, writer }) {
|
|
@@ -24,7 +24,11 @@ function normalizePersistedEvents(events = []) {
|
|
|
24
24
|
return events.filter((event) => (
|
|
25
25
|
isConversationEvent(event) &&
|
|
26
26
|
event.sessionId &&
|
|
27
|
-
|
|
27
|
+
(
|
|
28
|
+
event.extensions?.runtimeSource === 'acp' ||
|
|
29
|
+
event.rawRef?.runtime === 'acp' ||
|
|
30
|
+
PERSISTED_EVENT_KINDS.has(event.kind)
|
|
31
|
+
)
|
|
28
32
|
));
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
getOpencodeSessions,
|
|
11
11
|
getGeminiSessions
|
|
12
12
|
} from '../projects.js';
|
|
13
|
+
import {
|
|
14
|
+
findAcpSessionRecord,
|
|
15
|
+
listAcpSessions
|
|
16
|
+
} from '../acp-runtime/session-store.js';
|
|
13
17
|
|
|
14
18
|
async function flattenLegacyMessages(result) {
|
|
15
19
|
if (Array.isArray(result)) return result;
|
|
@@ -28,47 +32,131 @@ async function normalizeLegacyLoadResult(result, provider, sessionId) {
|
|
|
28
32
|
|
|
29
33
|
return {
|
|
30
34
|
...result,
|
|
31
|
-
events
|
|
35
|
+
events,
|
|
36
|
+
source: 'legacy'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mergeSessionLists(legacySessions = [], acpSessions = []) {
|
|
41
|
+
const merged = new Map();
|
|
42
|
+
|
|
43
|
+
legacySessions.forEach((session) => {
|
|
44
|
+
if (session?.id) {
|
|
45
|
+
merged.set(session.id, session);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
acpSessions.forEach((session) => {
|
|
50
|
+
if (session?.id) {
|
|
51
|
+
merged.set(session.id, session);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return Array.from(merged.values()).sort((left, right) => {
|
|
56
|
+
const leftTime = new Date(left?.lastActivity || left?.updatedAt || left?.createdAt || 0).getTime();
|
|
57
|
+
const rightTime = new Date(right?.lastActivity || right?.updatedAt || right?.createdAt || 0).getTime();
|
|
58
|
+
return rightTime - leftTime;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function loadAcpEvents(provider, sessionId) {
|
|
63
|
+
const record = await findAcpSessionRecord(sessionId, provider);
|
|
64
|
+
if (!record) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const events = await readMirroredConversationEvents(provider, sessionId);
|
|
69
|
+
return {
|
|
70
|
+
events,
|
|
71
|
+
total: events.length,
|
|
72
|
+
hasMore: false,
|
|
73
|
+
offset: 0,
|
|
74
|
+
limit: null,
|
|
75
|
+
source: 'acp'
|
|
32
76
|
};
|
|
33
77
|
}
|
|
34
78
|
|
|
35
79
|
const PROVIDER_ADAPTERS = {
|
|
36
80
|
claude: {
|
|
37
|
-
async listSessions({ projectName, limit = 50, offset = 0 }) {
|
|
38
|
-
const result = await
|
|
39
|
-
|
|
81
|
+
async listSessions({ projectName, projectPath, limit = 50, offset = 0 }) {
|
|
82
|
+
const [result, acpSessions] = await Promise.all([
|
|
83
|
+
getSessions(projectName, limit, offset),
|
|
84
|
+
listAcpSessions({ provider: 'claude', projectPath: projectPath || null })
|
|
85
|
+
]);
|
|
86
|
+
return mergeSessionLists(
|
|
87
|
+
(result?.sessions || []).map((session) => ({ ...session, provider: 'claude', source: 'legacy' })),
|
|
88
|
+
acpSessions
|
|
89
|
+
);
|
|
40
90
|
},
|
|
41
91
|
async loadEvents({ projectName, sessionId, limit = null, offset = 0 }) {
|
|
92
|
+
const acpResult = await loadAcpEvents('claude', sessionId);
|
|
93
|
+
if (acpResult) {
|
|
94
|
+
return acpResult;
|
|
95
|
+
}
|
|
96
|
+
|
|
42
97
|
const rawMessages = await getSessionMessages(projectName, sessionId, limit, offset);
|
|
43
98
|
return normalizeLegacyLoadResult(rawMessages, 'claude', sessionId);
|
|
44
99
|
}
|
|
45
100
|
},
|
|
46
101
|
codex: {
|
|
47
102
|
async listSessions({ projectPath, limit = 50 }) {
|
|
48
|
-
const sessions = await
|
|
49
|
-
|
|
103
|
+
const [sessions, acpSessions] = await Promise.all([
|
|
104
|
+
getCodexSessions(projectPath, { limit }),
|
|
105
|
+
listAcpSessions({ provider: 'codex', projectPath: projectPath || null })
|
|
106
|
+
]);
|
|
107
|
+
return mergeSessionLists(
|
|
108
|
+
sessions.map((session) => ({ ...session, provider: 'codex', source: 'legacy' })),
|
|
109
|
+
acpSessions
|
|
110
|
+
);
|
|
50
111
|
},
|
|
51
112
|
async loadEvents({ sessionId, limit = null, offset = 0 }) {
|
|
113
|
+
const acpResult = await loadAcpEvents('codex', sessionId);
|
|
114
|
+
if (acpResult) {
|
|
115
|
+
return acpResult;
|
|
116
|
+
}
|
|
117
|
+
|
|
52
118
|
const rawMessages = await getCodexSessionMessages(sessionId, limit, offset);
|
|
53
119
|
return normalizeLegacyLoadResult(rawMessages, 'codex', sessionId);
|
|
54
120
|
}
|
|
55
121
|
},
|
|
56
122
|
gemini: {
|
|
57
123
|
async listSessions({ projectPath, limit = 50 }) {
|
|
58
|
-
const sessions = await
|
|
59
|
-
|
|
124
|
+
const [sessions, acpSessions] = await Promise.all([
|
|
125
|
+
getGeminiSessions(projectPath, { limit }),
|
|
126
|
+
listAcpSessions({ provider: 'gemini', projectPath: projectPath || null })
|
|
127
|
+
]);
|
|
128
|
+
return mergeSessionLists(
|
|
129
|
+
sessions.map((session) => ({ ...session, provider: 'gemini', source: 'legacy' })),
|
|
130
|
+
acpSessions
|
|
131
|
+
);
|
|
60
132
|
},
|
|
61
133
|
async loadEvents({ sessionId, limit = null, offset = 0 }) {
|
|
134
|
+
const acpResult = await loadAcpEvents('gemini', sessionId);
|
|
135
|
+
if (acpResult) {
|
|
136
|
+
return acpResult;
|
|
137
|
+
}
|
|
138
|
+
|
|
62
139
|
const rawMessages = await getGeminiSessionMessages(sessionId, limit, offset);
|
|
63
140
|
return normalizeLegacyLoadResult(rawMessages, 'gemini', sessionId);
|
|
64
141
|
}
|
|
65
142
|
},
|
|
66
143
|
opencode: {
|
|
67
144
|
async listSessions({ projectPath, limit = 50 }) {
|
|
68
|
-
const sessions = await
|
|
69
|
-
|
|
145
|
+
const [sessions, acpSessions] = await Promise.all([
|
|
146
|
+
getOpencodeSessions(projectPath, { limit }),
|
|
147
|
+
listAcpSessions({ provider: 'opencode', projectPath: projectPath || null })
|
|
148
|
+
]);
|
|
149
|
+
return mergeSessionLists(
|
|
150
|
+
sessions.map((session) => ({ ...session, provider: 'opencode', source: 'legacy' })),
|
|
151
|
+
acpSessions
|
|
152
|
+
);
|
|
70
153
|
},
|
|
71
154
|
async loadEvents({ sessionId, limit = null, offset = 0 }) {
|
|
155
|
+
const acpResult = await loadAcpEvents('opencode', sessionId);
|
|
156
|
+
if (acpResult) {
|
|
157
|
+
return acpResult;
|
|
158
|
+
}
|
|
159
|
+
|
|
72
160
|
const rawMessages = await getOpencodeSessionMessages(sessionId, limit, offset);
|
|
73
161
|
return normalizeLegacyLoadResult(rawMessages, 'opencode', sessionId);
|
|
74
162
|
}
|
|
@@ -151,7 +151,12 @@ async function discoverOpenCodeModels(projectPath) {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
async function discoverClaudeModels() {
|
|
154
|
-
return {
|
|
154
|
+
return {
|
|
155
|
+
models: mapModelOptions(CLAUDE_MODELS.OPTIONS, 'runtime-fallback'),
|
|
156
|
+
source: 'runtime-fallback',
|
|
157
|
+
error: null,
|
|
158
|
+
currentModel: null
|
|
159
|
+
};
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
async function discoverCodexModels() {
|
|
@@ -163,11 +168,11 @@ async function discoverCodexModels() {
|
|
|
163
168
|
if (match) currentModel = match[1];
|
|
164
169
|
} catch {
|
|
165
170
|
}
|
|
166
|
-
return { models: mapModelOptions(CODEX_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: currentModel ||
|
|
171
|
+
return { models: mapModelOptions(CODEX_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: currentModel || null };
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
async function discoverGeminiModels() {
|
|
170
|
-
return { models: mapModelOptions(GEMINI_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel:
|
|
175
|
+
return { models: mapModelOptions(GEMINI_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: null };
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
const AUTH_CHECKERS = {
|
|
@@ -7,10 +7,8 @@ import {
|
|
|
7
7
|
} from './eventStore.js';
|
|
8
8
|
import { getProviderAdapter } from './providerAdapters.js';
|
|
9
9
|
import { getProjects } from '../projects.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { isGeminiSessionActive } from '../gemini-cli.js';
|
|
13
|
-
import { isOpencodeSessionActive } from '../opencode-cli.js';
|
|
10
|
+
import { findAcpSessionRecord } from '../acp-runtime/session-store.js';
|
|
11
|
+
import { isAgentSessionActive } from '../acp-runtime/index.js';
|
|
14
12
|
|
|
15
13
|
export const AGENT_RUNTIME_PHASES = {
|
|
16
14
|
IDLE: 'idle',
|
|
@@ -86,6 +84,18 @@ async function resolveSessionProjectContext(provider, sessionId) {
|
|
|
86
84
|
return null;
|
|
87
85
|
}
|
|
88
86
|
|
|
87
|
+
const acpRecord = await findAcpSessionRecord(normalizedSessionId, normalizedProvider);
|
|
88
|
+
if (acpRecord) {
|
|
89
|
+
return {
|
|
90
|
+
projectName: null,
|
|
91
|
+
projectPath: acpRecord.projectPath || null,
|
|
92
|
+
session: {
|
|
93
|
+
id: normalizedSessionId,
|
|
94
|
+
source: 'acp'
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
const projects = await getProjects();
|
|
90
100
|
|
|
91
101
|
for (const project of projects) {
|
|
@@ -149,19 +159,7 @@ function isSessionActive(provider, sessionId) {
|
|
|
149
159
|
return false;
|
|
150
160
|
}
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
return Boolean(isCodexSessionActive(normalizedSessionId));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (normalizedProvider === 'gemini') {
|
|
157
|
-
return Boolean(isGeminiSessionActive(normalizedSessionId));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (normalizedProvider === 'opencode') {
|
|
161
|
-
return Boolean(isOpencodeSessionActive(normalizedSessionId));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return Boolean(isClaudeSDKSessionActive(normalizedSessionId));
|
|
162
|
+
return Boolean(isAgentSessionActive(normalizedProvider, normalizedSessionId));
|
|
165
163
|
}
|
|
166
164
|
|
|
167
165
|
function inferPhaseFromEvents(events = []) {
|
|
@@ -204,6 +202,7 @@ function inferPhaseFromEvents(events = []) {
|
|
|
204
202
|
event.kind === CONVERSATION_EVENT_KINDS.TOOL_CALL_INPUT ||
|
|
205
203
|
event.kind === CONVERSATION_EVENT_KINDS.TOOL_CALL_END ||
|
|
206
204
|
event.kind === CONVERSATION_EVENT_KINDS.TOOL_RESULT ||
|
|
205
|
+
event.kind === CONVERSATION_EVENT_KINDS.PLAN_UPDATE ||
|
|
207
206
|
event.kind === CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE
|
|
208
207
|
) {
|
|
209
208
|
inferredPhase = AGENT_RUNTIME_PHASES.STREAMING;
|