@axhub/genie 0.2.6 → 0.2.7
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/assets/App-BWSqiXAT.js +220 -0
- package/dist/assets/App-DrlLKa8f.css +1 -0
- package/dist/assets/ReviewApp-nz3mbArg.js +1 -0
- package/dist/assets/{_basePickBy-DkiHsp3X.js → _basePickBy-C19AekOu.js} +1 -1
- package/dist/assets/{_baseUniq-7ElXb2sX.js → _baseUniq-JsnevLw_.js} +1 -1
- package/dist/assets/{arc-CEsS3MdK.js → arc-BLpcuBlf.js} +1 -1
- package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +36 -0
- package/dist/assets/{blockDiagram-WCTKOSBZ-Cza6M6Ht.js → blockDiagram-WCTKOSBZ-DQBLwsUS.js} +3 -3
- package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +10 -0
- package/dist/assets/channel-DkFNxV_H.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB--HkodwbY.js → chunk-4BX2VUAB-De63kbgc.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CyBuez4e.js → chunk-55IACEB6-DtTDDdM9.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CuzG4iAl.js → chunk-FMBD7UC4-DHuwd8tw.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-BNi8S861.js → chunk-JSJVCQXG-BgytFtmO.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-D817O-GT.js → chunk-KX2RTZJC-nZdp86aN.js} +1 -1
- package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +220 -0
- package/dist/assets/{chunk-QZHKN3VN-VMEn-zxh.js → chunk-QZHKN3VN-DvUQ3mnO.js} +1 -1
- package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +189 -0
- package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +1 -0
- package/dist/assets/clone-C0lCEIEO.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +1 -0
- package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
- package/dist/assets/{dagre-KLK3FWXG-Bqp7DjEa.js → dagre-KLK3FWXG-CHYIvW47.js} +1 -1
- package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +24 -0
- package/dist/assets/{diagram-IFDJBPK2--fHfW6V2.js → diagram-IFDJBPK2-Dzsiln_C.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-D1kQI5RB.js → diagram-P4PSJMXO-DKnGbUpE.js} +1 -1
- package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +70 -0
- package/dist/assets/{flowDiagram-PKNHOUZH-DWeNr4yg.js → flowDiagram-PKNHOUZH-BAZ2-jKp.js} +4 -4
- package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +292 -0
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-B5a8UWjN.js → gitGraphDiagram-K3NZZRJ6-BflpyjGy.js} +1 -1
- package/dist/assets/{graph-Cw1rYoD9.js → graph-suelaXFh.js} +1 -1
- package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +1 -0
- package/dist/assets/index-B01NxbUv.css +1 -0
- package/dist/assets/index-DW5pGgQ_.js +2 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-D2u70rhN.js → infoDiagram-LFFYTUFH-pfD1FA3p.js} +1 -1
- package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +70 -0
- package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +139 -0
- package/dist/assets/{kanban-definition-K7BYSVSG-DbVt0v29.js → kanban-definition-K7BYSVSG-FWinmur1.js} +5 -5
- package/dist/assets/{layout-W_tRx4UV.js → layout-vcz43XvZ.js} +1 -1
- package/dist/assets/{linear-CcMb2ay-.js → linear-le4gc0vx.js} +1 -1
- package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +870 -0
- package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +68 -0
- package/dist/assets/{pieDiagram-SKSYHLDU-CDyJaACv.js → pieDiagram-SKSYHLDU-C7PKDh3b.js} +2 -2
- package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +7 -0
- package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +73 -0
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-Di1ShaMF.js → sankeyDiagram-WA2Y5GQK-4gulcOP4.js} +3 -3
- package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +145 -0
- package/dist/assets/{stateDiagram-RAJIS63D-CVZYMqyW.js → stateDiagram-RAJIS63D-CB4Vl7qM.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +1 -0
- package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +61 -0
- package/dist/assets/{treemap-KZPCXAKY-CGG4gx3C.js → treemap-KZPCXAKY-DZSEE6Hz.js} +58 -58
- package/dist/assets/vendor-codemirror-CyOKkaQZ.js +31 -0
- package/dist/assets/vendor-react-CP4yFTs7.js +8 -0
- package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
- package/dist/assets/{vennDiagram-LZ73GAT5-Dds37L2k.js → vennDiagram-LZ73GAT5-8E_G06fI.js} +4 -4
- package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +7 -0
- package/dist/index.html +4 -4
- package/package.json +2 -1
- 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 +1805 -0
- package/server/acp-runtime/client.test.js +688 -0
- package/server/acp-runtime/index.js +419 -0
- package/server/acp-runtime/registry.js +45 -0
- package/server/acp-runtime/session-store.js +254 -0
- package/server/acp-runtime/session-store.test.js +89 -0
- package/server/channels/runtime/AgentRuntimeAdapter.js +21 -70
- package/server/claude-sdk.js +24 -944
- package/server/cli.js +4 -2
- package/server/external-agent/service.js +52 -63
- package/server/gemini-cli.js +23 -360
- package/server/index.js +47 -44
- package/server/openai-codex.js +24 -698
- package/server/opencode-cli.js +70 -640
- package/server/routes/agent.js +2 -0
- package/server/routes/git.js +3 -20
- 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/runtimeState.js +16 -17
- package/server/session-core/runtimeWriter.js +19 -12
- package/shared/conversationEvents.js +347 -10
- package/shared/conversationEvents.test.js +403 -0
- package/dist/assets/App-CYTE30Cf.js +0 -484
- package/dist/assets/App-qxJ8_QYu.css +0 -32
- package/dist/assets/ReviewApp-BEicSBzW.js +0 -1
- package/dist/assets/architectureDiagram-2XIMDMQ5-BubZ7T3U.js +0 -36
- package/dist/assets/c4Diagram-IC4MRINW-jhjtOQ12.js +0 -10
- package/dist/assets/channel-RmqTALN0.js +0 -1
- package/dist/assets/chunk-NQ4KR5QH-DyujyOvx.js +0 -220
- package/dist/assets/chunk-WL4C6EOR-CQHHFLvx.js +0 -189
- 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/cose-bilkent-S5V4N54A-qykDd54p.js +0 -1
- package/dist/assets/cytoscape.esm-2ZfV8NB5.js +0 -331
- package/dist/assets/diagram-E7M64L7V-BKtx468K.js +0 -24
- package/dist/assets/erDiagram-INFDFZHY-DT9YzdNw.js +0 -70
- package/dist/assets/ganttDiagram-A5KZAMGK--IgwcUhI.js +0 -292
- package/dist/assets/highlighted-body-TPN3WLV5-BCxJHuqY.js +0 -1
- package/dist/assets/index-CBuAXA5S.js +0 -2
- package/dist/assets/index-CyLWKyxy.css +0 -1
- package/dist/assets/ishikawaDiagram-PHBUUO56-Cl8yrezU.js +0 -70
- package/dist/assets/journeyDiagram-4ABVD52K-ddP0AMU9.js +0 -139
- package/dist/assets/mermaid-O7DHMXV3-BBJqt8pT.js +0 -988
- package/dist/assets/mindmap-definition-YRQLILUH-BGhZa7Na.js +0 -68
- package/dist/assets/quadrantDiagram-337W2JSQ-BSYuqf0Q.js +0 -7
- package/dist/assets/requirementDiagram-Z7DCOOCP-Cfi9YX9H.js +0 -73
- package/dist/assets/sequenceDiagram-2WXFIKYE-CYTTG38e.js +0 -145
- package/dist/assets/stateDiagram-v2-FVOUBMTO-Bbl0b4-i.js +0 -1
- package/dist/assets/timeline-definition-YZTLITO2-B1sdb5mK.js +0 -61
- package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
- package/dist/assets/vendor-react-Cpt6D04s.js +0 -59
- package/dist/assets/vendor-xterm-DfaPXD3y.js +0 -66
- package/dist/assets/xychartDiagram-JWTSCODW-C8QKSyRR.js +0 -7
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { resolveWorkingDirectory } from '../utils/defaultWorkingDirectory.js';
|
|
2
|
+
import {
|
|
3
|
+
CLAUDE_MODELS,
|
|
4
|
+
CODEX_MODELS,
|
|
5
|
+
GEMINI_MODELS,
|
|
6
|
+
OPENCODE_MODELS
|
|
7
|
+
} from '../../shared/modelConstants.js';
|
|
8
|
+
import { AcpClient } from './client.js';
|
|
9
|
+
import {
|
|
10
|
+
findAcpSessionRecord,
|
|
11
|
+
touchAcpSessionRecord
|
|
12
|
+
} from './session-store.js';
|
|
13
|
+
|
|
14
|
+
const activeSessionClients = new Map();
|
|
15
|
+
const pendingSessionClientLoads = new Map();
|
|
16
|
+
const DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS = parseInt(process.env.ACP_SESSION_IDLE_TIMEOUT_MS, 10) || (5 * 60 * 1000);
|
|
17
|
+
|
|
18
|
+
function normalizeProvider(value) {
|
|
19
|
+
return String(value || '').trim().toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createActiveSessionKey(provider, sessionId) {
|
|
23
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
24
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
25
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return `${normalizedProvider}:${normalizedSessionId}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function registerActiveClient(provider, sessionId, client) {
|
|
32
|
+
const key = createActiveSessionKey(provider, sessionId);
|
|
33
|
+
if (!key) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const existing = activeSessionClients.get(key);
|
|
38
|
+
if (existing?.idleTimer) {
|
|
39
|
+
clearTimeout(existing.idleTimer);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const entry = existing && existing.client === client
|
|
43
|
+
? existing
|
|
44
|
+
: {
|
|
45
|
+
client,
|
|
46
|
+
idleTimer: null,
|
|
47
|
+
lastUsedAt: null
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
entry.client = client;
|
|
51
|
+
entry.lastUsedAt = Date.now();
|
|
52
|
+
entry.idleTimer = null;
|
|
53
|
+
activeSessionClients.set(key, entry);
|
|
54
|
+
return entry;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveDefaultModel(agentKey, requestedModel) {
|
|
58
|
+
if (requestedModel) {
|
|
59
|
+
return requestedModel;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (agentKey === 'claude') return CLAUDE_MODELS.DEFAULT;
|
|
63
|
+
if (agentKey === 'codex') return CODEX_MODELS.DEFAULT;
|
|
64
|
+
if (agentKey === 'gemini') return GEMINI_MODELS.DEFAULT;
|
|
65
|
+
if (agentKey === 'opencode') return OPENCODE_MODELS.DEFAULT;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function unregisterActiveClient(provider, sessionId, client = null) {
|
|
70
|
+
const key = createActiveSessionKey(provider, sessionId);
|
|
71
|
+
if (!key) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const entry = activeSessionClients.get(key);
|
|
76
|
+
if (!entry) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (client && entry.client !== client) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (entry.idleTimer) {
|
|
85
|
+
clearTimeout(entry.idleTimer);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
activeSessionClients.delete(key);
|
|
89
|
+
return entry.client;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getActiveClient(provider, sessionId) {
|
|
93
|
+
const key = createActiveSessionKey(provider, sessionId);
|
|
94
|
+
return key ? activeSessionClients.get(key)?.client || null : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function disposeActiveClient(provider, sessionId, client = null) {
|
|
98
|
+
const resolvedClient = unregisterActiveClient(provider, sessionId, client) || client;
|
|
99
|
+
if (resolvedClient) {
|
|
100
|
+
await resolvedClient.close().catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function scheduleActiveClientCleanup(provider, sessionId, metadata = {}) {
|
|
105
|
+
if (DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS <= 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const key = createActiveSessionKey(provider, sessionId);
|
|
110
|
+
const entry = key ? activeSessionClients.get(key) : null;
|
|
111
|
+
if (!entry) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (entry.idleTimer) {
|
|
116
|
+
clearTimeout(entry.idleTimer);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
entry.lastUsedAt = Date.now();
|
|
120
|
+
entry.idleTimer = setTimeout(() => {
|
|
121
|
+
void (async () => {
|
|
122
|
+
const activeClient = unregisterActiveClient(provider, sessionId, entry.client);
|
|
123
|
+
if (!activeClient) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await touchAcpSessionRecord({
|
|
128
|
+
...metadata,
|
|
129
|
+
provider,
|
|
130
|
+
sessionId,
|
|
131
|
+
lastActivity: new Date().toISOString(),
|
|
132
|
+
isClosed: false
|
|
133
|
+
}).catch(() => {});
|
|
134
|
+
|
|
135
|
+
await activeClient.close().catch(() => {});
|
|
136
|
+
})();
|
|
137
|
+
}, DEFAULT_ACTIVE_SESSION_IDLE_TIMEOUT_MS);
|
|
138
|
+
|
|
139
|
+
entry.idleTimer.unref?.();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function resolveProjectPath(options = {}) {
|
|
143
|
+
const explicitProjectPath = resolveWorkingDirectory({
|
|
144
|
+
cwd: options.cwd,
|
|
145
|
+
projectPath: options.projectPath
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (explicitProjectPath) {
|
|
149
|
+
return explicitProjectPath;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options.sessionId) {
|
|
153
|
+
const record = await findAcpSessionRecord(options.sessionId, options.provider || options.agentKey || null);
|
|
154
|
+
if (record?.projectPath) {
|
|
155
|
+
return record.projectPath;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return process.cwd();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function createClient({ normalizedAgent, writer, projectPath, options = {} }) {
|
|
163
|
+
return new AcpClient({
|
|
164
|
+
agentKey: normalizedAgent,
|
|
165
|
+
writer,
|
|
166
|
+
projectPath,
|
|
167
|
+
agentCommandOverrides: options.agentCommandOverrides,
|
|
168
|
+
model: resolveDefaultModel(normalizedAgent, options.model),
|
|
169
|
+
permissionMode: options.permissionMode || 'default'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function createOrResumeClient({ normalizedAgent, options = {}, projectPath, writer }) {
|
|
174
|
+
const requestedSessionId = typeof options.sessionId === 'string' ? options.sessionId.trim() : '';
|
|
175
|
+
const activeClient = requestedSessionId ? getActiveClient(normalizedAgent, requestedSessionId) : null;
|
|
176
|
+
if (activeClient) {
|
|
177
|
+
registerActiveClient(normalizedAgent, requestedSessionId, activeClient);
|
|
178
|
+
return activeClient;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const pendingKey = requestedSessionId ? createActiveSessionKey(normalizedAgent, requestedSessionId) : null;
|
|
182
|
+
if (pendingKey && pendingSessionClientLoads.has(pendingKey)) {
|
|
183
|
+
return pendingSessionClientLoads.get(pendingKey);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const clientPromise = (async () => {
|
|
187
|
+
const client = createClient({
|
|
188
|
+
normalizedAgent,
|
|
189
|
+
writer,
|
|
190
|
+
projectPath,
|
|
191
|
+
options
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
await client.start();
|
|
196
|
+
|
|
197
|
+
if (requestedSessionId) {
|
|
198
|
+
try {
|
|
199
|
+
await client.loadSession(requestedSessionId);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn(`[ACP:${normalizedAgent}] Unable to resume session ${requestedSessionId}: ${error.message}`);
|
|
202
|
+
await client.createSession();
|
|
203
|
+
client.emitSystemNotice(`Unable to resume session ${requestedSessionId}; started a new session instead.`);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
await client.createSession();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
registerActiveClient(normalizedAgent, client.sessionId, client);
|
|
210
|
+
writer?.setSessionId?.(client.sessionId);
|
|
211
|
+
return client;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
await client.close().catch(() => {});
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
|
|
218
|
+
if (pendingKey) {
|
|
219
|
+
pendingSessionClientLoads.set(pendingKey, clientPromise);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
return await clientPromise;
|
|
224
|
+
} finally {
|
|
225
|
+
if (pendingKey) {
|
|
226
|
+
pendingSessionClientLoads.delete(pendingKey);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function executeAgentPrompt({ agentKey, command, options = {}, writer }) {
|
|
232
|
+
const normalizedAgent = normalizeProvider(agentKey || options.provider);
|
|
233
|
+
const projectPath = await resolveProjectPath({
|
|
234
|
+
...options,
|
|
235
|
+
agentKey: normalizedAgent,
|
|
236
|
+
provider: normalizedAgent
|
|
237
|
+
});
|
|
238
|
+
const resolvedModel = resolveDefaultModel(normalizedAgent, options.model);
|
|
239
|
+
const agentCommand = options.agentCommand || null;
|
|
240
|
+
let sessionRecord = null;
|
|
241
|
+
let client = null;
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
client = await createOrResumeClient({
|
|
245
|
+
normalizedAgent,
|
|
246
|
+
options,
|
|
247
|
+
projectPath,
|
|
248
|
+
writer
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const effectiveProjectPath = client.projectPath || projectPath;
|
|
252
|
+
const response = await client.runExclusive(async () => {
|
|
253
|
+
registerActiveClient(normalizedAgent, client.sessionId, client);
|
|
254
|
+
await client.configureForTurn({
|
|
255
|
+
writer,
|
|
256
|
+
permissionMode: options.permissionMode || 'default',
|
|
257
|
+
model: resolvedModel
|
|
258
|
+
});
|
|
259
|
+
writer?.setSessionId?.(client.sessionId);
|
|
260
|
+
|
|
261
|
+
sessionRecord = await touchAcpSessionRecord({
|
|
262
|
+
provider: normalizedAgent,
|
|
263
|
+
sessionId: client.sessionId,
|
|
264
|
+
projectPath: effectiveProjectPath,
|
|
265
|
+
agentCommand,
|
|
266
|
+
model: resolvedModel,
|
|
267
|
+
lastActivity: new Date().toISOString(),
|
|
268
|
+
lastPromptAt: new Date().toISOString(),
|
|
269
|
+
isClosed: false
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return client.sendPrompt(command, {
|
|
273
|
+
images: options.images || [],
|
|
274
|
+
resourceLinks: options.resourceLinks || [],
|
|
275
|
+
clientRequestId: options.clientRequestId || null
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await touchAcpSessionRecord({
|
|
280
|
+
provider: normalizedAgent,
|
|
281
|
+
sessionId: client.sessionId,
|
|
282
|
+
projectPath: effectiveProjectPath,
|
|
283
|
+
agentCommand,
|
|
284
|
+
model: resolvedModel,
|
|
285
|
+
lastActivity: new Date().toISOString(),
|
|
286
|
+
lastPromptAt: new Date().toISOString(),
|
|
287
|
+
isClosed: false
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
scheduleActiveClientCleanup(normalizedAgent, client.sessionId, {
|
|
291
|
+
projectPath: effectiveProjectPath,
|
|
292
|
+
agentCommand,
|
|
293
|
+
model: resolvedModel
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
sessionId: client.sessionId,
|
|
298
|
+
provider: normalizedAgent,
|
|
299
|
+
runtime: 'acp',
|
|
300
|
+
stopReason: response?.stopReason || null,
|
|
301
|
+
source: sessionRecord?.source || 'acp'
|
|
302
|
+
};
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (client?.sessionId && !client.hasReportedPromptFatalError) {
|
|
305
|
+
writer?.send?.({
|
|
306
|
+
type: 'error',
|
|
307
|
+
provider: normalizedAgent,
|
|
308
|
+
sessionId: client.sessionId,
|
|
309
|
+
runtime: 'acp',
|
|
310
|
+
error: error.message
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (client?.sessionId) {
|
|
315
|
+
await disposeActiveClient(normalizedAgent, client.sessionId, client);
|
|
316
|
+
} else if (client) {
|
|
317
|
+
await client.close().catch(() => {});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function abortAgentSession(agentKey, sessionId) {
|
|
325
|
+
const normalizedAgent = normalizeProvider(agentKey);
|
|
326
|
+
const client = getActiveClient(normalizedAgent, sessionId);
|
|
327
|
+
|
|
328
|
+
if (!client) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const cancelled = await client.cancel(sessionId);
|
|
333
|
+
if (cancelled) {
|
|
334
|
+
await touchAcpSessionRecord({
|
|
335
|
+
provider: normalizedAgent,
|
|
336
|
+
sessionId,
|
|
337
|
+
lastActivity: new Date().toISOString()
|
|
338
|
+
}).catch(() => {});
|
|
339
|
+
}
|
|
340
|
+
return cancelled;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export async function setAgentSessionMode(agentKey, sessionId, modeId, { writer = null } = {}) {
|
|
344
|
+
const normalizedAgent = normalizeProvider(agentKey);
|
|
345
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
346
|
+
const normalizedModeId = String(modeId || '').trim();
|
|
347
|
+
|
|
348
|
+
if (!normalizedAgent || !normalizedSessionId || !normalizedModeId) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
let client = getActiveClient(normalizedAgent, normalizedSessionId);
|
|
353
|
+
if (!client) {
|
|
354
|
+
const projectPath = await resolveProjectPath({
|
|
355
|
+
provider: normalizedAgent,
|
|
356
|
+
sessionId: normalizedSessionId
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
client = await createOrResumeClient({
|
|
360
|
+
normalizedAgent,
|
|
361
|
+
options: {
|
|
362
|
+
sessionId: normalizedSessionId
|
|
363
|
+
},
|
|
364
|
+
projectPath,
|
|
365
|
+
writer
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const changed = await client.runExclusive(async () => {
|
|
370
|
+
registerActiveClient(normalizedAgent, normalizedSessionId, client);
|
|
371
|
+
if (writer) {
|
|
372
|
+
client.attachWriter(writer);
|
|
373
|
+
writer.setSessionId?.(normalizedSessionId);
|
|
374
|
+
}
|
|
375
|
+
return client.setMode(normalizedModeId);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (changed) {
|
|
379
|
+
await touchAcpSessionRecord({
|
|
380
|
+
provider: normalizedAgent,
|
|
381
|
+
sessionId: normalizedSessionId,
|
|
382
|
+
projectPath: client.projectPath || null,
|
|
383
|
+
model: client.model || null,
|
|
384
|
+
lastActivity: new Date().toISOString(),
|
|
385
|
+
isClosed: false
|
|
386
|
+
}).catch(() => {});
|
|
387
|
+
|
|
388
|
+
scheduleActiveClientCleanup(normalizedAgent, normalizedSessionId, {
|
|
389
|
+
projectPath: client.projectPath || null,
|
|
390
|
+
model: client.model || null
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return changed;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function isAgentSessionActive(agentKey, sessionId) {
|
|
398
|
+
const key = createActiveSessionKey(agentKey, sessionId);
|
|
399
|
+
return key ? activeSessionClients.has(key) : false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function getActiveAgentSessions(agentKey = null) {
|
|
403
|
+
const normalizedAgent = agentKey ? normalizeProvider(agentKey) : null;
|
|
404
|
+
const sessions = [];
|
|
405
|
+
|
|
406
|
+
for (const key of activeSessionClients.keys()) {
|
|
407
|
+
const [provider, sessionId] = key.split(':');
|
|
408
|
+
if (normalizedAgent && provider !== normalizedAgent) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
sessions.push(sessionId);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return sessions;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function resolveAgentPermission(requestId, decision = {}) {
|
|
418
|
+
return AcpClient.resolvePermissionRequest(requestId, decision);
|
|
419
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const ACP_ADAPTER_PACKAGE_RANGES = {
|
|
2
|
+
claude: '^0.24.2',
|
|
3
|
+
codex: '^0.10.0'
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const AGENT_REGISTRY = {
|
|
7
|
+
claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
|
|
8
|
+
codex: `npx @zed-industries/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
|
|
9
|
+
gemini: 'gemini --acp',
|
|
10
|
+
opencode: 'npx -y opencode-ai acp'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const AGENT_ALIASES = {
|
|
14
|
+
openai: 'codex'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function normalizeAgentName(value) {
|
|
18
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
19
|
+
return AGENT_ALIASES[normalized] || normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function mergeAgentRegistry(overrides = {}) {
|
|
23
|
+
const merged = { ...AGENT_REGISTRY };
|
|
24
|
+
|
|
25
|
+
Object.entries(overrides || {}).forEach(([agentName, command]) => {
|
|
26
|
+
const normalizedAgent = normalizeAgentName(agentName);
|
|
27
|
+
const normalizedCommand = typeof command === 'string' ? command.trim() : '';
|
|
28
|
+
if (!normalizedAgent || !normalizedCommand) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
merged[normalizedAgent] = normalizedCommand;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveAgentCommand(agentKey, overrides = {}) {
|
|
38
|
+
const normalizedAgent = normalizeAgentName(agentKey);
|
|
39
|
+
const registry = mergeAgentRegistry(overrides);
|
|
40
|
+
return registry[normalizedAgent] || registry[AGENT_ALIASES[normalizedAgent] || normalizedAgent] || '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function listBuiltInAgents(overrides = {}) {
|
|
44
|
+
return Object.keys(mergeAgentRegistry(overrides)).sort();
|
|
45
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
function parsePositiveInteger(value, fallback) {
|
|
6
|
+
const parsed = parseInt(value, 10);
|
|
7
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ACP_SESSION_ROOT = process.env.AXHUB_GENIE_ACP_SESSION_ROOT
|
|
11
|
+
? path.resolve(process.env.AXHUB_GENIE_ACP_SESSION_ROOT)
|
|
12
|
+
: path.join(os.homedir(), '.axhub-genie', 'acp-sessions');
|
|
13
|
+
const DEFAULT_SESSION_GC_MAX_AGE_DAYS = parsePositiveInteger(process.env.ACP_SESSION_GC_MAX_AGE_DAYS, 30);
|
|
14
|
+
|
|
15
|
+
function normalizeProvider(value) {
|
|
16
|
+
return String(value || '').trim().toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeSessionId(value) {
|
|
20
|
+
const normalized = String(value || '').trim();
|
|
21
|
+
return normalized || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getProviderDir(provider) {
|
|
25
|
+
return path.join(ACP_SESSION_ROOT, normalizeProvider(provider));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSessionFilePath(provider, sessionId) {
|
|
29
|
+
return path.join(getProviderDir(provider), `${encodeURIComponent(sessionId)}.json`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sortSessionsByActivity(sessions = []) {
|
|
33
|
+
return sessions.sort((left, right) => {
|
|
34
|
+
const leftTime = new Date(left.lastActivity || left.updatedAt || left.createdAt || 0).getTime();
|
|
35
|
+
const rightTime = new Date(right.lastActivity || right.updatedAt || right.createdAt || 0).getTime();
|
|
36
|
+
return rightTime - leftTime;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function ensureProviderDir(provider) {
|
|
41
|
+
await fs.mkdir(getProviderDir(provider), { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function listProviderSessionFiles(provider) {
|
|
45
|
+
try {
|
|
46
|
+
const entries = await fs.readdir(getProviderDir(provider), { withFileTypes: true });
|
|
47
|
+
return entries
|
|
48
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
|
|
49
|
+
.map((entry) => path.join(getProviderDir(provider), entry.name));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error?.code === 'ENOENT') {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readJsonFile(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
61
|
+
return JSON.parse(content);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error?.code === 'ENOENT') {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createSessionSummary(record) {
|
|
71
|
+
if (!record || typeof record !== 'object') {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
id: record.sessionId,
|
|
77
|
+
sessionId: record.sessionId,
|
|
78
|
+
provider: record.provider,
|
|
79
|
+
source: 'acp',
|
|
80
|
+
runtime: 'acp',
|
|
81
|
+
projectPath: record.projectPath || null,
|
|
82
|
+
cwd: record.projectPath || null,
|
|
83
|
+
title: record.title || record.sessionId,
|
|
84
|
+
summary: record.summary || null,
|
|
85
|
+
model: record.model || null,
|
|
86
|
+
createdAt: record.createdAt || null,
|
|
87
|
+
updatedAt: record.updatedAt || null,
|
|
88
|
+
lastActivity: record.lastActivity || record.updatedAt || record.createdAt || null,
|
|
89
|
+
lastPromptAt: record.lastPromptAt || null,
|
|
90
|
+
closedAt: record.closedAt || null,
|
|
91
|
+
isClosed: Boolean(record.isClosed)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getSessionActivityTime(record) {
|
|
96
|
+
return new Date(
|
|
97
|
+
record?.closedAt
|
|
98
|
+
|| record?.lastActivity
|
|
99
|
+
|| record?.updatedAt
|
|
100
|
+
|| record?.createdAt
|
|
101
|
+
|| 0
|
|
102
|
+
).getTime();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function readAcpSessionRecord(provider, sessionId) {
|
|
106
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
107
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
108
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const record = await readJsonFile(getSessionFilePath(normalizedProvider, normalizedSessionId));
|
|
113
|
+
return record && typeof record === 'object' ? record : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function writeAcpSessionRecord(record) {
|
|
117
|
+
const normalizedProvider = normalizeProvider(record?.provider);
|
|
118
|
+
const normalizedSessionId = normalizeSessionId(record?.sessionId);
|
|
119
|
+
|
|
120
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
121
|
+
throw new Error('provider and sessionId are required to persist an ACP session');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const now = new Date().toISOString();
|
|
125
|
+
const existing = await readAcpSessionRecord(normalizedProvider, normalizedSessionId);
|
|
126
|
+
const nextRecord = {
|
|
127
|
+
source: 'acp',
|
|
128
|
+
runtime: 'acp',
|
|
129
|
+
provider: normalizedProvider,
|
|
130
|
+
sessionId: normalizedSessionId,
|
|
131
|
+
createdAt: existing?.createdAt || record.createdAt || now,
|
|
132
|
+
updatedAt: now,
|
|
133
|
+
lastActivity: record.lastActivity || now,
|
|
134
|
+
projectPath: record.projectPath || existing?.projectPath || null,
|
|
135
|
+
agentCommand: record.agentCommand || existing?.agentCommand || null,
|
|
136
|
+
model: record.model || existing?.model || null,
|
|
137
|
+
summary: record.summary || existing?.summary || null,
|
|
138
|
+
title: record.title || existing?.title || null,
|
|
139
|
+
lastPromptAt: record.lastPromptAt || existing?.lastPromptAt || null,
|
|
140
|
+
closedAt: record.closedAt || existing?.closedAt || null,
|
|
141
|
+
isClosed: record.isClosed ?? existing?.isClosed ?? false
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
await ensureProviderDir(normalizedProvider);
|
|
145
|
+
const filePath = getSessionFilePath(normalizedProvider, normalizedSessionId);
|
|
146
|
+
await fs.writeFile(filePath, `${JSON.stringify(nextRecord, null, 2)}\n`, 'utf8');
|
|
147
|
+
|
|
148
|
+
return nextRecord;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function touchAcpSessionRecord(record) {
|
|
152
|
+
return writeAcpSessionRecord(record);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function closeAcpSessionRecord(provider, sessionId, updates = {}) {
|
|
156
|
+
const now = new Date().toISOString();
|
|
157
|
+
return writeAcpSessionRecord({
|
|
158
|
+
...updates,
|
|
159
|
+
provider,
|
|
160
|
+
sessionId,
|
|
161
|
+
isClosed: true,
|
|
162
|
+
closedAt: updates.closedAt || now,
|
|
163
|
+
lastActivity: updates.lastActivity || now
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function gcOldAcpSessions({ provider = null, maxAgeDays = DEFAULT_SESSION_GC_MAX_AGE_DAYS, includeOpen = false } = {}) {
|
|
168
|
+
const normalizedMaxAgeDays = Number(maxAgeDays);
|
|
169
|
+
if (!Number.isFinite(normalizedMaxAgeDays) || normalizedMaxAgeDays <= 0) {
|
|
170
|
+
return { removed: 0 };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const providers = provider ? [normalizeProvider(provider)] : ['claude', 'codex', 'gemini', 'opencode'];
|
|
174
|
+
const cutoffTime = Date.now() - (normalizedMaxAgeDays * 24 * 60 * 60 * 1000);
|
|
175
|
+
let removed = 0;
|
|
176
|
+
|
|
177
|
+
for (const currentProvider of providers) {
|
|
178
|
+
if (!currentProvider) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const filePaths = await listProviderSessionFiles(currentProvider);
|
|
183
|
+
for (const filePath of filePaths) {
|
|
184
|
+
const record = await readJsonFile(filePath);
|
|
185
|
+
if (!record || typeof record !== 'object') {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!includeOpen && !record.isClosed) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const activityTime = getSessionActivityTime(record);
|
|
194
|
+
if (!Number.isFinite(activityTime) || activityTime >= cutoffTime) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await fs.rm(filePath, { force: true });
|
|
199
|
+
removed += 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { removed };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function listAcpSessions({ provider = null, projectPath = null } = {}) {
|
|
207
|
+
await gcOldAcpSessions({ provider }).catch(() => {});
|
|
208
|
+
const providers = provider ? [normalizeProvider(provider)] : ['claude', 'codex', 'gemini', 'opencode'];
|
|
209
|
+
const sessions = [];
|
|
210
|
+
|
|
211
|
+
for (const currentProvider of providers) {
|
|
212
|
+
if (!currentProvider) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const filePaths = await listProviderSessionFiles(currentProvider);
|
|
217
|
+
for (const filePath of filePaths) {
|
|
218
|
+
const record = await readJsonFile(filePath);
|
|
219
|
+
const summary = createSessionSummary(record);
|
|
220
|
+
if (!summary) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (projectPath && summary.projectPath !== projectPath) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
sessions.push(summary);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return sortSessionsByActivity(sessions);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function findAcpSessionRecord(sessionId, provider = null) {
|
|
236
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
237
|
+
if (!normalizedSessionId) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (provider) {
|
|
242
|
+
return readAcpSessionRecord(provider, normalizedSessionId);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const providers = ['claude', 'codex', 'gemini', 'opencode'];
|
|
246
|
+
for (const currentProvider of providers) {
|
|
247
|
+
const record = await readAcpSessionRecord(currentProvider, normalizedSessionId);
|
|
248
|
+
if (record) {
|
|
249
|
+
return record;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return null;
|
|
254
|
+
}
|