@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.
Files changed (116) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +275 -17
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/sessions.js +198 -15
  8. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  9. package/dist/src/cli/handlers/use.js +203 -234
  10. package/dist/src/cli/ui.js +1 -0
  11. package/dist/src/config/aiwg-config.js +38 -184
  12. package/dist/src/config/cli-help.js +54 -0
  13. package/dist/src/config/cli.js +5 -69
  14. package/dist/src/config/config-normalization.js +10 -0
  15. package/dist/src/config/worker-activation-gate.js +26 -0
  16. package/dist/src/config/worker-adoption-cli.js +102 -0
  17. package/dist/src/config/worker-adoption.js +368 -0
  18. package/dist/src/config/worker-config-digest.js +14 -0
  19. package/dist/src/config/worker-context.js +337 -0
  20. package/dist/src/config/worker-group-templates.js +150 -0
  21. package/dist/src/config/worker-guidance-warning.js +28 -0
  22. package/dist/src/config/worker-owner-lock.js +152 -0
  23. package/dist/src/config/worker-parallelism.js +154 -0
  24. package/dist/src/config/worker-write.js +75 -0
  25. package/dist/src/decision/telemetry/exporter.js +2 -1
  26. package/dist/src/decision/telemetry/index.js +1 -0
  27. package/dist/src/decision/telemetry/mapping.js +43 -0
  28. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  29. package/dist/src/decision/telemetry/redaction.js +6 -1
  30. package/dist/src/decision/telemetry/types.js +3 -0
  31. package/dist/src/decision/telemetry/worker.js +69 -0
  32. package/dist/src/lint/rule-activation.js +15 -7
  33. package/dist/src/lint/runner.js +2 -2
  34. package/dist/src/mcp/server.mjs +2 -11
  35. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  36. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  37. package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
  38. package/dist/src/mission-protocol/codecs.js +8 -38
  39. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  40. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  41. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  42. package/dist/src/models/conductor-data-policy.mjs +190 -0
  43. package/dist/src/models/config-loader.js +19 -3
  44. package/dist/src/models/config-writer.js +27 -0
  45. package/dist/src/models/model-capabilities.v1.json +275 -17
  46. package/dist/src/models/provider-policy.js +14 -155
  47. package/dist/src/models/resolver.js +2 -293
  48. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  49. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  50. package/dist/src/models/worker-direct-readiness.js +9 -218
  51. package/dist/src/models/worker-health-readiness.js +105 -26
  52. package/dist/src/models/worker-installation.mjs +12 -14
  53. package/dist/src/models/worker-member-references.js +64 -0
  54. package/dist/src/models/worker-plan.js +317 -0
  55. package/dist/src/models/worker-selection-policy.js +217 -0
  56. package/dist/src/models/wrapper-deployment.js +1 -1
  57. package/dist/src/policy/authorization.js +29 -1
  58. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  59. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  60. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  61. package/dist/src/serve/shared-host-scheduler.js +25 -107
  62. package/dist/src/serve/shared-host-starvation.js +73 -0
  63. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  64. package/dist/src/serve/worker-artifacts.mjs +56 -0
  65. package/dist/src/serve/worker-command-request.js +68 -0
  66. package/dist/src/serve/worker-invocation-paths.js +144 -0
  67. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  68. package/dist/src/serve/worker-invocation.js +24 -291
  69. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  70. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  71. package/dist/src/sessions/adapters/grokbot.js +7 -0
  72. package/dist/src/sessions/batch-import.js +34 -6
  73. package/dist/src/sessions/contracts.js +19 -0
  74. package/dist/src/sessions/coverage-preflight.js +7 -1
  75. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  76. package/dist/src/sessions/host-catalog.js +157 -0
  77. package/dist/src/sessions/image-blobs.js +141 -0
  78. package/dist/src/sessions/importer.js +23 -3
  79. package/dist/src/sessions/index.js +2 -0
  80. package/dist/src/sessions/refresh.js +25 -2
  81. package/dist/src/sessions/repository.js +164 -22
  82. package/dist/src/sessions/workspace-discovery.js +5 -2
  83. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  84. package/package.json +15 -1
  85. package/schemas/mission-v1.schema.json +20 -19
  86. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  87. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  88. package/tools/agents/providers/claude.mjs +3 -30
  89. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  90. package/tools/agents/providers/codex.mjs +2 -8
  91. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  92. package/tools/agents/providers/rule-delivery.mjs +3 -16
  93. package/tools/agents/providers/rule-presentation.mjs +38 -0
  94. package/tools/plugin/package-plugins.mjs +1 -1
  95. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  96. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  97. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  98. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  99. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  100. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  101. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  102. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  103. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  104. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  105. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  106. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  107. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  108. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  109. package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
  110. package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
  111. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  112. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  113. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  114. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  115. package/tools/ralph-external/session-launcher.mjs +124 -313
  116. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -1,5 +1,6 @@
1
1
  /** Shared installed-worker qualification owner, also used by the Ralph compatibility facade. */
