@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.
@@ -0,0 +1,675 @@
1
+ import {
2
+ BUILT_IN_ACP_STRATEGIES,
3
+ buildAcpBackendId
4
+ } from "./chunk-OFMIS7W6.js";
5
+
6
+ // src/discovery/acp-local-discovery.ts
7
+ import { execFile as execFileCallback } from "child_process";
8
+ import { readdirSync, realpathSync, statSync } from "fs";
9
+ import { homedir } from "os";
10
+ import path from "path";
11
+ import { promisify } from "util";
12
+ import {
13
+ createCommandInvocation,
14
+ prependCommandDirToPath
15
+ } from "@pwrdrvr/agent-transport";
16
+ var execFile = promisify(execFileCallback);
17
+ var DEFAULT_ACP_PROBE_TIMEOUT_MS = 5e3;
18
+ async function discoverLocalAcpAgentInstances(options = {}) {
19
+ const strategies = options.strategies ?? BUILT_IN_ACP_STRATEGIES;
20
+ const env = options.env ?? process.env;
21
+ const probe = options.probe ?? makeDefaultProbe(env);
22
+ const listExecutables = options.listExecutables ?? defaultListExecutables;
23
+ const now = options.now ?? Date.now;
24
+ const probeOptions = {
25
+ timeoutMs: options.probeTimeoutMs ?? DEFAULT_ACP_PROBE_TIMEOUT_MS,
26
+ signal: options.signal
27
+ };
28
+ const groups = await Promise.all(
29
+ strategies.map(
30
+ (strategy) => discoverStrategyInstances(
31
+ strategy,
32
+ probe,
33
+ now,
34
+ env,
35
+ listExecutables,
36
+ options.overrides?.[strategy.id],
37
+ options.includeRejectedCandidates === true,
38
+ probeOptions
39
+ )
40
+ )
41
+ );
42
+ return groups.filter((group) => group !== void 0);
43
+ }
44
+ async function discoverLocalAcpAgents(options = {}) {
45
+ const groups = await discoverLocalAcpAgentInstances(options);
46
+ return groups.flatMap((group) => {
47
+ const first = group.instances[0];
48
+ if (first === void 0) return [];
49
+ const agent = {
50
+ strategyId: group.strategyId,
51
+ backendId: group.backendId,
52
+ name: group.name,
53
+ command: first.command,
54
+ args: group.args,
55
+ env: group.env,
56
+ discoveredAt: group.discoveredAt
57
+ };
58
+ if (first.version !== void 0) agent.version = first.version;
59
+ return [agent];
60
+ });
61
+ }
62
+ async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override, includeRejectedCandidates = false, probeOptions = {}) {
63
+ const candidates = candidateCommands(strategy, env, listExecutables, override);
64
+ const instances = [];
65
+ const rejectedInstances = [];
66
+ for (const candidate of candidates) {
67
+ if (probeOptions.signal?.aborted) break;
68
+ const [versionAttempt, helpAttempt] = await Promise.all([
69
+ runProbe(probe, candidate.command, strategy.discoveryProbe.versionArgs, probeOptions),
70
+ runProbe(probe, candidate.command, strategy.discoveryProbe.helpArgs, probeOptions)
71
+ ]);
72
+ if (probeOptions.signal?.aborted) break;
73
+ const versionResult = versionAttempt.ok ? versionAttempt.result : void 0;
74
+ const helpResult = helpAttempt.ok ? helpAttempt.result : void 0;
75
+ const version = versionResult ? parseCliVersion(resultText(versionResult)) : void 0;
76
+ if (!versionResult || !helpResult) {
77
+ const timedOut = !versionAttempt.ok && versionAttempt.timedOut || !helpAttempt.ok && helpAttempt.timedOut;
78
+ if (candidate.detected && (timedOut || includeRejectedCandidates)) {
79
+ const rejected = {
80
+ command: candidate.command,
81
+ source: candidate.source,
82
+ reason: timedOut ? "probe-timed-out" : versionResult ? "acp-probe-failed" : "version-probe-failed"
83
+ };
84
+ if (version !== void 0) rejected.version = version;
85
+ rejectedInstances.push(rejected);
86
+ }
87
+ continue;
88
+ }
89
+ if (strategy.discoveryProbe.helpMatches !== void 0 && !strategy.discoveryProbe.helpMatches.test(resultText(helpResult))) {
90
+ if (includeRejectedCandidates && candidate.detected) {
91
+ const rejected = {
92
+ command: candidate.command,
93
+ source: candidate.source,
94
+ reason: "acp-help-mismatch"
95
+ };
96
+ if (version !== void 0) rejected.version = version;
97
+ rejectedInstances.push(rejected);
98
+ }
99
+ continue;
100
+ }
101
+ const instance = {
102
+ command: candidate.command,
103
+ source: candidate.source
104
+ };
105
+ if (version !== void 0) instance.version = version;
106
+ instances.push(instance);
107
+ }
108
+ if (instances.length === 0 && rejectedInstances.length === 0) {
109
+ return void 0;
110
+ }
111
+ return {
112
+ strategyId: strategy.id,
113
+ backendId: strategy.backendId,
114
+ name: strategy.displayName,
115
+ args: ensureArgs(strategy.spawn.args, strategy.spawn.ensureArgs),
116
+ env: strategy.spawn.env ?? {},
117
+ instances,
118
+ ...rejectedInstances.length > 0 ? { rejectedInstances } : {},
119
+ discoveredAt: now()
120
+ };
121
+ }
122
+ function candidateCommands(strategy, env, listExecutables, override) {
123
+ const out = [];
124
+ const seen = /* @__PURE__ */ new Set();
125
+ const push = (command, source, detected) => {
126
+ const trimmed = command.trim();
127
+ if (trimmed.length === 0) return;
128
+ const key = canonicalize(trimmed);
129
+ if (seen.has(key)) return;
130
+ seen.add(key);
131
+ out.push({ command: trimmed, source, detected });
132
+ };
133
+ if (override && override.trim().length > 0) {
134
+ push(override, "override", isExecutableFile(override));
135
+ }
136
+ const pathMatches = listExecutables(strategy.discoveryProbe.command, env);
137
+ if (pathMatches.length > 0) {
138
+ for (const match of pathMatches) push(match, "path", true);
139
+ } else {
140
+ push(strategy.discoveryProbe.command, "path", false);
141
+ }
142
+ for (const fallback of strategy.discoveryProbe.fallbackCommands ?? []) {
143
+ push(fallback, "fallback", isExecutableFile(fallback));
144
+ }
145
+ return out;
146
+ }
147
+ function isExecutableFile(command) {
148
+ try {
149
+ const stat = statSync(command);
150
+ return stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0);
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+ function canonicalize(command) {
156
+ if (!path.isAbsolute(command)) {
157
+ return process.platform === "win32" ? command.toLowerCase() : command;
158
+ }
159
+ try {
160
+ const canonical = realpathSync(command);
161
+ return process.platform === "win32" ? canonical.toLowerCase() : canonical;
162
+ } catch {
163
+ return process.platform === "win32" ? command.toLowerCase() : command;
164
+ }
165
+ }
166
+ function defaultListExecutables(command, env) {
167
+ if (command.includes("/") || command.includes("\\")) return [];
168
+ const found = [];
169
+ const seenDir = /* @__PURE__ */ new Set();
170
+ for (const dir of discoveryDirs(env)) {
171
+ const normalizedDir = normalizePathEntry(dir);
172
+ const dirKey = process.platform === "win32" ? normalizedDir.toLowerCase() : normalizedDir;
173
+ if (normalizedDir.length === 0 || seenDir.has(dirKey)) continue;
174
+ seenDir.add(dirKey);
175
+ for (const commandName of executableCommandNames(command, env)) {
176
+ const candidate = path.join(normalizedDir, commandName);
177
+ try {
178
+ const stat = statSync(candidate);
179
+ if (stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0)) {
180
+ found.push(candidate);
181
+ }
182
+ } catch {
183
+ }
184
+ }
185
+ }
186
+ return found;
187
+ }
188
+ function executableCommandNames(command, env) {
189
+ if (process.platform !== "win32") return [command];
190
+ const extensions = (readWindowsEnv(env, "PATHEXT")?.trim() || ".COM;.EXE;.BAT;.CMD").split(";").map((extension) => extension.trim()).filter(Boolean).map((extension) => extension.startsWith(".") ? extension : `.${extension}`);
191
+ const commandExtension = path.win32.extname(command);
192
+ if (commandExtension.length > 0 && extensions.some((extension) => extension.toLowerCase() === commandExtension.toLowerCase())) {
193
+ return [command];
194
+ }
195
+ return extensions.map((extension) => `${command}${extension}`);
196
+ }
197
+ function readWindowsEnv(env, name) {
198
+ const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
199
+ return key ? env[key] : void 0;
200
+ }
201
+ function normalizePathEntry(entry) {
202
+ const trimmed = entry.trim();
203
+ const quoted = trimmed.match(/^"(.*)"$/);
204
+ return quoted?.[1] ?? trimmed;
205
+ }
206
+ function discoveryDirs(env) {
207
+ const pathValue = process.platform === "win32" ? readWindowsEnv(env, "PATH") ?? "" : env.PATH ?? "";
208
+ const pathDirs = pathValue.split(path.delimiter);
209
+ return process.platform === "win32" ? pathDirs : [...pathDirs, ...wellKnownAgentBinDirs(homedir())];
210
+ }
211
+ function wellKnownAgentBinDirs(home = homedir()) {
212
+ return [
213
+ // Every installed nvm node version's bin (the agent may be under any one).
214
+ ...nvmNodeBinDirs(home),
215
+ // Other version / package managers.
216
+ path.join(home, ".volta", "bin"),
217
+ path.join(home, ".bun", "bin"),
218
+ path.join(home, ".asdf", "shims"),
219
+ path.join(home, ".deno", "bin"),
220
+ path.join(home, ".local", "bin"),
221
+ path.join(home, ".npm-global", "bin"),
222
+ // Common system install prefixes.
223
+ "/opt/homebrew/bin",
224
+ "/usr/local/bin"
225
+ ];
226
+ }
227
+ function nvmNodeBinDirs(home) {
228
+ const base = path.join(home, ".nvm", "versions", "node");
229
+ try {
230
+ return readdirSync(base).sort().reverse().map((version) => path.join(base, version, "bin"));
231
+ } catch {
232
+ return [];
233
+ }
234
+ }
235
+ function ensureArgs(args, ensure) {
236
+ if (!ensure || ensure.length === 0) {
237
+ return args;
238
+ }
239
+ const result = [...args];
240
+ for (const arg of ensure) {
241
+ if (!result.includes(arg)) result.push(arg);
242
+ }
243
+ return result;
244
+ }
245
+ var AcpProbeTimeoutError = class extends Error {
246
+ constructor(command, timeoutMs) {
247
+ super(`acp probe did not answer within ${timeoutMs}ms: ${command}`);
248
+ this.name = "AcpProbeTimeoutError";
249
+ }
250
+ };
251
+ function isProbeTimeout(error) {
252
+ if (error instanceof AcpProbeTimeoutError) return true;
253
+ const name = error?.name;
254
+ return error?.killed === true || name === "AbortError" || name === "TimeoutError";
255
+ }
256
+ function normalizeProbeTimeoutMs(timeoutMs) {
257
+ if (timeoutMs === void 0 || !Number.isFinite(timeoutMs)) {
258
+ return DEFAULT_ACP_PROBE_TIMEOUT_MS;
259
+ }
260
+ return Math.max(1, Math.floor(timeoutMs));
261
+ }
262
+ function makeDefaultProbe(env) {
263
+ return async (command, args, options = {}) => {
264
+ const timeoutMs = normalizeProbeTimeoutMs(options.timeoutMs);
265
+ const launchEnv = prependCommandDirToPath(command, env);
266
+ const invocation = createCommandInvocation({ command, args, env: launchEnv });
267
+ const controller = new AbortController();
268
+ let timedOut = false;
269
+ const onAbort = () => controller.abort();
270
+ options.signal?.addEventListener("abort", onAbort, { once: true });
271
+ const timer = setTimeout(() => {
272
+ timedOut = true;
273
+ controller.abort();
274
+ }, timeoutMs);
275
+ const expired = new Promise((resolve) => {
276
+ controller.signal.addEventListener("abort", () => resolve("expired"), {
277
+ once: true
278
+ });
279
+ });
280
+ try {
281
+ const execution = execFile(invocation.command, invocation.args, {
282
+ timeout: timeoutMs,
283
+ signal: controller.signal,
284
+ maxBuffer: 1024 * 1024,
285
+ // A Windows `.cmd` shim runs through `cmd.exe`; without this every
286
+ // discovery pass flashes console windows over the host app.
287
+ windowsHide: true,
288
+ // Prepend the candidate's own dir so a node-script CLI (e.g. an nvm-
289
+ // installed `qwen`) finds its sibling `node` during the probe.
290
+ env: launchEnv,
291
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
292
+ }).then(
293
+ (result) => ({ ok: true, result }),
294
+ (error) => ({ ok: false, error })
295
+ );
296
+ const settled = await Promise.race([execution, expired]);
297
+ if (settled === "expired") {
298
+ if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
299
+ throw new Error(`acp probe aborted: ${command}`);
300
+ }
301
+ if (!settled.ok) {
302
+ if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
303
+ throw settled.error;
304
+ }
305
+ return settled.result;
306
+ } finally {
307
+ clearTimeout(timer);
308
+ options.signal?.removeEventListener("abort", onAbort);
309
+ }
310
+ };
311
+ }
312
+ async function runProbe(probe, command, args, options) {
313
+ try {
314
+ return { ok: true, result: await probe(command, args, options) };
315
+ } catch (error) {
316
+ return { ok: false, timedOut: isProbeTimeout(error) };
317
+ }
318
+ }
319
+ function resultText(result) {
320
+ return [result.stdout, result.stderr].flatMap((value) => value === void 0 ? [] : [value]).map((value) => Buffer.isBuffer(value) ? value.toString("utf8") : value).join("\n");
321
+ }
322
+ function parseCliVersion(output) {
323
+ const trimmed = output.trim();
324
+ if (!trimmed) {
325
+ return void 0;
326
+ }
327
+ return trimmed.match(/\d+\.\d+\.\d+(?:[-+][\w.-]+)?/)?.[0] ?? trimmed;
328
+ }
329
+
330
+ // src/discovery/acp-agent-allowlist.ts
331
+ var BANNED_ACP_REGISTRY_IDS = /* @__PURE__ */ new Set(["codex-acp"]);
332
+ var DEFAULT_ACP_AGENT_ALLOWLIST = [
333
+ { id: "local-grok-cli", registryId: "grok", distributionKinds: ["local"] },
334
+ { id: "local-qwen-code-cli", registryId: "qwen", distributionKinds: ["local"] }
335
+ ];
336
+ var AcpAgentAllowlist = class {
337
+ constructor(rules) {
338
+ this.rules = rules;
339
+ }
340
+ rules;
341
+ evaluate(agent) {
342
+ if (isBannedAcpRegistryId(agent.id)) {
343
+ return { allowed: false, reason: "banned" };
344
+ }
345
+ const matchingRules = this.rules.filter((rule) => rule.registryId === agent.id);
346
+ if (matchingRules.length === 0) {
347
+ return { allowed: false, reason: "not-allowlisted" };
348
+ }
349
+ for (const rule of matchingRules) {
350
+ const denial = evaluateRule(rule, agent);
351
+ if (!denial) {
352
+ return {
353
+ allowed: true,
354
+ ruleId: rule.id,
355
+ unverifiedBinaryAllowed: rule.allowUnverifiedBinary === true
356
+ };
357
+ }
358
+ }
359
+ return { allowed: false, reason: "allowlist-rule-mismatch" };
360
+ }
361
+ evaluateDistribution(agent, distribution) {
362
+ if (isBannedAcpRegistryId(agent.id)) {
363
+ return { allowed: false, reason: "banned" };
364
+ }
365
+ const matchingRules = this.rules.filter((rule) => rule.registryId === agent.id);
366
+ if (matchingRules.length === 0) {
367
+ return { allowed: false, reason: "not-allowlisted" };
368
+ }
369
+ for (const rule of matchingRules) {
370
+ const denial = evaluateDistributionRule(rule, agent, distribution);
371
+ if (!denial) {
372
+ return {
373
+ allowed: true,
374
+ ruleId: rule.id,
375
+ unverifiedBinaryAllowed: rule.allowUnverifiedBinary === true
376
+ };
377
+ }
378
+ }
379
+ return { allowed: false, reason: "allowlist-rule-mismatch" };
380
+ }
381
+ };
382
+ var defaultAcpAgentAllowlist = new AcpAgentAllowlist(
383
+ DEFAULT_ACP_AGENT_ALLOWLIST
384
+ );
385
+ function isBannedAcpRegistryId(registryId) {
386
+ return BANNED_ACP_REGISTRY_IDS.has(registryId);
387
+ }
388
+ function evaluateRule(rule, agent) {
389
+ if (rule.versions && (!agent.version || !rule.versions.includes(agent.version))) {
390
+ return "version-not-allowed";
391
+ }
392
+ if (isGplFamilyLicense(agent.license) && !rule.allowGplFamilyLicense) {
393
+ return "license-not-allowed";
394
+ }
395
+ let distributionDeniedBySource = false;
396
+ for (const distribution of agent.distributions) {
397
+ const denial = evaluateDistributionRule(rule, agent, distribution, {
398
+ skipAgentChecks: true
399
+ });
400
+ if (!denial) {
401
+ return void 0;
402
+ }
403
+ if (denial === "distribution-source-not-allowed") {
404
+ distributionDeniedBySource = true;
405
+ }
406
+ }
407
+ return distributionDeniedBySource ? "distribution-source-not-allowed" : "distribution-not-allowed";
408
+ }
409
+ function evaluateDistributionRule(rule, agent, distribution, options = {}) {
410
+ if (!options.skipAgentChecks && rule.versions && (!agent.version || !rule.versions.includes(agent.version))) {
411
+ return "version-not-allowed";
412
+ }
413
+ if (!options.skipAgentChecks && isGplFamilyLicense(agent.license) && !rule.allowGplFamilyLicense) {
414
+ return "license-not-allowed";
415
+ }
416
+ if (!distributionAllowedByKind(rule, distribution)) {
417
+ return "distribution-not-allowed";
418
+ }
419
+ if (!distributionSourceAllowed(rule, distribution)) {
420
+ return "distribution-source-not-allowed";
421
+ }
422
+ return void 0;
423
+ }
424
+ function distributionAllowedByKind(rule, distribution) {
425
+ return !rule.distributionKinds || rule.distributionKinds.includes(distribution.kind);
426
+ }
427
+ function distributionSourceAllowed(rule, distribution) {
428
+ if (distribution.kind === "npx" || distribution.kind === "uvx") {
429
+ return !rule.allowedPackageNames || rule.allowedPackageNames.includes(distribution.packageName);
430
+ }
431
+ if (distribution.kind !== "binary") {
432
+ return false;
433
+ }
434
+ if (!rule.allowedArchiveHosts) {
435
+ return true;
436
+ }
437
+ try {
438
+ return rule.allowedArchiveHosts.includes(new URL(distribution.archiveUrl).host);
439
+ } catch {
440
+ return false;
441
+ }
442
+ }
443
+ function isGplFamilyLicense(license) {
444
+ return /\b(?:GPL|AGPL|LGPL)\b/i.test(license ?? "");
445
+ }
446
+
447
+ // src/discovery/acp-registry-types.ts
448
+ var ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
449
+
450
+ // src/discovery/acp-registry-service.ts
451
+ var AcpRegistryService = class {
452
+ allowlist;
453
+ fetcher;
454
+ now;
455
+ registryUrl;
456
+ constructor(options = {}) {
457
+ this.allowlist = options.allowlist ?? defaultAcpAgentAllowlist;
458
+ this.fetcher = options.fetch ?? (async (input, init) => {
459
+ const response = await globalThis.fetch(input, init);
460
+ return {
461
+ ok: response.ok,
462
+ status: response.status,
463
+ statusText: response.statusText,
464
+ json: () => response.json()
465
+ };
466
+ });
467
+ this.now = options.now ?? Date.now;
468
+ this.registryUrl = options.registryUrl ?? ACP_REGISTRY_URL;
469
+ }
470
+ async fetchRegistry() {
471
+ const response = await this.fetcher(this.registryUrl, {
472
+ headers: { accept: "application/json" }
473
+ });
474
+ if (!response.ok) {
475
+ throw new Error(
476
+ `ACP registry request failed: ${response.status} ${response.statusText}`
477
+ );
478
+ }
479
+ const raw = await response.json();
480
+ return { fetchedAt: this.now(), agents: normalizeRegistry(raw), raw };
481
+ }
482
+ applyAllowlist(snapshot) {
483
+ return snapshot.agents.map((agent) => {
484
+ const allowlist = this.allowlist.evaluate(agent);
485
+ const distributionPolicies = agent.distributions.map(
486
+ (distribution) => this.evaluateDistribution(agent, distribution)
487
+ );
488
+ const installablePolicy = distributionPolicies.find((policy) => policy.installable);
489
+ const firstBlockedAllowedPolicy = distributionPolicies.find(
490
+ (policy) => policy.allowlist.allowed
491
+ );
492
+ const installable = Boolean(installablePolicy);
493
+ const result = {
494
+ ...agent,
495
+ allowlist,
496
+ installable,
497
+ verificationStatus: installablePolicy?.verificationStatus ?? firstBlockedAllowedPolicy?.verificationStatus ?? "not-applicable"
498
+ };
499
+ const unavailableReason = installable ? void 0 : firstBlockedAllowedPolicy?.unavailableReason ?? (allowlist.allowed ? "distribution-not-installable" : allowlist.reason);
500
+ if (unavailableReason !== void 0) result.unavailableReason = unavailableReason;
501
+ return result;
502
+ });
503
+ }
504
+ evaluateDistribution(agent, distribution) {
505
+ const allowlist = this.allowlist.evaluateDistribution(agent, distribution);
506
+ return evaluateAcpDistributionPolicy(distribution, allowlist);
507
+ }
508
+ };
509
+ function evaluateAcpDistributionPolicy(distribution, allowlist) {
510
+ if (!allowlist.allowed) {
511
+ return {
512
+ allowlist,
513
+ installable: false,
514
+ verificationStatus: "not-applicable",
515
+ unavailableReason: allowlist.reason
516
+ };
517
+ }
518
+ if (distribution.kind !== "binary") {
519
+ return { allowlist, installable: true, verificationStatus: "not-applicable" };
520
+ }
521
+ if (distribution.checksum) {
522
+ return { allowlist, installable: true, verificationStatus: "verified" };
523
+ }
524
+ return {
525
+ allowlist,
526
+ installable: allowlist.unverifiedBinaryAllowed,
527
+ verificationStatus: allowlist.unverifiedBinaryAllowed ? "unverified-allowed" : "unverified-blocked",
528
+ ...allowlist.unverifiedBinaryAllowed ? {} : { unavailableReason: "binary-integrity-metadata-missing" }
529
+ };
530
+ }
531
+ function normalizeRegistry(raw) {
532
+ const record = asRecord(raw);
533
+ const rawAgents = Array.isArray(record?.agents) ? record.agents : [];
534
+ return rawAgents.flatMap((item) => {
535
+ const agent = normalizeAgent(item);
536
+ return agent ? [agent] : [];
537
+ });
538
+ }
539
+ function normalizeAgent(raw) {
540
+ const record = asRecord(raw);
541
+ if (!record || typeof record.id !== "string" || typeof record.name !== "string") {
542
+ return void 0;
543
+ }
544
+ const distributions = normalizeDistributions(record.distribution);
545
+ if (distributions.length === 0) {
546
+ return void 0;
547
+ }
548
+ const agent = {
549
+ id: record.id,
550
+ backendId: buildAcpBackendId(record.id),
551
+ name: record.name,
552
+ authors: Array.isArray(record.authors) ? record.authors.filter((author) => typeof author === "string") : [],
553
+ distributions,
554
+ distributionKinds: [...new Set(distributions.map((d) => d.kind))],
555
+ auth: normalizeAuth(record.auth),
556
+ raw
557
+ };
558
+ const version = stringValue(record.version);
559
+ if (version !== void 0) agent.version = version;
560
+ const description = stringValue(record.description);
561
+ if (description !== void 0) agent.description = description;
562
+ const license = stringValue(record.license);
563
+ if (license !== void 0) agent.license = license;
564
+ const repositoryUrl = stringValue(record.repository);
565
+ if (repositoryUrl !== void 0) agent.repositoryUrl = repositoryUrl;
566
+ const websiteUrl = stringValue(record.website);
567
+ if (websiteUrl !== void 0) agent.websiteUrl = websiteUrl;
568
+ const iconUrl = stringValue(record.icon);
569
+ if (iconUrl !== void 0) agent.iconUrl = iconUrl;
570
+ return agent;
571
+ }
572
+ function normalizeDistributions(raw) {
573
+ const record = asRecord(raw);
574
+ if (!record) {
575
+ return [];
576
+ }
577
+ const distributions = [];
578
+ const npx = normalizePackageDistribution("npx", record.npx);
579
+ if (npx) distributions.push(npx);
580
+ const uvx = normalizePackageDistribution("uvx", record.uvx);
581
+ if (uvx) distributions.push(uvx);
582
+ const binaryRecord = asRecord(record.binary);
583
+ if (binaryRecord) {
584
+ for (const [platform, value] of Object.entries(binaryRecord)) {
585
+ const binary = normalizeBinaryDistribution(platform, value);
586
+ if (binary) distributions.push(binary);
587
+ }
588
+ }
589
+ return distributions;
590
+ }
591
+ function normalizePackageDistribution(kind, raw) {
592
+ const record = asRecord(raw);
593
+ if (!record || typeof record.package !== "string") {
594
+ return void 0;
595
+ }
596
+ return {
597
+ kind,
598
+ packageName: record.package,
599
+ args: stringArray(record.args),
600
+ env: stringRecord(record.env)
601
+ };
602
+ }
603
+ function normalizeBinaryDistribution(platform, raw) {
604
+ const record = asRecord(raw);
605
+ if (!record || typeof record.archive !== "string" || typeof record.cmd !== "string") {
606
+ return void 0;
607
+ }
608
+ const distribution = {
609
+ kind: "binary",
610
+ platform,
611
+ archiveUrl: record.archive,
612
+ command: record.cmd,
613
+ args: stringArray(record.args),
614
+ env: stringRecord(record.env)
615
+ };
616
+ const checksum = stringValue(record.checksum) ?? stringValue(record.sha256);
617
+ if (checksum !== void 0) distribution.checksum = checksum;
618
+ const signatureUrl = stringValue(record.signature) ?? stringValue(record.signatureUrl);
619
+ if (signatureUrl !== void 0) distribution.signatureUrl = signatureUrl;
620
+ return distribution;
621
+ }
622
+ function normalizeAuth(raw) {
623
+ if (!raw) {
624
+ return { required: false, methods: [] };
625
+ }
626
+ const record = asRecord(raw);
627
+ if (!record) {
628
+ return { required: true, methods: ["unknown"], raw };
629
+ }
630
+ const methods = stringArray(record.methods).map(
631
+ (method) => method === "agent-managed" || method === "terminal" ? method : "unknown"
632
+ );
633
+ return {
634
+ required: record.required === false ? false : true,
635
+ methods: methods.length > 0 ? [...new Set(methods)] : ["unknown"],
636
+ raw
637
+ };
638
+ }
639
+ function asRecord(value) {
640
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
641
+ }
642
+ function stringValue(value) {
643
+ return typeof value === "string" && value.trim() ? value : void 0;
644
+ }
645
+ function stringArray(value) {
646
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
647
+ }
648
+ function stringRecord(value) {
649
+ const record = asRecord(value);
650
+ if (!record) {
651
+ return {};
652
+ }
653
+ return Object.fromEntries(
654
+ Object.entries(record).filter(
655
+ (entry) => typeof entry[1] === "string"
656
+ )
657
+ );
658
+ }
659
+
660
+ export {
661
+ DEFAULT_ACP_PROBE_TIMEOUT_MS,
662
+ discoverLocalAcpAgentInstances,
663
+ discoverLocalAcpAgents,
664
+ wellKnownAgentBinDirs,
665
+ BANNED_ACP_REGISTRY_IDS,
666
+ DEFAULT_ACP_AGENT_ALLOWLIST,
667
+ AcpAgentAllowlist,
668
+ defaultAcpAgentAllowlist,
669
+ isBannedAcpRegistryId,
670
+ ACP_REGISTRY_URL,
671
+ AcpRegistryService,
672
+ evaluateAcpDistributionPolicy,
673
+ normalizeRegistry
674
+ };
675
+ //# sourceMappingURL=chunk-GCWY5T5H.js.map