acpx 0.11.2 → 0.12.1
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 +6 -4
- package/dist/{cli-CXlEcGWV.js → cli-pKoIEH5O.js} +4 -4
- package/dist/{cli-CXlEcGWV.js.map → cli-pKoIEH5O.js.map} +1 -1
- package/dist/cli.d.ts +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +37 -16
- package/dist/cli.js.map +1 -1
- package/dist/{client-DIlpCkHw.d.ts → client-CFo_9MIo.d.ts} +5 -3
- package/dist/client-CFo_9MIo.d.ts.map +1 -0
- package/dist/{flags-Dl_mhic3.js → flags-CjeFX5ki.js} +4 -4
- package/dist/{flags-Dl_mhic3.js.map → flags-CjeFX5ki.js.map} +1 -1
- package/dist/{flows-7OHjgEgq.js → flows-Cym6H3vg.js} +3 -3
- package/dist/flows-Cym6H3vg.js.map +1 -0
- package/dist/flows.d.ts +2 -2
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{live-checkpoint-mdAaF3qJ.js → live-checkpoint-DQp4JSHD.js} +34 -6
- package/dist/live-checkpoint-DQp4JSHD.js.map +1 -0
- package/dist/{output-Smw6kFL_.js → output-fNqTAsFQ.js} +427 -90
- package/dist/output-fNqTAsFQ.js.map +1 -0
- package/dist/runtime.d.ts +16 -12
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +3 -2
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-jkYbBxGE.d.ts → session-options-C4uYkKDy.d.ts} +1 -2
- package/dist/session-options-C4uYkKDy.d.ts.map +1 -0
- package/package.json +19 -19
- package/skills/acpx/SKILL.md +7 -6
- package/dist/client-DIlpCkHw.d.ts.map +0 -1
- package/dist/flows-7OHjgEgq.js.map +0 -1
- package/dist/live-checkpoint-mdAaF3qJ.js.map +0 -1
- package/dist/output-Smw6kFL_.js.map +0 -1
- package/dist/session-options-jkYbBxGE.d.ts.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as getPerfMetricsSnapshot, B as absolutePath, C as mergeSessionOptions, Ct as promptToDisplayText, D as applyLifecycleSnapshotToRecord, Dt as withInterrupt, E as applyConversation, Et as TimeoutError, F as modelStateFromConfigOptions, Ft as isRetryablePromptError, G as listSessions, H as findSession, I as splitCommandLine, It as normalizeOutputError, J as pruneSessions, K as listSessionsForAgent, N as assertRequestedModelSupported, Ot as withTimeout, Pt as formatErrorMessage, Q as formatPerfMetric, Qt as QueueProtocolError, S as advertisedModelState, T as sessionOptionsFromRecord, Tt as InterruptedError, U as findSessionByDirectoryWalk, Ut as OUTPUT_ERROR_CODES, V as findGitRepositoryRoot, W as isoNow, Wt as OUTPUT_ERROR_ORIGINS, X as writeSessionRecord, Y as resolveSessionRecord, Zt as QueueConnectionError, _ as createSessionConversation, _t as parseJsonRpcErrorMessage, a as applyRequestedModelIfAdvertised, at as startPerfTimer, b as recordSessionUpdate, bt as isPromptInput, c as setCurrentModelId, ct as normalizeRuntimeSessionId, d as setDesiredModelId, dt as sessionBaseDir, et as incrementPerfCounter, f as syncAdvertisedModelState, ft as sessionEventActivePath, g as cloneSessionConversation, gt as isAcpJsonRpcMessage, h as cloneSessionAcpxState, ht as extractSessionUpdateNotification, i as connectAndLoadSession, it as setPerfGauge, k as AcpClient, l as setDesiredConfigOption, m as applyConfigOptionsToState, mt as sessionEventSegmentPath, n as runPromptTurn, nt as recordPerfDuration, o as currentModelIdFromSetModelResponse, p as applyConfigOptionsToRecord, pt as sessionEventLockPath, q as normalizeName, r as withConnectedSession, rt as resetPerfMetrics, s as clearDesiredConfigOption, t as LiveSessionCheckpoint, tt as measurePerf, u as setDesiredModeId, ut as defaultSessionEventLog, v as recordClientOperation, vt as parsePromptStopReason, w as persistSessionOptions, wt as textPrompt, x as trimConversationForRuntime, y as recordPromptSubmission, zt as toAcpErrorPayload } from "./live-checkpoint-
|
|
1
|
+
import { $ as getPerfMetricsSnapshot, B as absolutePath, C as mergeSessionOptions, Ct as promptToDisplayText, D as applyLifecycleSnapshotToRecord, Dt as withInterrupt, E as applyConversation, Et as TimeoutError, F as modelStateFromConfigOptions, Ft as isRetryablePromptError, G as listSessions, H as findSession, I as splitCommandLine, It as normalizeOutputError, J as pruneSessions, K as listSessionsForAgent, Lt as extractAcpError, N as assertRequestedModelSupported, Ot as withTimeout, Pt as formatErrorMessage, Q as formatPerfMetric, Qt as QueueProtocolError, S as advertisedModelState, T as sessionOptionsFromRecord, Tt as InterruptedError, U as findSessionByDirectoryWalk, Ut as OUTPUT_ERROR_CODES, V as findGitRepositoryRoot, W as isoNow, Wt as OUTPUT_ERROR_ORIGINS, X as writeSessionRecord, Y as resolveSessionRecord, Zt as QueueConnectionError, _ as createSessionConversation, _t as parseJsonRpcErrorMessage, a as applyRequestedModelIfAdvertised, at as startPerfTimer, b as recordSessionUpdate, bt as isPromptInput, c as setCurrentModelId, ct as normalizeRuntimeSessionId, d as setDesiredModelId, dt as sessionBaseDir, et as incrementPerfCounter, f as syncAdvertisedModelState, ft as sessionEventActivePath, g as cloneSessionConversation, gt as isAcpJsonRpcMessage, h as cloneSessionAcpxState, ht as extractSessionUpdateNotification, i as connectAndLoadSession, it as setPerfGauge, k as AcpClient, l as setDesiredConfigOption, m as applyConfigOptionsToState, mt as sessionEventSegmentPath, n as runPromptTurn, nt as recordPerfDuration, o as currentModelIdFromSetModelResponse, p as applyConfigOptionsToRecord, pt as sessionEventLockPath, q as normalizeName, r as withConnectedSession, rt as resetPerfMetrics, s as clearDesiredConfigOption, t as LiveSessionCheckpoint, tt as measurePerf, u as setDesiredModeId, ut as defaultSessionEventLog, v as recordClientOperation, vt as parsePromptStopReason, w as persistSessionOptions, wt as textPrompt, x as trimConversationForRuntime, y as recordPromptSubmission, zt as toAcpErrorPayload } from "./live-checkpoint-DQp4JSHD.js";
|
|
2
2
|
import fs, { mkdtempSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs$1 from "node:fs/promises";
|
|
@@ -62,7 +62,8 @@ function queueSocketPath(sessionId, homeDir = os.homedir()) {
|
|
|
62
62
|
}
|
|
63
63
|
//#endregion
|
|
64
64
|
//#region src/cli/queue/lease-store.ts
|
|
65
|
-
const
|
|
65
|
+
const PROCESS_SIGTERM_GRACE_MS = 4e3;
|
|
66
|
+
const PROCESS_SIGKILL_GRACE_MS = 1500;
|
|
66
67
|
const PROCESS_POLL_MS = 50;
|
|
67
68
|
const QUEUE_OWNER_STALE_HEARTBEAT_MS = 15e3;
|
|
68
69
|
function parseQueueOwnerRecord(raw) {
|
|
@@ -103,18 +104,26 @@ function isQueueOwnerHeartbeatStale(owner) {
|
|
|
103
104
|
}
|
|
104
105
|
async function ensureQueueDir() {
|
|
105
106
|
const baseDir = queueBaseDir();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
try {
|
|
108
|
+
await fs$1.mkdir(baseDir, {
|
|
109
|
+
recursive: true,
|
|
110
|
+
mode: 448
|
|
111
|
+
});
|
|
112
|
+
await fs$1.chmod(baseDir, 448);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
throw new Error(`Failed to prepare queue directory ${baseDir}: ${message}`, { cause: error });
|
|
116
|
+
}
|
|
111
117
|
const socketDir = queueSocketBaseDir();
|
|
112
|
-
if (socketDir) {
|
|
118
|
+
if (socketDir) try {
|
|
113
119
|
await fs$1.mkdir(socketDir, {
|
|
114
120
|
recursive: true,
|
|
115
121
|
mode: 448
|
|
116
122
|
});
|
|
117
123
|
await fs$1.chmod(socketDir, 448);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
throw new Error(`Failed to prepare queue socket directory ${socketDir}: ${message}`, { cause: error });
|
|
118
127
|
}
|
|
119
128
|
}
|
|
120
129
|
async function removeSocketFile(socketPath) {
|
|
@@ -160,13 +169,13 @@ async function terminateProcess(pid) {
|
|
|
160
169
|
} catch {
|
|
161
170
|
return false;
|
|
162
171
|
}
|
|
163
|
-
if (await waitForProcessExit(pid,
|
|
172
|
+
if (await waitForProcessExit(pid, PROCESS_SIGTERM_GRACE_MS)) return true;
|
|
164
173
|
try {
|
|
165
174
|
process.kill(pid, "SIGKILL");
|
|
166
175
|
} catch {
|
|
167
176
|
return false;
|
|
168
177
|
}
|
|
169
|
-
await waitForProcessExit(pid,
|
|
178
|
+
await waitForProcessExit(pid, PROCESS_SIGKILL_GRACE_MS);
|
|
170
179
|
return true;
|
|
171
180
|
}
|
|
172
181
|
async function ensureOwnerIsUsable(sessionId, owner) {
|
|
@@ -753,6 +762,7 @@ function isValidOwnerErrorCore(message) {
|
|
|
753
762
|
}
|
|
754
763
|
//#endregion
|
|
755
764
|
//#region src/cli/queue/ipc-server.ts
|
|
765
|
+
const QUEUE_SOCKET_DRAIN_TIMEOUT_MS = 1e3;
|
|
756
766
|
function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
|
|
757
767
|
return {
|
|
758
768
|
type: "error",
|
|
@@ -795,6 +805,9 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
795
805
|
onQueueDepthChanged;
|
|
796
806
|
pending = [];
|
|
797
807
|
waiters = [];
|
|
808
|
+
sockets = /* @__PURE__ */ new Set();
|
|
809
|
+
taskSockets = /* @__PURE__ */ new Set();
|
|
810
|
+
drainingSockets = /* @__PURE__ */ new Set();
|
|
798
811
|
closed = false;
|
|
799
812
|
constructor(server, controlHandlers, lease, options) {
|
|
800
813
|
this.server = server;
|
|
@@ -824,7 +837,7 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
824
837
|
});
|
|
825
838
|
return ownerRef.current;
|
|
826
839
|
}
|
|
827
|
-
|
|
840
|
+
beginShutdown() {
|
|
828
841
|
if (this.closed) return;
|
|
829
842
|
this.closed = true;
|
|
830
843
|
for (const waiter of this.waiters.splice(0)) waiter(void 0);
|
|
@@ -832,9 +845,20 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
832
845
|
if (task.waitForCompletion) task.send(makeQueueOwnerError(task.requestId, "Queue owner shutting down before prompt execution", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
|
|
833
846
|
task.close();
|
|
834
847
|
}
|
|
848
|
+
for (const socket of this.sockets) if (!this.taskSockets.has(socket) && !this.drainingSockets.has(socket)) socket.destroy();
|
|
835
849
|
this.emitQueueDepth();
|
|
850
|
+
}
|
|
851
|
+
async close() {
|
|
852
|
+
this.beginShutdown();
|
|
836
853
|
await new Promise((resolve) => {
|
|
837
|
-
|
|
854
|
+
const forceCloseTimer = setTimeout(() => {
|
|
855
|
+
for (const socket of this.sockets) socket.destroy();
|
|
856
|
+
}, QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
|
|
857
|
+
forceCloseTimer.unref();
|
|
858
|
+
this.server.close(() => {
|
|
859
|
+
clearTimeout(forceCloseTimer);
|
|
860
|
+
resolve();
|
|
861
|
+
});
|
|
838
862
|
});
|
|
839
863
|
}
|
|
840
864
|
async nextTask(timeoutMs) {
|
|
@@ -904,7 +928,16 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
904
928
|
ownerGeneration: this.ownerGeneration
|
|
905
929
|
});
|
|
906
930
|
}).finally(() => {
|
|
907
|
-
|
|
931
|
+
this.endSocket(options.socket);
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
endSocket(socket) {
|
|
935
|
+
if (socket.destroyed || this.drainingSockets.has(socket)) return;
|
|
936
|
+
this.taskSockets.delete(socket);
|
|
937
|
+
this.drainingSockets.add(socket);
|
|
938
|
+
socket.end(() => {
|
|
939
|
+
this.drainingSockets.delete(socket);
|
|
940
|
+
socket.destroy();
|
|
908
941
|
});
|
|
909
942
|
}
|
|
910
943
|
failRequest(socket, requestId, message, detailCode) {
|
|
@@ -912,7 +945,7 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
912
945
|
...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
|
|
913
946
|
ownerGeneration: this.ownerGeneration
|
|
914
947
|
});
|
|
915
|
-
|
|
948
|
+
this.endSocket(socket);
|
|
916
949
|
}
|
|
917
950
|
parseRequestLine(socket, line) {
|
|
918
951
|
let parsed;
|
|
@@ -931,6 +964,15 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
931
964
|
this.failRequest(socket, request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
|
|
932
965
|
return true;
|
|
933
966
|
}
|
|
967
|
+
rejectClosedOwner(socket, request) {
|
|
968
|
+
if (!this.closed) return false;
|
|
969
|
+
writeQueueMessage(socket, {
|
|
970
|
+
...makeQueueOwnerError(request.requestId, "Queue owner is shutting down", "QUEUE_OWNER_CLOSED", { retryable: true }),
|
|
971
|
+
ownerGeneration: this.ownerGeneration
|
|
972
|
+
});
|
|
973
|
+
this.endSocket(socket);
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
934
976
|
handleControlQueueRequest(socket, request) {
|
|
935
977
|
if (request.type === "cancel_prompt") {
|
|
936
978
|
this.handleControlRequest({
|
|
@@ -1005,6 +1047,7 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
1005
1047
|
return false;
|
|
1006
1048
|
}
|
|
1007
1049
|
enqueuePromptRequest(socket, request) {
|
|
1050
|
+
this.taskSockets.add(socket);
|
|
1008
1051
|
const task = {
|
|
1009
1052
|
requestId: request.requestId,
|
|
1010
1053
|
message: request.message,
|
|
@@ -1026,7 +1069,7 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
1026
1069
|
});
|
|
1027
1070
|
},
|
|
1028
1071
|
close: () => {
|
|
1029
|
-
|
|
1072
|
+
this.endSocket(socket);
|
|
1030
1073
|
}
|
|
1031
1074
|
};
|
|
1032
1075
|
if (!this.enqueue(task)) return;
|
|
@@ -1038,19 +1081,20 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
1038
1081
|
if (!request.waitForCompletion) task.close();
|
|
1039
1082
|
}
|
|
1040
1083
|
handleConnection(socket) {
|
|
1084
|
+
this.sockets.add(socket);
|
|
1041
1085
|
socket.setEncoding("utf8");
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1086
|
+
socket.once("close", () => {
|
|
1087
|
+
this.sockets.delete(socket);
|
|
1088
|
+
this.taskSockets.delete(socket);
|
|
1089
|
+
this.drainingSockets.delete(socket);
|
|
1090
|
+
});
|
|
1047
1091
|
let buffer = "";
|
|
1048
1092
|
let handled = false;
|
|
1049
1093
|
const processLine = (line) => {
|
|
1050
1094
|
if (handled) return;
|
|
1051
1095
|
handled = true;
|
|
1052
1096
|
const request = this.parseRequestLine(socket, line);
|
|
1053
|
-
if (!request || this.rejectStaleOwnerGeneration(socket, request)) return;
|
|
1097
|
+
if (!request || this.rejectClosedOwner(socket, request) || this.rejectStaleOwnerGeneration(socket, request)) return;
|
|
1054
1098
|
if (this.handleControlQueueRequest(socket, request)) return;
|
|
1055
1099
|
if (request.type !== "submit_prompt") {
|
|
1056
1100
|
this.failRequest(socket, request.requestId, "Invalid queue request", "QUEUE_REQUEST_INVALID");
|
|
@@ -1112,7 +1156,8 @@ function makeMalformedQueueMessageError() {
|
|
|
1112
1156
|
function emitQueueOwnerError(formatter, policy, sessionId, message) {
|
|
1113
1157
|
formatter.setContext({ sessionId });
|
|
1114
1158
|
const queueErrorAlreadyEmitted = policy?.queueErrorAlreadyEmitted ?? true;
|
|
1115
|
-
|
|
1159
|
+
const shouldEmitInFormatter = message.outputAlreadyEmitted !== true || !queueErrorAlreadyEmitted;
|
|
1160
|
+
if (shouldEmitInFormatter) {
|
|
1116
1161
|
formatter.onError({
|
|
1117
1162
|
code: message.code ?? "RUNTIME",
|
|
1118
1163
|
detailCode: message.detailCode,
|
|
@@ -1123,7 +1168,7 @@ function emitQueueOwnerError(formatter, policy, sessionId, message) {
|
|
|
1123
1168
|
});
|
|
1124
1169
|
formatter.flush();
|
|
1125
1170
|
}
|
|
1126
|
-
return queueConnectionErrorFromOwner(message, queueErrorAlreadyEmitted);
|
|
1171
|
+
return queueConnectionErrorFromOwner(message, queueErrorAlreadyEmitted || shouldEmitInFormatter);
|
|
1127
1172
|
}
|
|
1128
1173
|
function parseQueueOwnerResponseLine(owner, requestId, line) {
|
|
1129
1174
|
let parsed;
|
|
@@ -1273,6 +1318,7 @@ async function submitToQueueOwner(owner, options) {
|
|
|
1273
1318
|
owner,
|
|
1274
1319
|
request,
|
|
1275
1320
|
onAccepted: ({ resolve }) => {
|
|
1321
|
+
options.onQueueAccepted?.();
|
|
1276
1322
|
options.outputFormatter.setContext({ sessionId: options.sessionId });
|
|
1277
1323
|
if (!options.waitForCompletion) resolve({
|
|
1278
1324
|
queued: true,
|
|
@@ -2274,7 +2320,9 @@ function writeQueueOwnerPayloadFile(payload) {
|
|
|
2274
2320
|
});
|
|
2275
2321
|
return payloadPath;
|
|
2276
2322
|
}
|
|
2277
|
-
|
|
2323
|
+
/** Max stderr bytes retained for cold-start diagnostics. */
|
|
2324
|
+
const QUEUE_OWNER_STARTUP_STDERR_MAX_BYTES = 4e3;
|
|
2325
|
+
function buildQueueOwnerSpawnOptions(payloadFilePath, opts) {
|
|
2278
2326
|
const env = {
|
|
2279
2327
|
...process.env,
|
|
2280
2328
|
[QUEUE_OWNER_PAYLOAD_FILE_ENV]: payloadFilePath
|
|
@@ -2282,14 +2330,77 @@ function buildQueueOwnerSpawnOptions(payloadFilePath) {
|
|
|
2282
2330
|
delete env[QUEUE_OWNER_PAYLOAD_ENV];
|
|
2283
2331
|
return {
|
|
2284
2332
|
detached: true,
|
|
2285
|
-
stdio:
|
|
2333
|
+
stdio: opts?.captureStderr === true ? [
|
|
2334
|
+
"ignore",
|
|
2335
|
+
"ignore",
|
|
2336
|
+
"pipe"
|
|
2337
|
+
] : "ignore",
|
|
2286
2338
|
env,
|
|
2287
2339
|
windowsHide: true
|
|
2288
2340
|
};
|
|
2289
2341
|
}
|
|
2342
|
+
function formatQueueOwnerStartupFailure(params) {
|
|
2343
|
+
const parts = [`Session queue owner failed to start for session ${params.sessionId}`];
|
|
2344
|
+
if (params.exit.spawnError) parts.push(`spawn error: ${params.exit.spawnError.message}`);
|
|
2345
|
+
else if (params.exit.exited) {
|
|
2346
|
+
const codePart = params.exit.code === null ? "null" : String(params.exit.code);
|
|
2347
|
+
const signalPart = params.exit.signal ? `, signal ${params.exit.signal}` : "";
|
|
2348
|
+
parts.push(`exited with code ${codePart}${signalPart} before binding its socket`);
|
|
2349
|
+
}
|
|
2350
|
+
const tail = params.logTail.trim();
|
|
2351
|
+
if (tail.length > 0) parts.push(`stderr:\n${tail}`);
|
|
2352
|
+
return parts.join(": ");
|
|
2353
|
+
}
|
|
2290
2354
|
function spawnQueueOwnerProcess(options) {
|
|
2291
2355
|
const payloadPath = writeQueueOwnerPayloadFile(JSON.stringify(options));
|
|
2292
|
-
|
|
2356
|
+
let exited = false;
|
|
2357
|
+
let code = null;
|
|
2358
|
+
let signal = null;
|
|
2359
|
+
let spawnError;
|
|
2360
|
+
let capturing = true;
|
|
2361
|
+
let stderrTail = Buffer.alloc(0);
|
|
2362
|
+
const child = spawn(process.execPath, resolveQueueOwnerSpawnArgs(), buildQueueOwnerSpawnOptions(payloadPath, { captureStderr: true }));
|
|
2363
|
+
const stopStartupCapture = () => {
|
|
2364
|
+
if (!capturing) return;
|
|
2365
|
+
capturing = false;
|
|
2366
|
+
const stderr = child.stderr;
|
|
2367
|
+
if (!stderr) return;
|
|
2368
|
+
stderr.removeAllListeners("data");
|
|
2369
|
+
stderr.on("data", () => {});
|
|
2370
|
+
stderr.resume();
|
|
2371
|
+
stderr.unref();
|
|
2372
|
+
};
|
|
2373
|
+
child.stderr?.on("data", (chunk) => {
|
|
2374
|
+
if (!capturing) return;
|
|
2375
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
2376
|
+
const combined = Buffer.concat([stderrTail, buf]);
|
|
2377
|
+
stderrTail = combined.length > 4e3 ? combined.subarray(combined.length - QUEUE_OWNER_STARTUP_STDERR_MAX_BYTES) : combined;
|
|
2378
|
+
});
|
|
2379
|
+
child.stderr?.on("error", () => {});
|
|
2380
|
+
child.on("error", (error) => {
|
|
2381
|
+
spawnError = error;
|
|
2382
|
+
});
|
|
2383
|
+
child.on("close", (exitCode, exitSignal) => {
|
|
2384
|
+
exited = true;
|
|
2385
|
+
code = exitCode;
|
|
2386
|
+
signal = exitSignal;
|
|
2387
|
+
stopStartupCapture();
|
|
2388
|
+
});
|
|
2389
|
+
child.unref();
|
|
2390
|
+
return {
|
|
2391
|
+
pid: child.pid,
|
|
2392
|
+
getExitState: () => ({
|
|
2393
|
+
exited,
|
|
2394
|
+
code,
|
|
2395
|
+
signal,
|
|
2396
|
+
...spawnError ? { spawnError } : {}
|
|
2397
|
+
}),
|
|
2398
|
+
readLogTail: (maxBytes = QUEUE_OWNER_STARTUP_STDERR_MAX_BYTES) => {
|
|
2399
|
+
const start = Math.max(0, stderrTail.length - maxBytes);
|
|
2400
|
+
return stderrTail.subarray(start).toString("utf8");
|
|
2401
|
+
},
|
|
2402
|
+
stopStartupCapture
|
|
2403
|
+
};
|
|
2293
2404
|
}
|
|
2294
2405
|
//#endregion
|
|
2295
2406
|
//#region src/session/events.ts
|
|
@@ -2527,6 +2638,51 @@ const DISCARD_OUTPUT_FORMATTER = {
|
|
|
2527
2638
|
onPermissionEscalation() {},
|
|
2528
2639
|
flush() {}
|
|
2529
2640
|
};
|
|
2641
|
+
function markOutputAlreadyEmitted(error, outputAlreadyEmitted) {
|
|
2642
|
+
if (!outputAlreadyEmitted || !error || typeof error !== "object") return;
|
|
2643
|
+
error.outputAlreadyEmitted = true;
|
|
2644
|
+
}
|
|
2645
|
+
async function runWithOptionalInterrupt(options) {
|
|
2646
|
+
if (options.handleProcessInterrupts === false) return await options.run();
|
|
2647
|
+
return await withInterrupt(options.run, options.handleInterrupt);
|
|
2648
|
+
}
|
|
2649
|
+
function attachAcpErrorPayload(error, acp) {
|
|
2650
|
+
if (!acp || !error || typeof error !== "object") return;
|
|
2651
|
+
error.acp = acp;
|
|
2652
|
+
}
|
|
2653
|
+
function rendersAcpErrors(policy) {
|
|
2654
|
+
return policy?.queueErrorAlreadyEmitted ?? true;
|
|
2655
|
+
}
|
|
2656
|
+
function normalizedErrorText(value) {
|
|
2657
|
+
return value.trim().toLowerCase();
|
|
2658
|
+
}
|
|
2659
|
+
function outboundAcpErrorMatches(error, acp) {
|
|
2660
|
+
const errorText = normalizedErrorText(formatErrorMessage(error));
|
|
2661
|
+
const details = acp.data?.details;
|
|
2662
|
+
const candidateText = normalizedErrorText(typeof details === "string" && details.trim().length > 0 ? details : acp.message);
|
|
2663
|
+
return errorText === candidateText || errorText.includes(candidateText);
|
|
2664
|
+
}
|
|
2665
|
+
var AcpErrorTracker = class {
|
|
2666
|
+
latestInbound;
|
|
2667
|
+
outbound = [];
|
|
2668
|
+
reset() {
|
|
2669
|
+
this.latestInbound = void 0;
|
|
2670
|
+
this.outbound.length = 0;
|
|
2671
|
+
}
|
|
2672
|
+
observe(output, direction, message) {
|
|
2673
|
+
output.onAcpMessage(message);
|
|
2674
|
+
const acp = extractAcpError(message);
|
|
2675
|
+
if (!acp) return;
|
|
2676
|
+
if (direction === "inbound") {
|
|
2677
|
+
this.latestInbound = acp;
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
this.outbound.push(acp);
|
|
2681
|
+
}
|
|
2682
|
+
match(error) {
|
|
2683
|
+
return this.latestInbound ?? this.outbound.findLast((acp) => outboundAcpErrorMatches(error, acp));
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2530
2686
|
function toPromptResult(stopReason, sessionId, client) {
|
|
2531
2687
|
return {
|
|
2532
2688
|
stopReason,
|
|
@@ -2636,6 +2792,11 @@ function filterRecoverableLoadFallbackOutput(messages) {
|
|
|
2636
2792
|
return true;
|
|
2637
2793
|
});
|
|
2638
2794
|
}
|
|
2795
|
+
function filterBufferedConnectOutput(messages, loadError) {
|
|
2796
|
+
if (loadError == null) return messages;
|
|
2797
|
+
const filteredMessages = new Set(filterRecoverableLoadFallbackOutput(messages.map(({ message }) => message)));
|
|
2798
|
+
return messages.filter(({ message }) => filteredMessages.has(message));
|
|
2799
|
+
}
|
|
2639
2800
|
function emitPromptRetryNotice(params) {
|
|
2640
2801
|
if (params.suppressSdkConsoleErrors) return;
|
|
2641
2802
|
process.stderr.write(`[acpx] prompt failed (${formatErrorMessage(params.error)}), retrying in ${params.delayMs}ms (attempt ${params.attempt}/${params.maxRetries})\n`);
|
|
@@ -2687,6 +2848,7 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
|
|
|
2687
2848
|
authCredentials: options.authCredentials,
|
|
2688
2849
|
authPolicy: options.authPolicy,
|
|
2689
2850
|
outputFormatter,
|
|
2851
|
+
errorEmissionPolicy: { queueErrorAlreadyEmitted: true },
|
|
2690
2852
|
timeoutMs: task.timeoutMs,
|
|
2691
2853
|
suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
|
|
2692
2854
|
verbose: options.verbose,
|
|
@@ -2695,6 +2857,7 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
|
|
|
2695
2857
|
onClientAvailable: options.onClientAvailable,
|
|
2696
2858
|
onClientClosed: options.onClientClosed,
|
|
2697
2859
|
onPromptActive: options.onPromptActive,
|
|
2860
|
+
handleProcessInterrupts: options.handleProcessInterrupts,
|
|
2698
2861
|
client: options.sharedClient
|
|
2699
2862
|
};
|
|
2700
2863
|
}
|
|
@@ -2739,6 +2902,7 @@ async function runQueuedTask(sessionRecordId, task, options) {
|
|
|
2739
2902
|
async function runSessionPrompt(options) {
|
|
2740
2903
|
const stopTotalTimer = startPerfTimer("runtime.prompt.total");
|
|
2741
2904
|
const output = options.outputFormatter;
|
|
2905
|
+
const shouldMarkAcpErrorsEmitted = rendersAcpErrors(options.errorEmissionPolicy);
|
|
2742
2906
|
const record = await measurePerf("session.resolve_prompt_record", async () => {
|
|
2743
2907
|
return await resolveSessionRecord(options.sessionRecordId);
|
|
2744
2908
|
});
|
|
@@ -2761,7 +2925,7 @@ async function runSessionPrompt(options) {
|
|
|
2761
2925
|
let bufferingConnectOutput = true;
|
|
2762
2926
|
let promptTurnActive = false;
|
|
2763
2927
|
let promptTurnHadSideEffects = false;
|
|
2764
|
-
|
|
2928
|
+
const acpErrors = new AcpErrorTracker();
|
|
2765
2929
|
let eventWriterClosed = false;
|
|
2766
2930
|
const closeEventWriter = async (checkpoint) => {
|
|
2767
2931
|
if (eventWriterClosed) return;
|
|
@@ -2824,16 +2988,18 @@ async function runSessionPrompt(options) {
|
|
|
2824
2988
|
});
|
|
2825
2989
|
client.setEventHandlers({
|
|
2826
2990
|
onAcpMessage: (direction, message) => {
|
|
2827
|
-
sawAcpMessage = true;
|
|
2828
2991
|
pendingMessages.push(message);
|
|
2829
2992
|
options.onAcpMessage?.(direction, message);
|
|
2830
2993
|
},
|
|
2831
|
-
onAcpOutputMessage: (
|
|
2994
|
+
onAcpOutputMessage: (direction, message) => {
|
|
2832
2995
|
if (bufferingConnectOutput) {
|
|
2833
|
-
pendingConnectOutputMessages.push(
|
|
2996
|
+
pendingConnectOutputMessages.push({
|
|
2997
|
+
direction,
|
|
2998
|
+
message
|
|
2999
|
+
});
|
|
2834
3000
|
return;
|
|
2835
3001
|
}
|
|
2836
|
-
|
|
3002
|
+
acpErrors.observe(output, direction, message);
|
|
2837
3003
|
},
|
|
2838
3004
|
onSessionUpdate: (notification) => {
|
|
2839
3005
|
if (promptTurnActive) promptTurnHadSideEffects = true;
|
|
@@ -2898,8 +3064,8 @@ async function runSessionPrompt(options) {
|
|
|
2898
3064
|
};
|
|
2899
3065
|
const flushConnectOutput = (loadError) => {
|
|
2900
3066
|
bufferingConnectOutput = false;
|
|
2901
|
-
const
|
|
2902
|
-
for (const message of
|
|
3067
|
+
const outputMessages = filterBufferedConnectOutput(pendingConnectOutputMessages, loadError);
|
|
3068
|
+
for (const { direction, message } of outputMessages) acpErrors.observe(output, direction, message);
|
|
2903
3069
|
pendingConnectOutputMessages.length = 0;
|
|
2904
3070
|
};
|
|
2905
3071
|
const connectForPrompt = async () => {
|
|
@@ -2946,6 +3112,7 @@ async function runSessionPrompt(options) {
|
|
|
2946
3112
|
};
|
|
2947
3113
|
};
|
|
2948
3114
|
const runPromptAttempt = async (sessionId, attempt) => {
|
|
3115
|
+
acpErrors.reset();
|
|
2949
3116
|
const promptStartedAt = Date.now();
|
|
2950
3117
|
const response = await measurePerf("runtime.prompt.agent_turn", async () => {
|
|
2951
3118
|
return await runPromptTurn({
|
|
@@ -2973,14 +3140,19 @@ async function runSessionPrompt(options) {
|
|
|
2973
3140
|
promptTurnActive = false;
|
|
2974
3141
|
applyLifecycleSnapshotToRecord(record, snapshot);
|
|
2975
3142
|
emitPromptDisconnectNotice(snapshot, options.verbose);
|
|
2976
|
-
const
|
|
3143
|
+
const matchedAcpError = acpErrors.match(error);
|
|
3144
|
+
const normalizedError = normalizeOutputError(error, {
|
|
3145
|
+
origin: "runtime",
|
|
3146
|
+
acp: matchedAcpError
|
|
3147
|
+
});
|
|
2977
3148
|
await flushPendingMessages(false).catch(() => {});
|
|
2978
3149
|
output.flush();
|
|
2979
3150
|
record.lastUsedAt = isoNow();
|
|
2980
3151
|
applyConversation(record, conversation);
|
|
2981
3152
|
record.acpx = acpxState;
|
|
2982
3153
|
const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
|
|
2983
|
-
propagated.
|
|
3154
|
+
attachAcpErrorPayload(propagated, normalizedError.acp);
|
|
3155
|
+
propagated.outputAlreadyEmitted = matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted;
|
|
2984
3156
|
propagated.normalizedOutputError = normalizedError;
|
|
2985
3157
|
throw propagated;
|
|
2986
3158
|
};
|
|
@@ -2995,7 +3167,8 @@ async function runSessionPrompt(options) {
|
|
|
2995
3167
|
const savePromptSuccess = async (response) => {
|
|
2996
3168
|
await flushPendingMessages(false);
|
|
2997
3169
|
output.flush();
|
|
2998
|
-
|
|
3170
|
+
const now = isoNow();
|
|
3171
|
+
record.lastUsedAt = now;
|
|
2999
3172
|
record.closed = false;
|
|
3000
3173
|
record.closedAt = void 0;
|
|
3001
3174
|
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
@@ -3006,37 +3179,48 @@ async function runSessionPrompt(options) {
|
|
|
3006
3179
|
stopTotalTimer();
|
|
3007
3180
|
return response;
|
|
3008
3181
|
};
|
|
3182
|
+
const runPrompt = async () => {
|
|
3183
|
+
const { sessionId: activeSessionId, resumed, loadError } = await connectForPrompt();
|
|
3184
|
+
await applyPromptModelIfAdvertised({
|
|
3185
|
+
client,
|
|
3186
|
+
sessionId: activeSessionId,
|
|
3187
|
+
requestedModel: sessionOptions?.model,
|
|
3188
|
+
record,
|
|
3189
|
+
timeoutMs: options.timeoutMs,
|
|
3190
|
+
suppressWarnings: options.suppressSdkConsoleErrors
|
|
3191
|
+
});
|
|
3192
|
+
acpxState = cloneSessionAcpxState(record.acpx);
|
|
3193
|
+
output.setContext({ sessionId: record.acpxRecordId });
|
|
3194
|
+
await liveCheckpoint.checkpoint();
|
|
3195
|
+
const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
|
|
3196
|
+
promptTurnActive = false;
|
|
3197
|
+
return {
|
|
3198
|
+
...toPromptResult(response.stopReason, record.acpxRecordId, client),
|
|
3199
|
+
record,
|
|
3200
|
+
resumed,
|
|
3201
|
+
loadError
|
|
3202
|
+
};
|
|
3203
|
+
};
|
|
3204
|
+
const handleInterrupt = async () => {
|
|
3205
|
+
await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
|
|
3206
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
3207
|
+
record.lastUsedAt = isoNow();
|
|
3208
|
+
applyConversation(record, conversation);
|
|
3209
|
+
record.acpx = acpxState;
|
|
3210
|
+
await flushPendingMessages(false).catch(() => {});
|
|
3211
|
+
if (ownClient) await client.close();
|
|
3212
|
+
};
|
|
3009
3213
|
try {
|
|
3010
|
-
return await
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
sessionId: activeSessionId,
|
|
3015
|
-
requestedModel: sessionOptions?.model,
|
|
3016
|
-
record,
|
|
3017
|
-
timeoutMs: options.timeoutMs,
|
|
3018
|
-
suppressWarnings: options.suppressSdkConsoleErrors
|
|
3019
|
-
});
|
|
3020
|
-
acpxState = cloneSessionAcpxState(record.acpx);
|
|
3021
|
-
output.setContext({ sessionId: record.acpxRecordId });
|
|
3022
|
-
await liveCheckpoint.checkpoint();
|
|
3023
|
-
const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
|
|
3024
|
-
promptTurnActive = false;
|
|
3025
|
-
return {
|
|
3026
|
-
...toPromptResult(response.stopReason, record.acpxRecordId, client),
|
|
3027
|
-
record,
|
|
3028
|
-
resumed,
|
|
3029
|
-
loadError
|
|
3030
|
-
};
|
|
3031
|
-
}, async () => {
|
|
3032
|
-
await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
|
|
3033
|
-
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
3034
|
-
record.lastUsedAt = isoNow();
|
|
3035
|
-
applyConversation(record, conversation);
|
|
3036
|
-
record.acpx = acpxState;
|
|
3037
|
-
await flushPendingMessages(false).catch(() => {});
|
|
3038
|
-
if (ownClient) await client.close();
|
|
3214
|
+
return await runWithOptionalInterrupt({
|
|
3215
|
+
handleProcessInterrupts: options.handleProcessInterrupts,
|
|
3216
|
+
run: runPrompt,
|
|
3217
|
+
handleInterrupt
|
|
3039
3218
|
});
|
|
3219
|
+
} catch (error) {
|
|
3220
|
+
const matchedAcpError = acpErrors.match(error);
|
|
3221
|
+
attachAcpErrorPayload(error, matchedAcpError);
|
|
3222
|
+
markOutputAlreadyEmitted(error, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
|
|
3223
|
+
throw error;
|
|
3040
3224
|
} finally {
|
|
3041
3225
|
if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.total", stopTotalTimer())}\n`);
|
|
3042
3226
|
else stopTotalTimer();
|
|
@@ -3054,8 +3238,10 @@ async function runSessionPrompt(options) {
|
|
|
3054
3238
|
}
|
|
3055
3239
|
async function runOnce(options) {
|
|
3056
3240
|
const output = options.outputFormatter;
|
|
3241
|
+
const shouldMarkAcpErrorsEmitted = rendersAcpErrors(options.errorEmissionPolicy);
|
|
3057
3242
|
let promptTurnActive = false;
|
|
3058
3243
|
let promptTurnHadSideEffects = false;
|
|
3244
|
+
const acpErrors = new AcpErrorTracker();
|
|
3059
3245
|
const client = new AcpClient({
|
|
3060
3246
|
agentCommand: options.agentCommand,
|
|
3061
3247
|
cwd: absolutePath(options.cwd),
|
|
@@ -3069,7 +3255,9 @@ async function runOnce(options) {
|
|
|
3069
3255
|
suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
|
|
3070
3256
|
verbose: options.verbose,
|
|
3071
3257
|
onAcpMessage: options.onAcpMessage,
|
|
3072
|
-
onAcpOutputMessage: (
|
|
3258
|
+
onAcpOutputMessage: (direction, message) => {
|
|
3259
|
+
acpErrors.observe(output, direction, message);
|
|
3260
|
+
},
|
|
3073
3261
|
onSessionUpdate: (notification) => {
|
|
3074
3262
|
if (promptTurnActive) promptTurnHadSideEffects = true;
|
|
3075
3263
|
options.onSessionUpdate?.(notification);
|
|
@@ -3085,6 +3273,7 @@ async function runOnce(options) {
|
|
|
3085
3273
|
sessionOptions: options.sessionOptions
|
|
3086
3274
|
});
|
|
3087
3275
|
const runExecPromptAttempt = async (sessionId) => {
|
|
3276
|
+
acpErrors.reset();
|
|
3088
3277
|
return await measurePerf("runtime.exec.prompt", async () => {
|
|
3089
3278
|
return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
|
|
3090
3279
|
});
|
|
@@ -3130,6 +3319,11 @@ async function runOnce(options) {
|
|
|
3130
3319
|
await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
|
|
3131
3320
|
await client.close();
|
|
3132
3321
|
});
|
|
3322
|
+
} catch (error) {
|
|
3323
|
+
const matchedAcpError = acpErrors.match(error);
|
|
3324
|
+
attachAcpErrorPayload(error, matchedAcpError);
|
|
3325
|
+
markOutputAlreadyEmitted(error, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
|
|
3326
|
+
throw error;
|
|
3133
3327
|
} finally {
|
|
3134
3328
|
await client.close();
|
|
3135
3329
|
}
|
|
@@ -3147,6 +3341,7 @@ async function sendSessionDirect(options) {
|
|
|
3147
3341
|
authPolicy: options.authPolicy,
|
|
3148
3342
|
terminal: options.terminal,
|
|
3149
3343
|
outputFormatter: options.outputFormatter,
|
|
3344
|
+
errorEmissionPolicy: options.errorEmissionPolicy,
|
|
3150
3345
|
onAcpMessage: options.onAcpMessage,
|
|
3151
3346
|
onSessionUpdate: options.onSessionUpdate,
|
|
3152
3347
|
onClientOperation: options.onClientOperation,
|
|
@@ -3161,7 +3356,8 @@ async function sendSessionDirect(options) {
|
|
|
3161
3356
|
//#region src/cli/session/queue-owner-runtime.ts
|
|
3162
3357
|
const QUEUE_OWNER_STARTUP_MAX_ATTEMPTS = 120;
|
|
3163
3358
|
const QUEUE_OWNER_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
3164
|
-
|
|
3359
|
+
const QUEUE_OWNER_ACTIVE_TURN_CANCEL_GRACE_MS = 750;
|
|
3360
|
+
async function submitToRunningOwner(options, waitForCompletion, extras) {
|
|
3165
3361
|
return await trySubmitToRunningOwner({
|
|
3166
3362
|
sessionId: options.sessionId,
|
|
3167
3363
|
message: promptToDisplayText(options.prompt),
|
|
@@ -3178,7 +3374,8 @@ async function submitToRunningOwner(options, waitForCompletion) {
|
|
|
3178
3374
|
promptRetries: options.promptRetries,
|
|
3179
3375
|
waitForCompletion,
|
|
3180
3376
|
verbose: options.verbose,
|
|
3181
|
-
sessionOptions: options.sessionOptions
|
|
3377
|
+
sessionOptions: options.sessionOptions,
|
|
3378
|
+
onQueueAccepted: extras?.onQueueAccepted
|
|
3182
3379
|
});
|
|
3183
3380
|
}
|
|
3184
3381
|
function createQueueOwnerSharedClient(options, sessionRecord) {
|
|
@@ -3245,6 +3442,9 @@ function logDeferredCancelFailure(error, verbose) {
|
|
|
3245
3442
|
if (!verbose) return;
|
|
3246
3443
|
process.stderr.write(`[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}\n`);
|
|
3247
3444
|
}
|
|
3445
|
+
function queueOwnerExitIsFatal(exit) {
|
|
3446
|
+
return exit.exited && (exit.spawnError !== void 0 || exit.code !== 0 || exit.signal !== null);
|
|
3447
|
+
}
|
|
3248
3448
|
function logQueueOwnerReady(params) {
|
|
3249
3449
|
if (!params.verbose) return;
|
|
3250
3450
|
process.stderr.write(`[acpx] queue owner ready for session ${params.sessionId} (ttlMs=${params.ttlMs}, maxQueueDepth=${params.maxQueueDepth})\n`);
|
|
@@ -3252,8 +3452,8 @@ function logQueueOwnerReady(params) {
|
|
|
3252
3452
|
async function closeQueueOwnerRuntime(params) {
|
|
3253
3453
|
if (params.heartbeatTimer) clearInterval(params.heartbeatTimer);
|
|
3254
3454
|
params.turnController.beginClosing();
|
|
3255
|
-
await params.owner?.close();
|
|
3256
3455
|
await params.sharedClient.close().catch(() => {});
|
|
3456
|
+
await params.owner?.close();
|
|
3257
3457
|
await writeQueueOwnerLifecycleSnapshot(params.sessionId, params.sharedClient);
|
|
3258
3458
|
await releaseQueueOwnerLease(params.lease);
|
|
3259
3459
|
if (params.verbose) process.stderr.write(`[acpx] queue owner stopped for session ${params.sessionId}\n`);
|
|
@@ -3265,6 +3465,75 @@ async function writeQueueOwnerLifecycleSnapshot(sessionId, sharedClient) {
|
|
|
3265
3465
|
await writeSessionRecord(record);
|
|
3266
3466
|
} catch {}
|
|
3267
3467
|
}
|
|
3468
|
+
async function settlesWithin(promise, timeoutMs) {
|
|
3469
|
+
let timer;
|
|
3470
|
+
const timeout = new Promise((resolve) => {
|
|
3471
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
3472
|
+
});
|
|
3473
|
+
try {
|
|
3474
|
+
return await Promise.race([promise.then(() => true, () => true), timeout]);
|
|
3475
|
+
} finally {
|
|
3476
|
+
if (timer) clearTimeout(timer);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
function createQueueOwnerShutdownController(params) {
|
|
3480
|
+
let requested = false;
|
|
3481
|
+
let activeTurn;
|
|
3482
|
+
let activeTurnShutdown;
|
|
3483
|
+
let shutdownPromise;
|
|
3484
|
+
const drainActiveTurn = async () => {
|
|
3485
|
+
const turn = activeTurn;
|
|
3486
|
+
if (!turn) return;
|
|
3487
|
+
params.turnController.requestCancel().catch((error) => {
|
|
3488
|
+
logDeferredCancelFailure(error, params.verbose);
|
|
3489
|
+
});
|
|
3490
|
+
if (!await settlesWithin(turn, QUEUE_OWNER_ACTIVE_TURN_CANCEL_GRACE_MS)) await params.sharedClient.close().catch(() => {});
|
|
3491
|
+
await turn.catch(() => {});
|
|
3492
|
+
};
|
|
3493
|
+
const request = () => {
|
|
3494
|
+
requested = true;
|
|
3495
|
+
params.stopHeartbeat();
|
|
3496
|
+
params.getOwner()?.beginShutdown();
|
|
3497
|
+
activeTurnShutdown ??= drainActiveTurn();
|
|
3498
|
+
};
|
|
3499
|
+
return {
|
|
3500
|
+
get requested() {
|
|
3501
|
+
return requested;
|
|
3502
|
+
},
|
|
3503
|
+
request,
|
|
3504
|
+
setActiveTurn: (turn) => {
|
|
3505
|
+
activeTurn = turn;
|
|
3506
|
+
},
|
|
3507
|
+
clearActiveTurn: (turn) => {
|
|
3508
|
+
if (activeTurn === turn) activeTurn = void 0;
|
|
3509
|
+
},
|
|
3510
|
+
shutdown: () => {
|
|
3511
|
+
request();
|
|
3512
|
+
shutdownPromise ??= (async () => {
|
|
3513
|
+
await activeTurnShutdown;
|
|
3514
|
+
await closeQueueOwnerRuntime({
|
|
3515
|
+
lease: params.lease,
|
|
3516
|
+
owner: params.getOwner(),
|
|
3517
|
+
heartbeatTimer: void 0,
|
|
3518
|
+
turnController: params.turnController,
|
|
3519
|
+
sharedClient: params.sharedClient,
|
|
3520
|
+
sessionId: params.sessionId,
|
|
3521
|
+
verbose: params.verbose
|
|
3522
|
+
});
|
|
3523
|
+
})();
|
|
3524
|
+
return shutdownPromise;
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
function applyRequestedShutdown(owner, shutdown) {
|
|
3529
|
+
if (shutdown.requested) owner.beginShutdown();
|
|
3530
|
+
}
|
|
3531
|
+
function startQueueOwnerHeartbeat(params) {
|
|
3532
|
+
if (!params.enabled) return;
|
|
3533
|
+
return setInterval(() => {
|
|
3534
|
+
refreshQueueOwnerLease(params.lease, { queueDepth: params.owner.queueDepth() }).catch(() => {});
|
|
3535
|
+
}, QUEUE_OWNER_HEARTBEAT_INTERVAL_MS);
|
|
3536
|
+
}
|
|
3268
3537
|
async function runSessionQueueOwner(options) {
|
|
3269
3538
|
const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
|
|
3270
3539
|
path: options.mcpConfigPath,
|
|
@@ -3309,6 +3578,26 @@ async function runSessionQueueOwner(options) {
|
|
|
3309
3578
|
turnController.endTurn();
|
|
3310
3579
|
}
|
|
3311
3580
|
};
|
|
3581
|
+
const shutdown = createQueueOwnerShutdownController({
|
|
3582
|
+
lease,
|
|
3583
|
+
getOwner: () => owner,
|
|
3584
|
+
stopHeartbeat: () => {
|
|
3585
|
+
if (heartbeatTimer) {
|
|
3586
|
+
clearInterval(heartbeatTimer);
|
|
3587
|
+
heartbeatTimer = void 0;
|
|
3588
|
+
}
|
|
3589
|
+
},
|
|
3590
|
+
turnController,
|
|
3591
|
+
sharedClient,
|
|
3592
|
+
sessionId: options.sessionId,
|
|
3593
|
+
verbose: options.verbose
|
|
3594
|
+
});
|
|
3595
|
+
const onSignal = () => {
|
|
3596
|
+
shutdown.request();
|
|
3597
|
+
};
|
|
3598
|
+
process.once("SIGINT", onSignal);
|
|
3599
|
+
process.once("SIGTERM", onSignal);
|
|
3600
|
+
process.once("SIGHUP", onSignal);
|
|
3312
3601
|
try {
|
|
3313
3602
|
owner = await SessionQueueOwner.start(lease, {
|
|
3314
3603
|
cancelPrompt: async () => {
|
|
@@ -3331,6 +3620,7 @@ async function runSessionQueueOwner(options) {
|
|
|
3331
3620
|
refreshQueueOwnerLease(lease, { queueDepth }).catch(() => {});
|
|
3332
3621
|
}
|
|
3333
3622
|
});
|
|
3623
|
+
applyRequestedShutdown(owner, shutdown);
|
|
3334
3624
|
logQueueOwnerReady({
|
|
3335
3625
|
sessionId: options.sessionId,
|
|
3336
3626
|
ttlMs,
|
|
@@ -3338,16 +3628,18 @@ async function runSessionQueueOwner(options) {
|
|
|
3338
3628
|
verbose: options.verbose
|
|
3339
3629
|
});
|
|
3340
3630
|
await refreshQueueOwnerLease(lease, { queueDepth: owner.queueDepth() }).catch(() => {});
|
|
3341
|
-
heartbeatTimer =
|
|
3342
|
-
|
|
3343
|
-
|
|
3631
|
+
heartbeatTimer = startQueueOwnerHeartbeat({
|
|
3632
|
+
enabled: !shutdown.requested,
|
|
3633
|
+
lease,
|
|
3634
|
+
owner
|
|
3635
|
+
});
|
|
3344
3636
|
let isFirstTask = true;
|
|
3345
3637
|
while (true) {
|
|
3346
3638
|
const pollTimeoutMs = isFirstTask ? initialTaskPollTimeoutMs : taskPollTimeoutMs;
|
|
3347
3639
|
const task = await owner.nextTask(pollTimeoutMs);
|
|
3348
3640
|
if (!task) break;
|
|
3349
3641
|
isFirstTask = false;
|
|
3350
|
-
|
|
3642
|
+
const turnPromise = runPromptTurn(async () => {
|
|
3351
3643
|
try {
|
|
3352
3644
|
await runQueuedTask(options.sessionId, task, {
|
|
3353
3645
|
sharedClient,
|
|
@@ -3364,37 +3656,67 @@ async function runSessionQueueOwner(options) {
|
|
|
3364
3656
|
onPromptActive: async () => {
|
|
3365
3657
|
turnController.markPromptActive();
|
|
3366
3658
|
await applyPendingCancel();
|
|
3367
|
-
}
|
|
3659
|
+
},
|
|
3660
|
+
handleProcessInterrupts: false
|
|
3368
3661
|
});
|
|
3369
3662
|
} finally {
|
|
3370
3663
|
checkpointPerfMetricsCapture();
|
|
3371
3664
|
}
|
|
3372
3665
|
});
|
|
3666
|
+
shutdown.setActiveTurn(turnPromise);
|
|
3667
|
+
try {
|
|
3668
|
+
await turnPromise;
|
|
3669
|
+
} finally {
|
|
3670
|
+
shutdown.clearActiveTurn(turnPromise);
|
|
3671
|
+
}
|
|
3373
3672
|
}
|
|
3374
3673
|
} finally {
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
turnController,
|
|
3380
|
-
sharedClient,
|
|
3381
|
-
sessionId: options.sessionId,
|
|
3382
|
-
verbose: options.verbose
|
|
3383
|
-
});
|
|
3674
|
+
process.off("SIGINT", onSignal);
|
|
3675
|
+
process.off("SIGTERM", onSignal);
|
|
3676
|
+
process.off("SIGHUP", onSignal);
|
|
3677
|
+
await shutdown.shutdown();
|
|
3384
3678
|
}
|
|
3385
3679
|
}
|
|
3386
3680
|
async function sendSession(options) {
|
|
3387
3681
|
const waitForCompletion = options.waitForCompletion !== false;
|
|
3388
3682
|
const queuedToOwner = await submitToRunningOwner(options, waitForCompletion);
|
|
3389
3683
|
if (queuedToOwner) return queuedToOwner;
|
|
3390
|
-
spawnQueueOwnerProcess(queueOwnerRuntimeOptionsFromSend(options));
|
|
3684
|
+
const owner = spawnQueueOwnerProcess(queueOwnerRuntimeOptionsFromSend(options));
|
|
3685
|
+
const onQueueAccepted = () => {
|
|
3686
|
+
owner.stopStartupCapture();
|
|
3687
|
+
};
|
|
3391
3688
|
for (let attempt = 0; attempt < QUEUE_OWNER_STARTUP_MAX_ATTEMPTS; attempt += 1) {
|
|
3392
|
-
const queued = await submitToRunningOwner(options, waitForCompletion);
|
|
3393
|
-
if (queued)
|
|
3689
|
+
const queued = await submitToRunningOwner(options, waitForCompletion, { onQueueAccepted });
|
|
3690
|
+
if (queued) {
|
|
3691
|
+
owner.stopStartupCapture();
|
|
3692
|
+
return queued;
|
|
3693
|
+
}
|
|
3694
|
+
const exit = owner.getExitState();
|
|
3695
|
+
if (queueOwnerExitIsFatal(exit)) {
|
|
3696
|
+
const message = formatQueueOwnerStartupFailure({
|
|
3697
|
+
sessionId: options.sessionId,
|
|
3698
|
+
exit,
|
|
3699
|
+
logTail: owner.readLogTail()
|
|
3700
|
+
});
|
|
3701
|
+
owner.stopStartupCapture();
|
|
3702
|
+
throw new Error(message);
|
|
3703
|
+
}
|
|
3394
3704
|
await waitMs(50);
|
|
3395
3705
|
}
|
|
3396
|
-
|
|
3706
|
+
const finalExit = owner.getExitState();
|
|
3707
|
+
const message = formatQueueOwnerStartupFailure({
|
|
3708
|
+
sessionId: options.sessionId,
|
|
3709
|
+
exit: finalExit.exited ? finalExit : {
|
|
3710
|
+
exited: false,
|
|
3711
|
+
code: null,
|
|
3712
|
+
signal: null
|
|
3713
|
+
},
|
|
3714
|
+
logTail: owner.readLogTail()
|
|
3715
|
+
});
|
|
3716
|
+
owner.stopStartupCapture();
|
|
3717
|
+
throw new Error(message);
|
|
3397
3718
|
}
|
|
3719
|
+
const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
|
|
3398
3720
|
//#endregion
|
|
3399
3721
|
//#region src/session/session.ts
|
|
3400
3722
|
var session_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -3417,6 +3739,7 @@ var session_exports = /* @__PURE__ */ __exportAll({
|
|
|
3417
3739
|
mergeConnectedModelState: () => mergeConnectedModelState,
|
|
3418
3740
|
normalizeQueueOwnerTtlMs: () => normalizeQueueOwnerTtlMs,
|
|
3419
3741
|
pruneSessions: () => pruneSessions,
|
|
3742
|
+
queueOwnerRuntimeTestInternals: () => queueOwnerRuntimeTestInternals,
|
|
3420
3743
|
runOnce: () => runOnce,
|
|
3421
3744
|
runQueuedTask: () => runQueuedTask,
|
|
3422
3745
|
runSessionQueueOwner: () => runSessionQueueOwner,
|
|
@@ -3695,6 +4018,12 @@ function asRecord(value) {
|
|
|
3695
4018
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
3696
4019
|
return value;
|
|
3697
4020
|
}
|
|
4021
|
+
function parseJsonRpcErrorSummary(message) {
|
|
4022
|
+
const fallback = parseJsonRpcErrorMessage(message);
|
|
4023
|
+
if (!fallback) return;
|
|
4024
|
+
const details = asRecord(asRecord(message.error)?.data)?.details;
|
|
4025
|
+
return typeof details === "string" && details.trim().length > 0 ? details.trim() : fallback;
|
|
4026
|
+
}
|
|
3698
4027
|
function extractJsonRpcMethod(message) {
|
|
3699
4028
|
return Object.hasOwn(message, "method") ? message.method?.toString() : void 0;
|
|
3700
4029
|
}
|
|
@@ -4060,7 +4389,7 @@ var TextOutputFormatter = class {
|
|
|
4060
4389
|
this.renderDone(stopReason);
|
|
4061
4390
|
return;
|
|
4062
4391
|
}
|
|
4063
|
-
const errorMessage =
|
|
4392
|
+
const errorMessage = parseJsonRpcErrorSummary(message);
|
|
4064
4393
|
if (errorMessage) this.onError({
|
|
4065
4394
|
code: "RUNTIME",
|
|
4066
4395
|
origin: "acp",
|
|
@@ -4286,7 +4615,11 @@ var QuietOutputFormatter = class {
|
|
|
4286
4615
|
this.flushMetadata(message);
|
|
4287
4616
|
}
|
|
4288
4617
|
}
|
|
4289
|
-
onError(
|
|
4618
|
+
onError(params) {
|
|
4619
|
+
const qualifier = params.detailCode ? `${params.code} ${params.detailCode}` : params.code;
|
|
4620
|
+
const message = preferredAcpErrorDetails(params.acp) ?? params.message;
|
|
4621
|
+
this.stderr.write(`[acpx] error: ${qualifier} ${message.replace(/\r\n?|\n/g, " ")}\n`);
|
|
4622
|
+
}
|
|
4290
4623
|
onPermissionEscalation(_event) {}
|
|
4291
4624
|
flush() {}
|
|
4292
4625
|
flushBufferedOutput() {
|
|
@@ -4347,6 +4680,10 @@ var QuietOutputFormatter = class {
|
|
|
4347
4680
|
if (typeof cost === "string" && cost.trim()) return `[acpx] cost: ${cost.trim()}`;
|
|
4348
4681
|
}
|
|
4349
4682
|
};
|
|
4683
|
+
function preferredAcpErrorDetails(acp) {
|
|
4684
|
+
const details = asRecord(acp?.data)?.details;
|
|
4685
|
+
return typeof details === "string" && details.trim().length > 0 ? details.trim() : void 0;
|
|
4686
|
+
}
|
|
4350
4687
|
function createOutputFormatter(format, options = {}) {
|
|
4351
4688
|
const stdout = options.stdout ?? process.stdout;
|
|
4352
4689
|
const stderr = options.stderr ?? process.stderr;
|
|
@@ -4361,4 +4698,4 @@ function createOutputFormatter(format, options = {}) {
|
|
|
4361
4698
|
//#endregion
|
|
4362
4699
|
export { runSessionQueueOwner as a, buildQueueOwnerArgOverride as c, createSessionWithClient as d, cancelSessionPrompt as f, __exportAll as g, isProcessAlive 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 };
|
|
4363
4700
|
|
|
4364
|
-
//# sourceMappingURL=output-
|
|
4701
|
+
//# sourceMappingURL=output-fNqTAsFQ.js.map
|