@agentclientprotocol/claude-agent-acp 0.85.2-preview.3 → 0.85.2-preview.5
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 +41 -9
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +254 -107
- 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/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/package.json +2 -2
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) => {
|
|
@@ -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;
|
|
@@ -3365,17 +3460,17 @@ 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 },
|
|
3379
3474
|
},
|
|
3380
3475
|
});
|
|
3381
3476
|
break;
|
|
@@ -3534,11 +3629,12 @@ export class ClaudeAcpAgent {
|
|
|
3534
3629
|
// list with this payload. Forward message.commands directly —
|
|
3535
3630
|
// it's authoritative, and re-querying supportedCommands()
|
|
3536
3631
|
// would just return the same list with an extra round-trip.
|
|
3632
|
+
session.skillPaths = new Map();
|
|
3537
3633
|
await sendUpdate({
|
|
3538
3634
|
sessionId: params.sessionId,
|
|
3539
3635
|
update: {
|
|
3540
3636
|
sessionUpdate: "available_commands_update",
|
|
3541
|
-
availableCommands: getAvailableSlashCommands(message.commands, session.terminalSlashCommands),
|
|
3637
|
+
availableCommands: getAvailableSlashCommands(message.commands, session.terminalSlashCommands, this.toolCallCapabilities.air.client ? session.cwd : undefined, session.skillPaths),
|
|
3542
3638
|
},
|
|
3543
3639
|
});
|
|
3544
3640
|
break;
|
|
@@ -4384,10 +4480,13 @@ export class ClaudeAcpAgent {
|
|
|
4384
4480
|
(message.usage.output_tokens ?? 0) === 0 &&
|
|
4385
4481
|
!session.activeTurn?.noticeTexts?.includes(normalizeNoticeText(message.result)));
|
|
4386
4482
|
if (shouldForwardResult) {
|
|
4387
|
-
const
|
|
4388
|
-
|
|
4483
|
+
const commandTurn = session.activeTurn ?? firstUnsettledQueuedTurn();
|
|
4484
|
+
const commandMarkdown = await takeLocalCommandMarkdown(message.result);
|
|
4485
|
+
if (commandTurn?.localCommand && session.cancelled)
|
|
4389
4486
|
break;
|
|
4390
|
-
|
|
4487
|
+
if (commandMarkdown === null)
|
|
4488
|
+
break;
|
|
4489
|
+
for (const notification of toAcpNotifications(commandMarkdown ?? message.result, "assistant", params.sessionId, session.toolUseCache, routedNotificationClient, this.logger)) {
|
|
4391
4490
|
await sendUpdate(notification);
|
|
4392
4491
|
}
|
|
4393
4492
|
}
|
|
@@ -4780,16 +4879,16 @@ export class ClaudeAcpAgent {
|
|
|
4780
4879
|
// Depending on the Claude Code build, a local command can arrive
|
|
4781
4880
|
// as the dedicated system message above or as a synthetic
|
|
4782
4881
|
// assistant message. Replace only the output owned by the exact
|
|
4783
|
-
// /usage turn; no content signatures or text parsing are involved.
|
|
4882
|
+
// /usage or /mcp turn; no content signatures or text parsing are involved.
|
|
4784
4883
|
if (message.type === "assistant" &&
|
|
4785
4884
|
message.parent_tool_use_id === null &&
|
|
4786
4885
|
message.message.model === "<synthetic>") {
|
|
4787
|
-
const
|
|
4886
|
+
const commandMarkdown = await takeLocalCommandMarkdown(assistantMessageText(message.message) ?? "");
|
|
4788
4887
|
if (session.cancelled)
|
|
4789
4888
|
break;
|
|
4790
|
-
if (
|
|
4791
|
-
if (
|
|
4792
|
-
for (const notification of toAcpNotifications(
|
|
4889
|
+
if (commandMarkdown !== undefined) {
|
|
4890
|
+
if (commandMarkdown !== null) {
|
|
4891
|
+
for (const notification of toAcpNotifications(commandMarkdown, "assistant", params.sessionId, session.toolUseCache, routedNotificationClient, this.logger, { messageId: messageIdForGrouping(message) })) {
|
|
4793
4892
|
await sendUpdate(notification);
|
|
4794
4893
|
}
|
|
4795
4894
|
}
|
|
@@ -5089,7 +5188,7 @@ export class ClaudeAcpAgent {
|
|
|
5089
5188
|
}
|
|
5090
5189
|
session.cancelled = true;
|
|
5091
5190
|
for (const turn of session.turnQueue ?? [])
|
|
5092
|
-
turn.
|
|
5191
|
+
turn.localCommandAbort?.abort();
|
|
5093
5192
|
session.pendingExitPlanModeInterruption = undefined;
|
|
5094
5193
|
session.pendingExitPlanContextReset = undefined;
|
|
5095
5194
|
// The stream already ended (see closeQueryStream): every in-flight turn was
|
|
@@ -5531,13 +5630,15 @@ export class ClaudeAcpAgent {
|
|
|
5531
5630
|
await this.applyConfigOptionValue(params.sessionId, session, params.configId, effectiveValue);
|
|
5532
5631
|
return { configOptions: session.configOptions };
|
|
5533
5632
|
}
|
|
5534
|
-
async replaySessionHistory(sessionId, resumedMessages) {
|
|
5633
|
+
async replaySessionHistory(sessionId, resumedMessages, pending) {
|
|
5535
5634
|
const replayStartedAt = performance.now();
|
|
5536
5635
|
const toolUseCache = {};
|
|
5537
5636
|
const messages = resumedMessages ?? (await getSessionMessages(sessionId));
|
|
5538
5637
|
const historyLoadedAt = performance.now();
|
|
5539
5638
|
this.logger.log(`[session/replay] sessionId=${sessionId} phase=read durationMs=${Math.round(historyLoadedAt - replayStartedAt)} messages=${messages.length}`);
|
|
5540
|
-
|
|
5639
|
+
// A pending replay has no session record yet. It uses the state that the record takes later.
|
|
5640
|
+
const replayState = () => pending?.state ?? this.sessions[sessionId];
|
|
5641
|
+
const session = replayState();
|
|
5541
5642
|
// A replay rebuilds the client view, so its plan goes out again.
|
|
5542
5643
|
if (session?.taskState)
|
|
5543
5644
|
forgetPublishedTaskPlan(session.taskState);
|
|
@@ -5549,7 +5650,12 @@ export class ClaudeAcpAgent {
|
|
|
5549
5650
|
sessionId,
|
|
5550
5651
|
state: session.sessionFailureState,
|
|
5551
5652
|
capabilities: this.clientCapabilities,
|
|
5552
|
-
isCurrent: () =>
|
|
5653
|
+
isCurrent: () => {
|
|
5654
|
+
const live = this.sessions[sessionId];
|
|
5655
|
+
if (!live)
|
|
5656
|
+
return pending !== undefined && !pending.stopped;
|
|
5657
|
+
return live.sessionFailureState === session.sessionFailureState;
|
|
5658
|
+
},
|
|
5553
5659
|
sendUpdate: (notification) => this.client.sessionUpdate(notification),
|
|
5554
5660
|
logger: this.logger,
|
|
5555
5661
|
})
|
|
@@ -5704,7 +5810,7 @@ export class ClaudeAcpAgent {
|
|
|
5704
5810
|
await replayAsyncTasks.taskNotification(notification);
|
|
5705
5811
|
};
|
|
5706
5812
|
const replayMessage = async (message) => {
|
|
5707
|
-
if (isReplayHiddenMetaMessage(message)) {
|
|
5813
|
+
if (pending?.stopped || isReplayHiddenMetaMessage(message)) {
|
|
5708
5814
|
return;
|
|
5709
5815
|
}
|
|
5710
5816
|
if (message.type === "user" &&
|
|
@@ -5718,7 +5824,7 @@ export class ClaudeAcpAgent {
|
|
|
5718
5824
|
// a client-supplied id without an extra getSessionMessages read. Not read
|
|
5719
5825
|
// yet (see Session.messageIdToUuid).
|
|
5720
5826
|
const replayMessageId = messageIdForGrouping(message);
|
|
5721
|
-
const replaySession =
|
|
5827
|
+
const replaySession = replayState();
|
|
5722
5828
|
if (replaySession && replayMessageId && message.uuid) {
|
|
5723
5829
|
replaySession.messageIdToUuid.set(replayMessageId, message.uuid);
|
|
5724
5830
|
}
|
|
@@ -5812,8 +5918,8 @@ export class ClaudeAcpAgent {
|
|
|
5812
5918
|
registerHooks: false,
|
|
5813
5919
|
replay: true,
|
|
5814
5920
|
clientCapabilities: this.clientCapabilities,
|
|
5815
|
-
cwd:
|
|
5816
|
-
taskState:
|
|
5921
|
+
cwd: replayState()?.cwd,
|
|
5922
|
+
taskState: replayState()?.taskState,
|
|
5817
5923
|
messageId: replayMessageId,
|
|
5818
5924
|
parentToolUseId,
|
|
5819
5925
|
})) {
|
|
@@ -5858,7 +5964,7 @@ export class ClaudeAcpAgent {
|
|
|
5858
5964
|
};
|
|
5859
5965
|
for (const message of messages)
|
|
5860
5966
|
await replayMessage(message);
|
|
5861
|
-
if (nativeReplayEnabled) {
|
|
5967
|
+
if (nativeReplayEnabled && !pending?.stopped) {
|
|
5862
5968
|
// Claude history persists sidechain messages and Agent/Task tool uses,
|
|
5863
5969
|
// but not task_started/task_updated lifecycle frames. Recover terminal
|
|
5864
5970
|
// state from the launch tool_result. Missing results and malformed or
|
|
@@ -6292,7 +6398,7 @@ export class ClaudeAcpAgent {
|
|
|
6292
6398
|
sessionId,
|
|
6293
6399
|
update: {
|
|
6294
6400
|
sessionUpdate: "available_commands_update",
|
|
6295
|
-
availableCommands: getAvailableSlashCommands(commands, session.terminalSlashCommands),
|
|
6401
|
+
availableCommands: getAvailableSlashCommands(commands, session.terminalSlashCommands, this.toolCallCapabilities.air.client ? session.cwd : undefined, (session.skillPaths ??= new Map())),
|
|
6296
6402
|
},
|
|
6297
6403
|
});
|
|
6298
6404
|
}
|
|
@@ -6613,6 +6719,8 @@ export class ClaudeAcpAgent {
|
|
|
6613
6719
|
}
|
|
6614
6720
|
async getOrCreateSession(params, resumedSession) {
|
|
6615
6721
|
const existingSession = this.sessions[params.sessionId];
|
|
6722
|
+
// A recreated live session keeps its mode, not the mode of the transcript.
|
|
6723
|
+
const livePermissionMode = existingSession?.modes.currentModeId;
|
|
6616
6724
|
if (existingSession) {
|
|
6617
6725
|
const fingerprint = computeSessionFingerprint(params);
|
|
6618
6726
|
if (fingerprint === existingSession.sessionFingerprint) {
|
|
@@ -6628,9 +6736,6 @@ export class ClaudeAcpAgent {
|
|
|
6628
6736
|
// up the new values.
|
|
6629
6737
|
await this.teardownSession(params.sessionId);
|
|
6630
6738
|
}
|
|
6631
|
-
const resumedModelHint = resumedSession
|
|
6632
|
-
? resumedSession.model
|
|
6633
|
-
: await readResumedModel(params.sessionId, this.logger);
|
|
6634
6739
|
const response = await this.createSession({
|
|
6635
6740
|
cwd: params.cwd,
|
|
6636
6741
|
mcpServers: params.mcpServers ?? [],
|
|
@@ -6638,7 +6743,8 @@ export class ClaudeAcpAgent {
|
|
|
6638
6743
|
_meta: params._meta,
|
|
6639
6744
|
}, {
|
|
6640
6745
|
resume: params.sessionId,
|
|
6641
|
-
|
|
6746
|
+
permissionMode: livePermissionMode,
|
|
6747
|
+
...(resumedSession ? { resumedModelHint: resumedSession.model } : {}),
|
|
6642
6748
|
});
|
|
6643
6749
|
return {
|
|
6644
6750
|
sessionId: response.sessionId,
|
|
@@ -6667,6 +6773,12 @@ export class ClaudeAcpAgent {
|
|
|
6667
6773
|
throw RequestError.invalidParams({ cwd }, `\`cwd\` is not a directory: ${cwd}`);
|
|
6668
6774
|
}
|
|
6669
6775
|
}
|
|
6776
|
+
/** Whether the session forwards the text and the thinking of a subagent. */
|
|
6777
|
+
forwardsSubagentText(meta) {
|
|
6778
|
+
return (clientSupportsSubagents(this.clientCapabilities) ||
|
|
6779
|
+
supportsSubagentTranscript(this.clientCapabilities) ||
|
|
6780
|
+
meta?.claudeCode?.options?.forwardSubagentText === true);
|
|
6781
|
+
}
|
|
6670
6782
|
async createSession(params, creationOpts = {}) {
|
|
6671
6783
|
const createStartedAt = performance.now();
|
|
6672
6784
|
// Validate `cwd` up front. The ACP spec requires an absolute path, and the
|
|
@@ -6704,10 +6816,15 @@ export class ClaudeAcpAgent {
|
|
|
6704
6816
|
// sign-out respawn, provider rerouting), so fill the same fast local hint
|
|
6705
6817
|
// here when the caller did not provide it. Never fall back to the slow
|
|
6706
6818
|
// getContextUsage control request.
|
|
6819
|
+
// The same tail read gives the permission mode of the resumed session.
|
|
6707
6820
|
let resumedModelHint = creationOpts.resumedModelHint;
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6821
|
+
let resumedPermissionMode;
|
|
6822
|
+
if (creationOpts.resume !== undefined) {
|
|
6823
|
+
const withModel = !Object.prototype.hasOwnProperty.call(creationOpts, "resumedModelHint");
|
|
6824
|
+
const tail = await readResumedTail(creationOpts.resume, this.logger, withModel);
|
|
6825
|
+
if (withModel)
|
|
6826
|
+
resumedModelHint = tail.model;
|
|
6827
|
+
resumedPermissionMode = tail.permissionMode;
|
|
6711
6828
|
timing.phase("resume-transcript");
|
|
6712
6829
|
}
|
|
6713
6830
|
const input = new Pushable();
|
|
@@ -6769,7 +6886,11 @@ export class ClaudeAcpAgent {
|
|
|
6769
6886
|
const allowBypass = ALLOW_BYPASS &&
|
|
6770
6887
|
settingsManager.getSettings().permissions?.disableBypassPermissionsMode !== "disable" &&
|
|
6771
6888
|
sessionMeta?.claudeCode?.options?.allowDangerouslySkipPermissions !== false;
|
|
6772
|
-
const initialPermissionMode =
|
|
6889
|
+
const initialPermissionMode = resolveInitialPermissionMode({
|
|
6890
|
+
explicit: creationOpts.permissionMode,
|
|
6891
|
+
resumed: resumedPermissionMode,
|
|
6892
|
+
defaultMode: settingsManager.getSettings().permissions?.defaultMode,
|
|
6893
|
+
}, this.logger, allowBypass);
|
|
6773
6894
|
const userProvidedOptions = sessionMeta?.claudeCode?.options
|
|
6774
6895
|
? { ...sessionMeta.claudeCode.options }
|
|
6775
6896
|
: undefined;
|
|
@@ -6778,9 +6899,7 @@ export class ClaudeAcpAgent {
|
|
|
6778
6899
|
// hidden state that the Client cannot inspect or change.
|
|
6779
6900
|
if (userProvidedOptions)
|
|
6780
6901
|
delete userProvidedOptions.agent;
|
|
6781
|
-
const forwardSubagentText =
|
|
6782
|
-
supportsSubagentTranscript(this.clientCapabilities) ||
|
|
6783
|
-
userProvidedOptions?.forwardSubagentText === true;
|
|
6902
|
+
const forwardSubagentText = this.forwardsSubagentText(params._meta);
|
|
6784
6903
|
// Configure thinking behavior from environment variable
|
|
6785
6904
|
const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger);
|
|
6786
6905
|
// Parse model configuration from environment (e.g. Bedrock model overrides)
|
|
@@ -6806,7 +6925,7 @@ export class ClaudeAcpAgent {
|
|
|
6806
6925
|
// Per-session task state. Created here (rather than in the session record
|
|
6807
6926
|
// below) so the TaskCreated/TaskCompleted hook callbacks can close over
|
|
6808
6927
|
// the same Map that the streaming message handler will read from.
|
|
6809
|
-
const taskState = new Map();
|
|
6928
|
+
const taskState = creationOpts.replayState?.taskState ?? new Map();
|
|
6810
6929
|
// Resolve every workspace root once. The native checkpoint report uses
|
|
6811
6930
|
// this same set for lexical path validation, and the SDK receives it below.
|
|
6812
6931
|
const acpAdditionalDirectories = params.additionalDirectories ?? sessionMeta?.additionalRoots ?? [];
|
|
@@ -7099,7 +7218,7 @@ export class ClaudeAcpAgent {
|
|
|
7099
7218
|
const allowedModels = Array.isArray(settingsAvailableModels)
|
|
7100
7219
|
? applyAvailableModelsAllowlist(initializationResult.models, settingsAvailableModels, settingsModelOverrides, settingsManager.getManagedDeniedModels())
|
|
7101
7220
|
: initializationResult.models;
|
|
7102
|
-
const models = await getAvailableModels(q, allowedModels, initializationResult.models, settingsManager, this.logger, creationOpts.resume !== undefined, sessionId, resumedModelHint);
|
|
7221
|
+
const models = await getAvailableModels(q, allowedModels, initializationResult.models, settingsManager, this.logger, creationOpts.resume !== undefined, sessionId, await resumedModelHint);
|
|
7103
7222
|
timing.phase("models");
|
|
7104
7223
|
// Resolve the current model's capabilities separately from the stable
|
|
7105
7224
|
// permission-mode catalog advertised to ACP clients.
|
|
@@ -7248,8 +7367,8 @@ export class ClaudeAcpAgent {
|
|
|
7248
7367
|
nativeSubagentParentByToolUseId: new Map(),
|
|
7249
7368
|
emittedAssistantText: false,
|
|
7250
7369
|
owedTrailingIdles: 0,
|
|
7251
|
-
messageIdToUuid: new Map(),
|
|
7252
|
-
sessionFailureState: createSessionFailureState(),
|
|
7370
|
+
messageIdToUuid: creationOpts.replayState?.messageIdToUuid ?? new Map(),
|
|
7371
|
+
sessionFailureState: creationOpts.replayState?.sessionFailureState ?? createSessionFailureState(),
|
|
7253
7372
|
claudeSubscriptionGuard,
|
|
7254
7373
|
accountKind: fromAccountInfo(initializationResult.account)?.kind,
|
|
7255
7374
|
fileChangeReporter,
|
|
@@ -7290,7 +7409,10 @@ export class ClaudeAcpAgent {
|
|
|
7290
7409
|
this.closeQueryStream(session);
|
|
7291
7410
|
delete this.sessions[sessionId];
|
|
7292
7411
|
try {
|
|
7293
|
-
await this.createSession(session.creationParams, {
|
|
7412
|
+
await this.createSession(session.creationParams, {
|
|
7413
|
+
resume: sessionId,
|
|
7414
|
+
permissionMode: session.modes.currentModeId,
|
|
7415
|
+
});
|
|
7294
7416
|
}
|
|
7295
7417
|
catch (error) {
|
|
7296
7418
|
// One session that cannot come back must not abort the switch. The
|
|
@@ -7771,7 +7893,20 @@ function getAvailableSlashCommands(commands,
|
|
|
7771
7893
|
// Names the CLI tagged terminal-bound on `system`/init (their UX lives in
|
|
7772
7894
|
// the CLI's own terminal, which ACP clients aren't) — filtered alongside
|
|
7773
7895
|
// the static list. Raw CLI names, matched before the MCP rename.
|
|
7774
|
-
terminalCommands
|
|
7896
|
+
terminalCommands,
|
|
7897
|
+
// The session cwd of an AIR client, or undefined for every other client.
|
|
7898
|
+
// AIR gets the kind and the SKILL.md path of each skill command.
|
|
7899
|
+
airSkillCwd,
|
|
7900
|
+
// The resolved SKILL.md paths of the session, keyed by the cwd and the name.
|
|
7901
|
+
skillPaths) {
|
|
7902
|
+
const skillPath = (name, cwd) => {
|
|
7903
|
+
const key = `${cwd}\0${name}`;
|
|
7904
|
+
if (skillPaths?.has(key))
|
|
7905
|
+
return skillPaths.get(key);
|
|
7906
|
+
const resolved = resolveSkillPath(name, cwd);
|
|
7907
|
+
skillPaths?.set(key, resolved);
|
|
7908
|
+
return resolved;
|
|
7909
|
+
};
|
|
7775
7910
|
const UNSUPPORTED_COMMANDS = [
|
|
7776
7911
|
"clear",
|
|
7777
7912
|
"cost",
|
|
@@ -7782,8 +7917,10 @@ terminalCommands) {
|
|
|
7782
7917
|
"release-notes",
|
|
7783
7918
|
"todos",
|
|
7784
7919
|
];
|
|
7785
|
-
|
|
7786
|
-
|
|
7920
|
+
// The adapter replaces the terminal text of `/mcp` and runs its actions,
|
|
7921
|
+
// even when Claude Code tags the command terminal-bound.
|
|
7922
|
+
const advertised = commands
|
|
7923
|
+
.filter((command) => command.name !== "mcp" && !terminalCommands?.includes(command.name))
|
|
7787
7924
|
.map((command) => {
|
|
7788
7925
|
const input = command.argumentHint
|
|
7789
7926
|
? {
|
|
@@ -7793,16 +7930,26 @@ terminalCommands) {
|
|
|
7793
7930
|
}
|
|
7794
7931
|
: null;
|
|
7795
7932
|
let name = command.name;
|
|
7796
|
-
|
|
7933
|
+
const mcpPrompt = command.name.endsWith(" (MCP)");
|
|
7934
|
+
if (mcpPrompt) {
|
|
7797
7935
|
name = `mcp:${name.replace(" (MCP)", "")}`;
|
|
7798
7936
|
}
|
|
7937
|
+
const skillMdPath = airSkillCwd && !command.builtin && !mcpPrompt
|
|
7938
|
+
? skillPath(command.name, airSkillCwd)
|
|
7939
|
+
: undefined;
|
|
7799
7940
|
return {
|
|
7800
7941
|
name,
|
|
7801
7942
|
description: command.description || "",
|
|
7802
7943
|
input,
|
|
7944
|
+
...(skillMdPath
|
|
7945
|
+
? {
|
|
7946
|
+
_meta: withAirMeta(withAirMeta(undefined, AIR_KIND_KEY, "skill"), AIR_SKILL_PATH_KEY, skillMdPath),
|
|
7947
|
+
}
|
|
7948
|
+
: {}),
|
|
7803
7949
|
};
|
|
7804
7950
|
})
|
|
7805
7951
|
.filter((command) => !UNSUPPORTED_COMMANDS.includes(command.name));
|
|
7952
|
+
return [...advertised, MCP_AVAILABLE_COMMAND];
|
|
7806
7953
|
}
|
|
7807
7954
|
function formatUriAsLink(uri) {
|
|
7808
7955
|
try {
|