acpx 0.8.0 → 0.9.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/README.md +7 -4
- package/dist/{cli-BGYGVo3b.js → cli-Bf3yjqzE.js} +4 -4
- package/dist/{cli-BGYGVo3b.js.map → cli-Bf3yjqzE.js.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +545 -247
- package/dist/cli.js.map +1 -1
- package/dist/{client-FzXPdgP7.d.ts → client-BssohYqM.d.ts} +30 -3
- package/dist/client-BssohYqM.d.ts.map +1 -0
- package/dist/{flags-D706STfk.js → flags-C-rwARqg.js} +96 -39
- package/dist/flags-C-rwARqg.js.map +1 -0
- package/dist/{flows-hcjHmU7P.js → flows-WLs26_5Y.js} +400 -335
- package/dist/flows-WLs26_5Y.js.map +1 -0
- package/dist/flows.d.ts +21 -1
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{live-checkpoint-B9ctAuqV.js → live-checkpoint-D5d-K9s1.js} +1355 -700
- package/dist/live-checkpoint-D5d-K9s1.js.map +1 -0
- package/dist/{output-BL9XRWzS.js → output-DPg20dvn.js} +1151 -717
- package/dist/output-DPg20dvn.js.map +1 -0
- package/dist/runtime.d.ts +30 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +579 -425
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-BJyG6zEH.d.ts → session-options-CFudjdkU.d.ts} +7 -1
- package/dist/session-options-CFudjdkU.d.ts.map +1 -0
- package/package.json +15 -12
- package/skills/acpx/SKILL.md +11 -3
- package/dist/client-FzXPdgP7.d.ts.map +0 -1
- package/dist/flags-D706STfk.js.map +0 -1
- package/dist/flows-hcjHmU7P.js.map +0 -1
- package/dist/live-checkpoint-B9ctAuqV.js.map +0 -1
- package/dist/output-BL9XRWzS.js.map +0 -1
- package/dist/session-options-BJyG6zEH.d.ts.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dt as isAcpResourceNotFoundError, E as AcpClient, Et as extractAcpError, K as defaultSessionEventLog,
|
|
1
|
+
import { C as applyConversation, Dt as isAcpResourceNotFoundError, E as AcpClient, Et as extractAcpError, K as defaultSessionEventLog, T as reconcileAgentSessionId, Tt as normalizeOutputError, W as parseSessionRecord, Z as assertPersistedKeyPolicy, _ as recordPromptSubmission, _t as withTimeout, a as applyRequestedModelIfAdvertised, c as setDesiredConfigOption, d as syncAdvertisedModelState, f as applyConfigOptionsToRecord, g as recordClientOperation, h as createSessionConversation, i as connectAndLoadSession, l as setDesiredModeId, m as cloneSessionConversation, n as runPromptTurn, ot as serializeSessionRecordForDisk, p as cloneSessionAcpxState, pt as textPrompt, r as withConnectedSession, s as setCurrentModelId, t as LiveSessionCheckpoint, v as recordSessionUpdate, vt as DEFAULT_AGENT_NAME, w as applyLifecycleSnapshotToRecord, x as persistSessionOptions, xt as resolveAgentCommand, y as trimConversationForRuntime, yt as listBuiltInAgents } from "./live-checkpoint-D5d-K9s1.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -76,28 +76,36 @@ function resolveStructuredPromptPayload(parsed) {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
function resolveStatusTextForTag(params) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
79
|
+
const resolver = STATUS_TEXT_RESOLVERS[params.tag];
|
|
80
|
+
return resolver ? resolver(params.payload) : null;
|
|
81
|
+
}
|
|
82
|
+
const STATUS_TEXT_RESOLVERS = {
|
|
83
|
+
available_commands_update: availableCommandsStatusText,
|
|
84
|
+
current_mode_update: currentModeStatusText,
|
|
85
|
+
config_option_update: configOptionStatusText,
|
|
86
|
+
session_info_update: sessionInfoStatusText,
|
|
87
|
+
plan: planStatusText
|
|
88
|
+
};
|
|
89
|
+
function availableCommandsStatusText(payload) {
|
|
90
|
+
const commands = Array.isArray(payload.availableCommands) ? payload.availableCommands : [];
|
|
91
|
+
return commands.length > 0 ? `available commands updated (${commands.length})` : "available commands updated";
|
|
92
|
+
}
|
|
93
|
+
function currentModeStatusText(payload) {
|
|
94
|
+
const mode = asTrimmedString(payload.currentModeId) || asTrimmedString(payload.modeId) || asTrimmedString(payload.mode);
|
|
95
|
+
return mode ? `mode updated: ${mode}` : "mode updated";
|
|
96
|
+
}
|
|
97
|
+
function configOptionStatusText(payload) {
|
|
98
|
+
const id = asTrimmedString(payload.id) || asTrimmedString(payload.configOptionId);
|
|
99
|
+
const value = asTrimmedString(payload.currentValue) || asTrimmedString(payload.value) || asTrimmedString(payload.optionValue);
|
|
100
|
+
if (id && value) return `config updated: ${id}=${value}`;
|
|
101
|
+
return id ? `config updated: ${id}` : "config updated";
|
|
102
|
+
}
|
|
103
|
+
function sessionInfoStatusText(payload) {
|
|
104
|
+
return asTrimmedString(payload.summary) || asTrimmedString(payload.message) || "session updated";
|
|
105
|
+
}
|
|
106
|
+
function planStatusText(payload) {
|
|
107
|
+
const content = asTrimmedString((Array.isArray(payload.entries) ? payload.entries : []).find((entry) => isRecord(entry))?.content);
|
|
108
|
+
return content ? `plan: ${content}` : null;
|
|
101
109
|
}
|
|
102
110
|
function resolveTextChunk(params) {
|
|
103
111
|
const contentRaw = params.payload.content;
|
|
@@ -177,17 +185,24 @@ function readToolContentText(value) {
|
|
|
177
185
|
const record = isRecord(value) ? value : void 0;
|
|
178
186
|
if (!record) return;
|
|
179
187
|
if (record.type === "content") return readToolContentText(record.content);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
188
|
+
return toolContentTextReader(String(record.type))?.(record);
|
|
189
|
+
}
|
|
190
|
+
const TOOL_CONTENT_TEXT_READERS = {
|
|
191
|
+
text: (record) => asString(record.text),
|
|
192
|
+
audio: (record) => `[audio] ${asOptionalString(record.mimeType) || "audio"}`,
|
|
193
|
+
resource_link: (record) => asOptionalString(record.title) || asOptionalString(record.name) || asOptionalString(record.uri),
|
|
194
|
+
resource: (record) => {
|
|
183
195
|
const resource = isRecord(record.resource) ? record.resource : void 0;
|
|
184
196
|
return asString(resource?.text) || asOptionalString(resource?.uri);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
197
|
+
},
|
|
198
|
+
diff: (record) => `diff ${asOptionalString(record.path) || "file"}`,
|
|
199
|
+
terminal: (record) => {
|
|
188
200
|
const terminalId = asOptionalString(record.terminalId) || asOptionalString(record.id);
|
|
189
201
|
return terminalId ? `[terminal] ${terminalId}` : "[terminal]";
|
|
190
202
|
}
|
|
203
|
+
};
|
|
204
|
+
function toolContentTextReader(type) {
|
|
205
|
+
return Object.hasOwn(TOOL_CONTENT_TEXT_READERS, type) ? TOOL_CONTENT_TEXT_READERS[type] : void 0;
|
|
191
206
|
}
|
|
192
207
|
function summarizeToolContent(content) {
|
|
193
208
|
if (!Array.isArray(content)) return;
|
|
@@ -197,7 +212,7 @@ function summarizeToolContent(content) {
|
|
|
197
212
|
}
|
|
198
213
|
function summarizeToolOutput(rawOutput) {
|
|
199
214
|
if (rawOutput == null) return;
|
|
200
|
-
if (
|
|
215
|
+
if (isScalarToolOutput(rawOutput)) return truncateToolSummary(String(rawOutput));
|
|
201
216
|
const record = isRecord(rawOutput) ? rawOutput : void 0;
|
|
202
217
|
if (!record) return;
|
|
203
218
|
return truncateToolSummary(readFirstString(record, [
|
|
@@ -209,13 +224,27 @@ function summarizeToolOutput(rawOutput) {
|
|
|
209
224
|
"content"
|
|
210
225
|
]) ?? "") || void 0;
|
|
211
226
|
}
|
|
227
|
+
function isScalarToolOutput(value) {
|
|
228
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
229
|
+
}
|
|
212
230
|
function shouldForwardArray(value) {
|
|
213
231
|
return Array.isArray(value);
|
|
214
232
|
}
|
|
215
233
|
function readToolKind(value) {
|
|
216
234
|
const kind = asOptionalString(value);
|
|
217
|
-
|
|
235
|
+
return kind && TOOL_KINDS.has(kind) ? kind : void 0;
|
|
218
236
|
}
|
|
237
|
+
const TOOL_KINDS = new Set([
|
|
238
|
+
"read",
|
|
239
|
+
"edit",
|
|
240
|
+
"delete",
|
|
241
|
+
"move",
|
|
242
|
+
"search",
|
|
243
|
+
"execute",
|
|
244
|
+
"fetch",
|
|
245
|
+
"think",
|
|
246
|
+
"other"
|
|
247
|
+
]);
|
|
219
248
|
function createToolCallEvent(params) {
|
|
220
249
|
const title = asTrimmedString(params.payload.title) || "tool call";
|
|
221
250
|
const status = asTrimmedString(params.payload.status);
|
|
@@ -225,19 +254,31 @@ function createToolCallEvent(params) {
|
|
|
225
254
|
const kind = readToolKind(params.payload.kind);
|
|
226
255
|
const summaryText = status ? `${title} (${status})` : title;
|
|
227
256
|
const detailSummary = params.tag === "tool_call_update" ? outputSummary ?? inputSummary : inputSummary ?? outputSummary;
|
|
228
|
-
|
|
257
|
+
const event = {
|
|
229
258
|
type: "tool_call",
|
|
230
259
|
text: detailSummary ? `${summaryText}: ${detailSummary}` : summaryText,
|
|
231
260
|
tag: params.tag,
|
|
232
|
-
...toolCallId ? { toolCallId } : {},
|
|
233
|
-
...status ? { status } : {},
|
|
234
|
-
...kind ? { kind } : {},
|
|
235
|
-
...shouldForwardArray(params.payload.locations) ? { locations: params.payload.locations } : {},
|
|
236
|
-
...Object.prototype.hasOwnProperty.call(params.payload, "rawInput") ? { rawInput: params.payload.rawInput } : {},
|
|
237
|
-
...Object.prototype.hasOwnProperty.call(params.payload, "rawOutput") ? { rawOutput: params.payload.rawOutput } : {},
|
|
238
|
-
...shouldForwardArray(params.payload.content) ? { content: params.payload.content } : {},
|
|
239
261
|
title
|
|
240
262
|
};
|
|
263
|
+
assignToolCallEventMetadata(event, params.payload, {
|
|
264
|
+
toolCallId,
|
|
265
|
+
status,
|
|
266
|
+
kind
|
|
267
|
+
});
|
|
268
|
+
return event;
|
|
269
|
+
}
|
|
270
|
+
function assignToolCallEventMetadata(event, payload, values) {
|
|
271
|
+
if (event.type !== "tool_call") return;
|
|
272
|
+
if (values.toolCallId) event.toolCallId = values.toolCallId;
|
|
273
|
+
if (values.status) event.status = values.status;
|
|
274
|
+
if (values.kind) event.kind = values.kind;
|
|
275
|
+
assignForwardedToolPayload(event, payload);
|
|
276
|
+
}
|
|
277
|
+
function assignForwardedToolPayload(event, payload) {
|
|
278
|
+
if (shouldForwardArray(payload.locations)) event.locations = payload.locations;
|
|
279
|
+
if (Object.prototype.hasOwnProperty.call(payload, "rawInput")) event.rawInput = payload.rawInput;
|
|
280
|
+
if (Object.prototype.hasOwnProperty.call(payload, "rawOutput")) event.rawOutput = payload.rawOutput;
|
|
281
|
+
if (shouldForwardArray(payload.content)) event.content = payload.content;
|
|
241
282
|
}
|
|
242
283
|
function parsePromptEventLine(line) {
|
|
243
284
|
const trimmed = line.trim();
|
|
@@ -251,88 +292,94 @@ function parsePromptEventLine(line) {
|
|
|
251
292
|
const type = structured.type;
|
|
252
293
|
const payload = structured.payload;
|
|
253
294
|
const tag = structured.tag;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
295
|
+
const parser = promptEventParser(type);
|
|
296
|
+
return parser ? parser(payload, tag) : null;
|
|
297
|
+
}
|
|
298
|
+
const PROMPT_EVENT_PARSERS = {
|
|
299
|
+
text: (payload, tag) => createTextDeltaEvent({
|
|
300
|
+
content: asString(payload.content),
|
|
301
|
+
stream: "output",
|
|
302
|
+
tag
|
|
303
|
+
}),
|
|
304
|
+
thought: (payload, tag) => createTextDeltaEvent({
|
|
305
|
+
content: asString(payload.content),
|
|
306
|
+
stream: "thought",
|
|
307
|
+
tag
|
|
308
|
+
}),
|
|
309
|
+
tool_call: (payload, tag) => createToolCallEvent({
|
|
310
|
+
payload,
|
|
311
|
+
tag: tag ?? "tool_call"
|
|
312
|
+
}),
|
|
313
|
+
tool_call_update: (payload, tag) => createToolCallEvent({
|
|
314
|
+
payload,
|
|
315
|
+
tag: tag ?? "tool_call_update"
|
|
316
|
+
}),
|
|
317
|
+
agent_message_chunk: (payload) => resolveTextChunk({
|
|
318
|
+
payload,
|
|
319
|
+
stream: "output",
|
|
320
|
+
tag: "agent_message_chunk"
|
|
321
|
+
}),
|
|
322
|
+
agent_thought_chunk: (payload) => resolveTextChunk({
|
|
323
|
+
payload,
|
|
324
|
+
stream: "thought",
|
|
325
|
+
tag: "agent_thought_chunk"
|
|
326
|
+
}),
|
|
327
|
+
usage_update: usageUpdateEvent,
|
|
328
|
+
available_commands_update: (payload) => statusUpdateEvent("available_commands_update", payload),
|
|
329
|
+
current_mode_update: (payload) => statusUpdateEvent("current_mode_update", payload),
|
|
330
|
+
config_option_update: (payload) => statusUpdateEvent("config_option_update", payload),
|
|
331
|
+
session_info_update: (payload) => statusUpdateEvent("session_info_update", payload),
|
|
332
|
+
plan: (payload) => statusUpdateEvent("plan", payload),
|
|
333
|
+
client_operation: clientOperationEvent,
|
|
334
|
+
update: updateStatusEvent,
|
|
335
|
+
done: () => null,
|
|
336
|
+
error: () => null
|
|
337
|
+
};
|
|
338
|
+
function promptEventParser(type) {
|
|
339
|
+
return Object.hasOwn(PROMPT_EVENT_PARSERS, type) ? PROMPT_EVENT_PARSERS[type] : void 0;
|
|
340
|
+
}
|
|
341
|
+
function usageUpdateEvent(payload) {
|
|
342
|
+
const used = asOptionalFiniteNumber(payload.used);
|
|
343
|
+
const size = asOptionalFiniteNumber(payload.size);
|
|
344
|
+
return {
|
|
345
|
+
type: "status",
|
|
346
|
+
text: used != null && size != null ? `usage updated: ${used}/${size}` : "usage updated",
|
|
347
|
+
tag: "usage_update",
|
|
348
|
+
...used != null ? { used } : {},
|
|
349
|
+
...size != null ? { size } : {}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function statusUpdateEvent(tag, payload) {
|
|
353
|
+
const text = resolveStatusTextForTag({
|
|
354
|
+
tag,
|
|
355
|
+
payload
|
|
356
|
+
});
|
|
357
|
+
if (!text) return null;
|
|
358
|
+
return {
|
|
359
|
+
type: "status",
|
|
360
|
+
text,
|
|
361
|
+
tag
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function clientOperationEvent(payload, tag) {
|
|
365
|
+
const text = [
|
|
366
|
+
asTrimmedString(payload.method) || "operation",
|
|
367
|
+
asTrimmedString(payload.status),
|
|
368
|
+
asTrimmedString(payload.summary)
|
|
369
|
+
].filter(Boolean).join(" ");
|
|
370
|
+
return text ? {
|
|
371
|
+
type: "status",
|
|
372
|
+
text,
|
|
373
|
+
...tag ? { tag } : {}
|
|
374
|
+
} : null;
|
|
375
|
+
}
|
|
376
|
+
function updateStatusEvent(payload, tag) {
|
|
377
|
+
const update = asTrimmedString(payload.update);
|
|
378
|
+
return update ? {
|
|
379
|
+
type: "status",
|
|
380
|
+
text: update,
|
|
381
|
+
...tag ? { tag } : {}
|
|
382
|
+
} : null;
|
|
336
383
|
}
|
|
337
384
|
//#endregion
|
|
338
385
|
//#region src/runtime/engine/reuse-policy.ts
|
|
@@ -412,12 +459,23 @@ function toPromptInput(text, attachments) {
|
|
|
412
459
|
text
|
|
413
460
|
});
|
|
414
461
|
for (const attachment of attachments) {
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
462
|
+
if (attachment.mediaType.startsWith("image/")) {
|
|
463
|
+
blocks.push({
|
|
464
|
+
type: "image",
|
|
465
|
+
mimeType: attachment.mediaType,
|
|
466
|
+
data: attachment.data
|
|
467
|
+
});
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (attachment.mediaType.startsWith("audio/")) {
|
|
471
|
+
blocks.push({
|
|
472
|
+
type: "audio",
|
|
473
|
+
mimeType: attachment.mediaType,
|
|
474
|
+
data: attachment.data
|
|
475
|
+
});
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
throw new AcpRuntimeError("ACP_TURN_FAILED", `Unsupported ACP runtime attachment media type: ${attachment.mediaType}`);
|
|
421
479
|
}
|
|
422
480
|
return blocks.length > 0 ? blocks : textPrompt(text);
|
|
423
481
|
}
|
|
@@ -496,6 +554,31 @@ function resolveSupportedConfigOptionId(record, configId) {
|
|
|
496
554
|
const supportedText = supported.length > 0 ? supported.join(", ") : "none";
|
|
497
555
|
throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", `ACP session ${record.acpxRecordId} does not advertise config option '${configId}'. Supported config options: ${supportedText}.`);
|
|
498
556
|
}
|
|
557
|
+
async function createOrLoadRuntimeSession(client, resumeSessionId, cwd) {
|
|
558
|
+
if (resumeSessionId) {
|
|
559
|
+
if (client.supportsResumeSession()) {
|
|
560
|
+
const resumed = await client.resumeSession(resumeSessionId, cwd);
|
|
561
|
+
return {
|
|
562
|
+
sessionId: resumeSessionId,
|
|
563
|
+
agentSessionId: resumed.agentSessionId,
|
|
564
|
+
sessionResult: resumed
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (!client.supportsLoadSession()) throw new Error(`Agent does not support session/resume or session/load; cannot resume session ${resumeSessionId}`);
|
|
568
|
+
const loaded = await client.loadSession(resumeSessionId, cwd);
|
|
569
|
+
return {
|
|
570
|
+
sessionId: resumeSessionId,
|
|
571
|
+
agentSessionId: loaded.agentSessionId,
|
|
572
|
+
sessionResult: loaded
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const created = await client.createSession(cwd);
|
|
576
|
+
return {
|
|
577
|
+
sessionId: created.sessionId,
|
|
578
|
+
agentSessionId: created.agentSessionId,
|
|
579
|
+
sessionResult: created
|
|
580
|
+
};
|
|
581
|
+
}
|
|
499
582
|
var AcpRuntimeManager = class {
|
|
500
583
|
options;
|
|
501
584
|
deps;
|
|
@@ -611,56 +694,56 @@ var AcpRuntimeManager = class {
|
|
|
611
694
|
let keepClientOpen = false;
|
|
612
695
|
try {
|
|
613
696
|
await client.start();
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
if (input.resumeSessionId) {
|
|
618
|
-
const loaded = await client.loadSession(input.resumeSessionId, cwd);
|
|
619
|
-
sessionId = input.resumeSessionId;
|
|
620
|
-
agentSessionId = loaded.agentSessionId;
|
|
621
|
-
sessionResult = loaded;
|
|
622
|
-
} else {
|
|
623
|
-
const created = await client.createSession(cwd);
|
|
624
|
-
sessionId = created.sessionId;
|
|
625
|
-
agentSessionId = created.agentSessionId;
|
|
626
|
-
sessionResult = created;
|
|
627
|
-
}
|
|
628
|
-
const record = createInitialRecord({
|
|
629
|
-
recordId: createRecordId(input.sessionKey, input.mode),
|
|
630
|
-
sessionName: input.sessionKey,
|
|
631
|
-
sessionId,
|
|
632
|
-
agentCommand,
|
|
633
|
-
cwd,
|
|
634
|
-
agentSessionId
|
|
635
|
-
});
|
|
636
|
-
this.closingActiveRecords.delete(record.acpxRecordId);
|
|
637
|
-
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
638
|
-
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
639
|
-
applyConfigOptionsToRecord(record, sessionResult);
|
|
640
|
-
const requestedModelApplied = await applyRequestedModelIfAdvertised({
|
|
697
|
+
const session = await createOrLoadRuntimeSession(client, input.resumeSessionId, cwd);
|
|
698
|
+
const record = await this.createAndSaveRuntimeRecord({
|
|
699
|
+
input,
|
|
641
700
|
client,
|
|
642
|
-
sessionId,
|
|
643
|
-
requestedModel: input.sessionOptions?.model,
|
|
644
|
-
models: sessionResult.models,
|
|
645
701
|
agentCommand,
|
|
646
|
-
|
|
702
|
+
cwd,
|
|
703
|
+
session
|
|
647
704
|
});
|
|
648
|
-
|
|
649
|
-
if (requestedModelApplied) setCurrentModelId(record, input.sessionOptions?.model);
|
|
650
|
-
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
651
|
-
persistSessionOptions(record, input.sessionOptions);
|
|
652
|
-
await this.options.sessionStore.save(record);
|
|
653
|
-
if (input.mode === "persistent") {
|
|
654
|
-
const previousClient = this.pendingPersistentClients.get(record.acpxRecordId);
|
|
655
|
-
this.pendingPersistentClients.set(record.acpxRecordId, client);
|
|
656
|
-
keepClientOpen = true;
|
|
657
|
-
await previousClient?.close().catch(() => {});
|
|
658
|
-
}
|
|
705
|
+
keepClientOpen = await this.keepPersistentClient(input.mode, record.acpxRecordId, client);
|
|
659
706
|
return record;
|
|
660
707
|
} finally {
|
|
661
708
|
if (!keepClientOpen) await client.close();
|
|
662
709
|
}
|
|
663
710
|
}
|
|
711
|
+
async createAndSaveRuntimeRecord(params) {
|
|
712
|
+
const { input, client, agentCommand, cwd, session } = params;
|
|
713
|
+
const record = createInitialRecord({
|
|
714
|
+
recordId: createRecordId(input.sessionKey, input.mode),
|
|
715
|
+
sessionName: input.sessionKey,
|
|
716
|
+
sessionId: session.sessionId,
|
|
717
|
+
agentCommand,
|
|
718
|
+
cwd,
|
|
719
|
+
agentSessionId: session.agentSessionId
|
|
720
|
+
});
|
|
721
|
+
this.closingActiveRecords.delete(record.acpxRecordId);
|
|
722
|
+
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
723
|
+
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
724
|
+
applyConfigOptionsToRecord(record, session.sessionResult);
|
|
725
|
+
const requestedModelApplied = await applyRequestedModelIfAdvertised({
|
|
726
|
+
client,
|
|
727
|
+
sessionId: session.sessionId,
|
|
728
|
+
requestedModel: input.sessionOptions?.model,
|
|
729
|
+
models: session.sessionResult.models,
|
|
730
|
+
agentCommand,
|
|
731
|
+
timeoutMs: this.options.timeoutMs
|
|
732
|
+
});
|
|
733
|
+
syncAdvertisedModelState(record, session.sessionResult.models);
|
|
734
|
+
if (requestedModelApplied) setCurrentModelId(record, input.sessionOptions?.model);
|
|
735
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
736
|
+
persistSessionOptions(record, input.sessionOptions);
|
|
737
|
+
await this.options.sessionStore.save(record);
|
|
738
|
+
return record;
|
|
739
|
+
}
|
|
740
|
+
async keepPersistentClient(mode, recordId, client) {
|
|
741
|
+
if (mode !== "persistent") return false;
|
|
742
|
+
const previousClient = this.pendingPersistentClients.get(recordId);
|
|
743
|
+
this.pendingPersistentClients.set(recordId, client);
|
|
744
|
+
await previousClient?.close().catch(() => {});
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
664
747
|
startTurn(input) {
|
|
665
748
|
const promptInput = toPromptInput(input.text, input.attachments);
|
|
666
749
|
const queue = new AsyncEventQueue();
|
|
@@ -668,10 +751,12 @@ var AcpRuntimeManager = class {
|
|
|
668
751
|
const sessionReady = createDeferred();
|
|
669
752
|
sessionReady.promise.catch(() => {});
|
|
670
753
|
let resultSettled = false;
|
|
671
|
-
|
|
672
|
-
|
|
754
|
+
const state = {
|
|
755
|
+
pendingCancel: false,
|
|
756
|
+
turnActive: true,
|
|
757
|
+
activeController: null
|
|
758
|
+
};
|
|
673
759
|
let streamClosed = false;
|
|
674
|
-
let activeController = null;
|
|
675
760
|
const settleResult = (next) => {
|
|
676
761
|
if (resultSettled) return;
|
|
677
762
|
resultSettled = true;
|
|
@@ -684,9 +769,9 @@ var AcpRuntimeManager = class {
|
|
|
684
769
|
queue.close();
|
|
685
770
|
};
|
|
686
771
|
const requestCancel = async () => {
|
|
687
|
-
if (activeController) return await activeController.requestCancelActivePrompt();
|
|
688
|
-
if (!turnActive) return false;
|
|
689
|
-
pendingCancel = true;
|
|
772
|
+
if (state.activeController) return await state.activeController.requestCancelActivePrompt();
|
|
773
|
+
if (!state.turnActive) return false;
|
|
774
|
+
state.pendingCancel = true;
|
|
690
775
|
return true;
|
|
691
776
|
};
|
|
692
777
|
const abortHandler = () => {
|
|
@@ -709,230 +794,15 @@ var AcpRuntimeManager = class {
|
|
|
709
794
|
}
|
|
710
795
|
input.signal.addEventListener("abort", abortHandler, { once: true });
|
|
711
796
|
}
|
|
712
|
-
(
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
acpxState = cloneSessionAcpxState(record.acpx);
|
|
722
|
-
const promptStartedAt = isoNow();
|
|
723
|
-
const promptMessageId = recordPromptSubmission(conversation, promptInput, promptStartedAt);
|
|
724
|
-
trimConversationForRuntime(conversation);
|
|
725
|
-
record.lastPromptAt = promptStartedAt;
|
|
726
|
-
record.lastUsedAt = promptStartedAt;
|
|
727
|
-
record.acpx = acpxState;
|
|
728
|
-
applyConversation(record, conversation);
|
|
729
|
-
await this.options.sessionStore.save(record);
|
|
730
|
-
const pendingClient = await this.readPendingPersistentClient(record, { consume: true });
|
|
731
|
-
client = pendingClient ?? this.createClient({
|
|
732
|
-
agentCommand: record.agentCommand,
|
|
733
|
-
cwd: record.cwd,
|
|
734
|
-
mcpServers: [...this.options.mcpServers ?? []],
|
|
735
|
-
permissionMode: this.options.permissionMode,
|
|
736
|
-
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
737
|
-
onPermissionRequest: this.options.onPermissionRequest,
|
|
738
|
-
verbose: this.options.verbose
|
|
739
|
-
});
|
|
740
|
-
const runtimeClient = client;
|
|
741
|
-
const runtimeConversation = conversation;
|
|
742
|
-
const runtimeRecord = record;
|
|
743
|
-
liveCheckpoint = new LiveSessionCheckpoint({ save: async () => {
|
|
744
|
-
runtimeRecord.lastUsedAt = isoNow();
|
|
745
|
-
runtimeRecord.acpx = acpxState;
|
|
746
|
-
applyConversation(runtimeRecord, runtimeConversation);
|
|
747
|
-
await this.refreshClosedState(runtimeRecord);
|
|
748
|
-
await this.options.sessionStore.save(runtimeRecord);
|
|
749
|
-
} });
|
|
750
|
-
let activeSessionId = record.acpSessionId;
|
|
751
|
-
const applyPendingCancel = async () => {
|
|
752
|
-
if (!pendingCancel || !runtimeClient.hasActivePrompt()) return false;
|
|
753
|
-
const cancelled = await runtimeClient.requestCancelActivePrompt();
|
|
754
|
-
if (cancelled) pendingCancel = false;
|
|
755
|
-
return cancelled;
|
|
756
|
-
};
|
|
757
|
-
activeController = {
|
|
758
|
-
hasActivePrompt: () => runtimeClient.hasActivePrompt(),
|
|
759
|
-
requestCancelActivePrompt: async () => {
|
|
760
|
-
if (runtimeClient.hasActivePrompt()) return await runtimeClient.requestCancelActivePrompt();
|
|
761
|
-
if (!turnActive) return false;
|
|
762
|
-
pendingCancel = true;
|
|
763
|
-
return true;
|
|
764
|
-
},
|
|
765
|
-
setSessionMode: async (modeId) => {
|
|
766
|
-
if (!runtimeClient.hasActivePrompt()) await sessionReady.promise;
|
|
767
|
-
await runtimeClient.setSessionMode(activeSessionId, modeId);
|
|
768
|
-
const nextState = cloneSessionAcpxState(acpxState) ?? {};
|
|
769
|
-
nextState.desired_mode_id = modeId;
|
|
770
|
-
acpxState = nextState;
|
|
771
|
-
},
|
|
772
|
-
setSessionModel: async (modelId) => {
|
|
773
|
-
if (!runtimeClient.hasActivePrompt()) await sessionReady.promise;
|
|
774
|
-
await runtimeClient.setSessionModel(activeSessionId, modelId);
|
|
775
|
-
},
|
|
776
|
-
setSessionConfigOption: async (configId, value) => {
|
|
777
|
-
return (await activeController.setResolvedSessionConfigOption(configId, value)).response;
|
|
778
|
-
},
|
|
779
|
-
setResolvedSessionConfigOption: async (configId, value) => {
|
|
780
|
-
if (!runtimeClient.hasActivePrompt()) await sessionReady.promise;
|
|
781
|
-
const resolvedConfigId = resolveSupportedConfigOptionId({
|
|
782
|
-
...runtimeRecord,
|
|
783
|
-
acpx: acpxState ?? void 0
|
|
784
|
-
}, configId);
|
|
785
|
-
const response = await runtimeClient.setSessionConfigOption(activeSessionId, resolvedConfigId, value);
|
|
786
|
-
if (response?.configOptions) {
|
|
787
|
-
const nextState = cloneSessionAcpxState(acpxState) ?? {};
|
|
788
|
-
nextState.config_options = structuredClone(response.configOptions);
|
|
789
|
-
acpxState = nextState;
|
|
790
|
-
}
|
|
791
|
-
if (resolvedConfigId === "mode") {
|
|
792
|
-
const nextState = cloneSessionAcpxState(acpxState) ?? {};
|
|
793
|
-
nextState.desired_mode_id = value;
|
|
794
|
-
acpxState = nextState;
|
|
795
|
-
} else if (resolvedConfigId !== "model") {
|
|
796
|
-
const nextState = cloneSessionAcpxState(acpxState) ?? {};
|
|
797
|
-
nextState.desired_config_options = {
|
|
798
|
-
...nextState.desired_config_options,
|
|
799
|
-
[resolvedConfigId]: value
|
|
800
|
-
};
|
|
801
|
-
acpxState = nextState;
|
|
802
|
-
}
|
|
803
|
-
return {
|
|
804
|
-
configId: resolvedConfigId,
|
|
805
|
-
response
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
const emitParsed = (payload) => {
|
|
810
|
-
if (streamClosed) return;
|
|
811
|
-
const parsed = parsePromptEventLine(JSON.stringify(payload));
|
|
812
|
-
if (!parsed) return;
|
|
813
|
-
queue.push(parsed);
|
|
814
|
-
};
|
|
815
|
-
this.activeControllers.set(runtimeRecord.acpxRecordId, activeController);
|
|
816
|
-
runtimeClient.setEventHandlers({
|
|
817
|
-
onSessionUpdate: (notification) => {
|
|
818
|
-
acpxState = recordSessionUpdate(runtimeConversation, acpxState, notification);
|
|
819
|
-
trimConversationForRuntime(runtimeConversation);
|
|
820
|
-
liveCheckpoint?.request();
|
|
821
|
-
emitParsed({
|
|
822
|
-
jsonrpc: "2.0",
|
|
823
|
-
method: "session/update",
|
|
824
|
-
params: notification
|
|
825
|
-
});
|
|
826
|
-
},
|
|
827
|
-
onClientOperation: (operation) => {
|
|
828
|
-
acpxState = recordClientOperation(runtimeConversation, acpxState, operation);
|
|
829
|
-
trimConversationForRuntime(runtimeConversation);
|
|
830
|
-
liveCheckpoint?.request();
|
|
831
|
-
emitParsed({
|
|
832
|
-
type: "client_operation",
|
|
833
|
-
...operation
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
const { sessionId, resumed, loadError } = pendingClient ? {
|
|
838
|
-
sessionId: record.acpSessionId,
|
|
839
|
-
resumed: false,
|
|
840
|
-
loadError: void 0
|
|
841
|
-
} : await connectAndLoadSession({
|
|
842
|
-
client: runtimeClient,
|
|
843
|
-
record: runtimeRecord,
|
|
844
|
-
resumePolicy: resumePolicyForSessionMode(input.sessionMode),
|
|
845
|
-
timeoutMs: this.options.timeoutMs,
|
|
846
|
-
activeController,
|
|
847
|
-
onClientAvailable: () => {
|
|
848
|
-
if (activeController) this.activeControllers.set(runtimeRecord.acpxRecordId, activeController);
|
|
849
|
-
},
|
|
850
|
-
onConnectedRecord: (connectedRecord) => {
|
|
851
|
-
connectedRecord.lastPromptAt = isoNow();
|
|
852
|
-
},
|
|
853
|
-
onSessionIdResolved: (sessionIdValue) => {
|
|
854
|
-
activeSessionId = sessionIdValue;
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
acpxState = cloneSessionAcpxState(runtimeRecord.acpx);
|
|
858
|
-
sessionReady.resolve();
|
|
859
|
-
runtimeRecord.lastRequestId = input.requestId;
|
|
860
|
-
runtimeRecord.lastPromptAt = isoNow();
|
|
861
|
-
runtimeRecord.closed = false;
|
|
862
|
-
runtimeRecord.closedAt = void 0;
|
|
863
|
-
runtimeRecord.lastUsedAt = isoNow();
|
|
864
|
-
await liveCheckpoint.checkpoint();
|
|
865
|
-
if (resumed || loadError) emitParsed({
|
|
866
|
-
type: "status",
|
|
867
|
-
text: loadError ? `load fallback: ${loadError}` : "session resumed"
|
|
868
|
-
});
|
|
869
|
-
if (pendingCancel || input.signal?.aborted) {
|
|
870
|
-
pendingCancel = false;
|
|
871
|
-
settleResult({
|
|
872
|
-
status: "cancelled",
|
|
873
|
-
stopReason: "cancelled"
|
|
874
|
-
});
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
await applyPendingCancel();
|
|
878
|
-
const response = await runPromptTurn({
|
|
879
|
-
client: runtimeClient,
|
|
880
|
-
sessionId,
|
|
881
|
-
prompt: promptInput,
|
|
882
|
-
timeoutMs: input.timeoutMs ?? this.options.timeoutMs,
|
|
883
|
-
conversation: runtimeConversation,
|
|
884
|
-
promptMessageId
|
|
885
|
-
});
|
|
886
|
-
runtimeRecord.acpSessionId = activeSessionId;
|
|
887
|
-
reconcileAgentSessionId(runtimeRecord, runtimeRecord.agentSessionId);
|
|
888
|
-
runtimeRecord.protocolVersion = runtimeClient.initializeResult?.protocolVersion;
|
|
889
|
-
runtimeRecord.agentCapabilities = runtimeClient.initializeResult?.agentCapabilities;
|
|
890
|
-
runtimeRecord.acpx = acpxState;
|
|
891
|
-
applyConversation(runtimeRecord, runtimeConversation);
|
|
892
|
-
applyLifecycleSnapshotToRecord(runtimeRecord, runtimeClient.getAgentLifecycleSnapshot());
|
|
893
|
-
await this.options.sessionStore.save(runtimeRecord);
|
|
894
|
-
settleResult({
|
|
895
|
-
status: response.stopReason === "cancelled" ? "cancelled" : "completed",
|
|
896
|
-
...response.stopReason ? { stopReason: response.stopReason } : {}
|
|
897
|
-
});
|
|
898
|
-
} catch (error) {
|
|
899
|
-
sessionReady.reject(error);
|
|
900
|
-
const normalized = normalizeOutputError(error, { origin: "runtime" });
|
|
901
|
-
settleResult({
|
|
902
|
-
status: "failed",
|
|
903
|
-
error: {
|
|
904
|
-
message: normalized.message,
|
|
905
|
-
...normalized.code ? { code: normalized.code } : {},
|
|
906
|
-
...normalized.detailCode ? { detailCode: normalized.detailCode } : {},
|
|
907
|
-
...normalized.retryable !== void 0 ? { retryable: normalized.retryable } : {}
|
|
908
|
-
}
|
|
909
|
-
});
|
|
910
|
-
} finally {
|
|
911
|
-
turnActive = false;
|
|
912
|
-
if (input.signal) input.signal.removeEventListener("abort", abortHandler);
|
|
913
|
-
client?.clearEventHandlers();
|
|
914
|
-
let pooled = false;
|
|
915
|
-
if (record && conversation) {
|
|
916
|
-
applyLifecycleSnapshotToRecord(record, client?.getAgentLifecycleSnapshot() ?? { running: false });
|
|
917
|
-
record.acpx = acpxState;
|
|
918
|
-
applyConversation(record, conversation);
|
|
919
|
-
record.lastUsedAt = isoNow();
|
|
920
|
-
await liveCheckpoint?.flush().catch(() => {});
|
|
921
|
-
const closed = await this.refreshClosedState(record);
|
|
922
|
-
await this.options.sessionStore.save(record).catch(() => {});
|
|
923
|
-
if (!closed && client) pooled = await this.retainPersistentClientAfterTurn({
|
|
924
|
-
record,
|
|
925
|
-
client
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
if (!pooled) await client?.close().catch(() => {});
|
|
929
|
-
if (record) {
|
|
930
|
-
this.activeControllers.delete(record.acpxRecordId);
|
|
931
|
-
this.closingActiveRecords.delete(record.acpxRecordId);
|
|
932
|
-
}
|
|
933
|
-
queue.close();
|
|
934
|
-
}
|
|
935
|
-
})();
|
|
797
|
+
this.runRuntimeTurnTask({
|
|
798
|
+
input,
|
|
799
|
+
promptInput,
|
|
800
|
+
queue,
|
|
801
|
+
sessionReady,
|
|
802
|
+
state,
|
|
803
|
+
settleResult,
|
|
804
|
+
abortHandler
|
|
805
|
+
});
|
|
936
806
|
return {
|
|
937
807
|
requestId: input.requestId,
|
|
938
808
|
events: queue.iterate(),
|
|
@@ -945,6 +815,284 @@ var AcpRuntimeManager = class {
|
|
|
945
815
|
}
|
|
946
816
|
};
|
|
947
817
|
}
|
|
818
|
+
async runRuntimeTurnTask(task) {
|
|
819
|
+
let turn;
|
|
820
|
+
try {
|
|
821
|
+
turn = await this.prepareRuntimeTurn(task);
|
|
822
|
+
const { sessionId, resumed, loadError } = await this.connectRuntimeTurn(task, turn);
|
|
823
|
+
await this.resolveRuntimeTurnReady(task, turn, resumed, loadError);
|
|
824
|
+
if (this.cancelRuntimeTurnBeforePrompt(task)) return;
|
|
825
|
+
await this.applyPendingRuntimeTurnCancel(task, turn);
|
|
826
|
+
const response = await runPromptTurn({
|
|
827
|
+
client: turn.client,
|
|
828
|
+
sessionId,
|
|
829
|
+
prompt: task.promptInput,
|
|
830
|
+
timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs,
|
|
831
|
+
conversation: turn.conversation,
|
|
832
|
+
promptMessageId: turn.promptMessageId
|
|
833
|
+
});
|
|
834
|
+
await this.saveCompletedRuntimeTurn(turn, response.stopReason);
|
|
835
|
+
task.settleResult({
|
|
836
|
+
status: response.stopReason === "cancelled" ? "cancelled" : "completed",
|
|
837
|
+
...response.stopReason ? { stopReason: response.stopReason } : {}
|
|
838
|
+
});
|
|
839
|
+
} catch (error) {
|
|
840
|
+
this.failRuntimeTurn(task, error);
|
|
841
|
+
} finally {
|
|
842
|
+
await this.finalizeRuntimeTurn(task, turn);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async prepareRuntimeTurn(task) {
|
|
846
|
+
const record = await this.requireRecord(task.input.handle.acpxRecordId ?? task.input.handle.sessionKey);
|
|
847
|
+
const conversation = cloneSessionConversation(record);
|
|
848
|
+
let acpxState = cloneSessionAcpxState(record.acpx);
|
|
849
|
+
const promptStartedAt = isoNow();
|
|
850
|
+
const promptMessageId = recordPromptSubmission(conversation, task.promptInput, promptStartedAt);
|
|
851
|
+
trimConversationForRuntime(conversation);
|
|
852
|
+
record.lastPromptAt = promptStartedAt;
|
|
853
|
+
record.lastUsedAt = promptStartedAt;
|
|
854
|
+
record.acpx = acpxState;
|
|
855
|
+
applyConversation(record, conversation);
|
|
856
|
+
await this.options.sessionStore.save(record);
|
|
857
|
+
const pendingClient = await this.readPendingPersistentClient(record, { consume: true });
|
|
858
|
+
const client = pendingClient ?? this.createTurnClient(record);
|
|
859
|
+
const turn = {
|
|
860
|
+
record,
|
|
861
|
+
conversation,
|
|
862
|
+
acpxState,
|
|
863
|
+
liveCheckpoint: this.createRuntimeTurnCheckpoint(record, conversation, () => turn.acpxState),
|
|
864
|
+
client,
|
|
865
|
+
pendingClient,
|
|
866
|
+
promptMessageId,
|
|
867
|
+
activeSessionId: record.acpSessionId
|
|
868
|
+
};
|
|
869
|
+
task.state.activeController = this.buildRuntimeTurnController(task, turn);
|
|
870
|
+
this.activeControllers.set(record.acpxRecordId, task.state.activeController);
|
|
871
|
+
this.installRuntimeTurnEventHandlers(task, turn);
|
|
872
|
+
return turn;
|
|
873
|
+
}
|
|
874
|
+
createTurnClient(record) {
|
|
875
|
+
return this.createClient({
|
|
876
|
+
agentCommand: record.agentCommand,
|
|
877
|
+
cwd: record.cwd,
|
|
878
|
+
mcpServers: [...this.options.mcpServers ?? []],
|
|
879
|
+
permissionMode: this.options.permissionMode,
|
|
880
|
+
nonInteractivePermissions: this.options.nonInteractivePermissions,
|
|
881
|
+
onPermissionRequest: this.options.onPermissionRequest,
|
|
882
|
+
verbose: this.options.verbose
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
createRuntimeTurnCheckpoint(record, conversation, readAcpxState) {
|
|
886
|
+
return new LiveSessionCheckpoint({ save: async () => {
|
|
887
|
+
record.lastUsedAt = isoNow();
|
|
888
|
+
record.acpx = readAcpxState();
|
|
889
|
+
applyConversation(record, conversation);
|
|
890
|
+
await this.refreshClosedState(record);
|
|
891
|
+
await this.options.sessionStore.save(record);
|
|
892
|
+
} });
|
|
893
|
+
}
|
|
894
|
+
buildRuntimeTurnController(task, turn) {
|
|
895
|
+
return {
|
|
896
|
+
hasActivePrompt: () => turn.client.hasActivePrompt(),
|
|
897
|
+
requestCancelActivePrompt: async () => await this.requestRuntimeTurnCancel(task, turn),
|
|
898
|
+
setSessionMode: async (modeId) => {
|
|
899
|
+
await this.waitForRuntimeControlSession(task, turn);
|
|
900
|
+
await turn.client.setSessionMode(turn.activeSessionId, modeId);
|
|
901
|
+
const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
|
|
902
|
+
nextState.desired_mode_id = modeId;
|
|
903
|
+
turn.acpxState = nextState;
|
|
904
|
+
},
|
|
905
|
+
setSessionModel: async (modelId) => {
|
|
906
|
+
await this.waitForRuntimeControlSession(task, turn);
|
|
907
|
+
await turn.client.setSessionModel(turn.activeSessionId, modelId);
|
|
908
|
+
},
|
|
909
|
+
setSessionConfigOption: async (configId, value) => {
|
|
910
|
+
return (await task.state.activeController.setResolvedSessionConfigOption(configId, value)).response;
|
|
911
|
+
},
|
|
912
|
+
setResolvedSessionConfigOption: async (configId, value) => await this.setRuntimeResolvedSessionConfigOption(task, turn, configId, value)
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
async waitForRuntimeControlSession(task, turn) {
|
|
916
|
+
if (turn.client.hasActivePrompt()) return;
|
|
917
|
+
await task.sessionReady.promise;
|
|
918
|
+
}
|
|
919
|
+
async requestRuntimeTurnCancel(task, turn) {
|
|
920
|
+
if (turn.client.hasActivePrompt()) return await turn.client.requestCancelActivePrompt();
|
|
921
|
+
if (!task.state.turnActive) return false;
|
|
922
|
+
task.state.pendingCancel = true;
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
925
|
+
async setRuntimeResolvedSessionConfigOption(task, turn, configId, value) {
|
|
926
|
+
await this.waitForRuntimeControlSession(task, turn);
|
|
927
|
+
const resolvedConfigId = resolveSupportedConfigOptionId({
|
|
928
|
+
...turn.record,
|
|
929
|
+
acpx: turn.acpxState ?? void 0
|
|
930
|
+
}, configId);
|
|
931
|
+
const response = await turn.client.setSessionConfigOption(turn.activeSessionId, resolvedConfigId, value);
|
|
932
|
+
this.applyRuntimeConfigOptionState(turn, resolvedConfigId, value, response);
|
|
933
|
+
return {
|
|
934
|
+
configId: resolvedConfigId,
|
|
935
|
+
response
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
applyRuntimeConfigOptionState(turn, configId, value, response) {
|
|
939
|
+
if (response?.configOptions) {
|
|
940
|
+
const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
|
|
941
|
+
nextState.config_options = structuredClone(response.configOptions);
|
|
942
|
+
turn.acpxState = nextState;
|
|
943
|
+
}
|
|
944
|
+
if (configId === "mode") {
|
|
945
|
+
const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
|
|
946
|
+
nextState.desired_mode_id = value;
|
|
947
|
+
turn.acpxState = nextState;
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (configId !== "model") {
|
|
951
|
+
const nextState = cloneSessionAcpxState(turn.acpxState) ?? {};
|
|
952
|
+
nextState.desired_config_options = {
|
|
953
|
+
...nextState.desired_config_options,
|
|
954
|
+
[configId]: value
|
|
955
|
+
};
|
|
956
|
+
turn.acpxState = nextState;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
installRuntimeTurnEventHandlers(task, turn) {
|
|
960
|
+
turn.client.setEventHandlers({
|
|
961
|
+
onSessionUpdate: (notification) => {
|
|
962
|
+
turn.acpxState = recordSessionUpdate(turn.conversation, turn.acpxState, notification);
|
|
963
|
+
trimConversationForRuntime(turn.conversation);
|
|
964
|
+
turn.liveCheckpoint.request();
|
|
965
|
+
this.emitRuntimeTurnEvent(task, {
|
|
966
|
+
jsonrpc: "2.0",
|
|
967
|
+
method: "session/update",
|
|
968
|
+
params: notification
|
|
969
|
+
});
|
|
970
|
+
},
|
|
971
|
+
onClientOperation: (operation) => {
|
|
972
|
+
turn.acpxState = recordClientOperation(turn.conversation, turn.acpxState, operation);
|
|
973
|
+
trimConversationForRuntime(turn.conversation);
|
|
974
|
+
turn.liveCheckpoint.request();
|
|
975
|
+
this.emitRuntimeTurnEvent(task, {
|
|
976
|
+
type: "client_operation",
|
|
977
|
+
...operation
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
emitRuntimeTurnEvent(task, payload) {
|
|
983
|
+
const parsed = parsePromptEventLine(JSON.stringify(payload));
|
|
984
|
+
if (!parsed) return;
|
|
985
|
+
task.queue.push(parsed);
|
|
986
|
+
}
|
|
987
|
+
async connectRuntimeTurn(task, turn) {
|
|
988
|
+
const loaded = turn.pendingClient ? {
|
|
989
|
+
sessionId: turn.record.acpSessionId,
|
|
990
|
+
resumed: false,
|
|
991
|
+
loadError: void 0
|
|
992
|
+
} : await this.connectRuntimeTurnClient(task, turn);
|
|
993
|
+
turn.acpxState = cloneSessionAcpxState(turn.record.acpx);
|
|
994
|
+
return loaded;
|
|
995
|
+
}
|
|
996
|
+
async connectRuntimeTurnClient(task, turn) {
|
|
997
|
+
return await connectAndLoadSession({
|
|
998
|
+
client: turn.client,
|
|
999
|
+
record: turn.record,
|
|
1000
|
+
resumePolicy: resumePolicyForSessionMode(task.input.sessionMode),
|
|
1001
|
+
timeoutMs: this.options.timeoutMs,
|
|
1002
|
+
activeController: task.state.activeController,
|
|
1003
|
+
onClientAvailable: () => this.publishRuntimeTurnController(task, turn),
|
|
1004
|
+
onConnectedRecord: (connectedRecord) => {
|
|
1005
|
+
connectedRecord.lastPromptAt = isoNow();
|
|
1006
|
+
},
|
|
1007
|
+
onSessionIdResolved: (sessionIdValue) => {
|
|
1008
|
+
turn.activeSessionId = sessionIdValue;
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
publishRuntimeTurnController(task, turn) {
|
|
1013
|
+
const controller = task.state.activeController;
|
|
1014
|
+
if (controller) this.activeControllers.set(turn.record.acpxRecordId, controller);
|
|
1015
|
+
}
|
|
1016
|
+
async resolveRuntimeTurnReady(task, turn, resumed, loadError) {
|
|
1017
|
+
task.sessionReady.resolve();
|
|
1018
|
+
turn.record.lastRequestId = task.input.requestId;
|
|
1019
|
+
turn.record.lastPromptAt = isoNow();
|
|
1020
|
+
turn.record.closed = false;
|
|
1021
|
+
turn.record.closedAt = void 0;
|
|
1022
|
+
turn.record.lastUsedAt = isoNow();
|
|
1023
|
+
await turn.liveCheckpoint.checkpoint();
|
|
1024
|
+
this.emitRuntimeTurnLoadStatus(task, resumed, loadError);
|
|
1025
|
+
}
|
|
1026
|
+
emitRuntimeTurnLoadStatus(task, resumed, loadError) {
|
|
1027
|
+
if (!resumed && !loadError) return;
|
|
1028
|
+
this.emitRuntimeTurnEvent(task, {
|
|
1029
|
+
type: "status",
|
|
1030
|
+
text: loadError ? `session reconnect fallback: ${loadError}` : "session resumed"
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
cancelRuntimeTurnBeforePrompt(task) {
|
|
1034
|
+
if (!task.state.pendingCancel && !task.input.signal?.aborted) return false;
|
|
1035
|
+
task.state.pendingCancel = false;
|
|
1036
|
+
task.settleResult({
|
|
1037
|
+
status: "cancelled",
|
|
1038
|
+
stopReason: "cancelled"
|
|
1039
|
+
});
|
|
1040
|
+
return true;
|
|
1041
|
+
}
|
|
1042
|
+
async applyPendingRuntimeTurnCancel(task, turn) {
|
|
1043
|
+
if (!task.state.pendingCancel || !turn.client.hasActivePrompt()) return false;
|
|
1044
|
+
const cancelled = await turn.client.requestCancelActivePrompt();
|
|
1045
|
+
if (cancelled) task.state.pendingCancel = false;
|
|
1046
|
+
return cancelled;
|
|
1047
|
+
}
|
|
1048
|
+
async saveCompletedRuntimeTurn(turn, _stopReason) {
|
|
1049
|
+
turn.record.acpSessionId = turn.activeSessionId;
|
|
1050
|
+
reconcileAgentSessionId(turn.record, turn.record.agentSessionId);
|
|
1051
|
+
turn.record.protocolVersion = turn.client.initializeResult?.protocolVersion;
|
|
1052
|
+
turn.record.agentCapabilities = turn.client.initializeResult?.agentCapabilities;
|
|
1053
|
+
turn.record.acpx = turn.acpxState;
|
|
1054
|
+
applyConversation(turn.record, turn.conversation);
|
|
1055
|
+
applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot());
|
|
1056
|
+
await this.options.sessionStore.save(turn.record);
|
|
1057
|
+
}
|
|
1058
|
+
failRuntimeTurn(task, error) {
|
|
1059
|
+
task.sessionReady.reject(error);
|
|
1060
|
+
const normalized = normalizeOutputError(error, { origin: "runtime" });
|
|
1061
|
+
task.settleResult({
|
|
1062
|
+
status: "failed",
|
|
1063
|
+
error: {
|
|
1064
|
+
message: normalized.message,
|
|
1065
|
+
...normalized.code ? { code: normalized.code } : {},
|
|
1066
|
+
...normalized.detailCode ? { detailCode: normalized.detailCode } : {},
|
|
1067
|
+
...normalized.retryable !== void 0 ? { retryable: normalized.retryable } : {}
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
async finalizeRuntimeTurn(task, turn) {
|
|
1072
|
+
task.state.turnActive = false;
|
|
1073
|
+
task.input.signal?.removeEventListener("abort", task.abortHandler);
|
|
1074
|
+
turn?.client.clearEventHandlers();
|
|
1075
|
+
if (!(turn ? await this.finalizeRuntimeTurnRecord(turn) : false)) await turn?.client.close().catch(() => {});
|
|
1076
|
+
if (turn) {
|
|
1077
|
+
this.activeControllers.delete(turn.record.acpxRecordId);
|
|
1078
|
+
this.closingActiveRecords.delete(turn.record.acpxRecordId);
|
|
1079
|
+
}
|
|
1080
|
+
task.queue.close();
|
|
1081
|
+
}
|
|
1082
|
+
async finalizeRuntimeTurnRecord(turn) {
|
|
1083
|
+
applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot());
|
|
1084
|
+
turn.record.acpx = turn.acpxState;
|
|
1085
|
+
applyConversation(turn.record, turn.conversation);
|
|
1086
|
+
turn.record.lastUsedAt = isoNow();
|
|
1087
|
+
await turn.liveCheckpoint.flush().catch(() => {});
|
|
1088
|
+
const closed = await this.refreshClosedState(turn.record);
|
|
1089
|
+
await this.options.sessionStore.save(turn.record).catch(() => {});
|
|
1090
|
+
if (closed) return false;
|
|
1091
|
+
return await this.retainPersistentClientAfterTurn({
|
|
1092
|
+
record: turn.record,
|
|
1093
|
+
client: turn.client
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
948
1096
|
async *runTurn(input) {
|
|
949
1097
|
const turn = this.startTurn(input);
|
|
950
1098
|
yield* turn.events;
|
|
@@ -1125,21 +1273,30 @@ function writeHandleState(handle, state) {
|
|
|
1125
1273
|
}
|
|
1126
1274
|
//#endregion
|
|
1127
1275
|
//#region src/runtime/public/probe.ts
|
|
1276
|
+
function isPrimitiveDetail(value) {
|
|
1277
|
+
return value == null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol";
|
|
1278
|
+
}
|
|
1279
|
+
function formatFunctionDetail(value) {
|
|
1280
|
+
return value.name ? `[Function ${value.name}]` : "[Function]";
|
|
1281
|
+
}
|
|
1282
|
+
function serializeRuntimeDetail(value) {
|
|
1283
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1284
|
+
return JSON.stringify(value, (_key, nested) => {
|
|
1285
|
+
if (nested instanceof Error) return nested.message || nested.name;
|
|
1286
|
+
if (nested && typeof nested === "object") {
|
|
1287
|
+
if (seen.has(nested)) return "[Circular]";
|
|
1288
|
+
seen.add(nested);
|
|
1289
|
+
}
|
|
1290
|
+
return nested;
|
|
1291
|
+
}) ?? "undefined";
|
|
1292
|
+
}
|
|
1128
1293
|
function formatRuntimeDetail(value) {
|
|
1129
1294
|
if (value instanceof Error) return value.message || value.name;
|
|
1130
1295
|
if (typeof value === "string") return value;
|
|
1131
|
-
if (value
|
|
1132
|
-
if (typeof value === "function") return value
|
|
1133
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
1296
|
+
if (isPrimitiveDetail(value)) return String(value);
|
|
1297
|
+
if (typeof value === "function") return formatFunctionDetail(value);
|
|
1134
1298
|
try {
|
|
1135
|
-
return
|
|
1136
|
-
if (nested instanceof Error) return nested.message || nested.name;
|
|
1137
|
-
if (nested && typeof nested === "object") {
|
|
1138
|
-
if (seen.has(nested)) return "[Circular]";
|
|
1139
|
-
seen.add(nested);
|
|
1140
|
-
}
|
|
1141
|
-
return nested;
|
|
1142
|
-
}) ?? "undefined";
|
|
1299
|
+
return serializeRuntimeDetail(value);
|
|
1143
1300
|
} catch {
|
|
1144
1301
|
return "unserializable object";
|
|
1145
1302
|
}
|
|
@@ -1150,21 +1307,7 @@ function normalizeRuntimeDetails(details) {
|
|
|
1150
1307
|
async function probeRuntime(options, deps = {}) {
|
|
1151
1308
|
const agentName = options.probeAgent?.trim() || "codex";
|
|
1152
1309
|
const agentCommand = options.agentRegistry.resolve(agentName);
|
|
1153
|
-
const client = deps
|
|
1154
|
-
agentCommand,
|
|
1155
|
-
cwd: options.cwd,
|
|
1156
|
-
mcpServers: [...options.mcpServers ?? []],
|
|
1157
|
-
permissionMode: options.permissionMode,
|
|
1158
|
-
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
1159
|
-
verbose: options.verbose
|
|
1160
|
-
}) ?? new AcpClient({
|
|
1161
|
-
agentCommand,
|
|
1162
|
-
cwd: options.cwd,
|
|
1163
|
-
mcpServers: [...options.mcpServers ?? []],
|
|
1164
|
-
permissionMode: options.permissionMode,
|
|
1165
|
-
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
1166
|
-
verbose: options.verbose
|
|
1167
|
-
});
|
|
1310
|
+
const client = createProbeClient(options, agentCommand, deps);
|
|
1168
1311
|
try {
|
|
1169
1312
|
await client.start();
|
|
1170
1313
|
return {
|
|
@@ -1192,6 +1335,17 @@ async function probeRuntime(options, deps = {}) {
|
|
|
1192
1335
|
await client.close().catch(() => {});
|
|
1193
1336
|
}
|
|
1194
1337
|
}
|
|
1338
|
+
function createProbeClient(options, agentCommand, deps) {
|
|
1339
|
+
const clientOptions = {
|
|
1340
|
+
agentCommand,
|
|
1341
|
+
cwd: options.cwd,
|
|
1342
|
+
mcpServers: [...options.mcpServers ?? []],
|
|
1343
|
+
permissionMode: options.permissionMode,
|
|
1344
|
+
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
1345
|
+
verbose: options.verbose
|
|
1346
|
+
};
|
|
1347
|
+
return deps.clientFactory?.(clientOptions) ?? new AcpClient(clientOptions);
|
|
1348
|
+
}
|
|
1195
1349
|
//#endregion
|
|
1196
1350
|
//#region src/runtime.ts
|
|
1197
1351
|
const ACPX_BACKEND_ID = "acpx";
|