@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.js CHANGED
@@ -6,103 +6,10 @@ import {
6
6
  ndJsonStream
7
7
  } from "@agentclientprotocol/sdk";
8
8
  import { noopLogger } from "@pwrdrvr/agent-core";
9
- function isPlainObject(value) {
10
- return typeof value === "object" && value !== null && !Array.isArray(value);
11
- }
12
- function sanitizeAcpNotificationLine(line) {
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 child = spawn(command, args, {
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
- ...env !== void 0 ? { env } : {}
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 = rewriteOutboundStream(
176
- Writable.toWeb(child.stdin)
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.extMethod(method, params);
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.extNotification(method, params);
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 mcpServers = (options.mcpServers ?? this.defaultMcpServers).map((server) => ({
1181
- name: server.name,
1182
- command: server.command,
1183
- args: server.args ?? [],
1184
- env: Object.entries(server.env ?? {}).map(([name, value]) => ({ name, value }))
1185
- }));
1186
- const result = await this.transport.request("session/new", {
1187
- cwd,
1188
- mcpServers
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("ACP session/new did not return a session id");
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
- const session = {
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 its tools can be
1205
- // auto-approved even when the (pooled, shared) client has no client-level
1206
- // `mcpServers` default.
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
- this.sessions.set(threadId, session);
1210
- this.threadIdByProtocolId.set(protocolSessionId, threadId);
1211
- const runtimeCapabilities = this.captureRuntimeCapabilities("session-new", result);
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 started", {
1256
+ this.logger.debug("acp thread opened", {
1222
1257
  threadId,
1223
1258
  protocolSessionId,
1224
- resumed: options.bindThreadId !== void 0
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.knownMcpServerNames()
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 { prependCommandDirToPath } from "@pwrdrvr/agent-transport";
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)) return command;
2416
+ if (!path4.isAbsolute(command)) {
2417
+ return process.platform === "win32" ? command.toLowerCase() : command;
2418
+ }
2365
2419
  try {
2366
- return realpathSync(command);
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 (process.platform === "win32") return [];
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
- if (dir.length === 0 || seenDir.has(dir)) continue;
2378
- seenDir.add(dir);
2379
- const candidate = path4.join(dir, command);
2380
- try {
2381
- const stat = statSync(candidate);
2382
- if (stat.isFile() && (stat.mode & 73) !== 0) {
2383
- found.push(candidate);
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.PATH ?? env.Path ?? "";
2392
- return [...pathValue.split(path4.delimiter), ...wellKnownAgentBinDirs(homedir4())];
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
- return await execFile(command, args, {
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: prependCommandDirToPath(command, 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,