acpx 0.4.1 → 0.5.0
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/agent-registry-DGw0-3Tc.js +54 -0
- package/dist/agent-registry-DGw0-3Tc.js.map +1 -0
- package/dist/{cli-idpWyCOs.js → cli-CLRrs6eQ.js} +8 -12
- package/dist/cli-CLRrs6eQ.js.map +1 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1018 -1009
- package/dist/cli.js.map +1 -1
- package/dist/client-DLTWuu4w.d.ts +116 -0
- package/dist/client-DLTWuu4w.d.ts.map +1 -0
- package/dist/{flags-CCcX9fZj.js → flags-BmubjvOw.js} +5 -55
- package/dist/flags-BmubjvOw.js.map +1 -0
- package/dist/{flows-BL1tSvZT.js → flows-CR7xCmkR.js} +471 -281
- package/dist/flows-CR7xCmkR.js.map +1 -0
- package/dist/flows.d.ts +5 -9
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{queue-ipc-CE8_QGX3.js → ipc-DN6M4Ui9.js} +12 -571
- package/dist/ipc-DN6M4Ui9.js.map +1 -0
- package/dist/{acp-jsonrpc-BbBgC5gO.js → jsonrpc-M3y-qzy8.js} +2 -2
- package/dist/jsonrpc-M3y-qzy8.js.map +1 -0
- package/dist/{output-Du3m6oPQ.js → output-Di0M9Et8.js} +6 -6
- package/dist/output-Di0M9Et8.js.map +1 -0
- package/dist/perf-metrics-D9QC81lB.js +568 -0
- package/dist/perf-metrics-D9QC81lB.js.map +1 -0
- package/dist/{session-RO_LZUnv.js → prompt-turn-Bt8T3SRR.js} +2304 -3632
- package/dist/prompt-turn-Bt8T3SRR.js.map +1 -0
- package/dist/{output-render-Bz58qaQn.js → render-BL5ynRkN.js} +7 -6
- package/dist/render-BL5ynRkN.js.map +1 -0
- package/dist/runtime.d.ts +266 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +984 -0
- package/dist/runtime.js.map +1 -0
- package/dist/session-BbN0SBgf.js +1488 -0
- package/dist/session-BbN0SBgf.js.map +1 -0
- package/dist/{types-CeRKmEQ1.d.ts → types-DXxLBQc3.d.ts} +40 -3
- package/dist/types-DXxLBQc3.d.ts.map +1 -0
- package/package.json +5 -3
- package/dist/acp-jsonrpc-BbBgC5gO.js.map +0 -1
- package/dist/cli-idpWyCOs.js.map +0 -1
- package/dist/flags-CCcX9fZj.js.map +0 -1
- package/dist/flows-BL1tSvZT.js.map +0 -1
- package/dist/output-Du3m6oPQ.js.map +0 -1
- package/dist/output-render-Bz58qaQn.js.map +0 -1
- package/dist/queue-ipc-CE8_QGX3.js.map +0 -1
- package/dist/session-RO_LZUnv.js.map +0 -1
- package/dist/types-CeRKmEQ1.d.ts.map +0 -1
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
import { g as textPrompt, x as normalizeOutputError } from "./perf-metrics-D9QC81lB.js";
|
|
2
|
+
import { i as resolveAgentCommand, n as listBuiltInAgents, t as DEFAULT_AGENT_NAME } from "./agent-registry-DGw0-3Tc.js";
|
|
3
|
+
import { B as serializeSessionRecordForDisk, M as parseSessionRecord, P as defaultSessionEventLog, _ as recordSessionUpdate, a as applyConversation, f as cloneSessionAcpxState, g as recordPromptSubmission, h as recordClientOperation, i as connectAndLoadSession, l as setDesiredModeId, m as createSessionConversation, n as withConnectedSession, o as applyLifecycleSnapshotToRecord, p as cloneSessionConversation, s as reconcileAgentSessionId, t as runPromptTurn, v as trimConversationForRuntime, y as AcpClient, z as assertPersistedKeyPolicy } from "./prompt-turn-Bt8T3SRR.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
//#region src/runtime/public/errors.ts
|
|
8
|
+
var AcpRuntimeError = class extends Error {
|
|
9
|
+
code;
|
|
10
|
+
cause;
|
|
11
|
+
constructor(code, message, options) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "AcpRuntimeError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.cause = options?.cause;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function isAcpRuntimeError(value) {
|
|
19
|
+
return value instanceof AcpRuntimeError;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/runtime/public/shared.ts
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
function asTrimmedString(value) {
|
|
27
|
+
return typeof value === "string" ? value.trim() : "";
|
|
28
|
+
}
|
|
29
|
+
function asString(value) {
|
|
30
|
+
return typeof value === "string" ? value : void 0;
|
|
31
|
+
}
|
|
32
|
+
function asOptionalString(value) {
|
|
33
|
+
return asTrimmedString(value) || void 0;
|
|
34
|
+
}
|
|
35
|
+
function asOptionalBoolean(value) {
|
|
36
|
+
return typeof value === "boolean" ? value : void 0;
|
|
37
|
+
}
|
|
38
|
+
function deriveAgentFromSessionKey(sessionKey, fallbackAgent) {
|
|
39
|
+
const match = sessionKey.match(/^agent:([^:]+):/i);
|
|
40
|
+
return (match?.[1] ? asTrimmedString(match[1]) : "") || fallbackAgent;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/runtime/public/events.ts
|
|
44
|
+
function safeParseJsonObject(line) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(line);
|
|
47
|
+
return isRecord(parsed) ? parsed : null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function asOptionalFiniteNumber(value) {
|
|
53
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
54
|
+
}
|
|
55
|
+
function resolveStructuredPromptPayload(parsed) {
|
|
56
|
+
if (asTrimmedString(parsed.method) === "session/update") {
|
|
57
|
+
const params = parsed.params;
|
|
58
|
+
if (isRecord(params) && isRecord(params.update)) {
|
|
59
|
+
const update = params.update;
|
|
60
|
+
const tag = asOptionalString(update.sessionUpdate);
|
|
61
|
+
return {
|
|
62
|
+
type: tag ?? "",
|
|
63
|
+
payload: update,
|
|
64
|
+
...tag ? { tag } : {}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const sessionUpdate = asOptionalString(parsed.sessionUpdate);
|
|
69
|
+
if (sessionUpdate) return {
|
|
70
|
+
type: sessionUpdate,
|
|
71
|
+
payload: parsed,
|
|
72
|
+
tag: sessionUpdate
|
|
73
|
+
};
|
|
74
|
+
const type = asTrimmedString(parsed.type);
|
|
75
|
+
const tag = asOptionalString(parsed.tag);
|
|
76
|
+
return {
|
|
77
|
+
type,
|
|
78
|
+
payload: parsed,
|
|
79
|
+
...tag ? { tag } : {}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function resolveStatusTextForTag(params) {
|
|
83
|
+
const { tag, payload } = params;
|
|
84
|
+
if (tag === "available_commands_update") {
|
|
85
|
+
const commands = Array.isArray(payload.availableCommands) ? payload.availableCommands : [];
|
|
86
|
+
return commands.length > 0 ? `available commands updated (${commands.length})` : "available commands updated";
|
|
87
|
+
}
|
|
88
|
+
if (tag === "current_mode_update") {
|
|
89
|
+
const mode = asTrimmedString(payload.currentModeId) || asTrimmedString(payload.modeId) || asTrimmedString(payload.mode);
|
|
90
|
+
return mode ? `mode updated: ${mode}` : "mode updated";
|
|
91
|
+
}
|
|
92
|
+
if (tag === "config_option_update") {
|
|
93
|
+
const id = asTrimmedString(payload.id) || asTrimmedString(payload.configOptionId);
|
|
94
|
+
const value = asTrimmedString(payload.currentValue) || asTrimmedString(payload.value) || asTrimmedString(payload.optionValue);
|
|
95
|
+
if (id && value) return `config updated: ${id}=${value}`;
|
|
96
|
+
if (id) return `config updated: ${id}`;
|
|
97
|
+
return "config updated";
|
|
98
|
+
}
|
|
99
|
+
if (tag === "session_info_update") return asTrimmedString(payload.summary) || asTrimmedString(payload.message) || "session updated";
|
|
100
|
+
if (tag === "plan") {
|
|
101
|
+
const content = asTrimmedString((Array.isArray(payload.entries) ? payload.entries : []).find((entry) => isRecord(entry))?.content);
|
|
102
|
+
return content ? `plan: ${content}` : null;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
function resolveTextChunk(params) {
|
|
107
|
+
const contentRaw = params.payload.content;
|
|
108
|
+
if (isRecord(contentRaw)) {
|
|
109
|
+
const contentType = asTrimmedString(contentRaw.type);
|
|
110
|
+
if (contentType && contentType !== "text") return null;
|
|
111
|
+
const text = asString(contentRaw.text);
|
|
112
|
+
if (text && text.length > 0) return {
|
|
113
|
+
type: "text_delta",
|
|
114
|
+
text,
|
|
115
|
+
stream: params.stream,
|
|
116
|
+
tag: params.tag
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const text = asString(params.payload.text);
|
|
120
|
+
if (!text || text.length === 0) return null;
|
|
121
|
+
return {
|
|
122
|
+
type: "text_delta",
|
|
123
|
+
text,
|
|
124
|
+
stream: params.stream,
|
|
125
|
+
tag: params.tag
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function createTextDeltaEvent(params) {
|
|
129
|
+
if (params.content == null || params.content.length === 0) return null;
|
|
130
|
+
return {
|
|
131
|
+
type: "text_delta",
|
|
132
|
+
text: params.content,
|
|
133
|
+
stream: params.stream,
|
|
134
|
+
...params.tag ? { tag: params.tag } : {}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function createToolCallEvent(params) {
|
|
138
|
+
const title = asTrimmedString(params.payload.title) || "tool call";
|
|
139
|
+
const status = asTrimmedString(params.payload.status);
|
|
140
|
+
const toolCallId = asOptionalString(params.payload.toolCallId);
|
|
141
|
+
return {
|
|
142
|
+
type: "tool_call",
|
|
143
|
+
text: status ? `${title} (${status})` : title,
|
|
144
|
+
tag: params.tag,
|
|
145
|
+
...toolCallId ? { toolCallId } : {},
|
|
146
|
+
...status ? { status } : {},
|
|
147
|
+
title
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function parsePromptEventLine(line) {
|
|
151
|
+
const trimmed = line.trim();
|
|
152
|
+
if (!trimmed) return null;
|
|
153
|
+
const parsed = safeParseJsonObject(trimmed);
|
|
154
|
+
if (!parsed) return {
|
|
155
|
+
type: "status",
|
|
156
|
+
text: trimmed
|
|
157
|
+
};
|
|
158
|
+
const structured = resolveStructuredPromptPayload(parsed);
|
|
159
|
+
const type = structured.type;
|
|
160
|
+
const payload = structured.payload;
|
|
161
|
+
const tag = structured.tag;
|
|
162
|
+
switch (type) {
|
|
163
|
+
case "text": return createTextDeltaEvent({
|
|
164
|
+
content: asString(payload.content),
|
|
165
|
+
stream: "output",
|
|
166
|
+
tag
|
|
167
|
+
});
|
|
168
|
+
case "thought": return createTextDeltaEvent({
|
|
169
|
+
content: asString(payload.content),
|
|
170
|
+
stream: "thought",
|
|
171
|
+
tag
|
|
172
|
+
});
|
|
173
|
+
case "tool_call": return createToolCallEvent({
|
|
174
|
+
payload,
|
|
175
|
+
tag: tag ?? "tool_call"
|
|
176
|
+
});
|
|
177
|
+
case "tool_call_update": return createToolCallEvent({
|
|
178
|
+
payload,
|
|
179
|
+
tag: tag ?? "tool_call_update"
|
|
180
|
+
});
|
|
181
|
+
case "agent_message_chunk": return resolveTextChunk({
|
|
182
|
+
payload,
|
|
183
|
+
stream: "output",
|
|
184
|
+
tag: "agent_message_chunk"
|
|
185
|
+
});
|
|
186
|
+
case "agent_thought_chunk": return resolveTextChunk({
|
|
187
|
+
payload,
|
|
188
|
+
stream: "thought",
|
|
189
|
+
tag: "agent_thought_chunk"
|
|
190
|
+
});
|
|
191
|
+
case "usage_update": {
|
|
192
|
+
const used = asOptionalFiniteNumber(payload.used);
|
|
193
|
+
const size = asOptionalFiniteNumber(payload.size);
|
|
194
|
+
return {
|
|
195
|
+
type: "status",
|
|
196
|
+
text: used != null && size != null ? `usage updated: ${used}/${size}` : "usage updated",
|
|
197
|
+
tag: "usage_update",
|
|
198
|
+
...used != null ? { used } : {},
|
|
199
|
+
...size != null ? { size } : {}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
case "available_commands_update":
|
|
203
|
+
case "current_mode_update":
|
|
204
|
+
case "config_option_update":
|
|
205
|
+
case "session_info_update":
|
|
206
|
+
case "plan": {
|
|
207
|
+
const text = resolveStatusTextForTag({
|
|
208
|
+
tag: type,
|
|
209
|
+
payload
|
|
210
|
+
});
|
|
211
|
+
if (!text) return null;
|
|
212
|
+
return {
|
|
213
|
+
type: "status",
|
|
214
|
+
text,
|
|
215
|
+
tag: type
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
case "client_operation": {
|
|
219
|
+
const text = [
|
|
220
|
+
asTrimmedString(payload.method) || "operation",
|
|
221
|
+
asTrimmedString(payload.status),
|
|
222
|
+
asTrimmedString(payload.summary)
|
|
223
|
+
].filter(Boolean).join(" ");
|
|
224
|
+
if (!text) return null;
|
|
225
|
+
return {
|
|
226
|
+
type: "status",
|
|
227
|
+
text,
|
|
228
|
+
...tag ? { tag } : {}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
case "update": {
|
|
232
|
+
const update = asTrimmedString(payload.update);
|
|
233
|
+
if (!update) return null;
|
|
234
|
+
return {
|
|
235
|
+
type: "status",
|
|
236
|
+
text: update,
|
|
237
|
+
...tag ? { tag } : {}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
case "done": return {
|
|
241
|
+
type: "done",
|
|
242
|
+
stopReason: asOptionalString(payload.stopReason)
|
|
243
|
+
};
|
|
244
|
+
case "error": return {
|
|
245
|
+
type: "error",
|
|
246
|
+
message: asTrimmedString(payload.message) || "acpx runtime error",
|
|
247
|
+
code: asOptionalString(payload.code),
|
|
248
|
+
retryable: asOptionalBoolean(payload.retryable)
|
|
249
|
+
};
|
|
250
|
+
default: return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/runtime/engine/reuse-policy.ts
|
|
255
|
+
function shouldReuseExistingRecord(record, params) {
|
|
256
|
+
if (path.resolve(record.cwd) !== path.resolve(params.cwd)) return false;
|
|
257
|
+
if (record.agentCommand !== params.agentCommand) return false;
|
|
258
|
+
if (params.resumeSessionId && record.acpSessionId !== params.resumeSessionId) return false;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/runtime/engine/manager.ts
|
|
263
|
+
function createDeferred() {
|
|
264
|
+
let resolve;
|
|
265
|
+
let reject;
|
|
266
|
+
return {
|
|
267
|
+
promise: new Promise((res, rej) => {
|
|
268
|
+
resolve = res;
|
|
269
|
+
reject = rej;
|
|
270
|
+
}),
|
|
271
|
+
resolve,
|
|
272
|
+
reject
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
var AsyncEventQueue = class {
|
|
276
|
+
items = [];
|
|
277
|
+
waits = [];
|
|
278
|
+
closed = false;
|
|
279
|
+
push(item) {
|
|
280
|
+
if (this.closed) return;
|
|
281
|
+
const waiter = this.waits.shift();
|
|
282
|
+
if (waiter) {
|
|
283
|
+
waiter.resolve(item);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
this.items.push(item);
|
|
287
|
+
}
|
|
288
|
+
close() {
|
|
289
|
+
if (this.closed) return;
|
|
290
|
+
this.closed = true;
|
|
291
|
+
for (const waiter of this.waits.splice(0)) waiter.resolve(null);
|
|
292
|
+
}
|
|
293
|
+
async next() {
|
|
294
|
+
if (this.items.length > 0) return this.items.shift() ?? null;
|
|
295
|
+
if (this.closed) return null;
|
|
296
|
+
const waiter = createDeferred();
|
|
297
|
+
this.waits.push(waiter);
|
|
298
|
+
return await waiter.promise;
|
|
299
|
+
}
|
|
300
|
+
async *iterate() {
|
|
301
|
+
while (true) {
|
|
302
|
+
const next = await this.next();
|
|
303
|
+
if (!next) return;
|
|
304
|
+
yield next;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
function isoNow() {
|
|
309
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
310
|
+
}
|
|
311
|
+
function toPromptInput(text, attachments) {
|
|
312
|
+
if (!attachments || attachments.length === 0) return text;
|
|
313
|
+
const blocks = [];
|
|
314
|
+
if (text) blocks.push({
|
|
315
|
+
type: "text",
|
|
316
|
+
text
|
|
317
|
+
});
|
|
318
|
+
for (const attachment of attachments) {
|
|
319
|
+
if (!attachment.mediaType.startsWith("image/")) throw new AcpRuntimeError("ACP_TURN_FAILED", `Unsupported ACP runtime attachment media type: ${attachment.mediaType}`);
|
|
320
|
+
blocks.push({
|
|
321
|
+
type: "image",
|
|
322
|
+
mimeType: attachment.mediaType,
|
|
323
|
+
data: attachment.data
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return blocks.length > 0 ? blocks : textPrompt(text);
|
|
327
|
+
}
|
|
328
|
+
function createInitialRecord(params) {
|
|
329
|
+
const now = isoNow();
|
|
330
|
+
return {
|
|
331
|
+
schema: "acpx.session.v1",
|
|
332
|
+
acpxRecordId: params.recordId,
|
|
333
|
+
acpSessionId: params.sessionId,
|
|
334
|
+
agentSessionId: params.agentSessionId,
|
|
335
|
+
agentCommand: params.agentCommand,
|
|
336
|
+
cwd: params.cwd,
|
|
337
|
+
name: params.sessionName,
|
|
338
|
+
createdAt: now,
|
|
339
|
+
lastUsedAt: now,
|
|
340
|
+
lastSeq: 0,
|
|
341
|
+
eventLog: defaultSessionEventLog(params.recordId),
|
|
342
|
+
closed: false,
|
|
343
|
+
closedAt: void 0,
|
|
344
|
+
...createSessionConversation(now),
|
|
345
|
+
acpx: {}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function createRecordId(sessionKey, mode) {
|
|
349
|
+
if (mode === "persistent") return sessionKey;
|
|
350
|
+
return `${sessionKey}:oneshot:${randomUUID()}`;
|
|
351
|
+
}
|
|
352
|
+
function resumePolicyForSessionMode(mode) {
|
|
353
|
+
return mode === "persistent" ? "same-session-only" : "allow-new";
|
|
354
|
+
}
|
|
355
|
+
function statusSummary(record) {
|
|
356
|
+
return [
|
|
357
|
+
`session=${record.acpxRecordId}`,
|
|
358
|
+
`backendSessionId=${record.acpSessionId}`,
|
|
359
|
+
record.agentSessionId ? `agentSessionId=${record.agentSessionId}` : null,
|
|
360
|
+
record.pid != null ? `pid=${record.pid}` : null,
|
|
361
|
+
record.closed ? "closed" : "open"
|
|
362
|
+
].filter(Boolean).join(" ");
|
|
363
|
+
}
|
|
364
|
+
var AcpRuntimeManager = class {
|
|
365
|
+
activeControllers = /* @__PURE__ */ new Map();
|
|
366
|
+
pendingPersistentClients = /* @__PURE__ */ new Map();
|
|
367
|
+
constructor(options, deps = {}) {
|
|
368
|
+
this.options = options;
|
|
369
|
+
this.deps = deps;
|
|
370
|
+
}
|
|
371
|
+
createClient(options) {
|
|
372
|
+
return this.deps.clientFactory?.(options) ?? new AcpClient(options);
|
|
373
|
+
}
|
|
374
|
+
async ensureSession(input) {
|
|
375
|
+
const cwd = path.resolve(input.cwd?.trim() || this.options.cwd);
|
|
376
|
+
const agentCommand = this.options.agentRegistry.resolve(input.agent);
|
|
377
|
+
const existing = await this.options.sessionStore.load(input.sessionKey);
|
|
378
|
+
if (input.mode === "persistent" && existing && shouldReuseExistingRecord(existing, {
|
|
379
|
+
cwd,
|
|
380
|
+
agentCommand,
|
|
381
|
+
resumeSessionId: input.resumeSessionId
|
|
382
|
+
})) {
|
|
383
|
+
existing.closed = false;
|
|
384
|
+
existing.closedAt = void 0;
|
|
385
|
+
await this.options.sessionStore.save(existing);
|
|
386
|
+
return existing;
|
|
387
|
+
}
|
|
388
|
+
const client = this.createClient({
|
|
389
|
+
agentCommand,
|
|
390
|
+
cwd,
|
|
391
|
+
mcpServers: [...this.options.mcpServers ?? []],
|
|
392
|
+
permissionMode: this.options.permissionMode,
|
|
393
|
+
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
394
|
+
verbose: this.options.verbose
|
|
395
|
+
});
|
|
396
|
+
let keepClientOpen = false;
|
|
397
|
+
try {
|
|
398
|
+
await client.start();
|
|
399
|
+
let sessionId;
|
|
400
|
+
let agentSessionId;
|
|
401
|
+
if (input.resumeSessionId) {
|
|
402
|
+
const loaded = await client.loadSession(input.resumeSessionId, cwd);
|
|
403
|
+
sessionId = input.resumeSessionId;
|
|
404
|
+
agentSessionId = loaded.agentSessionId;
|
|
405
|
+
} else {
|
|
406
|
+
const created = await client.createSession(cwd);
|
|
407
|
+
sessionId = created.sessionId;
|
|
408
|
+
agentSessionId = created.agentSessionId;
|
|
409
|
+
}
|
|
410
|
+
const record = createInitialRecord({
|
|
411
|
+
recordId: createRecordId(input.sessionKey, input.mode),
|
|
412
|
+
sessionName: input.sessionKey,
|
|
413
|
+
sessionId,
|
|
414
|
+
agentCommand,
|
|
415
|
+
cwd,
|
|
416
|
+
agentSessionId
|
|
417
|
+
});
|
|
418
|
+
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
419
|
+
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
420
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
421
|
+
await this.options.sessionStore.save(record);
|
|
422
|
+
if (input.mode === "persistent") {
|
|
423
|
+
const previousClient = this.pendingPersistentClients.get(record.acpxRecordId);
|
|
424
|
+
this.pendingPersistentClients.set(record.acpxRecordId, client);
|
|
425
|
+
keepClientOpen = true;
|
|
426
|
+
await previousClient?.close().catch(() => {});
|
|
427
|
+
}
|
|
428
|
+
return record;
|
|
429
|
+
} finally {
|
|
430
|
+
if (!keepClientOpen) await client.close();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async *runTurn(input) {
|
|
434
|
+
const record = await this.requireRecord(input.handle.acpxRecordId ?? input.handle.sessionKey);
|
|
435
|
+
const conversation = cloneSessionConversation(record);
|
|
436
|
+
let acpxState = cloneSessionAcpxState(record.acpx);
|
|
437
|
+
const promptInput = toPromptInput(input.text, input.attachments);
|
|
438
|
+
const promptMessageId = recordPromptSubmission(conversation, promptInput, isoNow());
|
|
439
|
+
trimConversationForRuntime(conversation);
|
|
440
|
+
const queue = new AsyncEventQueue();
|
|
441
|
+
let pendingClient = this.pendingPersistentClients.get(record.acpxRecordId);
|
|
442
|
+
if (pendingClient) {
|
|
443
|
+
this.pendingPersistentClients.delete(record.acpxRecordId);
|
|
444
|
+
if (!pendingClient.hasReusableSession(record.acpSessionId)) {
|
|
445
|
+
await pendingClient.close().catch(() => {});
|
|
446
|
+
pendingClient = void 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const client = pendingClient ?? this.createClient({
|
|
450
|
+
agentCommand: record.agentCommand,
|
|
451
|
+
cwd: record.cwd,
|
|
452
|
+
mcpServers: [...this.options.mcpServers ?? []],
|
|
453
|
+
permissionMode: this.options.permissionMode,
|
|
454
|
+
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
455
|
+
verbose: this.options.verbose
|
|
456
|
+
});
|
|
457
|
+
let activeSessionId = record.acpSessionId;
|
|
458
|
+
let sawDone = false;
|
|
459
|
+
let pendingCancel = false;
|
|
460
|
+
let turnActive = true;
|
|
461
|
+
const sessionReady = createDeferred();
|
|
462
|
+
sessionReady.promise.catch(() => {});
|
|
463
|
+
const applyPendingCancel = async () => {
|
|
464
|
+
if (!pendingCancel || !client.hasActivePrompt()) return false;
|
|
465
|
+
const cancelled = await client.requestCancelActivePrompt();
|
|
466
|
+
if (cancelled) pendingCancel = false;
|
|
467
|
+
return cancelled;
|
|
468
|
+
};
|
|
469
|
+
const activeController = {
|
|
470
|
+
hasActivePrompt: () => client.hasActivePrompt(),
|
|
471
|
+
requestCancelActivePrompt: async () => {
|
|
472
|
+
if (client.hasActivePrompt()) return await client.requestCancelActivePrompt();
|
|
473
|
+
if (!turnActive) return false;
|
|
474
|
+
pendingCancel = true;
|
|
475
|
+
return true;
|
|
476
|
+
},
|
|
477
|
+
setSessionMode: async (modeId) => {
|
|
478
|
+
if (!client.hasActivePrompt()) await sessionReady.promise;
|
|
479
|
+
await client.setSessionMode(activeSessionId, modeId);
|
|
480
|
+
},
|
|
481
|
+
setSessionModel: async (modelId) => {
|
|
482
|
+
if (!client.hasActivePrompt()) await sessionReady.promise;
|
|
483
|
+
await client.setSessionModel(activeSessionId, modelId);
|
|
484
|
+
},
|
|
485
|
+
setSessionConfigOption: async (configId, value) => {
|
|
486
|
+
if (!client.hasActivePrompt()) await sessionReady.promise;
|
|
487
|
+
return await client.setSessionConfigOption(activeSessionId, configId, value);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
const emitParsed = (payload) => {
|
|
491
|
+
const parsed = parsePromptEventLine(JSON.stringify(payload));
|
|
492
|
+
if (!parsed) return;
|
|
493
|
+
if (parsed.type === "done") sawDone = true;
|
|
494
|
+
queue.push(parsed);
|
|
495
|
+
};
|
|
496
|
+
const abortHandler = () => {
|
|
497
|
+
activeController.requestCancelActivePrompt();
|
|
498
|
+
};
|
|
499
|
+
if (input.signal) {
|
|
500
|
+
if (input.signal.aborted) {
|
|
501
|
+
queue.close();
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
input.signal.addEventListener("abort", abortHandler, { once: true });
|
|
505
|
+
}
|
|
506
|
+
this.activeControllers.set(record.acpxRecordId, activeController);
|
|
507
|
+
(async () => {
|
|
508
|
+
try {
|
|
509
|
+
client.setEventHandlers({
|
|
510
|
+
onSessionUpdate: (notification) => {
|
|
511
|
+
acpxState = recordSessionUpdate(conversation, acpxState, notification);
|
|
512
|
+
trimConversationForRuntime(conversation);
|
|
513
|
+
emitParsed({
|
|
514
|
+
jsonrpc: "2.0",
|
|
515
|
+
method: "session/update",
|
|
516
|
+
params: notification
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
onClientOperation: (operation) => {
|
|
520
|
+
acpxState = recordClientOperation(conversation, acpxState, operation);
|
|
521
|
+
trimConversationForRuntime(conversation);
|
|
522
|
+
emitParsed({
|
|
523
|
+
type: "client_operation",
|
|
524
|
+
...operation
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
const { sessionId, resumed, loadError } = pendingClient ? {
|
|
529
|
+
sessionId: record.acpSessionId,
|
|
530
|
+
resumed: false,
|
|
531
|
+
loadError: void 0
|
|
532
|
+
} : await connectAndLoadSession({
|
|
533
|
+
client,
|
|
534
|
+
record,
|
|
535
|
+
resumePolicy: resumePolicyForSessionMode(input.sessionMode),
|
|
536
|
+
timeoutMs: this.options.timeoutMs,
|
|
537
|
+
activeController,
|
|
538
|
+
onClientAvailable: (controller) => {
|
|
539
|
+
this.activeControllers.set(record.acpxRecordId, controller);
|
|
540
|
+
},
|
|
541
|
+
onConnectedRecord: (connectedRecord) => {
|
|
542
|
+
connectedRecord.lastPromptAt = isoNow();
|
|
543
|
+
},
|
|
544
|
+
onSessionIdResolved: (sessionIdValue) => {
|
|
545
|
+
activeSessionId = sessionIdValue;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
sessionReady.resolve();
|
|
549
|
+
record.lastRequestId = input.requestId;
|
|
550
|
+
record.lastPromptAt = isoNow();
|
|
551
|
+
record.closed = false;
|
|
552
|
+
record.closedAt = void 0;
|
|
553
|
+
record.lastUsedAt = isoNow();
|
|
554
|
+
if (resumed || loadError) emitParsed({
|
|
555
|
+
type: "status",
|
|
556
|
+
text: loadError ? `load fallback: ${loadError}` : "session resumed"
|
|
557
|
+
});
|
|
558
|
+
if (pendingCancel || input.signal?.aborted) {
|
|
559
|
+
pendingCancel = false;
|
|
560
|
+
if (!sawDone) queue.push({
|
|
561
|
+
type: "done",
|
|
562
|
+
stopReason: "cancelled"
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
await applyPendingCancel();
|
|
567
|
+
const response = await runPromptTurn({
|
|
568
|
+
client,
|
|
569
|
+
sessionId,
|
|
570
|
+
prompt: promptInput,
|
|
571
|
+
timeoutMs: input.timeoutMs ?? this.options.timeoutMs,
|
|
572
|
+
conversation,
|
|
573
|
+
promptMessageId
|
|
574
|
+
});
|
|
575
|
+
record.acpSessionId = activeSessionId;
|
|
576
|
+
reconcileAgentSessionId(record, record.agentSessionId);
|
|
577
|
+
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
578
|
+
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
579
|
+
record.acpx = acpxState;
|
|
580
|
+
applyConversation(record, conversation);
|
|
581
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
582
|
+
await this.options.sessionStore.save(record);
|
|
583
|
+
if (!sawDone) queue.push({
|
|
584
|
+
type: "done",
|
|
585
|
+
stopReason: response.stopReason
|
|
586
|
+
});
|
|
587
|
+
} catch (error) {
|
|
588
|
+
sessionReady.reject(error);
|
|
589
|
+
const normalized = normalizeOutputError(error, { origin: "runtime" });
|
|
590
|
+
queue.push({
|
|
591
|
+
type: "error",
|
|
592
|
+
message: normalized.message,
|
|
593
|
+
code: normalized.code,
|
|
594
|
+
retryable: normalized.retryable
|
|
595
|
+
});
|
|
596
|
+
} finally {
|
|
597
|
+
turnActive = false;
|
|
598
|
+
if (input.signal) input.signal.removeEventListener("abort", abortHandler);
|
|
599
|
+
this.activeControllers.delete(record.acpxRecordId);
|
|
600
|
+
client.clearEventHandlers();
|
|
601
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
602
|
+
record.acpx = acpxState;
|
|
603
|
+
applyConversation(record, conversation);
|
|
604
|
+
record.lastUsedAt = isoNow();
|
|
605
|
+
await this.options.sessionStore.save(record).catch(() => {});
|
|
606
|
+
await client.close().catch(() => {});
|
|
607
|
+
queue.close();
|
|
608
|
+
}
|
|
609
|
+
})();
|
|
610
|
+
yield* queue.iterate();
|
|
611
|
+
}
|
|
612
|
+
async getStatus(handle) {
|
|
613
|
+
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
614
|
+
return {
|
|
615
|
+
summary: statusSummary(record),
|
|
616
|
+
acpxRecordId: record.acpxRecordId,
|
|
617
|
+
backendSessionId: record.acpSessionId,
|
|
618
|
+
agentSessionId: record.agentSessionId,
|
|
619
|
+
details: {
|
|
620
|
+
cwd: record.cwd,
|
|
621
|
+
lastUsedAt: record.lastUsedAt,
|
|
622
|
+
closed: record.closed === true
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
async setMode(handle, mode, sessionMode = "persistent") {
|
|
627
|
+
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
628
|
+
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
629
|
+
let targetRecord = record;
|
|
630
|
+
if (controller) await controller.setSessionMode(mode);
|
|
631
|
+
else targetRecord = (await withConnectedSession({
|
|
632
|
+
sessionRecordId: record.acpxRecordId,
|
|
633
|
+
loadRecord: async (sessionRecordId) => await this.requireRecord(sessionRecordId),
|
|
634
|
+
saveRecord: async (connectedRecord) => await this.options.sessionStore.save(connectedRecord),
|
|
635
|
+
createClient: (options) => this.createClient(options),
|
|
636
|
+
mcpServers: [...this.options.mcpServers ?? []],
|
|
637
|
+
permissionMode: this.options.permissionMode,
|
|
638
|
+
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
639
|
+
verbose: this.options.verbose,
|
|
640
|
+
timeoutMs: this.options.timeoutMs,
|
|
641
|
+
resumePolicy: resumePolicyForSessionMode(sessionMode),
|
|
642
|
+
run: async ({ client, sessionId }) => {
|
|
643
|
+
await client.setSessionMode(sessionId, mode);
|
|
644
|
+
}
|
|
645
|
+
})).record;
|
|
646
|
+
setDesiredModeId(targetRecord, mode);
|
|
647
|
+
await this.options.sessionStore.save(targetRecord);
|
|
648
|
+
}
|
|
649
|
+
async setConfigOption(handle, key, value, sessionMode = "persistent") {
|
|
650
|
+
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
651
|
+
const controller = this.activeControllers.get(record.acpxRecordId);
|
|
652
|
+
let targetRecord = record;
|
|
653
|
+
if (controller) await controller.setSessionConfigOption(key, value);
|
|
654
|
+
else targetRecord = (await withConnectedSession({
|
|
655
|
+
sessionRecordId: record.acpxRecordId,
|
|
656
|
+
loadRecord: async (sessionRecordId) => await this.requireRecord(sessionRecordId),
|
|
657
|
+
saveRecord: async (connectedRecord) => await this.options.sessionStore.save(connectedRecord),
|
|
658
|
+
createClient: (options) => this.createClient(options),
|
|
659
|
+
mcpServers: [...this.options.mcpServers ?? []],
|
|
660
|
+
permissionMode: this.options.permissionMode,
|
|
661
|
+
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
662
|
+
verbose: this.options.verbose,
|
|
663
|
+
timeoutMs: this.options.timeoutMs,
|
|
664
|
+
resumePolicy: resumePolicyForSessionMode(sessionMode),
|
|
665
|
+
run: async ({ client, sessionId, record: connectedRecord }) => {
|
|
666
|
+
await client.setSessionConfigOption(sessionId, key, value);
|
|
667
|
+
if (key === "mode") setDesiredModeId(connectedRecord, value);
|
|
668
|
+
}
|
|
669
|
+
})).record;
|
|
670
|
+
if (key === "mode") setDesiredModeId(targetRecord, value);
|
|
671
|
+
await this.options.sessionStore.save(targetRecord);
|
|
672
|
+
}
|
|
673
|
+
async cancel(handle) {
|
|
674
|
+
await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
|
|
675
|
+
}
|
|
676
|
+
async close(handle) {
|
|
677
|
+
const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
|
|
678
|
+
await this.cancel(handle);
|
|
679
|
+
record.closed = true;
|
|
680
|
+
record.closedAt = isoNow();
|
|
681
|
+
await this.options.sessionStore.save(record);
|
|
682
|
+
}
|
|
683
|
+
async requireRecord(sessionId) {
|
|
684
|
+
const record = await this.options.sessionStore.load(sessionId);
|
|
685
|
+
if (!record) throw new Error(`ACP session not found: ${sessionId}`);
|
|
686
|
+
return record;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/runtime/public/file-session-store.ts
|
|
691
|
+
function safeSessionId(sessionId) {
|
|
692
|
+
return encodeURIComponent(sessionId);
|
|
693
|
+
}
|
|
694
|
+
var FileSessionStore = class {
|
|
695
|
+
constructor(stateDir) {
|
|
696
|
+
this.stateDir = stateDir;
|
|
697
|
+
}
|
|
698
|
+
get sessionDir() {
|
|
699
|
+
return path.join(this.stateDir, "sessions");
|
|
700
|
+
}
|
|
701
|
+
filePath(sessionId) {
|
|
702
|
+
return path.join(this.sessionDir, `${safeSessionId(sessionId)}.json`);
|
|
703
|
+
}
|
|
704
|
+
async ensureDir() {
|
|
705
|
+
await fs.mkdir(this.sessionDir, { recursive: true });
|
|
706
|
+
}
|
|
707
|
+
async load(sessionId) {
|
|
708
|
+
await this.ensureDir();
|
|
709
|
+
try {
|
|
710
|
+
const payload = await fs.readFile(this.filePath(sessionId), "utf8");
|
|
711
|
+
return parseSessionRecord(JSON.parse(payload)) ?? void 0;
|
|
712
|
+
} catch (error) {
|
|
713
|
+
if (error.code === "ENOENT") return;
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async save(record) {
|
|
718
|
+
await this.ensureDir();
|
|
719
|
+
const persisted = serializeSessionRecordForDisk(record);
|
|
720
|
+
assertPersistedKeyPolicy(persisted);
|
|
721
|
+
const file = this.filePath(record.acpxRecordId);
|
|
722
|
+
const tempFile = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
723
|
+
const payload = JSON.stringify(persisted, null, 2);
|
|
724
|
+
await fs.writeFile(tempFile, `${payload}\n`, "utf8");
|
|
725
|
+
await fs.rename(tempFile, file);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
function createFileSessionStore(options) {
|
|
729
|
+
return new FileSessionStore(path.resolve(options.stateDir));
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/runtime/public/handle-state.ts
|
|
733
|
+
const ACPX_RUNTIME_HANDLE_PREFIX = "acpx:v2:";
|
|
734
|
+
function encodeAcpxRuntimeHandleState(state) {
|
|
735
|
+
return `${ACPX_RUNTIME_HANDLE_PREFIX}${Buffer.from(JSON.stringify(state), "utf8").toString("base64url")}`;
|
|
736
|
+
}
|
|
737
|
+
function decodeAcpxRuntimeHandleState(runtimeSessionName) {
|
|
738
|
+
const trimmed = runtimeSessionName.trim();
|
|
739
|
+
if (!trimmed.startsWith(ACPX_RUNTIME_HANDLE_PREFIX)) return null;
|
|
740
|
+
try {
|
|
741
|
+
const raw = Buffer.from(trimmed.slice(8), "base64url").toString("utf8");
|
|
742
|
+
const parsed = JSON.parse(raw);
|
|
743
|
+
const name = asOptionalString(parsed.name);
|
|
744
|
+
const agent = asOptionalString(parsed.agent);
|
|
745
|
+
const cwd = asOptionalString(parsed.cwd);
|
|
746
|
+
const mode = asOptionalString(parsed.mode);
|
|
747
|
+
if (!name || !agent || !cwd || mode !== "persistent" && mode !== "oneshot") return null;
|
|
748
|
+
return {
|
|
749
|
+
name,
|
|
750
|
+
agent,
|
|
751
|
+
cwd,
|
|
752
|
+
mode,
|
|
753
|
+
acpxRecordId: asOptionalString(parsed.acpxRecordId),
|
|
754
|
+
backendSessionId: asOptionalString(parsed.backendSessionId),
|
|
755
|
+
agentSessionId: asOptionalString(parsed.agentSessionId)
|
|
756
|
+
};
|
|
757
|
+
} catch {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function writeHandleState(handle, state) {
|
|
762
|
+
handle.runtimeSessionName = encodeAcpxRuntimeHandleState(state);
|
|
763
|
+
handle.cwd = state.cwd;
|
|
764
|
+
handle.acpxRecordId = state.acpxRecordId;
|
|
765
|
+
handle.backendSessionId = state.backendSessionId;
|
|
766
|
+
handle.agentSessionId = state.agentSessionId;
|
|
767
|
+
}
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/runtime/public/probe.ts
|
|
770
|
+
async function probeRuntime(options, deps = {}) {
|
|
771
|
+
const agentName = options.probeAgent?.trim() || "codex";
|
|
772
|
+
const agentCommand = options.agentRegistry.resolve(agentName);
|
|
773
|
+
const client = deps.clientFactory?.({
|
|
774
|
+
agentCommand,
|
|
775
|
+
cwd: options.cwd,
|
|
776
|
+
mcpServers: [...options.mcpServers ?? []],
|
|
777
|
+
permissionMode: options.permissionMode,
|
|
778
|
+
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
779
|
+
verbose: options.verbose
|
|
780
|
+
}) ?? new AcpClient({
|
|
781
|
+
agentCommand,
|
|
782
|
+
cwd: options.cwd,
|
|
783
|
+
mcpServers: [...options.mcpServers ?? []],
|
|
784
|
+
permissionMode: options.permissionMode,
|
|
785
|
+
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
786
|
+
verbose: options.verbose
|
|
787
|
+
});
|
|
788
|
+
try {
|
|
789
|
+
await client.start();
|
|
790
|
+
return {
|
|
791
|
+
ok: true,
|
|
792
|
+
message: "embedded ACP runtime ready",
|
|
793
|
+
details: [
|
|
794
|
+
`agent=${agentName}`,
|
|
795
|
+
`command=${agentCommand}`,
|
|
796
|
+
`cwd=${options.cwd}`,
|
|
797
|
+
...client.initializeResult?.protocolVersion ? [`protocolVersion=${client.initializeResult.protocolVersion}`] : []
|
|
798
|
+
]
|
|
799
|
+
};
|
|
800
|
+
} catch (error) {
|
|
801
|
+
return {
|
|
802
|
+
ok: false,
|
|
803
|
+
message: "embedded ACP runtime probe failed",
|
|
804
|
+
details: [
|
|
805
|
+
`agent=${agentName}`,
|
|
806
|
+
`command=${agentCommand}`,
|
|
807
|
+
`cwd=${options.cwd}`,
|
|
808
|
+
error instanceof Error ? error.message : String(error)
|
|
809
|
+
]
|
|
810
|
+
};
|
|
811
|
+
} finally {
|
|
812
|
+
await client.close().catch(() => {});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region src/runtime.ts
|
|
817
|
+
const ACPX_BACKEND_ID = "acpx";
|
|
818
|
+
const ACPX_CAPABILITIES = { controls: [
|
|
819
|
+
"session/set_mode",
|
|
820
|
+
"session/set_config_option",
|
|
821
|
+
"session/status"
|
|
822
|
+
] };
|
|
823
|
+
function createAgentRegistry(params) {
|
|
824
|
+
return {
|
|
825
|
+
resolve(agentName) {
|
|
826
|
+
return resolveAgentCommand(agentName, params?.overrides);
|
|
827
|
+
},
|
|
828
|
+
list() {
|
|
829
|
+
return listBuiltInAgents(params?.overrides);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
var AcpxRuntime = class {
|
|
834
|
+
healthy = false;
|
|
835
|
+
manager = null;
|
|
836
|
+
managerPromise = null;
|
|
837
|
+
constructor(options, testOptions) {
|
|
838
|
+
this.options = options;
|
|
839
|
+
this.testOptions = testOptions;
|
|
840
|
+
}
|
|
841
|
+
isHealthy() {
|
|
842
|
+
return this.healthy;
|
|
843
|
+
}
|
|
844
|
+
async probeAvailability() {
|
|
845
|
+
this.healthy = (await this.runProbe()).ok;
|
|
846
|
+
}
|
|
847
|
+
async doctor() {
|
|
848
|
+
const report = await this.runProbe();
|
|
849
|
+
this.healthy = report.ok;
|
|
850
|
+
return {
|
|
851
|
+
ok: report.ok,
|
|
852
|
+
code: report.ok ? void 0 : "ACP_BACKEND_UNAVAILABLE",
|
|
853
|
+
message: report.message,
|
|
854
|
+
details: report.details
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
async ensureSession(input) {
|
|
858
|
+
const sessionName = input.sessionKey.trim();
|
|
859
|
+
if (!sessionName) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP session key is required.");
|
|
860
|
+
const agent = input.agent.trim();
|
|
861
|
+
if (!agent) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP agent id is required.");
|
|
862
|
+
const record = await (await this.getManager()).ensureSession({
|
|
863
|
+
sessionKey: sessionName,
|
|
864
|
+
agent,
|
|
865
|
+
mode: input.mode,
|
|
866
|
+
cwd: input.cwd ?? this.options.cwd,
|
|
867
|
+
resumeSessionId: input.resumeSessionId
|
|
868
|
+
});
|
|
869
|
+
const handle = {
|
|
870
|
+
sessionKey: input.sessionKey,
|
|
871
|
+
backend: ACPX_BACKEND_ID,
|
|
872
|
+
runtimeSessionName: "",
|
|
873
|
+
cwd: record.cwd,
|
|
874
|
+
acpxRecordId: record.acpxRecordId,
|
|
875
|
+
backendSessionId: record.acpSessionId,
|
|
876
|
+
agentSessionId: record.agentSessionId
|
|
877
|
+
};
|
|
878
|
+
writeHandleState(handle, {
|
|
879
|
+
name: sessionName,
|
|
880
|
+
agent,
|
|
881
|
+
cwd: record.cwd,
|
|
882
|
+
mode: input.mode,
|
|
883
|
+
acpxRecordId: record.acpxRecordId,
|
|
884
|
+
backendSessionId: record.acpSessionId,
|
|
885
|
+
agentSessionId: record.agentSessionId
|
|
886
|
+
});
|
|
887
|
+
return handle;
|
|
888
|
+
}
|
|
889
|
+
async *runTurn(input) {
|
|
890
|
+
const state = this.resolveHandleState(input.handle);
|
|
891
|
+
yield* (await this.getManager()).runTurn({
|
|
892
|
+
handle: {
|
|
893
|
+
...input.handle,
|
|
894
|
+
acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
|
|
895
|
+
},
|
|
896
|
+
text: input.text,
|
|
897
|
+
attachments: input.attachments,
|
|
898
|
+
mode: input.mode,
|
|
899
|
+
sessionMode: state.mode,
|
|
900
|
+
requestId: input.requestId,
|
|
901
|
+
timeoutMs: input.timeoutMs,
|
|
902
|
+
signal: input.signal
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
getCapabilities() {
|
|
906
|
+
return ACPX_CAPABILITIES;
|
|
907
|
+
}
|
|
908
|
+
async getStatus(input) {
|
|
909
|
+
const state = this.resolveHandleState(input.handle);
|
|
910
|
+
return await (await this.getManager()).getStatus({
|
|
911
|
+
...input.handle,
|
|
912
|
+
acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
async setMode(input) {
|
|
916
|
+
const state = this.resolveHandleState(input.handle);
|
|
917
|
+
await (await this.getManager()).setMode({
|
|
918
|
+
...input.handle,
|
|
919
|
+
acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
|
|
920
|
+
}, input.mode, state.mode);
|
|
921
|
+
}
|
|
922
|
+
async setConfigOption(input) {
|
|
923
|
+
const state = this.resolveHandleState(input.handle);
|
|
924
|
+
await (await this.getManager()).setConfigOption({
|
|
925
|
+
...input.handle,
|
|
926
|
+
acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
|
|
927
|
+
}, input.key, input.value, state.mode);
|
|
928
|
+
}
|
|
929
|
+
async cancel(input) {
|
|
930
|
+
const state = this.resolveHandleState(input.handle);
|
|
931
|
+
await (await this.getManager()).cancel({
|
|
932
|
+
...input.handle,
|
|
933
|
+
acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
async close(input) {
|
|
937
|
+
const state = this.resolveHandleState(input.handle);
|
|
938
|
+
await (await this.getManager()).close({
|
|
939
|
+
...input.handle,
|
|
940
|
+
acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
async getManager() {
|
|
944
|
+
if (this.manager) return this.manager;
|
|
945
|
+
if (!this.managerPromise) this.managerPromise = Promise.resolve(this.testOptions?.managerFactory?.(this.options) ?? new AcpRuntimeManager(this.options)).then((manager) => {
|
|
946
|
+
this.manager = manager;
|
|
947
|
+
return manager;
|
|
948
|
+
});
|
|
949
|
+
return await this.managerPromise;
|
|
950
|
+
}
|
|
951
|
+
async runProbe() {
|
|
952
|
+
return await (this.testOptions?.probeRunner?.(this.options) ?? probeRuntime(this.options));
|
|
953
|
+
}
|
|
954
|
+
resolveHandleState(handle) {
|
|
955
|
+
const decoded = decodeAcpxRuntimeHandleState(handle.runtimeSessionName);
|
|
956
|
+
if (decoded) return {
|
|
957
|
+
...decoded,
|
|
958
|
+
acpxRecordId: decoded.acpxRecordId ?? handle.acpxRecordId,
|
|
959
|
+
backendSessionId: decoded.backendSessionId ?? handle.backendSessionId,
|
|
960
|
+
agentSessionId: decoded.agentSessionId ?? handle.agentSessionId
|
|
961
|
+
};
|
|
962
|
+
const runtimeSessionName = handle.runtimeSessionName.trim();
|
|
963
|
+
if (!runtimeSessionName) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "Invalid embedded ACP runtime handle: runtimeSessionName is missing.");
|
|
964
|
+
return {
|
|
965
|
+
name: runtimeSessionName,
|
|
966
|
+
agent: deriveAgentFromSessionKey(handle.sessionKey, DEFAULT_AGENT_NAME),
|
|
967
|
+
cwd: handle.cwd ?? this.options.cwd,
|
|
968
|
+
mode: "persistent",
|
|
969
|
+
acpxRecordId: handle.acpxRecordId,
|
|
970
|
+
backendSessionId: handle.backendSessionId,
|
|
971
|
+
agentSessionId: handle.agentSessionId
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
function createAcpRuntime(options) {
|
|
976
|
+
return new AcpxRuntime(options);
|
|
977
|
+
}
|
|
978
|
+
function createRuntimeStore(options) {
|
|
979
|
+
return createFileSessionStore(options);
|
|
980
|
+
}
|
|
981
|
+
//#endregion
|
|
982
|
+
export { ACPX_BACKEND_ID, AcpRuntimeError, AcpxRuntime, DEFAULT_AGENT_NAME, createAcpRuntime, createAgentRegistry, createFileSessionStore, createRuntimeStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, isAcpRuntimeError };
|
|
983
|
+
|
|
984
|
+
//# sourceMappingURL=runtime.js.map
|