@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.
Files changed (361) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/capability-matrix.yaml +270 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +3183 -24
  4. package/bin/aiwg.mjs +135 -47
  5. package/dist/src/a2a/codecs.js +9 -0
  6. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  7. package/dist/src/artifacts/browser-export.js +6 -0
  8. package/dist/src/artifacts/checksum-manifest.js +1 -0
  9. package/dist/src/artifacts/citation-parser.js +173 -92
  10. package/dist/src/artifacts/citation-table-schema.js +59 -0
  11. package/dist/src/artifacts/cli.js +57 -18
  12. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  13. package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
  14. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  15. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  16. package/dist/src/artifacts/discovery-eval.js +6 -4
  17. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  18. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  19. package/dist/src/artifacts/framework-capabilities.js +171 -0
  20. package/dist/src/artifacts/framework-index-paths.js +73 -0
  21. package/dist/src/artifacts/framework-source-digest.js +75 -0
  22. package/dist/src/artifacts/index-builder.js +285 -131
  23. package/dist/src/artifacts/index-files.js +132 -18
  24. package/dist/src/artifacts/index-preparation.js +65 -0
  25. package/dist/src/artifacts/index-refresh.js +576 -0
  26. package/dist/src/artifacts/index-worker.mjs +9 -0
  27. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  28. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  29. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  30. package/dist/src/artifacts/query-engine.js +410 -88
  31. package/dist/src/artifacts/relations-builder.js +168 -0
  32. package/dist/src/artifacts/relations.js +148 -0
  33. package/dist/src/artifacts/repair.js +9 -4
  34. package/dist/src/artifacts/stats.js +5 -2
  35. package/dist/src/artifacts/types.js +30 -10
  36. package/dist/src/artifacts/watcher.js +1 -1
  37. package/dist/src/auth/credential-store.js +2 -1
  38. package/dist/src/catalog/cli.d.mts +5 -0
  39. package/dist/src/channel/manager.d.mts +20 -0
  40. package/dist/src/channel/manager.mjs +78 -12
  41. package/dist/src/cli/child-process-result.js +40 -0
  42. package/dist/src/cli/command-log.js +2 -3
  43. package/dist/src/cli/env.js +38 -16
  44. package/dist/src/cli/flag-parsing.js +236 -0
  45. package/dist/src/cli/handlers/artifacts.js +3 -0
  46. package/dist/src/cli/handlers/auth.js +3 -0
  47. package/dist/src/cli/handlers/decision.js +218 -5
  48. package/dist/src/cli/handlers/diagnose.js +13 -5
  49. package/dist/src/cli/handlers/effect.js +4 -2
  50. package/dist/src/cli/handlers/evidence.js +3 -0
  51. package/dist/src/cli/handlers/feedback.js +14 -5
  52. package/dist/src/cli/handlers/help.js +1 -0
  53. package/dist/src/cli/handlers/index.js +3 -1
  54. package/dist/src/cli/handlers/init.js +248 -30
  55. package/dist/src/cli/handlers/lint.js +5 -0
  56. package/dist/src/cli/handlers/mc.js +23 -0
  57. package/dist/src/cli/handlers/models.js +169 -10
  58. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  59. package/dist/src/cli/handlers/ralph.js +3 -0
  60. package/dist/src/cli/handlers/refresh.js +28 -0
  61. package/dist/src/cli/handlers/regenerate.js +14 -0
  62. package/dist/src/cli/handlers/resource-versions.js +3 -0
  63. package/dist/src/cli/handlers/rules.js +195 -0
  64. package/dist/src/cli/handlers/run.js +4 -0
  65. package/dist/src/cli/handlers/runtime-info.js +25 -11
  66. package/dist/src/cli/handlers/script-runner.js +19 -8
  67. package/dist/src/cli/handlers/session.js +406 -62
  68. package/dist/src/cli/handlers/sessions.js +243 -76
  69. package/dist/src/cli/handlers/setup-manifest.js +474 -79
  70. package/dist/src/cli/handlers/setup.js +180 -39
  71. package/dist/src/cli/handlers/steward.js +11 -0
  72. package/dist/src/cli/handlers/subcommands.js +60 -3
  73. package/dist/src/cli/handlers/team.js +18 -8
  74. package/dist/src/cli/handlers/uhp.js +3 -0
  75. package/dist/src/cli/handlers/use.js +307 -55
  76. package/dist/src/cli/handlers/utilities.js +125 -26
  77. package/dist/src/cli/handlers/version.js +8 -0
  78. package/dist/src/cli/prompt-utils.js +72 -0
  79. package/dist/src/cli/provider-resolution.js +11 -0
  80. package/dist/src/cli/router.js +15 -1
  81. package/dist/src/cli/services/deployment-verification.js +19 -9
  82. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  83. package/dist/src/cli/skill-usage.js +2 -3
  84. package/dist/src/config/aiwg-config.js +349 -35
  85. package/dist/src/config/cli.js +256 -23
  86. package/dist/src/config/decision-config.js +106 -0
  87. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  88. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  89. package/dist/src/config/gitignore.js +50 -0
  90. package/dist/src/config/index-graph-overrides.js +36 -0
  91. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  92. package/dist/src/config/project-artifacts-health.mjs +14 -3
  93. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  94. package/dist/src/config/user-config-dir.d.mts +9 -0
  95. package/dist/src/config/user-config.js +38 -11
  96. package/dist/src/config/user-registry.js +23 -15
  97. package/dist/src/config/user-state.js +7 -0
  98. package/dist/src/decision/adapters/jev.js +75 -27
  99. package/dist/src/decision/ask.js +203 -43
  100. package/dist/src/decision/context-live-qualification.js +2 -2
  101. package/dist/src/decision/driver.js +14 -7
  102. package/dist/src/decision/ensemble/aggregate.js +23 -10
  103. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  104. package/dist/src/decision/entry.js +25 -6
  105. package/dist/src/decision/graph-live-qualification.js +1 -1
  106. package/dist/src/decision/graph-run.js +9 -0
  107. package/dist/src/decision/heldout/collector.js +15 -14
  108. package/dist/src/decision/heldout/contract.js +30 -7
  109. package/dist/src/decision/index.js +1 -0
  110. package/dist/src/decision/interop.js +60 -8
  111. package/dist/src/decision/jev-credentials.js +15 -1
  112. package/dist/src/decision/ledger.js +942 -0
  113. package/dist/src/decision/offload-activation.js +137 -0
  114. package/dist/src/decision/offload.js +550 -0
  115. package/dist/src/decision/qualification/load.js +4 -6
  116. package/dist/src/decision/savings.js +28 -0
  117. package/dist/src/decision/validate.js +6 -3
  118. package/dist/src/effects/dispatch-verifier.js +252 -0
  119. package/dist/src/effects/index.js +1 -0
  120. package/dist/src/effects/types.js +5 -1
  121. package/dist/src/effects/verifiers/git.js +2 -1
  122. package/dist/src/effects/verifiers/tracker.js +26 -14
  123. package/dist/src/extensions/commands/definitions.js +19 -0
  124. package/dist/src/extensions/project-local-remove.js +36 -1
  125. package/dist/src/features/installer.js +2 -2
  126. package/dist/src/flow/graph-metadata.js +5 -0
  127. package/dist/src/gates/providers/loader.js +64 -18
  128. package/dist/src/installation/manager-command.d.mts +18 -0
  129. package/dist/src/installation/manager-command.mjs +15 -3
  130. package/dist/src/installation/manager.d.mts +49 -0
  131. package/dist/src/installation/manager.mjs +95 -5
  132. package/dist/src/issues/cli.js +4 -4
  133. package/dist/src/issues/local.js +24 -5
  134. package/dist/src/jobs/runner.js +1 -1
  135. package/dist/src/lint/cli.js +27 -1
  136. package/dist/src/lint/loader.js +2 -1
  137. package/dist/src/lint/reporters.js +25 -1
  138. package/dist/src/lint/rule-activation.js +43 -0
  139. package/dist/src/lint/runner.js +137 -62
  140. package/dist/src/mcp/adapters/codex-runtime.js +366 -56
  141. package/dist/src/mcp/claude-install.d.mts +4 -0
  142. package/dist/src/mcp/claude-install.mjs +41 -0
  143. package/dist/src/mcp/cli.d.mts +1 -0
  144. package/dist/src/mcp/cli.mjs +620 -85
  145. package/dist/src/mcp/config-file.d.mts +28 -0
  146. package/dist/src/mcp/config-file.mjs +279 -0
  147. package/dist/src/mcp/config-layers.d.mts +36 -0
  148. package/dist/src/mcp/config-layers.mjs +348 -0
  149. package/dist/src/mcp/credentials.d.mts +51 -0
  150. package/dist/src/mcp/credentials.mjs +484 -0
  151. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  152. package/dist/src/mcp/grok-build-config.mjs +17 -27
  153. package/dist/src/mcp/helpers.mjs +109 -4
  154. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  155. package/dist/src/mcp/muse-native-config.mjs +1 -0
  156. package/dist/src/mcp/omp-config.d.mts +4 -0
  157. package/dist/src/mcp/omp-config.mjs +50 -29
  158. package/dist/src/mcp/profiles.js +97 -23
  159. package/dist/src/mcp/profiles.mjs +99 -27
  160. package/dist/src/mcp/registry.js +248 -131
  161. package/dist/src/mcp/registry.mjs +169 -100
  162. package/dist/src/mcp/server-config.d.mts +3 -0
  163. package/dist/src/mcp/server-config.mjs +89 -0
  164. package/dist/src/mcp/server.mjs +127 -82
  165. package/dist/src/mcp/test-simple.mjs +2 -1
  166. package/dist/src/mcp/toml-editor.d.mts +4 -0
  167. package/dist/src/mcp/toml-string.d.mts +1 -0
  168. package/dist/src/mcp/toml-string.mjs +9 -0
  169. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  170. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  171. package/dist/src/mcp/tool-filters.d.mts +46 -0
  172. package/dist/src/mcp/tool-filters.mjs +357 -0
  173. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  174. package/dist/src/mcp/tools/decision.mjs +22 -3
  175. package/dist/src/mcp/tools/discovery.mjs +62 -55
  176. package/dist/src/mcp/tools/orchestration.mjs +50 -2
  177. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  178. package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
  179. package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
  180. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  181. package/dist/src/memory/project-registry.js +2 -2
  182. package/dist/src/mission-protocol/codecs.js +647 -1
  183. package/dist/src/mission-protocol/types.js +110 -0
  184. package/dist/src/mission-protocol/worker-status.js +2 -0
  185. package/dist/src/models/config-loader.js +76 -14
  186. package/dist/src/models/grok-build-models.js +93 -41
  187. package/dist/src/models/model-capabilities.v1.json +3183 -24
  188. package/dist/src/models/model-discovery.js +931 -67
  189. package/dist/src/models/provider-policy.js +719 -11
  190. package/dist/src/models/qualification-digest.d.mts +2 -0
  191. package/dist/src/models/qualification-digest.mjs +17 -0
  192. package/dist/src/models/resolver.js +261 -0
  193. package/dist/src/models/worker-direct-readiness.js +550 -0
  194. package/dist/src/models/worker-dispatch-offer.js +169 -0
  195. package/dist/src/models/worker-health-readiness.js +144 -0
  196. package/dist/src/models/worker-installation.d.mts +39 -0
  197. package/dist/src/models/worker-installation.mjs +986 -0
  198. package/dist/src/models/wrapper-route.js +18 -0
  199. package/dist/src/ops/registry.js +9 -14
  200. package/dist/src/plugins/standalone-packager.js +4 -3
  201. package/dist/src/process/bounded-child-process.d.mts +84 -0
  202. package/dist/src/process/bounded-child-process.mjs +430 -0
  203. package/dist/src/process/cli-command.d.mts +16 -0
  204. package/dist/src/process/cli-command.mjs +75 -0
  205. package/dist/src/process/signal-child.d.mts +6 -0
  206. package/dist/src/process/signal-child.mjs +7 -0
  207. package/dist/src/providers/capability-matrix.js +8 -0
  208. package/dist/src/providers/capability-matrix.yaml +270 -0
  209. package/dist/src/providers/deployed-skill-roots.js +20 -0
  210. package/dist/src/providers/grok-build-acp.js +3 -2
  211. package/dist/src/providers/grok-build-headless.js +53 -23
  212. package/dist/src/providers/grok-build-inspect.js +1 -0
  213. package/dist/src/providers/omp-agent.d.mts +8 -0
  214. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  215. package/dist/src/providers/omp-paths.d.mts +13 -0
  216. package/dist/src/providers/provider-definitions.js +9 -1
  217. package/dist/src/providers/provider-definitions.mjs +9 -2
  218. package/dist/src/providers/skill-ownership.d.mts +9 -0
  219. package/dist/src/providers/skill-ownership.mjs +88 -0
  220. package/dist/src/rules/activation.d.mts +21 -0
  221. package/dist/src/rules/activation.mjs +62 -0
  222. package/dist/src/rules/inline-budget.d.mts +26 -0
  223. package/dist/src/rules/inline-budget.mjs +66 -0
  224. package/dist/src/rules/inventory.js +129 -0
  225. package/dist/src/rules/pin-state.js +73 -0
  226. package/dist/src/rules/pins.d.mts +14 -0
  227. package/dist/src/rules/pins.mjs +112 -0
  228. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  229. package/dist/src/serve/executor-registry.js +16 -24
  230. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  231. package/dist/src/serve/mission-conductor.js +47 -12
  232. package/dist/src/serve/shared-host-scheduler.js +2 -0
  233. package/dist/src/serve/worker-invocation.js +558 -0
  234. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  235. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  236. package/dist/src/sessions/adapter-registry.js +49 -0
  237. package/dist/src/sessions/adapters/claude.js +39 -54
  238. package/dist/src/sessions/adapters/codex.js +82 -67
  239. package/dist/src/sessions/adapters/copilot.js +4 -4
  240. package/dist/src/sessions/adapters/cursor.js +40 -57
  241. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  242. package/dist/src/sessions/adapters/factory.js +31 -27
  243. package/dist/src/sessions/adapters/generic.js +43 -9
  244. package/dist/src/sessions/adapters/grok-build.js +11 -11
  245. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  246. package/dist/src/sessions/adapters/grokbot.js +434 -24
  247. package/dist/src/sessions/adapters/hermes.js +27 -29
  248. package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
  249. package/dist/src/sessions/adapters/muse.js +200 -104
  250. package/dist/src/sessions/adapters/omp.js +28 -14
  251. package/dist/src/sessions/adapters/openclaw.js +89 -79
  252. package/dist/src/sessions/adapters/opencode.js +27 -24
  253. package/dist/src/sessions/adapters/openhuman.js +18 -60
  254. package/dist/src/sessions/adapters/pi.js +30 -14
  255. package/dist/src/sessions/adapters/warp.js +3 -3
  256. package/dist/src/sessions/adapters/windsurf.js +19 -48
  257. package/dist/src/sessions/batch-contracts.js +25 -5
  258. package/dist/src/sessions/batch-import.js +72 -51
  259. package/dist/src/sessions/candidates.js +47 -11
  260. package/dist/src/sessions/contracts.js +75 -2
  261. package/dist/src/sessions/coverage-preflight.js +134 -0
  262. package/dist/src/sessions/discovery.js +3 -0
  263. package/dist/src/sessions/export-plan.js +4 -3
  264. package/dist/src/sessions/grok-build-cli.js +67 -5
  265. package/dist/src/sessions/import-exclusions.js +72 -0
  266. package/dist/src/sessions/import-lease.js +34 -18
  267. package/dist/src/sessions/import-limits.js +37 -0
  268. package/dist/src/sessions/importer.js +84 -46
  269. package/dist/src/sessions/index.js +2 -0
  270. package/dist/src/sessions/lifecycle.js +45 -0
  271. package/dist/src/sessions/policy.js +133 -25
  272. package/dist/src/sessions/readers.js +106 -53
  273. package/dist/src/sessions/refresh.js +106 -0
  274. package/dist/src/sessions/repository.js +199 -63
  275. package/dist/src/sessions/workspace-discovery.js +315 -88
  276. package/dist/src/sessions/workspace-matching.js +107 -0
  277. package/dist/src/skills/adapters/clawhub.js +10 -8
  278. package/dist/src/skills/adapters/openclaw.js +10 -7
  279. package/dist/src/skills/importer.js +1 -0
  280. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  281. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  282. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  283. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  284. package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
  285. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  286. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  287. package/dist/src/smiths/mcpsmith/example.js +1 -1
  288. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  289. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  290. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
  291. package/dist/src/storage/backends/fs.js +18 -76
  292. package/dist/src/storage/backends/logseq.js +68 -7
  293. package/dist/src/storage/backends/obsidian.js +1 -0
  294. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  295. package/dist/src/testing/generators/test-code-generator.js +57 -30
  296. package/dist/src/tracker/forge-http.js +2 -1
  297. package/dist/src/uhp/client.js +11 -1
  298. package/dist/src/uhp/mission.js +5 -0
  299. package/dist/src/update/checker.d.mts +2 -0
  300. package/dist/src/update/checker.mjs +5 -5
  301. package/dist/src/update/notifier.mjs +54 -2
  302. package/dist/src/update/service.d.mts +22 -0
  303. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  304. package/dist/src/utils/contained-filesystem.mjs +683 -0
  305. package/dist/src/utils/temporary-directory.js +36 -0
  306. package/dist/src/writing/example-generator.js +1 -1
  307. package/dist/src/writing/fidelity.js +18 -1
  308. package/package.json +32 -2
  309. package/schemas/mission-v1.schema.json +2429 -0
  310. package/schemas/models/model-catalog.v1.schema.json +301 -0
  311. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  312. package/schemas/models/model-policy.v1.schema.json +14 -0
  313. package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
  314. package/tools/_resolve-impl.mjs +39 -2
  315. package/tools/agents/deploy-agents.mjs +64 -23
  316. package/tools/agents/providers/base.mjs +153 -166
  317. package/tools/agents/providers/claude.mjs +8 -24
  318. package/tools/agents/providers/codex.mjs +18 -11
  319. package/tools/agents/providers/cursor.mjs +19 -18
  320. package/tools/agents/providers/grok-build.mjs +3 -1
  321. package/tools/agents/providers/grokbot.mjs +11 -12
  322. package/tools/agents/providers/hermes.mjs +62 -49
  323. package/tools/agents/providers/omp.mjs +5 -2
  324. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  325. package/tools/agents/providers/openclaw.mjs +9 -4
  326. package/tools/agents/providers/pi.mjs +5 -0
  327. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  328. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  329. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  330. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  331. package/tools/agents/providers/warp.mjs +20 -26
  332. package/tools/cli/decision-offload-report.mjs +40 -0
  333. package/tools/cli/workspace-status.mjs +862 -0
  334. package/tools/plugin/package-plugins.mjs +186 -8
  335. package/tools/plugin/plugin-links.mjs +114 -0
  336. package/tools/providers/antigravity-transport.mjs +3 -1
  337. package/tools/providers/deepseek-harness-transport.mjs +360 -0
  338. package/tools/providers/omp-transport.mjs +194 -0
  339. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  340. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  341. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  342. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  343. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  344. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  345. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  346. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  347. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  348. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  349. package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
  350. package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
  351. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  352. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  353. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  354. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  355. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  356. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  357. package/tools/ralph-external/process-monitor.mjs +1017 -0
  358. package/tools/ralph-external/session-launcher.mjs +5057 -0
  359. package/tools/scripts/resource-preflight.mjs +541 -0
  360. package/tools/skills/deploy-skills-codex.mjs +2 -1
  361. 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
