@pwrdrvr/agent-acp 0.16.1 → 0.17.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.js CHANGED
@@ -1,3 +1,36 @@
1
+ import {
2
+ ACP_REGISTRY_URL,
3
+ AcpAgentAllowlist,
4
+ AcpRegistryService,
5
+ BANNED_ACP_REGISTRY_IDS,
6
+ DEFAULT_ACP_AGENT_ALLOWLIST,
7
+ DEFAULT_ACP_PROBE_TIMEOUT_MS,
8
+ defaultAcpAgentAllowlist,
9
+ discoverLocalAcpAgentInstances,
10
+ discoverLocalAcpAgents,
11
+ evaluateAcpDistributionPolicy,
12
+ isBannedAcpRegistryId,
13
+ normalizeRegistry,
14
+ wellKnownAgentBinDirs
15
+ } from "./chunk-GCWY5T5H.js";
16
+ import {
17
+ BUILT_IN_ACP_STRATEGIES,
18
+ buildAcpBackendId,
19
+ buildStrategyTable,
20
+ defaultQuirks,
21
+ geminiStrategy,
22
+ grokStrategy,
23
+ kimiStrategy,
24
+ qwenStrategy,
25
+ strategyByBackendId,
26
+ strategyById
27
+ } from "./chunk-OFMIS7W6.js";
28
+ import {
29
+ normalizeAcpMcpServerConfig,
30
+ normalizeAcpMcpServerConfigs,
31
+ redactAcpMcpCredentials
32
+ } from "./chunk-64RRSPSB.js";
33
+
1
34
  // src/acp-connection.ts
2
35
  import { spawn } from "child_process";
3
36
  import { Readable, Writable } from "stream";
@@ -265,9 +298,9 @@ function readFirstLocationPath(record) {
265
298
  return void 0;
266
299
  }
267
300
  for (const location of locations) {
268
- const path5 = readString(asRecord(location), "path");
269
- if (path5 && path5.trim()) {
270
- return path5;
301
+ const path = readString(asRecord(location), "path");
302
+ if (path && path.trim()) {
303
+ return path;
271
304
  }
272
305
  }
273
306
  return void 0;
@@ -303,7 +336,7 @@ function normalizeStatus(status) {
303
336
  return void 0;
304
337
  }
305
338
  }
