@aiwg/cli 2026.10.3 → 2026.10.5
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/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +69 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +286 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +130 -0
- package/dist/src/artifacts/index-refresh.js +591 -0
- package/dist/src/artifacts/index-worker.mjs +11 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +137 -43
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +95 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +252 -12
- package/dist/src/sessions/contracts.js +50 -3
- package/dist/src/sessions/coverage-preflight.js +27 -2
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +145 -26
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +105 -0
- package/dist/src/sessions/repository.js +151 -54
- package/dist/src/sessions/workspace-discovery.js +292 -60
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { signalChild } from '../../process/signal-child.mjs';
|
|
1
2
|
import { execFile } from 'node:child_process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
2
4
|
import { promisify } from 'node:util';
|
|
3
5
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
6
|
import path from 'node:path';
|
|
@@ -29,7 +31,7 @@ async function execCanonicalSystemctl(exec, args, options) {
|
|
|
29
31
|
|
|
30
32
|
/** Parse only explicit stop proof. Missing/unknown properties are observation
|
|
31
33
|
* failures, never evidence of termination. A collected unit has no exit result. */
|
|
32
|
-
export function parseShellUnitStatus(stdout) {
|
|
34
|
+
export function parseShellUnitStatus(stdout, { stopAccepted = false } = {}) {
|
|
33
35
|
const fields = Object.fromEntries(String(stdout).split('\n').filter(line => line.includes('='))
|
|
34
36
|
.map(line => { const at = line.indexOf('='); return [line.slice(0, at), line.slice(at + 1)]; }));
|
|
35
37
|
if (fields.LoadState === 'not-found' && fields.ActiveState === 'inactive' && fields.MainPID === '0') {
|
|
@@ -42,17 +44,31 @@ export function parseShellUnitStatus(stdout) {
|
|
|
42
44
|
throw new Error('worker status unavailable: missing or invalid systemd properties');
|
|
43
45
|
}
|
|
44
46
|
if (!['inactive', 'failed'].includes(fields.ActiveState) || fields.MainPID !== '0') return uncertain();
|
|
47
|
+
// An inactive unit may still have a queued start. Only an accepted stop
|
|
48
|
+
// followed by this fresh observation can prove a never-executed unit ended.
|
|
49
|
+
if (fields.ExecMainCode === '0' && stopAccepted) {
|
|
50
|
+
return { termination: 'confirmed', exitCode: null, neverExecuted: true };
|
|
51
|
+
}
|
|
52
|
+
if (!['1', '2', '3'].includes(fields.ExecMainCode)) return uncertain();
|
|
45
53
|
const signaled = ['2', '3'].includes(fields.ExecMainCode);
|
|
46
|
-
return { termination: 'confirmed', exitCode: fields.Result === 'success' && !signaled
|
|
54
|
+
return { termination: 'confirmed', nativeResult: fields.Result, exitCode: fields.Result === 'success' && !signaled
|
|
47
55
|
? Number(fields.ExecMainStatus) : Number(fields.ExecMainStatus) || 1,
|
|
48
56
|
...(signaled ? { signalCode: Number(fields.ExecMainStatus) } : {}) };
|
|
49
57
|
}
|
|
50
58
|
|
|
59
|
+
function boundedDeadline(deadlineMs, deadlineAtMs, now) {
|
|
60
|
+
if (deadlineAtMs !== undefined && (!Number.isFinite(deadlineAtMs)
|
|
61
|
+
|| (deadlineAtMs >= 1e12) !== (now >= 1e12))) {
|
|
62
|
+
throw new RangeError('Worker deadlineAtMs must use the same clock as nowMs (or clock); supply the matching clock');
|
|
63
|
+
}
|
|
64
|
+
return Math.min(deadlineAtMs ?? Infinity, now + deadlineMs);
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
export async function waitForUnitTermination(unit, deadlineMs, { signal, pollIntervalMs = 2000,
|
|
52
68
|
exec = execFileAsync, sleep = (ms, abortSignal) => delay(ms, undefined, { signal: abortSignal }),
|
|
53
|
-
clock =
|
|
69
|
+
clock = () => performance.now(), deadlineAtMs, stopAccepted = false } = {}) {
|
|
54
70
|
if (typeof unit !== 'string' || !/^[A-Za-z0-9:_.@-]+$/.test(unit)) throw new Error('Worker isolation requires a unit-safe unit name');
|
|
55
|
-
const deadline = clock()
|
|
71
|
+
const deadline = boundedDeadline(deadlineMs, deadlineAtMs, clock());
|
|
56
72
|
while (!signal?.aborted && clock() < deadline) {
|
|
57
73
|
try {
|
|
58
74
|
const { stdout } = await execCanonicalSystemctl(exec,
|
|
@@ -61,7 +77,7 @@ export async function waitForUnitTermination(unit, deadlineMs, { signal, pollInt
|
|
|
61
77
|
// overall grace/deadline even when a probe is still in flight.
|
|
62
78
|
{ timeout: Math.max(1000, Math.min(10000, deadline - clock())), signal });
|
|
63
79
|
if (signal?.aborted) return uncertain();
|
|
64
|
-
const outcome = parseShellUnitStatus(stdout);
|
|
80
|
+
const outcome = parseShellUnitStatus(stdout, { stopAccepted });
|
|
65
81
|
if (outcome.termination === 'confirmed') return outcome;
|
|
66
82
|
} catch (error) {
|
|
67
83
|
// systemctl may return nonzero for a collected unit while still showing
|
|
@@ -86,7 +102,8 @@ export async function waitForUnitTermination(unit, deadlineMs, { signal, pollInt
|
|
|
86
102
|
* after leader reap to limit (not eliminate) the PGID reuse race. */
|
|
87
103
|
export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermination,
|
|
88
104
|
stopUnit = defaultStopShellUnit, killUnit = defaultKillShellUnit, graceMs,
|
|
89
|
-
onConfirmed, platform = process.platform, recheckMaxAttempts = 6, recheckDelayMs = 250
|
|
105
|
+
onConfirmed, platform = process.platform, recheckMaxAttempts = 6, recheckDelayMs = 250,
|
|
106
|
+
nowMs = () => performance.now() }) {
|
|
90
107
|
let closed = false;
|
|
91
108
|
let exitCode = null;
|
|
92
109
|
let signalCode = null;
|
|
@@ -94,6 +111,7 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
94
111
|
let stopTask;
|
|
95
112
|
let delivery;
|
|
96
113
|
let stopped = false;
|
|
114
|
+
let stopAccepted = false;
|
|
97
115
|
let recheckTimer;
|
|
98
116
|
let rechecks = 0;
|
|
99
117
|
let disposed = false;
|
|
@@ -101,11 +119,21 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
101
119
|
let resolveClose;
|
|
102
120
|
const probes = new Set();
|
|
103
121
|
const child = launch.child;
|
|
104
|
-
const ownedGroup = platform !== 'win32' && launch.ownedGroup
|
|
122
|
+
const ownedGroup = platform !== 'win32' && launch.ownedGroup;
|
|
105
123
|
const closeProof = new Promise(resolve => { resolveClose = resolve; });
|
|
106
124
|
|
|
125
|
+
function safeGroupTarget() {
|
|
126
|
+
if (!Number.isSafeInteger(child?.pid) || child.pid <= 1 || child.pid === process.pid) return false;
|
|
127
|
+
if (platform !== 'linux') return true;
|
|
128
|
+
try {
|
|
129
|
+
const stat = readFileSync('/proc/self/stat', 'utf8');
|
|
130
|
+
const pgid = Number(stat.slice(stat.lastIndexOf(')') + 2).split(/\s+/)[2]);
|
|
131
|
+
return Number.isSafeInteger(pgid) && child.pid !== pgid;
|
|
132
|
+
} catch { return false; }
|
|
133
|
+
}
|
|
107
134
|
function groupGone() {
|
|
108
135
|
if (!ownedGroup) return true;
|
|
136
|
+
if (!safeGroupTarget()) return false;
|
|
109
137
|
try { process.kill(-child.pid, 0); return false; } catch (error) { return error.code === 'ESRCH'; }
|
|
110
138
|
}
|
|
111
139
|
function confirm(outcome) {
|
|
@@ -128,10 +156,10 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
128
156
|
if (groupGone()) confirm({ termination: 'confirmed', exitCode, signalCode });
|
|
129
157
|
else void control.stop(); // descendants outlived their leader
|
|
130
158
|
}
|
|
131
|
-
function onError() {
|
|
159
|
+
function onError(error) {
|
|
132
160
|
// A pid-bearing child error is an observation failure, not a deadline.
|
|
133
161
|
// ENOENT has no pid and cannot have started a worker.
|
|
134
|
-
if (!child?.pid && launch.mode !== 'systemd-unit') confirm({ termination: 'confirmed', exitCode: null });
|
|
162
|
+
if (!child?.pid && launch.mode !== 'systemd-unit') confirm({ termination: 'confirmed', exitCode: null, errorCode: error?.code ?? 'SPAWN_ERROR' });
|
|
135
163
|
}
|
|
136
164
|
child?.once('close', onClose);
|
|
137
165
|
child?.on('error', onError);
|
|
@@ -155,10 +183,11 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
155
183
|
abort.abort(); // cancel a losing poll or execFile, including its sleep
|
|
156
184
|
}
|
|
157
185
|
}
|
|
158
|
-
async function waitStopped(ms) {
|
|
186
|
+
async function waitStopped(ms, deadline) {
|
|
159
187
|
if (confirmed) return confirmed;
|
|
160
188
|
if (launch.mode === 'systemd-unit') {
|
|
161
|
-
const outcome = await bounded(signal => waitForUnit(launch.unit, ms, { signal
|
|
189
|
+
const outcome = await bounded(signal => waitForUnit(launch.unit, ms, { signal, stopAccepted,
|
|
190
|
+
...(deadline ? { deadlineAtMs: deadline.at, clock: deadline.now } : {}) }), ms);
|
|
162
191
|
return outcome.termination === 'confirmed' ? confirm(outcome) : outcome;
|
|
163
192
|
}
|
|
164
193
|
if (closed && groupGone()) return confirm({ termination: 'confirmed', exitCode, signalCode });
|
|
@@ -181,15 +210,17 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
181
210
|
if (launch.mode === 'systemd-unit') {
|
|
182
211
|
const result = await bounded(abortSignal => (signalName === 'SIGTERM' ? stopUnit : killUnit)(launch.unit,
|
|
183
212
|
{ signal: abortSignal, timeout: Math.max(1, budgetMs) }), budgetMs);
|
|
213
|
+
if (signalName === 'SIGTERM' && result.ok) stopAccepted = true;
|
|
184
214
|
return !!result.ok;
|
|
185
215
|
}
|
|
186
216
|
if (ownedGroup) {
|
|
217
|
+
if (!safeGroupTarget()) return false;
|
|
187
218
|
if ((closed || child.exitCode != null || child.signalCode != null) && groupSwept) return false;
|
|
188
219
|
if (signalName === 'SIGKILL') groupSwept = true;
|
|
189
220
|
process.kill(-child.pid, signalName);
|
|
190
221
|
return true;
|
|
191
222
|
}
|
|
192
|
-
return !closed && !!child
|
|
223
|
+
return !closed && !!signalChild(child, signalName);
|
|
193
224
|
} catch { return false; }
|
|
194
225
|
}
|
|
195
226
|
const control = {
|
|
@@ -205,15 +236,15 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
205
236
|
if (stopTask) return delivery;
|
|
206
237
|
stopped = true;
|
|
207
238
|
const phaseMs = launch.mode === 'systemd-unit' ? Math.max(1000, graceMs) : graceMs;
|
|
208
|
-
let phaseEnd =
|
|
239
|
+
let phaseEnd = nowMs() + phaseMs;
|
|
209
240
|
delivery = signal('SIGTERM', phaseMs);
|
|
210
241
|
stopTask = (async () => {
|
|
211
242
|
await delivery;
|
|
212
|
-
let outcome = await waitStopped(Math.max(0, phaseEnd -
|
|
243
|
+
let outcome = await waitStopped(Math.max(0, phaseEnd - nowMs()));
|
|
213
244
|
if (outcome.termination !== 'confirmed') {
|
|
214
|
-
phaseEnd =
|
|
245
|
+
phaseEnd = nowMs() + phaseMs;
|
|
215
246
|
await signal('SIGKILL', phaseMs);
|
|
216
|
-
outcome = await waitStopped(Math.max(0, phaseEnd -
|
|
247
|
+
outcome = await waitStopped(Math.max(0, phaseEnd - nowMs()));
|
|
217
248
|
}
|
|
218
249
|
if (outcome.termination !== 'confirmed') recheck();
|
|
219
250
|
return outcome;
|
|
@@ -224,16 +255,16 @@ export function buildShellStopControl(launch, { waitForUnit = waitForUnitTermina
|
|
|
224
255
|
await control.stop();
|
|
225
256
|
return confirmed ?? (stopTask ? await stopTask : uncertain());
|
|
226
257
|
},
|
|
227
|
-
wait: async deadlineMs => {
|
|
228
|
-
const deadline =
|
|
258
|
+
wait: async (deadlineMs, { deadlineAtMs, nowMs: waitNow = nowMs } = {}) => {
|
|
259
|
+
const deadline = boundedDeadline(deadlineMs, deadlineAtMs, waitNow());
|
|
229
260
|
let outcome;
|
|
230
261
|
do {
|
|
231
|
-
outcome = await waitStopped(Math.max(0, deadline -
|
|
262
|
+
outcome = await waitStopped(Math.max(0, deadline - waitNow()), { at: deadline, now: waitNow });
|
|
232
263
|
if (confirmed) return confirmed;
|
|
233
264
|
// Observation failure alone never mandates a stop. Avoid a hot loop
|
|
234
265
|
// when an injected/failed observer returns immediately.
|
|
235
|
-
if (!outcome.timedOut) await Promise.race([closeProof, delay(Math.min(50, Math.max(0, deadline -
|
|
236
|
-
} while (!disposed && !outcome.timedOut &&
|
|
266
|
+
if (!outcome.timedOut) await Promise.race([closeProof, delay(Math.min(50, Math.max(0, deadline - waitNow())))]);
|
|
267
|
+
} while (!disposed && !outcome.timedOut && waitNow() < deadline);
|
|
237
268
|
if (disposed) return uncertain();
|
|
238
269
|
return { ...await control.stopAndConfirm(), timedOut: true };
|
|
239
270
|
},
|
|
@@ -10,7 +10,7 @@ export async function sweepWorkerStdin(root = tmpdir(), { staleMs = 86400000, no
|
|
|
10
10
|
try {
|
|
11
11
|
for (const name of await readdir(root)) {
|
|
12
12
|
const match = /^aiwg-worker-stdin-(\d+)-[A-Za-z0-9]+$/.exec(name);
|
|
13
|
-
if (!match || isAlive(Number(match[1]))) continue;
|
|
13
|
+
if (!match || !Number.isSafeInteger(Number(match[1])) || Number(match[1]) <= 0 || isAlive(Number(match[1]))) continue;
|
|
14
14
|
const directory = path.join(root, name);
|
|
15
15
|
try {
|
|
16
16
|
const stat = await lstat(directory);
|
|
@@ -40,7 +40,7 @@ export async function prepareWorkerStdin(bytes, root = tmpdir()) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export async function launchPreparedShellWorker(invocation, { allowDegraded, config, name, live, readers, thresholdOverlay,
|
|
43
|
-
memoryMaxGb, driver, servedRoot, isolation, spawn, spawnAsync, stdinRuntimeRoot },
|
|
43
|
+
memoryMaxGb, driver, servedRoot, isolation, spawn, spawnAsync, stdinRuntimeRoot, admission, invocationId, dispatchGuard },
|
|
44
44
|
{ loadRalphExternal, readBoundedFile, importImpl, describeGateFailures }) {
|
|
45
45
|
let attempt;
|
|
46
46
|
let cleanup = () => Promise.resolve();
|
|
@@ -58,7 +58,7 @@ export async function launchPreparedShellWorker(invocation, { allowDegraded, con
|
|
|
58
58
|
({ stdinPath, cleanup } = await prepareWorkerStdin(bytes, stdinRuntimeRoot));
|
|
59
59
|
}
|
|
60
60
|
const launch = await planDirectWorkerLaunch({ invocation, config, allowDegraded, name, live, readers,
|
|
61
|
-
thresholdOverlay, memoryMaxGb, stdinPath, ownedGroup: true, isolation,
|
|
61
|
+
thresholdOverlay, memoryMaxGb, stdinPath, ownedGroup: true, isolation, admission, invocationId, dispatchGuard,
|
|
62
62
|
spawn, spawnAsync, onSpawnAttempt: value => {
|
|
63
63
|
attempt = value;
|
|
64
64
|
value.child?.once('close', (exitCode, signalCode) => { Object.assign(value, { closed: true, exitCode, signalCode }); });
|
|
@@ -410,6 +410,14 @@ export function assertWorkerTerminal(observation) {
|
|
|
410
410
|
throw new WorkerContractError('OG_CONTRACT_INVALID', 'Worker terminal evidence is missing, erroneous or uncertain; exit zero alone is insufficient.');
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
|
+
export function assertWorkerAttemptTerminal(observation) {
|
|
414
|
+
if (!observation || !['shell', 'mcp'].includes(observation.transport)
|
|
415
|
+
|| observation.status !== 'completed' || observation.attemptOutcome !== 'succeeded'
|
|
416
|
+
|| (observation.transport === 'shell' ? observation.exitCode !== 0 : observation.exitCode !== null && observation.exitCode !== 0)
|
|
417
|
+
|| (observation.isError !== undefined && observation.isError !== false)) {
|
|
418
|
+
throw new WorkerContractError('OG_CONTRACT_INVALID', 'Worker attempt evidence is missing, erroneous or uncertain; exit zero alone is insufficient.');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
413
421
|
/**
|
|
414
422
|
* Assemble detached, immutable evidence from separately attributed producers.
|
|
415
423
|
* artifactBytes are read by the conductor from the exact artifact (or protected
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* @architecture @.aiwg/architecture/enhanced-model-selection-design.md
|
|
7
7
|
*/
|
|
8
8
|
import { readFile } from 'fs/promises';
|
|
9
|
-
import { resolve } from 'path';
|
|
9
|
+
import { dirname, resolve } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { homedir } from 'os';
|
|
11
12
|
import { existsSync } from 'fs';
|
|
13
|
+
import { findPackageRoot } from '../cli/find-package-root.js';
|
|
12
14
|
import { workerConfigDigest, validateWorkerGroupReferences } from '../config/aiwg-config.js';
|
|
13
15
|
import { DEFAULT_WORKER_SELECTION, mergeWorkerSelection, WorkerSelectionPolicySchema, validateUserProjectModelConfig } from './provider-policy.js';
|
|
14
16
|
/**
|
|
@@ -20,7 +22,10 @@ export class ConfigLoader {
|
|
|
20
22
|
inputDigests = null;
|
|
21
23
|
locations;
|
|
22
24
|
constructor(projectPath, options) {
|
|
23
|
-
const aiwgHome = options?.aiwgHome || process.env.AIWG_HOME
|
|
25
|
+
const aiwgHome = options?.aiwgHome || process.env.AIWG_HOME
|
|
26
|
+
|| findPackageRoot(dirname(fileURLToPath(import.meta.url)));
|
|
27
|
+
if (!aiwgHome)
|
|
28
|
+
throw new Error('Failed to load AIWG default config: could not locate the running AIWG package root');
|
|
24
29
|
const userConfigDir = process.env.AIWG_CONFIG || resolve(homedir(), '.config/aiwg');
|
|
25
30
|
this.locations = {
|
|
26
31
|
aiwgDefault: resolve(aiwgHome, 'agentic/code/frameworks/sdlc-complete/config/models-v2.json'),
|
|
@@ -208,6 +208,28 @@
|
|
|
208
208
|
}
|
|
209
209
|
},
|
|
210
210
|
"claude": {
|
|
211
|
+
"sessionEnvAllowlist": [
|
|
212
|
+
"ANTHROPIC_API_KEY",
|
|
213
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
214
|
+
"ANTHROPIC_BASE_URL",
|
|
215
|
+
"ANTHROPIC_MODEL",
|
|
216
|
+
"CLAUDE_CONFIG_DIR",
|
|
217
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
218
|
+
"CLAUDE_CODE_DISABLE_1M_CONTEXT",
|
|
219
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
220
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
221
|
+
"AWS_PROFILE",
|
|
222
|
+
"AWS_REGION",
|
|
223
|
+
"AWS_DEFAULT_REGION",
|
|
224
|
+
"AWS_ACCESS_KEY_ID",
|
|
225
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
226
|
+
"AWS_SESSION_TOKEN",
|
|
227
|
+
"AWS_CONFIG_FILE",
|
|
228
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
229
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
230
|
+
"ANTHROPIC_VERTEX_PROJECT_ID",
|
|
231
|
+
"CLOUD_ML_REGION"
|
|
232
|
+
],
|
|
211
233
|
"agent": "native",
|
|
212
234
|
"skill": "native",
|
|
213
235
|
"globalChild": "inherited",
|
|
@@ -429,6 +451,14 @@
|
|
|
429
451
|
}
|
|
430
452
|
},
|
|
431
453
|
"codex": {
|
|
454
|
+
"sessionEnvAllowlist": [
|
|
455
|
+
"OPENAI_API_KEY",
|
|
456
|
+
"CODEX_API_KEY",
|
|
457
|
+
"OPENAI_BASE_URL",
|
|
458
|
+
"OPENAI_ORG_ID",
|
|
459
|
+
"OPENAI_PROJECT_ID",
|
|
460
|
+
"CODEX_HOME"
|
|
461
|
+
],
|
|
432
462
|
"agent": "native",
|
|
433
463
|
"skill": "unsupported",
|
|
434
464
|
"globalChild": "inherited",
|
|
@@ -452,7 +482,7 @@
|
|
|
452
482
|
"id": "codex",
|
|
453
483
|
"provider": "codex",
|
|
454
484
|
"adapterVersion": "1",
|
|
455
|
-
"supportedVersions": "0.161.0",
|
|
485
|
+
"supportedVersions": ">=0.161.0 <0.200.0",
|
|
456
486
|
"runtimeDriver": "installed:codex",
|
|
457
487
|
"catalogAdapter": {
|
|
458
488
|
"kind": "file",
|
|
@@ -513,7 +543,7 @@
|
|
|
513
543
|
"invocationProfiles": {
|
|
514
544
|
"direct-shell": {
|
|
515
545
|
"transport": "shell",
|
|
516
|
-
"supportedVersions": "0.161.0",
|
|
546
|
+
"supportedVersions": ">=0.161.0 <0.200.0",
|
|
517
547
|
"controls": {
|
|
518
548
|
"model": {
|
|
519
549
|
"support": "unqualified",
|
|
@@ -640,6 +670,12 @@
|
|
|
640
670
|
}
|
|
641
671
|
},
|
|
642
672
|
"copilot": {
|
|
673
|
+
"sessionEnvAllowlist": [
|
|
674
|
+
"COPILOT_GITHUB_TOKEN",
|
|
675
|
+
"GH_TOKEN",
|
|
676
|
+
"GITHUB_TOKEN",
|
|
677
|
+
"COPILOT_HOME"
|
|
678
|
+
],
|
|
643
679
|
"agent": "native",
|
|
644
680
|
"skill": "unsupported",
|
|
645
681
|
"globalChild": "inherited",
|
|
@@ -1025,6 +1061,9 @@
|
|
|
1025
1061
|
}
|
|
1026
1062
|
},
|
|
1027
1063
|
"factory": {
|
|
1064
|
+
"sessionEnvAllowlist": [
|
|
1065
|
+
"FACTORY_API_KEY"
|
|
1066
|
+
],
|
|
1028
1067
|
"agent": "native",
|
|
1029
1068
|
"skill": "informational",
|
|
1030
1069
|
"globalChild": "native",
|
|
@@ -1220,6 +1259,23 @@
|
|
|
1220
1259
|
}
|
|
1221
1260
|
},
|
|
1222
1261
|
"opencode": {
|
|
1262
|
+
"sessionEnvAllowlist": [
|
|
1263
|
+
"OPENAI_API_KEY",
|
|
1264
|
+
"ANTHROPIC_API_KEY",
|
|
1265
|
+
"GEMINI_API_KEY",
|
|
1266
|
+
"GOOGLE_API_KEY",
|
|
1267
|
+
"OPENROUTER_API_KEY",
|
|
1268
|
+
"GROQ_API_KEY",
|
|
1269
|
+
"MISTRAL_API_KEY",
|
|
1270
|
+
"XAI_API_KEY",
|
|
1271
|
+
"DEEPSEEK_API_KEY",
|
|
1272
|
+
"OPENCODE_API_KEY",
|
|
1273
|
+
"OPENCODE_CONFIG",
|
|
1274
|
+
"OPENCODE_CONFIG_DIR",
|
|
1275
|
+
"OPENCODE_CONFIG_CONTENT",
|
|
1276
|
+
"OPENCODE_SERVER_USERNAME",
|
|
1277
|
+
"OPENCODE_SERVER_PASSWORD"
|
|
1278
|
+
],
|
|
1223
1279
|
"agent": "native",
|
|
1224
1280
|
"skill": "unsupported",
|
|
1225
1281
|
"globalChild": "inherited",
|
|
@@ -1596,6 +1652,19 @@
|
|
|
1596
1652
|
}
|
|
1597
1653
|
},
|
|
1598
1654
|
"omp": {
|
|
1655
|
+
"sessionEnvAllowlist": [
|
|
1656
|
+
"OPENAI_API_KEY",
|
|
1657
|
+
"ANTHROPIC_API_KEY",
|
|
1658
|
+
"GEMINI_API_KEY",
|
|
1659
|
+
"GOOGLE_API_KEY",
|
|
1660
|
+
"OPENROUTER_API_KEY",
|
|
1661
|
+
"GROQ_API_KEY",
|
|
1662
|
+
"MISTRAL_API_KEY",
|
|
1663
|
+
"XAI_API_KEY",
|
|
1664
|
+
"DEEPSEEK_API_KEY",
|
|
1665
|
+
"PI_CONFIG_DIR",
|
|
1666
|
+
"PI_CODING_AGENT_DIR"
|
|
1667
|
+
],
|
|
1599
1668
|
"agent": "native",
|
|
1600
1669
|
"skill": "inherited",
|
|
1601
1670
|
"globalChild": "native",
|
|
@@ -1796,6 +1865,21 @@
|
|
|
1796
1865
|
}
|
|
1797
1866
|
},
|
|
1798
1867
|
"pi": {
|
|
1868
|
+
"sessionEnvAllowlist": [
|
|
1869
|
+
"OPENAI_API_KEY",
|
|
1870
|
+
"ANTHROPIC_API_KEY",
|
|
1871
|
+
"GEMINI_API_KEY",
|
|
1872
|
+
"GOOGLE_API_KEY",
|
|
1873
|
+
"OPENROUTER_API_KEY",
|
|
1874
|
+
"GROQ_API_KEY",
|
|
1875
|
+
"MISTRAL_API_KEY",
|
|
1876
|
+
"XAI_API_KEY",
|
|
1877
|
+
"DEEPSEEK_API_KEY",
|
|
1878
|
+
"PI_CODING_AGENT_DIR",
|
|
1879
|
+
"PI_CODING_AGENT_SESSION_DIR",
|
|
1880
|
+
"AIWG_PI_STUB_SCENARIO",
|
|
1881
|
+
"AIWG_PI_STUB_RECEIPT"
|
|
1882
|
+
],
|
|
1799
1883
|
"agent": "native",
|
|
1800
1884
|
"skill": "inherited",
|
|
1801
1885
|
"globalChild": "native",
|
|
@@ -1992,6 +2076,10 @@
|
|
|
1992
2076
|
}
|
|
1993
2077
|
},
|
|
1994
2078
|
"deepseek-harness": {
|
|
2079
|
+
"sessionEnvAllowlist": [
|
|
2080
|
+
"DEEPSEEK_API_KEY",
|
|
2081
|
+
"DSH_HOME"
|
|
2082
|
+
],
|
|
1995
2083
|
"agent": "native",
|
|
1996
2084
|
"skill": "inherited",
|
|
1997
2085
|
"globalChild": "native",
|
|
@@ -3001,6 +3089,10 @@
|
|
|
3001
3089
|
}
|
|
3002
3090
|
},
|
|
3003
3091
|
"muse": {
|
|
3092
|
+
"sessionEnvAllowlist": [
|
|
3093
|
+
"AIWG_MUSE_STUB_SCENARIO",
|
|
3094
|
+
"AIWG_MUSE_STUB_RECEIPT"
|
|
3095
|
+
],
|
|
3004
3096
|
"agent": "unsupported",
|
|
3005
3097
|
"skill": "unsupported",
|
|
3006
3098
|
"globalChild": "informational",
|
|
@@ -3027,7 +3119,7 @@
|
|
|
3027
3119
|
"id": "muse",
|
|
3028
3120
|
"provider": "muse",
|
|
3029
3121
|
"adapterVersion": "1",
|
|
3030
|
-
"supportedVersions": "1.4.4-R5419.1",
|
|
3122
|
+
"supportedVersions": ">=1.4.4-R5419.1 <2.0.0",
|
|
3031
3123
|
"runtimeDriver": "tools/ralph-external/lib/muse-adapter.mjs",
|
|
3032
3124
|
"catalogAdapter": {
|
|
3033
3125
|
"kind": "file",
|
|
@@ -3087,7 +3179,7 @@
|
|
|
3087
3179
|
"invocationProfiles": {
|
|
3088
3180
|
"direct-shell": {
|
|
3089
3181
|
"transport": "shell",
|
|
3090
|
-
"supportedVersions": "1.4.4-R5419.1",
|
|
3182
|
+
"supportedVersions": ">=1.4.4-R5419.1 <2.0.0",
|
|
3091
3183
|
"controls": {
|
|
3092
3184
|
"model": {
|
|
3093
3185
|
"support": "unqualified",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { signalChild } from '../process/signal-child.mjs';
|
|
1
2
|
import { hasSelectionRelevantConflicts, validateWorkerCatalogDiscovery } from './provider-policy.js';
|
|
2
3
|
import { spawn } from 'node:child_process';
|
|
3
4
|
import { createHash } from 'node:crypto';
|
|
@@ -6,6 +7,7 @@ import { lstat, mkdir, open, readFile, readdir, realpath, rename, writeFile } fr
|
|
|
6
7
|
import { dirname, join, relative } from 'node:path';
|
|
7
8
|
import { constants } from 'node:fs';
|
|
8
9
|
import { homedir, tmpdir } from 'node:os';
|
|
10
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
9
11
|
export const PROVIDER_DISCOVERY_DECISIONS = {
|
|
10
12
|
claude: {
|
|
11
13
|
provider: 'claude',
|
|
@@ -125,6 +127,12 @@ export const PROVIDER_DISCOVERY_DECISIONS = {
|
|
|
125
127
|
};
|
|
126
128
|
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
127
129
|
export function classifyDiscoveryError(message) {
|
|
130
|
+
message = stripVTControlCharacters(message);
|
|
131
|
+
if (/\b(?:EROFS|EACCES|EPERM)\b|read-only file system/i.test(message)
|
|
132
|
+
|| (/FileSystem\.open|permission denied|operation not permitted/i.test(message)
|
|
133
|
+
&& /[\\/]opencode[\\/]log[\\/]|[\\/]\.pi[\\/]agent(?:[\\/]|$)/i.test(message))) {
|
|
134
|
+
return 'environment';
|
|
135
|
+
}
|
|
128
136
|
if (/(?:401|403|unauth|forbidden|credential|login|required auth)/i.test(message)) {
|
|
129
137
|
return 'authentication';
|
|
130
138
|
}
|
|
@@ -138,7 +146,7 @@ const SECRET_ASSIGNMENT = /((?:api[_-]?key|authorization|token|secret|password|c
|
|
|
138
146
|
const BEARER_SECRET = /(bearer\s+)[A-Za-z0-9._~+/=-]+/gi;
|
|
139
147
|
/** Keep native diagnostics useful without ever echoing credential material. */
|
|
140
148
|
export function redactModelDiscoveryText(value) {
|
|
141
|
-
return value
|
|
149
|
+
return stripVTControlCharacters(value)
|
|
142
150
|
.replace(SECRET_ASSIGNMENT, '$1[REDACTED]')
|
|
143
151
|
.replace(BEARER_SECRET, '$1[REDACTED]')
|
|
144
152
|
.slice(0, 4096);
|
|
@@ -162,7 +170,7 @@ export const runModelDiscoveryCommand = (command, args, options = {}) => new Pro
|
|
|
162
170
|
child.stdout.on('data', chunk => { stdout += String(chunk); if (Buffer.byteLength(stdout) > 8 * 1024 * 1024) {
|
|
163
171
|
stdout = '';
|
|
164
172
|
stderr = 'Model catalog exceeded 8 MiB limit';
|
|
165
|
-
child
|
|
173
|
+
signalChild(child, 'SIGKILL');
|
|
166
174
|
finish(1);
|
|
167
175
|
} });
|
|
168
176
|
child.stderr.on('data', chunk => { stderr = (stderr + String(chunk)).slice(-4096); });
|
|
@@ -173,7 +181,7 @@ export const runModelDiscoveryCommand = (command, args, options = {}) => new Pro
|
|
|
173
181
|
child.on('close', code => finish(code ?? 1));
|
|
174
182
|
const timer = setTimeout(() => {
|
|
175
183
|
stderr = `${stderr}${stderr ? '\n' : ''}${command} timed out`;
|
|
176
|
-
child
|
|
184
|
+
signalChild(child);
|
|
177
185
|
finish(124);
|
|
178
186
|
}, options.timeoutMs ?? 10_000);
|
|
179
187
|
});
|
|
@@ -450,7 +458,7 @@ export async function discoverCodexModels(command = 'codex', timeoutMs = 10_000,
|
|
|
450
458
|
settled = true;
|
|
451
459
|
clearTimeout(timer);
|
|
452
460
|
lines.close();
|
|
453
|
-
child
|
|
461
|
+
signalChild(child);
|
|
454
462
|
resolveDiscovery(result);
|
|
455
463
|
};
|
|
456
464
|
child.stderr.on('data', chunk => { stderr = (stderr + String(chunk)).slice(-4096); });
|
|
@@ -656,6 +664,8 @@ export async function resolveDynamicModelCatalog(options) {
|
|
|
656
664
|
};
|
|
657
665
|
}
|
|
658
666
|
const next = providerDiscovery[provider];
|
|
667
|
+
if (next.error)
|
|
668
|
+
next.error = stripVTControlCharacters(next.error);
|
|
659
669
|
const instanceId = discoveryInstanceId(next);
|
|
660
670
|
const prior = cached?.discovery?.instances?.[instanceId]
|
|
661
671
|
?? cached?.discovery?.providers[provider];
|
|
@@ -967,6 +977,30 @@ function validTimestamp(value) {
|
|
|
967
977
|
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i.test(value)
|
|
968
978
|
&& validDate(value.slice(0, 10)) && Number.isFinite(Date.parse(value));
|
|
969
979
|
}
|
|
980
|
+
/** Use the active window, capped by the reported maximum, with native headroom. */
|
|
981
|
+
function codexContextTokens(row) {
|
|
982
|
+
const window = normalizePositiveInt(row.context_window);
|
|
983
|
+
const maximum = normalizePositiveInt(row.max_context_window);
|
|
984
|
+
const percent = row.effective_context_window_percent;
|
|
985
|
+
// A malformed supplied limit must not silently enlarge the qualified window.
|
|
986
|
+
if (window === null || row.max_context_window != null && maximum === null
|
|
987
|
+
|| percent != null && (typeof percent !== 'number' || !Number.isFinite(percent) || percent <= 0 || percent > 100))
|
|
988
|
+
return null;
|
|
989
|
+
return normalizePositiveInt(Math.floor(Math.min(window, maximum ?? window) * (typeof percent === 'number' ? percent : 100) / 100));
|
|
990
|
+
}
|
|
991
|
+
function catalogEfforts(ids, raw) {
|
|
992
|
+
return ids.map(id => {
|
|
993
|
+
const entry = Array.isArray(raw) ? raw.find(value => value && typeof value === 'object'
|
|
994
|
+
&& (value.effort ?? value.tier) === id) : undefined;
|
|
995
|
+
// Conductor-verified Codex/Muse catalog rule (#3083): only ultra is
|
|
996
|
+
// "Maximum reasoning with automatic task delegation". Prefer that explicit
|
|
997
|
+
// description when present; other listed levels do not self-delegate.
|
|
998
|
+
// multi_agent_reasoning_effort/version describe child execution, not which
|
|
999
|
+
// selected effort activates delegation (e.g. xhigh is not self-delegating).
|
|
1000
|
+
return { id, selfDelegates: id === 'ultra'
|
|
1001
|
+
|| typeof entry?.description === 'string' && /automatic task delegation/i.test(entry.description) };
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
970
1004
|
function normalizeObservationRows(rows, options, cli) {
|
|
971
1005
|
const diagnostics = [];
|
|
972
1006
|
const models = [];
|
|
@@ -987,12 +1021,17 @@ function normalizeObservationRows(rows, options, cli) {
|
|
|
987
1021
|
continue;
|
|
988
1022
|
}
|
|
989
1023
|
const times = observationTimestamps(sourceOptions);
|
|
990
|
-
for (const field of [cli === 'muse' ? 'context_limit' : 'context_window', 'output_limit']) {
|
|
1024
|
+
for (const field of [...(cli === 'muse' ? ['context_limit'] : ['context_window', 'max_context_window']), 'output_limit']) {
|
|
991
1025
|
if (row[field] != null && (typeof row[field] !== 'number' || !Number.isSafeInteger(row[field]) || row[field] < 1)) {
|
|
992
1026
|
diagnostics.push(`${source}: malformed ${field} for ${id}; retained as unknown`);
|
|
993
1027
|
// Do not round or coerce malformed evidence into a valid observation.
|
|
994
1028
|
}
|
|
995
1029
|
}
|
|
1030
|
+
if (cli === 'codex' && row.effective_context_window_percent != null
|
|
1031
|
+
&& (typeof row.effective_context_window_percent !== 'number' || !Number.isFinite(row.effective_context_window_percent)
|
|
1032
|
+
|| row.effective_context_window_percent <= 0 || row.effective_context_window_percent > 100)) {
|
|
1033
|
+
diagnostics.push(`${source}: malformed effective_context_window_percent for ${id}; context retained as unknown`);
|
|
1034
|
+
}
|
|
996
1035
|
for (const field of ['is_default', 'is_current', 'retired', 'deprecated']) {
|
|
997
1036
|
if (row[field] != null && typeof row[field] !== 'boolean')
|
|
998
1037
|
diagnostics.push(`${source}: malformed ${field} for ${id}`);
|
|
@@ -1040,8 +1079,16 @@ function normalizeObservationRows(rows, options, cli) {
|
|
|
1040
1079
|
}
|
|
1041
1080
|
const efforts = cli === 'muse' ? museEfforts(row) : [...new Set([...stringList(row.supported_reasoning_levels),
|
|
1042
1081
|
...(typeof row.default_reasoning_level === 'string' && row.default_reasoning_level ? [row.default_reasoning_level] : [])])];
|
|
1082
|
+
// Codex's registry provider and catalogAdapter contain no inference-provider
|
|
1083
|
+
// identity. Its providerless native catalog is OpenAI; never infer from a slug.
|
|
1084
|
+
// Muse's real local catalogs use provider_id, not provider.
|
|
1085
|
+
const llmProvider = typeof row.provider === 'string' && row.provider ? row.provider
|
|
1086
|
+
: cli === 'muse' && typeof row.provider_id === 'string' && row.provider_id ? row.provider_id
|
|
1087
|
+
: cli === 'codex' ? 'openai' : undefined;
|
|
1043
1088
|
const model = {
|
|
1044
|
-
id,
|
|
1089
|
+
id, apiModelId: typeof row.api_model_id === 'string' ? row.api_model_id : id,
|
|
1090
|
+
...(llmProvider ? { llmProvider } : {}),
|
|
1091
|
+
...(visibility === 'unknown' ? {} : { hidden: visibility === 'hidden' }),
|
|
1045
1092
|
...(cli === 'muse' ? { isDefault: row.is_default === true } : {}),
|
|
1046
1093
|
...(efforts.length ? { reasoningEfforts: efforts } : {}),
|
|
1047
1094
|
worker: buildWorkerObservation(sourceOptions, {
|
|
@@ -1049,18 +1096,25 @@ function normalizeObservationRows(rows, options, cli) {
|
|
|
1049
1096
|
visibility, ...lifecycle, releaseDate,
|
|
1050
1097
|
availability: lifecycle.lifecycle === 'retired' ? 'unavailable'
|
|
1051
1098
|
: cli === 'muse' ? (row.is_current === true ? 'accessible' : 'unknown') : 'accessible',
|
|
1052
|
-
contextTokens:
|
|
1099
|
+
contextTokens: cli === 'muse' ? normalizePositiveInt(row.context_limit) : codexContextTokens(row),
|
|
1053
1100
|
maxOutputTokens: normalizePositiveInt(row.output_limit),
|
|
1054
|
-
efforts: efforts
|
|
1101
|
+
efforts: catalogEfforts(efforts, rawEfforts),
|
|
1102
|
+
// The native renderer passes model_reasoning_effort (Muse: --reasoning-effort).
|
|
1055
1103
|
effortSupport: efforts.length ? 'explicit' : 'unknown',
|
|
1056
1104
|
...(cli === 'muse' ? { dataUseNote: 'contributor policy is config-scoped' } : {}),
|
|
1057
1105
|
provenanceFields: cli === 'muse'
|
|
1058
|
-
? ['/id:model_id', '/isDefault:is_default', '/availability:is_current', '/efforts:reasoning_effort_variants[].tier',
|
|
1059
|
-
|
|
1106
|
+
? ['/id:model_id', '/isDefault:is_default', '/availability:is_current', '/efforts:reasoning_effort_variants[].tier',
|
|
1107
|
+
'/efforts/selfDelegates:catalog-description+verified-ultra-rule', '/contextTokens:context_limit', '/maxOutputTokens:output_limit', '/releaseDate:release_date']
|
|
1108
|
+
: ['/id:slug', '/efforts:supported_reasoning_levels+default_reasoning_level',
|
|
1109
|
+
'/efforts/selfDelegates:catalog-description+verified-ultra-rule',
|
|
1110
|
+
'/contextTokens:context_window+max_context_window+effective_context_window_percent'],
|
|
1060
1111
|
}),
|
|
1061
1112
|
};
|
|
1062
1113
|
// Bind compatibility-row values without including unrelated credential-bearing row keys.
|
|
1063
|
-
|
|
1114
|
+
model.worker.provenance[0].fields.push('/apiModelId');
|
|
1115
|
+
if (model.llmProvider)
|
|
1116
|
+
model.worker.provenance[0].fields.push('/llmProvider', `/llmProvider:${typeof row.provider === 'string' && row.provider ? 'provider' : cli === 'muse' ? 'provider_id' : 'codex-openai-default'}`);
|
|
1117
|
+
const compatibility = { id: model.id, apiModelId: model.apiModelId, llmProvider: model.llmProvider, hidden: model.hidden, isDefault: model.isDefault, reasoningEfforts: model.reasoningEfforts };
|
|
1064
1118
|
model.worker.evidenceDigest = discoveryDigest(JSON.stringify({
|
|
1065
1119
|
workerDigest: model.worker.evidenceDigest, ...compatibility,
|
|
1066
1120
|
}));
|
|
@@ -1132,6 +1186,17 @@ function reconcileDuplicateObservations(models, diagnostics, sourcePointers) {
|
|
|
1132
1186
|
break;
|
|
1133
1187
|
}
|
|
1134
1188
|
}
|
|
1189
|
+
// Provider/API identity is selection evidence too. Conflicting rows must
|
|
1190
|
+
// not acquire a qualified route merely because one row appeared first.
|
|
1191
|
+
for (const field of ['apiModelId', 'llmProvider']) {
|
|
1192
|
+
if (new Set(group.map(row => row[field])).size < 2)
|
|
1193
|
+
continue;
|
|
1194
|
+
conflicts.push({ field: `/${field}`, evidence: group.map(row => ({ value: row[field] ?? null,
|
|
1195
|
+
source: row.worker.provenance[0].source, pointer: field === 'apiModelId' ? '/api_model_id' : '/provider',
|
|
1196
|
+
observedAt: row.worker.observedAt, digest: row.worker.provenance[0].digest })) });
|
|
1197
|
+
diagnostics.push(`${model.id}: conflicting ${field}`);
|
|
1198
|
+
delete model[field];
|
|
1199
|
+
}
|
|
1135
1200
|
// Compatibility defaults also remain evidence rather than silently keeping the first.
|
|
1136
1201
|
if (new Set(group.map(m => m.isDefault)).size > 1) {
|
|
1137
1202
|
diagnostics.push(`${model.id}: conflicting isDefault values from ${group.map(m => m.worker.provenance[0].source).join(', ')}`);
|