- let mcp = false;
51
- let provider;
52
- let noRepair = false;
53
- let profile;
54
- let persist = false;
55
- for (let i = 0; i < args.length; i++) {
56
- const a = args[i];
57
- if (a === 'mcp') {
58
- mcp = true;
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
- const result = spawnSync(process.execPath, [process.argv[1], 'doctor'], { stdio: 'inherit', cwd });
107
- return (result.status ?? 1) === 0;
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
- const syncResult = spawnSync(process.execPath, [process.argv[1], 'sync'], { stdio: 'inherit', cwd });
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
- const result = spawnSync(process.execPath, [process.argv[1], 'mcp', 'inject', '--provider', provider], { stdio: 'inherit', cwd });
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
- const result = spawnSync(process.execPath, [process.argv[1], 'mcp', 'inject', '--provider', provider, '--profile', profileName], { stdio: 'inherit', cwd });
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
- console.warn(' WARN Profile inject failed — continuing without profile.');
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
- const result = spawnSync(process.execPath, [
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
- console.warn(' WARN Ephemeral profile inject failed — launching without profile MCP config.');
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
- return tmpPath;
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 — print context-aware guidance
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
- const result = launchWithProfile(profile);
246
- return { exitCode: result.status ?? 0 };
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: if we have an ephemeral MCP config, pass it via --mcp-config
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
- return { exitCode: result.status ?? 0 };
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
- const { mcp, provider: explicitProvider, noRepair, profile, persist } = parseSessionArgs(ctx.args);
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
- // For codex: set up the runtime home and write profile config (#892)
304
- if ((provider === 'codex' || provider === 'openai') && !persist) {
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 ensureRuntimeHome(profile);
314
- await writeProfileConfig(profile, resolvedServers);
315
- console.log(` Runtime home ready. Profile servers: ${resolvedServers.map((s) => s.name).join(', ') || '(none)'}`);
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
- console.warn(' Falling back to standard launch.');
646
+ return { exitCode: 1 };
321
647
  }
322
648
  }
323
649
  else {
324
- mcpConfigPath = injectMcpProfile(provider, profile, cwd, persist);
325
- mcpInjected = mcpConfigPath !== null || persist;
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