@agentclientprotocol/claude-agent-acp 0.85.2-preview.4 → 0.85.2-preview.6
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 +1 -0
- package/dist/acp-agent.d.ts +43 -10
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +262 -113
- package/dist/air-extension.d.ts +2 -0
- package/dist/air-extension.d.ts.map +1 -1
- package/dist/air-extension.js +2 -0
- package/dist/mcp-command.d.ts +51 -0
- package/dist/mcp-command.d.ts.map +1 -0
- package/dist/mcp-command.js +321 -0
- package/dist/resumed-session.d.ts +16 -6
- package/dist/resumed-session.d.ts.map +1 -1
- package/dist/resumed-session.js +91 -25
- package/dist/session-mode.d.ts +14 -0
- package/dist/session-mode.d.ts.map +1 -1
- package/dist/session-mode.js +22 -0
- package/dist/turn-events.d.ts +3 -2
- package/dist/turn-events.d.ts.map +1 -1
- package/dist/usage-markdown.d.ts +2 -0
- package/dist/usage-markdown.d.ts.map +1 -1
- package/dist/usage-markdown.js +2 -1
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +28 -0
- package/dist/v2/agent.d.ts.map +1 -1
- package/dist/v2/agent.js +27 -5
- package/dist/v2/prompt.d.ts +47 -0
- package/dist/v2/prompt.d.ts.map +1 -0
- package/dist/v2/prompt.js +153 -0
- package/dist/v2/session-update.d.ts.map +1 -1
- package/dist/v2/session-update.js +10 -0
- package/package.json +1 -1
package/dist/acp-agent.js
CHANGED
|
@@ -4,7 +4,7 @@ import { GOAL_ACTIONS, GOAL_CONTROL_METHOD, GOAL_EXTENSION_VERSION, goalUpdateFr
|
|
|
4
4
|
import { sanitizeTitle, SessionTitles } from "./session-titles.js";
|
|
5
5
|
import { AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY, asSdkSessionNotification, clientSupportsSubagents, } from "./acp-subagents.js";
|
|
6
6
|
import { isNativeSubagentControlTool, isNativeSubagentControlUpdate, NativeSubagentRuntime, nativeSubagentState, resumedNativeSubagentId, sendMessageResumePrompt, } from "./native-subagents.js";
|
|
7
|
-
import { AIR_ASYNC_TASKS_CAPABILITY, AIR_DIFF_PATCH_CAPABILITY, AIR_PLAN_FILE_CAPABILITY, AIR_GOAL_KEY, AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, clientSupportsAirCapability, withAirMeta, } from "./air-extension.js";
|
|
7
|
+
import { AIR_ASYNC_TASKS_CAPABILITY, AIR_DIFF_PATCH_CAPABILITY, AIR_PLAN_FILE_CAPABILITY, AIR_GOAL_KEY, AIR_KIND_KEY, AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, AIR_SKILL_PATH_KEY, clientSupportsAirCapability, withAirMeta, } from "./air-extension.js";
|
|
8
8
|
import { AsyncTaskRuntime, backgroundBashTaskFromToolResult, clientSupportsAsyncTasks, } from "./async-tasks.js";
|
|
9
9
|
import { AUTH_STATUS_PROBE_TIMEOUT_MS, AUTH_STATUS_UPDATE_METHOD, authStatusCapability, fromAccountInfo, fromCliStatus, gatewayAuthStatus, mergeAuthStatus, notLoggedInAuthStatus, sameAuthStatus, } from "./auth-status.js";
|
|
10
10
|
import { execFile } from "node:child_process";
|
|
@@ -16,9 +16,9 @@ import packageJson from "../package.json" with { type: "json" };
|
|
|
16
16
|
import { applyAskElicitationResponse, askUserQuestionsToCreateRequest, createElicitationResponseToElicitResult, extractAskUserQuestions, extractRefusalFallbackPrompt, mcpElicitationToCreateRequest, REFUSAL_FALLBACK_DIALOG_KIND, refusalFallbackResultFromResponse, refusalFallbackToCreateRequest, } from "./elicitation.js";
|
|
17
17
|
import { forkSession } from "./fork-session.js";
|
|
18
18
|
import { subagentHistory } from "./subagent-history.js";
|
|
19
|
-
import {
|
|
19
|
+
import { readResumedSession, readResumedTail, } from "./resumed-session.js";
|
|
20
20
|
import { SessionTiming } from "./session-timing.js";
|
|
21
|
-
import { ALLOW_BYPASS
|
|
21
|
+
import { ALLOW_BYPASS } from "./permissions/modes.js";
|
|
22
22
|
import { normalizeDurablePermissionChangeSet } from "./permissions/normalization.js";
|
|
23
23
|
import { buildClaudePermissionOptions } from "./permissions/options.js";
|
|
24
24
|
import { buildClaudePermissionPresentation } from "./permissions/presentation.js";
|
|
@@ -36,11 +36,13 @@ import { ChangedMetaFilter } from "./tool-calls/changed-meta-filter.js";
|
|
|
36
36
|
import { ToolCallFieldTracker } from "./tool-calls/field-tracker.js";
|
|
37
37
|
import { ClientCapabilities as ToolCallClientCapabilities } from "./tool-calls/client-capabilities.js";
|
|
38
38
|
import { AcpToolCallRenderer } from "./tool-calls/renderer.js";
|
|
39
|
-
import {
|
|
39
|
+
import { resolveSkillPath } from "./tool-calls/reporters/interaction.js";
|
|
40
|
+
import { nodeToWebReadable, nodeToWebWritable, Pushable, raceTimeoutAndAbort, unreachable, } from "./utils.js";
|
|
40
41
|
import { acceptedPlanToolResult, ExitPlanCoordinator, isExitPlanInterruptionResult, observeExitPlanToolResults, } from "./exit-plan.js";
|
|
41
42
|
import { parseToolResultMeta } from "./tool-result-meta.js";
|
|
42
43
|
import { formatUsageResponse, isUsageCommandText, parseUsageResponse } from "./usage-markdown.js";
|
|
43
|
-
import {
|
|
44
|
+
import { MCP_AVAILABLE_COMMAND, parseMcpCommand, runMcpCommand } from "./mcp-command.js";
|
|
45
|
+
import { MODE_CONFIG_ID, resolveInitialPermissionMode, SessionModeManager, } from "./session-mode.js";
|
|
44
46
|
import { applyAvailableModelsAllowlist, buildModelConfigOption, getAvailableModels, MODEL_CONFIG_ID, resolveModelPreference, } from "./session-model.js";
|
|
45
47
|
import { buildEffortConfigOption, EFFORT_CONFIG_ID, effortFlagSettings, mergeEffortSettings, settingsEffortForModel, } from "./session-effort.js";
|
|
46
48
|
export { EFFORT_CONFIG_ID, settingsEffortForModel } from "./session-effort.js";
|
|
@@ -105,32 +107,18 @@ function settleLiveBackgroundTask(session, taskId) {
|
|
|
105
107
|
async function structuredUsageMarkdown(query, signal, logger) {
|
|
106
108
|
if (signal.aborted)
|
|
107
109
|
return null;
|
|
108
|
-
let timeout;
|
|
109
|
-
let onAbort;
|
|
110
110
|
try {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
timeout.unref?.();
|
|
118
|
-
}),
|
|
119
|
-
new Promise((resolve) => {
|
|
120
|
-
onAbort = () => resolve(null);
|
|
121
|
-
if (signal.aborted)
|
|
122
|
-
onAbort();
|
|
123
|
-
else
|
|
124
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
125
|
-
}),
|
|
126
|
-
]);
|
|
127
|
-
if (response === null) {
|
|
128
|
-
if (!signal.aborted) {
|
|
111
|
+
const outcome = await raceTimeoutAndAbort(
|
|
112
|
+
// Keeping the deliberately unstable method name visible makes an SDK
|
|
113
|
+
// upgrade fail at compile time if Anthropic removes or renames it.
|
|
114
|
+
query.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET(), STRUCTURED_USAGE_TIMEOUT_MS, signal);
|
|
115
|
+
if (outcome.type !== "done") {
|
|
116
|
+
if (outcome.type === "timeout") {
|
|
129
117
|
logger.error("Structured /usage timed out; preserving Claude Code output");
|
|
130
118
|
}
|
|
131
119
|
return null;
|
|
132
120
|
}
|
|
133
|
-
const usage = parseUsageResponse(
|
|
121
|
+
const usage = parseUsageResponse(outcome.value);
|
|
134
122
|
if (!usage) {
|
|
135
123
|
logger.error("Structured /usage returned an incompatible response; preserving Claude Code output");
|
|
136
124
|
return null;
|
|
@@ -141,12 +129,6 @@ async function structuredUsageMarkdown(query, signal, logger) {
|
|
|
141
129
|
logger.error(`Structured /usage failed; preserving Claude Code output: ${error}`);
|
|
142
130
|
return null;
|
|
143
131
|
}
|
|
144
|
-
finally {
|
|
145
|
-
if (timeout)
|
|
146
|
-
clearTimeout(timeout);
|
|
147
|
-
if (onAbort)
|
|
148
|
-
signal.removeEventListener("abort", onAbort);
|
|
149
|
-
}
|
|
150
132
|
}
|
|
151
133
|
/** Claude Code keeps the OAuth callback listener open in the background after
|
|
152
134
|
* `mcpAuthenticate` returns the authorization URL. The SDK does not expose
|
|
@@ -680,6 +662,7 @@ const REPLAY_HIDDEN_COMMANDS = new Set([
|
|
|
680
662
|
...LOCAL_ONLY_COMMANDS,
|
|
681
663
|
"/compact",
|
|
682
664
|
"/model",
|
|
665
|
+
"/mcp",
|
|
683
666
|
"/status",
|
|
684
667
|
"/usage",
|
|
685
668
|
]);
|
|
@@ -1017,12 +1000,19 @@ const mcpAuthentications = new WeakMap();
|
|
|
1017
1000
|
* authentication. This runs after session creation has returned so an
|
|
1018
1001
|
* interactive browser flow never delays `session/new`. */
|
|
1019
1002
|
function startMcpAuthentication(host, sessionId, mcpServers) {
|
|
1020
|
-
if (
|
|
1003
|
+
if (mcpServers.length === 0)
|
|
1004
|
+
return;
|
|
1005
|
+
startMcpServerAuthentication(host, sessionId, new Set(mcpServers.map((server) => server.name)));
|
|
1006
|
+
}
|
|
1007
|
+
/** Start OAuth in the background for the servers in `requestedServers` that
|
|
1008
|
+
* Claude reports as needing authentication. Startup and `/mcp` use it. It
|
|
1009
|
+
* does nothing while another OAuth run of the session is in progress. */
|
|
1010
|
+
function startMcpServerAuthentication(host, sessionId, requestedServers) {
|
|
1011
|
+
if (!host.clientCapabilities?.elicitation?.url)
|
|
1021
1012
|
return;
|
|
1022
1013
|
const session = host.sessions[sessionId];
|
|
1023
1014
|
if (!session || mcpAuthentications.has(session))
|
|
1024
1015
|
return;
|
|
1025
|
-
const requestedServers = new Set(mcpServers.map((server) => server.name));
|
|
1026
1016
|
const authentication = authenticateMcpServers(host, sessionId, session.query, requestedServers)
|
|
1027
1017
|
.catch((error) => {
|
|
1028
1018
|
if (!session.abortController.signal.aborted) {
|
|
@@ -1402,6 +1392,56 @@ export class ClaudeAcpAgent {
|
|
|
1402
1392
|
}, 0);
|
|
1403
1393
|
return response;
|
|
1404
1394
|
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Start the CLI of a loaded session, and replay the history while the CLI
|
|
1397
|
+
* starts. The CLI start takes most of the load time, and the replay needs
|
|
1398
|
+
* only the transcript and the {@link ReplayState}. An invalid `cwd` fails
|
|
1399
|
+
* before the first update. A failed CLI start stops the replay.
|
|
1400
|
+
*/
|
|
1401
|
+
async createSessionWhileReplaying(params, timing) {
|
|
1402
|
+
await this.validateCwd(params.cwd);
|
|
1403
|
+
const pending = {
|
|
1404
|
+
state: {
|
|
1405
|
+
cwd: params.cwd,
|
|
1406
|
+
taskState: new Map(),
|
|
1407
|
+
forwardSubagentText: this.forwardsSubagentText(params._meta),
|
|
1408
|
+
messageIdToUuid: new Map(),
|
|
1409
|
+
sessionFailureState: createSessionFailureState(),
|
|
1410
|
+
},
|
|
1411
|
+
stopped: false,
|
|
1412
|
+
};
|
|
1413
|
+
const transcript = readResumedSession(params.sessionId, this.logger);
|
|
1414
|
+
const replay = transcript
|
|
1415
|
+
.then(({ messages }) => this.replaySessionHistory(params.sessionId, messages, pending))
|
|
1416
|
+
.then(() => ({ ok: true }), (error) => ({ ok: false, error }));
|
|
1417
|
+
let response;
|
|
1418
|
+
try {
|
|
1419
|
+
response = await this.createSession({
|
|
1420
|
+
cwd: params.cwd,
|
|
1421
|
+
mcpServers: params.mcpServers ?? [],
|
|
1422
|
+
additionalDirectories: params.additionalDirectories,
|
|
1423
|
+
_meta: params._meta,
|
|
1424
|
+
}, {
|
|
1425
|
+
resume: params.sessionId,
|
|
1426
|
+
resumedModelHint: transcript.then(({ model }) => model),
|
|
1427
|
+
replayState: pending.state,
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
catch (error) {
|
|
1431
|
+
pending.stopped = true;
|
|
1432
|
+
await replay;
|
|
1433
|
+
throw error;
|
|
1434
|
+
}
|
|
1435
|
+
timing.phase("session-ready");
|
|
1436
|
+
const replayed = await replay;
|
|
1437
|
+
if (!replayed.ok)
|
|
1438
|
+
throw replayed.error;
|
|
1439
|
+
return {
|
|
1440
|
+
sessionId: response.sessionId,
|
|
1441
|
+
modes: response.modes,
|
|
1442
|
+
configOptions: response.configOptions,
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1405
1445
|
async unstable_forkSession(params) {
|
|
1406
1446
|
if (this.providerUpdate)
|
|
1407
1447
|
await this.providerUpdate;
|
|
@@ -1426,10 +1466,16 @@ export class ClaudeAcpAgent {
|
|
|
1426
1466
|
const timing = new SessionTiming(this.logger, "load", params.sessionId);
|
|
1427
1467
|
if (this.providerUpdate)
|
|
1428
1468
|
await this.providerUpdate;
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1469
|
+
let result;
|
|
1470
|
+
if (this.sessions[params.sessionId]) {
|
|
1471
|
+
const resumedSession = await readResumedSession(params.sessionId, this.logger);
|
|
1472
|
+
result = await this.getOrCreateSession(params, resumedSession);
|
|
1473
|
+
timing.phase("session-ready");
|
|
1474
|
+
await this.replaySessionHistory(params.sessionId, resumedSession.messages);
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
result = await this.createSessionWhileReplaying(params, timing);
|
|
1478
|
+
}
|
|
1433
1479
|
timing.phase("replay");
|
|
1434
1480
|
// Send available commands after replay so it doesn't interleave with history
|
|
1435
1481
|
setTimeout(() => {
|
|
@@ -1812,6 +1858,47 @@ export class ClaudeAcpAgent {
|
|
|
1812
1858
|
this.setAuthStatus(notLoggedInAuthStatus());
|
|
1813
1859
|
}
|
|
1814
1860
|
}
|
|
1861
|
+
/** The structured replacement for a prompt that is exactly `/usage` or a
|
|
1862
|
+
* `/mcp` command. Claude Code runs the command, and the replacement takes
|
|
1863
|
+
* the place of its text. Claude Code refuses a `/mcp` reconnect, enable,
|
|
1864
|
+
* or disable in SDK mode. The `/mcp` producer runs them through the SDK
|
|
1865
|
+
* control API when the text arrives, so the action stays in the turn. */
|
|
1866
|
+
localCommandMarkdown(params) {
|
|
1867
|
+
const text = params.prompt.length === 1 && params.prompt[0]?.type === "text"
|
|
1868
|
+
? params.prompt[0].text
|
|
1869
|
+
: undefined;
|
|
1870
|
+
if (text === undefined)
|
|
1871
|
+
return undefined;
|
|
1872
|
+
if (isUsageCommandText(text)) {
|
|
1873
|
+
return {
|
|
1874
|
+
startsAtActivation: true,
|
|
1875
|
+
produce: (query, signal) => structuredUsageMarkdown(query, signal, this.logger),
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
const command = parseMcpCommand(text);
|
|
1879
|
+
if (!command)
|
|
1880
|
+
return undefined;
|
|
1881
|
+
let reconnected = [];
|
|
1882
|
+
return {
|
|
1883
|
+
startsAtActivation: false,
|
|
1884
|
+
produce: async (query, signal, originalOutput) => {
|
|
1885
|
+
const outcome = await runMcpCommand(query, command, originalOutput, signal, (message) => this.logger.error(message));
|
|
1886
|
+
reconnected = outcome.reconnected;
|
|
1887
|
+
return outcome.markdown;
|
|
1888
|
+
},
|
|
1889
|
+
// A reconnect starts OAuth for the ACP servers that it tried, as the
|
|
1890
|
+
// session start does. A plain `/mcp` and a cancelled turn start none.
|
|
1891
|
+
afterDelivery: () => {
|
|
1892
|
+
const session = this.sessions[params.sessionId];
|
|
1893
|
+
if (!session || session.cancelled)
|
|
1894
|
+
return;
|
|
1895
|
+
const acpServers = new Set((session.creationParams?.mcpServers ?? []).map((server) => server.name));
|
|
1896
|
+
const servers = new Set(reconnected.filter((server) => acpServers.has(server)));
|
|
1897
|
+
if (servers.size > 0)
|
|
1898
|
+
startMcpServerAuthentication(this, params.sessionId, servers);
|
|
1899
|
+
},
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1815
1902
|
/**
|
|
1816
1903
|
* Serves an ACP v1 `session/prompt`, which answers when the turn ends. The
|
|
1817
1904
|
* outcome of a turn is field for field a v1 prompt response.
|
|
@@ -1878,10 +1965,8 @@ export class ClaudeAcpAgent {
|
|
|
1878
1965
|
const firstText = params.prompt[0]?.type === "text" ? params.prompt[0].text : "";
|
|
1879
1966
|
const isLocalOnlyCommand = firstText.startsWith("/") && LOCAL_ONLY_COMMANDS.has(firstText.split(" ", 1)[0]);
|
|
1880
1967
|
const fileChangeReport = session.fileChangeReporter?.request(params._meta);
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
isUsageCommandText(params.prompt[0].text);
|
|
1884
|
-
const usageMarkdownAbort = isUsageCommand ? new AbortController() : undefined;
|
|
1968
|
+
const localCommand = this.localCommandMarkdown(params);
|
|
1969
|
+
const localCommandAbort = localCommand ? new AbortController() : undefined;
|
|
1885
1970
|
session.titles.onPrompt(params.prompt);
|
|
1886
1971
|
// Each prompt is a Turn that the persistent consumer settles once the
|
|
1887
1972
|
// turn's outcome is known. `startTurn()` owns no loop: it enqueues the
|
|
@@ -1892,8 +1977,8 @@ export class ClaudeAcpAgent {
|
|
|
1892
1977
|
promptUuid,
|
|
1893
1978
|
events,
|
|
1894
1979
|
isLocalOnlyCommand,
|
|
1895
|
-
...(
|
|
1896
|
-
...(
|
|
1980
|
+
...(localCommand ? { localCommand } : {}),
|
|
1981
|
+
...(localCommandAbort ? { localCommandAbort } : {}),
|
|
1897
1982
|
...(fileChangeReport ? { fileChangeReport } : {}),
|
|
1898
1983
|
settled: false,
|
|
1899
1984
|
completion: new Promise((resolve) => {
|
|
@@ -1903,8 +1988,8 @@ export class ClaudeAcpAgent {
|
|
|
1903
1988
|
events.ended(outcome);
|
|
1904
1989
|
completeTurn();
|
|
1905
1990
|
},
|
|
1906
|
-
reject: (error) => {
|
|
1907
|
-
events.failed(error);
|
|
1991
|
+
reject: (error, title) => {
|
|
1992
|
+
events.failed(error, title);
|
|
1908
1993
|
completeTurn();
|
|
1909
1994
|
},
|
|
1910
1995
|
};
|
|
@@ -2015,7 +2100,10 @@ export class ClaudeAcpAgent {
|
|
|
2015
2100
|
try {
|
|
2016
2101
|
// `resume` names the Claude session, which shares the ACP session id, so
|
|
2017
2102
|
// the new query continues the same conversation under the same id.
|
|
2018
|
-
await this.createSession(creationParams, {
|
|
2103
|
+
await this.createSession(creationParams, {
|
|
2104
|
+
resume: sessionId,
|
|
2105
|
+
permissionMode: session.modes.currentModeId,
|
|
2106
|
+
});
|
|
2019
2107
|
}
|
|
2020
2108
|
catch (error) {
|
|
2021
2109
|
if (error instanceof RequestError && error.code === RequestError.resourceNotFound().code) {
|
|
@@ -2025,7 +2113,10 @@ export class ClaudeAcpAgent {
|
|
|
2025
2113
|
// better answer than a session the client can never use again.
|
|
2026
2114
|
this.logger.log(`session ${sessionId} was never persisted; starting a fresh query under the same id`);
|
|
2027
2115
|
try {
|
|
2028
|
-
await this.createSession(creationParams, {
|
|
2116
|
+
await this.createSession(creationParams, {
|
|
2117
|
+
reuseSessionId: sessionId,
|
|
2118
|
+
permissionMode: session.modes.currentModeId,
|
|
2119
|
+
});
|
|
2029
2120
|
return;
|
|
2030
2121
|
}
|
|
2031
2122
|
catch (fallbackError) {
|
|
@@ -2487,11 +2578,11 @@ export class ClaudeAcpAgent {
|
|
|
2487
2578
|
};
|
|
2488
2579
|
};
|
|
2489
2580
|
const internalErrorForClient = (data, rawDetail) => RequestError.internalError(data, supportsAirSessionFailures(this.clientCapabilities) ? undefined : rawDetail);
|
|
2490
|
-
const
|
|
2491
|
-
if (!turn.
|
|
2581
|
+
const ensureLocalCommandMarkdown = (turn, originalOutput = "") => {
|
|
2582
|
+
if (!turn.localCommand || !turn.localCommandAbort)
|
|
2492
2583
|
return undefined;
|
|
2493
|
-
turn.
|
|
2494
|
-
return turn.
|
|
2584
|
+
turn.localCommandMarkdown ??= turn.localCommand.produce(session.query, turn.localCommandAbort.signal, originalOutput);
|
|
2585
|
+
return turn.localCommandMarkdown;
|
|
2495
2586
|
};
|
|
2496
2587
|
const resetTurnScratch = () => {
|
|
2497
2588
|
lastAssistantTotalUsage = null;
|
|
@@ -2534,7 +2625,8 @@ export class ClaudeAcpAgent {
|
|
|
2534
2625
|
session.activeTurn = turn;
|
|
2535
2626
|
session.cancelled = false;
|
|
2536
2627
|
compaction.resume();
|
|
2537
|
-
|
|
2628
|
+
if (turn.localCommand?.startsAtActivation)
|
|
2629
|
+
ensureLocalCommandMarkdown(turn);
|
|
2538
2630
|
session.pendingOrphanResults = 0;
|
|
2539
2631
|
session.orphanCommands?.clear();
|
|
2540
2632
|
// Two-phase sweep of registry entries the level signal ended (see
|
|
@@ -2732,27 +2824,28 @@ export class ClaudeAcpAgent {
|
|
|
2732
2824
|
* the autonomous stretch-close guard. */
|
|
2733
2825
|
const firstUnsettledQueuedTurn = () => (session.turnQueue ?? []).find((t) => !t.settled);
|
|
2734
2826
|
/** Claim the structured replacement for the turn currently producing a
|
|
2735
|
-
* local-command output. Undefined means this is not a structured
|
|
2736
|
-
* turn (or its request failed), null means another SDK
|
|
2737
|
-
* already delivered it, and string is the one replacement
|
|
2738
|
-
|
|
2827
|
+
* local-command output. Undefined means this is not a structured
|
|
2828
|
+
* local-command turn (or its request failed), null means another SDK
|
|
2829
|
+
* message shape already delivered it, and string is the one replacement
|
|
2830
|
+
* to publish. */
|
|
2831
|
+
const takeLocalCommandMarkdown = async (originalOutput) => {
|
|
2739
2832
|
const turn = session.activeTurn ?? firstUnsettledQueuedTurn();
|
|
2740
2833
|
if (!turn)
|
|
2741
2834
|
return undefined;
|
|
2742
|
-
const
|
|
2743
|
-
if (!
|
|
2835
|
+
const pending = ensureLocalCommandMarkdown(turn, originalOutput);
|
|
2836
|
+
if (!pending)
|
|
2744
2837
|
return undefined;
|
|
2745
|
-
const markdown = await
|
|
2838
|
+
const markdown = await pending;
|
|
2746
2839
|
if (markdown === null)
|
|
2747
2840
|
return undefined;
|
|
2748
|
-
if (turn.
|
|
2841
|
+
if (turn.localCommandDelivered) {
|
|
2749
2842
|
// Different SDK message shapes can mirror the same local-command
|
|
2750
2843
|
// output. Suppress an exact mirror, but let a later, distinct frame
|
|
2751
2844
|
// (for example an interruption diagnostic) follow the normal path.
|
|
2752
|
-
return turn.
|
|
2845
|
+
return turn.localCommandOriginalOutput === originalOutput ? null : undefined;
|
|
2753
2846
|
}
|
|
2754
|
-
turn.
|
|
2755
|
-
turn.
|
|
2847
|
+
turn.localCommandDelivered = true;
|
|
2848
|
+
turn.localCommandOriginalOutput = originalOutput;
|
|
2756
2849
|
return markdown;
|
|
2757
2850
|
};
|
|
2758
2851
|
/** Registers a live background task (see `liveBackgroundTasks`). A
|
|
@@ -2905,7 +2998,9 @@ export class ClaudeAcpAgent {
|
|
|
2905
2998
|
// Captured before the settled flip below (isHeldOpen tests !settled).
|
|
2906
2999
|
const wasHeld = isHeldOpen(turn);
|
|
2907
3000
|
turn.settled = true;
|
|
2908
|
-
turn.
|
|
3001
|
+
turn.localCommandAbort?.abort();
|
|
3002
|
+
if (turn.localCommandDelivered)
|
|
3003
|
+
turn.localCommand?.afterDelivery?.();
|
|
2909
3004
|
disarmForceCancel(session);
|
|
2910
3005
|
session.turnQueue = (session.turnQueue ?? []).filter((t) => t !== turn);
|
|
2911
3006
|
session.activeTurn = null;
|
|
@@ -2929,7 +3024,7 @@ export class ClaudeAcpAgent {
|
|
|
2929
3024
|
};
|
|
2930
3025
|
/** Reject the active turn (auth required, error result, …) without tearing
|
|
2931
3026
|
* down the consumer: the stream continues to idle and later turns proceed. */
|
|
2932
|
-
const failActive = (error) => {
|
|
3027
|
+
const failActive = (error, title) => {
|
|
2933
3028
|
disarmForceCancel(session);
|
|
2934
3029
|
const turn = session.activeTurn;
|
|
2935
3030
|
if (!turn || turn.settled) {
|
|
@@ -2946,7 +3041,7 @@ export class ClaudeAcpAgent {
|
|
|
2946
3041
|
// start the next stretch clean, or its stale delivery record would
|
|
2947
3042
|
// suppress the next turn's issue-#453 result-text fallback.
|
|
2948
3043
|
session.emittedAssistantText = false;
|
|
2949
|
-
turn.reject(error);
|
|
3044
|
+
turn.reject(error, title);
|
|
2950
3045
|
};
|
|
2951
3046
|
/** Complete a negotiated terminal failure on the prompt response itself,
|
|
2952
3047
|
* which is the canonical AIR carrier. Legacy clients keep the historical
|
|
@@ -2970,7 +3065,7 @@ export class ClaudeAcpAgent {
|
|
|
2970
3065
|
return;
|
|
2971
3066
|
}
|
|
2972
3067
|
if (!supportsAirSessionFailures(this.clientCapabilities)) {
|
|
2973
|
-
failActive(error);
|
|
3068
|
+
failActive(error, title);
|
|
2974
3069
|
return;
|
|
2975
3070
|
}
|
|
2976
3071
|
if (!session.activeTurn || session.activeTurn.settled) {
|
|
@@ -2980,7 +3075,7 @@ export class ClaudeAcpAgent {
|
|
|
2980
3075
|
}
|
|
2981
3076
|
const failure = await createSessionFailure(kind, { title });
|
|
2982
3077
|
if (!failure) {
|
|
2983
|
-
failActive(error);
|
|
3078
|
+
failActive(error, title);
|
|
2984
3079
|
return;
|
|
2985
3080
|
}
|
|
2986
3081
|
sessionFailures.recordActive(failure);
|
|
@@ -3365,17 +3460,18 @@ export class ClaudeAcpAgent {
|
|
|
3365
3460
|
if (compaction.consumeDuplicateErrorOutput(message.content)) {
|
|
3366
3461
|
break;
|
|
3367
3462
|
}
|
|
3368
|
-
const
|
|
3369
|
-
const
|
|
3370
|
-
if (
|
|
3463
|
+
const commandTurn = session.activeTurn ?? firstUnsettledQueuedTurn();
|
|
3464
|
+
const commandMarkdown = await takeLocalCommandMarkdown(message.content);
|
|
3465
|
+
if (commandTurn?.localCommand && session.cancelled)
|
|
3371
3466
|
break;
|
|
3372
|
-
if (
|
|
3467
|
+
if (commandMarkdown === null)
|
|
3373
3468
|
break;
|
|
3374
3469
|
await sendUpdate({
|
|
3375
3470
|
sessionId: params.sessionId,
|
|
3376
3471
|
update: {
|
|
3377
3472
|
sessionUpdate: "agent_message_chunk",
|
|
3378
|
-
content: { type: "text", text:
|
|
3473
|
+
content: { type: "text", text: commandMarkdown ?? message.content },
|
|
3474
|
+
messageId: messageIdForGrouping(message),
|
|
3379
3475
|
},
|
|
3380
3476
|
});
|
|
3381
3477
|
break;
|
|
@@ -3534,11 +3630,12 @@ export class ClaudeAcpAgent {
|
|
|
3534
3630
|
// list with this payload. Forward message.commands directly —
|
|
3535
3631
|
// it's authoritative, and re-querying supportedCommands()
|
|
3536
3632
|
// would just return the same list with an extra round-trip.
|
|
3633
|
+
session.skillPaths = new Map();
|
|
3537
3634
|
await sendUpdate({
|
|
3538
3635
|
sessionId: params.sessionId,
|
|
3539
3636
|
update: {
|
|
3540
3637
|
sessionUpdate: "available_commands_update",
|
|
3541
|
-
availableCommands: getAvailableSlashCommands(message.commands, session.terminalSlashCommands),
|
|
3638
|
+
availableCommands: getAvailableSlashCommands(message.commands, session.terminalSlashCommands, this.toolCallCapabilities.air.client ? session.cwd : undefined, session.skillPaths),
|
|
3542
3639
|
},
|
|
3543
3640
|
});
|
|
3544
3641
|
break;
|
|
@@ -4310,6 +4407,7 @@ export class ClaudeAcpAgent {
|
|
|
4310
4407
|
update: {
|
|
4311
4408
|
sessionUpdate: "agent_message_chunk",
|
|
4312
4409
|
content: { type: "text", text: lastRefusalExplanation },
|
|
4410
|
+
messageId: messageIdForGrouping(message),
|
|
4313
4411
|
},
|
|
4314
4412
|
});
|
|
4315
4413
|
}
|
|
@@ -4384,10 +4482,13 @@ export class ClaudeAcpAgent {
|
|
|
4384
4482
|
(message.usage.output_tokens ?? 0) === 0 &&
|
|
4385
4483
|
!session.activeTurn?.noticeTexts?.includes(normalizeNoticeText(message.result)));
|
|
4386
4484
|
if (shouldForwardResult) {
|
|
4387
|
-
const
|
|
4388
|
-
|
|
4485
|
+
const commandTurn = session.activeTurn ?? firstUnsettledQueuedTurn();
|
|
4486
|
+
const commandMarkdown = await takeLocalCommandMarkdown(message.result);
|
|
4487
|
+
if (commandTurn?.localCommand && session.cancelled)
|
|
4389
4488
|
break;
|
|
4390
|
-
|
|
4489
|
+
if (commandMarkdown === null)
|
|
4490
|
+
break;
|
|
4491
|
+
for (const notification of toAcpNotifications(commandMarkdown ?? message.result, "assistant", params.sessionId, session.toolUseCache, routedNotificationClient, this.logger, { messageId: messageIdForGrouping(message) })) {
|
|
4391
4492
|
await sendUpdate(notification);
|
|
4392
4493
|
}
|
|
4393
4494
|
}
|
|
@@ -4780,16 +4881,16 @@ export class ClaudeAcpAgent {
|
|
|
4780
4881
|
// Depending on the Claude Code build, a local command can arrive
|
|
4781
4882
|
// as the dedicated system message above or as a synthetic
|
|
4782
4883
|
// assistant message. Replace only the output owned by the exact
|
|
4783
|
-
// /usage turn; no content signatures or text parsing are involved.
|
|
4884
|
+
// /usage or /mcp turn; no content signatures or text parsing are involved.
|
|
4784
4885
|
if (message.type === "assistant" &&
|
|
4785
4886
|
message.parent_tool_use_id === null &&
|
|
4786
4887
|
message.message.model === "<synthetic>") {
|
|
4787
|
-
const
|
|
4888
|
+
const commandMarkdown = await takeLocalCommandMarkdown(assistantMessageText(message.message) ?? "");
|
|
4788
4889
|
if (session.cancelled)
|
|
4789
4890
|
break;
|
|
4790
|
-
if (
|
|
4791
|
-
if (
|
|
4792
|
-
for (const notification of toAcpNotifications(
|
|
4891
|
+
if (commandMarkdown !== undefined) {
|
|
4892
|
+
if (commandMarkdown !== null) {
|
|
4893
|
+
for (const notification of toAcpNotifications(commandMarkdown, "assistant", params.sessionId, session.toolUseCache, routedNotificationClient, this.logger, { messageId: messageIdForGrouping(message) })) {
|
|
4793
4894
|
await sendUpdate(notification);
|
|
4794
4895
|
}
|
|
4795
4896
|
}
|
|
@@ -5089,7 +5190,7 @@ export class ClaudeAcpAgent {
|
|
|
5089
5190
|
}
|
|
5090
5191
|
session.cancelled = true;
|
|
5091
5192
|
for (const turn of session.turnQueue ?? [])
|
|
5092
|
-
turn.
|
|
5193
|
+
turn.localCommandAbort?.abort();
|
|
5093
5194
|
session.pendingExitPlanModeInterruption = undefined;
|
|
5094
5195
|
session.pendingExitPlanContextReset = undefined;
|
|
5095
5196
|
// The stream already ended (see closeQueryStream): every in-flight turn was
|
|
@@ -5531,13 +5632,15 @@ export class ClaudeAcpAgent {
|
|
|
5531
5632
|
await this.applyConfigOptionValue(params.sessionId, session, params.configId, effectiveValue);
|
|
5532
5633
|
return { configOptions: session.configOptions };
|
|
5533
5634
|
}
|
|
5534
|
-
async replaySessionHistory(sessionId, resumedMessages) {
|
|
5635
|
+
async replaySessionHistory(sessionId, resumedMessages, pending) {
|
|
5535
5636
|
const replayStartedAt = performance.now();
|
|
5536
5637
|
const toolUseCache = {};
|
|
5537
5638
|
const messages = resumedMessages ?? (await getSessionMessages(sessionId));
|
|
5538
5639
|
const historyLoadedAt = performance.now();
|
|
5539
5640
|
this.logger.log(`[session/replay] sessionId=${sessionId} phase=read durationMs=${Math.round(historyLoadedAt - replayStartedAt)} messages=${messages.length}`);
|
|
5540
|
-
|
|
5641
|
+
// A pending replay has no session record yet. It uses the state that the record takes later.
|
|
5642
|
+
const replayState = () => pending?.state ?? this.sessions[sessionId];
|
|
5643
|
+
const session = replayState();
|
|
5541
5644
|
// A replay rebuilds the client view, so its plan goes out again.
|
|
5542
5645
|
if (session?.taskState)
|
|
5543
5646
|
forgetPublishedTaskPlan(session.taskState);
|
|
@@ -5549,7 +5652,12 @@ export class ClaudeAcpAgent {
|
|
|
5549
5652
|
sessionId,
|
|
5550
5653
|
state: session.sessionFailureState,
|
|
5551
5654
|
capabilities: this.clientCapabilities,
|
|
5552
|
-
isCurrent: () =>
|
|
5655
|
+
isCurrent: () => {
|
|
5656
|
+
const live = this.sessions[sessionId];
|
|
5657
|
+
if (!live)
|
|
5658
|
+
return pending !== undefined && !pending.stopped;
|
|
5659
|
+
return live.sessionFailureState === session.sessionFailureState;
|
|
5660
|
+
},
|
|
5553
5661
|
sendUpdate: (notification) => this.client.sessionUpdate(notification),
|
|
5554
5662
|
logger: this.logger,
|
|
5555
5663
|
})
|
|
@@ -5704,7 +5812,7 @@ export class ClaudeAcpAgent {
|
|
|
5704
5812
|
await replayAsyncTasks.taskNotification(notification);
|
|
5705
5813
|
};
|
|
5706
5814
|
const replayMessage = async (message) => {
|
|
5707
|
-
if (isReplayHiddenMetaMessage(message)) {
|
|
5815
|
+
if (pending?.stopped || isReplayHiddenMetaMessage(message)) {
|
|
5708
5816
|
return;
|
|
5709
5817
|
}
|
|
5710
5818
|
if (message.type === "user" &&
|
|
@@ -5718,7 +5826,7 @@ export class ClaudeAcpAgent {
|
|
|
5718
5826
|
// a client-supplied id without an extra getSessionMessages read. Not read
|
|
5719
5827
|
// yet (see Session.messageIdToUuid).
|
|
5720
5828
|
const replayMessageId = messageIdForGrouping(message);
|
|
5721
|
-
const replaySession =
|
|
5829
|
+
const replaySession = replayState();
|
|
5722
5830
|
if (replaySession && replayMessageId && message.uuid) {
|
|
5723
5831
|
replaySession.messageIdToUuid.set(replayMessageId, message.uuid);
|
|
5724
5832
|
}
|
|
@@ -5812,8 +5920,8 @@ export class ClaudeAcpAgent {
|
|
|
5812
5920
|
registerHooks: false,
|
|
5813
5921
|
replay: true,
|
|
5814
5922
|
clientCapabilities: this.clientCapabilities,
|
|
5815
|
-
cwd:
|
|
5816
|
-
taskState:
|
|
5923
|
+
cwd: replayState()?.cwd,
|
|
5924
|
+
taskState: replayState()?.taskState,
|
|
5817
5925
|
messageId: replayMessageId,
|
|
5818
5926
|
parentToolUseId,
|
|
5819
5927
|
})) {
|
|
@@ -5858,7 +5966,7 @@ export class ClaudeAcpAgent {
|
|
|
5858
5966
|
};
|
|
5859
5967
|
for (const message of messages)
|
|
5860
5968
|
await replayMessage(message);
|
|
5861
|
-
if (nativeReplayEnabled) {
|
|
5969
|
+
if (nativeReplayEnabled && !pending?.stopped) {
|
|
5862
5970
|
// Claude history persists sidechain messages and Agent/Task tool uses,
|
|
5863
5971
|
// but not task_started/task_updated lifecycle frames. Recover terminal
|
|
5864
5972
|
// state from the launch tool_result. Missing results and malformed or
|
|
@@ -6292,7 +6400,7 @@ export class ClaudeAcpAgent {
|
|
|
6292
6400
|
sessionId,
|
|
6293
6401
|
update: {
|
|
6294
6402
|
sessionUpdate: "available_commands_update",
|
|
6295
|
-
availableCommands: getAvailableSlashCommands(commands, session.terminalSlashCommands),
|
|
6403
|
+
availableCommands: getAvailableSlashCommands(commands, session.terminalSlashCommands, this.toolCallCapabilities.air.client ? session.cwd : undefined, (session.skillPaths ??= new Map())),
|
|
6296
6404
|
},
|
|
6297
6405
|
});
|
|
6298
6406
|
}
|
|
@@ -6613,6 +6721,8 @@ export class ClaudeAcpAgent {
|
|
|
6613
6721
|
}
|
|
6614
6722
|
async getOrCreateSession(params, resumedSession) {
|
|
6615
6723
|
const existingSession = this.sessions[params.sessionId];
|
|
6724
|
+
// A recreated live session keeps its mode, not the mode of the transcript.
|
|
6725
|
+
const livePermissionMode = existingSession?.modes.currentModeId;
|
|
6616
6726
|
if (existingSession) {
|
|
6617
6727
|
const fingerprint = computeSessionFingerprint(params);
|
|
6618
6728
|
if (fingerprint === existingSession.sessionFingerprint) {
|
|
@@ -6628,9 +6738,6 @@ export class ClaudeAcpAgent {
|
|
|
6628
6738
|
// up the new values.
|
|
6629
6739
|
await this.teardownSession(params.sessionId);
|
|
6630
6740
|
}
|
|
6631
|
-
const resumedModelHint = resumedSession
|
|
6632
|
-
? resumedSession.model
|
|
6633
|
-
: await readResumedModel(params.sessionId, this.logger);
|
|
6634
6741
|
const response = await this.createSession({
|
|
6635
6742
|
cwd: params.cwd,
|
|
6636
6743
|
mcpServers: params.mcpServers ?? [],
|
|
@@ -6638,7 +6745,8 @@ export class ClaudeAcpAgent {
|
|
|
6638
6745
|
_meta: params._meta,
|
|
6639
6746
|
}, {
|
|
6640
6747
|
resume: params.sessionId,
|
|
6641
|
-
|
|
6748
|
+
permissionMode: livePermissionMode,
|
|
6749
|
+
...(resumedSession ? { resumedModelHint: resumedSession.model } : {}),
|
|
6642
6750
|
});
|
|
6643
6751
|
return {
|
|
6644
6752
|
sessionId: response.sessionId,
|
|
@@ -6667,6 +6775,12 @@ export class ClaudeAcpAgent {
|
|
|
6667
6775
|
throw RequestError.invalidParams({ cwd }, `\`cwd\` is not a directory: ${cwd}`);
|
|
6668
6776
|
}
|
|
6669
6777
|
}
|
|
6778
|
+
/** Whether the session forwards the text and the thinking of a subagent. */
|
|
6779
|
+
forwardsSubagentText(meta) {
|
|
6780
|
+
return (clientSupportsSubagents(this.clientCapabilities) ||
|
|
6781
|
+
supportsSubagentTranscript(this.clientCapabilities) ||
|
|
6782
|
+
meta?.claudeCode?.options?.forwardSubagentText === true);
|
|
6783
|
+
}
|
|
6670
6784
|
async createSession(params, creationOpts = {}) {
|
|
6671
6785
|
const createStartedAt = performance.now();
|
|
6672
6786
|
// Validate `cwd` up front. The ACP spec requires an absolute path, and the
|
|
@@ -6704,10 +6818,15 @@ export class ClaudeAcpAgent {
|
|
|
6704
6818
|
// sign-out respawn, provider rerouting), so fill the same fast local hint
|
|
6705
6819
|
// here when the caller did not provide it. Never fall back to the slow
|
|
6706
6820
|
// getContextUsage control request.
|
|
6821
|
+
// The same tail read gives the permission mode of the resumed session.
|
|
6707
6822
|
let resumedModelHint = creationOpts.resumedModelHint;
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6823
|
+
let resumedPermissionMode;
|
|
6824
|
+
if (creationOpts.resume !== undefined) {
|
|
6825
|
+
const withModel = !Object.prototype.hasOwnProperty.call(creationOpts, "resumedModelHint");
|
|
6826
|
+
const tail = await readResumedTail(creationOpts.resume, this.logger, withModel);
|
|
6827
|
+
if (withModel)
|
|
6828
|
+
resumedModelHint = tail.model;
|
|
6829
|
+
resumedPermissionMode = tail.permissionMode;
|
|
6711
6830
|
timing.phase("resume-transcript");
|
|
6712
6831
|
}
|
|
6713
6832
|
const input = new Pushable();
|
|
@@ -6769,7 +6888,11 @@ export class ClaudeAcpAgent {
|
|
|
6769
6888
|
const allowBypass = ALLOW_BYPASS &&
|
|
6770
6889
|
settingsManager.getSettings().permissions?.disableBypassPermissionsMode !== "disable" &&
|
|
6771
6890
|
sessionMeta?.claudeCode?.options?.allowDangerouslySkipPermissions !== false;
|
|
6772
|
-
const initialPermissionMode =
|
|
6891
|
+
const initialPermissionMode = resolveInitialPermissionMode({
|
|
6892
|
+
explicit: creationOpts.permissionMode,
|
|
6893
|
+
resumed: resumedPermissionMode,
|
|
6894
|
+
defaultMode: settingsManager.getSettings().permissions?.defaultMode,
|
|
6895
|
+
}, this.logger, allowBypass);
|
|
6773
6896
|
const userProvidedOptions = sessionMeta?.claudeCode?.options
|
|
6774
6897
|
? { ...sessionMeta.claudeCode.options }
|
|
6775
6898
|
: undefined;
|
|
@@ -6778,9 +6901,7 @@ export class ClaudeAcpAgent {
|
|
|
6778
6901
|
// hidden state that the Client cannot inspect or change.
|
|
6779
6902
|
if (userProvidedOptions)
|
|
6780
6903
|
delete userProvidedOptions.agent;
|
|
6781
|
-
const forwardSubagentText =
|
|
6782
|
-
supportsSubagentTranscript(this.clientCapabilities) ||
|
|
6783
|
-
userProvidedOptions?.forwardSubagentText === true;
|
|
6904
|
+
const forwardSubagentText = this.forwardsSubagentText(params._meta);
|
|
6784
6905
|
// Configure thinking behavior from environment variable
|
|
6785
6906
|
const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger);
|
|
6786
6907
|
// Parse model configuration from environment (e.g. Bedrock model overrides)
|
|
@@ -6806,7 +6927,7 @@ export class ClaudeAcpAgent {
|
|
|
6806
6927
|
// Per-session task state. Created here (rather than in the session record
|
|
6807
6928
|
// below) so the TaskCreated/TaskCompleted hook callbacks can close over
|
|
6808
6929
|
// the same Map that the streaming message handler will read from.
|
|
6809
|
-
const taskState = new Map();
|
|
6930
|
+
const taskState = creationOpts.replayState?.taskState ?? new Map();
|
|
6810
6931
|
// Resolve every workspace root once. The native checkpoint report uses
|
|
6811
6932
|
// this same set for lexical path validation, and the SDK receives it below.
|
|
6812
6933
|
const acpAdditionalDirectories = params.additionalDirectories ?? sessionMeta?.additionalRoots ?? [];
|
|
@@ -7099,7 +7220,7 @@ export class ClaudeAcpAgent {
|
|
|
7099
7220
|
const allowedModels = Array.isArray(settingsAvailableModels)
|
|
7100
7221
|
? applyAvailableModelsAllowlist(initializationResult.models, settingsAvailableModels, settingsModelOverrides, settingsManager.getManagedDeniedModels())
|
|
7101
7222
|
: initializationResult.models;
|
|
7102
|
-
const models = await getAvailableModels(q, allowedModels, initializationResult.models, settingsManager, this.logger, creationOpts.resume !== undefined, sessionId, resumedModelHint);
|
|
7223
|
+
const models = await getAvailableModels(q, allowedModels, initializationResult.models, settingsManager, this.logger, creationOpts.resume !== undefined, sessionId, await resumedModelHint);
|
|
7103
7224
|
timing.phase("models");
|
|
7104
7225
|
// Resolve the current model's capabilities separately from the stable
|
|
7105
7226
|
// permission-mode catalog advertised to ACP clients.
|
|
@@ -7248,8 +7369,8 @@ export class ClaudeAcpAgent {
|
|
|
7248
7369
|
nativeSubagentParentByToolUseId: new Map(),
|
|
7249
7370
|
emittedAssistantText: false,
|
|
7250
7371
|
owedTrailingIdles: 0,
|
|
7251
|
-
messageIdToUuid: new Map(),
|
|
7252
|
-
sessionFailureState: createSessionFailureState(),
|
|
7372
|
+
messageIdToUuid: creationOpts.replayState?.messageIdToUuid ?? new Map(),
|
|
7373
|
+
sessionFailureState: creationOpts.replayState?.sessionFailureState ?? createSessionFailureState(),
|
|
7253
7374
|
claudeSubscriptionGuard,
|
|
7254
7375
|
accountKind: fromAccountInfo(initializationResult.account)?.kind,
|
|
7255
7376
|
fileChangeReporter,
|
|
@@ -7290,7 +7411,10 @@ export class ClaudeAcpAgent {
|
|
|
7290
7411
|
this.closeQueryStream(session);
|
|
7291
7412
|
delete this.sessions[sessionId];
|
|
7292
7413
|
try {
|
|
7293
|
-
await this.createSession(session.creationParams, {
|
|
7414
|
+
await this.createSession(session.creationParams, {
|
|
7415
|
+
resume: sessionId,
|
|
7416
|
+
permissionMode: session.modes.currentModeId,
|
|
7417
|
+
});
|
|
7294
7418
|
}
|
|
7295
7419
|
catch (error) {
|
|
7296
7420
|
// One session that cannot come back must not abort the switch. The
|
|
@@ -7771,7 +7895,20 @@ function getAvailableSlashCommands(commands,
|
|
|
7771
7895
|
// Names the CLI tagged terminal-bound on `system`/init (their UX lives in
|
|
7772
7896
|
// the CLI's own terminal, which ACP clients aren't) — filtered alongside
|
|
7773
7897
|
// the static list. Raw CLI names, matched before the MCP rename.
|
|
7774
|
-
terminalCommands
|
|
7898
|
+
terminalCommands,
|
|
7899
|
+
// The session cwd of an AIR client, or undefined for every other client.
|
|
7900
|
+
// AIR gets the kind and the SKILL.md path of each skill command.
|
|
7901
|
+
airSkillCwd,
|
|
7902
|
+
// The resolved SKILL.md paths of the session, keyed by the cwd and the name.
|
|
7903
|
+
skillPaths) {
|
|
7904
|
+
const skillPath = (name, cwd) => {
|
|
7905
|
+
const key = `${cwd}\0${name}`;
|
|
7906
|
+
if (skillPaths?.has(key))
|
|
7907
|
+
return skillPaths.get(key);
|
|
7908
|
+
const resolved = resolveSkillPath(name, cwd);
|
|
7909
|
+
skillPaths?.set(key, resolved);
|
|
7910
|
+
return resolved;
|
|
7911
|
+
};
|
|
7775
7912
|
const UNSUPPORTED_COMMANDS = [
|
|
7776
7913
|
"clear",
|
|
7777
7914
|
"cost",
|
|
@@ -7782,8 +7919,10 @@ terminalCommands) {
|
|
|
7782
7919
|
"release-notes",
|
|
7783
7920
|
"todos",
|
|
7784
7921
|
];
|
|
7785
|
-
|
|
7786
|
-
|
|
7922
|
+
// The adapter replaces the terminal text of `/mcp` and runs its actions,
|
|
7923
|
+
// even when Claude Code tags the command terminal-bound.
|
|
7924
|
+
const advertised = commands
|
|
7925
|
+
.filter((command) => command.name !== "mcp" && !terminalCommands?.includes(command.name))
|
|
7787
7926
|
.map((command) => {
|
|
7788
7927
|
const input = command.argumentHint
|
|
7789
7928
|
? {
|
|
@@ -7793,16 +7932,26 @@ terminalCommands) {
|
|
|
7793
7932
|
}
|
|
7794
7933
|
: null;
|
|
7795
7934
|
let name = command.name;
|
|
7796
|
-
|
|
7935
|
+
const mcpPrompt = command.name.endsWith(" (MCP)");
|
|
7936
|
+
if (mcpPrompt) {
|
|
7797
7937
|
name = `mcp:${name.replace(" (MCP)", "")}`;
|
|
7798
7938
|
}
|
|
7939
|
+
const skillMdPath = airSkillCwd && !command.builtin && !mcpPrompt
|
|
7940
|
+
? skillPath(command.name, airSkillCwd)
|
|
7941
|
+
: undefined;
|
|
7799
7942
|
return {
|
|
7800
7943
|
name,
|
|
7801
7944
|
description: command.description || "",
|
|
7802
7945
|
input,
|
|
7946
|
+
...(skillMdPath
|
|
7947
|
+
? {
|
|
7948
|
+
_meta: withAirMeta(withAirMeta(undefined, AIR_KIND_KEY, "skill"), AIR_SKILL_PATH_KEY, skillMdPath),
|
|
7949
|
+
}
|
|
7950
|
+
: {}),
|
|
7803
7951
|
};
|
|
7804
7952
|
})
|
|
7805
7953
|
.filter((command) => !UNSUPPORTED_COMMANDS.includes(command.name));
|
|
7954
|
+
return [...advertised, MCP_AVAILABLE_COMMAND];
|
|
7806
7955
|
}
|
|
7807
7956
|
function formatUriAsLink(uri) {
|
|
7808
7957
|
try {
|