@jini-ai/agent-runtime 0.4.4 → 0.4.5
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/agent-protocol/acp/models.d.ts +11 -0
- package/dist/agent-protocol/acp/models.js +12 -3
- package/dist/capabilities.d.ts +1 -8
- package/dist/capabilities.js +8 -1
- package/dist/claude-code-models.d.ts +13 -1
- package/dist/claude-code-models.js +14 -16
- package/dist/claude-segment-usage.d.ts +9 -2
- package/dist/claude-segment-usage.js +20 -4
- package/dist/claude-stream.d.ts +4 -0
- package/dist/claude-stream.js +5 -1
- package/dist/defs/claude.d.ts +2 -0
- package/dist/defs/claude.js +10 -2
- package/dist/defs/codebuddy.js +1 -1
- package/dist/defs/cursor-agent.js +1 -1
- package/dist/defs/opencode.js +1 -1
- package/dist/detection.d.ts +28 -4
- package/dist/detection.js +81 -42
- package/dist/diagnostics.d.ts +6 -1
- package/dist/diagnostics.js +2 -2
- package/dist/executables.d.ts +15 -9
- package/dist/executables.js +72 -64
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/invocation.d.ts +24 -2
- package/dist/invocation.js +18 -6
- package/dist/launch.d.ts +2 -1
- package/dist/launch.js +2 -2
- package/dist/model-discovery-adapters.d.ts +5 -0
- package/dist/model-discovery-adapters.js +62 -49
- package/dist/model-discovery.js +2 -2
- package/dist/models.d.ts +9 -4
- package/dist/models.js +11 -11
- package/dist/probe-scheduler.d.ts +19 -0
- package/dist/probe-scheduler.js +30 -0
- package/dist/runtime-state.d.ts +41 -0
- package/dist/runtime-state.js +31 -0
- package/dist/types.d.ts +4 -0
- package/package.json +13 -8
package/dist/detection.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { userToolchainBinDirs } from './executables.js';
|
|
3
|
+
import { defaultRuntimeState } from './runtime-state.js';
|
|
4
|
+
import { getRuntimeModelDiscoveryDeps } from './model-discovery-adapters.js';
|
|
1
5
|
/**
|
|
2
6
|
* @module detection
|
|
3
7
|
*
|
|
@@ -14,7 +18,6 @@
|
|
|
14
18
|
import { execAgentFile } from './invocation.js';
|
|
15
19
|
import { offlineSnapshot, modelDiscoveryForDef } from './model-discovery.js';
|
|
16
20
|
import { rememberLiveModels } from './models.js';
|
|
17
|
-
import { agentCapabilities } from './capabilities.js';
|
|
18
21
|
import { installMetaForAgent } from './metadata.js';
|
|
19
22
|
import { AGENT_DEFS } from './registry.js';
|
|
20
23
|
import { applyAgentLaunchEnv, resolveAgentLaunch } from './launch.js';
|
|
@@ -109,8 +112,8 @@ function unavailableAgent(def, diagnostics = []) {
|
|
|
109
112
|
}
|
|
110
113
|
// Probe the agent's `--help` once and record which advertised flags the
|
|
111
114
|
// installed CLI supports, so buildArgs can consult the cache. Extracted
|
|
112
|
-
// from the main probe so it can run
|
|
113
|
-
//
|
|
115
|
+
// from the main probe so it can run before model discovery. It shares the existing
|
|
116
|
+
// capability cache with real launches. Returns the capability map (or null when the
|
|
114
117
|
// agent declares no help/capability metadata or the probe failed).
|
|
115
118
|
async function probeCapabilities(def, launchPath, env) {
|
|
116
119
|
if (!def.helpArgs || !def.capabilityFlags)
|
|
@@ -133,8 +136,6 @@ async function readCapabilities(helpArgs, capabilityFlags, launchPath, env) {
|
|
|
133
136
|
}
|
|
134
137
|
return caps;
|
|
135
138
|
}
|
|
136
|
-
/** One in-flight or settled `--help` probe per def id + launch path, so a run never re-spawns it. */
|
|
137
|
-
const capabilityProbes = new Map();
|
|
138
139
|
/**
|
|
139
140
|
* Makes sure `agentCapabilities` holds this def's `--help`-probed flags before its `buildArgs` runs.
|
|
140
141
|
*
|
|
@@ -154,7 +155,9 @@ const capabilityProbes = new Map();
|
|
|
154
155
|
* @param env - The env the run will spawn with.
|
|
155
156
|
* @complexity O(1) after the first call per def + path; the first call costs one `--help` spawn (5 s cap).
|
|
156
157
|
*/
|
|
157
|
-
export function ensureAgentCapabilities({ def, launchPath, env }) {
|
|
158
|
+
export function ensureAgentCapabilities({ def, launchPath, env }, { state = defaultRuntimeState, probe = ({ helpArgs, capabilityFlags, launchPath, env }) => readCapabilities(helpArgs, capabilityFlags, launchPath, env) } = {}) {
|
|
159
|
+
const agentCapabilities = state.capabilities;
|
|
160
|
+
const capabilityProbes = state.capabilityProbes;
|
|
158
161
|
const { helpArgs, capabilityFlags } = def;
|
|
159
162
|
if (!helpArgs || !capabilityFlags || agentCapabilities.has(def.id))
|
|
160
163
|
return Promise.resolve();
|
|
@@ -162,12 +165,12 @@ export function ensureAgentCapabilities({ def, launchPath, env }) {
|
|
|
162
165
|
const existing = capabilityProbes.get(key);
|
|
163
166
|
if (existing)
|
|
164
167
|
return existing;
|
|
165
|
-
const
|
|
168
|
+
const pending = probe({ helpArgs, capabilityFlags, launchPath, env }, {}).then((caps) => {
|
|
166
169
|
if (!agentCapabilities.has(def.id))
|
|
167
170
|
agentCapabilities.set(def.id, caps);
|
|
168
171
|
}, () => undefined);
|
|
169
|
-
capabilityProbes.set(key,
|
|
170
|
-
return
|
|
172
|
+
capabilityProbes.set(key, pending);
|
|
173
|
+
return pending;
|
|
171
174
|
}
|
|
172
175
|
/**
|
|
173
176
|
* The effort-level override a live model catalog earns, if any.
|
|
@@ -186,8 +189,8 @@ function derivedReasoningOverride(def, models) {
|
|
|
186
189
|
return derived && derived.length > 0 ? { reasoningOptions: derived } : {};
|
|
187
190
|
}
|
|
188
191
|
/** The env every probe of one def spawns with — the same env a real run would see. */
|
|
189
|
-
function probeEnvForLaunch(def, configuredEnv, launch) {
|
|
190
|
-
return applyAgentLaunchEnv({ env: spawnEnvForAgent({ agentId: def.id, baseEnv: { ...process.env, ...(def.env || {}) } }, { configuredEnv: configuredEnv, hooks: {} }), launch: launch });
|
|
192
|
+
function probeEnvForLaunch(def, configuredEnv, launch, options = {}) {
|
|
193
|
+
return applyAgentLaunchEnv({ env: spawnEnvForAgent({ agentId: def.id, baseEnv: { ...(options.env ?? process.env), ...(def.env || {}) } }, { configuredEnv: configuredEnv, hooks: {} }), launch: launch }, { appendPathDirs: userToolchainBinDirs({}, options) });
|
|
191
194
|
}
|
|
192
195
|
function unavailableModels(def) {
|
|
193
196
|
const catalog = offlineSnapshot('', Date.now(), def.fallbackModels);
|
|
@@ -205,15 +208,15 @@ function unavailableModels(def) {
|
|
|
205
208
|
* `source: 'fallback'` (including when the binary is not found). Never rejects.
|
|
206
209
|
* @complexity At most one model-listing spawn, bounded by the def's own timeout.
|
|
207
210
|
*/
|
|
208
|
-
export async function probeAgentModels({ def }, { configuredEnv = {}, amrProfileResolver = noopAmrProfileResolver, context, deps, force = false } = {}) {
|
|
211
|
+
export async function probeAgentModels({ def }, { configuredEnv = {}, amrProfileResolver = noopAmrProfileResolver, context, deps, force = false, state = defaultRuntimeState, env = process.env } = {}) {
|
|
209
212
|
try {
|
|
210
|
-
const launch = resolveAgentLaunch({ def: def }, { configuredEnv: configuredEnv });
|
|
213
|
+
const launch = resolveAgentLaunch({ def: def }, { configuredEnv: configuredEnv, state, env });
|
|
211
214
|
if (!launch.launchPath)
|
|
212
215
|
return unavailableModels(def);
|
|
213
|
-
const probeEnv = probeEnvForLaunch(def, configuredEnv, launch);
|
|
214
|
-
const fetched = await fetchModels(def, launch.launchPath, probeEnv, { ...(context ? { context } : {}),
|
|
216
|
+
const probeEnv = probeEnvForLaunch(def, configuredEnv, launch, { state, env });
|
|
217
|
+
const fetched = await fetchModels(def, launch.launchPath, probeEnv, { ...(context ? { context } : {}), deps: deps ?? getRuntimeModelDiscoveryDeps({ state }), force });
|
|
215
218
|
const result = withRememberedAmrModels(def, probeEnv, fetched, amrProfileResolver);
|
|
216
|
-
rememberDetectedModelList(def, configuredEnv, result.models, amrProfileResolver);
|
|
219
|
+
rememberDetectedModelList(def, configuredEnv, result.models, amrProfileResolver, { state, env });
|
|
217
220
|
return result;
|
|
218
221
|
}
|
|
219
222
|
catch {
|
|
@@ -229,28 +232,34 @@ async function probe(def, configuredEnv = {}, amrProfileResolver, options = {})
|
|
|
229
232
|
// detection probes the shim but chat/run spawns the native binary, the
|
|
230
233
|
// UI incorrectly reports "not installed" until the user pins the bin
|
|
231
234
|
// override by hand even though the real launch path is healthy.
|
|
232
|
-
const launch = resolveAgentLaunch({ def: def }, { configuredEnv: configuredEnv });
|
|
235
|
+
const launch = resolveAgentLaunch({ def: def }, { configuredEnv: configuredEnv, ...(options.state ? { state: options.state } : {}), ...(options.env ? { env: options.env } : {}) });
|
|
233
236
|
if (!launch.selectedPath || !launch.launchPath) {
|
|
234
|
-
return unavailableAgent(def, [buildExecutableDiagnostic({ def: def }, { configuredEnv: configuredEnv })]);
|
|
237
|
+
return unavailableAgent(def, [buildExecutableDiagnostic({ def: def }, { configuredEnv: configuredEnv, ...(options.state ? { state: options.state } : {}), ...(options.env ? { env: options.env } : {}) })]);
|
|
235
238
|
}
|
|
236
|
-
const probeEnv = probeEnvForLaunch(def, configuredEnv, launch);
|
|
239
|
+
const probeEnv = probeEnvForLaunch(def, configuredEnv, launch, options);
|
|
237
240
|
const outcome = await probeVersionAtPath(def, launch.launchPath, probeEnv);
|
|
238
241
|
if (outcome.kind === 'not-invocable') {
|
|
239
242
|
return unavailableAgent(def, [buildNotInvocableDiagnostic({ def: def, launch: launch, cause: outcome.cause })]);
|
|
240
243
|
}
|
|
241
|
-
// The version probe must finish first (it gates availability)
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
// The version probe must finish first (it gates availability). Cheap help/auth checks
|
|
245
|
+
// precede model discovery; the shared process scheduler prevents concurrent CLI startups.
|
|
246
|
+
// Capability evidence belongs to the existing ensureAgentCapabilities cache, also used by runs.
|
|
247
|
+
const runtimeState = options.state ?? defaultRuntimeState;
|
|
248
|
+
const capabilityKey = `${def.id}\u0000${launch.launchPath}`;
|
|
249
|
+
if (options.force || !runtimeState.capabilityProbes.has(capabilityKey)) {
|
|
250
|
+
runtimeState.capabilities.delete(def.id);
|
|
251
|
+
if (options.force)
|
|
252
|
+
runtimeState.capabilityProbes.delete(capabilityKey);
|
|
253
|
+
}
|
|
254
|
+
await ensureAgentCapabilities({ def, launchPath: launch.launchPath, env: probeEnv }, { state: runtimeState,
|
|
255
|
+
probe: async () => (await probeCapabilities(def, launch.launchPath, probeEnv)) ?? {},
|
|
256
|
+
});
|
|
257
|
+
const caps = runtimeState.capabilities.get(def.id);
|
|
258
|
+
const auth = await probeAgentAuthStatus({ def, resolvedBin: launch.launchPath, env: probeEnv });
|
|
259
|
+
const modelResult = await fetchModels(def, launch.launchPath, probeEnv, { version: outcome.version, ...options, deps: options.deps ?? getRuntimeModelDiscoveryDeps({ state: runtimeState }) });
|
|
251
260
|
const surfacedModelResult = withRememberedAmrModels(def, probeEnv, modelResult, amrProfileResolver);
|
|
252
261
|
if (caps) {
|
|
253
|
-
|
|
262
|
+
(options.state ?? defaultRuntimeState).capabilities.set(def.id, caps);
|
|
254
263
|
}
|
|
255
264
|
const authDiagnostic = auth ? buildAuthDiagnostic({ def: def, auth: auth }) : null;
|
|
256
265
|
return {
|
|
@@ -290,8 +299,38 @@ function stripFns(def) {
|
|
|
290
299
|
const { discoverModels: _discoverModels, resolveDefaultModel: _resolveDefaultModel, buildArgs: _buildArgs, listModels: _listModels, fetchModels: _fetchModels, deriveReasoningOptions: _deriveReasoningOptions, fallbackModels: _fallbackModels, fallbackModelsAssertedAt: _fallbackModelsAssertedAt, helpArgs: _helpArgs, capabilityFlags: _capabilityFlags, fallbackBins: _fallbackBins, versionProbeTimeoutMs: _versionProbeTimeoutMs, maxPromptArgBytes: _maxPromptArgBytes, env: _env, inactivityTimeoutMs: _inactivityTimeoutMs, authProbe: _authProbe, needsAgentLogFile: _needsAgentLogFile, stdoutPolicy: _stdoutPolicy, runtimeLock: _runtimeLock, ...rest } = def;
|
|
291
300
|
return rest;
|
|
292
301
|
}
|
|
293
|
-
|
|
302
|
+
/** Cache complete detection per host and effective launch. No TTL re-spawns version/help/auth.
|
|
303
|
+
* Explicit force queues after the current scan, so a rescan cannot overlap its predecessor.
|
|
304
|
+
* Hash credentials rather than retaining them in keys. @complexity O(e) launch-env hashing. */
|
|
305
|
+
function safeProbe(def, configuredEnv, amrProfileResolver, options = {}) {
|
|
306
|
+
const state = options.state ?? defaultRuntimeState;
|
|
307
|
+
let key;
|
|
308
|
+
try {
|
|
309
|
+
key = detectionCacheKey({ def, configuredEnv, options, state });
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return Promise.resolve(unavailableAgent(def));
|
|
313
|
+
}
|
|
314
|
+
const previous = state.detections.get(key);
|
|
315
|
+
if (previous && !options.force)
|
|
316
|
+
return previous;
|
|
317
|
+
const pending = (previous ?? Promise.resolve()).then(() => executeSafeProbe(def, configuredEnv, amrProfileResolver, options));
|
|
318
|
+
state.detections.set(key, pending);
|
|
319
|
+
return pending;
|
|
320
|
+
}
|
|
321
|
+
/** Effective launch material is hashed, so cache keys never expose credentials.
|
|
322
|
+
* Legacy remembered-model profile resolution must not gate executable availability. */
|
|
323
|
+
function detectionCacheKey({ def, configuredEnv, options, state }, _options = {}) {
|
|
324
|
+
const launch = resolveAgentLaunch({ def }, { configuredEnv, state, ...(options.env ? { env: options.env } : {}) });
|
|
325
|
+
const env = options.env ?? process.env;
|
|
326
|
+
return createHash('sha256').update(JSON.stringify({ id: def.id, path: launch.launchPath,
|
|
327
|
+
env: Object.entries(env).sort(([a], [b]) => a.localeCompare(b)), configuredEnv,
|
|
328
|
+
context: options.context })).digest('hex');
|
|
329
|
+
}
|
|
330
|
+
async function executeSafeProbe(def, configuredEnv = {}, amrProfileResolver, options = {}) {
|
|
294
331
|
try {
|
|
332
|
+
if (options.probeAgent)
|
|
333
|
+
return await options.probeAgent({ def, configuredEnv, amrProfileResolver }, options);
|
|
295
334
|
return await probe(def, configuredEnv, amrProfileResolver, options);
|
|
296
335
|
}
|
|
297
336
|
catch {
|
|
@@ -302,19 +341,19 @@ async function safeProbe(def, configuredEnv = {}, amrProfileResolver, options =
|
|
|
302
341
|
return unavailableAgent(def);
|
|
303
342
|
}
|
|
304
343
|
}
|
|
305
|
-
function rememberDetectedLiveModels(def, configuredEnv, agent, amrProfileResolver) {
|
|
306
|
-
rememberDetectedModelList(def, configuredEnv, agent.models, amrProfileResolver);
|
|
344
|
+
function rememberDetectedLiveModels(def, configuredEnv, agent, amrProfileResolver, options = {}) {
|
|
345
|
+
rememberDetectedModelList(def, configuredEnv, agent.models, amrProfileResolver, options);
|
|
307
346
|
}
|
|
308
|
-
function rememberDetectedModelList(def, configuredEnv, models, amrProfileResolver) {
|
|
347
|
+
function rememberDetectedModelList(def, configuredEnv, models, amrProfileResolver, options = {}) {
|
|
309
348
|
if (def.id === 'amr' && models.length === 0)
|
|
310
349
|
return;
|
|
311
350
|
const scope = def.id === 'amr'
|
|
312
|
-
? amrModelScopeFromEnv({ ...process.env, ...(def.env || {}), ...configuredEnv }, amrProfileResolver)
|
|
351
|
+
? amrModelScopeFromEnv({ ...(options.env ?? process.env), ...(def.env || {}), ...configuredEnv }, amrProfileResolver)
|
|
313
352
|
: null;
|
|
314
|
-
rememberLiveModels({ agentId: def.id, models: models.filter((row) => row.identityKind !== 'routing-mode') }, { scope: scope });
|
|
353
|
+
rememberLiveModels({ agentId: def.id, models: models.filter((row) => row.identityKind !== 'routing-mode') }, { scope: scope, ...(options.state ? { state: options.state } : {}) });
|
|
315
354
|
}
|
|
316
|
-
export async function detectAgents({}, { configuredEnvByAgent = {}, amrProfileResolver = noopAmrProfileResolver, modelContextByAgent = {}, modelDiscoveryDeps, force = false } = {}) {
|
|
317
|
-
const results = await Promise.all(AGENT_DEFS.map((def) => safeProbe(def, configuredEnvByAgent?.[def.id] ?? {}, amrProfileResolver, { ...(modelContextByAgent[def.id] ? { context: modelContextByAgent[def.id] } : {}), ...(
|
|
355
|
+
export async function detectAgents({}, { configuredEnvByAgent = {}, amrProfileResolver = noopAmrProfileResolver, modelContextByAgent = {}, modelDiscoveryDeps, force = false, state = defaultRuntimeState, env = process.env, probeAgent } = {}) {
|
|
356
|
+
const results = await Promise.all(AGENT_DEFS.map((def) => safeProbe(def, configuredEnvByAgent?.[def.id] ?? {}, amrProfileResolver, { ...(modelContextByAgent[def.id] ? { context: modelContextByAgent[def.id] } : {}), deps: modelDiscoveryDeps ?? getRuntimeModelDiscoveryDeps({ state }), force, state, env, ...(probeAgent ? { probeAgent } : {}) })));
|
|
318
357
|
// Refresh the validation cache from whatever we just surfaced to the UI
|
|
319
358
|
// so a chat endpoint can accept any model the user could have just
|
|
320
359
|
// picked, including ones that only showed up after a CLI re-auth.
|
|
@@ -323,7 +362,7 @@ export async function detectAgents({}, { configuredEnvByAgent = {}, amrProfileRe
|
|
|
323
362
|
// is in-bounds for `AGENT_DEFS` too — the assertion just satisfies
|
|
324
363
|
// `noUncheckedIndexedAccess`.
|
|
325
364
|
const def = AGENT_DEFS[index];
|
|
326
|
-
rememberDetectedLiveModels(def, configuredEnvByAgent?.[def.id] ?? {}, agent, amrProfileResolver);
|
|
365
|
+
rememberDetectedLiveModels(def, configuredEnvByAgent?.[def.id] ?? {}, agent, amrProfileResolver, { state, env });
|
|
327
366
|
}
|
|
328
367
|
return results;
|
|
329
368
|
}
|
|
@@ -333,9 +372,9 @@ export async function detectAgents({}, { configuredEnvByAgent = {}, amrProfileRe
|
|
|
333
372
|
// validation cache is refreshed per-agent (same effect as the batch path,
|
|
334
373
|
// just incrementally). `detectAgents` keeps the array contract for callers
|
|
335
374
|
// that don't care about incremental delivery.
|
|
336
|
-
export async function* detectAgentsStream({}, { configuredEnvByAgent = {}, amrProfileResolver = noopAmrProfileResolver, modelContextByAgent = {}, modelDiscoveryDeps, force = false } = {}) {
|
|
337
|
-
const tagged = AGENT_DEFS.map((def, index) => safeProbe(def, configuredEnvByAgent?.[def.id] ?? {}, amrProfileResolver, { ...(modelContextByAgent[def.id] ? { context: modelContextByAgent[def.id] } : {}), ...(
|
|
338
|
-
rememberDetectedLiveModels(def, configuredEnvByAgent?.[def.id] ?? {}, agent, amrProfileResolver);
|
|
375
|
+
export async function* detectAgentsStream({}, { configuredEnvByAgent = {}, amrProfileResolver = noopAmrProfileResolver, modelContextByAgent = {}, modelDiscoveryDeps, force = false, state = defaultRuntimeState, env = process.env, probeAgent } = {}) {
|
|
376
|
+
const tagged = AGENT_DEFS.map((def, index) => safeProbe(def, configuredEnvByAgent?.[def.id] ?? {}, amrProfileResolver, { ...(modelContextByAgent[def.id] ? { context: modelContextByAgent[def.id] } : {}), deps: modelDiscoveryDeps ?? getRuntimeModelDiscoveryDeps({ state }), force, state, env, ...(probeAgent ? { probeAgent } : {}) }).then((agent) => {
|
|
377
|
+
rememberDetectedLiveModels(def, configuredEnvByAgent?.[def.id] ?? {}, agent, amrProfileResolver, { state, env });
|
|
339
378
|
return { index, agent };
|
|
340
379
|
}));
|
|
341
380
|
const pending = new Set(tagged.keys());
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module diagnostics
|
|
3
|
+
* Explain three detection failures: unresolved executable, uninvocable executable or missing authentication.
|
|
4
|
+
*/
|
|
5
|
+
import { type ExecutableResolutionOptions } from './executables.js';
|
|
1
6
|
import type { AgentLaunchResolution } from './launch.js';
|
|
2
7
|
import type { AgentAuthProbeResult } from './auth.js';
|
|
3
8
|
import type { AgentDiagnostic, RuntimeAgentDef } from './types.js';
|
|
4
9
|
export declare function buildExecutableDiagnostic({ def }: {
|
|
5
10
|
def: Pick<RuntimeAgentDef, 'id' | 'name' | 'bin'>;
|
|
6
|
-
}, { configuredEnv }?: {
|
|
11
|
+
}, { configuredEnv, ...options }?: ExecutableResolutionOptions & {
|
|
7
12
|
configuredEnv?: Record<string, string>;
|
|
8
13
|
}): AgentDiagnostic;
|
|
9
14
|
export type NotInvocableCause = 'not-executable' | 'missing-target';
|
package/dist/diagnostics.js
CHANGED
|
@@ -16,7 +16,7 @@ function setEnvIntent(agentId) {
|
|
|
16
16
|
// user-supplied `*_BIN` override points at a missing/invalid file. The two
|
|
17
17
|
// cases get different copy + fix affordances because the remedy differs
|
|
18
18
|
// (install / point us at the binary vs. fix or clear the override).
|
|
19
|
-
export function buildExecutableDiagnostic({ def }, { configuredEnv = {} } = {}) {
|
|
19
|
+
export function buildExecutableDiagnostic({ def }, { configuredEnv = {}, ...options } = {}) {
|
|
20
20
|
const envKey = agentBinEnvKey({ agentId: def.id });
|
|
21
21
|
const overrideRaw = envKey ? configuredEnv?.[envKey]?.trim() : '';
|
|
22
22
|
if (envKey && overrideRaw) {
|
|
@@ -32,7 +32,7 @@ export function buildExecutableDiagnostic({ def }, { configuredEnv = {} } = {})
|
|
|
32
32
|
reason: 'not-on-path',
|
|
33
33
|
severity: 'error',
|
|
34
34
|
message: `${def.name} (\`${def.bin}\`) was not found on your PATH.`,
|
|
35
|
-
searchedDirs: agentSearchDirs().slice(0, MAX_SEARCHED_DIRS),
|
|
35
|
+
searchedDirs: agentSearchDirs({}, options).slice(0, MAX_SEARCHED_DIRS),
|
|
36
36
|
fixActions: [{ kind: 'openInstall' }, ...setEnvIntent(def.id), { kind: 'rescan' }],
|
|
37
37
|
};
|
|
38
38
|
}
|
package/dist/executables.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { type AgentRuntimeState } from './runtime-state.js';
|
|
1
2
|
import type { RuntimeAgentDef } from './types.js';
|
|
3
|
+
/** Host environment and runtime state used by executable resolution. */
|
|
4
|
+
export interface ExecutableResolutionOptions {
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
state?: AgentRuntimeState;
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Override the env var names `resolveDetectionHome` / the packaged-binary
|
|
4
10
|
* lookup read. The host application (e.g. the OD adapter) calls this once
|
|
@@ -10,27 +16,27 @@ export declare function configureExecutableResolutionEnv({ overrides }: {
|
|
|
10
16
|
agentHomeEnvVar?: string;
|
|
11
17
|
resourceRootEnvVar?: string;
|
|
12
18
|
};
|
|
19
|
+
}, { state }?: {
|
|
20
|
+
state?: AgentRuntimeState;
|
|
13
21
|
}): void;
|
|
14
|
-
export declare function userToolchainBinDirs(): string[];
|
|
15
|
-
export declare function agentSearchDirs(): string[];
|
|
22
|
+
export declare function userToolchainBinDirs(_required?: Record<string, never>, options?: ExecutableResolutionOptions): string[];
|
|
23
|
+
export declare function agentSearchDirs(_required?: Record<string, never>, options?: ExecutableResolutionOptions): string[];
|
|
16
24
|
export declare function agentBinEnvKey({ agentId }: {
|
|
17
25
|
agentId: string | undefined;
|
|
18
26
|
}): string | null;
|
|
19
27
|
export declare function resolveOnPath({ bin }: {
|
|
20
28
|
bin: string;
|
|
21
|
-
}): string | null;
|
|
22
|
-
export declare function resolveAmrOpenCodeExecutable({}: {}, { env }?:
|
|
23
|
-
|
|
24
|
-
}): string | null;
|
|
25
|
-
export declare function codexAppBundleCandidates(): string[];
|
|
29
|
+
}, options?: ExecutableResolutionOptions): string | null;
|
|
30
|
+
export declare function resolveAmrOpenCodeExecutable({}: {}, { env, state }?: ExecutableResolutionOptions): string | null;
|
|
31
|
+
export declare function codexAppBundleCandidates(_required?: Record<string, never>, options?: ExecutableResolutionOptions): string[];
|
|
26
32
|
export declare function resolveAgentExecutable({ def }: {
|
|
27
33
|
def: RuntimeAgentDef;
|
|
28
|
-
},
|
|
34
|
+
}, options?: ExecutableResolutionOptions & {
|
|
29
35
|
configuredEnv?: Record<string, string>;
|
|
30
36
|
}): string | null;
|
|
31
37
|
export declare function inspectAgentExecutableResolution({ def }: {
|
|
32
38
|
def: RuntimeAgentDef;
|
|
33
|
-
},
|
|
39
|
+
}, options?: ExecutableResolutionOptions & {
|
|
34
40
|
configuredEnv?: Record<string, string>;
|
|
35
41
|
}): {
|
|
36
42
|
configuredOverridePath: string | null;
|
package/dist/executables.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultRuntimeState } from './runtime-state.js';
|
|
1
2
|
/**
|
|
2
3
|
* @module executables
|
|
3
4
|
*
|
|
@@ -53,59 +54,58 @@ const AGENT_BIN_ENV_KEYS = new Map([
|
|
|
53
54
|
['trae-cli', 'TRAE_CLI_BIN'],
|
|
54
55
|
['vibe', 'VIBE_BIN'],
|
|
55
56
|
]);
|
|
56
|
-
/** Env var names consulted by detection-home/resource-root resolution. Overridable via `configureExecutableResolutionEnv`. */
|
|
57
|
-
const executableResolutionEnv = {
|
|
58
|
-
agentHomeEnvVar: 'AGENT_RUNTIME_HOME',
|
|
59
|
-
resourceRootEnvVar: 'AGENT_RUNTIME_RESOURCE_ROOT',
|
|
60
|
-
};
|
|
61
57
|
/**
|
|
62
58
|
* Override the env var names `resolveDetectionHome` / the packaged-binary
|
|
63
59
|
* lookup read. The host application (e.g. the OD adapter) calls this once
|
|
64
60
|
* at startup if it wants its own namespaced env vars instead of the
|
|
65
61
|
* generic defaults.
|
|
66
62
|
*/
|
|
67
|
-
export function configureExecutableResolutionEnv({ overrides }) {
|
|
63
|
+
export function configureExecutableResolutionEnv({ overrides }, { state = defaultRuntimeState } = {}) {
|
|
64
|
+
const executableResolutionEnv = state.executable.envNames;
|
|
68
65
|
if (overrides.agentHomeEnvVar)
|
|
69
66
|
executableResolutionEnv.agentHomeEnvVar = overrides.agentHomeEnvVar;
|
|
70
67
|
if (overrides.resourceRootEnvVar)
|
|
71
68
|
executableResolutionEnv.resourceRootEnvVar = overrides.resourceRootEnvVar;
|
|
72
69
|
}
|
|
73
70
|
const TOOLCHAIN_DIR_CACHE_TTL_MS = 5000;
|
|
74
|
-
|
|
75
|
-
let cachedToolchainDirs = null;
|
|
76
|
-
let cachedToolchainDirsAt = 0;
|
|
71
|
+
// Toolchain cache and env names live with the injected host state.
|
|
77
72
|
// Resolve the home directory detection should search, honoring the
|
|
78
73
|
// configured agent-home override env var. `hasOverride` lets callers scope
|
|
79
74
|
// strictly to the override home (skipping real-machine system locations) so
|
|
80
75
|
// sandboxed detection runs and tests stay deterministic instead of
|
|
81
76
|
// reaching the host.
|
|
82
|
-
function resolveDetectionHome() {
|
|
83
|
-
const
|
|
84
|
-
|
|
77
|
+
function resolveDetectionHome(options = {}) {
|
|
78
|
+
const env = options.env ?? process.env;
|
|
79
|
+
const homeOverride = env[(options.state ?? defaultRuntimeState).executable.envNames.agentHomeEnvVar];
|
|
80
|
+
return { home: homeOverride || env.HOME || env.USERPROFILE || homedir(), hasOverride: Boolean(homeOverride) };
|
|
85
81
|
}
|
|
86
|
-
function userToolchainDirs() {
|
|
87
|
-
const
|
|
82
|
+
function userToolchainDirs(options = {}) {
|
|
83
|
+
const env = options.env ?? process.env;
|
|
84
|
+
const cache = (options.state ?? defaultRuntimeState).executable;
|
|
85
|
+
const { home, hasOverride } = resolveDetectionHome(options);
|
|
86
|
+
// Every environment field the existing toolchain adapter consults participates in the cache key.
|
|
87
|
+
const key = JSON.stringify([home, hasOverride, ...['VP_HOME', 'NPM_CONFIG_PREFIX', 'npm_config_prefix', 'APPDATA', 'LOCALAPPDATA', 'MISE_DATA_DIR', 'FNM_DIR'].map(name => env[name])]);
|
|
88
88
|
const homeOverride = hasOverride ? home : undefined;
|
|
89
89
|
const now = Date.now();
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
now -
|
|
93
|
-
return
|
|
90
|
+
if (cache.toolchainKey === key &&
|
|
91
|
+
cache.toolchainDirs &&
|
|
92
|
+
now - cache.toolchainDirsAt < TOOLCHAIN_DIR_CACHE_TTL_MS) {
|
|
93
|
+
return cache.toolchainDirs;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
cache.toolchainKey = key;
|
|
96
|
+
cache.toolchainDirsAt = now;
|
|
97
97
|
// When the agent-home override is set, scope the search strictly to the
|
|
98
98
|
// override home: skip Homebrew / /usr/local *and* pass an empty env so
|
|
99
99
|
// that a developer or CI runner with NPM_CONFIG_PREFIX /
|
|
100
100
|
// npm_config_prefix exported can't leak the real machine's <prefix>/bin
|
|
101
101
|
// into a sandboxed detection run. Without this, tests that build a tmp
|
|
102
102
|
// home would be machine-environment-dependent.
|
|
103
|
-
|
|
103
|
+
cache.toolchainDirs = wellKnownUserToolchainBins({
|
|
104
104
|
home,
|
|
105
105
|
includeSystemBins: process.platform !== 'win32' && !homeOverride,
|
|
106
|
-
env: homeOverride ? {} :
|
|
106
|
+
env: homeOverride ? {} : env,
|
|
107
107
|
});
|
|
108
|
-
return
|
|
108
|
+
return cache.toolchainDirs;
|
|
109
109
|
}
|
|
110
110
|
// The user-level toolchain bin directories (Homebrew, ~/.local/bin, ~/.bun/bin,
|
|
111
111
|
// version-manager node dirs, npm prefixes, …) that binary *resolution* searches
|
|
@@ -113,18 +113,19 @@ function userToolchainDirs() {
|
|
|
113
113
|
// a binary can resolve here yet fail to *execute* if its shebang interpreter
|
|
114
114
|
// (e.g. `#!/usr/bin/env bun`) lives in one of these dirs and the spawn PATH
|
|
115
115
|
// doesn't include it. Keeping resolution and spawn PATH symmetric fixes that.
|
|
116
|
-
export function userToolchainBinDirs() {
|
|
117
|
-
return userToolchainDirs();
|
|
116
|
+
export function userToolchainBinDirs(_required = {}, options = {}) {
|
|
117
|
+
return userToolchainDirs(options);
|
|
118
118
|
}
|
|
119
|
-
function resolvePathDirs() {
|
|
119
|
+
function resolvePathDirs(options = {}) {
|
|
120
|
+
const env = options.env ?? process.env;
|
|
120
121
|
const seen = new Set();
|
|
121
122
|
const dirs = [
|
|
122
|
-
...(
|
|
123
|
+
...(env.PATH || '').split(delimiter),
|
|
123
124
|
// GUI launchers (macOS .app bundles, Linux .desktop files) often start
|
|
124
125
|
// with a minimal PATH. Include common user-level CLI install locations
|
|
125
126
|
// so agent detection matches the user's shell-installed tools,
|
|
126
127
|
// especially Node version managers.
|
|
127
|
-
...userToolchainDirs(),
|
|
128
|
+
...userToolchainDirs(options),
|
|
128
129
|
];
|
|
129
130
|
return dirs.filter((dir) => {
|
|
130
131
|
if (!dir || seen.has(dir))
|
|
@@ -137,8 +138,8 @@ function resolvePathDirs() {
|
|
|
137
138
|
// detection can attach it to a `not-on-path` diagnostic verbatim — the UI
|
|
138
139
|
// shows the user where we actually looked before asking them to set an
|
|
139
140
|
// explicit binary path, instead of recomputing PATH client-side.
|
|
140
|
-
export function agentSearchDirs() {
|
|
141
|
-
return resolvePathDirs();
|
|
141
|
+
export function agentSearchDirs(_required = {}, options = {}) {
|
|
142
|
+
return resolvePathDirs(options);
|
|
142
143
|
}
|
|
143
144
|
// The `*_BIN` environment variable that overrides PATH detection for a given
|
|
144
145
|
// agent id (e.g. `cursor-agent` → `CURSOR_AGENT_BIN`), or null when the agent
|
|
@@ -148,11 +149,12 @@ export function agentBinEnvKey({ agentId }) {
|
|
|
148
149
|
return null;
|
|
149
150
|
return AGENT_BIN_ENV_KEYS.get(agentId) ?? null;
|
|
150
151
|
}
|
|
151
|
-
export function resolveOnPath({ bin }) {
|
|
152
|
+
export function resolveOnPath({ bin }, options = {}) {
|
|
153
|
+
const env = options.env ?? process.env;
|
|
152
154
|
const exts = process.platform === 'win32'
|
|
153
|
-
? (
|
|
155
|
+
? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';')
|
|
154
156
|
: [''];
|
|
155
|
-
const dirs = resolvePathDirs();
|
|
157
|
+
const dirs = resolvePathDirs(options);
|
|
156
158
|
for (const dir of dirs) {
|
|
157
159
|
for (const ext of exts) {
|
|
158
160
|
const full = path.join(dir, bin + ext);
|
|
@@ -162,17 +164,18 @@ export function resolveOnPath({ bin }) {
|
|
|
162
164
|
}
|
|
163
165
|
return null;
|
|
164
166
|
}
|
|
165
|
-
function looksExecutableOnWindows(filePath) {
|
|
167
|
+
function looksExecutableOnWindows(filePath, options = {}) {
|
|
168
|
+
const env = options.env ?? process.env;
|
|
166
169
|
const ext = path.extname(filePath).trim().toUpperCase();
|
|
167
170
|
if (!ext)
|
|
168
171
|
return false;
|
|
169
|
-
const executableExts = (
|
|
172
|
+
const executableExts = (env.PATHEXT || '.EXE;.CMD;.BAT')
|
|
170
173
|
.split(';')
|
|
171
174
|
.map((value) => value.trim().toUpperCase())
|
|
172
175
|
.filter(Boolean);
|
|
173
176
|
return executableExts.includes(ext);
|
|
174
177
|
}
|
|
175
|
-
function executableFilePath(raw) {
|
|
178
|
+
function executableFilePath(raw, options = {}) {
|
|
176
179
|
if (typeof raw !== 'string' || raw.trim().length === 0)
|
|
177
180
|
return null;
|
|
178
181
|
const expanded = expandHomePath({ value: raw.trim() });
|
|
@@ -182,7 +185,7 @@ function executableFilePath(raw) {
|
|
|
182
185
|
if (!statSync(expanded).isFile())
|
|
183
186
|
return null;
|
|
184
187
|
if (process.platform === 'win32') {
|
|
185
|
-
if (!looksExecutableOnWindows(expanded))
|
|
188
|
+
if (!looksExecutableOnWindows(expanded, options))
|
|
186
189
|
return null;
|
|
187
190
|
}
|
|
188
191
|
else {
|
|
@@ -198,30 +201,32 @@ function executableFilePath(raw) {
|
|
|
198
201
|
// `def.bin` first, then walks `def.fallbackBins` in order. Used for agents
|
|
199
202
|
// whose forks ship under a different binary name but speak the exact same
|
|
200
203
|
// CLI. Returns null when no candidate is on PATH.
|
|
201
|
-
function configuredExecutableOverride(def, configuredEnv = {}) {
|
|
204
|
+
function configuredExecutableOverride(def, configuredEnv = {}, options = {}) {
|
|
202
205
|
const envKey = AGENT_BIN_ENV_KEYS.get(def?.id);
|
|
203
206
|
if (!envKey)
|
|
204
207
|
return null;
|
|
205
|
-
return executableFilePath(configuredEnv?.[envKey]);
|
|
208
|
+
return executableFilePath(configuredEnv?.[envKey], options);
|
|
206
209
|
}
|
|
207
|
-
export function resolveAmrOpenCodeExecutable({}, { env = process.env } = {}) {
|
|
208
|
-
const
|
|
210
|
+
export function resolveAmrOpenCodeExecutable({}, { env = process.env, state = defaultRuntimeState } = {}) {
|
|
211
|
+
const options = { env, state };
|
|
212
|
+
const executableResolutionEnv = state.executable.envNames;
|
|
213
|
+
const configured = executableFilePath(env.VELA_OPENCODE_BIN, options);
|
|
209
214
|
if (configured)
|
|
210
215
|
return configured;
|
|
211
216
|
// In packaged builds prefer the bundled companion under
|
|
212
217
|
// `<resourceRoot>/bin/libexec/opencode/opencode` so a stale global
|
|
213
218
|
// `opencode` on the user's PATH can't override the known-good build that
|
|
214
219
|
// shipped with this app. PATH is only consulted as a last resort.
|
|
215
|
-
const resourceRoot = (env[executableResolutionEnv.resourceRootEnvVar]
|
|
220
|
+
const resourceRoot = (env[executableResolutionEnv.resourceRootEnvVar])?.trim();
|
|
216
221
|
if (resourceRoot) {
|
|
217
|
-
const bundledDir = packagedVelaOpenCodeCompanionTree(resourceRoot);
|
|
222
|
+
const bundledDir = packagedVelaOpenCodeCompanionTree(resourceRoot, options);
|
|
218
223
|
if (bundledDir) {
|
|
219
|
-
const bundled = executableFilePath(path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode'));
|
|
224
|
+
const bundled = executableFilePath(path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode'), options);
|
|
220
225
|
if (bundled)
|
|
221
226
|
return bundled;
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
|
-
return resolveOnPath({ bin: 'opencode-cli' }) ?? resolveOnPath({ bin: 'opencode' });
|
|
229
|
+
return resolveOnPath({ bin: 'opencode-cli' }, options) ?? resolveOnPath({ bin: 'opencode' }, options);
|
|
225
230
|
}
|
|
226
231
|
// The AMR (vela) OpenCode companion ships as a
|
|
227
232
|
// `<resourceRoot>/bin/libexec/opencode/opencode` *executable file*, not just
|
|
@@ -229,7 +234,7 @@ export function resolveAmrOpenCodeExecutable({}, { env = process.env } = {}) {
|
|
|
229
234
|
// a false-positive availability path: `detectAgents()` would surface AMR as
|
|
230
235
|
// available even though the first real run can't launch. Verify the inner
|
|
231
236
|
// executable too.
|
|
232
|
-
function packagedVelaOpenCodeCompanionTree(resourceRoot) {
|
|
237
|
+
function packagedVelaOpenCodeCompanionTree(resourceRoot, options = {}) {
|
|
233
238
|
const candidate = path.join(resourceRoot, 'bin', 'libexec', 'opencode');
|
|
234
239
|
const exe = path.join(candidate, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
235
240
|
try {
|
|
@@ -238,7 +243,7 @@ function packagedVelaOpenCodeCompanionTree(resourceRoot) {
|
|
|
238
243
|
if (!statSync(exe).isFile())
|
|
239
244
|
return null;
|
|
240
245
|
if (process.platform === 'win32') {
|
|
241
|
-
if (!looksExecutableOnWindows(exe))
|
|
246
|
+
if (!looksExecutableOnWindows(exe, options))
|
|
242
247
|
return null;
|
|
243
248
|
}
|
|
244
249
|
else {
|
|
@@ -250,14 +255,16 @@ function packagedVelaOpenCodeCompanionTree(resourceRoot) {
|
|
|
250
255
|
return null;
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
|
-
function packagedBuiltInExecutable(def, configuredEnv = {}) {
|
|
258
|
+
function packagedBuiltInExecutable(def, configuredEnv = {}, options = {}) {
|
|
254
259
|
if (def.id !== 'amr')
|
|
255
260
|
return null;
|
|
256
|
-
const
|
|
261
|
+
const env = options.env ?? process.env;
|
|
262
|
+
const executableResolutionEnv = (options.state ?? defaultRuntimeState).executable.envNames;
|
|
263
|
+
const resourceRoot = env[executableResolutionEnv.resourceRootEnvVar]?.trim();
|
|
257
264
|
if (!resourceRoot)
|
|
258
265
|
return null;
|
|
259
|
-
if (!resolveAmrOpenCodeExecutable({}, { env: { ...
|
|
260
|
-
!packagedVelaOpenCodeCompanionTree(resourceRoot)) {
|
|
266
|
+
if (!resolveAmrOpenCodeExecutable({}, { ...options, env: { ...env, ...configuredEnv } }) &&
|
|
267
|
+
!packagedVelaOpenCodeCompanionTree(resourceRoot, options)) {
|
|
261
268
|
return null;
|
|
262
269
|
}
|
|
263
270
|
const candidate = path.join(resourceRoot, 'bin', process.platform === 'win32' ? 'vela.exe' : 'vela');
|
|
@@ -265,7 +272,7 @@ function packagedBuiltInExecutable(def, configuredEnv = {}) {
|
|
|
265
272
|
if (!statSync(candidate).isFile())
|
|
266
273
|
return null;
|
|
267
274
|
if (process.platform === 'win32') {
|
|
268
|
-
if (!looksExecutableOnWindows(candidate))
|
|
275
|
+
if (!looksExecutableOnWindows(candidate, options))
|
|
269
276
|
return null;
|
|
270
277
|
}
|
|
271
278
|
else {
|
|
@@ -286,11 +293,11 @@ function packagedBuiltInExecutable(def, configuredEnv = {}) {
|
|
|
286
293
|
// bundle. Probe the well-known bundle locations so app-only installs are
|
|
287
294
|
// detected. This is a last-resort fallback that ranks below PATH, so an
|
|
288
295
|
// explicit `npm i -g` / Homebrew / version-manager install always wins.
|
|
289
|
-
function codexAppBundleExecutable(def) {
|
|
296
|
+
function codexAppBundleExecutable(def, options = {}) {
|
|
290
297
|
if (def?.id !== 'codex')
|
|
291
298
|
return null;
|
|
292
|
-
for (const candidate of codexAppBundleCandidates()) {
|
|
293
|
-
const resolved = executableFilePath(candidate);
|
|
299
|
+
for (const candidate of codexAppBundleCandidates({}, options)) {
|
|
300
|
+
const resolved = executableFilePath(candidate, options);
|
|
294
301
|
if (resolved)
|
|
295
302
|
return resolved;
|
|
296
303
|
}
|
|
@@ -301,12 +308,12 @@ function codexAppBundleExecutable(def) {
|
|
|
301
308
|
// depend on the host actually having `/Applications/Codex.app`), so tests
|
|
302
309
|
// assert the built candidate list directly to catch a path typo or
|
|
303
310
|
// ordering regression in the common real-world install case.
|
|
304
|
-
export function codexAppBundleCandidates() {
|
|
311
|
+
export function codexAppBundleCandidates(_required = {}, options = {}) {
|
|
305
312
|
// The Codex app bundle is a macOS-only concept; other platforms have no
|
|
306
313
|
// analogous standalone install of the `codex` CLI to probe for here.
|
|
307
314
|
if (process.platform !== 'darwin')
|
|
308
315
|
return [];
|
|
309
|
-
const { home, hasOverride } = resolveDetectionHome();
|
|
316
|
+
const { home, hasOverride } = resolveDetectionHome(options);
|
|
310
317
|
const bundleSuffix = ['Codex.app', 'Contents', 'Resources', 'codex'];
|
|
311
318
|
// User-scoped install (~/Applications). Honors the override home so
|
|
312
319
|
// sandboxed detection runs and tests stay deterministic.
|
|
@@ -319,10 +326,10 @@ export function codexAppBundleCandidates() {
|
|
|
319
326
|
}
|
|
320
327
|
return candidates;
|
|
321
328
|
}
|
|
322
|
-
export function resolveAgentExecutable({ def },
|
|
323
|
-
return inspectAgentExecutableResolution({ def: def },
|
|
329
|
+
export function resolveAgentExecutable({ def }, options = {}) {
|
|
330
|
+
return inspectAgentExecutableResolution({ def: def }, options).selectedPath;
|
|
324
331
|
}
|
|
325
|
-
export function inspectAgentExecutableResolution({ def },
|
|
332
|
+
export function inspectAgentExecutableResolution({ def }, options = {}) {
|
|
326
333
|
if (!def?.bin) {
|
|
327
334
|
return {
|
|
328
335
|
configuredOverridePath: null,
|
|
@@ -330,21 +337,22 @@ export function inspectAgentExecutableResolution({ def }, { configuredEnv = {} }
|
|
|
330
337
|
selectedPath: null,
|
|
331
338
|
};
|
|
332
339
|
}
|
|
333
|
-
const
|
|
340
|
+
const configuredEnv = options.configuredEnv ?? {};
|
|
341
|
+
const configuredOverridePath = configuredExecutableOverride(def, configuredEnv, options);
|
|
334
342
|
const candidates = [
|
|
335
343
|
def.bin,
|
|
336
344
|
...(Array.isArray(def.fallbackBins) ? def.fallbackBins : []),
|
|
337
345
|
];
|
|
338
346
|
let pathResolvedPath = null;
|
|
339
347
|
for (const bin of candidates) {
|
|
340
|
-
const resolved = resolveOnPath({ bin: bin });
|
|
348
|
+
const resolved = resolveOnPath({ bin: bin }, options);
|
|
341
349
|
if (resolved) {
|
|
342
350
|
pathResolvedPath = resolved;
|
|
343
351
|
break;
|
|
344
352
|
}
|
|
345
353
|
}
|
|
346
|
-
const builtInPath = packagedBuiltInExecutable(def, configuredEnv);
|
|
347
|
-
const appBundlePath = codexAppBundleExecutable(def);
|
|
354
|
+
const builtInPath = packagedBuiltInExecutable(def, configuredEnv, options);
|
|
355
|
+
const appBundlePath = codexAppBundleExecutable(def, options);
|
|
348
356
|
return {
|
|
349
357
|
configuredOverridePath,
|
|
350
358
|
pathResolvedPath,
|