acpx 0.19.2 → 0.19.4
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-DWlx5BQ4.js → agent-registry-kHdpsK9F.js} +35 -21
- package/dist/agent-registry-kHdpsK9F.js.map +1 -0
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-D-s_eTUy.js → cli-DCtHeMEi.js} +34 -46
- package/dist/cli-DCtHeMEi.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +57 -40
- package/dist/cli.js.map +1 -1
- package/dist/{client-BchvQRpz.d.ts → client-DqCXdF0Q.d.ts} +6 -18
- package/dist/client-DqCXdF0Q.d.ts.map +1 -0
- package/dist/{flows-DdxEaeZo.js → flows--1zpaEh9.js} +38 -14
- package/dist/flows--1zpaEh9.js.map +1 -0
- package/dist/flows.d.ts +2 -2
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-HjxSBsK0.js → invocation-options-CvkenzCc.js} +6 -12
- package/dist/invocation-options-CvkenzCc.js.map +1 -0
- package/dist/{ipc-D0ZMQ0q9.js → ipc-D7zT86b9.js} +430 -307
- package/dist/ipc-D7zT86b9.js.map +1 -0
- package/dist/{output-GQnhkJMU.js → output-lylSoEBd.js} +12 -4
- package/dist/output-lylSoEBd.js.map +1 -0
- package/dist/{queue-owner-runtime-W0lly6ap.js → queue-owner-runtime-C0wIYowh.js} +766 -547
- package/dist/queue-owner-runtime-C0wIYowh.js.map +1 -0
- package/dist/runtime.d.ts +23 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +52 -22
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-Dp6mzNSy.d.ts → session-options-X7LhX9BF.d.ts} +2 -1
- package/dist/{session-options-Dp6mzNSy.d.ts.map → session-options-X7LhX9BF.d.ts.map} +1 -1
- package/dist/{session-watch-_8yxQ6ba.js → session-watch-DPOwDRf8.js} +4 -4
- package/dist/{session-watch-_8yxQ6ba.js.map → session-watch-DPOwDRf8.js.map} +1 -1
- package/dist/{watch-C0mwpJcN.js → watch-H7rNOJtv.js} +2 -2
- package/dist/{watch-C0mwpJcN.js.map → watch-H7rNOJtv.js.map} +1 -1
- package/package.json +17 -16
- package/skills/acpx/SKILL.md +13 -3
- package/dist/agent-registry-DWlx5BQ4.js.map +0 -1
- package/dist/cli-D-s_eTUy.js.map +0 -1
- package/dist/client-BchvQRpz.d.ts.map +0 -1
- package/dist/flows-DdxEaeZo.js.map +0 -1
- package/dist/invocation-options-HjxSBsK0.js.map +0 -1
- package/dist/ipc-D0ZMQ0q9.js.map +0 -1
- package/dist/output-GQnhkJMU.js.map +0 -1
- package/dist/queue-owner-runtime-W0lly6ap.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as resetPerfMetrics, Bt as
|
|
2
|
-
import { A as requireAgentStdio, C as PROCESS_HELPER_TIMEOUT_MS, D as isoNow$2, E as isChildProcessRunning, F as waitForChildExit, I as waitForSpawn, M as resolveAgentSessionCwd, N as runTimedExecFile, P as splitCommandLine, T as basenameToken, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentCommandParts, v as buildTerminalShellSpawnCommand, w as asAbsoluteCwd, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-
|
|
1
|
+
import { $ as resetPerfMetrics, Bt as GeminiAcpStartupTimeoutError, C as textPrompt, Ct as normalizeOutputError, F as normalizeName, Ft as AgentSpawnError, G as SESSION_JOURNAL_SCHEMA, Gt as SessionModeReplayError, Ht as PermissionPromptUnavailableError, It as AgentStartupError, Jt as SessionResumeRequiredError, K as SessionJournalReader, Kt as SessionModelReplayError, L as readSessionRecord, Lt as AuthPolicyError, M as isoNow$1, Nt as AcpxOperationalError, O as absolutePath, Pt as AgentDisconnectedError, Q as measurePerf, R as resolveSessionRecord, Rt as ClaudeAcpSessionCreateTimeoutError, S as promptToDisplayText, St as isRetryablePromptError, T as lockOwnerPid, Tt as isAcpResourceNotFoundError, U as extractAgentSessionId, Ut as QueueConnectionError, Vt as PermissionDeniedError, W as normalizeAgentSessionId, Wt as SessionConfigOptionReplayError, X as getPerfMetricsSnapshot, Y as formatPerfMetric, Yt as UnsupportedPromptContentError, Z as incrementPerfCounter, _ as ProcessDescendants, a as trySetModelOnRunningOwner, at as isSessionUpdateNotification, bt as formatErrorMessage, d as releaseQueueOwnerLease, dt as sessionBaseDir, et as setPerfGauge, f as terminateProcess, ft as sessionEventActivePath, g as QueueLeaseGuardSettlementError, h as waitMs$1, i as trySetModeOnRunningOwner, it as isAcpMessageObject, j as findSessionByDirectoryWalk, k as findGitRepositoryRoot, m as tryAcquireQueueOwnerLease, mt as sessionEventSegmentPath, n as tryCloseSessionOnRunningOwner, o as trySubmitToRunningOwner, p as terminateQueueOwnerForSession, pt as sessionEventLockPath, r as trySetConfigOptionOnRunningOwner, rt as isAcpJsonRpcMessage, s as SessionQueueOwner, t as tryCancelOnRunningOwner, tt as startPerfTimer, u as refreshQueueOwnerLease, ut as defaultSessionEventLog, vt as isProcessAlive, w as createLockOwner, wt as extractAcpError, xt as isAcpQueryClosedBeforeResponseError, y as getUnsupportedPromptContentMessage, z as writeSessionRecord, zt as CopilotAcpUnsupportedError } from "./ipc-D7zT86b9.js";
|
|
2
|
+
import { A as requireAgentStdio, C as PROCESS_HELPER_TIMEOUT_MS, D as isoNow$2, E as isChildProcessRunning, F as waitForChildExit, I as waitForSpawn, M as resolveAgentSessionCwd, N as runTimedExecFile, P as splitCommandLine, T as basenameToken, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentCommandParts, v as buildTerminalShellSpawnCommand, w as asAbsoluteCwd, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-kHdpsK9F.js";
|
|
3
3
|
import fs, { readFileSync, realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import path from "node:path";
|
|
@@ -8,14 +8,14 @@ import fs$1 from "node:fs/promises";
|
|
|
8
8
|
import { appendRegularFile, appendRegularFileSync, withTempFile } from "@openclaw/fs-safe/advanced";
|
|
9
9
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
|
+
import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
|
|
12
|
+
import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
|
|
11
13
|
import { Readable, Writable } from "node:stream";
|
|
12
14
|
import { PROTOCOL_VERSION, RequestError, client, methods } from "@agentclientprotocol/sdk";
|
|
13
15
|
import { FsSafeError } from "@openclaw/fs-safe/errors";
|
|
14
16
|
import { isPathInside } from "@openclaw/fs-safe/path";
|
|
15
17
|
import { root } from "@openclaw/fs-safe/root";
|
|
16
18
|
import readline from "node:readline/promises";
|
|
17
|
-
import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
|
|
18
|
-
import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
|
|
19
19
|
//#region src/async-control.ts
|
|
20
20
|
var TimeoutError = class extends Error {
|
|
21
21
|
constructor(timeoutMs) {
|
|
@@ -346,6 +346,246 @@ async function listSessionEvents(sessionId, maxSegments) {
|
|
|
346
346
|
}).readAcpMessages();
|
|
347
347
|
}
|
|
348
348
|
//#endregion
|
|
349
|
+
//#region src/session/turn-ownership.ts
|
|
350
|
+
const LOCK_RETRY_MS = 15;
|
|
351
|
+
const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
|
|
352
|
+
let lastCreatedAt = 0;
|
|
353
|
+
const localTurns = /* @__PURE__ */ new Map();
|
|
354
|
+
async function readLock(filePath) {
|
|
355
|
+
try {
|
|
356
|
+
const stat = await fs$1.lstat(filePath, { bigint: true });
|
|
357
|
+
if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
|
|
358
|
+
return {
|
|
359
|
+
stat,
|
|
360
|
+
payload: await fs$1.readFile(filePath, "utf8")
|
|
361
|
+
};
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (error.code === "ENOENT") return;
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function parseLock(payload) {
|
|
368
|
+
try {
|
|
369
|
+
return JSON.parse(payload);
|
|
370
|
+
} catch {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function tryAcquireGuard(filePath, owner, signal) {
|
|
375
|
+
try {
|
|
376
|
+
return await acquireFileLock(filePath, {
|
|
377
|
+
managerKey: "acpx.session-turn",
|
|
378
|
+
lockPath: `${filePath}.guard`,
|
|
379
|
+
staleMs: Infinity,
|
|
380
|
+
timeoutMs: LOCK_RETRY_MS,
|
|
381
|
+
retry: {
|
|
382
|
+
retries: 8,
|
|
383
|
+
minTimeout: 1,
|
|
384
|
+
maxTimeout: 2,
|
|
385
|
+
factor: 1,
|
|
386
|
+
randomize: false
|
|
387
|
+
},
|
|
388
|
+
staleRecovery: "remove-if-unchanged",
|
|
389
|
+
payload: () => owner.payload,
|
|
390
|
+
shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
|
|
391
|
+
shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
|
|
392
|
+
});
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (error.code === "file_lock_timeout") return;
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async function removeObservedLock(filePath, observed) {
|
|
399
|
+
const current = await readLock(filePath);
|
|
400
|
+
if (!current) return true;
|
|
401
|
+
if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
|
|
402
|
+
await fs$1.unlink(filePath).catch((error) => {
|
|
403
|
+
if (error.code !== "ENOENT") throw error;
|
|
404
|
+
});
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
async function recoverAbandonedLock(filePath, owner, signal) {
|
|
408
|
+
const observed = await readLock(filePath);
|
|
409
|
+
if (!observed) return true;
|
|
410
|
+
const payload = parseLock(observed.payload);
|
|
411
|
+
const pid = lockOwnerPid(payload);
|
|
412
|
+
if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
|
|
413
|
+
if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
|
|
414
|
+
if (await removeObservedLock(filePath, observed)) {
|
|
415
|
+
incrementPerfCounter("session.events.stale_lock_recovered");
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
async function publishLock(filePath, payload, signal) {
|
|
421
|
+
await withTempFile({
|
|
422
|
+
rootDir: path.dirname(filePath),
|
|
423
|
+
prefix: "session-turn",
|
|
424
|
+
fileName: "lock"
|
|
425
|
+
}, async (temporaryPath) => {
|
|
426
|
+
await fs$1.writeFile(temporaryPath, payload, {
|
|
427
|
+
encoding: "utf8",
|
|
428
|
+
flag: "wx",
|
|
429
|
+
mode: 384
|
|
430
|
+
});
|
|
431
|
+
signal?.throwIfAborted();
|
|
432
|
+
try {
|
|
433
|
+
await fs$1.link(temporaryPath, filePath);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (!isHardlinkFallbackError(error)) throw error;
|
|
436
|
+
await fs$1.writeFile(filePath, payload, {
|
|
437
|
+
encoding: "utf8",
|
|
438
|
+
flag: "wx",
|
|
439
|
+
mode: 384
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function removePublishedLock(filePath, payload) {
|
|
445
|
+
const current = await readLock(filePath);
|
|
446
|
+
if (current?.payload === payload) await removeObservedLock(filePath, current);
|
|
447
|
+
}
|
|
448
|
+
async function rejectAcquisition(filePath, receipt, error) {
|
|
449
|
+
const failures = [error];
|
|
450
|
+
await disposeTurn(filePath, receipt).catch((cleanupError) => {
|
|
451
|
+
failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
|
|
452
|
+
});
|
|
453
|
+
if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
async function releaseTurnGuard(receipt) {
|
|
457
|
+
await receipt.guard?.release();
|
|
458
|
+
receipt.guard = void 0;
|
|
459
|
+
}
|
|
460
|
+
async function removeTurnMarker(filePath, receipt) {
|
|
461
|
+
if (!receipt.removeMarker) return true;
|
|
462
|
+
if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
|
|
463
|
+
receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
|
|
464
|
+
if (!receipt.guard) return false;
|
|
465
|
+
await receipt.removeMarker();
|
|
466
|
+
receipt.removeMarker = void 0;
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
async function settleTurn(filePath, receipt) {
|
|
470
|
+
if (receipt.settled) return true;
|
|
471
|
+
const pending = receipt.pending ??= (async () => {
|
|
472
|
+
receipt.failed = false;
|
|
473
|
+
const failures = [];
|
|
474
|
+
try {
|
|
475
|
+
if (!await removeTurnMarker(filePath, receipt)) return false;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
failures.push(error);
|
|
478
|
+
}
|
|
479
|
+
if (failures.length === 0 || !receipt.admitted) try {
|
|
480
|
+
await releaseTurnGuard(receipt);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
failures.push(error);
|
|
483
|
+
}
|
|
484
|
+
if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
|
|
485
|
+
if (failures.length === 1) throw failures[0];
|
|
486
|
+
receipt.settled = true;
|
|
487
|
+
localTurns.delete(filePath);
|
|
488
|
+
return true;
|
|
489
|
+
})();
|
|
490
|
+
try {
|
|
491
|
+
return await pending;
|
|
492
|
+
} finally {
|
|
493
|
+
if (receipt.pending === pending) {
|
|
494
|
+
receipt.pending = void 0;
|
|
495
|
+
receipt.failed = !receipt.settled;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function disposeTurn(filePath, receipt) {
|
|
500
|
+
if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
|
|
501
|
+
}
|
|
502
|
+
async function tryPublishLock(filePath, payload, owner, signal) {
|
|
503
|
+
for (;;) try {
|
|
504
|
+
await publishLock(filePath, payload, signal);
|
|
505
|
+
return true;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (error.code !== "EEXIST") throw error;
|
|
508
|
+
if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
|
|
512
|
+
const previous = localTurns.get(filePath);
|
|
513
|
+
if (previous) {
|
|
514
|
+
if (previous.failed) await settleTurn(filePath, previous);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const receipt = {
|
|
518
|
+
owner,
|
|
519
|
+
removeMarker: async () => await removePublishedLock(filePath, payload),
|
|
520
|
+
admitted: false,
|
|
521
|
+
failed: false,
|
|
522
|
+
settled: false
|
|
523
|
+
};
|
|
524
|
+
localTurns.set(filePath, receipt);
|
|
525
|
+
try {
|
|
526
|
+
receipt.guard = await tryAcquireGuard(filePath, owner, signal);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
localTurns.delete(filePath);
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
if (!receipt.guard) {
|
|
532
|
+
localTurns.delete(filePath);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
return receipt;
|
|
536
|
+
}
|
|
537
|
+
async function tryAcquireTurn(filePath, payload, owner, signal) {
|
|
538
|
+
const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
|
|
539
|
+
if (!receipt) return;
|
|
540
|
+
try {
|
|
541
|
+
signal?.throwIfAborted();
|
|
542
|
+
if (!await tryPublishLock(filePath, payload, owner, signal)) {
|
|
543
|
+
await disposeTurn(filePath, receipt);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const observed = await readLock(filePath);
|
|
547
|
+
if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
|
|
548
|
+
signal?.throwIfAborted();
|
|
549
|
+
receipt.admitted = true;
|
|
550
|
+
receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
|
|
551
|
+
return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
|
|
552
|
+
} catch (error) {
|
|
553
|
+
return await rejectAcquisition(filePath, receipt, error);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function acquireSessionTurn(recordId, signal) {
|
|
557
|
+
return await acquireSessionOwnership(sessionEventLockPath(recordId), signal);
|
|
558
|
+
}
|
|
559
|
+
async function acquireSessionScope(scope, signal) {
|
|
560
|
+
return await acquireSessionTurn(`ensure:${createHash("sha256").update(JSON.stringify([
|
|
561
|
+
scope.agentCommand,
|
|
562
|
+
scope.cwd,
|
|
563
|
+
scope.name
|
|
564
|
+
])).digest("hex")}`, signal);
|
|
565
|
+
}
|
|
566
|
+
async function acquireSessionImport(signal) {
|
|
567
|
+
return await acquireSessionOwnership(path.join(sessionBaseDir(), ".import-admission.lock"), signal);
|
|
568
|
+
}
|
|
569
|
+
async function acquireSessionOwnership(requestedPath, signal) {
|
|
570
|
+
await fs$1.mkdir(path.dirname(requestedPath), {
|
|
571
|
+
recursive: true,
|
|
572
|
+
mode: 448
|
|
573
|
+
});
|
|
574
|
+
const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
|
|
575
|
+
const owner = await createLockOwner({ signal });
|
|
576
|
+
lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
|
|
577
|
+
const payload = `${JSON.stringify({
|
|
578
|
+
...owner.payload,
|
|
579
|
+
created_at: new Date(lastCreatedAt).toISOString()
|
|
580
|
+
}, null, 2)}\n`;
|
|
581
|
+
for (;;) {
|
|
582
|
+
signal?.throwIfAborted();
|
|
583
|
+
const turn = await tryAcquireTurn(filePath, payload, owner, signal);
|
|
584
|
+
if (turn) return turn;
|
|
585
|
+
await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
//#endregion
|
|
349
589
|
//#region src/session/execution/contracts.ts
|
|
350
590
|
const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
|
|
351
591
|
function normalizeQueueOwnerTtlMs(ttlMs) {
|
|
@@ -366,6 +606,18 @@ function sliceReadWindow(content, line, limit) {
|
|
|
366
606
|
}
|
|
367
607
|
//#endregion
|
|
368
608
|
//#region src/permission-prompt.ts
|
|
609
|
+
function visiblePromptText(value) {
|
|
610
|
+
let rendered = "";
|
|
611
|
+
for (const character of value) {
|
|
612
|
+
const codePoint = character.codePointAt(0);
|
|
613
|
+
if (codePoint !== void 0 && (codePoint <= 31 || codePoint >= 127 && codePoint <= 159)) {
|
|
614
|
+
rendered += `\\x${codePoint.toString(16).padStart(2, "0")}`;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
rendered += character;
|
|
618
|
+
}
|
|
619
|
+
return rendered;
|
|
620
|
+
}
|
|
369
621
|
let promptQueue = Promise.resolve();
|
|
370
622
|
async function promptForPermission(options) {
|
|
371
623
|
options.signal?.throwIfAborted();
|
|
@@ -384,6 +636,10 @@ async function promptForPermission(options) {
|
|
|
384
636
|
signal.removeEventListener("abort", onAbort);
|
|
385
637
|
}
|
|
386
638
|
}
|
|
639
|
+
function promptForDisplay(prompt) {
|
|
640
|
+
if (prompt.startsWith("\n")) return `\n${visiblePromptText(prompt.slice(1))}`;
|
|
641
|
+
return visiblePromptText(prompt);
|
|
642
|
+
}
|
|
387
643
|
async function askPermission(options) {
|
|
388
644
|
options.signal?.throwIfAborted();
|
|
389
645
|
if (!canPrompt()) return false;
|
|
@@ -400,7 +656,7 @@ async function askPermission(options) {
|
|
|
400
656
|
rl.once("close", onClose);
|
|
401
657
|
});
|
|
402
658
|
try {
|
|
403
|
-
const answer = await Promise.race([rl.question(options.prompt, { signal }), closed]);
|
|
659
|
+
const answer = await Promise.race([rl.question(promptForDisplay(options.prompt), { signal }), closed]);
|
|
404
660
|
options.signal?.throwIfAborted();
|
|
405
661
|
const normalized = answer?.trim().toLowerCase();
|
|
406
662
|
return normalized === "y" || normalized === "yes";
|
|
@@ -414,8 +670,8 @@ function canPrompt() {
|
|
|
414
670
|
return process.stdin.isTTY && process.stderr.isTTY && !process.stdin.readableEnded && !process.stdin.destroyed;
|
|
415
671
|
}
|
|
416
672
|
function writePromptDetails(options) {
|
|
417
|
-
if (options.header) process.stderr.write(`\n${options.header}\n`);
|
|
418
|
-
if (options.details && options.details.trim().length > 0) process.stderr.write(`${options.details}\n`);
|
|
673
|
+
if (options.header) process.stderr.write(`\n${visiblePromptText(options.header)}\n`);
|
|
674
|
+
if (options.details && options.details.trim().length > 0) process.stderr.write(`${options.details.split("\n").map(visiblePromptText).join("\n")}\n`);
|
|
419
675
|
}
|
|
420
676
|
//#endregion
|
|
421
677
|
//#region src/filesystem.ts
|
|
@@ -1016,10 +1272,10 @@ function assignClaudeCodeSystemPrompt(target, systemPrompt) {
|
|
|
1016
1272
|
function isAppendSystemPrompt(value) {
|
|
1017
1273
|
return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
|
|
1018
1274
|
}
|
|
1019
|
-
function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
|
|
1275
|
+
function resolveClaudeCodeExecutable(platform = process.platform, env = process.env, cwd) {
|
|
1020
1276
|
if (platform !== "win32") return;
|
|
1021
1277
|
if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
|
|
1022
|
-
return resolveWindowsExecutablePath("claude", env);
|
|
1278
|
+
return resolveWindowsExecutablePath("claude", env, cwd);
|
|
1023
1279
|
}
|
|
1024
1280
|
//#endregion
|
|
1025
1281
|
//#region src/acp/auth-env.ts
|
|
@@ -1066,7 +1322,7 @@ function promotePrefixedAuthEnvironment(env) {
|
|
|
1066
1322
|
if (!normalized) continue;
|
|
1067
1323
|
protectEnvKey(protectedKeys, key);
|
|
1068
1324
|
protectEnvKey(protectedKeys, normalized);
|
|
1069
|
-
|
|
1325
|
+
assignIfMissing(env, normalized, value);
|
|
1070
1326
|
}
|
|
1071
1327
|
return protectedKeys;
|
|
1072
1328
|
}
|
|
@@ -1090,7 +1346,12 @@ function buildAgentEnvironment(authCredentials, sessionEnv, agentProcessEnv) {
|
|
|
1090
1346
|
function assignSessionEnv(env, key, value) {
|
|
1091
1347
|
const normalizedKey = protectedEnvKey(key);
|
|
1092
1348
|
for (const existingKey of Object.keys(env)) if (protectedEnvKey(existingKey) === normalizedKey) delete env[existingKey];
|
|
1093
|
-
env
|
|
1349
|
+
Object.defineProperty(env, key, {
|
|
1350
|
+
value,
|
|
1351
|
+
writable: true,
|
|
1352
|
+
enumerable: true,
|
|
1353
|
+
configurable: true
|
|
1354
|
+
});
|
|
1094
1355
|
}
|
|
1095
1356
|
function addAuthCredentialEnvKeys(protectedKeys, methodId, credential) {
|
|
1096
1357
|
if (typeof credential !== "string" || credential.trim().length === 0) return;
|
|
@@ -1103,7 +1364,7 @@ function addAuthCredentialEnvKeys(protectedKeys, methodId, credential) {
|
|
|
1103
1364
|
}
|
|
1104
1365
|
function assignAuthCredentialEnv(env, methodId, credential) {
|
|
1105
1366
|
if (typeof credential !== "string" || credential.trim().length === 0) return;
|
|
1106
|
-
if (!methodId.includes("=") && !methodId.includes("\0")
|
|
1367
|
+
if (!methodId.includes("=") && !methodId.includes("\0")) assignIfMissing(env, methodId, credential);
|
|
1107
1368
|
const normalized = toEnvToken(methodId);
|
|
1108
1369
|
if (normalized) {
|
|
1109
1370
|
assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
|
|
@@ -1111,7 +1372,10 @@ function assignAuthCredentialEnv(env, methodId, credential) {
|
|
|
1111
1372
|
}
|
|
1112
1373
|
}
|
|
1113
1374
|
function assignIfMissing(env, key, value) {
|
|
1114
|
-
if (env[key]
|
|
1375
|
+
if (env[key] != null) return;
|
|
1376
|
+
const normalizedKey = protectedEnvKey(key);
|
|
1377
|
+
if (process.platform === "win32" && Object.entries(env).some(([existingKey, existingValue]) => existingValue != null && protectedEnvKey(existingKey) === normalizedKey)) return;
|
|
1378
|
+
env[key] = value;
|
|
1115
1379
|
}
|
|
1116
1380
|
function resolveConfiguredAuthCredential(methodId, authCredentials) {
|
|
1117
1381
|
const configCredentials = authCredentials ?? {};
|
|
@@ -1273,34 +1537,7 @@ function captureCommandProbeOutput(child, timeoutMs, retire) {
|
|
|
1273
1537
|
};
|
|
1274
1538
|
}
|
|
1275
1539
|
//#endregion
|
|
1276
|
-
//#region src/acp/model-
|
|
1277
|
-
const REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE = "ACP_MODEL_UNSUPPORTED";
|
|
1278
|
-
const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised-model"];
|
|
1279
|
-
var RequestedModelUnsupportedError = class extends Error {
|
|
1280
|
-
code = REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE;
|
|
1281
|
-
reason;
|
|
1282
|
-
ambiguous;
|
|
1283
|
-
constructor(message, reason, details) {
|
|
1284
|
-
super(message);
|
|
1285
|
-
this.name = "RequestedModelUnsupportedError";
|
|
1286
|
-
this.reason = reason;
|
|
1287
|
-
if (details?.ambiguous) this.ambiguous = true;
|
|
1288
|
-
}
|
|
1289
|
-
};
|
|
1290
|
-
function isRequestedModelUnsupportedReason(value) {
|
|
1291
|
-
return typeof value === "string" && REQUESTED_MODEL_UNSUPPORTED_REASONS.includes(value);
|
|
1292
|
-
}
|
|
1293
|
-
function isRequestedModelUnsupportedError(value) {
|
|
1294
|
-
if (value instanceof RequestedModelUnsupportedError) return true;
|
|
1295
|
-
if (!value || typeof value !== "object") return false;
|
|
1296
|
-
const candidate = value;
|
|
1297
|
-
return candidate.name === "RequestedModelUnsupportedError" && candidate.code === "ACP_MODEL_UNSUPPORTED" && isRequestedModelUnsupportedReason(candidate.reason);
|
|
1298
|
-
}
|
|
1299
|
-
function supportsLegacyClaudeCodeModelMetadata(agentCommand) {
|
|
1300
|
-
if (!agentCommand) return false;
|
|
1301
|
-
const { command, args } = splitCommandLine(agentCommand);
|
|
1302
|
-
return isClaudeAcpCommand(command, args);
|
|
1303
|
-
}
|
|
1540
|
+
//#region src/acp/model-config-state.ts
|
|
1304
1541
|
function asRecord$2(value) {
|
|
1305
1542
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
1306
1543
|
return value;
|
|
@@ -1359,6 +1596,35 @@ function modelStateFromConfigOptions(configOptions) {
|
|
|
1359
1596
|
}
|
|
1360
1597
|
return selected;
|
|
1361
1598
|
}
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region src/acp/model-support.ts
|
|
1601
|
+
const REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE = "ACP_MODEL_UNSUPPORTED";
|
|
1602
|
+
const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised-model"];
|
|
1603
|
+
var RequestedModelUnsupportedError = class extends Error {
|
|
1604
|
+
code = REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE;
|
|
1605
|
+
reason;
|
|
1606
|
+
ambiguous;
|
|
1607
|
+
constructor(message, reason, details) {
|
|
1608
|
+
super(message);
|
|
1609
|
+
this.name = "RequestedModelUnsupportedError";
|
|
1610
|
+
this.reason = reason;
|
|
1611
|
+
if (details?.ambiguous) this.ambiguous = true;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
function isRequestedModelUnsupportedReason(value) {
|
|
1615
|
+
return typeof value === "string" && REQUESTED_MODEL_UNSUPPORTED_REASONS.includes(value);
|
|
1616
|
+
}
|
|
1617
|
+
function isRequestedModelUnsupportedError(value) {
|
|
1618
|
+
if (value instanceof RequestedModelUnsupportedError) return true;
|
|
1619
|
+
if (!value || typeof value !== "object") return false;
|
|
1620
|
+
const candidate = value;
|
|
1621
|
+
return candidate.name === "RequestedModelUnsupportedError" && candidate.code === "ACP_MODEL_UNSUPPORTED" && isRequestedModelUnsupportedReason(candidate.reason);
|
|
1622
|
+
}
|
|
1623
|
+
function supportsLegacyClaudeCodeModelMetadata(agentCommand) {
|
|
1624
|
+
if (!agentCommand) return false;
|
|
1625
|
+
const { command, args } = splitCommandLine(agentCommand);
|
|
1626
|
+
return isClaudeAcpCommand(command, args);
|
|
1627
|
+
}
|
|
1362
1628
|
function modelStateFromLegacyResponse(response) {
|
|
1363
1629
|
if (!response || typeof response !== "object") return;
|
|
1364
1630
|
const models = response.models;
|
|
@@ -1390,6 +1656,16 @@ function resolveRequestedModelId(params) {
|
|
|
1390
1656
|
if (candidates.length > 1) throw new RequestedModelUnsupportedError(`Cannot select model "${params.requestedModel}": multiple advertised Cursor models match (${candidates.join(", ")}). Use an exact advertised model ID.`, "unadvertised-model", { ambiguous: true });
|
|
1391
1657
|
return candidates.length === 1 ? candidates[0] : params.requestedModel;
|
|
1392
1658
|
}
|
|
1659
|
+
function resolveRequestedConfigOption(params) {
|
|
1660
|
+
const modelConfigId = params.models?.configId;
|
|
1661
|
+
return {
|
|
1662
|
+
modelConfigId,
|
|
1663
|
+
resolvedValue: modelConfigId === params.configId ? resolveRequestedModelId({
|
|
1664
|
+
...params,
|
|
1665
|
+
requestedModel: params.value
|
|
1666
|
+
}) : params.value
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1393
1669
|
function isCursorAcpCommandForModelAlias(agentCommand) {
|
|
1394
1670
|
if (!agentCommand) return false;
|
|
1395
1671
|
const { command, args } = splitCommandLine(agentCommand);
|
|
@@ -1622,7 +1898,7 @@ function buildTerminalSpawnOptions(command, cwd, env, platform = process.platfor
|
|
|
1622
1898
|
"pipe"
|
|
1623
1899
|
],
|
|
1624
1900
|
windowsHide: true
|
|
1625
|
-
}, platform, resolvedEnv ?? process.env);
|
|
1901
|
+
}, platform, resolvedEnv ?? process.env, cwd);
|
|
1626
1902
|
}
|
|
1627
1903
|
function readTerminalOutputCeiling() {
|
|
1628
1904
|
const raw = process.env.ACPX_TERMINAL_MAX_OUTPUT_BYTES?.trim();
|
|
@@ -1673,6 +1949,7 @@ var TerminalManager = class {
|
|
|
1673
1949
|
processHelperTimeoutMs;
|
|
1674
1950
|
outputCeilingBytes;
|
|
1675
1951
|
terminals = /* @__PURE__ */ new Map();
|
|
1952
|
+
creations = /* @__PURE__ */ new Map();
|
|
1676
1953
|
constructor(options) {
|
|
1677
1954
|
this.outputCeilingBytes = readTerminalOutputCeiling();
|
|
1678
1955
|
this.cwd = options.cwd;
|
|
@@ -1689,7 +1966,15 @@ var TerminalManager = class {
|
|
|
1689
1966
|
this.permissionMode = permissionMode;
|
|
1690
1967
|
this.nonInteractivePermissions = nonInteractivePermissions ?? "deny";
|
|
1691
1968
|
}
|
|
1692
|
-
|
|
1969
|
+
createTerminal(params, authority) {
|
|
1970
|
+
const creation = {};
|
|
1971
|
+
const pending = Promise.resolve().then(() => this.create(params, authority, creation));
|
|
1972
|
+
this.creations.set(pending, creation);
|
|
1973
|
+
const remove = () => this.creations.delete(pending);
|
|
1974
|
+
pending.then(remove, remove);
|
|
1975
|
+
return pending;
|
|
1976
|
+
}
|
|
1977
|
+
async create(params, authority, creation) {
|
|
1693
1978
|
assertControlAuthority(authority);
|
|
1694
1979
|
const commandLine = toCommandLine(params.command, params.args);
|
|
1695
1980
|
const summary = `terminal/create: ${commandLine}`;
|
|
@@ -1753,10 +2038,15 @@ var TerminalManager = class {
|
|
|
1753
2038
|
await terminal.descendants?.capture(terminal.processHelperTimeoutMs);
|
|
1754
2039
|
assertControlAuthority(authority);
|
|
1755
2040
|
} catch (error) {
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2041
|
+
try {
|
|
2042
|
+
await this.releaseTerminal({
|
|
2043
|
+
terminalId,
|
|
2044
|
+
sessionId: params.sessionId
|
|
2045
|
+
});
|
|
2046
|
+
} catch (cleanupError) {
|
|
2047
|
+
creation.cleanupFailure = { error: cleanupError };
|
|
2048
|
+
throw cleanupError;
|
|
2049
|
+
}
|
|
1760
2050
|
throw error;
|
|
1761
2051
|
}
|
|
1762
2052
|
this.emitOperation({
|
|
@@ -1889,11 +2179,13 @@ var TerminalManager = class {
|
|
|
1889
2179
|
}
|
|
1890
2180
|
}
|
|
1891
2181
|
async shutdown() {
|
|
2182
|
+
const creations = [...this.creations];
|
|
2183
|
+
await Promise.allSettled(creations.map(([pending]) => pending));
|
|
1892
2184
|
const results = await Promise.allSettled(Array.from(this.terminals.keys(), (terminalId) => this.releaseTerminal({
|
|
1893
2185
|
terminalId,
|
|
1894
2186
|
sessionId: "shutdown"
|
|
1895
2187
|
})));
|
|
1896
|
-
const failures = [];
|
|
2188
|
+
const failures = creations.flatMap(([, creation]) => creation.cleanupFailure ? [creation.cleanupFailure.error] : []);
|
|
1897
2189
|
for (const result of results) if (result.status === "rejected") failures.push(result.reason);
|
|
1898
2190
|
if (failures.length > 0) throw new AggregateError(failures, "Terminal shutdown failed", { cause: failures[0] });
|
|
1899
2191
|
}
|
|
@@ -1983,7 +2275,7 @@ async function spawnTerminalProcess(params, defaultCwd, authority) {
|
|
|
1983
2275
|
}
|
|
1984
2276
|
async function spawnAndWait(spawnCommand, params, defaultCwd, authority) {
|
|
1985
2277
|
const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
|
|
1986
|
-
if (
|
|
2278
|
+
if (process.platform !== "win32") spawnOptions.detached = true;
|
|
1987
2279
|
assertControlAuthority(authority);
|
|
1988
2280
|
const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
|
|
1989
2281
|
await waitForSpawn(proc);
|
|
@@ -2147,7 +2439,14 @@ function hasResponseField(response, field) {
|
|
|
2147
2439
|
}
|
|
2148
2440
|
function normalizeResponseConfigOptions(response) {
|
|
2149
2441
|
if (!response || !("configOptions" in response)) return;
|
|
2150
|
-
return response.configOptions
|
|
2442
|
+
return Array.isArray(response.configOptions) ? response.configOptions : void 0;
|
|
2443
|
+
}
|
|
2444
|
+
function normalizeConfigOptionAcknowledgement(response) {
|
|
2445
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) return {};
|
|
2446
|
+
if (Array.isArray(response.configOptions)) return response;
|
|
2447
|
+
const acknowledgement = { ...response };
|
|
2448
|
+
delete acknowledgement.configOptions;
|
|
2449
|
+
return acknowledgement;
|
|
2151
2450
|
}
|
|
2152
2451
|
function toReconnectedSessionResult(response) {
|
|
2153
2452
|
const configOptions = normalizeResponseConfigOptions(response);
|
|
@@ -2158,7 +2457,7 @@ function toReconnectedSessionResult(response) {
|
|
|
2158
2457
|
configOptions,
|
|
2159
2458
|
response
|
|
2160
2459
|
}),
|
|
2161
|
-
configOptionsPresent:
|
|
2460
|
+
configOptionsPresent: configOptions !== void 0,
|
|
2162
2461
|
legacyModelMetadataPresent: hasResponseField(response, "models")
|
|
2163
2462
|
};
|
|
2164
2463
|
}
|
|
@@ -2337,8 +2636,9 @@ var AcpClient = class {
|
|
|
2337
2636
|
}
|
|
2338
2637
|
fallback?.abort();
|
|
2339
2638
|
}
|
|
2340
|
-
async start(authority) {
|
|
2639
|
+
async start(authority, options) {
|
|
2341
2640
|
assertControlAuthority(authority);
|
|
2641
|
+
if (options) this.options.sessionOptions = structuredClone(options.sessionOptions);
|
|
2342
2642
|
if (this.hasLiveConnection()) return;
|
|
2343
2643
|
const epoch = await this.waitForPreviousClose();
|
|
2344
2644
|
const maxMessageBytes = readMaxAcpMessageBytes();
|
|
@@ -2457,14 +2757,14 @@ var AcpClient = class {
|
|
|
2457
2757
|
async ensureLaunchSupport(plan) {
|
|
2458
2758
|
if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand, plan.commandContext);
|
|
2459
2759
|
if (!plan.claudeAcp) return;
|
|
2460
|
-
const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
|
|
2760
|
+
const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env, plan.spawnOptions.cwd);
|
|
2461
2761
|
if (claudeExe) {
|
|
2462
2762
|
plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
2463
2763
|
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
2464
2764
|
}
|
|
2465
2765
|
}
|
|
2466
2766
|
prepareProcessLaunch(command, args, options) {
|
|
2467
|
-
const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env);
|
|
2767
|
+
const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env, options.cwd);
|
|
2468
2768
|
return {
|
|
2469
2769
|
launch: Object.freeze({
|
|
2470
2770
|
launchId: randomUUID(),
|
|
@@ -2674,6 +2974,10 @@ var AcpClient = class {
|
|
|
2674
2974
|
onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
|
|
2675
2975
|
});
|
|
2676
2976
|
}
|
|
2977
|
+
sessionMeta() {
|
|
2978
|
+
const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
|
|
2979
|
+
return buildClaudeCodeOptionsMeta(this.options.sessionOptions, isClaudeAcpCommand(command, args));
|
|
2980
|
+
}
|
|
2677
2981
|
async createSession(cwd = this.options.cwd, authority) {
|
|
2678
2982
|
assertControlAuthority(authority);
|
|
2679
2983
|
const connection = this.getConnection();
|
|
@@ -2685,7 +2989,7 @@ var AcpClient = class {
|
|
|
2685
2989
|
const createPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.new, {
|
|
2686
2990
|
cwd: sessionCwd,
|
|
2687
2991
|
mcpServers: this.options.mcpServers ?? [],
|
|
2688
|
-
_meta:
|
|
2992
|
+
_meta: this.sessionMeta()
|
|
2689
2993
|
}), authority);
|
|
2690
2994
|
result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
|
|
2691
2995
|
} catch (error) {
|
|
@@ -2707,7 +3011,7 @@ var AcpClient = class {
|
|
|
2707
3011
|
agentSessionId: extractAgentSessionId(result._meta),
|
|
2708
3012
|
configOptions,
|
|
2709
3013
|
models,
|
|
2710
|
-
configOptionsPresent:
|
|
3014
|
+
configOptionsPresent: configOptions !== void 0,
|
|
2711
3015
|
legacyModelMetadataPresent: hasResponseField(result, "models")
|
|
2712
3016
|
};
|
|
2713
3017
|
}
|
|
@@ -2725,7 +3029,8 @@ var AcpClient = class {
|
|
|
2725
3029
|
response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.load, {
|
|
2726
3030
|
sessionId,
|
|
2727
3031
|
cwd: sessionCwd,
|
|
2728
|
-
mcpServers: this.options.mcpServers ?? []
|
|
3032
|
+
mcpServers: this.options.mcpServers ?? [],
|
|
3033
|
+
_meta: this.sessionMeta()
|
|
2729
3034
|
}), options.authority);
|
|
2730
3035
|
await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
|
|
2731
3036
|
} finally {
|
|
@@ -2743,7 +3048,8 @@ var AcpClient = class {
|
|
|
2743
3048
|
const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.resume, {
|
|
2744
3049
|
sessionId,
|
|
2745
3050
|
cwd: sessionCwd,
|
|
2746
|
-
mcpServers: this.options.mcpServers ?? []
|
|
3051
|
+
mcpServers: this.options.mcpServers ?? [],
|
|
3052
|
+
_meta: this.sessionMeta()
|
|
2747
3053
|
}), authority);
|
|
2748
3054
|
this.loadedSessionId = sessionId;
|
|
2749
3055
|
const result = toReconnectedSessionResult(response);
|
|
@@ -2862,11 +3168,11 @@ var AcpClient = class {
|
|
|
2862
3168
|
assertControlAuthority(authority);
|
|
2863
3169
|
const resolvedValue = models?.configId === configId ? this.resolveSessionModelId(value, models) : value;
|
|
2864
3170
|
const connection = this.getConnection();
|
|
2865
|
-
return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
|
|
3171
|
+
return normalizeConfigOptionAcknowledgement(await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
|
|
2866
3172
|
sessionId,
|
|
2867
3173
|
configId,
|
|
2868
3174
|
value: resolvedValue
|
|
2869
|
-
}), authority, (error) => maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`));
|
|
3175
|
+
}), authority, (error) => maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`)));
|
|
2870
3176
|
}
|
|
2871
3177
|
async setSessionModel(sessionId, modelId, models, authority) {
|
|
2872
3178
|
assertControlAuthority(authority);
|
|
@@ -2888,11 +3194,11 @@ var AcpClient = class {
|
|
|
2888
3194
|
}
|
|
2889
3195
|
async setSessionModelThroughConfig(sessionId, modelId, configId, authority) {
|
|
2890
3196
|
const connection = this.getConnection();
|
|
2891
|
-
return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
|
|
3197
|
+
return normalizeConfigOptionAcknowledgement(await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
|
|
2892
3198
|
sessionId,
|
|
2893
3199
|
configId,
|
|
2894
3200
|
value: modelId
|
|
2895
|
-
}), authority, (error) => this.throwSessionModelError("session/set_config_option", modelId, error));
|
|
3201
|
+
}), authority, (error) => this.throwSessionModelError("session/set_config_option", modelId, error)));
|
|
2896
3202
|
}
|
|
2897
3203
|
async setSessionModelThroughLegacyMethod(sessionId, modelId, authority) {
|
|
2898
3204
|
const connection = this.getConnection();
|
|
@@ -3659,6 +3965,7 @@ function removeModelConfigOptions(state) {
|
|
|
3659
3965
|
state.config_options = state.config_options.filter((option) => option.category !== "model" && option.id !== "model");
|
|
3660
3966
|
}
|
|
3661
3967
|
function applyConfigOptionsModelState(state, configOptions) {
|
|
3968
|
+
if (!Array.isArray(configOptions)) return;
|
|
3662
3969
|
const previousConfigModels = modelStateFromConfigOptions(state.config_options);
|
|
3663
3970
|
const preservesLegacyControl = state.model_control === "legacy_set_model" || state.model_control === void 0 && previousConfigModels === void 0 && legacyModelState(state) !== void 0;
|
|
3664
3971
|
state.config_options = structuredClone(configOptions);
|
|
@@ -3668,14 +3975,9 @@ function applyConfigOptionsModelState(state, configOptions) {
|
|
|
3668
3975
|
else clearAdvertisedModelState(state);
|
|
3669
3976
|
}
|
|
3670
3977
|
//#endregion
|
|
3671
|
-
//#region src/session/conversation-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
const MAX_RUNTIME_THINKING_CHARS = 4e3;
|
|
3675
|
-
const MAX_RUNTIME_TOOL_IO_CHARS = 4e3;
|
|
3676
|
-
const MAX_RUNTIME_REQUEST_TOKEN_USAGE = 100;
|
|
3677
|
-
function isoNow() {
|
|
3678
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3978
|
+
//#region src/session/conversation-reducer.ts
|
|
3979
|
+
function resolveUserMessageId(value) {
|
|
3980
|
+
return typeof value === "string" ? value : value();
|
|
3679
3981
|
}
|
|
3680
3982
|
function deepClone(value) {
|
|
3681
3983
|
try {
|
|
@@ -3731,6 +4033,7 @@ function contentToUserContent(content) {
|
|
|
3731
4033
|
if (content.type === "resource") return resourceToUserContent(content);
|
|
3732
4034
|
if (content.type === "image") return { Image: {
|
|
3733
4035
|
source: content.data,
|
|
4036
|
+
mime_type: content.mimeType,
|
|
3734
4037
|
size: null
|
|
3735
4038
|
} };
|
|
3736
4039
|
if (content.type === "audio") return { Audio: {
|
|
@@ -3745,9 +4048,6 @@ function resourceToUserContent(content) {
|
|
|
3745
4048
|
content: content.resource.uri
|
|
3746
4049
|
} };
|
|
3747
4050
|
}
|
|
3748
|
-
function nextUserMessageId() {
|
|
3749
|
-
return randomUUID();
|
|
3750
|
-
}
|
|
3751
4051
|
function isUserMessage(message) {
|
|
3752
4052
|
return typeof message === "object" && message !== null && hasOwn(message, "User");
|
|
3753
4053
|
}
|
|
@@ -3779,7 +4079,7 @@ function ensureAgentMessage(conversation) {
|
|
|
3779
4079
|
function appendAgentText(agent, text) {
|
|
3780
4080
|
const last = agent.content.at(-1);
|
|
3781
4081
|
if (last && isAgentTextContent(last)) {
|
|
3782
|
-
last.Text =
|
|
4082
|
+
last.Text = `${last.Text}${text}`;
|
|
3783
4083
|
return;
|
|
3784
4084
|
}
|
|
3785
4085
|
const next = { Text: text };
|
|
@@ -3788,7 +4088,7 @@ function appendAgentText(agent, text) {
|
|
|
3788
4088
|
function appendAgentThinking(agent, text) {
|
|
3789
4089
|
const last = agent.content.at(-1);
|
|
3790
4090
|
if (last && isAgentThinkingContent(last)) {
|
|
3791
|
-
last.Thinking.text =
|
|
4091
|
+
last.Thinking.text = `${last.Thinking.text}${text}`;
|
|
3792
4092
|
return;
|
|
3793
4093
|
}
|
|
3794
4094
|
const next = { Thinking: {
|
|
@@ -3797,10 +4097,6 @@ function appendAgentThinking(agent, text) {
|
|
|
3797
4097
|
} };
|
|
3798
4098
|
agent.content.push(next);
|
|
3799
4099
|
}
|
|
3800
|
-
function trimRuntimeText(value, maxChars) {
|
|
3801
|
-
if (value.length <= maxChars) return value;
|
|
3802
|
-
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
3803
|
-
}
|
|
3804
4100
|
function statusIndicatesComplete(status) {
|
|
3805
4101
|
if (typeof status !== "string") return false;
|
|
3806
4102
|
const normalized = status.toLowerCase();
|
|
@@ -3812,18 +4108,18 @@ function statusIndicatesError(status) {
|
|
|
3812
4108
|
return normalized.includes("fail") || normalized.includes("error");
|
|
3813
4109
|
}
|
|
3814
4110
|
function toToolResultContent(value) {
|
|
3815
|
-
if (typeof value === "string") return { Text:
|
|
4111
|
+
if (typeof value === "string") return { Text: value };
|
|
3816
4112
|
if (value != null) try {
|
|
3817
|
-
return { Text:
|
|
4113
|
+
return { Text: JSON.stringify(value) ?? "[Unserializable value]" };
|
|
3818
4114
|
} catch {
|
|
3819
4115
|
return { Text: "[Unserializable value]" };
|
|
3820
4116
|
}
|
|
3821
4117
|
return { Text: "" };
|
|
3822
4118
|
}
|
|
3823
4119
|
function toRawInput(value) {
|
|
3824
|
-
if (typeof value === "string") return
|
|
4120
|
+
if (typeof value === "string") return value;
|
|
3825
4121
|
try {
|
|
3826
|
-
return
|
|
4122
|
+
return JSON.stringify(value ?? {}) ?? "[Unserializable input]";
|
|
3827
4123
|
} catch {
|
|
3828
4124
|
return value == null ? "" : "[Unserializable input]";
|
|
3829
4125
|
}
|
|
@@ -3868,11 +4164,13 @@ function existingToolResultValues(existing) {
|
|
|
3868
4164
|
};
|
|
3869
4165
|
}
|
|
3870
4166
|
function applyToolCallUpdate(agent, update) {
|
|
4167
|
+
const contentCount = agent.content.length;
|
|
3871
4168
|
const tool = ensureToolUseContent(agent, update.toolCallId);
|
|
3872
4169
|
applyToolIdentityUpdate(tool, update);
|
|
3873
4170
|
applyToolInputUpdate(tool, update);
|
|
3874
4171
|
applyToolStatusUpdate(tool, update);
|
|
3875
4172
|
applyToolResultUpdate(agent, tool, update);
|
|
4173
|
+
return agent.content.length !== contentCount || hasOwn(update, "rawInput") || hasToolResultPatch(update);
|
|
3876
4174
|
}
|
|
3877
4175
|
function applyToolIdentityUpdate(tool, update) {
|
|
3878
4176
|
if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
|
|
@@ -3975,7 +4273,7 @@ function lastUserMessageId(conversation) {
|
|
|
3975
4273
|
if (message && isUserMessage(message)) return message.User.id;
|
|
3976
4274
|
}
|
|
3977
4275
|
}
|
|
3978
|
-
function
|
|
4276
|
+
function createConversation(timestamp) {
|
|
3979
4277
|
return {
|
|
3980
4278
|
title: null,
|
|
3981
4279
|
messages: [],
|
|
@@ -3985,8 +4283,7 @@ function createSessionConversation(timestamp = isoNow()) {
|
|
|
3985
4283
|
request_token_usage: {}
|
|
3986
4284
|
};
|
|
3987
4285
|
}
|
|
3988
|
-
function
|
|
3989
|
-
if (!conversation) return createSessionConversation();
|
|
4286
|
+
function cloneConversation(conversation) {
|
|
3990
4287
|
return {
|
|
3991
4288
|
title: conversation.title,
|
|
3992
4289
|
messages: deepClone(conversation.messages ?? []),
|
|
@@ -4027,57 +4324,47 @@ function cloneSessionOptions(options) {
|
|
|
4027
4324
|
function cloneSystemPromptOption(option) {
|
|
4028
4325
|
return typeof option === "string" ? option : { append: option.append };
|
|
4029
4326
|
}
|
|
4030
|
-
function
|
|
4327
|
+
function reducePromptSubmission(conversation, prompt, timestamp, messageId) {
|
|
4031
4328
|
const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
|
|
4032
|
-
if (userContent.length === 0) return;
|
|
4033
|
-
const promptMessageId = messageId ?? nextUserMessageId();
|
|
4329
|
+
if (userContent.length === 0) return { messageIndex: void 0 };
|
|
4034
4330
|
conversation.messages.push({ User: {
|
|
4035
|
-
id:
|
|
4036
|
-
content: userContent
|
|
4037
|
-
if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
|
|
4038
|
-
return content;
|
|
4039
|
-
})
|
|
4331
|
+
id: resolveUserMessageId(messageId),
|
|
4332
|
+
content: userContent
|
|
4040
4333
|
} });
|
|
4041
4334
|
updateConversationTimestamp(conversation, timestamp);
|
|
4042
|
-
|
|
4043
|
-
return promptMessageId;
|
|
4335
|
+
return { messageIndex: conversation.messages.length - 1 };
|
|
4044
4336
|
}
|
|
4045
|
-
function
|
|
4337
|
+
function reduceSessionUpdate(conversation, state, notification, timestamp, userMessageId) {
|
|
4046
4338
|
const acpx = ensureAcpxState$1(state);
|
|
4047
|
-
const
|
|
4048
|
-
applySessionUpdate(conversation, acpx, update, userMessageId);
|
|
4339
|
+
const messageIndex = applySessionUpdate(conversation, acpx, notification.update, userMessageId);
|
|
4049
4340
|
updateConversationTimestamp(conversation, timestamp);
|
|
4050
|
-
|
|
4051
|
-
|
|
4341
|
+
return {
|
|
4342
|
+
acpx,
|
|
4343
|
+
messageIndex
|
|
4344
|
+
};
|
|
4052
4345
|
}
|
|
4053
|
-
function
|
|
4346
|
+
function reducePromptResponseUsage(conversation, usage, promptMessageId, timestamp) {
|
|
4054
4347
|
const tokenUsage = sourceToTokenUsage(usage);
|
|
4055
4348
|
if (!tokenUsage) return false;
|
|
4056
4349
|
applyTokenUsage(conversation, tokenUsage, promptMessageId);
|
|
4057
4350
|
updateConversationTimestamp(conversation, timestamp);
|
|
4058
|
-
trimConversationForRuntime(conversation);
|
|
4059
4351
|
return true;
|
|
4060
4352
|
}
|
|
4061
4353
|
function applySessionUpdate(conversation, acpx, update, userMessageId) {
|
|
4062
|
-
|
|
4063
|
-
handler?.(conversation, acpx, update, userMessageId);
|
|
4354
|
+
return (Object.hasOwn(SESSION_UPDATE_HANDLERS, update.sessionUpdate) ? SESSION_UPDATE_HANDLERS[update.sessionUpdate] : void 0)?.(conversation, acpx, update, userMessageId);
|
|
4064
4355
|
}
|
|
4065
4356
|
const SESSION_UPDATE_HANDLERS = {
|
|
4066
4357
|
user_message_chunk: (conversation, _acpx, update, userMessageId) => {
|
|
4067
|
-
if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content, userMessageId);
|
|
4358
|
+
if (update.sessionUpdate === "user_message_chunk") return appendUserMessageChunk(conversation, update.content, userMessageId);
|
|
4068
4359
|
},
|
|
4069
4360
|
agent_message_chunk: (conversation, _acpx, update) => {
|
|
4070
|
-
if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
|
|
4361
|
+
if (update.sessionUpdate === "agent_message_chunk") return appendAgentMessageChunk(conversation, update.content, appendAgentText);
|
|
4071
4362
|
},
|
|
4072
4363
|
agent_thought_chunk: (conversation, _acpx, update) => {
|
|
4073
|
-
if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
|
|
4074
|
-
},
|
|
4075
|
-
tool_call: (conversation, _acpx, update) => {
|
|
4076
|
-
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4077
|
-
},
|
|
4078
|
-
tool_call_update: (conversation, _acpx, update) => {
|
|
4079
|
-
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4364
|
+
if (update.sessionUpdate === "agent_thought_chunk") return appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
|
|
4080
4365
|
},
|
|
4366
|
+
tool_call: applyToolUpdate,
|
|
4367
|
+
tool_call_update: applyToolUpdate,
|
|
4081
4368
|
usage_update: (conversation, _acpx, update) => {
|
|
4082
4369
|
if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
|
|
4083
4370
|
},
|
|
@@ -4094,17 +4381,24 @@ const SESSION_UPDATE_HANDLERS = {
|
|
|
4094
4381
|
if (update.sessionUpdate === "config_option_update") applyConfigOptionsModelState(acpx, deepClone(update.configOptions));
|
|
4095
4382
|
}
|
|
4096
4383
|
};
|
|
4384
|
+
function applyToolUpdate(conversation, _acpx, update) {
|
|
4385
|
+
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") return;
|
|
4386
|
+
return applyToolCallUpdate(ensureAgentMessage(conversation), update) ? conversation.messages.length - 1 : void 0;
|
|
4387
|
+
}
|
|
4097
4388
|
function appendUserMessageChunk(conversation, content, messageId) {
|
|
4098
4389
|
const userContent = contentToUserContent(content);
|
|
4099
4390
|
if (!userContent) return;
|
|
4100
4391
|
conversation.messages.push({ User: {
|
|
4101
|
-
id: messageId
|
|
4392
|
+
id: resolveUserMessageId(messageId),
|
|
4102
4393
|
content: [userContent]
|
|
4103
4394
|
} });
|
|
4395
|
+
return conversation.messages.length - 1;
|
|
4104
4396
|
}
|
|
4105
4397
|
function appendAgentMessageChunk(conversation, content, append) {
|
|
4106
4398
|
const text = extractText(content);
|
|
4107
|
-
if (text)
|
|
4399
|
+
if (!text) return;
|
|
4400
|
+
append(ensureAgentMessage(conversation), text);
|
|
4401
|
+
return conversation.messages.length - 1;
|
|
4108
4402
|
}
|
|
4109
4403
|
function applyUsageUpdate(conversation, update) {
|
|
4110
4404
|
const usage = usageToTokenUsage(update);
|
|
@@ -4116,17 +4410,30 @@ function applyUsageUpdate(conversation, update) {
|
|
|
4116
4410
|
function applyTokenUsage(conversation, usage, promptMessageId) {
|
|
4117
4411
|
conversation.cumulative_token_usage = usage;
|
|
4118
4412
|
const userId = promptMessageId ?? lastUserMessageId(conversation);
|
|
4119
|
-
if (userId) conversation.request_token_usage
|
|
4413
|
+
if (userId) Object.defineProperty(conversation.request_token_usage, userId, {
|
|
4414
|
+
value: usage,
|
|
4415
|
+
writable: true,
|
|
4416
|
+
enumerable: true,
|
|
4417
|
+
configurable: true
|
|
4418
|
+
});
|
|
4120
4419
|
}
|
|
4121
4420
|
function applySessionInfoUpdate(conversation, update) {
|
|
4122
4421
|
if (hasOwn(update, "title")) conversation.title = update.title ?? null;
|
|
4123
4422
|
if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
|
|
4124
4423
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4424
|
+
//#endregion
|
|
4425
|
+
//#region src/session/conversation-retention.ts
|
|
4426
|
+
const MAX_RUNTIME_MESSAGES = 200;
|
|
4427
|
+
const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
|
|
4428
|
+
const MAX_RUNTIME_THINKING_CHARS = 4e3;
|
|
4429
|
+
const MAX_RUNTIME_TOOL_IO_CHARS = 4e3;
|
|
4430
|
+
const MAX_RUNTIME_REQUEST_TOKEN_USAGE = 100;
|
|
4431
|
+
function trimRuntimeText(value, maxChars) {
|
|
4432
|
+
if (value.length <= maxChars) return value;
|
|
4433
|
+
let end = Math.max(0, maxChars - 3);
|
|
4434
|
+
const last = value.charCodeAt(end - 1);
|
|
4435
|
+
if (last >= 55296 && last <= 56319) end -= 1;
|
|
4436
|
+
return `${value.slice(0, end)}...`;
|
|
4130
4437
|
}
|
|
4131
4438
|
function trimConversationForRuntime(conversation) {
|
|
4132
4439
|
if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-200);
|
|
@@ -4161,6 +4468,39 @@ function trimRuntimeToolResult(result) {
|
|
|
4161
4468
|
if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4162
4469
|
}
|
|
4163
4470
|
//#endregion
|
|
4471
|
+
//#region src/session/conversation-model.ts
|
|
4472
|
+
function isoNow() {
|
|
4473
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4474
|
+
}
|
|
4475
|
+
function createSessionConversation(timestamp = isoNow()) {
|
|
4476
|
+
return createConversation(timestamp);
|
|
4477
|
+
}
|
|
4478
|
+
function cloneSessionConversation(conversation) {
|
|
4479
|
+
return conversation ? cloneConversation(conversation) : createSessionConversation();
|
|
4480
|
+
}
|
|
4481
|
+
function recordPromptSubmission(conversation, prompt, timestamp = isoNow(), messageId) {
|
|
4482
|
+
let promptMessageId = messageId;
|
|
4483
|
+
if (reducePromptSubmission(conversation, prompt, timestamp, () => promptMessageId ??= randomUUID()).messageIndex === void 0) return;
|
|
4484
|
+
trimConversationForRuntime(conversation);
|
|
4485
|
+
return promptMessageId;
|
|
4486
|
+
}
|
|
4487
|
+
function recordSessionUpdate(conversation, state, notification, timestamp = isoNow(), userMessageId) {
|
|
4488
|
+
const { acpx } = reduceSessionUpdate(conversation, state, notification, timestamp, userMessageId ?? randomUUID);
|
|
4489
|
+
trimConversationForRuntime(conversation);
|
|
4490
|
+
return acpx;
|
|
4491
|
+
}
|
|
4492
|
+
function recordPromptResponseUsage(conversation, usage, promptMessageId, timestamp = isoNow()) {
|
|
4493
|
+
if (!reducePromptResponseUsage(conversation, usage, promptMessageId, timestamp)) return false;
|
|
4494
|
+
trimConversationForRuntime(conversation);
|
|
4495
|
+
return true;
|
|
4496
|
+
}
|
|
4497
|
+
function recordClientOperation(conversation, state, _operation, timestamp = isoNow()) {
|
|
4498
|
+
const acpx = state ?? {};
|
|
4499
|
+
conversation.updated_at = timestamp;
|
|
4500
|
+
trimConversationForRuntime(conversation);
|
|
4501
|
+
return acpx;
|
|
4502
|
+
}
|
|
4503
|
+
//#endregion
|
|
4164
4504
|
//#region src/runtime/engine/lifecycle.ts
|
|
4165
4505
|
function createInitialSessionRecord(params) {
|
|
4166
4506
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4283,19 +4623,27 @@ function storedEnvRecord(value) {
|
|
|
4283
4623
|
const result = {};
|
|
4284
4624
|
for (const [key, raw] of entries) {
|
|
4285
4625
|
if (typeof raw !== "string") continue;
|
|
4286
|
-
result
|
|
4626
|
+
Object.defineProperty(result, key, {
|
|
4627
|
+
value: raw,
|
|
4628
|
+
writable: true,
|
|
4629
|
+
enumerable: true,
|
|
4630
|
+
configurable: true
|
|
4631
|
+
});
|
|
4287
4632
|
}
|
|
4288
4633
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
4289
4634
|
}
|
|
4290
4635
|
function normalizeSystemPromptOption(value) {
|
|
4291
|
-
const prompt =
|
|
4636
|
+
const prompt = nonEmptyPromptText(value);
|
|
4292
4637
|
if (prompt !== void 0) return prompt;
|
|
4293
4638
|
const append = appendedSystemPrompt(value);
|
|
4294
4639
|
return append === void 0 ? void 0 : { append };
|
|
4295
4640
|
}
|
|
4296
4641
|
function appendedSystemPrompt(value) {
|
|
4297
4642
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
4298
|
-
return
|
|
4643
|
+
return nonEmptyPromptText(value.append);
|
|
4644
|
+
}
|
|
4645
|
+
function nonEmptyPromptText(value) {
|
|
4646
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4299
4647
|
}
|
|
4300
4648
|
function storedAllowedTools(value) {
|
|
4301
4649
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
|
|
@@ -4346,316 +4694,108 @@ function clearDesiredConfigOption(state, configId) {
|
|
|
4346
4694
|
function getDesiredModelId(state) {
|
|
4347
4695
|
return normalizeModelId(state?.session_options?.model);
|
|
4348
4696
|
}
|
|
4349
|
-
function setCurrentModelId(record, modelId) {
|
|
4350
|
-
const acpx = ensureAcpxState(record.acpx);
|
|
4351
|
-
const normalized = normalizeModelId(modelId);
|
|
4352
|
-
if (normalized) acpx.current_model_id = normalized;
|
|
4353
|
-
else delete acpx.current_model_id;
|
|
4354
|
-
record.acpx = acpx;
|
|
4355
|
-
}
|
|
4356
4697
|
function syncAdvertisedModelState(record, models) {
|
|
4357
4698
|
if (!models) return;
|
|
4358
|
-
const acpx = ensureAcpxState(record.acpx);
|
|
4359
|
-
applyAdvertisedModelState(acpx, models);
|
|
4360
|
-
record.acpx = acpx;
|
|
4361
|
-
}
|
|
4362
|
-
//#endregion
|
|
4363
|
-
//#region src/session/model-application.ts
|
|
4364
|
-
function currentModelIdFromSetModelResponse(response, fallbackModelId) {
|
|
4365
|
-
return modelStateFromConfigOptions(response?.configOptions)?.currentModelId ?? fallbackModelId;
|
|
4366
|
-
}
|
|
4367
|
-
async function applyRequestedModelIfAdvertised(params) {
|
|
4368
|
-
const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
|
|
4369
|
-
if (!requestedModel) return { applied: false };
|
|
4370
|
-
const warning = assertRequestedModelSupported({
|
|
4371
|
-
requestedModel,
|
|
4372
|
-
models: params.models,
|
|
4373
|
-
agentCommand: params.agentCommand,
|
|
4374
|
-
context: "apply"
|
|
4375
|
-
});
|
|
4376
|
-
if (warning) params.onWarning?.(warning);
|
|
4377
|
-
if (!params.models) return { applied: false };
|
|
4378
|
-
if (params.models.currentModelId === requestedModel) return { applied: true };
|
|
4379
|
-
return {
|
|
4380
|
-
applied: true,
|
|
4381
|
-
response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
|
|
4382
|
-
};
|
|
4383
|
-
}
|
|
4384
|
-
//#endregion
|
|
4385
|
-
//#region src/session/config-options.ts
|
|
4386
|
-
function applyConfigOptionsToState(state, configOptions) {
|
|
4387
|
-
const acpxState = cloneSessionAcpxState(state) ?? {};
|
|
4388
|
-
applyConfigOptionsModelState(acpxState, configOptions);
|
|
4389
|
-
return acpxState;
|
|
4390
|
-
}
|
|
4391
|
-
function applyConfigOptionsToRecord(record, result) {
|
|
4392
|
-
const configOptions = result?.configOptions;
|
|
4393
|
-
if (!configOptions) return;
|
|
4394
|
-
record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
|
|
4395
|
-
}
|
|
4396
|
-
function applyInitialModelSelection(record, originalModels, requestedModel, modelApplication) {
|
|
4397
|
-
applyConfigOptionsToRecord(record, modelApplication.response);
|
|
4398
|
-
syncAdvertisedModelState(record, modelApplication.response ? modelStateFromConfigOptions(modelApplication.response.configOptions) : originalModels);
|
|
4399
|
-
if (modelApplication.applied) setCurrentModelId(record, currentModelIdFromSetModelResponse(modelApplication.response, requestedModel));
|
|
4400
|
-
}
|
|
4401
|
-
function applyAcceptedConfigOptions(state, response) {
|
|
4402
|
-
const next = cloneSessionAcpxState(state) ?? {};
|
|
4403
|
-
if (!response) return next;
|
|
4404
|
-
applyConfigOptionsModelState(next, response.configOptions);
|
|
4405
|
-
if (!next.desired_config_options) return next;
|
|
4406
|
-
const desiredEntries = [];
|
|
4407
|
-
for (const option of response.configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(next.desired_config_options, option.id)) desiredEntries.push([option.id, option.currentValue]);
|
|
4408
|
-
if (desiredEntries.length > 0) next.desired_config_options = Object.fromEntries(desiredEntries);
|
|
4409
|
-
else delete next.desired_config_options;
|
|
4410
|
-
return next;
|
|
4411
|
-
}
|
|
4412
|
-
function applyModelSelection(state, modelId, response) {
|
|
4413
|
-
const modelConfigId = advertisedModelState(state)?.configId;
|
|
4414
|
-
const next = applyAcceptedConfigOptions(state, response);
|
|
4415
|
-
next.session_options = {
|
|
4416
|
-
...next.session_options,
|
|
4417
|
-
model: modelId
|
|
4418
|
-
};
|
|
4419
|
-
next.current_model_id = currentModelIdFromSetModelResponse(response, modelId);
|
|
4420
|
-
clearDesiredConfigOption(next, modelConfigId ?? advertisedModelState(next)?.configId);
|
|
4421
|
-
return next;
|
|
4422
|
-
}
|
|
4423
|
-
function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId) {
|
|
4424
|
-
if (configId === modelConfigId || configId === modelStateFromConfigOptions(response.configOptions)?.configId) return applyModelSelection(state, value, response);
|
|
4425
|
-
const next = cloneSessionAcpxState(state) ?? {};
|
|
4426
|
-
next.desired_config_options = {
|
|
4427
|
-
...next.desired_config_options,
|
|
4428
|
-
[configId]: value
|
|
4429
|
-
};
|
|
4430
|
-
return applyAcceptedConfigOptions(next, response);
|
|
4431
|
-
}
|
|
4432
|
-
//#endregion
|
|
4433
|
-
//#region src/session/turn-ownership.ts
|
|
4434
|
-
const LOCK_RETRY_MS = 15;
|
|
4435
|
-
const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
|
|
4436
|
-
let lastCreatedAt = 0;
|
|
4437
|
-
const localTurns = /* @__PURE__ */ new Map();
|
|
4438
|
-
async function readLock(filePath) {
|
|
4439
|
-
try {
|
|
4440
|
-
const stat = await fs$1.lstat(filePath, { bigint: true });
|
|
4441
|
-
if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
|
|
4442
|
-
return {
|
|
4443
|
-
stat,
|
|
4444
|
-
payload: await fs$1.readFile(filePath, "utf8")
|
|
4445
|
-
};
|
|
4446
|
-
} catch (error) {
|
|
4447
|
-
if (error.code === "ENOENT") return;
|
|
4448
|
-
throw error;
|
|
4449
|
-
}
|
|
4450
|
-
}
|
|
4451
|
-
function parseLock(payload) {
|
|
4452
|
-
try {
|
|
4453
|
-
return JSON.parse(payload);
|
|
4454
|
-
} catch {
|
|
4455
|
-
return;
|
|
4456
|
-
}
|
|
4457
|
-
}
|
|
4458
|
-
async function tryAcquireGuard(filePath, owner, signal) {
|
|
4459
|
-
try {
|
|
4460
|
-
return await acquireFileLock(filePath, {
|
|
4461
|
-
managerKey: "acpx.session-turn",
|
|
4462
|
-
lockPath: `${filePath}.guard`,
|
|
4463
|
-
staleMs: Infinity,
|
|
4464
|
-
timeoutMs: LOCK_RETRY_MS,
|
|
4465
|
-
retry: {
|
|
4466
|
-
retries: 8,
|
|
4467
|
-
minTimeout: 1,
|
|
4468
|
-
maxTimeout: 2,
|
|
4469
|
-
factor: 1,
|
|
4470
|
-
randomize: false
|
|
4471
|
-
},
|
|
4472
|
-
staleRecovery: "remove-if-unchanged",
|
|
4473
|
-
payload: () => owner.payload,
|
|
4474
|
-
shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
|
|
4475
|
-
shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
|
|
4476
|
-
});
|
|
4477
|
-
} catch (error) {
|
|
4478
|
-
if (error.code === "file_lock_timeout") return;
|
|
4479
|
-
throw error;
|
|
4480
|
-
}
|
|
4481
|
-
}
|
|
4482
|
-
async function removeObservedLock(filePath, observed) {
|
|
4483
|
-
const current = await readLock(filePath);
|
|
4484
|
-
if (!current) return true;
|
|
4485
|
-
if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
|
|
4486
|
-
await fs$1.unlink(filePath).catch((error) => {
|
|
4487
|
-
if (error.code !== "ENOENT") throw error;
|
|
4488
|
-
});
|
|
4489
|
-
return true;
|
|
4490
|
-
}
|
|
4491
|
-
async function recoverAbandonedLock(filePath, owner, signal) {
|
|
4492
|
-
const observed = await readLock(filePath);
|
|
4493
|
-
if (!observed) return true;
|
|
4494
|
-
const payload = parseLock(observed.payload);
|
|
4495
|
-
const pid = lockOwnerPid(payload);
|
|
4496
|
-
if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
|
|
4497
|
-
if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
|
|
4498
|
-
if (await removeObservedLock(filePath, observed)) {
|
|
4499
|
-
incrementPerfCounter("session.events.stale_lock_recovered");
|
|
4500
|
-
return true;
|
|
4501
|
-
}
|
|
4502
|
-
return false;
|
|
4503
|
-
}
|
|
4504
|
-
async function publishLock(filePath, payload, signal) {
|
|
4505
|
-
await withTempFile({
|
|
4506
|
-
rootDir: path.dirname(filePath),
|
|
4507
|
-
prefix: "session-turn",
|
|
4508
|
-
fileName: "lock"
|
|
4509
|
-
}, async (temporaryPath) => {
|
|
4510
|
-
await fs$1.writeFile(temporaryPath, payload, {
|
|
4511
|
-
encoding: "utf8",
|
|
4512
|
-
flag: "wx",
|
|
4513
|
-
mode: 384
|
|
4514
|
-
});
|
|
4515
|
-
signal?.throwIfAborted();
|
|
4516
|
-
try {
|
|
4517
|
-
await fs$1.link(temporaryPath, filePath);
|
|
4518
|
-
} catch (error) {
|
|
4519
|
-
if (!isHardlinkFallbackError(error)) throw error;
|
|
4520
|
-
await fs$1.writeFile(filePath, payload, {
|
|
4521
|
-
encoding: "utf8",
|
|
4522
|
-
flag: "wx",
|
|
4523
|
-
mode: 384
|
|
4524
|
-
});
|
|
4525
|
-
}
|
|
4526
|
-
});
|
|
4699
|
+
const acpx = ensureAcpxState(record.acpx);
|
|
4700
|
+
applyAdvertisedModelState(acpx, models);
|
|
4701
|
+
record.acpx = acpx;
|
|
4527
4702
|
}
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4703
|
+
//#endregion
|
|
4704
|
+
//#region src/session/model-application.ts
|
|
4705
|
+
function currentModelIdFromSetModelResponse(response, fallbackModelId) {
|
|
4706
|
+
return modelStateFromConfigOptions(response?.configOptions)?.currentModelId ?? fallbackModelId;
|
|
4531
4707
|
}
|
|
4532
|
-
async function
|
|
4533
|
-
const
|
|
4534
|
-
|
|
4535
|
-
|
|
4708
|
+
async function applyRequestedModelIfAdvertised(params) {
|
|
4709
|
+
const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
|
|
4710
|
+
if (!requestedModel) return { applied: false };
|
|
4711
|
+
const warning = assertRequestedModelSupported({
|
|
4712
|
+
requestedModel,
|
|
4713
|
+
models: params.models,
|
|
4714
|
+
agentCommand: params.agentCommand,
|
|
4715
|
+
context: "apply"
|
|
4536
4716
|
});
|
|
4537
|
-
if (
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4717
|
+
if (warning) params.onWarning?.(warning);
|
|
4718
|
+
if (!params.models) return { applied: false };
|
|
4719
|
+
const resolvedModelId = resolveRequestedModelId({
|
|
4720
|
+
...params,
|
|
4721
|
+
requestedModel
|
|
4722
|
+
});
|
|
4723
|
+
if (params.models.currentModelId === resolvedModelId) return {
|
|
4724
|
+
applied: true,
|
|
4725
|
+
modelId: requestedModel,
|
|
4726
|
+
resolvedModelId
|
|
4727
|
+
};
|
|
4728
|
+
return {
|
|
4729
|
+
applied: true,
|
|
4730
|
+
modelId: requestedModel,
|
|
4731
|
+
resolvedModelId,
|
|
4732
|
+
response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
|
|
4733
|
+
};
|
|
4543
4734
|
}
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
receipt.removeMarker = void 0;
|
|
4551
|
-
return true;
|
|
4735
|
+
//#endregion
|
|
4736
|
+
//#region src/session/config-options.ts
|
|
4737
|
+
function applyConfigOptionsToState(state, configOptions) {
|
|
4738
|
+
const acpxState = cloneSessionAcpxState(state) ?? {};
|
|
4739
|
+
applyConfigOptionsModelState(acpxState, configOptions);
|
|
4740
|
+
return acpxState;
|
|
4552
4741
|
}
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
const failures = [];
|
|
4558
|
-
try {
|
|
4559
|
-
if (!await removeTurnMarker(filePath, receipt)) return false;
|
|
4560
|
-
} catch (error) {
|
|
4561
|
-
failures.push(error);
|
|
4562
|
-
}
|
|
4563
|
-
if (failures.length === 0 || !receipt.admitted) try {
|
|
4564
|
-
await releaseTurnGuard(receipt);
|
|
4565
|
-
} catch (error) {
|
|
4566
|
-
failures.push(error);
|
|
4567
|
-
}
|
|
4568
|
-
if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
|
|
4569
|
-
if (failures.length === 1) throw failures[0];
|
|
4570
|
-
receipt.settled = true;
|
|
4571
|
-
localTurns.delete(filePath);
|
|
4572
|
-
return true;
|
|
4573
|
-
})();
|
|
4574
|
-
try {
|
|
4575
|
-
return await pending;
|
|
4576
|
-
} finally {
|
|
4577
|
-
if (receipt.pending === pending) {
|
|
4578
|
-
receipt.pending = void 0;
|
|
4579
|
-
receipt.failed = !receipt.settled;
|
|
4580
|
-
}
|
|
4581
|
-
}
|
|
4742
|
+
function applyConfigOptionsToRecord(record, result) {
|
|
4743
|
+
const configOptions = result?.configOptions;
|
|
4744
|
+
if (!Array.isArray(configOptions)) return;
|
|
4745
|
+
record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
|
|
4582
4746
|
}
|
|
4583
|
-
|
|
4584
|
-
|
|
4747
|
+
function applyInitialModelSelection(record, originalModels, modelApplication) {
|
|
4748
|
+
applyConfigOptionsToRecord(record, modelApplication.response);
|
|
4749
|
+
const configOptions = modelApplication.response?.configOptions;
|
|
4750
|
+
syncAdvertisedModelState(record, Array.isArray(configOptions) ? modelStateFromConfigOptions(configOptions) : originalModels);
|
|
4751
|
+
if (modelApplication.applied) record.acpx = applyModelSelection(record.acpx, modelApplication.modelId, modelApplication.response, modelApplication.resolvedModelId);
|
|
4585
4752
|
}
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
|
|
4753
|
+
function applyAcceptedConfigOptions(state, response, selection) {
|
|
4754
|
+
const next = cloneSessionAcpxState(state) ?? {};
|
|
4755
|
+
if (!Array.isArray(response?.configOptions)) {
|
|
4756
|
+
const option = next.config_options?.find((entry) => entry.id === selection.configId);
|
|
4757
|
+
if (option) option.currentValue = selection.value;
|
|
4758
|
+
return next;
|
|
4593
4759
|
}
|
|
4760
|
+
applyConfigOptionsModelState(next, response.configOptions);
|
|
4761
|
+
reconcileDesiredConfigOptions(next, response.configOptions);
|
|
4762
|
+
return next;
|
|
4594
4763
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4764
|
+
function reconcileDesiredConfigOptions(state, configOptions) {
|
|
4765
|
+
if (!state.desired_config_options) return;
|
|
4766
|
+
const desiredEntries = [];
|
|
4767
|
+
for (const option of configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(state.desired_config_options, option.id)) desiredEntries.push([option.id, option.currentValue]);
|
|
4768
|
+
if (desiredEntries.length > 0) state.desired_config_options = Object.fromEntries(desiredEntries);
|
|
4769
|
+
else delete state.desired_config_options;
|
|
4770
|
+
}
|
|
4771
|
+
function applyModelSelection(state, modelId, response, resolvedModelId = modelId) {
|
|
4772
|
+
const modelConfigId = advertisedModelState(state)?.configId;
|
|
4773
|
+
const next = applyAcceptedConfigOptions(state, response, {
|
|
4774
|
+
configId: modelConfigId,
|
|
4775
|
+
value: resolvedModelId
|
|
4776
|
+
});
|
|
4777
|
+
next.session_options = {
|
|
4778
|
+
...next.session_options,
|
|
4779
|
+
model: modelId
|
|
4607
4780
|
};
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
} catch (error) {
|
|
4612
|
-
localTurns.delete(filePath);
|
|
4613
|
-
throw error;
|
|
4614
|
-
}
|
|
4615
|
-
if (!receipt.guard) {
|
|
4616
|
-
localTurns.delete(filePath);
|
|
4617
|
-
return;
|
|
4618
|
-
}
|
|
4619
|
-
return receipt;
|
|
4781
|
+
next.current_model_id = currentModelIdFromSetModelResponse(response, resolvedModelId);
|
|
4782
|
+
clearDesiredConfigOption(next, modelConfigId ?? advertisedModelState(next)?.configId);
|
|
4783
|
+
return next;
|
|
4620
4784
|
}
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
if (!receipt) return;
|
|
4624
|
-
try {
|
|
4625
|
-
signal?.throwIfAborted();
|
|
4626
|
-
if (!await tryPublishLock(filePath, payload, owner, signal)) {
|
|
4627
|
-
await disposeTurn(filePath, receipt);
|
|
4628
|
-
return;
|
|
4629
|
-
}
|
|
4630
|
-
const observed = await readLock(filePath);
|
|
4631
|
-
if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
|
|
4632
|
-
signal?.throwIfAborted();
|
|
4633
|
-
receipt.admitted = true;
|
|
4634
|
-
receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
|
|
4635
|
-
return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
|
|
4636
|
-
} catch (error) {
|
|
4637
|
-
return await rejectAcquisition(filePath, receipt, error);
|
|
4638
|
-
}
|
|
4785
|
+
function isModelConfigSelection(configId, response, modelConfigId) {
|
|
4786
|
+
return configId === modelConfigId || configId === modelStateFromConfigOptions(response?.configOptions)?.configId;
|
|
4639
4787
|
}
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4788
|
+
function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId, resolvedValue = value) {
|
|
4789
|
+
if (isModelConfigSelection(configId, response, modelConfigId)) return applyModelSelection(state, value, response, resolvedValue);
|
|
4790
|
+
const next = cloneSessionAcpxState(state) ?? {};
|
|
4791
|
+
next.desired_config_options = {
|
|
4792
|
+
...next.desired_config_options,
|
|
4793
|
+
[configId]: value
|
|
4794
|
+
};
|
|
4795
|
+
return applyAcceptedConfigOptions(next, response, {
|
|
4796
|
+
configId,
|
|
4797
|
+
value
|
|
4645
4798
|
});
|
|
4646
|
-
const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
|
|
4647
|
-
const owner = await createLockOwner({ signal });
|
|
4648
|
-
lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
|
|
4649
|
-
const payload = `${JSON.stringify({
|
|
4650
|
-
...owner.payload,
|
|
4651
|
-
created_at: new Date(lastCreatedAt).toISOString()
|
|
4652
|
-
}, null, 2)}\n`;
|
|
4653
|
-
for (;;) {
|
|
4654
|
-
signal?.throwIfAborted();
|
|
4655
|
-
const turn = await tryAcquireTurn(filePath, payload, owner, signal);
|
|
4656
|
-
if (turn) return turn;
|
|
4657
|
-
await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
|
|
4658
|
-
}
|
|
4659
4799
|
}
|
|
4660
4800
|
//#endregion
|
|
4661
4801
|
//#region src/session/execution/direct-lifetime.ts
|
|
@@ -4754,13 +4894,15 @@ async function replayDesiredModel(params) {
|
|
|
4754
4894
|
context: "replay"
|
|
4755
4895
|
}), params.suppressWarnings);
|
|
4756
4896
|
if (!params.models) return { replayed: false };
|
|
4897
|
+
const resolvedModelId = resolveRequestedModelId({
|
|
4898
|
+
requestedModel: params.desiredModelId,
|
|
4899
|
+
models: params.models,
|
|
4900
|
+
agentCommand: params.record.agentCommand
|
|
4901
|
+
});
|
|
4757
4902
|
const response = await withTimeout(params.client.setSessionModel(params.sessionId, params.desiredModelId, params.models, params.replay.authority), params.timeoutMs);
|
|
4758
4903
|
params.replay.acknowledged = true;
|
|
4759
|
-
params.record.acpx = applyModelSelection(params.record.acpx, params.desiredModelId, response);
|
|
4760
|
-
const models =
|
|
4761
|
-
...params.models,
|
|
4762
|
-
currentModelId: params.desiredModelId
|
|
4763
|
-
};
|
|
4904
|
+
params.record.acpx = applyModelSelection(params.record.acpx, params.desiredModelId, response, resolvedModelId);
|
|
4905
|
+
const models = advertisedModelState(params.record.acpx);
|
|
4764
4906
|
if (params.verbose) process.stderr.write(`[acpx] replayed desired model ${params.desiredModelId} on ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
|
|
4765
4907
|
return {
|
|
4766
4908
|
replayed: true,
|
|
@@ -4785,13 +4927,20 @@ async function replayDesiredConfigOptions(params) {
|
|
|
4785
4927
|
for (const [configId, value] of Object.entries(params.desiredConfigOptions)) {
|
|
4786
4928
|
if (configId === replacingKey || acceptedConfigOptions && !acceptsSavedConfigValue(acceptedConfigOptions.find((option) => option.id === configId), value)) continue;
|
|
4787
4929
|
try {
|
|
4788
|
-
const
|
|
4930
|
+
const models = advertisedModelState(params.record.acpx);
|
|
4931
|
+
const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
|
|
4932
|
+
configId,
|
|
4933
|
+
value,
|
|
4934
|
+
models,
|
|
4935
|
+
agentCommand: params.record.agentCommand
|
|
4936
|
+
});
|
|
4937
|
+
const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value, models, params.replay.authority), params.timeoutMs);
|
|
4789
4938
|
params.replay.acknowledged = true;
|
|
4790
|
-
params.record.acpx = applyConfigOptionSelection(params.record.acpx, configId, value, response);
|
|
4791
|
-
acceptedConfigOptions =
|
|
4939
|
+
params.record.acpx = applyConfigOptionSelection(params.record.acpx, configId, value, response, modelConfigId, resolvedValue);
|
|
4940
|
+
acceptedConfigOptions = params.record.acpx.config_options;
|
|
4792
4941
|
result = {
|
|
4793
4942
|
replayed: true,
|
|
4794
|
-
models:
|
|
4943
|
+
models: advertisedModelState(params.record.acpx)
|
|
4795
4944
|
};
|
|
4796
4945
|
if (params.verbose) process.stderr.write(`[acpx] replayed desired config option ${configId} on ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
|
|
4797
4946
|
} catch (error) {
|
|
@@ -4823,7 +4972,7 @@ async function connectAndLoadSession(options) {
|
|
|
4823
4972
|
logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
|
|
4824
4973
|
const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
|
|
4825
4974
|
if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
|
|
4826
|
-
else await withTimeout(client.start(options.authority), options.timeoutMs);
|
|
4975
|
+
else await withTimeout(client.start(options.authority, { sessionOptions: mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(record)) }), options.timeoutMs);
|
|
4827
4976
|
assertControlAuthority(options.authority);
|
|
4828
4977
|
options.onClientAvailable?.(options.activeController);
|
|
4829
4978
|
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
@@ -5240,8 +5389,13 @@ async function runSessionSetModelDirect(options) {
|
|
|
5240
5389
|
replacingConfigOption: { key: "model" }
|
|
5241
5390
|
}, async ({ client, sessionId, record }) => {
|
|
5242
5391
|
const models = advertisedModelState(record.acpx);
|
|
5392
|
+
const resolvedModelId = resolveRequestedModelId({
|
|
5393
|
+
requestedModel: options.modelId,
|
|
5394
|
+
models,
|
|
5395
|
+
agentCommand: record.agentCommand
|
|
5396
|
+
});
|
|
5243
5397
|
const response = await withTimeout(client.setSessionModel(sessionId, options.modelId, models), options.timeoutMs);
|
|
5244
|
-
record.acpx = applyModelSelection(record.acpx, options.modelId, response);
|
|
5398
|
+
record.acpx = applyModelSelection(record.acpx, options.modelId, response, resolvedModelId);
|
|
5245
5399
|
return response;
|
|
5246
5400
|
}));
|
|
5247
5401
|
return {
|
|
@@ -5255,8 +5409,14 @@ async function runSessionSetConfigOptionDirect(options) {
|
|
|
5255
5409
|
replacingConfigOption: { key: options.configId }
|
|
5256
5410
|
}, async ({ client, sessionId, record }) => {
|
|
5257
5411
|
const models = advertisedModelState(record.acpx);
|
|
5412
|
+
const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
|
|
5413
|
+
configId: options.configId,
|
|
5414
|
+
value: options.value,
|
|
5415
|
+
models,
|
|
5416
|
+
agentCommand: record.agentCommand
|
|
5417
|
+
});
|
|
5258
5418
|
const response = await withTimeout(client.setSessionConfigOption(sessionId, options.configId, options.value, models), options.timeoutMs);
|
|
5259
|
-
record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, response,
|
|
5419
|
+
record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, response, modelConfigId, resolvedValue);
|
|
5260
5420
|
return response;
|
|
5261
5421
|
}));
|
|
5262
5422
|
return {
|
|
@@ -5402,7 +5562,11 @@ async function setSessionModelOnOwner(options) {
|
|
|
5402
5562
|
if (!submittedToOwner) return;
|
|
5403
5563
|
const record = await resolveSessionRecord(options.sessionId);
|
|
5404
5564
|
if (!submittedToOwner.persistsControlState) {
|
|
5405
|
-
record.acpx = applyModelSelection(record.acpx, options.modelId, submittedToOwner.value.response
|
|
5565
|
+
record.acpx = applyModelSelection(record.acpx, options.modelId, submittedToOwner.value.response, resolveRequestedModelId({
|
|
5566
|
+
requestedModel: options.modelId,
|
|
5567
|
+
models: advertisedModelState(record.acpx),
|
|
5568
|
+
agentCommand: record.agentCommand
|
|
5569
|
+
}));
|
|
5406
5570
|
await writeSessionRecord(record);
|
|
5407
5571
|
}
|
|
5408
5572
|
return {
|
|
@@ -5433,7 +5597,14 @@ async function setSessionConfigOptionOnOwner(options) {
|
|
|
5433
5597
|
if (!ownerResponse) return;
|
|
5434
5598
|
const record = await resolveSessionRecord(options.sessionId);
|
|
5435
5599
|
if (!ownerResponse.persistsControlState) {
|
|
5436
|
-
|
|
5600
|
+
const models = advertisedModelState(record.acpx);
|
|
5601
|
+
const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
|
|
5602
|
+
configId: options.configId,
|
|
5603
|
+
value: options.value,
|
|
5604
|
+
models,
|
|
5605
|
+
agentCommand: record.agentCommand
|
|
5606
|
+
});
|
|
5607
|
+
record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, ownerResponse.value, modelConfigId, resolvedValue);
|
|
5437
5608
|
await writeSessionRecord(record);
|
|
5438
5609
|
}
|
|
5439
5610
|
return {
|
|
@@ -5551,7 +5722,7 @@ async function closeSession(sessionId) {
|
|
|
5551
5722
|
}
|
|
5552
5723
|
//#endregion
|
|
5553
5724
|
//#region src/session/execution/session-management.ts
|
|
5554
|
-
async function createSessionRecordWithClient(client, options) {
|
|
5725
|
+
async function createSessionRecordWithClient(client, options, resumedRecordId) {
|
|
5555
5726
|
const cwd = absolutePath(options.cwd);
|
|
5556
5727
|
await withTimeout(client.start({ signal: options.signal }), options.timeoutMs);
|
|
5557
5728
|
assertControlAuthority({ signal: options.signal });
|
|
@@ -5561,7 +5732,7 @@ async function createSessionRecordWithClient(client, options) {
|
|
|
5561
5732
|
const lifecycle = client.getAgentLifecycleSnapshot();
|
|
5562
5733
|
const record = {
|
|
5563
5734
|
...createInitialSessionRecord({
|
|
5564
|
-
recordId:
|
|
5735
|
+
recordId: resumedRecordId ?? randomUUID(),
|
|
5565
5736
|
sessionId,
|
|
5566
5737
|
agentSessionId,
|
|
5567
5738
|
agentCommand: options.agentCommand,
|
|
@@ -5577,7 +5748,7 @@ async function createSessionRecordWithClient(client, options) {
|
|
|
5577
5748
|
};
|
|
5578
5749
|
persistSessionOptions(record, options.sessionOptions);
|
|
5579
5750
|
applyConfigOptionsToRecord(record, createdState.sessionResult);
|
|
5580
|
-
applyInitialModelSelection(record, createdState.sessionResult.models,
|
|
5751
|
+
applyInitialModelSelection(record, createdState.sessionResult.models, createdState.modelApplication);
|
|
5581
5752
|
await writeSessionRecord(record);
|
|
5582
5753
|
assertControlAuthority({ signal: options.signal });
|
|
5583
5754
|
return record;
|
|
@@ -5632,6 +5803,20 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
|
|
|
5632
5803
|
}
|
|
5633
5804
|
async function createSessionWithClient(options) {
|
|
5634
5805
|
assertControlAuthority({ signal: options.signal });
|
|
5806
|
+
let resumedRecordId;
|
|
5807
|
+
if (options.resumeSessionId) {
|
|
5808
|
+
const resumedRecord = await readSessionRecord(options.resumeSessionId);
|
|
5809
|
+
assertControlAuthority({ signal: options.signal });
|
|
5810
|
+
if (resumedRecord && resumedRecord.agentCommand === options.agentCommand) {
|
|
5811
|
+
resumedRecordId = resumedRecord.acpxRecordId;
|
|
5812
|
+
if (!resumedRecord.closed) await closeSession(resumedRecord.acpxRecordId);
|
|
5813
|
+
options = {
|
|
5814
|
+
...options,
|
|
5815
|
+
resumeSessionId: resumedRecord.acpSessionId
|
|
5816
|
+
};
|
|
5817
|
+
assertControlAuthority({ signal: options.signal });
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5635
5820
|
const client = new AcpClient({
|
|
5636
5821
|
agentCommand: options.agentCommand,
|
|
5637
5822
|
agentArgv: options.agentArgv,
|
|
@@ -5651,7 +5836,7 @@ async function createSessionWithClient(options) {
|
|
|
5651
5836
|
try {
|
|
5652
5837
|
const create = async () => {
|
|
5653
5838
|
assertControlAuthority({ signal: options.signal });
|
|
5654
|
-
const record = await createSessionRecordWithClient(client, options);
|
|
5839
|
+
const record = await createSessionRecordWithClient(client, options, resumedRecordId);
|
|
5655
5840
|
assertControlAuthority({ signal: options.signal });
|
|
5656
5841
|
return record;
|
|
5657
5842
|
};
|
|
@@ -5719,11 +5904,11 @@ async function listAgentSessions(options) {
|
|
|
5719
5904
|
}
|
|
5720
5905
|
async function ensureSession(options) {
|
|
5721
5906
|
const cwd = absolutePath(options.cwd);
|
|
5722
|
-
const ownership = await
|
|
5723
|
-
options.agentCommand,
|
|
5907
|
+
const ownership = await acquireSessionScope({
|
|
5908
|
+
agentCommand: options.agentCommand,
|
|
5724
5909
|
cwd,
|
|
5725
|
-
normalizeName(options.name)
|
|
5726
|
-
|
|
5910
|
+
name: normalizeName(options.name)
|
|
5911
|
+
}, options.signal);
|
|
5727
5912
|
try {
|
|
5728
5913
|
return await ensureSessionWithOwnership(options, cwd);
|
|
5729
5914
|
} finally {
|
|
@@ -6133,14 +6318,25 @@ function createOwnedSessionControls(options) {
|
|
|
6133
6318
|
},
|
|
6134
6319
|
setSessionModel: async (modelId, deadline) => {
|
|
6135
6320
|
const models = advertisedModelState(options.record.acpx);
|
|
6321
|
+
const resolvedModelId = resolveRequestedModelId({
|
|
6322
|
+
requestedModel: modelId,
|
|
6323
|
+
models,
|
|
6324
|
+
agentCommand: options.record.agentCommand
|
|
6325
|
+
});
|
|
6136
6326
|
return await acceptControl(options, (authority) => options.client.setSessionModel(options.sessionId(), modelId, models, authority), (response) => {
|
|
6137
|
-
options.record.acpx = applyModelSelection(options.record.acpx, modelId, response);
|
|
6327
|
+
options.record.acpx = applyModelSelection(options.record.acpx, modelId, response, resolvedModelId);
|
|
6138
6328
|
}, deadline);
|
|
6139
6329
|
},
|
|
6140
6330
|
setSessionConfigOption: async (configId, value, deadline) => {
|
|
6141
6331
|
const models = advertisedModelState(options.record.acpx);
|
|
6332
|
+
const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
|
|
6333
|
+
configId,
|
|
6334
|
+
value,
|
|
6335
|
+
models,
|
|
6336
|
+
agentCommand: options.record.agentCommand
|
|
6337
|
+
});
|
|
6142
6338
|
return await acceptControl(options, (authority) => options.client.setSessionConfigOption(options.sessionId(), configId, value, models, authority), (response) => {
|
|
6143
|
-
options.record.acpx = applyConfigOptionSelection(options.record.acpx, configId, value, response,
|
|
6339
|
+
options.record.acpx = applyConfigOptionSelection(options.record.acpx, configId, value, response, modelConfigId, resolvedValue);
|
|
6144
6340
|
}, deadline);
|
|
6145
6341
|
}
|
|
6146
6342
|
};
|
|
@@ -6503,7 +6699,12 @@ var LiveSessionCheckpoint = class {
|
|
|
6503
6699
|
async flushDirty() {
|
|
6504
6700
|
while (this.dirty) {
|
|
6505
6701
|
this.dirty = false;
|
|
6506
|
-
|
|
6702
|
+
try {
|
|
6703
|
+
await this.save();
|
|
6704
|
+
} catch (error) {
|
|
6705
|
+
this.dirty = true;
|
|
6706
|
+
throw error;
|
|
6707
|
+
}
|
|
6507
6708
|
}
|
|
6508
6709
|
}
|
|
6509
6710
|
};
|
|
@@ -6557,8 +6758,8 @@ var QueueTaskOutputFormatter = class {
|
|
|
6557
6758
|
flush() {}
|
|
6558
6759
|
};
|
|
6559
6760
|
function markOutputAlreadyEmitted(error, outputAlreadyEmitted) {
|
|
6560
|
-
if (!
|
|
6561
|
-
error.outputAlreadyEmitted =
|
|
6761
|
+
if (!error || typeof error !== "object") return;
|
|
6762
|
+
error.outputAlreadyEmitted = outputAlreadyEmitted;
|
|
6562
6763
|
}
|
|
6563
6764
|
async function runWithOptionalInterrupt(options) {
|
|
6564
6765
|
if (options.handleProcessInterrupts === false) return await options.run();
|
|
@@ -6601,11 +6802,17 @@ var AcpErrorTracker = class {
|
|
|
6601
6802
|
return this.latestInbound ?? this.outbound.findLast((acp) => outboundAcpErrorMatches(error, acp));
|
|
6602
6803
|
}
|
|
6603
6804
|
};
|
|
6604
|
-
function toPromptResult(stopReason, sessionId, client, meta) {
|
|
6805
|
+
function toPromptResult(stopReason, sessionId, client, before, meta) {
|
|
6806
|
+
const after = client.getPermissionStats();
|
|
6605
6807
|
return {
|
|
6606
6808
|
stopReason,
|
|
6607
6809
|
sessionId,
|
|
6608
|
-
permissionStats:
|
|
6810
|
+
permissionStats: {
|
|
6811
|
+
requested: after.requested - before.requested,
|
|
6812
|
+
approved: after.approved - before.approved,
|
|
6813
|
+
denied: after.denied - before.denied,
|
|
6814
|
+
cancelled: after.cancelled - before.cancelled
|
|
6815
|
+
},
|
|
6609
6816
|
...meta === void 0 ? {} : { _meta: meta }
|
|
6610
6817
|
};
|
|
6611
6818
|
}
|
|
@@ -6625,62 +6832,39 @@ async function applyPromptModelIfAdvertised(params) {
|
|
|
6625
6832
|
authority: { signal: params.signal },
|
|
6626
6833
|
onWarning: params.suppressWarnings ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
|
|
6627
6834
|
});
|
|
6628
|
-
if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response);
|
|
6835
|
+
if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response, result.resolvedModelId);
|
|
6629
6836
|
}
|
|
6630
|
-
function jsonRpcIdKey(
|
|
6837
|
+
function jsonRpcIdKey(message) {
|
|
6838
|
+
if (!("id" in message)) return;
|
|
6839
|
+
const value = message.id;
|
|
6631
6840
|
if (typeof value === "string") return `s:${value}`;
|
|
6632
6841
|
if (typeof value === "number" && Number.isFinite(value)) return `n:${value}`;
|
|
6633
6842
|
}
|
|
6634
|
-
function extractJsonRpcRequestInfo(message) {
|
|
6635
|
-
const candidate = message;
|
|
6636
|
-
if (typeof candidate.method !== "string") return;
|
|
6637
|
-
const idKey = jsonRpcIdKey(candidate.id);
|
|
6638
|
-
if (!idKey) return;
|
|
6639
|
-
return {
|
|
6640
|
-
idKey,
|
|
6641
|
-
method: candidate.method
|
|
6642
|
-
};
|
|
6643
|
-
}
|
|
6644
|
-
function extractJsonRpcResponseInfo(message) {
|
|
6645
|
-
const candidate = message;
|
|
6646
|
-
const idKey = jsonRpcIdKey(candidate.id);
|
|
6647
|
-
if (!idKey) return;
|
|
6648
|
-
const hasError = Object.hasOwn(candidate, "error");
|
|
6649
|
-
const hasResult = Object.hasOwn(candidate, "result");
|
|
6650
|
-
if (!hasError && !hasResult) return;
|
|
6651
|
-
return {
|
|
6652
|
-
idKey,
|
|
6653
|
-
hasError
|
|
6654
|
-
};
|
|
6655
|
-
}
|
|
6656
6843
|
const SESSION_RECONNECT_METHODS = /* @__PURE__ */ new Set(["session/load", "session/resume"]);
|
|
6657
|
-
function
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
if (request) {
|
|
6663
|
-
requestMethodById.set(request.idKey, request.method);
|
|
6664
|
-
continue;
|
|
6665
|
-
}
|
|
6666
|
-
const response = extractJsonRpcResponseInfo(message);
|
|
6667
|
-
if (!response || !response.hasError) continue;
|
|
6668
|
-
const requestMethod = requestMethodById.get(response.idKey);
|
|
6669
|
-
if (requestMethod && SESSION_RECONNECT_METHODS.has(requestMethod)) failedLoadRequestIds.add(response.idKey);
|
|
6670
|
-
}
|
|
6671
|
-
if (failedLoadRequestIds.size === 0) return messages;
|
|
6672
|
-
return messages.filter((message) => {
|
|
6673
|
-
const request = extractJsonRpcRequestInfo(message);
|
|
6674
|
-
if (request && SESSION_RECONNECT_METHODS.has(request.method) && failedLoadRequestIds.has(request.idKey)) return false;
|
|
6675
|
-
const response = extractJsonRpcResponseInfo(message);
|
|
6676
|
-
if (response && failedLoadRequestIds.has(response.idKey)) return false;
|
|
6677
|
-
return true;
|
|
6678
|
-
});
|
|
6844
|
+
function isReconnectRequest(direction, message) {
|
|
6845
|
+
return direction === "outbound" && "method" in message && typeof message.method === "string" && SESSION_RECONNECT_METHODS.has(message.method);
|
|
6846
|
+
}
|
|
6847
|
+
function isInboundResponse(direction, message) {
|
|
6848
|
+
return direction === "inbound" && ("result" in message || "error" in message);
|
|
6679
6849
|
}
|
|
6680
6850
|
function filterBufferedConnectOutput(messages, loadError) {
|
|
6681
6851
|
if (loadError == null) return messages;
|
|
6682
|
-
const
|
|
6683
|
-
|
|
6852
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6853
|
+
const suppressed = /* @__PURE__ */ new Set();
|
|
6854
|
+
for (const [index, { direction, message }] of messages.entries()) {
|
|
6855
|
+
const id = jsonRpcIdKey(message);
|
|
6856
|
+
if (!id) continue;
|
|
6857
|
+
if (isReconnectRequest(direction, message)) pending.set(id, index);
|
|
6858
|
+
else if (isInboundResponse(direction, message)) {
|
|
6859
|
+
const requestIndex = pending.get(id);
|
|
6860
|
+
if (requestIndex !== void 0 && "error" in message) {
|
|
6861
|
+
suppressed.add(requestIndex);
|
|
6862
|
+
suppressed.add(index);
|
|
6863
|
+
}
|
|
6864
|
+
pending.delete(id);
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
return messages.filter((_message, index) => !suppressed.has(index));
|
|
6684
6868
|
}
|
|
6685
6869
|
function emitPromptRetryNotice(params) {
|
|
6686
6870
|
if (params.suppressSdkConsoleErrors) return;
|
|
@@ -6744,7 +6928,7 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
|
|
|
6744
6928
|
suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
|
|
6745
6929
|
verbose: options.verbose,
|
|
6746
6930
|
promptRetries: task.promptRetries ?? options.promptRetries ?? 0,
|
|
6747
|
-
sessionOptions:
|
|
6931
|
+
sessionOptions: task.sessionOptions,
|
|
6748
6932
|
onClientAvailable: options.onClientAvailable,
|
|
6749
6933
|
onClientClosed: options.onClientClosed,
|
|
6750
6934
|
onClientCloseFailure: options.onClientCloseFailure,
|
|
@@ -7016,6 +7200,7 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7016
7200
|
verbose: options.verbose,
|
|
7017
7201
|
sessionOptions
|
|
7018
7202
|
});
|
|
7203
|
+
const permissionStatsBefore = client.getPermissionStats();
|
|
7019
7204
|
client.updateRuntimeOptions({
|
|
7020
7205
|
permissionMode: options.permissionMode,
|
|
7021
7206
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
@@ -7094,6 +7279,7 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7094
7279
|
client,
|
|
7095
7280
|
record,
|
|
7096
7281
|
resumePolicy: options.resumePolicy,
|
|
7282
|
+
sessionOptions: options.sessionOptions,
|
|
7097
7283
|
replacingConfigOption: requestedModelId(options.sessionOptions?.model) ? { key: "model" } : void 0,
|
|
7098
7284
|
timeoutMs: options.timeoutMs,
|
|
7099
7285
|
verbose: options.verbose,
|
|
@@ -7171,7 +7357,7 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7171
7357
|
applyConversation(record, conversation);
|
|
7172
7358
|
const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
|
|
7173
7359
|
attachAcpErrorPayload(propagated, normalizedError.acp);
|
|
7174
|
-
propagated
|
|
7360
|
+
markOutputAlreadyEmitted(propagated, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
|
|
7175
7361
|
propagated.normalizedOutputError = normalizedError;
|
|
7176
7362
|
throw propagated;
|
|
7177
7363
|
};
|
|
@@ -7198,8 +7384,10 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7198
7384
|
record.lastUsedAt = now;
|
|
7199
7385
|
record.closed = false;
|
|
7200
7386
|
record.closedAt = void 0;
|
|
7201
|
-
|
|
7202
|
-
|
|
7387
|
+
if (client.initializeResult) {
|
|
7388
|
+
record.protocolVersion = client.initializeResult.protocolVersion;
|
|
7389
|
+
record.agentCapabilities = client.initializeResult.agentCapabilities;
|
|
7390
|
+
}
|
|
7203
7391
|
applyConversation(record, conversation);
|
|
7204
7392
|
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
7205
7393
|
stopTotalTimer();
|
|
@@ -7209,22 +7397,27 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7209
7397
|
options.ownedSignal?.throwIfAborted();
|
|
7210
7398
|
const { sessionId: activeSessionId, resumed, loadError } = await connectForPrompt();
|
|
7211
7399
|
options.ownedSignal?.throwIfAborted();
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7400
|
+
try {
|
|
7401
|
+
await applyPromptModelIfAdvertised({
|
|
7402
|
+
client,
|
|
7403
|
+
sessionId: activeSessionId,
|
|
7404
|
+
requestedModel: options.sessionOptions?.model,
|
|
7405
|
+
record,
|
|
7406
|
+
timeoutMs: options.timeoutMs,
|
|
7407
|
+
suppressWarnings: options.suppressSdkConsoleErrors,
|
|
7408
|
+
signal: options.ownedSignal
|
|
7409
|
+
});
|
|
7410
|
+
} catch (error) {
|
|
7411
|
+
if (error instanceof TimeoutError) closeClientOnExit = true;
|
|
7412
|
+
throw error;
|
|
7413
|
+
}
|
|
7221
7414
|
options.ownedSignal?.throwIfAborted();
|
|
7222
7415
|
output.setContext({ sessionId: record.acpxRecordId });
|
|
7223
7416
|
await liveCheckpoint.checkpoint();
|
|
7224
7417
|
const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
|
|
7225
7418
|
promptTurnActive = false;
|
|
7226
7419
|
return {
|
|
7227
|
-
...toPromptResult(response.stopReason, record.acpxRecordId, client, response._meta),
|
|
7420
|
+
...toPromptResult(response.stopReason, record.acpxRecordId, client, permissionStatsBefore, response._meta),
|
|
7228
7421
|
record,
|
|
7229
7422
|
resumed,
|
|
7230
7423
|
loadError
|
|
@@ -7310,6 +7503,7 @@ async function runOnce(options, control) {
|
|
|
7310
7503
|
let promptTurnActive = false;
|
|
7311
7504
|
let promptTurnHadSideEffects = false;
|
|
7312
7505
|
let controlState = {};
|
|
7506
|
+
let sawOwnedModelConfig = false;
|
|
7313
7507
|
const acpErrors = new AcpErrorTracker();
|
|
7314
7508
|
const client = new AcpClient({
|
|
7315
7509
|
agentCommand: options.agentCommand,
|
|
@@ -7330,7 +7524,10 @@ async function runOnce(options, control) {
|
|
|
7330
7524
|
acpErrors.observe(output, direction, message);
|
|
7331
7525
|
},
|
|
7332
7526
|
onSessionUpdate: (notification) => {
|
|
7333
|
-
if (notification.update.sessionUpdate === "config_option_update"
|
|
7527
|
+
if (notification.update.sessionUpdate === "config_option_update" && client.hasReusableSession(notification.sessionId)) {
|
|
7528
|
+
controlState = applyConfigOptionsToState(controlState, notification.update.configOptions);
|
|
7529
|
+
sawOwnedModelConfig ||= controlState.model_control === "config_option";
|
|
7530
|
+
}
|
|
7334
7531
|
if (promptTurnActive) promptTurnHadSideEffects = true;
|
|
7335
7532
|
options.onSessionUpdate?.(notification);
|
|
7336
7533
|
},
|
|
@@ -7344,7 +7541,13 @@ async function runOnce(options, control) {
|
|
|
7344
7541
|
},
|
|
7345
7542
|
sessionOptions: options.sessionOptions
|
|
7346
7543
|
});
|
|
7544
|
+
const permissionStatsBefore = client.getPermissionStats();
|
|
7347
7545
|
const closeOwnedClient = ownDirectClient(client, control?.signal);
|
|
7546
|
+
const initializeControlState = (createdSession) => {
|
|
7547
|
+
const configOptions = controlState.config_options ?? createdSession.configOptions ?? [];
|
|
7548
|
+
if (createdSession.models && !sawOwnedModelConfig) applyAdvertisedModelState(controlState, createdSession.models);
|
|
7549
|
+
controlState = applyConfigOptionsToState(controlState, configOptions);
|
|
7550
|
+
};
|
|
7348
7551
|
const runExecPromptAttempt = async (sessionId) => {
|
|
7349
7552
|
assertControlAuthority(authority);
|
|
7350
7553
|
acpErrors.reset();
|
|
@@ -7377,29 +7580,34 @@ async function runOnce(options, control) {
|
|
|
7377
7580
|
});
|
|
7378
7581
|
assertControlAuthority(authority);
|
|
7379
7582
|
const sessionId = createdSession.sessionId;
|
|
7380
|
-
|
|
7381
|
-
controlState = applyConfigOptionsToState(controlState, createdSession.configOptions ?? []);
|
|
7583
|
+
initializeControlState(createdSession);
|
|
7382
7584
|
const modelApplication = await applyRequestedModelIfAdvertised({
|
|
7383
7585
|
client,
|
|
7384
7586
|
sessionId,
|
|
7385
7587
|
requestedModel: options.sessionOptions?.model,
|
|
7386
|
-
models:
|
|
7588
|
+
models: advertisedModelState(controlState),
|
|
7387
7589
|
agentCommand: options.agentCommand,
|
|
7388
7590
|
timeoutMs: options.timeoutMs,
|
|
7389
7591
|
authority,
|
|
7390
7592
|
onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
|
|
7391
7593
|
});
|
|
7392
|
-
if (modelApplication.
|
|
7594
|
+
if (modelApplication.applied) controlState = applyModelSelection(controlState, modelApplication.modelId, modelApplication.response, modelApplication.resolvedModelId);
|
|
7393
7595
|
for (const configOption of options.configOptions ?? []) {
|
|
7394
7596
|
assertControlAuthority(authority);
|
|
7395
|
-
const
|
|
7396
|
-
|
|
7597
|
+
const models = advertisedModelState(controlState);
|
|
7598
|
+
const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
|
|
7599
|
+
...configOption,
|
|
7600
|
+
models,
|
|
7601
|
+
agentCommand: options.agentCommand
|
|
7602
|
+
});
|
|
7603
|
+
const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, models, authority), options.timeoutMs);
|
|
7604
|
+
controlState = applyConfigOptionSelection(controlState, configOption.configId, configOption.value, response, modelConfigId, resolvedValue);
|
|
7397
7605
|
}
|
|
7398
7606
|
assertControlAuthority(authority);
|
|
7399
7607
|
output.setContext({ sessionId });
|
|
7400
7608
|
const response = await runExecPromptWithRetries(sessionId);
|
|
7401
7609
|
promptTurnActive = false;
|
|
7402
|
-
return toPromptResult(response.stopReason, sessionId, client, response._meta);
|
|
7610
|
+
return toPromptResult(response.stopReason, sessionId, client, permissionStatsBefore, response._meta);
|
|
7403
7611
|
},
|
|
7404
7612
|
handleInterrupt: async () => {
|
|
7405
7613
|
await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
|
|
@@ -7626,31 +7834,50 @@ function createLeaseHeartbeat(lease, shutdown) {
|
|
|
7626
7834
|
};
|
|
7627
7835
|
}
|
|
7628
7836
|
async function runSessionQueueOwner(options) {
|
|
7629
|
-
const
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
let
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
}
|
|
7837
|
+
const signals = [
|
|
7838
|
+
"SIGINT",
|
|
7839
|
+
"SIGTERM",
|
|
7840
|
+
"SIGHUP"
|
|
7841
|
+
];
|
|
7842
|
+
let stopRequested = false;
|
|
7843
|
+
let requestRuntimeStop;
|
|
7844
|
+
const onSignal = () => {
|
|
7845
|
+
stopRequested = true;
|
|
7846
|
+
requestRuntimeStop?.();
|
|
7847
|
+
};
|
|
7640
7848
|
try {
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7849
|
+
for (const signal of signals) process.on(signal, onSignal);
|
|
7850
|
+
const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
|
|
7851
|
+
path: options.mcpConfigPath,
|
|
7852
|
+
fingerprint: options.mcpConfigFingerprint
|
|
7853
|
+
});
|
|
7854
|
+
if (!lease) return;
|
|
7855
|
+
let runtimeFailure;
|
|
7856
|
+
try {
|
|
7857
|
+
await runQueueOwnerRuntime(options, lease, (requestStop) => {
|
|
7858
|
+
requestRuntimeStop = requestStop;
|
|
7859
|
+
if (stopRequested) requestStop();
|
|
7860
|
+
});
|
|
7861
|
+
} catch (error) {
|
|
7862
|
+
runtimeFailure = { error };
|
|
7863
|
+
}
|
|
7864
|
+
try {
|
|
7865
|
+
await releaseQueueOwnerLease(lease);
|
|
7866
|
+
} catch (error) {
|
|
7867
|
+
if (runtimeFailure) throw new AggregateError([runtimeFailure.error, error], "Queue owner shutdown failed", { cause: error });
|
|
7868
|
+
throw error;
|
|
7869
|
+
}
|
|
7870
|
+
if (runtimeFailure) throw runtimeFailure.error;
|
|
7871
|
+
} finally {
|
|
7872
|
+
for (const signal of signals) process.off(signal, onSignal);
|
|
7645
7873
|
}
|
|
7646
|
-
if (runtimeFailure) throw runtimeFailure.error;
|
|
7647
7874
|
}
|
|
7648
7875
|
async function keepOwnerForPendingControls(controls, shutdown) {
|
|
7649
7876
|
if (shutdown.requested || !controls.hasPending) return false;
|
|
7650
7877
|
await controls.drainAdmitted();
|
|
7651
7878
|
return !shutdown.requested;
|
|
7652
7879
|
}
|
|
7653
|
-
async function runQueueOwnerRuntime(options, lease) {
|
|
7880
|
+
async function runQueueOwnerRuntime(options, lease, bindStopRequest) {
|
|
7654
7881
|
const sessionRecord = await resolveSessionRecord(options.sessionId);
|
|
7655
7882
|
let owner;
|
|
7656
7883
|
let heartbeatTimer;
|
|
@@ -7724,13 +7951,8 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7724
7951
|
sessionId: options.sessionId,
|
|
7725
7952
|
verbose: options.verbose
|
|
7726
7953
|
});
|
|
7727
|
-
|
|
7728
|
-
shutdown.request();
|
|
7729
|
-
};
|
|
7954
|
+
bindStopRequest(shutdown.request);
|
|
7730
7955
|
const heartbeat = createLeaseHeartbeat(lease, shutdown);
|
|
7731
|
-
process.once("SIGINT", onSignal);
|
|
7732
|
-
process.once("SIGTERM", onSignal);
|
|
7733
|
-
process.once("SIGHUP", onSignal);
|
|
7734
7956
|
try {
|
|
7735
7957
|
owner = await SessionQueueOwner.start(lease, {
|
|
7736
7958
|
cancelPrompt: async () => {
|
|
@@ -7812,9 +8034,6 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7812
8034
|
}
|
|
7813
8035
|
}
|
|
7814
8036
|
} finally {
|
|
7815
|
-
process.off("SIGINT", onSignal);
|
|
7816
|
-
process.off("SIGTERM", onSignal);
|
|
7817
|
-
process.off("SIGHUP", onSignal);
|
|
7818
8037
|
await shutdown.shutdown();
|
|
7819
8038
|
await lease.updates;
|
|
7820
8039
|
}
|
|
@@ -7858,6 +8077,6 @@ async function sendSession(options) {
|
|
|
7858
8077
|
}
|
|
7859
8078
|
const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
|
|
7860
8079
|
//#endregion
|
|
7861
|
-
export { isRequestedModelUnsupportedError as $, applyInitialModelSelection as A, reconcileAgentSessionId as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, persistSessionOptions as F,
|
|
8080
|
+
export { isRequestedModelUnsupportedError as $, applyInitialModelSelection as A, reconcileAgentSessionId as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, persistSessionOptions as F, recordSessionUpdate as G, createSessionConversation as H, sessionOptionsFromRecord as I, advertisedModelState as J, trimConversationForRuntime as K, applyConversation as L, applyRequestedModelIfAdvertised as M, setDesiredModeId as N, applyConfigOptionSelection as O, normalizeSessionOptionShape as P, RequestedModelUnsupportedError as Q, applyLifecycleSnapshotToRecord as R, setSessionMode as S, setSessionModelOnOwner as T, recordClientOperation as U, cloneSessionConversation as V, recordPromptSubmission as W, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as X, AcpClient as Y, REQUESTED_MODEL_UNSUPPORTED_REASONS as Z, cancelSessionPrompt as _, runQueuedTask as a, normalizeQueueOwnerTtlMs as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, listSessionEvents as ct, flushPerfMetricsCapture as d, TimeoutError as dt, resolveRequestedConfigOption as et, installPerfMetricsCapture as f, assertControlAuthority as ft, listAgentSessions as g, ensureSession as h, runOnce as i, DEFAULT_QUEUE_OWNER_TTL_MS as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, isLegacyZedCodexAcpInvocation as lt, createSessionWithClient as m, withTimeout as mt, runSessionQueueOwner as n, getAcpxVersion as nt, sendSessionDirect as o, acquireSessionImport as ot, createSession as p, withInterrupt as pt, cloneSessionAcpxState as q, sendSession as r, permissionModeSatisfies as rt, DISCARD_OUTPUT_FORMATTER as s, acquireSessionScope as st, queueOwnerRuntimeTestInternals as t, resolveRequestedModelId as tt, buildQueueOwnerArgOverride as u, InterruptedError as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, createInitialSessionRecord as z };
|
|
7862
8081
|
|
|
7863
|
-
//# sourceMappingURL=queue-owner-runtime-
|
|
8082
|
+
//# sourceMappingURL=queue-owner-runtime-C0wIYowh.js.map
|