306
- function toolKindFor(acpKind, command, path5, label) {
339
+ function toolKindFor(acpKind, command, path, label) {
307
340
  switch (acpKind) {
308
341
  case "edit":
309
342
  case "write":
@@ -324,18 +357,18 @@ function toolKindFor(acpKind, command, path5, label) {
324
357
  if (command) return "command";
325
358
  const inferred = inferToolKind(label);
326
359
  if (inferred !== "other") return inferred;
327
- return path5 ? "read" : "other";
360
+ return path ? "read" : "other";
328
361
  }
329
362
  function toolCallFromUpdate(update, kind, sessionId) {
330
363
  const id = readToolCallId(update, kind, sessionId);
331
364
  const label = readString(update, "title") ?? readString(update, "name") ?? readString(update, "kind") ?? kind.replaceAll("_", " ");
332
365
  const acpKind = readString(update, "kind");
333
- const path5 = readString(update, "path") ?? readFirstLocationPath(update);
366
+ const path = readString(update, "path") ?? readFirstLocationPath(update);
334
367
  const command = readString(update, "command");
335
368
  const output = readToolOutput(update);
336
369
  const exitCode = readNumber(update, "exitCode") ?? readNumber(update, "exit_code");
337
370
  const status = normalizeStatus(readString(update, "status"));
338
- const toolKind = toolKindFor(acpKind, command, path5, label);
371
+ const toolKind = toolKindFor(acpKind, command, path, label);
339
372
  const call = {
340
373
  id,
341
374
  name: readString(update, "name") ?? acpKind ?? label,
@@ -369,9 +402,9 @@ function readToolLocations(update) {
369
402
  const out = [];
370
403
  for (const item of raw) {
371
404
  const record = asRecord(item);
372
- const path5 = record ? readString(record, "path") : void 0;
373
- if (path5 === void 0) continue;
374
- const location = { path: path5 };
405
+ const path = record ? readString(record, "path") : void 0;
406
+ if (path === void 0) continue;
407
+ const location = { path };
375
408
  const line = record ? readNumber(record, "line") : void 0;
376
409
  if (line !== void 0) location.line = line;
377
410
  out.push(location);
@@ -950,51 +983,6 @@ function readAgentInfo(value) {
950
983
  return Object.keys(agentInfo).length > 0 ? agentInfo : void 0;
951
984
  }
952
985
 
953
- // src/mcp-server-config.ts
954
- function isKeyValueArray(collection) {
955
- return Array.isArray(collection);
956
- }
957
- function normalizeKeyValues(collection) {
958
- if (collection === void 0) return [];
959
- if (isKeyValueArray(collection)) {
960
- return collection.map(({ name, value }) => ({ name, value }));
961
- }
962
- return Object.entries(collection).map(([name, value]) => ({ name, value }));
963
- }
964
- function normalizeAcpMcpServerConfig(server) {
965
- if ("type" in server) {
966
- return {
967
- name: server.name,
968
- type: server.type,
969
- url: server.url,
970
- headers: normalizeKeyValues(server.headers)
971
- };
972
- }
973
- return {
974
- name: server.name,
975
- command: server.command,
976
- args: [...server.args ?? []],
977
- env: normalizeKeyValues(server.env)
978
- };
979
- }
980
- function normalizeAcpMcpServerConfigs(servers) {
981
- return servers.map(normalizeAcpMcpServerConfig);
982
- }
983
- function redactAcpMcpCredentials(message, servers) {
984
- const secrets = /* @__PURE__ */ new Set();
985
- for (const server of servers) {
986
- const values = "type" in server ? [server.url, ...normalizeKeyValues(server.headers).map(({ value }) => value)] : normalizeKeyValues(server.env).map(({ value }) => value);
987
- for (const value of values) {
988
- if (value.length > 0) secrets.add(value);
989
- }
990
- }
991
- let redacted = message;
992
- for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {
993
- redacted = redacted.split(secret).join("[REDACTED]");
994
- }
995
- return redacted;
996
- }
997
-
998
986
  // src/acp-client.ts
999
987
  var ACP_PROTOCOL_VERSION = 1;
1000
988
  var ACP_PROMPT_REQUEST_TIMEOUT_MS = 60 * 6e4;
@@ -2078,10 +2066,10 @@ function describeUsageShape(result) {
2078
2066
  const paths = [];
2079
2067
  const walk = (record, prefix, depth) => {
2080
2068
  for (const [key, value] of Object.entries(record)) {
2081
- const path5 = prefix ? `${prefix}.${key}` : key;
2082
- if (/token|usage|quota/i.test(key)) paths.push(path5);
2069
+ const path = prefix ? `${prefix}.${key}` : key;
2070
+ if (/token|usage|quota/i.test(key)) paths.push(path);
2083
2071
  const child = asRecord2(value);
2084
- if (child && depth < 3) walk(child, path5, depth + 1);
2072
+ if (child && depth < 3) walk(child, path, depth + 1);
2085
2073
  }
2086
2074
  };
2087
2075
  walk(root, "", 0);
@@ -2293,810 +2281,6 @@ var AcpOneShotClient = class {
2293
2281
  await this.client.close();
2294
2282
  }
2295
2283
  };
2296
-
2297
- // src/strategies/strategy-types.ts
2298
- function buildAcpBackendId(registryId) {
2299
- return `acp:${registryId}`;
2300
- }
2301
- var DEFAULT_QUIRKS = {
2302
- surfaceThoughts: true,
2303
- titleFrom: "topic-update"
2304
- };
2305
- function defaultQuirks(overrides = {}) {
2306
- return { ...DEFAULT_QUIRKS, ...overrides };
2307
- }
2308
-
2309
- // src/strategies/gemini.ts
2310
- var geminiStrategy = {
2311
- id: "gemini",
2312
- backendId: buildAcpBackendId("gemini"),
2313
- displayName: "Gemini CLI",
2314
- authors: ["Google"],
2315
- discoveryProbe: {
2316
- command: "gemini",
2317
- versionArgs: ["--version"],
2318
- helpArgs: ["--help"],
2319
- helpMatches: /(^|\s)--acp(\s|,|$)/
2320
- },
2321
- spawn: {
2322
- command: "gemini",
2323
- args: ["--acp"],
2324
- // Gemini refuses to operate without workspace trust; matching PwrAgnt's
2325
- // launch-descriptor normalization, append --skip-trust + set the env flag.
2326
- ensureArgs: ["--skip-trust"],
2327
- env: { GEMINI_CLI_TRUST_WORKSPACE: "true" }
2328
- },
2329
- quirks: defaultQuirks({ surfaceThoughts: true, titleFrom: "topic-update" })
2330
- };
2331
-
2332
- // src/strategies/grok.ts
2333
- import { homedir } from "os";
2334
- import path from "path";
2335
- var grokStrategy = {
2336
- id: "grok",
2337
- backendId: buildAcpBackendId("grok"),
2338
- displayName: "Grok",
2339
- authors: ["xAI"],
2340
- discoveryProbe: {
2341
- command: "grok",
2342
- versionArgs: ["--version"],
2343
- helpArgs: ["agent", "stdio", "--help"],
2344
- helpMatches: /Run the agent over stdio/i,
2345
- fallbackCommands: [
2346
- path.join(homedir(), ".grok", "bin", "grok"),
2347
- "/opt/homebrew/bin/grok",
2348
- "/usr/local/bin/grok"
2349
- ]
2350
- },
2351
- spawn: {
2352
- command: "grok",
2353
- args: ["agent", "stdio"]
2354
- },
2355
- // Grok auto-generates the thread title via its vendor notification
2356
- // `_x.ai/session_notification` carrying `session_summary_generated`.
2357
- quirks: defaultQuirks({
2358
- surfaceThoughts: true,
2359
- titleFrom: "session-summary",
2360
- vendorNotificationMethods: ["_x.ai/session_notification"]
2361
- })
2362
- };
2363
-
2364
- // src/strategies/kimi.ts
2365
- import { homedir as homedir2 } from "os";
2366
- import path2 from "path";
2367
- var kimiStrategy = {
2368
- id: "kimi",
2369
- backendId: buildAcpBackendId("kimi"),
2370
- displayName: "Kimi Code CLI",
2371
- authors: ["Moonshot AI"],
2372
- discoveryProbe: {
2373
- command: "kimi",
2374
- versionArgs: ["--version"],
2375
- // Capability signal is the EXIT CODE of `kimi acp --help`, NOT its prose.
2376
- // kimi's commander CLI exits non-zero for an unknown subcommand, so a
2377
- // zero-exit proves the `acp` subcommand exists. No `helpMatches`: the help
2378
- // wording has already drifted across kimi versions (0.11.0 prints "Agent
2379
- // Client Protocol (ACP) server over stdio"), so matching it is fragile.
2380
- helpArgs: ["acp", "--help"],
2381
- // The official Kimi Code installer drops a standalone binary at
2382
- // ~/.kimi-code/bin/kimi and does NOT add it to PATH — so a Finder-launched
2383
- // app misses it without this fallback (mirrors grok/qwen).
2384
- fallbackCommands: [
2385
- path2.join(homedir2(), ".kimi-code", "bin", "kimi"),
2386
- "/opt/homebrew/bin/kimi",
2387
- "/usr/local/bin/kimi"
2388
- ]
2389
- },
2390
- spawn: {
2391
- command: "kimi",
2392
- args: ["acp"]
2393
- },
2394
- quirks: defaultQuirks({ surfaceThoughts: true, titleFrom: "topic-update" })
2395
- };
2396
-
2397
- // src/strategies/qwen.ts
2398
- import { homedir as homedir3 } from "os";
2399
- import path3 from "path";
2400
- var qwenStrategy = {
2401
- id: "qwen",
2402
- backendId: buildAcpBackendId("qwen"),
2403
- displayName: "Qwen Code",
2404
- authors: ["Qwen Team"],
2405
- license: "Apache-2.0",
2406
- repositoryUrl: "https://github.com/QwenLM/qwen-code",
2407
- discoveryProbe: {
2408
- command: "qwen",
2409
- versionArgs: ["--version"],
2410
- helpArgs: ["--help"],
2411
- helpMatches: /(^|\s)--acp(\s|,|$)/,
2412
- fallbackCommands: [
2413
- path3.join(homedir3(), ".qwen", "bin", "qwen"),
2414
- "/opt/homebrew/bin/qwen",
2415
- "/usr/local/bin/qwen"
2416
- ]
2417
- },
2418
- spawn: {
2419
- command: "qwen",
2420
- args: ["--acp"]
2421
- },
2422
- // Qwen's thought chunks are noisy internal scaffolding — suppress them.
2423
- quirks: defaultQuirks({ surfaceThoughts: false, titleFrom: "topic-update" })
2424
- };
2425
-
2426
- // src/strategies/index.ts
2427
- var BUILT_IN_ACP_STRATEGIES = [
2428
- geminiStrategy,
2429
- kimiStrategy,
2430
- grokStrategy,
2431
- qwenStrategy
2432
- ];
2433
- function buildStrategyTable(strategies = BUILT_IN_ACP_STRATEGIES) {
2434
- const table = /* @__PURE__ */ new Map();
2435
- for (const strategy of strategies) {
2436
- table.set(strategy.id, strategy);
2437
- }
2438
- return table;
2439
- }
2440
- function strategyById(id, table = buildStrategyTable()) {
2441
- return table.get(id);
2442
- }
2443
- function strategyByBackendId(backendId, strategies = BUILT_IN_ACP_STRATEGIES) {
2444
- return strategies.find((strategy) => strategy.backendId === backendId);
2445
- }
2446
-
2447
- // src/discovery/acp-local-discovery.ts
2448
- import { execFile as execFileCallback } from "child_process";
2449
- import { readdirSync, realpathSync, statSync } from "fs";
2450
- import { homedir as homedir4 } from "os";
2451
- import path4 from "path";
2452
- import { promisify } from "util";
2453
- import {
2454
- createCommandInvocation as createCommandInvocation2,
2455
- prependCommandDirToPath as prependCommandDirToPath2
2456
- } from "@pwrdrvr/agent-transport";
2457
- var execFile = promisify(execFileCallback);
2458
- var DEFAULT_ACP_PROBE_TIMEOUT_MS = 5e3;
2459
- async function discoverLocalAcpAgentInstances(options = {}) {
2460
- const strategies = options.strategies ?? BUILT_IN_ACP_STRATEGIES;
2461
- const env = options.env ?? process.env;
2462
- const probe = options.probe ?? makeDefaultProbe(env);
2463
- const listExecutables = options.listExecutables ?? defaultListExecutables;
2464
- const now = options.now ?? Date.now;
2465
- const probeOptions = {
2466
- timeoutMs: options.probeTimeoutMs ?? DEFAULT_ACP_PROBE_TIMEOUT_MS,
2467
- signal: options.signal
2468
- };
2469
- const groups = await Promise.all(
2470
- strategies.map(
2471
- (strategy) => discoverStrategyInstances(
2472
- strategy,
2473
- probe,
2474
- now,
2475
- env,
2476
- listExecutables,
2477
- options.overrides?.[strategy.id],
2478
- options.includeRejectedCandidates === true,
2479
- probeOptions
2480
- )
2481
- )
2482
- );
2483
- return groups.filter((group) => group !== void 0);
2484
- }
2485
- async function discoverLocalAcpAgents(options = {}) {
2486
- const groups = await discoverLocalAcpAgentInstances(options);
2487
- return groups.flatMap((group) => {
2488
- const first = group.instances[0];
2489
- if (first === void 0) return [];
2490
- const agent = {
2491
- strategyId: group.strategyId,
2492
- backendId: group.backendId,
2493
- name: group.name,
2494
- command: first.command,
2495
- args: group.args,
2496
- env: group.env,
2497
- discoveredAt: group.discoveredAt
2498
- };
2499
- if (first.version !== void 0) agent.version = first.version;
2500
- return [agent];
2501
- });
2502
- }
2503
- async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override, includeRejectedCandidates = false, probeOptions = {}) {
2504
- const candidates = candidateCommands(strategy, env, listExecutables, override);
2505
- const instances = [];
2506
- const rejectedInstances = [];
2507
- for (const candidate of candidates) {
2508
- if (probeOptions.signal?.aborted) break;
2509
- const [versionAttempt, helpAttempt] = await Promise.all([
2510
- runProbe(probe, candidate.command, strategy.discoveryProbe.versionArgs, probeOptions),
2511
- runProbe(probe, candidate.command, strategy.discoveryProbe.helpArgs, probeOptions)
2512
- ]);
2513
- if (probeOptions.signal?.aborted) break;
2514
- const versionResult = versionAttempt.ok ? versionAttempt.result : void 0;
2515
- const helpResult = helpAttempt.ok ? helpAttempt.result : void 0;
2516
- const version = versionResult ? parseCliVersion(resultText(versionResult)) : void 0;
2517
- if (!versionResult || !helpResult) {
2518
- const timedOut = !versionAttempt.ok && versionAttempt.timedOut || !helpAttempt.ok && helpAttempt.timedOut;
2519
- if (candidate.detected && (timedOut || includeRejectedCandidates)) {
2520
- const rejected = {
2521
- command: candidate.command,
2522
- source: candidate.source,
2523
- reason: timedOut ? "probe-timed-out" : versionResult ? "acp-probe-failed" : "version-probe-failed"
2524
- };
2525
- if (version !== void 0) rejected.version = version;
2526
- rejectedInstances.push(rejected);
2527
- }
2528
- continue;
2529
- }
2530
- if (strategy.discoveryProbe.helpMatches !== void 0 && !strategy.discoveryProbe.helpMatches.test(resultText(helpResult))) {
2531
- if (includeRejectedCandidates && candidate.detected) {
2532
- const rejected = {
2533
- command: candidate.command,
2534
- source: candidate.source,
2535
- reason: "acp-help-mismatch"
2536
- };
2537
- if (version !== void 0) rejected.version = version;
2538
- rejectedInstances.push(rejected);
2539
- }
2540
- continue;
2541
- }
2542
- const instance = {
2543
- command: candidate.command,
2544
- source: candidate.source
2545
- };
2546
- if (version !== void 0) instance.version = version;
2547
- instances.push(instance);
2548
- }
2549
- if (instances.length === 0 && rejectedInstances.length === 0) {
2550
- return void 0;
2551
- }
2552
- return {
2553
- strategyId: strategy.id,
2554
- backendId: strategy.backendId,
2555
- name: strategy.displayName,
2556
- args: ensureArgs(strategy.spawn.args, strategy.spawn.ensureArgs),
2557
- env: strategy.spawn.env ?? {},
2558
- instances,
2559
- ...rejectedInstances.length > 0 ? { rejectedInstances } : {},
2560
- discoveredAt: now()
2561
- };
2562
- }
2563
- function candidateCommands(strategy, env, listExecutables, override) {
2564
- const out = [];
2565
- const seen = /* @__PURE__ */ new Set();
2566
- const push = (command, source, detected) => {
2567
- const trimmed = command.trim();
2568
- if (trimmed.length === 0) return;
2569
- const key = canonicalize(trimmed);
2570
- if (seen.has(key)) return;
2571
- seen.add(key);
2572
- out.push({ command: trimmed, source, detected });
2573
- };
2574
- if (override && override.trim().length > 0) {
2575
- push(override, "override", isExecutableFile(override));
2576
- }
2577
- const pathMatches = listExecutables(strategy.discoveryProbe.command, env);
2578
- if (pathMatches.length > 0) {
2579
- for (const match of pathMatches) push(match, "path", true);
2580
- } else {
2581
- push(strategy.discoveryProbe.command, "path", false);
2582
- }
2583
- for (const fallback of strategy.discoveryProbe.fallbackCommands ?? []) {
2584
- push(fallback, "fallback", isExecutableFile(fallback));
2585
- }
2586
- return out;
2587
- }
2588
- function isExecutableFile(command) {
2589
- try {
2590
- const stat = statSync(command);
2591
- return stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0);
2592
- } catch {
2593
- return false;
2594
- }
2595
- }
2596
- function canonicalize(command) {
2597
- if (!path4.isAbsolute(command)) {
2598
- return process.platform === "win32" ? command.toLowerCase() : command;
2599
- }
2600
- try {
2601
- const canonical = realpathSync(command);
2602
- return process.platform === "win32" ? canonical.toLowerCase() : canonical;
2603
- } catch {
2604
- return process.platform === "win32" ? command.toLowerCase() : command;
2605
- }
2606
- }
2607
- function defaultListExecutables(command, env) {
2608
- if (command.includes("/") || command.includes("\\")) return [];
2609
- const found = [];
2610
- const seenDir = /* @__PURE__ */ new Set();
2611
- for (const dir of discoveryDirs(env)) {
2612
- const normalizedDir = normalizePathEntry(dir);
2613
- const dirKey = process.platform === "win32" ? normalizedDir.toLowerCase() : normalizedDir;
2614
- if (normalizedDir.length === 0 || seenDir.has(dirKey)) continue;
2615
- seenDir.add(dirKey);
2616
- for (const commandName of executableCommandNames(command, env)) {
2617
- const candidate = path4.join(normalizedDir, commandName);
2618
- try {
2619
- const stat = statSync(candidate);
2620
- if (stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0)) {
2621
- found.push(candidate);
2622
- }
2623
- } catch {
2624
- }
2625
- }
2626
- }
2627
- return found;
2628
- }
2629
- function executableCommandNames(command, env) {
2630
- if (process.platform !== "win32") return [command];
2631
- const extensions = (readWindowsEnv(env, "PATHEXT")?.trim() || ".COM;.EXE;.BAT;.CMD").split(";").map((extension) => extension.trim()).filter(Boolean).map((extension) => extension.startsWith(".") ? extension : `.${extension}`);
2632
- const commandExtension = path4.win32.extname(command);
2633
- if (commandExtension.length > 0 && extensions.some((extension) => extension.toLowerCase() === commandExtension.toLowerCase())) {
2634
- return [command];
2635
- }
2636
- return extensions.map((extension) => `${command}${extension}`);
2637
- }
2638
- function readWindowsEnv(env, name) {
2639
- const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
2640
- return key ? env[key] : void 0;
2641
- }
2642
- function normalizePathEntry(entry) {
2643
- const trimmed = entry.trim();
2644
- const quoted = trimmed.match(/^"(.*)"$/);
2645
- return quoted?.[1] ?? trimmed;
2646
- }
2647
- function discoveryDirs(env) {
2648
- const pathValue = process.platform === "win32" ? readWindowsEnv(env, "PATH") ?? "" : env.PATH ?? "";
2649
- const pathDirs = pathValue.split(path4.delimiter);
2650
- return process.platform === "win32" ? pathDirs : [...pathDirs, ...wellKnownAgentBinDirs(homedir4())];
2651
- }
2652
- function wellKnownAgentBinDirs(home = homedir4()) {
2653
- return [
2654
- // Every installed nvm node version's bin (the agent may be under any one).
2655
- ...nvmNodeBinDirs(home),
2656
- // Other version / package managers.
2657
- path4.join(home, ".volta", "bin"),
2658
- path4.join(home, ".bun", "bin"),
2659
- path4.join(home, ".asdf", "shims"),
2660
- path4.join(home, ".deno", "bin"),
2661
- path4.join(home, ".local", "bin"),
2662
- path4.join(home, ".npm-global", "bin"),
2663
- // Common system install prefixes.
2664
- "/opt/homebrew/bin",
2665
- "/usr/local/bin"
2666
- ];
2667
- }
2668
- function nvmNodeBinDirs(home) {
2669
- const base = path4.join(home, ".nvm", "versions", "node");
2670
- try {
2671
- return readdirSync(base).sort().reverse().map((version) => path4.join(base, version, "bin"));
2672
- } catch {
2673
- return [];
2674
- }
2675
- }
2676
- function ensureArgs(args, ensure) {
2677
- if (!ensure || ensure.length === 0) {
2678
- return args;
2679
- }
2680
- const result = [...args];
2681
- for (const arg of ensure) {
2682
- if (!result.includes(arg)) result.push(arg);
2683
- }
2684
- return result;
2685
- }
2686
- var AcpProbeTimeoutError = class extends Error {
2687
- constructor(command, timeoutMs) {
2688
- super(`acp probe did not answer within ${timeoutMs}ms: ${command}`);
2689
- this.name = "AcpProbeTimeoutError";
2690
- }
2691
- };
2692
- function isProbeTimeout(error) {
2693
- if (error instanceof AcpProbeTimeoutError) return true;
2694
- const name = error?.name;
2695
- return error?.killed === true || name === "AbortError" || name === "TimeoutError";
2696
- }
2697
- function normalizeProbeTimeoutMs(timeoutMs) {
2698
- if (timeoutMs === void 0 || !Number.isFinite(timeoutMs)) {
2699
- return DEFAULT_ACP_PROBE_TIMEOUT_MS;
2700
- }
2701
- return Math.max(1, Math.floor(timeoutMs));
2702
- }
2703
- function makeDefaultProbe(env) {
2704
- return async (command, args, options = {}) => {
2705
- const timeoutMs = normalizeProbeTimeoutMs(options.timeoutMs);
2706
- const launchEnv = prependCommandDirToPath2(command, env);
2707
- const invocation = createCommandInvocation2({ command, args, env: launchEnv });
2708
- const controller = new AbortController();
2709
- let timedOut = false;
2710
- const onAbort = () => controller.abort();
2711
- options.signal?.addEventListener("abort", onAbort, { once: true });
2712
- const timer = setTimeout(() => {
2713
- timedOut = true;
2714
- controller.abort();
2715
- }, timeoutMs);
2716
- const expired = new Promise((resolve) => {
2717
- controller.signal.addEventListener("abort", () => resolve("expired"), {
2718
- once: true
2719
- });
2720
- });
2721
- try {
2722
- const execution = execFile(invocation.command, invocation.args, {
2723
- timeout: timeoutMs,
2724
- signal: controller.signal,
2725
- maxBuffer: 1024 * 1024,
2726
- // A Windows `.cmd` shim runs through `cmd.exe`; without this every
2727
- // discovery pass flashes console windows over the host app.
2728
- windowsHide: true,
2729
- // Prepend the candidate's own dir so a node-script CLI (e.g. an nvm-
2730
- // installed `qwen`) finds its sibling `node` during the probe.
2731
- env: launchEnv,
2732
- windowsVerbatimArguments: invocation.windowsVerbatimArguments
2733
- }).then(
2734
- (result) => ({ ok: true, result }),
2735
- (error) => ({ ok: false, error })
2736
- );
2737
- const settled = await Promise.race([execution, expired]);
2738
- if (settled === "expired") {
2739
- if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
2740
- throw new Error(`acp probe aborted: ${command}`);
2741
- }
2742
- if (!settled.ok) {
2743
- if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
2744
- throw settled.error;
2745
- }
2746
- return settled.result;
2747
- } finally {
2748
- clearTimeout(timer);
2749
- options.signal?.removeEventListener("abort", onAbort);
2750
- }
2751
- };
2752
- }
2753
- async function runProbe(probe, command, args, options) {
2754
- try {
2755
- return { ok: true, result: await probe(command, args, options) };
2756
- } catch (error) {
2757
- return { ok: false, timedOut: isProbeTimeout(error) };
2758
- }
2759
- }
2760
- function resultText(result) {
2761
- return [result.stdout, result.stderr].flatMap((value) => value === void 0 ? [] : [value]).map((value) => Buffer.isBuffer(value) ? value.toString("utf8") : value).join("\n");
2762
- }
2763
- function parseCliVersion(output) {
2764
- const trimmed = output.trim();
2765
- if (!trimmed) {
2766
- return void 0;
2767
- }
2768
- return trimmed.match(/\d+\.\d+\.\d+(?:[-+][\w.-]+)?/)?.[0] ?? trimmed;
2769
- }
2770
-
2771
- // src/discovery/acp-agent-allowlist.ts
2772
- var BANNED_ACP_REGISTRY_IDS = /* @__PURE__ */ new Set(["codex-acp"]);
2773
- var DEFAULT_ACP_AGENT_ALLOWLIST = [
2774
- { id: "local-grok-cli", registryId: "grok", distributionKinds: ["local"] },
2775
- { id: "local-qwen-code-cli", registryId: "qwen", distributionKinds: ["local"] }
2776
- ];
2777
- var AcpAgentAllowlist = class {
2778
- constructor(rules) {
2779
- this.rules = rules;
2780
- }
2781
- rules;
2782
- evaluate(agent) {
2783
- if (isBannedAcpRegistryId(agent.id)) {
2784
- return { allowed: false, reason: "banned" };
2785
- }
2786
- const matchingRules = this.rules.filter((rule) => rule.registryId === agent.id);
2787
- if (matchingRules.length === 0) {
2788
- return { allowed: false, reason: "not-allowlisted" };
2789
- }
2790
- for (const rule of matchingRules) {
2791
- const denial = evaluateRule(rule, agent);
2792
- if (!denial) {
2793
- return {
2794
- allowed: true,
2795
- ruleId: rule.id,
2796
- unverifiedBinaryAllowed: rule.allowUnverifiedBinary === true
2797
- };
2798
- }
2799
- }
2800
- return { allowed: false, reason: "allowlist-rule-mismatch" };
2801
- }
2802
- evaluateDistribution(agent, distribution) {
2803
- if (isBannedAcpRegistryId(agent.id)) {
2804
- return { allowed: false, reason: "banned" };
2805
- }
2806
- const matchingRules = this.rules.filter((rule) => rule.registryId === agent.id);
2807
- if (matchingRules.length === 0) {
2808
- return { allowed: false, reason: "not-allowlisted" };
2809
- }
2810
- for (const rule of matchingRules) {
2811
- const denial = evaluateDistributionRule(rule, agent, distribution);
2812
- if (!denial) {
2813
- return {
2814
- allowed: true,
2815
- ruleId: rule.id,
2816
- unverifiedBinaryAllowed: rule.allowUnverifiedBinary === true
2817
- };
2818
- }
2819
- }
2820
- return { allowed: false, reason: "allowlist-rule-mismatch" };
2821
- }
2822
- };
2823
- var defaultAcpAgentAllowlist = new AcpAgentAllowlist(
2824
- DEFAULT_ACP_AGENT_ALLOWLIST
2825
- );
2826
- function isBannedAcpRegistryId(registryId) {
2827
- return BANNED_ACP_REGISTRY_IDS.has(registryId);
2828
- }
2829
- function evaluateRule(rule, agent) {
2830
- if (rule.versions && (!agent.version || !rule.versions.includes(agent.version))) {
2831
- return "version-not-allowed";
2832
- }
2833
- if (isGplFamilyLicense(agent.license) && !rule.allowGplFamilyLicense) {
2834
- return "license-not-allowed";
2835
- }
2836
- let distributionDeniedBySource = false;
2837
- for (const distribution of agent.distributions) {
2838
- const denial = evaluateDistributionRule(rule, agent, distribution, {
2839
- skipAgentChecks: true
2840
- });
2841
- if (!denial) {
2842
- return void 0;
2843
- }
2844
- if (denial === "distribution-source-not-allowed") {
2845
- distributionDeniedBySource = true;
2846
- }
2847
- }
2848
- return distributionDeniedBySource ? "distribution-source-not-allowed" : "distribution-not-allowed";
2849
- }
2850
- function evaluateDistributionRule(rule, agent, distribution, options = {}) {
2851
- if (!options.skipAgentChecks && rule.versions && (!agent.version || !rule.versions.includes(agent.version))) {
2852
- return "version-not-allowed";
2853
- }
2854
- if (!options.skipAgentChecks && isGplFamilyLicense(agent.license) && !rule.allowGplFamilyLicense) {
2855
- return "license-not-allowed";
2856
- }
2857
- if (!distributionAllowedByKind(rule, distribution)) {
2858
- return "distribution-not-allowed";
2859
- }
2860
- if (!distributionSourceAllowed(rule, distribution)) {
2861
- return "distribution-source-not-allowed";
2862
- }
2863
- return void 0;
2864
- }
2865
- function distributionAllowedByKind(rule, distribution) {
2866
- return !rule.distributionKinds || rule.distributionKinds.includes(distribution.kind);
2867
- }
2868
- function distributionSourceAllowed(rule, distribution) {
2869
- if (distribution.kind === "npx" || distribution.kind === "uvx") {
2870
- return !rule.allowedPackageNames || rule.allowedPackageNames.includes(distribution.packageName);
2871
- }
2872
- if (distribution.kind !== "binary") {
2873
- return false;
2874
- }
2875
- if (!rule.allowedArchiveHosts) {
2876
- return true;
2877
- }
2878
- try {
2879
- return rule.allowedArchiveHosts.includes(new URL(distribution.archiveUrl).host);
2880
- } catch {
2881
- return false;
2882
- }
2883
- }
2884
- function isGplFamilyLicense(license) {
2885
- return /\b(?:GPL|AGPL|LGPL)\b/i.test(license ?? "");
2886
- }
2887
-
2888
- // src/discovery/acp-registry-types.ts
2889
- var ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
2890
-
2891
- // src/discovery/acp-registry-service.ts
2892
- var AcpRegistryService = class {
2893
- allowlist;
2894
- fetcher;
2895
- now;
2896
- registryUrl;
2897
- constructor(options = {}) {
2898
- this.allowlist = options.allowlist ?? defaultAcpAgentAllowlist;
2899
- this.fetcher = options.fetch ?? (async (input, init) => {
2900
- const response = await globalThis.fetch(input, init);
2901
- return {
2902
- ok: response.ok,
2903
- status: response.status,
2904
- statusText: response.statusText,
2905
- json: () => response.json()
2906
- };
2907
- });
2908
- this.now = options.now ?? Date.now;
2909
- this.registryUrl = options.registryUrl ?? ACP_REGISTRY_URL;
2910
- }
2911
- async fetchRegistry() {
2912
- const response = await this.fetcher(this.registryUrl, {
2913
- headers: { accept: "application/json" }
2914
- });
2915
- if (!response.ok) {
2916
- throw new Error(
2917
- `ACP registry request failed: ${response.status} ${response.statusText}`
2918
- );
2919
- }
2920
- const raw = await response.json();
2921
- return { fetchedAt: this.now(), agents: normalizeRegistry(raw), raw };
2922
- }
2923
- applyAllowlist(snapshot) {
2924
- return snapshot.agents.map((agent) => {
2925
- const allowlist = this.allowlist.evaluate(agent);
2926
- const distributionPolicies = agent.distributions.map(
2927
- (distribution) => this.evaluateDistribution(agent, distribution)
2928
- );
2929
- const installablePolicy = distributionPolicies.find((policy) => policy.installable);
2930
- const firstBlockedAllowedPolicy = distributionPolicies.find(
2931
- (policy) => policy.allowlist.allowed
2932
- );
2933
- const installable = Boolean(installablePolicy);
2934
- const result = {
2935
- ...agent,
2936
- allowlist,
2937
- installable,
2938
- verificationStatus: installablePolicy?.verificationStatus ?? firstBlockedAllowedPolicy?.verificationStatus ?? "not-applicable"
2939
- };
2940
- const unavailableReason = installable ? void 0 : firstBlockedAllowedPolicy?.unavailableReason ?? (allowlist.allowed ? "distribution-not-installable" : allowlist.reason);
2941
- if (unavailableReason !== void 0) result.unavailableReason = unavailableReason;
2942
- return result;
2943
- });
2944
- }
2945
- evaluateDistribution(agent, distribution) {
2946
- const allowlist = this.allowlist.evaluateDistribution(agent, distribution);
2947
- return evaluateAcpDistributionPolicy(distribution, allowlist);
2948
- }
2949
- };
2950
- function evaluateAcpDistributionPolicy(distribution, allowlist) {
2951
- if (!allowlist.allowed) {
2952
- return {
2953
- allowlist,
2954
- installable: false,
2955
- verificationStatus: "not-applicable",
2956
- unavailableReason: allowlist.reason
2957
- };
2958
- }
2959
- if (distribution.kind !== "binary") {
2960
- return { allowlist, installable: true, verificationStatus: "not-applicable" };
2961
- }
2962
- if (distribution.checksum) {
2963
- return { allowlist, installable: true, verificationStatus: "verified" };
2964
- }
2965
- return {
2966
- allowlist,
2967
- installable: allowlist.unverifiedBinaryAllowed,
2968
- verificationStatus: allowlist.unverifiedBinaryAllowed ? "unverified-allowed" : "unverified-blocked",
2969
- ...allowlist.unverifiedBinaryAllowed ? {} : { unavailableReason: "binary-integrity-metadata-missing" }
2970
- };
2971
- }
2972
- function normalizeRegistry(raw) {
2973
- const record = asRecord3(raw);
2974
- const rawAgents = Array.isArray(record?.agents) ? record.agents : [];
2975
- return rawAgents.flatMap((item) => {
2976
- const agent = normalizeAgent(item);
2977
- return agent ? [agent] : [];
2978
- });
2979
- }
2980
- function normalizeAgent(raw) {
2981
- const record = asRecord3(raw);
2982
- if (!record || typeof record.id !== "string" || typeof record.name !== "string") {
2983
- return void 0;
2984
- }
2985
- const distributions = normalizeDistributions(record.distribution);
2986
- if (distributions.length === 0) {
2987
- return void 0;
2988
- }
2989
- const agent = {
2990
- id: record.id,
2991
- backendId: buildAcpBackendId(record.id),
2992
- name: record.name,
2993
- authors: Array.isArray(record.authors) ? record.authors.filter((author) => typeof author === "string") : [],
2994
- distributions,
2995
- distributionKinds: [...new Set(distributions.map((d) => d.kind))],
2996
- auth: normalizeAuth(record.auth),
2997
- raw
2998
- };
2999
- const version = stringValue(record.version);
3000
- if (version !== void 0) agent.version = version;
3001
- const description = stringValue(record.description);
3002
- if (description !== void 0) agent.description = description;
3003
- const license = stringValue(record.license);
3004
- if (license !== void 0) agent.license = license;
3005
- const repositoryUrl = stringValue(record.repository);
3006
- if (repositoryUrl !== void 0) agent.repositoryUrl = repositoryUrl;
3007
- const websiteUrl = stringValue(record.website);
3008
- if (websiteUrl !== void 0) agent.websiteUrl = websiteUrl;
3009
- const iconUrl = stringValue(record.icon);
3010
- if (iconUrl !== void 0) agent.iconUrl = iconUrl;
3011
- return agent;
3012
- }
3013
- function normalizeDistributions(raw) {
3014
- const record = asRecord3(raw);
3015
- if (!record) {
3016
- return [];
3017
- }
3018
- const distributions = [];
3019
- const npx = normalizePackageDistribution("npx", record.npx);
3020
- if (npx) distributions.push(npx);
3021
- const uvx = normalizePackageDistribution("uvx", record.uvx);
3022
- if (uvx) distributions.push(uvx);
3023
- const binaryRecord = asRecord3(record.binary);
3024
- if (binaryRecord) {
3025
- for (const [platform, value] of Object.entries(binaryRecord)) {
3026
- const binary = normalizeBinaryDistribution(platform, value);
3027
- if (binary) distributions.push(binary);
3028
- }
3029
- }
3030
- return distributions;
3031
- }
3032
- function normalizePackageDistribution(kind, raw) {
3033
- const record = asRecord3(raw);
3034
- if (!record || typeof record.package !== "string") {
3035
- return void 0;
3036
- }
3037
- return {
3038
- kind,
3039
- packageName: record.package,
3040
- args: stringArray(record.args),
3041
- env: stringRecord(record.env)
3042
- };
3043
- }
3044
- function normalizeBinaryDistribution(platform, raw) {
3045
- const record = asRecord3(raw);
3046
- if (!record || typeof record.archive !== "string" || typeof record.cmd !== "string") {
3047
- return void 0;
3048
- }
3049
- const distribution = {
3050
- kind: "binary",
3051
- platform,
3052
- archiveUrl: record.archive,
3053
- command: record.cmd,
3054
- args: stringArray(record.args),
3055
- env: stringRecord(record.env)
3056
- };
3057
- const checksum = stringValue(record.checksum) ?? stringValue(record.sha256);
3058
- if (checksum !== void 0) distribution.checksum = checksum;
3059
- const signatureUrl = stringValue(record.signature) ?? stringValue(record.signatureUrl);
3060
- if (signatureUrl !== void 0) distribution.signatureUrl = signatureUrl;
3061
- return distribution;
3062
- }
3063
- function normalizeAuth(raw) {
3064
- if (!raw) {
3065
- return { required: false, methods: [] };
3066
- }
3067
- const record = asRecord3(raw);
3068
- if (!record) {
3069
- return { required: true, methods: ["unknown"], raw };
3070
- }
3071
- const methods = stringArray(record.methods).map(
3072
- (method) => method === "agent-managed" || method === "terminal" ? method : "unknown"
3073
- );
3074
- return {
3075
- required: record.required === false ? false : true,
3076
- methods: methods.length > 0 ? [...new Set(methods)] : ["unknown"],
3077
- raw
3078
- };
3079
- }
3080
- function asRecord3(value) {
3081
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3082
- }
3083
- function stringValue(value) {
3084
- return typeof value === "string" && value.trim() ? value : void 0;
3085
- }
3086
- function stringArray(value) {
3087
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
3088
- }
3089
- function stringRecord(value) {
3090
- const record = asRecord3(value);
3091
- if (!record) {
3092
- return {};
3093
- }
3094
- return Object.fromEntries(
3095
- Object.entries(record).filter(
3096
- (entry) => typeof entry[1] === "string"
3097
- )
3098
- );
3099
- }
3100
2284
  export {
3101
2285
  ACP_REGISTRY_URL,
3102
2286
  ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX,