acpx 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-registry-DU6mhZBL.js +785 -0
- package/dist/agent-registry-DU6mhZBL.js.map +1 -0
- package/dist/agent-registry.d.ts +70 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +2 -0
- package/dist/{cli-Cen2Rb6S.js → cli-BkYIxj_N.js} +6 -14
- package/dist/cli-BkYIxj_N.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +137 -333
- package/dist/cli.js.map +1 -1
- package/dist/{client-Cvz6msGc.d.ts → client-5levcBIs.d.ts} +11 -9
- package/dist/client-5levcBIs.d.ts.map +1 -0
- package/dist/{flows-D-F3Y2o9.js → flows-Bxs4udrs.js} +205 -277
- package/dist/flows-Bxs4udrs.js.map +1 -0
- package/dist/flows.d.ts +6 -17
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{flags-CRh8BJre.js → invocation-options-BY4lxkn3.js} +82 -58
- package/dist/invocation-options-BY4lxkn3.js.map +1 -0
- package/dist/{live-checkpoint-CYRs-D9t.js → live-checkpoint-BEfxBKCh.js} +1033 -1437
- package/dist/live-checkpoint-BEfxBKCh.js.map +1 -0
- package/dist/{output-CIVecnHU.js → output-uo5a27KB.js} +487 -490
- package/dist/output-uo5a27KB.js.map +1 -0
- package/dist/runtime.d.ts +105 -22
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +582 -366
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-DyIGRNfu.d.ts → session-options-t3d-F4dn.d.ts} +8 -6
- package/dist/session-options-t3d-F4dn.d.ts.map +1 -0
- package/package.json +27 -25
- package/skills/acpx/SKILL.md +19 -17
- package/dist/cli-Cen2Rb6S.js.map +0 -1
- package/dist/client-Cvz6msGc.d.ts.map +0 -1
- package/dist/flags-CRh8BJre.js.map +0 -1
- package/dist/flows-D-F3Y2o9.js.map +0 -1
- package/dist/live-checkpoint-CYRs-D9t.js.map +0 -1
- package/dist/output-CIVecnHU.js.map +0 -1
- package/dist/session-options-DyIGRNfu.d.ts.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import
|
|
1
|
+
import { $ as getPerfMetricsSnapshot, At as isRetryablePromptError, B as absolutePath, C as recordSessionUpdate, Dt as withTimeout, E as AcpClient, Et as withInterrupt, G as listSessions, H as findSession, I as promptToDisplayText, J as pruneSessions, K as listSessionsForAgent, Kt as QueueConnectionError, L as textPrompt, Mt as extractAcpError, N as isPromptInput, Pt as toAcpErrorPayload, Q as formatPerfMetric, Rt as OUTPUT_ERROR_CODES, S as recordPromptSubmission, St as parsePromptStopReason, T as advertisedModelState, Tt as TimeoutError, U as findSessionByDirectoryWalk, V as findGitRepositoryRoot, W as isoNow, X as writeSessionRecord, Y as resolveSessionRecord, _t as isProcessAlive, a as applyConfigOptionSelection, at as startPerfTimer, bt as parseJsonRpcErrorMessage, c as applyModelSelection, ct as normalizeAgentSessionId, d as mergeSessionOptions, dt as sessionBaseDir, et as incrementPerfCounter, f as persistSessionOptions, ft as sessionEventActivePath, g as createInitialSessionRecord, h as applyLifecycleSnapshotToRecord, i as connectAndLoadSession, it as setPerfGauge, jt as normalizeOutputError, kt as formatErrorMessage, l as applyRequestedModelIfAdvertised, m as applyConversation, mt as sessionEventSegmentPath, n as runPromptTurn, nt as recordPerfDuration, o as applyConfigOptionsToRecord, p as sessionOptionsFromRecord, pt as sessionEventLockPath, q as normalizeName, qt as QueueProtocolError, r as withConnectedSession, rt as resetPerfMetrics, s as applyInitialModelSelection, t as LiveSessionCheckpoint, tt as measurePerf, u as setDesiredModeId, v as cloneSessionAcpxState, vt as extractSessionUpdateNotification, w as trimConversationForRuntime, wt as InterruptedError, x as recordClientOperation, xt as parsePermissionNotice, y as cloneSessionConversation, yt as isAcpJsonRpcMessage, zt as OUTPUT_ERROR_ORIGINS } from "./live-checkpoint-BEfxBKCh.js";
|
|
2
|
+
import { M as runTimedExecFile, N as splitCommandLine } from "./agent-registry-DU6mhZBL.js";
|
|
3
|
+
import fs, { realpathSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
4
6
|
import fs$1 from "node:fs/promises";
|
|
5
7
|
import os from "node:os";
|
|
8
|
+
import { appendRegularFile, appendRegularFileSync, withTempFile } from "@openclaw/fs-safe/advanced";
|
|
6
9
|
import { createHash, randomInt, randomUUID } from "node:crypto";
|
|
7
|
-
import {
|
|
10
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
8
11
|
import net from "node:net";
|
|
12
|
+
import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
|
|
9
13
|
//#region \0rolldown/runtime.js
|
|
10
14
|
var __defProp = Object.defineProperty;
|
|
11
15
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -18,17 +22,6 @@ var __exportAll = (all, no_symbols) => {
|
|
|
18
22
|
return target;
|
|
19
23
|
};
|
|
20
24
|
//#endregion
|
|
21
|
-
//#region src/process-liveness.ts
|
|
22
|
-
function isProcessAlive(pid) {
|
|
23
|
-
if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
24
|
-
try {
|
|
25
|
-
process.kill(pid, 0);
|
|
26
|
-
return true;
|
|
27
|
-
} catch {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
25
|
//#region src/cli/session/contracts.ts
|
|
33
26
|
const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
|
|
34
27
|
function normalizeQueueOwnerTtlMs(ttlMs) {
|
|
@@ -61,7 +54,7 @@ function queueSocketPath(sessionId, homeDir = os.homedir()) {
|
|
|
61
54
|
}
|
|
62
55
|
//#endregion
|
|
63
56
|
//#region src/cli/queue/lease-store.ts
|
|
64
|
-
const PROCESS_SIGTERM_GRACE_MS = 4e3;
|
|
57
|
+
const PROCESS_SIGTERM_GRACE_MS = process.platform === "win32" ? 4e3 : 12e3;
|
|
65
58
|
const PROCESS_SIGKILL_GRACE_MS = 1500;
|
|
66
59
|
const PROCESS_POLL_MS = 50;
|
|
67
60
|
const QUEUE_OWNER_STALE_HEARTBEAT_MS = 15e3;
|
|
@@ -141,61 +134,62 @@ async function waitForProcessExit(pid, timeoutMs) {
|
|
|
141
134
|
}
|
|
142
135
|
return !isProcessAlive(pid);
|
|
143
136
|
}
|
|
144
|
-
async function
|
|
145
|
-
|
|
146
|
-
await removeSocketFile(
|
|
147
|
-
await
|
|
137
|
+
async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent) {
|
|
138
|
+
if (!await isCurrent()) return;
|
|
139
|
+
await removeSocketFile(socketPath).catch(() => {});
|
|
140
|
+
if (!await isCurrent()) return;
|
|
141
|
+
await fs$1.unlink(queueLockFilePath(sessionId)).catch((error) => {
|
|
148
142
|
if (error.code !== "ENOENT") throw error;
|
|
149
143
|
});
|
|
150
144
|
}
|
|
151
|
-
async function
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
async function ownsQueueLease(owner, requireStale = false) {
|
|
146
|
+
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
147
|
+
return current?.pid === owner.pid && current.ownerGeneration === owner.ownerGeneration && current.sessionId === owner.sessionId && (!requireStale || !ownerIsAlive(current) || isQueueOwnerHeartbeatStale(current));
|
|
148
|
+
}
|
|
149
|
+
function ownerIsAlive(owner) {
|
|
150
|
+
return owner.pid === process.pid || isProcessAlive(owner.pid);
|
|
154
151
|
}
|
|
155
152
|
async function readQueueOwnerRecord(sessionId) {
|
|
156
153
|
const lockPath = queueLockFilePath(sessionId);
|
|
157
154
|
try {
|
|
158
155
|
const payload = await fs$1.readFile(lockPath, "utf8");
|
|
159
|
-
|
|
156
|
+
const parsed = parseQueueOwnerRecord(JSON.parse(payload));
|
|
157
|
+
return parsed?.sessionId === sessionId ? parsed : void 0;
|
|
160
158
|
} catch {
|
|
161
159
|
return;
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
|
-
async function terminateProcess(pid) {
|
|
162
|
+
async function terminateProcess(pid, beforeSignal) {
|
|
165
163
|
if (!isProcessAlive(pid)) return false;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} catch {
|
|
175
|
-
return false;
|
|
164
|
+
for (const [signal, graceMs] of [["SIGTERM", PROCESS_SIGTERM_GRACE_MS], ["SIGKILL", PROCESS_SIGKILL_GRACE_MS]]) {
|
|
165
|
+
if (beforeSignal && !await beforeSignal(signal)) return false;
|
|
166
|
+
try {
|
|
167
|
+
process.kill(pid, signal);
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (await waitForProcessExit(pid, graceMs)) return true;
|
|
176
172
|
}
|
|
177
|
-
await waitForProcessExit(pid, PROCESS_SIGKILL_GRACE_MS);
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
async function ensureOwnerIsUsable(sessionId, owner) {
|
|
181
|
-
const alive = isProcessAlive(owner.pid);
|
|
182
|
-
const stale = isQueueOwnerHeartbeatStale(owner);
|
|
183
|
-
if (alive && !stale) return true;
|
|
184
|
-
await retireStaleQueueOwner(sessionId, owner);
|
|
185
173
|
return false;
|
|
186
174
|
}
|
|
175
|
+
async function resolveUsableQueueOwner(sessionId, owner) {
|
|
176
|
+
if (ownerIsAlive(owner) && !isQueueOwnerHeartbeatStale(owner)) return owner;
|
|
177
|
+
await terminateQueueOwnerForSession(sessionId, owner, true);
|
|
178
|
+
const current = await readQueueOwnerRecord(sessionId);
|
|
179
|
+
return current?.pid === owner.pid && current.ownerGeneration === owner.ownerGeneration && ownerIsAlive(current) && !isQueueOwnerHeartbeatStale(current) ? current : void 0;
|
|
180
|
+
}
|
|
187
181
|
async function readQueueOwnerStatus(sessionId) {
|
|
188
|
-
const
|
|
182
|
+
const observed = await readQueueOwnerRecord(sessionId);
|
|
183
|
+
if (!observed) return;
|
|
184
|
+
const owner = await resolveUsableQueueOwner(sessionId, observed);
|
|
189
185
|
if (!owner) return;
|
|
190
|
-
const alive = await ensureOwnerIsUsable(sessionId, owner);
|
|
191
|
-
if (!alive) return;
|
|
192
186
|
return {
|
|
193
187
|
pid: owner.pid,
|
|
194
188
|
socketPath: owner.socketPath,
|
|
195
189
|
heartbeatAt: owner.heartbeatAt,
|
|
196
190
|
ownerGeneration: owner.ownerGeneration,
|
|
197
191
|
queueDepth: owner.queueDepth,
|
|
198
|
-
alive,
|
|
192
|
+
alive: true,
|
|
199
193
|
stale: isQueueOwnerHeartbeatStale(owner)
|
|
200
194
|
};
|
|
201
195
|
}
|
|
@@ -207,30 +201,32 @@ async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, no
|
|
|
207
201
|
const socketPath = queueSocketPath(sessionId);
|
|
208
202
|
const createdAt = clock();
|
|
209
203
|
const ownerGeneration = createOwnerGeneration();
|
|
210
|
-
const
|
|
204
|
+
const lease = {
|
|
211
205
|
pid: process.pid,
|
|
212
206
|
sessionId,
|
|
207
|
+
lockPath,
|
|
213
208
|
socketPath,
|
|
214
209
|
createdAt,
|
|
215
|
-
heartbeatAt: createdAt,
|
|
216
210
|
ownerGeneration,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
211
|
+
...mcpConfigMetadata,
|
|
212
|
+
updates: Promise.resolve(),
|
|
213
|
+
released: false
|
|
214
|
+
};
|
|
220
215
|
try {
|
|
221
|
-
await
|
|
222
|
-
|
|
223
|
-
|
|
216
|
+
await stageQueueOwnerRecord(lease, 0, () => createdAt, async (tempPath, payload) => {
|
|
217
|
+
try {
|
|
218
|
+
await fs$1.link(tempPath, lockPath);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (!isHardlinkFallbackError(error)) throw error;
|
|
221
|
+
await fs$1.writeFile(lockPath, payload, {
|
|
222
|
+
encoding: "utf8",
|
|
223
|
+
flag: "wx",
|
|
224
|
+
mode: 384
|
|
225
|
+
});
|
|
226
|
+
}
|
|
224
227
|
});
|
|
225
228
|
await removeSocketFile(socketPath).catch(() => {});
|
|
226
|
-
return
|
|
227
|
-
sessionId,
|
|
228
|
-
lockPath,
|
|
229
|
-
socketPath,
|
|
230
|
-
createdAt,
|
|
231
|
-
ownerGeneration,
|
|
232
|
-
...mcpConfigMetadata
|
|
233
|
-
};
|
|
229
|
+
return lease;
|
|
234
230
|
} catch (error) {
|
|
235
231
|
return await handleLeaseCollision(sessionId, error);
|
|
236
232
|
}
|
|
@@ -248,10 +244,25 @@ async function handleLeaseCollision(sessionId, error) {
|
|
|
248
244
|
if (error.code !== "EEXIST") throw error;
|
|
249
245
|
const owner = await readQueueOwnerRecord(sessionId);
|
|
250
246
|
if (!owner) {
|
|
251
|
-
await
|
|
247
|
+
await cleanupAbandonedReservation(sessionId);
|
|
252
248
|
return;
|
|
253
249
|
}
|
|
254
|
-
|
|
250
|
+
await resolveUsableQueueOwner(sessionId, owner);
|
|
251
|
+
}
|
|
252
|
+
async function cleanupAbandonedReservation(sessionId) {
|
|
253
|
+
const lockPath = queueLockFilePath(sessionId);
|
|
254
|
+
try {
|
|
255
|
+
const observed = await fs$1.lstat(lockPath, { bigint: true });
|
|
256
|
+
if (!observed.isFile() || Date.now() - Number(observed.mtimeMs) <= QUEUE_OWNER_STALE_HEARTBEAT_MS) return;
|
|
257
|
+
const raw = await fs$1.readFile(lockPath, "utf8");
|
|
258
|
+
if (await readQueueOwnerRecord(sessionId)) return;
|
|
259
|
+
await cleanupQueueOwnerFiles(sessionId, queueSocketPath(sessionId), async () => {
|
|
260
|
+
const current = await fs$1.lstat(lockPath, { bigint: true });
|
|
261
|
+
return current.isFile() && current.dev === observed.dev && current.ino === observed.ino && current.mtimeNs === observed.mtimeNs && await fs$1.readFile(lockPath, "utf8") === raw;
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (error.code !== "ENOENT") throw error;
|
|
265
|
+
}
|
|
255
266
|
}
|
|
256
267
|
function resolveLeaseArguments(mcpConfigOrNowIsoFactory, nowIsoFactory) {
|
|
257
268
|
if (typeof mcpConfigOrNowIsoFactory === "string") return {
|
|
@@ -271,31 +282,52 @@ function resolveLeaseArguments(mcpConfigOrNowIsoFactory, nowIsoFactory) {
|
|
|
271
282
|
clock: nowIsoFactory
|
|
272
283
|
};
|
|
273
284
|
}
|
|
274
|
-
|
|
285
|
+
function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
|
|
286
|
+
if (lease.released) return Promise.resolve();
|
|
287
|
+
const update = lease.updates.then(async () => {
|
|
288
|
+
await stageQueueOwnerRecord(lease, options.queueDepth, nowIsoFactory, async (tempPath) => {
|
|
289
|
+
if (await ownsQueueLease(lease)) await fs$1.rename(tempPath, lease.lockPath);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
lease.updates = update.catch(() => {});
|
|
293
|
+
return update;
|
|
294
|
+
}
|
|
295
|
+
async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
|
|
275
296
|
const payload = JSON.stringify({
|
|
276
297
|
pid: process.pid,
|
|
277
298
|
sessionId: lease.sessionId,
|
|
278
299
|
socketPath: lease.socketPath,
|
|
279
300
|
createdAt: lease.createdAt,
|
|
280
|
-
heartbeatAt:
|
|
301
|
+
heartbeatAt: clock(),
|
|
281
302
|
ownerGeneration: lease.ownerGeneration,
|
|
282
|
-
queueDepth: Math.max(0, Math.round(
|
|
303
|
+
queueDepth: Math.max(0, Math.round(queueDepth)),
|
|
283
304
|
...lease.mcpConfigPath ? { mcpConfigPath: lease.mcpConfigPath } : {},
|
|
284
305
|
...lease.mcpConfigFingerprint ? { mcpConfigFingerprint: lease.mcpConfigFingerprint } : {}
|
|
285
306
|
}, null, 2);
|
|
286
|
-
await
|
|
307
|
+
await withTempFile({
|
|
308
|
+
rootDir: path.dirname(lease.lockPath),
|
|
309
|
+
prefix: "owner",
|
|
310
|
+
fileName: "lease"
|
|
311
|
+
}, async (tempPath) => {
|
|
312
|
+
await fs$1.writeFile(tempPath, `${payload}\n`, {
|
|
313
|
+
encoding: "utf8",
|
|
314
|
+
flag: "wx",
|
|
315
|
+
mode: 384
|
|
316
|
+
});
|
|
317
|
+
await publish(tempPath, `${payload}\n`);
|
|
318
|
+
});
|
|
287
319
|
}
|
|
288
320
|
async function releaseQueueOwnerLease(lease) {
|
|
289
|
-
|
|
290
|
-
await
|
|
291
|
-
|
|
292
|
-
});
|
|
321
|
+
lease.released = true;
|
|
322
|
+
await lease.updates;
|
|
323
|
+
await cleanupQueueOwnerFiles(lease.sessionId, lease.socketPath, () => ownsQueueLease(lease));
|
|
293
324
|
}
|
|
294
|
-
async function terminateQueueOwnerForSession(sessionId) {
|
|
295
|
-
const owner = await readQueueOwnerRecord(sessionId);
|
|
296
|
-
if (!owner) return;
|
|
297
|
-
if (
|
|
298
|
-
|
|
325
|
+
async function terminateQueueOwnerForSession(sessionId, expectedOwner, requireStale = false) {
|
|
326
|
+
const owner = expectedOwner ?? await readQueueOwnerRecord(sessionId);
|
|
327
|
+
if (!owner || owner.sessionId !== sessionId) return;
|
|
328
|
+
if (ownerIsAlive(owner)) await terminateProcess(owner.pid, (signal) => ownsQueueLease(owner, requireStale && signal === "SIGTERM"));
|
|
329
|
+
if (ownerIsAlive(owner)) return;
|
|
330
|
+
await cleanupQueueOwnerFiles(sessionId, owner.socketPath, () => ownsQueueLease(owner));
|
|
299
331
|
}
|
|
300
332
|
async function waitMs(ms) {
|
|
301
333
|
await new Promise((resolve) => {
|
|
@@ -339,16 +371,13 @@ async function connectToSocket(socketPath, timeoutMs = SOCKET_CONNECTION_TIMEOUT
|
|
|
339
371
|
});
|
|
340
372
|
}
|
|
341
373
|
async function connectToQueueOwner(owner, maxAttempts = QUEUE_CONNECT_ATTEMPTS) {
|
|
342
|
-
let lastError;
|
|
343
374
|
const attempts = Math.max(1, Math.trunc(maxAttempts));
|
|
344
375
|
for (let attempt = 0; attempt < attempts; attempt += 1) try {
|
|
345
376
|
return await measurePerf("queue.connect", async () => await connectToSocket(owner.socketPath));
|
|
346
377
|
} catch (error) {
|
|
347
|
-
lastError = error;
|
|
348
378
|
if (!shouldRetryQueueConnect(error)) throw error;
|
|
349
379
|
await waitMs(50);
|
|
350
380
|
}
|
|
351
|
-
if (lastError && !shouldRetryQueueConnect(lastError)) throw lastError;
|
|
352
381
|
}
|
|
353
382
|
//#endregion
|
|
354
383
|
//#region src/cli/queue/ipc-health.ts
|
|
@@ -1151,7 +1180,7 @@ async function maybeRecoverStaleOwnerAfterProtocolMismatch(params) {
|
|
|
1151
1180
|
if (!(params.error instanceof QueueProtocolError)) return false;
|
|
1152
1181
|
const detailCode = params.error.detailCode;
|
|
1153
1182
|
if (!detailCode || !STALE_OWNER_PROTOCOL_DETAIL_CODES.has(detailCode)) return false;
|
|
1154
|
-
await terminateQueueOwnerForSession(params.sessionId).catch(() => {});
|
|
1183
|
+
await terminateQueueOwnerForSession(params.sessionId, params.owner).catch(() => {});
|
|
1155
1184
|
incrementPerfCounter("queue.owner.stale_recovered");
|
|
1156
1185
|
if (params.verbose) process.stderr.write(`[acpx] dropped stale queue owner metadata after protocol mismatch for session ${params.sessionId} (${detailCode})\n`);
|
|
1157
1186
|
return true;
|
|
@@ -1390,29 +1419,15 @@ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
|
|
|
1390
1419
|
request,
|
|
1391
1420
|
onMessage: (message, { state, resolve, reject }) => {
|
|
1392
1421
|
if (message.type === "error") {
|
|
1393
|
-
reject(
|
|
1394
|
-
outputCode: message.code,
|
|
1395
|
-
detailCode: message.detailCode,
|
|
1396
|
-
origin: message.origin ?? "queue",
|
|
1397
|
-
retryable: message.retryable,
|
|
1398
|
-
acp: message.acp
|
|
1399
|
-
}));
|
|
1422
|
+
reject(queueConnectionErrorFromOwner(message, false));
|
|
1400
1423
|
return;
|
|
1401
1424
|
}
|
|
1402
1425
|
if (!state.acknowledged) {
|
|
1403
|
-
reject(
|
|
1404
|
-
detailCode: "QUEUE_ACK_MISSING",
|
|
1405
|
-
origin: "queue",
|
|
1406
|
-
retryable: true
|
|
1407
|
-
}));
|
|
1426
|
+
reject(missingQueueAckError());
|
|
1408
1427
|
return;
|
|
1409
1428
|
}
|
|
1410
1429
|
if (!isExpectedResponse(message)) {
|
|
1411
|
-
reject(
|
|
1412
|
-
detailCode: "QUEUE_PROTOCOL_UNEXPECTED_RESPONSE",
|
|
1413
|
-
origin: "queue",
|
|
1414
|
-
retryable: true
|
|
1415
|
-
}));
|
|
1430
|
+
reject(unexpectedQueueResponseError());
|
|
1416
1431
|
return;
|
|
1417
1432
|
}
|
|
1418
1433
|
resolve(message);
|
|
@@ -1435,87 +1450,47 @@ async function submitControlToQueueOwner(owner, request, isExpectedResponse) {
|
|
|
1435
1450
|
});
|
|
1436
1451
|
}
|
|
1437
1452
|
async function submitCancelToQueueOwner(owner) {
|
|
1438
|
-
|
|
1453
|
+
return (await submitControlToQueueOwner(owner, {
|
|
1439
1454
|
type: "cancel_prompt",
|
|
1440
1455
|
requestId: randomUUID(),
|
|
1441
1456
|
ownerGeneration: owner.ownerGeneration
|
|
1442
|
-
};
|
|
1443
|
-
const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "cancel_result");
|
|
1444
|
-
if (!response) return;
|
|
1445
|
-
if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched cancel response", {
|
|
1446
|
-
detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
|
|
1447
|
-
origin: "queue",
|
|
1448
|
-
retryable: true
|
|
1449
|
-
});
|
|
1450
|
-
return response.cancelled;
|
|
1457
|
+
}, (message) => message.type === "cancel_result"))?.cancelled;
|
|
1451
1458
|
}
|
|
1452
1459
|
async function submitSetModeToQueueOwner(owner, modeId, timeoutMs) {
|
|
1453
|
-
|
|
1460
|
+
return await submitControlToQueueOwner(owner, {
|
|
1454
1461
|
type: "set_mode",
|
|
1455
1462
|
requestId: randomUUID(),
|
|
1456
1463
|
ownerGeneration: owner.ownerGeneration,
|
|
1457
1464
|
modeId,
|
|
1458
1465
|
timeoutMs
|
|
1459
|
-
};
|
|
1460
|
-
const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "set_mode_result");
|
|
1461
|
-
if (!response) return;
|
|
1462
|
-
if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched set_mode response", {
|
|
1463
|
-
detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
|
|
1464
|
-
origin: "queue",
|
|
1465
|
-
retryable: true
|
|
1466
|
-
});
|
|
1467
|
-
return true;
|
|
1466
|
+
}, (message) => message.type === "set_mode_result") ? true : void 0;
|
|
1468
1467
|
}
|
|
1469
1468
|
async function submitSetModelToQueueOwner(owner, modelId, timeoutMs) {
|
|
1470
|
-
|
|
1469
|
+
return await submitControlToQueueOwner(owner, {
|
|
1471
1470
|
type: "set_model",
|
|
1472
1471
|
requestId: randomUUID(),
|
|
1473
1472
|
ownerGeneration: owner.ownerGeneration,
|
|
1474
1473
|
modelId,
|
|
1475
1474
|
timeoutMs
|
|
1476
|
-
};
|
|
1477
|
-
const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "set_model_result");
|
|
1478
|
-
if (!response) return;
|
|
1479
|
-
if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched set_model response", {
|
|
1480
|
-
detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
|
|
1481
|
-
origin: "queue",
|
|
1482
|
-
retryable: true
|
|
1483
|
-
});
|
|
1484
|
-
return response;
|
|
1475
|
+
}, (message) => message.type === "set_model_result");
|
|
1485
1476
|
}
|
|
1486
1477
|
async function submitSetConfigOptionToQueueOwner(owner, configId, value, timeoutMs) {
|
|
1487
|
-
|
|
1478
|
+
return (await submitControlToQueueOwner(owner, {
|
|
1488
1479
|
type: "set_config_option",
|
|
1489
1480
|
requestId: randomUUID(),
|
|
1490
1481
|
ownerGeneration: owner.ownerGeneration,
|
|
1491
1482
|
configId,
|
|
1492
1483
|
value,
|
|
1493
1484
|
timeoutMs
|
|
1494
|
-
};
|
|
1495
|
-
const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "set_config_option_result");
|
|
1496
|
-
if (!response) return;
|
|
1497
|
-
if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched set_config_option response", {
|
|
1498
|
-
detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
|
|
1499
|
-
origin: "queue",
|
|
1500
|
-
retryable: true
|
|
1501
|
-
});
|
|
1502
|
-
return response.response;
|
|
1485
|
+
}, (message) => message.type === "set_config_option_result"))?.response;
|
|
1503
1486
|
}
|
|
1504
1487
|
async function submitCloseSessionToQueueOwner(owner, timeoutMs) {
|
|
1505
|
-
|
|
1488
|
+
return (await submitControlToQueueOwner(owner, {
|
|
1506
1489
|
type: "close_session",
|
|
1507
1490
|
requestId: randomUUID(),
|
|
1508
1491
|
ownerGeneration: owner.ownerGeneration,
|
|
1509
1492
|
timeoutMs
|
|
1510
|
-
};
|
|
1511
|
-
const response = await submitControlToQueueOwner(owner, request, (message) => message.type === "close_session_result");
|
|
1512
|
-
if (!response) return;
|
|
1513
|
-
if (response.requestId !== request.requestId) throw new QueueProtocolError("Queue owner returned mismatched close_session response", {
|
|
1514
|
-
detailCode: "QUEUE_PROTOCOL_MALFORMED_MESSAGE",
|
|
1515
|
-
origin: "queue",
|
|
1516
|
-
retryable: true
|
|
1517
|
-
});
|
|
1518
|
-
return response.closed;
|
|
1493
|
+
}, (message) => message.type === "close_session_result"))?.closed;
|
|
1519
1494
|
}
|
|
1520
1495
|
function queueOwnerMcpConfigMatches(owner, options) {
|
|
1521
1496
|
return owner.mcpConfigPath === options.mcpConfigPath && owner.mcpConfigFingerprint === options.mcpConfigFingerprint;
|
|
@@ -1529,9 +1504,10 @@ function assertQueueOwnerMcpConfigMatches(owner, options) {
|
|
|
1529
1504
|
});
|
|
1530
1505
|
}
|
|
1531
1506
|
async function trySubmitToRunningOwner(options) {
|
|
1532
|
-
const
|
|
1507
|
+
const observed = await readQueueOwnerRecord(options.sessionId);
|
|
1508
|
+
if (!observed) return;
|
|
1509
|
+
const owner = await resolveUsableQueueOwner(options.sessionId, observed);
|
|
1533
1510
|
if (!owner) return;
|
|
1534
|
-
if (!await ensureOwnerIsUsable(options.sessionId, owner)) return;
|
|
1535
1511
|
assertQueueOwnerMcpConfigMatches(owner, options);
|
|
1536
1512
|
let submitted;
|
|
1537
1513
|
try {
|
|
@@ -1556,78 +1532,64 @@ async function trySubmitToRunningOwner(options) {
|
|
|
1556
1532
|
retryable: true
|
|
1557
1533
|
});
|
|
1558
1534
|
}
|
|
1559
|
-
async function
|
|
1535
|
+
async function tryControlOnRunningOwner(options) {
|
|
1560
1536
|
const owner = await readQueueOwnerRecord(options.sessionId);
|
|
1561
1537
|
if (!owner) return;
|
|
1562
|
-
const
|
|
1563
|
-
if (
|
|
1564
|
-
if (options.verbose) process.stderr.write(
|
|
1565
|
-
return
|
|
1538
|
+
const response = await options.submit(owner);
|
|
1539
|
+
if (response !== void 0) {
|
|
1540
|
+
if (options.verbose) process.stderr.write(`${options.logPrefix} ${owner.pid} for session ${options.sessionId}\n`);
|
|
1541
|
+
return response;
|
|
1566
1542
|
}
|
|
1567
1543
|
if (!(await probeQueueOwnerHealth(options.sessionId)).hasLease) return;
|
|
1568
|
-
throw new QueueConnectionError(
|
|
1544
|
+
throw new QueueConnectionError(`Session queue owner is running but not accepting ${options.requestName} requests`, {
|
|
1569
1545
|
detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
|
|
1570
1546
|
origin: "queue",
|
|
1571
1547
|
retryable: true
|
|
1572
1548
|
});
|
|
1573
1549
|
}
|
|
1550
|
+
async function tryCloseSessionOnRunningOwner(options) {
|
|
1551
|
+
return await tryControlOnRunningOwner({
|
|
1552
|
+
sessionId: options.sessionId,
|
|
1553
|
+
verbose: options.verbose,
|
|
1554
|
+
requestName: "close_session",
|
|
1555
|
+
logPrefix: "[acpx] requested session/close on active owner pid",
|
|
1556
|
+
submit: (owner) => submitCloseSessionToQueueOwner(owner, options.timeoutMs)
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1574
1559
|
async function tryCancelOnRunningOwner(options) {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1582
|
-
if (!(await probeQueueOwnerHealth(options.sessionId)).hasLease) return;
|
|
1583
|
-
throw new QueueConnectionError("Session queue owner is running but not accepting cancel requests", {
|
|
1584
|
-
detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
|
|
1585
|
-
origin: "queue",
|
|
1586
|
-
retryable: true
|
|
1560
|
+
return await tryControlOnRunningOwner({
|
|
1561
|
+
sessionId: options.sessionId,
|
|
1562
|
+
verbose: options.verbose,
|
|
1563
|
+
requestName: "cancel",
|
|
1564
|
+
logPrefix: "[acpx] requested cancel on active owner pid",
|
|
1565
|
+
submit: submitCancelToQueueOwner
|
|
1587
1566
|
});
|
|
1588
1567
|
}
|
|
1589
1568
|
async function trySetModeOnRunningOwner(sessionId, modeId, timeoutMs, verbose) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
if (!(await probeQueueOwnerHealth(sessionId)).hasLease) return;
|
|
1597
|
-
throw new QueueConnectionError("Session queue owner is running but not accepting set_mode requests", {
|
|
1598
|
-
detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
|
|
1599
|
-
origin: "queue",
|
|
1600
|
-
retryable: true
|
|
1569
|
+
return await tryControlOnRunningOwner({
|
|
1570
|
+
sessionId,
|
|
1571
|
+
verbose,
|
|
1572
|
+
requestName: "set_mode",
|
|
1573
|
+
logPrefix: "[acpx] requested session/set_mode on owner pid",
|
|
1574
|
+
submit: (owner) => submitSetModeToQueueOwner(owner, modeId, timeoutMs)
|
|
1601
1575
|
});
|
|
1602
1576
|
}
|
|
1603
1577
|
async function trySetModelOnRunningOwner(sessionId, modelId, timeoutMs, verbose) {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
}
|
|
1611
|
-
if (!(await probeQueueOwnerHealth(sessionId)).hasLease) return;
|
|
1612
|
-
throw new QueueConnectionError("Session queue owner is running but not accepting set_model requests", {
|
|
1613
|
-
detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
|
|
1614
|
-
origin: "queue",
|
|
1615
|
-
retryable: true
|
|
1578
|
+
return await tryControlOnRunningOwner({
|
|
1579
|
+
sessionId,
|
|
1580
|
+
verbose,
|
|
1581
|
+
requestName: "set_model",
|
|
1582
|
+
logPrefix: "[acpx] requested a model config update on owner pid",
|
|
1583
|
+
submit: (owner) => submitSetModelToQueueOwner(owner, modelId, timeoutMs)
|
|
1616
1584
|
});
|
|
1617
1585
|
}
|
|
1618
1586
|
async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, timeoutMs, verbose) {
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
if (!(await probeQueueOwnerHealth(sessionId)).hasLease) return;
|
|
1627
|
-
throw new QueueConnectionError("Session queue owner is running but not accepting set_config_option requests", {
|
|
1628
|
-
detailCode: "QUEUE_NOT_ACCEPTING_REQUESTS",
|
|
1629
|
-
origin: "queue",
|
|
1630
|
-
retryable: true
|
|
1587
|
+
return await tryControlOnRunningOwner({
|
|
1588
|
+
sessionId,
|
|
1589
|
+
verbose,
|
|
1590
|
+
requestName: "set_config_option",
|
|
1591
|
+
logPrefix: "[acpx] requested session/set_config_option on owner pid",
|
|
1592
|
+
submit: (owner) => submitSetConfigOptionToQueueOwner(owner, configId, value, timeoutMs)
|
|
1631
1593
|
});
|
|
1632
1594
|
}
|
|
1633
1595
|
//#endregion
|
|
@@ -1864,41 +1826,28 @@ async function createSessionRecordWithClient(client, options) {
|
|
|
1864
1826
|
const createdState = options.resumeSessionId ? await resumeSessionRecordWithClient(client, options, cwd) : await createFreshSessionState(client, options, cwd);
|
|
1865
1827
|
const { sessionId, agentSessionId } = createdState;
|
|
1866
1828
|
const lifecycle = client.getAgentLifecycleSnapshot();
|
|
1867
|
-
const now = isoNow();
|
|
1868
1829
|
const record = {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
lastUsedAt: now,
|
|
1879
|
-
lastSeq: 0,
|
|
1830
|
+
...createInitialSessionRecord({
|
|
1831
|
+
recordId: sessionId,
|
|
1832
|
+
sessionId,
|
|
1833
|
+
agentSessionId,
|
|
1834
|
+
agentCommand: options.agentCommand,
|
|
1835
|
+
agentArgv: options.agentArgv,
|
|
1836
|
+
cwd,
|
|
1837
|
+
name: normalizeName(options.name)
|
|
1838
|
+
}),
|
|
1880
1839
|
lastRequestId: void 0,
|
|
1881
|
-
eventLog: defaultSessionEventLog(sessionId),
|
|
1882
|
-
closed: false,
|
|
1883
|
-
closedAt: void 0,
|
|
1884
1840
|
pid: lifecycle.running ? lifecycle.pid : void 0,
|
|
1885
1841
|
agentStartedAt: lifecycle.startedAt,
|
|
1886
1842
|
protocolVersion: client.initializeResult?.protocolVersion,
|
|
1887
|
-
agentCapabilities: client.initializeResult?.agentCapabilities
|
|
1888
|
-
...createSessionConversation(now),
|
|
1889
|
-
acpx: {}
|
|
1843
|
+
agentCapabilities: client.initializeResult?.agentCapabilities
|
|
1890
1844
|
};
|
|
1891
1845
|
persistSessionOptions(record, options.sessionOptions);
|
|
1892
|
-
|
|
1846
|
+
applyConfigOptionsToRecord(record, createdState.sessionResult);
|
|
1847
|
+
applyInitialModelSelection(record, createdState.sessionResult.models, options.sessionOptions?.model, createdState.modelApplication);
|
|
1893
1848
|
await writeSessionRecord(record);
|
|
1894
1849
|
return record;
|
|
1895
1850
|
}
|
|
1896
|
-
function applyCreatedSessionModelState(record, state, requestedModel) {
|
|
1897
|
-
applyConfigOptionsToRecord(record, state.sessionResult);
|
|
1898
|
-
applyConfigOptionsToRecord(record, state.requestedModelResponse);
|
|
1899
|
-
syncAdvertisedModelState(record, state.requestedModelResponse ? modelStateFromConfigOptions(state.requestedModelResponse.configOptions) : state.sessionModels);
|
|
1900
|
-
if (state.requestedModelApplied) setCurrentModelId(record, currentModelIdFromSetModelResponse(state.requestedModelResponse, requestedModel));
|
|
1901
|
-
}
|
|
1902
1851
|
async function createFreshSessionState(client, options, cwd) {
|
|
1903
1852
|
const createdSession = await withTimeout(client.createSession(cwd), options.timeoutMs);
|
|
1904
1853
|
const modelApplication = await applyRequestedModelIfAdvertised({
|
|
@@ -1912,11 +1861,9 @@ async function createFreshSessionState(client, options, cwd) {
|
|
|
1912
1861
|
});
|
|
1913
1862
|
return {
|
|
1914
1863
|
sessionId: createdSession.sessionId,
|
|
1915
|
-
agentSessionId:
|
|
1864
|
+
agentSessionId: normalizeAgentSessionId(createdSession.agentSessionId),
|
|
1916
1865
|
sessionResult: createdSession,
|
|
1917
|
-
|
|
1918
|
-
requestedModelApplied: modelApplication.applied,
|
|
1919
|
-
requestedModelResponse: modelApplication.response
|
|
1866
|
+
modelApplication
|
|
1920
1867
|
};
|
|
1921
1868
|
}
|
|
1922
1869
|
async function resumeSessionRecordWithClient(client, options, cwd) {
|
|
@@ -1937,11 +1884,9 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
|
|
|
1937
1884
|
});
|
|
1938
1885
|
return {
|
|
1939
1886
|
sessionId: options.resumeSessionId,
|
|
1940
|
-
agentSessionId:
|
|
1887
|
+
agentSessionId: normalizeAgentSessionId(resumedSession.agentSessionId),
|
|
1941
1888
|
sessionResult: resumedSession,
|
|
1942
|
-
|
|
1943
|
-
requestedModelApplied: modelApplication.applied,
|
|
1944
|
-
requestedModelResponse: modelApplication.response
|
|
1889
|
+
modelApplication
|
|
1945
1890
|
};
|
|
1946
1891
|
} catch (error) {
|
|
1947
1892
|
throw new Error(`Failed to resume ACP session ${options.resumeSessionId}: ${formatErrorMessage(error)}`, { cause: error });
|
|
@@ -2113,7 +2058,11 @@ function payloadHasMetrics(payload) {
|
|
|
2113
2058
|
}
|
|
2114
2059
|
function appendPerfMetricsPayload(payload) {
|
|
2115
2060
|
fs.mkdirSync(path.dirname(captureFilePath), { recursive: true });
|
|
2116
|
-
|
|
2061
|
+
appendRegularFileSync({
|
|
2062
|
+
filePath: captureFilePath,
|
|
2063
|
+
content: `${JSON.stringify(payload)}\n`,
|
|
2064
|
+
mode: 384
|
|
2065
|
+
});
|
|
2117
2066
|
captureSequence += 1;
|
|
2118
2067
|
}
|
|
2119
2068
|
function writePerfMetricsCapture(reason, resetAfterWrite) {
|
|
@@ -2166,6 +2115,7 @@ var QueueOwnerTurnController = class {
|
|
|
2166
2115
|
state = "idle";
|
|
2167
2116
|
pendingCancel = false;
|
|
2168
2117
|
activeController;
|
|
2118
|
+
waitingTurn;
|
|
2169
2119
|
constructor(options) {
|
|
2170
2120
|
this.options = options;
|
|
2171
2121
|
}
|
|
@@ -2178,6 +2128,8 @@ var QueueOwnerTurnController = class {
|
|
|
2178
2128
|
beginTurn() {
|
|
2179
2129
|
this.state = "starting";
|
|
2180
2130
|
this.pendingCancel = false;
|
|
2131
|
+
this.waitingTurn = new AbortController();
|
|
2132
|
+
return this.waitingTurn.signal;
|
|
2181
2133
|
}
|
|
2182
2134
|
markPromptActive() {
|
|
2183
2135
|
if (this.state === "starting" || this.state === "active") this.state = "active";
|
|
@@ -2185,6 +2137,7 @@ var QueueOwnerTurnController = class {
|
|
|
2185
2137
|
endTurn() {
|
|
2186
2138
|
this.state = "idle";
|
|
2187
2139
|
this.pendingCancel = false;
|
|
2140
|
+
this.waitingTurn = void 0;
|
|
2188
2141
|
}
|
|
2189
2142
|
beginClosing() {
|
|
2190
2143
|
this.state = "closing";
|
|
@@ -2213,6 +2166,7 @@ var QueueOwnerTurnController = class {
|
|
|
2213
2166
|
}
|
|
2214
2167
|
if (this.state === "starting" || this.state === "active") {
|
|
2215
2168
|
this.pendingCancel = true;
|
|
2169
|
+
this.waitingTurn?.abort();
|
|
2216
2170
|
return true;
|
|
2217
2171
|
}
|
|
2218
2172
|
return false;
|
|
@@ -2248,8 +2202,6 @@ var QueueOwnerTurnController = class {
|
|
|
2248
2202
|
};
|
|
2249
2203
|
//#endregion
|
|
2250
2204
|
//#region src/cli/session/queue-owner-process.ts
|
|
2251
|
-
const QUEUE_OWNER_PAYLOAD_FILE_ENV = "ACPX_QUEUE_OWNER_PAYLOAD_FILE";
|
|
2252
|
-
const QUEUE_OWNER_PAYLOAD_ENV = "ACPX_QUEUE_OWNER_PAYLOAD";
|
|
2253
2205
|
function isNonEmptyStringArray(value) {
|
|
2254
2206
|
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string" && entry.length > 0);
|
|
2255
2207
|
}
|
|
@@ -2341,35 +2293,8 @@ function queueOwnerRuntimeOptionsFromSend(options) {
|
|
|
2341
2293
|
sessionOptions: options.sessionOptions
|
|
2342
2294
|
};
|
|
2343
2295
|
}
|
|
2344
|
-
function writeQueueOwnerPayloadFile(payload) {
|
|
2345
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "acpx-queue-owner-"));
|
|
2346
|
-
const payloadPath = path.join(dir, "payload.json");
|
|
2347
|
-
writeFileSync(payloadPath, payload, {
|
|
2348
|
-
encoding: "utf8",
|
|
2349
|
-
flag: "wx",
|
|
2350
|
-
mode: 384
|
|
2351
|
-
});
|
|
2352
|
-
return payloadPath;
|
|
2353
|
-
}
|
|
2354
2296
|
/** Max stderr bytes retained for cold-start diagnostics. */
|
|
2355
2297
|
const QUEUE_OWNER_STARTUP_STDERR_MAX_BYTES = 4e3;
|
|
2356
|
-
function buildQueueOwnerSpawnOptions(payloadFilePath, opts) {
|
|
2357
|
-
const env = {
|
|
2358
|
-
...process.env,
|
|
2359
|
-
[QUEUE_OWNER_PAYLOAD_FILE_ENV]: payloadFilePath
|
|
2360
|
-
};
|
|
2361
|
-
delete env[QUEUE_OWNER_PAYLOAD_ENV];
|
|
2362
|
-
return {
|
|
2363
|
-
detached: true,
|
|
2364
|
-
stdio: opts?.captureStderr === true ? [
|
|
2365
|
-
"ignore",
|
|
2366
|
-
"ignore",
|
|
2367
|
-
"pipe"
|
|
2368
|
-
] : "ignore",
|
|
2369
|
-
env,
|
|
2370
|
-
windowsHide: true
|
|
2371
|
-
};
|
|
2372
|
-
}
|
|
2373
2298
|
function formatQueueOwnerStartupFailure(params) {
|
|
2374
2299
|
const parts = [`Session queue owner failed to start for session ${params.sessionId}`];
|
|
2375
2300
|
if (params.exit.spawnError) parts.push(`spawn error: ${params.exit.spawnError.message}`);
|
|
@@ -2378,19 +2303,33 @@ function formatQueueOwnerStartupFailure(params) {
|
|
|
2378
2303
|
const signalPart = params.exit.signal ? `, signal ${params.exit.signal}` : "";
|
|
2379
2304
|
parts.push(`exited with code ${codePart}${signalPart} before binding its socket`);
|
|
2380
2305
|
}
|
|
2306
|
+
if (params.exit.inputError) parts.push(`startup input failed: ${params.exit.inputError.message}`);
|
|
2381
2307
|
const tail = params.logTail.trim();
|
|
2382
2308
|
if (tail.length > 0) parts.push(`stderr:\n${tail}`);
|
|
2383
2309
|
return parts.join(": ");
|
|
2384
2310
|
}
|
|
2385
2311
|
function spawnQueueOwnerProcess(options) {
|
|
2386
|
-
const
|
|
2312
|
+
const payload = JSON.stringify(options);
|
|
2387
2313
|
let exited = false;
|
|
2388
2314
|
let code = null;
|
|
2389
2315
|
let signal = null;
|
|
2390
2316
|
let spawnError;
|
|
2317
|
+
let inputError;
|
|
2391
2318
|
let capturing = true;
|
|
2392
2319
|
let stderrTail = Buffer.alloc(0);
|
|
2393
|
-
const child = spawn(process.execPath, resolveQueueOwnerSpawnArgs(),
|
|
2320
|
+
const child = spawn(process.execPath, resolveQueueOwnerSpawnArgs(), {
|
|
2321
|
+
detached: true,
|
|
2322
|
+
stdio: [
|
|
2323
|
+
"pipe",
|
|
2324
|
+
"ignore",
|
|
2325
|
+
"pipe"
|
|
2326
|
+
],
|
|
2327
|
+
windowsHide: true
|
|
2328
|
+
});
|
|
2329
|
+
child.stdin.on("error", (error) => {
|
|
2330
|
+
inputError = error;
|
|
2331
|
+
});
|
|
2332
|
+
child.stdin.end(payload);
|
|
2394
2333
|
const stopStartupCapture = () => {
|
|
2395
2334
|
if (!capturing) return;
|
|
2396
2335
|
capturing = false;
|
|
@@ -2424,7 +2363,8 @@ function spawnQueueOwnerProcess(options) {
|
|
|
2424
2363
|
exited,
|
|
2425
2364
|
code,
|
|
2426
2365
|
signal,
|
|
2427
|
-
...spawnError ? { spawnError } : {}
|
|
2366
|
+
...spawnError ? { spawnError } : {},
|
|
2367
|
+
...inputError ? { inputError } : {}
|
|
2428
2368
|
}),
|
|
2429
2369
|
readLogTail: (maxBytes = QUEUE_OWNER_STARTUP_STDERR_MAX_BYTES) => {
|
|
2430
2370
|
const start = Math.max(0, stderrTail.length - maxBytes);
|
|
@@ -2435,10 +2375,11 @@ function spawnQueueOwnerProcess(options) {
|
|
|
2435
2375
|
}
|
|
2436
2376
|
//#endregion
|
|
2437
2377
|
//#region src/session/events.ts
|
|
2438
|
-
const LOCK_RETRY_MS = 15;
|
|
2439
|
-
const EVENT_LOCK_STALE_MS = 15e3;
|
|
2440
2378
|
async function ensureSessionDir() {
|
|
2441
|
-
await fs$1.mkdir(sessionBaseDir(), {
|
|
2379
|
+
await fs$1.mkdir(sessionBaseDir(), {
|
|
2380
|
+
recursive: true,
|
|
2381
|
+
mode: 448
|
|
2382
|
+
});
|
|
2442
2383
|
}
|
|
2443
2384
|
async function pathExists(filePath) {
|
|
2444
2385
|
try {
|
|
@@ -2479,71 +2420,16 @@ async function rotateSegments(sessionId, maxSegments) {
|
|
|
2479
2420
|
}
|
|
2480
2421
|
if (await pathExists(active)) await fs$1.rename(active, sessionEventSegmentPath(sessionId, 1));
|
|
2481
2422
|
}
|
|
2482
|
-
function parseEventLockPayload(raw) {
|
|
2483
|
-
try {
|
|
2484
|
-
const parsed = JSON.parse(raw);
|
|
2485
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
2486
|
-
const record = parsed;
|
|
2487
|
-
return {
|
|
2488
|
-
pid: typeof record.pid === "number" ? record.pid : void 0,
|
|
2489
|
-
created_at: typeof record.created_at === "string" ? record.created_at : void 0
|
|
2490
|
-
};
|
|
2491
|
-
} catch {
|
|
2492
|
-
return {};
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
async function removeStaleEventLock(lockPath) {
|
|
2496
|
-
try {
|
|
2497
|
-
const parsed = parseEventLockPayload(await fs$1.readFile(lockPath, "utf8"));
|
|
2498
|
-
const createdAtMs = parsed.created_at ? Date.parse(parsed.created_at) : NaN;
|
|
2499
|
-
const lockAgeMs = Number.isFinite(createdAtMs) ? Date.now() - createdAtMs : Number.POSITIVE_INFINITY;
|
|
2500
|
-
if (isProcessAlive(parsed.pid) && lockAgeMs <= EVENT_LOCK_STALE_MS) return false;
|
|
2501
|
-
await fs$1.unlink(lockPath);
|
|
2502
|
-
incrementPerfCounter("session.events.stale_lock_recovered");
|
|
2503
|
-
return true;
|
|
2504
|
-
} catch (error) {
|
|
2505
|
-
if (error.code === "ENOENT") return true;
|
|
2506
|
-
return false;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
async function acquireEventsLock(sessionId) {
|
|
2510
|
-
await ensureSessionDir();
|
|
2511
|
-
const lockPath = sessionEventLockPath(sessionId);
|
|
2512
|
-
const payload = JSON.stringify({
|
|
2513
|
-
pid: process.pid,
|
|
2514
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2515
|
-
}, null, 2);
|
|
2516
|
-
for (;;) try {
|
|
2517
|
-
await fs$1.writeFile(lockPath, `${payload}\n`, {
|
|
2518
|
-
encoding: "utf8",
|
|
2519
|
-
flag: "wx"
|
|
2520
|
-
});
|
|
2521
|
-
return { filePath: lockPath };
|
|
2522
|
-
} catch (error) {
|
|
2523
|
-
if (error.code !== "EEXIST") throw error;
|
|
2524
|
-
if (await removeStaleEventLock(lockPath)) continue;
|
|
2525
|
-
await new Promise((resolve) => {
|
|
2526
|
-
setTimeout(resolve, LOCK_RETRY_MS);
|
|
2527
|
-
});
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
async function releaseEventsLock(lock) {
|
|
2531
|
-
await fs$1.unlink(lock.filePath).catch((error) => {
|
|
2532
|
-
if (error.code !== "ENOENT") throw error;
|
|
2533
|
-
});
|
|
2534
|
-
}
|
|
2535
2423
|
var SessionEventWriter = class SessionEventWriter {
|
|
2536
2424
|
record;
|
|
2537
|
-
lock;
|
|
2538
2425
|
maxSegmentBytes;
|
|
2539
2426
|
maxSegments;
|
|
2540
2427
|
activePath;
|
|
2541
2428
|
activeSizeBytes;
|
|
2542
2429
|
segmentCount;
|
|
2543
2430
|
closed = false;
|
|
2544
|
-
constructor(record,
|
|
2431
|
+
constructor(record, options, state) {
|
|
2545
2432
|
this.record = record;
|
|
2546
|
-
this.lock = lock;
|
|
2547
2433
|
this.maxSegmentBytes = options.maxSegmentBytes;
|
|
2548
2434
|
this.maxSegments = options.maxSegments;
|
|
2549
2435
|
this.activePath = state.activePath;
|
|
@@ -2551,13 +2437,12 @@ var SessionEventWriter = class SessionEventWriter {
|
|
|
2551
2437
|
this.segmentCount = state.segmentCount;
|
|
2552
2438
|
}
|
|
2553
2439
|
static async open(record, options = {}) {
|
|
2554
|
-
const lock = await acquireEventsLock(record.acpxRecordId);
|
|
2555
2440
|
const maxSegmentBytes = options.maxSegmentBytes ?? record.eventLog.max_segment_bytes ?? 67108864;
|
|
2556
2441
|
const maxSegments = options.maxSegments ?? record.eventLog.max_segments ?? 5;
|
|
2557
2442
|
const activePath = sessionEventActivePath(record.acpxRecordId);
|
|
2558
2443
|
const activeSizeBytes = await statSize(activePath);
|
|
2559
2444
|
const segmentCount = await resolveInitialSegmentCount(record, maxSegments);
|
|
2560
|
-
return new SessionEventWriter(record,
|
|
2445
|
+
return new SessionEventWriter(record, {
|
|
2561
2446
|
maxSegmentBytes,
|
|
2562
2447
|
maxSegments
|
|
2563
2448
|
}, {
|
|
@@ -2588,7 +2473,11 @@ var SessionEventWriter = class SessionEventWriter {
|
|
|
2588
2473
|
this.segmentCount = Math.min(this.segmentCount + 1, this.maxSegments);
|
|
2589
2474
|
incrementPerfCounter("session.events.rotate");
|
|
2590
2475
|
}
|
|
2591
|
-
await
|
|
2476
|
+
await appendRegularFile({
|
|
2477
|
+
filePath: this.activePath,
|
|
2478
|
+
content: line,
|
|
2479
|
+
mode: 384
|
|
2480
|
+
});
|
|
2592
2481
|
this.activeSizeBytes += lineBytes;
|
|
2593
2482
|
this.record.lastSeq += 1;
|
|
2594
2483
|
if (Object.hasOwn(message, "id")) {
|
|
@@ -2619,11 +2508,132 @@ var SessionEventWriter = class SessionEventWriter {
|
|
|
2619
2508
|
if (options.checkpoint !== false) await writeSessionRecord(this.record);
|
|
2620
2509
|
} finally {
|
|
2621
2510
|
this.closed = true;
|
|
2622
|
-
await releaseEventsLock(this.lock);
|
|
2623
2511
|
}
|
|
2624
2512
|
}
|
|
2625
2513
|
};
|
|
2626
2514
|
//#endregion
|
|
2515
|
+
//#region src/session/turn-ownership.ts
|
|
2516
|
+
const LOCK_RETRY_MS = 15;
|
|
2517
|
+
const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
|
|
2518
|
+
let lastCreatedAt = 0;
|
|
2519
|
+
async function readLock(filePath) {
|
|
2520
|
+
try {
|
|
2521
|
+
const stat = await fs$1.lstat(filePath, { bigint: true });
|
|
2522
|
+
if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
|
|
2523
|
+
return {
|
|
2524
|
+
stat,
|
|
2525
|
+
payload: await fs$1.readFile(filePath, "utf8")
|
|
2526
|
+
};
|
|
2527
|
+
} catch (error) {
|
|
2528
|
+
if (error.code === "ENOENT") return;
|
|
2529
|
+
throw error;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
function lockPid(payload) {
|
|
2533
|
+
try {
|
|
2534
|
+
const pid = JSON.parse(payload)?.pid;
|
|
2535
|
+
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
2536
|
+
} catch {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
async function removeObservedLock(filePath, observed) {
|
|
2541
|
+
const current = await readLock(filePath);
|
|
2542
|
+
if (!current) return true;
|
|
2543
|
+
if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
|
|
2544
|
+
await fs$1.unlink(filePath).catch((error) => {
|
|
2545
|
+
if (error.code !== "ENOENT") throw error;
|
|
2546
|
+
});
|
|
2547
|
+
return true;
|
|
2548
|
+
}
|
|
2549
|
+
async function recoverAbandonedLock(filePath) {
|
|
2550
|
+
const observed = await readLock(filePath);
|
|
2551
|
+
if (!observed) return;
|
|
2552
|
+
const pid = lockPid(observed.payload);
|
|
2553
|
+
if (pid === process.pid || isProcessAlive(pid)) return;
|
|
2554
|
+
if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return;
|
|
2555
|
+
if (await removeObservedLock(filePath, observed)) incrementPerfCounter("session.events.stale_lock_recovered");
|
|
2556
|
+
}
|
|
2557
|
+
async function publishLock(filePath, payload, signal) {
|
|
2558
|
+
try {
|
|
2559
|
+
await withTempFile({
|
|
2560
|
+
rootDir: path.dirname(filePath),
|
|
2561
|
+
prefix: "session-turn",
|
|
2562
|
+
fileName: "lock"
|
|
2563
|
+
}, async (temporaryPath) => {
|
|
2564
|
+
await fs$1.writeFile(temporaryPath, payload, {
|
|
2565
|
+
encoding: "utf8",
|
|
2566
|
+
flag: "wx",
|
|
2567
|
+
mode: 384
|
|
2568
|
+
});
|
|
2569
|
+
signal?.throwIfAborted();
|
|
2570
|
+
try {
|
|
2571
|
+
await fs$1.link(temporaryPath, filePath);
|
|
2572
|
+
} catch (error) {
|
|
2573
|
+
if (!isHardlinkFallbackError(error)) throw error;
|
|
2574
|
+
await fs$1.writeFile(filePath, payload, {
|
|
2575
|
+
encoding: "utf8",
|
|
2576
|
+
flag: "wx",
|
|
2577
|
+
mode: 384
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
2581
|
+
} catch (error) {
|
|
2582
|
+
await removePublishedLock(filePath, payload).catch(() => {});
|
|
2583
|
+
throw error;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
async function removePublishedLock(filePath, payload) {
|
|
2587
|
+
const current = await readLock(filePath);
|
|
2588
|
+
if (current?.payload === payload) await removeObservedLock(filePath, current);
|
|
2589
|
+
}
|
|
2590
|
+
async function observePublishedLock(filePath, payload, signal) {
|
|
2591
|
+
try {
|
|
2592
|
+
const observed = await readLock(filePath);
|
|
2593
|
+
if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
|
|
2594
|
+
signal?.throwIfAborted();
|
|
2595
|
+
return { [Symbol.asyncDispose]: async () => {
|
|
2596
|
+
await removeObservedLock(filePath, observed);
|
|
2597
|
+
} };
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
await removePublishedLock(filePath, payload).catch(() => {});
|
|
2600
|
+
throw error;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
async function acquireSessionTurn(recordId, signal) {
|
|
2604
|
+
const filePath = sessionEventLockPath(recordId);
|
|
2605
|
+
await fs$1.mkdir(path.dirname(filePath), {
|
|
2606
|
+
recursive: true,
|
|
2607
|
+
mode: 448
|
|
2608
|
+
});
|
|
2609
|
+
lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
|
|
2610
|
+
const payload = `${JSON.stringify({
|
|
2611
|
+
pid: process.pid,
|
|
2612
|
+
created_at: new Date(lastCreatedAt).toISOString()
|
|
2613
|
+
}, null, 2)}\n`;
|
|
2614
|
+
for (;;) {
|
|
2615
|
+
signal?.throwIfAborted();
|
|
2616
|
+
try {
|
|
2617
|
+
await publishLock(filePath, payload, signal);
|
|
2618
|
+
break;
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
if (error.code !== "EEXIST") throw error;
|
|
2621
|
+
await recoverAbandonedLock(filePath);
|
|
2622
|
+
await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
return await observePublishedLock(filePath, payload, signal);
|
|
2626
|
+
}
|
|
2627
|
+
//#endregion
|
|
2628
|
+
//#region src/cli/output/discard.ts
|
|
2629
|
+
const DISCARD_OUTPUT_FORMATTER = {
|
|
2630
|
+
setContext() {},
|
|
2631
|
+
onAcpMessage() {},
|
|
2632
|
+
onError() {},
|
|
2633
|
+
onPermissionEscalation() {},
|
|
2634
|
+
flush() {}
|
|
2635
|
+
};
|
|
2636
|
+
//#endregion
|
|
2627
2637
|
//#region src/cli/session/runtime.ts
|
|
2628
2638
|
const INTERRUPT_CANCEL_WAIT_MS = 2500;
|
|
2629
2639
|
var QueueTaskOutputFormatter = class {
|
|
@@ -2662,13 +2672,6 @@ var QueueTaskOutputFormatter = class {
|
|
|
2662
2672
|
}
|
|
2663
2673
|
flush() {}
|
|
2664
2674
|
};
|
|
2665
|
-
const DISCARD_OUTPUT_FORMATTER = {
|
|
2666
|
-
setContext() {},
|
|
2667
|
-
onAcpMessage() {},
|
|
2668
|
-
onError() {},
|
|
2669
|
-
onPermissionEscalation() {},
|
|
2670
|
-
flush() {}
|
|
2671
|
-
};
|
|
2672
2675
|
function markOutputAlreadyEmitted(error, outputAlreadyEmitted) {
|
|
2673
2676
|
if (!outputAlreadyEmitted || !error || typeof error !== "object") return;
|
|
2674
2677
|
error.outputAlreadyEmitted = true;
|
|
@@ -2725,29 +2728,6 @@ function toPromptResult(stopReason, sessionId, client, meta) {
|
|
|
2725
2728
|
function requestedModelId(value) {
|
|
2726
2729
|
return typeof value === "string" ? value.trim() : "";
|
|
2727
2730
|
}
|
|
2728
|
-
function mergeConnectedModelState(state, connectedState) {
|
|
2729
|
-
if (!connectedState) return state;
|
|
2730
|
-
const nextState = cloneSessionAcpxState(state) ?? {};
|
|
2731
|
-
mergeConnectedAdvertisedModelState(nextState, connectedState);
|
|
2732
|
-
mergeConnectedModelPreferences(nextState, connectedState);
|
|
2733
|
-
return nextState;
|
|
2734
|
-
}
|
|
2735
|
-
function mergeConnectedAdvertisedModelState(nextState, connectedState) {
|
|
2736
|
-
if (connectedState.config_options !== void 0) nextState.config_options = structuredClone(connectedState.config_options);
|
|
2737
|
-
else delete nextState.config_options;
|
|
2738
|
-
if (connectedState.current_model_id !== void 0) nextState.current_model_id = connectedState.current_model_id;
|
|
2739
|
-
else delete nextState.current_model_id;
|
|
2740
|
-
if (connectedState.available_models) nextState.available_models = [...connectedState.available_models];
|
|
2741
|
-
else delete nextState.available_models;
|
|
2742
|
-
if (connectedState.model_control) nextState.model_control = connectedState.model_control;
|
|
2743
|
-
else delete nextState.model_control;
|
|
2744
|
-
}
|
|
2745
|
-
function mergeConnectedModelPreferences(nextState, connectedState) {
|
|
2746
|
-
if (connectedState.session_options) nextState.session_options = cloneSessionAcpxState(connectedState)?.session_options;
|
|
2747
|
-
if (connectedState.desired_mode_id !== void 0) nextState.desired_mode_id = connectedState.desired_mode_id;
|
|
2748
|
-
if (connectedState.desired_config_options) nextState.desired_config_options = { ...connectedState.desired_config_options };
|
|
2749
|
-
else delete nextState.desired_config_options;
|
|
2750
|
-
}
|
|
2751
2731
|
async function applyPromptModelIfAdvertised(params) {
|
|
2752
2732
|
const requestedModel = requestedModelId(params.requestedModel);
|
|
2753
2733
|
if (!requestedModel) return;
|
|
@@ -2838,23 +2818,20 @@ function emitPromptDisconnectNotice(snapshot, verbose) {
|
|
|
2838
2818
|
if (!lastExit?.unexpectedDuringPrompt || !verbose) return;
|
|
2839
2819
|
process.stderr.write("[acpx] agent disconnected during prompt (" + lastExit.reason + ", exit=" + lastExit.exitCode + ", signal=" + (lastExit.signal ?? "none") + ")\n");
|
|
2840
2820
|
}
|
|
2841
|
-
function
|
|
2842
|
-
if (
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
suppressSdkConsoleErrors
|
|
2856
|
-
});
|
|
2857
|
-
await waitMs(delayMs);
|
|
2821
|
+
async function preparePromptRetry(error, attempt, maxRetries, hasSideEffects, suppressSdkConsoleErrors) {
|
|
2822
|
+
if (attempt < maxRetries && !hasSideEffects() && isRetryablePromptError(error)) {
|
|
2823
|
+
const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
2824
|
+
emitPromptRetryNotice({
|
|
2825
|
+
error,
|
|
2826
|
+
delayMs,
|
|
2827
|
+
attempt: attempt + 1,
|
|
2828
|
+
maxRetries,
|
|
2829
|
+
suppressSdkConsoleErrors
|
|
2830
|
+
});
|
|
2831
|
+
await waitMs(delayMs);
|
|
2832
|
+
return !hasSideEffects();
|
|
2833
|
+
}
|
|
2834
|
+
return false;
|
|
2858
2835
|
}
|
|
2859
2836
|
function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatter) {
|
|
2860
2837
|
return {
|
|
@@ -2878,6 +2855,7 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
|
|
|
2878
2855
|
onClientClosed: options.onClientClosed,
|
|
2879
2856
|
onPromptActive: options.onPromptActive,
|
|
2880
2857
|
handleProcessInterrupts: options.handleProcessInterrupts,
|
|
2858
|
+
waitSignal: options.waitSignal,
|
|
2881
2859
|
client: options.sharedClient
|
|
2882
2860
|
};
|
|
2883
2861
|
}
|
|
@@ -2919,7 +2897,64 @@ async function runQueuedTask(sessionRecordId, task, options) {
|
|
|
2919
2897
|
task.close();
|
|
2920
2898
|
}
|
|
2921
2899
|
}
|
|
2900
|
+
async function waitForSessionTurn(options) {
|
|
2901
|
+
const waiting = new AbortController();
|
|
2902
|
+
const onInterrupt = () => waiting.abort(new InterruptedError());
|
|
2903
|
+
const signals = [
|
|
2904
|
+
"SIGINT",
|
|
2905
|
+
"SIGTERM",
|
|
2906
|
+
"SIGHUP"
|
|
2907
|
+
];
|
|
2908
|
+
for (const signal of signals) process.once(signal, onInterrupt);
|
|
2909
|
+
const timeoutMs = options.timeoutMs;
|
|
2910
|
+
const timeout = timeoutMs != null && timeoutMs > 0 ? setTimeout(() => waiting.abort(new TimeoutError(timeoutMs)), timeoutMs) : void 0;
|
|
2911
|
+
const signal = options.waitSignal ? AbortSignal.any([waiting.signal, options.waitSignal]) : waiting.signal;
|
|
2912
|
+
try {
|
|
2913
|
+
const { acpxRecordId } = await resolveSessionRecord(options.sessionRecordId);
|
|
2914
|
+
return {
|
|
2915
|
+
recordId: acpxRecordId,
|
|
2916
|
+
ownership: await acquireSessionTurn(acpxRecordId, signal)
|
|
2917
|
+
};
|
|
2918
|
+
} catch (error) {
|
|
2919
|
+
signal.throwIfAborted();
|
|
2920
|
+
throw error;
|
|
2921
|
+
} finally {
|
|
2922
|
+
clearTimeout(timeout);
|
|
2923
|
+
for (const signal of signals) process.off(signal, onInterrupt);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2922
2926
|
async function runSessionPrompt(options) {
|
|
2927
|
+
let turn;
|
|
2928
|
+
try {
|
|
2929
|
+
turn = await waitForSessionTurn(options);
|
|
2930
|
+
} catch (error) {
|
|
2931
|
+
if (options.waitSignal?.aborted && error === options.waitSignal.reason) {
|
|
2932
|
+
const record = await resolveSessionRecord(options.sessionRecordId);
|
|
2933
|
+
return {
|
|
2934
|
+
sessionId: record.acpxRecordId,
|
|
2935
|
+
stopReason: "cancelled",
|
|
2936
|
+
permissionStats: {
|
|
2937
|
+
requested: 0,
|
|
2938
|
+
approved: 0,
|
|
2939
|
+
denied: 0,
|
|
2940
|
+
cancelled: 0
|
|
2941
|
+
},
|
|
2942
|
+
record,
|
|
2943
|
+
resumed: false
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
throw error;
|
|
2947
|
+
}
|
|
2948
|
+
try {
|
|
2949
|
+
return await runOwnedSessionPrompt({
|
|
2950
|
+
...options,
|
|
2951
|
+
sessionRecordId: turn.recordId
|
|
2952
|
+
});
|
|
2953
|
+
} finally {
|
|
2954
|
+
await turn.ownership[Symbol.asyncDispose]();
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
async function runOwnedSessionPrompt(options) {
|
|
2923
2958
|
const stopTotalTimer = startPerfTimer("runtime.prompt.total");
|
|
2924
2959
|
const output = options.outputFormatter;
|
|
2925
2960
|
const shouldMarkAcpErrorsEmitted = rendersAcpErrors(options.errorEmissionPolicy);
|
|
@@ -2927,13 +2962,12 @@ async function runSessionPrompt(options) {
|
|
|
2927
2962
|
return await resolveSessionRecord(options.sessionRecordId);
|
|
2928
2963
|
});
|
|
2929
2964
|
const conversation = cloneSessionConversation(record);
|
|
2930
|
-
|
|
2965
|
+
record.acpx = cloneSessionAcpxState(record.acpx);
|
|
2931
2966
|
const promptStartedAt = isoNow();
|
|
2932
2967
|
const promptMessageId = recordPromptSubmission(conversation, options.prompt, promptStartedAt);
|
|
2933
2968
|
record.lastPromptAt = promptStartedAt;
|
|
2934
2969
|
record.lastUsedAt = promptStartedAt;
|
|
2935
2970
|
applyConversation(record, conversation);
|
|
2936
|
-
record.acpx = acpxState;
|
|
2937
2971
|
await writeSessionRecord(record);
|
|
2938
2972
|
output.setContext({ sessionId: record.acpxRecordId });
|
|
2939
2973
|
const eventWriter = await measurePerf("session.events.open", async () => {
|
|
@@ -2975,7 +3009,6 @@ async function runSessionPrompt(options) {
|
|
|
2975
3009
|
await flushPendingMessages(false);
|
|
2976
3010
|
record.lastUsedAt = isoNow();
|
|
2977
3011
|
applyConversation(record, conversation);
|
|
2978
|
-
record.acpx = acpxState;
|
|
2979
3012
|
await preserveClosedState();
|
|
2980
3013
|
await eventWriter.checkpoint();
|
|
2981
3014
|
},
|
|
@@ -3026,14 +3059,14 @@ async function runSessionPrompt(options) {
|
|
|
3026
3059
|
},
|
|
3027
3060
|
onSessionUpdate: (notification) => {
|
|
3028
3061
|
if (promptTurnActive) promptTurnHadSideEffects = true;
|
|
3029
|
-
|
|
3062
|
+
record.acpx = recordSessionUpdate(conversation, record.acpx, notification);
|
|
3030
3063
|
trimConversationForRuntime(conversation);
|
|
3031
3064
|
liveCheckpoint.request();
|
|
3032
3065
|
options.onSessionUpdate?.(notification);
|
|
3033
3066
|
},
|
|
3034
3067
|
onClientOperation: (operation) => {
|
|
3035
3068
|
if (promptTurnActive) promptTurnHadSideEffects = true;
|
|
3036
|
-
|
|
3069
|
+
record.acpx = recordClientOperation(conversation, record.acpx, operation);
|
|
3037
3070
|
trimConversationForRuntime(conversation);
|
|
3038
3071
|
liveCheckpoint.request();
|
|
3039
3072
|
options.onClientOperation?.(operation);
|
|
@@ -3052,15 +3085,15 @@ async function runSessionPrompt(options) {
|
|
|
3052
3085
|
await client.setSessionMode(activeSessionIdForControl, modeId);
|
|
3053
3086
|
},
|
|
3054
3087
|
setSessionModel: async (modelId) => {
|
|
3055
|
-
const models = advertisedModelState(
|
|
3088
|
+
const models = advertisedModelState(record.acpx);
|
|
3056
3089
|
const response = await client.setSessionModel(activeSessionIdForControl, modelId, models);
|
|
3057
|
-
|
|
3090
|
+
record.acpx = applyModelSelection(record.acpx, modelId, response);
|
|
3058
3091
|
return response;
|
|
3059
3092
|
},
|
|
3060
3093
|
setSessionConfigOption: async (configId, value) => {
|
|
3061
|
-
const modelConfigId = advertisedModelState(
|
|
3094
|
+
const modelConfigId = advertisedModelState(record.acpx)?.configId;
|
|
3062
3095
|
const response = await client.setSessionConfigOption(activeSessionIdForControl, configId, value);
|
|
3063
|
-
|
|
3096
|
+
record.acpx = applyConfigOptionSelection(record.acpx, configId, value, response, modelConfigId);
|
|
3064
3097
|
return response;
|
|
3065
3098
|
}
|
|
3066
3099
|
};
|
|
@@ -3095,7 +3128,6 @@ async function runSessionPrompt(options) {
|
|
|
3095
3128
|
}
|
|
3096
3129
|
});
|
|
3097
3130
|
});
|
|
3098
|
-
acpxState = mergeConnectedModelState(acpxState, record.acpx);
|
|
3099
3131
|
flushConnectOutput(connected.loadError);
|
|
3100
3132
|
emitConnectPerfMetric(connectStartedAt, options.verbose);
|
|
3101
3133
|
return connected;
|
|
@@ -3134,11 +3166,8 @@ async function runSessionPrompt(options) {
|
|
|
3134
3166
|
};
|
|
3135
3167
|
const handlePromptFailure = async (error, attempt) => {
|
|
3136
3168
|
const snapshot = client.getAgentLifecycleSnapshot();
|
|
3137
|
-
if (
|
|
3138
|
-
|
|
3139
|
-
return promptTurnHadSideEffects ? await failRuntimePrompt(error, snapshot) : "retry";
|
|
3140
|
-
}
|
|
3141
|
-
return await failRuntimePrompt(error, snapshot);
|
|
3169
|
+
if (snapshot.lastExit?.unexpectedDuringPrompt !== true && await preparePromptRetry(error, attempt, options.promptRetries ?? 0, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors)) return;
|
|
3170
|
+
await failRuntimePrompt(error, snapshot);
|
|
3142
3171
|
};
|
|
3143
3172
|
const failRuntimePrompt = async (error, snapshot) => {
|
|
3144
3173
|
promptTurnActive = false;
|
|
@@ -3153,7 +3182,6 @@ async function runSessionPrompt(options) {
|
|
|
3153
3182
|
output.flush();
|
|
3154
3183
|
record.lastUsedAt = isoNow();
|
|
3155
3184
|
applyConversation(record, conversation);
|
|
3156
|
-
record.acpx = acpxState;
|
|
3157
3185
|
const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
|
|
3158
3186
|
attachAcpErrorPayload(propagated, normalizedError.acp);
|
|
3159
3187
|
propagated.outputAlreadyEmitted = matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted;
|
|
@@ -3165,7 +3193,7 @@ async function runSessionPrompt(options) {
|
|
|
3165
3193
|
for (let attempt = 0;; attempt++) try {
|
|
3166
3194
|
return await runPromptAttempt(sessionId, attempt);
|
|
3167
3195
|
} catch (error) {
|
|
3168
|
-
|
|
3196
|
+
await handlePromptFailure(error, attempt);
|
|
3169
3197
|
}
|
|
3170
3198
|
};
|
|
3171
3199
|
const savePromptSuccess = async (response) => {
|
|
@@ -3178,7 +3206,6 @@ async function runSessionPrompt(options) {
|
|
|
3178
3206
|
record.protocolVersion = client.initializeResult?.protocolVersion;
|
|
3179
3207
|
record.agentCapabilities = client.initializeResult?.agentCapabilities;
|
|
3180
3208
|
applyConversation(record, conversation);
|
|
3181
|
-
record.acpx = acpxState;
|
|
3182
3209
|
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
3183
3210
|
stopTotalTimer();
|
|
3184
3211
|
return response;
|
|
@@ -3193,7 +3220,6 @@ async function runSessionPrompt(options) {
|
|
|
3193
3220
|
timeoutMs: options.timeoutMs,
|
|
3194
3221
|
suppressWarnings: options.suppressSdkConsoleErrors
|
|
3195
3222
|
});
|
|
3196
|
-
acpxState = cloneSessionAcpxState(record.acpx);
|
|
3197
3223
|
output.setContext({ sessionId: record.acpxRecordId });
|
|
3198
3224
|
await liveCheckpoint.checkpoint();
|
|
3199
3225
|
const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
|
|
@@ -3210,7 +3236,6 @@ async function runSessionPrompt(options) {
|
|
|
3210
3236
|
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
3211
3237
|
record.lastUsedAt = isoNow();
|
|
3212
3238
|
applyConversation(record, conversation);
|
|
3213
|
-
record.acpx = acpxState;
|
|
3214
3239
|
await flushPendingMessages(false).catch(() => {});
|
|
3215
3240
|
if (closeClientOnExit) await client.close();
|
|
3216
3241
|
};
|
|
@@ -3233,7 +3258,6 @@ async function runSessionPrompt(options) {
|
|
|
3233
3258
|
if (closeClientOnExit) await client.close();
|
|
3234
3259
|
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
3235
3260
|
applyConversation(record, conversation);
|
|
3236
|
-
record.acpx = acpxState;
|
|
3237
3261
|
await liveCheckpoint.flush().catch(() => {});
|
|
3238
3262
|
await flushPendingMessages(false).catch(() => {});
|
|
3239
3263
|
await preserveClosedState().catch(() => {});
|
|
@@ -3290,10 +3314,7 @@ async function runOnce(options) {
|
|
|
3290
3314
|
for (let attempt = 0;; attempt++) try {
|
|
3291
3315
|
return await runExecPromptAttempt(sessionId);
|
|
3292
3316
|
} catch (error) {
|
|
3293
|
-
if (
|
|
3294
|
-
await waitBeforePromptRetry(error, attempt, maxRetries, options.suppressSdkConsoleErrors);
|
|
3295
|
-
if (!promptTurnHadSideEffects) continue;
|
|
3296
|
-
}
|
|
3317
|
+
if (await preparePromptRetry(error, attempt, maxRetries, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors)) continue;
|
|
3297
3318
|
promptTurnActive = false;
|
|
3298
3319
|
throw error;
|
|
3299
3320
|
}
|
|
@@ -3456,19 +3477,17 @@ function logDeferredCancelFailure(error, verbose) {
|
|
|
3456
3477
|
process.stderr.write(`[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}\n`);
|
|
3457
3478
|
}
|
|
3458
3479
|
function queueOwnerExitIsFatal(exit) {
|
|
3459
|
-
return exit.exited && (exit.spawnError !== void 0 || exit.code !== 0 || exit.signal !== null);
|
|
3480
|
+
return exit.exited && (exit.spawnError !== void 0 || exit.inputError !== void 0 || exit.code !== 0 || exit.signal !== null);
|
|
3460
3481
|
}
|
|
3461
3482
|
function logQueueOwnerReady(params) {
|
|
3462
3483
|
if (!params.verbose) return;
|
|
3463
3484
|
process.stderr.write(`[acpx] queue owner ready for session ${params.sessionId} (ttlMs=${params.ttlMs}, maxQueueDepth=${params.maxQueueDepth})\n`);
|
|
3464
3485
|
}
|
|
3465
3486
|
async function closeQueueOwnerRuntime(params) {
|
|
3466
|
-
if (params.heartbeatTimer) clearInterval(params.heartbeatTimer);
|
|
3467
3487
|
params.turnController.beginClosing();
|
|
3468
3488
|
await params.sharedClient.close().catch(() => {});
|
|
3469
3489
|
await params.owner?.close();
|
|
3470
3490
|
await writeQueueOwnerLifecycleSnapshot(params.sessionId, params.sharedClient);
|
|
3471
|
-
await releaseQueueOwnerLease(params.lease);
|
|
3472
3491
|
if (params.verbose) process.stderr.write(`[acpx] queue owner stopped for session ${params.sessionId}\n`);
|
|
3473
3492
|
}
|
|
3474
3493
|
async function writeQueueOwnerLifecycleSnapshot(sessionId, sharedClient) {
|
|
@@ -3525,9 +3544,7 @@ function createQueueOwnerShutdownController(params) {
|
|
|
3525
3544
|
shutdownPromise ??= (async () => {
|
|
3526
3545
|
await activeTurnShutdown;
|
|
3527
3546
|
await closeQueueOwnerRuntime({
|
|
3528
|
-
lease: params.lease,
|
|
3529
3547
|
owner: params.getOwner(),
|
|
3530
|
-
heartbeatTimer: void 0,
|
|
3531
3548
|
turnController: params.turnController,
|
|
3532
3549
|
sharedClient: params.sharedClient,
|
|
3533
3550
|
sessionId: params.sessionId,
|
|
@@ -3553,6 +3570,13 @@ async function runSessionQueueOwner(options) {
|
|
|
3553
3570
|
fingerprint: options.mcpConfigFingerprint
|
|
3554
3571
|
});
|
|
3555
3572
|
if (!lease) return;
|
|
3573
|
+
try {
|
|
3574
|
+
await runQueueOwnerRuntime(options, lease);
|
|
3575
|
+
} finally {
|
|
3576
|
+
await releaseQueueOwnerLease(lease);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
async function runQueueOwnerRuntime(options, lease) {
|
|
3556
3580
|
const sessionRecord = await resolveSessionRecord(options.sessionId);
|
|
3557
3581
|
let owner;
|
|
3558
3582
|
let heartbeatTimer;
|
|
@@ -3584,15 +3608,14 @@ async function runSessionQueueOwner(options) {
|
|
|
3584
3608
|
return true;
|
|
3585
3609
|
};
|
|
3586
3610
|
const runPromptTurn = async (run) => {
|
|
3587
|
-
turnController.beginTurn();
|
|
3611
|
+
const waitSignal = turnController.beginTurn();
|
|
3588
3612
|
try {
|
|
3589
|
-
return await run();
|
|
3613
|
+
return await run(waitSignal);
|
|
3590
3614
|
} finally {
|
|
3591
3615
|
turnController.endTurn();
|
|
3592
3616
|
}
|
|
3593
3617
|
};
|
|
3594
3618
|
const shutdown = createQueueOwnerShutdownController({
|
|
3595
|
-
lease,
|
|
3596
3619
|
getOwner: () => owner,
|
|
3597
3620
|
stopHeartbeat: () => {
|
|
3598
3621
|
if (heartbeatTimer) {
|
|
@@ -3652,7 +3675,7 @@ async function runSessionQueueOwner(options) {
|
|
|
3652
3675
|
const task = await owner.nextTask(pollTimeoutMs);
|
|
3653
3676
|
if (!task) break;
|
|
3654
3677
|
isFirstTask = false;
|
|
3655
|
-
const turnPromise = runPromptTurn(async () => {
|
|
3678
|
+
const turnPromise = runPromptTurn(async (waitSignal) => {
|
|
3656
3679
|
try {
|
|
3657
3680
|
await runQueuedTask(options.sessionId, task, {
|
|
3658
3681
|
sharedClient,
|
|
@@ -3670,7 +3693,8 @@ async function runSessionQueueOwner(options) {
|
|
|
3670
3693
|
turnController.markPromptActive();
|
|
3671
3694
|
await applyPendingCancel();
|
|
3672
3695
|
},
|
|
3673
|
-
handleProcessInterrupts: false
|
|
3696
|
+
handleProcessInterrupts: false,
|
|
3697
|
+
waitSignal
|
|
3674
3698
|
});
|
|
3675
3699
|
} finally {
|
|
3676
3700
|
checkpointPerfMetricsCapture();
|
|
@@ -3749,7 +3773,6 @@ var session_exports = /* @__PURE__ */ __exportAll({
|
|
|
3749
3773
|
listAgentSessions: () => listAgentSessions,
|
|
3750
3774
|
listSessions: () => listSessions,
|
|
3751
3775
|
listSessionsForAgent: () => listSessionsForAgent,
|
|
3752
|
-
mergeConnectedModelState: () => mergeConnectedModelState,
|
|
3753
3776
|
normalizeQueueOwnerTtlMs: () => normalizeQueueOwnerTtlMs,
|
|
3754
3777
|
pruneSessions: () => pruneSessions,
|
|
3755
3778
|
queueOwnerRuntimeTestInternals: () => queueOwnerRuntimeTestInternals,
|
|
@@ -4019,13 +4042,7 @@ function isFinalStatus(status) {
|
|
|
4019
4042
|
return status === "completed" || status === "failed";
|
|
4020
4043
|
}
|
|
4021
4044
|
function toStatusLabel(status) {
|
|
4022
|
-
|
|
4023
|
-
case "in_progress": return "running";
|
|
4024
|
-
case "pending": return "pending";
|
|
4025
|
-
case "completed": return "completed";
|
|
4026
|
-
case "failed": return "failed";
|
|
4027
|
-
default: return "running";
|
|
4028
|
-
}
|
|
4045
|
+
return isFinalStatus(status) || status === "pending" ? status : "running";
|
|
4029
4046
|
}
|
|
4030
4047
|
function asRecord(value) {
|
|
4031
4048
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
@@ -4058,14 +4075,7 @@ function indentBlock(value, prefix) {
|
|
|
4058
4075
|
return value.split("\n").map((line) => `${prefix}${line}`).join("\n");
|
|
4059
4076
|
}
|
|
4060
4077
|
function dedupeStrings(values) {
|
|
4061
|
-
|
|
4062
|
-
const result = [];
|
|
4063
|
-
for (const value of values) {
|
|
4064
|
-
if (seen.has(value)) continue;
|
|
4065
|
-
seen.add(value);
|
|
4066
|
-
result.push(value);
|
|
4067
|
-
}
|
|
4068
|
-
return result;
|
|
4078
|
+
return [...new Set(values)];
|
|
4069
4079
|
}
|
|
4070
4080
|
function safeJson(value, spacing) {
|
|
4071
4081
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
@@ -4374,7 +4384,6 @@ var TextOutputFormatter = class {
|
|
|
4374
4384
|
thoughtBuffer = "";
|
|
4375
4385
|
wroteAny = false;
|
|
4376
4386
|
atLineStart = true;
|
|
4377
|
-
section = null;
|
|
4378
4387
|
constructor(stdout, suppressReads) {
|
|
4379
4388
|
this.stdout = stdout;
|
|
4380
4389
|
this.useColor = Boolean(stdout.isTTY);
|
|
@@ -4382,6 +4391,12 @@ var TextOutputFormatter = class {
|
|
|
4382
4391
|
}
|
|
4383
4392
|
setContext(_context) {}
|
|
4384
4393
|
onAcpMessage(message) {
|
|
4394
|
+
const notice = parsePermissionNotice(message);
|
|
4395
|
+
if (notice) {
|
|
4396
|
+
this.beginSection();
|
|
4397
|
+
this.writeLine(`${this.bold("[permission]")} ${notice}`);
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4385
4400
|
const notification = extractSessionUpdateNotification(message);
|
|
4386
4401
|
if (notification) {
|
|
4387
4402
|
this.renderSessionUpdate(notification);
|
|
@@ -4417,7 +4432,7 @@ var TextOutputFormatter = class {
|
|
|
4417
4432
|
renderSessionUpdateBody(update) {
|
|
4418
4433
|
switch (update.sessionUpdate) {
|
|
4419
4434
|
case "agent_message_chunk":
|
|
4420
|
-
if (update.content.type === "text") this.
|
|
4435
|
+
if (update.content.type === "text") this.write(update.content.text);
|
|
4421
4436
|
return;
|
|
4422
4437
|
case "agent_thought_chunk":
|
|
4423
4438
|
if (update.content.type === "text") this.thoughtBuffer += update.content.text;
|
|
@@ -4435,24 +4450,24 @@ var TextOutputFormatter = class {
|
|
|
4435
4450
|
}
|
|
4436
4451
|
}
|
|
4437
4452
|
renderPlanUpdate(entries) {
|
|
4438
|
-
this.beginSection(
|
|
4453
|
+
this.beginSection();
|
|
4439
4454
|
this.writeLine(this.bold("[plan]"));
|
|
4440
4455
|
for (const entry of entries) this.writeLine(` - [${entry.status}] ${entry.content}`);
|
|
4441
4456
|
}
|
|
4442
4457
|
renderDone(stopReason) {
|
|
4443
4458
|
this.flushThoughtBuffer();
|
|
4444
|
-
this.beginSection(
|
|
4459
|
+
this.beginSection();
|
|
4445
4460
|
this.writeLine(this.dim(`[done] ${stopReason}`));
|
|
4446
4461
|
}
|
|
4447
4462
|
onError(params) {
|
|
4448
4463
|
this.flushThoughtBuffer();
|
|
4449
|
-
this.beginSection(
|
|
4464
|
+
this.beginSection();
|
|
4450
4465
|
this.writeLine(this.formatAnsi(`[error] ${params.code}: ${params.message}`, "31"));
|
|
4451
4466
|
for (const hint of getTextErrorRemediationHints(params)) this.writeLine(this.dim(hint));
|
|
4452
4467
|
}
|
|
4453
4468
|
onClientOperation(operation) {
|
|
4454
4469
|
this.flushThoughtBuffer();
|
|
4455
|
-
this.beginSection(
|
|
4470
|
+
this.beginSection();
|
|
4456
4471
|
const normalizedStatus = operation.status === "completed" ? "completed" : operation.status === "failed" ? "failed" : "in_progress";
|
|
4457
4472
|
const statusText = this.colorStatus(operation.status, normalizedStatus);
|
|
4458
4473
|
this.writeLine(`${this.bold("[client]")} ${operation.summary} (${statusText})`);
|
|
@@ -4463,7 +4478,7 @@ var TextOutputFormatter = class {
|
|
|
4463
4478
|
}
|
|
4464
4479
|
onPermissionEscalation(event) {
|
|
4465
4480
|
this.flushThoughtBuffer();
|
|
4466
|
-
this.beginSection(
|
|
4481
|
+
this.beginSection();
|
|
4467
4482
|
this.writeLine(`${this.bold("[permission]")} ${event.message}`);
|
|
4468
4483
|
const details = [
|
|
4469
4484
|
`sessionId: ${event.sessionId}`,
|
|
@@ -4489,21 +4504,15 @@ var TextOutputFormatter = class {
|
|
|
4489
4504
|
writeLine(line) {
|
|
4490
4505
|
this.write(`${line}\n`);
|
|
4491
4506
|
}
|
|
4492
|
-
beginSection(
|
|
4507
|
+
beginSection() {
|
|
4493
4508
|
if (!this.atLineStart) this.write("\n");
|
|
4494
4509
|
if (this.wroteAny) this.write("\n");
|
|
4495
|
-
this.section = next;
|
|
4496
|
-
}
|
|
4497
|
-
writeAssistantChunk(text) {
|
|
4498
|
-
if (!text) return;
|
|
4499
|
-
this.section = "assistant";
|
|
4500
|
-
this.write(text);
|
|
4501
4510
|
}
|
|
4502
4511
|
flushThoughtBuffer() {
|
|
4503
4512
|
const thought = truncate(normalizeLineEndings(this.thoughtBuffer).trim(), MAX_THOUGHT_CHARS);
|
|
4504
4513
|
this.thoughtBuffer = "";
|
|
4505
4514
|
if (!thought) return;
|
|
4506
|
-
this.beginSection(
|
|
4515
|
+
this.beginSection();
|
|
4507
4516
|
const [firstLine, ...restLines] = thought.split("\n");
|
|
4508
4517
|
this.writeLine(this.dim(`[thinking] ${firstLine}`));
|
|
4509
4518
|
for (const line of restLines) this.writeLine(this.dim(` ${line}`));
|
|
@@ -4512,17 +4521,25 @@ var TextOutputFormatter = class {
|
|
|
4512
4521
|
const state = this.getOrCreateToolState(update.toolCallId);
|
|
4513
4522
|
this.mergeToolState(state, update);
|
|
4514
4523
|
const status = asStatus(state.status);
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4524
|
+
const final = isFinalStatus(status);
|
|
4525
|
+
if (!final && state.startedPrinted) return;
|
|
4526
|
+
const display = {
|
|
4527
|
+
input: summarizeToolInput(state.rawInput),
|
|
4528
|
+
files: formatLocations(state.locations),
|
|
4529
|
+
output: final ? renderToolOutput(state, this.suppressReads) : void 0
|
|
4530
|
+
};
|
|
4531
|
+
if (final) {
|
|
4532
|
+
const payload = {
|
|
4533
|
+
title: state.title,
|
|
4534
|
+
status: state.status,
|
|
4535
|
+
kind: state.kind,
|
|
4536
|
+
...display
|
|
4537
|
+
};
|
|
4538
|
+
const signature = safeJson(payload, 0) ?? JSON.stringify(payload);
|
|
4539
|
+
if (signature === state.finalSignature) return;
|
|
4540
|
+
state.finalSignature = signature;
|
|
4541
|
+
} else state.startedPrinted = true;
|
|
4542
|
+
this.renderToolState(state, status, display);
|
|
4526
4543
|
}
|
|
4527
4544
|
getOrCreateToolState(toolCallId) {
|
|
4528
4545
|
const existing = this.toolStates.get(toolCallId);
|
|
@@ -4549,42 +4566,17 @@ var TextOutputFormatter = class {
|
|
|
4549
4566
|
if (update.rawOutput !== void 0) state.rawOutput = update.rawOutput;
|
|
4550
4567
|
if (update.content !== void 0) state.content = update.content;
|
|
4551
4568
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
title: state.title,
|
|
4555
|
-
status: state.status,
|
|
4556
|
-
kind: state.kind,
|
|
4557
|
-
input: summarizeToolInput(state.rawInput),
|
|
4558
|
-
files: formatLocations(state.locations),
|
|
4559
|
-
output: renderToolOutput(state, this.suppressReads)
|
|
4560
|
-
};
|
|
4561
|
-
return safeJson(signaturePayload, 0) ?? JSON.stringify(signaturePayload);
|
|
4562
|
-
}
|
|
4563
|
-
renderStartingToolState(state, status) {
|
|
4564
|
-
this.beginSection("tool");
|
|
4565
|
-
const title = state.title ?? state.id;
|
|
4566
|
-
const label = status === "pending" ? "pending" : "running";
|
|
4567
|
-
const statusText = this.colorStatus(label, status);
|
|
4568
|
-
this.writeLine(`${this.bold("[tool]")} ${title} (${statusText})`);
|
|
4569
|
-
const input = summarizeToolInput(state.rawInput);
|
|
4570
|
-
if (input) this.writeLine(` input: ${input}`);
|
|
4571
|
-
const files = formatLocations(state.locations);
|
|
4572
|
-
if (files) this.writeLine(` files: ${files}`);
|
|
4573
|
-
}
|
|
4574
|
-
renderFinalToolState(state, status) {
|
|
4575
|
-
this.beginSection("tool");
|
|
4569
|
+
renderToolState(state, status, display) {
|
|
4570
|
+
this.beginSection();
|
|
4576
4571
|
const title = state.title ?? state.id;
|
|
4577
4572
|
const statusText = this.colorStatus(toStatusLabel(status), status);
|
|
4578
4573
|
this.writeLine(`${this.bold("[tool]")} ${title} (${statusText})`);
|
|
4579
|
-
if (state.kind) this.writeLine(` kind: ${state.kind}`);
|
|
4580
|
-
|
|
4581
|
-
if (
|
|
4582
|
-
|
|
4583
|
-
if (files) this.writeLine(` files: ${files}`);
|
|
4584
|
-
const output = renderToolOutput(state, this.suppressReads);
|
|
4585
|
-
if (output) {
|
|
4574
|
+
if (isFinalStatus(status) && state.kind) this.writeLine(` kind: ${state.kind}`);
|
|
4575
|
+
if (display.input) this.writeLine(` input: ${display.input}`);
|
|
4576
|
+
if (display.files) this.writeLine(` files: ${display.files}`);
|
|
4577
|
+
if (display.output) {
|
|
4586
4578
|
this.writeLine(" output:");
|
|
4587
|
-
this.writeLine(indentBlock(limitOutputBlock(output), " "));
|
|
4579
|
+
this.writeLine(indentBlock(limitOutputBlock(display.output), " "));
|
|
4588
4580
|
}
|
|
4589
4581
|
}
|
|
4590
4582
|
formatAnsi(text, code) {
|
|
@@ -4618,6 +4610,11 @@ var QuietOutputFormatter = class {
|
|
|
4618
4610
|
}
|
|
4619
4611
|
setContext(_context) {}
|
|
4620
4612
|
onAcpMessage(message) {
|
|
4613
|
+
const notice = parsePermissionNotice(message);
|
|
4614
|
+
if (notice) {
|
|
4615
|
+
this.stderr.write(`[acpx] permission: ${notice.replace(/\r\n?|\n/g, " ")}\n`);
|
|
4616
|
+
return;
|
|
4617
|
+
}
|
|
4621
4618
|
const update = extractSessionUpdateNotification(message);
|
|
4622
4619
|
if (update?.update.sessionUpdate === "agent_message_chunk" && update.update.content.type === "text") {
|
|
4623
4620
|
this.chunks.push(update.update.content.text);
|
|
@@ -4709,6 +4706,6 @@ function createOutputFormatter(format, options = {}) {
|
|
|
4709
4706
|
}
|
|
4710
4707
|
}
|
|
4711
4708
|
//#endregion
|
|
4712
|
-
export { runSessionQueueOwner as a,
|
|
4709
|
+
export { runSessionQueueOwner as a, DISCARD_OUTPUT_FORMATTER as c, installPerfMetricsCapture as d, createSessionWithClient as f, __exportAll as g, DEFAULT_QUEUE_OWNER_TTL_MS as h, session_exports as i, buildQueueOwnerArgOverride as l, probeQueueOwnerHealth as m, getTextErrorRemediationHints as n, runOnce as o, cancelSessionPrompt as p, output_exports as r, sendSessionDirect as s, createOutputFormatter as t, flushPerfMetricsCapture as u };
|
|
4713
4710
|
|
|
4714
|
-
//# sourceMappingURL=output-
|
|
4711
|
+
//# sourceMappingURL=output-uo5a27KB.js.map
|