scoutline 0.10.2 → 0.11.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/README.md +72 -44
- package/dist/capabilities/vision.d.ts +4 -3
- package/dist/capabilities/vision.d.ts.map +1 -1
- package/dist/capabilities/vision.js +7 -5
- package/dist/capabilities/vision.js.map +1 -1
- package/dist/commands/crawl.d.ts.map +1 -1
- package/dist/commands/crawl.js +14 -3
- package/dist/commands/crawl.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +15 -10
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/map.d.ts.map +1 -1
- package/dist/commands/map.js +16 -3
- package/dist/commands/map.js.map +1 -1
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +9 -3
- package/dist/commands/read.js.map +1 -1
- package/dist/commands/repo.d.ts.map +1 -1
- package/dist/commands/repo.js +5 -3
- package/dist/commands/repo.js.map +1 -1
- package/dist/commands/research.d.ts +35 -7
- package/dist/commands/research.d.ts.map +1 -1
- package/dist/commands/research.js +93 -15
- package/dist/commands/research.js.map +1 -1
- package/dist/commands/vision.d.ts.map +1 -1
- package/dist/commands/vision.js +2 -1
- package/dist/commands/vision.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +338 -250
- package/dist/index.js.map +1 -1
- package/dist/lib/errors.d.ts +11 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +14 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/execution.d.ts.map +1 -1
- package/dist/lib/execution.js +12 -8
- package/dist/lib/execution.js.map +1 -1
- package/dist/lib/provider-fallback.d.ts +150 -0
- package/dist/lib/provider-fallback.d.ts.map +1 -0
- package/dist/lib/provider-fallback.js +510 -0
- package/dist/lib/provider-fallback.js.map +1 -0
- package/dist/providers/brave/adapter.d.ts.map +1 -1
- package/dist/providers/brave/adapter.js +2 -0
- package/dist/providers/brave/adapter.js.map +1 -1
- package/dist/providers/exa/adapter.d.ts.map +1 -1
- package/dist/providers/exa/adapter.js +10 -1
- package/dist/providers/exa/adapter.js.map +1 -1
- package/dist/providers/firecrawl/adapter.d.ts.map +1 -1
- package/dist/providers/firecrawl/adapter.js +13 -3
- package/dist/providers/firecrawl/adapter.js.map +1 -1
- package/dist/providers/minimax/adapter.d.ts.map +1 -1
- package/dist/providers/minimax/adapter.js +4 -0
- package/dist/providers/minimax/adapter.js.map +1 -1
- package/dist/providers/tavily/adapter.d.ts.map +1 -1
- package/dist/providers/tavily/adapter.js +2 -0
- package/dist/providers/tavily/adapter.js.map +1 -1
- package/dist/providers/types.d.ts +28 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +8 -0
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/zai/adapter.d.ts.map +1 -1
- package/dist/providers/zai/adapter.js +4 -0
- package/dist/providers/zai/adapter.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,12 +17,13 @@ import { isExtractMode } from "./lib/extract.js";
|
|
|
17
17
|
import { runCodeFile, evalCode, printInterfaces, printPromptTemplate, CODE_HELP, } from "./commands/code.js";
|
|
18
18
|
import { isOutputMode, OUTPUT_MODES } from "./lib/output.js";
|
|
19
19
|
import { formatErrorOutput } from "./lib/output.js";
|
|
20
|
-
import {
|
|
20
|
+
import { ValidationError, UnsupportedCapabilityError, getErrorExitCode, } from "./lib/errors.js";
|
|
21
21
|
import { invokeCommand } from "./command-invocation.js";
|
|
22
22
|
import { defaultResponseCache } from "./lib/cache.js";
|
|
23
23
|
import { configuredSecrets } from "./lib/redact.js";
|
|
24
24
|
import { resolveProviderId } from "./providers/selection.js";
|
|
25
|
-
import { BUILT_IN_PROVIDER_DESCRIPTORS
|
|
25
|
+
import { BUILT_IN_PROVIDER_DESCRIPTORS } from "./providers/registry.js";
|
|
26
|
+
import { executeWithFallback } from "./lib/provider-fallback.js";
|
|
26
27
|
import { visionOperationToCapability } from "./capabilities/vision.js";
|
|
27
28
|
import { createRequire } from "node:module";
|
|
28
29
|
const require = createRequire(import.meta.url);
|
|
@@ -64,11 +65,14 @@ and 'research' commands participate in Provider selection: Z.AI
|
|
|
64
65
|
advertises and supplies repository-exploration and reader; Tavily
|
|
65
66
|
advertises and supplies reader plus crawl, map, and research; Exa
|
|
66
67
|
advertises and supplies search, reader, and research; MiniMax
|
|
67
|
-
advertises and supplies none of those Provider-only Capabilities.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
advertises and supplies none of those Provider-only Capabilities.
|
|
69
|
+
Provider fallback is always-on by default (0.11.0+): selecting a
|
|
70
|
+
non-supplier emits a stderr notice and silently reroutes to the next
|
|
71
|
+
eligible configured Provider in registry order. Use --no-fallback (or
|
|
72
|
+
SCOUTLINE_NO_FALLBACK=1) to restore the previous strict
|
|
73
|
+
UNSUPPORTED_CAPABILITY behavior. Z.AI-only commands (tools, tool,
|
|
74
|
+
call, code) carry the flag but ignore it. Quota and doctor report
|
|
75
|
+
per-Provider; --provider picks the effective Provider for metadata.
|
|
72
76
|
|
|
73
77
|
Global Options:
|
|
74
78
|
--output-format <data|json|pretty|compact|markdown|refs|tty> Output mode (default: data)
|
|
@@ -137,6 +141,7 @@ function extractGlobalOptions(args) {
|
|
|
137
141
|
let forcePretty = false;
|
|
138
142
|
let forceRaw = false;
|
|
139
143
|
let provider;
|
|
144
|
+
let noFallback = false;
|
|
140
145
|
for (let i = 0; i < args.length; i += 1) {
|
|
141
146
|
const arg = args[i];
|
|
142
147
|
if (arg === "--output-format" || arg === "-O") {
|
|
@@ -161,9 +166,19 @@ function extractGlobalOptions(args) {
|
|
|
161
166
|
i += 1;
|
|
162
167
|
continue;
|
|
163
168
|
}
|
|
169
|
+
if (arg === "--no-fallback") {
|
|
170
|
+
// Provider-fallback kill-switch. Accepted before OR after the
|
|
171
|
+
// command token and removed from the rest stream so command-local
|
|
172
|
+
// parsing never observes it. Resolution against
|
|
173
|
+
// `SCOUTLINE_NO_FALLBACK` lives in `main` so a test that drives
|
|
174
|
+
// `extractGlobalOptions` directly can assert the flag shape in
|
|
175
|
+
// isolation.
|
|
176
|
+
noFallback = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
164
179
|
rest.push(arg);
|
|
165
180
|
}
|
|
166
|
-
return { outputFormat, forcePretty, forceRaw, provider, rest };
|
|
181
|
+
return { outputFormat, forcePretty, forceRaw, provider, noFallback, rest };
|
|
167
182
|
}
|
|
168
183
|
function resolveOutputMode(explicit, forcePretty, forceRaw, adapter) {
|
|
169
184
|
if (explicit !== undefined) {
|
|
@@ -200,63 +215,83 @@ async function handleVision(args, outputMode, deps) {
|
|
|
200
215
|
// Resolve the effective Provider for Vision (DESIGN.md §6). Invalid
|
|
201
216
|
// explicit/env input fails here with VALIDATION_ERROR before any Vision
|
|
202
217
|
// support check or media access. Selection never consults credentials
|
|
203
|
-
// (FR-003); the configured check below is the
|
|
218
|
+
// (FR-003); the configured check below is the executor's responsibility
|
|
219
|
+
// (Provider Fallback Tech Plan §"Per-handler refactor pattern").
|
|
204
220
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
// branches on a Provider ID: the support check alone decides availability.
|
|
221
|
+
// Per-operation capability id (e.g. `vision.interpret-image`,
|
|
222
|
+
// `vision.chart`). The executor's preflight uses this to filter
|
|
223
|
+
// descriptors, and notice wording carries it verbatim. Vision
|
|
224
|
+
// sub-operations share the same `adapter.vision` slot — the
|
|
225
|
+
// per-operation capability is in the descriptor metadata.
|
|
211
226
|
const capabilityId = visionOperationToCapability(operation);
|
|
212
|
-
if (!descriptor.capabilities().has(capabilityId)) {
|
|
213
|
-
throw new UnsupportedCapabilityError(providerId, capabilityId);
|
|
214
|
-
}
|
|
215
|
-
// FR-003: selection returns the default zai even when unconfigured. The
|
|
216
|
-
// dispatch layer surfaces a missing credential as ConfigurationError
|
|
217
|
-
// (exit 3), AFTER the capability support check (FR-023) but before any
|
|
218
|
-
// Adapter construction or media access (Fixup A — B5).
|
|
219
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
220
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
221
|
-
}
|
|
222
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
223
|
-
const visionCapability = adapter.vision;
|
|
224
|
-
if (!visionCapability || !visionCapability.supports(operation)) {
|
|
225
|
-
throw new UnsupportedCapabilityError(providerId, capabilityId);
|
|
226
|
-
}
|
|
227
227
|
// Vision bypasses the response cache (FR-022). The shared execution
|
|
228
228
|
// primitives (sleep/random) are the same ones Search consumes; they
|
|
229
229
|
// drive retry backoff deterministically under test.
|
|
230
|
-
const
|
|
231
|
-
capability: visionCapability,
|
|
230
|
+
const visionDepsShape = {
|
|
232
231
|
sleep: deps.searchSleep,
|
|
233
232
|
random: deps.searchRandom,
|
|
234
233
|
};
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
234
|
+
// Provider-fallback Ticket 02: route every vision operation through
|
|
235
|
+
// the shared executor. The existing vision URL→temp-file fallback
|
|
236
|
+
// stays inside the Z.AI adapter's invoke, layered beneath provider
|
|
237
|
+
// fallback.
|
|
238
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
239
|
+
const outcome = await executeWithFallback({
|
|
240
|
+
capabilityId,
|
|
241
|
+
commandLabel: "vision",
|
|
242
|
+
effectiveProvider: providerId,
|
|
243
|
+
descriptors: deps.providerDescriptors,
|
|
244
|
+
env: deps.env,
|
|
245
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
246
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
247
|
+
}, async (descriptor) => {
|
|
248
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
249
|
+
// The executor's preflight guarantees the descriptor advertises
|
|
250
|
+
// the capability and supplies a non-null adapter slot, so the
|
|
251
|
+
// cast is safe. Operation-level support is checked immediately
|
|
252
|
+
// against `visionCapability.supports(operation)` — descriptor
|
|
253
|
+
// metadata and adapter capability are independent sources of
|
|
254
|
+
// truth, and the live dispatch must fail closed on disagreement
|
|
255
|
+
// (Review Fix 4). A mismatch throws `UnsupportedCapabilityError`
|
|
256
|
+
// before any media work / transport use, and the Executor treats
|
|
257
|
+
// it as a `continue` so the next candidate (if any) gets a
|
|
258
|
+
// chance.
|
|
259
|
+
const visionCapability = adapter.vision;
|
|
260
|
+
if (!visionCapability || !visionCapability.supports(operation)) {
|
|
261
|
+
throw new UnsupportedCapabilityError(descriptor.id, capabilityId);
|
|
262
|
+
}
|
|
263
|
+
const visionDeps = {
|
|
264
|
+
capability: visionCapability,
|
|
265
|
+
...visionDepsShape,
|
|
266
|
+
};
|
|
267
|
+
switch (command) {
|
|
268
|
+
case "analyze":
|
|
269
|
+
return vision.analyze(source, prompt, visionDeps, context);
|
|
270
|
+
case "ui-to-code": {
|
|
271
|
+
const outputType = flags.output || "code";
|
|
272
|
+
return vision.uiToCode(source, prompt, outputType, visionDeps, context);
|
|
273
|
+
}
|
|
274
|
+
case "extract-text":
|
|
275
|
+
return vision.extractText(source, prompt, flags.language, visionDeps, context);
|
|
276
|
+
case "diagnose-error":
|
|
277
|
+
return vision.diagnoseError(source, prompt, flags.context, visionDeps, context);
|
|
278
|
+
case "diagram":
|
|
279
|
+
return vision.diagram(source, prompt, flags.type, visionDeps, context);
|
|
280
|
+
case "chart":
|
|
281
|
+
return vision.chart(source, prompt, flags.focus, visionDeps, context);
|
|
282
|
+
case "diff": {
|
|
283
|
+
const actual = positional[2];
|
|
284
|
+
const diffPrompt = positional[3];
|
|
285
|
+
return vision.diff(source, actual, diffPrompt, visionDeps, context);
|
|
286
|
+
}
|
|
287
|
+
case "video":
|
|
288
|
+
return vision.video(source, prompt, visionDeps, context);
|
|
289
|
+
default:
|
|
290
|
+
throw new ValidationError(`Unknown vision command: ${command}`, 'Run "scoutline vision --help" for available commands');
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
return outcome.result;
|
|
294
|
+
}, outputMode, deps.now, deps.secrets);
|
|
260
295
|
}
|
|
261
296
|
/**
|
|
262
297
|
* Map a Vision subcommand to its discriminated operation id. Used by
|
|
@@ -380,29 +415,14 @@ async function handleSearch(args, outputMode, deps) {
|
|
|
380
415
|
if (type !== undefined && topic !== undefined) {
|
|
381
416
|
throw new ValidationError("--type and --topic are mutually exclusive (--type has no editorial topic axis).", "Pass either --type or --topic, not both.");
|
|
382
417
|
}
|
|
383
|
-
// Resolve the Provider ONLY inside shared Search (DESIGN.md §6).
|
|
384
|
-
// command families carry the parsed flag but never resolve or
|
|
385
|
-
// it. An invalid explicit/env value throws VALIDATION_ERROR
|
|
386
|
-
// any Adapter construction or invocation. Selection never
|
|
387
|
-
// credentials (FR-003); the configured check
|
|
388
|
-
// responsibility (
|
|
418
|
+
// Resolve the effective Provider ONLY inside shared Search (DESIGN.md §6).
|
|
419
|
+
// Other command families carry the parsed flag but never resolve or
|
|
420
|
+
// validate it. An invalid explicit/env value throws VALIDATION_ERROR
|
|
421
|
+
// here, before any Adapter construction or invocation. Selection never
|
|
422
|
+
// consults credentials (FR-003); the configured check is the
|
|
423
|
+
// executor's responsibility (Provider Fallback Tech Plan §"Per-handler
|
|
424
|
+
// refactor pattern").
|
|
389
425
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
390
|
-
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
391
|
-
if (!descriptor.capabilities().has("search")) {
|
|
392
|
-
throw new UnsupportedCapabilityError(providerId, "search");
|
|
393
|
-
}
|
|
394
|
-
// FR-003: selection returns the default zai even when unconfigured. The
|
|
395
|
-
// dispatch layer surfaces a missing credential as ConfigurationError
|
|
396
|
-
// (exit 3), AFTER the capability support check (FR-023) but before any
|
|
397
|
-
// Adapter construction (Fixup A — B5).
|
|
398
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
399
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
400
|
-
}
|
|
401
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
402
|
-
const capability = adapter.search;
|
|
403
|
-
if (!capability) {
|
|
404
|
-
throw new UnsupportedCapabilityError(providerId, "search");
|
|
405
|
-
}
|
|
406
426
|
const query = positional.join(" ");
|
|
407
427
|
const fieldsRaw = flags.fields;
|
|
408
428
|
const fields = fieldsRaw
|
|
@@ -411,7 +431,7 @@ async function handleSearch(args, outputMode, deps) {
|
|
|
411
431
|
.map((f) => f.trim())
|
|
412
432
|
.filter(Boolean)
|
|
413
433
|
: undefined;
|
|
414
|
-
|
|
434
|
+
const searchOptions = {
|
|
415
435
|
count,
|
|
416
436
|
domain: flags.domain,
|
|
417
437
|
recency: flags.recency,
|
|
@@ -425,12 +445,36 @@ async function handleSearch(args, outputMode, deps) {
|
|
|
425
445
|
fields: fields && fields.length > 0 ? fields : undefined,
|
|
426
446
|
noCache: flags["no-cache"] === true,
|
|
427
447
|
merge: flags.merge === true,
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
448
|
+
};
|
|
449
|
+
// Provider-fallback Ticket 02: route the call through the shared
|
|
450
|
+
// fallback executor. The executor owns capability+configured+adapter
|
|
451
|
+
// preflight (FR-023/024), candidate plan ordering, error
|
|
452
|
+
// classification, exhaustion semantics, and notices. The handler
|
|
453
|
+
// keeps command-specific work (request building, flag parsing, which
|
|
454
|
+
// executeX). `--merge` runs the whole parallel sub-query batch inside
|
|
455
|
+
// a single attempt so a fallback switch replaces the entire batch,
|
|
456
|
+
// never individual sub-queries (Tech Plan §"Handler non-uniformity").
|
|
457
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
458
|
+
const outcome = await executeWithFallback({
|
|
459
|
+
capabilityId: "search",
|
|
460
|
+
commandLabel: "search",
|
|
461
|
+
effectiveProvider: providerId,
|
|
462
|
+
descriptors: deps.providerDescriptors,
|
|
463
|
+
env: deps.env,
|
|
464
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
465
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
466
|
+
}, async (descriptor) => {
|
|
467
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
468
|
+
const capability = adapter.search;
|
|
469
|
+
return search(query, searchOptions, {
|
|
470
|
+
capability,
|
|
471
|
+
cache: deps.searchCache,
|
|
472
|
+
sleep: deps.searchSleep,
|
|
473
|
+
random: deps.searchRandom,
|
|
474
|
+
}, context);
|
|
475
|
+
});
|
|
476
|
+
return outcome.result;
|
|
477
|
+
}, outputMode, deps.now, deps.secrets);
|
|
434
478
|
}
|
|
435
479
|
async function handleRead(args, outputMode, deps) {
|
|
436
480
|
const { flags, positional } = parseArgs(args);
|
|
@@ -460,40 +504,7 @@ async function handleRead(args, outputMode, deps) {
|
|
|
460
504
|
// never consults credentials (FR-003) and never branches on Provider
|
|
461
505
|
// ID; an unknown explicit/env value throws VALIDATION_ERROR here.
|
|
462
506
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
463
|
-
const
|
|
464
|
-
// Capability support check BEFORE `descriptor.isConfigured`,
|
|
465
|
-
// `descriptor.create`, Adapter access, operation validation/
|
|
466
|
-
// cacheIdentity, credential use, cache, or transport. Unsupported
|
|
467
|
-
// MiniMax (explicit or environment) returns UNSUPPORTED_CAPABILITY
|
|
468
|
-
// with no fallback to Z.AI; zero selected-Provider work occurs.
|
|
469
|
-
if (!descriptor.capabilities().has("reader")) {
|
|
470
|
-
throw new UnsupportedCapabilityError(providerId, "reader");
|
|
471
|
-
}
|
|
472
|
-
// FR-003: selection returns the default zai even when unconfigured.
|
|
473
|
-
// Supported-but-unconfigured Z.AI surfaces a missing credential as
|
|
474
|
-
// ConfigurationError (exit 3) AFTER the support metadata check and
|
|
475
|
-
// BEFORE `descriptor.create`.
|
|
476
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
477
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
478
|
-
}
|
|
479
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
480
|
-
const capability = adapter.reader;
|
|
481
|
-
// Defensive fail-closed: the descriptor advertised support but the
|
|
482
|
-
// Adapter omitted the handle. Treat as unsupported so a registry
|
|
483
|
-
// mismatch can never reach transport.
|
|
484
|
-
if (!capability) {
|
|
485
|
-
throw new UnsupportedCapabilityError(providerId, "reader");
|
|
486
|
-
}
|
|
487
|
-
// Shared Reader execution dependencies. The cache/sleep/random
|
|
488
|
-
// default to the same production values as Search and Repository
|
|
489
|
-
// but are kept as separate optional MainDependencies so reader
|
|
490
|
-
// tests can inject isolated in-memory doubles.
|
|
491
|
-
const executionDeps = {
|
|
492
|
-
cache: deps.readerCache,
|
|
493
|
-
sleep: deps.readerSleep,
|
|
494
|
-
random: deps.readerRandom,
|
|
495
|
-
};
|
|
496
|
-
return invokeCommand(deps.invocation, (context) => read(url, {
|
|
507
|
+
const readOptions = {
|
|
497
508
|
format: flags.format,
|
|
498
509
|
noImages: flags["no-images"] === true,
|
|
499
510
|
noCache: flags["no-cache"] === true,
|
|
@@ -505,7 +516,39 @@ async function handleRead(args, outputMode, deps) {
|
|
|
505
516
|
maxChars: flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined,
|
|
506
517
|
fullEnvelope: flags["full-envelope"] === true,
|
|
507
518
|
extract,
|
|
508
|
-
}
|
|
519
|
+
};
|
|
520
|
+
// Shared Reader execution dependencies. The cache/sleep/random
|
|
521
|
+
// default to the same production values as Search and Repository
|
|
522
|
+
// but are kept as separate optional MainDependencies so reader
|
|
523
|
+
// tests can inject isolated in-memory doubles.
|
|
524
|
+
const executionDeps = {
|
|
525
|
+
cache: deps.readerCache,
|
|
526
|
+
sleep: deps.readerSleep,
|
|
527
|
+
random: deps.readerRandom,
|
|
528
|
+
};
|
|
529
|
+
// Provider-fallback Ticket 02: route the call through the shared
|
|
530
|
+
// fallback executor. Capability + configured + adapter-handle
|
|
531
|
+
// agreement preflight now lives in the executor (FR-023/024
|
|
532
|
+
// preserved). Under --no-fallback the same preflight runs on the
|
|
533
|
+
// effective Provider only.
|
|
534
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
535
|
+
const outcome = await executeWithFallback({
|
|
536
|
+
capabilityId: "reader",
|
|
537
|
+
commandLabel: "read",
|
|
538
|
+
effectiveProvider: providerId,
|
|
539
|
+
descriptors: deps.providerDescriptors,
|
|
540
|
+
env: deps.env,
|
|
541
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
542
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
543
|
+
}, async (descriptor) => {
|
|
544
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
545
|
+
return read(url, readOptions, {
|
|
546
|
+
capability: adapter.reader,
|
|
547
|
+
execution: executionDeps,
|
|
548
|
+
}, context);
|
|
549
|
+
});
|
|
550
|
+
return outcome.result;
|
|
551
|
+
}, outputMode, deps.now, deps.secrets);
|
|
509
552
|
}
|
|
510
553
|
async function handleCrawl(args, outputMode, deps) {
|
|
511
554
|
const { flags, positional } = parseArgs(args);
|
|
@@ -524,21 +567,6 @@ async function handleCrawl(args, outputMode, deps) {
|
|
|
524
567
|
// Resolve the effective Provider (DESIGN.md §6, FR-001–FR-005):
|
|
525
568
|
// explicit --provider > SCOUTLINE_PROVIDER > default zai.
|
|
526
569
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
527
|
-
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
528
|
-
// Capability support check BEFORE descriptor.isConfigured, descriptor.create,
|
|
529
|
-
// or any Adapter work. Unsupported Z.AI/MiniMax returns
|
|
530
|
-
// UNSUPPORTED_CAPABILITY with no fallback to Tavily.
|
|
531
|
-
if (!descriptor.capabilities().has("crawl")) {
|
|
532
|
-
throw new UnsupportedCapabilityError(providerId, "crawl");
|
|
533
|
-
}
|
|
534
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
535
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
536
|
-
}
|
|
537
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
538
|
-
const capability = adapter.crawl;
|
|
539
|
-
if (!capability) {
|
|
540
|
-
throw new UnsupportedCapabilityError(providerId, "crawl");
|
|
541
|
-
}
|
|
542
570
|
// Shared Crawl execution dependencies. The cache/sleep/random default
|
|
543
571
|
// to the same production values as Search/Repository/Reader but are
|
|
544
572
|
// kept as separate optional MainDependencies so crawl tests can inject
|
|
@@ -548,19 +576,40 @@ async function handleCrawl(args, outputMode, deps) {
|
|
|
548
576
|
sleep: deps.crawlSleep,
|
|
549
577
|
random: deps.crawlRandom,
|
|
550
578
|
};
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
579
|
+
// Provider-fallback Ticket 03: route the call through the shared
|
|
580
|
+
// fallback executor. Capability + configured + adapter-handle
|
|
581
|
+
// preflight lives in the executor (FR-023/024 preserved). The
|
|
582
|
+
// async cost-bearing accepted-risk path (Tech Plan §"Accepted
|
|
583
|
+
// risk"): a runtime failure on crawl may fall back to another
|
|
584
|
+
// Provider even if the failed Provider had already accepted
|
|
585
|
+
// the job. `--no-fallback` eliminates this risk.
|
|
586
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
587
|
+
const outcome = await executeWithFallback({
|
|
588
|
+
capabilityId: "crawl",
|
|
589
|
+
commandLabel: "crawl",
|
|
590
|
+
effectiveProvider: providerId,
|
|
591
|
+
descriptors: deps.providerDescriptors,
|
|
592
|
+
env: deps.env,
|
|
593
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
594
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
595
|
+
}, async (descriptor) => {
|
|
596
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
597
|
+
return crawl(url, {
|
|
598
|
+
depth: flags.depth ? parseInt(flags.depth, 10) : undefined,
|
|
599
|
+
breadth: flags.breadth ? parseInt(flags.breadth, 10) : undefined,
|
|
600
|
+
limit: flags.limit ? parseInt(flags.limit, 10) : undefined,
|
|
601
|
+
selectPaths: flags["select-paths"],
|
|
602
|
+
excludePaths: flags["exclude-paths"],
|
|
603
|
+
instructions: flags.instructions,
|
|
604
|
+
format: flags.format,
|
|
605
|
+
contentSize: flags["content-size"],
|
|
606
|
+
timeout: flags.timeout ? parseInt(flags.timeout, 10) : undefined,
|
|
607
|
+
noCache: flags["no-cache"] === true,
|
|
608
|
+
maxChars: flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined,
|
|
609
|
+
}, { capability: adapter.crawl, execution: executionDeps }, context);
|
|
610
|
+
});
|
|
611
|
+
return outcome.result;
|
|
612
|
+
}, outputMode, deps.now, deps.secrets);
|
|
564
613
|
}
|
|
565
614
|
async function handleMap(args, outputMode, deps) {
|
|
566
615
|
const { flags, positional } = parseArgs(args);
|
|
@@ -579,21 +628,6 @@ async function handleMap(args, outputMode, deps) {
|
|
|
579
628
|
// Resolve the effective Provider (DESIGN.md §6, FR-001–FR-005):
|
|
580
629
|
// explicit --provider > SCOUTLINE_PROVIDER > default zai.
|
|
581
630
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
582
|
-
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
583
|
-
// Capability support check BEFORE descriptor.isConfigured, descriptor.create,
|
|
584
|
-
// or any Adapter work. Unsupported Z.AI/MiniMax returns
|
|
585
|
-
// UNSUPPORTED_CAPABILITY with no fallback to Tavily.
|
|
586
|
-
if (!descriptor.capabilities().has("map")) {
|
|
587
|
-
throw new UnsupportedCapabilityError(providerId, "map");
|
|
588
|
-
}
|
|
589
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
590
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
591
|
-
}
|
|
592
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
593
|
-
const capability = adapter.map;
|
|
594
|
-
if (!capability) {
|
|
595
|
-
throw new UnsupportedCapabilityError(providerId, "map");
|
|
596
|
-
}
|
|
597
631
|
// Shared Map execution dependencies. The cache/sleep/random default
|
|
598
632
|
// to the same production values as Search/Repository/Reader/Crawl but
|
|
599
633
|
// are kept as separate optional MainDependencies so map tests can
|
|
@@ -603,15 +637,34 @@ async function handleMap(args, outputMode, deps) {
|
|
|
603
637
|
sleep: deps.mapSleep,
|
|
604
638
|
random: deps.mapRandom,
|
|
605
639
|
};
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
640
|
+
// Provider-fallback Ticket 03: route the call through the shared
|
|
641
|
+
// fallback executor. Map retries once per Provider today, so the
|
|
642
|
+
// documented worst-case charged request count under retry+fallback
|
|
643
|
+
// is `2 × N candidates` — see the troubleshooting entry added in
|
|
644
|
+
// Ticket 04. `--no-fallback` is the strict-mode opt-out.
|
|
645
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
646
|
+
const outcome = await executeWithFallback({
|
|
647
|
+
capabilityId: "map",
|
|
648
|
+
commandLabel: "map",
|
|
649
|
+
effectiveProvider: providerId,
|
|
650
|
+
descriptors: deps.providerDescriptors,
|
|
651
|
+
env: deps.env,
|
|
652
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
653
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
654
|
+
}, async (descriptor) => {
|
|
655
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
656
|
+
return map(url, {
|
|
657
|
+
depth: flags.depth ? parseInt(flags.depth, 10) : undefined,
|
|
658
|
+
breadth: flags.breadth ? parseInt(flags.breadth, 10) : undefined,
|
|
659
|
+
limit: flags.limit ? parseInt(flags.limit, 10) : undefined,
|
|
660
|
+
selectPaths: flags["select-paths"],
|
|
661
|
+
excludePaths: flags["exclude-paths"],
|
|
662
|
+
instructions: flags.instructions,
|
|
663
|
+
noCache: flags["no-cache"] === true,
|
|
664
|
+
}, { capability: adapter.map, execution: executionDeps }, context);
|
|
665
|
+
});
|
|
666
|
+
return outcome.result;
|
|
667
|
+
}, outputMode, deps.now, deps.secrets);
|
|
615
668
|
}
|
|
616
669
|
async function handleResearch(args, outputMode, deps) {
|
|
617
670
|
const { flags, positional } = parseArgs(args);
|
|
@@ -629,40 +682,73 @@ async function handleResearch(args, outputMode, deps) {
|
|
|
629
682
|
// Resolve the effective Provider (DESIGN.md §6, FR-001–FR-005):
|
|
630
683
|
// explicit --provider > SCOUTLINE_PROVIDER > default zai.
|
|
631
684
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
632
|
-
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
633
|
-
// Capability support check BEFORE descriptor.isConfigured,
|
|
634
|
-
// descriptor.create, or any Adapter work. Unsupported Z.AI/MiniMax
|
|
635
|
-
// returns UNSUPPORTED_CAPABILITY with no fallback to Tavily.
|
|
636
|
-
if (!descriptor.capabilities().has("research")) {
|
|
637
|
-
throw new UnsupportedCapabilityError(providerId, "research");
|
|
638
|
-
}
|
|
639
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
640
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
641
|
-
}
|
|
642
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
643
|
-
const capability = adapter.research;
|
|
644
|
-
if (!capability) {
|
|
645
|
-
throw new UnsupportedCapabilityError(providerId, "research");
|
|
646
|
-
}
|
|
647
685
|
// Shared Research execution dependencies.
|
|
648
686
|
const executionDeps = {
|
|
649
687
|
cache: deps.researchCache,
|
|
650
688
|
sleep: deps.researchSleep,
|
|
651
689
|
random: deps.researchRandom,
|
|
652
690
|
};
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
691
|
+
// Provider-fallback Ticket 03: route the call through the shared
|
|
692
|
+
// fallback executor. The research command is the riskiest piece
|
|
693
|
+
// of the async set: the SIGINT handler, the polling-timeout
|
|
694
|
+
// controller, and the on-disk state-file identity/timeout must
|
|
695
|
+
// RE-BIND to the Provider that actually wins the candidate loop
|
|
696
|
+
// (Tech Plan §"Handler non-uniformity" — research bullet). The
|
|
697
|
+
// `research` command accepts a `registerInterrupt` injection on
|
|
698
|
+
// its handler dependencies; the executor's per-attempt callback
|
|
699
|
+
// builds the descriptor's `capability.run` and re-registers the
|
|
700
|
+
// SIGINT handler under THAT provider's identity before invoke,
|
|
701
|
+
// tearing it down on throw so the next candidate installs its
|
|
702
|
+
// own. The one-time credit warning is emitted ONCE, before the
|
|
703
|
+
// first attempt, so it is not duplicated across candidate
|
|
704
|
+
// switches.
|
|
705
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
706
|
+
const options = {
|
|
707
|
+
model,
|
|
708
|
+
outputLength,
|
|
709
|
+
citationFormat,
|
|
710
|
+
domain: flags.domain,
|
|
711
|
+
maxChars: flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined,
|
|
712
|
+
timeout: flags.timeout ? parseInt(flags.timeout, 10) : undefined,
|
|
713
|
+
noCache: flags["no-cache"] === true,
|
|
714
|
+
};
|
|
715
|
+
// Closure-guarded one-time credit warning (Review Fix 7). The
|
|
716
|
+
// flag is captured INSIDE the attempt closure, so the line is
|
|
717
|
+
// written exactly once — the first time the executor actually
|
|
718
|
+
// visits a candidate. When the executor's preflight rejects
|
|
719
|
+
// every plan entry before invoking `attempt` (capability-
|
|
720
|
+
// mismatch, no candidates configured), the closure is never
|
|
721
|
+
// entered and the warning stays silent. Strict mode
|
|
722
|
+
// (`--no-fallback`) already runs the same preflight on the
|
|
723
|
+
// single remaining candidate so an ineligible effective also
|
|
724
|
+
// stays silent.
|
|
725
|
+
let warned = false;
|
|
726
|
+
const creditWarning = `Research in progress — this is a credit-intensive operation that may take several minutes.\n`;
|
|
727
|
+
const emitCreditWarning = () => {
|
|
728
|
+
if (warned)
|
|
729
|
+
return;
|
|
730
|
+
warned = true;
|
|
731
|
+
deps.invocation.writeStderr(creditWarning);
|
|
732
|
+
};
|
|
733
|
+
const outcome = await executeWithFallback({
|
|
734
|
+
capabilityId: "research",
|
|
735
|
+
commandLabel: "research",
|
|
736
|
+
effectiveProvider: providerId,
|
|
737
|
+
descriptors: deps.providerDescriptors,
|
|
738
|
+
env: deps.env,
|
|
739
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
740
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
741
|
+
}, async (descriptor) => {
|
|
742
|
+
emitCreditWarning();
|
|
743
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
744
|
+
return research(query, options, {
|
|
745
|
+
capability: adapter.research,
|
|
746
|
+
execution: executionDeps,
|
|
747
|
+
registerInterrupt: deps.researchRegisterInterrupt,
|
|
748
|
+
}, context);
|
|
749
|
+
});
|
|
750
|
+
return outcome.result;
|
|
751
|
+
}, outputMode, deps.now, deps.secrets);
|
|
666
752
|
}
|
|
667
753
|
/**
|
|
668
754
|
* Validate a research enum flag (--model, --output-length,
|
|
@@ -725,30 +811,6 @@ async function handleRepo(args, outputMode, deps) {
|
|
|
725
811
|
// never consults credentials (FR-003) and never branches on Provider
|
|
726
812
|
// ID; an unknown explicit/env value throws VALIDATION_ERROR here.
|
|
727
813
|
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
728
|
-
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
729
|
-
// Capability support check BEFORE `descriptor.isConfigured`,
|
|
730
|
-
// `descriptor.create`, Adapter access, operation validation/
|
|
731
|
-
// cacheIdentity, credential use, cache, or transport. Unsupported
|
|
732
|
-
// MiniMax (explicit or environment) returns UNSUPPORTED_CAPABILITY
|
|
733
|
-
// with no fallback to Z.AI; zero selected-Provider work occurs.
|
|
734
|
-
if (!descriptor.capabilities().has("repository-exploration")) {
|
|
735
|
-
throw new UnsupportedCapabilityError(providerId, "repository-exploration");
|
|
736
|
-
}
|
|
737
|
-
// FR-003: selection returns the default zai even when unconfigured.
|
|
738
|
-
// Supported-but-unconfigured Z.AI surfaces a missing credential as
|
|
739
|
-
// ConfigurationError (exit 3) AFTER the support metadata check and
|
|
740
|
-
// BEFORE `descriptor.create`.
|
|
741
|
-
if (!descriptor.isConfigured(deps.env)) {
|
|
742
|
-
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
743
|
-
}
|
|
744
|
-
const adapter = descriptor.create({ env: deps.env });
|
|
745
|
-
const capability = adapter.repository;
|
|
746
|
-
// Defensive fail-closed: the descriptor advertised support but the
|
|
747
|
-
// Adapter omitted the handle. Treat as unsupported so a registry
|
|
748
|
-
// mismatch can never reach transport.
|
|
749
|
-
if (!capability) {
|
|
750
|
-
throw new UnsupportedCapabilityError(providerId, "repository-exploration");
|
|
751
|
-
}
|
|
752
814
|
// Shared Repository execution dependencies. The cache/sleep/random
|
|
753
815
|
// default to the same production values as Search but are kept as
|
|
754
816
|
// separate optional MainDependencies so repository tests can inject
|
|
@@ -758,30 +820,43 @@ async function handleRepo(args, outputMode, deps) {
|
|
|
758
820
|
sleep: deps.repositorySleep,
|
|
759
821
|
random: deps.repositoryRandom,
|
|
760
822
|
};
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
823
|
+
const language = flags.language;
|
|
824
|
+
const maxChars = flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined;
|
|
825
|
+
const noCache = flags["no-cache"] === true;
|
|
826
|
+
const treePath = flags.path;
|
|
827
|
+
const depth = flags.depth ? parseInt(flags.depth, 10) : undefined;
|
|
828
|
+
// Provider-fallback Ticket 02: route every subcommand through the
|
|
829
|
+
// shared executor. All three projections (search/tree/read) share
|
|
830
|
+
// the `repository-exploration` Capability, so a single executor
|
|
831
|
+
// invocation wraps the chosen subcommand.
|
|
832
|
+
return invokeCommand(deps.invocation, async (context) => {
|
|
833
|
+
const outcome = await executeWithFallback({
|
|
834
|
+
capabilityId: "repository-exploration",
|
|
835
|
+
commandLabel: "repo",
|
|
836
|
+
effectiveProvider: providerId,
|
|
837
|
+
descriptors: deps.providerDescriptors,
|
|
838
|
+
env: deps.env,
|
|
839
|
+
fallbackEnabled: deps.fallbackEnabled,
|
|
840
|
+
writeStderr: (s) => deps.invocation.writeStderr(s),
|
|
841
|
+
}, async (descriptor) => {
|
|
842
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
843
|
+
const capability = adapter.repository;
|
|
844
|
+
switch (command) {
|
|
845
|
+
case "search":
|
|
846
|
+
return repoSearch(repo, searchQuery, { language, maxChars, noCache }, { capability, execution: executionDeps }, context);
|
|
847
|
+
case "tree":
|
|
848
|
+
return repoTree(repo, { path: treePath, depth, noCache }, { capability, execution: executionDeps }, context);
|
|
849
|
+
case "read":
|
|
850
|
+
return repoRead(repo, readPath, { maxChars, noCache }, { capability, execution: executionDeps }, context);
|
|
851
|
+
default:
|
|
852
|
+
// Unreachable: the parse-level validation above already
|
|
853
|
+
// rejected unknown subcommands. Keep a defensive throw so
|
|
854
|
+
// the dispatch table stays total.
|
|
855
|
+
throw new ValidationError(`Unknown repo command: ${command}`, 'Run "scoutline repo --help" for available commands');
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
return outcome.result;
|
|
859
|
+
}, outputMode, deps.now, deps.secrets);
|
|
785
860
|
}
|
|
786
861
|
async function handleTools(args, outputMode, deps) {
|
|
787
862
|
const { flags } = parseArgs(args);
|
|
@@ -986,7 +1061,18 @@ export async function main(args, dependencies) {
|
|
|
986
1061
|
// redaction follows the same environment the handlers see — a secret
|
|
987
1062
|
// that exists only in MainDependencies.env is still redacted from output.
|
|
988
1063
|
const secrets = configuredSecrets(env);
|
|
989
|
-
const { outputFormat, forcePretty, forceRaw, provider, rest } = extractGlobalOptions([...args]);
|
|
1064
|
+
const { outputFormat, forcePretty, forceRaw, provider, noFallback, rest } = extractGlobalOptions([...args]);
|
|
1065
|
+
// Provider-fallback kill-switch. The flag and the env are both
|
|
1066
|
+
// opt-outs; either is sufficient to disable the cross-Provider
|
|
1067
|
+
// candidate loop. The flag wins when both are present (the user
|
|
1068
|
+
// is being explicit). `SCOUTLINE_NO_FALLBACK` is read from the
|
|
1069
|
+
// injected `env` (B3) so redaction and executor preflight see
|
|
1070
|
+
// the same environment the user actually configured. Truthy
|
|
1071
|
+
// values include any non-empty string, so `SCOUTLINE_NO_FALLBACK=1`
|
|
1072
|
+
// and `SCOUTLINE_NO_FALLBACK=true` both opt out; an empty string
|
|
1073
|
+
// is the conventional "unset" form.
|
|
1074
|
+
const envDisablesFallback = typeof env.SCOUTLINE_NO_FALLBACK === "string" && env.SCOUTLINE_NO_FALLBACK.length > 0;
|
|
1075
|
+
const fallbackEnabled = !(noFallback || envDisablesFallback);
|
|
990
1076
|
// Fixup C — B10: resolve the output mode BEFORE the dispatch try/catch.
|
|
991
1077
|
// An invalid explicit mode still surfaces as a typed ValidationError,
|
|
992
1078
|
// but the surface formatter uses the user's REQUESTED mode (or the
|
|
@@ -1021,6 +1107,7 @@ export async function main(args, dependencies) {
|
|
|
1021
1107
|
now,
|
|
1022
1108
|
provider,
|
|
1023
1109
|
providerDescriptors,
|
|
1110
|
+
fallbackEnabled,
|
|
1024
1111
|
searchCache,
|
|
1025
1112
|
searchSleep,
|
|
1026
1113
|
searchRandom,
|
|
@@ -1039,6 +1126,7 @@ export async function main(args, dependencies) {
|
|
|
1039
1126
|
researchCache,
|
|
1040
1127
|
researchSleep,
|
|
1041
1128
|
researchRandom,
|
|
1129
|
+
researchRegisterInterrupt: dependencies.researchRegisterInterrupt,
|
|
1042
1130
|
};
|
|
1043
1131
|
try {
|
|
1044
1132
|
switch (command) {
|