acpx 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/{acp-jsonrpc-CGT_1Mel.js → acp-jsonrpc-BNHXq7qK.js} +2 -2
- package/dist/{acp-jsonrpc-CGT_1Mel.js.map → acp-jsonrpc-BNHXq7qK.js.map} +1 -1
- package/dist/cli.js +43 -35
- package/dist/cli.js.map +1 -1
- package/dist/{output-BgMdEq3x.js → output-BmkPP7qE.js} +3 -6
- package/dist/{output-BgMdEq3x.js.map → output-BmkPP7qE.js.map} +1 -1
- package/dist/{output-render-Cvz0eKSb.js → output-render-DEAaMxg8.js} +5 -6
- package/dist/{output-render-Cvz0eKSb.js.map → output-render-DEAaMxg8.js.map} +1 -1
- package/dist/{queue-ipc-C8StWiZt.js → queue-ipc-EQLpBMKv.js} +133 -98
- package/dist/queue-ipc-EQLpBMKv.js.map +1 -0
- package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/{runtime-session-id-B03l5p1Q.js → runtime-session-id-C544sPPL.js} +2 -3
- package/dist/{runtime-session-id-B03l5p1Q.js.map → runtime-session-id-C544sPPL.js.map} +1 -1
- package/dist/{session-C6nyqSfk.js → session-C2Q8ktsN.js} +244 -80
- package/dist/session-C2Q8ktsN.js.map +1 -0
- package/package.json +4 -3
- package/skills/acpx/SKILL.md +7 -4
- package/dist/queue-ipc-C8StWiZt.js.map +0 -1
- package/dist/rolldown-runtime-CjeV3_4I.js +0 -18
- package/dist/session-C6nyqSfk.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { n as normalizeRuntimeSessionId, t as extractRuntimeSessionId } from "./runtime-session-id-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
|
|
2
|
+
import { B as CopilotAcpUnsupportedError, E as normalizeOutputError, F as promptToDisplayText, G as SessionModeReplayError, H as PermissionDeniedError, I as textPrompt, J as extractAcpError, K as SessionNotFoundError, L as AgentSpawnError, R as AuthPolicyError, S as startPerfTimer, T as isAcpQueryClosedBeforeResponseError, U as PermissionPromptUnavailableError, V as GeminiAcpStartupTimeoutError, W as QueueConnectionError, Y as isAcpResourceNotFoundError, _ as getPerfMetricsSnapshot, a as trySetConfigOptionOnRunningOwner, b as resetPerfMetrics, c as SessionQueueOwner, d as releaseQueueOwnerLease, f as terminateProcess, g as formatPerfMetric, h as waitMs$1, i as tryCancelOnRunningOwner, j as SESSION_RECORD_SCHEMA, l as isProcessAlive, m as tryAcquireQueueOwnerLease, o as trySetModeOnRunningOwner, p as terminateQueueOwnerForSession, q as SessionResolutionError, s as trySubmitToRunningOwner, u as refreshQueueOwnerLease, v as incrementPerfCounter, w as formatErrorMessage, x as setPerfGauge, y as measurePerf, z as ClaudeAcpSessionCreateTimeoutError } from "./queue-ipc-EQLpBMKv.js";
|
|
3
|
+
import { n as isSessionUpdateNotification, t as isAcpJsonRpcMessage } from "./acp-jsonrpc-BNHXq7qK.js";
|
|
4
|
+
import { n as normalizeRuntimeSessionId, t as extractRuntimeSessionId } from "./runtime-session-id-C544sPPL.js";
|
|
5
5
|
import fs, { realpathSync, statSync } from "node:fs";
|
|
6
6
|
import fs$1 from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
@@ -11,7 +11,6 @@ import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream } from "@agentclie
|
|
|
11
11
|
import readline from "node:readline/promises";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import os from "node:os";
|
|
14
|
-
|
|
15
14
|
//#region src/permission-prompt.ts
|
|
16
15
|
async function promptForPermission(options) {
|
|
17
16
|
if (!process.stdin.isTTY || !process.stderr.isTTY) return false;
|
|
@@ -28,7 +27,6 @@ async function promptForPermission(options) {
|
|
|
28
27
|
rl.close();
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
|
-
|
|
32
30
|
//#endregion
|
|
33
31
|
//#region src/filesystem.ts
|
|
34
32
|
const WRITE_PREVIEW_MAX_LINES = 16;
|
|
@@ -176,7 +174,6 @@ var FileSystemHandlers = class {
|
|
|
176
174
|
this.onOperation?.(operation);
|
|
177
175
|
}
|
|
178
176
|
};
|
|
179
|
-
|
|
180
177
|
//#endregion
|
|
181
178
|
//#region src/permissions.ts
|
|
182
179
|
function selected(optionId) {
|
|
@@ -251,7 +248,6 @@ function classifyPermissionDecision(params, response) {
|
|
|
251
248
|
if (selectedOption.kind === "allow_once" || selectedOption.kind === "allow_always") return "approved";
|
|
252
249
|
return "denied";
|
|
253
250
|
}
|
|
254
|
-
|
|
255
251
|
//#endregion
|
|
256
252
|
//#region src/session-runtime-helpers.ts
|
|
257
253
|
var TimeoutError = class extends Error {
|
|
@@ -305,7 +301,6 @@ async function withInterrupt(run, onInterrupt) {
|
|
|
305
301
|
run().then((result) => finish(() => resolve(result)), (error) => finish(() => reject(error)));
|
|
306
302
|
});
|
|
307
303
|
}
|
|
308
|
-
|
|
309
304
|
//#endregion
|
|
310
305
|
//#region src/terminal.ts
|
|
311
306
|
const DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES = 64 * 1024;
|
|
@@ -602,7 +597,6 @@ var TerminalManager = class {
|
|
|
602
597
|
await Promise.race([terminal.exitPromise.then(() => void 0), waitMs(this.killGraceMs)]);
|
|
603
598
|
}
|
|
604
599
|
};
|
|
605
|
-
|
|
606
600
|
//#endregion
|
|
607
601
|
//#region src/client.ts
|
|
608
602
|
const REPLAY_IDLE_MS = 80;
|
|
@@ -614,7 +608,13 @@ const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
|
|
|
614
608
|
const GEMINI_ACP_STARTUP_TIMEOUT_MS = 15e3;
|
|
615
609
|
const CLAUDE_ACP_SESSION_CREATE_TIMEOUT_MS = 6e4;
|
|
616
610
|
const GEMINI_VERSION_TIMEOUT_MS = 2e3;
|
|
611
|
+
const GEMINI_ACP_FLAG_VERSION = [
|
|
612
|
+
0,
|
|
613
|
+
33,
|
|
614
|
+
0
|
|
615
|
+
];
|
|
617
616
|
const COPILOT_HELP_TIMEOUT_MS = 2e3;
|
|
617
|
+
const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
|
|
618
618
|
function shouldSuppressSdkConsoleError(args) {
|
|
619
619
|
if (args.length === 0) return false;
|
|
620
620
|
return typeof args[0] === "string" && args[0] === "Error handling request";
|
|
@@ -724,7 +724,7 @@ function basenameToken(value) {
|
|
|
724
724
|
return path.basename(value).toLowerCase().replace(/\.(cmd|exe|bat)$/u, "");
|
|
725
725
|
}
|
|
726
726
|
function isGeminiAcpCommand(command, args) {
|
|
727
|
-
return basenameToken(command) === "gemini" && args.includes("--experimental-acp");
|
|
727
|
+
return basenameToken(command) === "gemini" && (args.includes("--acp") || args.includes("--experimental-acp"));
|
|
728
728
|
}
|
|
729
729
|
function isClaudeAcpCommand(command, args) {
|
|
730
730
|
if (basenameToken(command) === "claude-agent-acp") return true;
|
|
@@ -733,6 +733,38 @@ function isClaudeAcpCommand(command, args) {
|
|
|
733
733
|
function isCopilotAcpCommand(command, args) {
|
|
734
734
|
return basenameToken(command) === "copilot" && args.includes("--acp");
|
|
735
735
|
}
|
|
736
|
+
function readWindowsEnvValue(env, key) {
|
|
737
|
+
const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
|
|
738
|
+
return matchedKey ? env[matchedKey] : void 0;
|
|
739
|
+
}
|
|
740
|
+
function resolveWindowsCommand(command, env = process.env) {
|
|
741
|
+
const extensions = (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
742
|
+
const candidates = path.extname(command).length > 0 ? [command] : extensions.map((extension) => `${command}${extension}`);
|
|
743
|
+
if (command.includes("/") || command.includes("\\") || path.isAbsolute(command)) return candidates.find((candidate) => fs.existsSync(candidate));
|
|
744
|
+
const pathValue = readWindowsEnvValue(env, "PATH");
|
|
745
|
+
if (!pathValue) return;
|
|
746
|
+
for (const directory of pathValue.split(";")) {
|
|
747
|
+
const trimmedDirectory = directory.trim();
|
|
748
|
+
if (trimmedDirectory.length === 0) continue;
|
|
749
|
+
for (const candidate of candidates) {
|
|
750
|
+
const resolved = path.join(trimmedDirectory, candidate);
|
|
751
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
|
|
756
|
+
if (platform !== "win32") return false;
|
|
757
|
+
const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
|
|
758
|
+
const ext = path.extname(resolvedCommand).toLowerCase();
|
|
759
|
+
return ext === ".cmd" || ext === ".bat";
|
|
760
|
+
}
|
|
761
|
+
function buildSpawnCommandOptions(command, options, platform = process.platform, env = process.env) {
|
|
762
|
+
if (!shouldUseWindowsBatchShell(command, platform, env)) return options;
|
|
763
|
+
return {
|
|
764
|
+
...options,
|
|
765
|
+
shell: true
|
|
766
|
+
};
|
|
767
|
+
}
|
|
736
768
|
function resolveGeminiAcpStartupTimeoutMs() {
|
|
737
769
|
const raw = process.env.ACPX_GEMINI_ACP_STARTUP_TIMEOUT_MS;
|
|
738
770
|
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
@@ -749,16 +781,38 @@ function resolveClaudeAcpSessionCreateTimeoutMs() {
|
|
|
749
781
|
}
|
|
750
782
|
return CLAUDE_ACP_SESSION_CREATE_TIMEOUT_MS;
|
|
751
783
|
}
|
|
784
|
+
function parseGeminiVersion(value) {
|
|
785
|
+
if (typeof value !== "string") return;
|
|
786
|
+
const normalized = value.trim();
|
|
787
|
+
const match = normalized.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
788
|
+
if (!match) return;
|
|
789
|
+
return {
|
|
790
|
+
raw: normalized,
|
|
791
|
+
parts: [
|
|
792
|
+
Number(match[1]),
|
|
793
|
+
Number(match[2]),
|
|
794
|
+
Number(match[3])
|
|
795
|
+
]
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function compareVersionParts(left, right) {
|
|
799
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
800
|
+
const leftPart = left[index] ?? 0;
|
|
801
|
+
const rightPart = right[index] ?? 0;
|
|
802
|
+
if (leftPart !== rightPart) return leftPart - rightPart;
|
|
803
|
+
}
|
|
804
|
+
return 0;
|
|
805
|
+
}
|
|
752
806
|
async function detectGeminiVersion(command) {
|
|
753
807
|
return await new Promise((resolve) => {
|
|
754
|
-
const child = spawn(command, ["--version"], {
|
|
808
|
+
const child = spawn(command, ["--version"], buildSpawnCommandOptions(command, {
|
|
755
809
|
stdio: [
|
|
756
810
|
"ignore",
|
|
757
811
|
"pipe",
|
|
758
812
|
"pipe"
|
|
759
813
|
],
|
|
760
814
|
windowsHide: true
|
|
761
|
-
});
|
|
815
|
+
}));
|
|
762
816
|
let stdout = "";
|
|
763
817
|
let stderr = "";
|
|
764
818
|
let settled = false;
|
|
@@ -787,20 +841,26 @@ async function detectGeminiVersion(command) {
|
|
|
787
841
|
finish(void 0);
|
|
788
842
|
});
|
|
789
843
|
child.once("close", () => {
|
|
790
|
-
finish(`${stdout}\n${stderr}`.split(/\r?\n/).map((line) => line.trim()).find((line) =>
|
|
844
|
+
finish(parseGeminiVersion(`${stdout}\n${stderr}`.split(/\r?\n/).map((line) => line.trim()).find((line) => /\d+\.\d+\.\d+/.test(line))));
|
|
791
845
|
});
|
|
792
846
|
});
|
|
793
847
|
}
|
|
848
|
+
async function resolveGeminiCommandArgs(command, args) {
|
|
849
|
+
if (basenameToken(command) !== "gemini" || !args.includes("--acp")) return [...args];
|
|
850
|
+
const version = await detectGeminiVersion(command);
|
|
851
|
+
if (version && compareVersionParts(version.parts, GEMINI_ACP_FLAG_VERSION) < 0) return args.map((arg) => arg === "--acp" ? "--experimental-acp" : arg);
|
|
852
|
+
return [...args];
|
|
853
|
+
}
|
|
794
854
|
async function readCommandOutput(command, args, timeoutMs) {
|
|
795
855
|
return await new Promise((resolve) => {
|
|
796
|
-
const child = spawn(command, [...args], {
|
|
856
|
+
const child = spawn(command, [...args], buildSpawnCommandOptions(command, {
|
|
797
857
|
stdio: [
|
|
798
858
|
"ignore",
|
|
799
859
|
"pipe",
|
|
800
860
|
"pipe"
|
|
801
861
|
],
|
|
802
862
|
windowsHide: true
|
|
803
|
-
});
|
|
863
|
+
}));
|
|
804
864
|
let stdout = "";
|
|
805
865
|
let stderr = "";
|
|
806
866
|
let settled = false;
|
|
@@ -836,7 +896,7 @@ async function readCommandOutput(command, args, timeoutMs) {
|
|
|
836
896
|
async function buildGeminiAcpStartupTimeoutMessage(command) {
|
|
837
897
|
const parts = ["Gemini CLI ACP startup timed out before initialize completed.", "This usually means the local Gemini CLI is waiting on interactive OAuth or has incompatible ACP subprocess behavior."];
|
|
838
898
|
const version = await detectGeminiVersion(command);
|
|
839
|
-
if (version) parts.push(`Detected Gemini CLI version: ${version}.`);
|
|
899
|
+
if (version) parts.push(`Detected Gemini CLI version: ${version.raw}.`);
|
|
840
900
|
if (!process.env.GEMINI_API_KEY && !process.env.GOOGLE_API_KEY) parts.push("No GEMINI_API_KEY or GOOGLE_API_KEY was set for non-interactive auth.");
|
|
841
901
|
parts.push("Try upgrading Gemini CLI and using API-key-based auth for non-interactive ACP runs.");
|
|
842
902
|
return parts.join(" ");
|
|
@@ -886,6 +946,30 @@ function buildClaudeCodeOptionsMeta(options) {
|
|
|
886
946
|
if (Object.keys(claudeCodeOptions).length === 0) return;
|
|
887
947
|
return { claudeCode: { options: claudeCodeOptions } };
|
|
888
948
|
}
|
|
949
|
+
function asRecord$3(value) {
|
|
950
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
951
|
+
return value;
|
|
952
|
+
}
|
|
953
|
+
function isLikelySessionControlUnsupportedError(acp) {
|
|
954
|
+
if (SESSION_CONTROL_UNSUPPORTED_ACP_CODES.has(acp.code)) return true;
|
|
955
|
+
if (acp.code !== -32603) return false;
|
|
956
|
+
const details = asRecord$3(acp.data)?.details;
|
|
957
|
+
return typeof details === "string" && details.toLowerCase().includes("invalid params");
|
|
958
|
+
}
|
|
959
|
+
function formatSessionControlAcpSummary(acp) {
|
|
960
|
+
const details = asRecord$3(acp.data)?.details;
|
|
961
|
+
if (typeof details === "string" && details.trim().length > 0) return `${details.trim()} (ACP ${acp.code}, adapter reported "${acp.message}")`;
|
|
962
|
+
return `${acp.message} (ACP ${acp.code})`;
|
|
963
|
+
}
|
|
964
|
+
function maybeWrapSessionControlError(method, error, context) {
|
|
965
|
+
const acp = extractAcpError(error);
|
|
966
|
+
if (!acp || !isLikelySessionControlUnsupportedError(acp)) return error;
|
|
967
|
+
const acpSummary = formatSessionControlAcpSummary(acp);
|
|
968
|
+
const message = `Agent rejected ${method}${context ? ` ${context}` : ""}: ${acpSummary}. The adapter may not implement ${method}, or the requested value is not supported.`;
|
|
969
|
+
const wrapped = new Error(message, { cause: error instanceof Error ? error : void 0 });
|
|
970
|
+
wrapped.acp = acp;
|
|
971
|
+
return wrapped;
|
|
972
|
+
}
|
|
889
973
|
function buildAgentEnvironment(authCredentials) {
|
|
890
974
|
const env = { ...process.env };
|
|
891
975
|
if (!authCredentials) return env;
|
|
@@ -1018,11 +1102,12 @@ var AcpClient = class {
|
|
|
1018
1102
|
async start() {
|
|
1019
1103
|
if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
|
|
1020
1104
|
if (this.connection || this.agent) await this.close();
|
|
1021
|
-
const { command, args } = splitCommandLine(this.options.agentCommand);
|
|
1105
|
+
const { command, args: initialArgs } = splitCommandLine(this.options.agentCommand);
|
|
1106
|
+
const args = await resolveGeminiCommandArgs(command, initialArgs);
|
|
1022
1107
|
this.log(`spawning agent: ${command} ${args.join(" ")}`);
|
|
1023
1108
|
const geminiAcp = isGeminiAcpCommand(command, args);
|
|
1024
1109
|
if (isCopilotAcpCommand(command, args)) await ensureCopilotAcpSupport(command);
|
|
1025
|
-
const spawnedChild = spawn(command, args, buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials));
|
|
1110
|
+
const spawnedChild = spawn(command, args, buildSpawnCommandOptions(command, buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials)));
|
|
1026
1111
|
try {
|
|
1027
1112
|
await waitForSpawn(spawnedChild);
|
|
1028
1113
|
} catch (error) {
|
|
@@ -1223,17 +1308,27 @@ var AcpClient = class {
|
|
|
1223
1308
|
}
|
|
1224
1309
|
}
|
|
1225
1310
|
async setSessionMode(sessionId, modeId) {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1311
|
+
const connection = this.getConnection();
|
|
1312
|
+
try {
|
|
1313
|
+
await connection.setSessionMode({
|
|
1314
|
+
sessionId,
|
|
1315
|
+
modeId
|
|
1316
|
+
});
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
throw maybeWrapSessionControlError("session/set_mode", error, `for mode "${modeId}"`);
|
|
1319
|
+
}
|
|
1230
1320
|
}
|
|
1231
1321
|
async setSessionConfigOption(sessionId, configId, value) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1322
|
+
const connection = this.getConnection();
|
|
1323
|
+
try {
|
|
1324
|
+
return await connection.setSessionConfigOption({
|
|
1325
|
+
sessionId,
|
|
1326
|
+
configId,
|
|
1327
|
+
value
|
|
1328
|
+
});
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
throw maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`);
|
|
1331
|
+
}
|
|
1237
1332
|
}
|
|
1238
1333
|
async cancel(sessionId) {
|
|
1239
1334
|
const connection = this.getConnection();
|
|
@@ -1359,17 +1454,13 @@ var AcpClient = class {
|
|
|
1359
1454
|
} catch (error) {
|
|
1360
1455
|
if (error instanceof PermissionPromptUnavailableError) {
|
|
1361
1456
|
this.notePromptPermissionFailure(params.sessionId, error);
|
|
1362
|
-
this.
|
|
1363
|
-
this.permissionStats.cancelled += 1;
|
|
1457
|
+
this.recordPermissionDecision("cancelled");
|
|
1364
1458
|
return { outcome: { outcome: "cancelled" } };
|
|
1365
1459
|
}
|
|
1366
1460
|
throw error;
|
|
1367
1461
|
}
|
|
1368
1462
|
const decision = classifyPermissionDecision(params, response);
|
|
1369
|
-
this.
|
|
1370
|
-
if (decision === "approved") this.permissionStats.approved += 1;
|
|
1371
|
-
else if (decision === "denied") this.permissionStats.denied += 1;
|
|
1372
|
-
else this.permissionStats.cancelled += 1;
|
|
1463
|
+
this.recordPermissionDecision(decision);
|
|
1373
1464
|
return response;
|
|
1374
1465
|
}
|
|
1375
1466
|
attachAgentLifecycleObservers(child) {
|
|
@@ -1402,13 +1493,18 @@ var AcpClient = class {
|
|
|
1402
1493
|
return error;
|
|
1403
1494
|
}
|
|
1404
1495
|
async handleReadTextFile(params) {
|
|
1405
|
-
|
|
1496
|
+
try {
|
|
1497
|
+
return await this.filesystem.readTextFile(params);
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
this.recordPermissionError(params.sessionId, error);
|
|
1500
|
+
throw error;
|
|
1501
|
+
}
|
|
1406
1502
|
}
|
|
1407
1503
|
async handleWriteTextFile(params) {
|
|
1408
1504
|
try {
|
|
1409
1505
|
return await this.filesystem.writeTextFile(params);
|
|
1410
1506
|
} catch (error) {
|
|
1411
|
-
|
|
1507
|
+
this.recordPermissionError(params.sessionId, error);
|
|
1412
1508
|
throw error;
|
|
1413
1509
|
}
|
|
1414
1510
|
}
|
|
@@ -1416,7 +1512,7 @@ var AcpClient = class {
|
|
|
1416
1512
|
try {
|
|
1417
1513
|
return await this.terminalManager.createTerminal(params);
|
|
1418
1514
|
} catch (error) {
|
|
1419
|
-
|
|
1515
|
+
this.recordPermissionError(params.sessionId, error);
|
|
1420
1516
|
throw error;
|
|
1421
1517
|
}
|
|
1422
1518
|
}
|
|
@@ -1432,6 +1528,26 @@ var AcpClient = class {
|
|
|
1432
1528
|
async handleReleaseTerminal(params) {
|
|
1433
1529
|
return await this.terminalManager.releaseTerminal(params);
|
|
1434
1530
|
}
|
|
1531
|
+
recordPermissionDecision(decision) {
|
|
1532
|
+
this.permissionStats.requested += 1;
|
|
1533
|
+
if (decision === "approved") {
|
|
1534
|
+
this.permissionStats.approved += 1;
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
if (decision === "denied") {
|
|
1538
|
+
this.permissionStats.denied += 1;
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
this.permissionStats.cancelled += 1;
|
|
1542
|
+
}
|
|
1543
|
+
recordPermissionError(sessionId, error) {
|
|
1544
|
+
if (error instanceof PermissionPromptUnavailableError) {
|
|
1545
|
+
this.notePromptPermissionFailure(sessionId, error);
|
|
1546
|
+
this.recordPermissionDecision("cancelled");
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (error instanceof PermissionDeniedError) this.recordPermissionDecision("denied");
|
|
1550
|
+
}
|
|
1435
1551
|
async handleSessionUpdate(notification) {
|
|
1436
1552
|
const sequence = ++this.observedSessionUpdates;
|
|
1437
1553
|
this.sessionUpdateChain = this.sessionUpdateChain.then(async () => {
|
|
@@ -1469,7 +1585,6 @@ var AcpClient = class {
|
|
|
1469
1585
|
throw new Error(`Timed out waiting for session replay drain after ${normalizedTimeoutMs}ms`);
|
|
1470
1586
|
}
|
|
1471
1587
|
};
|
|
1472
|
-
|
|
1473
1588
|
//#endregion
|
|
1474
1589
|
//#region src/perf-metrics-capture.ts
|
|
1475
1590
|
const PERF_METRICS_FILE_ENV = "ACPX_PERF_METRICS_FILE";
|
|
@@ -1541,7 +1656,6 @@ function installPerfMetricsCapture(options = {}) {
|
|
|
1541
1656
|
process.once(signal, handler);
|
|
1542
1657
|
}
|
|
1543
1658
|
}
|
|
1544
|
-
|
|
1545
1659
|
//#endregion
|
|
1546
1660
|
//#region src/session-conversation-model.ts
|
|
1547
1661
|
const MAX_RUNTIME_MESSAGES = 200;
|
|
@@ -1892,11 +2006,9 @@ function trimConversationForRuntime(conversation) {
|
|
|
1892
2006
|
const requestUsageEntries = Object.entries(conversation.request_token_usage);
|
|
1893
2007
|
if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
|
|
1894
2008
|
}
|
|
1895
|
-
|
|
1896
2009
|
//#endregion
|
|
1897
2010
|
//#region src/session-event-log.ts
|
|
1898
2011
|
const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
|
|
1899
|
-
const DEFAULT_EVENT_MAX_SEGMENTS = 5;
|
|
1900
2012
|
function sessionBaseDir$1() {
|
|
1901
2013
|
return path.join(os.homedir(), ".acpx", "sessions");
|
|
1902
2014
|
}
|
|
@@ -1915,14 +2027,13 @@ function sessionEventLockPath(sessionId) {
|
|
|
1915
2027
|
function defaultSessionEventLog(sessionId) {
|
|
1916
2028
|
return {
|
|
1917
2029
|
active_path: sessionEventActivePath(sessionId),
|
|
1918
|
-
segment_count:
|
|
2030
|
+
segment_count: 5,
|
|
1919
2031
|
max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
|
|
1920
|
-
max_segments:
|
|
2032
|
+
max_segments: 5,
|
|
1921
2033
|
last_write_at: void 0,
|
|
1922
2034
|
last_write_error: null
|
|
1923
2035
|
};
|
|
1924
2036
|
}
|
|
1925
|
-
|
|
1926
2037
|
//#endregion
|
|
1927
2038
|
//#region src/session-persistence/serialize.ts
|
|
1928
2039
|
function serializeSessionRecordForDisk(record) {
|
|
@@ -1962,7 +2073,6 @@ function serializeSessionRecordForDisk(record) {
|
|
|
1962
2073
|
acpx: canonical.acpx
|
|
1963
2074
|
};
|
|
1964
2075
|
}
|
|
1965
|
-
|
|
1966
2076
|
//#endregion
|
|
1967
2077
|
//#region src/persisted-key-policy.ts
|
|
1968
2078
|
const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
|
|
@@ -2029,7 +2139,6 @@ function assertPersistedKeyPolicy(value) {
|
|
|
2029
2139
|
if (violations.length === 0) return;
|
|
2030
2140
|
throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
|
|
2031
2141
|
}
|
|
2032
|
-
|
|
2033
2142
|
//#endregion
|
|
2034
2143
|
//#region src/session-persistence/parse.ts
|
|
2035
2144
|
function asRecord$1(value) {
|
|
@@ -2206,7 +2315,7 @@ function normalizeOptionalSignal(value) {
|
|
|
2206
2315
|
function parseSessionRecord(raw) {
|
|
2207
2316
|
const record = asRecord$1(raw);
|
|
2208
2317
|
if (!record) return null;
|
|
2209
|
-
if (record.schema !==
|
|
2318
|
+
if (record.schema !== "acpx.session.v1") return null;
|
|
2210
2319
|
const name = normalizeOptionalName(record.name);
|
|
2211
2320
|
const pid = normalizeOptionalPid(record.pid);
|
|
2212
2321
|
const closed = normalizeOptionalBoolean(record.closed, false);
|
|
@@ -2255,7 +2364,6 @@ function parseSessionRecord(raw) {
|
|
|
2255
2364
|
acpx: parseAcpxState(record.acpx)
|
|
2256
2365
|
};
|
|
2257
2366
|
}
|
|
2258
|
-
|
|
2259
2367
|
//#endregion
|
|
2260
2368
|
//#region src/session-persistence/index.ts
|
|
2261
2369
|
const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
|
|
@@ -2347,7 +2455,6 @@ async function loadOrRebuildSessionIndex(sessionDir) {
|
|
|
2347
2455
|
if (existing && existing.files.length === files.length && existing.files.every((file, index) => file === files[index])) return existing;
|
|
2348
2456
|
return await rebuildSessionIndex(sessionDir);
|
|
2349
2457
|
}
|
|
2350
|
-
|
|
2351
2458
|
//#endregion
|
|
2352
2459
|
//#region src/session-persistence/repository.ts
|
|
2353
2460
|
const DEFAULT_HISTORY_LIMIT = 20;
|
|
@@ -2498,7 +2605,6 @@ async function findSessionByDirectoryWalk(options) {
|
|
|
2498
2605
|
if (!isWithinBoundary(walkBoundary, current)) return;
|
|
2499
2606
|
}
|
|
2500
2607
|
}
|
|
2501
|
-
|
|
2502
2608
|
//#endregion
|
|
2503
2609
|
//#region src/session-events.ts
|
|
2504
2610
|
const LOCK_RETRY_MS = 15;
|
|
@@ -2614,8 +2720,8 @@ var SessionEventWriter = class SessionEventWriter {
|
|
|
2614
2720
|
}
|
|
2615
2721
|
static async open(record, options = {}) {
|
|
2616
2722
|
const lock = await acquireEventsLock(record.acpxRecordId);
|
|
2617
|
-
const maxSegmentBytes = options.maxSegmentBytes ?? record.eventLog.max_segment_bytes ??
|
|
2618
|
-
const maxSegments = options.maxSegments ?? record.eventLog.max_segments ??
|
|
2723
|
+
const maxSegmentBytes = options.maxSegmentBytes ?? record.eventLog.max_segment_bytes ?? 67108864;
|
|
2724
|
+
const maxSegments = options.maxSegments ?? record.eventLog.max_segments ?? 5;
|
|
2619
2725
|
const activePath = sessionEventActivePath(record.acpxRecordId);
|
|
2620
2726
|
const activeSizeBytes = await statSize(activePath);
|
|
2621
2727
|
const segmentCount = Number.isInteger(record.eventLog.segment_count) && record.eventLog.segment_count > 0 ? record.eventLog.segment_count : await countExistingSegments(record.acpxRecordId, maxSegments) || 1;
|
|
@@ -2685,7 +2791,6 @@ var SessionEventWriter = class SessionEventWriter {
|
|
|
2685
2791
|
}
|
|
2686
2792
|
}
|
|
2687
2793
|
};
|
|
2688
|
-
|
|
2689
2794
|
//#endregion
|
|
2690
2795
|
//#region src/queue-owner-turn-controller.ts
|
|
2691
2796
|
var QueueOwnerTurnController = class {
|
|
@@ -2767,7 +2872,6 @@ var QueueOwnerTurnController = class {
|
|
|
2767
2872
|
return await this.options.setSessionConfigOptionFallback(configId, value, timeoutMs);
|
|
2768
2873
|
}
|
|
2769
2874
|
};
|
|
2770
|
-
|
|
2771
2875
|
//#endregion
|
|
2772
2876
|
//#region src/session-mode-preference.ts
|
|
2773
2877
|
function ensureAcpxState(state) {
|
|
@@ -2788,7 +2892,6 @@ function setDesiredModeId(record, modeId) {
|
|
|
2788
2892
|
else delete acpx.desired_mode_id;
|
|
2789
2893
|
record.acpx = acpx;
|
|
2790
2894
|
}
|
|
2791
|
-
|
|
2792
2895
|
//#endregion
|
|
2793
2896
|
//#region src/session-runtime/lifecycle.ts
|
|
2794
2897
|
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
@@ -2821,7 +2924,6 @@ function applyConversation(record, conversation) {
|
|
|
2821
2924
|
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
|
2822
2925
|
record.request_token_usage = conversation.request_token_usage;
|
|
2823
2926
|
}
|
|
2824
|
-
|
|
2825
2927
|
//#endregion
|
|
2826
2928
|
//#region src/session-runtime/connect-load.ts
|
|
2827
2929
|
function shouldFallbackToNewSession(error, record) {
|
|
@@ -2901,7 +3003,6 @@ async function connectAndLoadSession(options) {
|
|
|
2901
3003
|
loadError
|
|
2902
3004
|
};
|
|
2903
3005
|
}
|
|
2904
|
-
|
|
2905
3006
|
//#endregion
|
|
2906
3007
|
//#region src/session-runtime/prompt-runner.ts
|
|
2907
3008
|
async function withConnectedSession(options) {
|
|
@@ -3018,7 +3119,6 @@ async function runSessionSetConfigOptionDirect(options) {
|
|
|
3018
3119
|
loadError: result.loadError
|
|
3019
3120
|
};
|
|
3020
3121
|
}
|
|
3021
|
-
|
|
3022
3122
|
//#endregion
|
|
3023
3123
|
//#region src/session-runtime/queue-owner-process.ts
|
|
3024
3124
|
function resolveQueueOwnerSpawnArgs(argv = process.argv) {
|
|
@@ -3055,7 +3155,6 @@ function spawnQueueOwnerProcess(options) {
|
|
|
3055
3155
|
const payload = JSON.stringify(options);
|
|
3056
3156
|
spawn(process.execPath, resolveQueueOwnerSpawnArgs(), buildQueueOwnerSpawnOptions(payload)).unref();
|
|
3057
3157
|
}
|
|
3058
|
-
|
|
3059
3158
|
//#endregion
|
|
3060
3159
|
//#region src/session-runtime.ts
|
|
3061
3160
|
const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
|
|
@@ -3104,6 +3203,53 @@ const DISCARD_OUTPUT_FORMATTER = {
|
|
|
3104
3203
|
onError() {},
|
|
3105
3204
|
flush() {}
|
|
3106
3205
|
};
|
|
3206
|
+
function jsonRpcIdKey(value) {
|
|
3207
|
+
if (typeof value === "string") return `s:${value}`;
|
|
3208
|
+
if (typeof value === "number" && Number.isFinite(value)) return `n:${value}`;
|
|
3209
|
+
}
|
|
3210
|
+
function extractJsonRpcRequestInfo(message) {
|
|
3211
|
+
const candidate = message;
|
|
3212
|
+
if (typeof candidate.method !== "string") return;
|
|
3213
|
+
const idKey = jsonRpcIdKey(candidate.id);
|
|
3214
|
+
if (!idKey) return;
|
|
3215
|
+
return {
|
|
3216
|
+
idKey,
|
|
3217
|
+
method: candidate.method
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
function extractJsonRpcResponseInfo(message) {
|
|
3221
|
+
const candidate = message;
|
|
3222
|
+
const idKey = jsonRpcIdKey(candidate.id);
|
|
3223
|
+
if (!idKey) return;
|
|
3224
|
+
const hasError = Object.hasOwn(candidate, "error");
|
|
3225
|
+
if (!hasError && !Object.hasOwn(candidate, "result")) return;
|
|
3226
|
+
return {
|
|
3227
|
+
idKey,
|
|
3228
|
+
hasError
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
function filterRecoverableLoadFallbackOutput(messages) {
|
|
3232
|
+
const requestMethodById = /* @__PURE__ */ new Map();
|
|
3233
|
+
const failedLoadRequestIds = /* @__PURE__ */ new Set();
|
|
3234
|
+
for (const message of messages) {
|
|
3235
|
+
const request = extractJsonRpcRequestInfo(message);
|
|
3236
|
+
if (request) {
|
|
3237
|
+
requestMethodById.set(request.idKey, request.method);
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
const response = extractJsonRpcResponseInfo(message);
|
|
3241
|
+
if (!response || !response.hasError) continue;
|
|
3242
|
+
if (requestMethodById.get(response.idKey) === "session/load") failedLoadRequestIds.add(response.idKey);
|
|
3243
|
+
}
|
|
3244
|
+
if (failedLoadRequestIds.size === 0) return messages;
|
|
3245
|
+
return messages.filter((message) => {
|
|
3246
|
+
const request = extractJsonRpcRequestInfo(message);
|
|
3247
|
+
if (request && request.method === "session/load" && failedLoadRequestIds.has(request.idKey)) return false;
|
|
3248
|
+
const response = extractJsonRpcResponseInfo(message);
|
|
3249
|
+
if (response && failedLoadRequestIds.has(response.idKey)) return false;
|
|
3250
|
+
return true;
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3107
3253
|
function normalizeQueueOwnerTtlMs(ttlMs) {
|
|
3108
3254
|
if (ttlMs == null) return DEFAULT_QUEUE_OWNER_TTL_MS;
|
|
3109
3255
|
if (!Number.isFinite(ttlMs) || ttlMs < 0) return DEFAULT_QUEUE_OWNER_TTL_MS;
|
|
@@ -3170,6 +3316,8 @@ async function runSessionPrompt(options) {
|
|
|
3170
3316
|
return await SessionEventWriter.open(record);
|
|
3171
3317
|
});
|
|
3172
3318
|
const pendingMessages = [];
|
|
3319
|
+
const pendingConnectOutputMessages = [];
|
|
3320
|
+
let bufferingConnectOutput = true;
|
|
3173
3321
|
let sawAcpMessage = false;
|
|
3174
3322
|
let eventWriterClosed = false;
|
|
3175
3323
|
const closeEventWriter = async (checkpoint) => {
|
|
@@ -3208,6 +3356,10 @@ async function runSessionPrompt(options) {
|
|
|
3208
3356
|
pendingMessages.push(message);
|
|
3209
3357
|
},
|
|
3210
3358
|
onAcpOutputMessage: (_direction, message) => {
|
|
3359
|
+
if (bufferingConnectOutput) {
|
|
3360
|
+
pendingConnectOutputMessages.push(message);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3211
3363
|
output.onAcpMessage(message);
|
|
3212
3364
|
},
|
|
3213
3365
|
onSessionUpdate: (notification) => {
|
|
@@ -3234,23 +3386,36 @@ async function runSessionPrompt(options) {
|
|
|
3234
3386
|
try {
|
|
3235
3387
|
return await withInterrupt(async () => {
|
|
3236
3388
|
const connectStartedAt = Date.now();
|
|
3237
|
-
const { sessionId: activeSessionId, resumed, loadError } = await measurePerf("runtime.connect_and_load", async () =>
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3389
|
+
const { sessionId: activeSessionId, resumed, loadError } = await measurePerf("runtime.connect_and_load", async () => {
|
|
3390
|
+
try {
|
|
3391
|
+
return await connectAndLoadSession({
|
|
3392
|
+
client,
|
|
3393
|
+
record,
|
|
3394
|
+
timeoutMs: options.timeoutMs,
|
|
3395
|
+
verbose: options.verbose,
|
|
3396
|
+
activeController,
|
|
3397
|
+
onClientAvailable: (controller) => {
|
|
3398
|
+
options.onClientAvailable?.(controller);
|
|
3399
|
+
notifiedClientAvailable = true;
|
|
3400
|
+
},
|
|
3401
|
+
onConnectedRecord: (connectedRecord) => {
|
|
3402
|
+
connectedRecord.lastPromptAt = isoNow();
|
|
3403
|
+
},
|
|
3404
|
+
onSessionIdResolved: (sessionId) => {
|
|
3405
|
+
activeSessionIdForControl = sessionId;
|
|
3406
|
+
}
|
|
3407
|
+
});
|
|
3408
|
+
} catch (error) {
|
|
3409
|
+
bufferingConnectOutput = false;
|
|
3410
|
+
for (const message of pendingConnectOutputMessages) output.onAcpMessage(message);
|
|
3411
|
+
pendingConnectOutputMessages.length = 0;
|
|
3412
|
+
throw error;
|
|
3252
3413
|
}
|
|
3253
|
-
})
|
|
3414
|
+
});
|
|
3415
|
+
bufferingConnectOutput = false;
|
|
3416
|
+
const connectOutputMessages = loadError == null ? pendingConnectOutputMessages : filterRecoverableLoadFallbackOutput(pendingConnectOutputMessages);
|
|
3417
|
+
for (const message of connectOutputMessages) output.onAcpMessage(message);
|
|
3418
|
+
pendingConnectOutputMessages.length = 0;
|
|
3254
3419
|
if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.connect_and_load", Date.now() - connectStartedAt)}\n`);
|
|
3255
3420
|
output.setContext({ sessionId: record.acpxRecordId });
|
|
3256
3421
|
await flushPendingMessages(false);
|
|
@@ -3619,7 +3784,7 @@ async function sendSession(options) {
|
|
|
3619
3784
|
for (let attempt = 0; attempt < QUEUE_OWNER_STARTUP_MAX_ATTEMPTS; attempt += 1) {
|
|
3620
3785
|
const queued = await submitToRunningOwner(options, waitForCompletion);
|
|
3621
3786
|
if (queued) return queued;
|
|
3622
|
-
await waitMs$1(
|
|
3787
|
+
await waitMs$1(50);
|
|
3623
3788
|
}
|
|
3624
3789
|
throw new Error(`Session queue owner failed to start for session ${options.sessionId}`);
|
|
3625
3790
|
}
|
|
@@ -3707,11 +3872,10 @@ async function closeSession(sessionId) {
|
|
|
3707
3872
|
await writeSessionRecord(record);
|
|
3708
3873
|
return record;
|
|
3709
3874
|
}
|
|
3710
|
-
|
|
3711
3875
|
//#endregion
|
|
3712
3876
|
//#region src/session.ts
|
|
3713
3877
|
var session_exports = /* @__PURE__ */ __exportAll({
|
|
3714
|
-
DEFAULT_HISTORY_LIMIT: () =>
|
|
3878
|
+
DEFAULT_HISTORY_LIMIT: () => 20,
|
|
3715
3879
|
DEFAULT_QUEUE_OWNER_TTL_MS: () => DEFAULT_QUEUE_OWNER_TTL_MS,
|
|
3716
3880
|
InterruptedError: () => InterruptedError,
|
|
3717
3881
|
TimeoutError: () => TimeoutError,
|
|
@@ -3732,7 +3896,7 @@ var session_exports = /* @__PURE__ */ __exportAll({
|
|
|
3732
3896
|
setSessionConfigOption: () => setSessionConfigOption,
|
|
3733
3897
|
setSessionMode: () => setSessionMode
|
|
3734
3898
|
});
|
|
3735
|
-
|
|
3736
3899
|
//#endregion
|
|
3737
3900
|
export { findGitRepositoryRoot as a, flushPerfMetricsCapture as c, DEFAULT_HISTORY_LIMIT as i, installPerfMetricsCapture as l, DEFAULT_QUEUE_OWNER_TTL_MS as n, findSession as o, runSessionQueueOwner as r, findSessionByDirectoryWalk as s, session_exports as t, InterruptedError as u };
|
|
3738
|
-
|
|
3901
|
+
|
|
3902
|
+
//# sourceMappingURL=session-C2Q8ktsN.js.map
|