@pwrdrvr/agent-acp 0.12.3 → 0.13.1
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/index.d.ts +71 -17
- package/dist/index.js +243 -159
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6,103 +6,10 @@ import {
|
|
|
6
6
|
ndJsonStream
|
|
7
7
|
} from "@agentclientprotocol/sdk";
|
|
8
8
|
import { noopLogger } from "@pwrdrvr/agent-core";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (line.trim().length === 0) return line;
|
|
14
|
-
let message;
|
|
15
|
-
try {
|
|
16
|
-
message = JSON.parse(line);
|
|
17
|
-
} catch {
|
|
18
|
-
return line;
|
|
19
|
-
}
|
|
20
|
-
if (!isPlainObject(message) || message.method !== "session/update") return line;
|
|
21
|
-
const params = message.params;
|
|
22
|
-
if (!isPlainObject(params)) return line;
|
|
23
|
-
const update = params.update;
|
|
24
|
-
if (!isPlainObject(update)) return line;
|
|
25
|
-
if (update.sessionUpdate === "config_option_update") return "";
|
|
26
|
-
let changed = false;
|
|
27
|
-
for (const key of ["rawInput", "rawOutput"]) {
|
|
28
|
-
if (!(key in update)) continue;
|
|
29
|
-
const value = update[key];
|
|
30
|
-
if (value === void 0 || isPlainObject(value)) continue;
|
|
31
|
-
if (value === null) {
|
|
32
|
-
delete update[key];
|
|
33
|
-
} else {
|
|
34
|
-
update[key] = { value };
|
|
35
|
-
}
|
|
36
|
-
changed = true;
|
|
37
|
-
}
|
|
38
|
-
return changed ? JSON.stringify(message) : line;
|
|
39
|
-
}
|
|
40
|
-
function sanitizeInboundStream(source) {
|
|
41
|
-
const decoder = new TextDecoder();
|
|
42
|
-
const encoder = new TextEncoder();
|
|
43
|
-
let buffer = "";
|
|
44
|
-
return source.pipeThrough(
|
|
45
|
-
new TransformStream({
|
|
46
|
-
transform(chunk, controller) {
|
|
47
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
48
|
-
let nl = buffer.indexOf("\n");
|
|
49
|
-
while (nl !== -1) {
|
|
50
|
-
const line = buffer.slice(0, nl);
|
|
51
|
-
buffer = buffer.slice(nl + 1);
|
|
52
|
-
const sanitized = sanitizeAcpNotificationLine(line);
|
|
53
|
-
if (sanitized.length > 0) controller.enqueue(encoder.encode(sanitized + "\n"));
|
|
54
|
-
nl = buffer.indexOf("\n");
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
flush(controller) {
|
|
58
|
-
if (buffer.length > 0) {
|
|
59
|
-
const sanitized = sanitizeAcpNotificationLine(buffer);
|
|
60
|
-
if (sanitized.length > 0) controller.enqueue(encoder.encode(sanitized));
|
|
61
|
-
buffer = "";
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
function rewriteOutboundAcpLine(line) {
|
|
68
|
-
if (line.trim().length === 0) return line;
|
|
69
|
-
let message;
|
|
70
|
-
try {
|
|
71
|
-
message = JSON.parse(line);
|
|
72
|
-
} catch {
|
|
73
|
-
return line;
|
|
74
|
-
}
|
|
75
|
-
if (!isPlainObject(message) || message.method !== "_session/set_config_option") {
|
|
76
|
-
return line;
|
|
77
|
-
}
|
|
78
|
-
message.method = "session/set_config_option";
|
|
79
|
-
return JSON.stringify(message);
|
|
80
|
-
}
|
|
81
|
-
function rewriteOutboundStream(target) {
|
|
82
|
-
const decoder = new TextDecoder();
|
|
83
|
-
const encoder = new TextEncoder();
|
|
84
|
-
let buffer = "";
|
|
85
|
-
const transform = new TransformStream({
|
|
86
|
-
transform(chunk, controller) {
|
|
87
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
88
|
-
let nl = buffer.indexOf("\n");
|
|
89
|
-
while (nl !== -1) {
|
|
90
|
-
const line = buffer.slice(0, nl);
|
|
91
|
-
buffer = buffer.slice(nl + 1);
|
|
92
|
-
controller.enqueue(encoder.encode(rewriteOutboundAcpLine(line) + "\n"));
|
|
93
|
-
nl = buffer.indexOf("\n");
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
flush(controller) {
|
|
97
|
-
if (buffer.length > 0) {
|
|
98
|
-
controller.enqueue(encoder.encode(rewriteOutboundAcpLine(buffer)));
|
|
99
|
-
buffer = "";
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
void transform.readable.pipeTo(target).catch(() => void 0);
|
|
104
|
-
return transform.writable;
|
|
105
|
-
}
|
|
9
|
+
import {
|
|
10
|
+
createCommandInvocation,
|
|
11
|
+
prependCommandDirToPath
|
|
12
|
+
} from "@pwrdrvr/agent-transport";
|
|
106
13
|
var AcpConnection = class {
|
|
107
14
|
constructor(options) {
|
|
108
15
|
this.options = options;
|
|
@@ -161,9 +68,12 @@ var AcpConnection = class {
|
|
|
161
68
|
const { command, args, env } = this.options;
|
|
162
69
|
const logger = this.logger;
|
|
163
70
|
return (client) => {
|
|
164
|
-
const
|
|
71
|
+
const launchEnv = prependCommandDirToPath(command, env ?? process.env);
|
|
72
|
+
const invocation = createCommandInvocation({ command, args, env: launchEnv });
|
|
73
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
165
74
|
stdio: ["pipe", "pipe", "pipe"],
|
|
166
|
-
|
|
75
|
+
env: launchEnv,
|
|
76
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
167
77
|
});
|
|
168
78
|
child.on("error", (error) => {
|
|
169
79
|
logger.error("acp agent process error", { command, message: error.message });
|
|
@@ -172,12 +82,8 @@ var AcpConnection = class {
|
|
|
172
82
|
const line = chunk.toString("utf8").trimEnd();
|
|
173
83
|
if (line.length > 0) logger.debug("acp agent stderr", { line });
|
|
174
84
|
});
|
|
175
|
-
const toAgentStdin =
|
|
176
|
-
|
|
177
|
-
);
|
|
178
|
-
const fromAgentStdout = sanitizeInboundStream(
|
|
179
|
-
Readable.toWeb(child.stdout)
|
|
180
|
-
);
|
|
85
|
+
const toAgentStdin = Writable.toWeb(child.stdin);
|
|
86
|
+
const fromAgentStdout = Readable.toWeb(child.stdout);
|
|
181
87
|
const stream = ndJsonStream(toAgentStdin, fromAgentStdout);
|
|
182
88
|
const connection = new ClientSideConnection(
|
|
183
89
|
(_agent) => client,
|
|
@@ -218,7 +124,7 @@ var AcpConnection = class {
|
|
|
218
124
|
case "authenticate":
|
|
219
125
|
return await conn.authenticate(params);
|
|
220
126
|
default:
|
|
221
|
-
return await conn.
|
|
127
|
+
return await conn.request(method, params);
|
|
222
128
|
}
|
|
223
129
|
}
|
|
224
130
|
async notify(method, params = {}) {
|
|
@@ -229,7 +135,7 @@ var AcpConnection = class {
|
|
|
229
135
|
await conn.cancel(params);
|
|
230
136
|
return;
|
|
231
137
|
}
|
|
232
|
-
await conn.
|
|
138
|
+
await conn.notify(method, params);
|
|
233
139
|
}
|
|
234
140
|
async close() {
|
|
235
141
|
try {
|
|
@@ -760,6 +666,12 @@ function normalizeAcpRuntimeCapabilities(params) {
|
|
|
760
666
|
function acpRuntimeSupportsSessionLoad(capabilities) {
|
|
761
667
|
return capabilities?.agentCapabilities?.loadSession !== false;
|
|
762
668
|
}
|
|
669
|
+
function acpRuntimeSupportsHttpMcp(capabilities) {
|
|
670
|
+
return capabilities?.agentCapabilities?.mcp?.http === true;
|
|
671
|
+
}
|
|
672
|
+
function acpRuntimeSupportsSseMcp(capabilities) {
|
|
673
|
+
return capabilities?.agentCapabilities?.mcp?.sse === true;
|
|
674
|
+
}
|
|
763
675
|
function modelConfigOption(capabilities) {
|
|
764
676
|
return capabilities?.configOptions?.find(
|
|
765
677
|
(option) => option.category === "model" || option.id === "model"
|
|
@@ -947,10 +859,21 @@ function readAgentCapabilities(value, sessionCapabilitiesValue) {
|
|
|
947
859
|
const close = readBoolean(asRecord(record?.session), "close");
|
|
948
860
|
const cancel = readBoolean(asRecord(record?.session), "cancel");
|
|
949
861
|
const sessionHistoryReplay = readBoolean(kimiMeta, "sessionHistoryReplay") ?? readBoolean(kimiMeta, "session_history_replay");
|
|
862
|
+
const mcpRecord = asRecord(
|
|
863
|
+
record?.mcpCapabilities ?? record?.mcp_capabilities ?? record?.mcp
|
|
864
|
+
);
|
|
865
|
+
const http = readBoolean(mcpRecord, "http");
|
|
866
|
+
const sse = readBoolean(mcpRecord, "sse");
|
|
950
867
|
const capabilities = {
|
|
951
868
|
raw: record ?? sessionCapabilities
|
|
952
869
|
};
|
|
953
870
|
if (loadSession !== void 0) capabilities.loadSession = loadSession;
|
|
871
|
+
if (http !== void 0 || sse !== void 0) {
|
|
872
|
+
capabilities.mcp = {
|
|
873
|
+
...http !== void 0 ? { http } : {},
|
|
874
|
+
...sse !== void 0 ? { sse } : {}
|
|
875
|
+
};
|
|
876
|
+
}
|
|
954
877
|
if (sessionHistoryReplay !== void 0) {
|
|
955
878
|
capabilities.sessionHistoryReplay = sessionHistoryReplay;
|
|
956
879
|
}
|
|
@@ -977,6 +900,51 @@ function readAgentInfo(value) {
|
|
|
977
900
|
return Object.keys(agentInfo).length > 0 ? agentInfo : void 0;
|
|
978
901
|
}
|
|
979
902
|
|
|
903
|
+
// src/mcp-server-config.ts
|
|
904
|
+
function isKeyValueArray(collection) {
|
|
905
|
+
return Array.isArray(collection);
|
|
906
|
+
}
|
|
907
|
+
function normalizeKeyValues(collection) {
|
|
908
|
+
if (collection === void 0) return [];
|
|
909
|
+
if (isKeyValueArray(collection)) {
|
|
910
|
+
return collection.map(({ name, value }) => ({ name, value }));
|
|
911
|
+
}
|
|
912
|
+
return Object.entries(collection).map(([name, value]) => ({ name, value }));
|
|
913
|
+
}
|
|
914
|
+
function normalizeAcpMcpServerConfig(server) {
|
|
915
|
+
if ("type" in server) {
|
|
916
|
+
return {
|
|
917
|
+
name: server.name,
|
|
918
|
+
type: server.type,
|
|
919
|
+
url: server.url,
|
|
920
|
+
headers: normalizeKeyValues(server.headers)
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
name: server.name,
|
|
925
|
+
command: server.command,
|
|
926
|
+
args: [...server.args ?? []],
|
|
927
|
+
env: normalizeKeyValues(server.env)
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function normalizeAcpMcpServerConfigs(servers) {
|
|
931
|
+
return servers.map(normalizeAcpMcpServerConfig);
|
|
932
|
+
}
|
|
933
|
+
function redactAcpMcpCredentials(message, servers) {
|
|
934
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
935
|
+
for (const server of servers) {
|
|
936
|
+
const values = "type" in server ? [server.url, ...normalizeKeyValues(server.headers).map(({ value }) => value)] : normalizeKeyValues(server.env).map(({ value }) => value);
|
|
937
|
+
for (const value of values) {
|
|
938
|
+
if (value.length > 0) secrets.add(value);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
let redacted = message;
|
|
942
|
+
for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {
|
|
943
|
+
redacted = redacted.split(secret).join("[REDACTED]");
|
|
944
|
+
}
|
|
945
|
+
return redacted;
|
|
946
|
+
}
|
|
947
|
+
|
|
980
948
|
// src/acp-client.ts
|
|
981
949
|
var ACP_PROTOCOL_VERSION = 1;
|
|
982
950
|
var ACP_PROMPT_REQUEST_TIMEOUT_MS = 60 * 6e4;
|
|
@@ -1122,6 +1090,18 @@ var AcpAgentClient = class {
|
|
|
1122
1090
|
...options.mcpServers !== void 0 ? { mcpServers: options.mcpServers } : {}
|
|
1123
1091
|
});
|
|
1124
1092
|
}
|
|
1093
|
+
/** ACP-native `session/load`. The pinned ACP v1 library has no separate
|
|
1094
|
+
* `session/resume` method; loading is the protocol-defined history restore
|
|
1095
|
+
* path and receives the same per-thread MCP configuration as `session/new`. */
|
|
1096
|
+
async loadThreadNative(options) {
|
|
1097
|
+
return this.openSession({
|
|
1098
|
+
lifecycle: "load",
|
|
1099
|
+
protocolSessionId: options.sessionId,
|
|
1100
|
+
...options.threadId !== void 0 ? { bindThreadId: options.threadId } : {},
|
|
1101
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
1102
|
+
...options.mcpServers !== void 0 ? { mcpServers: options.mcpServers } : {}
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1125
1105
|
/**
|
|
1126
1106
|
* Re-establish a fresh ACP session for a PERSISTED thread id whose underlying
|
|
1127
1107
|
* session is gone (e.g. resuming a chat after an app restart — the agent
|
|
@@ -1167,7 +1147,13 @@ var AcpAgentClient = class {
|
|
|
1167
1147
|
* `bindThreadId` is given (resume), in which case the new ACP session is
|
|
1168
1148
|
* bound to that existing host thread id. */
|
|
1169
1149
|
async establishSession(options) {
|
|
1150
|
+
return this.openSession({ lifecycle: "new", ...options });
|
|
1151
|
+
}
|
|
1152
|
+
async openSession(options) {
|
|
1170
1153
|
await this.initialize();
|
|
1154
|
+
if (options.lifecycle === "load" && !acpRuntimeSupportsSessionLoad(this.runtimeCapabilities)) {
|
|
1155
|
+
throw new Error("ACP agent does not advertise session/load support");
|
|
1156
|
+
}
|
|
1171
1157
|
const cwd = options.cwd ?? this.defaultCwd ?? process.cwd();
|
|
1172
1158
|
try {
|
|
1173
1159
|
mkdirSync(cwd, { recursive: true });
|
|
@@ -1177,23 +1163,68 @@ var AcpAgentClient = class {
|
|
|
1177
1163
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
1178
1164
|
});
|
|
1179
1165
|
}
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1166
|
+
const configuredMcpServers = options.mcpServers ?? this.defaultMcpServers;
|
|
1167
|
+
this.assertMcpTransportSupport(configuredMcpServers);
|
|
1168
|
+
const mcpServers = normalizeAcpMcpServerConfigs(configuredMcpServers);
|
|
1169
|
+
const method = options.lifecycle === "load" ? "session/load" : "session/new";
|
|
1170
|
+
let session;
|
|
1171
|
+
if (options.lifecycle === "load") {
|
|
1172
|
+
const protocolSessionId2 = options.protocolSessionId;
|
|
1173
|
+
if (!protocolSessionId2) {
|
|
1174
|
+
throw new Error("ACP session/load did not provide a session id");
|
|
1175
|
+
}
|
|
1176
|
+
const threadId2 = options.bindThreadId ?? `acp:${this.strategy.id}:${isUuid(protocolSessionId2) ? protocolSessionId2 : randomUUID()}`;
|
|
1177
|
+
if (this.sessions.has(threadId2)) {
|
|
1178
|
+
throw new Error(`ACP thread is already open: ${threadId2}`);
|
|
1179
|
+
}
|
|
1180
|
+
const mappedThreadId = this.threadIdByProtocolId.get(protocolSessionId2);
|
|
1181
|
+
if (mappedThreadId !== void 0) {
|
|
1182
|
+
throw new Error(
|
|
1183
|
+
`ACP session is already loaded for thread: ${mappedThreadId}`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
session = {
|
|
1187
|
+
threadId: threadId2,
|
|
1188
|
+
protocolSessionId: protocolSessionId2,
|
|
1189
|
+
normalizer: new AcpSessionNormalizer({ quirks: this.strategy.quirks }),
|
|
1190
|
+
turnId: void 0,
|
|
1191
|
+
runtimeState: void 0,
|
|
1192
|
+
pendingTurn: void 0,
|
|
1193
|
+
pendingInstructions: options.instructions,
|
|
1194
|
+
mcpServerNames: configuredMcpServers.map((server) => server.name)
|
|
1195
|
+
};
|
|
1196
|
+
this.sessions.set(threadId2, session);
|
|
1197
|
+
this.threadIdByProtocolId.set(protocolSessionId2, threadId2);
|
|
1198
|
+
}
|
|
1199
|
+
let result;
|
|
1200
|
+
try {
|
|
1201
|
+
result = await this.transport.request(method, {
|
|
1202
|
+
cwd,
|
|
1203
|
+
mcpServers,
|
|
1204
|
+
...options.lifecycle === "load" ? { sessionId: options.protocolSessionId } : {}
|
|
1205
|
+
});
|
|
1206
|
+
} catch (cause) {
|
|
1207
|
+
const message = redactAcpMcpCredentials(
|
|
1208
|
+
errorMessage(cause),
|
|
1209
|
+
configuredMcpServers
|
|
1210
|
+
);
|
|
1211
|
+
if (session !== void 0) {
|
|
1212
|
+
if (this.sessions.get(session.threadId) === session) {
|
|
1213
|
+
this.sessions.delete(session.threadId);
|
|
1214
|
+
}
|
|
1215
|
+
if (this.threadIdByProtocolId.get(session.protocolSessionId) === session.threadId) {
|
|
1216
|
+
this.threadIdByProtocolId.delete(session.protocolSessionId);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
throw new Error(`ACP ${method} failed: ${message}`);
|
|
1220
|
+
}
|
|
1190
1221
|
const record = asRecord2(result);
|
|
1191
|
-
const protocolSessionId = readString2(record, "sessionId") ?? readString2(record, "session_id");
|
|
1222
|
+
const protocolSessionId = options.lifecycle === "load" ? options.protocolSessionId : readString2(record, "sessionId") ?? readString2(record, "session_id");
|
|
1192
1223
|
if (!protocolSessionId) {
|
|
1193
|
-
throw new Error(
|
|
1224
|
+
throw new Error(`ACP ${method} did not provide a session id`);
|
|
1194
1225
|
}
|
|
1195
|
-
const threadId = options.bindThreadId ?? `acp:${this.strategy.id}:${isUuid(protocolSessionId) ? protocolSessionId : randomUUID()}`;
|
|
1196
|
-
|
|
1226
|
+
const threadId = session?.threadId ?? options.bindThreadId ?? `acp:${this.strategy.id}:${isUuid(protocolSessionId) ? protocolSessionId : randomUUID()}`;
|
|
1227
|
+
session ??= {
|
|
1197
1228
|
threadId,
|
|
1198
1229
|
protocolSessionId,
|
|
1199
1230
|
normalizer: new AcpSessionNormalizer({ quirks: this.strategy.quirks }),
|
|
@@ -1201,14 +1232,18 @@ var AcpAgentClient = class {
|
|
|
1201
1232
|
runtimeState: void 0,
|
|
1202
1233
|
pendingTurn: void 0,
|
|
1203
1234
|
pendingInstructions: options.instructions,
|
|
1204
|
-
// Remember the MCP servers attached to THIS session so
|
|
1205
|
-
//
|
|
1206
|
-
|
|
1207
|
-
mcpServerNames: (options.mcpServers ?? this.defaultMcpServers).map((server) => server.name)
|
|
1235
|
+
// Remember ONLY the MCP servers attached to THIS session so the host's
|
|
1236
|
+
// approval policy never sees another pooled thread's tool set.
|
|
1237
|
+
mcpServerNames: configuredMcpServers.map((server) => server.name)
|
|
1208
1238
|
};
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1239
|
+
if (options.lifecycle === "new") {
|
|
1240
|
+
this.sessions.set(threadId, session);
|
|
1241
|
+
this.threadIdByProtocolId.set(protocolSessionId, threadId);
|
|
1242
|
+
}
|
|
1243
|
+
const runtimeCapabilities = this.captureRuntimeCapabilities(
|
|
1244
|
+
options.lifecycle === "load" ? "session-load" : "session-new",
|
|
1245
|
+
result
|
|
1246
|
+
);
|
|
1212
1247
|
if (runtimeCapabilities) {
|
|
1213
1248
|
const runtimeState = acpSessionRuntimeStateFromCapabilities(
|
|
1214
1249
|
runtimeCapabilities,
|
|
@@ -1218,10 +1253,11 @@ var AcpAgentClient = class {
|
|
|
1218
1253
|
this.notifyRuntimeCapabilities({ threadId, runtimeCapabilities, runtimeState });
|
|
1219
1254
|
this.emitThreadSettings(session, runtimeCapabilities, runtimeState);
|
|
1220
1255
|
}
|
|
1221
|
-
this.logger.debug("acp thread
|
|
1256
|
+
this.logger.debug("acp thread opened", {
|
|
1222
1257
|
threadId,
|
|
1223
1258
|
protocolSessionId,
|
|
1224
|
-
|
|
1259
|
+
lifecycle: options.lifecycle,
|
|
1260
|
+
rebound: options.bindThreadId !== void 0
|
|
1225
1261
|
});
|
|
1226
1262
|
const out = { threadId };
|
|
1227
1263
|
const model = modelIdFromCapabilities(runtimeCapabilities);
|
|
@@ -1496,16 +1532,6 @@ var AcpAgentClient = class {
|
|
|
1496
1532
|
}
|
|
1497
1533
|
throw new Error(`Unsupported ACP request: ${method}`);
|
|
1498
1534
|
}
|
|
1499
|
-
/** Every MCP server name this client knows about — the client-level default
|
|
1500
|
-
* plus the per-thread servers of every live session. Used to auto-approve a
|
|
1501
|
-
* configured MCP tool regardless of which session's permission request it is. */
|
|
1502
|
-
knownMcpServerNames() {
|
|
1503
|
-
const names = new Set(this.defaultMcpServers.map((server) => server.name));
|
|
1504
|
-
for (const session of this.sessions.values()) {
|
|
1505
|
-
for (const name of session.mcpServerNames) names.add(name);
|
|
1506
|
-
}
|
|
1507
|
-
return [...names];
|
|
1508
|
-
}
|
|
1509
1535
|
async handlePermissionRequest(params, id) {
|
|
1510
1536
|
const protocolSessionId = readString2(params, "sessionId") ?? readString2(params, "session_id");
|
|
1511
1537
|
const threadId = protocolSessionId ? this.threadIdByProtocolId.get(protocolSessionId) : void 0;
|
|
@@ -1526,7 +1552,7 @@ var AcpAgentClient = class {
|
|
|
1526
1552
|
}
|
|
1527
1553
|
const handlerParams = {
|
|
1528
1554
|
...params,
|
|
1529
|
-
mcpServerNames: this.
|
|
1555
|
+
mcpServerNames: threadId ? this.sessions.get(threadId)?.mcpServerNames ?? [] : []
|
|
1530
1556
|
};
|
|
1531
1557
|
if (threadId !== void 0) handlerParams.threadId = threadId;
|
|
1532
1558
|
const decision = await handler("session/request_permission", handlerParams);
|
|
@@ -1661,10 +1687,33 @@ var AcpAgentClient = class {
|
|
|
1661
1687
|
}
|
|
1662
1688
|
return session;
|
|
1663
1689
|
}
|
|
1690
|
+
assertMcpTransportSupport(servers) {
|
|
1691
|
+
for (const server of servers) {
|
|
1692
|
+
if (!("type" in server)) continue;
|
|
1693
|
+
if (server.type === "http" && !acpRuntimeSupportsHttpMcp(this.runtimeCapabilities)) {
|
|
1694
|
+
throw new Error(
|
|
1695
|
+
`ACP agent does not advertise HTTP MCP support for server "${server.name}"`
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
if (server.type === "sse" && !acpRuntimeSupportsSseMcp(this.runtimeCapabilities)) {
|
|
1699
|
+
throw new Error(
|
|
1700
|
+
`ACP agent does not advertise SSE MCP support for server "${server.name}"`
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1664
1705
|
/** Whether the agent advertises session/load support (for hosts that resume). */
|
|
1665
1706
|
supportsSessionLoad() {
|
|
1666
1707
|
return acpRuntimeSupportsSessionLoad(this.runtimeCapabilities);
|
|
1667
1708
|
}
|
|
1709
|
+
/** Whether the initialize result explicitly advertises streamable HTTP MCP. */
|
|
1710
|
+
supportsHttpMcp() {
|
|
1711
|
+
return acpRuntimeSupportsHttpMcp(this.runtimeCapabilities);
|
|
1712
|
+
}
|
|
1713
|
+
/** Whether the initialize result explicitly advertises legacy HTTP+SSE MCP. */
|
|
1714
|
+
supportsSseMcp() {
|
|
1715
|
+
return acpRuntimeSupportsSseMcp(this.runtimeCapabilities);
|
|
1716
|
+
}
|
|
1668
1717
|
};
|
|
1669
1718
|
var LOW_EFFORT_TOKENS = /* @__PURE__ */ new Set([
|
|
1670
1719
|
"off",
|
|
@@ -2260,7 +2309,10 @@ import { readdirSync, realpathSync, statSync } from "fs";
|
|
|
2260
2309
|
import { homedir as homedir4 } from "os";
|
|
2261
2310
|
import path4 from "path";
|
|
2262
2311
|
import { promisify } from "util";
|
|
2263
|
-
import {
|
|
2312
|
+
import {
|
|
2313
|
+
createCommandInvocation as createCommandInvocation2,
|
|
2314
|
+
prependCommandDirToPath as prependCommandDirToPath2
|
|
2315
|
+
} from "@pwrdrvr/agent-transport";
|
|
2264
2316
|
var execFile = promisify(execFileCallback);
|
|
2265
2317
|
async function discoverLocalAcpAgentInstances(options = {}) {
|
|
2266
2318
|
const strategies = options.strategies ?? BUILT_IN_ACP_STRATEGIES;
|
|
@@ -2361,35 +2413,60 @@ function candidateCommands(strategy, env, listExecutables, override) {
|
|
|
2361
2413
|
return out;
|
|
2362
2414
|
}
|
|
2363
2415
|
function canonicalize(command) {
|
|
2364
|
-
if (!path4.isAbsolute(command))
|
|
2416
|
+
if (!path4.isAbsolute(command)) {
|
|
2417
|
+
return process.platform === "win32" ? command.toLowerCase() : command;
|
|
2418
|
+
}
|
|
2365
2419
|
try {
|
|
2366
|
-
|
|
2420
|
+
const canonical = realpathSync(command);
|
|
2421
|
+
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
2367
2422
|
} catch {
|
|
2368
|
-
return command;
|
|
2423
|
+
return process.platform === "win32" ? command.toLowerCase() : command;
|
|
2369
2424
|
}
|
|
2370
2425
|
}
|
|
2371
2426
|
function defaultListExecutables(command, env) {
|
|
2372
|
-
if (
|
|
2373
|
-
if (command.includes(path4.sep)) return [];
|
|
2427
|
+
if (command.includes("/") || command.includes("\\")) return [];
|
|
2374
2428
|
const found = [];
|
|
2375
2429
|
const seenDir = /* @__PURE__ */ new Set();
|
|
2376
2430
|
for (const dir of discoveryDirs(env)) {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2431
|
+
const normalizedDir = normalizePathEntry(dir);
|
|
2432
|
+
const dirKey = process.platform === "win32" ? normalizedDir.toLowerCase() : normalizedDir;
|
|
2433
|
+
if (normalizedDir.length === 0 || seenDir.has(dirKey)) continue;
|
|
2434
|
+
seenDir.add(dirKey);
|
|
2435
|
+
for (const commandName of executableCommandNames(command, env)) {
|
|
2436
|
+
const candidate = path4.join(normalizedDir, commandName);
|
|
2437
|
+
try {
|
|
2438
|
+
const stat = statSync(candidate);
|
|
2439
|
+
if (stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0)) {
|
|
2440
|
+
found.push(candidate);
|
|
2441
|
+
}
|
|
2442
|
+
} catch {
|
|
2384
2443
|
}
|
|
2385
|
-
} catch {
|
|
2386
2444
|
}
|
|
2387
2445
|
}
|
|
2388
2446
|
return found;
|
|
2389
2447
|
}
|
|
2448
|
+
function executableCommandNames(command, env) {
|
|
2449
|
+
if (process.platform !== "win32") return [command];
|
|
2450
|
+
const extensions = (readWindowsEnv(env, "PATHEXT")?.trim() || ".COM;.EXE;.BAT;.CMD").split(";").map((extension) => extension.trim()).filter(Boolean).map((extension) => extension.startsWith(".") ? extension : `.${extension}`);
|
|
2451
|
+
const commandExtension = path4.win32.extname(command);
|
|
2452
|
+
if (commandExtension.length > 0 && extensions.some((extension) => extension.toLowerCase() === commandExtension.toLowerCase())) {
|
|
2453
|
+
return [command];
|
|
2454
|
+
}
|
|
2455
|
+
return extensions.map((extension) => `${command}${extension}`);
|
|
2456
|
+
}
|
|
2457
|
+
function readWindowsEnv(env, name) {
|
|
2458
|
+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
2459
|
+
return key ? env[key] : void 0;
|
|
2460
|
+
}
|
|
2461
|
+
function normalizePathEntry(entry) {
|
|
2462
|
+
const trimmed = entry.trim();
|
|
2463
|
+
const quoted = trimmed.match(/^"(.*)"$/);
|
|
2464
|
+
return quoted?.[1] ?? trimmed;
|
|
2465
|
+
}
|
|
2390
2466
|
function discoveryDirs(env) {
|
|
2391
|
-
const pathValue = env
|
|
2392
|
-
|
|
2467
|
+
const pathValue = process.platform === "win32" ? readWindowsEnv(env, "PATH") ?? "" : env.PATH ?? "";
|
|
2468
|
+
const pathDirs = pathValue.split(path4.delimiter);
|
|
2469
|
+
return process.platform === "win32" ? pathDirs : [...pathDirs, ...wellKnownAgentBinDirs(homedir4())];
|
|
2393
2470
|
}
|
|
2394
2471
|
function wellKnownAgentBinDirs(home = homedir4()) {
|
|
2395
2472
|
return [
|
|
@@ -2427,12 +2504,15 @@ function ensureArgs(args, ensure) {
|
|
|
2427
2504
|
}
|
|
2428
2505
|
function makeDefaultProbe(env) {
|
|
2429
2506
|
return async (command, args) => {
|
|
2430
|
-
|
|
2507
|
+
const launchEnv = prependCommandDirToPath2(command, env);
|
|
2508
|
+
const invocation = createCommandInvocation2({ command, args, env: launchEnv });
|
|
2509
|
+
return await execFile(invocation.command, invocation.args, {
|
|
2431
2510
|
timeout: 5e3,
|
|
2432
2511
|
maxBuffer: 1024 * 1024,
|
|
2433
2512
|
// Prepend the candidate's own dir so a node-script CLI (e.g. an nvm-
|
|
2434
2513
|
// installed `qwen`) finds its sibling `node` during the probe.
|
|
2435
|
-
env:
|
|
2514
|
+
env: launchEnv,
|
|
2515
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
2436
2516
|
});
|
|
2437
2517
|
};
|
|
2438
2518
|
}
|
|
@@ -2795,7 +2875,9 @@ export {
|
|
|
2795
2875
|
BANNED_ACP_REGISTRY_IDS,
|
|
2796
2876
|
BUILT_IN_ACP_STRATEGIES,
|
|
2797
2877
|
DEFAULT_ACP_AGENT_ALLOWLIST,
|
|
2878
|
+
acpRuntimeSupportsHttpMcp,
|
|
2798
2879
|
acpRuntimeSupportsSessionLoad,
|
|
2880
|
+
acpRuntimeSupportsSseMcp,
|
|
2799
2881
|
acpSessionRuntimeStateFromCapabilities,
|
|
2800
2882
|
acpSessionRuntimeStateFromUpdate,
|
|
2801
2883
|
asRecord,
|
|
@@ -2815,6 +2897,8 @@ export {
|
|
|
2815
2897
|
modelConfigOption,
|
|
2816
2898
|
modelIdFromCapabilities,
|
|
2817
2899
|
modelsFromCapabilities,
|
|
2900
|
+
normalizeAcpMcpServerConfig,
|
|
2901
|
+
normalizeAcpMcpServerConfigs,
|
|
2818
2902
|
normalizeAcpRuntimeCapabilities,
|
|
2819
2903
|
normalizeRegistry,
|
|
2820
2904
|
qwenStrategy,
|