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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as getPerfMetricsSnapshot, A as parsePromptStopReason, B as normalizeName, C as
|
|
1
|
+
import { $ as getPerfMetricsSnapshot, A as parsePromptStopReason, B as normalizeName, Bt as QueueProtocolError, C as applyConversation, Ct as formatErrorMessage, D as extractSessionUpdateNotification, E as AcpClient, F as findSession, H as resolveSessionRecord, I as findSessionByDirectoryWalk, J as sessionEventActivePath, K as defaultSessionEventLog, L as isoNow, Mt as OUTPUT_ERROR_CODES, N as absolutePath, Nt as OUTPUT_ERROR_ORIGINS, O as isAcpJsonRpcMessage, Ot as toAcpErrorPayload, P as findGitRepositoryRoot, Q as formatPerfMetric, R as listSessions, S as sessionOptionsFromRecord, Tt as normalizeOutputError, U as writeSessionRecord, V as pruneSessions, X as sessionEventSegmentPath, Y as sessionEventLockPath, _ as recordPromptSubmission, _t as withTimeout, a as applyRequestedModelIfAdvertised, at as startPerfTimer, b as mergeSessionOptions, c as setDesiredConfigOption, d as syncAdvertisedModelState, et as incrementPerfCounter, f as applyConfigOptionsToRecord, ft as promptToDisplayText, g as recordClientOperation, gt as withInterrupt, h as createSessionConversation, ht as TimeoutError, i as connectAndLoadSession, it as setPerfGauge, k as parseJsonRpcErrorMessage, l as setDesiredModeId, lt as isPromptInput, m as cloneSessionConversation, mt as InterruptedError, n as runPromptTurn, nt as recordPerfDuration, o as assertRequestedModelSupported, p as cloneSessionAcpxState, pt as textPrompt, q as sessionBaseDir, r as withConnectedSession, rt as resetPerfMetrics, s as setCurrentModelId, st as normalizeRuntimeSessionId, t as LiveSessionCheckpoint, tt as measurePerf, u as setDesiredModelId, v as recordSessionUpdate, w as applyLifecycleSnapshotToRecord, wt as isRetryablePromptError, x as persistSessionOptions, y as trimConversationForRuntime, z as listSessionsForAgent, zt as QueueConnectionError } from "./live-checkpoint-D5d-K9s1.js";
|
|
2
2
|
import fs, { realpathSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs$1 from "node:fs/promises";
|
|
@@ -26,6 +26,17 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
|
|
|
26
26
|
return Math.round(ttlMs);
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
+
//#region src/process-liveness.ts
|
|
30
|
+
function isProcessAlive(pid) {
|
|
31
|
+
if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
32
|
+
try {
|
|
33
|
+
process.kill(pid, 0);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
29
40
|
//#region src/cli/queue/paths.ts
|
|
30
41
|
function shortHash(value, length) {
|
|
31
42
|
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
@@ -56,7 +67,7 @@ const QUEUE_OWNER_STALE_HEARTBEAT_MS = 15e3;
|
|
|
56
67
|
function parseQueueOwnerRecord(raw) {
|
|
57
68
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
58
69
|
const record = raw;
|
|
59
|
-
if (!
|
|
70
|
+
if (!hasValidQueueOwnerRecordFields(record)) return null;
|
|
60
71
|
return {
|
|
61
72
|
pid: record.pid,
|
|
62
73
|
sessionId: record.sessionId,
|
|
@@ -67,6 +78,15 @@ function parseQueueOwnerRecord(raw) {
|
|
|
67
78
|
queueDepth: record.queueDepth
|
|
68
79
|
};
|
|
69
80
|
}
|
|
81
|
+
function hasValidQueueOwnerRecordFields(record) {
|
|
82
|
+
return isPositiveInteger(record.pid) && typeof record.sessionId === "string" && typeof record.socketPath === "string" && typeof record.createdAt === "string" && typeof record.heartbeatAt === "string" && isPositiveInteger(record.ownerGeneration) && isNonNegativeInteger(record.queueDepth);
|
|
83
|
+
}
|
|
84
|
+
function isPositiveInteger(value) {
|
|
85
|
+
return Number.isInteger(value) && value > 0;
|
|
86
|
+
}
|
|
87
|
+
function isNonNegativeInteger(value) {
|
|
88
|
+
return Number.isInteger(value) && value >= 0;
|
|
89
|
+
}
|
|
70
90
|
function createOwnerGeneration() {
|
|
71
91
|
return randomInt(1, 2 ** 48);
|
|
72
92
|
}
|
|
@@ -126,15 +146,6 @@ async function readQueueOwnerRecord(sessionId) {
|
|
|
126
146
|
return;
|
|
127
147
|
}
|
|
128
148
|
}
|
|
129
|
-
function isProcessAlive(pid) {
|
|
130
|
-
if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
131
|
-
try {
|
|
132
|
-
process.kill(pid, 0);
|
|
133
|
-
return true;
|
|
134
|
-
} catch {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
149
|
async function terminateProcess(pid) {
|
|
139
150
|
if (!isProcessAlive(pid)) return false;
|
|
140
151
|
try {
|
|
@@ -351,18 +362,22 @@ function isNonInteractivePermissionPolicy(value) {
|
|
|
351
362
|
return value === "deny" || value === "fail";
|
|
352
363
|
}
|
|
353
364
|
function isPermissionPolicy(value) {
|
|
354
|
-
|
|
355
|
-
if (
|
|
356
|
-
|
|
365
|
+
const record = asRecord$2(value);
|
|
366
|
+
if (!record) return false;
|
|
367
|
+
return hasValidPermissionRuleLists(record) && hasValidPermissionDefaultAction(record);
|
|
368
|
+
}
|
|
369
|
+
function hasValidPermissionRuleLists(record) {
|
|
357
370
|
for (const key of [
|
|
358
371
|
"autoApprove",
|
|
359
372
|
"autoDeny",
|
|
360
373
|
"escalate"
|
|
361
|
-
])
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
374
|
+
]) if (!isOptionalStringList(record[key])) return false;
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
function isOptionalStringList(value) {
|
|
378
|
+
return value == null || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
379
|
+
}
|
|
380
|
+
function hasValidPermissionDefaultAction(record) {
|
|
366
381
|
return record.defaultAction == null || record.defaultAction === "approve" || record.defaultAction === "deny" || record.defaultAction === "escalate";
|
|
367
382
|
}
|
|
368
383
|
function isOutputErrorCode(value) {
|
|
@@ -373,35 +388,58 @@ function isOutputErrorOrigin(value) {
|
|
|
373
388
|
}
|
|
374
389
|
function isPermissionEscalationEvent(value) {
|
|
375
390
|
const event = asRecord$2(value);
|
|
376
|
-
return event
|
|
391
|
+
return !!event && hasRequiredPermissionEscalationFields(event) && hasOptionalStringFields(event, [
|
|
392
|
+
"toolName",
|
|
393
|
+
"toolKind",
|
|
394
|
+
"matchedRule"
|
|
395
|
+
]);
|
|
396
|
+
}
|
|
397
|
+
function hasRequiredPermissionEscalationFields(event) {
|
|
398
|
+
return event.type === "permission_escalation" && typeof event.sessionId === "string" && typeof event.toolCallId === "string" && typeof event.toolTitle === "string" && event.action === "escalate" && typeof event.message === "string" && typeof event.timestamp === "string";
|
|
399
|
+
}
|
|
400
|
+
function hasOptionalStringFields(record, keys) {
|
|
401
|
+
return keys.every((key) => record[key] == null || typeof record[key] === "string");
|
|
377
402
|
}
|
|
378
403
|
function parseSessionOptions(value) {
|
|
379
404
|
if (value == null) return;
|
|
380
405
|
const record = asRecord$2(value);
|
|
381
406
|
if (!record) return null;
|
|
382
407
|
const sessionOptions = {};
|
|
383
|
-
if (record.model
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (record.allowedTools != null) {
|
|
388
|
-
if (!Array.isArray(record.allowedTools)) return null;
|
|
389
|
-
const allowedTools = record.allowedTools.filter((tool) => typeof tool === "string");
|
|
390
|
-
if (allowedTools.length !== record.allowedTools.length) return null;
|
|
391
|
-
sessionOptions.allowedTools = allowedTools;
|
|
392
|
-
}
|
|
393
|
-
if (record.maxTurns != null) {
|
|
394
|
-
if (typeof record.maxTurns !== "number" || !Number.isFinite(record.maxTurns)) return null;
|
|
395
|
-
sessionOptions.maxTurns = Math.max(1, Math.round(record.maxTurns));
|
|
396
|
-
}
|
|
397
|
-
if (record.systemPrompt != null) if (typeof record.systemPrompt === "string") sessionOptions.systemPrompt = record.systemPrompt;
|
|
398
|
-
else {
|
|
399
|
-
const systemPrompt = asRecord$2(record.systemPrompt);
|
|
400
|
-
if (!systemPrompt || typeof systemPrompt.append !== "string") return null;
|
|
401
|
-
sessionOptions.systemPrompt = { append: systemPrompt.append };
|
|
402
|
-
}
|
|
408
|
+
if (!assignSessionModel(sessionOptions, record.model)) return null;
|
|
409
|
+
if (!assignSessionAllowedTools(sessionOptions, record.allowedTools)) return null;
|
|
410
|
+
if (!assignSessionMaxTurns(sessionOptions, record.maxTurns)) return null;
|
|
411
|
+
if (!assignSessionSystemPrompt(sessionOptions, record.systemPrompt)) return null;
|
|
403
412
|
return sessionOptions;
|
|
404
413
|
}
|
|
414
|
+
function assignSessionModel(options, value) {
|
|
415
|
+
if (value == null) return true;
|
|
416
|
+
if (typeof value !== "string" || value.trim().length === 0) return false;
|
|
417
|
+
options.model = value;
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
function assignSessionAllowedTools(options, value) {
|
|
421
|
+
if (value == null) return true;
|
|
422
|
+
if (!Array.isArray(value) || value.some((tool) => typeof tool !== "string")) return false;
|
|
423
|
+
options.allowedTools = value;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
function assignSessionMaxTurns(options, value) {
|
|
427
|
+
if (value == null) return true;
|
|
428
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
429
|
+
options.maxTurns = Math.max(1, Math.round(value));
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
function assignSessionSystemPrompt(options, value) {
|
|
433
|
+
if (value == null) return true;
|
|
434
|
+
if (typeof value === "string") {
|
|
435
|
+
options.systemPrompt = value;
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
const systemPrompt = asRecord$2(value);
|
|
439
|
+
if (!systemPrompt || typeof systemPrompt.append !== "string") return false;
|
|
440
|
+
options.systemPrompt = { append: systemPrompt.append };
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
405
443
|
function parseOwnerGeneration(value) {
|
|
406
444
|
if (value == null) return;
|
|
407
445
|
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return null;
|
|
@@ -414,189 +452,245 @@ function parseNonNegativeInteger(value) {
|
|
|
414
452
|
}
|
|
415
453
|
function parseQueueRequest(raw) {
|
|
416
454
|
const request = asRecord$2(raw);
|
|
417
|
-
if (!request) return null;
|
|
418
|
-
if (typeof request.type !== "string" || typeof request.requestId !== "string") return null;
|
|
455
|
+
if (!request || typeof request.type !== "string" || typeof request.requestId !== "string") return null;
|
|
419
456
|
const ownerGeneration = parseOwnerGeneration(request.ownerGeneration);
|
|
420
457
|
if (ownerGeneration === null) return null;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const permissionPolicy = request.permissionPolicy == null ? void 0 : isPermissionPolicy(request.permissionPolicy) ? request.permissionPolicy : null;
|
|
427
|
-
const suppressSdkConsoleErrors = request.suppressSdkConsoleErrors == null ? void 0 : typeof request.suppressSdkConsoleErrors === "boolean" ? request.suppressSdkConsoleErrors : null;
|
|
428
|
-
const promptRetries = parseNonNegativeInteger(request.promptRetries);
|
|
429
|
-
const sessionOptions = parseSessionOptions(request.sessionOptions);
|
|
430
|
-
const prompt = request.prompt == null ? void 0 : isPromptInput(request.prompt) ? request.prompt : null;
|
|
431
|
-
if (typeof request.message !== "string" || !isPermissionMode(request.permissionMode) || resumePolicy === null || prompt === null || nonInteractivePermissions === null || permissionPolicy === null || suppressSdkConsoleErrors === null || promptRetries === null || sessionOptions === null || typeof request.waitForCompletion !== "boolean") return null;
|
|
432
|
-
return {
|
|
433
|
-
type: "submit_prompt",
|
|
434
|
-
requestId: request.requestId,
|
|
435
|
-
ownerGeneration,
|
|
436
|
-
message: request.message,
|
|
437
|
-
prompt: prompt ?? textPrompt(request.message),
|
|
438
|
-
permissionMode: request.permissionMode,
|
|
439
|
-
...resumePolicy !== void 0 ? { resumePolicy } : {},
|
|
440
|
-
nonInteractivePermissions,
|
|
441
|
-
...permissionPolicy !== void 0 ? { permissionPolicy } : {},
|
|
442
|
-
timeoutMs,
|
|
443
|
-
...suppressSdkConsoleErrors !== void 0 ? { suppressSdkConsoleErrors } : {},
|
|
444
|
-
...promptRetries !== void 0 ? { promptRetries } : {},
|
|
445
|
-
waitForCompletion: request.waitForCompletion,
|
|
446
|
-
...sessionOptions !== void 0 ? { sessionOptions } : {}
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
if (request.type === "cancel_prompt") return {
|
|
450
|
-
type: "cancel_prompt",
|
|
451
|
-
requestId: request.requestId,
|
|
452
|
-
ownerGeneration
|
|
453
|
-
};
|
|
454
|
-
if (request.type === "close_session") return {
|
|
455
|
-
type: "close_session",
|
|
456
|
-
requestId: request.requestId,
|
|
458
|
+
return parseTypedQueueRequest(request, parseQueueRequestContext(request.requestId, ownerGeneration, request.timeoutMs));
|
|
459
|
+
}
|
|
460
|
+
function parseQueueRequestContext(requestId, ownerGeneration, timeoutRaw) {
|
|
461
|
+
return {
|
|
462
|
+
requestId,
|
|
457
463
|
ownerGeneration,
|
|
458
|
-
timeoutMs
|
|
464
|
+
timeoutMs: parsePositiveTimeout(timeoutRaw)
|
|
459
465
|
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
type: "set_model",
|
|
474
|
-
requestId: request.requestId,
|
|
475
|
-
ownerGeneration,
|
|
476
|
-
modelId: request.modelId,
|
|
477
|
-
timeoutMs
|
|
466
|
+
}
|
|
467
|
+
function parsePositiveTimeout(value) {
|
|
468
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return;
|
|
469
|
+
return Math.round(value);
|
|
470
|
+
}
|
|
471
|
+
function parseTypedQueueRequest(request, context) {
|
|
472
|
+
switch (request.type) {
|
|
473
|
+
case "submit_prompt": return parseSubmitRequest(request, context);
|
|
474
|
+
case "cancel_prompt": return {
|
|
475
|
+
type: "cancel_prompt",
|
|
476
|
+
requestId: context.requestId,
|
|
477
|
+
ownerGeneration: context.ownerGeneration
|
|
478
478
|
};
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return {
|
|
483
|
-
type: "set_config_option",
|
|
484
|
-
requestId: request.requestId,
|
|
485
|
-
ownerGeneration,
|
|
486
|
-
configId: request.configId,
|
|
487
|
-
value: request.value,
|
|
488
|
-
timeoutMs
|
|
479
|
+
case "close_session": return {
|
|
480
|
+
type: "close_session",
|
|
481
|
+
...context
|
|
489
482
|
};
|
|
483
|
+
case "set_mode": return parseStringFieldRequest(request, context, "set_mode", "modeId");
|
|
484
|
+
case "set_model": return parseStringFieldRequest(request, context, "set_model", "modelId");
|
|
485
|
+
case "set_config_option": return parseSetConfigOptionRequest(request, context);
|
|
486
|
+
default: return null;
|
|
490
487
|
}
|
|
491
|
-
|
|
488
|
+
}
|
|
489
|
+
function parseSubmitRequest(request, context) {
|
|
490
|
+
const parsed = parseSubmitRequestFields(request);
|
|
491
|
+
if (!parsed) return null;
|
|
492
|
+
return {
|
|
493
|
+
type: "submit_prompt",
|
|
494
|
+
requestId: context.requestId,
|
|
495
|
+
ownerGeneration: context.ownerGeneration,
|
|
496
|
+
message: parsed.message,
|
|
497
|
+
prompt: parsed.prompt ?? textPrompt(parsed.message),
|
|
498
|
+
permissionMode: parsed.permissionMode,
|
|
499
|
+
...parsed.resumePolicy !== void 0 ? { resumePolicy: parsed.resumePolicy } : {},
|
|
500
|
+
nonInteractivePermissions: parsed.nonInteractivePermissions,
|
|
501
|
+
...parsed.permissionPolicy !== void 0 ? { permissionPolicy: parsed.permissionPolicy } : {},
|
|
502
|
+
timeoutMs: context.timeoutMs,
|
|
503
|
+
...parsed.suppressSdkConsoleErrors !== void 0 ? { suppressSdkConsoleErrors: parsed.suppressSdkConsoleErrors } : {},
|
|
504
|
+
...parsed.promptRetries !== void 0 ? { promptRetries: parsed.promptRetries } : {},
|
|
505
|
+
waitForCompletion: parsed.waitForCompletion,
|
|
506
|
+
...parsed.sessionOptions !== void 0 ? { sessionOptions: parsed.sessionOptions } : {}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function parseSubmitRequestFields(request) {
|
|
510
|
+
const parsed = {
|
|
511
|
+
message: typeof request.message === "string" ? request.message : null,
|
|
512
|
+
prompt: parseOptionalValue(request.prompt, isPromptInput),
|
|
513
|
+
permissionMode: parseRequiredValue(request.permissionMode, isPermissionMode),
|
|
514
|
+
resumePolicy: parseOptionalValue(request.resumePolicy, isSessionResumePolicy),
|
|
515
|
+
nonInteractivePermissions: parseOptionalValue(request.nonInteractivePermissions, isNonInteractivePermissionPolicy),
|
|
516
|
+
permissionPolicy: parseOptionalValue(request.permissionPolicy, isPermissionPolicy),
|
|
517
|
+
suppressSdkConsoleErrors: parseOptionalBoolean(request.suppressSdkConsoleErrors),
|
|
518
|
+
promptRetries: parseNonNegativeInteger(request.promptRetries),
|
|
519
|
+
waitForCompletion: typeof request.waitForCompletion === "boolean" ? request.waitForCompletion : null,
|
|
520
|
+
sessionOptions: parseSessionOptions(request.sessionOptions)
|
|
521
|
+
};
|
|
522
|
+
if (Object.values(parsed).some((value) => value === null)) return null;
|
|
523
|
+
return parsed;
|
|
524
|
+
}
|
|
525
|
+
function parseOptionalValue(value, guard) {
|
|
526
|
+
if (value == null) return;
|
|
527
|
+
return guard(value) ? value : null;
|
|
528
|
+
}
|
|
529
|
+
function parseRequiredValue(value, guard) {
|
|
530
|
+
return guard(value) ? value : null;
|
|
531
|
+
}
|
|
532
|
+
function parseOptionalBoolean(value) {
|
|
533
|
+
if (value == null) return;
|
|
534
|
+
return typeof value === "boolean" ? value : null;
|
|
535
|
+
}
|
|
536
|
+
function parseStringFieldRequest(request, context, type, field) {
|
|
537
|
+
const value = parseNonEmptyString(request[field]);
|
|
538
|
+
if (!value) return null;
|
|
539
|
+
return {
|
|
540
|
+
type,
|
|
541
|
+
...context,
|
|
542
|
+
[field]: value
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function parseSetConfigOptionRequest(request, context) {
|
|
546
|
+
const configId = parseNonEmptyString(request.configId);
|
|
547
|
+
const value = parseNonEmptyString(request.value);
|
|
548
|
+
if (!configId || !value) return null;
|
|
549
|
+
return {
|
|
550
|
+
type: "set_config_option",
|
|
551
|
+
...context,
|
|
552
|
+
configId,
|
|
553
|
+
value
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function parseNonEmptyString(value) {
|
|
557
|
+
if (typeof value !== "string" || value.trim().length === 0) return null;
|
|
558
|
+
return value;
|
|
492
559
|
}
|
|
493
560
|
function parseSessionSendResult(raw) {
|
|
494
561
|
const result = asRecord$2(raw);
|
|
495
|
-
if (!result) return null;
|
|
496
|
-
if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") return null;
|
|
562
|
+
if (!result || !hasValidSessionSendResultCore(result)) return null;
|
|
497
563
|
const permissionStats = asRecord$2(result.permissionStats);
|
|
498
564
|
const record = asRecord$2(result.record);
|
|
499
565
|
if (!permissionStats || !record) return null;
|
|
500
|
-
if (!(
|
|
501
|
-
if (!(
|
|
566
|
+
if (!hasValidPermissionStats(permissionStats)) return null;
|
|
567
|
+
if (!hasValidSessionRecordShape(record)) return null;
|
|
502
568
|
return result;
|
|
503
569
|
}
|
|
570
|
+
function hasValidSessionSendResultCore(result) {
|
|
571
|
+
return typeof result.stopReason === "string" && typeof result.sessionId === "string" && typeof result.resumed === "boolean";
|
|
572
|
+
}
|
|
573
|
+
function hasValidPermissionStats(permissionStats) {
|
|
574
|
+
return [
|
|
575
|
+
"requested",
|
|
576
|
+
"approved",
|
|
577
|
+
"denied",
|
|
578
|
+
"cancelled"
|
|
579
|
+
].every((key) => typeof permissionStats[key] === "number");
|
|
580
|
+
}
|
|
581
|
+
function hasValidSessionRecordShape(record) {
|
|
582
|
+
return hasStringFields(record, [
|
|
583
|
+
"acpxRecordId",
|
|
584
|
+
"acpSessionId",
|
|
585
|
+
"agentCommand",
|
|
586
|
+
"cwd",
|
|
587
|
+
"createdAt",
|
|
588
|
+
"lastUsedAt",
|
|
589
|
+
"updated_at"
|
|
590
|
+
]) && Array.isArray(record.messages) && typeof record.lastSeq === "number" && Number.isInteger(record.lastSeq) && !!record.eventLog && typeof record.eventLog === "object";
|
|
591
|
+
}
|
|
592
|
+
function hasStringFields(record, keys) {
|
|
593
|
+
return keys.every((key) => typeof record[key] === "string");
|
|
594
|
+
}
|
|
504
595
|
function parseQueueOwnerMessage(raw) {
|
|
505
596
|
const message = asRecord$2(raw);
|
|
506
|
-
if (!message || typeof message.type !== "string") return null;
|
|
507
|
-
if (typeof message.requestId !== "string") return null;
|
|
597
|
+
if (!message || typeof message.type !== "string" || typeof message.requestId !== "string") return null;
|
|
508
598
|
const ownerGeneration = parseOwnerGeneration(message.ownerGeneration);
|
|
509
599
|
if (ownerGeneration === null) return null;
|
|
510
|
-
|
|
511
|
-
type: "accepted",
|
|
600
|
+
return parseTypedQueueOwnerMessage(message, {
|
|
512
601
|
requestId: message.requestId,
|
|
513
602
|
ownerGeneration
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function parseTypedQueueOwnerMessage(message, context) {
|
|
606
|
+
const parser = queueOwnerMessageParser(message.type);
|
|
607
|
+
return parser ? parser(message, context) : null;
|
|
608
|
+
}
|
|
609
|
+
const QUEUE_OWNER_MESSAGE_PARSERS = {
|
|
610
|
+
accepted: (_message, context) => ({
|
|
611
|
+
type: "accepted",
|
|
612
|
+
...context
|
|
613
|
+
}),
|
|
614
|
+
event: parseEventOwnerMessage,
|
|
615
|
+
permission_escalation: parsePermissionEscalationOwnerMessage,
|
|
616
|
+
result: parseResultOwnerMessage,
|
|
617
|
+
cancel_result: (message, context) => parseBooleanResultOwnerMessage(message, context, "cancel_result", "cancelled"),
|
|
618
|
+
close_session_result: (message, context) => parseBooleanResultOwnerMessage(message, context, "close_session_result", "closed"),
|
|
619
|
+
set_mode_result: (message, context) => parseStringResultOwnerMessage(message, context, "set_mode_result", "modeId"),
|
|
620
|
+
set_model_result: (message, context) => parseStringResultOwnerMessage(message, context, "set_model_result", "modelId"),
|
|
621
|
+
set_config_option_result: parseSetConfigOptionOwnerMessage,
|
|
622
|
+
error: parseErrorOwnerMessage
|
|
623
|
+
};
|
|
624
|
+
function queueOwnerMessageParser(type) {
|
|
625
|
+
return Object.hasOwn(QUEUE_OWNER_MESSAGE_PARSERS, type) ? QUEUE_OWNER_MESSAGE_PARSERS[type] : void 0;
|
|
626
|
+
}
|
|
627
|
+
function parseEventOwnerMessage(message, context) {
|
|
628
|
+
if (!isAcpJsonRpcMessage(message.message)) return null;
|
|
629
|
+
return {
|
|
630
|
+
type: "event",
|
|
631
|
+
...context,
|
|
632
|
+
message: message.message
|
|
514
633
|
};
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
requestId: message.requestId,
|
|
576
|
-
ownerGeneration,
|
|
577
|
-
response
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
if (message.type === "error") {
|
|
581
|
-
if (typeof message.message !== "string" || !isOutputErrorCode(message.code) || !isOutputErrorOrigin(message.origin)) return null;
|
|
582
|
-
const detailCode = typeof message.detailCode === "string" && message.detailCode.trim().length > 0 ? message.detailCode : void 0;
|
|
583
|
-
const retryable = typeof message.retryable === "boolean" ? message.retryable : void 0;
|
|
584
|
-
const acp = toAcpErrorPayload(message.acp);
|
|
585
|
-
const outputAlreadyEmitted = typeof message.outputAlreadyEmitted === "boolean" ? message.outputAlreadyEmitted : void 0;
|
|
586
|
-
return {
|
|
587
|
-
type: "error",
|
|
588
|
-
requestId: message.requestId,
|
|
589
|
-
ownerGeneration,
|
|
590
|
-
code: message.code,
|
|
591
|
-
detailCode,
|
|
592
|
-
origin: message.origin,
|
|
593
|
-
message: message.message,
|
|
594
|
-
retryable,
|
|
595
|
-
acp,
|
|
596
|
-
...outputAlreadyEmitted === void 0 ? {} : { outputAlreadyEmitted }
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
return null;
|
|
634
|
+
}
|
|
635
|
+
function parsePermissionEscalationOwnerMessage(message, context) {
|
|
636
|
+
if (!isPermissionEscalationEvent(message.event)) return null;
|
|
637
|
+
return {
|
|
638
|
+
type: "permission_escalation",
|
|
639
|
+
...context,
|
|
640
|
+
event: message.event
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function parseResultOwnerMessage(message, context) {
|
|
644
|
+
const result = parseSessionSendResult(message.result);
|
|
645
|
+
if (!result) return null;
|
|
646
|
+
return {
|
|
647
|
+
type: "result",
|
|
648
|
+
...context,
|
|
649
|
+
result
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function parseBooleanResultOwnerMessage(message, context, type, field) {
|
|
653
|
+
if (typeof message[field] !== "boolean") return null;
|
|
654
|
+
return {
|
|
655
|
+
type,
|
|
656
|
+
...context,
|
|
657
|
+
[field]: message[field]
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function parseStringResultOwnerMessage(message, context, type, field) {
|
|
661
|
+
if (typeof message[field] !== "string") return null;
|
|
662
|
+
return {
|
|
663
|
+
type,
|
|
664
|
+
...context,
|
|
665
|
+
[field]: message[field]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function parseSetConfigOptionOwnerMessage(message, context) {
|
|
669
|
+
const response = asRecord$2(message.response);
|
|
670
|
+
if (!response || !Array.isArray(response.configOptions)) return null;
|
|
671
|
+
return {
|
|
672
|
+
type: "set_config_option_result",
|
|
673
|
+
...context,
|
|
674
|
+
response
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function parseErrorOwnerMessage(message, context) {
|
|
678
|
+
if (!isValidOwnerErrorCore(message)) return null;
|
|
679
|
+
const outputAlreadyEmitted = typeof message.outputAlreadyEmitted === "boolean" ? message.outputAlreadyEmitted : void 0;
|
|
680
|
+
return {
|
|
681
|
+
type: "error",
|
|
682
|
+
...context,
|
|
683
|
+
code: message.code,
|
|
684
|
+
detailCode: parseNonEmptyString(message.detailCode) ?? void 0,
|
|
685
|
+
origin: message.origin,
|
|
686
|
+
message: message.message,
|
|
687
|
+
retryable: typeof message.retryable === "boolean" ? message.retryable : void 0,
|
|
688
|
+
acp: toAcpErrorPayload(message.acp),
|
|
689
|
+
...outputAlreadyEmitted === void 0 ? {} : { outputAlreadyEmitted }
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function isValidOwnerErrorCore(message) {
|
|
693
|
+
return typeof message.message === "string" && isOutputErrorCode(message.code) && isOutputErrorOrigin(message.origin);
|
|
600
694
|
}
|
|
601
695
|
//#endregion
|
|
602
696
|
//#region src/cli/queue/ipc-server.ts
|
|
@@ -753,6 +847,135 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
753
847
|
if (!options.socket.destroyed) options.socket.end();
|
|
754
848
|
});
|
|
755
849
|
}
|
|
850
|
+
failRequest(socket, requestId, message, detailCode) {
|
|
851
|
+
writeQueueMessage(socket, {
|
|
852
|
+
...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
|
|
853
|
+
ownerGeneration: this.ownerGeneration
|
|
854
|
+
});
|
|
855
|
+
socket.end();
|
|
856
|
+
}
|
|
857
|
+
parseRequestLine(socket, line) {
|
|
858
|
+
let parsed;
|
|
859
|
+
try {
|
|
860
|
+
parsed = JSON.parse(line);
|
|
861
|
+
} catch {
|
|
862
|
+
this.failRequest(socket, "unknown", "Invalid queue request payload", "QUEUE_REQUEST_PAYLOAD_INVALID_JSON");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const request = parseQueueRequest(parsed);
|
|
866
|
+
if (!request) this.failRequest(socket, "unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
|
|
867
|
+
return request ?? void 0;
|
|
868
|
+
}
|
|
869
|
+
rejectStaleOwnerGeneration(socket, request) {
|
|
870
|
+
if (request.ownerGeneration === void 0 || this.ownerGeneration === void 0 || request.ownerGeneration === this.ownerGeneration) return false;
|
|
871
|
+
this.failRequest(socket, request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
handleControlQueueRequest(socket, request) {
|
|
875
|
+
if (request.type === "cancel_prompt") {
|
|
876
|
+
this.handleControlRequest({
|
|
877
|
+
socket,
|
|
878
|
+
requestId: request.requestId,
|
|
879
|
+
run: async () => ({
|
|
880
|
+
type: "cancel_result",
|
|
881
|
+
requestId: request.requestId,
|
|
882
|
+
cancelled: await this.controlHandlers.cancelPrompt()
|
|
883
|
+
})
|
|
884
|
+
});
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (request.type === "close_session") {
|
|
888
|
+
this.handleControlRequest({
|
|
889
|
+
socket,
|
|
890
|
+
requestId: request.requestId,
|
|
891
|
+
run: async () => ({
|
|
892
|
+
type: "close_session_result",
|
|
893
|
+
requestId: request.requestId,
|
|
894
|
+
closed: await this.controlHandlers.closeSession(request.timeoutMs)
|
|
895
|
+
})
|
|
896
|
+
});
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
return this.handleSessionControlQueueRequest(socket, request);
|
|
900
|
+
}
|
|
901
|
+
handleSessionControlQueueRequest(socket, request) {
|
|
902
|
+
if (request.type === "set_mode") {
|
|
903
|
+
this.handleControlRequest({
|
|
904
|
+
socket,
|
|
905
|
+
requestId: request.requestId,
|
|
906
|
+
run: async () => {
|
|
907
|
+
await this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs);
|
|
908
|
+
return {
|
|
909
|
+
type: "set_mode_result",
|
|
910
|
+
requestId: request.requestId,
|
|
911
|
+
modeId: request.modeId
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
if (request.type === "set_model") {
|
|
918
|
+
this.handleControlRequest({
|
|
919
|
+
socket,
|
|
920
|
+
requestId: request.requestId,
|
|
921
|
+
run: async () => {
|
|
922
|
+
await this.controlHandlers.setSessionModel(request.modelId, request.timeoutMs);
|
|
923
|
+
return {
|
|
924
|
+
type: "set_model_result",
|
|
925
|
+
requestId: request.requestId,
|
|
926
|
+
modelId: request.modelId
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
if (request.type === "set_config_option") {
|
|
933
|
+
this.handleControlRequest({
|
|
934
|
+
socket,
|
|
935
|
+
requestId: request.requestId,
|
|
936
|
+
run: async () => ({
|
|
937
|
+
type: "set_config_option_result",
|
|
938
|
+
requestId: request.requestId,
|
|
939
|
+
response: await this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs)
|
|
940
|
+
})
|
|
941
|
+
});
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
enqueuePromptRequest(socket, request) {
|
|
947
|
+
const task = {
|
|
948
|
+
requestId: request.requestId,
|
|
949
|
+
message: request.message,
|
|
950
|
+
prompt: request.prompt ?? textPrompt(request.message),
|
|
951
|
+
permissionMode: request.permissionMode,
|
|
952
|
+
resumePolicy: request.resumePolicy,
|
|
953
|
+
nonInteractivePermissions: request.nonInteractivePermissions,
|
|
954
|
+
permissionPolicy: request.permissionPolicy,
|
|
955
|
+
timeoutMs: request.timeoutMs,
|
|
956
|
+
suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
|
|
957
|
+
promptRetries: request.promptRetries,
|
|
958
|
+
sessionOptions: request.sessionOptions,
|
|
959
|
+
waitForCompletion: request.waitForCompletion,
|
|
960
|
+
enqueuedAt: Date.now(),
|
|
961
|
+
send: (message) => {
|
|
962
|
+
writeQueueMessage(socket, {
|
|
963
|
+
...message,
|
|
964
|
+
ownerGeneration: this.ownerGeneration
|
|
965
|
+
});
|
|
966
|
+
},
|
|
967
|
+
close: () => {
|
|
968
|
+
if (!socket.destroyed) socket.end();
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
writeQueueMessage(socket, {
|
|
972
|
+
type: "accepted",
|
|
973
|
+
requestId: request.requestId,
|
|
974
|
+
ownerGeneration: this.ownerGeneration
|
|
975
|
+
});
|
|
976
|
+
if (!request.waitForCompletion) task.close();
|
|
977
|
+
this.enqueue(task);
|
|
978
|
+
}
|
|
756
979
|
handleConnection(socket) {
|
|
757
980
|
socket.setEncoding("utf8");
|
|
758
981
|
if (this.closed) {
|
|
@@ -762,129 +985,17 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
762
985
|
}
|
|
763
986
|
let buffer = "";
|
|
764
987
|
let handled = false;
|
|
765
|
-
const fail = (requestId, message, detailCode) => {
|
|
766
|
-
writeQueueMessage(socket, {
|
|
767
|
-
...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
|
|
768
|
-
ownerGeneration: this.ownerGeneration
|
|
769
|
-
});
|
|
770
|
-
socket.end();
|
|
771
|
-
};
|
|
772
988
|
const processLine = (line) => {
|
|
773
989
|
if (handled) return;
|
|
774
990
|
handled = true;
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
const request = parseQueueRequest(parsed);
|
|
783
|
-
if (!request) {
|
|
784
|
-
fail("unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
if (request.ownerGeneration !== void 0 && this.ownerGeneration !== void 0 && request.ownerGeneration !== this.ownerGeneration) {
|
|
788
|
-
fail(request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
if (request.type === "cancel_prompt") {
|
|
792
|
-
this.handleControlRequest({
|
|
793
|
-
socket,
|
|
794
|
-
requestId: request.requestId,
|
|
795
|
-
run: async () => ({
|
|
796
|
-
type: "cancel_result",
|
|
797
|
-
requestId: request.requestId,
|
|
798
|
-
cancelled: await this.controlHandlers.cancelPrompt()
|
|
799
|
-
})
|
|
800
|
-
});
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
if (request.type === "close_session") {
|
|
804
|
-
this.handleControlRequest({
|
|
805
|
-
socket,
|
|
806
|
-
requestId: request.requestId,
|
|
807
|
-
run: async () => ({
|
|
808
|
-
type: "close_session_result",
|
|
809
|
-
requestId: request.requestId,
|
|
810
|
-
closed: await this.controlHandlers.closeSession(request.timeoutMs)
|
|
811
|
-
})
|
|
812
|
-
});
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
if (request.type === "set_mode") {
|
|
816
|
-
this.handleControlRequest({
|
|
817
|
-
socket,
|
|
818
|
-
requestId: request.requestId,
|
|
819
|
-
run: async () => {
|
|
820
|
-
await this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs);
|
|
821
|
-
return {
|
|
822
|
-
type: "set_mode_result",
|
|
823
|
-
requestId: request.requestId,
|
|
824
|
-
modeId: request.modeId
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
if (request.type === "set_model") {
|
|
831
|
-
this.handleControlRequest({
|
|
832
|
-
socket,
|
|
833
|
-
requestId: request.requestId,
|
|
834
|
-
run: async () => {
|
|
835
|
-
await this.controlHandlers.setSessionModel(request.modelId, request.timeoutMs);
|
|
836
|
-
return {
|
|
837
|
-
type: "set_model_result",
|
|
838
|
-
requestId: request.requestId,
|
|
839
|
-
modelId: request.modelId
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
});
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (request.type === "set_config_option") {
|
|
846
|
-
this.handleControlRequest({
|
|
847
|
-
socket,
|
|
848
|
-
requestId: request.requestId,
|
|
849
|
-
run: async () => ({
|
|
850
|
-
type: "set_config_option_result",
|
|
851
|
-
requestId: request.requestId,
|
|
852
|
-
response: await this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs)
|
|
853
|
-
})
|
|
854
|
-
});
|
|
991
|
+
const request = this.parseRequestLine(socket, line);
|
|
992
|
+
if (!request || this.rejectStaleOwnerGeneration(socket, request)) return;
|
|
993
|
+
if (this.handleControlQueueRequest(socket, request)) return;
|
|
994
|
+
if (request.type !== "submit_prompt") {
|
|
995
|
+
this.failRequest(socket, request.requestId, "Invalid queue request", "QUEUE_REQUEST_INVALID");
|
|
855
996
|
return;
|
|
856
997
|
}
|
|
857
|
-
|
|
858
|
-
requestId: request.requestId,
|
|
859
|
-
message: request.message,
|
|
860
|
-
prompt: request.prompt ?? textPrompt(request.message),
|
|
861
|
-
permissionMode: request.permissionMode,
|
|
862
|
-
resumePolicy: request.resumePolicy,
|
|
863
|
-
nonInteractivePermissions: request.nonInteractivePermissions,
|
|
864
|
-
permissionPolicy: request.permissionPolicy,
|
|
865
|
-
timeoutMs: request.timeoutMs,
|
|
866
|
-
suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
|
|
867
|
-
promptRetries: request.promptRetries,
|
|
868
|
-
sessionOptions: request.sessionOptions,
|
|
869
|
-
waitForCompletion: request.waitForCompletion,
|
|
870
|
-
enqueuedAt: Date.now(),
|
|
871
|
-
send: (message) => {
|
|
872
|
-
writeQueueMessage(socket, {
|
|
873
|
-
...message,
|
|
874
|
-
ownerGeneration: this.ownerGeneration
|
|
875
|
-
});
|
|
876
|
-
},
|
|
877
|
-
close: () => {
|
|
878
|
-
if (!socket.destroyed) socket.end();
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
writeQueueMessage(socket, {
|
|
882
|
-
type: "accepted",
|
|
883
|
-
requestId: request.requestId,
|
|
884
|
-
ownerGeneration: this.ownerGeneration
|
|
885
|
-
});
|
|
886
|
-
if (!request.waitForCompletion) task.close();
|
|
887
|
-
this.enqueue(task);
|
|
998
|
+
this.enqueuePromptRequest(socket, request);
|
|
888
999
|
};
|
|
889
1000
|
socket.on("data", (chunk) => {
|
|
890
1001
|
buffer += chunk;
|
|
@@ -920,6 +1031,16 @@ function assertOwnerGeneration(owner, message) {
|
|
|
920
1031
|
});
|
|
921
1032
|
return message;
|
|
922
1033
|
}
|
|
1034
|
+
function queueConnectionErrorFromOwner(message, outputAlreadyEmitted) {
|
|
1035
|
+
return new QueueConnectionError(message.message, {
|
|
1036
|
+
outputCode: message.code,
|
|
1037
|
+
detailCode: message.detailCode,
|
|
1038
|
+
origin: message.origin ?? "queue",
|
|
1039
|
+
retryable: message.retryable,
|
|
1040
|
+
acp: message.acp,
|
|
1041
|
+
...outputAlreadyEmitted ? { outputAlreadyEmitted: true } : {}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
923
1044
|
function makeMalformedQueueMessageError() {
|
|
924
1045
|
return new QueueProtocolError("Queue owner sent malformed message", {
|
|
925
1046
|
detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
|
|
@@ -927,6 +1048,22 @@ function makeMalformedQueueMessageError() {
|
|
|
927
1048
|
retryable: true
|
|
928
1049
|
});
|
|
929
1050
|
}
|
|
1051
|
+
function emitQueueOwnerError(formatter, policy, sessionId, message) {
|
|
1052
|
+
formatter.setContext({ sessionId });
|
|
1053
|
+
const queueErrorAlreadyEmitted = policy?.queueErrorAlreadyEmitted ?? true;
|
|
1054
|
+
if (message.outputAlreadyEmitted !== true || !queueErrorAlreadyEmitted) {
|
|
1055
|
+
formatter.onError({
|
|
1056
|
+
code: message.code ?? "RUNTIME",
|
|
1057
|
+
detailCode: message.detailCode,
|
|
1058
|
+
origin: message.origin ?? "queue",
|
|
1059
|
+
message: message.message,
|
|
1060
|
+
retryable: message.retryable,
|
|
1061
|
+
acp: message.acp
|
|
1062
|
+
});
|
|
1063
|
+
formatter.flush();
|
|
1064
|
+
}
|
|
1065
|
+
return queueConnectionErrorFromOwner(message, queueErrorAlreadyEmitted);
|
|
1066
|
+
}
|
|
930
1067
|
function parseQueueOwnerResponseLine(owner, requestId, line) {
|
|
931
1068
|
let parsed;
|
|
932
1069
|
try {
|
|
@@ -1011,6 +1148,47 @@ async function runQueueOwnerRequest(options) {
|
|
|
1011
1148
|
socket.write(`${JSON.stringify(options.request)}\n`);
|
|
1012
1149
|
});
|
|
1013
1150
|
}
|
|
1151
|
+
function missingQueueAckError() {
|
|
1152
|
+
return new QueueConnectionError("Queue owner did not acknowledge request", {
|
|
1153
|
+
detailCode: "QUEUE_ACK_MISSING",
|
|
1154
|
+
origin: "queue",
|
|
1155
|
+
retryable: true
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
function unexpectedQueueResponseError() {
|
|
1159
|
+
return new QueueProtocolError("Queue owner returned unexpected response", {
|
|
1160
|
+
detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
|
|
1161
|
+
origin: "queue",
|
|
1162
|
+
retryable: true
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
function handleAcknowledgedSubmitMessage(message, controls, formatter) {
|
|
1166
|
+
if (message.type === "event") {
|
|
1167
|
+
formatter.onAcpMessage(message.message);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (message.type === "permission_escalation") {
|
|
1171
|
+
formatter.onPermissionEscalation(message.event);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (message.type === "result") {
|
|
1175
|
+
formatter.flush();
|
|
1176
|
+
controls.resolve(message.result);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
controls.reject(unexpectedQueueResponseError());
|
|
1180
|
+
}
|
|
1181
|
+
function handleSubmitQueueOwnerMessage(message, controls, options) {
|
|
1182
|
+
if (message.type === "error") {
|
|
1183
|
+
controls.reject(emitQueueOwnerError(options.outputFormatter, options.errorEmissionPolicy, options.sessionId, message));
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
if (!controls.state.acknowledged) {
|
|
1187
|
+
controls.reject(missingQueueAckError());
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
handleAcknowledgedSubmitMessage(message, controls, options.outputFormatter);
|
|
1191
|
+
}
|
|
1014
1192
|
async function submitToQueueOwner(owner, options) {
|
|
1015
1193
|
const requestId = randomUUID();
|
|
1016
1194
|
const request = {
|
|
@@ -1041,57 +1219,8 @@ async function submitToQueueOwner(owner, options) {
|
|
|
1041
1219
|
requestId
|
|
1042
1220
|
});
|
|
1043
1221
|
},
|
|
1044
|
-
onMessage: (message,
|
|
1045
|
-
|
|
1046
|
-
options.outputFormatter.setContext({ sessionId: options.sessionId });
|
|
1047
|
-
const queueErrorAlreadyEmitted = options.errorEmissionPolicy?.queueErrorAlreadyEmitted ?? true;
|
|
1048
|
-
if (!(message.outputAlreadyEmitted === true) || !queueErrorAlreadyEmitted) {
|
|
1049
|
-
options.outputFormatter.onError({
|
|
1050
|
-
code: message.code ?? "RUNTIME",
|
|
1051
|
-
detailCode: message.detailCode,
|
|
1052
|
-
origin: message.origin ?? "queue",
|
|
1053
|
-
message: message.message,
|
|
1054
|
-
retryable: message.retryable,
|
|
1055
|
-
acp: message.acp
|
|
1056
|
-
});
|
|
1057
|
-
options.outputFormatter.flush();
|
|
1058
|
-
}
|
|
1059
|
-
reject(new QueueConnectionError(message.message, {
|
|
1060
|
-
outputCode: message.code,
|
|
1061
|
-
detailCode: message.detailCode,
|
|
1062
|
-
origin: message.origin ?? "queue",
|
|
1063
|
-
retryable: message.retryable,
|
|
1064
|
-
acp: message.acp,
|
|
1065
|
-
...queueErrorAlreadyEmitted ? { outputAlreadyEmitted: true } : {}
|
|
1066
|
-
}));
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
if (!state.acknowledged) {
|
|
1070
|
-
reject(new QueueConnectionError("Queue owner did not acknowledge request", {
|
|
1071
|
-
detailCode: "QUEUE_ACK_MISSING",
|
|
1072
|
-
origin: "queue",
|
|
1073
|
-
retryable: true
|
|
1074
|
-
}));
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
if (message.type === "event") {
|
|
1078
|
-
options.outputFormatter.onAcpMessage(message.message);
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
if (message.type === "permission_escalation") {
|
|
1082
|
-
options.outputFormatter.onPermissionEscalation(message.event);
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
if (message.type === "result") {
|
|
1086
|
-
options.outputFormatter.flush();
|
|
1087
|
-
resolve(message.result);
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
reject(new QueueProtocolError("Queue owner returned unexpected response", {
|
|
1091
|
-
detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
|
|
1092
|
-
origin: "queue",
|
|
1093
|
-
retryable: true
|
|
1094
|
-
}));
|
|
1222
|
+
onMessage: (message, controls) => {
|
|
1223
|
+
handleSubmitQueueOwnerMessage(message, controls, options);
|
|
1095
1224
|
},
|
|
1096
1225
|
onClose: ({ state, resolve, reject }) => {
|
|
1097
1226
|
if (!state.acknowledged) {
|
|
@@ -1520,31 +1649,19 @@ async function closeSession(sessionId) {
|
|
|
1520
1649
|
//#region src/cli/session/session-management.ts
|
|
1521
1650
|
async function createSessionRecordWithClient(client, options) {
|
|
1522
1651
|
const cwd = absolutePath(options.cwd);
|
|
1523
|
-
await withTimeout(client.start(), options.timeoutMs);
|
|
1524
|
-
let sessionId;
|
|
1525
|
-
let agentSessionId;
|
|
1526
|
-
let sessionResult;
|
|
1527
|
-
let sessionModels;
|
|
1528
|
-
let requestedModelApplied = false;
|
|
1529
|
-
if (options.resumeSessionId) {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
sessionModels = loadedSession.models;
|
|
1537
|
-
requestedModelApplied = await applyRequestedModelIfAdvertised({
|
|
1538
|
-
client,
|
|
1539
|
-
sessionId,
|
|
1540
|
-
requestedModel: options.sessionOptions?.model,
|
|
1541
|
-
models: sessionModels,
|
|
1542
|
-
agentCommand: options.agentCommand,
|
|
1543
|
-
timeoutMs: options.timeoutMs
|
|
1544
|
-
});
|
|
1545
|
-
} catch (error) {
|
|
1546
|
-
throw new Error(`Failed to resume ACP session ${options.resumeSessionId}: ${formatErrorMessage(error)}`, { cause: error });
|
|
1547
|
-
}
|
|
1652
|
+
await withTimeout(client.start(), options.timeoutMs);
|
|
1653
|
+
let sessionId;
|
|
1654
|
+
let agentSessionId;
|
|
1655
|
+
let sessionResult;
|
|
1656
|
+
let sessionModels;
|
|
1657
|
+
let requestedModelApplied = false;
|
|
1658
|
+
if (options.resumeSessionId) {
|
|
1659
|
+
const resumed = await resumeSessionRecordWithClient(client, options, cwd);
|
|
1660
|
+
sessionId = resumed.sessionId;
|
|
1661
|
+
agentSessionId = resumed.agentSessionId;
|
|
1662
|
+
sessionResult = resumed.sessionResult;
|
|
1663
|
+
sessionModels = resumed.sessionModels;
|
|
1664
|
+
requestedModelApplied = resumed.requestedModelApplied;
|
|
1548
1665
|
} else {
|
|
1549
1666
|
const createdSession = await withTimeout(client.createSession(cwd), options.timeoutMs);
|
|
1550
1667
|
sessionId = createdSession.sessionId;
|
|
@@ -1577,7 +1694,7 @@ async function createSessionRecordWithClient(client, options) {
|
|
|
1577
1694
|
eventLog: defaultSessionEventLog(sessionId),
|
|
1578
1695
|
closed: false,
|
|
1579
1696
|
closedAt: void 0,
|
|
1580
|
-
pid: lifecycle.pid,
|
|
1697
|
+
pid: lifecycle.running ? lifecycle.pid : void 0,
|
|
1581
1698
|
agentStartedAt: lifecycle.startedAt,
|
|
1582
1699
|
protocolVersion: client.initializeResult?.protocolVersion,
|
|
1583
1700
|
agentCapabilities: client.initializeResult?.agentCapabilities,
|
|
@@ -1591,6 +1708,31 @@ async function createSessionRecordWithClient(client, options) {
|
|
|
1591
1708
|
await writeSessionRecord(record);
|
|
1592
1709
|
return record;
|
|
1593
1710
|
}
|
|
1711
|
+
async function resumeSessionRecordWithClient(client, options, cwd) {
|
|
1712
|
+
if (!options.resumeSessionId) throw new Error("resumeSessionId is required");
|
|
1713
|
+
const resumeMethod = client.supportsResumeSession() ? "session/resume" : client.supportsLoadSession() ? "session/load" : void 0;
|
|
1714
|
+
if (!resumeMethod) throw new Error(`Agent command "${options.agentCommand}" does not support session/resume or session/load; cannot resume session ${options.resumeSessionId}`);
|
|
1715
|
+
try {
|
|
1716
|
+
const resumedSession = await withTimeout(resumeMethod === "session/resume" ? client.resumeSession(options.resumeSessionId, cwd) : client.loadSession(options.resumeSessionId, cwd), options.timeoutMs);
|
|
1717
|
+
const sessionModels = resumedSession.models;
|
|
1718
|
+
return {
|
|
1719
|
+
sessionId: options.resumeSessionId,
|
|
1720
|
+
agentSessionId: normalizeRuntimeSessionId(resumedSession.agentSessionId),
|
|
1721
|
+
sessionResult: resumedSession,
|
|
1722
|
+
sessionModels,
|
|
1723
|
+
requestedModelApplied: await applyRequestedModelIfAdvertised({
|
|
1724
|
+
client,
|
|
1725
|
+
sessionId: options.resumeSessionId,
|
|
1726
|
+
requestedModel: options.sessionOptions?.model,
|
|
1727
|
+
models: sessionModels,
|
|
1728
|
+
agentCommand: options.agentCommand,
|
|
1729
|
+
timeoutMs: options.timeoutMs
|
|
1730
|
+
})
|
|
1731
|
+
};
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
throw new Error(`Failed to resume ACP session ${options.resumeSessionId}: ${formatErrorMessage(error)}`, { cause: error });
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1594
1736
|
async function createSessionWithClient(options) {
|
|
1595
1737
|
const client = new AcpClient({
|
|
1596
1738
|
agentCommand: options.agentCommand,
|
|
@@ -1621,6 +1763,45 @@ async function createSession(options) {
|
|
|
1621
1763
|
const { record, client } = await createSessionWithClient(options);
|
|
1622
1764
|
try {
|
|
1623
1765
|
return record;
|
|
1766
|
+
} finally {
|
|
1767
|
+
await client.close();
|
|
1768
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
1769
|
+
await writeSessionRecord(record);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
async function listAgentSessions(options) {
|
|
1773
|
+
const client = new AcpClient({
|
|
1774
|
+
agentCommand: options.agentCommand,
|
|
1775
|
+
cwd: absolutePath(options.cwd),
|
|
1776
|
+
mcpServers: options.mcpServers,
|
|
1777
|
+
permissionMode: options.permissionMode,
|
|
1778
|
+
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
1779
|
+
permissionPolicy: options.permissionPolicy,
|
|
1780
|
+
authCredentials: options.authCredentials,
|
|
1781
|
+
authPolicy: options.authPolicy,
|
|
1782
|
+
terminal: options.terminal,
|
|
1783
|
+
verbose: options.verbose
|
|
1784
|
+
});
|
|
1785
|
+
try {
|
|
1786
|
+
return await withInterrupt(async () => {
|
|
1787
|
+
await withTimeout(client.start(), options.timeoutMs);
|
|
1788
|
+
if (!client.supportsListSessions()) return;
|
|
1789
|
+
const cwd = options.filterCwd ? absolutePath(options.filterCwd) : void 0;
|
|
1790
|
+
const response = await withTimeout(client.listSessions({
|
|
1791
|
+
...cwd ? { cwd } : {},
|
|
1792
|
+
...options.cursor ? { cursor: options.cursor } : {}
|
|
1793
|
+
}), options.timeoutMs);
|
|
1794
|
+
return {
|
|
1795
|
+
_meta: response._meta,
|
|
1796
|
+
source: "agent",
|
|
1797
|
+
sessions: response.sessions,
|
|
1798
|
+
cursor: options.cursor,
|
|
1799
|
+
cwd,
|
|
1800
|
+
nextCursor: response.nextCursor
|
|
1801
|
+
};
|
|
1802
|
+
}, async () => {
|
|
1803
|
+
await client.close();
|
|
1804
|
+
});
|
|
1624
1805
|
} finally {
|
|
1625
1806
|
await client.close();
|
|
1626
1807
|
}
|
|
@@ -1701,15 +1882,25 @@ function buildPayload(reason) {
|
|
|
1701
1882
|
metrics: getPerfMetricsSnapshot()
|
|
1702
1883
|
};
|
|
1703
1884
|
}
|
|
1885
|
+
function payloadHasMetrics(payload) {
|
|
1886
|
+
const metrics = payload.metrics;
|
|
1887
|
+
return [
|
|
1888
|
+
metrics.counters,
|
|
1889
|
+
metrics.gauges,
|
|
1890
|
+
metrics.timings
|
|
1891
|
+
].some((entries) => Object.keys(entries ?? {}).length > 0);
|
|
1892
|
+
}
|
|
1893
|
+
function appendPerfMetricsPayload(payload) {
|
|
1894
|
+
fs.mkdirSync(path.dirname(captureFilePath), { recursive: true });
|
|
1895
|
+
fs.appendFileSync(captureFilePath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
1896
|
+
captureSequence += 1;
|
|
1897
|
+
}
|
|
1704
1898
|
function writePerfMetricsCapture(reason, resetAfterWrite) {
|
|
1705
1899
|
if (!shouldCapture()) return false;
|
|
1706
1900
|
const payload = buildPayload(reason);
|
|
1707
|
-
|
|
1708
|
-
if (!(Object.keys(metrics.counters ?? {}).length > 0 || Object.keys(metrics.gauges ?? {}).length > 0 || Object.keys(metrics.timings ?? {}).length > 0)) return false;
|
|
1901
|
+
if (!payloadHasMetrics(payload)) return false;
|
|
1709
1902
|
try {
|
|
1710
|
-
|
|
1711
|
-
fs.appendFileSync(captureFilePath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
1712
|
-
captureSequence += 1;
|
|
1903
|
+
appendPerfMetricsPayload(payload);
|
|
1713
1904
|
if (resetAfterWrite) resetPerfMetrics();
|
|
1714
1905
|
return true;
|
|
1715
1906
|
} catch {
|
|
@@ -1839,20 +2030,53 @@ var QueueOwnerTurnController = class {
|
|
|
1839
2030
|
};
|
|
1840
2031
|
//#endregion
|
|
1841
2032
|
//#region src/cli/session/queue-owner-process.ts
|
|
2033
|
+
function isNonEmptyStringArray(value) {
|
|
2034
|
+
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string" && entry.length > 0);
|
|
2035
|
+
}
|
|
2036
|
+
const NODE_TEST_FLAGS = new Set([
|
|
2037
|
+
"--experimental-test-coverage",
|
|
2038
|
+
"--test",
|
|
2039
|
+
"--test-name-pattern",
|
|
2040
|
+
"--test-reporter",
|
|
2041
|
+
"--test-reporter-destination"
|
|
2042
|
+
]);
|
|
2043
|
+
const NODE_TEST_FLAGS_WITH_VALUE = new Set([
|
|
2044
|
+
"--test-name-pattern",
|
|
2045
|
+
"--test-reporter",
|
|
2046
|
+
"--test-reporter-destination"
|
|
2047
|
+
]);
|
|
2048
|
+
const INSPECTOR_FLAGS_WITH_VALUE = new Set([
|
|
2049
|
+
"--inspect",
|
|
2050
|
+
"--inspect-brk",
|
|
2051
|
+
"--inspect-port",
|
|
2052
|
+
"--inspect-publish-uid",
|
|
2053
|
+
"--debug-port"
|
|
2054
|
+
]);
|
|
2055
|
+
const INSPECTOR_FLAG_PREFIXES = [
|
|
2056
|
+
"--inspect=",
|
|
2057
|
+
"--inspect-brk=",
|
|
2058
|
+
"--inspect-port=",
|
|
2059
|
+
"--inspect-publish-uid=",
|
|
2060
|
+
"--debug-port="
|
|
2061
|
+
];
|
|
2062
|
+
function classifyExecArgv(value) {
|
|
2063
|
+
if (value === void 0) return "drop";
|
|
2064
|
+
if (NODE_TEST_FLAGS_WITH_VALUE.has(value) || INSPECTOR_FLAGS_WITH_VALUE.has(value)) return "drop-with-value";
|
|
2065
|
+
return dropSingleExecArgv(value) ? "drop" : "keep";
|
|
2066
|
+
}
|
|
2067
|
+
function dropSingleExecArgv(value) {
|
|
2068
|
+
return NODE_TEST_FLAGS.has(value) || value.startsWith("--test-") || INSPECTOR_FLAG_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
2069
|
+
}
|
|
1842
2070
|
function sanitizeQueueOwnerExecArgv(execArgv = process.execArgv) {
|
|
1843
2071
|
const sanitized = [];
|
|
1844
2072
|
for (let index = 0; index < execArgv.length; index += 1) {
|
|
1845
|
-
const value = execArgv[index];
|
|
1846
|
-
|
|
1847
|
-
if (
|
|
2073
|
+
const value = execArgv[index] ?? "";
|
|
2074
|
+
const decision = classifyExecArgv(value);
|
|
2075
|
+
if (decision === "drop") continue;
|
|
2076
|
+
if (decision === "drop-with-value") {
|
|
1848
2077
|
index += 1;
|
|
1849
2078
|
continue;
|
|
1850
2079
|
}
|
|
1851
|
-
if (value.startsWith("--test-")) continue;
|
|
1852
|
-
if (value === "--inspect" || value === "--inspect-brk" || value === "--inspect-port" || value === "--inspect-publish-uid" || value.startsWith("--inspect=") || value.startsWith("--inspect-brk=") || value.startsWith("--inspect-port=") || value.startsWith("--inspect-publish-uid=") || value === "--debug-port" || value.startsWith("--debug-port=")) {
|
|
1853
|
-
if (value === "--inspect" || value === "--inspect-brk" || value === "--inspect-port" || value === "--inspect-publish-uid" || value === "--debug-port") index += 1;
|
|
1854
|
-
continue;
|
|
1855
|
-
}
|
|
1856
2080
|
sanitized.push(value);
|
|
1857
2081
|
}
|
|
1858
2082
|
return sanitized;
|
|
@@ -1870,7 +2094,7 @@ function resolveQueueOwnerSpawnArgs(argv = process.argv) {
|
|
|
1870
2094
|
const override = process.env.ACPX_QUEUE_OWNER_ARGS;
|
|
1871
2095
|
if (override) {
|
|
1872
2096
|
const parsed = JSON.parse(override);
|
|
1873
|
-
if (
|
|
2097
|
+
if (isNonEmptyStringArray(parsed)) return [...parsed];
|
|
1874
2098
|
throw new Error("acpx self-spawn failed: invalid ACPX_QUEUE_OWNER_ARGS");
|
|
1875
2099
|
}
|
|
1876
2100
|
const entry = argv[1];
|
|
@@ -1937,6 +2161,10 @@ async function countExistingSegments(sessionId, maxSegments) {
|
|
|
1937
2161
|
if (await pathExists(sessionEventActivePath(sessionId))) count += 1;
|
|
1938
2162
|
return count;
|
|
1939
2163
|
}
|
|
2164
|
+
async function resolveInitialSegmentCount(record, maxSegments) {
|
|
2165
|
+
if (Number.isInteger(record.eventLog.segment_count) && record.eventLog.segment_count > 0) return record.eventLog.segment_count;
|
|
2166
|
+
return await countExistingSegments(record.acpxRecordId, maxSegments) || 1;
|
|
2167
|
+
}
|
|
1940
2168
|
async function rotateSegments(sessionId, maxSegments) {
|
|
1941
2169
|
const active = sessionEventActivePath(sessionId);
|
|
1942
2170
|
const overflow = sessionEventSegmentPath(sessionId, maxSegments);
|
|
@@ -2028,7 +2256,7 @@ var SessionEventWriter = class SessionEventWriter {
|
|
|
2028
2256
|
const maxSegments = options.maxSegments ?? record.eventLog.max_segments ?? 5;
|
|
2029
2257
|
const activePath = sessionEventActivePath(record.acpxRecordId);
|
|
2030
2258
|
const activeSizeBytes = await statSize(activePath);
|
|
2031
|
-
const segmentCount =
|
|
2259
|
+
const segmentCount = await resolveInitialSegmentCount(record, maxSegments);
|
|
2032
2260
|
return new SessionEventWriter(record, lock, {
|
|
2033
2261
|
maxSegmentBytes,
|
|
2034
2262
|
maxSegments
|
|
@@ -2148,23 +2376,31 @@ function toPromptResult(stopReason, sessionId, client) {
|
|
|
2148
2376
|
permissionStats: client.getPermissionStats()
|
|
2149
2377
|
};
|
|
2150
2378
|
}
|
|
2379
|
+
function requestedModelId(value) {
|
|
2380
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2381
|
+
}
|
|
2382
|
+
function advertisedModelsForRecord(record) {
|
|
2383
|
+
const availableModels = record.acpx?.available_models;
|
|
2384
|
+
if (!Array.isArray(availableModels)) return;
|
|
2385
|
+
return {
|
|
2386
|
+
currentModelId: record.acpx?.current_model_id ?? "",
|
|
2387
|
+
availableModels: availableModels.map((modelId) => ({
|
|
2388
|
+
modelId,
|
|
2389
|
+
name: modelId
|
|
2390
|
+
}))
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2151
2393
|
async function applyPromptModelIfAdvertised(params) {
|
|
2152
|
-
const requestedModel =
|
|
2394
|
+
const requestedModel = requestedModelId(params.requestedModel);
|
|
2153
2395
|
if (!requestedModel) return;
|
|
2154
|
-
const
|
|
2396
|
+
const models = advertisedModelsForRecord(params.record);
|
|
2155
2397
|
assertRequestedModelSupported({
|
|
2156
2398
|
requestedModel,
|
|
2157
|
-
models
|
|
2158
|
-
currentModelId: params.record.acpx?.current_model_id ?? "",
|
|
2159
|
-
availableModels: availableModels.map((modelId) => ({
|
|
2160
|
-
modelId,
|
|
2161
|
-
name: modelId
|
|
2162
|
-
}))
|
|
2163
|
-
} : void 0,
|
|
2399
|
+
models,
|
|
2164
2400
|
agentCommand: params.record.agentCommand,
|
|
2165
2401
|
context: "apply"
|
|
2166
2402
|
});
|
|
2167
|
-
if (!
|
|
2403
|
+
if (!models) return;
|
|
2168
2404
|
if (params.record.acpx?.current_model_id === requestedModel) {
|
|
2169
2405
|
setDesiredModelId(params.record, requestedModel);
|
|
2170
2406
|
return;
|
|
@@ -2198,6 +2434,7 @@ function extractJsonRpcResponseInfo(message) {
|
|
|
2198
2434
|
hasError
|
|
2199
2435
|
};
|
|
2200
2436
|
}
|
|
2437
|
+
const SESSION_RECONNECT_METHODS = new Set(["session/load", "session/resume"]);
|
|
2201
2438
|
function filterRecoverableLoadFallbackOutput(messages) {
|
|
2202
2439
|
const requestMethodById = /* @__PURE__ */ new Map();
|
|
2203
2440
|
const failedLoadRequestIds = /* @__PURE__ */ new Set();
|
|
@@ -2209,12 +2446,13 @@ function filterRecoverableLoadFallbackOutput(messages) {
|
|
|
2209
2446
|
}
|
|
2210
2447
|
const response = extractJsonRpcResponseInfo(message);
|
|
2211
2448
|
if (!response || !response.hasError) continue;
|
|
2212
|
-
|
|
2449
|
+
const requestMethod = requestMethodById.get(response.idKey);
|
|
2450
|
+
if (requestMethod && SESSION_RECONNECT_METHODS.has(requestMethod)) failedLoadRequestIds.add(response.idKey);
|
|
2213
2451
|
}
|
|
2214
2452
|
if (failedLoadRequestIds.size === 0) return messages;
|
|
2215
2453
|
return messages.filter((message) => {
|
|
2216
2454
|
const request = extractJsonRpcRequestInfo(message);
|
|
2217
|
-
if (request && request.method
|
|
2455
|
+
if (request && SESSION_RECONNECT_METHODS.has(request.method) && failedLoadRequestIds.has(request.idKey)) return false;
|
|
2218
2456
|
const response = extractJsonRpcResponseInfo(message);
|
|
2219
2457
|
if (response && failedLoadRequestIds.has(response.idKey)) return false;
|
|
2220
2458
|
return true;
|
|
@@ -2224,52 +2462,97 @@ function emitPromptRetryNotice(params) {
|
|
|
2224
2462
|
if (params.suppressSdkConsoleErrors) return;
|
|
2225
2463
|
process.stderr.write(`[acpx] prompt failed (${formatErrorMessage(params.error)}), retrying in ${params.delayMs}ms (attempt ${params.attempt}/${params.maxRetries})\n`);
|
|
2226
2464
|
}
|
|
2465
|
+
function emitConnectPerfMetric(startedAt, verbose) {
|
|
2466
|
+
if (!verbose) return;
|
|
2467
|
+
process.stderr.write(`[acpx] ${formatPerfMetric("prompt.connect_and_load", Date.now() - startedAt)}\n`);
|
|
2468
|
+
}
|
|
2469
|
+
function emitPromptPerfMetric(startedAt, verbose) {
|
|
2470
|
+
if (!verbose) return;
|
|
2471
|
+
process.stderr.write(`[acpx] ${formatPerfMetric("prompt.agent_turn", Date.now() - startedAt)}\n`);
|
|
2472
|
+
}
|
|
2473
|
+
function emitPromptHookError(error, verbose) {
|
|
2474
|
+
if (!verbose) return;
|
|
2475
|
+
process.stderr.write("[acpx] onPromptActive hook failed: " + formatErrorMessage(error) + "\n");
|
|
2476
|
+
}
|
|
2477
|
+
function emitPromptDisconnectNotice(snapshot, verbose) {
|
|
2478
|
+
const lastExit = snapshot.lastExit;
|
|
2479
|
+
if (!lastExit?.unexpectedDuringPrompt || !verbose) return;
|
|
2480
|
+
process.stderr.write("[acpx] agent disconnected during prompt (" + lastExit.reason + ", exit=" + lastExit.exitCode + ", signal=" + (lastExit.signal ?? "none") + ")\n");
|
|
2481
|
+
}
|
|
2482
|
+
function shouldRetryRuntimePrompt(error, attempt, maxRetries, snapshot, hasSideEffects) {
|
|
2483
|
+
if (!shouldRetryPromptAttempt(error, attempt, maxRetries, hasSideEffects)) return false;
|
|
2484
|
+
return snapshot.lastExit?.unexpectedDuringPrompt !== true;
|
|
2485
|
+
}
|
|
2486
|
+
function shouldRetryPromptAttempt(error, attempt, maxRetries, hasSideEffects) {
|
|
2487
|
+
return attempt < maxRetries && !hasSideEffects() && isRetryablePromptError(error);
|
|
2488
|
+
}
|
|
2489
|
+
async function waitBeforePromptRetry(error, attempt, maxRetries, suppressSdkConsoleErrors) {
|
|
2490
|
+
const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
2491
|
+
emitPromptRetryNotice({
|
|
2492
|
+
error,
|
|
2493
|
+
delayMs,
|
|
2494
|
+
attempt: attempt + 1,
|
|
2495
|
+
maxRetries,
|
|
2496
|
+
suppressSdkConsoleErrors
|
|
2497
|
+
});
|
|
2498
|
+
await waitMs(delayMs);
|
|
2499
|
+
}
|
|
2500
|
+
function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatter) {
|
|
2501
|
+
return {
|
|
2502
|
+
sessionRecordId,
|
|
2503
|
+
mcpServers: options.mcpServers,
|
|
2504
|
+
prompt: task.prompt ?? textPrompt(task.message),
|
|
2505
|
+
permissionMode: task.permissionMode,
|
|
2506
|
+
resumePolicy: task.resumePolicy,
|
|
2507
|
+
nonInteractivePermissions: task.nonInteractivePermissions ?? options.nonInteractivePermissions,
|
|
2508
|
+
permissionPolicy: task.permissionPolicy,
|
|
2509
|
+
authCredentials: options.authCredentials,
|
|
2510
|
+
authPolicy: options.authPolicy,
|
|
2511
|
+
outputFormatter,
|
|
2512
|
+
timeoutMs: task.timeoutMs,
|
|
2513
|
+
suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
|
|
2514
|
+
verbose: options.verbose,
|
|
2515
|
+
promptRetries: task.promptRetries ?? options.promptRetries ?? 0,
|
|
2516
|
+
sessionOptions: mergeSessionOptions(task.sessionOptions, options.sessionOptions),
|
|
2517
|
+
onClientAvailable: options.onClientAvailable,
|
|
2518
|
+
onClientClosed: options.onClientClosed,
|
|
2519
|
+
onPromptActive: options.onPromptActive,
|
|
2520
|
+
client: options.sharedClient
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
function sendQueuedTaskResult(task, result) {
|
|
2524
|
+
if (!task.waitForCompletion) return;
|
|
2525
|
+
task.send({
|
|
2526
|
+
type: "result",
|
|
2527
|
+
requestId: task.requestId,
|
|
2528
|
+
result
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
function sendQueuedTaskError(task, error) {
|
|
2532
|
+
if (!task.waitForCompletion) return;
|
|
2533
|
+
const normalizedError = normalizeOutputError(error, {
|
|
2534
|
+
origin: "runtime",
|
|
2535
|
+
detailCode: "QUEUE_RUNTIME_PROMPT_FAILED"
|
|
2536
|
+
});
|
|
2537
|
+
const alreadyEmitted = error.outputAlreadyEmitted === true;
|
|
2538
|
+
task.send({
|
|
2539
|
+
type: "error",
|
|
2540
|
+
requestId: task.requestId,
|
|
2541
|
+
code: normalizedError.code,
|
|
2542
|
+
detailCode: normalizedError.detailCode,
|
|
2543
|
+
origin: normalizedError.origin,
|
|
2544
|
+
message: normalizedError.message,
|
|
2545
|
+
retryable: normalizedError.retryable,
|
|
2546
|
+
acp: normalizedError.acp,
|
|
2547
|
+
outputAlreadyEmitted: alreadyEmitted
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2227
2550
|
async function runQueuedTask(sessionRecordId, task, options) {
|
|
2228
2551
|
const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
|
|
2229
2552
|
try {
|
|
2230
|
-
|
|
2231
|
-
sessionRecordId,
|
|
2232
|
-
mcpServers: options.mcpServers,
|
|
2233
|
-
prompt: task.prompt ?? textPrompt(task.message),
|
|
2234
|
-
permissionMode: task.permissionMode,
|
|
2235
|
-
resumePolicy: task.resumePolicy,
|
|
2236
|
-
nonInteractivePermissions: task.nonInteractivePermissions ?? options.nonInteractivePermissions,
|
|
2237
|
-
permissionPolicy: task.permissionPolicy,
|
|
2238
|
-
authCredentials: options.authCredentials,
|
|
2239
|
-
authPolicy: options.authPolicy,
|
|
2240
|
-
outputFormatter,
|
|
2241
|
-
timeoutMs: task.timeoutMs,
|
|
2242
|
-
suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
|
|
2243
|
-
verbose: options.verbose,
|
|
2244
|
-
promptRetries: task.promptRetries ?? options.promptRetries ?? 0,
|
|
2245
|
-
sessionOptions: mergeSessionOptions(task.sessionOptions, options.sessionOptions),
|
|
2246
|
-
onClientAvailable: options.onClientAvailable,
|
|
2247
|
-
onClientClosed: options.onClientClosed,
|
|
2248
|
-
onPromptActive: options.onPromptActive,
|
|
2249
|
-
client: options.sharedClient
|
|
2250
|
-
});
|
|
2251
|
-
if (task.waitForCompletion) task.send({
|
|
2252
|
-
type: "result",
|
|
2253
|
-
requestId: task.requestId,
|
|
2254
|
-
result
|
|
2255
|
-
});
|
|
2553
|
+
sendQueuedTaskResult(task, await runSessionPrompt(buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatter)));
|
|
2256
2554
|
} catch (error) {
|
|
2257
|
-
|
|
2258
|
-
origin: "runtime",
|
|
2259
|
-
detailCode: "QUEUE_RUNTIME_PROMPT_FAILED"
|
|
2260
|
-
});
|
|
2261
|
-
const alreadyEmitted = error.outputAlreadyEmitted === true;
|
|
2262
|
-
if (task.waitForCompletion) task.send({
|
|
2263
|
-
type: "error",
|
|
2264
|
-
requestId: task.requestId,
|
|
2265
|
-
code: normalizedError.code,
|
|
2266
|
-
detailCode: normalizedError.detailCode,
|
|
2267
|
-
origin: normalizedError.origin,
|
|
2268
|
-
message: normalizedError.message,
|
|
2269
|
-
retryable: normalizedError.retryable,
|
|
2270
|
-
acp: normalizedError.acp,
|
|
2271
|
-
outputAlreadyEmitted: alreadyEmitted
|
|
2272
|
-
});
|
|
2555
|
+
sendQueuedTaskError(task, error);
|
|
2273
2556
|
if (error instanceof InterruptedError) throw error;
|
|
2274
2557
|
} finally {
|
|
2275
2558
|
task.close();
|
|
@@ -2408,41 +2691,117 @@ async function runSessionPrompt(options) {
|
|
|
2408
2691
|
return await client.setSessionConfigOption(activeSessionIdForControl, configId, value);
|
|
2409
2692
|
}
|
|
2410
2693
|
};
|
|
2694
|
+
const flushConnectOutput = (loadError) => {
|
|
2695
|
+
bufferingConnectOutput = false;
|
|
2696
|
+
const messages = loadError == null ? pendingConnectOutputMessages : filterRecoverableLoadFallbackOutput(pendingConnectOutputMessages);
|
|
2697
|
+
for (const message of messages) output.onAcpMessage(message);
|
|
2698
|
+
pendingConnectOutputMessages.length = 0;
|
|
2699
|
+
};
|
|
2700
|
+
const connectForPrompt = async () => {
|
|
2701
|
+
const connectStartedAt = Date.now();
|
|
2702
|
+
try {
|
|
2703
|
+
const connected = await measurePerf("runtime.connect_and_load", async () => {
|
|
2704
|
+
return await connectAndLoadSession({
|
|
2705
|
+
client,
|
|
2706
|
+
record,
|
|
2707
|
+
resumePolicy: options.resumePolicy,
|
|
2708
|
+
timeoutMs: options.timeoutMs,
|
|
2709
|
+
verbose: options.verbose,
|
|
2710
|
+
activeController,
|
|
2711
|
+
onClientAvailable: (controller) => {
|
|
2712
|
+
options.onClientAvailable?.(controller);
|
|
2713
|
+
notifiedClientAvailable = true;
|
|
2714
|
+
},
|
|
2715
|
+
onConnectedRecord: (connectedRecord) => {
|
|
2716
|
+
connectedRecord.lastPromptAt = isoNow();
|
|
2717
|
+
},
|
|
2718
|
+
onSessionIdResolved: (sessionId) => {
|
|
2719
|
+
activeSessionIdForControl = sessionId;
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
});
|
|
2723
|
+
flushConnectOutput(connected.loadError);
|
|
2724
|
+
emitConnectPerfMetric(connectStartedAt, options.verbose);
|
|
2725
|
+
return connected;
|
|
2726
|
+
} catch (error) {
|
|
2727
|
+
flushConnectOutput();
|
|
2728
|
+
throw error;
|
|
2729
|
+
}
|
|
2730
|
+
};
|
|
2731
|
+
const buildPromptStartedHook = (attempt) => {
|
|
2732
|
+
if (attempt !== 0 || !options.onPromptActive) return;
|
|
2733
|
+
return async () => {
|
|
2734
|
+
try {
|
|
2735
|
+
await options.onPromptActive?.();
|
|
2736
|
+
} catch (error) {
|
|
2737
|
+
emitPromptHookError(error, options.verbose);
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
};
|
|
2741
|
+
const runPromptAttempt = async (sessionId, attempt) => {
|
|
2742
|
+
const promptStartedAt = Date.now();
|
|
2743
|
+
const response = await measurePerf("runtime.prompt.agent_turn", async () => {
|
|
2744
|
+
return await runPromptTurn({
|
|
2745
|
+
client,
|
|
2746
|
+
sessionId,
|
|
2747
|
+
prompt: options.prompt,
|
|
2748
|
+
timeoutMs: options.timeoutMs,
|
|
2749
|
+
conversation,
|
|
2750
|
+
promptMessageId,
|
|
2751
|
+
onPromptStarted: buildPromptStartedHook(attempt)
|
|
2752
|
+
});
|
|
2753
|
+
});
|
|
2754
|
+
emitPromptPerfMetric(promptStartedAt, options.verbose);
|
|
2755
|
+
return response;
|
|
2756
|
+
};
|
|
2757
|
+
const handlePromptFailure = async (error, attempt) => {
|
|
2758
|
+
const snapshot = client.getAgentLifecycleSnapshot();
|
|
2759
|
+
if (shouldRetryRuntimePrompt(error, attempt, options.promptRetries ?? 0, snapshot, () => promptTurnHadSideEffects)) {
|
|
2760
|
+
await waitBeforePromptRetry(error, attempt, options.promptRetries ?? 0, options.suppressSdkConsoleErrors);
|
|
2761
|
+
return promptTurnHadSideEffects ? await failRuntimePrompt(error, snapshot) : "retry";
|
|
2762
|
+
}
|
|
2763
|
+
return await failRuntimePrompt(error, snapshot);
|
|
2764
|
+
};
|
|
2765
|
+
const failRuntimePrompt = async (error, snapshot) => {
|
|
2766
|
+
promptTurnActive = false;
|
|
2767
|
+
applyLifecycleSnapshotToRecord(record, snapshot);
|
|
2768
|
+
emitPromptDisconnectNotice(snapshot, options.verbose);
|
|
2769
|
+
const normalizedError = normalizeOutputError(error, { origin: "runtime" });
|
|
2770
|
+
await flushPendingMessages(false).catch(() => {});
|
|
2771
|
+
output.flush();
|
|
2772
|
+
record.lastUsedAt = isoNow();
|
|
2773
|
+
applyConversation(record, conversation);
|
|
2774
|
+
record.acpx = acpxState;
|
|
2775
|
+
const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
|
|
2776
|
+
propagated.outputAlreadyEmitted = sawAcpMessage;
|
|
2777
|
+
propagated.normalizedOutputError = normalizedError;
|
|
2778
|
+
throw propagated;
|
|
2779
|
+
};
|
|
2780
|
+
const runPromptWithRetries = async (sessionId) => {
|
|
2781
|
+
promptTurnActive = true;
|
|
2782
|
+
for (let attempt = 0;; attempt++) try {
|
|
2783
|
+
return await runPromptAttempt(sessionId, attempt);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (await handlePromptFailure(error, attempt) === "retry") continue;
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
const savePromptSuccess = async (response) => {
|
|
2789
|
+
await flushPendingMessages(false);
|
|
2790
|
+
output.flush();
|
|
2791
|
+
record.lastUsedAt = isoNow();
|
|
2792
|
+
record.closed = false;
|
|
2793
|
+
record.closedAt = void 0;
|
|
2794
|
+
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
2795
|
+
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
2796
|
+
applyConversation(record, conversation);
|
|
2797
|
+
record.acpx = acpxState;
|
|
2798
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
2799
|
+
stopTotalTimer();
|
|
2800
|
+
return response;
|
|
2801
|
+
};
|
|
2411
2802
|
try {
|
|
2412
2803
|
return await withInterrupt(async () => {
|
|
2413
|
-
const
|
|
2414
|
-
const { sessionId: activeSessionId, resumed, loadError } = await measurePerf("runtime.connect_and_load", async () => {
|
|
2415
|
-
try {
|
|
2416
|
-
return await connectAndLoadSession({
|
|
2417
|
-
client,
|
|
2418
|
-
record,
|
|
2419
|
-
resumePolicy: options.resumePolicy,
|
|
2420
|
-
timeoutMs: options.timeoutMs,
|
|
2421
|
-
verbose: options.verbose,
|
|
2422
|
-
activeController,
|
|
2423
|
-
onClientAvailable: (controller) => {
|
|
2424
|
-
options.onClientAvailable?.(controller);
|
|
2425
|
-
notifiedClientAvailable = true;
|
|
2426
|
-
},
|
|
2427
|
-
onConnectedRecord: (connectedRecord) => {
|
|
2428
|
-
connectedRecord.lastPromptAt = isoNow();
|
|
2429
|
-
},
|
|
2430
|
-
onSessionIdResolved: (sessionId) => {
|
|
2431
|
-
activeSessionIdForControl = sessionId;
|
|
2432
|
-
}
|
|
2433
|
-
});
|
|
2434
|
-
} catch (error) {
|
|
2435
|
-
bufferingConnectOutput = false;
|
|
2436
|
-
for (const message of pendingConnectOutputMessages) output.onAcpMessage(message);
|
|
2437
|
-
pendingConnectOutputMessages.length = 0;
|
|
2438
|
-
throw error;
|
|
2439
|
-
}
|
|
2440
|
-
});
|
|
2441
|
-
bufferingConnectOutput = false;
|
|
2442
|
-
const connectOutputMessages = loadError == null ? pendingConnectOutputMessages : filterRecoverableLoadFallbackOutput(pendingConnectOutputMessages);
|
|
2443
|
-
for (const message of connectOutputMessages) output.onAcpMessage(message);
|
|
2444
|
-
pendingConnectOutputMessages.length = 0;
|
|
2445
|
-
if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.connect_and_load", Date.now() - connectStartedAt)}\n`);
|
|
2804
|
+
const { sessionId: activeSessionId, resumed, loadError } = await connectForPrompt();
|
|
2446
2805
|
await applyPromptModelIfAdvertised({
|
|
2447
2806
|
client,
|
|
2448
2807
|
sessionId: activeSessionId,
|
|
@@ -2452,72 +2811,8 @@ async function runSessionPrompt(options) {
|
|
|
2452
2811
|
});
|
|
2453
2812
|
output.setContext({ sessionId: record.acpxRecordId });
|
|
2454
2813
|
await liveCheckpoint.checkpoint();
|
|
2455
|
-
const
|
|
2456
|
-
let response;
|
|
2457
|
-
promptTurnActive = true;
|
|
2458
|
-
for (let attempt = 0;; attempt++) try {
|
|
2459
|
-
const promptStartedAt = Date.now();
|
|
2460
|
-
response = await measurePerf("runtime.prompt.agent_turn", async () => {
|
|
2461
|
-
return await runPromptTurn({
|
|
2462
|
-
client,
|
|
2463
|
-
sessionId: activeSessionId,
|
|
2464
|
-
prompt: options.prompt,
|
|
2465
|
-
timeoutMs: options.timeoutMs,
|
|
2466
|
-
conversation,
|
|
2467
|
-
promptMessageId,
|
|
2468
|
-
onPromptStarted: attempt === 0 && options.onPromptActive ? async () => {
|
|
2469
|
-
try {
|
|
2470
|
-
await options.onPromptActive?.();
|
|
2471
|
-
} catch (error) {
|
|
2472
|
-
if (options.verbose) process.stderr.write("[acpx] onPromptActive hook failed: " + formatErrorMessage(error) + "\n");
|
|
2473
|
-
}
|
|
2474
|
-
} : void 0
|
|
2475
|
-
});
|
|
2476
|
-
});
|
|
2477
|
-
if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.agent_turn", Date.now() - promptStartedAt)}\n`);
|
|
2478
|
-
break;
|
|
2479
|
-
} catch (error) {
|
|
2480
|
-
const snapshot = client.getAgentLifecycleSnapshot();
|
|
2481
|
-
const agentCrashed = snapshot.lastExit?.unexpectedDuringPrompt === true;
|
|
2482
|
-
if (attempt < maxRetries && !agentCrashed && !promptTurnHadSideEffects && isRetryablePromptError(error)) {
|
|
2483
|
-
const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
2484
|
-
emitPromptRetryNotice({
|
|
2485
|
-
error,
|
|
2486
|
-
delayMs,
|
|
2487
|
-
attempt: attempt + 1,
|
|
2488
|
-
maxRetries,
|
|
2489
|
-
suppressSdkConsoleErrors: options.suppressSdkConsoleErrors
|
|
2490
|
-
});
|
|
2491
|
-
await waitMs(delayMs);
|
|
2492
|
-
if (!promptTurnHadSideEffects) continue;
|
|
2493
|
-
}
|
|
2494
|
-
promptTurnActive = false;
|
|
2495
|
-
applyLifecycleSnapshotToRecord(record, snapshot);
|
|
2496
|
-
const lastExit = snapshot.lastExit;
|
|
2497
|
-
if (lastExit?.unexpectedDuringPrompt && options.verbose) process.stderr.write("[acpx] agent disconnected during prompt (" + lastExit.reason + ", exit=" + lastExit.exitCode + ", signal=" + (lastExit.signal ?? "none") + ")\n");
|
|
2498
|
-
const normalizedError = normalizeOutputError(error, { origin: "runtime" });
|
|
2499
|
-
await flushPendingMessages(false).catch(() => {});
|
|
2500
|
-
output.flush();
|
|
2501
|
-
record.lastUsedAt = isoNow();
|
|
2502
|
-
applyConversation(record, conversation);
|
|
2503
|
-
record.acpx = acpxState;
|
|
2504
|
-
const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
|
|
2505
|
-
propagated.outputAlreadyEmitted = sawAcpMessage;
|
|
2506
|
-
propagated.normalizedOutputError = normalizedError;
|
|
2507
|
-
throw propagated;
|
|
2508
|
-
}
|
|
2814
|
+
const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
|
|
2509
2815
|
promptTurnActive = false;
|
|
2510
|
-
await flushPendingMessages(false);
|
|
2511
|
-
output.flush();
|
|
2512
|
-
record.lastUsedAt = isoNow();
|
|
2513
|
-
record.closed = false;
|
|
2514
|
-
record.closedAt = void 0;
|
|
2515
|
-
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
2516
|
-
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
2517
|
-
applyConversation(record, conversation);
|
|
2518
|
-
record.acpx = acpxState;
|
|
2519
|
-
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
2520
|
-
stopTotalTimer();
|
|
2521
2816
|
return {
|
|
2522
2817
|
...toPromptResult(response.stopReason, record.acpxRecordId, client),
|
|
2523
2818
|
record,
|
|
@@ -2580,6 +2875,25 @@ async function runOnce(options) {
|
|
|
2580
2875
|
},
|
|
2581
2876
|
sessionOptions: options.sessionOptions
|
|
2582
2877
|
});
|
|
2878
|
+
const runExecPromptAttempt = async (sessionId) => {
|
|
2879
|
+
return await measurePerf("runtime.exec.prompt", async () => {
|
|
2880
|
+
return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
|
|
2881
|
+
});
|
|
2882
|
+
};
|
|
2883
|
+
const runExecPromptWithRetries = async (sessionId) => {
|
|
2884
|
+
const maxRetries = options.promptRetries ?? 0;
|
|
2885
|
+
promptTurnActive = true;
|
|
2886
|
+
for (let attempt = 0;; attempt++) try {
|
|
2887
|
+
return await runExecPromptAttempt(sessionId);
|
|
2888
|
+
} catch (error) {
|
|
2889
|
+
if (shouldRetryPromptAttempt(error, attempt, maxRetries, () => promptTurnHadSideEffects)) {
|
|
2890
|
+
await waitBeforePromptRetry(error, attempt, maxRetries, options.suppressSdkConsoleErrors);
|
|
2891
|
+
if (!promptTurnHadSideEffects) continue;
|
|
2892
|
+
}
|
|
2893
|
+
promptTurnActive = false;
|
|
2894
|
+
throw error;
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2583
2897
|
try {
|
|
2584
2898
|
return await withInterrupt(async () => {
|
|
2585
2899
|
await measurePerf("runtime.exec.start", async () => {
|
|
@@ -2598,30 +2912,7 @@ async function runOnce(options) {
|
|
|
2598
2912
|
timeoutMs: options.timeoutMs
|
|
2599
2913
|
});
|
|
2600
2914
|
output.setContext({ sessionId });
|
|
2601
|
-
const
|
|
2602
|
-
let response;
|
|
2603
|
-
promptTurnActive = true;
|
|
2604
|
-
for (let attempt = 0;; attempt++) try {
|
|
2605
|
-
response = await measurePerf("runtime.exec.prompt", async () => {
|
|
2606
|
-
return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
|
|
2607
|
-
});
|
|
2608
|
-
break;
|
|
2609
|
-
} catch (error) {
|
|
2610
|
-
if (attempt < maxRetries && !promptTurnHadSideEffects && isRetryablePromptError(error)) {
|
|
2611
|
-
const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
2612
|
-
emitPromptRetryNotice({
|
|
2613
|
-
error,
|
|
2614
|
-
delayMs,
|
|
2615
|
-
attempt: attempt + 1,
|
|
2616
|
-
maxRetries,
|
|
2617
|
-
suppressSdkConsoleErrors: options.suppressSdkConsoleErrors
|
|
2618
|
-
});
|
|
2619
|
-
await waitMs(delayMs);
|
|
2620
|
-
if (!promptTurnHadSideEffects) continue;
|
|
2621
|
-
}
|
|
2622
|
-
promptTurnActive = false;
|
|
2623
|
-
throw error;
|
|
2624
|
-
}
|
|
2915
|
+
const response = await runExecPromptWithRetries(sessionId);
|
|
2625
2916
|
promptTurnActive = false;
|
|
2626
2917
|
output.flush();
|
|
2627
2918
|
return toPromptResult(response.stopReason, sessionId, client);
|
|
@@ -2678,13 +2969,8 @@ async function submitToRunningOwner(options, waitForCompletion) {
|
|
|
2678
2969
|
sessionOptions: options.sessionOptions
|
|
2679
2970
|
});
|
|
2680
2971
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
if (!lease) return;
|
|
2684
|
-
const sessionRecord = await resolveSessionRecord(options.sessionId);
|
|
2685
|
-
let owner;
|
|
2686
|
-
let heartbeatTimer;
|
|
2687
|
-
const sharedClient = new AcpClient({
|
|
2972
|
+
function createQueueOwnerSharedClient(options, sessionRecord) {
|
|
2973
|
+
return new AcpClient({
|
|
2688
2974
|
agentCommand: sessionRecord.agentCommand,
|
|
2689
2975
|
cwd: absolutePath(sessionRecord.cwd),
|
|
2690
2976
|
mcpServers: options.mcpServers,
|
|
@@ -2697,11 +2983,9 @@ async function runSessionQueueOwner(options) {
|
|
|
2697
2983
|
verbose: options.verbose,
|
|
2698
2984
|
sessionOptions: mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(sessionRecord))
|
|
2699
2985
|
});
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
const initialTaskPollTimeoutMs = taskPollTimeoutMs == null ? void 0 : Math.max(taskPollTimeoutMs, 1e3);
|
|
2704
|
-
const turnController = new QueueOwnerTurnController({
|
|
2986
|
+
}
|
|
2987
|
+
function createQueueOwnerTurnController(options) {
|
|
2988
|
+
return new QueueOwnerTurnController({
|
|
2705
2989
|
withTimeout: async (run, timeoutMs) => await withTimeout(run(), timeoutMs),
|
|
2706
2990
|
setSessionModeFallback: async (modeId, timeoutMs) => {
|
|
2707
2991
|
await runSessionSetModeDirect({
|
|
@@ -2744,12 +3028,49 @@ async function runSessionQueueOwner(options) {
|
|
|
2744
3028
|
})).response;
|
|
2745
3029
|
}
|
|
2746
3030
|
});
|
|
3031
|
+
}
|
|
3032
|
+
function logDeferredCancelFailure(error, verbose) {
|
|
3033
|
+
if (!verbose) return;
|
|
3034
|
+
process.stderr.write(`[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}\n`);
|
|
3035
|
+
}
|
|
3036
|
+
function logQueueOwnerReady(params) {
|
|
3037
|
+
if (!params.verbose) return;
|
|
3038
|
+
process.stderr.write(`[acpx] queue owner ready for session ${params.sessionId} (ttlMs=${params.ttlMs}, maxQueueDepth=${params.maxQueueDepth})\n`);
|
|
3039
|
+
}
|
|
3040
|
+
async function closeQueueOwnerRuntime(params) {
|
|
3041
|
+
if (params.heartbeatTimer) clearInterval(params.heartbeatTimer);
|
|
3042
|
+
params.turnController.beginClosing();
|
|
3043
|
+
await params.owner?.close();
|
|
3044
|
+
await params.sharedClient.close().catch(() => {});
|
|
3045
|
+
await writeQueueOwnerLifecycleSnapshot(params.sessionId, params.sharedClient);
|
|
3046
|
+
await releaseQueueOwnerLease(params.lease);
|
|
3047
|
+
if (params.verbose) process.stderr.write(`[acpx] queue owner stopped for session ${params.sessionId}\n`);
|
|
3048
|
+
}
|
|
3049
|
+
async function writeQueueOwnerLifecycleSnapshot(sessionId, sharedClient) {
|
|
3050
|
+
try {
|
|
3051
|
+
const record = await resolveSessionRecord(sessionId);
|
|
3052
|
+
applyLifecycleSnapshotToRecord(record, sharedClient.getAgentLifecycleSnapshot());
|
|
3053
|
+
await writeSessionRecord(record);
|
|
3054
|
+
} catch {}
|
|
3055
|
+
}
|
|
3056
|
+
async function runSessionQueueOwner(options) {
|
|
3057
|
+
const lease = await tryAcquireQueueOwnerLease(options.sessionId);
|
|
3058
|
+
if (!lease) return;
|
|
3059
|
+
const sessionRecord = await resolveSessionRecord(options.sessionId);
|
|
3060
|
+
let owner;
|
|
3061
|
+
let heartbeatTimer;
|
|
3062
|
+
const sharedClient = createQueueOwnerSharedClient(options, sessionRecord);
|
|
3063
|
+
const ttlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
|
|
3064
|
+
const maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth ?? 16));
|
|
3065
|
+
const taskPollTimeoutMs = ttlMs === 0 ? void 0 : ttlMs;
|
|
3066
|
+
const initialTaskPollTimeoutMs = taskPollTimeoutMs == null ? void 0 : Math.max(taskPollTimeoutMs, 1e3);
|
|
3067
|
+
const turnController = createQueueOwnerTurnController(options);
|
|
2747
3068
|
const applyPendingCancel = async () => {
|
|
2748
3069
|
return await turnController.applyPendingCancel();
|
|
2749
3070
|
};
|
|
2750
3071
|
const scheduleApplyPendingCancel = () => {
|
|
2751
3072
|
applyPendingCancel().catch((error) => {
|
|
2752
|
-
|
|
3073
|
+
logDeferredCancelFailure(error, options.verbose);
|
|
2753
3074
|
});
|
|
2754
3075
|
};
|
|
2755
3076
|
const setActiveController = (controller) => {
|
|
@@ -2797,7 +3118,12 @@ async function runSessionQueueOwner(options) {
|
|
|
2797
3118
|
refreshQueueOwnerLease(lease, { queueDepth }).catch(() => {});
|
|
2798
3119
|
}
|
|
2799
3120
|
});
|
|
2800
|
-
|
|
3121
|
+
logQueueOwnerReady({
|
|
3122
|
+
sessionId: options.sessionId,
|
|
3123
|
+
ttlMs,
|
|
3124
|
+
maxQueueDepth,
|
|
3125
|
+
verbose: options.verbose
|
|
3126
|
+
});
|
|
2801
3127
|
await refreshQueueOwnerLease(lease, { queueDepth: owner.queueDepth() }).catch(() => {});
|
|
2802
3128
|
heartbeatTimer = setInterval(() => {
|
|
2803
3129
|
refreshQueueOwnerLease(lease, { queueDepth: owner?.queueDepth() ?? 0 }).catch(() => {});
|
|
@@ -2833,17 +3159,15 @@ async function runSessionQueueOwner(options) {
|
|
|
2833
3159
|
});
|
|
2834
3160
|
}
|
|
2835
3161
|
} finally {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
}
|
|
2845
|
-
await releaseQueueOwnerLease(lease);
|
|
2846
|
-
if (options.verbose) process.stderr.write(`[acpx] queue owner stopped for session ${options.sessionId}\n`);
|
|
3162
|
+
await closeQueueOwnerRuntime({
|
|
3163
|
+
lease,
|
|
3164
|
+
owner,
|
|
3165
|
+
heartbeatTimer,
|
|
3166
|
+
turnController,
|
|
3167
|
+
sharedClient,
|
|
3168
|
+
sessionId: options.sessionId,
|
|
3169
|
+
verbose: options.verbose
|
|
3170
|
+
});
|
|
2847
3171
|
}
|
|
2848
3172
|
}
|
|
2849
3173
|
async function sendSession(options) {
|
|
@@ -2874,6 +3198,7 @@ var session_exports = /* @__PURE__ */ __exportAll({
|
|
|
2874
3198
|
findSession: () => findSession,
|
|
2875
3199
|
findSessionByDirectoryWalk: () => findSessionByDirectoryWalk,
|
|
2876
3200
|
isProcessAlive: () => isProcessAlive,
|
|
3201
|
+
listAgentSessions: () => listAgentSessions,
|
|
2877
3202
|
listSessions: () => listSessions,
|
|
2878
3203
|
listSessionsForAgent: () => listSessionsForAgent,
|
|
2879
3204
|
normalizeQueueOwnerTtlMs: () => normalizeQueueOwnerTtlMs,
|
|
@@ -2953,7 +3278,12 @@ function inferToolKindFromTitle(title) {
|
|
|
2953
3278
|
if (!normalized) return;
|
|
2954
3279
|
const head = normalized.split(":", 1)[0]?.trim();
|
|
2955
3280
|
if (!head) return;
|
|
2956
|
-
if (
|
|
3281
|
+
if ([
|
|
3282
|
+
"read",
|
|
3283
|
+
"cat",
|
|
3284
|
+
"open",
|
|
3285
|
+
"view"
|
|
3286
|
+
].some((needle) => head.includes(needle))) return "read";
|
|
2957
3287
|
}
|
|
2958
3288
|
function isReadLikeTool(tool) {
|
|
2959
3289
|
return tool.kind?.trim().toLowerCase() === "read" || inferToolKindFromTitle(tool.title) === "read";
|
|
@@ -2996,14 +3326,25 @@ function sanitizeToolMessage(message) {
|
|
|
2996
3326
|
...root,
|
|
2997
3327
|
params: {
|
|
2998
3328
|
...params,
|
|
2999
|
-
update:
|
|
3000
|
-
...update,
|
|
3001
|
-
rawOutput: Object.prototype.hasOwnProperty.call(update, "rawOutput") && update.rawOutput !== void 0 ? { content: SUPPRESSED_READ_OUTPUT } : update.rawOutput,
|
|
3002
|
-
content: Object.prototype.hasOwnProperty.call(update, "content") && update.content !== void 0 ? sanitizeToolContent(update.content) : update.content
|
|
3003
|
-
}
|
|
3329
|
+
update: sanitizeToolUpdate(update)
|
|
3004
3330
|
}
|
|
3005
3331
|
};
|
|
3006
3332
|
}
|
|
3333
|
+
function sanitizeToolUpdate(update) {
|
|
3334
|
+
return {
|
|
3335
|
+
...update,
|
|
3336
|
+
rawOutput: sanitizedRawOutput(update),
|
|
3337
|
+
content: sanitizedContent(update)
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
function sanitizedRawOutput(update) {
|
|
3341
|
+
if (Object.prototype.hasOwnProperty.call(update, "rawOutput") && update.rawOutput !== void 0) return { content: SUPPRESSED_READ_OUTPUT };
|
|
3342
|
+
return update.rawOutput;
|
|
3343
|
+
}
|
|
3344
|
+
function sanitizedContent(update) {
|
|
3345
|
+
if (Object.prototype.hasOwnProperty.call(update, "content") && update.content !== void 0) return sanitizeToolContent(update.content);
|
|
3346
|
+
return update.content;
|
|
3347
|
+
}
|
|
3007
3348
|
var JsonOutputFormatter = class {
|
|
3008
3349
|
stdout;
|
|
3009
3350
|
suppressReads;
|
|
@@ -3052,23 +3393,30 @@ var JsonOutputFormatter = class {
|
|
|
3052
3393
|
};
|
|
3053
3394
|
}
|
|
3054
3395
|
sanitizeReadToolMessage(message) {
|
|
3396
|
+
const update = this.readToolUpdate(message);
|
|
3397
|
+
if (!update) return message;
|
|
3398
|
+
const toolCallId = typeof update.toolCallId === "string" ? update.toolCallId : void 0;
|
|
3399
|
+
if (!toolCallId) return message;
|
|
3400
|
+
const current = this.mergeToolState(toolCallId, update);
|
|
3401
|
+
this.toolStateById.set(toolCallId, current);
|
|
3402
|
+
return isReadLikeTool(current) ? sanitizeToolMessage(message) : message;
|
|
3403
|
+
}
|
|
3404
|
+
readToolUpdate(message) {
|
|
3055
3405
|
const root = asRecord$1(message);
|
|
3056
|
-
if (root?.method !== "session/update") return
|
|
3406
|
+
if (root?.method !== "session/update") return;
|
|
3057
3407
|
const params = asRecord$1(root.params);
|
|
3058
3408
|
const update = asRecord$1(params?.update);
|
|
3059
|
-
if (!params || !update) return
|
|
3409
|
+
if (!params || !update) return;
|
|
3060
3410
|
const sessionUpdate = update.sessionUpdate;
|
|
3061
|
-
if (sessionUpdate !== "tool_call" && sessionUpdate !== "tool_call_update") return
|
|
3062
|
-
|
|
3063
|
-
|
|
3411
|
+
if (sessionUpdate !== "tool_call" && sessionUpdate !== "tool_call_update") return;
|
|
3412
|
+
return update;
|
|
3413
|
+
}
|
|
3414
|
+
mergeToolState(toolCallId, update) {
|
|
3064
3415
|
const previous = this.toolStateById.get(toolCallId) ?? {};
|
|
3065
|
-
|
|
3416
|
+
return {
|
|
3066
3417
|
title: typeof update.title === "string" ? update.title : previous.title,
|
|
3067
3418
|
kind: typeof update.kind === "string" || update.kind === null ? update.kind : previous.kind
|
|
3068
3419
|
};
|
|
3069
|
-
this.toolStateById.set(toolCallId, current);
|
|
3070
|
-
if (!isReadLikeTool(current)) return message;
|
|
3071
|
-
return sanitizeToolMessage(message);
|
|
3072
3420
|
}
|
|
3073
3421
|
onError(params) {
|
|
3074
3422
|
this.stdout.write(`${JSON.stringify(buildJsonRpcErrorResponse({
|
|
@@ -3218,15 +3566,16 @@ function parseAuthMethodIdsFromAcpData(data) {
|
|
|
3218
3566
|
const methodIds = [];
|
|
3219
3567
|
if (typeof record.methodId === "string" && record.methodId.trim().length > 0) methodIds.push(record.methodId.trim());
|
|
3220
3568
|
if (Array.isArray(record.methods)) for (const entry of record.methods) {
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
continue;
|
|
3224
|
-
}
|
|
3225
|
-
const id = asRecord(entry)?.id;
|
|
3226
|
-
if (typeof id === "string" && id.trim().length > 0) methodIds.push(id.trim());
|
|
3569
|
+
const id = parseAuthMethodIdEntry(entry);
|
|
3570
|
+
if (id) methodIds.push(id);
|
|
3227
3571
|
}
|
|
3228
3572
|
return dedupeStrings(methodIds);
|
|
3229
3573
|
}
|
|
3574
|
+
function parseAuthMethodIdEntry(entry) {
|
|
3575
|
+
if (typeof entry === "string" && entry.trim().length > 0) return entry.trim();
|
|
3576
|
+
const id = asRecord(entry)?.id;
|
|
3577
|
+
return typeof id === "string" && id.trim().length > 0 ? id.trim() : void 0;
|
|
3578
|
+
}
|
|
3230
3579
|
function renderAuthRequiredHint(params) {
|
|
3231
3580
|
const methodIds = dedupeStrings([...parseAuthMethodIdsFromAcpData(params.acp?.data), ...parseAuthMethodIdsFromMessage(params.message)]);
|
|
3232
3581
|
if (methodIds.length === 0) return "hint: run `acpx config show` to locate the active config, then add the required credential under `auth` and retry.";
|
|
@@ -3236,48 +3585,104 @@ function getTextErrorRemediationHints(params) {
|
|
|
3236
3585
|
const lowerMessage = params.message.toLowerCase();
|
|
3237
3586
|
if (params.detailCode === "AUTH_REQUIRED") return [renderAuthRequiredHint(params)];
|
|
3238
3587
|
if (params.code === "TIMEOUT") return ["hint: increase `--timeout <seconds>` for long-running prompts, or check whether the agent/provider is stalled."];
|
|
3239
|
-
if (params.code === "NO_SESSION")
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3588
|
+
if (params.code === "NO_SESSION") return noSessionHints(lowerMessage);
|
|
3589
|
+
return matchingTextErrorRule(params, lowerMessage)?.hints ?? [];
|
|
3590
|
+
}
|
|
3591
|
+
const TEXT_ERROR_HINT_RULES = [
|
|
3592
|
+
{
|
|
3593
|
+
matches: (_params, lowerMessage) => isUnsupportedSessionLoadError(lowerMessage),
|
|
3594
|
+
hints: ["hint: this adapter cannot resume saved ACP sessions; create a fresh one with `acpx <agent> sessions new` instead of reusing `--resume-session`."]
|
|
3595
|
+
},
|
|
3596
|
+
{
|
|
3597
|
+
matches: (_params, lowerMessage) => isSessionLoadError(lowerMessage),
|
|
3598
|
+
hints: ["hint: rerun with `--verbose` to capture the ACP load failure details.", "hint: if you do not need the old backend session, start a fresh one with `acpx <agent> sessions new` and retry."]
|
|
3599
|
+
},
|
|
3600
|
+
{
|
|
3601
|
+
matches: (params, lowerMessage) => isRateLimitError(params.message, lowerMessage),
|
|
3602
|
+
hints: ["hint: the provider appears rate-limited; retry later, switch model, or check provider quota/billing."]
|
|
3603
|
+
},
|
|
3604
|
+
{
|
|
3605
|
+
matches: (_params, lowerMessage) => isModelLookupError(lowerMessage),
|
|
3606
|
+
hints: ["hint: check the configured model name for this agent, then retry with `--model <model>` or `sessions set-model <model>`."]
|
|
3607
|
+
},
|
|
3608
|
+
{
|
|
3609
|
+
matches: (_params, lowerMessage) => isSessionConfigMethodError(lowerMessage),
|
|
3610
|
+
hints: ["hint: rerun with `--verbose` to capture the ACP method/error details before retrying."]
|
|
3611
|
+
},
|
|
3612
|
+
{
|
|
3613
|
+
matches: isRuntimeAcpProtocolError,
|
|
3614
|
+
hints: ["hint: rerun with `--verbose` to capture the underlying ACP error details."]
|
|
3615
|
+
}
|
|
3616
|
+
];
|
|
3617
|
+
function matchingTextErrorRule(params, lowerMessage) {
|
|
3618
|
+
return TEXT_ERROR_HINT_RULES.find((rule) => rule.matches(params, lowerMessage));
|
|
3619
|
+
}
|
|
3620
|
+
function noSessionHints(lowerMessage) {
|
|
3621
|
+
if (lowerMessage.includes("create one:")) return [];
|
|
3622
|
+
return ["hint: the saved ACP session is missing or stale; start a fresh session with `acpx <agent> sessions new`, then retry."];
|
|
3623
|
+
}
|
|
3624
|
+
function isUnsupportedSessionLoadError(lowerMessage) {
|
|
3625
|
+
return lowerMessage.includes("does not support session/resume") || lowerMessage.includes("does not support session/load");
|
|
3626
|
+
}
|
|
3627
|
+
function isSessionLoadError(lowerMessage) {
|
|
3628
|
+
return lowerMessage.includes("failed to resume acp session") || lowerMessage.includes("session/resume") || lowerMessage.includes("session/load");
|
|
3629
|
+
}
|
|
3630
|
+
function isRateLimitError(message, lowerMessage) {
|
|
3631
|
+
return /\b429\b/u.test(message) || ["rate limit", "quota exceeded"].some((text) => lowerMessage.includes(text));
|
|
3632
|
+
}
|
|
3633
|
+
function isModelLookupError(lowerMessage) {
|
|
3634
|
+
return [
|
|
3635
|
+
"model not found",
|
|
3636
|
+
"unknown model",
|
|
3637
|
+
"invalid model"
|
|
3638
|
+
].some((text) => lowerMessage.includes(text));
|
|
3639
|
+
}
|
|
3640
|
+
function isSessionConfigMethodError(lowerMessage) {
|
|
3641
|
+
return [
|
|
3642
|
+
"session/set_mode",
|
|
3643
|
+
"session/set_model",
|
|
3644
|
+
"session/set_config_option"
|
|
3645
|
+
].some((text) => lowerMessage.includes(text));
|
|
3646
|
+
}
|
|
3647
|
+
function isRuntimeAcpProtocolError(params, lowerMessage) {
|
|
3648
|
+
return params.origin === "acp" && params.code === "RUNTIME" && (params.acp?.code === -32602 || params.acp?.code === -32603 || lowerMessage.includes("internal error"));
|
|
3250
3649
|
}
|
|
3251
3650
|
function summarizeToolInput(rawInput) {
|
|
3252
3651
|
if (rawInput == null) return;
|
|
3253
|
-
if (
|
|
3652
|
+
if (isScalarToolInput(rawInput)) return toInline(String(rawInput));
|
|
3254
3653
|
const record = asRecord(rawInput);
|
|
3255
|
-
if (record)
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3654
|
+
if (record) return summarizeToolInputRecord(record) ?? summarizeToolInputJson(rawInput);
|
|
3655
|
+
return summarizeToolInputJson(rawInput);
|
|
3656
|
+
}
|
|
3657
|
+
function isScalarToolInput(value) {
|
|
3658
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
3659
|
+
}
|
|
3660
|
+
function summarizeToolInputRecord(record) {
|
|
3661
|
+
const command = readFirstString(record, [
|
|
3662
|
+
"command",
|
|
3663
|
+
"cmd",
|
|
3664
|
+
"program"
|
|
3665
|
+
]);
|
|
3666
|
+
const args = readFirstStringArray(record, ["args", "arguments"]);
|
|
3667
|
+
if (command) return toInline([command, ...args ?? []].join(" "));
|
|
3668
|
+
const location = readFirstString(record, [
|
|
3669
|
+
"path",
|
|
3670
|
+
"file",
|
|
3671
|
+
"filePath",
|
|
3672
|
+
"filepath",
|
|
3673
|
+
"target",
|
|
3674
|
+
"uri",
|
|
3675
|
+
"url"
|
|
3676
|
+
]);
|
|
3677
|
+
const query = readFirstString(record, [
|
|
3678
|
+
"query",
|
|
3679
|
+
"pattern",
|
|
3680
|
+
"text",
|
|
3681
|
+
"search"
|
|
3682
|
+
]);
|
|
3683
|
+
return location || query ? toInline(location ?? query ?? "") : void 0;
|
|
3684
|
+
}
|
|
3685
|
+
function summarizeToolInputJson(rawInput) {
|
|
3281
3686
|
const json = safeJson(rawInput, 0);
|
|
3282
3687
|
return json ? toInline(json) : void 0;
|
|
3283
3688
|
}
|
|
@@ -3285,10 +3690,8 @@ function formatLocations(locations) {
|
|
|
3285
3690
|
if (!locations || locations.length === 0) return;
|
|
3286
3691
|
const unique = /* @__PURE__ */ new Set();
|
|
3287
3692
|
for (const location of locations) {
|
|
3288
|
-
const
|
|
3289
|
-
if (
|
|
3290
|
-
const line = typeof location.line === "number" && Number.isFinite(location.line) ? `:${Math.max(1, Math.trunc(location.line))}` : "";
|
|
3291
|
-
unique.add(`${path}${line}`);
|
|
3693
|
+
const formatted = formatLocation(location);
|
|
3694
|
+
if (formatted) unique.add(formatted);
|
|
3292
3695
|
}
|
|
3293
3696
|
const items = [...unique];
|
|
3294
3697
|
if (items.length === 0) return;
|
|
@@ -3297,6 +3700,11 @@ function formatLocations(locations) {
|
|
|
3297
3700
|
if (hidden <= 0) return visible.join(", ");
|
|
3298
3701
|
return `${visible.join(", ")}, +${hidden} more`;
|
|
3299
3702
|
}
|
|
3703
|
+
function formatLocation(location) {
|
|
3704
|
+
const path = location.path?.trim();
|
|
3705
|
+
if (!path) return;
|
|
3706
|
+
return `${path}${typeof location.line === "number" && Number.isFinite(location.line) ? `:${Math.max(1, Math.trunc(location.line))}` : ""}`;
|
|
3707
|
+
}
|
|
3300
3708
|
function summarizeDiff(path, oldText, newText) {
|
|
3301
3709
|
const oldLines = oldText ? oldText.split("\n").length : 0;
|
|
3302
3710
|
const delta = newText.split("\n").length - oldLines;
|
|
@@ -3307,64 +3715,74 @@ function textFromContentBlock(content) {
|
|
|
3307
3715
|
switch (content.type) {
|
|
3308
3716
|
case "text": return content.text;
|
|
3309
3717
|
case "resource_link": return content.title ?? content.name ?? content.uri;
|
|
3310
|
-
case "resource":
|
|
3311
|
-
if ("text" in content.resource && typeof content.resource.text === "string") return content.resource.text;
|
|
3312
|
-
const uri = content.resource.uri;
|
|
3313
|
-
const mimeType = content.resource.mimeType;
|
|
3314
|
-
return `[resource] ${uri}${mimeType ? ` (${mimeType})` : ""}`;
|
|
3315
|
-
}
|
|
3718
|
+
case "resource": return textFromResourceBlock(content);
|
|
3316
3719
|
case "image": return `[image] ${content.mimeType}`;
|
|
3317
3720
|
case "audio": return `[audio] ${content.mimeType}`;
|
|
3318
3721
|
default: return;
|
|
3319
3722
|
}
|
|
3320
3723
|
}
|
|
3724
|
+
function textFromResourceBlock(content) {
|
|
3725
|
+
if ("text" in content.resource && typeof content.resource.text === "string") return content.resource.text;
|
|
3726
|
+
const uri = content.resource.uri;
|
|
3727
|
+
const mimeType = content.resource.mimeType;
|
|
3728
|
+
return `[resource] ${uri}${mimeType ? ` (${mimeType})` : ""}`;
|
|
3729
|
+
}
|
|
3321
3730
|
function summarizeToolContent(content) {
|
|
3322
3731
|
if (!content || content.length === 0) return;
|
|
3323
3732
|
const fragments = [];
|
|
3324
3733
|
for (const entry of content) {
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
if (text && text.trim()) fragments.push(text.trimEnd());
|
|
3328
|
-
continue;
|
|
3329
|
-
}
|
|
3330
|
-
if (entry.type === "diff") {
|
|
3331
|
-
fragments.push(summarizeDiff(entry.path, entry.oldText, entry.newText));
|
|
3332
|
-
continue;
|
|
3333
|
-
}
|
|
3334
|
-
if (entry.type === "terminal") fragments.push(`[terminal] ${entry.terminalId}`);
|
|
3734
|
+
const fragment = summarizeToolContentEntry(entry);
|
|
3735
|
+
if (fragment) fragments.push(fragment);
|
|
3335
3736
|
}
|
|
3336
3737
|
const unique = dedupeStrings(fragments.map((fragment) => fragment.trim()).filter((fragment) => fragment.length > 0));
|
|
3337
3738
|
if (unique.length === 0) return;
|
|
3338
3739
|
return unique.join("\n\n");
|
|
3339
3740
|
}
|
|
3741
|
+
function summarizeToolContentEntry(entry) {
|
|
3742
|
+
if (entry.type === "content") {
|
|
3743
|
+
const text = textFromContentBlock(entry.content);
|
|
3744
|
+
return text && text.trim() ? text.trimEnd() : void 0;
|
|
3745
|
+
}
|
|
3746
|
+
if (entry.type === "diff") return summarizeDiff(entry.path, entry.oldText, entry.newText);
|
|
3747
|
+
if (entry.type === "terminal") return `[terminal] ${entry.terminalId}`;
|
|
3748
|
+
}
|
|
3340
3749
|
function extractOutputText(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
3341
3750
|
if (value == null) return;
|
|
3751
|
+
const scalar = extractScalarOutputText(value);
|
|
3752
|
+
if (scalar !== null) return scalar;
|
|
3753
|
+
if (depth >= 4) return;
|
|
3754
|
+
if (Array.isArray(value)) return extractOutputTextArray(value, depth, seen);
|
|
3755
|
+
return extractOutputTextRecord(value, depth, seen);
|
|
3756
|
+
}
|
|
3757
|
+
function extractOutputTextRecord(value, depth, seen) {
|
|
3758
|
+
const record = asRecord(value);
|
|
3759
|
+
if (!record || seen.has(record)) return;
|
|
3760
|
+
seen.add(record);
|
|
3761
|
+
return extractPreferredOutputText(record, depth, seen) ?? extractJsonOutputText(record);
|
|
3762
|
+
}
|
|
3763
|
+
function extractScalarOutputText(value) {
|
|
3342
3764
|
if (typeof value === "string") {
|
|
3343
3765
|
const trimmed = value.trimEnd();
|
|
3344
3766
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
3345
3767
|
}
|
|
3346
3768
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
seen.add(record);
|
|
3357
|
-
const preferred = [];
|
|
3358
|
-
for (const key of OUTPUT_PRIORITY_KEYS) {
|
|
3359
|
-
if (!(key in record)) continue;
|
|
3769
|
+
return null;
|
|
3770
|
+
}
|
|
3771
|
+
function extractOutputTextArray(value, depth, seen) {
|
|
3772
|
+
const parts = value.map((entry) => extractOutputText(entry, depth + 1, seen)).filter((entry) => Boolean(entry));
|
|
3773
|
+
return parts.length > 0 ? dedupeStrings(parts).join("\n") : void 0;
|
|
3774
|
+
}
|
|
3775
|
+
function extractPreferredOutputText(record, depth, seen) {
|
|
3776
|
+
const uniquePreferred = dedupeStrings(OUTPUT_PRIORITY_KEYS.flatMap((key) => {
|
|
3777
|
+
if (!(key in record)) return [];
|
|
3360
3778
|
const extracted = extractOutputText(record[key], depth + 1, seen);
|
|
3361
|
-
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
|
|
3779
|
+
return extracted ? [extracted] : [];
|
|
3780
|
+
}));
|
|
3781
|
+
return uniquePreferred.length > 0 ? uniquePreferred.join("\n") : void 0;
|
|
3782
|
+
}
|
|
3783
|
+
function extractJsonOutputText(record) {
|
|
3365
3784
|
const json = safeJson(record, 2);
|
|
3366
|
-
|
|
3367
|
-
return json;
|
|
3785
|
+
return !json || json === "{}" ? void 0 : json;
|
|
3368
3786
|
}
|
|
3369
3787
|
function summarizeToolOutput(rawOutput, content) {
|
|
3370
3788
|
const fragments = dedupeStrings([extractOutputText(rawOutput), summarizeToolContent(content)].map((fragment) => fragment?.trim()).filter((fragment) => Boolean(fragment)));
|
|
@@ -3434,6 +3852,9 @@ var TextOutputFormatter = class {
|
|
|
3434
3852
|
renderSessionUpdate(notification) {
|
|
3435
3853
|
const update = notification.update;
|
|
3436
3854
|
if (update.sessionUpdate !== "agent_thought_chunk") this.flushThoughtBuffer();
|
|
3855
|
+
this.renderSessionUpdateBody(update);
|
|
3856
|
+
}
|
|
3857
|
+
renderSessionUpdateBody(update) {
|
|
3437
3858
|
switch (update.sessionUpdate) {
|
|
3438
3859
|
case "agent_message_chunk":
|
|
3439
3860
|
if (update.content.type === "text") this.writeAssistantChunk(update.content.text);
|
|
@@ -3448,13 +3869,16 @@ var TextOutputFormatter = class {
|
|
|
3448
3869
|
this.renderToolUpdate(update);
|
|
3449
3870
|
return;
|
|
3450
3871
|
case "plan":
|
|
3451
|
-
this.
|
|
3452
|
-
this.writeLine(this.bold("[plan]"));
|
|
3453
|
-
for (const entry of update.entries) this.writeLine(` - [${entry.status}] ${entry.content}`);
|
|
3872
|
+
this.renderPlanUpdate(update.entries);
|
|
3454
3873
|
return;
|
|
3455
3874
|
default: return;
|
|
3456
3875
|
}
|
|
3457
3876
|
}
|
|
3877
|
+
renderPlanUpdate(entries) {
|
|
3878
|
+
this.beginSection("plan");
|
|
3879
|
+
this.writeLine(this.bold("[plan]"));
|
|
3880
|
+
for (const entry of entries) this.writeLine(` - [${entry.status}] ${entry.content}`);
|
|
3881
|
+
}
|
|
3458
3882
|
renderDone(stopReason) {
|
|
3459
3883
|
this.flushThoughtBuffer();
|
|
3460
3884
|
this.beginSection("done");
|
|
@@ -3551,7 +3975,13 @@ var TextOutputFormatter = class {
|
|
|
3551
3975
|
return created;
|
|
3552
3976
|
}
|
|
3553
3977
|
mergeToolState(state, update) {
|
|
3554
|
-
|
|
3978
|
+
this.mergeToolTitle(state, update.title);
|
|
3979
|
+
this.mergeToolPayloadState(state, update);
|
|
3980
|
+
}
|
|
3981
|
+
mergeToolTitle(state, title) {
|
|
3982
|
+
if (typeof title === "string" && title.trim().length > 0) state.title = title;
|
|
3983
|
+
}
|
|
3984
|
+
mergeToolPayloadState(state, update) {
|
|
3555
3985
|
if (update.status !== void 0) state.status = update.status;
|
|
3556
3986
|
if (update.kind !== void 0) state.kind = update.kind;
|
|
3557
3987
|
if (update.locations !== void 0) state.locations = update.locations;
|
|
@@ -3681,8 +4111,8 @@ var QuietOutputFormatter = class {
|
|
|
3681
4111
|
return parts.length > 0 ? `[acpx] tokens: ${parts.join(" ")}` : void 0;
|
|
3682
4112
|
}
|
|
3683
4113
|
formatCostLine(cost) {
|
|
3684
|
-
|
|
3685
|
-
if (
|
|
4114
|
+
const scalar = this.formatScalarCostLine(cost);
|
|
4115
|
+
if (scalar) return scalar;
|
|
3686
4116
|
const record = asRecord(cost);
|
|
3687
4117
|
if (!record) return;
|
|
3688
4118
|
const amount = readFirstFiniteNumber(record, [
|
|
@@ -3694,6 +4124,10 @@ var QuietOutputFormatter = class {
|
|
|
3694
4124
|
const currency = typeof record.currency === "string" && record.currency.trim() ? ` ${record.currency.trim()}` : "";
|
|
3695
4125
|
return `[acpx] cost: ${formatMetadataNumber(amount)}${currency}`;
|
|
3696
4126
|
}
|
|
4127
|
+
formatScalarCostLine(cost) {
|
|
4128
|
+
if (typeof cost === "number" && Number.isFinite(cost)) return `[acpx] cost: ${formatMetadataNumber(cost)}`;
|
|
4129
|
+
if (typeof cost === "string" && cost.trim()) return `[acpx] cost: ${cost.trim()}`;
|
|
4130
|
+
}
|
|
3697
4131
|
};
|
|
3698
4132
|
function createOutputFormatter(format, options = {}) {
|
|
3699
4133
|
const stdout = options.stdout ?? process.stdout;
|
|
@@ -3709,4 +4143,4 @@ function createOutputFormatter(format, options = {}) {
|
|
|
3709
4143
|
//#endregion
|
|
3710
4144
|
export { runSessionQueueOwner as a, buildQueueOwnerArgOverride as c, createSessionWithClient as d, cancelSessionPrompt as f, __exportAll as h, session_exports as i, flushPerfMetricsCapture as l, DEFAULT_QUEUE_OWNER_TTL_MS as m, getTextErrorRemediationHints as n, runOnce as o, probeQueueOwnerHealth as p, output_exports as r, sendSessionDirect as s, createOutputFormatter as t, installPerfMetricsCapture as u };
|
|
3711
4145
|
|
|
3712
|
-
//# sourceMappingURL=output-
|
|
4146
|
+
//# sourceMappingURL=output-DPg20dvn.js.map
|