@aiwg/cli 2026.10.9 → 2026.10.10
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 +197 -3
- 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/use-addon-profile.js +72 -0
- package/dist/src/cli/handlers/use.js +191 -230
- 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-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 +6 -6
- 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 +197 -3
- package/dist/src/models/provider-policy.js +7 -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/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/smiths/context-pipeline/parallelism-section.js +19 -7
- package/package.json +9 -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/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-final.mjs +143 -0
- package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
- package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
- package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
- package/tools/ralph-external/session-launcher.mjs +73 -217
- package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
|
@@ -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 {
|
|
10
|
-
|
|
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
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Resolves direct-worker bindings, member context and offline dispatch eligibility.
|
|
2
|
+
import { resolveWorkerGroupInstallation } from '../config/aiwg-config.js';
|
|
3
|
+
import { resolveWorkerPlan } from './resolver.js';
|
|
4
|
+
export { memberReferences, memberLabels, describeConfigContext, allConfiguredCliIds, isCliRequired, advisoryStatus } from './worker-member-references.js';
|
|
5
|
+
export const SMOKE_NOTE = 'A version probe alone leaves profiles unqualified. To record direct CLI operator approval, run aiwg doctor --probe-workers --qualify; a version probe is not an authenticated smoke run (see #3005).';
|
|
6
|
+
const ADAPTER_REFERENCE = /^(providers|workerAdapters):(.+)$/;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the configured binding by exact, case-sensitive namespace:id
|
|
9
|
+
* lookup — the same shape `resolveWorkerAdapter` (provider-policy.ts) uses
|
|
10
|
+
* for dispatch and `validateParallelismConfig` uses for config validation.
|
|
11
|
+
* No alias normalization happens for either namespace, so a binding this
|
|
12
|
+
* accepts is also a binding the public config validator accepts, and a
|
|
13
|
+
* custom `workerAdapters:<id>` reference resolves against
|
|
14
|
+
* `registry.workerAdapters`, never against the provider-alias registry.
|
|
15
|
+
*
|
|
16
|
+
* For the `providers` namespace this falls back from `workerAdapter` to
|
|
17
|
+
* `workerHealthAdapter` when no direct-dispatch binding is pinned yet — the
|
|
18
|
+
* install/version metadata is identical either way, and that is exactly
|
|
19
|
+
* what lets install readiness and dispatch eligibility disagree honestly
|
|
20
|
+
* (installed and in range, but the resolver still has no workerAdapter to
|
|
21
|
+
* dispatch through).
|
|
22
|
+
*/
|
|
23
|
+
export function resolveDirectBinding(registry, configuredAdapter, cli) {
|
|
24
|
+
let namespace;
|
|
25
|
+
let id;
|
|
26
|
+
if (configuredAdapter !== undefined) {
|
|
27
|
+
const match = ADAPTER_REFERENCE.exec(configuredAdapter.trim());
|
|
28
|
+
if (!match)
|
|
29
|
+
return { error: `Invalid worker adapter reference '${configuredAdapter}'` };
|
|
30
|
+
[, namespace, id] = match;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
namespace = 'providers';
|
|
34
|
+
id = cli;
|
|
35
|
+
}
|
|
36
|
+
if (namespace === 'providers') {
|
|
37
|
+
const entry = registry.providers[id];
|
|
38
|
+
const adapter = entry?.workerAdapter ?? entry?.workerHealthAdapter;
|
|
39
|
+
if (!adapter)
|
|
40
|
+
return { error: `No direct worker profile is registered for binding 'providers:${id}'` };
|
|
41
|
+
return { adapter, adapterRef: `providers:${id}` };
|
|
42
|
+
}
|
|
43
|
+
const adapter = registry.workerAdapters?.[id];
|
|
44
|
+
if (!adapter)
|
|
45
|
+
return { error: `Unknown worker adapter reference 'workerAdapters:${id}'` };
|
|
46
|
+
return { adapter, adapterRef: `workerAdapters:${id}` };
|
|
47
|
+
}
|
|
48
|
+
const offlineDirectContext = () => ({ estimator: 'doctor-offline-direct-context-v1', briefTokens: 0, inputTokens: 0, toolOutputAllowance: 0, reservedOutput: 0 });
|
|
49
|
+
/**
|
|
50
|
+
* Dispatch eligibility from the actual offline dispatch resolver
|
|
51
|
+
* (`resolveWorkerPlan`): called once per role for every group member
|
|
52
|
+
* referencing this cli, never re-derived. A resolvable binding alone is not
|
|
53
|
+
* reported as eligible — eligibility requires the resolver to actually place
|
|
54
|
+
* at least one referencing member in its eligible/alternatives set. Refusals
|
|
55
|
+
* are member-specific and name the resolver's own missing-evidence or policy
|
|
56
|
+
* diagnostics.
|
|
57
|
+
*/
|
|
58
|
+
function resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs) {
|
|
59
|
+
const snapshot = {
|
|
60
|
+
config: resolverSnapshot.config, models: resolverSnapshot.models, modelDigests: resolverSnapshot.modelDigests,
|
|
61
|
+
catalog: resolverSnapshot.catalog, registry, installations: {},
|
|
62
|
+
};
|
|
63
|
+
const diagnostics = new Map();
|
|
64
|
+
const uncovered = [];
|
|
65
|
+
for (const ref of refs) {
|
|
66
|
+
if (!ref.enabled)
|
|
67
|
+
continue;
|
|
68
|
+
const matches = (tuple) => tuple.group === ref.groupId && tuple.member === ref.memberId;
|
|
69
|
+
const seen = [];
|
|
70
|
+
let eligibleHere = false;
|
|
71
|
+
for (const role of ref.member.roles) {
|
|
72
|
+
const plan = resolveWorkerPlan(snapshot, {
|
|
73
|
+
role: role, group: ref.groupId, member: ref.memberId,
|
|
74
|
+
now: resolverSnapshot.now, context: offlineDirectContext(),
|
|
75
|
+
});
|
|
76
|
+
if ([plan.selected, ...plan.alternatives].some(tuple => tuple && matches(tuple))) {
|
|
77
|
+
eligibleHere = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
const exclusion = plan.exclusions.find(matches);
|
|
81
|
+
for (const diagnostic of exclusion?.diagnostics ?? plan.diagnostics) {
|
|
82
|
+
// Only an exact member pointer is generalized; other config pointers
|
|
83
|
+
// remain distinct even when their diagnostic text happens to match.
|
|
84
|
+
const memberPointer = `/parallelism/groups/${ref.groupId}/members/${ref.memberId}`;
|
|
85
|
+
const pointer = diagnostic.pointer === memberPointer || diagnostic.pointer.startsWith(`${memberPointer}/`)
|
|
86
|
+
? `/parallelism/groups/${ref.groupId}/members${diagnostic.pointer.slice(memberPointer.length)}`
|
|
87
|
+
: diagnostic.pointer;
|
|
88
|
+
seen.push({ ...diagnostic, pointer, members: [`${ref.groupId}/${ref.memberId}`],
|
|
89
|
+
roles: diagnostic.roles ?? [role] });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (eligibleHere) {
|
|
93
|
+
return { eligibility: 'eligible',
|
|
94
|
+
detail: `Dispatch resolver accepts cli '${cli}' for group '${ref.groupId}' member '${ref.memberId}'. `
|
|
95
|
+
+ `Dispatch eligibility: eligible. ${SMOKE_NOTE}` };
|
|
96
|
+
}
|
|
97
|
+
if (!seen.length)
|
|
98
|
+
uncovered.push(`${ref.groupId}/${ref.memberId}`);
|
|
99
|
+
for (const diagnostic of seen) {
|
|
100
|
+
const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message,
|
|
101
|
+
diagnostic.blocking, diagnostic.origins]);
|
|
102
|
+
const shared = diagnostics.get(key);
|
|
103
|
+
if (shared) {
|
|
104
|
+
shared.members = [...new Set([...shared.members, ...diagnostic.members])];
|
|
105
|
+
shared.roles = [...new Set([...shared.roles, ...diagnostic.roles])];
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
diagnostics.set(key, diagnostic);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!diagnostics.size && !uncovered.length) {
|
|
112
|
+
return { eligibility: 'blocked',
|
|
113
|
+
detail: `Dispatch resolver has no enabled group member to evaluate for cli '${cli}'. `
|
|
114
|
+
+ `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
|
|
115
|
+
}
|
|
116
|
+
const distinct = [...diagnostics.values()];
|
|
117
|
+
const reasons = distinct.map(diagnostic => `${diagnostic.code} ${diagnostic.pointer}: ${diagnostic.message}`
|
|
118
|
+
+ ` [roles: ${diagnostic.roles.join(', ')}; members: ${diagnostic.members.join(', ')}]`);
|
|
119
|
+
if (uncovered.length)
|
|
120
|
+
reasons.push(`No qualified route [members: ${uncovered.join(', ')}]`);
|
|
121
|
+
return { eligibility: 'blocked', diagnostics: distinct,
|
|
122
|
+
detail: `Dispatch resolver refuses cli '${cli}': ${reasons.join('; ')}. `
|
|
123
|
+
+ `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Binding-only fallback used only when the caller has not supplied a
|
|
127
|
+
* `resolverSnapshot` (for example a direct unit-level call that never
|
|
128
|
+
* resolved the wider model config/catalog). States plainly that this is a
|
|
129
|
+
* binding-level result, not the dispatch resolver's verdict.
|
|
130
|
+
*/
|
|
131
|
+
function resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady) {
|
|
132
|
+
try {
|
|
133
|
+
const resolved = resolveWorkerGroupInstallation(parallelism, cli, registry);
|
|
134
|
+
return { eligibility: 'eligible',
|
|
135
|
+
detail: `Resolver accepts cli '${cli}' (binding '${resolved.installation.adapter}'). Dispatch eligibility: eligible at binding level only — `
|
|
136
|
+
+ `no resolver snapshot was supplied, so group/member qualification was not evaluated. `
|
|
137
|
+
+ `Launch additionally requires current installation evidence and model observation under the dispatch resolver's gates. `
|
|
138
|
+
+ `${SMOKE_NOTE}` };
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
142
|
+
const readyNote = installReady
|
|
143
|
+
? ' The CLI is installed with an in-range version, but dispatch stays refused for the reason above; '
|
|
144
|
+
+ `an authenticated smoke run (see #3005) alone does not satisfy the resolver gates. ${SMOKE_NOTE}`
|
|
145
|
+
: ` ${SMOKE_NOTE}`;
|
|
146
|
+
return { eligibility: 'blocked',
|
|
147
|
+
detail: `Resolver refuses cli '${cli}': ${reason}. Dispatch eligibility: blocked.${readyNote}` };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function checkDispatchEligibility(parallelism, registry, cli, installReady, resolverSnapshot, refs) {
|
|
151
|
+
if (resolverSnapshot && refs.length)
|
|
152
|
+
return resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs);
|
|
153
|
+
return resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=worker-direct-readiness-policy.js.map
|