@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
@@ -14,6 +14,8 @@ import { createRequire } from 'node:module';
14
14
  import { compileWorkerVersionPattern, workerVersionRangeMismatch } from './worker-installation.mjs';
15
15
  import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
16
16
  import { qualificationDigest } from './qualification-digest.mjs';
17
+ import { DEFAULT_WORKER_SELECTION, WorkerSelectionPolicySchema, workerModelKey, ownWorkerRoleGuidance } from './worker-selection-policy.js';
18
+ export { DEFAULT_WORKER_SELECTION, WorkerSelectionPolicySchema, mergeWorkerSelection, workerModelKey, isCanonicalWorkerModelKey } from './worker-selection-policy.js';
17
19
  const require = createRequire(import.meta.url);
18
20
  function requireModelResource(filename) {
19
21
  const candidates = [
@@ -183,156 +185,6 @@ const PolicySchema = z.strictObject({
183
185
  effort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
184
186
  override: z.string().min(1).optional(),
185
187
  });
186
- const uniqueStrings = z.array(z.string().min(1)).refine(values => new Set(values).size === values.length, 'Duplicate entries');
187
- const WorkerDataPolicySchema = z.strictObject({
188
- allowedProcessing: z.array(z.enum(['local', 'remote'])).refine(values => new Set(values).size === values.length).optional(),
189
- allowProductImprovement: z.boolean().optional(),
190
- allowUnknown: z.boolean().optional(),
191
- maxRetentionDays: integer().nonnegative().optional(),
192
- });
193
- const WorkerRoleSchema = z.strictObject({
194
- groups: uniqueStrings.refine(values => values.length > 0),
195
- tier: z.enum(['economy', 'standard', 'premium', 'max-quality']),
196
- initialEffort: z.string().min(1).nullable(),
197
- effortLadder: uniqueStrings,
198
- escalateAfter: integer().positive().optional(),
199
- maxEscalations: integer().nonnegative().optional(),
200
- deescalateAfter: integer().positive().optional(),
201
- allowNotApplicableEffort: z.boolean().optional(),
202
- });
203
- export const WorkerSelectionPolicySchema = z.strictObject({
204
- roleGuidance: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerRoleSchema).optional(),
205
- guards: z.strictObject({
206
- deniedEfforts: uniqueStrings.optional(),
207
- deniedModels: uniqueStrings.optional(),
208
- denySelfDelegation: z.boolean().optional(),
209
- requireExactModel: z.boolean().optional(),
210
- allowedVisibility: z.array(z.enum(['listed', 'hidden'])).refine(v => new Set(v).size === v.length).optional(),
211
- allowedLifecycle: z.array(z.enum(['active', 'deprecated'])).refine(v => new Set(v).size === v.length).optional(),
212
- minimumIdentityEvidence: z.enum(['runtime-configured', 'backend-attested']).optional(),
213
- }).optional(),
214
- dataPolicy: WorkerDataPolicySchema.optional(),
215
- dataClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerDataPolicySchema).optional(),
216
- freshness: z.strictObject({
217
- warnHours: integer().positive().optional(),
218
- blockHours: integer().positive().optional(),
219
- policyDays: integer().positive().optional(),
220
- }).optional(),
221
- taskClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), z.string().min(1)).optional(),
222
- }).superRefine((policy, context) => {
223
- for (const [role, guidance] of Object.entries(policy.roleGuidance ?? {})) {
224
- if (!['efficiency', 'coding', 'reasoning'].includes(role))
225
- context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Unknown worker role' });
226
- if (guidance.effortLadder.includes('none') || (guidance.initialEffort === 'none' ? guidance.effortLadder.length !== 0 : guidance.initialEffort !== null && !guidance.effortLadder.includes(guidance.initialEffort))) {
227
- context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Initial effort must belong to the ladder; real none requires an empty ladder' });
228
- }
229
- if (guidance.initialEffort === null && (guidance.allowNotApplicableEffort !== true || guidance.effortLadder.length !== 0)) {
230
- context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Null effort requires an empty ladder and allowNotApplicableEffort=true' });
231
- }
232
- }
233
- for (const key of policy.guards?.deniedModels ?? []) {
234
- if (!isCanonicalWorkerModelKey(key))
235
- context.addIssue({ code: 'custom', path: ['guards', 'deniedModels'], message: 'Noncanonical inference model key' });
236
- }
237
- if (policy.freshness?.warnHours !== undefined && policy.freshness.blockHours !== undefined && policy.freshness.warnHours > policy.freshness.blockHours) {
238
- context.addIssue({ code: 'custom', path: ['freshness'], message: 'warnHours must not exceed blockHours' });
239
- }
240
- for (const role of Object.values(policy.taskClasses ?? {})) {
241
- if (!['coding', 'reasoning', 'efficiency'].includes(role))
242
- context.addIssue({ code: 'custom', path: ['taskClasses'], message: 'Unknown worker role' });
243
- }
244
- });
245
- /** Canonical RFC3986 identity; installation and account never enter the key. */
246
- export function workerModelKey(inferenceProvider, apiModelId) {
247
- if (!/^[a-z][a-z0-9-]{0,62}$/.test(inferenceProvider) || !apiModelId)
248
- throw new Error('OG_CONFIG_REFERENCE: invalid inference model identity');
249
- return `${inferenceProvider}/${encodeURIComponent(apiModelId).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
250
- }
251
- export function isCanonicalWorkerModelKey(key) {
252
- const slash = key.indexOf('/');
253
- try {
254
- return slash > 0 && workerModelKey(key.slice(0, slash), decodeURIComponent(key.slice(slash + 1))) === key;
255
- }
256
- catch {
257
- return false;
258
- }
259
- }
260
- export const DEFAULT_WORKER_SELECTION = {
261
- guards: { deniedEfforts: ['ultra'], deniedModels: [], denySelfDelegation: true, requireExactModel: true, allowedVisibility: ['listed'], allowedLifecycle: ['active'], minimumIdentityEvidence: 'runtime-configured' },
262
- dataPolicy: { allowedProcessing: ['local', 'remote'], allowProductImprovement: false, allowUnknown: false },
263
- freshness: { warnHours: 24, blockHours: 168, policyDays: 30 },
264
- taskClasses: { inventory: 'efficiency', implementation: 'coding', architecture: 'reasoning', review: 'reasoning' },
265
- };
266
- /** Merge leaves, preserving pre-merge explicit origins and attempted relaxations. */
267
- export function mergeWorkerSelection(layers) {
268
- const policy = {};
269
- const origins = {};
270
- const diagnostics = [];
271
- const visit = (target, input, parts, layer) => {
272
- for (const [key, value] of Object.entries(input)) {
273
- const path = [...parts, key];
274
- const pointer = `/defaults/workerSelection/${path.map(p => p.replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`;
275
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
276
- target[key] ??= {};
277
- visit(target[key], value, path, layer);
278
- continue;
279
- }
280
- const origin = { source: layer.source, authority: layer.authority, digest: layer.digest, pointer, contribution: structuredClone(value) };
281
- const prior = origins[pointer] ?? [];
282
- const explicit = prior.filter(o => o.authority !== 'builtin');
283
- let effective = structuredClone(value);
284
- let relaxation = false;
285
- if (layer.authority !== 'builtin' && explicit.length > 0) {
286
- const previous = target[key];
287
- if (['deniedEfforts', 'deniedModels'].includes(key))
288
- effective = [...new Set([...previous, ...value])];
289
- else if (['allowedVisibility', 'allowedLifecycle', 'allowedProcessing'].includes(key)) {
290
- relaxation = value.some((v) => !previous.includes(v));
291
- effective = previous.filter((v) => value.includes(v));
292
- }
293
- else if (['denySelfDelegation', 'requireExactModel'].includes(key)) {
294
- relaxation = previous === true && value === false;
295
- effective = previous || value;
296
- }
297
- else if (['allowUnknown', 'allowProductImprovement'].includes(key)) {
298
- relaxation = previous === false && value === true;
299
- effective = previous && value;
300
- }
301
- else if (['maxRetentionDays', 'warnHours', 'blockHours', 'policyDays'].includes(key)) {
302
- relaxation = value > previous;
303
- effective = Math.min(previous, value);
304
- }
305
- else if (key === 'minimumIdentityEvidence') {
306
- relaxation = previous === 'backend-attested' && value !== previous;
307
- effective = previous === 'backend-attested' ? previous : value;
308
- }
309
- }
310
- if (relaxation)
311
- diagnostics.push({ code: 'OG_POLICY_RELAXATION', pointer, message: 'Explicit restriction cannot relax an inherited restriction', origins: [...explicit, origin] });
312
- target[key] = effective;
313
- origins[pointer] = [...prior, origin];
314
- }
315
- };
316
- for (const layer of layers)
317
- if (layer.policy)
318
- visit(policy, layer.policy, [], layer);
319
- const validation = WorkerSelectionPolicySchema.safeParse(policy);
320
- if (!validation.success)
321
- for (const issue of validation.error.issues)
322
- diagnostics.push({ code: 'OG_CONFIG_REFERENCE', pointer: `/defaults/workerSelection/${issue.path.join('/')}`, message: issue.message });
323
- // Data classes are applied as further restrictions to the base path.
324
- for (const dataClass of Object.values(policy.dataClasses ?? {})) {
325
- const base = policy.dataPolicy ?? {};
326
- if (dataClass.allowedProcessing)
327
- dataClass.allowedProcessing = dataClass.allowedProcessing.filter(p => !base.allowedProcessing || base.allowedProcessing.includes(p));
328
- for (const key of ['allowUnknown', 'allowProductImprovement'])
329
- if (base[key] === false)
330
- dataClass[key] = false;
331
- if (base.maxRetentionDays !== undefined)
332
- dataClass.maxRetentionDays = Math.min(dataClass.maxRetentionDays ?? base.maxRetentionDays, base.maxRetentionDays);
333
- }
334
- return { policy, origins, diagnostics };
335
- }
336
188
  const UserProjectModelConfigSchema = z.looseObject({
337
189
  defaults: z.strictObject({
338
190
  tier: z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
@@ -435,7 +287,7 @@ export class WorkerRoleEffortError extends WorkerPolicyError {
435
287
  /** Validate policy input before entering the sanitized adapter boundary. Null
436
288
  * remains valid for models whose effort is explicitly not applicable. */
437
289
  export function assertWorkerRoleEffort(policy, role, requiresExplicit = false) {
438
- const effort = policy?.roleGuidance?.[role]?.initialEffort;
290
+ const effort = ownWorkerRoleGuidance(policy, role)?.initialEffort;
439
291
  if (effort === undefined || requiresExplicit && (effort === null || !effort.trim()))
440
292
  throw new WorkerRoleEffortError(role);
441
293
  }
@@ -459,7 +311,7 @@ export function assertWorkerSelectionAllowed(selected, context) {
459
311
  const observation = context.modelObservation ?? context.defaultObservation;
460
312
  if (!observation)
461
313
  throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', 'Worker invocation requires model or default observation evidence');
462
- const guidance = context.role ? policy.roleGuidance?.[context.role] : undefined;
314
+ const guidance = context.role ? ownWorkerRoleGuidance(policy, context.role) : undefined;
463
315
  if (isWorkerNullEffortDenied(selected.effort.value, observation, guidance)) {
464
316
  throw new WorkerPolicyError('OG_EFFORT_DENIED', 'Worker invocation denied effort: null effort requires qualified absence of an effort control');
465
317
  }
@@ -572,6 +424,13 @@ export function assertOperatorWorkerNativeArgv(adapter, argv, selected, permissi
572
424
  throw new Error('Muse direct worker approval requires never');
573
425
  requirePair('--approval-mode', 'never');
574
426
  requirePair('--approval-judge', 'off');
427
+ // #3685: Tier 0 Muse workers run in conductor-prepared worker trees, so
428
+ // project context must load. An argv without exactly one
429
+ // --trust-workspace would run with project_sources=0 (no project
430
+ // rules/skills/hooks) and is refused rather than silently degraded.
431
+ if (args.filter(token => token === '--trust-workspace').length !== 1) {
432
+ throw new Error('Requested native worker control is absent or ambiguous: --trust-workspace');
433
+ }
575
434
  if (args.includes('--disable-sandbox') !== (sandbox === 'external')) {
576
435
  throw new Error('Requested native worker sandbox control does not match');
577
436
  }
@@ -907,7 +766,7 @@ function isWorkerNullEffortDenied(effort, observation, guidance) {
907
766
  }
908
767
  function workerRouteDataPolicy(policy, dataClass) {
909
768
  const data = { ...DEFAULT_WORKER_SELECTION.dataPolicy, ...policy.dataPolicy };
910
- const narrowed = dataClass ? policy.dataClasses?.[dataClass] : undefined;
769
+ const narrowed = dataClass && policy.dataClasses && Object.hasOwn(policy.dataClasses, dataClass) ? policy.dataClasses[dataClass] : undefined;
911
770
  if (narrowed) {
912
771
  if (narrowed.allowedProcessing)
913
772
  data.allowedProcessing = data.allowedProcessing?.filter(p => narrowed.allowedProcessing.includes(p));
@@ -956,8 +815,8 @@ export function validateWorkerPolicyRoute(input) {
956
815
  message: `Conflicting optional model evidence remains unknown: ${observation.conflicts.map(conflict => conflict.field).join(', ')}`,
957
816
  });
958
817
  const guards = input.policy.guards ?? DEFAULT_WORKER_SELECTION.guards;
959
- const guidance = input.policy.roleGuidance?.[input.role];
960
- if (observation.qualification.status !== 'qualified' || !observation.qualification.roles.includes(input.role))
818
+ const guidance = ownWorkerRoleGuidance(input.policy, input.role);
819
+ if (observation.qualification.status !== 'qualified' || !observation.qualification.roles.includes(guidance?.baseRole ?? input.role))
961
820
  deny('OG_ADAPTER_UNQUALIFIED', '/worker/qualification', 'Role lacks qualified evidence. Run aiwg doctor --probe-workers --qualify.');
962
821
  if (!guidance)
963
822
  deny('OG_CONFIG_REFERENCE', `/defaults/workerSelection/roleGuidance/${input.role}`, 'Role guidance is unavailable');
@@ -6,11 +6,8 @@
6
6
  * @architecture @.aiwg/architecture/enhanced-model-selection-design.md
7
7
  */
8
8
  import { ConfigLoader } from './config-loader.js';
9
- import { WORKER_DISPATCH_ENABLE_COMMAND, WORKER_DISPATCH_READINESS_COMMAND } from './worker-dispatch-offer.js';
10
- import { resolveWorkerGroupInstallation, resolveParallelism, validateWorkerGroupQualifications, validateWorkerGroupReferences, validateWorkerBudgetEnforcement, workerActivationDigests, workerConfigDigest, } from '../config/aiwg-config.js';
11
- import { qualifyWorkerInvocationProfile, validateWorkerPolicyRoute, validateWorkerModelObservation, assertWorkerLaunchMode, hasOperatorWorkerNativeRenderer, hasSelectionRelevantConflicts, workerContextHeadroom, workerModelKey, isCanonicalWorkerModelKey, WorkerSelectionPolicySchema, validateWorkerCatalogDiscovery, } from './provider-policy.js';
12
- import { MODEL_WRAPPERS } from './provider-models.js';
13
- import { routeModelTier } from './router.js';
9
+ import { resolveWorkerPlan } from './worker-plan.js';
10
+ export * from './worker-plan.js';
14
11
  /**
15
12
  * Resolves models for agents using tier system
16
13
  */
@@ -252,292 +249,4 @@ export class ModelResolver {
252
249
  this.loader.clearCache();
253
250
  }
254
251
  }
255
- const matchesChoice = (tuple, choice) => tuple.group === choice.group && tuple.member === choice.member && tuple.model === choice.model && tuple.effort === choice.effort;
256
- function findWorkerModelRow(rows, model) {
257
- const matches = rows?.filter(row => row.id === model || row.apiModelId === model) ?? [];
258
- return matches.length === 1 ? matches[0] : undefined;
259
- }
260
- function workerCatalogDigest(catalog) {
261
- if (!catalog.discovery)
262
- return workerConfigDigest(catalog);
263
- // Offline loads synthesize fetchedAt. Keep catalog facts and persisted
264
- // observation/qualification times pinned, independent of the load clock.
265
- const { fetchedAt, ...discovery } = catalog.discovery;
266
- return workerConfigDigest({ ...catalog, discovery });
267
- }
268
- /** Pure S1 resolution. No probing, launch, activation, admission, or writes.
269
- * Recheck against freshly loaded snapshots before preparing an invocation. */
270
- export function resolveWorkerPlan(snapshot, request) {
271
- let parallelism = snapshot.config.parallelism ?? {};
272
- const policy = snapshot.models.defaults.workerSelection;
273
- const digests = {
274
- ...snapshot.modelDigests,
275
- configDigest: workerActivationDigests(snapshot.config).configDigest,
276
- resolvedModelsDigest: workerConfigDigest(snapshot.models),
277
- catalogDigest: workerCatalogDigest(snapshot.catalog),
278
- profileDigest: workerConfigDigest([snapshot.registry, snapshot.installations]),
279
- constraintsDigest: workerConfigDigest(snapshot.constraints),
280
- };
281
- const plan = {
282
- mode: parallelism.worker_mode ?? 'direct', status: 'blocked', role: null, selected: null,
283
- eligible: [], alternatives: [], exclusions: [], diagnostics: [], digests,
284
- policy: structuredClone(policy), origins: structuredClone(snapshot.models.workerPolicyOrigins),
285
- choice: null, capacity: 'conductor-guidance-only', ceilings: {},
286
- };
287
- const diagnostic = (code, pointer, message) => ({ code, pointer, message });
288
- const block = (code, pointer, message) => {
289
- plan.diagnostics.push(diagnostic(code, pointer, message));
290
- };
291
- if (!parallelism.worker_dispatch) {
292
- plan.status = 'disabled';
293
- block('OG_CONFIG_REFERENCE', '/parallelism/worker_dispatch', `Direct worker dispatch is off (parallelism.worker_dispatch). Enable with \`${WORKER_DISPATCH_ENABLE_COMMAND}\` `
294
- + `(then check readiness with \`${WORKER_DISPATCH_READINESS_COMMAND}\`).`);
295
- return plan;
296
- }
297
- const now = new Date(request.now);
298
- if (!Number.isFinite(now.getTime()))
299
- block('OG_CONFIG_REFERENCE', '/request/now', 'A valid current clock is required');
300
- if (!request.context || typeof request.context.estimator !== 'string')
301
- block('OG_CONFIG_REFERENCE', '/request/context', 'A named, bounded context estimate is required');
302
- if (snapshot.constraints?.revokedModels?.some(key => !isCanonicalWorkerModelKey(key)))
303
- block('OG_CONFIG_REFERENCE', '/constraints/revokedModels', 'Revocations require canonical inference model keys');
304
- if (request.expectedDigests && (Object.keys(request.expectedDigests).length !== Object.keys(digests).length
305
- || Object.entries(digests).some(([key, value]) => request.expectedDigests[key] !== value))) {
306
- block('OG_CONFIG_REFERENCE', '/request/expectedDigests', 'Current input digests changed; resolve again');
307
- }
308
- if (Object.values(snapshot.modelDigests).length !== 3 || Object.values(digests).some(value => !/^sha256:[a-f0-9]{64}$/.test(value))) {
309
- block('OG_CONFIG_REFERENCE', '/models', 'Current loader input digests are required');
310
- }
311
- if (plan.mode === 'managed') {
312
- const activation = parallelism.activation;
313
- if (!activation || ['configDigest', 'projectModelsDigest', 'userModelsDigest', 'builtinModelsDigest'].some(key => activation[key] !== digests[key])) {
314
- block('OG_CONFIG_REFERENCE', '/parallelism/activation', 'Managed activation is missing or stale');
315
- }
316
- }
317
- if (!policy)
318
- block('OG_CONFIG_REFERENCE', '/defaults/workerSelection', 'Current merged worker policy is required');
319
- const catalogErrors = validateWorkerCatalogDiscovery(snapshot.catalog.discovery);
320
- if (catalogErrors.length)
321
- block('OG_ADAPTER_UNQUALIFIED', '/catalog/discovery', catalogErrors.join('; '));
322
- if (plan.diagnostics.length || !policy)
323
- return plan;
324
- const policyShape = WorkerSelectionPolicySchema.safeParse(policy);
325
- if (!policyShape.success) {
326
- block('OG_CONFIG_REFERENCE', '/defaults/workerSelection', policyShape.error.message);
327
- return plan;
328
- }
329
- for (const message of validateWorkerGroupReferences(parallelism, policy))
330
- block('OG_CONFIG_REFERENCE', '/parallelism', message);
331
- if (plan.diagnostics.length)
332
- return plan;
333
- parallelism = resolveParallelism(parallelism, snapshot.config.providers?.[0]);
334
- const ceilingLayers = [parallelism.budgets?.perRun, parallelism.budgets?.perTask,
335
- { maxAttempts: parallelism.retry?.maxAttempts ?? 3 }, snapshot.constraints?.ceilings, request.ceilings];
336
- for (const key of ['tokens', 'costUsd', 'wallSeconds', 'maxAttempts']) {
337
- const values = ceilingLayers.map(layer => layer?.[key]).filter((value) => value !== undefined);
338
- if (values.some(value => !Number.isFinite(value) || value < 0 || key !== 'costUsd' && !Number.isSafeInteger(value))) {
339
- block('OG_CONFIG_REFERENCE', `/request/ceilings/${key}`, 'Ceilings must be finite nonnegative values; counts must be integers');
340
- }
341
- else if (values.length)
342
- plan.ceilings[key] = Math.min(...values);
343
- }
344
- if (plan.ceilings.maxAttempts === 0)
345
- block('OG_BUDGET_UNMEASURABLE', '/request/ceilings/maxAttempts', 'No task attempts remain');
346
- const qualifiedRoles = {};
347
- for (const [groupId, group] of Object.entries(parallelism.groups ?? {})) {
348
- for (const member of group.members) {
349
- const observed = findWorkerModelRow(snapshot.catalog.discovery?.instances?.[member.cli]?.models, member.model)?.worker;
350
- qualifiedRoles[`${groupId}/${member.id}`] = observed?.qualification.status === 'qualified'
351
- ? observed.qualification.roles.filter(role => policy.roleGuidance?.[role]?.groups.includes(groupId)) : [];
352
- }
353
- }
354
- // Qualification gaps are group-scoped: an unqualified group must not block a
355
- // request that never considers it. Gaps are applied once the candidates are known.
356
- const qualificationGaps = validateWorkerGroupQualifications(parallelism, qualifiedRoles).map(diagnostic => ({
357
- ...diagnostic, message: `${diagnostic.message}. Run aiwg doctor --probe-workers --qualify.`,
358
- }));
359
- const gapGroup = (diagnostic) => diagnostic.pointer.split('/')[3] ?? '';
360
- const taskRole = request.taskClass ? policy.taskClasses?.[request.taskClass] : undefined;
361
- const role = request.role ?? taskRole;
362
- plan.role = role ?? null;
363
- if (request.taskClass && (!taskRole || request.role && taskRole !== request.role))
364
- block('OG_CONFIG_REFERENCE', '/request/taskClass', 'Unknown task class or conflicting role');
365
- const guidance = role ? policy.roleGuidance?.[role] : undefined;
366
- if (!role || !guidance)
367
- block('OG_CONFIG_REFERENCE', '/defaults/workerSelection/roleGuidance', 'Requested role has no guidance');
368
- if (request.member && !request.group)
369
- block('OG_CONFIG_REFERENCE', '/request/member', 'Group-local member pins require a group');
370
- if (request.group && !guidance?.groups.includes(request.group))
371
- block('OG_CONFIG_REFERENCE', '/request/group', 'Group is outside role guidance');
372
- if ([request.maxParallel, snapshot.constraints?.maxParallel].some(value => value !== undefined && (!Number.isSafeInteger(value) || value < 0)))
373
- block('OG_CONFIG_REFERENCE', '/request/maxParallel', 'Capacity ceilings must be nonnegative integers');
374
- if (request.requireDifferentFamily && (!request.reviewOf || validateWorkerModelObservation(request.reviewOf).length
375
- || hasSelectionRelevantConflicts(request.reviewOf) || !request.reviewOf.family || request.reviewOf.family === 'unknown')) {
376
- block('OG_MODEL_UNAPPROVED', '/request/reviewOf', 'Required review needs unambiguous observed source lineage');
377
- }
378
- const candidateGroups = request.group ? [request.group] : guidance?.groups ?? [];
379
- const unqualifiedGroups = new Set(qualificationGaps.map(gapGroup));
380
- const qualifiedGroups = candidateGroups.filter(groupId => !unqualifiedGroups.has(groupId));
381
- if (!qualifiedGroups.length) {
382
- plan.diagnostics.push(...qualificationGaps.filter(gap => candidateGroups.includes(gapGroup(gap))));
383
- }
384
- else {
385
- // Unpinned requests skip an unqualified guidance group visibly instead of failing the role.
386
- for (const groupId of candidateGroups.filter(groupId => unqualifiedGroups.has(groupId))) {
387
- const gaps = qualificationGaps.filter(gap => gapGroup(gap) === groupId);
388
- for (const member of parallelism.groups?.[groupId]?.members ?? []) {
389
- plan.exclusions.push({ group: groupId, member: member.id, model: member.model, effort: null, diagnostics: gaps });
390
- }
391
- }
392
- }
393
- if (plan.diagnostics.length || !role || !guidance)
394
- return plan;
395
- const wrapperPin = snapshot.models.agentOverrides[MODEL_WRAPPERS[role]]?.['model-override'];
396
- const groups = qualifiedGroups;
397
- for (const groupId of groups) {
398
- const group = parallelism.groups[groupId];
399
- const permissions = { ...group.execution, transport: group.execution?.transport ?? 'shell' };
400
- const transport = permissions.transport;
401
- // Priority is guidance only. Preserve declaration order for ties; fallback order stays explicit.
402
- const members = group.members.map((member, index) => ({ member, index })).sort((a, b) => (a.member.priority ?? 100) - (b.member.priority ?? 100) || a.index - b.index);
403
- for (const { member } of members) {
404
- const binding = snapshot.installations[member.cli];
405
- const discovery = snapshot.catalog.discovery?.instances?.[member.cli];
406
- const row = findWorkerModelRow(discovery?.models, member.model);
407
- const observation = row?.worker;
408
- const efforts = request.effort !== undefined ? [request.effort]
409
- : [...new Set([guidance.initialEffort, ...guidance.effortLadder])];
410
- for (const effort of efforts) {
411
- const tuple = { group: groupId, member: member.id, model: member.model, effort };
412
- const reasons = [];
413
- const deny = (code, pointer, message) => { reasons.push(diagnostic(code, pointer, message)); };
414
- const pointer = `/parallelism/groups/${groupId}/members/${member.id}`;
415
- if (request.transport !== undefined && request.transport !== transport)
416
- deny('OG_POLICY_RELAXATION', '/request/transport', 'Requested transport differs from resolved group permissions');
417
- if (member.enabled === false || parallelism.clis?.[member.cli]?.enabled === false)
418
- deny('OG_CONFIG_REFERENCE', pointer, 'Member or installation is disabled');
419
- if (!member.roles.includes(role))
420
- deny('OG_CONFIG_REFERENCE', pointer, 'Member does not cover requested role');
421
- const pinMismatch = request.member && request.member !== member.id || request.model && request.model !== member.model || wrapperPin && wrapperPin !== member.model;
422
- const substitution = request.substitution && matchesChoice(tuple, request.substitution) && request.substitution.rationale.trim();
423
- if (pinMismatch && !substitution)
424
- deny('OG_PIN_MISMATCH', pointer, 'Exact request or wrapper pin requires explicit substitution');
425
- if (!observation || !binding || !row?.llmProvider || !row.apiModelId)
426
- deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Missing exact model, inference provider, observation or current installation evidence. Run aiwg doctor --probe-workers --qualify.');
427
- if (observation && binding && row?.llmProvider && row.apiModelId) {
428
- const shapeErrors = validateWorkerModelObservation(observation);
429
- if (shapeErrors.length) {
430
- deny('OG_ADAPTER_UNQUALIFIED', '/worker', shapeErrors.join('; '));
431
- plan.exclusions.push({ ...tuple, diagnostics: reasons });
432
- continue;
433
- }
434
- if (observation.cliId !== member.cli || observation.runtimeVersion !== binding.cliVersion
435
- || observation.accountScope !== binding.accountScope || observation.endpointFingerprint !== binding.endpointFingerprint
436
- || discovery?.accountBinding !== binding.accountScope || discovery?.runtimeVersion !== binding.cliVersion
437
- || parallelism.clis?.[member.cli]?.accountScope && parallelism.clis[member.cli].accountScope !== binding.accountScope) {
438
- deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Catalog and current installation scope differ');
439
- }
440
- reasons.push(...validateWorkerPolicyRoute({ policy, role, inferenceProvider: row.llmProvider, apiModelId: row.apiModelId,
441
- selectedModel: member.model, effort, identityEvidence: binding.identityEvidence, observation,
442
- wrapperPin, ...(substitution ? { substitution: request.substitution } : {}), dataClass: request.dataClass, now, context: request.context }));
443
- if (request.requireDifferentFamily && (observation.family === 'unknown' || observation.family === request.reviewOf.family))
444
- deny('OG_MODEL_UNAPPROVED', '/worker/family', 'Required review must use a different observed family');
445
- try {
446
- const { adapter } = resolveWorkerGroupInstallation(parallelism, member.cli, snapshot.registry);
447
- const configuredProfile = parallelism.clis?.[member.cli]?.invocationProfile;
448
- const profilePointer = `/parallelism/clis/${member.cli}/invocationProfile`;
449
- if (configuredProfile && !Object.hasOwn(adapter.invocationProfiles ?? {}, configuredProfile)) {
450
- deny('OG_CONFIG_REFERENCE', profilePointer, 'Configured invocation profile is not registered for this adapter; select a registered profile.');
451
- }
452
- else if (configuredProfile && configuredProfile !== binding.profile) {
453
- deny('OG_PIN_MISMATCH', profilePointer, 'Configured invocation profile differs from installation qualification; qualify the selected profile again.');
454
- }
455
- const configuredServer = parallelism.clis?.[member.cli]?.mcpServerRef;
456
- if (transport === 'mcp' && configuredServer && configuredServer !== binding.mcp?.serverRef)
457
- throw new Error('Configured MCP server changed');
458
- if (!hasOperatorWorkerNativeRenderer(adapter) && (adapter.capabilities.modelPin === 'unsupported' || !adapter.capabilities.nativeFallbackControllable))
459
- throw new Error('Exact model or fallback control is unqualified; qualify an adapter with these controls');
460
- if (policy.guards?.denySelfDelegation !== false && !adapter.capabilities.nestedAgentsControllable)
461
- throw new Error('Child-agent control is unqualified. Review models.json defaults.workerSelection.guards.denySelfDelegation; false permits uncontrolled child agents without qualifying their behavior.');
462
- if (binding.identityEvidence === 'backend-attested' && adapter.capabilities.modelPin !== 'observed')
463
- throw new Error('Requested-only mapping cannot attest backend identity');
464
- if (!hasOperatorWorkerNativeRenderer(adapter) && group.execution?.sandbox && !adapter.capabilities.sandboxModes.includes(group.execution.sandbox))
465
- throw new Error('Requested sandbox mode is unqualified');
466
- assertWorkerLaunchMode(adapter, permissions);
467
- qualifyWorkerInvocationProfile(adapter, { profile: binding.profile, transport, cliVersion: binding.cliVersion,
468
- policy, permissions, effort, boundary: binding.boundary,
469
- requiredControls: request.requiredControls, mcp: binding.mcp });
470
- if (hasOperatorWorkerNativeRenderer(adapter) && transport === 'shell' && permissions.sandbox === 'external') {
471
- reasons.push({ code: 'OG_SANDBOX_EXTERNAL', blocking: false,
472
- pointer: `/parallelism/groups/${groupId}/execution/sandbox`,
473
- message: `${adapter.provider} CLI's own sandbox is disabled by explicit external policy; isolation relies on the per-task worktree and launcher resource scope.` });
474
- }
475
- const { maxAttempts: _maxAttempts, ...budgetLimits } = plan.ceilings;
476
- const budgetDimensions = { tokens: 'tokens', cost: 'costUsd', 'wall-time': 'wallSeconds' };
477
- const enforceable = adapter.capabilities.hardBudget.map(key => budgetDimensions[key]);
478
- for (const message of validateWorkerBudgetEnforcement({ ...parallelism.budgets, perTask: budgetLimits }, enforceable))
479
- deny('OG_BUDGET_UNMEASURABLE', '/parallelism/budgets', message);
480
- }
481
- catch (error) {
482
- deny('OG_ADAPTER_UNQUALIFIED', pointer, String(error));
483
- }
484
- let key;
485
- try {
486
- key = workerModelKey(row.llmProvider, row.apiModelId);
487
- }
488
- catch {
489
- plan.exclusions.push({ ...tuple, diagnostics: reasons });
490
- continue;
491
- }
492
- if (snapshot.constraints?.revokedModels?.includes(key))
493
- deny('OG_MODEL_UNAPPROVED', '/constraints/revokedModels', 'Current model revocation');
494
- if (snapshot.constraints?.unavailableMembers?.includes(`${groupId}/${member.id}`))
495
- deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Member is currently unavailable');
496
- const premium = ['premium', 'max-quality'].includes(guidance.tier) || observation.pricing.costClass === 'premium';
497
- // Worker candidates always invoke a model; deterministic/routine hints must not bypass premium authority.
498
- const authority = routeModelTier({ ...request.authority, deterministic: false, routine: false,
499
- defaultTier: guidance.tier === 'economy' ? 1 : 2, requestedPremium: premium || request.authority?.requestedPremium });
500
- if (authority.requiresConfirmation)
501
- 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)`);
502
- const installation = parallelism.clis?.[member.cli];
503
- const caps = [parallelism.max_parallel_subagents, group.maxParallel, parallelism.admission?.groupCaps?.[groupId],
504
- parallelism.admission?.memberCaps?.[`${groupId}/${member.id}`], parallelism.admission?.cliCaps?.[member.cli],
505
- parallelism.admission?.modelCaps?.[key], installation && parallelism.admission?.quotaCaps?.[installation.quotaScope],
506
- snapshot.constraints?.maxParallel, request.maxParallel].filter((value) => value !== undefined);
507
- const effectiveMaxParallel = caps.length ? Math.min(...caps) : null;
508
- if (effectiveMaxParallel === 0)
509
- deny('OG_ADAPTER_UNQUALIFIED', '/constraints/maxParallel', 'Current capacity ceiling is zero');
510
- if (!reasons.some(reason => reason.blocking !== false))
511
- plan.eligible.push({ ...tuple, cli: member.cli,
512
- inferenceProvider: row.llmProvider, modelKey: key, family: observation.family, identityEvidence: binding.identityEvidence,
513
- profile: binding.profile, transport,
514
- permissions: structuredClone(permissions), context: workerContextHeadroom(request.context, observation),
515
- observedAt: observation.observedAt, verifiedAt: observation.verifiedAt, observationRef: observation.recordId,
516
- effectiveMaxParallel, diagnostics: reasons });
517
- }
518
- if (reasons.some(reason => reason.blocking !== false))
519
- plan.exclusions.push({ ...tuple, diagnostics: reasons });
520
- }
521
- }
522
- }
523
- if (request.choice) {
524
- if (!request.choice.rationale.trim())
525
- block('OG_CONFIG_REFERENCE', '/request/choice/rationale', 'Conductor choice requires a rationale');
526
- plan.selected = plan.eligible.find(tuple => matchesChoice(tuple, request.choice)) ?? null;
527
- if (!plan.selected)
528
- block('OG_MODEL_UNAPPROVED', '/request/choice', 'Conductor choice is outside the eligible set');
529
- }
530
- else
531
- plan.selected = plan.eligible[0] ?? null;
532
- if (plan.diagnostics.length)
533
- plan.selected = null;
534
- if (plan.selected) {
535
- plan.status = 'eligible';
536
- plan.diagnostics.push(...plan.selected.diagnostics.filter(item => item.code === 'OG_SANDBOX_EXTERNAL'));
537
- plan.choice = { source: request.choice ? 'conductor' : 'default', rationale: request.choice?.rationale ?? 'Role group order, member priority/declaration order, then qualified effort ladder',
538
- ...(request.substitution && matchesChoice(plan.selected, request.substitution) ? { substitution: structuredClone(request.substitution) } : {}) };
539
- }
540
- plan.alternatives = plan.eligible.filter(tuple => tuple !== plan.selected);
541
- return plan;
542
- }
543
252
  //# sourceMappingURL=resolver.js.map
@@ -0,0 +1,12 @@
1
+ /** Bind pure artifact declarations to the installed adapter's argv and invocation identity. */
2
+ import { validateWorkerArtifacts } from '../serve/worker-artifacts.mjs';
3
+
4
+ export function buildInstalledWorkerTransport(instance, input, profile, invocation, executable, environment) {
5
+ const artifacts = typeof instance.buildWorkerArtifacts === 'function' ? instance.buildWorkerArtifacts(input) : undefined;
6
+ validateWorkerArtifacts({ ...input, artifacts });
7
+ return {
8
+ argv: [executable, ...instance.buildWorkerArgs(input, profile, input.permissions)],
9
+ stdin: invocation.stdin, envNames: Object.keys(environment),
10
+ ...(artifacts === undefined ? {} : { artifacts }),
11
+ };
12
+ }