@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
|
@@ -14,19 +14,40 @@
|
|
|
14
14
|
* aiwg session --provider codex # explicit provider
|
|
15
15
|
* aiwg session mcp --provider cursor # MCP inject for cursor + start instructions
|
|
16
16
|
* aiwg session --no-repair # skip auto-repair (still check and report)
|
|
17
|
+
* aiwg session --prompt "<text>" # headless: one-shot, bounded, exits (#3062)
|
|
18
|
+
* aiwg session --prompt-file <path> # headless from a file instead of argv
|
|
19
|
+
* aiwg session --non-interactive # force the non-interactive decision (#3062)
|
|
20
|
+
*
|
|
21
|
+
* Without a terminal attached, launching a spawnable provider with no
|
|
22
|
+
* `--prompt`/`--prompt-file` would hand over a terminal that does not exist
|
|
23
|
+
* and hang. `--prompt`/`--prompt-file` map to each provider's documented
|
|
24
|
+
* non-interactive CLI form, delivered off argv (see
|
|
25
|
+
* `HEADLESS_SESSION_TRANSPORTS` below); providers without an evidenced one
|
|
26
|
+
* refuse instead of guessing. A provider with no spawnable binary at all
|
|
27
|
+
* (IDE-integrated) refuses `--prompt`/`--prompt-file` outright rather than
|
|
28
|
+
* discarding it. `--non-interactive` forces the same refusal even with a
|
|
29
|
+
* terminal attached, taking priority over the environment-derived check.
|
|
17
30
|
*
|
|
18
31
|
* @issue #884
|
|
19
32
|
*/
|
|
20
33
|
import { spawnSync } from 'child_process';
|
|
34
|
+
import { readFile } from 'node:fs/promises';
|
|
35
|
+
import { mkdtempSync, rmSync, writeFileSync, realpathSync, statSync } from 'node:fs';
|
|
36
|
+
import { join } from 'node:path';
|
|
37
|
+
import { boundedSpawn, boundedSpawnSync } from '../../process/bounded-child-process.mjs';
|
|
38
|
+
import { existsSync } from 'node:fs';
|
|
21
39
|
import { ensureRuntimeHome, writeProfileConfig, launchWithProfile } from '../../mcp/adapters/codex-runtime.js';
|
|
22
40
|
import { getFrameworkRoot } from '../../channel/manager.mjs';
|
|
23
41
|
import { forceUpdateCheck } from '../../update/checker.mjs';
|
|
24
42
|
import { updateInstallation } from '../../update/service.mjs';
|
|
43
|
+
import { isInteractive } from '../env.js';
|
|
25
44
|
import { readAiwgConfig, getDeploymentSummary, VALID_PROVIDERS, } from '../../config/aiwg-config.js';
|
|
26
45
|
import { getProviderConfig, isSpawnableProvider, PROVIDER_CONFIGS, } from '../agent-spawn.js';
|
|
27
46
|
import { useHandler as useFrameworkHandler } from './use.js';
|
|
28
47
|
import { debug } from '../log.js';
|
|
29
48
|
import { normalizeProviderDefinitionId } from '../../providers/provider-definitions.js';
|
|
49
|
+
import { requireFlagValue, MissingFlagValueError, hasPositionalOrSwitch } from '../flag-parsing.js';
|
|
50
|
+
import { exitCodeForSpawnResult } from '../child-process-result.js';
|
|
30
51
|
// ── Provider resolution ──────────────────────────────────────────────────────
|
|
31
52
|
/**
|
|
32
53
|
* Resolve which provider to target.
|
|
@@ -46,31 +67,44 @@ async function resolveProvider(explicitProvider, cwd) {
|
|
|
46
67
|
}
|
|
47
68
|
return 'claude';
|
|
48
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Parse `aiwg session` flags.
|
|
72
|
+
*
|
|
73
|
+
* Every value-taking flag below is parsed through {@link requireFlagValue}
|
|
74
|
+
* (#3062 review item 11): a flag present with no value, or immediately
|
|
75
|
+
* followed by another flag, throws {@link MissingFlagValueError} rather
|
|
76
|
+
* than silently resolving to "not given" — callers MUST catch it before
|
|
77
|
+
* any preflight check, spawn, or write. `precedence: 'last'` matches this
|
|
78
|
+
* command's own pre-#3062 behavior (a hand-rolled loop that kept scanning
|
|
79
|
+
* and overwrote on every match, so a later `--provider`/`--profile` always
|
|
80
|
+
* won) — the first-round shared parser silently flipped that to
|
|
81
|
+
* first-wins; this restores it (#3062 second review, finding 4).
|
|
82
|
+
* `--prompt`/`--prompt-file` reject an empty string: an empty prompt can
|
|
83
|
+
* never be meaningful, and silently accepting one would let a mistake
|
|
84
|
+
* reach either the interactive launch path or a headless launch with
|
|
85
|
+
* nothing to say.
|
|
86
|
+
*
|
|
87
|
+
* The `mcp` positional and every boolean switch below go through
|
|
88
|
+
* {@link hasPositionalOrSwitch} rather than `args.includes(...)` (#3062
|
|
89
|
+
* go/no-go review, blocker 4): a plain `args.includes('mcp')` matches the
|
|
90
|
+
* literal word `mcp` *anywhere* in `args`, including inside a free-text
|
|
91
|
+
* `--prompt`/`--prompt-file` value (`aiwg session --prompt mcp` used to
|
|
92
|
+
* set `mcp: true` and reach MCP config injection) or after a bare `--`
|
|
93
|
+
* (`aiwg session -- --persist` used to still set `persist: true`).
|
|
94
|
+
* {@link hasPositionalOrSwitch} excludes both: anything consumed as the
|
|
95
|
+
* value of `VALUE_FLAGS` below, and anything at or after `--`.
|
|
96
|
+
*/
|
|
97
|
+
const VALUE_FLAGS = ['--provider', '--profile', '--prompt', '--prompt-file'];
|
|
49
98
|
function parseSessionArgs(args) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
else if (a === '--provider' && args[i + 1]) {
|
|
61
|
-
provider = args[++i];
|
|
62
|
-
}
|
|
63
|
-
else if (a === '--no-repair') {
|
|
64
|
-
noRepair = true;
|
|
65
|
-
}
|
|
66
|
-
else if (a === '--profile' && args[i + 1]) {
|
|
67
|
-
profile = args[++i];
|
|
68
|
-
}
|
|
69
|
-
else if (a === '--persist') {
|
|
70
|
-
persist = true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { mcp, provider, noRepair, profile, persist };
|
|
99
|
+
const mcp = hasPositionalOrSwitch(args, 'mcp', VALUE_FLAGS);
|
|
100
|
+
const noRepair = hasPositionalOrSwitch(args, '--no-repair', VALUE_FLAGS);
|
|
101
|
+
const persist = hasPositionalOrSwitch(args, '--persist', VALUE_FLAGS);
|
|
102
|
+
const nonInteractive = hasPositionalOrSwitch(args, '--non-interactive', VALUE_FLAGS);
|
|
103
|
+
const provider = requireFlagValue(args, { precedence: 'last' }, '--provider');
|
|
104
|
+
const profile = requireFlagValue(args, { precedence: 'last' }, '--profile');
|
|
105
|
+
const prompt = requireFlagValue(args, { precedence: 'last', rejectEmpty: true }, '--prompt');
|
|
106
|
+
const promptFile = requireFlagValue(args, { precedence: 'last', rejectEmpty: true }, '--prompt-file');
|
|
107
|
+
return { mcp, provider, noRepair, profile, persist, prompt, promptFile, nonInteractive };
|
|
74
108
|
}
|
|
75
109
|
// ── Pre-flight steps ─────────────────────────────────────────────────────────
|
|
76
110
|
/** Run `npm view aiwg version` and compare to current. Returns true if up-to-date. */
|
|
@@ -103,8 +137,17 @@ async function checkAndUpdateVersion(noRepair) {
|
|
|
103
137
|
* Uses the same script runner path as doctorHandler to avoid duplication.
|
|
104
138
|
*/
|
|
105
139
|
function runDoctor(_frameworkRoot, cwd) {
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
// Self-invocation of the aiwg binary, not the harness launch below; bounded
|
|
141
|
+
// as a long-tier call (#3062) since `aiwg doctor` can touch the network.
|
|
142
|
+
const result = boundedSpawnSync(process.execPath, [process.argv[1], 'doctor'], 'long', { stdio: 'inherit', cwd });
|
|
143
|
+
// #3062 review item 7: a `null` status (killed/timed out/failed to spawn)
|
|
144
|
+
// already falls through to `false` below via `?? 1`; surface the bounded
|
|
145
|
+
// helper's diagnostic (e.g. the enriched timeout message) instead of
|
|
146
|
+
// silently dropping it.
|
|
147
|
+
const { exitCode, message } = exitCodeForSpawnResult(result, 'aiwg doctor');
|
|
148
|
+
if (message)
|
|
149
|
+
debug('cli:session:doctor', message);
|
|
150
|
+
return exitCode === 0;
|
|
108
151
|
}
|
|
109
152
|
/**
|
|
110
153
|
* Attempt to repair a failed doctor result.
|
|
@@ -115,7 +158,15 @@ function runDoctor(_frameworkRoot, cwd) {
|
|
|
115
158
|
function repairInstallation(frameworkRoot, cwd, provider) {
|
|
116
159
|
// Strategy 1: sync (update + redeploy)
|
|
117
160
|
console.log('\n Attempting auto-repair via `aiwg sync`...');
|
|
118
|
-
|
|
161
|
+
// Self-invocation of the aiwg binary; bounded as a long-tier call (#3062)
|
|
162
|
+
// since `aiwg sync` updates and redeploys (network + install-shaped work).
|
|
163
|
+
const syncResult = boundedSpawnSync(process.execPath, [process.argv[1], 'sync'], 'long', { stdio: 'inherit', cwd });
|
|
164
|
+
// #3062 review item 7: `status === 0` already treats a `null` status
|
|
165
|
+
// (timeout/signal/spawn failure) as "not repaired", falling through to
|
|
166
|
+
// the manual-options message below; surface the diagnostic too.
|
|
167
|
+
const syncDiagnostic = exitCodeForSpawnResult(syncResult, 'aiwg sync').message;
|
|
168
|
+
if (syncDiagnostic)
|
|
169
|
+
debug('cli:session:repair', syncDiagnostic);
|
|
119
170
|
if (syncResult.status === 0) {
|
|
120
171
|
// Re-check doctor
|
|
121
172
|
const doctorOk = runDoctor(frameworkRoot, cwd);
|
|
@@ -170,28 +221,32 @@ async function redeployFrameworks(ctx, frameworks, provider) {
|
|
|
170
221
|
// ── MCP inject ────────────────────────────────────────────────────────────────
|
|
171
222
|
function injectMcp(provider, cwd) {
|
|
172
223
|
console.log(`\n Injecting configured MCP servers into ${provider}...`);
|
|
173
|
-
|
|
224
|
+
// Self-invocation of the aiwg binary; bounded as a long-tier call (#3062).
|
|
225
|
+
const result = boundedSpawnSync(process.execPath, [process.argv[1], 'mcp', 'inject', '--provider', provider], 'long', { stdio: 'inherit', cwd });
|
|
174
226
|
if (result.status !== 0) {
|
|
227
|
+
// #3062 review item 7: surface the bounded helper's diagnostic (if any)
|
|
228
|
+
// alongside the existing warning.
|
|
229
|
+
const { message } = exitCodeForSpawnResult(result, 'aiwg mcp inject');
|
|
230
|
+
if (message)
|
|
231
|
+
debug('cli:session:mcp-inject', message);
|
|
175
232
|
console.warn(' WARN MCP inject reported issues — continuing. Run `aiwg mcp list` to check registered servers.');
|
|
176
233
|
return false;
|
|
177
234
|
}
|
|
178
235
|
return true;
|
|
179
236
|
}
|
|
180
|
-
|
|
181
|
-
* Profile-aware inject (#891).
|
|
182
|
-
* Default is ephemeral (no persistent config mutation).
|
|
183
|
-
*
|
|
184
|
-
* For Claude: returns the ephemeral config path (for claude --mcp-config).
|
|
185
|
-
* For Codex: sets up the runtime home via the codex-runtime adapter (#892).
|
|
186
|
-
* Returns null for persistent mode or on failure.
|
|
187
|
-
*/
|
|
188
|
-
function injectMcpProfile(provider, profileName, cwd, persist) {
|
|
237
|
+
function injectMcpProfile(provider, profileName, cwd, persist, hasClaudePermissions = false) {
|
|
189
238
|
console.log(`\n Resolving profile "${profileName}" for ${provider}...`);
|
|
190
239
|
if (persist) {
|
|
191
240
|
// Persistent: write to provider's default config
|
|
192
|
-
|
|
241
|
+
// Self-invocation of the aiwg binary; bounded as a long-tier call (#3062).
|
|
242
|
+
const result = boundedSpawnSync(process.execPath, [process.argv[1], 'mcp', 'inject', '--provider', provider, '--profile', profileName], 'long', { stdio: 'inherit', cwd });
|
|
193
243
|
if (result.status !== 0) {
|
|
194
|
-
|
|
244
|
+
// #3062 review item 7: surface the bounded helper's diagnostic.
|
|
245
|
+
const { message } = exitCodeForSpawnResult(result, 'aiwg mcp inject');
|
|
246
|
+
if (provider === 'codex' || provider === 'openai' || provider === 'claude' || provider === 'claude-code') {
|
|
247
|
+
throw new Error(`${provider} profile injection failed; refusing to launch.${message ? ` ${message}` : ''}`);
|
|
248
|
+
}
|
|
249
|
+
console.warn(` WARN Profile inject failed — continuing without profile.${message ? ` ${message}` : ''}`);
|
|
195
250
|
}
|
|
196
251
|
return null; // persistent mode, no ephemeral path
|
|
197
252
|
}
|
|
@@ -202,28 +257,127 @@ function injectMcpProfile(provider, profileName, cwd, persist) {
|
|
|
202
257
|
}
|
|
203
258
|
// Claude/others: generate a temp config file
|
|
204
259
|
const tmpPath = `${process.env.TMPDIR ?? '/tmp'}/aiwg-mcp-${profileName}-${provider}-${Date.now()}.json`;
|
|
205
|
-
|
|
260
|
+
// Self-invocation of the aiwg binary; bounded as a long-tier call (#3062).
|
|
261
|
+
const result = boundedSpawnSync(process.execPath, [
|
|
206
262
|
process.argv[1], 'mcp', 'inject',
|
|
207
263
|
'--provider', provider,
|
|
208
264
|
'--profile', profileName,
|
|
209
265
|
'--ephemeral',
|
|
210
266
|
'--out', tmpPath,
|
|
211
|
-
], { stdio: 'inherit', cwd });
|
|
267
|
+
], 'long', { stdio: ['inherit', 'pipe', 'inherit'], encoding: 'utf-8', cwd });
|
|
268
|
+
if (result.stdout)
|
|
269
|
+
process.stdout.write(result.stdout);
|
|
212
270
|
if (result.status !== 0) {
|
|
213
|
-
|
|
271
|
+
// #3062 review item 7: surface the bounded helper's diagnostic.
|
|
272
|
+
const { message } = exitCodeForSpawnResult(result, 'aiwg mcp inject');
|
|
273
|
+
if (provider === 'claude' || provider === 'claude-code') {
|
|
274
|
+
throw new Error(`Claude profile injection failed; refusing to launch.${message ? ` ${message}` : ''}`);
|
|
275
|
+
}
|
|
276
|
+
console.warn(` WARN Ephemeral profile inject failed — launching without profile MCP config.${message ? ` ${message}` : ''}`);
|
|
214
277
|
return null;
|
|
215
278
|
}
|
|
216
|
-
|
|
279
|
+
const reportedSettingsPath = result.stdout?.match(/^\s*Tool permissions: (.+)$/m)?.[1]?.trim();
|
|
280
|
+
const settingsPath = reportedSettingsPath && existsSync(reportedSettingsPath) ? reportedSettingsPath : null;
|
|
281
|
+
if (hasClaudePermissions && !settingsPath) {
|
|
282
|
+
throw new Error('Claude profile permission settings were not generated; refusing to launch.');
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
mcpConfigPath: tmpPath,
|
|
286
|
+
settingsPath,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const HEADLESS_SESSION_TRANSPORTS = {
|
|
290
|
+
claude: { kind: 'stdin', flags: ['--print', '--input-format', 'text'] },
|
|
291
|
+
'claude-code': { kind: 'stdin', flags: ['--print', '--input-format', 'text'] },
|
|
292
|
+
codex: { kind: 'stdin', flags: ['exec'], trailingArg: '-' },
|
|
293
|
+
openai: { kind: 'stdin', flags: ['exec'], trailingArg: '-' },
|
|
294
|
+
'deepseek-harness': { kind: 'stdin', flags: ['--profile', 'headless'], trailingArg: '-' },
|
|
295
|
+
opencode: {
|
|
296
|
+
kind: 'file',
|
|
297
|
+
flags: ['run', '--format', 'json'],
|
|
298
|
+
fileFlag: '-f',
|
|
299
|
+
fixedPositional: 'Follow the instructions in the attached file exactly.',
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
/**
|
|
303
|
+
* Build the final argv (and stdin/temp-file payload) for a headless launch,
|
|
304
|
+
* or `null` if the provider has no evidenced non-interactive form.
|
|
305
|
+
*/
|
|
306
|
+
function buildHeadlessLaunch(provider, binaryArgs, prompt) {
|
|
307
|
+
const transport = HEADLESS_SESSION_TRANSPORTS[provider];
|
|
308
|
+
if (!transport)
|
|
309
|
+
return null;
|
|
310
|
+
if (transport.kind === 'stdin') {
|
|
311
|
+
const args = [...binaryArgs, ...transport.flags, ...(transport.trailingArg ? [transport.trailingArg] : [])];
|
|
312
|
+
return { args, input: prompt };
|
|
313
|
+
}
|
|
314
|
+
// File transport (opencode): the prompt never touches argv or stdin — it
|
|
315
|
+
// is written to a private temp file only this process can read, passed
|
|
316
|
+
// by path. `mkdtemp()` under the independently verified system temp
|
|
317
|
+
// directory gives the
|
|
318
|
+
// directory itself a private, unpredictable name owned by this process;
|
|
319
|
+
// the file within it additionally gets an explicit `0600` mode via an
|
|
320
|
+
// exclusive create (`flag: 'wx'` — fails rather than silently
|
|
321
|
+
// overwriting if a file with that exact name already existed, which it
|
|
322
|
+
// never should in a fresh `mkdtemp()` directory).
|
|
323
|
+
//
|
|
324
|
+
// Cleanup ownership starts the moment `mkdtemp()` returns, *before* the
|
|
325
|
+
// write: a failed write must not leave the directory behind any more
|
|
326
|
+
// than a failed launch would. The caller's own `finally` (in
|
|
327
|
+
// `launchProvider()`, keyed off `plan.tempDir`) handles the ordinary
|
|
328
|
+
// "launch ran, now clean up" path; this one exists only for the write
|
|
329
|
+
// itself failing before a `plan` is ever returned to that caller.
|
|
330
|
+
// os.tmpdir() trusts TMPDIR/TEMP/TMP, which can point into the project.
|
|
331
|
+
// Select and validate the OS location independently of those overrides.
|
|
332
|
+
const systemTemp = realpathSync(process.platform === 'win32' ? join(process.env.SystemRoot ?? 'C:/Windows', 'Temp') : '/tmp');
|
|
333
|
+
const info = statSync(systemTemp);
|
|
334
|
+
if (!info.isDirectory() || (process.platform !== 'win32'
|
|
335
|
+
&& (info.uid !== 0 && info.uid !== process.getuid?.() || (info.mode & 0o022) !== 0 && (info.mode & 0o1000) === 0))) {
|
|
336
|
+
throw new Error('Unsafe system temporary directory for session prompt');
|
|
337
|
+
}
|
|
338
|
+
const tempDir = mkdtempSync(join(systemTemp, 'aiwg-session-prompt-'));
|
|
339
|
+
try {
|
|
340
|
+
const promptPath = join(tempDir, 'prompt.txt');
|
|
341
|
+
writeFileSync(promptPath, prompt, { encoding: 'utf-8', mode: 0o600, flag: 'wx' });
|
|
342
|
+
const args = [...binaryArgs, ...transport.flags, transport.fileFlag, promptPath, transport.fixedPositional];
|
|
343
|
+
return { args, tempDir };
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
try {
|
|
347
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
// Best-effort cleanup only; the original write failure is what matters.
|
|
351
|
+
}
|
|
352
|
+
throw err;
|
|
353
|
+
}
|
|
217
354
|
}
|
|
218
|
-
// ── Launch ────────────────────────────────────────────────────────────────────
|
|
219
355
|
/**
|
|
220
356
|
* Launch a spawnable provider binary, replacing the current process (exec-style).
|
|
221
357
|
* For IDE-integrated providers, print guidance and exit.
|
|
358
|
+
*
|
|
359
|
+
* `prompt`, when given, selects the headless path (#3062): the provider is
|
|
360
|
+
* launched with its documented non-interactive form, bounded and with stdin
|
|
361
|
+
* ignored, regardless of whether a terminal is attached. Without `prompt`
|
|
362
|
+
* and without a terminal, this refuses before spawning anything that would
|
|
363
|
+
* hang.
|
|
222
364
|
*/
|
|
223
|
-
function launchProvider(provider, mcpInjected, mcpConfigPath, profile) {
|
|
365
|
+
async function launchProvider(provider, mcpInjected, mcpConfigPath, profile, settingsPath, prompt, interactive) {
|
|
224
366
|
const cfg = getProviderConfig(provider);
|
|
225
367
|
if (!isSpawnableProvider(provider)) {
|
|
226
|
-
// Not spawnable —
|
|
368
|
+
// Not spawnable — a prompt can never be delivered to a binary that does
|
|
369
|
+
// not exist. Refuse explicitly instead of discarding it and printing
|
|
370
|
+
// success (#3062 review item 9). `execute()` already checks this before
|
|
371
|
+
// any pre-flight step runs; this is defense-in-depth for this function
|
|
372
|
+
// on its own.
|
|
373
|
+
if (prompt) {
|
|
374
|
+
return {
|
|
375
|
+
exitCode: 1,
|
|
376
|
+
message: `Provider '${cfg.name}' has no CLI binary to spawn; --prompt/--prompt-file requires a ` +
|
|
377
|
+
'spawnable provider.',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
// Print context-aware guidance
|
|
227
381
|
const mcpNote = mcpInjected
|
|
228
382
|
? '\n MCP servers have been injected into your provider config.\n'
|
|
229
383
|
: '';
|
|
@@ -237,25 +391,87 @@ ${cfg.guidanceMessage ?? `Open ${cfg.name} in your project directory to begin.`}
|
|
|
237
391
|
`);
|
|
238
392
|
return { exitCode: 0 };
|
|
239
393
|
}
|
|
240
|
-
// Spawnable — hand off the terminal
|
|
394
|
+
// Spawnable — hand off the terminal (or run headless, see below)
|
|
241
395
|
console.log(`\n── Launching ${cfg.name} ──\n`);
|
|
242
396
|
// Codex + profile: use runtime-home adapter (#892)
|
|
243
397
|
if ((provider === 'codex' || provider === 'openai') && profile) {
|
|
244
398
|
console.log(` Using runtime home for profile "${profile}"`);
|
|
245
|
-
|
|
246
|
-
|
|
399
|
+
// Headless (#3062): the prompt travels on stdin via a trailing `-`
|
|
400
|
+
// positional (docs/providers/codex-direct.md: "`-` reads stdin"),
|
|
401
|
+
// never on argv (review item 8). `launchWithProfile` itself
|
|
402
|
+
// bounds/refuses based on whether `extraArgs` is a non-interactive form
|
|
403
|
+
// and whether a terminal is attached — see its own doc comment.
|
|
404
|
+
const extraArgs = prompt ? ['exec', '-'] : [];
|
|
405
|
+
const result = launchWithProfile(profile, extraArgs, prompt ? { input: prompt, interactive } : { interactive });
|
|
406
|
+
const { exitCode, message } = exitCodeForSpawnResult(result, 'codex');
|
|
407
|
+
return message ? { exitCode, message } : { exitCode };
|
|
247
408
|
}
|
|
248
|
-
// Claude:
|
|
409
|
+
// Claude: load both the ephemeral MCP config and any permission settings.
|
|
249
410
|
const binaryArgs = [];
|
|
250
411
|
if (mcpConfigPath && (provider === 'claude' || provider === 'claude-code')) {
|
|
251
412
|
binaryArgs.push('--mcp-config', mcpConfigPath);
|
|
252
413
|
console.log(` Using ephemeral MCP config: ${mcpConfigPath}`);
|
|
414
|
+
if (settingsPath) {
|
|
415
|
+
binaryArgs.push('--settings', settingsPath);
|
|
416
|
+
console.log(` Using ephemeral permission settings: ${settingsPath}`);
|
|
417
|
+
}
|
|
253
418
|
}
|
|
419
|
+
if (prompt) {
|
|
420
|
+
const plan = buildHeadlessLaunch(provider, binaryArgs, prompt);
|
|
421
|
+
if (!plan) {
|
|
422
|
+
return {
|
|
423
|
+
exitCode: 1,
|
|
424
|
+
message: `Provider '${cfg.name}' has no evidenced non-interactive CLI form; --prompt/--prompt-file ` +
|
|
425
|
+
`is not supported for it. Run \`aiwg session --provider ${provider}\` from a terminal instead.`,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
// Bounded (#3062): a headless run never hands over the terminal, so
|
|
430
|
+
// it is safe to bound. The prompt never rides on argv (review item
|
|
431
|
+
// 8); stdin-transport providers get it via `input` (never inherited
|
|
432
|
+
// from the parent), file-transport providers get it via a private
|
|
433
|
+
// temp file path already baked into `plan.args`.
|
|
434
|
+
const launchOptions = {
|
|
435
|
+
stdio: plan.input !== undefined ? ['pipe', 'inherit', 'inherit'] : ['ignore', 'inherit', 'inherit'],
|
|
436
|
+
env: process.env,
|
|
437
|
+
...(plan.input !== undefined ? { input: plan.input } : {}),
|
|
438
|
+
};
|
|
439
|
+
const result = plan.tempDir
|
|
440
|
+
? await boundedSpawn(cfg.binary, plan.args, 'long', { ...launchOptions, stdio: ['ignore', 'inherit', 'inherit'] }, () => rmSync(plan.tempDir, { recursive: true, force: true }))
|
|
441
|
+
: boundedSpawnSync(cfg.binary, plan.args, 'long', launchOptions);
|
|
442
|
+
const { exitCode, message } = exitCodeForSpawnResult(result, cfg.binary);
|
|
443
|
+
return message ? { exitCode, message } : { exitCode };
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
if (plan.tempDir) {
|
|
447
|
+
try {
|
|
448
|
+
rmSync(plan.tempDir, { recursive: true, force: true });
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// Best-effort cleanup only; a leftover temp file is not fatal.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (!interactive) {
|
|
457
|
+
return {
|
|
458
|
+
exitCode: 1,
|
|
459
|
+
message: 'aiwg session requires an interactive terminal to launch a provider without a prompt. ' +
|
|
460
|
+
'Pass --prompt <text> or --prompt-file <path> to run headless.',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// sync-spawn: interactive harness-launch — hands the terminal to the
|
|
464
|
+
// provider binary the user is meant to talk to, when no --prompt/
|
|
465
|
+
// --prompt-file was supplied (see tools/testing/sync-spawn-allowlist.json).
|
|
466
|
+
// Reached only when `interactive` is true (#3062) — the headless and
|
|
467
|
+
// no-terminal paths above are handled before this point. Not a candidate
|
|
468
|
+
// for a bounded timeout.
|
|
254
469
|
const result = spawnSync(cfg.binary, binaryArgs, {
|
|
255
470
|
stdio: 'inherit',
|
|
256
471
|
env: process.env,
|
|
257
472
|
});
|
|
258
|
-
|
|
473
|
+
const { exitCode, message } = exitCodeForSpawnResult(result, cfg.binary);
|
|
474
|
+
return message ? { exitCode, message } : { exitCode };
|
|
259
475
|
}
|
|
260
476
|
// ── Handler ───────────────────────────────────────────────────────────────────
|
|
261
477
|
export const sessionHandler = {
|
|
@@ -265,11 +481,93 @@ export const sessionHandler = {
|
|
|
265
481
|
category: 'project',
|
|
266
482
|
aliases: [],
|
|
267
483
|
async execute(ctx) {
|
|
268
|
-
|
|
484
|
+
let parsed;
|
|
485
|
+
try {
|
|
486
|
+
parsed = parseSessionArgs(ctx.args);
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
// #3062 review item 11: a value-taking flag present without a usable
|
|
490
|
+
// value is a usage error, reported before any preflight/spawn/write.
|
|
491
|
+
if (err instanceof MissingFlagValueError) {
|
|
492
|
+
return { exitCode: 2, message: `aiwg session: ${err.message}.` };
|
|
493
|
+
}
|
|
494
|
+
throw err;
|
|
495
|
+
}
|
|
496
|
+
const { mcp, provider: explicitProvider, noRepair, profile, persist, prompt: promptArg, promptFile, nonInteractive, } = parsed;
|
|
269
497
|
const cwd = ctx.cwd || process.cwd();
|
|
498
|
+
// #3062 review item 12: the one shared interactive decision, taking
|
|
499
|
+
// this command's own explicit `--non-interactive` flag as input instead
|
|
500
|
+
// of only ever checking the environment.
|
|
501
|
+
const interactive = isInteractive(nonInteractive);
|
|
502
|
+
// ── Resolve headless prompt (#3062) — before any pre-flight spawn ──────
|
|
503
|
+
if (promptArg && promptFile) {
|
|
504
|
+
return { exitCode: 2, message: 'aiwg session: pass only one of --prompt or --prompt-file.' };
|
|
505
|
+
}
|
|
506
|
+
let prompt = promptArg;
|
|
507
|
+
if (!prompt && promptFile) {
|
|
508
|
+
let fileText;
|
|
509
|
+
try {
|
|
510
|
+
fileText = await readFile(promptFile, 'utf-8');
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
return {
|
|
514
|
+
exitCode: 1,
|
|
515
|
+
message: `aiwg session: could not read --prompt-file '${promptFile}': ${err instanceof Error ? err.message : String(err)}`,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
prompt = fileText.trim();
|
|
519
|
+
if (!prompt) {
|
|
520
|
+
return { exitCode: 1, message: `aiwg session: --prompt-file '${promptFile}' is empty.` };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
270
523
|
// ── Resolve provider ─────────────────────────────────────────
|
|
271
524
|
const provider = await resolveProvider(explicitProvider, cwd);
|
|
272
525
|
const providerCfg = PROVIDER_CONFIGS[provider];
|
|
526
|
+
// An unsupported (non-spawnable) provider can never run a supplied
|
|
527
|
+
// prompt — it has no binary to hand it to. Validate this before any
|
|
528
|
+
// pre-flight step runs a child process (#3062 review item 9): the old
|
|
529
|
+
// behavior silently discarded the prompt and printed IDE guidance at
|
|
530
|
+
// exit 0.
|
|
531
|
+
if (prompt && !isSpawnableProvider(provider)) {
|
|
532
|
+
const unsupportedCfg = getProviderConfig(provider);
|
|
533
|
+
return {
|
|
534
|
+
exitCode: 1,
|
|
535
|
+
message: `Provider '${unsupportedCfg.name}' has no CLI binary to spawn; --prompt/--prompt-file ` +
|
|
536
|
+
'requires a spawnable provider. Run `aiwg session --provider ' + provider +
|
|
537
|
+
'` from a terminal for IDE-integrated usage instead.',
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
// A spawnable provider with no evidenced non-interactive CLI form
|
|
541
|
+
// (e.g. antigravity) can never run a supplied prompt either — check
|
|
542
|
+
// this in the same early spot as the non-spawnable case above (#3062
|
|
543
|
+
// second review, finding 6): the old behavior deferred this to
|
|
544
|
+
// `buildHeadlessLaunch()` inside `launchProvider()`, reached only
|
|
545
|
+
// after the version-check/doctor/deployment/mcp-inject pre-flight
|
|
546
|
+
// already ran. The codex-profile launch path is exempt — it always
|
|
547
|
+
// uses `codex exec -`, never `HEADLESS_SESSION_TRANSPORTS`.
|
|
548
|
+
if (prompt && isSpawnableProvider(provider) && !HEADLESS_SESSION_TRANSPORTS[provider]
|
|
549
|
+
&& !((provider === 'codex' || provider === 'openai') && profile)) {
|
|
550
|
+
const unsupportedTransportCfg = getProviderConfig(provider);
|
|
551
|
+
return {
|
|
552
|
+
exitCode: 1,
|
|
553
|
+
message: `Provider '${unsupportedTransportCfg.name}' has no evidenced non-interactive CLI form; ` +
|
|
554
|
+
`--prompt/--prompt-file is not supported for it. Run \`aiwg session --provider ${provider}\` ` +
|
|
555
|
+
'from a terminal instead.',
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
// Without a terminal, a spawnable provider launched with no prompt would
|
|
559
|
+
// hand over a terminal that does not exist and hang. Fail fast, before
|
|
560
|
+
// any pre-flight step runs a child process (#3062). IDE-integrated
|
|
561
|
+
// providers only ever print guidance — never spawn — so they don't need
|
|
562
|
+
// a prompt to stay safe headless.
|
|
563
|
+
if (!prompt && isSpawnableProvider(provider) && !interactive) {
|
|
564
|
+
return {
|
|
565
|
+
exitCode: 1,
|
|
566
|
+
message: `aiwg session requires --prompt <text> or --prompt-file <path> when no interactive terminal is ` +
|
|
567
|
+
`attached (provider '${provider}' launches a binary that would otherwise hand over the terminal ` +
|
|
568
|
+
'and hang).',
|
|
569
|
+
};
|
|
570
|
+
}
|
|
273
571
|
const profileSuffix = profile ? ` · profile: ${profile}` : '';
|
|
274
572
|
console.log(`\n── aiwg session ── provider: ${providerCfg?.name ?? provider}${profileSuffix} ──\n`);
|
|
275
573
|
const frameworkRoot = await getFrameworkRoot();
|
|
@@ -298,38 +596,84 @@ export const sessionHandler = {
|
|
|
298
596
|
// ── Step 4: MCP inject ────────────────────────────────────────
|
|
299
597
|
let mcpInjected = false;
|
|
300
598
|
let mcpConfigPath = null;
|
|
599
|
+
let settingsPath = null;
|
|
301
600
|
if (profile) {
|
|
302
601
|
// Profile-aware injection (#891) — ephemeral by default
|
|
303
|
-
//
|
|
304
|
-
if (
|
|
602
|
+
// Codex profiles must pass policy and setup before any launch (#892)
|
|
603
|
+
if (provider === 'codex' || provider === 'openai') {
|
|
305
604
|
try {
|
|
306
|
-
console.log(`\n Setting up Codex runtime home for profile "${profile}"...`);
|
|
605
|
+
console.log(`\n Setting up Codex ${persist ? 'persistent config' : 'runtime home'} for profile "${profile}"...`);
|
|
307
606
|
// Import server list for this profile
|
|
308
607
|
const { McpProfileRegistry } = await import('../../mcp/profiles.js');
|
|
309
608
|
const { McpServerRegistry } = await import('../../mcp/registry.js');
|
|
310
609
|
const profiles = new McpProfileRegistry();
|
|
311
610
|
const registry = new McpServerRegistry();
|
|
312
611
|
const resolvedServers = await profiles.resolveServers(profile, registry);
|
|
313
|
-
await
|
|
314
|
-
await
|
|
315
|
-
|
|
612
|
+
const { resolveToolFilters } = await import('../../mcp/tool-filters.mjs');
|
|
613
|
+
const { assertCredentialPolicy, resolveCredentialPolicy } = await import('../../mcp/credentials.mjs');
|
|
614
|
+
const registryPolicy = await registry.getCredentialPolicy();
|
|
615
|
+
const floorPolicy = await registry.getCredentialPolicyFloor();
|
|
616
|
+
const floorSource = await registry.getCredentialPolicyFloorSource();
|
|
617
|
+
let credentialPolicy;
|
|
618
|
+
try {
|
|
619
|
+
credentialPolicy = resolveCredentialPolicy({ registryPolicy, floorPolicy, floorSource });
|
|
620
|
+
assertCredentialPolicy(resolvedServers, credentialPolicy);
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
console.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
624
|
+
return { exitCode: 1 };
|
|
625
|
+
}
|
|
626
|
+
if (persist) {
|
|
627
|
+
injectMcpProfile(provider, profile, cwd, true);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
await ensureRuntimeHome(profile);
|
|
631
|
+
// writeProfileConfig re-resolves and re-enforces the floor itself (so a
|
|
632
|
+
// direct API call cannot bypass it); passing it here too keeps this
|
|
633
|
+
// call's diagnostics consistent with the one already performed above.
|
|
634
|
+
const warnings = await writeProfileConfig(profile, resolvedServers, {
|
|
635
|
+
credentialPolicy, floorPolicy,
|
|
636
|
+
toolFilters: resolveToolFilters(await profiles.resolve(profile), 'codex'),
|
|
637
|
+
});
|
|
638
|
+
for (const warning of warnings)
|
|
639
|
+
console.warn(` WARN codex: ${warning}`);
|
|
640
|
+
console.log(` Runtime home ready. Profile servers: ${resolvedServers.map((s) => s.name).join(', ') || '(none)'}`);
|
|
641
|
+
}
|
|
316
642
|
mcpInjected = true;
|
|
317
643
|
}
|
|
318
644
|
catch (err) {
|
|
319
645
|
console.warn(` WARN Codex runtime home setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
320
|
-
|
|
646
|
+
return { exitCode: 1 };
|
|
321
647
|
}
|
|
322
648
|
}
|
|
323
649
|
else {
|
|
324
|
-
|
|
325
|
-
|
|
650
|
+
try {
|
|
651
|
+
let hasClaudePermissions = false;
|
|
652
|
+
if (provider === 'claude' || provider === 'claude-code') {
|
|
653
|
+
const { McpProfileRegistry } = await import('../../mcp/profiles.js');
|
|
654
|
+
const { McpServerRegistry } = await import('../../mcp/registry.js');
|
|
655
|
+
const { resolveToolFilters, planToolFilters } = await import('../../mcp/tool-filters.mjs');
|
|
656
|
+
const profiles = new McpProfileRegistry();
|
|
657
|
+
const servers = await profiles.resolveServers(profile, new McpServerRegistry());
|
|
658
|
+
const plan = planToolFilters(provider, servers.map(server => server.name), resolveToolFilters(await profiles.resolve(profile), provider));
|
|
659
|
+
hasClaudePermissions = plan.claudePermissions !== null;
|
|
660
|
+
}
|
|
661
|
+
const ephemeral = injectMcpProfile(provider, profile, cwd, persist, hasClaudePermissions);
|
|
662
|
+
mcpConfigPath = ephemeral?.mcpConfigPath ?? null;
|
|
663
|
+
settingsPath = ephemeral?.settingsPath ?? null;
|
|
664
|
+
mcpInjected = ephemeral !== null || persist;
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
console.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
668
|
+
return { exitCode: 1 };
|
|
669
|
+
}
|
|
326
670
|
}
|
|
327
671
|
}
|
|
328
672
|
else if (mcp) {
|
|
329
673
|
mcpInjected = injectMcp(provider, cwd);
|
|
330
674
|
}
|
|
331
675
|
// ── Step 5: Launch ────────────────────────────────────────────
|
|
332
|
-
return launchProvider(provider, mcpInjected, mcpConfigPath, profile);
|
|
676
|
+
return launchProvider(provider, mcpInjected, mcpConfigPath, persist && (provider === 'codex' || provider === 'openai') ? null : profile, settingsPath, prompt, interactive);
|
|
333
677
|
},
|
|
334
678
|
};
|
|
335
679
|
//# sourceMappingURL=session.js.map
|