@aiwg/cli 2026.10.8 → 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/artifacts/cli.js +9 -5
- package/dist/src/auth/config.js +3 -8
- package/dist/src/auth/resource-credentials.js +35 -2
- package/dist/src/cli/handlers/diagnose.js +13 -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/resource-versions.js +8 -5
- package/dist/src/cli/handlers/script-runner.js +13 -1
- package/dist/src/cli/handlers/use-addon-profile.js +72 -0
- package/dist/src/cli/handlers/use.js +209 -233
- 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/resources/web-release.js +76 -65
- 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/dist/src/storage/backends/fortemi.js +33 -29
- package/dist/src/storage/backends/logseq.js +16 -3
- package/dist/src/storage/backends/postgres.js +74 -33
- package/dist/src/storage/backends/postgrest.js +37 -13
- package/dist/src/storage/config.js +6 -1
- package/dist/src/uhp/config.js +4 -0
- 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
|
@@ -57,11 +57,9 @@
|
|
|
57
57
|
* performs no live-qualification recording and requires no evidence files.
|
|
58
58
|
*/
|
|
59
59
|
import { tmpdir } from 'node:os';
|
|
60
|
+
import { advisoryStatus, allConfiguredCliIds, checkDispatchEligibility, describeConfigContext, isCliRequired, memberLabels, memberReferences, resolveDirectBinding, SMOKE_NOTE, } from './worker-direct-readiness-policy.js';
|
|
60
61
|
import { isAbsolute, resolve as resolvePath } from 'node:path';
|
|
61
62
|
import { boundedVersionProbe, normalizeWorkerVersionBanner, workerVersionProbeDefaults, workerVersionProbeEnvironment, workerVersionRangeMismatch, } from './worker-installation.mjs';
|
|
62
|
-
import { resolveWorkerGroupInstallation } from '../config/aiwg-config.js';
|
|
63
|
-
import { resolveWorkerPlan } from './resolver.js';
|
|
64
|
-
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).';
|
|
65
63
|
function truncateBanner(banner, limit = 200) {
|
|
66
64
|
const singleLine = banner.replace(/[\r\n]+/g, '\\n');
|
|
67
65
|
return singleLine.length > limit ? `${singleLine.slice(0, limit)}…` : singleLine;
|
|
@@ -100,214 +98,6 @@ export async function probeDirectWorkerVersion(executable, versionArgv, options
|
|
|
100
98
|
return { ok: false, reason: error?.code === 'ENOENT' ? 'not-installed' : 'probe-failed' };
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
|
-
const ADAPTER_REFERENCE = /^(providers|workerAdapters):(.+)$/;
|
|
104
|
-
/**
|
|
105
|
-
* Resolve the configured binding by exact, case-sensitive namespace:id
|
|
106
|
-
* lookup — the same shape `resolveWorkerAdapter` (provider-policy.ts) uses
|
|
107
|
-
* for dispatch and `validateParallelismConfig` uses for config validation.
|
|
108
|
-
* No alias normalization happens for either namespace, so a binding this
|
|
109
|
-
* accepts is also a binding the public config validator accepts, and a
|
|
110
|
-
* custom `workerAdapters:<id>` reference resolves against
|
|
111
|
-
* `registry.workerAdapters`, never against the provider-alias registry.
|
|
112
|
-
*
|
|
113
|
-
* For the `providers` namespace this falls back from `workerAdapter` to
|
|
114
|
-
* `workerHealthAdapter` when no direct-dispatch binding is pinned yet — the
|
|
115
|
-
* install/version metadata is identical either way, and that is exactly
|
|
116
|
-
* what lets install readiness and dispatch eligibility disagree honestly
|
|
117
|
-
* (installed and in range, but the resolver still has no workerAdapter to
|
|
118
|
-
* dispatch through).
|
|
119
|
-
*/
|
|
120
|
-
function resolveDirectBinding(registry, configuredAdapter, cli) {
|
|
121
|
-
let namespace;
|
|
122
|
-
let id;
|
|
123
|
-
if (configuredAdapter !== undefined) {
|
|
124
|
-
const match = ADAPTER_REFERENCE.exec(configuredAdapter.trim());
|
|
125
|
-
if (!match)
|
|
126
|
-
return { error: `Invalid worker adapter reference '${configuredAdapter}'` };
|
|
127
|
-
[, namespace, id] = match;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
namespace = 'providers';
|
|
131
|
-
id = cli;
|
|
132
|
-
}
|
|
133
|
-
if (namespace === 'providers') {
|
|
134
|
-
const entry = registry.providers[id];
|
|
135
|
-
const adapter = entry?.workerAdapter ?? entry?.workerHealthAdapter;
|
|
136
|
-
if (!adapter)
|
|
137
|
-
return { error: `No direct worker profile is registered for binding 'providers:${id}'` };
|
|
138
|
-
return { adapter, adapterRef: `providers:${id}` };
|
|
139
|
-
}
|
|
140
|
-
const adapter = registry.workerAdapters?.[id];
|
|
141
|
-
if (!adapter)
|
|
142
|
-
return { error: `Unknown worker adapter reference 'workerAdapters:${id}'` };
|
|
143
|
-
return { adapter, adapterRef: `workerAdapters:${id}` };
|
|
144
|
-
}
|
|
145
|
-
/** Every group member referencing this cli, including disabled ones (for context). */
|
|
146
|
-
function memberReferences(parallelism, cli) {
|
|
147
|
-
const refs = [];
|
|
148
|
-
for (const [groupId, group] of Object.entries(parallelism.groups ?? {})) {
|
|
149
|
-
for (const member of group?.members ?? []) {
|
|
150
|
-
if (member.cli !== cli)
|
|
151
|
-
continue;
|
|
152
|
-
refs.push({ groupId, memberId: member.id, member,
|
|
153
|
-
enabled: member.enabled !== false && parallelism.clis?.[cli]?.enabled !== false });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return refs.sort((a, b) => `${a.groupId}/${a.memberId}`.localeCompare(`${b.groupId}/${b.memberId}`));
|
|
157
|
-
}
|
|
158
|
-
function memberLabels(refs) {
|
|
159
|
-
return refs.map(ref => `${ref.groupId}/${ref.memberId}${ref.enabled ? '' : ' (disabled)'}`);
|
|
160
|
-
}
|
|
161
|
-
function describeConfigContext(members) {
|
|
162
|
-
return members.length ? `members: ${members.join(', ')}` : 'members: no group member references this cli';
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Union of every cli id a readiness pass must consider: explicit
|
|
166
|
-
* `parallelism.clis` entries plus any cli id referenced only implicitly by a
|
|
167
|
-
* group member (canonical default binding, no explicit installation entry).
|
|
168
|
-
*/
|
|
169
|
-
function allConfiguredCliIds(parallelism, cli) {
|
|
170
|
-
const ids = new Set(Object.keys(parallelism.clis ?? {}));
|
|
171
|
-
for (const group of Object.values(parallelism.groups ?? {})) {
|
|
172
|
-
for (const member of group?.members ?? [])
|
|
173
|
-
ids.add(member.cli);
|
|
174
|
-
}
|
|
175
|
-
return [...ids].filter(id => cli === undefined || id === cli).sort();
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* A cli is "required" only when an enabled group member referencing it also
|
|
179
|
-
* covers a role its group lists in `requiredRoles`. Every other configured
|
|
180
|
-
* cli is optional: installation failures stay advisory (never 'error', so
|
|
181
|
-
* they never flip doctor's exit code) until something actually requires it.
|
|
182
|
-
*/
|
|
183
|
-
function isCliRequired(parallelism, cli) {
|
|
184
|
-
for (const group of Object.values(parallelism.groups ?? {})) {
|
|
185
|
-
const required = new Set(group?.requiredRoles ?? []);
|
|
186
|
-
if (!required.size)
|
|
187
|
-
continue;
|
|
188
|
-
for (const member of group?.members ?? []) {
|
|
189
|
-
if (member.cli !== cli || member.enabled === false)
|
|
190
|
-
continue;
|
|
191
|
-
if (parallelism.clis?.[cli]?.enabled === false)
|
|
192
|
-
continue;
|
|
193
|
-
if (member.roles.some(role => required.has(role)))
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
/** Cap a status at 'warn' for optional (non-required) clis so an unreferenced
|
|
200
|
-
* or not-yet-wired profile never flips doctor's exit code. */
|
|
201
|
-
function advisoryStatus(status, required) {
|
|
202
|
-
return required || status !== 'error' ? status : 'warn';
|
|
203
|
-
}
|
|
204
|
-
const offlineDirectContext = () => ({ estimator: 'doctor-offline-direct-context-v1', briefTokens: 0, inputTokens: 0, toolOutputAllowance: 0, reservedOutput: 0 });
|
|
205
|
-
/**
|
|
206
|
-
* Dispatch eligibility from the actual offline dispatch resolver
|
|
207
|
-
* (`resolveWorkerPlan`): called once per role for every group member
|
|
208
|
-
* referencing this cli, never re-derived. A resolvable binding alone is not
|
|
209
|
-
* reported as eligible — eligibility requires the resolver to actually place
|
|
210
|
-
* at least one referencing member in its eligible/alternatives set. Refusals
|
|
211
|
-
* are member-specific and name the resolver's own missing-evidence or policy
|
|
212
|
-
* diagnostics.
|
|
213
|
-
*/
|
|
214
|
-
function resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs) {
|
|
215
|
-
const snapshot = {
|
|
216
|
-
config: resolverSnapshot.config, models: resolverSnapshot.models, modelDigests: resolverSnapshot.modelDigests,
|
|
217
|
-
catalog: resolverSnapshot.catalog, registry, installations: {},
|
|
218
|
-
};
|
|
219
|
-
const diagnostics = new Map();
|
|
220
|
-
const uncovered = [];
|
|
221
|
-
for (const ref of refs) {
|
|
222
|
-
if (!ref.enabled)
|
|
223
|
-
continue;
|
|
224
|
-
const matches = (tuple) => tuple.group === ref.groupId && tuple.member === ref.memberId;
|
|
225
|
-
const seen = [];
|
|
226
|
-
let eligibleHere = false;
|
|
227
|
-
for (const role of ref.member.roles) {
|
|
228
|
-
const plan = resolveWorkerPlan(snapshot, {
|
|
229
|
-
role: role, group: ref.groupId, member: ref.memberId,
|
|
230
|
-
now: resolverSnapshot.now, context: offlineDirectContext(),
|
|
231
|
-
});
|
|
232
|
-
if ([plan.selected, ...plan.alternatives].some(tuple => tuple && matches(tuple))) {
|
|
233
|
-
eligibleHere = true;
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
const exclusion = plan.exclusions.find(matches);
|
|
237
|
-
for (const diagnostic of exclusion?.diagnostics ?? plan.diagnostics) {
|
|
238
|
-
// Only an exact member pointer is generalized; other config pointers
|
|
239
|
-
// remain distinct even when their diagnostic text happens to match.
|
|
240
|
-
const memberPointer = `/parallelism/groups/${ref.groupId}/members/${ref.memberId}`;
|
|
241
|
-
const pointer = diagnostic.pointer === memberPointer || diagnostic.pointer.startsWith(`${memberPointer}/`)
|
|
242
|
-
? `/parallelism/groups/${ref.groupId}/members${diagnostic.pointer.slice(memberPointer.length)}`
|
|
243
|
-
: diagnostic.pointer;
|
|
244
|
-
seen.push({ ...diagnostic, pointer, members: [`${ref.groupId}/${ref.memberId}`],
|
|
245
|
-
roles: diagnostic.roles ?? [role] });
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (eligibleHere) {
|
|
249
|
-
return { eligibility: 'eligible',
|
|
250
|
-
detail: `Dispatch resolver accepts cli '${cli}' for group '${ref.groupId}' member '${ref.memberId}'. `
|
|
251
|
-
+ `Dispatch eligibility: eligible. ${SMOKE_NOTE}` };
|
|
252
|
-
}
|
|
253
|
-
if (!seen.length)
|
|
254
|
-
uncovered.push(`${ref.groupId}/${ref.memberId}`);
|
|
255
|
-
for (const diagnostic of seen) {
|
|
256
|
-
const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message,
|
|
257
|
-
diagnostic.blocking, diagnostic.origins]);
|
|
258
|
-
const shared = diagnostics.get(key);
|
|
259
|
-
if (shared) {
|
|
260
|
-
shared.members = [...new Set([...shared.members, ...diagnostic.members])];
|
|
261
|
-
shared.roles = [...new Set([...shared.roles, ...diagnostic.roles])];
|
|
262
|
-
}
|
|
263
|
-
else
|
|
264
|
-
diagnostics.set(key, diagnostic);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (!diagnostics.size && !uncovered.length) {
|
|
268
|
-
return { eligibility: 'blocked',
|
|
269
|
-
detail: `Dispatch resolver has no enabled group member to evaluate for cli '${cli}'. `
|
|
270
|
-
+ `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
|
|
271
|
-
}
|
|
272
|
-
const distinct = [...diagnostics.values()];
|
|
273
|
-
const reasons = distinct.map(diagnostic => `${diagnostic.code} ${diagnostic.pointer}: ${diagnostic.message}`
|
|
274
|
-
+ ` [roles: ${diagnostic.roles.join(', ')}; members: ${diagnostic.members.join(', ')}]`);
|
|
275
|
-
if (uncovered.length)
|
|
276
|
-
reasons.push(`No qualified route [members: ${uncovered.join(', ')}]`);
|
|
277
|
-
return { eligibility: 'blocked', diagnostics: distinct,
|
|
278
|
-
detail: `Dispatch resolver refuses cli '${cli}': ${reasons.join('; ')}. `
|
|
279
|
-
+ `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Binding-only fallback used only when the caller has not supplied a
|
|
283
|
-
* `resolverSnapshot` (for example a direct unit-level call that never
|
|
284
|
-
* resolved the wider model config/catalog). States plainly that this is a
|
|
285
|
-
* binding-level result, not the dispatch resolver's verdict.
|
|
286
|
-
*/
|
|
287
|
-
function resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady) {
|
|
288
|
-
try {
|
|
289
|
-
const resolved = resolveWorkerGroupInstallation(parallelism, cli, registry);
|
|
290
|
-
return { eligibility: 'eligible',
|
|
291
|
-
detail: `Resolver accepts cli '${cli}' (binding '${resolved.installation.adapter}'). Dispatch eligibility: eligible at binding level only — `
|
|
292
|
-
+ `no resolver snapshot was supplied, so group/member qualification was not evaluated. `
|
|
293
|
-
+ `Launch additionally requires current installation evidence and model observation under the dispatch resolver's gates. `
|
|
294
|
-
+ `${SMOKE_NOTE}` };
|
|
295
|
-
}
|
|
296
|
-
catch (error) {
|
|
297
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
298
|
-
const readyNote = installReady
|
|
299
|
-
? ' The CLI is installed with an in-range version, but dispatch stays refused for the reason above; '
|
|
300
|
-
+ `an authenticated smoke run (see #3005) alone does not satisfy the resolver gates. ${SMOKE_NOTE}`
|
|
301
|
-
: ` ${SMOKE_NOTE}`;
|
|
302
|
-
return { eligibility: 'blocked',
|
|
303
|
-
detail: `Resolver refuses cli '${cli}': ${reason}. Dispatch eligibility: blocked.${readyNote}` };
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
function checkDispatchEligibility(parallelism, registry, cli, installReady, resolverSnapshot, refs) {
|
|
307
|
-
if (resolverSnapshot && refs.length)
|
|
308
|
-
return resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs);
|
|
309
|
-
return resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady);
|
|
310
|
-
}
|
|
311
101
|
/**
|
|
312
102
|
* Read-only readiness for every configured worker. Starts no process unless
|
|
313
103
|
* `probe` is supplied; without a prober each cli gets a config-only install
|
|
@@ -325,14 +115,15 @@ export async function diagnoseDirectWorkerReadiness(input) {
|
|
|
325
115
|
findings: [],
|
|
326
116
|
};
|
|
327
117
|
if (!report.enabled) {
|
|
118
|
+
const members = allConfiguredCliIds(parallelism, input.cli).flatMap(cli => memberLabels(memberReferences(parallelism, cli)));
|
|
328
119
|
report.findings.push({ cli: '*', kind: 'dispatch', profile: null, executable: null, installedVersion: null,
|
|
329
120
|
status: 'info', code: 'WORKER_DIRECT_DISABLED',
|
|
330
|
-
message: `Group dispatch is disabled (config ${configFile} key /parallelism/worker_dispatch); install/version checks do not apply and onboarding cannot proceed
|
|
121
|
+
message: `Group dispatch is disabled (config ${configFile} key /parallelism/worker_dispatch); install/version checks do not apply and onboarding cannot proceed. (${describeConfigContext(members)})`,
|
|
331
122
|
nextAction: 'enable-worker-dispatch-to-onboard',
|
|
332
123
|
configFile, configKey: '/parallelism/worker_dispatch',
|
|
333
124
|
readiness: null, eligibility: 'blocked',
|
|
334
125
|
eligibilityDetail: 'Dispatch eligibility: blocked — group dispatch is disabled.',
|
|
335
|
-
members
|
|
126
|
+
members });
|
|
336
127
|
return report;
|
|
337
128
|
}
|
|
338
129
|
const cliIds = allConfiguredCliIds(parallelism, input.cli);
|
|
@@ -363,9 +154,9 @@ export async function diagnoseDirectWorkerReadiness(input) {
|
|
|
363
154
|
status: 'info', code: 'WORKER_DIRECT_CLI_DISABLED',
|
|
364
155
|
message: `Dispatch is disabled for cli '${cli}' (config ${configFile} key ${configKey}/enabled); the version probe was skipped. Installation readiness: skipped. (${describeConfigContext(members)})`,
|
|
365
156
|
nextAction: 'enable-cli-to-onboard',
|
|
366
|
-
configFile, configKey
|
|
157
|
+
configFile, configKey: `${configKey}/enabled`, readiness: 'skipped', members });
|
|
367
158
|
const { eligibility, detail, diagnostics } = checkDispatchEligibility(parallelism, registry, cli, false, resolverSnapshot, refs);
|
|
368
|
-
pushDispatch(cli, configured.invocationProfile ?? null, 'info', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, configKey
|
|
159
|
+
pushDispatch(cli, configured.invocationProfile ?? null, 'info', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, `${configKey}/enabled`, members, 'enable-cli-to-onboard', diagnostics);
|
|
369
160
|
return;
|
|
370
161
|
}
|
|
371
162
|
const resolved = resolveDirectBinding(registry, configured?.adapter, cli);
|
|
@@ -399,9 +190,9 @@ export async function diagnoseDirectWorkerReadiness(input) {
|
|
|
399
190
|
status: advisoryStatus('error', required), code: 'WORKER_DIRECT_PROFILE_UNKNOWN',
|
|
400
191
|
message: configured?.invocationProfile
|
|
401
192
|
? `Configured invocation profile '${configured.invocationProfile}' is not registered for binding '${adapterRef}' (config ${configFile} key ${configKey}/invocationProfile). Installation readiness: not-ready. (${describeConfigContext(members)})`
|
|
402
|
-
: `Cli '${cli}' binding '${adapterRef}' has no single direct profile to check (config ${configFile} key ${configKey}); configure invocationProfile explicitly. Installation readiness: not-ready. (${describeConfigContext(members)})`,
|
|
193
|
+
: `Cli '${cli}' binding '${adapterRef}' has no single direct profile to check (config ${configFile} key ${configKey}/invocationProfile); configure invocationProfile explicitly. Installation readiness: not-ready. (${describeConfigContext(members)})`,
|
|
403
194
|
nextAction: 'review-worker-configuration',
|
|
404
|
-
configFile, configKey
|
|
195
|
+
configFile, configKey: `${configKey}/invocationProfile`, readiness: 'not-ready', members });
|
|
405
196
|
const { eligibility, detail, diagnostics } = checkDispatchEligibility(parallelism, registry, cli, false, resolverSnapshot, refs);
|
|
406
197
|
pushDispatch(cli, configured?.invocationProfile ?? null, 'warn', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, configKey, members, 'review-worker-configuration', diagnostics);
|
|
407
198
|
return;
|
|
@@ -435,7 +226,7 @@ export async function diagnoseDirectWorkerReadiness(input) {
|
|
|
435
226
|
? `CLI '${executable}' is not installed (version probe failed to start; config ${configFile} key ${configKey}/executable); install it before onboarding cli '${cli}'. Installation readiness: not-ready. (${describeConfigContext(members)})`
|
|
436
227
|
: `Version probe for cli '${cli}' ('${executable}', config ${configFile} key ${configKey}) failed; retry once, then check PATH and execute permission. Installation readiness: not-ready. (${describeConfigContext(members)})`,
|
|
437
228
|
nextAction: notInstalled ? 'install-cli' : 'retry-version-probe',
|
|
438
|
-
configFile, configKey, readiness: 'not-ready', members });
|
|
229
|
+
configFile, configKey: notInstalled ? `${configKey}/executable` : configKey, readiness: 'not-ready', members });
|
|
439
230
|
const { eligibility, detail, diagnostics } = checkDispatchEligibility(parallelism, registry, cli, false, resolverSnapshot, refs);
|
|
440
231
|
pushDispatch(cli, profileRef, 'warn', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, configKey, members, notInstalled ? 'install-cli' : 'retry-version-probe', diagnostics);
|
|
441
232
|
return;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Offline S1 health readiness projection; never launch authority or a probe. */
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { collectLocalOnlyPaths } from './conductor-data-policy.mjs';
|
|
4
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { resolveParallelism, validateParallelismConfig, resolveWorkerGroupInstallation, workerConfigDigest } from '../config/aiwg-config.js';
|
|
3
6
|
import { assertWorkerHealthQualification } from './provider-policy.js';
|
|
4
7
|
import { resolveWorkerPlan } from './resolver.js';
|
|
5
8
|
import { resolveWorkerEffectiveHealth } from '../serve/worker-invocation.js';
|
|
@@ -18,11 +21,86 @@ const repairAction = (code, pointer) => {
|
|
|
18
21
|
return 'qualify-installation-and-profile';
|
|
19
22
|
return 'review-worker-configuration';
|
|
20
23
|
};
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
/** Preserve the same repair context in human-readable doctor output and JSON. */
|
|
25
|
+
export function formatWorkerHealthReadinessFinding(finding) {
|
|
26
|
+
return `${finding.code}: ${finding.message} (config ${finding.configFile} key ${finding.configKey}; members: ${finding.members.join(', ') || 'none'}) [next: ${finding.nextAction}]`;
|
|
27
|
+
}
|
|
28
|
+
/** Build offline configuration repair findings before model/catalog loading is possible. */
|
|
29
|
+
export function workerHealthConfigurationFindings(parallelism, errors, paths = {}) {
|
|
30
|
+
return errors.map(message => {
|
|
31
|
+
const pointer = message.match(/^OG_CONFIG_REFERENCE (\/[^:]*):/)?.[1] ?? '/parallelism';
|
|
32
|
+
const parts = pointer.split('/');
|
|
33
|
+
const members = Object.entries(parallelism?.groups ?? {}).flatMap(([group, value]) => (Array.isArray(value?.members) ? value.members : []).filter((member, index) => {
|
|
34
|
+
if (!member || typeof member.id !== 'string')
|
|
35
|
+
return false;
|
|
36
|
+
if (parts[2] === 'groups')
|
|
37
|
+
return group === parts[3]
|
|
38
|
+
&& (parts[4] !== 'members' || member.id === parts[5] || String(index) === parts[5]);
|
|
39
|
+
return parts[2] !== 'clis' || member.cli === parts[3];
|
|
40
|
+
}).map(member => `${group}/${member.id}`));
|
|
41
|
+
return { code: 'OG_CONFIG_REFERENCE', status: 'error', pointer,
|
|
42
|
+
configFile: paths.configFile ?? resolve('.aiwg/aiwg.config'), configKey: pointer,
|
|
43
|
+
members, message, nextAction: 'review-worker-configuration', role: null };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function diagnoseWorkerHealthReadiness(snapshot, request, paths = {}) {
|
|
47
|
+
let p = snapshot.config.parallelism;
|
|
23
48
|
const report = { enabled: p?.worker_dispatch === true,
|
|
24
49
|
ready: true, contextDigest: null, findings: [], thresholds: [] };
|
|
25
|
-
const
|
|
50
|
+
const configFile = paths.configFile ?? resolve('.aiwg/aiwg.config');
|
|
51
|
+
const modelsFile = paths.modelsFile ?? resolve('models.json');
|
|
52
|
+
const catalogFile = paths.catalogFile ?? join(homedir(), '.cache/aiwg/model-catalog.v1.json');
|
|
53
|
+
const errors = validateParallelismConfig(p);
|
|
54
|
+
if (!errors.length && p?.worker_dispatch)
|
|
55
|
+
p = resolveParallelism(p, snapshot.config.providers[0]);
|
|
56
|
+
const allMembers = Object.entries(p?.groups ?? {}).flatMap(([group, value]) => (value.members ?? []).map(member => ({ group, member, label: `${group}/${member.id}` })));
|
|
57
|
+
const membersFor = (pointer, role) => {
|
|
58
|
+
const parts = pointer.split('/');
|
|
59
|
+
const groups = role ? snapshot.models.defaults.workerSelection?.roleGuidance?.[role]?.groups : undefined;
|
|
60
|
+
return allMembers.filter(row => {
|
|
61
|
+
if (parts[1] === 'parallelism' && parts[2] === 'groups')
|
|
62
|
+
return row.group === parts[3]
|
|
63
|
+
&& (parts[4] !== 'members' || row.member.id === parts[5])
|
|
64
|
+
&& (parts[4] !== 'requiredRoles' || !role || row.member.roles.includes(role));
|
|
65
|
+
if (parts[1] === 'parallelism' && parts[2] === 'clis')
|
|
66
|
+
return row.member.cli === parts[3];
|
|
67
|
+
return !groups || groups.includes(row.group);
|
|
68
|
+
}).map(row => row.label);
|
|
69
|
+
};
|
|
70
|
+
const contextualize = (finding, members) => {
|
|
71
|
+
let file = configFile;
|
|
72
|
+
let key = finding.pointer;
|
|
73
|
+
if (key.startsWith('/defaults/') || key.startsWith('/agentOverrides')) {
|
|
74
|
+
const origins = Object.entries(snapshot.models.workerPolicyOrigins ?? {})
|
|
75
|
+
.filter(([pointer]) => pointer === key || pointer.startsWith(`${key}/`))
|
|
76
|
+
.flatMap(([, values]) => values).filter(origin => isAbsolute(origin.source))
|
|
77
|
+
.sort((a, b) => ['builtin', 'user', 'project'].indexOf(a.authority)
|
|
78
|
+
- ['builtin', 'user', 'project'].indexOf(b.authority));
|
|
79
|
+
file = origins.at(-1)?.source ?? modelsFile;
|
|
80
|
+
}
|
|
81
|
+
else if (key.startsWith('/catalog/')) {
|
|
82
|
+
file = catalogFile;
|
|
83
|
+
key = key.slice('/catalog'.length);
|
|
84
|
+
}
|
|
85
|
+
else if (key === '/worker' || key.startsWith('/worker/')) {
|
|
86
|
+
file = catalogFile;
|
|
87
|
+
const affected = allMembers.find(row => row.label === members[0]);
|
|
88
|
+
const cli = affected?.member.cli;
|
|
89
|
+
const index = cli ? snapshot.catalog.discovery?.instances?.[cli]?.models.findIndex(row => row.id === affected.member.model || row.apiModelId === affected.member.model) : undefined;
|
|
90
|
+
if (cli && index !== undefined && index >= 0)
|
|
91
|
+
key = `/discovery/instances/${cli}/models/${index}${key}`;
|
|
92
|
+
}
|
|
93
|
+
return { ...finding, configFile: file, configKey: key, members: [...new Set(members)] };
|
|
94
|
+
};
|
|
95
|
+
const add = (finding, members = membersFor(finding.pointer, finding.role)) => {
|
|
96
|
+
report.findings.push(contextualize(finding, members));
|
|
97
|
+
};
|
|
98
|
+
const localOnlyPaths = collectLocalOnlyPaths(snapshot.models.defaults.workerSelection, p);
|
|
99
|
+
if (localOnlyPaths.length)
|
|
100
|
+
add({ code: 'CONDUCTOR_LOCAL_ONLY_PATHS', status: 'info',
|
|
101
|
+
pointer: '/defaults/workerSelection/dataPolicy/localOnlyPaths', role: null,
|
|
102
|
+
message: `Declared local-only conductor inputs: ${localOnlyPaths.join(', ')}. Deploy provider guards before opening task inputs; shell coverage is limited.`,
|
|
103
|
+
nextAction: 'deploy-provider-data-guards' });
|
|
26
104
|
if (!report.enabled) {
|
|
27
105
|
add({ code: 'WORKER_HEALTH_DISABLED', status: 'info', pointer: '/parallelism/worker_dispatch',
|
|
28
106
|
message: 'Group dispatch is disabled; legacy execution has no worker health readiness requirement.',
|
|
@@ -30,12 +108,9 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
|
|
|
30
108
|
return report;
|
|
31
109
|
}
|
|
32
110
|
report.contextDigest = workerConfigDigest({ context: request.context, modelDigests: snapshot.modelDigests });
|
|
33
|
-
const errors = validateParallelismConfig(p);
|
|
34
111
|
if (errors.length) {
|
|
35
112
|
report.ready = false;
|
|
36
|
-
|
|
37
|
-
add({ code: 'OG_CONFIG_REFERENCE', status: 'error',
|
|
38
|
-
pointer: '/parallelism', message, nextAction: 'review-worker-configuration', role: null });
|
|
113
|
+
report.findings.push(...workerHealthConfigurationFindings(p, errors, { configFile }));
|
|
39
114
|
return report;
|
|
40
115
|
}
|
|
41
116
|
if (p?.worker_mode === 'managed') {
|
|
@@ -53,7 +128,7 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
|
|
|
53
128
|
message: 'No worker role guidance is configured; onboarding cannot offer a qualified route.',
|
|
54
129
|
nextAction: 'configure-role-guidance', role: null });
|
|
55
130
|
}
|
|
56
|
-
const installations = new Set(enabledGroups.flatMap(group => group.members.filter(member => member.enabled !== false).map(member => member.cli)));
|
|
131
|
+
const installations = new Set(enabledGroups.flatMap(group => (group.members ?? []).filter(member => member.enabled !== false).map(member => member.cli)));
|
|
57
132
|
for (const cli of installations) {
|
|
58
133
|
if (!p?.clis?.[cli]?.quotaScope)
|
|
59
134
|
add({ code: 'WORKER_QUOTA_SCOPE_UNKNOWN', status: 'warn', role: null,
|
|
@@ -65,26 +140,30 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
|
|
|
65
140
|
for (const role of roles) {
|
|
66
141
|
const plan = resolveWorkerPlan(snapshot, { ...request, role: role });
|
|
67
142
|
const isRequired = required.has(role);
|
|
68
|
-
const diagnostics = [
|
|
69
|
-
|
|
70
|
-
|
|
143
|
+
const diagnostics = [
|
|
144
|
+
...plan.diagnostics.map(diagnostic => ({ diagnostic, members: membersFor(diagnostic.pointer, role) })),
|
|
145
|
+
...plan.exclusions.flatMap(item => item.diagnostics.map(diagnostic => ({ diagnostic,
|
|
146
|
+
members: [`${item.group}/${item.member}`] }))),
|
|
147
|
+
];
|
|
148
|
+
for (const { diagnostic, members } of diagnostics) {
|
|
71
149
|
if (diagnostic.roles && !diagnostic.roles.includes(role))
|
|
72
150
|
continue;
|
|
73
|
-
const
|
|
74
|
-
if (seen.has(key))
|
|
75
|
-
continue;
|
|
76
|
-
seen.add(key);
|
|
77
|
-
const finding = { ...diagnostic,
|
|
151
|
+
const finding = contextualize({ ...diagnostic,
|
|
78
152
|
status: diagnostic.blocking === false ? 'info' : 'warn', role,
|
|
79
|
-
nextAction: repairAction(diagnostic.code, diagnostic.pointer) };
|
|
153
|
+
nextAction: repairAction(diagnostic.code, diagnostic.pointer) }, members);
|
|
154
|
+
const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message, diagnostic.blocking,
|
|
155
|
+
diagnostic.origins, finding.configFile, finding.configKey]);
|
|
80
156
|
const shared = sharedDiagnostics.get(key);
|
|
81
157
|
if (shared) {
|
|
82
|
-
shared.
|
|
83
|
-
shared.role
|
|
158
|
+
shared.members = [...new Set([...shared.members, ...finding.members])];
|
|
159
|
+
if (shared.role !== role) {
|
|
160
|
+
shared.roles = [...new Set([...(shared.roles ?? [shared.role]), role])];
|
|
161
|
+
shared.role = null;
|
|
162
|
+
}
|
|
84
163
|
}
|
|
85
164
|
else {
|
|
86
165
|
sharedDiagnostics.set(key, finding);
|
|
87
|
-
|
|
166
|
+
report.findings.push(finding);
|
|
88
167
|
}
|
|
89
168
|
}
|
|
90
169
|
if (!plan.selected) {
|
|
@@ -111,11 +190,11 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
|
|
|
111
190
|
add({ code: 'WORKER_STEP_CAP_NOT_READY', status: isRequired ? 'error' : 'warn', role,
|
|
112
191
|
pointer: '/parallelism/health/requireStepCap',
|
|
113
192
|
message: 'Required step cap lacks native enforcement or qualified runtime pre-step admission evidence.',
|
|
114
|
-
nextAction: 'qualify-step-enforcement' });
|
|
193
|
+
nextAction: 'qualify-step-enforcement' }, [`${tuple.group}/${tuple.member}`]);
|
|
115
194
|
continue;
|
|
116
195
|
}
|
|
117
196
|
// External admission needs runtime gate evidence; a config declaration alone cannot prove it.
|
|
118
|
-
const health = resolveWorkerEffectiveHealth(p?.health, { role, effort: tuple.effort,
|
|
197
|
+
const health = resolveWorkerEffectiveHealth(p?.health, { role: plan.policy?.roleGuidance?.[role]?.baseRole ?? role, effort: tuple.effort,
|
|
119
198
|
modelKey: tuple.modelKey, profileRef: tuple.profile, wallSeconds: plan.ceilings.wallSeconds ?? 1800,
|
|
120
199
|
stepEnforcement: nativeSteps ? 'native' : 'unavailable' });
|
|
121
200
|
report.thresholds.push({ role, group: tuple.group, member: tuple.member, health });
|
|
@@ -123,12 +202,12 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
|
|
|
123
202
|
add({ code: 'WORKER_HEALTH_DEGRADED', status: 'warn', role,
|
|
124
203
|
pointer: `/parallelism/clis/${tuple.cli}/invocationProfile`,
|
|
125
204
|
message: 'Profile has degraded or unobserved health coverage; progress and stop evidence may be unavailable.',
|
|
126
|
-
nextAction: 'qualify-health-signals' });
|
|
205
|
+
nextAction: 'qualify-health-signals' }, [`${tuple.group}/${tuple.member}`]);
|
|
127
206
|
}
|
|
128
207
|
add({ code: 'WORKER_ROLE_ELIGIBLE', status: 'ok', role,
|
|
129
208
|
pointer: `/defaults/workerSelection/roleGuidance/${role}`,
|
|
130
209
|
message: 'Policy route is eligible; launch still requires current installation and task qualification.',
|
|
131
|
-
nextAction: 'resolve-current-task' });
|
|
210
|
+
nextAction: 'resolve-current-task' }, [`${tuple.group}/${tuple.member}`]);
|
|
132
211
|
}
|
|
133
212
|
catch {
|
|
134
213
|
if (isRequired)
|
|
@@ -136,7 +215,7 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
|
|
|
136
215
|
add({ code: 'WORKER_HEALTH_UNQUALIFIED', status: isRequired ? 'error' : 'warn', role,
|
|
137
216
|
pointer: `/parallelism/clis/${tuple.cli}/invocationProfile`,
|
|
138
217
|
message: 'Health profile scope or effective settings are invalid; requalify the exact version/account/model/effort.',
|
|
139
|
-
nextAction: 'qualify-health-signals' });
|
|
218
|
+
nextAction: 'qualify-health-signals' }, [`${tuple.group}/${tuple.member}`]);
|
|
140
219
|
}
|
|
141
220
|
}
|
|
142
221
|
return report;
|
|
@@ -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
|
|
211
|
-
//
|
|
212
|
-
//
|
|
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
|
|
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
|
-
|
|
1147
|
-
|
|
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
|