@aiwg/cli 2026.10.1 → 2026.10.3
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/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +210 -4
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/driver.js +3 -1
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +21 -3
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +49 -32
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
|
@@ -4,9 +4,21 @@
|
|
|
4
4
|
* Implements `aiwg init` — the first-run wizard that creates
|
|
5
5
|
* `.aiwg/aiwg.config` for the current project.
|
|
6
6
|
*
|
|
7
|
-
* Prompts:
|
|
8
|
-
* 1. Which AI provider toolchains does this project target?
|
|
9
|
-
* 2. Would you like to add
|
|
7
|
+
* Prompts, and the argument that answers each one directly (#3062):
|
|
8
|
+
* 1. Which AI provider toolchains does this project target? → `--providers <list>`
|
|
9
|
+
* 2. Would you like to add default scripts (deploy/doctor/sync)? → `--default-scripts true|false`
|
|
10
|
+
* 3. Add a custom script (name + command)? → `--script <name>=<command>` (repeatable;
|
|
11
|
+
* implies yes) or `--custom-scripts true|false` (answers the question directly
|
|
12
|
+
* without supplying a script; `false` declines it outright)
|
|
13
|
+
* 4. Enable direct worker dispatch? → `--worker-dispatch enable|decline`
|
|
14
|
+
*
|
|
15
|
+
* With a terminal attached, any of these given directly skips that
|
|
16
|
+
* question — only genuinely missing values are still asked. Without one,
|
|
17
|
+
* omitted inputs fall back to their documented defaults (`claude`, default
|
|
18
|
+
* scripts on) rather than erroring, since those defaults are always safe to
|
|
19
|
+
* apply headless. `--non-interactive`/`--yes` force the non-interactive
|
|
20
|
+
* path even with a terminal attached. `--dry-run` never writes anything,
|
|
21
|
+
* including the baseline config itself.
|
|
10
22
|
*
|
|
11
23
|
* Runs automatically on first `aiwg use` when no config exists.
|
|
12
24
|
* Can also be run explicitly to reconfigure.
|
|
@@ -16,10 +28,14 @@
|
|
|
16
28
|
import readline from 'readline';
|
|
17
29
|
import { readAiwgConfig, writeAiwgConfig, emptyConfig, populateDeployedTo, VALID_PROVIDERS, getConfigPath, } from '../../config/aiwg-config.js';
|
|
18
30
|
import * as ui from '../ui.js';
|
|
19
|
-
import { askString as sharedAskString, askYesNo as sharedAskYesNo } from '../prompt-utils.js';
|
|
31
|
+
import { askString as sharedAskString, askYesNo as sharedAskYesNo, askConsent } from '../prompt-utils.js';
|
|
20
32
|
import { normalizeProviderDefinitionId } from '../../providers/provider-definitions.js';
|
|
21
33
|
import { writeNormalizedAiwgMd } from '../../smiths/context-pipeline/finalization.js';
|
|
22
34
|
import { ensureWorkspaceContext } from '../../smiths/context-pipeline/workspace-context.js';
|
|
35
|
+
import { detectInstalledWorkerClis, evaluateWorkerDispatchOffer, workerDispatchOfferQuestion, workerDispatchOfferHintWithArg, parseWorkerDispatchArg, } from '../../models/worker-dispatch-offer.js';
|
|
36
|
+
import { EXIT_CODES } from '../errors.js';
|
|
37
|
+
import { isInteractive } from '../env.js';
|
|
38
|
+
import { requireFlagValue, collectFlagValues, parseBooleanFlagValue, parseListFlagValue, parseKeyValueEntry, MissingFlagValueError, } from '../flag-parsing.js';
|
|
23
39
|
const PROVIDER_LABELS = {
|
|
24
40
|
antigravity: 'Google Antigravity .agents/',
|
|
25
41
|
claude: 'Claude Code .claude/',
|
|
@@ -76,6 +92,89 @@ async function askProviders(rl, signal) {
|
|
|
76
92
|
}
|
|
77
93
|
return selected.length > 0 ? selected : ['claude'];
|
|
78
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Non-interactive / --yes / no-TTY runs never prompt and never touch the
|
|
97
|
+
* config (#3050 item 2) — they only print one line naming the enable
|
|
98
|
+
* command, when two or more worker CLIs are detected and dispatch is not
|
|
99
|
+
* already on or declined. The hint also names `--worker-dispatch` (#3062):
|
|
100
|
+
* that argument answers the same question headlessly.
|
|
101
|
+
*/
|
|
102
|
+
function renderWorkerDispatchHintIfApplicable(parallelism, setup) {
|
|
103
|
+
const offer = evaluateWorkerDispatchOffer(parallelism, setup, detectInstalledWorkerClis());
|
|
104
|
+
if (offer.shouldOffer)
|
|
105
|
+
ui.dim(` ${workerDispatchOfferHintWithArg(offer.detected)}`);
|
|
106
|
+
}
|
|
107
|
+
function parseInitArgInputs(args) {
|
|
108
|
+
// #3062 second review, finding 4: `precedence: 'first'` matches this
|
|
109
|
+
// command's pre-existing (and, for `--providers`/`--default-scripts`/
|
|
110
|
+
// `--script`/`--custom-scripts`, only) convention — see
|
|
111
|
+
// `parseInitArgInputs`'s module-level callers for why `--worker-dispatch`
|
|
112
|
+
// specifically needs `rejectEmpty` (an explicit empty choice is never
|
|
113
|
+
// meaningful and must not silently fall through to "not given").
|
|
114
|
+
const workerDispatchArg = parseWorkerDispatchArg(requireFlagValue(args, { rejectEmpty: true }, '--worker-dispatch'));
|
|
115
|
+
const rawProviders = parseListFlagValue(requireFlagValue(args, {}, '--providers'));
|
|
116
|
+
const providersArg = rawProviders?.map((entry) => {
|
|
117
|
+
const canonical = normalizeProviderDefinitionId(entry);
|
|
118
|
+
if (!canonical || canonical === 'generic' || !VALID_PROVIDERS.includes(canonical)) {
|
|
119
|
+
throw new Error(`--providers: unknown provider '${entry}' (valid: ${VALID_PROVIDERS.join(', ')})`);
|
|
120
|
+
}
|
|
121
|
+
return canonical;
|
|
122
|
+
});
|
|
123
|
+
if (providersArg && providersArg.length === 0) {
|
|
124
|
+
throw new Error('--providers requires at least one provider name');
|
|
125
|
+
}
|
|
126
|
+
const defaultScriptsArg = parseBooleanFlagValue(requireFlagValue(args, {}, '--default-scripts'), '--default-scripts');
|
|
127
|
+
const customScriptsArg = parseBooleanFlagValue(requireFlagValue(args, {}, '--custom-scripts'), '--custom-scripts');
|
|
128
|
+
const scriptPairs = {};
|
|
129
|
+
for (const entry of collectFlagValues(args, {}, '--script')) {
|
|
130
|
+
const [name, command] = parseKeyValueEntry(entry, '--script');
|
|
131
|
+
scriptPairs[name] = command;
|
|
132
|
+
}
|
|
133
|
+
return { workerDispatchArg, providersArg, defaultScriptsArg, customScriptsArg, scriptPairs };
|
|
134
|
+
}
|
|
135
|
+
/** One-line description of what an explicit `--worker-dispatch` choice would write. */
|
|
136
|
+
function describeWorkerDispatchWrite(arg) {
|
|
137
|
+
return arg === 'enable'
|
|
138
|
+
? 'enable parallelism.worker_dispatch'
|
|
139
|
+
: 'record a worker-dispatch decline (setup.worker_dispatch_offer)';
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve an explicit `--worker-dispatch` choice into a decision to apply to
|
|
143
|
+
* a brand-new config (#3062). Under `--dry-run`, writes nothing — reports
|
|
144
|
+
* what would be written instead and returns `undefined` so the caller never
|
|
145
|
+
* applies it to the config it builds.
|
|
146
|
+
*/
|
|
147
|
+
function resolveWorkerDispatchDecisionForNewConfig(arg, dryRun) {
|
|
148
|
+
if (dryRun) {
|
|
149
|
+
ui.dim(` Would ${describeWorkerDispatchWrite(arg)} (--dry-run writes nothing).`);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return { kind: arg };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Interactive-only: asks the one consent question (default/unanswered is
|
|
156
|
+
* no, strict `y`/`yes` parsing only) when two or more worker CLIs are
|
|
157
|
+
* detected and dispatch is not already on or declined. Returns the
|
|
158
|
+
* decision to apply, or `undefined` when there is nothing to offer, or
|
|
159
|
+
* when the prompt timed out / stdin closed without an answer — only an
|
|
160
|
+
* actually-answered "no" is recorded as a persistent decline (#3050
|
|
161
|
+
* review item 4).
|
|
162
|
+
*/
|
|
163
|
+
async function askWorkerDispatchOffer(rl, parallelism, setup, signal) {
|
|
164
|
+
const offer = evaluateWorkerDispatchOffer(parallelism, setup, detectInstalledWorkerClis());
|
|
165
|
+
if (!offer.shouldOffer)
|
|
166
|
+
return undefined;
|
|
167
|
+
console.log('');
|
|
168
|
+
const outcome = await askConsent(rl, workerDispatchOfferQuestion(offer.detected), undefined, signal);
|
|
169
|
+
if (outcome.kind !== 'answered')
|
|
170
|
+
return undefined;
|
|
171
|
+
if (outcome.value) {
|
|
172
|
+
ui.success(' Will enable parallelism.worker_dispatch.');
|
|
173
|
+
return { kind: 'enable' };
|
|
174
|
+
}
|
|
175
|
+
ui.dim(' Noted — enable later with `aiwg config set --project parallelism.worker_dispatch true`.');
|
|
176
|
+
return { kind: 'decline' };
|
|
177
|
+
}
|
|
79
178
|
export const initHandler = {
|
|
80
179
|
id: 'init',
|
|
81
180
|
name: 'Init',
|
|
@@ -84,16 +183,34 @@ export const initHandler = {
|
|
|
84
183
|
aliases: [],
|
|
85
184
|
async execute(ctx) {
|
|
86
185
|
const force = ctx.args.includes('--force');
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
186
|
+
// `--non-interactive`/`--yes` are this command's own explicit flags for
|
|
187
|
+
// the one shared interactive decision (#3062 review item 12) — honored
|
|
188
|
+
// ahead of (and regardless of) the environment-only checks `isInteractive`
|
|
189
|
+
// otherwise applies (TTY, TERM, CI). Downstream code MUST NOT re-derive
|
|
90
190
|
// interactive-ness from process.stdin.isTTY again.
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
!process.stdin.isTTY ||
|
|
94
|
-
!process.stdout.isTTY ||
|
|
95
|
-
process.env['CI'] === 'true';
|
|
191
|
+
const explicitNonInteractive = ctx.args.includes('--non-interactive') || ctx.args.includes('--yes');
|
|
192
|
+
const nonInteractive = !isInteractive(explicitNonInteractive);
|
|
96
193
|
const projectDir = ctx.cwd;
|
|
194
|
+
// `--worker-dispatch enable|decline` answers the worker-dispatch offer
|
|
195
|
+
// directly (#3062), with or without a terminal, and is honored even when
|
|
196
|
+
// fewer than two worker CLIs are detected — an explicit choice overrides
|
|
197
|
+
// the presence-only detection gate. `--providers`/`--default-scripts`/
|
|
198
|
+
// `--script` answer the remaining wizard inputs the same way (review
|
|
199
|
+
// item 10). Every flag is parsed strictly (review item 11): present
|
|
200
|
+
// without a usable value is a usage error, raised here before any
|
|
201
|
+
// preflight check, read, or write.
|
|
202
|
+
let workerDispatchArg;
|
|
203
|
+
let providersArg;
|
|
204
|
+
let defaultScriptsArg;
|
|
205
|
+
let customScriptsArg;
|
|
206
|
+
let scriptPairs;
|
|
207
|
+
try {
|
|
208
|
+
({ workerDispatchArg, providersArg, defaultScriptsArg, customScriptsArg, scriptPairs } = parseInitArgInputs(ctx.args));
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
const message = err instanceof MissingFlagValueError ? `${err.message}.` : err instanceof Error ? err.message : String(err);
|
|
212
|
+
return { exitCode: EXIT_CODES.USAGE, message };
|
|
213
|
+
}
|
|
97
214
|
ui.blank();
|
|
98
215
|
console.log(` ${ui.brandMark()} ${ui.bold('AIWG Baseline Config')}`);
|
|
99
216
|
ui.rule();
|
|
@@ -101,7 +218,10 @@ export const initHandler = {
|
|
|
101
218
|
const existing = await readAiwgConfig(projectDir);
|
|
102
219
|
if (existing && !force) {
|
|
103
220
|
ui.info(`Config already exists: ${getConfigPath(projectDir)}`);
|
|
104
|
-
|
|
221
|
+
// #3062 review item 18: `--dry-run` must write nothing at all, not
|
|
222
|
+
// just skip the worker-dispatch increment below.
|
|
223
|
+
if (!ctx.dryRun)
|
|
224
|
+
await writeNormalizedAiwgMd(projectDir);
|
|
105
225
|
console.log(` Providers: ${existing.providers.join(', ')}`);
|
|
106
226
|
const scriptCount = Object.keys(existing.scripts).length;
|
|
107
227
|
if (scriptCount > 0) {
|
|
@@ -111,22 +231,71 @@ export const initHandler = {
|
|
|
111
231
|
ui.dim(' Ask your AIWG agent to review repo, tracker, delivery, signing, and provider policy.');
|
|
112
232
|
ui.dim(' Agents may use `aiwg setup project --dry-run` as the CLI helper for that review.');
|
|
113
233
|
ui.dim(' Run `aiwg init --force` only when you need to recreate the baseline config.');
|
|
234
|
+
// #3050: guide users to direct worker dispatch from the setup step they
|
|
235
|
+
// already run, instead of leaving it undiscoverable. `--dry-run` must
|
|
236
|
+
// never prompt and never write (review item 1) — it degrades to the
|
|
237
|
+
// same hint-only behavior as non-interactive runs.
|
|
238
|
+
// #3062: an explicit `--worker-dispatch` argument always answers the
|
|
239
|
+
// question directly (bypassing the ask/hint entirely) except under
|
|
240
|
+
// `--dry-run`, which still writes nothing — it reports what would be
|
|
241
|
+
// written instead.
|
|
242
|
+
if (workerDispatchArg) {
|
|
243
|
+
if (ctx.dryRun) {
|
|
244
|
+
ui.dim(` Would ${describeWorkerDispatchWrite(workerDispatchArg)} (--dry-run writes nothing).`);
|
|
245
|
+
}
|
|
246
|
+
else if (workerDispatchArg === 'enable') {
|
|
247
|
+
await writeAiwgConfig(projectDir, { ...existing, parallelism: { ...existing.parallelism, worker_dispatch: true } });
|
|
248
|
+
ui.success(' Enabled parallelism.worker_dispatch.');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
await writeAiwgConfig(projectDir, { ...existing, setup: { ...existing.setup, worker_dispatch_offer: 'declined' } });
|
|
252
|
+
ui.success(' Recorded worker-dispatch decline (setup.worker_dispatch_offer).');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (nonInteractive || ctx.dryRun) {
|
|
256
|
+
renderWorkerDispatchHintIfApplicable(existing.parallelism, existing.setup);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
260
|
+
try {
|
|
261
|
+
const decision = await askWorkerDispatchOffer(rl, existing.parallelism, existing.setup, ctx.signal);
|
|
262
|
+
if (decision?.kind === 'enable') {
|
|
263
|
+
await writeAiwgConfig(projectDir, { ...existing, parallelism: { ...existing.parallelism, worker_dispatch: true } });
|
|
264
|
+
}
|
|
265
|
+
else if (decision?.kind === 'decline') {
|
|
266
|
+
await writeAiwgConfig(projectDir, { ...existing, setup: { ...existing.setup, worker_dispatch_offer: 'declined' } });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
rl.close();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
114
273
|
ui.blank();
|
|
115
274
|
return { exitCode: 0 };
|
|
116
275
|
}
|
|
117
276
|
let providers;
|
|
118
277
|
let scripts = {};
|
|
278
|
+
let workerDispatchDecision;
|
|
279
|
+
const DEFAULT_SCRIPTS = { deploy: 'aiwg use all', doctor: 'aiwg doctor', sync: 'aiwg sync' };
|
|
119
280
|
if (nonInteractive) {
|
|
120
|
-
// Non-interactive:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
281
|
+
// Non-interactive: an explicit `--providers`/`--default-scripts`
|
|
282
|
+
// answers the corresponding wizard input directly (review item 10);
|
|
283
|
+
// anything not supplied keeps its prior default.
|
|
284
|
+
providers = providersArg ?? ['claude'];
|
|
285
|
+
const includeDefaultScripts = defaultScriptsArg ?? true;
|
|
286
|
+
scripts = { ...(includeDefaultScripts ? DEFAULT_SCRIPTS : {}), ...scriptPairs };
|
|
287
|
+
if (workerDispatchArg) {
|
|
288
|
+
workerDispatchDecision = resolveWorkerDispatchDecisionForNewConfig(workerDispatchArg, ctx.dryRun);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
renderWorkerDispatchHintIfApplicable(undefined, undefined);
|
|
292
|
+
}
|
|
127
293
|
}
|
|
128
294
|
else {
|
|
129
|
-
// Interactive wizard
|
|
295
|
+
// Interactive wizard — skip a prompt whenever its argument already
|
|
296
|
+
// answered it (review item 10/12: "with a terminal we can skip steps
|
|
297
|
+
// for any params provided and only provide interaction when we are
|
|
298
|
+
// missing a value").
|
|
130
299
|
const rl = readline.createInterface({
|
|
131
300
|
input: process.stdin,
|
|
132
301
|
output: process.stdout,
|
|
@@ -134,16 +303,29 @@ export const initHandler = {
|
|
|
134
303
|
try {
|
|
135
304
|
// Thread ctx.signal through every prompt so Ctrl-C during the wizard
|
|
136
305
|
// cancels cleanly instead of being absorbed by readline (POC #926).
|
|
137
|
-
providers = await askProviders(rl, ctx.signal);
|
|
306
|
+
providers = providersArg ?? await askProviders(rl, ctx.signal);
|
|
138
307
|
console.log('');
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
scripts
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
308
|
+
const includeDefaultScripts = defaultScriptsArg !== undefined
|
|
309
|
+
? defaultScriptsArg
|
|
310
|
+
: await askYesNo(rl, ' Add default scripts (deploy, doctor, sync)? [Y/n]: ', true, ctx.signal);
|
|
311
|
+
scripts = includeDefaultScripts ? { ...DEFAULT_SCRIPTS } : {};
|
|
312
|
+
if (Object.keys(scriptPairs).length > 0) {
|
|
313
|
+
// `--script` already answered "add a custom script?" (implicit
|
|
314
|
+
// yes) and its name/command sub-prompts — never ask any of the
|
|
315
|
+
// three (#3062 second review, finding 5a).
|
|
316
|
+
scripts = { ...scripts, ...scriptPairs };
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// `--custom-scripts true|false` answers "add a custom script?"
|
|
320
|
+
// directly when `--script` itself was not given — an explicit
|
|
321
|
+
// decline (`false`) skips the question entirely, same as every
|
|
322
|
+
// other already-supplied input; `true` still asks for the
|
|
323
|
+
// name/command, since those were not supplied either.
|
|
324
|
+
const addCustom = customScriptsArg !== undefined
|
|
325
|
+
? customScriptsArg
|
|
326
|
+
: includeDefaultScripts
|
|
327
|
+
? await askYesNo(rl, ' Add a custom script? [y/N]: ', false, ctx.signal)
|
|
328
|
+
: false;
|
|
147
329
|
if (addCustom) {
|
|
148
330
|
const name = await askString(rl, ' Script name: ', '', ctx.signal);
|
|
149
331
|
const cmd = await askString(rl, ' Command: ', '', ctx.signal);
|
|
@@ -152,6 +334,21 @@ export const initHandler = {
|
|
|
152
334
|
}
|
|
153
335
|
}
|
|
154
336
|
}
|
|
337
|
+
// #3050: offer direct worker dispatch inside the same wizard — but
|
|
338
|
+
// `--dry-run` must never prompt or write anything from the offer
|
|
339
|
+
// (review item 1), so it degrades to the hint instead. An explicit
|
|
340
|
+
// `--worker-dispatch` argument (#3062) always answers the question
|
|
341
|
+
// directly instead of asking or hinting, still subject to `--dry-run`
|
|
342
|
+
// writing nothing.
|
|
343
|
+
if (workerDispatchArg) {
|
|
344
|
+
workerDispatchDecision = resolveWorkerDispatchDecisionForNewConfig(workerDispatchArg, ctx.dryRun);
|
|
345
|
+
}
|
|
346
|
+
else if (ctx.dryRun) {
|
|
347
|
+
renderWorkerDispatchHintIfApplicable(undefined, undefined);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
workerDispatchDecision = await askWorkerDispatchOffer(rl, undefined, undefined, ctx.signal);
|
|
351
|
+
}
|
|
155
352
|
}
|
|
156
353
|
finally {
|
|
157
354
|
rl.close();
|
|
@@ -160,6 +357,24 @@ export const initHandler = {
|
|
|
160
357
|
// Build config
|
|
161
358
|
let config = emptyConfig(providers);
|
|
162
359
|
config.scripts = scripts;
|
|
360
|
+
if (workerDispatchDecision?.kind === 'enable') {
|
|
361
|
+
config.parallelism = { ...config.parallelism, worker_dispatch: true };
|
|
362
|
+
}
|
|
363
|
+
else if (workerDispatchDecision?.kind === 'decline') {
|
|
364
|
+
config.setup = { ...config.setup, worker_dispatch_offer: 'declined' };
|
|
365
|
+
}
|
|
366
|
+
// #3062 review item 18: `--dry-run` writes nothing at all — not even
|
|
367
|
+
// the baseline config — rather than only skipping the worker-dispatch
|
|
368
|
+
// increment. Report what would be written instead.
|
|
369
|
+
if (ctx.dryRun) {
|
|
370
|
+
ui.blank();
|
|
371
|
+
ui.dim(` Would create ${getConfigPath(projectDir)} (--dry-run writes nothing).`);
|
|
372
|
+
ui.dim(` Providers: ${providers.join(', ')}`);
|
|
373
|
+
if (Object.keys(scripts).length > 0)
|
|
374
|
+
ui.dim(` Scripts: ${Object.keys(scripts).join(', ')}`);
|
|
375
|
+
ui.blank();
|
|
376
|
+
return { exitCode: 0 };
|
|
377
|
+
}
|
|
163
378
|
// Populate deployedTo from disk for entries with empty deployedTo (#721)
|
|
164
379
|
config = await populateDeployedTo(config, projectDir);
|
|
165
380
|
// Write
|
|
@@ -15,6 +15,7 @@ import * as ui from '../ui.js';
|
|
|
15
15
|
import { promises as fs } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import { createHash, randomBytes } from 'node:crypto';
|
|
18
|
+
import { assertMissionWorkerHandoff } from '../../mission-protocol/codecs.js';
|
|
18
19
|
// ── Constants ────────────────────────────────────────────────
|
|
19
20
|
const MC_ROOT = '.aiwg/ralph-external/mc';
|
|
20
21
|
const SESSIONS_DIR = join(MC_ROOT, 'sessions');
|
|
@@ -378,6 +379,18 @@ async function mcDispatch(ctx) {
|
|
|
378
379
|
ui.error(sessionId ? `Session not found: ${sessionId}` : 'No active session. Run `aiwg mc start` first.');
|
|
379
380
|
return { exitCode: 1 };
|
|
380
381
|
}
|
|
382
|
+
// mission-dispatch accepts only the frozen objective envelope. Worker
|
|
383
|
+
// bindings can arrive through its existing session, so inspect that payload.
|
|
384
|
+
try {
|
|
385
|
+
assertMissionWorkerHandoff(selected, 'mc-cli');
|
|
386
|
+
for (const mission of selected.missions)
|
|
387
|
+
assertMissionWorkerHandoff(mission, 'mc-cli');
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
391
|
+
ui.error(message);
|
|
392
|
+
return { exitCode: 1, message };
|
|
393
|
+
}
|
|
381
394
|
// #1361: Check project-level parallelism cap. Active missions = running or
|
|
382
395
|
// queued; if at cap, warn but still queue (FIFO behavior — the mission goes
|
|
383
396
|
// into the session as 'queued' and will run when a slot frees up).
|
|
@@ -560,6 +573,16 @@ async function mcRun(ctx) {
|
|
|
560
573
|
return { exitCode: 1 };
|
|
561
574
|
}
|
|
562
575
|
const queued = session.missions.filter((m) => m.status === 'queued');
|
|
576
|
+
try {
|
|
577
|
+
assertMissionWorkerHandoff(session, 'mc-cli');
|
|
578
|
+
for (const mission of queued)
|
|
579
|
+
assertMissionWorkerHandoff(mission, 'mc-cli');
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
583
|
+
ui.error(message);
|
|
584
|
+
return { exitCode: 1, message };
|
|
585
|
+
}
|
|
563
586
|
if (queued.length === 0) {
|
|
564
587
|
ui.info(`No queued missions in session ${session.id}. Run \`aiwg mc dispatch ${session.id} "<objective>"\` to add one.`);
|
|
565
588
|
return { exitCode: 0 };
|
|
@@ -7,13 +7,63 @@ import os from 'node:os';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { glob } from 'glob';
|
|
9
9
|
import { load as loadYaml } from 'js-yaml';
|
|
10
|
-
import { compileModelPolicy, validateCanonicalModelPolicy, validateUserProjectModelConfig, } from '../../models/provider-policy.js';
|
|
10
|
+
import { compileModelPolicy, validateCanonicalModelPolicy, validateUserProjectModelConfig, loadProviderModelCapabilities, WorkerPolicyError, } from '../../models/provider-policy.js';
|
|
11
11
|
import { resolveGrokBuildRoleModel } from '../../models/grok-build-models.js';
|
|
12
|
+
import { readAiwgConfig, emptyConfig } from '../../config/aiwg-config.js';
|
|
13
|
+
import { ConfigLoader } from '../../models/config-loader.js';
|
|
14
|
+
import { loadRecordedWorkerInstallations } from '../../models/worker-installation.mjs';
|
|
15
|
+
import { parseWorkerCommandArgs, resolveWorkerInvocation, workerInvocationDiagnostic } from '../../serve/worker-invocation.js';
|
|
16
|
+
/**
|
|
17
|
+
* #2892: exported so the MCP worker-invoke bridge can resolve a direct worker
|
|
18
|
+
* plan in-process (same snapshot/driver construction as `aiwg models resolve`),
|
|
19
|
+
* without shelling out to the CLI. No new loader is introduced; this is the
|
|
20
|
+
* CLI's own existing default `ModelsWorkerServices.load`.
|
|
21
|
+
*/
|
|
22
|
+
export async function loadWorkerInputs(ctx) {
|
|
23
|
+
const config = await readAiwgConfig(ctx.cwd) ?? emptyConfig();
|
|
24
|
+
const loader = new ConfigLoader(ctx.cwd);
|
|
25
|
+
const models = await loader.load(config.parallelism);
|
|
26
|
+
const catalog = await effectiveCatalog(ctx, ctx.cwd, false, parseArgs(['resolve']));
|
|
27
|
+
const registry = loadProviderModelCapabilities();
|
|
28
|
+
// Let the resolver explain the dispatch gate before requiring host qualification (#3070).
|
|
29
|
+
if (!config.parallelism?.worker_dispatch) {
|
|
30
|
+
return { snapshot: { config, models, modelDigests: loader.getInputDigests(), catalog,
|
|
31
|
+
registry, installations: {} }, drivers: {} };
|
|
32
|
+
}
|
|
33
|
+
const { registry: qualifiedRegistry, installations, drivers } = await loadRecordedWorkerInstallations({
|
|
34
|
+
registry, cwd: ctx.cwd, policy: models.defaults.workerSelection,
|
|
35
|
+
});
|
|
36
|
+
return { snapshot: { config, models, modelDigests: loader.getInputDigests(), catalog,
|
|
37
|
+
registry: qualifiedRegistry, installations }, drivers };
|
|
38
|
+
}
|
|
39
|
+
async function resolveDirect(ctx, services) {
|
|
40
|
+
let shell = false;
|
|
41
|
+
try {
|
|
42
|
+
const request = parseWorkerCommandArgs(ctx.args.slice(1));
|
|
43
|
+
shell = request.format === 'shell';
|
|
44
|
+
const { snapshot, drivers } = await services.load(ctx);
|
|
45
|
+
const plan = await resolveWorkerInvocation(snapshot, request, drivers, services.now?.());
|
|
46
|
+
const runnable = plan.invocation?.readiness === 'launchable';
|
|
47
|
+
if (shell && !runnable)
|
|
48
|
+
console.error(JSON.stringify({ status: 'blocked', diagnostics: plan.diagnostics }, null, 2));
|
|
49
|
+
else if (shell && plan.invocation?.transport === 'shell')
|
|
50
|
+
print(plan.invocation.shellCommand, false);
|
|
51
|
+
else
|
|
52
|
+
print(plan, true);
|
|
53
|
+
return { exitCode: runnable ? 0 : 2 };
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const diagnostic = { status: 'blocked', diagnostics: [workerInvocationDiagnostic(error, error instanceof WorkerPolicyError
|
|
57
|
+
&& error.code === 'OG_ADAPTER_UNQUALIFIED' ? '/parallelism/clis' : '/request')] };
|
|
58
|
+
if (shell)
|
|
59
|
+
console.error(JSON.stringify(diagnostic, null, 2));
|
|
60
|
+
else
|
|
61
|
+
print(diagnostic, true);
|
|
62
|
+
return { exitCode: 2 };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
12
65
|
const TIERS = new Set(['economy', 'standard', 'premium', 'max-quality']);
|
|
13
|
-
const PROVIDERS = new Set(
|
|
14
|
-
'antigravity', 'claude', 'codex', 'copilot', 'cursor', 'deepseek-harness', 'factory', 'grok-build', 'grokbot', 'hermes',
|
|
15
|
-
'opencode', 'openclaw', 'openhuman', 'omp', 'pi', 'warp', 'windsurf',
|
|
16
|
-
]);
|
|
66
|
+
const PROVIDERS = new Set(Object.keys(loadProviderModelCapabilities().providers));
|
|
17
67
|
function parseArgs(args) {
|
|
18
68
|
const [subcommand = 'help', ...rest] = args;
|
|
19
69
|
const flags = new Map();
|
|
@@ -222,8 +272,18 @@ function replaceFrontmatter(raw, item, tier) {
|
|
|
222
272
|
}
|
|
223
273
|
return `---\n${frontmatter}\n---\n${match[2]}`;
|
|
224
274
|
}
|
|
225
|
-
async function execute(ctx) {
|
|
275
|
+
async function execute(ctx, services = { load: loadWorkerInputs }) {
|
|
276
|
+
// Preserve artifact/native resolve when direct-only flags are absent.
|
|
277
|
+
if (ctx.args[0] === 'resolve' && ctx.args.slice(1).some(arg => /^--(?:task|group|brief|workdir|final-output|transport|format)(?:=|$)/.test(arg))) {
|
|
278
|
+
return resolveDirect(ctx, services);
|
|
279
|
+
}
|
|
226
280
|
const parsed = parseArgs(ctx.args);
|
|
281
|
+
if (parsed.subcommand === 'resolve') {
|
|
282
|
+
const supported = new Set(['agent', 'skill', 'glob', 'role', 'tier', 'framework', 'all', 'target', 'provider', 'model', 'json', 'help', 'dry-run']);
|
|
283
|
+
for (const key of parsed.flags.keys())
|
|
284
|
+
if (!supported.has(key))
|
|
285
|
+
return { exitCode: 2, message: `Unsupported models resolve flag: --${key}` };
|
|
286
|
+
}
|
|
227
287
|
const json = parsed.flags.has('json');
|
|
228
288
|
const dryRun = parsed.flags.has('dry-run') || ctx.dryRun === true;
|
|
229
289
|
const target = path.resolve(flagString(parsed, 'target') ?? ctx.cwd);
|
|
@@ -380,4 +440,7 @@ export const modelsHandler = {
|
|
|
380
440
|
aliases: [],
|
|
381
441
|
execute,
|
|
382
442
|
};
|
|
443
|
+
export function createModelsHandler(services) {
|
|
444
|
+
return { ...modelsHandler, execute: ctx => execute(ctx, services) };
|
|
445
|
+
}
|
|
383
446
|
//# sourceMappingURL=models.js.map
|
|
@@ -336,6 +336,9 @@ export class RalphStatusHandler {
|
|
|
336
336
|
category = 'ralph';
|
|
337
337
|
aliases = ['agent-loop-status', 'ralph-status'];
|
|
338
338
|
async execute(ctx) {
|
|
339
|
+
if (ctx.args.includes('--workers')) {
|
|
340
|
+
return createScriptRunner(ctx.frameworkRoot).run('tools/ralph-external/index.mjs', ['--status', ...ctx.args]);
|
|
341
|
+
}
|
|
339
342
|
const showAll = ctx.args.includes('--all') || ctx.args.includes('-a');
|
|
340
343
|
const statuses = getLoopStatuses(process.cwd());
|
|
341
344
|
if (statuses.length === 0) {
|
|
@@ -24,6 +24,7 @@ import { refreshAllPackages } from '../../packages/registry.js';
|
|
|
24
24
|
import { resolveActiveProvider } from '../provider-resolution.js';
|
|
25
25
|
import { readAiwgConfig, writeAiwgConfig, hashManifest, getProviderParallelismDefaults, } from '../../config/aiwg-config.js';
|
|
26
26
|
import { discoverProjectLocalBundles } from '../../extensions/project-local-discovery.js';
|
|
27
|
+
import { detectInstalledWorkerClis, evaluateWorkerDispatchOffer, workerDispatchOfferHint } from '../../models/worker-dispatch-offer.js';
|
|
27
28
|
import { getProviderArtifactPathStrings } from '../../providers/provider-definitions.js';
|
|
28
29
|
import { collectPackagedAgentInventory, normalizeAgentArtifactName, parseManagedArtifactMarker, } from '../../agents/packaged-agent-inventory.js';
|
|
29
30
|
import * as ui from '../ui.js';
|
|
@@ -786,6 +787,21 @@ export const refreshHandler = {
|
|
|
786
787
|
// Non-fatal — refresh continues even if migration fails
|
|
787
788
|
}
|
|
788
789
|
}
|
|
790
|
+
// Step 4.65: Direct worker dispatch opt-in hint (#3050). Refresh never
|
|
791
|
+
// prompts; this only names the enable command when dispatch is off and
|
|
792
|
+
// two or more worker CLIs are detected (presence-only PATH scan, no
|
|
793
|
+
// spawn). Silent otherwise, including after a previous decline.
|
|
794
|
+
if (!quiet) {
|
|
795
|
+
try {
|
|
796
|
+
const config = await readAiwgConfig(process.cwd());
|
|
797
|
+
const offer = evaluateWorkerDispatchOffer(config?.parallelism, config?.setup, detectInstalledWorkerClis());
|
|
798
|
+
if (offer.shouldOffer)
|
|
799
|
+
ui.info(workerDispatchOfferHint(offer.detected));
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
// Non-fatal — refresh continues even if the hint check fails
|
|
803
|
+
}
|
|
804
|
+
}
|
|
789
805
|
// Step 5: Health check
|
|
790
806
|
if (!quiet)
|
|
791
807
|
ui.info(dryRun ? 'Would run health check...' : 'Running health check...');
|
|
@@ -150,6 +150,20 @@ async function handleRegenerate(args, cwd) {
|
|
|
150
150
|
console.log(` Selected: ${selection.explicit ? 'explicit' : 'inferred'} — ${selection.reason}`);
|
|
151
151
|
if (selection.evidence.length > 0)
|
|
152
152
|
console.log(` Evidence: ${selection.evidence.join(', ')}`);
|
|
153
|
+
// #3050: regenerate never prompts. Point at direct worker dispatch when
|
|
154
|
+
// it is off and two or more worker CLIs are detected (presence-only PATH
|
|
155
|
+
// scan, no spawn); silent otherwise, including after a decline.
|
|
156
|
+
try {
|
|
157
|
+
const { readAiwgConfig } = await import('../../config/aiwg-config.js');
|
|
158
|
+
const { detectInstalledWorkerClis, evaluateWorkerDispatchOffer, workerDispatchOfferHint } = await import('../../models/worker-dispatch-offer.js');
|
|
159
|
+
const dispatchConfig = await readAiwgConfig(target);
|
|
160
|
+
const offer = evaluateWorkerDispatchOffer(dispatchConfig?.parallelism, dispatchConfig?.setup, detectInstalledWorkerClis());
|
|
161
|
+
if (offer.shouldOffer)
|
|
162
|
+
console.log(` ${workerDispatchOfferHint(offer.detected)}`);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Non-fatal — this is an informational nudge, never a regenerate gate.
|
|
166
|
+
}
|
|
153
167
|
if (existingProject) {
|
|
154
168
|
const preflight = await migrateWorkspaceContext(target, {
|
|
155
169
|
dryRun: true,
|
|
@@ -12,6 +12,7 @@ import path from 'path';
|
|
|
12
12
|
import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js';
|
|
13
13
|
import { getPackageRoot } from '../../channel/manager.mjs';
|
|
14
14
|
import { inspectInstallation } from '../../installation/manager.mjs';
|
|
15
|
+
import { isSessionProviderResolution, resolveActiveProvider } from '../provider-resolution.js';
|
|
15
16
|
function isMissingRuntimeCatalogError(error) {
|
|
16
17
|
return error instanceof Error && error.message.includes('No catalog found');
|
|
17
18
|
}
|
|
@@ -304,21 +305,35 @@ async function handleRuntimeInfo(args, cwd = process.cwd()) {
|
|
|
304
305
|
console.log(` Run mode: ${installation.identity?.runMode ?? '(unrecorded)'}`);
|
|
305
306
|
console.log(` State: ${installation.state}`);
|
|
306
307
|
// Scheduler backend detection
|
|
307
|
-
const {
|
|
308
|
+
const { boundedExecFileSync } = await import('../../process/bounded-child-process.mjs');
|
|
308
309
|
let schedulerBackend = 'external trigger required (system cron/systemd/CI)';
|
|
309
310
|
let chronyInstalled = false;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
311
|
+
for (const tool of ['chronyc', 'chronyd']) {
|
|
312
|
+
try {
|
|
313
|
+
boundedExecFileSync('which', [tool], 'quick', { stdio: ['pipe', 'pipe', 'ignore'] });
|
|
314
|
+
chronyInstalled = true;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Try the next chrony binary.
|
|
319
|
+
}
|
|
316
320
|
}
|
|
317
321
|
// Native CronCreate is only available when running inside a Claude Code agent context.
|
|
318
322
|
// The CLI itself cannot call agent tools, so we report based on platform heuristics.
|
|
319
323
|
// When invoked from within an agent, the schedule skill performs live detection.
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
//
|
|
325
|
+
// Resolved through the same shared provider-resolution path `steward
|
|
326
|
+
// capabilities` uses (`resolveActiveProvider` -> the `claude` provider's
|
|
327
|
+
// `detection.env` markers in provider-definitions.ts) so the two never
|
|
328
|
+
// disagree about which provider the current session is running under
|
|
329
|
+
// (#2623, #2627). The ad-hoc `CLAUDE_CODE_VERSION`/`ANTHROPIC_API_KEY`
|
|
330
|
+
// check this replaced was stale (neither is set by current Claude Code)
|
|
331
|
+
// and `ANTHROPIC_API_KEY` was a false positive for any non-Claude
|
|
332
|
+
// session with that key set.
|
|
333
|
+
const activeProviderResolution = await resolveActiveProvider({ cwd });
|
|
334
|
+
// Only a session signal counts: a project merely configured or deployed
|
|
335
|
+
// for Claude is not a Claude Code agent context.
|
|
336
|
+
const isClaudeCodeContext = activeProviderResolution.provider === 'claude' && isSessionProviderResolution(activeProviderResolution);
|
|
322
337
|
if (isClaudeCodeContext) {
|
|
323
338
|
schedulerBackend = 'native-cron (CronCreate); external trigger outside agent sessions';
|
|
324
339
|
}
|