@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,748 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Adapter Base Class for External Ralph Loop
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface that all CLI provider adapters must implement.
|
|
5
|
+
* Each adapter wraps a specific agentic coding CLI (claude, codex, etc.)
|
|
6
|
+
* and handles binary resolution, argument construction, model mapping,
|
|
7
|
+
* and output parsing.
|
|
8
|
+
*
|
|
9
|
+
* @implements Plan: Multi-Provider Support for External Ralph Loop
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { spawn } from 'child_process';
|
|
13
|
+
import { importImpl } from '../../_resolve-impl.mjs';
|
|
14
|
+
import { createWorkerHealthObserver, observeProviderHealthEvent } from './worker-health.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Thrown by {@link parseStrictWorkerJson} with a stable `code` so callers can
|
|
18
|
+
* map a scan failure to a specific contract-violation message without
|
|
19
|
+
* string-matching free-text.
|
|
20
|
+
*/
|
|
21
|
+
class StrictJsonScanError extends Error {
|
|
22
|
+
constructor(code, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* RFC 8259 JSON parser used on every strict worker-final path (JSONL lines,
|
|
30
|
+
* `format: 'json'`, and the MCP `content[0].text`/top-level envelope) instead
|
|
31
|
+
* of `JSON.parse`. `JSON.parse` silently keeps the LAST value when an object
|
|
32
|
+
* repeats a member name — RFC 8259 §4 explicitly leaves this to the
|
|
33
|
+
* implementation, and V8 picks "last wins". That lets a forged worker final
|
|
34
|
+
* smuggle a second, hidden value past a first one: a trailing duplicate
|
|
35
|
+
* `"status":"completed"` can overwrite an earlier `"status":"failed"`, or a
|
|
36
|
+
* trailing duplicate `"type":"result"` can collapse a `"type":"message"`
|
|
37
|
+
* framing envelope into a bogus result in one line (#2890 BLOCKER 1+2).
|
|
38
|
+
*
|
|
39
|
+
* This scanner tracks member names per object independently at every
|
|
40
|
+
* nesting level (a sibling object may reuse a name the scanner already saw
|
|
41
|
+
* in its parent or an earlier sibling) and rejects any object that repeats
|
|
42
|
+
* one. Names are compared only after the scanner has fully decoded them —
|
|
43
|
+
* resolving `\"`, other single-character escapes, and `\uXXXX` escapes
|
|
44
|
+
* (including surrogate pairs, since JS string concatenation of two decoded
|
|
45
|
+
* UTF-16 code units forms the pair automatically) — so an escape-obfuscated
|
|
46
|
+
* collision (`"status"` vs `"status"`) is caught exactly like a literal
|
|
47
|
+
* one. A non-colliding unknown key, escaped or not, is left for the existing
|
|
48
|
+
* `additionalProperties: false` schema check to reject, unchanged.
|
|
49
|
+
*
|
|
50
|
+
* Nesting depth is bounded during the scan (not only afterward, by
|
|
51
|
+
* `boundedJson()`'s post-parse walk) so pathological depth fails fast with a
|
|
52
|
+
* clear `DEPTH` code instead of relying on a recursive-descent stack
|
|
53
|
+
* overflow to be caught as a generic syntax error.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} text - Decoded JSON text (already UTF-8 validated by the caller)
|
|
56
|
+
* @param {number} maxDepth - Same bound as `WORKER_MAX_NESTING_DEPTH`; root value is depth 1
|
|
57
|
+
* @returns {unknown}
|
|
58
|
+
*/
|
|
59
|
+
function parseStrictWorkerJson(text, maxDepth) {
|
|
60
|
+
const length = text.length;
|
|
61
|
+
let index = 0;
|
|
62
|
+
|
|
63
|
+
function fail(code, message) {
|
|
64
|
+
throw new StrictJsonScanError(code, `${message} (offset ${index})`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function skipWhitespace() {
|
|
68
|
+
while (index < length) {
|
|
69
|
+
const code = text.charCodeAt(index);
|
|
70
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) index += 1;
|
|
71
|
+
else break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseValue(depth) {
|
|
76
|
+
skipWhitespace();
|
|
77
|
+
if (index >= length) fail('SYNTAX', 'Unexpected end of JSON input');
|
|
78
|
+
const code = text.charCodeAt(index);
|
|
79
|
+
if (code === 0x7b) return parseObject(depth);
|
|
80
|
+
if (code === 0x5b) return parseArray(depth);
|
|
81
|
+
if (code === 0x22) return parseString();
|
|
82
|
+
if (code === 0x2d || (code >= 0x30 && code <= 0x39)) return parseNumber();
|
|
83
|
+
if (text.startsWith('true', index)) { index += 4; return true; }
|
|
84
|
+
if (text.startsWith('false', index)) { index += 5; return false; }
|
|
85
|
+
if (text.startsWith('null', index)) { index += 4; return null; }
|
|
86
|
+
fail('SYNTAX', 'Unexpected token');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseObject(depth) {
|
|
90
|
+
if (depth > maxDepth) fail('DEPTH', 'Worker nesting depth exceeds bound');
|
|
91
|
+
index += 1; // {
|
|
92
|
+
const result = {};
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
skipWhitespace();
|
|
95
|
+
if (text.charCodeAt(index) === 0x7d) { index += 1; return result; }
|
|
96
|
+
for (;;) {
|
|
97
|
+
skipWhitespace();
|
|
98
|
+
if (text.charCodeAt(index) !== 0x22) fail('SYNTAX', 'Expected object member name');
|
|
99
|
+
const key = parseString();
|
|
100
|
+
if (seen.has(key)) fail('DUPLICATE', `Duplicate object member name ${JSON.stringify(key)}`);
|
|
101
|
+
seen.add(key);
|
|
102
|
+
skipWhitespace();
|
|
103
|
+
if (text.charCodeAt(index) !== 0x3a) fail('SYNTAX', 'Expected colon after object member name');
|
|
104
|
+
index += 1;
|
|
105
|
+
// Define, never assign: assignment would invoke the inherited __proto__
|
|
106
|
+
// setter, hiding the member from own-key validation and diverging from
|
|
107
|
+
// JSON.parse, which creates an ordinary own property.
|
|
108
|
+
Object.defineProperty(result, key, { value: parseValue(depth + 1), enumerable: true, writable: true, configurable: true });
|
|
109
|
+
skipWhitespace();
|
|
110
|
+
const delimiter = text.charCodeAt(index);
|
|
111
|
+
if (delimiter === 0x2c) { index += 1; continue; }
|
|
112
|
+
if (delimiter === 0x7d) { index += 1; break; }
|
|
113
|
+
fail('SYNTAX', 'Expected , or } in object');
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseArray(depth) {
|
|
119
|
+
if (depth > maxDepth) fail('DEPTH', 'Worker nesting depth exceeds bound');
|
|
120
|
+
index += 1; // [
|
|
121
|
+
const result = [];
|
|
122
|
+
skipWhitespace();
|
|
123
|
+
if (text.charCodeAt(index) === 0x5d) { index += 1; return result; }
|
|
124
|
+
for (;;) {
|
|
125
|
+
result.push(parseValue(depth + 1));
|
|
126
|
+
skipWhitespace();
|
|
127
|
+
const delimiter = text.charCodeAt(index);
|
|
128
|
+
if (delimiter === 0x2c) { index += 1; continue; }
|
|
129
|
+
if (delimiter === 0x5d) { index += 1; break; }
|
|
130
|
+
fail('SYNTAX', 'Expected , or ] in array');
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parseString() {
|
|
136
|
+
index += 1; // opening quote
|
|
137
|
+
let value = '';
|
|
138
|
+
for (;;) {
|
|
139
|
+
if (index >= length) fail('SYNTAX', 'Unterminated string');
|
|
140
|
+
const code = text.charCodeAt(index);
|
|
141
|
+
if (code === 0x22) { index += 1; return value; }
|
|
142
|
+
if (code === 0x5c) {
|
|
143
|
+
index += 1;
|
|
144
|
+
const escape = text[index];
|
|
145
|
+
if (escape === undefined) fail('SYNTAX', 'Unterminated escape sequence');
|
|
146
|
+
switch (escape) {
|
|
147
|
+
case '"': value += '"'; index += 1; break;
|
|
148
|
+
case '\\': value += '\\'; index += 1; break;
|
|
149
|
+
case '/': value += '/'; index += 1; break;
|
|
150
|
+
case 'b': value += '\b'; index += 1; break;
|
|
151
|
+
case 'f': value += '\f'; index += 1; break;
|
|
152
|
+
case 'n': value += '\n'; index += 1; break;
|
|
153
|
+
case 'r': value += '\r'; index += 1; break;
|
|
154
|
+
case 't': value += '\t'; index += 1; break;
|
|
155
|
+
case 'u': {
|
|
156
|
+
const hex = text.slice(index + 1, index + 5);
|
|
157
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail('SYNTAX', 'Invalid unicode escape');
|
|
158
|
+
// Decoded code unit is appended as-is; two consecutive decoded
|
|
159
|
+
// surrogate halves recombine into one character automatically
|
|
160
|
+
// through normal JS UTF-16 string concatenation.
|
|
161
|
+
value += String.fromCharCode(parseInt(hex, 16));
|
|
162
|
+
index += 5;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default: fail('SYNTAX', 'Invalid escape sequence');
|
|
166
|
+
}
|
|
167
|
+
} else if (code < 0x20) {
|
|
168
|
+
fail('SYNTAX', 'Unescaped control character in string');
|
|
169
|
+
} else {
|
|
170
|
+
value += text[index];
|
|
171
|
+
index += 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseNumber() {
|
|
177
|
+
const start = index;
|
|
178
|
+
if (text.charCodeAt(index) === 0x2d) index += 1;
|
|
179
|
+
if (text.charCodeAt(index) === 0x30) {
|
|
180
|
+
index += 1;
|
|
181
|
+
} else if (text.charCodeAt(index) >= 0x31 && text.charCodeAt(index) <= 0x39) {
|
|
182
|
+
index += 1;
|
|
183
|
+
while (text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39) index += 1;
|
|
184
|
+
} else fail('SYNTAX', 'Invalid number');
|
|
185
|
+
if (text.charCodeAt(index) === 0x2e) {
|
|
186
|
+
index += 1;
|
|
187
|
+
if (!(text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39)) fail('SYNTAX', 'Invalid number fraction');
|
|
188
|
+
while (text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39) index += 1;
|
|
189
|
+
}
|
|
190
|
+
if (text.charCodeAt(index) === 0x65 || text.charCodeAt(index) === 0x45) {
|
|
191
|
+
index += 1;
|
|
192
|
+
if (text.charCodeAt(index) === 0x2b || text.charCodeAt(index) === 0x2d) index += 1;
|
|
193
|
+
if (!(text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39)) fail('SYNTAX', 'Invalid number exponent');
|
|
194
|
+
while (text.charCodeAt(index) >= 0x30 && text.charCodeAt(index) <= 0x39) index += 1;
|
|
195
|
+
}
|
|
196
|
+
return Number(text.slice(start, index));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const result = parseValue(1);
|
|
200
|
+
skipWhitespace();
|
|
201
|
+
if (index !== length) fail('SYNTAX', 'Unexpected trailing characters after JSON value');
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const installedOwner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
|
|
206
|
+
export const { workerNegativeFixtureSuite, workerNegativeFixtureDigest, registerProvider, createProvider, listProviders, hasProvider } = installedOwner;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Whether the optional muse exec Ralph adapter is registered.
|
|
210
|
+
* Enabled by default; set `AIWG_MUSE_RALPH_ENABLED` to `0`, `false`, `no`,
|
|
211
|
+
* or `off` to disable the adapter without affecting
|
|
212
|
+
* `aiwg use --provider muse` (the deploy/writer path lives in `src/` and
|
|
213
|
+
* never consults this registry). See
|
|
214
|
+
* `tools/ralph-external/lib/muse-adapter.mjs` (#230).
|
|
215
|
+
*
|
|
216
|
+
* @returns {boolean}
|
|
217
|
+
*/
|
|
218
|
+
export function isMuseRalphEnabled() {
|
|
219
|
+
const raw = String(process.env.AIWG_MUSE_RALPH_ENABLED ?? '').trim().toLowerCase();
|
|
220
|
+
return !['0', 'false', 'no', 'off'].includes(raw);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @typedef {Object} ProviderCapabilities
|
|
225
|
+
* @property {boolean} streamJson - Supports --output-format stream-json
|
|
226
|
+
* @property {boolean} sessionResume - Supports --session-id for resumption
|
|
227
|
+
* @property {boolean} budgetControl - Supports --max-budget-usd
|
|
228
|
+
* @property {boolean} systemPrompt - Supports --append-system-prompt
|
|
229
|
+
* @property {boolean} agentMode - Supports --agent flag
|
|
230
|
+
* @property {boolean} mcpConfig - Supports --mcp-config
|
|
231
|
+
* @property {boolean} maxTurns - Supports --max-turns
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @typedef {Object} SessionArgs
|
|
236
|
+
* @property {string} prompt - The prompt to send
|
|
237
|
+
* @property {string} [sessionId] - Session UUID for tracking
|
|
238
|
+
* @property {string} [model] - Model name (generic: opus/sonnet/haiku)
|
|
239
|
+
* @property {number} [budget] - Budget per iteration in USD
|
|
240
|
+
* @property {number} [maxTurns] - Maximum turns
|
|
241
|
+
* @property {boolean} [verbose] - Enable verbose output
|
|
242
|
+
* @property {string} [systemPrompt] - System prompt to append
|
|
243
|
+
* @property {Object} [mcpConfig] - MCP server configuration
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @typedef {Object} AnalysisArgs
|
|
248
|
+
* @property {string} prompt - The analysis prompt
|
|
249
|
+
* @property {string} [model] - Model name (generic: opus/sonnet/haiku)
|
|
250
|
+
* @property {string} [agent] - Agent name to use
|
|
251
|
+
* @property {number} [timeout] - Timeout in ms
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Base class for provider adapters.
|
|
256
|
+
* Subclasses must override all methods that throw NotImplementedError.
|
|
257
|
+
*/
|
|
258
|
+
export class ProviderAdapter {
|
|
259
|
+
constructor() {
|
|
260
|
+
if (new.target === ProviderAdapter) {
|
|
261
|
+
throw new Error('ProviderAdapter is abstract and cannot be instantiated directly');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the CLI binary name (e.g., 'claude', 'codex')
|
|
267
|
+
* @returns {string}
|
|
268
|
+
*/
|
|
269
|
+
getBinary() {
|
|
270
|
+
throw new Error('Not implemented: getBinary()');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the provider name for display and config
|
|
275
|
+
* @returns {string}
|
|
276
|
+
*/
|
|
277
|
+
getName() {
|
|
278
|
+
throw new Error('Not implemented: getName()');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get provider capabilities
|
|
283
|
+
* @returns {ProviderCapabilities}
|
|
284
|
+
*/
|
|
285
|
+
getCapabilities() {
|
|
286
|
+
throw new Error('Not implemented: getCapabilities()');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Build CLI arguments for a main headless session (async spawn).
|
|
291
|
+
* This is the primary long-running session that performs the task.
|
|
292
|
+
*
|
|
293
|
+
* @param {SessionArgs} options
|
|
294
|
+
* @returns {string[]}
|
|
295
|
+
*/
|
|
296
|
+
buildSessionArgs(options) {
|
|
297
|
+
throw new Error('Not implemented: buildSessionArgs()');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Qualified drivers own exact argv mapping; permissions come from resolved policy,
|
|
301
|
+
* never legacy session defaults or worker-supplied invocation data. */
|
|
302
|
+
buildWorkerArgs(invocation, profile, permissions) {
|
|
303
|
+
throw new Error('Not implemented: buildWorkerArgs()');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Inspect a trusted host installation, without importing code or running inference.
|
|
308
|
+
* The returned digest binds negative qualification evidence to this exact scope.
|
|
309
|
+
* Install roots and the enforcing boundary must remain protected by the host.
|
|
310
|
+
*/
|
|
311
|
+
async inspectWorkerInstallation(options) {
|
|
312
|
+
const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
|
|
313
|
+
return owner.inspectWorkerInstallation(this, structuredClone(options));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Bounded version probe plus host-owned negative evidence, never worker claims.
|
|
318
|
+
* Missing, unsupported or changed installations stay DISABLED. No downloads,
|
|
319
|
+
* login, credentials discovery, server startup or managed state is involved.
|
|
320
|
+
*/
|
|
321
|
+
async qualifyWorkerInstallation(options) {
|
|
322
|
+
const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
|
|
323
|
+
return owner.qualifyWorkerInstallation(this, options);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async recordWorkerInstallation(options) {
|
|
327
|
+
const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
|
|
328
|
+
return owner.recordWorkerInstallation(this, options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Prepare one policy-resolved invocation through the supported installed path.
|
|
333
|
+
* This only validates/renders data: the conductor still owns path/digest
|
|
334
|
+
* rechecks, launch, observation and verification. No Ralph loop is started.
|
|
335
|
+
* Qualified drivers supply explicit argv; legacy buildSessionArgs defaults
|
|
336
|
+
* must never be used as a fallback for a worker profile.
|
|
337
|
+
*
|
|
338
|
+
* Callers gate-then-launch through `checkWorkerDispatch()` (from
|
|
339
|
+
* `tools/scripts/resource-preflight.mjs`) and `launchIsolatedWorker()` (from
|
|
340
|
+
* `./worker-isolation.mjs`): over resource threshold means queue, pick
|
|
341
|
+
* another qualified host, or reject — never launch. Preparation itself stays
|
|
342
|
+
* launch-free and performs no resource gating (#2954).
|
|
343
|
+
*
|
|
344
|
+
* @param {Object} invocation - Existing workerResolvedInvocation payload
|
|
345
|
+
* @param {Object} qualification - Trusted adapter, cliVersion, requiredControls,
|
|
346
|
+
* now (epoch milliseconds), platform, current mcp/schema bindings, and optional
|
|
347
|
+
* merged policy and dataClass from trusted resolution. Observation evidence
|
|
348
|
+
* (modelObservation or defaultObservation) is required; null effort also
|
|
349
|
+
* requires the selected role and policy role guidance allowing its absence.
|
|
350
|
+
* @returns {Promise<Object>} A detached typed descriptor; never executes it
|
|
351
|
+
*/
|
|
352
|
+
async prepareWorkerInvocation(invocation, qualification) {
|
|
353
|
+
const owner = await importImpl(import.meta.url, 'models/worker-installation.mjs');
|
|
354
|
+
return owner.prepareInstalledWorkerInvocation(this, invocation, qualification);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Build CLI arguments for a short analysis call (spawnSync).
|
|
359
|
+
* Used by OutputAnalyzer, StateAssessor, and ClaudePromptGenerator
|
|
360
|
+
* for quick Claude-powered analysis between iterations.
|
|
361
|
+
*
|
|
362
|
+
* @param {AnalysisArgs} options
|
|
363
|
+
* @returns {string[]}
|
|
364
|
+
*/
|
|
365
|
+
buildAnalysisArgs(options) {
|
|
366
|
+
throw new Error('Not implemented: buildAnalysisArgs()');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Map generic model names (opus/sonnet/haiku) to provider-specific models.
|
|
371
|
+
*
|
|
372
|
+
* @param {string} genericModel - Generic model name
|
|
373
|
+
* @returns {string} Provider-specific model name
|
|
374
|
+
*/
|
|
375
|
+
mapModel(genericModel) {
|
|
376
|
+
throw new Error('Not implemented: mapModel()');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Check if the provider CLI is available on the system.
|
|
381
|
+
* @returns {Promise<boolean>}
|
|
382
|
+
*/
|
|
383
|
+
async isAvailable() {
|
|
384
|
+
return new Promise((resolve) => {
|
|
385
|
+
const child = spawn(this.getBinary(), ['--version'], {
|
|
386
|
+
stdio: 'pipe',
|
|
387
|
+
});
|
|
388
|
+
child.on('close', (code) => resolve(code === 0));
|
|
389
|
+
child.on('error', () => resolve(false));
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get the provider CLI version string.
|
|
395
|
+
* @returns {Promise<string|null>}
|
|
396
|
+
*/
|
|
397
|
+
async getVersion() {
|
|
398
|
+
return new Promise((resolve) => {
|
|
399
|
+
let output = '';
|
|
400
|
+
const child = spawn(this.getBinary(), ['--version'], {
|
|
401
|
+
stdio: 'pipe',
|
|
402
|
+
});
|
|
403
|
+
child.stdout.on('data', (chunk) => {
|
|
404
|
+
output += chunk.toString();
|
|
405
|
+
});
|
|
406
|
+
child.on('close', (code) => {
|
|
407
|
+
resolve(code === 0 ? output.trim() : null);
|
|
408
|
+
});
|
|
409
|
+
child.on('error', () => resolve(null));
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Parse provider-specific output to extract JSON.
|
|
415
|
+
* Default implementation extracts the first JSON object from stdout.
|
|
416
|
+
* Legacy, synchronous contract only: always returns `Object|null`, never a
|
|
417
|
+
* Promise. Group-mode/worker-final parsing is a distinct async contract —
|
|
418
|
+
* call `parseWorkerFinal()` (or `parseWorkerOutput()` directly), never this
|
|
419
|
+
* method with a `groupMode` flag. An un-awaited Promise is a truthy object,
|
|
420
|
+
* so a caller that mixed the two contracts through one return type could
|
|
421
|
+
* mistake a pending parse for a successful legacy result (#2890 NOTE 6).
|
|
422
|
+
*
|
|
423
|
+
* @param {string} stdout - Raw stdout from the CLI
|
|
424
|
+
* @returns {Object|null} Parsed JSON, or null if none found/malformed
|
|
425
|
+
*/
|
|
426
|
+
parseOutput(stdout) {
|
|
427
|
+
try {
|
|
428
|
+
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
|
|
429
|
+
if (!jsonMatch) return null;
|
|
430
|
+
return JSON.parse(jsonMatch[0]);
|
|
431
|
+
} catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Strict group-mode entry point. Always returns a Promise; callers MUST
|
|
438
|
+
* await it. Thin, intentionally-named alias over `parseWorkerOutput()` so
|
|
439
|
+
* group-mode call sites never have to pass a `groupMode` flag into the
|
|
440
|
+
* legacy, synchronous `parseOutput()` contract above.
|
|
441
|
+
*
|
|
442
|
+
* @param {string|Uint8Array|Object} output
|
|
443
|
+
* @param {Object} [options] - format is json, jsonl or mcp; observation is
|
|
444
|
+
* supplied by the adapter.
|
|
445
|
+
* @returns {Promise<Object>} Validated workerClaims
|
|
446
|
+
*/
|
|
447
|
+
parseWorkerFinal(output, options = {}) {
|
|
448
|
+
return this.parseWorkerOutput(output, options);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* #2915 (evidence contract): the attempt-terminal sibling of
|
|
453
|
+
* `parseWorkerFinal`/`parseWorkerOutput` below. Shares the exact same
|
|
454
|
+
* strict decoding AND the exact same per-CLI extraction every subclass's
|
|
455
|
+
* own `parseWorkerFinal` override performs — the only difference is the
|
|
456
|
+
* terminal assertion: `assertWorkerAttemptTerminal` requires the
|
|
457
|
+
* launcher/adapter-owned `attemptOutcome` (never confirmed descendant
|
|
458
|
+
* termination) instead of `assertWorkerTerminal`'s
|
|
459
|
+
* `termination: 'confirmed'`. This lets a degraded-isolation shell
|
|
460
|
+
* attempt or an MCP response — neither of which can ever produce a
|
|
461
|
+
* confirmed descendant-stop — still validate a genuinely successful
|
|
462
|
+
* attempt's final claims (session-launcher.mjs `captureOneWorker`). It
|
|
463
|
+
* never manufactures confirmed termination: the caller's own
|
|
464
|
+
* `termination`/`descendants` fields are untouched.
|
|
465
|
+
*
|
|
466
|
+
* Fixed once here, for every adapter, with NO per-adapter edit required
|
|
467
|
+
* (2915 follow-up: the previous version called `parseWorkerOutput`
|
|
468
|
+
* directly, skipping every subclass's own extraction override entirely).
|
|
469
|
+
* The trick is calling `this.parseWorkerFinal` — polymorphic dispatch
|
|
470
|
+
* picks up the ACTUAL (possibly overridden) method — with
|
|
471
|
+
* `assertTerminal` folded into `options`. Every subclass override in this
|
|
472
|
+
* directory follows the same shape: extract a CLI-specific final, then
|
|
473
|
+
* `return super.parseWorkerFinal(extracted, { ...options, format: '...' })`
|
|
474
|
+
* — the `...options` spread carries `assertTerminal` straight through to
|
|
475
|
+
* the base `parseWorkerOutput` below, so the base class never needs
|
|
476
|
+
* subclass-specific knowledge of each extraction. An adapter whose
|
|
477
|
+
* override always throws regardless of input (Warp, Copilot:
|
|
478
|
+
* `supportsAttemptVerification() === false`) throws here too, by the same
|
|
479
|
+
* unconditional path — exactly the intended "never verifiable" behavior
|
|
480
|
+
* for those two; `admitDirectWorker` treats such a CLI as `not-gated`
|
|
481
|
+
* (recovery never consulted) rather than permanently blocking it.
|
|
482
|
+
*/
|
|
483
|
+
parseWorkerAttemptFinal(output, options = {}) {
|
|
484
|
+
return this.parseWorkerAttemptOutput(output, options);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async parseWorkerAttemptOutput(output, options = {}) {
|
|
488
|
+
const { assertWorkerAttemptTerminal } = await importImpl(import.meta.url, 'mission-protocol/index.js');
|
|
489
|
+
return this.parseWorkerFinal(output, { ...options, assertTerminal: assertWorkerAttemptTerminal });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Capability signal (#2915 follow-up), default `true`: can this adapter's
|
|
494
|
+
* `parseWorkerFinal`/`parseWorkerAttemptFinal` ever validate a genuine
|
|
495
|
+
* final for ANY input, given a real completed attempt? Override `false`
|
|
496
|
+
* only for an adapter whose `parseWorkerFinal` always throws regardless
|
|
497
|
+
* of input (no observed output envelope exists to extract from yet —
|
|
498
|
+
* Warp, Copilot). `admitDirectWorker` uses this to treat such a CLI as
|
|
499
|
+
* never eligible for recovery-breaker gating, rather than admitting a
|
|
500
|
+
* canary that can structurally never report success.
|
|
501
|
+
*/
|
|
502
|
+
supportsAttemptVerification() { return true; }
|
|
503
|
+
|
|
504
|
+
/** Strict group-mode path, shared by CLI and MCP. Never falls back to regex.
|
|
505
|
+
* Drivers normalize qualified vendor event framing to result/message/terminal
|
|
506
|
+
* JSONL, or supply one claims JSON document. Observation comes from the trusted
|
|
507
|
+
* process/request owner, not from the final. This returns claims, not acceptance.
|
|
508
|
+
* Called directly or through the `parseWorkerFinal()` alias; never through
|
|
509
|
+
* `parseOutput()`, which keeps its own synchronous, non-group contract.
|
|
510
|
+
* `options.assertTerminal`, when supplied (by `parseWorkerAttemptOutput`
|
|
511
|
+
* above), replaces the default `assertWorkerTerminal` confirmed-termination
|
|
512
|
+
* gate with the attempt-terminal one — every other step of decoding is
|
|
513
|
+
* identical and unmodified.
|
|
514
|
+
*/
|
|
515
|
+
async parseWorkerOutput(output, options = {}) {
|
|
516
|
+
const {
|
|
517
|
+
WORKER_MAX_JSON_BYTES, WORKER_MAX_NESTING_DEPTH, WorkerContractError,
|
|
518
|
+
decodeWorkerProducedPayload, assertWorkerTerminal,
|
|
519
|
+
} = await importImpl(import.meta.url, 'mission-protocol/index.js');
|
|
520
|
+
const assertTerminal = options.assertTerminal ?? assertWorkerTerminal;
|
|
521
|
+
const fail = message => { throw new WorkerContractError('OG_CONTRACT_INVALID', message); };
|
|
522
|
+
const format = options.format ?? 'jsonl';
|
|
523
|
+
const streamLimit = 10 * 1024 * 1024;
|
|
524
|
+
const limit = options.maxOutputBytes ?? streamLimit;
|
|
525
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > streamLimit) fail('Invalid worker stream bound.');
|
|
526
|
+
function boundedJson(value, bound) {
|
|
527
|
+
// Check depth before serializing MCP objects supplied by a transport SDK.
|
|
528
|
+
function visit(node, depth) {
|
|
529
|
+
if (!node || typeof node !== 'object') return;
|
|
530
|
+
if (depth > WORKER_MAX_NESTING_DEPTH) fail('Worker nesting depth exceeds 16.');
|
|
531
|
+
for (const child of Object.values(node)) visit(child, depth + 1);
|
|
532
|
+
}
|
|
533
|
+
visit(value, 1);
|
|
534
|
+
const bytes = JSON.stringify(value);
|
|
535
|
+
if (bytes === undefined || Buffer.byteLength(bytes) > bound) fail('Worker final exceeds JSON byte bound.');
|
|
536
|
+
return bytes;
|
|
537
|
+
}
|
|
538
|
+
function parse(bytes, bound) {
|
|
539
|
+
if (typeof bytes !== 'string' && !(bytes instanceof Uint8Array)) fail('Worker output must be UTF-8 bytes or text.');
|
|
540
|
+
// Encoding would silently replace lone surrogates with U+FFFD; reject.
|
|
541
|
+
if (typeof bytes === 'string' && !bytes.isWellFormed()) fail('Malformed worker final JSON or UTF-8.');
|
|
542
|
+
const buffer = typeof bytes === 'string' ? Buffer.from(bytes, 'utf8') : bytes;
|
|
543
|
+
if (buffer.byteLength > bound) fail('Worker output exceeds byte bound.');
|
|
544
|
+
let value;
|
|
545
|
+
try {
|
|
546
|
+
// ignoreBOM keeps a leading BOM in the text so the scanner rejects it
|
|
547
|
+
// (RFC 8259 §8.1: a BOM must not be added).
|
|
548
|
+
const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(buffer);
|
|
549
|
+
// Strict scanner, not JSON.parse: rejects duplicate object member
|
|
550
|
+
// names (RFC 8259 §4) on every worker-final path instead of silently
|
|
551
|
+
// keeping JSON.parse's "last value wins". See parseStrictWorkerJson.
|
|
552
|
+
value = parseStrictWorkerJson(text, WORKER_MAX_NESTING_DEPTH);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error instanceof StrictJsonScanError && error.code === 'DEPTH') fail('Worker nesting depth exceeds 16.');
|
|
555
|
+
if (error instanceof StrictJsonScanError && error.code === 'DUPLICATE') fail(`Duplicate worker JSON member name: ${error.message}`);
|
|
556
|
+
fail('Malformed worker final JSON or UTF-8.');
|
|
557
|
+
}
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
let final;
|
|
561
|
+
if (format === 'mcp') {
|
|
562
|
+
const response = typeof output === 'string' || output instanceof Uint8Array ? parse(output, limit) : output;
|
|
563
|
+
boundedJson(response, limit);
|
|
564
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) fail('Malformed MCP worker result.');
|
|
565
|
+
if (Object.keys(response).some(key => !['content', 'structuredContent', 'isError', '_meta'].includes(key))) fail('Unknown MCP result field.');
|
|
566
|
+
const isError = response.isError;
|
|
567
|
+
if (isError !== undefined && isError !== false) fail('MCP worker isError result.');
|
|
568
|
+
const content = response.content;
|
|
569
|
+
if (content !== undefined && (!Array.isArray(content) || content.length > 1)) fail('Missing or double MCP final.');
|
|
570
|
+
let textFinal;
|
|
571
|
+
if (content?.length) {
|
|
572
|
+
const item = content[0];
|
|
573
|
+
if (item?.type !== 'text' || typeof item.text !== 'string') fail('MCP final must be typed JSON text.');
|
|
574
|
+
textFinal = parse(item.text, WORKER_MAX_JSON_BYTES);
|
|
575
|
+
}
|
|
576
|
+
final = response.structuredContent ?? textFinal;
|
|
577
|
+
if (final === undefined) fail('Missing MCP worker final.');
|
|
578
|
+
if (response.structuredContent !== undefined && textFinal !== undefined) {
|
|
579
|
+
// MCP's required text mirror is not a second final, but must agree.
|
|
580
|
+
const { isDeepStrictEqual } = await import('node:util');
|
|
581
|
+
if (!isDeepStrictEqual(final, textFinal)) fail('Conflicting MCP final representations.');
|
|
582
|
+
}
|
|
583
|
+
} else if (format === 'json') {
|
|
584
|
+
final = parse(output, Math.min(limit, WORKER_MAX_JSON_BYTES));
|
|
585
|
+
} else if (format === 'jsonl') {
|
|
586
|
+
if (typeof output !== 'string' && !(output instanceof Uint8Array)) fail('Worker stream must be UTF-8 bytes or text.');
|
|
587
|
+
const bytes = typeof output === 'string' ? Buffer.from(output, 'utf8') : output;
|
|
588
|
+
if (bytes.byteLength > limit) fail('Worker stream exceeds byte bound.');
|
|
589
|
+
let stream;
|
|
590
|
+
try { stream = new TextDecoder('utf-8', { fatal: true }).decode(bytes); }
|
|
591
|
+
catch { fail('Malformed worker stream UTF-8.'); }
|
|
592
|
+
// The `{"type":"terminal",...}` line is optional by design (05 §5,
|
|
593
|
+
// 11 §B-OG-14: "qualified successful terminal event/exit" — either is
|
|
594
|
+
// acceptable evidence, not both). It is in-band confirmation only.
|
|
595
|
+
// The authoritative truncation backstop is `options.observation`
|
|
596
|
+
// (adapter-owned process/transport exit), enforced unconditionally
|
|
597
|
+
// below via `assertWorkerTerminal()` regardless of whether this line
|
|
598
|
+
// was present. A stream cut immediately after the `result` line, with
|
|
599
|
+
// no observed clean exit, still fails there — never on the missing
|
|
600
|
+
// terminal line alone (#2890 MINOR 4).
|
|
601
|
+
let terminal = false;
|
|
602
|
+
for (const line of stream.split('\n')) {
|
|
603
|
+
if (!line.trim()) continue;
|
|
604
|
+
const event = parse(line, WORKER_MAX_JSON_BYTES);
|
|
605
|
+
if (terminal || !event || typeof event !== 'object' || Array.isArray(event)) fail('Inconsistent worker terminal stream.');
|
|
606
|
+
if (event.type === 'result') {
|
|
607
|
+
if (final !== undefined) fail('Double worker final.');
|
|
608
|
+
if (Object.keys(event).some(key => !['type', 'result'].includes(key)) || event.result === undefined) fail('Malformed worker result envelope.');
|
|
609
|
+
final = event.result;
|
|
610
|
+
} else if (event.type === 'message') {
|
|
611
|
+
if (final !== undefined || typeof event.text !== 'string' || Object.keys(event).some(key => !['type', 'text'].includes(key))) fail('Inconsistent worker message stream.');
|
|
612
|
+
} else if (event.type === 'terminal') {
|
|
613
|
+
if (final === undefined || event.status !== 'completed' || Object.keys(event).some(key => !['type', 'status'].includes(key))) fail('Inconsistent worker terminal stream.');
|
|
614
|
+
terminal = true;
|
|
615
|
+
} else fail('Unknown or error worker stream event.');
|
|
616
|
+
}
|
|
617
|
+
if (final === undefined) fail('Missing worker final.');
|
|
618
|
+
} else fail('Unknown worker final format.');
|
|
619
|
+
boundedJson(final, WORKER_MAX_JSON_BYTES);
|
|
620
|
+
const claims = decodeWorkerProducedPayload('workerClaims', 'worker', final);
|
|
621
|
+
if (options.observation?.transport !== (format === 'mcp' ? 'mcp' : 'shell')) fail('Worker transport observation mismatch.');
|
|
622
|
+
assertTerminal(options.observation);
|
|
623
|
+
return claims;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Get environment variable overrides for headless mode.
|
|
628
|
+
* @returns {Object<string, string>}
|
|
629
|
+
*/
|
|
630
|
+
getEnvOverrides() {
|
|
631
|
+
return {};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** JSONL written to stdin before bounded process termination, when supported. */
|
|
635
|
+
getAbortInput() { return null; }
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Observe execution through the selected profile's framing and grammar.
|
|
639
|
+
* This hook never parses or verifies worker claims: parseWorkerFinal remains
|
|
640
|
+
* the separate strict final contract, and the conductor owns verification.
|
|
641
|
+
*/
|
|
642
|
+
createHealthObserver(options = {}) {
|
|
643
|
+
return createWorkerHealthObserver({
|
|
644
|
+
...options,
|
|
645
|
+
provider: this.getName(),
|
|
646
|
+
normalizeEvent: (event, source, binding) => this.observeHealthEvent(event, source, binding),
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Unknown provider grammar supplies no semantic execution evidence. */
|
|
651
|
+
observeHealthEvent(event, source, binding) { return observeProviderHealthEvent(this.getName(), event, source, binding); }
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Get the path where the provider stores session transcripts.
|
|
655
|
+
*
|
|
656
|
+
* @param {string} sessionId - Session UUID
|
|
657
|
+
* @param {string} workingDir - Working directory path
|
|
658
|
+
* @returns {string|null} Path to transcript file, or null if not supported
|
|
659
|
+
*/
|
|
660
|
+
getTranscriptPath(sessionId, workingDir) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Check if a capability is supported, and optionally log a warning.
|
|
666
|
+
*
|
|
667
|
+
* @param {string} capability - Capability name from ProviderCapabilities
|
|
668
|
+
* @param {string} [context] - Context for the warning message
|
|
669
|
+
* @returns {boolean}
|
|
670
|
+
*/
|
|
671
|
+
hasCapability(capability) {
|
|
672
|
+
const caps = this.getCapabilities();
|
|
673
|
+
return !!caps[capability];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Log a capability warning when a feature is not supported.
|
|
678
|
+
*
|
|
679
|
+
* @param {string} capability - Capability name
|
|
680
|
+
* @param {string} feature - Human-readable feature description
|
|
681
|
+
*/
|
|
682
|
+
warnUnsupported(capability, feature) {
|
|
683
|
+
if (!this.hasCapability(capability)) {
|
|
684
|
+
console.warn(
|
|
685
|
+
`[${this.getName()}] Warning: ${feature} not supported by this provider, skipping`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ============================================================================
|
|
692
|
+
// Provider Registry & Factory
|
|
693
|
+
// ============================================================================
|
|
694
|
+
|
|
695
|
+
export async function loadInstalledWorkerAdapters(request) {
|
|
696
|
+
await ensureProvidersRegistered();
|
|
697
|
+
return installedOwner.loadInstalledWorkerAdapters(request);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ============================================================================
|
|
701
|
+
// Auto-register built-in providers on import
|
|
702
|
+
// ============================================================================
|
|
703
|
+
|
|
704
|
+
// Lazy imports to avoid circular dependencies - adapters register themselves
|
|
705
|
+
// when their modules are loaded. We trigger that here.
|
|
706
|
+
async function registerBuiltinProviders() {
|
|
707
|
+
try {
|
|
708
|
+
await import('./claude-adapter.mjs');
|
|
709
|
+
} catch { /* ignore if not found */ }
|
|
710
|
+
try {
|
|
711
|
+
await import('./codex-adapter.mjs');
|
|
712
|
+
} catch { /* ignore if not found */ }
|
|
713
|
+
try {
|
|
714
|
+
await import('./opencode-adapter.mjs');
|
|
715
|
+
} catch { /* ignore if not found */ }
|
|
716
|
+
try {
|
|
717
|
+
await import('./factory-adapter.mjs');
|
|
718
|
+
} catch { /* ignore if not found */ }
|
|
719
|
+
try {
|
|
720
|
+
await import('./pi-adapter.mjs');
|
|
721
|
+
await import('./omp-adapter.mjs');
|
|
722
|
+
} catch { /* ignore if not found */ }
|
|
723
|
+
try {
|
|
724
|
+
await import('./deepseek-harness-adapter.mjs');
|
|
725
|
+
} catch { /* ignore if not found */ }
|
|
726
|
+
// #230: optional / post-experimental. Disabled via AIWG_MUSE_RALPH_ENABLED=0
|
|
727
|
+
// without affecting `aiwg use --provider muse`.
|
|
728
|
+
if (isMuseRalphEnabled()) {
|
|
729
|
+
try {
|
|
730
|
+
await import('./muse-adapter.mjs');
|
|
731
|
+
} catch { /* ignore if not found */ }
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Run registration — store promise so callers can await it
|
|
736
|
+
const _registrationPromise = registerBuiltinProviders();
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Ensure all built-in providers are registered before use.
|
|
740
|
+
* Must be awaited before calling createProvider().
|
|
741
|
+
* Optional installed bindings come only from the trusted host registration path.
|
|
742
|
+
*/
|
|
743
|
+
export async function ensureProvidersRegistered(installed) {
|
|
744
|
+
await _registrationPromise;
|
|
745
|
+
if (installed !== undefined) return loadInstalledWorkerAdapters(installed);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export default ProviderAdapter;
|