@pwrdrvr/agent-acp 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +51 -4
- package/dist/index.js +151 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ interface AcpAgentConnection {
|
|
|
27
27
|
request(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
28
28
|
notify(method: string, params: Record<string, unknown>): Promise<void>;
|
|
29
29
|
}
|
|
30
|
+
/** Prefix on the rejection when a request outlived its budget. Lets a caller
|
|
31
|
+
* tell "the agent did not answer in time" apart from "the agent answered with
|
|
32
|
+
* an error" — the two want different recovery (retry / restart vs. surface). */
|
|
33
|
+
declare const ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX = "acp request timed out";
|
|
30
34
|
/** Built connection + a teardown handle (kills the child / closes streams). */
|
|
31
35
|
type AcpAgentConnectionHandle = {
|
|
32
36
|
connection: AcpAgentConnection;
|
|
@@ -55,7 +59,8 @@ declare class AcpConnection implements AcpJsonRpcTransport {
|
|
|
55
59
|
private ensureConnected;
|
|
56
60
|
private connect;
|
|
57
61
|
private defaultCreateConnection;
|
|
58
|
-
request(method: string, params?: Record<string, unknown>,
|
|
62
|
+
request(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
|
|
63
|
+
private dispatch;
|
|
59
64
|
notify(method: string, params?: Record<string, unknown>): Promise<void>;
|
|
60
65
|
close(): Promise<void>;
|
|
61
66
|
onNotification(listener: (method: string, params: Record<string, unknown>) => void): () => void;
|
|
@@ -66,7 +71,14 @@ type LocalAcpProbeResult = {
|
|
|
66
71
|
stdout?: string | Buffer;
|
|
67
72
|
stderr?: string | Buffer;
|
|
68
73
|
};
|
|
69
|
-
type
|
|
74
|
+
type LocalAcpProbeOptions = {
|
|
75
|
+
/** Budget for this probe spawn. An injected probe should honor it so a slow
|
|
76
|
+
* CLI is reported as "did not answer in time", not as "not installed". */
|
|
77
|
+
timeoutMs?: number | undefined;
|
|
78
|
+
/** Fires when discovery is abandoned mid-probe. */
|
|
79
|
+
signal?: AbortSignal | undefined;
|
|
80
|
+
};
|
|
81
|
+
type LocalAcpAgentProbe = (command: string, args: string[], options?: LocalAcpProbeOptions) => Promise<LocalAcpProbeResult>;
|
|
70
82
|
/** How a strategy decides it is installed + ACP-capable on this machine. */
|
|
71
83
|
type AcpDiscoveryProbe = {
|
|
72
84
|
/** Args that print a version (probed first; failure = not installed). */
|
|
@@ -700,6 +712,18 @@ type DiscoveredAcpAgentInstance = {
|
|
|
700
712
|
/** Where this candidate came from (user override, `PATH`, or a fallback path). */
|
|
701
713
|
source: AcpAgentInstanceSource;
|
|
702
714
|
};
|
|
715
|
+
/** A detected executable that did not satisfy a strategy's discovery probe.
|
|
716
|
+
* It is diagnostic-only: hosts must never use it to launch an ACP session. */
|
|
717
|
+
type RejectedAcpAgentInstance = {
|
|
718
|
+
command: string;
|
|
719
|
+
/** Parsed from successful `--version` output when available. */
|
|
720
|
+
version?: string;
|
|
721
|
+
source: AcpAgentInstanceSource;
|
|
722
|
+
/** `probe-timed-out` means the CLI did not answer within the probe budget —
|
|
723
|
+
* UNLIKE the other reasons it is not a verdict on the CLI, and a host may
|
|
724
|
+
* re-probe on a larger `probeTimeoutMs` instead of reporting it unusable. */
|
|
725
|
+
reason: "version-probe-failed" | "acp-probe-failed" | "acp-help-mismatch" | "probe-timed-out";
|
|
726
|
+
};
|
|
703
727
|
/** Every installed instance of one agent found on this machine. */
|
|
704
728
|
type DiscoveredAcpAgentGroup = {
|
|
705
729
|
strategyId: string;
|
|
@@ -711,6 +735,9 @@ type DiscoveredAcpAgentGroup = {
|
|
|
711
735
|
env: Record<string, string>;
|
|
712
736
|
/** All instances that passed the probe, in candidate order (override → PATH → fallback). */
|
|
713
737
|
instances: DiscoveredAcpAgentInstance[];
|
|
738
|
+
/** Detected candidates that failed the version or ACP-capability probe. Only
|
|
739
|
+
* populated when `includeRejectedCandidates` is enabled. */
|
|
740
|
+
rejectedInstances?: RejectedAcpAgentInstance[];
|
|
714
741
|
discoveredAt: number;
|
|
715
742
|
};
|
|
716
743
|
type DiscoveredAcpAgent = {
|
|
@@ -740,11 +767,31 @@ type LocalAcpDiscoveryOptions = {
|
|
|
740
767
|
env?: NodeJS.ProcessEnv;
|
|
741
768
|
/** Injectable `PATH` executable lister (real-fs scan by default). */
|
|
742
769
|
listExecutables?: AcpPathExecutableLister;
|
|
770
|
+
/** Include candidates known to exist but rejected by a discovery probe.
|
|
771
|
+
* Defaults false to preserve the legacy installed-only result. */
|
|
772
|
+
includeRejectedCandidates?: boolean;
|
|
773
|
+
/** Budget for ONE probe spawn (the default probe only — an injected `probe`
|
|
774
|
+
* owns its own timing). Defaults to `DEFAULT_ACP_PROBE_TIMEOUT_MS`. */
|
|
775
|
+
probeTimeoutMs?: number;
|
|
776
|
+
/** Abandon discovery in flight (re-triggered discovery, app quitting).
|
|
777
|
+
* Already-probed groups are still returned. */
|
|
778
|
+
signal?: AbortSignal;
|
|
743
779
|
};
|
|
780
|
+
/**
|
|
781
|
+
* Budget for one ACP discovery probe spawn, in ms.
|
|
782
|
+
*
|
|
783
|
+
* Unchanged from the value this module has always used. Kept lower than
|
|
784
|
+
* codex-discovery's version-probe budget on purpose: `discoverStrategyInstances`
|
|
785
|
+
* walks its candidates SERIALLY, so the worst case here is (candidates x
|
|
786
|
+
* budget) rather than a single budget. Hosts that scan machines with many
|
|
787
|
+
* installed copies can raise it knowingly via `probeTimeoutMs`.
|
|
788
|
+
*/
|
|
789
|
+
declare const DEFAULT_ACP_PROBE_TIMEOUT_MS = 5000;
|
|
744
790
|
/**
|
|
745
791
|
* Discover every installed instance of each agent (all `PATH` matches +
|
|
746
792
|
* fallbacks + override that pass the probe), grouped per strategy. Groups with
|
|
747
|
-
* no passing instance are omitted
|
|
793
|
+
* no passing instance are omitted unless `includeRejectedCandidates` is true
|
|
794
|
+
* and a detected candidate failed a probe.
|
|
748
795
|
*/
|
|
749
796
|
declare function discoverLocalAcpAgentInstances(options?: LocalAcpDiscoveryOptions): Promise<DiscoveredAcpAgentGroup[]>;
|
|
750
797
|
/**
|
|
@@ -874,4 +921,4 @@ declare class AcpRegistryService {
|
|
|
874
921
|
declare function evaluateAcpDistributionPolicy(distribution: AcpRegistryDistribution, allowlist: AcpAllowlistDecision): AcpDistributionPolicy;
|
|
875
922
|
declare function normalizeRegistry(raw: unknown): AcpRegistryAgent[];
|
|
876
923
|
|
|
877
|
-
export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
|
|
924
|
+
export { ACP_REGISTRY_URL, ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, DEFAULT_ACP_PROBE_TIMEOUT_MS, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeOptions, type LocalAcpProbeResult, type RejectedAcpAgentInstance, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,27 @@ import {
|
|
|
10
10
|
createCommandInvocation,
|
|
11
11
|
prependCommandDirToPath
|
|
12
12
|
} from "@pwrdrvr/agent-transport";
|
|
13
|
+
var ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX = "acp request timed out";
|
|
14
|
+
function withAcpRequestTimeout(method, timeoutMs, run) {
|
|
15
|
+
if (timeoutMs === void 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
16
|
+
return run();
|
|
17
|
+
}
|
|
18
|
+
const budget = Math.max(1, Math.floor(timeoutMs));
|
|
19
|
+
const call = run();
|
|
20
|
+
void call.catch(() => void 0);
|
|
21
|
+
let timer;
|
|
22
|
+
const expiry = new Promise((_, reject) => {
|
|
23
|
+
timer = setTimeout(
|
|
24
|
+
() => reject(
|
|
25
|
+
new Error(`${ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX} after ${budget}ms: ${method}`)
|
|
26
|
+
),
|
|
27
|
+
budget
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
return Promise.race([call, expiry]).finally(() => {
|
|
31
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
13
34
|
var AcpConnection = class {
|
|
14
35
|
constructor(options) {
|
|
15
36
|
this.options = options;
|
|
@@ -101,10 +122,17 @@ var AcpConnection = class {
|
|
|
101
122
|
};
|
|
102
123
|
};
|
|
103
124
|
}
|
|
104
|
-
async request(method, params = {},
|
|
125
|
+
async request(method, params = {}, timeoutMs) {
|
|
105
126
|
await this.ensureConnected();
|
|
106
127
|
const conn = this.connection;
|
|
107
128
|
if (!conn) throw new Error("acp connection unavailable");
|
|
129
|
+
return await withAcpRequestTimeout(
|
|
130
|
+
method,
|
|
131
|
+
timeoutMs,
|
|
132
|
+
() => this.dispatch(conn, method, params)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
async dispatch(conn, method, params) {
|
|
108
136
|
switch (method) {
|
|
109
137
|
case "initialize":
|
|
110
138
|
return await conn.initialize(params);
|
|
@@ -1436,6 +1464,8 @@ var AcpAgentClient = class {
|
|
|
1436
1464
|
}
|
|
1437
1465
|
async initialize() {
|
|
1438
1466
|
if (this.initialized) return;
|
|
1467
|
+
this.unsubscribeNotification?.();
|
|
1468
|
+
this.unsubscribeRequest?.();
|
|
1439
1469
|
this.unsubscribeNotification = this.transport.onNotification((method, params) => {
|
|
1440
1470
|
this.handleNotification(method, params);
|
|
1441
1471
|
});
|
|
@@ -2314,12 +2344,17 @@ import {
|
|
|
2314
2344
|
prependCommandDirToPath as prependCommandDirToPath2
|
|
2315
2345
|
} from "@pwrdrvr/agent-transport";
|
|
2316
2346
|
var execFile = promisify(execFileCallback);
|
|
2347
|
+
var DEFAULT_ACP_PROBE_TIMEOUT_MS = 5e3;
|
|
2317
2348
|
async function discoverLocalAcpAgentInstances(options = {}) {
|
|
2318
2349
|
const strategies = options.strategies ?? BUILT_IN_ACP_STRATEGIES;
|
|
2319
2350
|
const env = options.env ?? process.env;
|
|
2320
2351
|
const probe = options.probe ?? makeDefaultProbe(env);
|
|
2321
2352
|
const listExecutables = options.listExecutables ?? defaultListExecutables;
|
|
2322
2353
|
const now = options.now ?? Date.now;
|
|
2354
|
+
const probeOptions = {
|
|
2355
|
+
timeoutMs: options.probeTimeoutMs ?? DEFAULT_ACP_PROBE_TIMEOUT_MS,
|
|
2356
|
+
signal: options.signal
|
|
2357
|
+
};
|
|
2323
2358
|
const groups = await Promise.all(
|
|
2324
2359
|
strategies.map(
|
|
2325
2360
|
(strategy) => discoverStrategyInstances(
|
|
@@ -2328,7 +2363,9 @@ async function discoverLocalAcpAgentInstances(options = {}) {
|
|
|
2328
2363
|
now,
|
|
2329
2364
|
env,
|
|
2330
2365
|
listExecutables,
|
|
2331
|
-
options.overrides?.[strategy.id]
|
|
2366
|
+
options.overrides?.[strategy.id],
|
|
2367
|
+
options.includeRejectedCandidates === true,
|
|
2368
|
+
probeOptions
|
|
2332
2369
|
)
|
|
2333
2370
|
)
|
|
2334
2371
|
);
|
|
@@ -2352,21 +2389,45 @@ async function discoverLocalAcpAgents(options = {}) {
|
|
|
2352
2389
|
return [agent];
|
|
2353
2390
|
});
|
|
2354
2391
|
}
|
|
2355
|
-
async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override) {
|
|
2392
|
+
async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override, includeRejectedCandidates = false, probeOptions = {}) {
|
|
2356
2393
|
const candidates = candidateCommands(strategy, env, listExecutables, override);
|
|
2357
2394
|
const instances = [];
|
|
2395
|
+
const rejectedInstances = [];
|
|
2358
2396
|
for (const candidate of candidates) {
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
runProbe(probe, candidate.command, strategy.discoveryProbe.
|
|
2397
|
+
if (probeOptions.signal?.aborted) break;
|
|
2398
|
+
const [versionAttempt, helpAttempt] = await Promise.all([
|
|
2399
|
+
runProbe(probe, candidate.command, strategy.discoveryProbe.versionArgs, probeOptions),
|
|
2400
|
+
runProbe(probe, candidate.command, strategy.discoveryProbe.helpArgs, probeOptions)
|
|
2362
2401
|
]);
|
|
2402
|
+
if (probeOptions.signal?.aborted) break;
|
|
2403
|
+
const versionResult = versionAttempt.ok ? versionAttempt.result : void 0;
|
|
2404
|
+
const helpResult = helpAttempt.ok ? helpAttempt.result : void 0;
|
|
2405
|
+
const version = versionResult ? parseCliVersion(resultText(versionResult)) : void 0;
|
|
2363
2406
|
if (!versionResult || !helpResult) {
|
|
2407
|
+
const timedOut = !versionAttempt.ok && versionAttempt.timedOut || !helpAttempt.ok && helpAttempt.timedOut;
|
|
2408
|
+
if (candidate.detected && (timedOut || includeRejectedCandidates)) {
|
|
2409
|
+
const rejected = {
|
|
2410
|
+
command: candidate.command,
|
|
2411
|
+
source: candidate.source,
|
|
2412
|
+
reason: timedOut ? "probe-timed-out" : versionResult ? "acp-probe-failed" : "version-probe-failed"
|
|
2413
|
+
};
|
|
2414
|
+
if (version !== void 0) rejected.version = version;
|
|
2415
|
+
rejectedInstances.push(rejected);
|
|
2416
|
+
}
|
|
2364
2417
|
continue;
|
|
2365
2418
|
}
|
|
2366
2419
|
if (strategy.discoveryProbe.helpMatches !== void 0 && !strategy.discoveryProbe.helpMatches.test(resultText(helpResult))) {
|
|
2420
|
+
if (includeRejectedCandidates && candidate.detected) {
|
|
2421
|
+
const rejected = {
|
|
2422
|
+
command: candidate.command,
|
|
2423
|
+
source: candidate.source,
|
|
2424
|
+
reason: "acp-help-mismatch"
|
|
2425
|
+
};
|
|
2426
|
+
if (version !== void 0) rejected.version = version;
|
|
2427
|
+
rejectedInstances.push(rejected);
|
|
2428
|
+
}
|
|
2367
2429
|
continue;
|
|
2368
2430
|
}
|
|
2369
|
-
const version = parseCliVersion(resultText(versionResult));
|
|
2370
2431
|
const instance = {
|
|
2371
2432
|
command: candidate.command,
|
|
2372
2433
|
source: candidate.source
|
|
@@ -2374,7 +2435,7 @@ async function discoverStrategyInstances(strategy, probe, now, env, listExecutab
|
|
|
2374
2435
|
if (version !== void 0) instance.version = version;
|
|
2375
2436
|
instances.push(instance);
|
|
2376
2437
|
}
|
|
2377
|
-
if (instances.length === 0) {
|
|
2438
|
+
if (instances.length === 0 && rejectedInstances.length === 0) {
|
|
2378
2439
|
return void 0;
|
|
2379
2440
|
}
|
|
2380
2441
|
return {
|
|
@@ -2384,34 +2445,43 @@ async function discoverStrategyInstances(strategy, probe, now, env, listExecutab
|
|
|
2384
2445
|
args: ensureArgs(strategy.spawn.args, strategy.spawn.ensureArgs),
|
|
2385
2446
|
env: strategy.spawn.env ?? {},
|
|
2386
2447
|
instances,
|
|
2448
|
+
...rejectedInstances.length > 0 ? { rejectedInstances } : {},
|
|
2387
2449
|
discoveredAt: now()
|
|
2388
2450
|
};
|
|
2389
2451
|
}
|
|
2390
2452
|
function candidateCommands(strategy, env, listExecutables, override) {
|
|
2391
2453
|
const out = [];
|
|
2392
2454
|
const seen = /* @__PURE__ */ new Set();
|
|
2393
|
-
const push = (command, source) => {
|
|
2455
|
+
const push = (command, source, detected) => {
|
|
2394
2456
|
const trimmed = command.trim();
|
|
2395
2457
|
if (trimmed.length === 0) return;
|
|
2396
2458
|
const key = canonicalize(trimmed);
|
|
2397
2459
|
if (seen.has(key)) return;
|
|
2398
2460
|
seen.add(key);
|
|
2399
|
-
out.push({ command: trimmed, source });
|
|
2461
|
+
out.push({ command: trimmed, source, detected });
|
|
2400
2462
|
};
|
|
2401
2463
|
if (override && override.trim().length > 0) {
|
|
2402
|
-
push(override, "override");
|
|
2464
|
+
push(override, "override", isExecutableFile(override));
|
|
2403
2465
|
}
|
|
2404
2466
|
const pathMatches = listExecutables(strategy.discoveryProbe.command, env);
|
|
2405
2467
|
if (pathMatches.length > 0) {
|
|
2406
|
-
for (const match of pathMatches) push(match, "path");
|
|
2468
|
+
for (const match of pathMatches) push(match, "path", true);
|
|
2407
2469
|
} else {
|
|
2408
|
-
push(strategy.discoveryProbe.command, "path");
|
|
2470
|
+
push(strategy.discoveryProbe.command, "path", false);
|
|
2409
2471
|
}
|
|
2410
2472
|
for (const fallback of strategy.discoveryProbe.fallbackCommands ?? []) {
|
|
2411
|
-
push(fallback, "fallback");
|
|
2473
|
+
push(fallback, "fallback", isExecutableFile(fallback));
|
|
2412
2474
|
}
|
|
2413
2475
|
return out;
|
|
2414
2476
|
}
|
|
2477
|
+
function isExecutableFile(command) {
|
|
2478
|
+
try {
|
|
2479
|
+
const stat = statSync(command);
|
|
2480
|
+
return stat.isFile() && (process.platform === "win32" || (stat.mode & 73) !== 0);
|
|
2481
|
+
} catch {
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2415
2485
|
function canonicalize(command) {
|
|
2416
2486
|
if (!path4.isAbsolute(command)) {
|
|
2417
2487
|
return process.platform === "win32" ? command.toLowerCase() : command;
|
|
@@ -2502,25 +2572,78 @@ function ensureArgs(args, ensure) {
|
|
|
2502
2572
|
}
|
|
2503
2573
|
return result;
|
|
2504
2574
|
}
|
|
2575
|
+
var AcpProbeTimeoutError = class extends Error {
|
|
2576
|
+
constructor(command, timeoutMs) {
|
|
2577
|
+
super(`acp probe did not answer within ${timeoutMs}ms: ${command}`);
|
|
2578
|
+
this.name = "AcpProbeTimeoutError";
|
|
2579
|
+
}
|
|
2580
|
+
};
|
|
2581
|
+
function isProbeTimeout(error) {
|
|
2582
|
+
if (error instanceof AcpProbeTimeoutError) return true;
|
|
2583
|
+
const name = error?.name;
|
|
2584
|
+
return error?.killed === true || name === "AbortError" || name === "TimeoutError";
|
|
2585
|
+
}
|
|
2586
|
+
function normalizeProbeTimeoutMs(timeoutMs) {
|
|
2587
|
+
if (timeoutMs === void 0 || !Number.isFinite(timeoutMs)) {
|
|
2588
|
+
return DEFAULT_ACP_PROBE_TIMEOUT_MS;
|
|
2589
|
+
}
|
|
2590
|
+
return Math.max(1, Math.floor(timeoutMs));
|
|
2591
|
+
}
|
|
2505
2592
|
function makeDefaultProbe(env) {
|
|
2506
|
-
return async (command, args) => {
|
|
2593
|
+
return async (command, args, options = {}) => {
|
|
2594
|
+
const timeoutMs = normalizeProbeTimeoutMs(options.timeoutMs);
|
|
2507
2595
|
const launchEnv = prependCommandDirToPath2(command, env);
|
|
2508
2596
|
const invocation = createCommandInvocation2({ command, args, env: launchEnv });
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2597
|
+
const controller = new AbortController();
|
|
2598
|
+
let timedOut = false;
|
|
2599
|
+
const onAbort = () => controller.abort();
|
|
2600
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2601
|
+
const timer = setTimeout(() => {
|
|
2602
|
+
timedOut = true;
|
|
2603
|
+
controller.abort();
|
|
2604
|
+
}, timeoutMs);
|
|
2605
|
+
const expired = new Promise((resolve) => {
|
|
2606
|
+
controller.signal.addEventListener("abort", () => resolve("expired"), {
|
|
2607
|
+
once: true
|
|
2608
|
+
});
|
|
2516
2609
|
});
|
|
2610
|
+
try {
|
|
2611
|
+
const execution = execFile(invocation.command, invocation.args, {
|
|
2612
|
+
timeout: timeoutMs,
|
|
2613
|
+
signal: controller.signal,
|
|
2614
|
+
maxBuffer: 1024 * 1024,
|
|
2615
|
+
// A Windows `.cmd` shim runs through `cmd.exe`; without this every
|
|
2616
|
+
// discovery pass flashes console windows over the host app.
|
|
2617
|
+
windowsHide: true,
|
|
2618
|
+
// Prepend the candidate's own dir so a node-script CLI (e.g. an nvm-
|
|
2619
|
+
// installed `qwen`) finds its sibling `node` during the probe.
|
|
2620
|
+
env: launchEnv,
|
|
2621
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
2622
|
+
}).then(
|
|
2623
|
+
(result) => ({ ok: true, result }),
|
|
2624
|
+
(error) => ({ ok: false, error })
|
|
2625
|
+
);
|
|
2626
|
+
const settled = await Promise.race([execution, expired]);
|
|
2627
|
+
if (settled === "expired") {
|
|
2628
|
+
if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
|
|
2629
|
+
throw new Error(`acp probe aborted: ${command}`);
|
|
2630
|
+
}
|
|
2631
|
+
if (!settled.ok) {
|
|
2632
|
+
if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
|
|
2633
|
+
throw settled.error;
|
|
2634
|
+
}
|
|
2635
|
+
return settled.result;
|
|
2636
|
+
} finally {
|
|
2637
|
+
clearTimeout(timer);
|
|
2638
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
2639
|
+
}
|
|
2517
2640
|
};
|
|
2518
2641
|
}
|
|
2519
|
-
async function runProbe(probe, command, args) {
|
|
2642
|
+
async function runProbe(probe, command, args, options) {
|
|
2520
2643
|
try {
|
|
2521
|
-
return await probe(command, args);
|
|
2522
|
-
} catch {
|
|
2523
|
-
return
|
|
2644
|
+
return { ok: true, result: await probe(command, args, options) };
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
return { ok: false, timedOut: isProbeTimeout(error) };
|
|
2524
2647
|
}
|
|
2525
2648
|
}
|
|
2526
2649
|
function resultText(result) {
|
|
@@ -2865,6 +2988,7 @@ function stringRecord(value) {
|
|
|
2865
2988
|
}
|
|
2866
2989
|
export {
|
|
2867
2990
|
ACP_REGISTRY_URL,
|
|
2991
|
+
ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX,
|
|
2868
2992
|
AcpAgentAllowlist,
|
|
2869
2993
|
AcpAgentClient,
|
|
2870
2994
|
AcpAgentClientPool,
|
|
@@ -2875,6 +2999,7 @@ export {
|
|
|
2875
2999
|
BANNED_ACP_REGISTRY_IDS,
|
|
2876
3000
|
BUILT_IN_ACP_STRATEGIES,
|
|
2877
3001
|
DEFAULT_ACP_AGENT_ALLOWLIST,
|
|
3002
|
+
DEFAULT_ACP_PROBE_TIMEOUT_MS,
|
|
2878
3003
|
acpRuntimeSupportsHttpMcp,
|
|
2879
3004
|
acpRuntimeSupportsSessionLoad,
|
|
2880
3005
|
acpRuntimeSupportsSseMcp,
|