@aiwg/cli 2026.10.1 → 2026.10.4
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/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- 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 +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
- 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 +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -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/repair.js +9 -4
- 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 +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- 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 +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- 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 +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -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 +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -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 +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -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-health.mjs +14 -3
- 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 +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- 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 +25 -6
- 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 +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- 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/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -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/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- 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 +366 -56
- 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 +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- 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/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- 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 +127 -82
- 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-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +647 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -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 +53 -23
- 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/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- 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/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -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 +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- 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 +30 -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 +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- 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 +84 -46
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- 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 +195 -14
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- 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 +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- 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/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- 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/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- 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 +360 -0
- package/tools/providers/omp-transport.mjs +194 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- 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 +439 -0
- 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 +2916 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
/** One nonlaunching typed renderer shared by models resolve and Ralph. */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { constants } from 'node:fs';
|
|
5
|
+
import { lstat, realpath, open } from 'node:fs/promises';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { resolveWorkerPlan } from '../models/resolver.js';
|
|
8
|
+
import { workerConfigDigest, validateWorkerHealthConfig } from '../config/aiwg-config.js';
|
|
9
|
+
import { assertValidWorkerPayload } from '../mission-protocol/codecs.js';
|
|
10
|
+
import { qualificationDigest } from '../models/qualification-digest.mjs';
|
|
11
|
+
import { qualifyWorkerInvocationProfile, isOperatorQualifiedWorkerAdapter, assertOperatorWorkerNativeArgv, assertWorkerSelectionAllowed, requiredWorkerControls, requiresWorkerExternalBoundary, isCanonicalWorkerModelKey, assertWorkerHealthQualification, WorkerPolicyError, DEFAULT_WORKER_SELECTION } from '../models/provider-policy.js';
|
|
12
|
+
/** Frozen per-attempt windows; parent/transport ceilings include time already spent. */
|
|
13
|
+
export function resolveWorkerEffectiveHealth(config, input) {
|
|
14
|
+
const roleDefaults = { efficiency: 1, coding: 1, reasoning: 2 };
|
|
15
|
+
const effortDefaults = { xhigh: 2, max: 3 };
|
|
16
|
+
if (!Object.hasOwn(roleDefaults, input.role) || !isCanonicalWorkerModelKey(input.modelKey))
|
|
17
|
+
throw new Error('Invalid worker health role/model identity');
|
|
18
|
+
const configErrors = validateWorkerHealthConfig(config);
|
|
19
|
+
if (configErrors.length)
|
|
20
|
+
throw new Error(configErrors.join('; '));
|
|
21
|
+
if (config?.requireStepCap && !['native', 'external'].includes(input.stepEnforcement))
|
|
22
|
+
throw new Error('Required model-step cap is not qualified for enforcement');
|
|
23
|
+
for (const value of [input.wallSeconds, input.transportSeconds, input.remainingSteps, input.startupCeilingSeconds, input.checkSeconds, input.profileSteps]) {
|
|
24
|
+
if (value !== undefined && (!Number.isFinite(value) || value <= 0))
|
|
25
|
+
throw new Error('Invalid remaining worker health ceiling');
|
|
26
|
+
}
|
|
27
|
+
const factors = [config?.roleScale?.[input.role] ?? roleDefaults[input.role] ?? 1,
|
|
28
|
+
input.effort === null ? 1 : config?.effortScale?.[input.effort] ?? effortDefaults[input.effort] ?? 1,
|
|
29
|
+
config?.modelScale?.[input.modelKey] ?? 1];
|
|
30
|
+
if (factors.some(factor => !Number.isFinite(factor) || factor < 1))
|
|
31
|
+
throw new Error('Invalid worker health scale');
|
|
32
|
+
const scale = Math.max(1, Math.min(6, Math.max(...factors)));
|
|
33
|
+
const pointer = (field) => config?.[field] === undefined ? `defaults/parallelism.health.${field}` : `/parallelism/health/${field}`;
|
|
34
|
+
const origins = {};
|
|
35
|
+
for (const field of ['startupSeconds', 'checkSeconds', 'quietSeconds', 'stallSeconds', 'diagnosticGraceSeconds', 'terminateGraceSeconds', 'hardTimeoutSeconds', 'maxSteps'])
|
|
36
|
+
origins[field] = [pointer(field)];
|
|
37
|
+
const modelPointer = input.modelKey.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
38
|
+
if (config?.modelStartupSeconds?.[input.modelKey] !== undefined)
|
|
39
|
+
origins.startupSeconds = [`/parallelism/health/modelStartupSeconds/${modelPointer}`];
|
|
40
|
+
const factorOrigins = [config?.roleScale?.[input.role] === undefined ? `defaults/parallelism.health.roleScale.${input.role}` : `/parallelism/health/roleScale/${input.role}`,
|
|
41
|
+
input.effort === null || config?.effortScale?.[input.effort] === undefined ? `defaults/parallelism.health.effortScale.${input.effort ?? 'not-applicable'}` : `/parallelism/health/effortScale/${input.effort.replace(/~/g, '~0').replace(/\//g, '~1')}`,
|
|
42
|
+
config?.modelScale?.[input.modelKey] === undefined ? 'defaults/parallelism.health.modelScale' : `/parallelism/health/modelScale/${modelPointer}`];
|
|
43
|
+
origins.scale = factorOrigins.filter((_, index) => factors[index] === Math.max(...factors));
|
|
44
|
+
const limitingOrigins = (values) => {
|
|
45
|
+
const minimum = Math.min(...values.map(([value]) => value ?? Infinity));
|
|
46
|
+
return values.filter(([value]) => value === minimum).map(([, origin]) => origin);
|
|
47
|
+
};
|
|
48
|
+
origins.hardTimeoutSeconds = limitingOrigins([[config?.hardTimeoutSeconds ?? 1800, pointer('hardTimeoutSeconds')], [input.wallSeconds, 'runtime/remainingWallSeconds'], [input.transportSeconds, 'profile/transportSeconds']]);
|
|
49
|
+
origins.maxSteps = limitingOrigins([[config?.maxSteps ?? 100, pointer('maxSteps')], [input.remainingSteps, 'runtime/remainingSteps'], [input.profileSteps, 'profile/maxSteps']]);
|
|
50
|
+
origins.stepEnforcement = ['profile/controls/stepCap'];
|
|
51
|
+
const health = { version: 1,
|
|
52
|
+
startupSeconds: Math.min(config?.modelStartupSeconds?.[input.modelKey] ?? config?.startupSeconds ?? 60, input.startupCeilingSeconds ?? Infinity),
|
|
53
|
+
checkSeconds: Math.min(config?.checkSeconds ?? 15, input.checkSeconds ?? Infinity), quietSeconds: (config?.quietSeconds ?? 90) * scale,
|
|
54
|
+
stallSeconds: (config?.stallSeconds ?? 300) * scale, diagnosticGraceSeconds: config?.diagnosticGraceSeconds ?? 60,
|
|
55
|
+
terminateGraceSeconds: config?.terminateGraceSeconds ?? 5,
|
|
56
|
+
hardTimeoutSeconds: Math.min(config?.hardTimeoutSeconds ?? 1800, input.wallSeconds, input.transportSeconds ?? Infinity),
|
|
57
|
+
maxSteps: Math.min(config?.maxSteps ?? 100, input.remainingSteps ?? Infinity, input.profileSteps ?? Infinity),
|
|
58
|
+
stepEnforcement: input.stepEnforcement, scale, profileRef: input.profileRef, runDir: null, origins };
|
|
59
|
+
if (input.startupCeilingSeconds !== undefined)
|
|
60
|
+
origins.startupSeconds = limitingOrigins([[config?.modelStartupSeconds?.[input.modelKey] ?? config?.startupSeconds ?? 60, origins.startupSeconds[0]], [input.startupCeilingSeconds, 'runtime/startupCeilingSeconds']]);
|
|
61
|
+
if (input.checkSeconds !== undefined)
|
|
62
|
+
origins.checkSeconds = limitingOrigins([[config?.checkSeconds ?? 15, pointer('checkSeconds')], [input.checkSeconds, 'task/checkSeconds']]);
|
|
63
|
+
health.truncatedWindows = ['startup', 'quiet', 'stall'].filter(window => health[`${window}Seconds`] > health.hardTimeoutSeconds);
|
|
64
|
+
health.diagnosticGraceSeconds = Math.min(health.diagnosticGraceSeconds, health.hardTimeoutSeconds);
|
|
65
|
+
health.terminateGraceSeconds = Math.min(health.terminateGraceSeconds, health.hardTimeoutSeconds);
|
|
66
|
+
for (const field of ['diagnosticGraceSeconds', 'terminateGraceSeconds']) {
|
|
67
|
+
if (health[field] === health.hardTimeoutSeconds)
|
|
68
|
+
origins[field].push(...origins.hardTimeoutSeconds);
|
|
69
|
+
}
|
|
70
|
+
assertValidWorkerPayload('workerEffectiveHealth', health);
|
|
71
|
+
return health;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The exact digest formula an MCP worker binding's `inputSchemaDigest`/
|
|
75
|
+
* `outputSchemaDigest` are pinned against (used below and reused verbatim by
|
|
76
|
+
* the #2892 MCP worker-invoke bridge's own live `tools/list` schema-drift
|
|
77
|
+
* check — see `src/mcp/tools/worker-invoke.mjs` M3). Deliberately the raw
|
|
78
|
+
* `JSON.stringify` of the schema object, NOT `workerConfigDigest`'s
|
|
79
|
+
* canonicalized (sorted-key) form — a schema's own declared key order is
|
|
80
|
+
* part of what gets pinned at qualification time.
|
|
81
|
+
*/
|
|
82
|
+
export function mcpToolSchemaDigest(schema) {
|
|
83
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(schema)).digest('hex')}`;
|
|
84
|
+
}
|
|
85
|
+
export async function prepareWorkerInvocation(invocation, qualification) {
|
|
86
|
+
const result = structuredClone(invocation);
|
|
87
|
+
// shellCommand is derived syntax, never authority supplied by a brief.
|
|
88
|
+
if (result.transport === 'shell')
|
|
89
|
+
result.shellCommand = '';
|
|
90
|
+
// Validate the established invocation shape first. Health is optional and
|
|
91
|
+
// is checked after policy, installation and transport qualification below.
|
|
92
|
+
const contract = { ...result };
|
|
93
|
+
delete contract.health;
|
|
94
|
+
assertValidWorkerPayload('workerResolvedInvocation', contract);
|
|
95
|
+
if ((qualification.platform ?? process.platform) !== 'linux') {
|
|
96
|
+
throw new Error('Unsupported worker invocation platform; Linux is required');
|
|
97
|
+
}
|
|
98
|
+
if (!Number.isFinite(qualification.now) || Date.parse(result.expiresAt) <= qualification.now) {
|
|
99
|
+
throw new Error('Worker invocation is expired or has no validation clock');
|
|
100
|
+
}
|
|
101
|
+
if (qualification.adapter.id !== qualification.adapterId) {
|
|
102
|
+
throw new Error('Worker invocation adapter identity mismatch');
|
|
103
|
+
}
|
|
104
|
+
for (const [key, value] of Object.entries(qualification.expectedDigests ?? {})) {
|
|
105
|
+
if (result[key] !== value)
|
|
106
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', `Worker invocation ${key} changed`);
|
|
107
|
+
}
|
|
108
|
+
const verified = Date.parse(qualification.adapter.qualification.verifiedAt ?? '');
|
|
109
|
+
const policyDays = qualification.policy?.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays;
|
|
110
|
+
if (!Number.isFinite(verified) || verified > qualification.now || qualification.now - verified >= policyDays * 86400000) {
|
|
111
|
+
throw new Error('Worker installation qualification is stale or invalid');
|
|
112
|
+
}
|
|
113
|
+
assertWorkerSelectionAllowed(result.selected, qualification);
|
|
114
|
+
const profile = qualifyWorkerInvocationProfile(qualification.adapter, {
|
|
115
|
+
profile: result.selected.profileRef, transport: result.transport,
|
|
116
|
+
cliVersion: qualification.cliVersion,
|
|
117
|
+
requiredControls: qualification.requiredControls, policy: qualification.policy,
|
|
118
|
+
permissions: qualification.permissions, effort: result.selected.effort.value, boundary: qualification.boundary,
|
|
119
|
+
mcp: qualification.mcp,
|
|
120
|
+
});
|
|
121
|
+
if (requiresWorkerExternalBoundary(qualification.adapter, { ...qualification,
|
|
122
|
+
profile: result.selected.profileRef, transport: result.transport, effort: result.selected.effort.value })) {
|
|
123
|
+
const installed = qualification.installation;
|
|
124
|
+
const evidence = profile.installationQualification;
|
|
125
|
+
if (!installed || !evidence || ['scopeDigest', 'fixtureDigest', 'verifiedAt', 'evidenceRef']
|
|
126
|
+
.some(key => installed.evidence?.[key] !== evidence[key])
|
|
127
|
+
|| !qualification.boundary || qualificationDigest(qualification.boundary) !== qualificationDigest(installed.boundary)) {
|
|
128
|
+
throw new WorkerPolicyError('OG_ADAPTER_UNQUALIFIED', 'Worker installation evidence and qualified boundary must match');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (profile.installationQualification) {
|
|
132
|
+
const installedAt = Date.parse(profile.installationQualification.verifiedAt);
|
|
133
|
+
if (!Number.isFinite(installedAt) || installedAt > qualification.now
|
|
134
|
+
|| qualification.now - installedAt >= policyDays * 86400000) {
|
|
135
|
+
throw new Error('Worker installation qualification is stale or invalid');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const scopeDigest = profile.installationQualification?.scopeDigest ?? qualification.adapter.qualification.fixtureDigest;
|
|
139
|
+
if (result.selected.qualificationDigest !== scopeDigest)
|
|
140
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker qualification digest changed');
|
|
141
|
+
const absolute = (value) => typeof value === 'string' && path.posix.isAbsolute(value) && !value.includes('\0');
|
|
142
|
+
for (const key of ['cwd', 'briefPath', 'stdoutPath', 'stderrPath', 'finalPath']) {
|
|
143
|
+
if (!absolute(result[key]))
|
|
144
|
+
throw new Error(`Worker invocation requires absolute ${key}`);
|
|
145
|
+
}
|
|
146
|
+
const briefPath = path.posix.normalize(result.briefPath);
|
|
147
|
+
if ([result.stdoutPath, result.stderrPath, result.finalPath].some(value => path.posix.normalize(value) === briefPath)
|
|
148
|
+
|| path.posix.normalize(result.stdoutPath) === path.posix.normalize(result.stderrPath)) {
|
|
149
|
+
throw new Error('Worker invocation input/output paths must be distinct');
|
|
150
|
+
}
|
|
151
|
+
if (result.transport === 'shell') {
|
|
152
|
+
assertOperatorWorkerNativeArgv(qualification.adapter, result.argv, result.selected, qualification.permissions);
|
|
153
|
+
if (!absolute(result.argv[0]) || result.argv[0].includes('=') || result.argv.some(value => value.includes('\0'))) {
|
|
154
|
+
throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Worker invocation requires an absolute binary without equals and NUL-free argv');
|
|
155
|
+
}
|
|
156
|
+
if (result.envNames.some(name => !profile.envAllowlist.includes(name))) {
|
|
157
|
+
throw new Error('Worker invocation environment is outside the qualified allowlist');
|
|
158
|
+
}
|
|
159
|
+
const quote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
160
|
+
// Only validated environment names appear as shell syntax; their values
|
|
161
|
+
// remain in the conductor environment and are never printed in the plan.
|
|
162
|
+
const environment = result.envNames.map(name => `${name}="\${${name}-}"`).join(' ');
|
|
163
|
+
// The frozen 'closed' value means empty stdin (immediate EOF). 'brief-json'
|
|
164
|
+
// pipes a conductor-built params document framed from the brief file
|
|
165
|
+
// (worker-stdin-frame.mjs), so brief text never enters any argv.
|
|
166
|
+
const worker = `env -i${environment ? ` ${environment}` : ''} ${result.argv.map(quote).join(' ')}`;
|
|
167
|
+
const outputs = `> ${quote(result.stdoutPath)} 2> ${quote(result.stderrPath)}`;
|
|
168
|
+
if (result.stdin === 'brief-json') {
|
|
169
|
+
const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
|
|
170
|
+
const frame = [process.execPath, framer, result.briefPath, result.selected.model, result.cwd].map(quote).join(' ');
|
|
171
|
+
result.shellCommand = `cd ${quote(result.cwd)} && env -i ${frame} | ${worker} ${outputs}`;
|
|
172
|
+
}
|
|
173
|
+
else if (result.stdin === 'adapter-frame') {
|
|
174
|
+
const binding = qualification.stdinFramerBinding;
|
|
175
|
+
if (!binding || !absolute(binding.storePath) || !/^sha256:[a-f0-9]{64}$/.test(binding.storeDigest)) {
|
|
176
|
+
throw new Error('Adapter stdin framer requires a trusted installation binding');
|
|
177
|
+
}
|
|
178
|
+
const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
|
|
179
|
+
const frame = [process.execPath, framer, '--adapter-frame', result.briefPath, result.cwd,
|
|
180
|
+
result.selected.cli, qualification.adapterId, result.selected.qualificationDigest, result.briefDigest,
|
|
181
|
+
binding.storePath, binding.storeDigest, JSON.stringify(qualification.policy ?? null)].map(quote).join(' ');
|
|
182
|
+
result.shellCommand = `cd ${quote(result.cwd)} && env -i${environment ? ` ${environment}` : ''} ${frame} | ${worker} ${outputs}`;
|
|
183
|
+
}
|
|
184
|
+
else if (result.stdin === 'closed' || result.stdin === 'brief-text') {
|
|
185
|
+
const stdin = result.stdin === 'closed' ? '< /dev/null' : `< ${quote(result.briefPath)}`;
|
|
186
|
+
result.shellCommand = `cd ${quote(result.cwd)} && ${worker} ${stdin} ${outputs}`;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
throw new Error(`Unsupported worker stdin mode: ${result.stdin}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const binding = result.mcp;
|
|
194
|
+
for (const key of ['serverRef', 'tool', 'inputSchemaDigest', 'outputSchemaDigest', 'sessionIsolation']) {
|
|
195
|
+
if (binding[key] !== profile.mcp[key])
|
|
196
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', `MCP worker binding changed: ${key}`);
|
|
197
|
+
}
|
|
198
|
+
if (binding.timeoutSeconds > result.limits.wallSeconds) {
|
|
199
|
+
throw new Error('MCP worker timeout exceeds the invocation deadline');
|
|
200
|
+
}
|
|
201
|
+
for (const key of ['input', 'output']) {
|
|
202
|
+
const schema = qualification.mcpSchemas?.[key];
|
|
203
|
+
if (!schema || mcpToolSchemaDigest(schema) !== binding[`${key}SchemaDigest`]) {
|
|
204
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', `MCP worker ${key} schema digest changed`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const { default: Ajv2020 } = await import('ajv/dist/2020.js');
|
|
208
|
+
const { default: addFormats } = await import('ajv-formats');
|
|
209
|
+
const validate = addFormats(new Ajv2020({ strict: true, allErrors: true })).compile(qualification.mcpSchemas.input);
|
|
210
|
+
if (!validate(binding.arguments))
|
|
211
|
+
throw new Error('MCP worker arguments violate the pinned input schema');
|
|
212
|
+
}
|
|
213
|
+
assertValidWorkerPayload('workerResolvedInvocation', result);
|
|
214
|
+
if (result.health !== undefined) {
|
|
215
|
+
if (result.health.profileRef !== result.selected.profileRef
|
|
216
|
+
|| result.health.hardTimeoutSeconds > Math.min(result.limits.wallSeconds, result.mcp?.timeoutSeconds ?? Infinity)) {
|
|
217
|
+
throw new Error('Worker health profile or wall ceiling mismatch');
|
|
218
|
+
}
|
|
219
|
+
if (['native', 'external'].includes(result.health.stepEnforcement)
|
|
220
|
+
&& profile.controls.stepCap?.support !== result.health.stepEnforcement)
|
|
221
|
+
throw new Error('Unqualified worker step enforcement');
|
|
222
|
+
}
|
|
223
|
+
assertWorkerHealthQualification(profile, { profileRef: result.selected.profileRef, cliVersion: qualification.cliVersion,
|
|
224
|
+
accountScope: qualification.installation?.accountScope ?? '', inferenceProvider: result.selected.inferenceProvider,
|
|
225
|
+
model: result.selected.model, effort: result.selected.effort.value });
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
/** Reject unsupported syntax before discovery or any path reads. */
|
|
229
|
+
export function parseWorkerCommandArgs(args) {
|
|
230
|
+
const names = { role: 'role', task: 'task', group: 'group', brief: 'brief',
|
|
231
|
+
workdir: 'workdir', 'final-output': 'final_output', transport: 'transport', format: 'format' };
|
|
232
|
+
const result = {};
|
|
233
|
+
let json = false;
|
|
234
|
+
let premiumAuthorized = false;
|
|
235
|
+
for (let i = 0; i < args.length; i++) {
|
|
236
|
+
const token = args[i];
|
|
237
|
+
if (token === '--authorize-premium') {
|
|
238
|
+
if (premiumAuthorized)
|
|
239
|
+
throw new Error('Duplicate --authorize-premium');
|
|
240
|
+
premiumAuthorized = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (token === '--json') {
|
|
244
|
+
if (json)
|
|
245
|
+
throw new Error('Duplicate --json');
|
|
246
|
+
json = true;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const match = /^--([^=]+)(?:=([\s\S]*))?$/.exec(token);
|
|
250
|
+
if (!match || !Object.hasOwn(names, match[1]))
|
|
251
|
+
throw new Error(`Unsupported models resolve argument: ${token}`);
|
|
252
|
+
const key = names[match[1]];
|
|
253
|
+
if (key in result)
|
|
254
|
+
throw new Error(`Duplicate --${match[1]}`);
|
|
255
|
+
const value = match[2] ?? args[++i];
|
|
256
|
+
if (!value || value.startsWith('--') && match[2] === undefined || value.includes('\0')) {
|
|
257
|
+
throw new Error(`--${match[1]} requires a value`);
|
|
258
|
+
}
|
|
259
|
+
result[key] = value;
|
|
260
|
+
}
|
|
261
|
+
if (json && result.format && result.format !== 'json')
|
|
262
|
+
throw new Error('--json conflicts with --format');
|
|
263
|
+
if (json)
|
|
264
|
+
result.format = 'json';
|
|
265
|
+
for (const key of ['role', 'task', 'group', 'brief', 'workdir', 'final_output']) {
|
|
266
|
+
if (!result[key])
|
|
267
|
+
throw new Error(`Missing ${key}`);
|
|
268
|
+
}
|
|
269
|
+
if (!['efficiency', 'coding', 'reasoning'].includes(result.role))
|
|
270
|
+
throw new Error('Unsupported role');
|
|
271
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(result.group))
|
|
272
|
+
throw new Error('Invalid group');
|
|
273
|
+
if (result.transport && !['shell', 'mcp'].includes(result.transport))
|
|
274
|
+
throw new Error('Unsupported transport');
|
|
275
|
+
if (result.format && !['shell', 'json'].includes(result.format))
|
|
276
|
+
throw new Error('Unsupported renderer; use POSIX shell or JSON');
|
|
277
|
+
return { ...result, ...(premiumAuthorized ? { authority: { premiumAuthorized: true, maxAutoTier: 3 } } : {}) };
|
|
278
|
+
}
|
|
279
|
+
const bytesDigest = (bytes) => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
280
|
+
export async function inspectWorkerPath(filename, kind) {
|
|
281
|
+
if (!path.isAbsolute(filename) || filename.includes('\0'))
|
|
282
|
+
throw new Error('Absolute NUL-free paths are required');
|
|
283
|
+
if (filename.split('/').includes('..'))
|
|
284
|
+
throw new Error('Worker path traversal is not allowed');
|
|
285
|
+
const normalized = path.resolve(filename);
|
|
286
|
+
let current = normalized;
|
|
287
|
+
while (true) {
|
|
288
|
+
try {
|
|
289
|
+
const canonical = await realpath(current);
|
|
290
|
+
if (canonical !== current)
|
|
291
|
+
throw new Error(`Symlink path is not allowed: ${filename}`);
|
|
292
|
+
if (current !== normalized)
|
|
293
|
+
return false;
|
|
294
|
+
const stat = await lstat(normalized);
|
|
295
|
+
if (kind === 'directory' ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1) {
|
|
296
|
+
throw new Error(`Expected an unaliased ${kind}: ${filename}`);
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (error.code !== 'ENOENT')
|
|
302
|
+
throw error;
|
|
303
|
+
const parent = path.dirname(current);
|
|
304
|
+
if (parent === current)
|
|
305
|
+
throw error;
|
|
306
|
+
current = parent;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const inspectPath = inspectWorkerPath;
|
|
311
|
+
async function readBrief(filename) {
|
|
312
|
+
if (!await inspectPath(filename, 'file'))
|
|
313
|
+
return null;
|
|
314
|
+
const handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
315
|
+
try {
|
|
316
|
+
const stat = await handle.stat();
|
|
317
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 262144)
|
|
318
|
+
throw new Error('Brief must be a bounded regular file (256 KiB)');
|
|
319
|
+
const buffer = Buffer.alloc(262145);
|
|
320
|
+
let length = 0;
|
|
321
|
+
while (length < buffer.length) {
|
|
322
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
|
|
323
|
+
if (!bytesRead)
|
|
324
|
+
break;
|
|
325
|
+
length += bytesRead;
|
|
326
|
+
}
|
|
327
|
+
if (length > 262144)
|
|
328
|
+
throw new Error('Brief exceeds 256 KiB');
|
|
329
|
+
return buffer.subarray(0, length);
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
await handle.close();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/** Structured root-check failure; only these explicit path details are public. */
|
|
336
|
+
export class WorkerPathScopeError extends WorkerPolicyError {
|
|
337
|
+
pointer;
|
|
338
|
+
constructor(key, filename, roots) {
|
|
339
|
+
super('OG_CONFIG_REFERENCE', `${filename} must be inside the worker-writable ${roots.length === 1 ? 'root' : 'roots'} ${roots.join(', ')}.`);
|
|
340
|
+
this.pointer = key === 'briefPath' ? '/request/brief' : '/request/finalOutput';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/** Re-resolve saved JSON through the same trusted owners; saved argv is never authority. */
|
|
344
|
+
export async function revalidateWorkerInvocationPlan(saved, snapshot, drivers, now = new Date().toISOString()) {
|
|
345
|
+
if (!saved.request || saved.mode !== 'direct' || saved.status !== 'eligible'
|
|
346
|
+
|| saved.invocation?.readiness !== 'launchable' || Date.parse(saved.invocation.expiresAt) <= Date.parse(now)) {
|
|
347
|
+
throw new Error('A fresh launchable models resolve JSON plan is required');
|
|
348
|
+
}
|
|
349
|
+
assertValidWorkerPayload('workerResolvedInvocation', saved.invocation);
|
|
350
|
+
const current = await resolveWorkerInvocation(snapshot, saved.request, drivers, now, { digests: saved.digests, briefDigest: saved.invocation.briefDigest ?? null });
|
|
351
|
+
// Expiry is calculated from the resolution clock. All execution bindings,
|
|
352
|
+
// controls, thresholds and paths must remain byte-for-byte digest equivalent.
|
|
353
|
+
if (!current.invocation || current.status !== 'eligible'
|
|
354
|
+
|| Date.parse(saved.invocation.expiresAt) > Date.parse(current.invocation.expiresAt)
|
|
355
|
+
|| qualificationDigest({ ...current.invocation, expiresAt: saved.invocation.expiresAt }) !== qualificationDigest(saved.invocation)) {
|
|
356
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Saved invocation differs from current qualified plan; resolve again');
|
|
357
|
+
}
|
|
358
|
+
return structuredClone(saved.invocation);
|
|
359
|
+
}
|
|
360
|
+
/** Snapshot/driver inputs are supplied by the existing trusted installation owner.
|
|
361
|
+
* This function does no probing, spawning, registration, admission or writes. */
|
|
362
|
+
export async function resolveWorkerInvocation(snapshot, request, drivers, now = new Date().toISOString(), expected) {
|
|
363
|
+
snapshot = structuredClone(snapshot);
|
|
364
|
+
request = structuredClone(request);
|
|
365
|
+
let pointer = '/request/brief';
|
|
366
|
+
let failureCode = 'OG_CONFIG_REFERENCE';
|
|
367
|
+
let plan;
|
|
368
|
+
try {
|
|
369
|
+
if (!path.isAbsolute(request.brief))
|
|
370
|
+
throw new WorkerPolicyError('OG_CONFIG_REFERENCE', '--brief must be an absolute path; relative paths are not resolved against cwd or --workdir.');
|
|
371
|
+
const bytes = await readBrief(request.brief);
|
|
372
|
+
const briefDigest = bytes === null ? null : bytesDigest(bytes);
|
|
373
|
+
if (expected && expected.briefDigest !== briefDigest)
|
|
374
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Brief digest changed; resolve again');
|
|
375
|
+
let brief;
|
|
376
|
+
if (bytes?.toString('utf8').trimStart().startsWith('{')) {
|
|
377
|
+
brief = JSON.parse(bytes.toString('utf8'));
|
|
378
|
+
assertValidWorkerPayload('workerDirectBrief', brief);
|
|
379
|
+
if (brief.intent.role !== request.role || brief.intent.taskClass !== request.task || brief.intent.group !== request.group
|
|
380
|
+
|| brief.workspace.path !== request.workdir || brief.output.resultPath !== request.final_output) {
|
|
381
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Brief role/task/group/workspace/output differs from request');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
plan = resolveWorkerPlan(snapshot, { role: request.role, taskClass: request.task, group: request.group,
|
|
385
|
+
transport: request.transport, authority: request.authority, now, expectedDigests: expected?.digests,
|
|
386
|
+
...(brief ? { dataClass: brief.intent.dataClass, member: brief.intent.pins.member,
|
|
387
|
+
model: brief.intent.pins.model, effort: brief.intent.pins.effort?.value, ceilings: {
|
|
388
|
+
...brief.intent.limits, costUsd: brief.intent.limits.costUsd ?? undefined
|
|
389
|
+
} } : {}),
|
|
390
|
+
context: { estimator: 'utf8-byte-upper-bound-v1', briefTokens: bytes?.length ?? 0,
|
|
391
|
+
inputTokens: 0, toolOutputAllowance: 1024, reservedOutput: 1024 },
|
|
392
|
+
requiredControls: snapshot.config.parallelism?.health?.requireStepCap ? ['stepCap'] : [],
|
|
393
|
+
});
|
|
394
|
+
plan.request = structuredClone(request);
|
|
395
|
+
if (!plan.selected)
|
|
396
|
+
return plan;
|
|
397
|
+
const tuple = plan.selected;
|
|
398
|
+
pointer = `/parallelism/clis/${tuple.cli}`;
|
|
399
|
+
failureCode = 'OG_ADAPTER_UNQUALIFIED';
|
|
400
|
+
const driver = drivers[tuple.cli];
|
|
401
|
+
const binding = snapshot.installations[tuple.cli];
|
|
402
|
+
if (!driver)
|
|
403
|
+
throw new Error('Qualified installation renderer unavailable (#2885 installation owner required)');
|
|
404
|
+
const qualification = structuredClone(driver.qualification);
|
|
405
|
+
const qualificationDigest = workerConfigDigest(qualification);
|
|
406
|
+
if (qualification.cliVersion !== binding.cliVersion)
|
|
407
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Installation version changed');
|
|
408
|
+
const adapter = qualification.adapter;
|
|
409
|
+
// The driver must be bound to the same registry metadata used by the resolver.
|
|
410
|
+
const { resolveWorkerGroupInstallation } = await import('../config/aiwg-config.js');
|
|
411
|
+
const resolved = resolveWorkerGroupInstallation(snapshot.config.parallelism, tuple.cli, snapshot.registry);
|
|
412
|
+
if (workerConfigDigest(resolved.adapter) !== workerConfigDigest(adapter)
|
|
413
|
+
|| workerConfigDigest(qualification.boundary) !== workerConfigDigest(binding.boundary))
|
|
414
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Installation profile/boundary digest changed');
|
|
415
|
+
const selected = { group: tuple.group, member: tuple.member,
|
|
416
|
+
cli: tuple.cli, model: tuple.model, inferenceProvider: tuple.inferenceProvider, family: tuple.family,
|
|
417
|
+
effort: { support: tuple.effort === null ? 'not-applicable' : 'explicit', value: tuple.effort },
|
|
418
|
+
profileRef: tuple.profile, qualificationDigest: adapter.invocationProfiles[tuple.profile].installationQualification?.scopeDigest
|
|
419
|
+
?? adapter.qualification.fixtureDigest };
|
|
420
|
+
const policyDigest = workerConfigDigest([plan.digests.configDigest, plan.digests.resolvedModelsDigest]);
|
|
421
|
+
if (brief && (workerConfigDigest(brief.selected) !== workerConfigDigest(selected)
|
|
422
|
+
|| brief.policyDigest !== policyDigest || brief.catalogDigest !== plan.digests.catalogDigest)) {
|
|
423
|
+
pointer = '/request/brief';
|
|
424
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Brief selection/policy/catalog digest changed; prepare and resolve again');
|
|
425
|
+
}
|
|
426
|
+
pointer = '/request/workdir';
|
|
427
|
+
failureCode = 'OG_CONFIG_REFERENCE';
|
|
428
|
+
const workspaceExists = await inspectPath(request.workdir, 'directory');
|
|
429
|
+
pointer = '/request/final_output';
|
|
430
|
+
await inspectPath(request.final_output, 'output');
|
|
431
|
+
const finalPath = path.resolve(request.final_output);
|
|
432
|
+
const stdoutPath = `${finalPath}.stdout`;
|
|
433
|
+
const stderrPath = `${finalPath}.stderr`;
|
|
434
|
+
let outputParentsExist = true;
|
|
435
|
+
for (const output of [finalPath, stdoutPath, stderrPath]) {
|
|
436
|
+
await inspectPath(output, 'output');
|
|
437
|
+
if (path.resolve(request.brief) === output)
|
|
438
|
+
throw new Error('Brief and output paths must be distinct');
|
|
439
|
+
if (!await inspectPath(path.dirname(output), 'directory'))
|
|
440
|
+
outputParentsExist = false;
|
|
441
|
+
}
|
|
442
|
+
const limits = { tokens: Math.min(50000, plan.ceilings.tokens ?? 50000), costUsd: plan.ceilings.costUsd ?? null,
|
|
443
|
+
wallSeconds: Math.min(900, plan.ceilings.wallSeconds ?? 900), maxAttempts: plan.ceilings.maxAttempts ?? 3 };
|
|
444
|
+
const observation = snapshot.catalog.discovery.instances[tuple.cli].models.find(row => row.id === tuple.model || row.apiModelId === tuple.model).worker;
|
|
445
|
+
const profile = adapter.invocationProfiles[tuple.profile];
|
|
446
|
+
const expiresAt = new Date(Math.min(Date.parse(now) + 300000, Date.parse(tuple.observedAt) + (plan.policy?.freshness?.blockHours ?? DEFAULT_WORKER_SELECTION.freshness.blockHours) * 3600000, Math.min(Date.parse(tuple.verifiedAt), Date.parse(adapter.qualification.verifiedAt), profile.installationQualification ? Date.parse(profile.installationQualification.verifiedAt) : Infinity)
|
|
447
|
+
+ (plan.policy?.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays) * 86400000)).toISOString();
|
|
448
|
+
const healthConfig = snapshot.config.parallelism?.health;
|
|
449
|
+
if (profile.installationQualification && !driver.prepareWorkerInvocation) {
|
|
450
|
+
throw new Error('Installation-scoped profile requires the #2885 adapter preparation/recheck method');
|
|
451
|
+
}
|
|
452
|
+
if (tuple.transport === 'mcp') {
|
|
453
|
+
const serverRef = profile.mcp.serverRef;
|
|
454
|
+
const server = await driver.mcpRegistry?.get(serverRef);
|
|
455
|
+
const allowlist = driver.mcpProfile?.servers;
|
|
456
|
+
const tools = driver.mcpProfile?.providerOverrides?.[tuple.cli];
|
|
457
|
+
if (!server || server.enabled === false || !allowlist?.some(name => name === serverRef || name === '__all__')
|
|
458
|
+
|| tools?.toolDeny?.includes(profile.mcp.tool)
|
|
459
|
+
|| tools?.toolAllow && !tools.toolAllow.includes(profile.mcp.tool)) {
|
|
460
|
+
throw new Error('MCP server/tool is not registered and allowed by the current MCP profile');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const controls = isOperatorQualifiedWorkerAdapter(adapter) ? (healthConfig?.requireStepCap ? ['stepCap'] : []) : requiredWorkerControls({ policy: plan.policy, permissions: tuple.permissions, effort: tuple.effort,
|
|
464
|
+
requiredControls: healthConfig?.requireStepCap ? ['stepCap'] : [] });
|
|
465
|
+
pointer = `/parallelism/clis/${tuple.cli}/invocationProfile`;
|
|
466
|
+
failureCode = 'OG_ADAPTER_UNQUALIFIED';
|
|
467
|
+
const healthInput = { role: request.role, effort: tuple.effort,
|
|
468
|
+
modelKey: tuple.modelKey, profileRef: tuple.profile, wallSeconds: limits.wallSeconds,
|
|
469
|
+
transportSeconds: adapter.invocation.kind === 'http' ? adapter.invocation.timeoutSeconds : undefined,
|
|
470
|
+
stepEnforcement: controls.includes('stepCap') ? profile.controls.stepCap.support : 'unavailable' };
|
|
471
|
+
let effectiveHealth = resolveWorkerEffectiveHealth(healthConfig, healthInput);
|
|
472
|
+
const transport = await driver.buildInvocation({ selected: structuredClone(selected), permissions: structuredClone(tuple.permissions),
|
|
473
|
+
cwd: request.workdir, briefPath: request.brief, brief: bytes?.toString('utf8') ?? '', finalPath, limits: structuredClone(limits),
|
|
474
|
+
health: structuredClone(effectiveHealth) });
|
|
475
|
+
if (transport.stdin === 'adapter-frame' && typeof driver.buildWorkerStdinFrame !== 'function')
|
|
476
|
+
throw new Error('Installed adapter stdin framer is required');
|
|
477
|
+
if (transport.mcp?.timeoutSeconds !== undefined)
|
|
478
|
+
effectiveHealth = resolveWorkerEffectiveHealth(healthConfig, { ...healthInput, transportSeconds: Math.min(healthInput.transportSeconds ?? Infinity, transport.mcp.timeoutSeconds) });
|
|
479
|
+
if (workerConfigDigest(driver.qualification) !== qualificationDigest)
|
|
480
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Installation profile digest changed during resolution');
|
|
481
|
+
const invocation = { version: 1, mode: 'direct', transport: tuple.transport, selected,
|
|
482
|
+
policyDigest, catalogDigest: plan.digests.catalogDigest, profileDigest: plan.digests.profileDigest,
|
|
483
|
+
expiresAt, cwd: request.workdir, briefPath: request.brief, stdoutPath, stderrPath, finalPath,
|
|
484
|
+
...(transport.artifacts ? { artifacts: transport.artifacts } : {}),
|
|
485
|
+
argv: transport.argv, stdin: transport.stdin, envNames: transport.envNames, mcp: transport.mcp,
|
|
486
|
+
...(tuple.transport === 'shell' ? { shellCommand: '' } : {}),
|
|
487
|
+
readiness: bytes !== null && workspaceExists && outputParentsExist ? 'launchable' : 'preparatory', briefDigest,
|
|
488
|
+
limits, maxOutputBytes: adapter.invocation.maxOutputBytes ?? 10485760,
|
|
489
|
+
effectiveCaps: tuple.effectiveMaxParallel === null ? {} : { parallel: tuple.effectiveMaxParallel },
|
|
490
|
+
controlEvidence: isOperatorQualifiedWorkerAdapter(adapter) ? [adapter.qualification.evidenceRef, ...controls.map(name => profile.controls[name].evidence)] : controls.map(name => profile.controls[name].evidence),
|
|
491
|
+
health: effectiveHealth, };
|
|
492
|
+
const invocationQualification = { ...qualification, now: Date.parse(now),
|
|
493
|
+
policy: plan.policy, permissions: tuple.permissions, role: request.role, modelObservation: observation,
|
|
494
|
+
expectedDigests: { policyDigest, catalogDigest: plan.digests.catalogDigest, profileDigest: plan.digests.profileDigest, briefDigest },
|
|
495
|
+
requiredControls: controls };
|
|
496
|
+
const prepared = await (driver.prepareWorkerInvocation
|
|
497
|
+
? driver.prepareWorkerInvocation(structuredClone(invocation), invocationQualification)
|
|
498
|
+
: prepareWorkerInvocation(invocation, invocationQualification));
|
|
499
|
+
if (workerConfigDigest({ ...prepared, shellCommand: invocation.shellCommand }) !== workerConfigDigest(invocation)) {
|
|
500
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Installation preparation changed the resolved invocation');
|
|
501
|
+
}
|
|
502
|
+
if (tuple.transport === 'mcp' && profile.installationQualification) {
|
|
503
|
+
const installation = qualification.installation;
|
|
504
|
+
if (!installation?.mcpServerDigest || !driver.mcpRegistry?.assertWorkerQualified) {
|
|
505
|
+
throw new Error('MCP installation registration recheck is required');
|
|
506
|
+
}
|
|
507
|
+
const input = {
|
|
508
|
+
authorizedServerDigest: installation.mcpServerDigest, adapter, profile: tuple.profile,
|
|
509
|
+
cliVersion: qualification.cliVersion, requiredControls: controls,
|
|
510
|
+
policy: plan.policy, permissions: tuple.permissions, effort: tuple.effort, boundary: qualification.boundary,
|
|
511
|
+
mcp: profile.mcp, installationQualification: {
|
|
512
|
+
...profile.installationQualification, status: 'ENABLED', cliVersion: qualification.cliVersion,
|
|
513
|
+
platform: qualification.platform ?? process.platform, serverDigest: installation.mcpServerDigest,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
await driver.mcpRegistry.assertWorkerQualified(profile.mcp.serverRef, input);
|
|
517
|
+
}
|
|
518
|
+
plan.invocation = prepared;
|
|
519
|
+
pointer = '/request/brief';
|
|
520
|
+
const rechecked = await readBrief(request.brief);
|
|
521
|
+
if ((rechecked === null ? null : bytesDigest(rechecked)) !== briefDigest)
|
|
522
|
+
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Brief digest changed during resolution');
|
|
523
|
+
if (prepared.readiness !== 'launchable') {
|
|
524
|
+
plan.diagnostics.push({ code: 'OG_CONFIG_REFERENCE', pointer: bytes === null ? '/request/brief'
|
|
525
|
+
: !workspaceExists ? '/request/workdir' : '/request/final_output',
|
|
526
|
+
message: 'Preparatory plan: create the brief, workspace and output directories, then resolve again.' });
|
|
527
|
+
}
|
|
528
|
+
return plan;
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
plan ??= resolveWorkerPlan(snapshot, { role: request.role, taskClass: request.task, group: request.group, now,
|
|
532
|
+
context: { estimator: 'blocked-path-v1', briefTokens: 0, inputTokens: 0, toolOutputAllowance: 0, reservedOutput: 0 } });
|
|
533
|
+
delete plan.invocation;
|
|
534
|
+
plan.status = 'blocked';
|
|
535
|
+
plan.selected = null;
|
|
536
|
+
plan.diagnostics.push(workerInvocationDiagnostic(error, pointer, failureCode));
|
|
537
|
+
return plan;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/** Public diagnostics contain fixed summaries or explicit typed request-path details. */
|
|
541
|
+
export function workerInvocationDiagnostic(error, pointer, fallback = 'OG_CONFIG_REFERENCE') {
|
|
542
|
+
if (error instanceof WorkerPathScopeError)
|
|
543
|
+
return { code: error.code, pointer: error.pointer, message: error.message };
|
|
544
|
+
const code = error instanceof WorkerPolicyError ? error.code : fallback;
|
|
545
|
+
const messages = {
|
|
546
|
+
OG_CONFIG_REFERENCE: 'Invalid request or configuration reference; check the paths and settings.',
|
|
547
|
+
OG_PIN_MISMATCH: 'Worker selection or pinned evidence changed; resolve again.',
|
|
548
|
+
OG_MODEL_UNAPPROVED: 'The selected model lacks current approved observation evidence.',
|
|
549
|
+
OG_EFFORT_DENIED: 'The selected effort is denied by the resolved worker policy.',
|
|
550
|
+
OG_DATA_POLICY: 'The selected model does not satisfy the resolved data policy.',
|
|
551
|
+
OG_ADAPTER_UNQUALIFIED: 'The installation or invocation could not be revalidated. Run aiwg doctor --probe-workers --qualify to qualify this workspace.',
|
|
552
|
+
OG_POLICY_RELAXATION: 'The request would relax the resolved worker policy.',
|
|
553
|
+
OG_BUDGET_UNMEASURABLE: 'The requested worker budget cannot be enforced.',
|
|
554
|
+
};
|
|
555
|
+
return { code, pointer, message: error instanceof WorkerPolicyError && error.message === '--brief must be an absolute path; relative paths are not resolved against cwd or --workdir.'
|
|
556
|
+
? error.message : messages[code] };
|
|
557
|
+
}
|
|
558
|
+
//# sourceMappingURL=worker-invocation.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Conductor-side stdin framing shared by the shell renderer and the one-shot launcher. */
|
|
2
|
+
export const WORKER_STDIN_FRAME_MAX_BRIEF_BYTES: number;
|
|
3
|
+
export const WORKER_STDIN_FRAME_MAX_BYTES: number;
|
|
4
|
+
export function recordedWorkerStdinBytes(input: {
|
|
5
|
+
filename: string; workspace: string; cli: string; adapterId: string; scopeDigest: string; briefDigest: string;
|
|
6
|
+
storePath: string; storeDigest: string; policy?: import('../models/types.js').WorkerSelectionPolicy;
|
|
7
|
+
}): Promise<Buffer | null>;
|
|
8
|
+
export function workerStdinDocument(input: { brief: string | Buffer; model: string; cwd: string }): string;
|
|
9
|
+
export function workerStdinBytes(invocation: { stdin: 'closed' | 'brief-text' | 'brief-json' | 'adapter-frame'; cwd?: string;
|
|
10
|
+
selected?: { model?: string } }, brief: string | Buffer, adapter?: { buildWorkerStdinFrame?(brief: string | Buffer): string | Uint8Array }): Buffer | null;
|