@aiwg/cli 2026.10.9 → 2026.10.11
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/model-capabilities.v1.json +275 -17
- package/bin/aiwg.mjs +8 -0
- package/dist/src/cli/handlers/mc-admission.js +39 -0
- package/dist/src/cli/handlers/mc.js +2 -31
- package/dist/src/cli/handlers/models-help.js +52 -0
- package/dist/src/cli/handlers/models.js +11 -81
- package/dist/src/cli/handlers/sessions.js +198 -15
- package/dist/src/cli/handlers/use-addon-profile.js +72 -0
- package/dist/src/cli/handlers/use.js +203 -234
- package/dist/src/cli/ui.js +1 -0
- package/dist/src/config/aiwg-config.js +38 -184
- package/dist/src/config/cli-help.js +54 -0
- package/dist/src/config/cli.js +5 -69
- package/dist/src/config/config-normalization.js +10 -0
- package/dist/src/config/worker-activation-gate.js +26 -0
- package/dist/src/config/worker-adoption-cli.js +102 -0
- package/dist/src/config/worker-adoption.js +368 -0
- package/dist/src/config/worker-config-digest.js +14 -0
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/config/worker-group-templates.js +150 -0
- package/dist/src/config/worker-guidance-warning.js +28 -0
- package/dist/src/config/worker-owner-lock.js +152 -0
- package/dist/src/config/worker-parallelism.js +154 -0
- package/dist/src/config/worker-write.js +75 -0
- package/dist/src/decision/telemetry/exporter.js +2 -1
- package/dist/src/decision/telemetry/index.js +1 -0
- package/dist/src/decision/telemetry/mapping.js +43 -0
- package/dist/src/decision/telemetry/otlp-http.js +5 -2
- package/dist/src/decision/telemetry/redaction.js +6 -1
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/telemetry/worker.js +69 -0
- package/dist/src/lint/rule-activation.js +15 -7
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/server.mjs +2 -11
- package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
- package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
- package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
- package/dist/src/mission-protocol/codecs.js +8 -38
- package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
- package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
- package/dist/src/models/conductor-data-policy.d.mts +8 -0
- package/dist/src/models/conductor-data-policy.mjs +190 -0
- package/dist/src/models/config-loader.js +19 -3
- package/dist/src/models/config-writer.js +27 -0
- package/dist/src/models/model-capabilities.v1.json +275 -17
- package/dist/src/models/provider-policy.js +14 -155
- package/dist/src/models/resolver.js +2 -293
- package/dist/src/models/worker-artifact-binding.mjs +12 -0
- package/dist/src/models/worker-direct-readiness-policy.js +155 -0
- package/dist/src/models/worker-direct-readiness.js +9 -218
- package/dist/src/models/worker-health-readiness.js +105 -26
- package/dist/src/models/worker-installation.mjs +12 -14
- package/dist/src/models/worker-member-references.js +64 -0
- package/dist/src/models/worker-plan.js +317 -0
- package/dist/src/models/worker-selection-policy.js +217 -0
- package/dist/src/models/wrapper-deployment.js +1 -1
- package/dist/src/policy/authorization.js +29 -1
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/serve/shared-host-admission-checks.js +116 -0
- package/dist/src/serve/shared-host-scheduler.js +25 -107
- package/dist/src/serve/shared-host-starvation.js +73 -0
- package/dist/src/serve/worker-artifacts.d.mts +4 -0
- package/dist/src/serve/worker-artifacts.mjs +56 -0
- package/dist/src/serve/worker-command-request.js +68 -0
- package/dist/src/serve/worker-invocation-paths.js +144 -0
- package/dist/src/serve/worker-invocation-preparation.js +169 -0
- package/dist/src/serve/worker-invocation.js +24 -291
- package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
- package/dist/src/serve/worker-stdin-frame.mjs +6 -6
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/batch-import.js +34 -6
- package/dist/src/sessions/contracts.js +19 -0
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +23 -3
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/workspace-discovery.js +5 -2
- package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
- package/package.json +15 -1
- package/schemas/mission-v1.schema.json +20 -19
- package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
- package/tools/agents/providers/claude-post-deploy.mjs +35 -0
- package/tools/agents/providers/claude.mjs +3 -30
- package/tools/agents/providers/codex-post-deploy.mjs +13 -0
- package/tools/agents/providers/codex.mjs +2 -8
- package/tools/agents/providers/conductor-data-guard.mjs +61 -0
- package/tools/agents/providers/rule-delivery.mjs +3 -16
- package/tools/agents/providers/rule-presentation.mjs +38 -0
- package/tools/plugin/package-plugins.mjs +1 -1
- package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
- package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
- package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
- package/tools/ralph-external/lib/session-attempt.mjs +85 -0
- package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
- package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-final.mjs +143 -0
- package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
- package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
- package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +124 -313
- package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { normalizeAiwgConfigCandidate } from './config-normalization.js';
|
|
2
|
+
import { atomicConfigWrite, withWorkerWriteLock } from './worker-write.js';
|
|
3
|
+
import { workerConfigDigest } from './worker-config-digest.js';
|
|
4
|
+
export { workerConfigDigest } from './worker-config-digest.js';
|
|
5
|
+
import { validateParallelismConfig } from './worker-parallelism.js';
|
|
6
|
+
export { validateParallelismConfig, validateWorkerHealthConfig, resolveWorkerGroupInstallation } from './worker-parallelism.js';
|
|
7
|
+
import { expandWorkerGroupTemplates } from './worker-group-templates.js';
|
|
1
8
|
import { validateDecisionConfig } from './decision-config.js';
|
|
2
9
|
/**
|
|
3
10
|
* Project-Level AIWG Config
|
|
@@ -10,20 +17,15 @@ import { validateDecisionConfig } from './decision-config.js';
|
|
|
10
17
|
* @implements #621
|
|
11
18
|
*/
|
|
12
19
|
import { validateImportExclusions } from '../sessions/import-exclusions.js';
|
|
13
|
-
import { resolveDispatchHostThresholds } from './dispatch-thresholds-runtime.mjs';
|
|
14
20
|
export { DEFAULT_DISPATCH_HOST_THRESHOLDS, resolveDispatchHostThresholds } from './dispatch-thresholds-runtime.mjs';
|
|
15
21
|
import { validateIndexGraphOverrides } from './index-graph-overrides.js';
|
|
16
22
|
import { DEFAULT_RULES_INLINE_BUDGET_TOKENS } from '../rules/inline-budget.mjs';
|
|
17
23
|
export { validateRulesConfig, resolveRulesInlineBudget } from '../rules/inline-budget.mjs';
|
|
18
24
|
import { loadRulePinConfigs, installedRuleCapabilities, validateRulePinCapabilities } from '../rules/pins.mjs';
|
|
19
|
-
import Ajv from 'ajv';
|
|
20
|
-
import addFormats from 'ajv-formats';
|
|
21
25
|
import { readFileSync, existsSync } from 'node:fs';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { createHash, randomBytes } from 'crypto';
|
|
26
|
-
import { resolve, join, dirname, isAbsolute } from 'path';
|
|
26
|
+
import { readFile, access, readdir } from 'fs/promises';
|
|
27
|
+
import { createHash } from 'crypto';
|
|
28
|
+
import { resolve, join, isAbsolute } from 'path';
|
|
27
29
|
import { normalizeNamedCaptures } from '../artifacts/index-builder.js';
|
|
28
30
|
import { getProviderDefinition, getProviderKernelSkillPath, PROVIDER_IDS, resolveProviderPathValue, } from '../providers/provider-definitions.js';
|
|
29
31
|
import { validateAuthorization, } from '../policy/authorization.js';
|
|
@@ -175,142 +177,22 @@ export function resolveDelivery(delivery) {
|
|
|
175
177
|
issue_comment_on_cycle: delivery?.issue_comment_on_cycle ?? true,
|
|
176
178
|
};
|
|
177
179
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (!parallelismValidator(value))
|
|
194
|
-
return (parallelismValidator.errors ?? []).map(error => `OG_CONFIG_REFERENCE /parallelism${error.instancePath}: ${error.message}`);
|
|
195
|
-
const policy = value;
|
|
196
|
-
const errors = [];
|
|
197
|
-
const error = (pointer, message) => { errors.push(`OG_CONFIG_REFERENCE /parallelism/${pointer}: ${message}`); };
|
|
198
|
-
const builtinIds = new Set(PROVIDER_IDS.filter(id => id !== 'generic'));
|
|
199
|
-
for (const [id, installation] of Object.entries(policy.clis ?? {})) {
|
|
200
|
-
if (installation.adapter.startsWith('providers:') && !builtinIds.has(installation.adapter.slice(10)))
|
|
201
|
-
error(`clis/${id}/adapter`, 'Unknown provider binding');
|
|
202
|
-
}
|
|
203
|
-
for (const [id, group] of Object.entries(policy.groups ?? {})) {
|
|
204
|
-
if (group.execution?.launchMode === 'native-sandbox'
|
|
205
|
-
&& (!['read-only', 'workspace-write'].includes(group.execution.sandbox ?? '')
|
|
206
|
-
|| (group.execution.transport ?? 'shell') !== 'shell')) {
|
|
207
|
-
error(`groups/${id}/execution/launchMode`, 'native-sandbox requires an enforcing read-only or workspace-write shell sandbox');
|
|
208
|
-
}
|
|
209
|
-
if (group.execution?.launchMode === 'native-sandbox') {
|
|
210
|
-
if (group.execution.approval === 'on-request')
|
|
211
|
-
error(`groups/${id}/execution/approval`, 'native-sandbox requires approval="never"');
|
|
212
|
-
if (group.execution.network === 'allowlisted')
|
|
213
|
-
error(`groups/${id}/execution/network`, 'native-sandbox requires network="deny"');
|
|
214
|
-
}
|
|
215
|
-
const ids = new Set();
|
|
216
|
-
for (const member of group.members) {
|
|
217
|
-
if (ids.has(member.id))
|
|
218
|
-
error(`groups/${id}/members`, 'Duplicate member ID');
|
|
219
|
-
ids.add(member.id);
|
|
220
|
-
if (!policy.clis?.[member.cli] && !builtinIds.has(member.cli))
|
|
221
|
-
error(`groups/${id}/members/${member.id}/cli`, 'Unknown installation');
|
|
222
|
-
if (group.execution?.launchMode === 'native-sandbox'
|
|
223
|
-
&& (policy.clis?.[member.cli]?.adapter ?? `providers:${member.cli}`) !== 'providers:codex') {
|
|
224
|
-
error(`groups/${id}/members/${member.id}/cli`, 'native-sandbox requires a codex adapter');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
for (const member of group.fallback ?? [])
|
|
228
|
-
if (!ids.has(member))
|
|
229
|
-
error(`groups/${id}/fallback`, 'Unknown fallback member');
|
|
230
|
-
for (const role of group.requiredRoles ?? []) {
|
|
231
|
-
if (!group.members.some(m => m.enabled !== false && policy.clis?.[m.cli]?.enabled !== false && m.roles.includes(role)))
|
|
232
|
-
error(`groups/${id}/requiredRoles`, `No enabled member covers ${role}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const admission = policy.admission;
|
|
236
|
-
for (const group of Object.keys(admission?.groupCaps ?? {}))
|
|
237
|
-
if (!policy.groups?.[group])
|
|
238
|
-
error('admission/groupCaps', 'Unknown group');
|
|
239
|
-
for (const cli of Object.keys(admission?.cliCaps ?? {}))
|
|
240
|
-
if (!policy.clis?.[cli] && !builtinIds.has(cli))
|
|
241
|
-
error('admission/cliCaps', 'Unknown installation');
|
|
242
|
-
for (const key of Object.keys(admission?.memberCaps ?? {})) {
|
|
243
|
-
const [group, member, extra] = key.split('/');
|
|
244
|
-
if (extra !== undefined || !policy.groups?.[group]?.members.some(m => m.id === member))
|
|
245
|
-
error('admission/memberCaps', 'Unknown group/member');
|
|
246
|
-
}
|
|
247
|
-
for (const key of Object.keys(admission?.modelCaps ?? {}))
|
|
248
|
-
if (!isCanonicalWorkerModelKey(key))
|
|
249
|
-
error('admission/modelCaps', 'Noncanonical inference model key');
|
|
250
|
-
if (admission?.hostResources !== undefined) {
|
|
251
|
-
try {
|
|
252
|
-
resolveDispatchHostThresholds(undefined, admission.hostResources);
|
|
253
|
-
}
|
|
254
|
-
catch (validation) {
|
|
255
|
-
error('admission/hostResources', validation instanceof Error ? validation.message : String(validation));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
errors.push(...validateWorkerHealthConfig(policy.health));
|
|
259
|
-
return errors;
|
|
260
|
-
}
|
|
261
|
-
/** Cross-field health preferences are validated offline; controls are qualified at dispatch. */
|
|
262
|
-
export function validateWorkerHealthConfig(health) {
|
|
263
|
-
if (health === undefined)
|
|
264
|
-
return [];
|
|
265
|
-
const errors = [];
|
|
266
|
-
const error = (field, message) => { errors.push(`OG_CONFIG_REFERENCE /parallelism/health/${field}: ${message}`); };
|
|
267
|
-
const checkSeconds = health.checkSeconds ?? 15;
|
|
268
|
-
if (checkSeconds > (health.startupSeconds ?? 60))
|
|
269
|
-
error('checkSeconds', 'Must not exceed startupSeconds');
|
|
270
|
-
if ((health.quietSeconds ?? 90) >= (health.stallSeconds ?? 300))
|
|
271
|
-
error('quietSeconds', 'Must be less than stallSeconds');
|
|
272
|
-
if ((health.cooldownSeconds ?? 120) > (health.maxCooldownSeconds ?? 900))
|
|
273
|
-
error('cooldownSeconds', 'Must not exceed maxCooldownSeconds');
|
|
274
|
-
for (const role of Object.keys(health.roleScale ?? {})) {
|
|
275
|
-
if (!['efficiency', 'coding', 'reasoning'].includes(role))
|
|
276
|
-
error('roleScale', `Unknown role ${role}`);
|
|
277
|
-
}
|
|
278
|
-
for (const field of ['modelScale', 'modelStartupSeconds']) {
|
|
279
|
-
for (const [key, value] of Object.entries(health[field] ?? {})) {
|
|
280
|
-
if (!isCanonicalWorkerModelKey(key))
|
|
281
|
-
error(field, 'Noncanonical inference model key');
|
|
282
|
-
if (field === 'modelStartupSeconds' && checkSeconds > value)
|
|
283
|
-
error(field, 'Model startup must cover checkSeconds');
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return errors;
|
|
287
|
-
}
|
|
288
|
-
/** Resolve one enabled installation through trusted metadata, without probes.
|
|
289
|
-
* Implicit canonical IDs inherit current-account auth; no account/quota identity
|
|
290
|
-
* is invented. Profile qualification remains the invocation owner's obligation.
|
|
180
|
+
/**
|
|
181
|
+
* Provider-scoped parallelism cap — limits how many concurrent subagents,
|
|
182
|
+
* Ralph loops, and Mission Control missions agents may spawn. Designed to
|
|
183
|
+
* keep AIWG within the rate-limit envelope of the underlying model provider
|
|
184
|
+
* (Anthropic per-key TPM/RPM caps are the most-reported trigger).
|
|
185
|
+
*
|
|
186
|
+
* Composes with (effective limit = MIN of):
|
|
187
|
+
* - `parallelism.max_parallel_subagents` (this config)
|
|
188
|
+
* - `context-budget` rule's `AIWG_CONTEXT_WINDOW`-derived cap, if set
|
|
189
|
+
* - `rlm-context-management` Rule 8's 7-agent hard cap (RLM dispatches only)
|
|
190
|
+
* - The natural task decomposition (no point spawning 4 when only 2 subtasks exist)
|
|
191
|
+
*
|
|
192
|
+
* Every field is optional. Defaults applied via {@link resolveParallelism}.
|
|
193
|
+
*
|
|
194
|
+
* @implements #1359
|
|
291
195
|
*/
|
|
292
|
-
export function resolveWorkerGroupInstallation(parallelism, installationId, registry = loadProviderModelCapabilities()) {
|
|
293
|
-
const errors = validateParallelismConfig(parallelism);
|
|
294
|
-
if (errors.length)
|
|
295
|
-
throw new Error(errors.join('; '));
|
|
296
|
-
if (!parallelism.worker_dispatch)
|
|
297
|
-
throw new Error('OG_CONFIG_REFERENCE /parallelism/worker_dispatch: Worker dispatch is disabled');
|
|
298
|
-
const explicit = parallelism.clis?.[installationId];
|
|
299
|
-
const installation = explicit
|
|
300
|
-
? structuredClone(explicit)
|
|
301
|
-
: { adapter: `providers:${installationId}`, enabled: true };
|
|
302
|
-
if (!explicit && (!PROVIDER_IDS.includes(installationId) || installationId === 'generic')) {
|
|
303
|
-
throw new Error(`OG_CONFIG_REFERENCE /parallelism/clis/${installationId}: Unknown canonical installation`);
|
|
304
|
-
}
|
|
305
|
-
if (installation.enabled === false)
|
|
306
|
-
throw new Error(`OG_CONFIG_REFERENCE /parallelism/clis/${installationId}: Installation is disabled`);
|
|
307
|
-
try {
|
|
308
|
-
return { installation: { ...installation, enabled: true }, ...resolveWorkerInstallation(registry, installation) };
|
|
309
|
-
}
|
|
310
|
-
catch (error) {
|
|
311
|
-
throw new Error(`OG_ADAPTER_UNQUALIFIED /parallelism/clis/${installationId}: ${error}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
196
|
/** Validate current model restrictions and group references before any write.
|
|
315
197
|
* Disabled/unbound projects retain their existing config-only edit behavior.
|
|
316
198
|
* This is shared validation, not Tier 1 coherent activation.
|
|
@@ -329,17 +211,6 @@ export async function validateWorkerConfigCandidate(projectDir, config, options)
|
|
|
329
211
|
const { ConfigLoader } = await import('../models/config-loader.js');
|
|
330
212
|
await new ConfigLoader(projectDir, options).load(config.parallelism);
|
|
331
213
|
}
|
|
332
|
-
/** Hash canonical JSON, treating absent optional model files as null. */
|
|
333
|
-
export function workerConfigDigest(value) {
|
|
334
|
-
const canonical = (node) => {
|
|
335
|
-
if (Array.isArray(node))
|
|
336
|
-
return node.map(canonical);
|
|
337
|
-
if (node !== null && typeof node === 'object')
|
|
338
|
-
return Object.fromEntries(Object.keys(node).sort().filter(key => node[key] !== undefined).map(key => [key, canonical(node[key])]));
|
|
339
|
-
return node ?? null;
|
|
340
|
-
};
|
|
341
|
-
return `sha256:${createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')}`;
|
|
342
|
-
}
|
|
343
214
|
export function workerActivationDigests(config, projectModels, userModels, builtinModels) {
|
|
344
215
|
const candidate = structuredClone(config);
|
|
345
216
|
if (candidate.parallelism)
|
|
@@ -415,6 +286,10 @@ export function getProviderParallelismDefaults(provider) {
|
|
|
415
286
|
* @implements #1359
|
|
416
287
|
*/
|
|
417
288
|
export function resolveParallelism(parallelism, primaryProvider) {
|
|
289
|
+
const expansion = expandWorkerGroupTemplates(parallelism);
|
|
290
|
+
if (expansion.errors.length)
|
|
291
|
+
throw new Error(expansion.errors.join('; '));
|
|
292
|
+
parallelism = expansion.parallelism;
|
|
418
293
|
const defaults = getProviderParallelismDefaults(primaryProvider);
|
|
419
294
|
const resolved = {
|
|
420
295
|
max_parallel_subagents: parallelism?.max_parallel_subagents ?? defaults.max_parallel_subagents,
|
|
@@ -441,7 +316,7 @@ export function resolveParallelism(parallelism, primaryProvider) {
|
|
|
441
316
|
for (const cli of Object.values(resolved.clis ?? {}))
|
|
442
317
|
cli.enabled ??= true;
|
|
443
318
|
if (resolved.admission)
|
|
444
|
-
resolved.admission = { maxQueued: 128, queueTimeoutSeconds: 300, leaseSeconds: 60, authorityRef: 'project', ...resolved.admission };
|
|
319
|
+
resolved.admission = { maxQueued: 128, queueTimeoutSeconds: 300, leaseSeconds: 60, starvationSeconds: 120, authorityRef: 'project', ...resolved.admission };
|
|
445
320
|
if (resolved.budgets)
|
|
446
321
|
resolved.budgets = { enforcement: 'observed', unknownCost: 'deny', ...resolved.budgets };
|
|
447
322
|
if (resolved.retry)
|
|
@@ -1071,12 +946,7 @@ export async function readAiwgConfigCandidate(projectDir) {
|
|
|
1071
946
|
const content = await readFile(filePath, 'utf-8');
|
|
1072
947
|
const parsed = JSON.parse(content);
|
|
1073
948
|
// Ensure required fields exist (forward-compat)
|
|
1074
|
-
|
|
1075
|
-
parsed.providers = ['claude'];
|
|
1076
|
-
if (!parsed.installed)
|
|
1077
|
-
parsed.installed = {};
|
|
1078
|
-
if (!parsed.scripts)
|
|
1079
|
-
parsed.scripts = {};
|
|
949
|
+
normalizeAiwgConfigCandidate(parsed);
|
|
1080
950
|
if (parsed.sessions !== undefined && (parsed.sessions === null || typeof parsed.sessions !== 'object' || Array.isArray(parsed.sessions)
|
|
1081
951
|
|| (parsed.sessions.codex_roots !== undefined && (!Array.isArray(parsed.sessions.codex_roots)
|
|
1082
952
|
|| !parsed.sessions.codex_roots.every((root) => typeof root === 'string' && root.trim().length > 0))))) {
|
|
@@ -1164,6 +1034,9 @@ export async function readAiwgConfig(projectDir, options) {
|
|
|
1164
1034
|
* sync so split-root health remains deterministic.
|
|
1165
1035
|
*/
|
|
1166
1036
|
export async function writeAiwgConfig(projectDir, config) {
|
|
1037
|
+
return withWorkerWriteLock(projectDir, () => writeAiwgConfigUnlocked(projectDir, config));
|
|
1038
|
+
}
|
|
1039
|
+
async function writeAiwgConfigUnlocked(projectDir, config) {
|
|
1167
1040
|
// Uninstall is an explicit capability change. Retire project selectors for
|
|
1168
1041
|
// removed capabilities before strict validation, and tell the operator.
|
|
1169
1042
|
const previousFile = getConfigPath(projectDir);
|
|
@@ -1222,28 +1095,7 @@ export async function writeAiwgConfig(projectDir, config) {
|
|
|
1222
1095
|
const artifactDir = resolveProjectAiwgDir(projectDir);
|
|
1223
1096
|
const artifactPath = join(artifactDir, CONFIG_FILENAME);
|
|
1224
1097
|
const content = JSON.stringify(config, null, 2) + '\n';
|
|
1225
|
-
const writeAtomic =
|
|
1226
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
1227
|
-
// Atomic write: emit to a temp sibling, fsync-ish via rename. Prevents a
|
|
1228
|
-
// crash or kill mid-write from corrupting the config file. Rename is
|
|
1229
|
-
// atomic on POSIX and on NTFS when both paths are on the same volume.
|
|
1230
|
-
// The random suffix avoids collisions if two concurrent writers run.
|
|
1231
|
-
const tmpPath = `${filePath}.${randomBytes(6).toString('hex')}.tmp`;
|
|
1232
|
-
try {
|
|
1233
|
-
await writeFile(tmpPath, content, 'utf-8');
|
|
1234
|
-
await rename(tmpPath, filePath);
|
|
1235
|
-
}
|
|
1236
|
-
catch (err) {
|
|
1237
|
-
// Best-effort cleanup of the temp file on failure, ignoring ENOENT.
|
|
1238
|
-
try {
|
|
1239
|
-
await unlink(tmpPath);
|
|
1240
|
-
}
|
|
1241
|
-
catch {
|
|
1242
|
-
/* ignore */
|
|
1243
|
-
}
|
|
1244
|
-
throw err;
|
|
1245
|
-
}
|
|
1246
|
-
};
|
|
1098
|
+
const writeAtomic = (file) => atomicConfigWrite(file, content);
|
|
1247
1099
|
await writeAtomic(localPath);
|
|
1248
1100
|
if (artifactPath !== localPath) {
|
|
1249
1101
|
try {
|
|
@@ -1454,6 +1306,7 @@ export function validateWorkerGroupReferences(parallelism, selection, qualifiedR
|
|
|
1454
1306
|
const errors = validateParallelismConfig(parallelism);
|
|
1455
1307
|
if (errors.length)
|
|
1456
1308
|
return errors;
|
|
1309
|
+
parallelism = expandWorkerGroupTemplates(parallelism).parallelism;
|
|
1457
1310
|
for (const [role, guidance] of Object.entries(selection.roleGuidance ?? {})) {
|
|
1458
1311
|
for (const group of guidance.groups)
|
|
1459
1312
|
if (!parallelism.groups?.[group])
|
|
@@ -1467,9 +1320,10 @@ export function validateWorkerGroupReferences(parallelism, selection, qualifiedR
|
|
|
1467
1320
|
/** Structured qualification gaps retain role scope for readiness projections. */
|
|
1468
1321
|
export function validateWorkerGroupQualifications(parallelism, qualifiedRoles) {
|
|
1469
1322
|
const diagnostics = [];
|
|
1323
|
+
parallelism = expandWorkerGroupTemplates(parallelism).parallelism;
|
|
1470
1324
|
for (const [id, group] of Object.entries(parallelism.groups ?? {})) {
|
|
1471
1325
|
for (const role of group.requiredRoles ?? []) {
|
|
1472
|
-
if (!group.members.some(m => m.enabled !== false && parallelism.clis?.[m.cli]?.enabled !== false
|
|
1326
|
+
if (!(group.members ?? []).some(m => m.enabled !== false && parallelism.clis?.[m.cli]?.enabled !== false
|
|
1473
1327
|
&& m.roles.includes(role) && qualifiedRoles[`${id}/${m.id}`]?.includes(role))) {
|
|
1474
1328
|
diagnostics.push({ code: 'OG_ADAPTER_UNQUALIFIED', pointer: `/parallelism/groups/${id}/requiredRoles`,
|
|
1475
1329
|
message: `No qualified route covers ${role}`, roles: [role] });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Documents the existing config commands and reviewed worker maintenance flags.
|
|
2
|
+
export function printUsage() {
|
|
3
|
+
console.log(`Usage: aiwg config <subcommand> [options]
|
|
4
|
+
|
|
5
|
+
Subcommands:
|
|
6
|
+
get <key> Read a user config value
|
|
7
|
+
get --project <key> Read a project config value or merged defaults.workerSelection
|
|
8
|
+
[--origins] Include model policy sources, pointers and input digests
|
|
9
|
+
set <key> <value> Write a user config value
|
|
10
|
+
set --project <key> <value> Write a project config value (validates enums)
|
|
11
|
+
list Show all user config
|
|
12
|
+
show --project Show resolved project config and artifact root
|
|
13
|
+
validate Validate user config files
|
|
14
|
+
validate --project Validate resolved project config taxonomy/index semantics
|
|
15
|
+
[--provider gitea|github|local] [--available-label NAME ...]
|
|
16
|
+
reset [<key>] Reset key or all config to defaults
|
|
17
|
+
path Print config directory path
|
|
18
|
+
edit Open config in $EDITOR/$VISUAL (requires a terminal)
|
|
19
|
+
edit --print-path Print the config file path instead of opening an editor
|
|
20
|
+
gitignore Show/check/fix .gitignore AIWG entries
|
|
21
|
+
|
|
22
|
+
Reviewed worker policy pair (project scope, no prompts):
|
|
23
|
+
set --project --parallelism FILE --worker-selection FILE --propose FILE
|
|
24
|
+
set --project --apply FILE --authorize DIGEST
|
|
25
|
+
set --project --apply FILE --standing-authorization FILE
|
|
26
|
+
set --project --recover --authorize DIGEST
|
|
27
|
+
--target DIRECTORY selects the project. Proposal inputs are JSON files.
|
|
28
|
+
|
|
29
|
+
Global flags:
|
|
30
|
+
--config-dir <path> Override config directory
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
aiwg config get defaults.provider
|
|
34
|
+
aiwg config set defaults.verbosity quiet
|
|
35
|
+
aiwg config set updates.channel next
|
|
36
|
+
aiwg config list
|
|
37
|
+
aiwg config show --project
|
|
38
|
+
aiwg config show --project --json
|
|
39
|
+
aiwg config get --project delivery.mode
|
|
40
|
+
aiwg config set --project delivery.mode pr-required
|
|
41
|
+
aiwg config set --project delivery.merge_style squash
|
|
42
|
+
aiwg config get --project parallelism
|
|
43
|
+
aiwg config get --project defaults.workerSelection --origins
|
|
44
|
+
aiwg config set --project parallelism.max_parallel_subagents 6
|
|
45
|
+
aiwg config reset --project parallelism
|
|
46
|
+
aiwg config validate
|
|
47
|
+
aiwg config path
|
|
48
|
+
aiwg config reset defaults.provider
|
|
49
|
+
aiwg config --config-dir /custom/path list
|
|
50
|
+
aiwg config gitignore
|
|
51
|
+
aiwg config gitignore --fix
|
|
52
|
+
aiwg config gitignore --check`);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=cli-help.js.map
|
package/dist/src/config/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { printUsage } from './cli-help.js';
|
|
1
2
|
/**
|
|
2
3
|
* Config CLI — Subcommand router for `aiwg config`
|
|
3
4
|
*
|
|
@@ -19,8 +20,8 @@
|
|
|
19
20
|
*/
|
|
20
21
|
import { fileURLToPath } from 'url';
|
|
21
22
|
import path from 'path';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
23
|
+
import { warnStaleParallelism } from './worker-guidance-warning.js';
|
|
24
|
+
import { handleWorkerAdoptionArgs } from './worker-adoption-cli.js';
|
|
24
25
|
import { UserConfig } from './user-config.js';
|
|
25
26
|
import { AiwgError, EXIT_CODES } from '../cli/errors.js';
|
|
26
27
|
import { isInteractive } from '../cli/env.js';
|
|
@@ -28,32 +29,12 @@ import { splitParams } from '../cli/agent-spawn.js';
|
|
|
28
29
|
import { exitCodeForSpawnResult } from '../cli/child-process-result.js';
|
|
29
30
|
import { projectControlPath, resolveProjectAiwgDir } from './project-artifacts.js';
|
|
30
31
|
const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
function warnStaleParallelism(cfg, projectDir, key, before) {
|
|
32
|
-
if (key !== 'parallelism' && !key.startsWith('parallelism.'))
|
|
33
|
-
return;
|
|
34
|
-
if (isDeepStrictEqual(before, getDottedPath(cfg, key)))
|
|
35
|
-
return;
|
|
36
|
-
const configured = (cfg.providers ?? []).map(normalizeProviderDefinitionId).filter((id) => !!id);
|
|
37
|
-
const recorded = Object.values(cfg.installed ?? {}).flatMap(entry => Object.keys(entry.deployedTo ?? {}))
|
|
38
|
-
.map(normalizeProviderDefinitionId).filter((id) => !!id).sort();
|
|
39
|
-
const providers = [...new Set([...configured, ...recorded])];
|
|
40
|
-
const targets = providers.length ? providers : ['<provider>'];
|
|
41
|
-
const lines = [
|
|
42
|
-
`WARNING: ${key} changed project configuration; generated workspace/provider guidance may now be stale.`,
|
|
43
|
-
`From project root ${path.resolve(projectDir)}, preview and refresh every configured or recorded provider context:`,
|
|
44
|
-
...targets.flatMap(provider => [
|
|
45
|
-
` aiwg regenerate --workspace --provider ${provider} --dry-run`,
|
|
46
|
-
` aiwg regenerate --workspace --provider ${provider}`,
|
|
47
|
-
]),
|
|
48
|
-
' aiwg workspace-context doctor',
|
|
49
|
-
`Shared parallelism defaults and labels use primary provider '${cfg.providers?.[0] ?? 'unknown'}' (providers[0]); --provider selects the adapter/context target.`,
|
|
50
|
-
];
|
|
51
|
-
console.error(lines.join('\n'));
|
|
52
|
-
}
|
|
53
32
|
/**
|
|
54
33
|
* Main CLI entry point for `aiwg config <subcommand> [args]`
|
|
55
34
|
*/
|
|
56
35
|
export async function main(args) {
|
|
36
|
+
if (await handleWorkerAdoptionArgs(args, process.cwd(), 'config'))
|
|
37
|
+
return;
|
|
57
38
|
// Extract --config-dir flag before routing
|
|
58
39
|
let configDir;
|
|
59
40
|
const filteredArgs = [];
|
|
@@ -910,51 +891,6 @@ async function handleGitignore(args) {
|
|
|
910
891
|
process.exit(result.status ?? 1);
|
|
911
892
|
}
|
|
912
893
|
}
|
|
913
|
-
function printUsage() {
|
|
914
|
-
console.log(`Usage: aiwg config <subcommand> [options]
|
|
915
|
-
|
|
916
|
-
Subcommands:
|
|
917
|
-
get <key> Read a user config value
|
|
918
|
-
get --project <key> Read a project config value or merged defaults.workerSelection
|
|
919
|
-
[--origins] Include model policy sources, pointers and input digests
|
|
920
|
-
set <key> <value> Write a user config value
|
|
921
|
-
set --project <key> <value> Write a project config value (validates enums)
|
|
922
|
-
list Show all user config
|
|
923
|
-
show --project Show resolved project config and artifact root
|
|
924
|
-
validate Validate user config files
|
|
925
|
-
validate --project Validate resolved project config taxonomy/index semantics
|
|
926
|
-
[--provider gitea|github|local] [--available-label NAME ...]
|
|
927
|
-
reset [<key>] Reset key or all config to defaults
|
|
928
|
-
path Print config directory path
|
|
929
|
-
edit Open config in $EDITOR/$VISUAL (requires a terminal)
|
|
930
|
-
edit --print-path Print the config file path instead of opening an editor
|
|
931
|
-
gitignore Show/check/fix .gitignore AIWG entries
|
|
932
|
-
|
|
933
|
-
Global flags:
|
|
934
|
-
--config-dir <path> Override config directory
|
|
935
|
-
|
|
936
|
-
Examples:
|
|
937
|
-
aiwg config get defaults.provider
|
|
938
|
-
aiwg config set defaults.verbosity quiet
|
|
939
|
-
aiwg config set updates.channel next
|
|
940
|
-
aiwg config list
|
|
941
|
-
aiwg config show --project
|
|
942
|
-
aiwg config show --project --json
|
|
943
|
-
aiwg config get --project delivery.mode
|
|
944
|
-
aiwg config set --project delivery.mode pr-required
|
|
945
|
-
aiwg config set --project delivery.merge_style squash
|
|
946
|
-
aiwg config get --project parallelism
|
|
947
|
-
aiwg config get --project defaults.workerSelection --origins
|
|
948
|
-
aiwg config set --project parallelism.max_parallel_subagents 6
|
|
949
|
-
aiwg config reset --project parallelism
|
|
950
|
-
aiwg config validate
|
|
951
|
-
aiwg config path
|
|
952
|
-
aiwg config reset defaults.provider
|
|
953
|
-
aiwg config --config-dir /custom/path list
|
|
954
|
-
aiwg config gitignore
|
|
955
|
-
aiwg config gitignore --fix
|
|
956
|
-
aiwg config gitignore --check`);
|
|
957
|
-
}
|
|
958
894
|
/** Same configuration owner and validation used by the rules command wrappers. */
|
|
959
895
|
export async function setRuleConfigKey(cwd, source, key, raw, userConfigDir) {
|
|
960
896
|
assertSafeConfigPath(key);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function normalizeAiwgConfigCandidate(parsed) {
|
|
2
|
+
if (!parsed.providers)
|
|
3
|
+
parsed.providers = ['claude'];
|
|
4
|
+
if (!parsed.installed)
|
|
5
|
+
parsed.installed = {};
|
|
6
|
+
if (!parsed.scripts)
|
|
7
|
+
parsed.scripts = {};
|
|
8
|
+
return parsed;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=config-normalization.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Rechecks the persistent apply block at managed admission, including already-created schedulers.
|
|
2
|
+
import { normalizeAiwgConfigCandidate } from './config-normalization.js';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { getConfigPath, workerActivationDigests, workerConfigDigest } from './aiwg-config.js';
|
|
5
|
+
import { workerDispatchBlocked } from './worker-write.js';
|
|
6
|
+
export function assertWorkerActivationCurrent(project, expected) {
|
|
7
|
+
if (workerDispatchBlocked(project))
|
|
8
|
+
throw new Error('Managed dispatch is closed for worker policy apply/recovery');
|
|
9
|
+
if (expected) {
|
|
10
|
+
let raw;
|
|
11
|
+
try {
|
|
12
|
+
raw = readFileSync(getConfigPath(project), 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// Fail closed: an admission bound to an activation cannot be verified without the project config.
|
|
16
|
+
if (error.code === 'ENOENT')
|
|
17
|
+
throw new Error(`Managed activation cannot be verified: ${getConfigPath(project)} is missing`);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
const config = normalizeAiwgConfigCandidate(JSON.parse(raw));
|
|
21
|
+
if (workerConfigDigest(config.parallelism?.activation) !== workerConfigDigest(expected)
|
|
22
|
+
|| workerActivationDigests(config).configDigest !== expected.configDigest)
|
|
23
|
+
throw new Error('Managed activation changed; reload the current config and admission binding');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=worker-activation-gate.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Adds headless proposal/review/apply flags to the existing config and models setters.
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { atomicConfigWrite, workerWriteLock } from './worker-write.js';
|
|
5
|
+
export async function handleWorkerAdoptionArgs(args, cwd, owner, signal, dryRun = false) {
|
|
6
|
+
args = args.flatMap(arg => {
|
|
7
|
+
const equals = arg.startsWith('--') ? arg.indexOf('=') : -1;
|
|
8
|
+
return equals > 0 ? [arg.slice(0, equals), arg.slice(equals + 1)] : [arg];
|
|
9
|
+
});
|
|
10
|
+
if (!args.some(arg => ['--propose', '--apply', '--authorize', '--standing-authorization', '--parallelism', '--worker-selection', '--recover'].includes(arg)))
|
|
11
|
+
return false;
|
|
12
|
+
if (dryRun)
|
|
13
|
+
throw new Error('Reviewed maintenance cannot write in dry-run mode; use --propose explicitly to create a review');
|
|
14
|
+
if (!['set', 'set-default'].includes(args[0]) || (owner === 'config' && args[0] !== 'set'))
|
|
15
|
+
throw new Error('Reviewed adoption flags are supported only on existing config/models setters; refresh cannot authorize or apply preferences');
|
|
16
|
+
const { applyWorkerAdoption, proposeWorkerAdoption, recoverWorkerAdoption } = await import('./worker-adoption.js');
|
|
17
|
+
const flags = new Map();
|
|
18
|
+
const positionals = [];
|
|
19
|
+
const values = new Set(['--target', '--propose', '--apply', '--authorize', '--standing-authorization', '--parallelism', '--worker-selection']);
|
|
20
|
+
for (let i = 1; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (values.has(arg)) {
|
|
23
|
+
if (!args[i + 1] || args[i + 1].startsWith('--') || flags.has(arg))
|
|
24
|
+
throw new Error(`A single value is required for ${arg}`);
|
|
25
|
+
flags.set(arg, args[++i]);
|
|
26
|
+
}
|
|
27
|
+
else if (arg === '--project' || arg === '--json' || arg === '--recover')
|
|
28
|
+
continue;
|
|
29
|
+
else if (arg.startsWith('--'))
|
|
30
|
+
throw new Error(`Unsupported reviewed adoption flag: ${arg}`);
|
|
31
|
+
else
|
|
32
|
+
positionals.push(arg);
|
|
33
|
+
}
|
|
34
|
+
const project = resolve(flags.get('--target') ?? cwd);
|
|
35
|
+
const readJson = async (file) => JSON.parse(await readFile(resolve(cwd, file), 'utf8'));
|
|
36
|
+
if (args.includes('--recover')) {
|
|
37
|
+
if (flags.has('--apply') || flags.has('--propose') || flags.has('--parallelism') || flags.has('--worker-selection') || positionals.length)
|
|
38
|
+
throw new Error('Recovery accepts only authorization flags');
|
|
39
|
+
if (flags.has('--authorize') === flags.has('--standing-authorization'))
|
|
40
|
+
throw new Error('Recovery requires exactly one authorization');
|
|
41
|
+
const authority = flags.has('--authorize') ? { digest: flags.get('--authorize') } : await readJson(flags.get('--standing-authorization'));
|
|
42
|
+
await recoverWorkerAdoption(project, authority, { signal });
|
|
43
|
+
console.log('Previous validated policy pair restored; guidance regenerated.');
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (flags.has('--apply')) {
|
|
47
|
+
if (flags.has('--propose') || flags.has('--parallelism') || flags.has('--worker-selection') || positionals.length)
|
|
48
|
+
throw new Error('Apply accepts only a reviewed proposal and authorization');
|
|
49
|
+
if (flags.has('--authorize') === flags.has('--standing-authorization'))
|
|
50
|
+
throw new Error('Supply exactly one of --authorize DIGEST or --standing-authorization FILE');
|
|
51
|
+
const proposal = await readJson(flags.get('--apply'));
|
|
52
|
+
const authority = flags.has('--authorize') ? { digest: flags.get('--authorize') } : await readJson(flags.get('--standing-authorization'));
|
|
53
|
+
await applyWorkerAdoption(project, proposal, authority, { signal });
|
|
54
|
+
console.log(JSON.stringify({ applied: proposal.digest, revision: proposal.after.parallelism?.worker_mode === 'managed' ? proposal.baseRevision + 1 : null }));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (!flags.has('--propose') || flags.has('--authorize') || flags.has('--standing-authorization'))
|
|
58
|
+
throw new Error('Use --propose FILE to review first, then --apply FILE with separate authorization');
|
|
59
|
+
const changes = {};
|
|
60
|
+
if (flags.has('--parallelism'))
|
|
61
|
+
changes.parallelism = await readJson(flags.get('--parallelism'));
|
|
62
|
+
if (flags.has('--worker-selection'))
|
|
63
|
+
changes.workerSelection = await readJson(flags.get('--worker-selection'));
|
|
64
|
+
if (positionals.length) {
|
|
65
|
+
const [key, raw] = positionals;
|
|
66
|
+
const parts = key.split('.');
|
|
67
|
+
if (owner !== 'config' || parts[0] !== 'parallelism' || positionals.length !== 2 || flags.has('--parallelism')
|
|
68
|
+
|| parts.some(part => !part || ['__proto__', 'prototype', 'constructor', 'activation'].includes(part)))
|
|
69
|
+
throw new Error('Reviewed edits support safe parallelism fields; model preferences use --worker-selection FILE');
|
|
70
|
+
let value;
|
|
71
|
+
try {
|
|
72
|
+
value = JSON.parse(raw);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
value = raw;
|
|
76
|
+
}
|
|
77
|
+
if (parts.length === 1)
|
|
78
|
+
changes.parallelism = value;
|
|
79
|
+
else {
|
|
80
|
+
const base = await proposeWorkerAdoption(project, {});
|
|
81
|
+
const parallelism = structuredClone(base.before.parallelism ?? {});
|
|
82
|
+
let node = parallelism;
|
|
83
|
+
for (const part of parts.slice(1, -1)) {
|
|
84
|
+
if (!Object.hasOwn(node, part) || !node[part] || typeof node[part] !== 'object')
|
|
85
|
+
node[part] = {};
|
|
86
|
+
node = node[part];
|
|
87
|
+
}
|
|
88
|
+
node[parts.at(-1)] = value;
|
|
89
|
+
changes.parallelism = parallelism;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!Object.keys(changes).length)
|
|
93
|
+
throw new Error('Proposal requires --parallelism FILE or --worker-selection FILE');
|
|
94
|
+
const proposal = await proposeWorkerAdoption(project, changes, { signal });
|
|
95
|
+
const file = resolve(cwd, flags.get('--propose'));
|
|
96
|
+
if (proposal.inputs.some(input => input.file === file) || file.startsWith(workerWriteLock(project)))
|
|
97
|
+
throw new Error('Proposal output must be separate from activation inputs and recovery files');
|
|
98
|
+
await atomicConfigWrite(file, JSON.stringify(proposal, null, 2) + '\n');
|
|
99
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=worker-adoption-cli.js.map
|