@pwrdrvr/agent-acp 0.13.0 → 0.14.0

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 CHANGED
@@ -700,6 +700,15 @@ type DiscoveredAcpAgentInstance = {
700
700
  /** Where this candidate came from (user override, `PATH`, or a fallback path). */
701
701
  source: AcpAgentInstanceSource;
702
702
  };
703
+ /** A detected executable that did not satisfy a strategy's discovery probe.
704
+ * It is diagnostic-only: hosts must never use it to launch an ACP session. */
705
+ type RejectedAcpAgentInstance = {
706
+ command: string;
707
+ /** Parsed from successful `--version` output when available. */
708
+ version?: string;
709
+ source: AcpAgentInstanceSource;
710
+ reason: "version-probe-failed" | "acp-probe-failed" | "acp-help-mismatch";
711
+ };
703
712
  /** Every installed instance of one agent found on this machine. */
704
713
  type DiscoveredAcpAgentGroup = {
705
714
  strategyId: string;
@@ -711,6 +720,9 @@ type DiscoveredAcpAgentGroup = {
711
720
  env: Record<string, string>;
712
721
  /** All instances that passed the probe, in candidate order (override → PATH → fallback). */
713
722
  instances: DiscoveredAcpAgentInstance[];
723
+ /** Detected candidates that failed the version or ACP-capability probe. Only
724
+ * populated when `includeRejectedCandidates` is enabled. */
725
+ rejectedInstances?: RejectedAcpAgentInstance[];
714
726
  discoveredAt: number;
715
727
  };
716
728
  type DiscoveredAcpAgent = {
@@ -740,11 +752,15 @@ type LocalAcpDiscoveryOptions = {
740
752
  env?: NodeJS.ProcessEnv;
741
753
  /** Injectable `PATH` executable lister (real-fs scan by default). */
742
754
  listExecutables?: AcpPathExecutableLister;
755
+ /** Include candidates known to exist but rejected by a discovery probe.
756
+ * Defaults false to preserve the legacy installed-only result. */
757
+ includeRejectedCandidates?: boolean;
743
758
  };
744
759
  /**
745
760
  * Discover every installed instance of each agent (all `PATH` matches +
746
761
  * fallbacks + override that pass the probe), grouped per strategy. Groups with
747
- * no passing instance are omitted.
762
+ * no passing instance are omitted unless `includeRejectedCandidates` is true
763
+ * and a detected candidate failed a probe.
748
764
  */
749
765
  declare function discoverLocalAcpAgentInstances(options?: LocalAcpDiscoveryOptions): Promise<DiscoveredAcpAgentGroup[]>;
750
766
  /**
@@ -874,4 +890,4 @@ declare class AcpRegistryService {
874
890
  declare function evaluateAcpDistributionPolicy(distribution: AcpRegistryDistribution, allowlist: AcpAllowlistDecision): AcpDistributionPolicy;
875
891
  declare function normalizeRegistry(raw: unknown): AcpRegistryAgent[];
876
892
 
877
- export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
893
+ export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, type RejectedAcpAgentInstance, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
package/dist/index.js CHANGED
@@ -6,6 +6,10 @@ import {
6
6
  ndJsonStream
7
7
  } from "@agentclientprotocol/sdk";
8
8
  import { noopLogger } from "@pwrdrvr/agent-core";
9
+ import {
10
+ createCommandInvocation,
11
+ prependCommandDirToPath
12
+ } from "@pwrdrvr/agent-transport";
9
13
  var AcpConnection = class {
10
14
  constructor(options) {
11
15
  this.options = options;
@@ -64,9 +68,12 @@ var AcpConnection = class {
64
68
  const { command, args, env } = this.options;
65
69
  const logger = this.logger;
66
70
  return (client) => {
67
- 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, {
68
74
  stdio: ["pipe", "pipe", "pipe"],
69
- ...env !== void 0 ? { env } : {}
75
+ env: launchEnv,
76
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
70
77
  });
71
78
  child.on("error", (error) => {
72
79
  logger.error("acp agent process error", { command, message: error.message });
@@ -2302,7 +2309,10 @@ import { readdirSync, realpathSync, statSync } from "fs";
2302
2309
  import { homedir as homedir4 } from "os";
2303
2310
  import path4 from "path";
2304
2311
  import { promisify } from "util";
2305
- import { prependCommandDirToPath } from "@pwrdrvr/agent-transport";
2312
+ import {
2313
+ createCommandInvocation as createCommandInvocation2,
2314
+ prependCommandDirToPath as prependCommandDirToPath2
2315
+ } from "@pwrdrvr/agent-transport";
2306
2316
  var execFile = promisify(execFileCallback);
2307
2317
  async function discoverLocalAcpAgentInstances(options = {}) {
2308
2318
  const strategies = options.strategies ?? BUILT_IN_ACP_STRATEGIES;
@@ -2318,7 +2328,8 @@ async function discoverLocalAcpAgentInstances(options = {}) {
2318
2328
  now,
2319
2329
  env,
2320
2330
  listExecutables,
2321
- options.overrides?.[strategy.id]
2331
+ options.overrides?.[strategy.id],
2332
+ options.includeRejectedCandidates === true
2322
2333
  )
2323
2334
  )
2324
2335
  );
@@ -2342,21 +2353,40 @@ async function discoverLocalAcpAgents(options = {}) {
2342
2353
  return [agent];
2343
2354
  });
2344
2355
  }
2345
- async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override) {
2356
+ async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override, includeRejectedCandidates = false) {
2346
2357
  const candidates = candidateCommands(strategy, env, listExecutables, override);
2347
2358
  const instances = [];
2359
+ const rejectedInstances = [];
2348
2360
  for (const candidate of candidates) {
2349
2361
  const [versionResult, helpResult] = await Promise.all([
2350
2362
  runProbe(probe, candidate.command, strategy.discoveryProbe.versionArgs),
2351
2363
  runProbe(probe, candidate.command, strategy.discoveryProbe.helpArgs)
2352
2364
  ]);
2365
+ const version = versionResult ? parseCliVersion(resultText(versionResult)) : void 0;
2353
2366
  if (!versionResult || !helpResult) {
2367
+ if (includeRejectedCandidates && candidate.detected) {
2368
+ const rejected = {
2369
+ command: candidate.command,
2370
+ source: candidate.source,
2371
+ reason: versionResult ? "acp-probe-failed" : "version-probe-failed"
2372
+ };
2373
+ if (version !== void 0) rejected.version = version;
2374
+ rejectedInstances.push(rejected);
2375
+ }
2354
2376
  continue;
2355
2377
  }
2356
2378
  if (strategy.discoveryProbe.helpMatches !== void 0 && !strategy.discoveryProbe.helpMatches.test(resultText(helpResult))) {
2379
+ if (includeRejectedCandidates && candidate.detected) {
2380
+ const rejected = {
2381
+ command: candidate.command,
2382
+ source: candidate.source,
2383
+ reason: "acp-help-mismatch"
2384
+ };
2385
+ if (version !== void 0) rejected.version = version;
2386
+ rejectedInstances.push(rejected);
2387
+ }
2357
2388
  continue;
2358
2389
  }
2359
- const version = parseCliVersion(resultText(versionResult));
2360
2390
  const instance = {
2361
2391
  command: candidate.command,
2362
2392
  source: candidate.source
@@ -2364,7 +2394,7 @@ async function discoverStrategyInstances(strategy, probe, now, env, listExecutab
2364
2394
  if (version !== void 0) instance.version = version;
2365
2395
  instances.push(instance);
2366
2396
  }
2367
- if (instances.length === 0) {
2397
+ if (instances.length === 0 && rejectedInstances.length === 0) {
2368
2398
  return void 0;
2369
2399
  }
2370
2400
  return {
@@ -2374,64 +2404,98 @@ async function discoverStrategyInstances(strategy, probe, now, env, listExecutab
2374
2404
  args: ensureArgs(strategy.spawn.args, strategy.spawn.ensureArgs),
2375
2405
  env: strategy.spawn.env ?? {},
2376
2406
  instances,
2407
+ ...rejectedInstances.length > 0 ? { rejectedInstances } : {},
2377
2408
  discoveredAt: now()
2378
2409
  };
2379
2410
  }
2380
2411
  function candidateCommands(strategy, env, listExecutables, override) {
2381
2412
  const out = [];
2382
2413
  const seen = /* @__PURE__ */ new Set();
2383
- const push = (command, source) => {
2414
+ const push = (command, source, detected) => {
2384
2415
  const trimmed = command.trim();
2385
2416
  if (trimmed.length === 0) return;
2386
2417
  const key = canonicalize(trimmed);
2387
2418
  if (seen.has(key)) return;
2388
2419
  seen.add(key);
2389
- out.push({ command: trimmed, source });
2420
+ out.push({ command: trimmed, source, detected });
2390
2421
  };
2391
2422
  if (override && override.trim().length > 0) {
2392
- push(override, "override");
2423
+ push(override, "override", isExecutableFile(override));
2393
2424
  }
2394
2425
  const pathMatches = listExecutables(strategy.discoveryProbe.command, env);
2395
2426
  if (pathMatches.length > 0) {
2396
- for (const match of pathMatches) push(match, "path");
2427
+ for (const match of pathMatches) push(match, "path", true);
2397
2428
  } else {
2398
- push(strategy.discoveryProbe.command, "path");
2429
+ push(strategy.discoveryProbe.command, "path", false);
2399
2430
  }
2400
2431
  for (const fallback of strategy.discoveryProbe.fallbackCommands ?? []) {
2401
- push(fallback, "fallback");
2432
+ push(fallback, "fallback", isExecutableFile(fallback));
2402
2433
  }
2403
2434
  return out;
2404
2435
  }
2436
+ function isExecutableFile(command) {
2437
+ try {
2438
+ const stat = statSync(command);
2439
+ return stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0);
2440
+ } catch {
2441
+ return false;
2442
+ }
2443
+ }
2405
2444
  function canonicalize(command) {
2406
- if (!path4.isAbsolute(command)) return command;
2445
+ if (!path4.isAbsolute(command)) {
2446
+ return process.platform === "win32" ? command.toLowerCase() : command;
2447
+ }
2407
2448
  try {
2408
- return realpathSync(command);
2449
+ const canonical = realpathSync(command);
2450
+ return process.platform === "win32" ? canonical.toLowerCase() : canonical;
2409
2451
  } catch {
2410
- return command;
2452
+ return process.platform === "win32" ? command.toLowerCase() : command;
2411
2453
  }
2412
2454
  }
2413
2455
  function defaultListExecutables(command, env) {
2414
- if (process.platform === "win32") return [];
2415
- if (command.includes(path4.sep)) return [];
2456
+ if (command.includes("/") || command.includes("\\")) return [];
2416
2457
  const found = [];
2417
2458
  const seenDir = /* @__PURE__ */ new Set();
2418
2459
  for (const dir of discoveryDirs(env)) {
2419
- if (dir.length === 0 || seenDir.has(dir)) continue;
2420
- seenDir.add(dir);
2421
- const candidate = path4.join(dir, command);
2422
- try {
2423
- const stat = statSync(candidate);
2424
- if (stat.isFile() && (stat.mode & 73) !== 0) {
2425
- found.push(candidate);
2460
+ const normalizedDir = normalizePathEntry(dir);
2461
+ const dirKey = process.platform === "win32" ? normalizedDir.toLowerCase() : normalizedDir;
2462
+ if (normalizedDir.length === 0 || seenDir.has(dirKey)) continue;
2463
+ seenDir.add(dirKey);
2464
+ for (const commandName of executableCommandNames(command, env)) {
2465
+ const candidate = path4.join(normalizedDir, commandName);
2466
+ try {
2467
+ const stat = statSync(candidate);
2468
+ if (stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0)) {
2469
+ found.push(candidate);
2470
+ }
2471
+ } catch {
2426
2472
  }
2427
- } catch {
2428
2473
  }
2429
2474
  }
2430
2475
  return found;
2431
2476
  }
2477
+ function executableCommandNames(command, env) {
2478
+ if (process.platform !== "win32") return [command];
2479
+ const extensions = (readWindowsEnv(env, "PATHEXT")?.trim() || ".COM;.EXE;.BAT;.CMD").split(";").map((extension) => extension.trim()).filter(Boolean).map((extension) => extension.startsWith(".") ? extension : `.${extension}`);
2480
+ const commandExtension = path4.win32.extname(command);
2481
+ if (commandExtension.length > 0 && extensions.some((extension) => extension.toLowerCase() === commandExtension.toLowerCase())) {
2482
+ return [command];
2483
+ }
2484
+ return extensions.map((extension) => `${command}${extension}`);
2485
+ }
2486
+ function readWindowsEnv(env, name) {
2487
+ const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
2488
+ return key ? env[key] : void 0;
2489
+ }
2490
+ function normalizePathEntry(entry) {
2491
+ const trimmed = entry.trim();
2492
+ const quoted = trimmed.match(/^"(.*)"$/);
2493
+ return quoted?.[1] ?? trimmed;
2494
+ }
2432
2495
  function discoveryDirs(env) {
2433
- const pathValue = env.PATH ?? env.Path ?? "";
2434
- return [...pathValue.split(path4.delimiter), ...wellKnownAgentBinDirs(homedir4())];
2496
+ const pathValue = process.platform === "win32" ? readWindowsEnv(env, "PATH") ?? "" : env.PATH ?? "";
2497
+ const pathDirs = pathValue.split(path4.delimiter);
2498
+ return process.platform === "win32" ? pathDirs : [...pathDirs, ...wellKnownAgentBinDirs(homedir4())];
2435
2499
  }
2436
2500
  function wellKnownAgentBinDirs(home = homedir4()) {
2437
2501
  return [
@@ -2469,12 +2533,15 @@ function ensureArgs(args, ensure) {
2469
2533
  }
2470
2534
  function makeDefaultProbe(env) {
2471
2535
  return async (command, args) => {
2472
- return await execFile(command, args, {
2536
+ const launchEnv = prependCommandDirToPath2(command, env);
2537
+ const invocation = createCommandInvocation2({ command, args, env: launchEnv });
2538
+ return await execFile(invocation.command, invocation.args, {
2473
2539
  timeout: 5e3,
2474
2540
  maxBuffer: 1024 * 1024,
2475
2541
  // Prepend the candidate's own dir so a node-script CLI (e.g. an nvm-
2476
2542
  // installed `qwen`) finds its sibling `node` during the probe.
2477
- env: prependCommandDirToPath(command, env)
2543
+ env: launchEnv,
2544
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
2478
2545
  });
2479
2546
  };
2480
2547
  }