@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
package/server/gemini-cli.js
CHANGED
|
@@ -1,368 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
payload.forEach(item => {
|
|
17
|
-
chunks.push(...collectTextChunks(item));
|
|
18
|
-
});
|
|
19
|
-
return chunks;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof payload !== 'object') {
|
|
23
|
-
return chunks;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const directKeys = ['text', 'response', 'content', 'message', 'delta'];
|
|
27
|
-
for (const key of directKeys) {
|
|
28
|
-
if (payload[key] !== undefined) {
|
|
29
|
-
chunks.push(...collectTextChunks(payload[key]));
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (Array.isArray(payload.parts)) {
|
|
34
|
-
payload.parts.forEach(part => {
|
|
35
|
-
chunks.push(...collectTextChunks(part));
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (payload.data && typeof payload.data === 'object') {
|
|
40
|
-
chunks.push(...collectTextChunks(payload.data));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return chunks;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getEventRole(event) {
|
|
47
|
-
return (
|
|
48
|
-
event?.role ||
|
|
49
|
-
event?.author ||
|
|
50
|
-
event?.sender ||
|
|
51
|
-
event?.message?.role ||
|
|
52
|
-
event?.data?.role ||
|
|
53
|
-
event?.content?.role ||
|
|
54
|
-
null
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function extractAssistantTextChunks(event, command) {
|
|
59
|
-
const role = String(getEventRole(event) || '').toLowerCase();
|
|
60
|
-
if (role === 'user') return [];
|
|
61
|
-
|
|
62
|
-
const eventType = String(event?.type || event?.event || event?.kind || '').toLowerCase();
|
|
63
|
-
const hasAssistantPayload = !!(event?.response || event?.candidates || event?.delta || event?.text || event?.content || event?.message);
|
|
64
|
-
if (eventType.includes('prompt') && !hasAssistantPayload) {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const normalizedPrompt = String(command || '').trim();
|
|
69
|
-
return collectTextChunks(event).filter((text) => {
|
|
70
|
-
const trimmed = String(text || '').trim();
|
|
71
|
-
if (!trimmed) return false;
|
|
72
|
-
if (normalizedPrompt && trimmed === normalizedPrompt) return false;
|
|
73
|
-
return true;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function parseGeminiJsonLine(line) {
|
|
78
|
-
const trimmed = line.trim();
|
|
79
|
-
if (!trimmed) return null;
|
|
80
|
-
|
|
81
|
-
const payload = trimmed.startsWith('data:') ? trimmed.slice(5).trim() : trimmed;
|
|
82
|
-
if (!payload) return null;
|
|
83
|
-
|
|
84
|
-
return JSON.parse(payload);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function emitTextChunks(ws, sessionId, chunks) {
|
|
88
|
-
chunks.forEach((text) => {
|
|
89
|
-
if (!text) return;
|
|
90
|
-
ws.send({
|
|
91
|
-
type: 'claude-response',
|
|
92
|
-
data: {
|
|
93
|
-
type: 'content_block_delta',
|
|
94
|
-
delta: {
|
|
95
|
-
type: 'text_delta',
|
|
96
|
-
text
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
provider: 'gemini',
|
|
100
|
-
sessionId
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function formatGeminiProcessError(code, stderrOutput) {
|
|
106
|
-
const normalizedStderr = String(stderrOutput || '').trim();
|
|
107
|
-
if (normalizedStderr) {
|
|
108
|
-
const apiMessageMatch =
|
|
109
|
-
normalizedStderr.match(/"message":"([^"]+)"/) ||
|
|
110
|
-
normalizedStderr.match(/message:\s*"([^"]+)"/i) ||
|
|
111
|
-
normalizedStderr.match(/ApiError:\s*\{.*?"message":"([^"]+)"/i) ||
|
|
112
|
-
normalizedStderr.match(/Error when talking to Gemini API[\s\S]*?message":"([^"]+)"/i);
|
|
113
|
-
|
|
114
|
-
if (apiMessageMatch?.[1]) {
|
|
115
|
-
return apiMessageMatch[1];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const firstMeaningfulLine = normalizedStderr
|
|
119
|
-
.split('\n')
|
|
120
|
-
.map((line) => line.trim())
|
|
121
|
-
.find((line) => line && !line.startsWith('at '));
|
|
122
|
-
|
|
123
|
-
if (firstMeaningfulLine) {
|
|
124
|
-
return firstMeaningfulLine;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return normalizedStderr;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return `Gemini CLI exited with code ${code}`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function extractGeminiEventError(event) {
|
|
134
|
-
if (!event || typeof event !== 'object') {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const candidates = [
|
|
139
|
-
event?.error?.message,
|
|
140
|
-
event?.error?.details,
|
|
141
|
-
event?.result?.error?.message,
|
|
142
|
-
event?.result?.error,
|
|
143
|
-
event?.data?.error?.message,
|
|
144
|
-
event?.data?.error,
|
|
145
|
-
event?.message
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
for (const candidate of candidates) {
|
|
149
|
-
const normalized = String(candidate || '').trim();
|
|
150
|
-
if (normalized) {
|
|
151
|
-
return normalized;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function queryGemini(command, options = {}, ws) {
|
|
159
|
-
return new Promise((resolve, reject) => {
|
|
160
|
-
const { sessionId, cwd, projectPath, resume, model, permissionMode } = options;
|
|
161
|
-
let capturedSessionId = sessionId;
|
|
162
|
-
let sentSessionCreated = false;
|
|
163
|
-
let stderrBuffer = '';
|
|
164
|
-
let fatalErrorSent = false;
|
|
165
|
-
let sawSuccessfulTerminalEvent = false;
|
|
166
|
-
let sawErroredTerminalEvent = false;
|
|
167
|
-
|
|
168
|
-
const args = ['-y', '@google/gemini-cli'];
|
|
169
|
-
|
|
170
|
-
if (sessionId && (resume || !command || !command.trim())) {
|
|
171
|
-
args.push('--resume', sessionId);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (command && command.trim()) {
|
|
175
|
-
args.push('--prompt', command);
|
|
176
|
-
args.push('--output-format', 'stream-json');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (model) {
|
|
180
|
-
args.push('--model', model);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits') {
|
|
184
|
-
args.push('--yolo');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const workingDir = resolveWorkingDirectory({ cwd, projectPath });
|
|
188
|
-
|
|
189
|
-
const geminiProcess = spawnFunction('npx', args, {
|
|
190
|
-
cwd: workingDir,
|
|
191
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
192
|
-
env: {
|
|
193
|
-
...process.env,
|
|
194
|
-
GEMINI_NONINTERACTIVE: '1'
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const processKey = capturedSessionId || Date.now().toString();
|
|
199
|
-
let processRegistryKey = processKey;
|
|
200
|
-
activeGeminiProcesses.set(processRegistryKey, geminiProcess);
|
|
201
|
-
|
|
202
|
-
const finalizeSessionId = () => capturedSessionId || sessionId || null;
|
|
203
|
-
const emitFatalError = (errorMessage) => {
|
|
204
|
-
if (fatalErrorSent || !errorMessage) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
fatalErrorSent = true;
|
|
209
|
-
ws.send({
|
|
210
|
-
type: 'claude-error',
|
|
211
|
-
error: errorMessage,
|
|
212
|
-
provider: 'gemini',
|
|
213
|
-
sessionId: finalizeSessionId()
|
|
214
|
-
});
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const handleJsonEvent = (event) => {
|
|
218
|
-
const incomingSessionId = event?.session_id || event?.sessionId || event?.data?.session_id || event?.data?.sessionId;
|
|
219
|
-
if (incomingSessionId && !capturedSessionId) {
|
|
220
|
-
capturedSessionId = incomingSessionId;
|
|
221
|
-
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
222
|
-
ws.setSessionId(capturedSessionId);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (processRegistryKey !== capturedSessionId) {
|
|
226
|
-
activeGeminiProcesses.delete(processRegistryKey);
|
|
227
|
-
activeGeminiProcesses.set(capturedSessionId, geminiProcess);
|
|
228
|
-
processRegistryKey = capturedSessionId;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!sessionId && !sentSessionCreated) {
|
|
232
|
-
sentSessionCreated = true;
|
|
233
|
-
ws.send({
|
|
234
|
-
type: 'session-created',
|
|
235
|
-
sessionId: capturedSessionId,
|
|
236
|
-
provider: 'gemini'
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const type = event?.type || event?.event || event?.kind;
|
|
242
|
-
if (type === 'result' || type === 'done' || type === 'complete') {
|
|
243
|
-
const eventStatus = String(event?.status || event?.result?.status || '').trim().toLowerCase();
|
|
244
|
-
const eventError = extractGeminiEventError(event);
|
|
245
|
-
if (eventStatus === 'error' || eventError) {
|
|
246
|
-
sawErroredTerminalEvent = true;
|
|
247
|
-
emitFatalError(eventError || 'Gemini CLI request failed');
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
sawSuccessfulTerminalEvent = true;
|
|
252
|
-
ws.send({
|
|
253
|
-
type: 'claude-response',
|
|
254
|
-
data: { type: 'content_block_stop' },
|
|
255
|
-
provider: 'gemini',
|
|
256
|
-
sessionId: finalizeSessionId()
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
ws.send({
|
|
260
|
-
type: 'gemini-result',
|
|
261
|
-
data: event,
|
|
262
|
-
sessionId: finalizeSessionId()
|
|
263
|
-
});
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const textChunks = extractAssistantTextChunks(event, command);
|
|
268
|
-
emitTextChunks(ws, finalizeSessionId(), textChunks);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
let stdoutBuffer = '';
|
|
272
|
-
geminiProcess.stdout.on('data', (data) => {
|
|
273
|
-
stdoutBuffer += data.toString();
|
|
274
|
-
const lines = stdoutBuffer.split('\n');
|
|
275
|
-
stdoutBuffer = lines.pop() || '';
|
|
276
|
-
|
|
277
|
-
for (const line of lines) {
|
|
278
|
-
try {
|
|
279
|
-
const event = parseGeminiJsonLine(line);
|
|
280
|
-
if (!event) continue;
|
|
281
|
-
handleJsonEvent(event);
|
|
282
|
-
} catch {}
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
geminiProcess.stderr.on('data', (data) => {
|
|
287
|
-
const chunk = data.toString();
|
|
288
|
-
stderrBuffer += chunk;
|
|
289
|
-
|
|
290
|
-
const normalizedChunk = chunk.trim();
|
|
291
|
-
if (normalizedChunk) {
|
|
292
|
-
console.warn('Gemini CLI stderr:', normalizedChunk);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
geminiProcess.on('close', (code) => {
|
|
297
|
-
if (stdoutBuffer.trim()) {
|
|
298
|
-
try {
|
|
299
|
-
const finalEvent = parseGeminiJsonLine(stdoutBuffer);
|
|
300
|
-
if (finalEvent) {
|
|
301
|
-
handleJsonEvent(finalEvent);
|
|
302
|
-
}
|
|
303
|
-
} catch {}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
activeGeminiProcesses.delete(processRegistryKey);
|
|
307
|
-
|
|
308
|
-
if (code === 0 && !sawErroredTerminalEvent) {
|
|
309
|
-
if (!sawSuccessfulTerminalEvent) {
|
|
310
|
-
ws.send({
|
|
311
|
-
type: 'claude-response',
|
|
312
|
-
data: { type: 'content_block_stop' },
|
|
313
|
-
provider: 'gemini',
|
|
314
|
-
sessionId: finalizeSessionId()
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
ws.send({
|
|
319
|
-
type: 'claude-complete',
|
|
320
|
-
sessionId: finalizeSessionId(),
|
|
321
|
-
provider: 'gemini',
|
|
322
|
-
exitCode: code,
|
|
323
|
-
isNewSession: !sessionId && !!command
|
|
324
|
-
});
|
|
325
|
-
resolve();
|
|
326
|
-
} else {
|
|
327
|
-
const errorMessage = formatGeminiProcessError(code, stderrBuffer);
|
|
328
|
-
emitFatalError(errorMessage);
|
|
329
|
-
reject(new Error(errorMessage));
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
geminiProcess.on('error', (error) => {
|
|
334
|
-
activeGeminiProcesses.delete(processRegistryKey);
|
|
335
|
-
|
|
336
|
-
emitFatalError(error.message);
|
|
337
|
-
|
|
338
|
-
reject(error);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
geminiProcess.stdin.end();
|
|
1
|
+
import {
|
|
2
|
+
abortAgentSession,
|
|
3
|
+
executeAgentPrompt,
|
|
4
|
+
getActiveAgentSessions,
|
|
5
|
+
isAgentSessionActive
|
|
6
|
+
} from './acp-runtime/index.js';
|
|
7
|
+
|
|
8
|
+
export async function queryGemini(command, options = {}, writer) {
|
|
9
|
+
return executeAgentPrompt({
|
|
10
|
+
agentKey: 'gemini',
|
|
11
|
+
command,
|
|
12
|
+
options: {
|
|
13
|
+
...options
|
|
14
|
+
},
|
|
15
|
+
writer
|
|
342
16
|
});
|
|
343
17
|
}
|
|
344
18
|
|
|
345
|
-
function abortGeminiSession(sessionId) {
|
|
346
|
-
|
|
347
|
-
if (process) {
|
|
348
|
-
process.kill('SIGTERM');
|
|
349
|
-
activeGeminiProcesses.delete(sessionId);
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
return false;
|
|
19
|
+
export async function abortGeminiSession(sessionId) {
|
|
20
|
+
return abortAgentSession('gemini', sessionId);
|
|
353
21
|
}
|
|
354
22
|
|
|
355
|
-
function isGeminiSessionActive(sessionId) {
|
|
356
|
-
return
|
|
23
|
+
export function isGeminiSessionActive(sessionId) {
|
|
24
|
+
return isAgentSessionActive('gemini', sessionId);
|
|
357
25
|
}
|
|
358
26
|
|
|
359
|
-
function getActiveGeminiSessions() {
|
|
360
|
-
return
|
|
27
|
+
export function getActiveGeminiSessions() {
|
|
28
|
+
return getActiveAgentSessions('gemini');
|
|
361
29
|
}
|
|
362
|
-
|
|
363
|
-
export {
|
|
364
|
-
queryGemini,
|
|
365
|
-
abortGeminiSession,
|
|
366
|
-
isGeminiSessionActive,
|
|
367
|
-
getActiveGeminiSessions
|
|
368
|
-
};
|
package/server/index.js
CHANGED
|
@@ -97,10 +97,13 @@ import fetch from 'node-fetch';
|
|
|
97
97
|
import mime from 'mime-types';
|
|
98
98
|
|
|
99
99
|
import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache, clearProviderSessionLookupCaches, getGeminiSessionMessages } from './projects.js';
|
|
100
|
-
import {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
import {
|
|
101
|
+
executeAgentPrompt,
|
|
102
|
+
getActiveAgentSessions,
|
|
103
|
+
isAgentSessionActive,
|
|
104
|
+
resolveAgentPermission,
|
|
105
|
+
setAgentSessionMode
|
|
106
|
+
} from './acp-runtime/index.js';
|
|
104
107
|
import gitRoutes from './routes/git.js';
|
|
105
108
|
import authRoutes from './routes/auth.js';
|
|
106
109
|
import mcpRoutes from './routes/mcp.js';
|
|
@@ -110,8 +113,20 @@ import commandsRoutes from './routes/commands.js';
|
|
|
110
113
|
import settingsRoutes from './routes/settings.js';
|
|
111
114
|
import channelsRoutes from './routes/channels.js';
|
|
112
115
|
import agentRoutes from './routes/agent.js';
|
|
113
|
-
import projectsRoutes, {
|
|
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';
|
|
114
128
|
import cliAuthRoutes, { detectProviderInstallationStatus } from './routes/cli-auth.js';
|
|
129
|
+
import ccConnectRoutes from './routes/cc-connect.js';
|
|
115
130
|
import userRoutes from './routes/user.js';
|
|
116
131
|
import codexRoutes from './routes/codex.js';
|
|
117
132
|
import opencodeRoutes from './routes/opencode.js';
|
|
@@ -135,6 +150,7 @@ const UPDATE_PACKAGE_NAME = process.env.UPDATE_PACKAGE_NAME || packageInfo.id ||
|
|
|
135
150
|
const VERSION_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
136
151
|
let cachedSystemVersion = null;
|
|
137
152
|
let cachedSystemVersionExpiresAt = 0;
|
|
153
|
+
const WINDOWS_DRIVES_ROOT = '__windows_drives__';
|
|
138
154
|
const RUNTIME_STATUS_PATH = (() => {
|
|
139
155
|
if (process.env.AXHUB_GENIE_STATUS_PATH) {
|
|
140
156
|
return process.env.AXHUB_GENIE_STATUS_PATH;
|
|
@@ -501,6 +517,7 @@ app.use('/api/channels', authenticateToken, channelsRoutes);
|
|
|
501
517
|
|
|
502
518
|
// CLI Authentication API Routes (protected)
|
|
503
519
|
app.use('/api/cli', authenticateToken, cliAuthRoutes);
|
|
520
|
+
app.use('/api/cc-connect', authenticateToken, ccConnectRoutes);
|
|
504
521
|
|
|
505
522
|
// User API Routes (protected)
|
|
506
523
|
app.use('/api/user', authenticateToken, userRoutes);
|
|
@@ -740,16 +757,31 @@ app.post('/api/projects/create', authenticateToken, async (req, res) => {
|
|
|
740
757
|
}
|
|
741
758
|
});
|
|
742
759
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
760
|
+
async function listWindowsDrives() {
|
|
761
|
+
const checks = await Promise.all(
|
|
762
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(async (letter) => {
|
|
763
|
+
const drivePath = `${letter}:\\`;
|
|
764
|
+
try {
|
|
765
|
+
await fsPromises.access(drivePath, fs.constants.R_OK);
|
|
766
|
+
const stats = await fsPromises.stat(drivePath);
|
|
767
|
+
if (!stats.isDirectory()) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
path: drivePath,
|
|
773
|
+
name: drivePath,
|
|
774
|
+
type: 'directory',
|
|
775
|
+
isDrive: true
|
|
776
|
+
};
|
|
777
|
+
} catch (error) {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
return checks.filter(Boolean);
|
|
784
|
+
}
|
|
753
785
|
|
|
754
786
|
// Browse filesystem endpoint for project suggestions - uses existing getFileTree
|
|
755
787
|
app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|
@@ -757,9 +789,47 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|
|
757
789
|
const { path: dirPath } = req.query;
|
|
758
790
|
|
|
759
791
|
console.log('[API] Browse filesystem request for path:', dirPath);
|
|
760
|
-
console.log('[API]
|
|
792
|
+
console.log('[API] WORKSPACES_ROOTS are:', WORKSPACES_ROOTS.length > 0 ? WORKSPACES_ROOTS : '(unrestricted)');
|
|
793
|
+
|
|
794
|
+
if (process.platform === 'win32' && dirPath === WINDOWS_DRIVES_ROOT) {
|
|
795
|
+
const suggestions = await listWindowsDrives();
|
|
796
|
+
return res.json({
|
|
797
|
+
path: WINDOWS_DRIVES_ROOT,
|
|
798
|
+
displayPath: 'This PC',
|
|
799
|
+
suggestions,
|
|
800
|
+
platform: process.platform,
|
|
801
|
+
virtualRoot: 'windows-drives',
|
|
802
|
+
hasWorkspaceRootRestriction: HAS_WORKSPACES_ROOT_RESTRICTION,
|
|
803
|
+
allowedRoots: WORKSPACES_ROOTS
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (shouldUseVirtualWorkspaceRoots({ dirPath })) {
|
|
808
|
+
const suggestions = buildWorkspaceRootSuggestions({
|
|
809
|
+
allowedWorkspaceRoots: WORKSPACES_ROOTS,
|
|
810
|
+
homeDir: DEFAULT_WORKSPACES_ROOT,
|
|
811
|
+
platform: process.platform
|
|
812
|
+
}).filter((entry) => {
|
|
813
|
+
try {
|
|
814
|
+
return fs.existsSync(entry.path);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
return res.json({
|
|
821
|
+
path: UNIX_WORKSPACE_ROOTS,
|
|
822
|
+
displayPath: 'Allowed workspace roots',
|
|
823
|
+
suggestions,
|
|
824
|
+
platform: process.platform,
|
|
825
|
+
virtualRoot: 'workspace-roots',
|
|
826
|
+
hasWorkspaceRootRestriction: HAS_WORKSPACES_ROOT_RESTRICTION,
|
|
827
|
+
allowedRoots: WORKSPACES_ROOTS
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
761
831
|
// Default to home directory if no path provided
|
|
762
|
-
const defaultRoot =
|
|
832
|
+
const defaultRoot = DEFAULT_WORKSPACES_ROOT;
|
|
763
833
|
let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
|
|
764
834
|
|
|
765
835
|
// Resolve and normalize the path
|
|
@@ -823,7 +893,12 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|
|
823
893
|
|
|
824
894
|
res.json({
|
|
825
895
|
path: resolvedPath,
|
|
826
|
-
|
|
896
|
+
displayPath: resolvedPath,
|
|
897
|
+
suggestions: suggestions,
|
|
898
|
+
platform: process.platform,
|
|
899
|
+
virtualRoot: null,
|
|
900
|
+
hasWorkspaceRootRestriction: HAS_WORKSPACES_ROOT_RESTRICTION,
|
|
901
|
+
allowedRoots: WORKSPACES_ROOTS
|
|
827
902
|
});
|
|
828
903
|
|
|
829
904
|
} catch (error) {
|
|
@@ -1112,31 +1187,24 @@ function handleChatConnection(ws) {
|
|
|
1112
1187
|
try {
|
|
1113
1188
|
data = JSON.parse(message);
|
|
1114
1189
|
|
|
1115
|
-
|
|
1116
|
-
console.log('[DEBUG] User message:', data.command || '[Continue/Resume]');
|
|
1117
|
-
console.log('📁 Project:', data.options?.projectPath || 'Unknown');
|
|
1118
|
-
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
|
1190
|
+
const agentCommandTypes = ['claude-command', 'codex-command', 'gemini-command', 'opencode-command'];
|
|
1119
1191
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
console.log('[DEBUG] Codex message:', data.command || '[Continue/Resume]');
|
|
1124
|
-
console.log('📁 Project:', data.options?.projectPath || data.options?.cwd || 'Unknown');
|
|
1125
|
-
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
|
1126
|
-
console.log('🤖 Model:', data.options?.model || 'default');
|
|
1127
|
-
await queryCodex(data.command, data.options, new SessionEventMirrorWriter(baseWriter, 'codex'));
|
|
1128
|
-
} else if (data.type === 'gemini-command') {
|
|
1129
|
-
console.log('[DEBUG] Gemini message:', data.command || '[Continue/Resume]');
|
|
1192
|
+
if (agentCommandTypes.includes(data.type)) {
|
|
1193
|
+
const agentKey = data.type.replace(/-command$/, '');
|
|
1194
|
+
console.log(`[DEBUG] ${agentKey} message:`, data.command || '[Continue/Resume]');
|
|
1130
1195
|
console.log('📁 Project:', data.options?.projectPath || data.options?.cwd || 'Unknown');
|
|
1131
1196
|
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
|
1132
1197
|
console.log('🤖 Model:', data.options?.model || 'default');
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1198
|
+
|
|
1199
|
+
await executeAgentPrompt({
|
|
1200
|
+
agentKey,
|
|
1201
|
+
command: data.command,
|
|
1202
|
+
options: {
|
|
1203
|
+
...(data.options || {}),
|
|
1204
|
+
clientRequestId: data.clientRequestId || data.options?.clientRequestId || null
|
|
1205
|
+
},
|
|
1206
|
+
writer: new SessionEventMirrorWriter(baseWriter, agentKey)
|
|
1207
|
+
});
|
|
1140
1208
|
} else if (data.type === 'abort-session') {
|
|
1141
1209
|
console.log('[DEBUG] Abort session request:', data.sessionId);
|
|
1142
1210
|
const provider = typeof data.provider === 'string' && data.provider.trim()
|
|
@@ -1147,13 +1215,29 @@ function handleChatConnection(ws) {
|
|
|
1147
1215
|
sessionId: data.sessionId,
|
|
1148
1216
|
writer: getSessionWriter(provider)
|
|
1149
1217
|
});
|
|
1150
|
-
} else if (data.type === '
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1218
|
+
} else if (data.type === 'set-session-mode') {
|
|
1219
|
+
const provider = typeof data.provider === 'string' && data.provider.trim()
|
|
1220
|
+
? data.provider.trim().toLowerCase()
|
|
1221
|
+
: 'claude';
|
|
1222
|
+
const modeId = String(data.modeId || '').trim();
|
|
1223
|
+
if (!data.sessionId || !modeId) {
|
|
1224
|
+
throw new Error('set-session-mode requires sessionId and modeId');
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const changed = await setAgentSessionMode(provider, data.sessionId, modeId, {
|
|
1228
|
+
writer: getSessionWriter(provider)
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
if (!changed) {
|
|
1232
|
+
throw new Error(`Unable to set session mode for ${provider}:${data.sessionId}`);
|
|
1233
|
+
}
|
|
1234
|
+
} else if (data.type === 'permission-response' || data.type === 'claude-permission-response') {
|
|
1154
1235
|
if (data.requestId) {
|
|
1155
|
-
|
|
1236
|
+
resolveAgentPermission(data.requestId, {
|
|
1156
1237
|
allow: Boolean(data.allow),
|
|
1238
|
+
optionId: data.optionId,
|
|
1239
|
+
outcome: data.outcome,
|
|
1240
|
+
cancelled: data.outcome?.outcome === 'cancelled' || Boolean(data.cancelled),
|
|
1157
1241
|
updatedInput: data.updatedInput,
|
|
1158
1242
|
message: data.message,
|
|
1159
1243
|
rememberEntry: data.rememberEntry
|
|
@@ -1164,17 +1248,7 @@ function handleChatConnection(ws) {
|
|
|
1164
1248
|
const provider = data.provider || 'claude';
|
|
1165
1249
|
const sessionId = data.sessionId;
|
|
1166
1250
|
const sessionWriter = getSessionWriter(provider);
|
|
1167
|
-
|
|
1168
|
-
if (provider === 'codex') {
|
|
1169
|
-
isActive = isCodexSessionActive(sessionId);
|
|
1170
|
-
} else if (provider === 'gemini') {
|
|
1171
|
-
isActive = isGeminiSessionActive(sessionId);
|
|
1172
|
-
} else if (provider === 'opencode') {
|
|
1173
|
-
isActive = isOpencodeSessionActive(sessionId);
|
|
1174
|
-
} else {
|
|
1175
|
-
// Use Claude Agents SDK
|
|
1176
|
-
isActive = isClaudeSDKSessionActive(sessionId);
|
|
1177
|
-
}
|
|
1251
|
+
const isActive = isAgentSessionActive(provider, sessionId);
|
|
1178
1252
|
|
|
1179
1253
|
sessionWriter.send({
|
|
1180
1254
|
type: 'session-status',
|
|
@@ -1185,9 +1259,10 @@ function handleChatConnection(ws) {
|
|
|
1185
1259
|
} else if (data.type === 'get-active-sessions') {
|
|
1186
1260
|
// Get all currently active sessions
|
|
1187
1261
|
const activeSessions = {
|
|
1188
|
-
claude:
|
|
1189
|
-
|
|
1190
|
-
|
|
1262
|
+
claude: getActiveAgentSessions('claude'),
|
|
1263
|
+
codex: getActiveAgentSessions('codex'),
|
|
1264
|
+
gemini: getActiveAgentSessions('gemini'),
|
|
1265
|
+
opencode: getActiveAgentSessions('opencode')
|
|
1191
1266
|
};
|
|
1192
1267
|
baseWriter.send({
|
|
1193
1268
|
type: 'active-sessions',
|