@wrongstack/acp 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/stdio-transport.d.ts +2 -0
- package/dist/client/acp-request-state.d.ts +4 -0
- package/dist/client/acp-session-ops.d.ts +19 -0
- package/dist/client/acp-session-prompt.d.ts +2 -2
- package/dist/client/acp-session-types.d.ts +67 -1
- package/dist/client/acp-session-updates.d.ts +9 -0
- package/dist/client/acp-session.d.ts +21 -1
- package/dist/client.js +330 -35
- package/dist/index.d.ts +3 -1
- package/dist/index.js +334 -36
- package/dist/integration/acp-agent-failure.d.ts +20 -0
- package/dist/integration/acp-subagent-runner-options.d.ts +88 -0
- package/dist/integration/acp-subagent-runner.d.ts +4 -63
- package/dist/integration/run-one-acp-task.d.ts +3 -0
- package/dist/registry/contracts.d.ts +6 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1015,6 +1015,19 @@ import {
|
|
|
1015
1015
|
var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
|
|
1016
1016
|
var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
|
|
1017
1017
|
var DEFAULT_MAX_QUEUED_CHARS = 32 * 1024 * 1024;
|
|
1018
|
+
var STDERR_TAIL_CHARS = 4096;
|
|
1019
|
+
function echoChildStderr() {
|
|
1020
|
+
const v = process.env.WRONGSTACK_DEBUG;
|
|
1021
|
+
return !!v && v !== "0" && v !== "false";
|
|
1022
|
+
}
|
|
1023
|
+
function lastLine(text) {
|
|
1024
|
+
const lines = text.split(/\r?\n/);
|
|
1025
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1026
|
+
const line = lines[i]?.trim();
|
|
1027
|
+
if (line) return line.length > 300 ? `${line.slice(0, 300)}\u2026` : line;
|
|
1028
|
+
}
|
|
1029
|
+
return "";
|
|
1030
|
+
}
|
|
1018
1031
|
function takeLines(pending, chunk) {
|
|
1019
1032
|
const lines = [];
|
|
1020
1033
|
let start = 0;
|
|
@@ -1196,6 +1209,8 @@ var StdioTransport = class {
|
|
|
1196
1209
|
var ClientTransport = class {
|
|
1197
1210
|
child = null;
|
|
1198
1211
|
buffer = "";
|
|
1212
|
+
stderrTail = "";
|
|
1213
|
+
stopRequested = false;
|
|
1199
1214
|
handlers = /* @__PURE__ */ new Set();
|
|
1200
1215
|
claimHandlers = /* @__PURE__ */ new Set();
|
|
1201
1216
|
closeHandlers = /* @__PURE__ */ new Set();
|
|
@@ -1296,9 +1311,11 @@ var ClientTransport = class {
|
|
|
1296
1311
|
}
|
|
1297
1312
|
const idx = this.buffer.indexOf("[wstack-acp]\n");
|
|
1298
1313
|
if (idx !== -1) {
|
|
1299
|
-
|
|
1314
|
+
const rest = this.buffer.slice(idx + "[wstack-acp]\n".length);
|
|
1315
|
+
this.buffer = "";
|
|
1300
1316
|
child.stdout.removeListener("data", waitForMarker);
|
|
1301
1317
|
onReady();
|
|
1318
|
+
if (rest) this.onChildData(rest);
|
|
1302
1319
|
}
|
|
1303
1320
|
};
|
|
1304
1321
|
child.stdout.on("data", waitForMarker);
|
|
@@ -1361,6 +1378,7 @@ var ClientTransport = class {
|
|
|
1361
1378
|
}
|
|
1362
1379
|
stop(reason = "transport stopped") {
|
|
1363
1380
|
this.closed = true;
|
|
1381
|
+
this.stopRequested = true;
|
|
1364
1382
|
this.resolveRead?.(null);
|
|
1365
1383
|
this.resolveRead = null;
|
|
1366
1384
|
this.buffer = "";
|
|
@@ -1398,7 +1416,8 @@ var ClientTransport = class {
|
|
|
1398
1416
|
}
|
|
1399
1417
|
}
|
|
1400
1418
|
onChildError(chunk) {
|
|
1401
|
-
|
|
1419
|
+
this.stderrTail = (this.stderrTail + chunk).slice(-STDERR_TAIL_CHARS);
|
|
1420
|
+
if (echoChildStderr()) writeErr(`[acp-child stderr] ${chunk}`);
|
|
1402
1421
|
}
|
|
1403
1422
|
onChildClose(code) {
|
|
1404
1423
|
this.closed = true;
|
|
@@ -1410,11 +1429,16 @@ var ClientTransport = class {
|
|
|
1410
1429
|
this.queuedChars = 0;
|
|
1411
1430
|
this.handlers.clear();
|
|
1412
1431
|
this.claimHandlers.clear();
|
|
1413
|
-
|
|
1432
|
+
const failed = code !== 0 && code !== null && !this.stopRequested;
|
|
1433
|
+
if (failed) {
|
|
1434
|
+
const tail = echoChildStderr() ? "" : this.stderrTail.trimEnd();
|
|
1414
1435
|
writeErr(`[acp-child exited with code ${code}]
|
|
1415
|
-
|
|
1436
|
+
${tail ? `${tail}
|
|
1437
|
+
` : ""}`);
|
|
1416
1438
|
}
|
|
1417
|
-
|
|
1439
|
+
const cause = failed ? lastLine(this.stderrTail) : "";
|
|
1440
|
+
this.stderrTail = "";
|
|
1441
|
+
this.notifyClosed(`agent process exited with code ${code}${cause ? `: ${cause}` : ""}`);
|
|
1418
1442
|
}
|
|
1419
1443
|
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
1420
1444
|
if (this.resolveRead) {
|
|
@@ -1939,6 +1963,9 @@ function isAuthRequiredError(err) {
|
|
|
1939
1963
|
}
|
|
1940
1964
|
|
|
1941
1965
|
// src/client/acp-request-state.ts
|
|
1966
|
+
function touchIdleRequests(pending) {
|
|
1967
|
+
for (const p of pending.values()) p.rearm?.();
|
|
1968
|
+
}
|
|
1942
1969
|
function rejectPendingRequests(pending, reason) {
|
|
1943
1970
|
for (const p of pending.values()) {
|
|
1944
1971
|
clearTimeout(p.timeoutHandle);
|
|
@@ -2135,11 +2162,21 @@ async function executeMcpMessage(ctx, connectionId, message) {
|
|
|
2135
2162
|
}
|
|
2136
2163
|
async function executeCreateSession(ctx) {
|
|
2137
2164
|
const servers = filterMcpServers(ctx.agentCapabilities, ctx.opts.mcpServers);
|
|
2138
|
-
const
|
|
2139
|
-
const result = await ctx.sendRequest(id, "session/new", {
|
|
2165
|
+
const params = {
|
|
2140
2166
|
cwd: ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
2141
|
-
mcpServers: servers
|
|
2142
|
-
|
|
2167
|
+
mcpServers: servers,
|
|
2168
|
+
...ctx.opts.sessionMeta ? { _meta: ctx.opts.sessionMeta } : {}
|
|
2169
|
+
};
|
|
2170
|
+
const resumeId = ctx.opts.resumeSessionId;
|
|
2171
|
+
if (resumeId && ctx.agentCapabilities.sessionCapabilities?.resume) {
|
|
2172
|
+
const resumed = await ctx.sendRequest(ctx.allocId(), "session/resume", { ...params, sessionId: resumeId }).catch((err) => ({ refused: err }));
|
|
2173
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2174
|
+
if (!isJsonRpcError(resumed) && !resumed.refused) {
|
|
2175
|
+
return adoptSessionMode(ctx, resumeId, resumed);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
const id = ctx.allocId();
|
|
2179
|
+
const result = await ctx.sendRequest(id, "session/new", params);
|
|
2143
2180
|
if (isJsonRpcError(result)) {
|
|
2144
2181
|
throw new ACPSessionError(
|
|
2145
2182
|
"session_create_failed",
|
|
@@ -2151,8 +2188,53 @@ async function executeCreateSession(ctx) {
|
|
|
2151
2188
|
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
2152
2189
|
throw new ACPSessionError("protocol_error", "session/new returned no sessionId", result);
|
|
2153
2190
|
}
|
|
2191
|
+
return adoptSessionMode(ctx, sessionId, result);
|
|
2192
|
+
}
|
|
2193
|
+
async function executeOpenSession(ctx, create) {
|
|
2194
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
2195
|
+
if (ctx.sessionId) return { sessionId: ctx.sessionId, resumed: false };
|
|
2196
|
+
const sessionId = await create();
|
|
2197
|
+
ctx.setSessionId(sessionId);
|
|
2198
|
+
return { sessionId, resumed: sessionId === ctx.opts.resumeSessionId };
|
|
2199
|
+
}
|
|
2200
|
+
async function adoptSessionMode(ctx, sessionId, result) {
|
|
2201
|
+
const want = ctx.opts.initialModeId;
|
|
2202
|
+
const modes = result.modes;
|
|
2203
|
+
const offered = Array.isArray(modes?.availableModes) && modes.availableModes.some((m) => m?.id === want);
|
|
2204
|
+
if (want && !offered && ctx.opts.requireInitialMode) {
|
|
2205
|
+
throw new ACPSessionError(
|
|
2206
|
+
"session_create_failed",
|
|
2207
|
+
`the agent does not offer the "${want}" session mode this host requires`,
|
|
2208
|
+
result
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
if (want && offered && modes?.currentModeId !== want) {
|
|
2212
|
+
await executeSetMode(ctx, sessionId, want);
|
|
2213
|
+
}
|
|
2154
2214
|
return sessionId;
|
|
2155
2215
|
}
|
|
2216
|
+
async function executeSteer(ctx, supported, text) {
|
|
2217
|
+
if (ctx.closed || !ctx.sessionId || !supported) return "unsupported";
|
|
2218
|
+
const result = await ctx.sendRequest(
|
|
2219
|
+
ctx.allocId(),
|
|
2220
|
+
"_session/steering",
|
|
2221
|
+
{
|
|
2222
|
+
sessionId: ctx.sessionId,
|
|
2223
|
+
prompt: [{ type: "text", text }],
|
|
2224
|
+
_meta: { steering: { idleBehavior: "promptRequired" } }
|
|
2225
|
+
},
|
|
2226
|
+
1e4
|
|
2227
|
+
);
|
|
2228
|
+
if (isJsonRpcError(result)) {
|
|
2229
|
+
throw new ACPSessionError(
|
|
2230
|
+
"prompt_failed",
|
|
2231
|
+
`_session/steering failed: ${result.message}`,
|
|
2232
|
+
result
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
const outcome = result?.outcome;
|
|
2236
|
+
return outcome === "injected" || outcome === "promptRequired" || outcome === "startedNewTurn" ? outcome : "unsupported";
|
|
2237
|
+
}
|
|
2156
2238
|
|
|
2157
2239
|
// src/client/acp-session-auth.ts
|
|
2158
2240
|
function ensureOpen(host) {
|
|
@@ -2759,6 +2841,7 @@ function emptyRunResult(stopReason) {
|
|
|
2759
2841
|
function createSessionScratch() {
|
|
2760
2842
|
return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
|
|
2761
2843
|
}
|
|
2844
|
+
var MESSAGE_SEPARATOR = "\n\n";
|
|
2762
2845
|
function handleAcpSessionUpdate(msg, scratch, emitProgress2) {
|
|
2763
2846
|
const update = msg.params?.update;
|
|
2764
2847
|
if (typeof update !== "object" || update === null) return;
|
|
@@ -2768,6 +2851,10 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress2) {
|
|
|
2768
2851
|
case "agent_message_chunk": {
|
|
2769
2852
|
const text = extractText(u.content);
|
|
2770
2853
|
if (text) {
|
|
2854
|
+
if (scratch.messageBreak && scratch.text && !/\s$/.test(scratch.text)) {
|
|
2855
|
+
scratch.text += MESSAGE_SEPARATOR;
|
|
2856
|
+
}
|
|
2857
|
+
scratch.messageBreak = false;
|
|
2771
2858
|
scratch.text += text;
|
|
2772
2859
|
emitProgress2({ type: "message", text });
|
|
2773
2860
|
}
|
|
@@ -2792,7 +2879,9 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress2) {
|
|
|
2792
2879
|
emitProgress2({ type: "plan", entries: u.entries });
|
|
2793
2880
|
}
|
|
2794
2881
|
return;
|
|
2795
|
-
case "usage_update":
|
|
2882
|
+
case "usage_update": {
|
|
2883
|
+
const rateLimit = u._meta?.["_claude/rateLimit"];
|
|
2884
|
+
if (isRecord(rateLimit)) emitProgress2({ type: "rate_limit", info: rateLimit });
|
|
2796
2885
|
if (typeof u.used === "number" && Number.isFinite(u.used) && u.used >= 0 && typeof u.size === "number" && Number.isFinite(u.size) && u.size >= 0) {
|
|
2797
2886
|
const usage = {
|
|
2798
2887
|
used: u.used,
|
|
@@ -2803,6 +2892,7 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress2) {
|
|
|
2803
2892
|
emitProgress2({ type: "usage", usage });
|
|
2804
2893
|
}
|
|
2805
2894
|
return;
|
|
2895
|
+
}
|
|
2806
2896
|
case "available_commands_update":
|
|
2807
2897
|
case "current_mode_update":
|
|
2808
2898
|
case "config_option_update":
|
|
@@ -2815,6 +2905,20 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress2) {
|
|
|
2815
2905
|
return;
|
|
2816
2906
|
}
|
|
2817
2907
|
}
|
|
2908
|
+
function toolOutputText(u) {
|
|
2909
|
+
const joined = (blocks) => blocks.map(extractText).filter((text) => text.length > 0).join("\n");
|
|
2910
|
+
if (Array.isArray(u.content)) {
|
|
2911
|
+
const text = joined(
|
|
2912
|
+
u.content.map(
|
|
2913
|
+
(c) => c && typeof c === "object" && c.type === "content" ? c.content : void 0
|
|
2914
|
+
)
|
|
2915
|
+
);
|
|
2916
|
+
if (text) return text;
|
|
2917
|
+
}
|
|
2918
|
+
if (typeof u.rawOutput === "string") return u.rawOutput || void 0;
|
|
2919
|
+
if (Array.isArray(u.rawOutput)) return joined(u.rawOutput) || void 0;
|
|
2920
|
+
return void 0;
|
|
2921
|
+
}
|
|
2818
2922
|
function captureToolCall(u, isNew, scratch, emitProgress2) {
|
|
2819
2923
|
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
2820
2924
|
if (!toolCallId) return;
|
|
@@ -2825,12 +2929,18 @@ function captureToolCall(u, isNew, scratch, emitProgress2) {
|
|
|
2825
2929
|
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
2826
2930
|
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
2827
2931
|
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
2828
|
-
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
2932
|
+
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput,
|
|
2933
|
+
output: toolOutputText(u) ?? prev?.output
|
|
2829
2934
|
};
|
|
2830
2935
|
scratch.toolCalls.set(toolCallId, record);
|
|
2936
|
+
if (scratch.text) scratch.messageBreak = true;
|
|
2831
2937
|
if (Array.isArray(u.content)) {
|
|
2832
2938
|
for (const c of u.content) {
|
|
2833
2939
|
if (c && typeof c === "object" && c.type === "diff") {
|
|
2940
|
+
const key = `${toolCallId}\0${c.path}\0${c.oldText ?? ""}\0${c.newText}`;
|
|
2941
|
+
scratch.seenDiffs ??= /* @__PURE__ */ new Set();
|
|
2942
|
+
if (scratch.seenDiffs.has(key)) continue;
|
|
2943
|
+
scratch.seenDiffs.add(key);
|
|
2834
2944
|
const diff = {
|
|
2835
2945
|
path: c.path,
|
|
2836
2946
|
oldText: c.oldText,
|
|
@@ -2841,10 +2951,15 @@ function captureToolCall(u, isNew, scratch, emitProgress2) {
|
|
|
2841
2951
|
}
|
|
2842
2952
|
}
|
|
2843
2953
|
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2954
|
+
scratch.announced ??= /* @__PURE__ */ new Set();
|
|
2955
|
+
if (!scratch.announced.has(toolCallId)) {
|
|
2956
|
+
const hasInput = record.rawInput !== void 0 && Object.keys(record.rawInput).length > 0;
|
|
2957
|
+
if (isNew && !hasInput) return;
|
|
2958
|
+
scratch.announced.add(toolCallId);
|
|
2959
|
+
emitProgress2({ type: "tool_call", toolCall: record });
|
|
2960
|
+
if (isNew || record.status !== "completed" && record.status !== "failed") return;
|
|
2961
|
+
}
|
|
2962
|
+
emitProgress2({ type: "tool_call_update", toolCall: record });
|
|
2848
2963
|
}
|
|
2849
2964
|
|
|
2850
2965
|
// src/client/acp-session-messages.ts
|
|
@@ -2876,6 +2991,7 @@ function handleMessage(msg) {
|
|
|
2876
2991
|
}
|
|
2877
2992
|
return;
|
|
2878
2993
|
}
|
|
2994
|
+
touchIdleRequests(this.pending);
|
|
2879
2995
|
if (msg.method === "session/update") {
|
|
2880
2996
|
const updateSessionId = msg.params?.sessionId;
|
|
2881
2997
|
if (typeof updateSessionId === "string" && updateSessionId !== this.sessionId) return;
|
|
@@ -2932,6 +3048,7 @@ function handleMessage(msg) {
|
|
|
2932
3048
|
);
|
|
2933
3049
|
return;
|
|
2934
3050
|
}
|
|
3051
|
+
if (msg.method.startsWith("_")) return;
|
|
2935
3052
|
console.warn(
|
|
2936
3053
|
JSON.stringify({
|
|
2937
3054
|
level: "warn",
|
|
@@ -3041,7 +3158,9 @@ async function prompt(host, blocks, signal, onProgress) {
|
|
|
3041
3158
|
sessionId: host.sessionId,
|
|
3042
3159
|
prompt: blocks
|
|
3043
3160
|
},
|
|
3044
|
-
|
|
3161
|
+
// Timed by silence, not length: a working agent keeps it alive.
|
|
3162
|
+
host.promptIdleTimeoutMs,
|
|
3163
|
+
true
|
|
3045
3164
|
),
|
|
3046
3165
|
new Promise((_, reject) => {
|
|
3047
3166
|
rejectTurn = reject;
|
|
@@ -3057,6 +3176,17 @@ async function prompt(host, blocks, signal, onProgress) {
|
|
|
3057
3176
|
if (cancelled || abortedKind) {
|
|
3058
3177
|
return emptyRunResult("cancelled");
|
|
3059
3178
|
}
|
|
3179
|
+
if (err instanceof ACPSessionError && err.kind === "stalled") {
|
|
3180
|
+
if (host.sessionId) {
|
|
3181
|
+
host.transport.send({
|
|
3182
|
+
jsonrpc: "2.0",
|
|
3183
|
+
method: "session/cancel",
|
|
3184
|
+
params: { sessionId: host.sessionId }
|
|
3185
|
+
}).catch(() => {
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
throw err;
|
|
3189
|
+
}
|
|
3060
3190
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3061
3191
|
if (signal.aborted) {
|
|
3062
3192
|
throw new ACPSessionError("aborted", "prompt was aborted by the parent", err);
|
|
@@ -3078,17 +3208,31 @@ async function prompt(host, blocks, signal, onProgress) {
|
|
|
3078
3208
|
}
|
|
3079
3209
|
const stopReason = response.stopReason ?? "end_turn";
|
|
3080
3210
|
const finalText = host.scratch.text;
|
|
3211
|
+
const tokens = readTokenUsage(response.usage);
|
|
3081
3212
|
return {
|
|
3082
3213
|
text: finalText,
|
|
3083
3214
|
stopReason,
|
|
3084
3215
|
hasText: finalText.length > 0,
|
|
3085
3216
|
usage: host.scratch.usage,
|
|
3217
|
+
...tokens ? { tokens } : {},
|
|
3086
3218
|
plan: host.scratch.plan,
|
|
3087
3219
|
toolCalls: [...host.scratch.toolCalls.values()],
|
|
3088
3220
|
diffs: host.scratch.diffs,
|
|
3089
3221
|
thoughts: host.scratch.thoughts
|
|
3090
3222
|
};
|
|
3091
3223
|
}
|
|
3224
|
+
function readTokenUsage(raw) {
|
|
3225
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
3226
|
+
const r = raw;
|
|
3227
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
3228
|
+
if (typeof r.inputTokens !== "number" && typeof r.outputTokens !== "number") return void 0;
|
|
3229
|
+
return {
|
|
3230
|
+
input: n(r.inputTokens),
|
|
3231
|
+
output: n(r.outputTokens),
|
|
3232
|
+
cacheRead: n(r.cachedReadTokens),
|
|
3233
|
+
cacheWrite: n(r.cachedWriteTokens)
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3092
3236
|
|
|
3093
3237
|
// src/client/permission.ts
|
|
3094
3238
|
function pickAllow(options) {
|
|
@@ -3738,6 +3882,7 @@ var ACPSession = class _ACPSession {
|
|
|
3738
3882
|
terminalServer;
|
|
3739
3883
|
permissionPolicy;
|
|
3740
3884
|
timeoutMs;
|
|
3885
|
+
promptIdleTimeoutMs;
|
|
3741
3886
|
opts;
|
|
3742
3887
|
transportOff = null;
|
|
3743
3888
|
callbackAbort = new AbortController();
|
|
@@ -3753,12 +3898,15 @@ var ACPSession = class _ACPSession {
|
|
|
3753
3898
|
agentCapabilities = {};
|
|
3754
3899
|
agentInfo = null;
|
|
3755
3900
|
authMethods = [];
|
|
3901
|
+
/** The agent accepts `_session/steering` (see executeSteer). */
|
|
3902
|
+
steeringSupported = false;
|
|
3756
3903
|
/** Protocol version negotiated with the agent during initialize. */
|
|
3757
3904
|
negotiatedVersion = ACP_PROTOCOL_VERSION;
|
|
3758
3905
|
constructor(opts, transport) {
|
|
3759
3906
|
this.opts = opts;
|
|
3760
3907
|
this.transport = transport;
|
|
3761
3908
|
this.timeoutMs = opts.timeoutMs ?? 5 * 6e4;
|
|
3909
|
+
this.promptIdleTimeoutMs = opts.promptIdleTimeoutMs ?? 10 * 6e4;
|
|
3762
3910
|
const fsOpts = {
|
|
3763
3911
|
projectRoot: opts.projectRoot
|
|
3764
3912
|
};
|
|
@@ -3913,6 +4061,8 @@ var ACPSession = class _ACPSession {
|
|
|
3913
4061
|
}
|
|
3914
4062
|
this.negotiatedVersion = r.protocolVersion;
|
|
3915
4063
|
this.agentCapabilities = r.agentCapabilities ?? {};
|
|
4064
|
+
const meta = result._meta;
|
|
4065
|
+
this.steeringSupported = meta?.steering?.supported === true;
|
|
3916
4066
|
this.agentInfo = r.agentInfo ?? null;
|
|
3917
4067
|
this.authMethods = r.authMethods ?? [];
|
|
3918
4068
|
this.state = "ready";
|
|
@@ -3957,6 +4107,10 @@ var ACPSession = class _ACPSession {
|
|
|
3957
4107
|
closeSession: () => this.closeSession()
|
|
3958
4108
|
};
|
|
3959
4109
|
}
|
|
4110
|
+
/** Start (or, with `resumeSessionId`, resume) the agent session now, not at the first prompt. */
|
|
4111
|
+
async openSession() {
|
|
4112
|
+
return executeOpenSession(this.opContext(), () => this.createSessionWithAuth());
|
|
4113
|
+
}
|
|
3960
4114
|
async loadSession(sessionId, mcpServers, cwd) {
|
|
3961
4115
|
return executeLoadSession(this.opContext(), sessionId, mcpServers, cwd);
|
|
3962
4116
|
}
|
|
@@ -3990,6 +4144,14 @@ var ACPSession = class _ACPSession {
|
|
|
3990
4144
|
async disableProvider() {
|
|
3991
4145
|
return executeDisableProvider(this.opContext());
|
|
3992
4146
|
}
|
|
4147
|
+
/** Whether {@link steer} can reach the agent. */
|
|
4148
|
+
canSteer() {
|
|
4149
|
+
return this.steeringSupported;
|
|
4150
|
+
}
|
|
4151
|
+
/** Inject a message into the agent's running turn. */
|
|
4152
|
+
async steer(text) {
|
|
4153
|
+
return executeSteer(this.opContext(), this.steeringSupported, text);
|
|
4154
|
+
}
|
|
3993
4155
|
// ──────────────────────────────────────────────────────────────────────
|
|
3994
4156
|
// Prompt
|
|
3995
4157
|
// ──────────────────────────────────────────────────────────────────────
|
|
@@ -4008,14 +4170,14 @@ var ACPSession = class _ACPSession {
|
|
|
4008
4170
|
cancelLateSession(createPromise) {
|
|
4009
4171
|
cancelLateAcpSession(this.transport, createPromise);
|
|
4010
4172
|
}
|
|
4011
|
-
async closeSession() {
|
|
4173
|
+
async closeSession(graceMs = 1e4) {
|
|
4012
4174
|
if (!this.sessionId) return;
|
|
4013
4175
|
const sid = this.sessionId;
|
|
4014
4176
|
this.sessionId = null;
|
|
4015
4177
|
if (this.agentCapabilities.sessionCapabilities?.close) {
|
|
4016
4178
|
const id = this.allocId();
|
|
4017
4179
|
try {
|
|
4018
|
-
await this.sendRequest(id, "session/close", { sessionId: sid },
|
|
4180
|
+
await this.sendRequest(id, "session/close", { sessionId: sid }, graceMs);
|
|
4019
4181
|
} catch {
|
|
4020
4182
|
}
|
|
4021
4183
|
}
|
|
@@ -4023,7 +4185,12 @@ var ACPSession = class _ACPSession {
|
|
|
4023
4185
|
// ──────────────────────────────────────────────────────────────────────
|
|
4024
4186
|
// Lifecycle — close
|
|
4025
4187
|
// ──────────────────────────────────────────────────────────────────────
|
|
4026
|
-
|
|
4188
|
+
/**
|
|
4189
|
+
* Close the session and stop the agent. `graceMs` bounds how long the
|
|
4190
|
+
* agent may take to answer `session/close` before it is stopped anyway —
|
|
4191
|
+
* shutdown passes a short one so a slow answer cannot outlive the process.
|
|
4192
|
+
*/
|
|
4193
|
+
async close(opts = {}) {
|
|
4027
4194
|
if (this.closed) return;
|
|
4028
4195
|
this.closed = true;
|
|
4029
4196
|
this.state = "closed";
|
|
@@ -4033,7 +4200,7 @@ var ACPSession = class _ACPSession {
|
|
|
4033
4200
|
this.terminalServer.dispose();
|
|
4034
4201
|
if (this.sessionId && this.agentCapabilities.sessionCapabilities?.close) {
|
|
4035
4202
|
try {
|
|
4036
|
-
await this.closeSession();
|
|
4203
|
+
await this.closeSession(opts.graceMs);
|
|
4037
4204
|
} catch {
|
|
4038
4205
|
}
|
|
4039
4206
|
}
|
|
@@ -4057,24 +4224,37 @@ var ACPSession = class _ACPSession {
|
|
|
4057
4224
|
}
|
|
4058
4225
|
throw new ACPSessionError("protocol_error", "no JSON-RPC request id is available");
|
|
4059
4226
|
}
|
|
4060
|
-
async sendRequest(id, method, params, timeoutMs) {
|
|
4227
|
+
async sendRequest(id, method, params, timeoutMs, idle = false) {
|
|
4061
4228
|
return new Promise((resolve5, reject) => {
|
|
4062
4229
|
const effectiveTimeout = timeoutMs ?? this.timeoutMs;
|
|
4063
|
-
const
|
|
4230
|
+
const expire = () => {
|
|
4064
4231
|
this.pending.delete(id);
|
|
4065
4232
|
reject(
|
|
4066
|
-
new ACPSessionError(
|
|
4233
|
+
idle ? new ACPSessionError(
|
|
4234
|
+
"stalled",
|
|
4235
|
+
`${method}: no activity from the agent for ${Math.round(effectiveTimeout / 1e3)}s`
|
|
4236
|
+
) : new ACPSessionError(
|
|
4237
|
+
"protocol_error",
|
|
4238
|
+
`${method} timed out after ${effectiveTimeout}ms`
|
|
4239
|
+
)
|
|
4067
4240
|
);
|
|
4068
|
-
}
|
|
4069
|
-
|
|
4241
|
+
};
|
|
4242
|
+
const entry = {
|
|
4070
4243
|
method,
|
|
4071
4244
|
resolve: resolve5,
|
|
4072
4245
|
reject,
|
|
4073
4246
|
timeoutMs: effectiveTimeout,
|
|
4074
|
-
timeoutHandle:
|
|
4075
|
-
}
|
|
4247
|
+
timeoutHandle: setTimeout(expire, effectiveTimeout)
|
|
4248
|
+
};
|
|
4249
|
+
if (idle) {
|
|
4250
|
+
entry.rearm = () => {
|
|
4251
|
+
clearTimeout(entry.timeoutHandle);
|
|
4252
|
+
entry.timeoutHandle = setTimeout(expire, effectiveTimeout);
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
this.pending.set(id, entry);
|
|
4076
4256
|
this.transport.send({ jsonrpc: "2.0", id, method, params }).catch((err) => {
|
|
4077
|
-
clearTimeout(
|
|
4257
|
+
clearTimeout(entry.timeoutHandle);
|
|
4078
4258
|
this.pending.delete(id);
|
|
4079
4259
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4080
4260
|
reject(new ACPSessionError("protocol_error", `send ${method} failed: ${msg}`, err));
|
|
@@ -4178,7 +4358,7 @@ var ACPSession = class _ACPSession {
|
|
|
4178
4358
|
progressHandler: this.progressHandler,
|
|
4179
4359
|
allocId: this.allocId,
|
|
4180
4360
|
sendRequest: this.sendRequest,
|
|
4181
|
-
|
|
4361
|
+
promptIdleTimeoutMs: this.promptIdleTimeoutMs,
|
|
4182
4362
|
scratch: this.scratch
|
|
4183
4363
|
};
|
|
4184
4364
|
return this;
|
|
@@ -4263,6 +4443,67 @@ var ToolTranslator = class {
|
|
|
4263
4443
|
}
|
|
4264
4444
|
};
|
|
4265
4445
|
|
|
4446
|
+
// src/integration/acp-agent-failure.ts
|
|
4447
|
+
var USAGE_LIMIT_PREFIXES = [
|
|
4448
|
+
"You've hit your",
|
|
4449
|
+
"You've reached your",
|
|
4450
|
+
"You're out of usage credits",
|
|
4451
|
+
"Your org is out of usage",
|
|
4452
|
+
"Your seat type doesn't include usage",
|
|
4453
|
+
"Your seat type doesn't include extra usage",
|
|
4454
|
+
"Your usage allocation has been disabled",
|
|
4455
|
+
"Your group's usage limit is set to $0",
|
|
4456
|
+
"requires usage credits",
|
|
4457
|
+
"You're out of extra usage",
|
|
4458
|
+
"Claude AI usage limit reached"
|
|
4459
|
+
];
|
|
4460
|
+
var DEFAULT_QUOTA_HOLD_MS = 15 * 6e4;
|
|
4461
|
+
function parseQuotaResetAt(text, now = Date.now()) {
|
|
4462
|
+
const epoch = /usage limit reached\|(\d{9,11})/i.exec(text);
|
|
4463
|
+
if (epoch?.[1]) return Number(epoch[1]) * 1e3;
|
|
4464
|
+
const m = /\bresets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/i.exec(text);
|
|
4465
|
+
if (!m?.[1]) return void 0;
|
|
4466
|
+
let hour = Number(m[1]);
|
|
4467
|
+
const minute = m[2] ? Number(m[2]) : 0;
|
|
4468
|
+
const meridiem = m[3]?.toLowerCase();
|
|
4469
|
+
if (meridiem === "pm" && hour < 12) hour += 12;
|
|
4470
|
+
if (meridiem === "am" && hour === 12) hour = 0;
|
|
4471
|
+
if (hour > 23 || minute > 59 || !meridiem && !m[2]) return void 0;
|
|
4472
|
+
const at = new Date(now);
|
|
4473
|
+
at.setHours(hour, minute, 0, 0);
|
|
4474
|
+
if (at.getTime() <= now) at.setDate(at.getDate() + 1);
|
|
4475
|
+
return at.getTime();
|
|
4476
|
+
}
|
|
4477
|
+
function classifyAgentFailure(rpc, agent, now = Date.now()) {
|
|
4478
|
+
const text = rpc.message.replace(/^Internal error:\s*/i, "").trim();
|
|
4479
|
+
const errorKind = rpc.data?.errorKind;
|
|
4480
|
+
const isQuota = USAGE_LIMIT_PREFIXES.some((p) => text.startsWith(p) || text.includes(p)) || errorKind === "billing_error" || errorKind === "account_on_hold";
|
|
4481
|
+
if (isQuota) {
|
|
4482
|
+
const resetAt = parseQuotaResetAt(text, now);
|
|
4483
|
+
return {
|
|
4484
|
+
kind: "provider_quota",
|
|
4485
|
+
message: `${agent}: usage quota spent \u2014 ${text || "the account has no usage left"}`,
|
|
4486
|
+
retryable: false,
|
|
4487
|
+
backoffMs: resetAt !== void 0 ? Math.max(0, resetAt - now) : DEFAULT_QUOTA_HOLD_MS
|
|
4488
|
+
};
|
|
4489
|
+
}
|
|
4490
|
+
const message = `${agent}: ${text}`;
|
|
4491
|
+
switch (errorKind) {
|
|
4492
|
+
case "rate_limit":
|
|
4493
|
+
return { kind: "provider_rate_limit", message, retryable: true, backoffMs: 3e4 };
|
|
4494
|
+
case "overloaded":
|
|
4495
|
+
case "server_error":
|
|
4496
|
+
return { kind: "provider_5xx", message, retryable: true, backoffMs: 1e4 };
|
|
4497
|
+
case "authentication_failed":
|
|
4498
|
+
case "oauth_org_not_allowed":
|
|
4499
|
+
case "verification_required":
|
|
4500
|
+
case "cloud_credential_error":
|
|
4501
|
+
return { kind: "provider_auth", message, retryable: false };
|
|
4502
|
+
default:
|
|
4503
|
+
return rpc.code === -32e3 ? { kind: "provider_auth", message, retryable: false } : void 0;
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4266
4507
|
// src/integration/acp-bench.ts
|
|
4267
4508
|
import * as fsp3 from "node:fs/promises";
|
|
4268
4509
|
import * as path4 from "node:path";
|
|
@@ -4498,6 +4739,7 @@ var AGENTS_CATALOG = [
|
|
|
4498
4739
|
fs: true
|
|
4499
4740
|
},
|
|
4500
4741
|
integration: "adapter",
|
|
4742
|
+
contextWindow: 1e6,
|
|
4501
4743
|
docs: "https://docs.anthropic.com/en/docs/claude-code"
|
|
4502
4744
|
},
|
|
4503
4745
|
// ── Google ───────────────────────────────────────────────────────────
|
|
@@ -4752,13 +4994,37 @@ async function makeACPSubagentRunner(options) {
|
|
|
4752
4994
|
};
|
|
4753
4995
|
return wrappedRunner;
|
|
4754
4996
|
}
|
|
4997
|
+
var sessionCostSeen = /* @__PURE__ */ new WeakMap();
|
|
4998
|
+
function recordTurnUsage(ctx, session, result) {
|
|
4999
|
+
const cost = result.usage?.cost;
|
|
5000
|
+
const total = cost?.currency === "USD" && Number.isFinite(cost.amount) && cost.amount >= 0 ? cost.amount : void 0;
|
|
5001
|
+
const turnCost = total === void 0 ? 0 : Math.max(0, total - (sessionCostSeen.get(session) ?? 0));
|
|
5002
|
+
if (total !== void 0) sessionCostSeen.set(session, total);
|
|
5003
|
+
const t = result.tokens;
|
|
5004
|
+
if (!t && turnCost === 0) return void 0;
|
|
5005
|
+
try {
|
|
5006
|
+
ctx.budget.recordUsage(
|
|
5007
|
+
{
|
|
5008
|
+
input: t?.input ?? 0,
|
|
5009
|
+
output: t?.output ?? 0,
|
|
5010
|
+
cacheRead: t?.cacheRead ?? 0,
|
|
5011
|
+
cacheWrite: t?.cacheWrite ?? 0
|
|
5012
|
+
},
|
|
5013
|
+
turnCost
|
|
5014
|
+
);
|
|
5015
|
+
} catch {
|
|
5016
|
+
}
|
|
5017
|
+
return { type: "turn_usage", ...t ? { tokens: t } : {}, costUsd: turnCost };
|
|
5018
|
+
}
|
|
4755
5019
|
async function makeACPSubagentRunnerWithStop(options) {
|
|
4756
5020
|
const projectRoot = options.projectRoot ?? options.cwd ?? process.cwd();
|
|
4757
5021
|
const timeoutMs = options.timeoutMs ?? 5 * 6e4;
|
|
4758
5022
|
const persistent = options.persistent === true;
|
|
4759
5023
|
let shared = null;
|
|
4760
5024
|
let turns = Promise.resolve();
|
|
4761
|
-
const startSession = async () => {
|
|
5025
|
+
const startSession = async (setup) => {
|
|
5026
|
+
const mcpServers = setup?.mcpServers && setup.mcpServers.length > 0 ? [...options.mcpServers ?? [], ...setup.mcpServers] : options.mcpServers;
|
|
5027
|
+
const permissionPolicy = setup?.permissionPolicy ?? options.permissionPolicy;
|
|
4762
5028
|
return ACPSession.start({
|
|
4763
5029
|
command: options.command,
|
|
4764
5030
|
...options.args !== void 0 ? { args: options.args } : {},
|
|
@@ -4766,23 +5032,29 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4766
5032
|
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
4767
5033
|
projectRoot,
|
|
4768
5034
|
timeoutMs,
|
|
5035
|
+
...options.promptIdleTimeoutMs !== void 0 ? { promptIdleTimeoutMs: options.promptIdleTimeoutMs } : {},
|
|
4769
5036
|
role: options.role,
|
|
4770
|
-
...
|
|
4771
|
-
...
|
|
5037
|
+
...permissionPolicy !== void 0 ? { permissionPolicy } : {},
|
|
5038
|
+
...mcpServers !== void 0 ? { mcpServers } : {},
|
|
5039
|
+
...setup?.meta ? { sessionMeta: setup.meta } : {},
|
|
5040
|
+
...options.initialModeId ? { initialModeId: options.initialModeId } : {},
|
|
5041
|
+
...options.requireInitialMode ? { requireInitialMode: true } : {}
|
|
4772
5042
|
});
|
|
4773
5043
|
};
|
|
4774
5044
|
const runner = async (task, ctx) => {
|
|
4775
5045
|
let session;
|
|
5046
|
+
let setup;
|
|
4776
5047
|
try {
|
|
5048
|
+
setup = options.sessionSetup?.(task, ctx);
|
|
4777
5049
|
if (persistent) {
|
|
4778
|
-
shared ??= startSession();
|
|
5050
|
+
shared ??= startSession(setup);
|
|
4779
5051
|
const starting = shared;
|
|
4780
5052
|
session = await starting.catch((err) => {
|
|
4781
5053
|
if (shared === starting) shared = null;
|
|
4782
5054
|
throw err;
|
|
4783
5055
|
});
|
|
4784
5056
|
} else {
|
|
4785
|
-
session = await startSession();
|
|
5057
|
+
session = await startSession(setup);
|
|
4786
5058
|
}
|
|
4787
5059
|
} catch (err) {
|
|
4788
5060
|
throw acpErrorToSubagentError(err, options.role ?? "acp-subagent");
|
|
@@ -4799,14 +5071,20 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4799
5071
|
}
|
|
4800
5072
|
};
|
|
4801
5073
|
let result;
|
|
5074
|
+
let endLive;
|
|
4802
5075
|
try {
|
|
4803
|
-
|
|
5076
|
+
if (session.canSteer()) endLive = options.onTurnLive?.(ctx, task, (t) => session.steer(t));
|
|
5077
|
+
const prompt2 = setup?.promptPrefix ? `${setup.promptPrefix}
|
|
5078
|
+
|
|
5079
|
+
${task.description}` : task.description;
|
|
5080
|
+
const turn = () => session.prompt([textContent(prompt2)], ctx.signal, onProgress);
|
|
4804
5081
|
const run = persistent ? turns.then(turn, turn) : turn();
|
|
4805
5082
|
if (persistent) turns = run;
|
|
4806
5083
|
result = await run;
|
|
4807
5084
|
} catch (err) {
|
|
4808
5085
|
throw acpErrorToSubagentError(err, options.role ?? "acp-subagent");
|
|
4809
5086
|
} finally {
|
|
5087
|
+
endLive?.();
|
|
4810
5088
|
if (!persistent) {
|
|
4811
5089
|
try {
|
|
4812
5090
|
await session.close();
|
|
@@ -4814,6 +5092,8 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4814
5092
|
}
|
|
4815
5093
|
}
|
|
4816
5094
|
}
|
|
5095
|
+
const turnUsage = recordTurnUsage(ctx, session, result);
|
|
5096
|
+
if (turnUsage) onProgress(turnUsage);
|
|
4817
5097
|
const failure = acpTurnFailure(result, options.role ?? "acp-subagent");
|
|
4818
5098
|
if (failure) throw failure;
|
|
4819
5099
|
return {
|
|
@@ -4834,7 +5114,20 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4834
5114
|
};
|
|
4835
5115
|
return { runner, stop };
|
|
4836
5116
|
}
|
|
5117
|
+
function agentRpcError(err) {
|
|
5118
|
+
let cur = err;
|
|
5119
|
+
for (let depth = 0; depth < 4 && cur; depth++) {
|
|
5120
|
+
if (isJsonRpcError(cur)) return cur;
|
|
5121
|
+
cur = cur.cause;
|
|
5122
|
+
}
|
|
5123
|
+
return void 0;
|
|
5124
|
+
}
|
|
4837
5125
|
function acpErrorToSubagentError(err, subagentId) {
|
|
5126
|
+
const rpc = agentRpcError(err);
|
|
5127
|
+
if (rpc) {
|
|
5128
|
+
const classified = classifyAgentFailure(rpc, subagentId);
|
|
5129
|
+
if (classified) return classified;
|
|
5130
|
+
}
|
|
4838
5131
|
if (err instanceof ACPSessionError) {
|
|
4839
5132
|
const kind = mapACPKind(err.kind);
|
|
4840
5133
|
return {
|
|
@@ -4902,6 +5195,9 @@ function mapACPKind(acpKind) {
|
|
|
4902
5195
|
return "bridge_failed";
|
|
4903
5196
|
case "aborted":
|
|
4904
5197
|
return "aborted_by_parent";
|
|
5198
|
+
// The agent went silent mid-turn and the turn was cancelled.
|
|
5199
|
+
case "stalled":
|
|
5200
|
+
return "budget_timeout";
|
|
4905
5201
|
case "closed":
|
|
4906
5202
|
case "unsupported_capability":
|
|
4907
5203
|
return "unknown";
|
|
@@ -5448,7 +5744,8 @@ async function runOneAcpTask(opts) {
|
|
|
5448
5744
|
role,
|
|
5449
5745
|
timeoutMs,
|
|
5450
5746
|
...opts.onProgress !== void 0 ? { onProgress: opts.onProgress } : {},
|
|
5451
|
-
...opts.permissionPolicy !== void 0 ? { permissionPolicy: opts.permissionPolicy } : {}
|
|
5747
|
+
...opts.permissionPolicy !== void 0 ? { permissionPolicy: opts.permissionPolicy } : {},
|
|
5748
|
+
...opts.sessionSetup !== void 0 ? { sessionSetup: opts.sessionSetup } : {}
|
|
5452
5749
|
});
|
|
5453
5750
|
try {
|
|
5454
5751
|
const budget = new SubagentBudget2({
|
|
@@ -5592,6 +5889,7 @@ export {
|
|
|
5592
5889
|
WrongStackACPServer,
|
|
5593
5890
|
assertNeverSessionUpdate,
|
|
5594
5891
|
audioContent,
|
|
5892
|
+
classifyAgentFailure,
|
|
5595
5893
|
defaultEnsembleCmdResolver,
|
|
5596
5894
|
defaultPermissionPolicy,
|
|
5597
5895
|
fetchAcpRegistry,
|