acpx 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-registry-DU6mhZBL.js → agent-registry-DdKB-zia.js} +20 -17
- package/dist/agent-registry-DdKB-zia.js.map +1 -0
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-CiyfEC7L.js → cli-8mVHeUXh.js} +5 -5
- package/dist/{cli-CiyfEC7L.js.map → cli-8mVHeUXh.js.map} +1 -1
- package/dist/cli.js +122 -94
- package/dist/cli.js.map +1 -1
- package/dist/{client-C6NUFqr-.d.ts → client-DeNKZZcu.d.ts} +19 -13
- package/dist/client-DeNKZZcu.d.ts.map +1 -0
- package/dist/{flows-CTOdIk-m.js → flows-WOQNzCMC.js} +5 -5
- package/dist/{flows-CTOdIk-m.js.map → flows-WOQNzCMC.js.map} +1 -1
- package/dist/flows.d.ts +1 -1
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-DbCcOAzM.js → invocation-options-B6d7TI-y.js} +7 -6
- package/dist/{invocation-options-DbCcOAzM.js.map → invocation-options-B6d7TI-y.js.map} +1 -1
- package/dist/{ipc-D3akHMEq.js → ipc-BJrvfNXr.js} +307 -90
- package/dist/ipc-BJrvfNXr.js.map +1 -0
- package/dist/{output-BPPjvJTI.js → output-CL18YmsG.js} +9 -6
- package/dist/output-CL18YmsG.js.map +1 -0
- package/dist/{queue-owner-runtime--IbbndGz.js → queue-owner-runtime-Cr0dWJuL.js} +1008 -463
- package/dist/queue-owner-runtime-Cr0dWJuL.js.map +1 -0
- package/dist/runtime.d.ts +129 -39
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +378 -176
- package/dist/runtime.js.map +1 -1
- package/dist/{session-watch--UX5dh00.js → session-watch-B8Q_TsGf.js} +4 -4
- package/dist/{session-watch--UX5dh00.js.map → session-watch-B8Q_TsGf.js.map} +1 -1
- package/dist/{watch-BbBxO-kb.js → watch-Dp73ph8h.js} +2 -2
- package/dist/{watch-BbBxO-kb.js.map → watch-Dp73ph8h.js.map} +1 -1
- package/package.json +3 -3
- package/skills/acpx/SKILL.md +27 -7
- package/dist/agent-registry-DU6mhZBL.js.map +0 -1
- package/dist/client-C6NUFqr-.d.ts.map +0 -1
- package/dist/ipc-D3akHMEq.js.map +0 -1
- package/dist/output-BPPjvJTI.js.map +0 -1
- package/dist/queue-owner-runtime--IbbndGz.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { A as resolveAgentCommandParts, C as asAbsoluteCwd, E as isoNow$2, F as waitForSpawn, M as runTimedExecFile, N as splitCommandLine, P as waitForChildExit, S as PROCESS_HELPER_TIMEOUT_MS, T as isChildProcessRunning, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentSessionCwd, k as requireAgentStdio, v as buildTerminalShellSpawnCommand, w as basenameToken, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-
|
|
1
|
+
import { $ as setPerfGauge, A as isoNow$1, At as AcpxOperationalError, Bt as QueueConnectionError, C as promptToDisplayText, D as findGitRepositoryRoot, E as absolutePath, Ft as ClaudeAcpSessionCreateTimeoutError, Gt as SessionResumeRequiredError, H as normalizeAgentSessionId, Ht as SessionModeReplayError, I as resolveSessionRecord, It as CopilotAcpUnsupportedError, J as getPerfMetricsSnapshot, Kt as UnsupportedPromptContentError, L as writeSessionRecord, Lt as GeminiAcpStartupTimeoutError, Mt as AgentSpawnError, N as normalizeName, Nt as AgentStartupError, Pt as AuthPolicyError, Q as resetPerfMetrics, Rt as PermissionDeniedError, St as EXIT_CODES, Tt as OUTPUT_ERROR_ORIGINS, U as SESSION_JOURNAL_SCHEMA, Ut as SessionModelReplayError, V as extractAgentSessionId, Vt as SessionConfigOptionReplayError, W as SessionJournalReader, X as measurePerf, Y as incrementPerfCounter, Z as recordPerfDuration, _ as waitMs$1, _t as isProcessDefinitelyDead, a as trySetModelOnRunningOwner, b as getUnsupportedPromptContentMessage, bt as isAcpResourceNotFoundError, c as queueRequestExceedsLimit, dt as sessionEventActivePath, et as startPerfTimer, f as refreshQueueOwnerLease, ft as sessionEventLockPath, g as tryAcquireQueueOwnerLease, gt as isProcessAlive, h as terminateQueueOwnerForSession, i as trySetModeOnRunningOwner, it as isSessionUpdateNotification, jt as AgentDisconnectedError, k as findSessionByDirectoryWalk, l as parseQueueRequest, lt as defaultSessionEventLog, m as terminateProcess, n as tryCloseSessionOnRunningOwner, nt as isAcpJsonRpcMessage, o as trySubmitToRunningOwner, p as releaseQueueOwnerLease, pt as sessionEventSegmentPath, q as formatPerfMetric, r as trySetConfigOptionOnRunningOwner, rt as isAcpMessageObject, s as queueRequestByteLimit, t as tryCancelOnRunningOwner, ut as sessionBaseDir, v as QueueLeaseGuardSettlementError, vt as extractAcpError, w as textPrompt, wt as OUTPUT_ERROR_CODES, yt as formatUnknownErrorMessage, zt as PermissionPromptUnavailableError } from "./ipc-BJrvfNXr.js";
|
|
2
|
+
import { A as resolveAgentCommandParts, C as asAbsoluteCwd, E as isoNow$2, F as waitForSpawn, M as runTimedExecFile, N as splitCommandLine, P as waitForChildExit, S as PROCESS_HELPER_TIMEOUT_MS, T as isChildProcessRunning, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentSessionCwd, k as requireAgentStdio, v as buildTerminalShellSpawnCommand, w as basenameToken, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DdKB-zia.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";
|
|
@@ -15,6 +15,7 @@ import { isPathInside } from "@openclaw/fs-safe/path";
|
|
|
15
15
|
import { root } from "@openclaw/fs-safe/root";
|
|
16
16
|
import readline from "node:readline/promises";
|
|
17
17
|
import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
|
|
18
|
+
import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
|
|
18
19
|
import net from "node:net";
|
|
19
20
|
//#region src/acp/error-normalization.ts
|
|
20
21
|
const AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
|
|
@@ -485,21 +486,57 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
|
|
|
485
486
|
}
|
|
486
487
|
//#endregion
|
|
487
488
|
//#region src/permission-prompt.ts
|
|
489
|
+
let promptQueue = Promise.resolve();
|
|
488
490
|
async function promptForPermission(options) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
491
|
+
options.signal?.throwIfAborted();
|
|
492
|
+
const result = promptQueue.then(() => askPermission(options));
|
|
493
|
+
promptQueue = result.then(() => {}, () => {});
|
|
494
|
+
const signal = options.signal;
|
|
495
|
+
if (!signal) return await result;
|
|
496
|
+
let onAbort = () => {};
|
|
497
|
+
const aborted = new Promise((_, reject) => {
|
|
498
|
+
onAbort = () => reject(signal.reason);
|
|
499
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
500
|
+
});
|
|
501
|
+
try {
|
|
502
|
+
return await Promise.race([result, aborted]);
|
|
503
|
+
} finally {
|
|
504
|
+
signal.removeEventListener("abort", onAbort);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function askPermission(options) {
|
|
508
|
+
options.signal?.throwIfAborted();
|
|
509
|
+
if (!canPrompt()) return false;
|
|
510
|
+
writePromptDetails(options);
|
|
492
511
|
const rl = readline.createInterface({
|
|
493
512
|
input: process.stdin,
|
|
494
513
|
output: process.stderr
|
|
495
514
|
});
|
|
515
|
+
const questionController = new AbortController();
|
|
516
|
+
const signal = options.signal ? AbortSignal.any([options.signal, questionController.signal]) : questionController.signal;
|
|
517
|
+
let onClose = () => {};
|
|
518
|
+
const closed = new Promise((resolve) => {
|
|
519
|
+
onClose = () => resolve(void 0);
|
|
520
|
+
rl.once("close", onClose);
|
|
521
|
+
});
|
|
496
522
|
try {
|
|
497
|
-
const
|
|
523
|
+
const answer = await Promise.race([rl.question(options.prompt, { signal }), closed]);
|
|
524
|
+
options.signal?.throwIfAborted();
|
|
525
|
+
const normalized = answer?.trim().toLowerCase();
|
|
498
526
|
return normalized === "y" || normalized === "yes";
|
|
499
527
|
} finally {
|
|
528
|
+
rl.removeListener("close", onClose);
|
|
529
|
+
questionController.abort();
|
|
500
530
|
rl.close();
|
|
501
531
|
}
|
|
502
532
|
}
|
|
533
|
+
function canPrompt() {
|
|
534
|
+
return process.stdin.isTTY && process.stderr.isTTY && !process.stdin.readableEnded && !process.stdin.destroyed;
|
|
535
|
+
}
|
|
536
|
+
function writePromptDetails(options) {
|
|
537
|
+
if (options.header) process.stderr.write(`\n${options.header}\n`);
|
|
538
|
+
if (options.details && options.details.trim().length > 0) process.stderr.write(`${options.details}\n`);
|
|
539
|
+
}
|
|
503
540
|
//#endregion
|
|
504
541
|
//#region src/filesystem.ts
|
|
505
542
|
const WRITE_PREVIEW_MAX_LINES = 16;
|
|
@@ -515,11 +552,12 @@ function toWritePreview(content) {
|
|
|
515
552
|
if (preview.length > WRITE_PREVIEW_MAX_CHARS) preview = `${preview.slice(0, 1197)}...`;
|
|
516
553
|
return preview;
|
|
517
554
|
}
|
|
518
|
-
async function defaultConfirmWrite(filePath, preview) {
|
|
555
|
+
async function defaultConfirmWrite(filePath, preview, signal) {
|
|
519
556
|
return await promptForPermission({
|
|
520
557
|
header: `[permission] Allow write to ${filePath}?`,
|
|
521
558
|
details: preview,
|
|
522
|
-
prompt: "Allow write? (y/N) "
|
|
559
|
+
prompt: "Allow write? (y/N) ",
|
|
560
|
+
signal
|
|
523
561
|
});
|
|
524
562
|
}
|
|
525
563
|
function canPromptForPermission$2() {
|
|
@@ -545,7 +583,8 @@ var FileSystemHandlers = class {
|
|
|
545
583
|
this.permissionMode = permissionMode;
|
|
546
584
|
this.nonInteractivePermissions = nonInteractivePermissions ?? "deny";
|
|
547
585
|
}
|
|
548
|
-
async readTextFile(params) {
|
|
586
|
+
async readTextFile(params, authority) {
|
|
587
|
+
assertControlAuthority(authority);
|
|
549
588
|
const filePath = this.resolvePathWithinRoot(params.path);
|
|
550
589
|
const summary = `read_text_file: ${filePath}`;
|
|
551
590
|
this.emitOperation({
|
|
@@ -557,7 +596,8 @@ var FileSystemHandlers = class {
|
|
|
557
596
|
});
|
|
558
597
|
try {
|
|
559
598
|
if (this.permissionMode === "deny-all") throw new PermissionDeniedError("Permission denied for fs/read_text_file (--deny-all)");
|
|
560
|
-
const content = await (await this.getWorkspace()).readText(
|
|
599
|
+
const content = await (await this.getWorkspace()).readText(filePath);
|
|
600
|
+
assertControlAuthority(authority);
|
|
561
601
|
const sliced = this.sliceContent(content, params.line, params.limit);
|
|
562
602
|
this.emitOperation({
|
|
563
603
|
method: "fs/read_text_file",
|
|
@@ -579,7 +619,8 @@ var FileSystemHandlers = class {
|
|
|
579
619
|
throw error;
|
|
580
620
|
}
|
|
581
621
|
}
|
|
582
|
-
async writeTextFile(params) {
|
|
622
|
+
async writeTextFile(params, authority) {
|
|
623
|
+
assertControlAuthority(authority);
|
|
583
624
|
const filePath = this.resolvePathWithinRoot(params.path);
|
|
584
625
|
const preview = toWritePreview(params.content);
|
|
585
626
|
const summary = `write_text_file: ${filePath}`;
|
|
@@ -591,9 +632,17 @@ var FileSystemHandlers = class {
|
|
|
591
632
|
timestamp: nowIso$1()
|
|
592
633
|
});
|
|
593
634
|
try {
|
|
594
|
-
|
|
595
|
-
|
|
635
|
+
const approved = await this.isWriteApproved(filePath, preview, authority?.signal);
|
|
636
|
+
assertControlAuthority(authority);
|
|
637
|
+
if (!approved) throw new PermissionDeniedError("Permission denied for fs/write_text_file");
|
|
638
|
+
const workspace = await this.getWorkspace();
|
|
639
|
+
const target = await workspace.resolve(filePath);
|
|
640
|
+
const file = await workspace.openWritable(target, {
|
|
641
|
+
mode: 438,
|
|
642
|
+
assertBeforeMutation: () => assertControlAuthority(authority)
|
|
643
|
+
});
|
|
596
644
|
try {
|
|
645
|
+
assertControlAuthority(authority);
|
|
597
646
|
await file.handle.writeFile(params.content, "utf8");
|
|
598
647
|
} finally {
|
|
599
648
|
await file.handle.close();
|
|
@@ -618,17 +667,17 @@ var FileSystemHandlers = class {
|
|
|
618
667
|
throw error;
|
|
619
668
|
}
|
|
620
669
|
}
|
|
621
|
-
async isWriteApproved(filePath, preview) {
|
|
670
|
+
async isWriteApproved(filePath, preview, signal) {
|
|
622
671
|
if (this.permissionMode === "approve-all") return true;
|
|
623
672
|
if (this.permissionMode === "deny-all") return false;
|
|
624
673
|
if (this.usesDefaultConfirmWrite && this.nonInteractivePermissions === "fail" && !canPromptForPermission$2()) throw new PermissionPromptUnavailableError();
|
|
625
|
-
return await this.confirmWrite(filePath, preview);
|
|
674
|
+
return await this.confirmWrite(filePath, preview, signal);
|
|
626
675
|
}
|
|
627
676
|
resolvePathWithinRoot(rawPath) {
|
|
628
677
|
if (!path.isAbsolute(rawPath)) throw new Error(`Path must be absolute: ${rawPath}`);
|
|
629
678
|
const resolved = path.resolve(rawPath);
|
|
630
679
|
if (!isPathInside(this.rootDir, resolved)) throw new Error(`Path is outside allowed cwd subtree: ${resolved}`);
|
|
631
|
-
return
|
|
680
|
+
return rawPath;
|
|
632
681
|
}
|
|
633
682
|
getWorkspace() {
|
|
634
683
|
return this.workspace ??= root(this.rootDir, {
|
|
@@ -691,11 +740,11 @@ function pickOption(options, kinds) {
|
|
|
691
740
|
const TOOL_KIND_TITLE_MATCHERS = [
|
|
692
741
|
{
|
|
693
742
|
kind: "read",
|
|
694
|
-
|
|
743
|
+
names: ["read", "cat"]
|
|
695
744
|
},
|
|
696
745
|
{
|
|
697
746
|
kind: "search",
|
|
698
|
-
|
|
747
|
+
names: [
|
|
699
748
|
"search",
|
|
700
749
|
"find",
|
|
701
750
|
"grep"
|
|
@@ -703,7 +752,7 @@ const TOOL_KIND_TITLE_MATCHERS = [
|
|
|
703
752
|
},
|
|
704
753
|
{
|
|
705
754
|
kind: "edit",
|
|
706
|
-
|
|
755
|
+
names: [
|
|
707
756
|
"write",
|
|
708
757
|
"edit",
|
|
709
758
|
"patch"
|
|
@@ -711,15 +760,15 @@ const TOOL_KIND_TITLE_MATCHERS = [
|
|
|
711
760
|
},
|
|
712
761
|
{
|
|
713
762
|
kind: "delete",
|
|
714
|
-
|
|
763
|
+
names: ["delete", "remove"]
|
|
715
764
|
},
|
|
716
765
|
{
|
|
717
766
|
kind: "move",
|
|
718
|
-
|
|
767
|
+
names: ["move", "rename"]
|
|
719
768
|
},
|
|
720
769
|
{
|
|
721
770
|
kind: "execute",
|
|
722
|
-
|
|
771
|
+
names: [
|
|
723
772
|
"run",
|
|
724
773
|
"execute",
|
|
725
774
|
"bash"
|
|
@@ -727,7 +776,7 @@ const TOOL_KIND_TITLE_MATCHERS = [
|
|
|
727
776
|
},
|
|
728
777
|
{
|
|
729
778
|
kind: "fetch",
|
|
730
|
-
|
|
779
|
+
names: [
|
|
731
780
|
"fetch",
|
|
732
781
|
"http",
|
|
733
782
|
"url"
|
|
@@ -735,25 +784,25 @@ const TOOL_KIND_TITLE_MATCHERS = [
|
|
|
735
784
|
},
|
|
736
785
|
{
|
|
737
786
|
kind: "think",
|
|
738
|
-
|
|
787
|
+
names: ["think"]
|
|
739
788
|
}
|
|
740
789
|
];
|
|
741
790
|
function inferToolKind(params) {
|
|
742
791
|
if (params.toolCall.kind) return params.toolCall.kind;
|
|
743
792
|
const title = params.toolCall.title?.trim().toLowerCase();
|
|
744
793
|
if (!title) return;
|
|
745
|
-
const head = title.split(
|
|
794
|
+
const head = title.split(/[:\s]/, 1)[0];
|
|
746
795
|
if (!head) return;
|
|
747
|
-
return
|
|
748
|
-
}
|
|
749
|
-
function titleHeadToolKind(head) {
|
|
750
|
-
return TOOL_KIND_TITLE_MATCHERS.find(({ needles }) => needles.some((needle) => head.includes(needle)))?.kind;
|
|
796
|
+
return TOOL_KIND_TITLE_MATCHERS.find(({ names }) => names.includes(head))?.kind ?? "other";
|
|
751
797
|
}
|
|
752
798
|
function isAutoApprovedReadKind(kind) {
|
|
753
799
|
return kind === "read" || kind === "search";
|
|
754
800
|
}
|
|
755
|
-
async function promptForToolPermission(params) {
|
|
756
|
-
return await promptForPermission({
|
|
801
|
+
async function promptForToolPermission(params, signal) {
|
|
802
|
+
return await promptForPermission({
|
|
803
|
+
prompt: `\n[permission] Allow ${params.toolCall.title ?? "tool"} [${inferToolKind(params) ?? "other"}]? (y/N) `,
|
|
804
|
+
signal
|
|
805
|
+
});
|
|
757
806
|
}
|
|
758
807
|
function canPromptForPermission$1() {
|
|
759
808
|
return process.stdin.isTTY && process.stderr.isTTY;
|
|
@@ -848,24 +897,24 @@ function selectedOrFirst(options, allowOption) {
|
|
|
848
897
|
function selectedOrCancelled(option) {
|
|
849
898
|
return { response: option ? selected(option.optionId) : cancelled() };
|
|
850
899
|
}
|
|
851
|
-
async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption) {
|
|
852
|
-
if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
900
|
+
async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption, signal) {
|
|
901
|
+
if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption, signal);
|
|
853
902
|
const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
|
|
854
903
|
return {
|
|
855
904
|
response: withPermissionMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), { permissionEscalation: escalation }),
|
|
856
905
|
escalation
|
|
857
906
|
};
|
|
858
907
|
}
|
|
859
|
-
async function resolveInteractivePromptResult(params, allowOption, rejectOption) {
|
|
860
|
-
const approved = await promptForToolPermission(params);
|
|
908
|
+
async function resolveInteractivePromptResult(params, allowOption, rejectOption, signal) {
|
|
909
|
+
const approved = await promptForToolPermission(params, signal);
|
|
861
910
|
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
862
911
|
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
863
912
|
return { response: cancelled() };
|
|
864
913
|
}
|
|
865
|
-
function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption) {
|
|
914
|
+
function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption, signal) {
|
|
866
915
|
if (policyMatch?.action === "approve") return selectedOrFirst(options, allowOption);
|
|
867
916
|
if (policyMatch?.action === "deny") return selectedOrCancelled(rejectOption);
|
|
868
|
-
if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption);
|
|
917
|
+
if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption, signal);
|
|
869
918
|
}
|
|
870
919
|
function resolveModeMatch(options, mode, allowOption, rejectOption) {
|
|
871
920
|
if (mode === "approve-all") return selectedOrFirst(options, allowOption);
|
|
@@ -875,24 +924,35 @@ function resolveNonInteractivePermission(nonInteractivePolicy, rejectOption) {
|
|
|
875
924
|
if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
|
|
876
925
|
return selectedOrCancelled(rejectOption);
|
|
877
926
|
}
|
|
878
|
-
async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption) {
|
|
927
|
+
async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption, signal) {
|
|
879
928
|
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
|
|
880
929
|
if (!canPromptForPermission$1()) return resolveNonInteractivePermission(nonInteractivePolicy, rejectOption);
|
|
881
|
-
return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
930
|
+
return resolveInteractivePromptResult(params, allowOption, rejectOption, signal);
|
|
882
931
|
}
|
|
883
932
|
function permissionModeSatisfies(actual, required) {
|
|
884
933
|
return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
|
|
885
934
|
}
|
|
886
|
-
async function resolvePermissionRequestWithDetails(params, mode, nonInteractivePolicy = "deny", policy) {
|
|
935
|
+
async function resolvePermissionRequestWithDetails(params, mode, nonInteractivePolicy = "deny", policy, signal) {
|
|
936
|
+
try {
|
|
937
|
+
signal?.throwIfAborted();
|
|
938
|
+
const result = await resolvePermissionByPolicy(params, mode, nonInteractivePolicy, policy, signal);
|
|
939
|
+
signal?.throwIfAborted();
|
|
940
|
+
return result;
|
|
941
|
+
} catch (error) {
|
|
942
|
+
if (signal?.aborted) return { response: cancelled() };
|
|
943
|
+
throw error;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
async function resolvePermissionByPolicy(params, mode, nonInteractivePolicy, policy, signal) {
|
|
887
947
|
const options = params.options ?? [];
|
|
888
948
|
if (options.length === 0) return { response: cancelled() };
|
|
889
949
|
const allowOption = pickOption(options, ["allow_once", "allow_always"]);
|
|
890
950
|
const rejectOption = pickOption(options, ["reject_once", "reject_always"]);
|
|
891
|
-
const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption);
|
|
951
|
+
const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption, signal);
|
|
892
952
|
if (resolvedByPolicy) return resolvedByPolicy;
|
|
893
953
|
const resolvedByMode = resolveModeMatch(options, mode, allowOption, rejectOption);
|
|
894
954
|
if (resolvedByMode) return resolvedByMode;
|
|
895
|
-
return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption);
|
|
955
|
+
return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption, signal);
|
|
896
956
|
}
|
|
897
957
|
const DECISION_FALLBACK_ORDER = {
|
|
898
958
|
allow_once: ["allow_once", "allow_always"],
|
|
@@ -1321,10 +1381,12 @@ const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised
|
|
|
1321
1381
|
var RequestedModelUnsupportedError = class extends Error {
|
|
1322
1382
|
code = REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE;
|
|
1323
1383
|
reason;
|
|
1324
|
-
|
|
1384
|
+
ambiguous;
|
|
1385
|
+
constructor(message, reason, details) {
|
|
1325
1386
|
super(message);
|
|
1326
1387
|
this.name = "RequestedModelUnsupportedError";
|
|
1327
1388
|
this.reason = reason;
|
|
1389
|
+
if (details?.ambiguous) this.ambiguous = true;
|
|
1328
1390
|
}
|
|
1329
1391
|
};
|
|
1330
1392
|
function isRequestedModelUnsupportedReason(value) {
|
|
@@ -1427,6 +1489,7 @@ function resolveRequestedModelId(params) {
|
|
|
1427
1489
|
if (!params.models || !isCursorAcpCommandForModelAlias(params.agentCommand)) return params.requestedModel;
|
|
1428
1490
|
if (params.models.availableModels.some((model) => model.modelId === params.requestedModel)) return params.requestedModel;
|
|
1429
1491
|
const candidates = params.models.availableModels.map((model) => model.modelId).filter((modelId) => modelId.startsWith(`${params.requestedModel}[`));
|
|
1492
|
+
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 });
|
|
1430
1493
|
return candidates.length === 1 ? candidates[0] : params.requestedModel;
|
|
1431
1494
|
}
|
|
1432
1495
|
function isCursorAcpCommandForModelAlias(agentCommand) {
|
|
@@ -1571,6 +1634,7 @@ function observeAcpStream(base, options) {
|
|
|
1571
1634
|
const activePrompt = promptOwner ? options.bindPromptOwner(promptOwner) : void 0;
|
|
1572
1635
|
const id = responseId(message);
|
|
1573
1636
|
if (!(id !== void 0 && elicitationRequestIds.delete(id))) options.onMessage("outbound", message);
|
|
1637
|
+
if (activePrompt) options.assertPromptActive(activePrompt);
|
|
1574
1638
|
const writer = base.writable.getWriter();
|
|
1575
1639
|
try {
|
|
1576
1640
|
await writer.write(message);
|
|
@@ -1756,8 +1820,11 @@ function trimToUtf8Boundary(buffer, limit) {
|
|
|
1756
1820
|
if (start >= buffer.length) start = buffer.length - limit;
|
|
1757
1821
|
return buffer.subarray(start);
|
|
1758
1822
|
}
|
|
1759
|
-
async function defaultConfirmExecute(commandLine) {
|
|
1760
|
-
return await promptForPermission({
|
|
1823
|
+
async function defaultConfirmExecute(commandLine, signal) {
|
|
1824
|
+
return await promptForPermission({
|
|
1825
|
+
prompt: `\n[permission] Allow terminal command "${commandLine}"? (y/N) `,
|
|
1826
|
+
signal
|
|
1827
|
+
});
|
|
1761
1828
|
}
|
|
1762
1829
|
function canPromptForPermission() {
|
|
1763
1830
|
return process.stdin.isTTY && process.stderr.isTTY;
|
|
@@ -1794,7 +1861,8 @@ var TerminalManager = class {
|
|
|
1794
1861
|
this.permissionMode = permissionMode;
|
|
1795
1862
|
this.nonInteractivePermissions = nonInteractivePermissions ?? "deny";
|
|
1796
1863
|
}
|
|
1797
|
-
async createTerminal(params) {
|
|
1864
|
+
async createTerminal(params, authority) {
|
|
1865
|
+
assertControlAuthority(authority);
|
|
1798
1866
|
const commandLine = toCommandLine(params.command, params.args);
|
|
1799
1867
|
const summary = `terminal/create: ${commandLine}`;
|
|
1800
1868
|
this.emitOperation({
|
|
@@ -1804,10 +1872,12 @@ var TerminalManager = class {
|
|
|
1804
1872
|
timestamp: nowIso()
|
|
1805
1873
|
});
|
|
1806
1874
|
try {
|
|
1807
|
-
|
|
1875
|
+
const approved = await this.isExecuteApproved(commandLine, authority?.signal);
|
|
1876
|
+
assertControlAuthority(authority);
|
|
1877
|
+
if (!approved) throw new PermissionDeniedError("Permission denied for terminal/create");
|
|
1808
1878
|
const requestedLimit = Math.max(0, Math.round(params.outputByteLimit ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES));
|
|
1809
1879
|
const outputByteLimit = Math.min(requestedLimit, this.outputCeilingBytes ?? Number.POSITIVE_INFINITY);
|
|
1810
|
-
const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd);
|
|
1880
|
+
const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd, authority);
|
|
1811
1881
|
let resolveExit = () => {};
|
|
1812
1882
|
const exitPromise = new Promise((resolve) => {
|
|
1813
1883
|
resolveExit = resolve;
|
|
@@ -1852,6 +1922,15 @@ var TerminalManager = class {
|
|
|
1852
1922
|
});
|
|
1853
1923
|
const terminalId = randomUUID();
|
|
1854
1924
|
this.terminals.set(terminalId, terminal);
|
|
1925
|
+
try {
|
|
1926
|
+
assertControlAuthority(authority);
|
|
1927
|
+
} catch (error) {
|
|
1928
|
+
await this.releaseTerminal({
|
|
1929
|
+
terminalId,
|
|
1930
|
+
sessionId: params.sessionId
|
|
1931
|
+
});
|
|
1932
|
+
throw error;
|
|
1933
|
+
}
|
|
1855
1934
|
this.emitOperation({
|
|
1856
1935
|
method: "terminal/create",
|
|
1857
1936
|
status: "completed",
|
|
@@ -1990,11 +2069,11 @@ var TerminalManager = class {
|
|
|
1990
2069
|
emitOperation(operation) {
|
|
1991
2070
|
this.onOperation?.(operation);
|
|
1992
2071
|
}
|
|
1993
|
-
async isExecuteApproved(commandLine) {
|
|
2072
|
+
async isExecuteApproved(commandLine, signal) {
|
|
1994
2073
|
if (this.permissionMode === "approve-all") return true;
|
|
1995
2074
|
if (this.permissionMode === "deny-all") return false;
|
|
1996
2075
|
if (this.usesDefaultConfirmExecute && this.nonInteractivePermissions === "fail" && !canPromptForPermission()) throw new PermissionPromptUnavailableError();
|
|
1997
|
-
return await this.confirmExecute(commandLine);
|
|
2076
|
+
return await this.confirmExecute(commandLine, signal);
|
|
1998
2077
|
}
|
|
1999
2078
|
isRunning(terminal) {
|
|
2000
2079
|
return terminal.exitCode === void 0 && terminal.signal === void 0;
|
|
@@ -2058,25 +2137,26 @@ var TerminalManager = class {
|
|
|
2058
2137
|
while (hasLivePid(terminal.descendantPids)) await waitMs(25);
|
|
2059
2138
|
}
|
|
2060
2139
|
};
|
|
2061
|
-
async function spawnTerminalProcess(params, defaultCwd) {
|
|
2140
|
+
async function spawnTerminalProcess(params, defaultCwd, authority) {
|
|
2062
2141
|
const directCommand = buildTerminalSpawnCommand(params.command, params.args);
|
|
2063
2142
|
try {
|
|
2064
2143
|
return {
|
|
2065
|
-
proc: await spawnAndWait(directCommand, params, defaultCwd),
|
|
2144
|
+
proc: await spawnAndWait(directCommand, params, defaultCwd, authority),
|
|
2066
2145
|
spawnCommand: directCommand
|
|
2067
2146
|
};
|
|
2068
2147
|
} catch (error) {
|
|
2069
2148
|
const fallbackCommand = params.args === void 0 && isNotFoundSpawnError(error) ? buildTerminalFallbackSpawnCommand(params.command, params.cwd ?? defaultCwd) : void 0;
|
|
2070
2149
|
if (!fallbackCommand) throw error;
|
|
2071
2150
|
return {
|
|
2072
|
-
proc: await spawnAndWait(fallbackCommand, params, defaultCwd),
|
|
2151
|
+
proc: await spawnAndWait(fallbackCommand, params, defaultCwd, authority),
|
|
2073
2152
|
spawnCommand: fallbackCommand
|
|
2074
2153
|
};
|
|
2075
2154
|
}
|
|
2076
2155
|
}
|
|
2077
|
-
async function spawnAndWait(spawnCommand, params, defaultCwd) {
|
|
2156
|
+
async function spawnAndWait(spawnCommand, params, defaultCwd, authority) {
|
|
2078
2157
|
const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
|
|
2079
2158
|
if (spawnCommand.killProcessGroup) spawnOptions.detached = true;
|
|
2159
|
+
assertControlAuthority(authority);
|
|
2080
2160
|
const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
|
|
2081
2161
|
await waitForSpawn(proc);
|
|
2082
2162
|
return proc;
|
|
@@ -2361,13 +2441,12 @@ var AcpClient = class {
|
|
|
2361
2441
|
cancellingSessionIds = /* @__PURE__ */ new Set();
|
|
2362
2442
|
permissionAbortControllers = /* @__PURE__ */ new Map();
|
|
2363
2443
|
closing = false;
|
|
2444
|
+
closeEpoch = 0;
|
|
2364
2445
|
agentStartedAt;
|
|
2365
2446
|
lastAgentExit;
|
|
2366
2447
|
lastKnownPid;
|
|
2367
2448
|
promptPermissionFailures = /* @__PURE__ */ new Map();
|
|
2368
2449
|
pendingConnectionRequests = /* @__PURE__ */ new Set();
|
|
2369
|
-
modelConfigIds = /* @__PURE__ */ new Map();
|
|
2370
|
-
legacyModelSessionIds = /* @__PURE__ */ new Set();
|
|
2371
2450
|
constructor(options) {
|
|
2372
2451
|
this.options = {
|
|
2373
2452
|
...options,
|
|
@@ -2457,8 +2536,11 @@ var AcpClient = class {
|
|
|
2457
2536
|
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
2458
2537
|
this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
2459
2538
|
}
|
|
2539
|
+
hasLiveConnection() {
|
|
2540
|
+
return this.connection != null && !this.connection.signal.aborted && this.agent != null && isChildProcessRunning(this.agent);
|
|
2541
|
+
}
|
|
2460
2542
|
hasReusableSession(sessionId) {
|
|
2461
|
-
return this.
|
|
2543
|
+
return this.hasLiveConnection() && this.loadedSessionId === sessionId;
|
|
2462
2544
|
}
|
|
2463
2545
|
hasActivePrompt(sessionId) {
|
|
2464
2546
|
if (!this.activePrompt) return false;
|
|
@@ -2468,25 +2550,32 @@ var AcpClient = class {
|
|
|
2468
2550
|
hasUnresolvedPrompt() {
|
|
2469
2551
|
return this.activePrompt !== void 0;
|
|
2470
2552
|
}
|
|
2471
|
-
|
|
2472
|
-
const
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2553
|
+
abortSessionRequests(sessionId) {
|
|
2554
|
+
const owners = this.pendingPromptOwners.filter((owner) => owner.sessionId === sessionId);
|
|
2555
|
+
const fallback = this.takePermissionAbortController(sessionId);
|
|
2556
|
+
for (const owner of owners) {
|
|
2557
|
+
owner.elicitationHandler = void 0;
|
|
2558
|
+
owner.elicitationController.abort();
|
|
2559
|
+
owner.requestController.abort();
|
|
2560
|
+
}
|
|
2561
|
+
fallback?.abort();
|
|
2476
2562
|
}
|
|
2477
2563
|
async start() {
|
|
2478
|
-
if (this.
|
|
2564
|
+
if (this.hasLiveConnection()) return;
|
|
2479
2565
|
if (this.connection || this.agent) await this.close();
|
|
2566
|
+
const epoch = this.closeEpoch;
|
|
2480
2567
|
const maxMessageBytes = readMaxAcpMessageBytes();
|
|
2481
2568
|
const launch = await this.resolveAgentLaunchPlan();
|
|
2482
2569
|
this.logAgentLaunch(launch);
|
|
2483
2570
|
await this.ensureLaunchSupport(launch);
|
|
2484
2571
|
const { child, process: startedProcess } = await this.spawnAgentProcess(launch);
|
|
2485
|
-
this.
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2572
|
+
if (this.closeEpoch === epoch) {
|
|
2573
|
+
this.agent = child;
|
|
2574
|
+
this.closing = false;
|
|
2575
|
+
this.agentStartedAt = startedProcess.startedAt;
|
|
2576
|
+
this.lastAgentExit = void 0;
|
|
2577
|
+
this.lastKnownPid = startedProcess.pid;
|
|
2578
|
+
}
|
|
2490
2579
|
const startupStderr = [];
|
|
2491
2580
|
child.stderr.on("data", (chunk) => {
|
|
2492
2581
|
this.captureStartupStderr(startupStderr, chunk);
|
|
@@ -2502,6 +2591,7 @@ var AcpClient = class {
|
|
|
2502
2591
|
startupFailure.dispose();
|
|
2503
2592
|
throw error;
|
|
2504
2593
|
}
|
|
2594
|
+
await this.stopIfClosedDuringLaunch(epoch, child, startupFailure);
|
|
2505
2595
|
const input = Writable.toWeb(child.stdin);
|
|
2506
2596
|
const output = Readable.toWeb(child.stdout);
|
|
2507
2597
|
let connection;
|
|
@@ -2509,7 +2599,13 @@ var AcpClient = class {
|
|
|
2509
2599
|
this.rejectPendingConnectionRequests(error);
|
|
2510
2600
|
connection?.close(error);
|
|
2511
2601
|
}));
|
|
2512
|
-
|
|
2602
|
+
const capabilities = resolveClientCapabilities({
|
|
2603
|
+
devinAcp: launch.devinAcp,
|
|
2604
|
+
fs: this.options.fs !== false,
|
|
2605
|
+
terminal: this.options.terminal !== false,
|
|
2606
|
+
elicitationModes: this.options.elicitationModes
|
|
2607
|
+
});
|
|
2608
|
+
connection = this.createConnection(stream, launch, capabilities);
|
|
2513
2609
|
connection.signal.addEventListener("abort", () => {
|
|
2514
2610
|
this.recordAgentExit(child, "connection_close", child.exitCode ?? null, child.signalCode ?? null);
|
|
2515
2611
|
}, { once: true });
|
|
@@ -2518,7 +2614,8 @@ var AcpClient = class {
|
|
|
2518
2614
|
connection,
|
|
2519
2615
|
startupFailure,
|
|
2520
2616
|
startupStderr,
|
|
2521
|
-
launch
|
|
2617
|
+
launch,
|
|
2618
|
+
capabilities
|
|
2522
2619
|
});
|
|
2523
2620
|
}
|
|
2524
2621
|
async resolveAgentLaunchPlan() {
|
|
@@ -2615,19 +2712,22 @@ var AcpClient = class {
|
|
|
2615
2712
|
releaseExitNotification();
|
|
2616
2713
|
}
|
|
2617
2714
|
}
|
|
2618
|
-
createConnection(stream, launch) {
|
|
2715
|
+
createConnection(stream, launch, capabilities) {
|
|
2619
2716
|
const app = client({ name: "acpx" }).onNotification(methods.client.session.update, async ({ params }) => {
|
|
2620
2717
|
await this.handleSessionUpdate(params);
|
|
2621
|
-
}).onNotification(methods.client.elicitation.complete, async () => {}).onRequest(methods.client.session.requestPermission, async ({ params }) => {
|
|
2622
|
-
return await this.handlePermissionRequest(params);
|
|
2718
|
+
}).onNotification(methods.client.elicitation.complete, async () => {}).onRequest(methods.client.session.requestPermission, async ({ params, signal }) => {
|
|
2719
|
+
return await this.handlePermissionRequest(params, signal);
|
|
2623
2720
|
}).onRequest(methods.client.elicitation.create, async ({ params, requestId, signal }) => {
|
|
2624
2721
|
return await this.handleElicitationRequest(params, requestId, signal);
|
|
2625
|
-
})
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
return await this.
|
|
2722
|
+
});
|
|
2723
|
+
if (capabilities.fs?.readTextFile) app.onRequest(methods.client.fs.readTextFile, async ({ params, signal }) => {
|
|
2724
|
+
return await this.handleReadTextFile(params, signal);
|
|
2725
|
+
});
|
|
2726
|
+
if (capabilities.fs?.writeTextFile) app.onRequest(methods.client.fs.writeTextFile, async ({ params, signal }) => {
|
|
2727
|
+
return await this.handleWriteTextFile(params, signal);
|
|
2728
|
+
});
|
|
2729
|
+
if (capabilities.terminal) app.onRequest(methods.client.terminal.create, async ({ params, signal }) => {
|
|
2730
|
+
return await this.handleCreateTerminal(params, signal);
|
|
2631
2731
|
}).onRequest(methods.client.terminal.output, async ({ params }) => {
|
|
2632
2732
|
return await this.terminalManager.terminalOutput(params);
|
|
2633
2733
|
}).onRequest(methods.client.terminal.waitForExit, async ({ params }) => {
|
|
@@ -2644,7 +2744,7 @@ var AcpClient = class {
|
|
|
2644
2744
|
}
|
|
2645
2745
|
async initializeAgentConnection(params) {
|
|
2646
2746
|
try {
|
|
2647
|
-
const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch), params.startupFailure.promise]);
|
|
2747
|
+
const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch, params.capabilities), params.startupFailure.promise]);
|
|
2648
2748
|
params.startupFailure.dispose();
|
|
2649
2749
|
this.connection = params.connection;
|
|
2650
2750
|
this.initResult = initResult;
|
|
@@ -2655,15 +2755,10 @@ var AcpClient = class {
|
|
|
2655
2755
|
await this.handleInitializeFailure(params, error);
|
|
2656
2756
|
}
|
|
2657
2757
|
}
|
|
2658
|
-
async initializeProtocolConnection(connection, launch) {
|
|
2758
|
+
async initializeProtocolConnection(connection, launch, capabilities) {
|
|
2659
2759
|
const initializePromise = connection.agent.request(methods.agent.initialize, {
|
|
2660
2760
|
protocolVersion: PROTOCOL_VERSION,
|
|
2661
|
-
clientCapabilities:
|
|
2662
|
-
devinAcp: launch.devinAcp,
|
|
2663
|
-
fs: this.options.fs !== false,
|
|
2664
|
-
terminal: this.options.terminal !== false,
|
|
2665
|
-
elicitationModes: this.options.elicitationModes ?? []
|
|
2666
|
-
}),
|
|
2761
|
+
clientCapabilities: capabilities,
|
|
2667
2762
|
clientInfo: resolveClientInfo(launch.devinAcp)
|
|
2668
2763
|
});
|
|
2669
2764
|
const initialized = launch.geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
|
@@ -2689,6 +2784,7 @@ var AcpClient = class {
|
|
|
2689
2784
|
},
|
|
2690
2785
|
suppressReplaySessionUpdates: () => this.suppressReplaySessionUpdateMessages,
|
|
2691
2786
|
bindPromptOwner: (owner) => this.bindPromptOwner(owner),
|
|
2787
|
+
assertPromptActive: (active) => this.assertPromptActive(active),
|
|
2692
2788
|
onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
|
|
2693
2789
|
});
|
|
2694
2790
|
}
|
|
@@ -2718,7 +2814,6 @@ var AcpClient = class {
|
|
|
2718
2814
|
configOptions,
|
|
2719
2815
|
response: result
|
|
2720
2816
|
});
|
|
2721
|
-
this.rememberSessionModels(result.sessionId, models);
|
|
2722
2817
|
await this.captureAgentDescendants(this.agent);
|
|
2723
2818
|
return {
|
|
2724
2819
|
sessionId: result.sessionId,
|
|
@@ -2750,7 +2845,6 @@ var AcpClient = class {
|
|
|
2750
2845
|
}
|
|
2751
2846
|
this.loadedSessionId = sessionId;
|
|
2752
2847
|
const result = toReconnectedSessionResult(response);
|
|
2753
|
-
this.updateRememberedSessionModels(sessionId, result);
|
|
2754
2848
|
await this.captureAgentDescendants(this.agent);
|
|
2755
2849
|
return result;
|
|
2756
2850
|
}
|
|
@@ -2764,7 +2858,6 @@ var AcpClient = class {
|
|
|
2764
2858
|
}));
|
|
2765
2859
|
this.loadedSessionId = sessionId;
|
|
2766
2860
|
const result = toReconnectedSessionResult(response);
|
|
2767
|
-
this.updateRememberedSessionModels(sessionId, result);
|
|
2768
2861
|
await this.captureAgentDescendants(this.agent);
|
|
2769
2862
|
return result;
|
|
2770
2863
|
}
|
|
@@ -2781,28 +2874,24 @@ var AcpClient = class {
|
|
|
2781
2874
|
this.suppressSessionUpdates = previous.suppressSessionUpdates;
|
|
2782
2875
|
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
|
|
2783
2876
|
}
|
|
2784
|
-
async prompt(sessionId, prompt, onRequestWritten, onElicitation, onPermissionRequest) {
|
|
2877
|
+
async prompt(sessionId, prompt, onRequestWritten, onElicitation, onPermissionRequest, authority) {
|
|
2785
2878
|
const connection = this.getConnection();
|
|
2786
2879
|
const normalizedPrompt = this.normalizePromptForAgent(prompt);
|
|
2787
2880
|
const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
|
|
2788
2881
|
const previousActivePrompt = this.activePrompt;
|
|
2789
|
-
const
|
|
2790
|
-
|
|
2882
|
+
const replacedOwners = this.pendingPromptOwners.filter((owner) => owner.sessionId === sessionId);
|
|
2883
|
+
const activePrompt = this.beginActivePrompt(sessionId, onRequestWritten, onElicitation, onPermissionRequest, authority);
|
|
2791
2884
|
try {
|
|
2792
|
-
promptPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.prompt, {
|
|
2885
|
+
const promptPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.prompt, {
|
|
2793
2886
|
sessionId,
|
|
2794
2887
|
prompt: normalizedPrompt
|
|
2795
|
-
}));
|
|
2796
|
-
|
|
2797
|
-
this.clearActivePrompt(activePrompt);
|
|
2798
|
-
restoreConsoleError?.();
|
|
2799
|
-
throw error;
|
|
2800
|
-
}
|
|
2801
|
-
activePrompt.promise = promptPromise;
|
|
2802
|
-
try {
|
|
2888
|
+
}), authority);
|
|
2889
|
+
activePrompt.promise = promptPromise;
|
|
2803
2890
|
previousActivePrompt?.elicitationController?.abort();
|
|
2891
|
+
for (const owner of replacedOwners) owner.requestController.abort();
|
|
2804
2892
|
return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
|
|
2805
2893
|
} catch (error) {
|
|
2894
|
+
if (activePrompt.admissionFailure) throw activePrompt.admissionFailure.error;
|
|
2806
2895
|
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
2807
2896
|
throw error;
|
|
2808
2897
|
} finally {
|
|
@@ -2810,14 +2899,16 @@ var AcpClient = class {
|
|
|
2810
2899
|
this.clearActivePrompt(activePrompt);
|
|
2811
2900
|
}
|
|
2812
2901
|
}
|
|
2813
|
-
beginActivePrompt(sessionId, onRequestWritten, elicitationHandler, permissionHandler) {
|
|
2902
|
+
beginActivePrompt(sessionId, onRequestWritten, elicitationHandler, permissionHandler, authority) {
|
|
2814
2903
|
this.cancellingSessionIds.delete(sessionId);
|
|
2815
2904
|
const active = {
|
|
2816
2905
|
sessionId,
|
|
2817
2906
|
onRequestWritten,
|
|
2907
|
+
authority,
|
|
2818
2908
|
elicitationHandler,
|
|
2819
2909
|
permissionHandler,
|
|
2820
|
-
elicitationController: new AbortController()
|
|
2910
|
+
elicitationController: new AbortController(),
|
|
2911
|
+
requestController: new AbortController()
|
|
2821
2912
|
};
|
|
2822
2913
|
this.activePrompt = active;
|
|
2823
2914
|
this.pendingPromptOwners.push(active);
|
|
@@ -2830,6 +2921,14 @@ var AcpClient = class {
|
|
|
2830
2921
|
if (active) active.requestId = owner.requestId;
|
|
2831
2922
|
return active;
|
|
2832
2923
|
}
|
|
2924
|
+
assertPromptActive(active) {
|
|
2925
|
+
try {
|
|
2926
|
+
assertControlAuthority(active.authority);
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
active.admissionFailure = { error };
|
|
2929
|
+
throw error;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2833
2932
|
onPromptRequestWritten(active, owner) {
|
|
2834
2933
|
if (active.requestId !== owner.requestId || active.sessionId !== owner.sessionId) return;
|
|
2835
2934
|
try {
|
|
@@ -2848,6 +2947,7 @@ var AcpClient = class {
|
|
|
2848
2947
|
this.promptPermissionFailures.delete(active.sessionId);
|
|
2849
2948
|
}
|
|
2850
2949
|
active.elicitationController.abort();
|
|
2950
|
+
active.requestController.abort();
|
|
2851
2951
|
permissionController?.abort();
|
|
2852
2952
|
}
|
|
2853
2953
|
normalizePromptForAgent(prompt) {
|
|
@@ -2871,33 +2971,41 @@ var AcpClient = class {
|
|
|
2871
2971
|
modeId
|
|
2872
2972
|
}), authority, (error) => maybeWrapSessionControlError("session/set_mode", error, `for mode "${modeId}"`));
|
|
2873
2973
|
}
|
|
2874
|
-
async setSessionConfigOption(sessionId, configId, value, authority) {
|
|
2974
|
+
async setSessionConfigOption(sessionId, configId, value, models, authority) {
|
|
2975
|
+
assertControlAuthority(authority);
|
|
2976
|
+
const resolvedValue = models?.configId === configId ? this.resolveSessionModelId(value, models) : value;
|
|
2875
2977
|
const connection = this.getConnection();
|
|
2876
2978
|
return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
|
|
2877
2979
|
sessionId,
|
|
2878
2980
|
configId,
|
|
2879
|
-
value
|
|
2981
|
+
value: resolvedValue
|
|
2880
2982
|
}), authority, (error) => maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`));
|
|
2881
2983
|
}
|
|
2882
|
-
async setSessionModel(sessionId, modelId,
|
|
2883
|
-
|
|
2884
|
-
if (!
|
|
2885
|
-
const resolvedModelId =
|
|
2984
|
+
async setSessionModel(sessionId, modelId, models, authority) {
|
|
2985
|
+
assertControlAuthority(authority);
|
|
2986
|
+
if (!models) throw new RequestedModelUnsupportedError(`Cannot set model "${modelId}": the ACP session did not advertise a model config option or legacy session/set_model support.`, "missing-capability");
|
|
2987
|
+
const resolvedModelId = this.resolveSessionModelId(modelId, models);
|
|
2988
|
+
return models.configId ? await this.setSessionModelThroughConfig(sessionId, resolvedModelId, models.configId, authority) : await this.setSessionModelThroughLegacyMethod(sessionId, resolvedModelId, authority);
|
|
2989
|
+
}
|
|
2990
|
+
resolveSessionModelId(modelId, models) {
|
|
2991
|
+
const params = {
|
|
2886
2992
|
requestedModel: modelId,
|
|
2887
|
-
models
|
|
2993
|
+
models,
|
|
2888
2994
|
agentCommand: this.options.agentCommand
|
|
2995
|
+
};
|
|
2996
|
+
assertRequestedModelSupported({
|
|
2997
|
+
...params,
|
|
2998
|
+
context: "apply"
|
|
2889
2999
|
});
|
|
2890
|
-
return
|
|
3000
|
+
return resolveRequestedModelId(params);
|
|
2891
3001
|
}
|
|
2892
3002
|
async setSessionModelThroughConfig(sessionId, modelId, configId, authority) {
|
|
2893
3003
|
const connection = this.getConnection();
|
|
2894
|
-
|
|
3004
|
+
return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
|
|
2895
3005
|
sessionId,
|
|
2896
3006
|
configId,
|
|
2897
3007
|
value: modelId
|
|
2898
3008
|
}), authority, (error) => this.throwSessionModelError("session/set_config_option", modelId, error));
|
|
2899
|
-
this.rememberSessionModels(sessionId, modelStateFromConfigOptions(response.configOptions));
|
|
2900
|
-
return response;
|
|
2901
3009
|
}
|
|
2902
3010
|
async setSessionModelThroughLegacyMethod(sessionId, modelId, authority) {
|
|
2903
3011
|
const connection = this.getConnection();
|
|
@@ -2913,39 +3021,10 @@ var AcpClient = class {
|
|
|
2913
3021
|
const summary = acp ? formatSessionControlAcpSummary(acp) : error instanceof Error ? error.message : String(error);
|
|
2914
3022
|
throw new Error(`Failed ${method} for model "${modelId}": ${summary}`, { cause: error });
|
|
2915
3023
|
}
|
|
2916
|
-
resolveModelControl(sessionId, controlOverride) {
|
|
2917
|
-
if (controlOverride) return controlOverride.configId ? {
|
|
2918
|
-
kind: "config_option",
|
|
2919
|
-
configId: controlOverride.configId
|
|
2920
|
-
} : { kind: "legacy_set_model" };
|
|
2921
|
-
const configId = this.modelConfigIds.get(sessionId);
|
|
2922
|
-
if (configId) return {
|
|
2923
|
-
kind: "config_option",
|
|
2924
|
-
configId
|
|
2925
|
-
};
|
|
2926
|
-
return this.legacyModelSessionIds.has(sessionId) ? { kind: "legacy_set_model" } : void 0;
|
|
2927
|
-
}
|
|
2928
|
-
rememberSessionModels(sessionId, models) {
|
|
2929
|
-
if (!models) {
|
|
2930
|
-
this.modelConfigIds.delete(sessionId);
|
|
2931
|
-
this.legacyModelSessionIds.delete(sessionId);
|
|
2932
|
-
return;
|
|
2933
|
-
}
|
|
2934
|
-
if (models.configId) {
|
|
2935
|
-
this.modelConfigIds.set(sessionId, models.configId);
|
|
2936
|
-
this.legacyModelSessionIds.delete(sessionId);
|
|
2937
|
-
return;
|
|
2938
|
-
}
|
|
2939
|
-
this.modelConfigIds.delete(sessionId);
|
|
2940
|
-
this.legacyModelSessionIds.add(sessionId);
|
|
2941
|
-
}
|
|
2942
|
-
updateRememberedSessionModels(sessionId, result) {
|
|
2943
|
-
const explicitConfigRemoval = result.configOptionsPresent && this.modelConfigIds.has(sessionId);
|
|
2944
|
-
if (result.models || result.legacyModelMetadataPresent || explicitConfigRemoval) this.rememberSessionModels(sessionId, result.models);
|
|
2945
|
-
}
|
|
2946
3024
|
async cancel(sessionId) {
|
|
2947
3025
|
const connection = this.getConnection();
|
|
2948
|
-
const
|
|
3026
|
+
const owners = this.pendingPromptOwners.filter((owner) => owner.sessionId === sessionId);
|
|
3027
|
+
const active = owners.at(-1);
|
|
2949
3028
|
const cancellation = active?.cancelPromise ?? this.runConnectionRequest(() => connection.agent.notify(methods.agent.session.cancel, { sessionId })).catch((error) => {
|
|
2950
3029
|
if (active?.cancelPromise === cancellation) active.cancelPromise = void 0;
|
|
2951
3030
|
throw error;
|
|
@@ -2954,19 +3033,20 @@ var AcpClient = class {
|
|
|
2954
3033
|
if (active) {
|
|
2955
3034
|
active.cancelPromise = cancellation;
|
|
2956
3035
|
this.cancellingSessionIds.add(sessionId);
|
|
2957
|
-
active.elicitationController?.abort();
|
|
2958
3036
|
} else this.cancellingSessionIds.delete(sessionId);
|
|
3037
|
+
for (const owner of owners) {
|
|
3038
|
+
owner.elicitationController.abort();
|
|
3039
|
+
owner.requestController.abort();
|
|
3040
|
+
}
|
|
2959
3041
|
permissionController?.abort();
|
|
2960
3042
|
await cancellation;
|
|
2961
3043
|
}
|
|
2962
3044
|
async closeSession(sessionId) {
|
|
2963
3045
|
const connection = this.getConnection();
|
|
2964
3046
|
this.cancellingSessionIds.add(sessionId);
|
|
2965
|
-
|
|
3047
|
+
this.abortSessionRequests(sessionId);
|
|
2966
3048
|
await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.close, { sessionId }));
|
|
2967
3049
|
if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
|
|
2968
|
-
this.modelConfigIds.delete(sessionId);
|
|
2969
|
-
this.legacyModelSessionIds.delete(sessionId);
|
|
2970
3050
|
}
|
|
2971
3051
|
async listSessions(params = {}) {
|
|
2972
3052
|
const connection = this.getConnection();
|
|
@@ -3002,7 +3082,13 @@ var AcpClient = class {
|
|
|
3002
3082
|
}
|
|
3003
3083
|
async close() {
|
|
3004
3084
|
this.closing = true;
|
|
3085
|
+
this.closeEpoch += 1;
|
|
3086
|
+
const permissionControllers = [...this.permissionAbortControllers.values()];
|
|
3087
|
+
this.permissionAbortControllers.clear();
|
|
3088
|
+
const owners = [...this.pendingPromptOwners];
|
|
3005
3089
|
this.abortActiveElicitation();
|
|
3090
|
+
for (const owner of owners) owner.requestController.abort();
|
|
3091
|
+
for (const controller of permissionControllers) controller.abort();
|
|
3006
3092
|
await this.terminalManager.shutdown();
|
|
3007
3093
|
const agent = this.agent;
|
|
3008
3094
|
if (agent) await this.terminateAgentProcess(agent);
|
|
@@ -3020,12 +3106,16 @@ var AcpClient = class {
|
|
|
3020
3106
|
this.permissionAbortControllers.clear();
|
|
3021
3107
|
this.promptPermissionFailures.clear();
|
|
3022
3108
|
this.loadedSessionId = void 0;
|
|
3023
|
-
this.modelConfigIds.clear();
|
|
3024
|
-
this.legacyModelSessionIds.clear();
|
|
3025
3109
|
this.initResult = void 0;
|
|
3026
3110
|
this.connection = void 0;
|
|
3027
3111
|
this.agent = void 0;
|
|
3028
3112
|
}
|
|
3113
|
+
async stopIfClosedDuringLaunch(epoch, child, startupFailure) {
|
|
3114
|
+
if (this.closeEpoch === epoch) return;
|
|
3115
|
+
startupFailure.dispose();
|
|
3116
|
+
await this.terminateAgentProcess(child);
|
|
3117
|
+
throw new Error("ACP client was closed while the agent was starting");
|
|
3118
|
+
}
|
|
3029
3119
|
abortActiveElicitation() {
|
|
3030
3120
|
this.activePrompt?.elicitationController?.abort();
|
|
3031
3121
|
}
|
|
@@ -3230,26 +3320,41 @@ var AcpClient = class {
|
|
|
3230
3320
|
await connection.agent.request(methods.agent.authenticate, { methodId: selected.methodId });
|
|
3231
3321
|
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
|
|
3232
3322
|
}
|
|
3233
|
-
async handlePermissionRequest(params) {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
if (
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
this.recordPermissionDecision(decision);
|
|
3323
|
+
async handlePermissionRequest(params, requestSignal) {
|
|
3324
|
+
const owner = this.captureDelegatedRequestOwner(params.sessionId, requestSignal);
|
|
3325
|
+
if (!this.isDelegatedRequestActive(owner)) return cancelledPermissionResponse();
|
|
3326
|
+
if (!this.hasPermissionOwner(params.sessionId)) return this.finishPermissionRequest(params, owner, cancelledPermissionResponse());
|
|
3327
|
+
if (this.initResult?.agentInfo?.name === "antigravity-acp" && params.toolCall.toolCallId.startsWith("interaction_")) {
|
|
3328
|
+
const response = this.handleModePermissionError(params.sessionId, new PermissionPromptUnavailableError("Antigravity requested a user answer. acpx cannot answer Antigravity interaction questions; continue in an interactive client."));
|
|
3329
|
+
return this.finishPermissionRequest(params, owner, response);
|
|
3241
3330
|
}
|
|
3242
|
-
|
|
3331
|
+
const response = await this.tryHandlePermissionRequestWithHost(params, owner) ?? await this.resolvePermissionRequestFromMode(params, owner);
|
|
3332
|
+
return this.finishPermissionRequest(params, owner, response);
|
|
3333
|
+
}
|
|
3334
|
+
finishPermissionRequest(params, owner, response) {
|
|
3335
|
+
const result = this.isDelegatedRequestActive(owner) ? response : cancelledPermissionResponse();
|
|
3336
|
+
this.recordPermissionDecision(classifyPermissionDecision(params, result));
|
|
3337
|
+
return result;
|
|
3338
|
+
}
|
|
3339
|
+
hasPermissionOwner(sessionId) {
|
|
3340
|
+
return !this.pendingPromptOwners.some((owner) => owner.permissionHandler !== void 0) || this.pendingPromptOwners.some((owner) => owner.sessionId === sessionId);
|
|
3243
3341
|
}
|
|
3244
3342
|
async handleElicitationRequest(request, requestId, requestSignal) {
|
|
3245
3343
|
const resolved = this.resolveElicitationOwner(request);
|
|
3246
3344
|
if ("response" in resolved) return resolved.response;
|
|
3247
3345
|
const { active, handler } = resolved.owner;
|
|
3248
|
-
const signal = AbortSignal.any([
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
signal
|
|
3252
|
-
|
|
3346
|
+
const signal = AbortSignal.any([
|
|
3347
|
+
requestSignal,
|
|
3348
|
+
active.elicitationController.signal,
|
|
3349
|
+
active.authority?.signal
|
|
3350
|
+
].filter((candidate) => candidate !== void 0));
|
|
3351
|
+
const outcome = await raceWithAbort(signal, Promise.resolve().then(async () => {
|
|
3352
|
+
signal.throwIfAborted();
|
|
3353
|
+
return await handler(request, {
|
|
3354
|
+
requestId,
|
|
3355
|
+
signal
|
|
3356
|
+
});
|
|
3357
|
+
}).then((response) => ({
|
|
3253
3358
|
kind: "response",
|
|
3254
3359
|
response
|
|
3255
3360
|
}), (error) => ({
|
|
@@ -3284,62 +3389,65 @@ var AcpClient = class {
|
|
|
3284
3389
|
isElicitationSessionCancelling(sessionId) {
|
|
3285
3390
|
return this.closing || this.cancellingSessionIds.has(sessionId);
|
|
3286
3391
|
}
|
|
3287
|
-
|
|
3288
|
-
const active = this.
|
|
3289
|
-
const
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
};
|
|
3392
|
+
captureDelegatedRequestOwner(sessionId, requestSignal) {
|
|
3393
|
+
const active = this.pendingPromptOwners.findLast((owner) => owner.sessionId === sessionId);
|
|
3394
|
+
const signal = AbortSignal.any([
|
|
3395
|
+
active?.requestController.signal ?? this.cancellationSignalForSession(sessionId),
|
|
3396
|
+
active?.authority?.signal,
|
|
3397
|
+
requestSignal
|
|
3398
|
+
].filter((candidate) => candidate !== void 0));
|
|
3399
|
+
const epoch = this.closeEpoch;
|
|
3296
3400
|
return {
|
|
3297
|
-
|
|
3298
|
-
|
|
3401
|
+
signal,
|
|
3402
|
+
permissionHandler: active?.permissionHandler ?? this.options.onPermissionRequest,
|
|
3403
|
+
assertActive: () => {
|
|
3404
|
+
if (this.closing || this.closeEpoch !== epoch || this.cancellingSessionIds.has(sessionId)) throw RequestError.requestCancelled();
|
|
3405
|
+
}
|
|
3299
3406
|
};
|
|
3300
3407
|
}
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3408
|
+
isDelegatedRequestActive(owner) {
|
|
3409
|
+
try {
|
|
3410
|
+
assertControlAuthority(owner);
|
|
3411
|
+
return true;
|
|
3412
|
+
} catch {
|
|
3413
|
+
return false;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
async tryHandlePermissionRequestWithHost(params, owner) {
|
|
3417
|
+
const handler = owner.permissionHandler;
|
|
3418
|
+
if (!handler) return;
|
|
3419
|
+
const { signal } = owner;
|
|
3420
|
+
if (!this.isDelegatedRequestActive(owner)) return cancelledPermissionResponse();
|
|
3306
3421
|
try {
|
|
3307
3422
|
const decision = await raceWithAbort(signal, handler({
|
|
3308
3423
|
sessionId: params.sessionId,
|
|
3309
3424
|
raw: params,
|
|
3310
3425
|
inferredKind: inferToolKind(params)
|
|
3311
3426
|
}, { signal }));
|
|
3312
|
-
return this.hostPermissionDecisionResponse(params,
|
|
3427
|
+
return this.hostPermissionDecisionResponse(params, owner, decision);
|
|
3313
3428
|
} catch (error) {
|
|
3314
|
-
return this.hostPermissionErrorResponse(
|
|
3429
|
+
return this.hostPermissionErrorResponse(owner, error);
|
|
3315
3430
|
}
|
|
3316
3431
|
}
|
|
3317
|
-
hostPermissionDecisionResponse(params,
|
|
3318
|
-
if (
|
|
3319
|
-
this.recordPermissionDecision("cancelled");
|
|
3320
|
-
return cancelledPermissionResponse();
|
|
3321
|
-
}
|
|
3432
|
+
hostPermissionDecisionResponse(params, owner, decision) {
|
|
3433
|
+
if (!this.isDelegatedRequestActive(owner)) return cancelledPermissionResponse();
|
|
3322
3434
|
if (!decision) return;
|
|
3323
3435
|
const response = decisionToResponse(preferCodexPermissionRefusal(params, this.initResult?.agentInfo?.name), decision);
|
|
3324
|
-
this.recordPermissionDecision(classifyPermissionDecision(params, response));
|
|
3325
3436
|
return decision.outcome === "cancel" ? response : this.explainPermissionRefusal(params, response);
|
|
3326
3437
|
}
|
|
3327
|
-
hostPermissionErrorResponse(
|
|
3328
|
-
if (
|
|
3329
|
-
this.recordPermissionDecision("cancelled");
|
|
3330
|
-
return cancelledPermissionResponse();
|
|
3331
|
-
}
|
|
3438
|
+
hostPermissionErrorResponse(owner, error) {
|
|
3439
|
+
if (!this.isDelegatedRequestActive(owner)) return cancelledPermissionResponse();
|
|
3332
3440
|
this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
|
|
3333
3441
|
}
|
|
3334
|
-
async resolvePermissionRequestFromMode(params) {
|
|
3442
|
+
async resolvePermissionRequestFromMode(params, owner) {
|
|
3335
3443
|
try {
|
|
3336
|
-
|
|
3444
|
+
assertControlAuthority(owner);
|
|
3445
|
+
const result = await resolvePermissionRequestWithDetails(preferCodexPermissionRefusal(params, this.initResult?.agentInfo?.name), this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy, owner.signal);
|
|
3446
|
+
assertControlAuthority(owner);
|
|
3337
3447
|
this.emitPermissionEscalation(result.escalation);
|
|
3338
|
-
return
|
|
3339
|
-
response: this.explainPermissionRefusal(params, result.response),
|
|
3340
|
-
recorded: false
|
|
3341
|
-
};
|
|
3448
|
+
return this.explainPermissionRefusal(params, result.response);
|
|
3342
3449
|
} catch (error) {
|
|
3450
|
+
if (!this.isDelegatedRequestActive(owner)) return cancelledPermissionResponse();
|
|
3343
3451
|
return this.handleModePermissionError(params.sessionId, error);
|
|
3344
3452
|
}
|
|
3345
3453
|
}
|
|
@@ -3362,11 +3470,7 @@ var AcpClient = class {
|
|
|
3362
3470
|
handleModePermissionError(sessionId, error) {
|
|
3363
3471
|
if (!(error instanceof PermissionPromptUnavailableError)) throw error;
|
|
3364
3472
|
this.notePromptPermissionFailure(sessionId, error);
|
|
3365
|
-
|
|
3366
|
-
return {
|
|
3367
|
-
response: cancelledPermissionResponse(),
|
|
3368
|
-
recorded: true
|
|
3369
|
-
};
|
|
3473
|
+
return cancelledPermissionResponse();
|
|
3370
3474
|
}
|
|
3371
3475
|
attachAgentLifecycleObservers(child, startedProcess, exitNotificationBarrier) {
|
|
3372
3476
|
const onExit = (exitCode, signal) => {
|
|
@@ -3493,28 +3597,30 @@ var AcpClient = class {
|
|
|
3493
3597
|
pending.reject(error);
|
|
3494
3598
|
}
|
|
3495
3599
|
}
|
|
3496
|
-
async handleReadTextFile(params) {
|
|
3600
|
+
async handleReadTextFile(params, requestSignal) {
|
|
3497
3601
|
try {
|
|
3498
|
-
return await this.filesystem.readTextFile(params);
|
|
3602
|
+
return await this.runDelegatedOperation(params.sessionId, requestSignal, (owner) => this.filesystem.readTextFile(params, owner));
|
|
3499
3603
|
} catch (error) {
|
|
3500
|
-
this.recordPermissionError(params.sessionId, error);
|
|
3501
3604
|
if (error instanceof FsSafeError && error.code === "not-found" || error instanceof Error && "code" in error && error.code === "ENOENT") throw RequestError.resourceNotFound(pathToFileURL(params.path).href);
|
|
3502
3605
|
throw error;
|
|
3503
3606
|
}
|
|
3504
3607
|
}
|
|
3505
|
-
async handleWriteTextFile(params) {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
throw error;
|
|
3511
|
-
}
|
|
3608
|
+
async handleWriteTextFile(params, requestSignal) {
|
|
3609
|
+
return await this.runDelegatedOperation(params.sessionId, requestSignal, (owner) => this.filesystem.writeTextFile(params, owner));
|
|
3610
|
+
}
|
|
3611
|
+
async handleCreateTerminal(params, requestSignal) {
|
|
3612
|
+
return await this.runDelegatedOperation(params.sessionId, requestSignal, (owner) => this.terminalManager.createTerminal(params, owner));
|
|
3512
3613
|
}
|
|
3513
|
-
async
|
|
3614
|
+
async runDelegatedOperation(sessionId, requestSignal, run) {
|
|
3615
|
+
const owner = this.captureDelegatedRequestOwner(sessionId, requestSignal);
|
|
3514
3616
|
try {
|
|
3515
|
-
|
|
3617
|
+
assertControlAuthority(owner);
|
|
3618
|
+
const result = await run(owner);
|
|
3619
|
+
assertControlAuthority(owner);
|
|
3620
|
+
return result;
|
|
3516
3621
|
} catch (error) {
|
|
3517
|
-
this.
|
|
3622
|
+
if (!this.isDelegatedRequestActive(owner)) throw RequestError.requestCancelled();
|
|
3623
|
+
this.recordPermissionError(sessionId, error);
|
|
3518
3624
|
throw error;
|
|
3519
3625
|
}
|
|
3520
3626
|
}
|
|
@@ -4011,20 +4117,6 @@ function recordPromptSubmission(conversation, prompt, timestamp = isoNow(), mess
|
|
|
4011
4117
|
trimConversationForRuntime(conversation);
|
|
4012
4118
|
return promptMessageId;
|
|
4013
4119
|
}
|
|
4014
|
-
function agentMessageHasObservedReply(message) {
|
|
4015
|
-
return message.content.length > 0 || Object.keys(message.tool_results).length > 0;
|
|
4016
|
-
}
|
|
4017
|
-
function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
|
|
4018
|
-
let sawPrompt = false;
|
|
4019
|
-
for (const message of conversation.messages) {
|
|
4020
|
-
if (!sawPrompt) {
|
|
4021
|
-
if (isUserMessage(message) && message.User.id === promptMessageId) sawPrompt = true;
|
|
4022
|
-
continue;
|
|
4023
|
-
}
|
|
4024
|
-
if (isAgentMessage(message) && agentMessageHasObservedReply(message.Agent)) return true;
|
|
4025
|
-
}
|
|
4026
|
-
return false;
|
|
4027
|
-
}
|
|
4028
4120
|
function recordSessionUpdate(conversation, state, notification, timestamp = isoNow(), userMessageId) {
|
|
4029
4121
|
const acpx = ensureAcpxState$1(state);
|
|
4030
4122
|
const update = notification.update;
|
|
@@ -4430,14 +4522,42 @@ async function readLock(filePath) {
|
|
|
4430
4522
|
throw error;
|
|
4431
4523
|
}
|
|
4432
4524
|
}
|
|
4433
|
-
function lockPid(
|
|
4525
|
+
function lockPid(value) {
|
|
4526
|
+
if (!value || typeof value !== "object" || !("pid" in value)) return;
|
|
4527
|
+
const pid = value.pid;
|
|
4528
|
+
return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
4529
|
+
}
|
|
4530
|
+
function parseLock(payload) {
|
|
4434
4531
|
try {
|
|
4435
|
-
|
|
4436
|
-
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
4532
|
+
return JSON.parse(payload);
|
|
4437
4533
|
} catch {
|
|
4438
4534
|
return;
|
|
4439
4535
|
}
|
|
4440
4536
|
}
|
|
4537
|
+
async function tryAcquireGuard(filePath) {
|
|
4538
|
+
try {
|
|
4539
|
+
return await acquireFileLock(filePath, {
|
|
4540
|
+
managerKey: "acpx.session-turn",
|
|
4541
|
+
lockPath: `${filePath}.guard`,
|
|
4542
|
+
staleMs: Infinity,
|
|
4543
|
+
timeoutMs: LOCK_RETRY_MS,
|
|
4544
|
+
retry: {
|
|
4545
|
+
retries: 8,
|
|
4546
|
+
minTimeout: 1,
|
|
4547
|
+
maxTimeout: 2,
|
|
4548
|
+
factor: 1,
|
|
4549
|
+
randomize: false
|
|
4550
|
+
},
|
|
4551
|
+
staleRecovery: "remove-if-unchanged",
|
|
4552
|
+
payload: () => ({ pid: process.pid }),
|
|
4553
|
+
shouldReclaim: ({ payload }) => isProcessDefinitelyDead(lockPid(payload)),
|
|
4554
|
+
shouldRemoveStaleLock: ({ payload }) => isProcessDefinitelyDead(lockPid(payload))
|
|
4555
|
+
});
|
|
4556
|
+
} catch (error) {
|
|
4557
|
+
if (error.code === "file_lock_timeout") return;
|
|
4558
|
+
throw error;
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4441
4561
|
async function removeObservedLock(filePath, observed) {
|
|
4442
4562
|
const current = await readLock(filePath);
|
|
4443
4563
|
if (!current) return true;
|
|
@@ -4449,64 +4569,105 @@ async function removeObservedLock(filePath, observed) {
|
|
|
4449
4569
|
}
|
|
4450
4570
|
async function recoverAbandonedLock(filePath) {
|
|
4451
4571
|
const observed = await readLock(filePath);
|
|
4452
|
-
if (!observed) return;
|
|
4453
|
-
const pid = lockPid(observed.payload);
|
|
4454
|
-
if (pid
|
|
4455
|
-
if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return;
|
|
4456
|
-
if (await removeObservedLock(filePath, observed))
|
|
4572
|
+
if (!observed) return true;
|
|
4573
|
+
const pid = lockPid(parseLock(observed.payload));
|
|
4574
|
+
if (pid && !isProcessDefinitelyDead(pid)) return false;
|
|
4575
|
+
if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
|
|
4576
|
+
if (await removeObservedLock(filePath, observed)) {
|
|
4577
|
+
incrementPerfCounter("session.events.stale_lock_recovered");
|
|
4578
|
+
return true;
|
|
4579
|
+
}
|
|
4580
|
+
return false;
|
|
4457
4581
|
}
|
|
4458
4582
|
async function publishLock(filePath, payload, signal) {
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4583
|
+
await withTempFile({
|
|
4584
|
+
rootDir: path.dirname(filePath),
|
|
4585
|
+
prefix: "session-turn",
|
|
4586
|
+
fileName: "lock"
|
|
4587
|
+
}, async (temporaryPath) => {
|
|
4588
|
+
await fs$1.writeFile(temporaryPath, payload, {
|
|
4589
|
+
encoding: "utf8",
|
|
4590
|
+
flag: "wx",
|
|
4591
|
+
mode: 384
|
|
4592
|
+
});
|
|
4593
|
+
signal?.throwIfAborted();
|
|
4594
|
+
try {
|
|
4595
|
+
await fs$1.link(temporaryPath, filePath);
|
|
4596
|
+
} catch (error) {
|
|
4597
|
+
if (!isHardlinkFallbackError(error)) throw error;
|
|
4598
|
+
await fs$1.writeFile(filePath, payload, {
|
|
4466
4599
|
encoding: "utf8",
|
|
4467
4600
|
flag: "wx",
|
|
4468
4601
|
mode: 384
|
|
4469
4602
|
});
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
await fs$1.link(temporaryPath, filePath);
|
|
4473
|
-
} catch (error) {
|
|
4474
|
-
if (!isHardlinkFallbackError(error)) throw error;
|
|
4475
|
-
await fs$1.writeFile(filePath, payload, {
|
|
4476
|
-
encoding: "utf8",
|
|
4477
|
-
flag: "wx",
|
|
4478
|
-
mode: 384
|
|
4479
|
-
});
|
|
4480
|
-
}
|
|
4481
|
-
});
|
|
4482
|
-
} catch (error) {
|
|
4483
|
-
await removePublishedLock(filePath, payload).catch(() => {});
|
|
4484
|
-
throw error;
|
|
4485
|
-
}
|
|
4603
|
+
}
|
|
4604
|
+
});
|
|
4486
4605
|
}
|
|
4487
4606
|
async function removePublishedLock(filePath, payload) {
|
|
4488
4607
|
const current = await readLock(filePath);
|
|
4489
4608
|
if (current?.payload === payload) await removeObservedLock(filePath, current);
|
|
4490
4609
|
}
|
|
4491
|
-
async function
|
|
4610
|
+
async function rejectAcquisition(filePath, payload, guard, error) {
|
|
4611
|
+
const failures = [error];
|
|
4612
|
+
await removePublishedLock(filePath, payload).catch((cleanupError) => {
|
|
4613
|
+
failures.push(cleanupError);
|
|
4614
|
+
});
|
|
4615
|
+
await guard.release().catch((releaseError) => {
|
|
4616
|
+
failures.push(releaseError);
|
|
4617
|
+
});
|
|
4618
|
+
if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
|
|
4619
|
+
throw error;
|
|
4620
|
+
}
|
|
4621
|
+
function turnReceipt(filePath, observed, guard) {
|
|
4622
|
+
let disposed = false;
|
|
4623
|
+
let disposal;
|
|
4624
|
+
return { [Symbol.asyncDispose]: async () => {
|
|
4625
|
+
if (disposed) return;
|
|
4626
|
+
const pending = disposal ??= (async () => {
|
|
4627
|
+
await removeObservedLock(filePath, observed);
|
|
4628
|
+
await guard.release();
|
|
4629
|
+
disposed = true;
|
|
4630
|
+
})();
|
|
4631
|
+
try {
|
|
4632
|
+
await pending;
|
|
4633
|
+
} finally {
|
|
4634
|
+
if (disposal === pending) disposal = void 0;
|
|
4635
|
+
}
|
|
4636
|
+
} };
|
|
4637
|
+
}
|
|
4638
|
+
async function tryPublishLock(filePath, payload, signal) {
|
|
4639
|
+
for (;;) try {
|
|
4640
|
+
await publishLock(filePath, payload, signal);
|
|
4641
|
+
return true;
|
|
4642
|
+
} catch (error) {
|
|
4643
|
+
if (error.code !== "EEXIST") throw error;
|
|
4644
|
+
if (!await recoverAbandonedLock(filePath)) return false;
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
async function tryAcquireTurn(filePath, payload, signal) {
|
|
4648
|
+
const guard = await tryAcquireGuard(filePath);
|
|
4649
|
+
if (!guard) return;
|
|
4492
4650
|
try {
|
|
4651
|
+
signal?.throwIfAborted();
|
|
4652
|
+
if (!await tryPublishLock(filePath, payload, signal)) {
|
|
4653
|
+
await guard.release();
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4493
4656
|
const observed = await readLock(filePath);
|
|
4494
4657
|
if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
|
|
4495
4658
|
signal?.throwIfAborted();
|
|
4496
|
-
return
|
|
4497
|
-
await removeObservedLock(filePath, observed);
|
|
4498
|
-
} };
|
|
4659
|
+
return turnReceipt(filePath, observed, guard);
|
|
4499
4660
|
} catch (error) {
|
|
4500
|
-
await
|
|
4501
|
-
throw error;
|
|
4661
|
+
return await rejectAcquisition(filePath, payload, guard, error);
|
|
4502
4662
|
}
|
|
4503
4663
|
}
|
|
4504
4664
|
async function acquireSessionTurn(recordId, signal) {
|
|
4505
|
-
const
|
|
4506
|
-
await fs$1.mkdir(path.dirname(
|
|
4665
|
+
const requestedPath = sessionEventLockPath(recordId);
|
|
4666
|
+
await fs$1.mkdir(path.dirname(requestedPath), {
|
|
4507
4667
|
recursive: true,
|
|
4508
4668
|
mode: 448
|
|
4509
4669
|
});
|
|
4670
|
+
const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
|
|
4510
4671
|
lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
|
|
4511
4672
|
const payload = `${JSON.stringify({
|
|
4512
4673
|
pid: process.pid,
|
|
@@ -4514,16 +4675,10 @@ async function acquireSessionTurn(recordId, signal) {
|
|
|
4514
4675
|
}, null, 2)}\n`;
|
|
4515
4676
|
for (;;) {
|
|
4516
4677
|
signal?.throwIfAborted();
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
} catch (error) {
|
|
4521
|
-
if (error.code !== "EEXIST") throw error;
|
|
4522
|
-
await recoverAbandonedLock(filePath);
|
|
4523
|
-
await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
|
|
4524
|
-
}
|
|
4678
|
+
const turn = await tryAcquireTurn(filePath, payload, signal);
|
|
4679
|
+
if (turn) return turn;
|
|
4680
|
+
await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
|
|
4525
4681
|
}
|
|
4526
|
-
return await observePublishedLock(filePath, payload, signal);
|
|
4527
4682
|
}
|
|
4528
4683
|
//#endregion
|
|
4529
4684
|
//#region src/session/queue/ipc-server.ts
|
|
@@ -4704,6 +4859,7 @@ var SessionQueueOwner = class SessionQueueOwner {
|
|
|
4704
4859
|
return true;
|
|
4705
4860
|
}
|
|
4706
4861
|
handleControlRequest(options) {
|
|
4862
|
+
this.taskSockets.add(options.socket);
|
|
4707
4863
|
writeQueueMessage(options.socket, {
|
|
4708
4864
|
type: "accepted",
|
|
4709
4865
|
requestId: options.requestId,
|
|
@@ -5016,7 +5172,7 @@ async function replayDesiredConfigOptions(params) {
|
|
|
5016
5172
|
for (const [configId, value] of Object.entries(params.desiredConfigOptions)) {
|
|
5017
5173
|
if (configId === replacingKey || acceptedConfigOptions && !acceptsSavedConfigValue(acceptedConfigOptions.find((option) => option.id === configId), value)) continue;
|
|
5018
5174
|
try {
|
|
5019
|
-
const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value, params.replay.authority), params.timeoutMs);
|
|
5175
|
+
const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value, advertisedModelState(params.record.acpx), params.replay.authority), params.timeoutMs);
|
|
5020
5176
|
params.replay.acknowledged = true;
|
|
5021
5177
|
params.record.acpx = applyConfigOptionSelection(params.record.acpx, configId, value, response);
|
|
5022
5178
|
acceptedConfigOptions = response.configOptions;
|
|
@@ -5067,6 +5223,7 @@ async function connectAndLoadSession(options) {
|
|
|
5067
5223
|
timeoutMs: options.timeoutMs
|
|
5068
5224
|
});
|
|
5069
5225
|
const { resumed, loadError, sessionId, createdFreshSession, pendingAgentSessionId, sessionModels } = loadState;
|
|
5226
|
+
applyReconnectedModelState(record, sessionModels, loadState.configOptionsPresent, loadState.legacyModelMetadataPresent, createdFreshSession);
|
|
5070
5227
|
const preferenceReplay = await replaySessionPreferences({
|
|
5071
5228
|
client,
|
|
5072
5229
|
record,
|
|
@@ -5287,7 +5444,7 @@ function createActiveSessionController(params) {
|
|
|
5287
5444
|
return response;
|
|
5288
5445
|
},
|
|
5289
5446
|
setSessionConfigOption: async (configId, value) => {
|
|
5290
|
-
return await params.client.setSessionConfigOption(getActiveSessionId(), configId, value);
|
|
5447
|
+
return await params.client.setSessionConfigOption(getActiveSessionId(), configId, value, advertisedModelState(params.record.acpx));
|
|
5291
5448
|
}
|
|
5292
5449
|
};
|
|
5293
5450
|
}
|
|
@@ -5410,14 +5567,38 @@ function toSessionMutationResult(result) {
|
|
|
5410
5567
|
loadError: result.loadError
|
|
5411
5568
|
};
|
|
5412
5569
|
}
|
|
5570
|
+
async function withOwnedDirectSession(options) {
|
|
5571
|
+
const waiting = new AbortController();
|
|
5572
|
+
const timeoutMs = options.timeoutMs;
|
|
5573
|
+
const timer = timeoutMs != null && timeoutMs > 0 ? setTimeout(() => waiting.abort(new TimeoutError(timeoutMs)), timeoutMs) : void 0;
|
|
5574
|
+
let ownership;
|
|
5575
|
+
let recordId;
|
|
5576
|
+
try {
|
|
5577
|
+
recordId = (await options.loadRecord(options.sessionRecordId)).acpxRecordId;
|
|
5578
|
+
ownership = await acquireSessionTurn(recordId, waiting.signal);
|
|
5579
|
+
} catch (error) {
|
|
5580
|
+
waiting.signal.throwIfAborted();
|
|
5581
|
+
throw error;
|
|
5582
|
+
} finally {
|
|
5583
|
+
clearTimeout(timer);
|
|
5584
|
+
}
|
|
5585
|
+
try {
|
|
5586
|
+
return await withConnectedSession({
|
|
5587
|
+
...options,
|
|
5588
|
+
sessionRecordId: recordId
|
|
5589
|
+
});
|
|
5590
|
+
} finally {
|
|
5591
|
+
await ownership[Symbol.asyncDispose]();
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5413
5594
|
async function runSessionSetModeDirect(options) {
|
|
5414
|
-
return toSessionMutationResult(await
|
|
5595
|
+
return toSessionMutationResult(await withOwnedDirectSession(buildDirectConnectedSessionOptions(options, async ({ client, sessionId, record }) => {
|
|
5415
5596
|
await withTimeout(client.setSessionMode(sessionId, options.modeId), options.timeoutMs);
|
|
5416
5597
|
setDesiredModeId(record, options.modeId);
|
|
5417
5598
|
})));
|
|
5418
5599
|
}
|
|
5419
5600
|
async function runSessionSetModelDirect(options) {
|
|
5420
|
-
const result = await
|
|
5601
|
+
const result = await withOwnedDirectSession(buildDirectConnectedSessionOptions({
|
|
5421
5602
|
...options,
|
|
5422
5603
|
replacingConfigOption: { key: "model" }
|
|
5423
5604
|
}, async ({ client, sessionId, record }) => {
|
|
@@ -5432,12 +5613,13 @@ async function runSessionSetModelDirect(options) {
|
|
|
5432
5613
|
};
|
|
5433
5614
|
}
|
|
5434
5615
|
async function runSessionSetConfigOptionDirect(options) {
|
|
5435
|
-
const result = await
|
|
5616
|
+
const result = await withOwnedDirectSession(buildDirectConnectedSessionOptions({
|
|
5436
5617
|
...options,
|
|
5437
5618
|
replacingConfigOption: { key: options.configId }
|
|
5438
5619
|
}, async ({ client, sessionId, record }) => {
|
|
5439
|
-
const
|
|
5440
|
-
|
|
5620
|
+
const models = advertisedModelState(record.acpx);
|
|
5621
|
+
const response = await withTimeout(client.setSessionConfigOption(sessionId, options.configId, options.value, models), options.timeoutMs);
|
|
5622
|
+
record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, response, models?.configId);
|
|
5441
5623
|
return response;
|
|
5442
5624
|
}));
|
|
5443
5625
|
return {
|
|
@@ -5456,16 +5638,31 @@ async function cancelSessionPrompt(options) {
|
|
|
5456
5638
|
cancelled: cancelled === true
|
|
5457
5639
|
};
|
|
5458
5640
|
}
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5641
|
+
/**
|
|
5642
|
+
* Dispatches the mode change to a running queue owner. Returns `undefined`
|
|
5643
|
+
* when no owner holds the session, leaving the direct-connection decision to
|
|
5644
|
+
* the caller.
|
|
5645
|
+
*
|
|
5646
|
+
* An owner advertising `persistsControlState` saves the accepted state itself.
|
|
5647
|
+
* Only the older caller-save path still reloads and rewrites the record here,
|
|
5648
|
+
* which is not serialized against another process doing the same.
|
|
5649
|
+
*/
|
|
5650
|
+
async function setSessionModeOnOwner(options) {
|
|
5651
|
+
const submittedToOwner = await trySetModeOnRunningOwner(options.sessionId, options.modeId, options.timeoutMs, options.verbose, options.assertDispatch);
|
|
5652
|
+
if (!submittedToOwner?.value) return;
|
|
5653
|
+
const record = await resolveSessionRecord(options.sessionId);
|
|
5654
|
+
if (!submittedToOwner.persistsControlState) {
|
|
5462
5655
|
setDesiredModeId(record, options.modeId);
|
|
5463
5656
|
await writeSessionRecord(record);
|
|
5464
|
-
return {
|
|
5465
|
-
record,
|
|
5466
|
-
resumed: false
|
|
5467
|
-
};
|
|
5468
5657
|
}
|
|
5658
|
+
return {
|
|
5659
|
+
record,
|
|
5660
|
+
resumed: false
|
|
5661
|
+
};
|
|
5662
|
+
}
|
|
5663
|
+
async function setSessionMode(options) {
|
|
5664
|
+
const onOwner = await setSessionModeOnOwner(options);
|
|
5665
|
+
if (onOwner) return onOwner;
|
|
5469
5666
|
return await runSessionSetModeDirect({
|
|
5470
5667
|
sessionRecordId: options.sessionId,
|
|
5471
5668
|
modeId: options.modeId,
|
|
@@ -5479,18 +5676,24 @@ async function setSessionMode(options) {
|
|
|
5479
5676
|
verbose: options.verbose
|
|
5480
5677
|
});
|
|
5481
5678
|
}
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5679
|
+
/** Owner-only counterpart of {@link setSessionMode} for the model control. */
|
|
5680
|
+
async function setSessionModelOnOwner(options) {
|
|
5681
|
+
const submittedToOwner = await trySetModelOnRunningOwner(options.sessionId, options.modelId, options.timeoutMs, options.verbose, options.assertDispatch);
|
|
5682
|
+
if (!submittedToOwner) return;
|
|
5683
|
+
const record = await resolveSessionRecord(options.sessionId);
|
|
5684
|
+
if (!submittedToOwner.persistsControlState) {
|
|
5685
|
+
record.acpx = applyModelSelection(record.acpx, options.modelId, submittedToOwner.value.response);
|
|
5487
5686
|
await writeSessionRecord(record);
|
|
5488
|
-
return {
|
|
5489
|
-
record,
|
|
5490
|
-
response: submittedToOwner.response,
|
|
5491
|
-
resumed: false
|
|
5492
|
-
};
|
|
5493
5687
|
}
|
|
5688
|
+
return {
|
|
5689
|
+
record,
|
|
5690
|
+
response: submittedToOwner.value.response,
|
|
5691
|
+
resumed: false
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
async function setSessionModel(options) {
|
|
5695
|
+
const onOwner = await setSessionModelOnOwner(options);
|
|
5696
|
+
if (onOwner) return onOwner;
|
|
5494
5697
|
return await runSessionSetModelDirect({
|
|
5495
5698
|
sessionRecordId: options.sessionId,
|
|
5496
5699
|
modelId: options.modelId,
|
|
@@ -5504,18 +5707,24 @@ async function setSessionModel(options) {
|
|
|
5504
5707
|
verbose: options.verbose
|
|
5505
5708
|
});
|
|
5506
5709
|
}
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5710
|
+
/** Owner-only counterpart of {@link setSessionMode} for the config option control. */
|
|
5711
|
+
async function setSessionConfigOptionOnOwner(options) {
|
|
5712
|
+
const ownerResponse = await trySetConfigOptionOnRunningOwner(options.sessionId, options.configId, options.value, options.timeoutMs, options.verbose, options.assertDispatch);
|
|
5713
|
+
if (!ownerResponse) return;
|
|
5714
|
+
const record = await resolveSessionRecord(options.sessionId);
|
|
5715
|
+
if (!ownerResponse.persistsControlState) {
|
|
5716
|
+
record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, ownerResponse.value);
|
|
5512
5717
|
await writeSessionRecord(record);
|
|
5513
|
-
return {
|
|
5514
|
-
record,
|
|
5515
|
-
response: ownerResponse,
|
|
5516
|
-
resumed: false
|
|
5517
|
-
};
|
|
5518
5718
|
}
|
|
5719
|
+
return {
|
|
5720
|
+
record,
|
|
5721
|
+
response: ownerResponse.value,
|
|
5722
|
+
resumed: false
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
async function setSessionConfigOption(options) {
|
|
5726
|
+
const onOwner = await setSessionConfigOptionOnOwner(options);
|
|
5727
|
+
if (onOwner) return onOwner;
|
|
5519
5728
|
return await runSessionSetConfigOptionDirect({
|
|
5520
5729
|
sessionRecordId: options.sessionId,
|
|
5521
5730
|
configId: options.configId,
|
|
@@ -5600,11 +5809,17 @@ async function closeSession(sessionId) {
|
|
|
5600
5809
|
await tryCloseSessionOnRunningOwner({ sessionId: record.acpxRecordId }).catch(() => {});
|
|
5601
5810
|
await terminateQueueOwnerForSession(record.acpxRecordId);
|
|
5602
5811
|
if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record.agentCommand)) await terminateProcess(record.pid);
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5812
|
+
const ownership = await acquireSessionTurn(record.acpxRecordId);
|
|
5813
|
+
try {
|
|
5814
|
+
const current = await resolveSessionRecord(record.acpxRecordId);
|
|
5815
|
+
current.pid = void 0;
|
|
5816
|
+
current.closed = true;
|
|
5817
|
+
current.closedAt = isoNow$1();
|
|
5818
|
+
await writeSessionRecord(current);
|
|
5819
|
+
return current;
|
|
5820
|
+
} finally {
|
|
5821
|
+
await ownership[Symbol.asyncDispose]();
|
|
5822
|
+
}
|
|
5608
5823
|
}
|
|
5609
5824
|
//#endregion
|
|
5610
5825
|
//#region src/session/execution/session-management.ts
|
|
@@ -5925,16 +6140,182 @@ function installPerfMetricsCapture(options = {}) {
|
|
|
5925
6140
|
}
|
|
5926
6141
|
}
|
|
5927
6142
|
//#endregion
|
|
6143
|
+
//#region src/session/queue/control-admission.ts
|
|
6144
|
+
var QueueControlDeadline = class {
|
|
6145
|
+
controller = new AbortController();
|
|
6146
|
+
admissionSignal;
|
|
6147
|
+
expiresAt;
|
|
6148
|
+
expired;
|
|
6149
|
+
timeoutError;
|
|
6150
|
+
rejectExpiry;
|
|
6151
|
+
timer;
|
|
6152
|
+
nativeSettled = false;
|
|
6153
|
+
admitted = false;
|
|
6154
|
+
constructor(timeoutMs, ownerSignal) {
|
|
6155
|
+
this.admissionSignal = ownerSignal ? AbortSignal.any([this.controller.signal, ownerSignal]) : this.controller.signal;
|
|
6156
|
+
this.expiresAt = timeoutMs != null && timeoutMs > 0 ? performance.now() + timeoutMs : void 0;
|
|
6157
|
+
this.timeoutError = this.expiresAt === void 0 ? void 0 : new TimeoutError(timeoutMs);
|
|
6158
|
+
this.expired = new Promise((_resolve, reject) => {
|
|
6159
|
+
this.rejectExpiry = reject;
|
|
6160
|
+
if (this.expiresAt !== void 0) this.timer = setTimeout(() => this.expire(), timeoutMs);
|
|
6161
|
+
});
|
|
6162
|
+
this.expired.catch(() => {});
|
|
6163
|
+
}
|
|
6164
|
+
get signal() {
|
|
6165
|
+
return this.admissionSignal;
|
|
6166
|
+
}
|
|
6167
|
+
get timeoutSignal() {
|
|
6168
|
+
return this.controller.signal;
|
|
6169
|
+
}
|
|
6170
|
+
get remainingMs() {
|
|
6171
|
+
this.assertActive();
|
|
6172
|
+
return this.expiresAt === void 0 || this.nativeSettled ? void 0 : Math.max(1, this.expiresAt - performance.now());
|
|
6173
|
+
}
|
|
6174
|
+
get nativeAdmitted() {
|
|
6175
|
+
return this.admitted;
|
|
6176
|
+
}
|
|
6177
|
+
get authority() {
|
|
6178
|
+
return {
|
|
6179
|
+
signal: this.signal,
|
|
6180
|
+
assertActive: () => {
|
|
6181
|
+
this.assertActive();
|
|
6182
|
+
this.admitted = true;
|
|
6183
|
+
}
|
|
6184
|
+
};
|
|
6185
|
+
}
|
|
6186
|
+
assertActive() {
|
|
6187
|
+
if (this.expiresAt !== void 0 && performance.now() >= this.expiresAt) this.expire();
|
|
6188
|
+
this.signal.throwIfAborted();
|
|
6189
|
+
}
|
|
6190
|
+
expire() {
|
|
6191
|
+
if (this.nativeSettled || this.timeoutSignal.aborted || !this.timeoutError) return;
|
|
6192
|
+
this.controller.abort(this.timeoutError);
|
|
6193
|
+
this.rejectExpiry(this.timeoutError);
|
|
6194
|
+
}
|
|
6195
|
+
responseSettled() {
|
|
6196
|
+
this.nativeSettled = true;
|
|
6197
|
+
clearTimeout(this.timer);
|
|
6198
|
+
this.timer = void 0;
|
|
6199
|
+
}
|
|
6200
|
+
isExpired(error) {
|
|
6201
|
+
return this.timeoutSignal.aborted && error === this.timeoutSignal.reason;
|
|
6202
|
+
}
|
|
6203
|
+
async wait(operation) {
|
|
6204
|
+
return await Promise.race([operation, this.expired]);
|
|
6205
|
+
}
|
|
6206
|
+
};
|
|
6207
|
+
function deferred() {
|
|
6208
|
+
let resolve;
|
|
6209
|
+
let reject;
|
|
6210
|
+
const promise = new Promise((accept, fail) => {
|
|
6211
|
+
resolve = accept;
|
|
6212
|
+
reject = fail;
|
|
6213
|
+
});
|
|
6214
|
+
promise.catch(() => {});
|
|
6215
|
+
return {
|
|
6216
|
+
promise,
|
|
6217
|
+
resolve,
|
|
6218
|
+
reject
|
|
6219
|
+
};
|
|
6220
|
+
}
|
|
6221
|
+
function drained(operation) {
|
|
6222
|
+
return operation.then(() => {}, () => {});
|
|
6223
|
+
}
|
|
6224
|
+
var QueueOwnerControlAdmission = class {
|
|
6225
|
+
options;
|
|
6226
|
+
idleTail = Promise.resolve();
|
|
6227
|
+
ticket;
|
|
6228
|
+
stopped = new AbortController();
|
|
6229
|
+
pending = 0;
|
|
6230
|
+
constructor(options) {
|
|
6231
|
+
this.options = options;
|
|
6232
|
+
}
|
|
6233
|
+
get hasPending() {
|
|
6234
|
+
return this.pending > 0;
|
|
6235
|
+
}
|
|
6236
|
+
beginPrompt() {
|
|
6237
|
+
const ticket = {
|
|
6238
|
+
ready: deferred(),
|
|
6239
|
+
released: deferred(),
|
|
6240
|
+
rawTail: Promise.resolve(),
|
|
6241
|
+
sealed: false
|
|
6242
|
+
};
|
|
6243
|
+
this.ticket = ticket;
|
|
6244
|
+
return {
|
|
6245
|
+
ticket,
|
|
6246
|
+
priorIdle: this.idleTail
|
|
6247
|
+
};
|
|
6248
|
+
}
|
|
6249
|
+
publish(ticket, controls) {
|
|
6250
|
+
if (!ticket.sealed) ticket.ready.resolve(controls);
|
|
6251
|
+
}
|
|
6252
|
+
seal(ticket) {
|
|
6253
|
+
ticket.sealed = true;
|
|
6254
|
+
ticket.ready.reject(new QueueConnectionError("Prompt ended before controls became ready", {
|
|
6255
|
+
detailCode: "QUEUE_CONTROL_REQUEST_FAILED",
|
|
6256
|
+
origin: "queue",
|
|
6257
|
+
retryable: true
|
|
6258
|
+
}));
|
|
6259
|
+
return ticket.rawTail;
|
|
6260
|
+
}
|
|
6261
|
+
release(ticket) {
|
|
6262
|
+
if (this.ticket === ticket) this.ticket = void 0;
|
|
6263
|
+
ticket.released.resolve();
|
|
6264
|
+
}
|
|
6265
|
+
stopAdmission() {
|
|
6266
|
+
this.stopped.abort(new QueueConnectionError("Queue owner is closing", {
|
|
6267
|
+
detailCode: "QUEUE_OWNER_SHUTTING_DOWN",
|
|
6268
|
+
origin: "queue",
|
|
6269
|
+
retryable: true
|
|
6270
|
+
}));
|
|
6271
|
+
}
|
|
6272
|
+
async drainAdmitted() {
|
|
6273
|
+
await Promise.all([this.idleTail, this.ticket?.rawTail]);
|
|
6274
|
+
}
|
|
6275
|
+
async run(invoke, timeoutMs, replacingConfigOption) {
|
|
6276
|
+
const deadline = new QueueControlDeadline(timeoutMs, this.stopped.signal);
|
|
6277
|
+
this.pending += 1;
|
|
6278
|
+
const operation = this.enqueue(invoke, {
|
|
6279
|
+
deadline,
|
|
6280
|
+
replacingConfigOption
|
|
6281
|
+
});
|
|
6282
|
+
operation.finally(() => {
|
|
6283
|
+
this.pending -= 1;
|
|
6284
|
+
deadline.responseSettled();
|
|
6285
|
+
}).catch(() => {});
|
|
6286
|
+
return await deadline.wait(operation);
|
|
6287
|
+
}
|
|
6288
|
+
async enqueue(invoke, options) {
|
|
6289
|
+
options.deadline.assertActive();
|
|
6290
|
+
const ticket = this.ticket;
|
|
6291
|
+
if (ticket?.sealed) return ticket.released.promise.then(() => this.enqueue(invoke, options));
|
|
6292
|
+
if (ticket) {
|
|
6293
|
+
const operation = ticket.rawTail.then(async () => {
|
|
6294
|
+
const controls = await ticket.ready.promise.catch((error) => {
|
|
6295
|
+
options.deadline.assertActive();
|
|
6296
|
+
throw error;
|
|
6297
|
+
});
|
|
6298
|
+
options.deadline.assertActive();
|
|
6299
|
+
return await invoke(controls, options.deadline);
|
|
6300
|
+
});
|
|
6301
|
+
ticket.rawTail = drained(operation);
|
|
6302
|
+
return operation;
|
|
6303
|
+
}
|
|
6304
|
+
const operation = this.idleTail.then(async () => {
|
|
6305
|
+
options.deadline.assertActive();
|
|
6306
|
+
return await this.options.runIdle(invoke, options);
|
|
6307
|
+
});
|
|
6308
|
+
this.idleTail = drained(operation);
|
|
6309
|
+
return operation;
|
|
6310
|
+
}
|
|
6311
|
+
};
|
|
6312
|
+
//#endregion
|
|
5928
6313
|
//#region src/session/queue/owner-turn-controller.ts
|
|
5929
6314
|
var QueueOwnerTurnController = class {
|
|
5930
|
-
options;
|
|
5931
6315
|
state = "idle";
|
|
5932
6316
|
pendingCancel = false;
|
|
5933
6317
|
activeController;
|
|
5934
6318
|
waitingTurn;
|
|
5935
|
-
constructor(options) {
|
|
5936
|
-
this.options = options;
|
|
5937
|
-
}
|
|
5938
6319
|
get lifecycleState() {
|
|
5939
6320
|
return this.state;
|
|
5940
6321
|
}
|
|
@@ -5966,20 +6347,9 @@ var QueueOwnerTurnController = class {
|
|
|
5966
6347
|
clearActiveController() {
|
|
5967
6348
|
this.activeController = void 0;
|
|
5968
6349
|
}
|
|
5969
|
-
assertCanHandleControlRequest() {
|
|
5970
|
-
if (this.state === "closing") throw new QueueConnectionError("Queue owner is closing", {
|
|
5971
|
-
detailCode: "QUEUE_OWNER_SHUTTING_DOWN",
|
|
5972
|
-
origin: "queue",
|
|
5973
|
-
retryable: true
|
|
5974
|
-
});
|
|
5975
|
-
}
|
|
5976
6350
|
async requestCancel() {
|
|
5977
6351
|
const activeController = this.activeController;
|
|
5978
|
-
if (activeController?.hasActivePrompt())
|
|
5979
|
-
const cancelled = await activeController.requestCancelActivePrompt();
|
|
5980
|
-
if (cancelled) this.pendingCancel = false;
|
|
5981
|
-
return cancelled;
|
|
5982
|
-
}
|
|
6352
|
+
if (activeController?.hasActivePrompt()) return await this.cancelActivePrompt(activeController);
|
|
5983
6353
|
if (this.state === "starting" || this.state === "active") {
|
|
5984
6354
|
this.pendingCancel = true;
|
|
5985
6355
|
this.waitingTurn?.abort();
|
|
@@ -5990,32 +6360,138 @@ var QueueOwnerTurnController = class {
|
|
|
5990
6360
|
async applyPendingCancel() {
|
|
5991
6361
|
const activeController = this.activeController;
|
|
5992
6362
|
if (!this.pendingCancel || !activeController || !activeController.hasActivePrompt()) return false;
|
|
5993
|
-
|
|
5994
|
-
|
|
6363
|
+
return await this.cancelActivePrompt(activeController);
|
|
6364
|
+
}
|
|
6365
|
+
async cancelActivePrompt(activeController) {
|
|
6366
|
+
const turn = this.waitingTurn;
|
|
6367
|
+
const cancellation = activeController.requestCancelActivePrompt();
|
|
6368
|
+
turn?.abort();
|
|
6369
|
+
const cancelled = await cancellation;
|
|
6370
|
+
if (cancelled && this.waitingTurn === turn) this.pendingCancel = false;
|
|
5995
6371
|
return cancelled;
|
|
5996
6372
|
}
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6373
|
+
};
|
|
6374
|
+
//#endregion
|
|
6375
|
+
//#region src/session/execution/owned-controls.ts
|
|
6376
|
+
async function acceptControl(options, invoke, apply, deadline = new QueueControlDeadline()) {
|
|
6377
|
+
deadline.assertActive();
|
|
6378
|
+
const raw = invoke(deadline.authority).then(async (response) => {
|
|
6379
|
+
deadline.responseSettled();
|
|
6380
|
+
apply(response);
|
|
6381
|
+
await options.checkpoint();
|
|
6382
|
+
return response;
|
|
6383
|
+
}, (error) => {
|
|
6384
|
+
deadline.responseSettled();
|
|
6385
|
+
throw error;
|
|
6386
|
+
});
|
|
6387
|
+
try {
|
|
6388
|
+
return await deadline.wait(raw);
|
|
6389
|
+
} catch (error) {
|
|
6390
|
+
if (deadline.isExpired(error) && deadline.nativeAdmitted) {
|
|
6391
|
+
const [retired] = await Promise.allSettled([options.retire(), raw]);
|
|
6392
|
+
if (retired.status === "rejected") throw retired.reason;
|
|
6003
6393
|
}
|
|
6004
|
-
|
|
6394
|
+
throw error;
|
|
6005
6395
|
}
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6396
|
+
}
|
|
6397
|
+
function createOwnedSessionControls(options) {
|
|
6398
|
+
return {
|
|
6399
|
+
setSessionMode: async (modeId, deadline) => {
|
|
6400
|
+
await acceptControl(options, (authority) => options.client.setSessionMode(options.sessionId(), modeId, authority), () => setDesiredModeId(options.record, modeId), deadline);
|
|
6401
|
+
},
|
|
6402
|
+
setSessionModel: async (modelId, deadline) => {
|
|
6403
|
+
const models = advertisedModelState(options.record.acpx);
|
|
6404
|
+
return await acceptControl(options, (authority) => options.client.setSessionModel(options.sessionId(), modelId, models, authority), (response) => {
|
|
6405
|
+
options.record.acpx = applyModelSelection(options.record.acpx, modelId, response);
|
|
6406
|
+
}, deadline);
|
|
6407
|
+
},
|
|
6408
|
+
setSessionConfigOption: async (configId, value, deadline) => {
|
|
6409
|
+
const models = advertisedModelState(options.record.acpx);
|
|
6410
|
+
return await acceptControl(options, (authority) => options.client.setSessionConfigOption(options.sessionId(), configId, value, models, authority), (response) => {
|
|
6411
|
+
options.record.acpx = applyConfigOptionSelection(options.record.acpx, configId, value, response, models?.configId);
|
|
6412
|
+
}, deadline);
|
|
6413
|
+
}
|
|
6414
|
+
};
|
|
6415
|
+
}
|
|
6416
|
+
async function runIdleControlWithRecord(options, record, invoke) {
|
|
6417
|
+
const { client, deadline } = options;
|
|
6418
|
+
deadline.assertActive();
|
|
6419
|
+
let retirement;
|
|
6420
|
+
const retire = () => {
|
|
6421
|
+
retirement ??= client.close().catch((error) => {
|
|
6422
|
+
options.onRetirementFailure();
|
|
6423
|
+
throw error;
|
|
6424
|
+
});
|
|
6425
|
+
return retirement;
|
|
6426
|
+
};
|
|
6427
|
+
const onAbort = () => {
|
|
6428
|
+
retire().catch(() => {});
|
|
6429
|
+
};
|
|
6430
|
+
const checkpoint = async () => {
|
|
6431
|
+
record.lastUsedAt = isoNow$1();
|
|
6432
|
+
applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
|
|
6433
|
+
await writeSessionRecord(record);
|
|
6434
|
+
};
|
|
6435
|
+
let sessionId = record.acpSessionId;
|
|
6436
|
+
const controls = createOwnedSessionControls({
|
|
6437
|
+
client,
|
|
6438
|
+
record,
|
|
6439
|
+
sessionId: () => sessionId,
|
|
6440
|
+
checkpoint,
|
|
6441
|
+
retire
|
|
6442
|
+
});
|
|
6443
|
+
client.setEventHandlers({ onSessionUpdate: (notification) => {
|
|
6444
|
+
record.acpx = recordSessionUpdate(record, record.acpx, notification);
|
|
6445
|
+
} });
|
|
6446
|
+
deadline.timeoutSignal.addEventListener("abort", onAbort, { once: true });
|
|
6447
|
+
let connected = false;
|
|
6448
|
+
let completed = false;
|
|
6449
|
+
try {
|
|
6450
|
+
await connectAndLoadSession({
|
|
6451
|
+
client,
|
|
6452
|
+
record,
|
|
6453
|
+
resumePolicy: "same-session-only",
|
|
6454
|
+
replacingConfigOption: options.replacingConfigOption,
|
|
6455
|
+
timeoutMs: deadline.remainingMs,
|
|
6456
|
+
authority: deadline.authority,
|
|
6457
|
+
verbose: options.verbose,
|
|
6458
|
+
activeController: {
|
|
6459
|
+
...controls,
|
|
6460
|
+
hasActivePrompt: () => client.hasActivePrompt(),
|
|
6461
|
+
requestCancelActivePrompt: () => client.requestCancelActivePrompt()
|
|
6462
|
+
},
|
|
6463
|
+
onSessionIdResolved: (value) => {
|
|
6464
|
+
sessionId = value;
|
|
6465
|
+
}
|
|
6466
|
+
});
|
|
6467
|
+
connected = true;
|
|
6468
|
+
deadline.assertActive();
|
|
6469
|
+
const value = await invoke(controls, deadline);
|
|
6470
|
+
completed = true;
|
|
6471
|
+
return value;
|
|
6472
|
+
} finally {
|
|
6473
|
+
deadline.timeoutSignal.removeEventListener("abort", onAbort);
|
|
6474
|
+
try {
|
|
6475
|
+
if (!connected) await retire();
|
|
6476
|
+
await retirement;
|
|
6477
|
+
if (!completed || retirement) await checkpoint().catch(() => {});
|
|
6478
|
+
} finally {
|
|
6479
|
+
client.clearEventHandlers();
|
|
6480
|
+
}
|
|
6011
6481
|
}
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6482
|
+
}
|
|
6483
|
+
async function runIdleOwnerControl(options, invoke) {
|
|
6484
|
+
const { acpxRecordId } = await resolveSessionRecord(options.sessionId);
|
|
6485
|
+
const ownership = await acquireSessionTurn(acpxRecordId, options.deadline.signal).catch((error) => {
|
|
6486
|
+
options.deadline.assertActive();
|
|
6487
|
+
throw error;
|
|
6488
|
+
});
|
|
6489
|
+
try {
|
|
6490
|
+
return await runIdleControlWithRecord(options, await resolveSessionRecord(acpxRecordId), invoke);
|
|
6491
|
+
} finally {
|
|
6492
|
+
await ownership[Symbol.asyncDispose]();
|
|
6017
6493
|
}
|
|
6018
|
-
}
|
|
6494
|
+
}
|
|
6019
6495
|
//#endregion
|
|
6020
6496
|
//#region src/session/execution/queue-owner-process.ts
|
|
6021
6497
|
function isNonEmptyStringArray(value) {
|
|
@@ -6197,20 +6673,27 @@ function responseMetaField(meta) {
|
|
|
6197
6673
|
return meta === void 0 ? {} : { _meta: meta };
|
|
6198
6674
|
}
|
|
6199
6675
|
function recoveredSessionResult(response, conversation, promptMessageId) {
|
|
6200
|
-
recordPromptResponseUsage(conversation, response
|
|
6676
|
+
recordPromptResponseUsage(conversation, response.usage, promptMessageId);
|
|
6201
6677
|
return {
|
|
6202
|
-
stopReason:
|
|
6678
|
+
stopReason: response.stopReason,
|
|
6203
6679
|
source: "session",
|
|
6204
|
-
...responseMetaField(response
|
|
6680
|
+
...responseMetaField(response._meta)
|
|
6205
6681
|
};
|
|
6206
6682
|
}
|
|
6207
|
-
function abortTimedOutRequests(error,
|
|
6208
|
-
if (error instanceof TimeoutError)
|
|
6683
|
+
function abortTimedOutRequests(error, lifetime) {
|
|
6684
|
+
if (error instanceof TimeoutError) lifetime.abort(error);
|
|
6685
|
+
}
|
|
6686
|
+
function promptRequestAuthority(lifetime, authority) {
|
|
6687
|
+
return {
|
|
6688
|
+
signal: authority?.signal ? AbortSignal.any([authority.signal, lifetime.signal]) : lifetime.signal,
|
|
6689
|
+
assertActive: () => assertControlAuthority(authority)
|
|
6690
|
+
};
|
|
6209
6691
|
}
|
|
6210
6692
|
async function runPromptTurn(params) {
|
|
6211
6693
|
let settledResponse;
|
|
6694
|
+
const lifetime = new AbortController();
|
|
6212
6695
|
try {
|
|
6213
|
-
const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestWritten, params.onElicitation, params.onPermissionRequest);
|
|
6696
|
+
const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestWritten, params.onElicitation, params.onPermissionRequest, promptRequestAuthority(lifetime, params.authority));
|
|
6214
6697
|
promptPromise.then((response) => {
|
|
6215
6698
|
settledResponse = response;
|
|
6216
6699
|
}, () => {});
|
|
@@ -6227,14 +6710,16 @@ async function runPromptTurn(params) {
|
|
|
6227
6710
|
...responseMetaField(response._meta)
|
|
6228
6711
|
};
|
|
6229
6712
|
} catch (error) {
|
|
6230
|
-
abortTimedOutRequests(error,
|
|
6713
|
+
abortTimedOutRequests(error, lifetime);
|
|
6231
6714
|
if (!(error instanceof TimeoutError) || !params.promptMessageId) throw error;
|
|
6232
6715
|
await params.client.waitForSessionUpdatesIdle?.({
|
|
6233
6716
|
idleMs: SESSION_REPLY_IDLE_MS,
|
|
6234
6717
|
timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS
|
|
6235
6718
|
}).catch(() => {});
|
|
6236
|
-
if (
|
|
6719
|
+
if (settledResponse) return recoveredSessionResult(settledResponse, params.conversation, params.promptMessageId);
|
|
6237
6720
|
throw error;
|
|
6721
|
+
} finally {
|
|
6722
|
+
lifetime.abort();
|
|
6238
6723
|
}
|
|
6239
6724
|
}
|
|
6240
6725
|
//#endregion
|
|
@@ -6484,8 +6969,9 @@ function emitPromptDisconnectNotice(snapshot, verbose) {
|
|
|
6484
6969
|
if (!lastExit?.unexpectedDuringPrompt || !verbose) return;
|
|
6485
6970
|
process.stderr.write("[acpx] agent disconnected during prompt (" + lastExit.reason + ", exit=" + lastExit.exitCode + ", signal=" + (lastExit.signal ?? "none") + ")\n");
|
|
6486
6971
|
}
|
|
6487
|
-
async function preparePromptRetry(error, attempt, maxRetries, hasSideEffects, suppressSdkConsoleErrors) {
|
|
6972
|
+
async function preparePromptRetry(error, attempt, maxRetries, hasSideEffects, suppressSdkConsoleErrors, signal) {
|
|
6488
6973
|
if (attempt < maxRetries && !hasSideEffects() && isRetryablePromptError(error)) {
|
|
6974
|
+
signal?.throwIfAborted();
|
|
6489
6975
|
const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
6490
6976
|
emitPromptRetryNotice({
|
|
6491
6977
|
error,
|
|
@@ -6494,11 +6980,18 @@ async function preparePromptRetry(error, attempt, maxRetries, hasSideEffects, su
|
|
|
6494
6980
|
maxRetries,
|
|
6495
6981
|
suppressSdkConsoleErrors
|
|
6496
6982
|
});
|
|
6497
|
-
await
|
|
6983
|
+
await setTimeout$1(delayMs, void 0, { signal }).catch((error) => {
|
|
6984
|
+
signal?.throwIfAborted();
|
|
6985
|
+
throw error;
|
|
6986
|
+
});
|
|
6987
|
+
signal?.throwIfAborted();
|
|
6498
6988
|
return !hasSideEffects();
|
|
6499
6989
|
}
|
|
6500
6990
|
return false;
|
|
6501
6991
|
}
|
|
6992
|
+
function isTurnCancellation(error, signal) {
|
|
6993
|
+
return signal?.aborted === true && error === signal.reason;
|
|
6994
|
+
}
|
|
6502
6995
|
function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatter) {
|
|
6503
6996
|
return {
|
|
6504
6997
|
sessionRecordId,
|
|
@@ -6520,7 +7013,9 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
|
|
|
6520
7013
|
sessionOptions: mergeSessionOptions(task.sessionOptions, options.sessionOptions),
|
|
6521
7014
|
onClientAvailable: options.onClientAvailable,
|
|
6522
7015
|
onClientClosed: options.onClientClosed,
|
|
7016
|
+
onClientCloseFailure: options.onClientCloseFailure,
|
|
6523
7017
|
onPromptActive: options.onPromptActive,
|
|
7018
|
+
onPromptFinalizing: options.onPromptFinalizing,
|
|
6524
7019
|
onPromptRequestWritten: () => {
|
|
6525
7020
|
if (task.reportPromptStarted) task.send({
|
|
6526
7021
|
type: "prompt_started",
|
|
@@ -6826,24 +7321,23 @@ async function runOwnedSessionPrompt(options) {
|
|
|
6826
7321
|
});
|
|
6827
7322
|
let activeSessionIdForControl = record.acpSessionId;
|
|
6828
7323
|
let notifiedClientAvailable = false;
|
|
7324
|
+
let controlRetirement;
|
|
6829
7325
|
const activeController = {
|
|
6830
7326
|
hasActivePrompt: () => client.hasActivePrompt(),
|
|
6831
7327
|
requestCancelActivePrompt: async () => await client.requestCancelActivePrompt(),
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
return response;
|
|
6846
|
-
}
|
|
7328
|
+
...createOwnedSessionControls({
|
|
7329
|
+
client,
|
|
7330
|
+
record,
|
|
7331
|
+
sessionId: () => activeSessionIdForControl,
|
|
7332
|
+
checkpoint: () => liveCheckpoint.checkpoint(),
|
|
7333
|
+
retire: () => {
|
|
7334
|
+
controlRetirement ??= client.close().catch((error) => {
|
|
7335
|
+
options.onClientCloseFailure?.();
|
|
7336
|
+
throw error;
|
|
7337
|
+
});
|
|
7338
|
+
return controlRetirement;
|
|
7339
|
+
}
|
|
7340
|
+
})
|
|
6847
7341
|
};
|
|
6848
7342
|
const flushConnectOutput = (loadError) => {
|
|
6849
7343
|
bufferingConnectOutput = false;
|
|
@@ -6907,15 +7401,17 @@ async function runOwnedSessionPrompt(options) {
|
|
|
6907
7401
|
conversation,
|
|
6908
7402
|
promptMessageId,
|
|
6909
7403
|
onPromptRequestWritten: options.onPromptRequestWritten,
|
|
6910
|
-
onPromptStarted: buildPromptStartedHook(attempt)
|
|
7404
|
+
onPromptStarted: buildPromptStartedHook(attempt),
|
|
7405
|
+
authority: { signal: options.waitSignal }
|
|
6911
7406
|
});
|
|
6912
7407
|
});
|
|
6913
7408
|
emitPromptPerfMetric(promptStartedAt, options.verbose);
|
|
6914
7409
|
return response;
|
|
6915
7410
|
};
|
|
6916
7411
|
const handlePromptFailure = async (error, attempt) => {
|
|
7412
|
+
if (isTurnCancellation(error, options.waitSignal)) throw error;
|
|
6917
7413
|
const snapshot = client.getAgentLifecycleSnapshot();
|
|
6918
|
-
if (snapshot.lastExit?.unexpectedDuringPrompt !== true && await preparePromptRetry(error, attempt, options.promptRetries ?? 0, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors)) return;
|
|
7414
|
+
if (snapshot.lastExit?.unexpectedDuringPrompt !== true && await preparePromptRetry(error, attempt, options.promptRetries ?? 0, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors, options.waitSignal)) return;
|
|
6919
7415
|
await failRuntimePrompt(error, snapshot);
|
|
6920
7416
|
};
|
|
6921
7417
|
const failRuntimePrompt = async (error, snapshot) => {
|
|
@@ -6939,10 +7435,18 @@ async function runOwnedSessionPrompt(options) {
|
|
|
6939
7435
|
};
|
|
6940
7436
|
const runPromptWithRetries = async (sessionId) => {
|
|
6941
7437
|
promptTurnActive = true;
|
|
6942
|
-
|
|
6943
|
-
|
|
7438
|
+
try {
|
|
7439
|
+
for (let attempt = 0;; attempt++) {
|
|
7440
|
+
options.waitSignal?.throwIfAborted();
|
|
7441
|
+
try {
|
|
7442
|
+
return await runPromptAttempt(sessionId, attempt);
|
|
7443
|
+
} catch (error) {
|
|
7444
|
+
await handlePromptFailure(error, attempt);
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
6944
7447
|
} catch (error) {
|
|
6945
|
-
|
|
7448
|
+
if (isTurnCancellation(error, options.waitSignal)) return { stopReason: "cancelled" };
|
|
7449
|
+
throw error;
|
|
6946
7450
|
}
|
|
6947
7451
|
};
|
|
6948
7452
|
const savePromptSuccess = async (response) => {
|
|
@@ -7003,7 +7507,24 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7003
7507
|
}
|
|
7004
7508
|
};
|
|
7005
7509
|
const cleanupPrompt = async (attempt) => {
|
|
7510
|
+
const controlDrain = options.onPromptFinalizing?.();
|
|
7511
|
+
const unresolvedPrompt = client.hasUnresolvedPrompt();
|
|
7006
7512
|
const steps = [
|
|
7513
|
+
async () => {
|
|
7514
|
+
if (unresolvedPrompt) await withTimeout(client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS), INTERRUPT_CANCEL_WAIT_MS).catch(() => {});
|
|
7515
|
+
},
|
|
7516
|
+
async () => {
|
|
7517
|
+
const journalFailed = attempt.status === "rejected" && attempt.error instanceof AcpxOperationalError && attempt.error.detailCode === "SESSION_JOURNAL_WRITE_FAILED";
|
|
7518
|
+
if (unresolvedPrompt || closeClientOnExit || journalFailed) try {
|
|
7519
|
+
await client.close();
|
|
7520
|
+
} catch (error) {
|
|
7521
|
+
options.onClientCloseFailure?.();
|
|
7522
|
+
throw error;
|
|
7523
|
+
}
|
|
7524
|
+
},
|
|
7525
|
+
async () => {
|
|
7526
|
+
await controlDrain;
|
|
7527
|
+
},
|
|
7007
7528
|
() => {
|
|
7008
7529
|
const duration = stopTotalTimer();
|
|
7009
7530
|
if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.total", duration)}\n`);
|
|
@@ -7012,10 +7533,6 @@ async function runOwnedSessionPrompt(options) {
|
|
|
7012
7533
|
if (notifiedClientAvailable) options.onClientClosed?.();
|
|
7013
7534
|
},
|
|
7014
7535
|
() => client.clearEventHandlers(),
|
|
7015
|
-
async () => {
|
|
7016
|
-
const journalFailed = attempt.status === "rejected" && attempt.error instanceof AcpxOperationalError && attempt.error.detailCode === "SESSION_JOURNAL_WRITE_FAILED";
|
|
7017
|
-
if (closeClientOnExit || journalFailed) await client.close();
|
|
7018
|
-
},
|
|
7019
7536
|
() => applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot()),
|
|
7020
7537
|
() => applyConversation(record, conversation)
|
|
7021
7538
|
];
|
|
@@ -7042,6 +7559,7 @@ async function runOnce(options) {
|
|
|
7042
7559
|
const shouldMarkAcpErrorsEmitted = rendersAcpErrors(options.errorEmissionPolicy);
|
|
7043
7560
|
let promptTurnActive = false;
|
|
7044
7561
|
let promptTurnHadSideEffects = false;
|
|
7562
|
+
let controlState = {};
|
|
7045
7563
|
const acpErrors = new AcpErrorTracker();
|
|
7046
7564
|
const client = new AcpClient({
|
|
7047
7565
|
agentCommand: options.agentCommand,
|
|
@@ -7062,6 +7580,7 @@ async function runOnce(options) {
|
|
|
7062
7580
|
acpErrors.observe(output, direction, message);
|
|
7063
7581
|
},
|
|
7064
7582
|
onSessionUpdate: (notification) => {
|
|
7583
|
+
if (notification.update.sessionUpdate === "config_option_update") controlState = applyConfigOptionsToState(controlState, notification.update.configOptions);
|
|
7065
7584
|
if (promptTurnActive) promptTurnHadSideEffects = true;
|
|
7066
7585
|
options.onSessionUpdate?.(notification);
|
|
7067
7586
|
},
|
|
@@ -7101,7 +7620,9 @@ async function runOnce(options) {
|
|
|
7101
7620
|
return await withTimeout(client.createSession(absolutePath(options.cwd)), options.timeoutMs);
|
|
7102
7621
|
});
|
|
7103
7622
|
const sessionId = createdSession.sessionId;
|
|
7104
|
-
|
|
7623
|
+
if (createdSession.models) applyAdvertisedModelState(controlState, createdSession.models);
|
|
7624
|
+
controlState = applyConfigOptionsToState(controlState, createdSession.configOptions ?? []);
|
|
7625
|
+
const modelApplication = await applyRequestedModelIfAdvertised({
|
|
7105
7626
|
client,
|
|
7106
7627
|
sessionId,
|
|
7107
7628
|
requestedModel: options.sessionOptions?.model,
|
|
@@ -7110,7 +7631,11 @@ async function runOnce(options) {
|
|
|
7110
7631
|
timeoutMs: options.timeoutMs,
|
|
7111
7632
|
onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
|
|
7112
7633
|
});
|
|
7113
|
-
|
|
7634
|
+
if (modelApplication.response) controlState = applyConfigOptionsToState(controlState, modelApplication.response.configOptions);
|
|
7635
|
+
for (const configOption of options.configOptions ?? []) {
|
|
7636
|
+
const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, advertisedModelState(controlState)), options.timeoutMs);
|
|
7637
|
+
controlState = applyConfigOptionsToState(controlState, response.configOptions);
|
|
7638
|
+
}
|
|
7114
7639
|
output.setContext({ sessionId });
|
|
7115
7640
|
const response = await runExecPromptWithRetries(sessionId);
|
|
7116
7641
|
promptTurnActive = false;
|
|
@@ -7205,54 +7730,6 @@ function createQueueOwnerSharedClient(options, sessionRecord) {
|
|
|
7205
7730
|
sessionOptions: mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(sessionRecord))
|
|
7206
7731
|
});
|
|
7207
7732
|
}
|
|
7208
|
-
function createQueueOwnerTurnController(options) {
|
|
7209
|
-
return new QueueOwnerTurnController({
|
|
7210
|
-
withTimeout: async (run, timeoutMs) => await withTimeout(run(), timeoutMs),
|
|
7211
|
-
setSessionModeFallback: async (modeId, timeoutMs) => {
|
|
7212
|
-
await runSessionSetModeDirect({
|
|
7213
|
-
sessionRecordId: options.sessionId,
|
|
7214
|
-
modeId,
|
|
7215
|
-
mcpServers: options.mcpServers,
|
|
7216
|
-
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
7217
|
-
authCredentials: options.authCredentials,
|
|
7218
|
-
authPolicy: options.authPolicy,
|
|
7219
|
-
fs: options.fs,
|
|
7220
|
-
terminal: options.terminal,
|
|
7221
|
-
timeoutMs,
|
|
7222
|
-
verbose: options.verbose
|
|
7223
|
-
});
|
|
7224
|
-
},
|
|
7225
|
-
setSessionModelFallback: async (modelId, timeoutMs) => {
|
|
7226
|
-
return (await runSessionSetModelDirect({
|
|
7227
|
-
sessionRecordId: options.sessionId,
|
|
7228
|
-
modelId,
|
|
7229
|
-
mcpServers: options.mcpServers,
|
|
7230
|
-
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
7231
|
-
authCredentials: options.authCredentials,
|
|
7232
|
-
authPolicy: options.authPolicy,
|
|
7233
|
-
fs: options.fs,
|
|
7234
|
-
terminal: options.terminal,
|
|
7235
|
-
timeoutMs,
|
|
7236
|
-
verbose: options.verbose
|
|
7237
|
-
})).response;
|
|
7238
|
-
},
|
|
7239
|
-
setSessionConfigOptionFallback: async (configId, value, timeoutMs) => {
|
|
7240
|
-
return (await runSessionSetConfigOptionDirect({
|
|
7241
|
-
sessionRecordId: options.sessionId,
|
|
7242
|
-
configId,
|
|
7243
|
-
value,
|
|
7244
|
-
mcpServers: options.mcpServers,
|
|
7245
|
-
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
7246
|
-
authCredentials: options.authCredentials,
|
|
7247
|
-
authPolicy: options.authPolicy,
|
|
7248
|
-
fs: options.fs,
|
|
7249
|
-
terminal: options.terminal,
|
|
7250
|
-
timeoutMs,
|
|
7251
|
-
verbose: options.verbose
|
|
7252
|
-
})).response;
|
|
7253
|
-
}
|
|
7254
|
-
});
|
|
7255
|
-
}
|
|
7256
7733
|
function logDeferredCancelFailure(error, verbose) {
|
|
7257
7734
|
if (!verbose) return;
|
|
7258
7735
|
process.stderr.write(`[acpx] failed to apply deferred cancel: ${formatErrorMessage(error)}\n`);
|
|
@@ -7296,16 +7773,17 @@ function createQueueOwnerShutdownController(params) {
|
|
|
7296
7773
|
let shutdownPromise;
|
|
7297
7774
|
const drainActiveTurn = async () => {
|
|
7298
7775
|
const turn = activeTurn;
|
|
7299
|
-
|
|
7300
|
-
params.turnController.requestCancel().catch((error) => {
|
|
7776
|
+
const pending = Promise.allSettled([turn, params.controls.drainAdmitted()]);
|
|
7777
|
+
if (turn) params.turnController.requestCancel().catch((error) => {
|
|
7301
7778
|
logDeferredCancelFailure(error, params.verbose);
|
|
7302
7779
|
});
|
|
7303
|
-
if (!await settlesWithin(
|
|
7304
|
-
await
|
|
7780
|
+
if (!await settlesWithin(pending, QUEUE_OWNER_ACTIVE_TURN_CANCEL_GRACE_MS)) await params.sharedClient.close().catch(() => {});
|
|
7781
|
+
await pending;
|
|
7305
7782
|
};
|
|
7306
7783
|
const request = () => {
|
|
7307
7784
|
requested = true;
|
|
7308
7785
|
params.stopHeartbeat();
|
|
7786
|
+
params.controls.stopAdmission();
|
|
7309
7787
|
params.getOwner()?.beginShutdown();
|
|
7310
7788
|
activeTurnShutdown ??= drainActiveTurn();
|
|
7311
7789
|
};
|
|
@@ -7316,14 +7794,20 @@ function createQueueOwnerShutdownController(params) {
|
|
|
7316
7794
|
request,
|
|
7317
7795
|
setActiveTurn: (turn) => {
|
|
7318
7796
|
activeTurn = turn;
|
|
7797
|
+
if (requested) activeTurnShutdown = Promise.all([activeTurnShutdown, drainActiveTurn()]).then(() => {});
|
|
7319
7798
|
},
|
|
7320
7799
|
clearActiveTurn: (turn) => {
|
|
7321
7800
|
if (activeTurn === turn) activeTurn = void 0;
|
|
7322
7801
|
},
|
|
7802
|
+
drain: () => {
|
|
7803
|
+
request();
|
|
7804
|
+
return activeTurnShutdown;
|
|
7805
|
+
},
|
|
7323
7806
|
shutdown: () => {
|
|
7324
7807
|
request();
|
|
7325
7808
|
shutdownPromise ??= (async () => {
|
|
7326
7809
|
await activeTurnShutdown;
|
|
7810
|
+
await params.getBackendClose()?.catch(() => {});
|
|
7327
7811
|
await closeQueueOwnerRuntime({
|
|
7328
7812
|
owner: params.getOwner(),
|
|
7329
7813
|
turnController: params.turnController,
|
|
@@ -7342,20 +7826,50 @@ function applyRequestedShutdown(owner, shutdown) {
|
|
|
7342
7826
|
function startQueueOwnerHeartbeat(params) {
|
|
7343
7827
|
if (!params.enabled) return;
|
|
7344
7828
|
return setInterval(() => {
|
|
7345
|
-
|
|
7829
|
+
params.refresh(params.owner.queueDepth());
|
|
7346
7830
|
}, QUEUE_OWNER_HEARTBEAT_INTERVAL_MS);
|
|
7347
7831
|
}
|
|
7832
|
+
function createLeaseHeartbeat(lease, shutdown) {
|
|
7833
|
+
let guardFailure;
|
|
7834
|
+
return {
|
|
7835
|
+
refresh: async (queueDepth) => {
|
|
7836
|
+
if (shutdown.requested) return;
|
|
7837
|
+
await refreshQueueOwnerLease(lease, { queueDepth }).catch((error) => {
|
|
7838
|
+
if (error instanceof QueueLeaseGuardSettlementError) {
|
|
7839
|
+
guardFailure ??= error;
|
|
7840
|
+
shutdown.request();
|
|
7841
|
+
}
|
|
7842
|
+
});
|
|
7843
|
+
},
|
|
7844
|
+
throwIfFailed: () => {
|
|
7845
|
+
if (guardFailure) throw guardFailure;
|
|
7846
|
+
}
|
|
7847
|
+
};
|
|
7848
|
+
}
|
|
7348
7849
|
async function runSessionQueueOwner(options) {
|
|
7349
7850
|
const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
|
|
7350
7851
|
path: options.mcpConfigPath,
|
|
7351
7852
|
fingerprint: options.mcpConfigFingerprint
|
|
7352
7853
|
});
|
|
7353
7854
|
if (!lease) return;
|
|
7855
|
+
let runtimeFailure;
|
|
7354
7856
|
try {
|
|
7355
7857
|
await runQueueOwnerRuntime(options, lease);
|
|
7356
|
-
}
|
|
7858
|
+
} catch (error) {
|
|
7859
|
+
runtimeFailure = { error };
|
|
7860
|
+
}
|
|
7861
|
+
try {
|
|
7357
7862
|
await releaseQueueOwnerLease(lease);
|
|
7863
|
+
} catch (error) {
|
|
7864
|
+
if (runtimeFailure) throw new AggregateError([runtimeFailure.error, error], "Queue owner shutdown failed", { cause: error });
|
|
7865
|
+
throw error;
|
|
7358
7866
|
}
|
|
7867
|
+
if (runtimeFailure) throw runtimeFailure.error;
|
|
7868
|
+
}
|
|
7869
|
+
async function keepOwnerForPendingControls(controls, shutdown) {
|
|
7870
|
+
if (shutdown.requested || !controls.hasPending) return false;
|
|
7871
|
+
await controls.drainAdmitted();
|
|
7872
|
+
return !shutdown.requested;
|
|
7359
7873
|
}
|
|
7360
7874
|
async function runQueueOwnerRuntime(options, lease) {
|
|
7361
7875
|
const sessionRecord = await resolveSessionRecord(options.sessionId);
|
|
@@ -7366,7 +7880,16 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7366
7880
|
const maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth ?? 16));
|
|
7367
7881
|
const taskPollTimeoutMs = ttlMs === 0 ? void 0 : ttlMs;
|
|
7368
7882
|
const initialTaskPollTimeoutMs = taskPollTimeoutMs == null ? void 0 : Math.max(taskPollTimeoutMs, 1e3);
|
|
7369
|
-
const turnController =
|
|
7883
|
+
const turnController = new QueueOwnerTurnController();
|
|
7884
|
+
let activeControls;
|
|
7885
|
+
let backendCloseRequest;
|
|
7886
|
+
const controls = new QueueOwnerControlAdmission({ runIdle: async (invoke, controlOptions) => await runIdleOwnerControl({
|
|
7887
|
+
...controlOptions,
|
|
7888
|
+
client: sharedClient,
|
|
7889
|
+
sessionId: options.sessionId,
|
|
7890
|
+
verbose: options.verbose,
|
|
7891
|
+
onRetirementFailure: () => shutdown.request()
|
|
7892
|
+
}, invoke) });
|
|
7370
7893
|
const applyPendingCancel = async () => {
|
|
7371
7894
|
return await turnController.applyPendingCancel();
|
|
7372
7895
|
};
|
|
@@ -7376,23 +7899,34 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7376
7899
|
});
|
|
7377
7900
|
};
|
|
7378
7901
|
const setActiveController = (controller) => {
|
|
7902
|
+
activeControls = controller;
|
|
7379
7903
|
turnController.setActiveController(controller);
|
|
7380
7904
|
scheduleApplyPendingCancel();
|
|
7381
7905
|
};
|
|
7382
7906
|
const clearActiveController = () => {
|
|
7907
|
+
activeControls = void 0;
|
|
7383
7908
|
turnController.clearActiveController();
|
|
7384
7909
|
};
|
|
7385
|
-
const closeActiveBackendSession =
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7910
|
+
const closeActiveBackendSession = (timeoutMs) => {
|
|
7911
|
+
backendCloseRequest ??= Promise.resolve().then(async () => {
|
|
7912
|
+
await shutdown.drain();
|
|
7913
|
+
const latestRecord = await resolveSessionRecord(options.sessionId);
|
|
7914
|
+
if (!sharedClient.supportsCloseSession()) return false;
|
|
7915
|
+
await withTimeout(sharedClient.closeSession(latestRecord.acpSessionId), timeoutMs);
|
|
7916
|
+
return true;
|
|
7917
|
+
});
|
|
7918
|
+
return backendCloseRequest;
|
|
7390
7919
|
};
|
|
7391
7920
|
const runPromptTurn = async (run) => {
|
|
7392
7921
|
const waitSignal = turnController.beginTurn();
|
|
7922
|
+
const { ticket, priorIdle } = controls.beginPrompt();
|
|
7923
|
+
if (shutdown.requested) turnController.requestCancel().catch(() => {});
|
|
7393
7924
|
try {
|
|
7394
|
-
|
|
7925
|
+
await priorIdle;
|
|
7926
|
+
return await run(waitSignal, ticket);
|
|
7395
7927
|
} finally {
|
|
7928
|
+
await controls.seal(ticket);
|
|
7929
|
+
controls.release(ticket);
|
|
7396
7930
|
turnController.endTurn();
|
|
7397
7931
|
}
|
|
7398
7932
|
};
|
|
@@ -7405,6 +7939,8 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7405
7939
|
}
|
|
7406
7940
|
},
|
|
7407
7941
|
turnController,
|
|
7942
|
+
controls,
|
|
7943
|
+
getBackendClose: () => backendCloseRequest,
|
|
7408
7944
|
sharedClient,
|
|
7409
7945
|
sessionId: options.sessionId,
|
|
7410
7946
|
verbose: options.verbose
|
|
@@ -7412,6 +7948,7 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7412
7948
|
const onSignal = () => {
|
|
7413
7949
|
shutdown.request();
|
|
7414
7950
|
};
|
|
7951
|
+
const heartbeat = createLeaseHeartbeat(lease, shutdown);
|
|
7415
7952
|
process.once("SIGINT", onSignal);
|
|
7416
7953
|
process.once("SIGTERM", onSignal);
|
|
7417
7954
|
process.once("SIGHUP", onSignal);
|
|
@@ -7424,17 +7961,17 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7424
7961
|
},
|
|
7425
7962
|
closeSession: async (timeoutMs) => await closeActiveBackendSession(timeoutMs),
|
|
7426
7963
|
setSessionMode: async (modeId, timeoutMs) => {
|
|
7427
|
-
await
|
|
7964
|
+
await controls.run((context, deadline) => context.setSessionMode(modeId, deadline), timeoutMs);
|
|
7428
7965
|
},
|
|
7429
|
-
setSessionModel: async (modelId, timeoutMs) => await
|
|
7966
|
+
setSessionModel: async (modelId, timeoutMs) => await controls.run((context, deadline) => context.setSessionModel(modelId, deadline), timeoutMs, { key: "model" }),
|
|
7430
7967
|
setSessionConfigOption: async (configId, value, timeoutMs) => {
|
|
7431
|
-
return await
|
|
7968
|
+
return await controls.run((context, deadline) => context.setSessionConfigOption(configId, value, deadline), timeoutMs, { key: configId });
|
|
7432
7969
|
}
|
|
7433
7970
|
}, {
|
|
7434
7971
|
maxQueueDepth,
|
|
7435
7972
|
onQueueDepthChanged: (queueDepth) => {
|
|
7436
7973
|
setPerfGauge("queue.owner.depth", queueDepth);
|
|
7437
|
-
|
|
7974
|
+
heartbeat.refresh(queueDepth);
|
|
7438
7975
|
}
|
|
7439
7976
|
});
|
|
7440
7977
|
applyRequestedShutdown(owner, shutdown);
|
|
@@ -7444,19 +7981,22 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7444
7981
|
maxQueueDepth,
|
|
7445
7982
|
verbose: options.verbose
|
|
7446
7983
|
});
|
|
7447
|
-
await
|
|
7984
|
+
await heartbeat.refresh(owner.queueDepth());
|
|
7448
7985
|
heartbeatTimer = startQueueOwnerHeartbeat({
|
|
7449
7986
|
enabled: !shutdown.requested,
|
|
7450
|
-
|
|
7987
|
+
refresh: heartbeat.refresh,
|
|
7451
7988
|
owner
|
|
7452
7989
|
});
|
|
7453
7990
|
let isFirstTask = true;
|
|
7454
7991
|
while (true) {
|
|
7455
7992
|
const pollTimeoutMs = isFirstTask ? initialTaskPollTimeoutMs : taskPollTimeoutMs;
|
|
7456
7993
|
const task = await owner.nextTask(pollTimeoutMs);
|
|
7457
|
-
if (!task)
|
|
7994
|
+
if (!task) {
|
|
7995
|
+
if (await keepOwnerForPendingControls(controls, shutdown)) continue;
|
|
7996
|
+
break;
|
|
7997
|
+
}
|
|
7458
7998
|
isFirstTask = false;
|
|
7459
|
-
const turnPromise = runPromptTurn(async (waitSignal) => {
|
|
7999
|
+
const turnPromise = runPromptTurn(async (waitSignal, ticket) => {
|
|
7460
8000
|
try {
|
|
7461
8001
|
await runQueuedTask(options.sessionId, task, {
|
|
7462
8002
|
sharedClient,
|
|
@@ -7470,7 +8010,10 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7470
8010
|
sessionOptions: options.sessionOptions,
|
|
7471
8011
|
onClientAvailable: setActiveController,
|
|
7472
8012
|
onClientClosed: clearActiveController,
|
|
8013
|
+
onClientCloseFailure: shutdown.request,
|
|
8014
|
+
onPromptFinalizing: () => controls.seal(ticket),
|
|
7473
8015
|
onPromptActive: async () => {
|
|
8016
|
+
if (activeControls) controls.publish(ticket, activeControls);
|
|
7474
8017
|
turnController.markPromptActive();
|
|
7475
8018
|
await applyPendingCancel();
|
|
7476
8019
|
},
|
|
@@ -7494,7 +8037,9 @@ async function runQueueOwnerRuntime(options, lease) {
|
|
|
7494
8037
|
process.off("SIGTERM", onSignal);
|
|
7495
8038
|
process.off("SIGHUP", onSignal);
|
|
7496
8039
|
await shutdown.shutdown();
|
|
8040
|
+
await lease.updates;
|
|
7497
8041
|
}
|
|
8042
|
+
heartbeat.throwIfFailed();
|
|
7498
8043
|
}
|
|
7499
8044
|
async function sendSession(options) {
|
|
7500
8045
|
options.signal?.throwIfAborted();
|
|
@@ -7534,6 +8079,6 @@ async function sendSession(options) {
|
|
|
7534
8079
|
}
|
|
7535
8080
|
const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
|
|
7536
8081
|
//#endregion
|
|
7537
|
-
export {
|
|
8082
|
+
export { getAcpxVersion as $, applyInitialModelSelection as A, cloneSessionAcpxState as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, sessionOptionsFromRecord as F, recordSessionUpdate as G, createSessionConversation as H, applyConversation as I, AcpClient as J, trimConversationForRuntime as K, applyLifecycleSnapshotToRecord as L, applyRequestedModelIfAdvertised as M, setDesiredModeId as N, applyConfigOptionSelection as O, persistSessionOptions as P, isRequestedModelUnsupportedError as Q, createInitialSessionRecord as R, setSessionMode as S, setSessionModelOnOwner as T, recordClientOperation as U, cloneSessionConversation as V, recordPromptSubmission as W, REQUESTED_MODEL_UNSUPPORTED_REASONS as X, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as Y, RequestedModelUnsupportedError as Z, cancelSessionPrompt as _, runQueuedTask as a, InterruptedError as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, withInterrupt as ct, flushPerfMetricsCapture as d, normalizeOutputError as dt, permissionModeSatisfies as et, installPerfMetricsCapture as f, listAgentSessions as g, ensureSession as h, runOnce as i, isLegacyZedCodexAcpInvocation as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, withTimeout as lt, createSessionWithClient as m, runSessionQueueOwner as n, normalizeQueueOwnerTtlMs as nt, sendSessionDirect as o, TimeoutError as ot, createSession as p, advertisedModelState as q, sendSession as r, listSessionEvents as rt, DISCARD_OUTPUT_FORMATTER as s, assertControlAuthority as st, queueOwnerRuntimeTestInternals as t, DEFAULT_QUEUE_OWNER_TTL_MS as tt, buildQueueOwnerArgOverride as u, exitCodeForOutputErrorCode as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, reconcileAgentSessionId as z };
|
|
7538
8083
|
|
|
7539
|
-
//# sourceMappingURL=queue-owner-runtime
|
|
8084
|
+
//# sourceMappingURL=queue-owner-runtime-Cr0dWJuL.js.map
|