2
2
  import { spawn } from 'node:child_process';
3
+ import { buildInstalledWorkerTransport } from './worker-artifact-binding.mjs';
3
4
  import { constants } from 'node:fs';
4
5
  import { open, realpath, lstat, mkdir, rename, writeFile, unlink } from 'node:fs/promises';
5
6
  import path from 'node:path';
@@ -207,11 +208,11 @@ export async function prepareInstalledWorkerInvocation(instance, invocation, qua
207
208
  || result.stdin !== qualification.adapter.invocation.stdin) {
208
209
  throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker argv/stdin differs from the authorized driver mapping');
209
210
  }
210
- // Shell plans contain names, never credential values. Recheck the named
211
- // launch environment now; the conductor must repeat preparation if it
212
- // changes before copying the command into its shell.
211
+ // Shell plans contain names, never values. PATH is host plumbing; its
212
+ // allowlisted name stays bound, while the executable is pinned separately.
213
+ // Other launch values must still match qualification.
213
214
  if (JSON.stringify([...result.envNames].sort()) !== JSON.stringify(Object.keys(installation.environment ?? {}).sort())
214
- || result.envNames.some(name => process.env[name] !== installation.environment[name])) {
215
+ || result.envNames.some(name => name !== 'PATH' && process.env[name] !== installation.environment[name])) {
215
216
  throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker launch environment differs from qualification');
216
217
  }
217
218
  }
@@ -345,13 +346,13 @@ export async function inspectWorkerInstallation(instance, request) {
345
346
  return { binary, cwd: canonicalCwd, inputRoots, outputRoots, scopeDigest: qualificationDigest({ metadata, profileId, cliVersion, platform, binary, modules, runtimeFiles,
346
347
  permissionRequirements, controlArgv, fixtureDigest: await workerNegativeFixtureDigest(),
347
348
  requiredControls: [...requiredControls].sort(), effectiveConfigDigest, accountScope, boundary, cwd: canonicalCwd,
348
- workerWritableRoots: canonicalWritableRoots, inputRoots, outputRoots, environmentDigest: qualificationDigest(environment), endpoints, mcp: request.mcp ?? null,
349
+ workerWritableRoots: canonicalWritableRoots, inputRoots, outputRoots, environmentDigest: qualificationDigest(Object.fromEntries(Object.entries(environment).filter(([name]) => name !== 'PATH'))), endpoints, mcp: request.mcp ?? null,
349
350
  mcpServerDigest: request.mcpServerDigest ?? null }) };
350
351
  }
351
352
 
