@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/chunk-64RRSPSB.js +51 -0
- package/dist/chunk-64RRSPSB.js.map +1 -0
- package/dist/chunk-GCWY5T5H.js +675 -0
- package/dist/chunk-GCWY5T5H.js.map +1 -0
- package/dist/chunk-OFMIS7W6.js +163 -0
- package/dist/chunk-OFMIS7W6.js.map +1 -0
- package/dist/discovery.d.ts +223 -0
- package/dist/discovery.js +32 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +8 -356
- package/dist/index.js +46 -862
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-config.d.ts +40 -0
- package/dist/mcp-server-config.js +9 -0
- package/dist/mcp-server-config.js.map +1 -0
- package/dist/strategies.d.ts +20 -0
- package/dist/strategies.js +25 -0
- package/dist/strategies.js.map +1 -0
- package/dist/strategy-types-C7yw6yQL.d.ts +82 -0
- package/package.json +14 -2
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
|
|
269
|
-
if (
|
|
270
|
-
return
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
373
|
-
if (
|
|
374
|
-
const location = { path
|
|
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
|
|
2082
|
-
if (/token|usage|quota/i.test(key)) paths.push(
|
|
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,
|
|
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,
|