352
353
  /** Restricted whole-banner regex: one capture, no branching, assertions,
353
354
  * backreferences, wildcards, unbounded repetition or repeated groups. Bounded
354
- * at most two atom repetitions, each capped at 128; banners are capped at 4096 bytes. */
355
+ * at most two atom repetitions capped at 128, or a final [\s\S] banner tail capped at 4096 bytes. */
355
356
  export function compileWorkerVersionPattern(pattern) {
356
357
  if (typeof pattern !== 'string' || pattern.length > 256 || !pattern.startsWith('^') || !pattern.endsWith('$')) throw new Error('Invalid worker version pattern');
357
358
  let captures = 0, depth = 0, repetitions = 0, atom = false;
@@ -376,7 +377,7 @@ export function compileWorkerVersionPattern(pattern) {
376
377
  depth--; atom = false; // groups cannot be repeated
377
378
  } else if (c === '{') {
378
379
  const match = /^\{([0-9]+),([0-9]+)\}/.exec(pattern.slice(i));
379
- if (++repetitions > 2 || !atom || !match || +match[1] < 1 || +match[2] < +match[1] || +match[2] > 128) throw new Error('Unsafe worker version repetition');
380
+ if (++repetitions > 2 || !atom || !match || +match[1] < 1 || +match[2] < +match[1] || +match[2] > (captures === 1 && depth === 0 && pattern.slice(i - 6) === '[\\s\\S]{1,4096}$' ? 4096 : 128)) throw new Error('Unsafe worker version repetition');
380
381
  i += match[0].length - 1; atom = false;
381
382
  } else {
382
383
  if ('.*+?|^$}'.includes(c)) throw new Error('Unsafe worker version pattern');
@@ -1141,13 +1142,10 @@ export async function loadRecordedWorkerInstallations({ registry, cwd, policy, p
1141
1142
  platform: process.platform, installation, stdinFramerBinding: { storePath: filename, storeDigest: digest },
1142
1143
  ...(profile.transport === 'mcp' ? { mcp: binding.mcp, mcpSchemas: record.mcpSchemas } : {}) },
1143
1144
  ...(typeof instance.buildWorkerStdinFrame === 'function'
1144
- ? { buildWorkerStdinFrame: brief => instance.buildWorkerStdinFrame(brief) } : {}),
1145
- buildInvocation: async input => profile.transport === 'shell' ? {
1146
- argv: [scope.binary.realpath, ...instance.buildWorkerArgs(input, profile, input.permissions)],
1147
- stdin: adapter.invocation.stdin, envNames: Object.keys(installation.environment),
1148
- ...(typeof instance.buildWorkerArtifacts === 'function'
1149
- ? { artifacts: instance.buildWorkerArtifacts(input) } : {}),
1150
- } : instance.buildInvocation(input),
1145
+ ? { buildWorkerStdinFrame: (brief, invocation) => instance.buildWorkerStdinFrame(brief, invocation) } : {}),
1146
+ buildInvocation: async input => profile.transport === 'shell'
1147
+ ? buildInstalledWorkerTransport(instance, input, profile, adapter.invocation, scope.binary.realpath, installation.environment)
1148
+ : instance.buildInvocation(input),
1151
1149
  prepareWorkerInvocation: (invocation, qualification) => prepareInstalledWorkerInvocation(instance, invocation, qualification),
1152
1150
  ...(mcpRegistry ? { mcpRegistry, mcpProfile: record.mcpProfile } : {}),
1153
1151
  };
@@ -0,0 +1,64 @@
1
+ import { expandWorkerGroupTemplates } from '../config/worker-group-templates.js';
2
+ /** Every group member referencing this cli, including disabled ones (for context). */
3
+ export function memberReferences(parallelism, cli) {
4
+ parallelism = expandWorkerGroupTemplates(parallelism).parallelism;
5
+ const refs = [];
6
+ for (const [groupId, group] of Object.entries(parallelism.groups ?? {})) {
7
+ for (const member of group?.members ?? []) {
8
+ if (member.cli !== cli)
9
+ continue;
10
+ refs.push({ groupId, memberId: member.id, member,
11
+ enabled: member.enabled !== false && parallelism.clis?.[cli]?.enabled !== false });
12
+ }
13
+ }
14
+ return refs.sort((a, b) => `${a.groupId}/${a.memberId}`.localeCompare(`${b.groupId}/${b.memberId}`));
15
+ }
16
+ export function memberLabels(refs) {
17
+ return refs.map(ref => `${ref.groupId}/${ref.memberId}${ref.enabled ? '' : ' (disabled)'}`);
18
+ }
19
+ export function describeConfigContext(members) {
20
+ return members.length ? `members: ${members.join(', ')}` : 'members: no group member references this cli';
21
+ }
22
+ /**
23
+ * Union of every cli id a readiness pass must consider: explicit
24
+ * `parallelism.clis` entries plus any cli id referenced only implicitly by a
25
+ * group member (canonical default binding, no explicit installation entry).
26
+ */
27
+ export function allConfiguredCliIds(parallelism, cli) {
28
+ parallelism = expandWorkerGroupTemplates(parallelism).parallelism;
29
+ const ids = new Set(Object.keys(parallelism.clis ?? {}));
30
+ for (const group of Object.values(parallelism.groups ?? {})) {
31
+ for (const member of group?.members ?? [])
32
+ ids.add(member.cli);
33
+ }
34
+ return [...ids].filter(id => cli === undefined || id === cli).sort();
35
+ }
36
+ /**
37
+ * A cli is "required" only when an enabled group member referencing it also
38
+ * covers a role its group lists in `requiredRoles`. Every other configured
39
+ * cli is optional: installation failures stay advisory (never 'error', so
40
+ * they never flip doctor's exit code) until something actually requires it.
41
+ */
42
+ export function isCliRequired(parallelism, cli) {
43
+ parallelism = expandWorkerGroupTemplates(parallelism).parallelism;
44
+ for (const group of Object.values(parallelism.groups ?? {})) {
45
+ const required = new Set(group?.requiredRoles ?? []);
46
+ if (!required.size)
47
+ continue;
48
+ for (const member of group?.members ?? []) {
49
+ if (member.cli !== cli || member.enabled === false)
50
+ continue;
51
+ if (parallelism.clis?.[cli]?.enabled === false)
52
+ continue;
53
+ if (member.roles.some(role => required.has(role)))
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ /** Cap a status at 'warn' for optional (non-required) clis so an unreferenced
60
+ * or not-yet-wired profile never flips doctor's exit code. */
61
+ export function advisoryStatus(status, required) {
62
+ return required || status !== 'error' ? status : 'warn';
63
+ }
64
+ //# sourceMappingURL=worker-member-references.js.map
@@ -0,0 +1,317 @@
1
+ // Resolves eligible worker tuples against current config, catalog and installation snapshots.
2
+ import { ownWorkerRoleGuidance } from './worker-selection-policy.js';
3
+ import { mergeGroupWorkerPolicy } from '../config/worker-group-templates.js';
4
+ import { WORKER_DISPATCH_ENABLE_COMMAND, WORKER_DISPATCH_READINESS_COMMAND } from './worker-dispatch-offer.js';
5
+ import { resolveWorkerGroupInstallation, resolveParallelism, validateWorkerGroupQualifications, validateWorkerGroupReferences, validateWorkerBudgetEnforcement, workerActivationDigests, workerConfigDigest, } from '../config/aiwg-config.js';
6
+ import { qualifyWorkerInvocationProfile, validateWorkerPolicyRoute, validateWorkerModelObservation, assertWorkerLaunchMode, hasOperatorWorkerNativeRenderer, hasSelectionRelevantConflicts, workerContextHeadroom, workerModelKey, isCanonicalWorkerModelKey, WorkerSelectionPolicySchema, validateWorkerCatalogDiscovery, } from './provider-policy.js';
7
+ import { MODEL_WRAPPERS } from './provider-models.js';
8
+ import { routeModelTier } from './router.js';
9
+ const matchesChoice = (tuple, choice) => tuple.group === choice.group && tuple.member === choice.member && tuple.model === choice.model && tuple.effort === choice.effort;
10
+ function findWorkerModelRow(rows, model) {
11
+ const matches = rows?.filter(row => row.id === model || row.apiModelId === model) ?? [];
12
+ return matches.length === 1 ? matches[0] : undefined;
13
+ }
14
+ function workerCatalogDigest(catalog) {
15
+ if (!catalog.discovery)
16
+ return workerConfigDigest(catalog);
17
+ // Offline loads synthesize fetchedAt. Keep catalog facts and persisted
18
+ // observation/qualification times pinned, independent of the load clock.
19
+ const { fetchedAt, ...discovery } = catalog.discovery;
20
+ return workerConfigDigest({ ...catalog, discovery });
21
+ }
22
+ /** Pure S1 resolution. No probing, launch, activation, admission, or writes.
23
+ * Recheck against freshly loaded snapshots before preparing an invocation. */
24
+ export function resolveWorkerPlan(snapshot, request) {
25
+ let parallelism = snapshot.config.parallelism ?? {};
26
+ const policy = snapshot.models.defaults.workerSelection;
27
+ const digests = {
28
+ ...snapshot.modelDigests,
29
+ configDigest: workerActivationDigests(snapshot.config).configDigest,
30
+ resolvedModelsDigest: workerConfigDigest(snapshot.models),
31
+ catalogDigest: workerCatalogDigest(snapshot.catalog),
32
+ profileDigest: workerConfigDigest([snapshot.registry, snapshot.installations]),
33
+ constraintsDigest: workerConfigDigest(snapshot.constraints),
34
+ };
35
+ const plan = {
36
+ mode: parallelism.worker_mode ?? 'direct', status: 'blocked', role: null, selected: null,
37
+ eligible: [], alternatives: [], exclusions: [], diagnostics: [], digests,
38
+ policy: structuredClone(policy), origins: structuredClone(snapshot.models.workerPolicyOrigins),
39
+ choice: null, capacity: 'conductor-guidance-only', ceilings: {},
40
+ };
41
+ const diagnostic = (code, pointer, message) => ({ code, pointer, message });
42
+ const block = (code, pointer, message) => {
43
+ plan.diagnostics.push(diagnostic(code, pointer, message));
44
+ };
45
+ if (!parallelism.worker_dispatch) {
46
+ plan.status = 'disabled';
47
+ block('OG_CONFIG_REFERENCE', '/parallelism/worker_dispatch', `Direct worker dispatch is off (parallelism.worker_dispatch). Enable with \`${WORKER_DISPATCH_ENABLE_COMMAND}\` `
48
+ + `(then check readiness with \`${WORKER_DISPATCH_READINESS_COMMAND}\`).`);
49
+ return plan;
50
+ }
51
+ const now = new Date(request.now);
52
+ if (!Number.isFinite(now.getTime()))
53
+ block('OG_CONFIG_REFERENCE', '/request/now', 'A valid current clock is required');
54
+ if (!request.context || typeof request.context.estimator !== 'string')
55
+ block('OG_CONFIG_REFERENCE', '/request/context', 'A named, bounded context estimate is required');
56
+ if (snapshot.constraints?.revokedModels?.some(key => !isCanonicalWorkerModelKey(key)))
57
+ block('OG_CONFIG_REFERENCE', '/constraints/revokedModels', 'Revocations require canonical inference model keys');
58
+ if (request.expectedDigests && (Object.keys(request.expectedDigests).length !== Object.keys(digests).length
59
+ || Object.entries(digests).some(([key, value]) => request.expectedDigests[key] !== value))) {
60
+ block('OG_CONFIG_REFERENCE', '/request/expectedDigests', 'Current input digests changed; resolve again');
61
+ }
62
+ if (Object.values(snapshot.modelDigests).length !== 3 || Object.values(digests).some(value => !/^sha256:[a-f0-9]{64}$/.test(value))) {
63
+ block('OG_CONFIG_REFERENCE', '/models', 'Current loader input digests are required');
64
+ }
65
+ if (plan.mode === 'managed') {
66
+ const activation = parallelism.activation;
67
+ if (!activation || ['configDigest', 'projectModelsDigest', 'userModelsDigest', 'builtinModelsDigest'].some(key => activation[key] !== digests[key])) {
68
+ block('OG_CONFIG_REFERENCE', '/parallelism/activation', 'Managed activation is missing or stale');
69
+ }
70
+ }
71
+ if (!policy)
72
+ block('OG_CONFIG_REFERENCE', '/defaults/workerSelection', 'Current merged worker policy is required');
73
+ const catalogErrors = validateWorkerCatalogDiscovery(snapshot.catalog.discovery);
74
+ if (catalogErrors.length)
75
+ block('OG_ADAPTER_UNQUALIFIED', '/catalog/discovery', catalogErrors.join('; '));
76
+ if (plan.diagnostics.length || !policy)
77
+ return plan;
78
+ const policyShape = WorkerSelectionPolicySchema.safeParse(policy);
79
+ if (!policyShape.success) {
80
+ block('OG_CONFIG_REFERENCE', '/defaults/workerSelection', policyShape.error.message);
81
+ return plan;
82
+ }
83
+ for (const message of validateWorkerGroupReferences(parallelism, policy))
84
+ block('OG_CONFIG_REFERENCE', '/parallelism', message);
85
+ if (plan.diagnostics.length)
86
+ return plan;
87
+ parallelism = resolveParallelism(parallelism, snapshot.config.providers?.[0]);
88
+ const ceilingLayers = [parallelism.budgets?.perRun, parallelism.budgets?.perTask,
89
+ { maxAttempts: parallelism.retry?.maxAttempts ?? 3 }, snapshot.constraints?.ceilings, request.ceilings];
90
+ for (const key of ['tokens', 'costUsd', 'wallSeconds', 'maxAttempts']) {
91
+ const values = ceilingLayers.map(layer => layer?.[key]).filter((value) => value !== undefined);
92
+ if (values.some(value => !Number.isFinite(value) || value < 0 || key !== 'costUsd' && !Number.isSafeInteger(value))) {
93
+ block('OG_CONFIG_REFERENCE', `/request/ceilings/${key}`, 'Ceilings must be finite nonnegative values; counts must be integers');
94
+ }
95
+ else if (values.length)
96
+ plan.ceilings[key] = Math.min(...values);
97
+ }
98
+ if (plan.ceilings.maxAttempts === 0)
99
+ block('OG_BUDGET_UNMEASURABLE', '/request/ceilings/maxAttempts', 'No task attempts remain');
100
+ const qualifiedRoles = {};
101
+ for (const [groupId, group] of Object.entries(parallelism.groups ?? {})) {
102
+ for (const member of group.members ?? []) {
103
+ const observed = findWorkerModelRow(snapshot.catalog.discovery?.instances?.[member.cli]?.models, member.model)?.worker;
104
+ qualifiedRoles[`${groupId}/${member.id}`] = observed?.qualification.status === 'qualified'
105
+ ? observed.qualification.roles.filter(role => ownWorkerRoleGuidance(policy, role)?.groups.includes(groupId)) : [];
106
+ }
107
+ }
108
+ // Qualification gaps are group-scoped: an unqualified group must not block a
109
+ // request that never considers it. Gaps are applied once the candidates are known.
110
+ const qualificationGaps = validateWorkerGroupQualifications(parallelism, qualifiedRoles).map(diagnostic => ({
111
+ ...diagnostic, message: `${diagnostic.message}. Run aiwg doctor --probe-workers --qualify.`,
112
+ }));
113
+ const gapGroup = (diagnostic) => diagnostic.pointer.split('/')[3] ?? '';
114
+ const taskRole = request.taskClass && policy.taskClasses && Object.hasOwn(policy.taskClasses, request.taskClass) ? policy.taskClasses[request.taskClass] : undefined;
115
+ const role = request.role ?? taskRole;
116
+ plan.role = role ?? null;
117
+ if (request.taskClass && (!taskRole || request.role && taskRole !== request.role))
118
+ block('OG_CONFIG_REFERENCE', '/request/taskClass', 'Unknown task class or conflicting role');
119
+ const guidance = role ? ownWorkerRoleGuidance(policy, role) : undefined;
120
+ if (!role || !guidance)
121
+ block('OG_CONFIG_REFERENCE', '/defaults/workerSelection/roleGuidance', 'Requested role has no guidance');
122
+ if (request.member && !request.group)
123
+ block('OG_CONFIG_REFERENCE', '/request/member', 'Group-local member pins require a group');
124
+ if (request.group && !guidance?.groups.includes(request.group))
125
+ block('OG_CONFIG_REFERENCE', '/request/group', 'Group is outside role guidance');
126
+ if ([request.maxParallel, snapshot.constraints?.maxParallel].some(value => value !== undefined && (!Number.isSafeInteger(value) || value < 0)))
127
+ block('OG_CONFIG_REFERENCE', '/request/maxParallel', 'Capacity ceilings must be nonnegative integers');
128
+ if (request.requireDifferentFamily && (!request.reviewOf || validateWorkerModelObservation(request.reviewOf).length
129
+ || hasSelectionRelevantConflicts(request.reviewOf) || !request.reviewOf.family || request.reviewOf.family === 'unknown')) {
130
+ block('OG_MODEL_UNAPPROVED', '/request/reviewOf', 'Required review needs unambiguous observed source lineage');
131
+ }
132
+ const candidateGroups = request.group ? [request.group] : guidance?.groups ?? [];
133
+ const unqualifiedGroups = new Set(qualificationGaps.map(gapGroup));
134
+ const qualifiedGroups = candidateGroups.filter(groupId => !unqualifiedGroups.has(groupId));
135
+ if (!qualifiedGroups.length) {
136
+ plan.diagnostics.push(...qualificationGaps.filter(gap => candidateGroups.includes(gapGroup(gap))));
137
+ }
138
+ else {
139
+ // Unpinned requests skip an unqualified guidance group visibly instead of failing the role.
140
+ for (const groupId of candidateGroups.filter(groupId => unqualifiedGroups.has(groupId))) {
141
+ const gaps = qualificationGaps.filter(gap => gapGroup(gap) === groupId);
142
+ for (const member of parallelism.groups?.[groupId]?.members ?? []) {
143
+ plan.exclusions.push({ group: groupId, member: member.id, model: member.model, effort: null, diagnostics: gaps });
144
+ }
145
+ }
146
+ }
147
+ if (plan.diagnostics.length || !role || !guidance)
148
+ return plan;
149
+ const baseRole = guidance.baseRole ?? role;
150
+ const wrapperPin = snapshot.models.agentOverrides[MODEL_WRAPPERS[baseRole]]?.['model-override'];
151
+ const groupPolicies = new Map();
152
+ const groups = qualifiedGroups;
153
+ for (const groupId of groups) {
154
+ const group = parallelism.groups[groupId];
155
+ const narrowed = mergeGroupWorkerPolicy(policy, group);
156
+ groupPolicies.set(groupId, narrowed.policy);
157
+ if (narrowed.diagnostics.length) {
158
+ plan.diagnostics.push(...narrowed.diagnostics);
159
+ continue;
160
+ }
161
+ const groupPolicy = narrowed.policy;
162
+ const permissions = { ...group.execution, transport: group.execution?.transport ?? 'shell' };
163
+ const transport = permissions.transport;
164
+ if (request.transport === 'mcp' && permissions.launchMode === 'native-sandbox') {
165
+ const reason = diagnostic('OG_POLICY_RELAXATION', `/parallelism/groups/${groupId}/execution/launchMode`, 'MCP worker-invoke does not support native-sandbox isolation. Use models resolve --transport shell and the shell launcher, or select a group with systemd-unit isolation.');
166
+ for (const member of group.members ?? [])
167
+ plan.exclusions.push({ group: groupId, member: member.id, model: member.model, effort: null, diagnostics: [reason] });
168
+ if (request.group === groupId)
169
+ plan.diagnostics.push(reason);
170
+ continue;
171
+ }
172
+ // Priority is guidance only. Preserve declaration order for ties; fallback order stays explicit.
173
+ const members = (group.members ?? []).map((member, index) => ({ member, index })).sort((a, b) => (a.member.priority ?? 100) - (b.member.priority ?? 100) || a.index - b.index);
174
+ for (const { member } of members) {
175
+ const binding = snapshot.installations[member.cli];
176
+ const discovery = snapshot.catalog.discovery?.instances?.[member.cli];
177
+ const row = findWorkerModelRow(discovery?.models, member.model);
178
+ const observation = row?.worker;
179
+ const efforts = request.effort !== undefined ? [request.effort]
180
+ : [...new Set([guidance.initialEffort, ...guidance.effortLadder])];
181
+ for (const effort of efforts) {
182
+ const tuple = { group: groupId, member: member.id, model: member.model, effort };
183
+ const reasons = [];
184
+ const deny = (code, pointer, message) => { reasons.push(diagnostic(code, pointer, message)); };
185
+ const pointer = `/parallelism/groups/${groupId}/members/${member.id}`;
186
+ if (request.transport !== undefined && request.transport !== transport)
187
+ deny('OG_POLICY_RELAXATION', '/request/transport', 'Requested transport differs from resolved group permissions');
188
+ if (member.enabled === false || parallelism.clis?.[member.cli]?.enabled === false)
189
+ deny('OG_CONFIG_REFERENCE', pointer, 'Member or installation is disabled');
190
+ if (!member.roles.includes(baseRole))
191
+ deny('OG_CONFIG_REFERENCE', pointer, 'Member does not cover requested role');
192
+ const pinMismatch = request.member && request.member !== member.id || request.model && request.model !== member.model || wrapperPin && wrapperPin !== member.model;
193
+ const substitution = request.substitution && matchesChoice(tuple, request.substitution) && request.substitution.rationale.trim();
194
+ if (pinMismatch && !substitution)
195
+ deny('OG_PIN_MISMATCH', pointer, 'Exact request or wrapper pin requires explicit substitution');
196
+ if (!observation || !binding || !row?.llmProvider || !row.apiModelId)
197
+ deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Missing exact model, inference provider, observation or current installation evidence. Run aiwg doctor --probe-workers --qualify.');
198
+ if (observation && binding && row?.llmProvider && row.apiModelId) {
199
+ const shapeErrors = validateWorkerModelObservation(observation);
200
+ if (shapeErrors.length) {
201
+ deny('OG_ADAPTER_UNQUALIFIED', '/worker', shapeErrors.join('; '));
202
+ plan.exclusions.push({ ...tuple, diagnostics: reasons });
203
+ continue;
204
+ }
205
+ if (observation.cliId !== member.cli || observation.runtimeVersion !== binding.cliVersion
206
+ || observation.accountScope !== binding.accountScope || observation.endpointFingerprint !== binding.endpointFingerprint
207
+ || discovery?.accountBinding !== binding.accountScope || discovery?.runtimeVersion !== binding.cliVersion
208
+ || parallelism.clis?.[member.cli]?.accountScope && parallelism.clis[member.cli].accountScope !== binding.accountScope) {
209
+ deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Catalog and current installation scope differ');
210
+ }
211
+ if (group.efforts && (effort === null || !group.efforts.includes(effort)))
212
+ deny('OG_EFFORT_DENIED', pointer, 'Effort is outside group ceiling');
213
+ reasons.push(...validateWorkerPolicyRoute({ policy: groupPolicy, role, inferenceProvider: row.llmProvider, apiModelId: row.apiModelId,
214
+ selectedModel: member.model, effort, identityEvidence: binding.identityEvidence, observation,
215
+ wrapperPin, ...(substitution ? { substitution: request.substitution } : {}), dataClass: request.dataClass, now, context: request.context }));
216
+ if (request.requireDifferentFamily && (observation.family === 'unknown' || observation.family === request.reviewOf.family))
217
+ deny('OG_MODEL_UNAPPROVED', '/worker/family', 'Required review must use a different observed family');
218
+ try {
219
+ const { adapter } = resolveWorkerGroupInstallation(parallelism, member.cli, snapshot.registry);
220
+ const configuredProfile = parallelism.clis?.[member.cli]?.invocationProfile;
221
+ const profilePointer = `/parallelism/clis/${member.cli}/invocationProfile`;
222
+ if (configuredProfile && !Object.hasOwn(adapter.invocationProfiles ?? {}, configuredProfile)) {
223
+ deny('OG_CONFIG_REFERENCE', profilePointer, 'Configured invocation profile is not registered for this adapter; select a registered profile.');
224
+ }
225
+ else if (configuredProfile && configuredProfile !== binding.profile) {
226
+ deny('OG_PIN_MISMATCH', profilePointer, 'Configured invocation profile differs from installation qualification; qualify the selected profile again.');
227
+ }
228
+ const configuredServer = parallelism.clis?.[member.cli]?.mcpServerRef;
229
+ if (transport === 'mcp' && configuredServer && configuredServer !== binding.mcp?.serverRef)
230
+ throw new Error('Configured MCP server changed');
231
+ if (!hasOperatorWorkerNativeRenderer(adapter) && (adapter.capabilities.modelPin === 'unsupported' || !adapter.capabilities.nativeFallbackControllable))
232
+ throw new Error('Exact model or fallback control is unqualified; qualify an adapter with these controls');
233
+ if (policy.guards?.denySelfDelegation !== false && !adapter.capabilities.nestedAgentsControllable)
234
+ throw new Error('Child-agent control is unqualified. Review models.json defaults.workerSelection.guards.denySelfDelegation; false permits uncontrolled child agents without qualifying their behavior.');
235
+ if (binding.identityEvidence === 'backend-attested' && adapter.capabilities.modelPin !== 'observed')
236
+ throw new Error('Requested-only mapping cannot attest backend identity');
237
+ if (!hasOperatorWorkerNativeRenderer(adapter) && group.execution?.sandbox && !adapter.capabilities.sandboxModes.includes(group.execution.sandbox))
238
+ throw new Error('Requested sandbox mode is unqualified');
239
+ assertWorkerLaunchMode(adapter, permissions);
240
+ qualifyWorkerInvocationProfile(adapter, { profile: binding.profile, transport, cliVersion: binding.cliVersion,
241
+ policy, permissions, effort, boundary: binding.boundary,
242
+ requiredControls: request.requiredControls, mcp: binding.mcp });
243
+ if (hasOperatorWorkerNativeRenderer(adapter) && transport === 'shell' && permissions.sandbox === 'external') {
244
+ reasons.push({ code: 'OG_SANDBOX_EXTERNAL', blocking: false,
245
+ pointer: `/parallelism/groups/${groupId}/execution/sandbox`,
246
+ message: `${adapter.provider} CLI's own sandbox is disabled by explicit external policy; isolation relies on the per-task worktree and launcher resource scope.` });
247
+ }
248
+ const { maxAttempts: _maxAttempts, ...budgetLimits } = plan.ceilings;
249
+ const budgetDimensions = { tokens: 'tokens', cost: 'costUsd', 'wall-time': 'wallSeconds' };
250
+ const enforceable = adapter.capabilities.hardBudget.map(key => budgetDimensions[key]);
251
+ for (const message of validateWorkerBudgetEnforcement({ ...parallelism.budgets, perTask: budgetLimits }, enforceable))
252
+ deny('OG_BUDGET_UNMEASURABLE', '/parallelism/budgets', message);
253
+ }
254
+ catch (error) {
255
+ deny('OG_ADAPTER_UNQUALIFIED', pointer, String(error));
256
+ }
257
+ let key;
258
+ try {
259
+ key = workerModelKey(row.llmProvider, row.apiModelId);
260
+ }
261
+ catch {
262
+ plan.exclusions.push({ ...tuple, diagnostics: reasons });
263
+ continue;
264
+ }
265
+ if (snapshot.constraints?.revokedModels?.includes(key))
266
+ deny('OG_MODEL_UNAPPROVED', '/constraints/revokedModels', 'Current model revocation');
267
+ if (snapshot.constraints?.unavailableMembers?.includes(`${groupId}/${member.id}`))
268
+ deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Member is currently unavailable');
269
+ const premium = ['premium', 'max-quality'].includes(guidance.tier) || observation.pricing.costClass === 'premium';
270
+ // Worker candidates always invoke a model; deterministic/routine hints must not bypass premium authority.
271
+ const authority = routeModelTier({ ...request.authority, deterministic: false, routine: false,
272
+ defaultTier: guidance.tier === 'economy' ? 1 : 2, requestedPremium: premium || request.authority?.requestedPremium });
273
+ if (authority.requiresConfirmation)
274
+ deny('OG_MODEL_UNAPPROVED', '/request/authority', `${authority.rationale.join('; ')}; agents may pass --authorize-premium only after the human explicitly confirmed the premium escalation (escalation-discipline rule)`);
275
+ const installation = parallelism.clis?.[member.cli];
276
+ const caps = [parallelism.max_parallel_subagents, group.maxParallel, parallelism.admission?.groupCaps?.[groupId],
277
+ parallelism.admission?.memberCaps?.[`${groupId}/${member.id}`], parallelism.admission?.cliCaps?.[member.cli],
278
+ parallelism.admission?.modelCaps?.[key], installation && parallelism.admission?.quotaCaps?.[installation.quotaScope],
279
+ snapshot.constraints?.maxParallel, request.maxParallel].filter((value) => value !== undefined);
280
+ const effectiveMaxParallel = caps.length ? Math.min(...caps) : null;
281
+ if (effectiveMaxParallel === 0)
282
+ deny('OG_ADAPTER_UNQUALIFIED', '/constraints/maxParallel', 'Current capacity ceiling is zero');
283
+ if (!reasons.some(reason => reason.blocking !== false))
284
+ plan.eligible.push({ ...tuple, cli: member.cli,
285
+ inferenceProvider: row.llmProvider, modelKey: key, family: observation.family, identityEvidence: binding.identityEvidence,
286
+ profile: binding.profile, transport,
287
+ permissions: structuredClone(permissions), context: workerContextHeadroom(request.context, observation),
288
+ observedAt: observation.observedAt, verifiedAt: observation.verifiedAt, observationRef: observation.recordId,
289
+ effectiveMaxParallel, diagnostics: reasons });
290
+ }
291
+ if (reasons.some(reason => reason.blocking !== false))
292
+ plan.exclusions.push({ ...tuple, diagnostics: reasons });
293
+ }
294
+ }
295
+ }
296
+ if (request.choice) {
297
+ if (!request.choice.rationale.trim())
298
+ block('OG_CONFIG_REFERENCE', '/request/choice/rationale', 'Conductor choice requires a rationale');
299
+ plan.selected = plan.eligible.find(tuple => matchesChoice(tuple, request.choice)) ?? null;
300
+ if (!plan.selected)
301
+ block('OG_MODEL_UNAPPROVED', '/request/choice', 'Conductor choice is outside the eligible set');
302
+ }
303
+ else
304
+ plan.selected = plan.eligible[0] ?? null;
305
+ if (plan.diagnostics.length)
306
+ plan.selected = null;
307
+ if (plan.selected) {
308
+ plan.policy = groupPolicies.get(plan.selected.group);
309
+ plan.status = 'eligible';
310
+ plan.diagnostics.push(...plan.selected.diagnostics.filter(item => item.code === 'OG_SANDBOX_EXTERNAL'));
311
+ plan.choice = { source: request.choice ? 'conductor' : 'default', rationale: request.choice?.rationale ?? 'Role group order, member priority/declaration order, then qualified effort ladder',
312
+ ...(request.substitution && matchesChoice(plan.selected, request.substitution) ? { substitution: structuredClone(request.substitution) } : {}) };
313
+ }
314
+ plan.alternatives = plan.eligible.filter(tuple => tuple !== plan.selected);
315
+ return plan;
316
+ }
317
+ //# sourceMappingURL=worker-plan.js.map