@webpresso/agent-kit 3.1.28 → 3.2.0

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.

Potentially problematic release.


This version of @webpresso/agent-kit might be problematic. Click here for more details.

Files changed (329) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  3. package/catalog/AGENTS.md.tpl +6 -6
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  6. package/catalog/agent/rules/ci-test-perf.md +5 -1
  7. package/catalog/agent/rules/cmd-execution.md +9 -0
  8. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  9. package/catalog/agent/rules/pre-implementation.md +30 -2
  10. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  11. package/catalog/agent/rules/test-scan-perf.md +80 -0
  12. package/catalog/agent/rules/typescript-version.md +50 -0
  13. package/catalog/agent/rules/workflow-skills-routing.md +4 -2
  14. package/catalog/agent/skills/codex/SKILL.md +25 -1
  15. package/catalog/agent/skills/fix/SKILL.md +3 -0
  16. package/catalog/agent/skills/verify/SKILL.md +22 -2
  17. package/catalog/docs/templates/blueprint.md +1 -1
  18. package/dist/esm/audit/agents.js +1 -0
  19. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  20. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  21. package/dist/esm/audit/blueprint-pr-coverage.js +72 -7
  22. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  23. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  24. package/dist/esm/audit/ci-test-perf.js +30 -0
  25. package/dist/esm/audit/command-surface.js +4 -2
  26. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  27. package/dist/esm/audit/host-substitution-risk.js +256 -0
  28. package/dist/esm/audit/registry.d.ts +4 -4
  29. package/dist/esm/audit/registry.js +11 -1
  30. package/dist/esm/audit/roadmap-links.js +6 -0
  31. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  32. package/dist/esm/audit/test-scan-perf.js +275 -0
  33. package/dist/esm/audit/toolchain-isolation.js +1 -2
  34. package/dist/esm/audit/typescript-version.d.ts +2 -0
  35. package/dist/esm/audit/typescript-version.js +123 -0
  36. package/dist/esm/blueprint/core/parser.js +43 -1
  37. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  38. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  39. package/dist/esm/blueprint/core/validation/state.js +25 -14
  40. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  41. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  42. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  43. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  44. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  45. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  46. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  47. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  48. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  49. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  50. package/dist/esm/blueprint/lifecycle/review-provenance.js +118 -12
  51. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  52. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  53. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  54. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  55. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  56. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  57. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  58. package/dist/esm/blueprint/trust/gates.js +20 -0
  59. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  60. package/dist/esm/blueprint/trust/promotion.js +62 -112
  61. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  62. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  63. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  64. package/dist/esm/blueprint/trust/validator.js +7 -1
  65. package/dist/esm/blueprint/utils/archive.js +27 -5
  66. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  67. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  68. package/dist/esm/build/cli-mcp-parity.js +10 -0
  69. package/dist/esm/ci/act-replay.js +110 -7
  70. package/dist/esm/ci/act-runner.d.ts +4 -0
  71. package/dist/esm/ci/act-runner.js +85 -26
  72. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  73. package/dist/esm/ci/act-worktree-git.js +219 -0
  74. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  75. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  76. package/dist/esm/cli/cli.d.ts +1 -1
  77. package/dist/esm/cli/cli.js +34 -0
  78. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  79. package/dist/esm/cli/commands/agent-launch.js +82 -4
  80. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  81. package/dist/esm/cli/commands/audit-core.js +8 -1
  82. package/dist/esm/cli/commands/audit.js +20 -5
  83. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  84. package/dist/esm/cli/commands/blueprint/mutations.js +83 -47
  85. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  86. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  87. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  88. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  89. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  90. package/dist/esm/cli/commands/blueprint/router.js +40 -3
  91. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  92. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  93. package/dist/esm/cli/commands/ci.d.ts +8 -1
  94. package/dist/esm/cli/commands/ci.js +80 -21
  95. package/dist/esm/cli/commands/compile.js +5 -31
  96. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  97. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  98. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  99. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  100. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  101. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  102. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  103. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  104. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  105. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  106. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  107. package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
  108. package/dist/esm/cli/commands/dash/plan-once.js +41 -4
  109. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  110. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  111. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  112. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  113. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  114. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  115. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  116. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  117. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  118. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  119. package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
  120. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  121. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  122. package/dist/esm/cli/commands/doctor.js +5 -0
  123. package/dist/esm/cli/commands/format.js +0 -2
  124. package/dist/esm/cli/commands/hook.d.ts +5 -0
  125. package/dist/esm/cli/commands/hook.js +15 -9
  126. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  127. package/dist/esm/cli/commands/init/config.js +3 -6
  128. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  129. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  130. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  131. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  132. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  133. package/dist/esm/cli/commands/init/convergence.js +57 -0
  134. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  135. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  136. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  137. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  138. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  139. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  140. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  141. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  142. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  143. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  144. package/dist/esm/cli/commands/init/index.js +326 -173
  145. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  146. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  147. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  148. package/dist/esm/cli/commands/init/merge.js +30 -1
  149. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  150. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  151. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  152. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  153. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  154. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  155. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  156. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  157. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  158. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  159. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  160. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  161. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  162. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  163. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  164. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  165. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  166. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +297 -173
  167. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  168. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  169. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  170. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  171. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  172. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  173. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  174. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  175. package/dist/esm/cli/commands/logs.d.ts +15 -0
  176. package/dist/esm/cli/commands/logs.js +192 -3
  177. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  178. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  179. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  180. package/dist/esm/cli/commands/public-readiness.js +42 -0
  181. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  182. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  183. package/dist/esm/cli/commands/review.d.ts +35 -3
  184. package/dist/esm/cli/commands/review.js +734 -217
  185. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  186. package/dist/esm/cli/commands/run-wait.js +122 -0
  187. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  188. package/dist/esm/cli/commands/secrets.js +4 -1
  189. package/dist/esm/cli/commands/setup-help.js +1 -1
  190. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  191. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  192. package/dist/esm/cli/direct-provider-launch.js +31 -3
  193. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  194. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  195. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  196. package/dist/esm/daemon/domains/git-actions.js +136 -0
  197. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  198. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  199. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  200. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  201. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  202. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  203. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  204. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  205. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  206. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  207. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  208. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  209. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  210. package/dist/esm/daemon/handlers/product.js +10 -119
  211. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  212. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  213. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  214. package/dist/esm/daemon/handlers/shared.js +16 -0
  215. package/dist/esm/daemon/methods.d.ts +8 -1
  216. package/dist/esm/daemon/methods.js +7 -0
  217. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  218. package/dist/esm/daemon/protocol/contract.js +26 -0
  219. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  220. package/dist/esm/daemon/protocol/index.js +1 -1
  221. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  222. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  223. package/dist/esm/hooks/conformance/batch.js +5 -0
  224. package/dist/esm/hooks/doctor.d.ts +15 -0
  225. package/dist/esm/hooks/doctor.js +79 -8
  226. package/dist/esm/hooks/permission-request/index.js +9 -3
  227. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  228. package/dist/esm/hooks/permission-request/policy.js +13 -0
  229. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  230. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  231. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  232. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  233. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  234. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  235. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  236. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +58 -4
  237. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  238. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  239. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  240. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  241. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  242. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  243. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  244. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  245. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  246. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  247. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  248. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  249. package/dist/esm/hooks/shared/types.d.ts +8 -5
  250. package/dist/esm/hooks/shared/types.js +23 -8
  251. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  252. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  253. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  254. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  255. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  256. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  257. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  258. package/dist/esm/mcp/blueprint/registration.js +3 -2
  259. package/dist/esm/mcp/server.d.ts +1 -0
  260. package/dist/esm/mcp/server.js +27 -0
  261. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  262. package/dist/esm/mcp/tools/_names.js +5 -0
  263. package/dist/esm/mcp/tools/_registry.js +10 -0
  264. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  265. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  266. package/dist/esm/mcp/tools/audit.d.ts +5 -1
  267. package/dist/esm/mcp/tools/audit.js +40 -0
  268. package/dist/esm/mcp/tools/audits.d.ts +4 -0
  269. package/dist/esm/mcp/tools/ci-act.js +9 -11
  270. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  271. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  272. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  273. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  274. package/dist/esm/mcp/tools/review-run.js +216 -0
  275. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  276. package/dist/esm/mcp/tools/run-wait.js +593 -0
  277. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  278. package/dist/esm/mcp/tools/session-id.js +45 -0
  279. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  280. package/dist/esm/mcp/tools/session-info.js +49 -0
  281. package/dist/esm/output-transforms/audit.d.ts +23 -0
  282. package/dist/esm/output-transforms/audit.js +47 -0
  283. package/dist/esm/output-transforms/index.js +2 -0
  284. package/dist/esm/paths/state-root.js +3 -0
  285. package/dist/esm/review/authority.js +30 -7
  286. package/dist/esm/review/availability.d.ts +22 -0
  287. package/dist/esm/review/availability.js +123 -23
  288. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  289. package/dist/esm/review/delivery-verifier.js +115 -0
  290. package/dist/esm/review/events.d.ts +3 -2
  291. package/dist/esm/review/events.js +23 -3
  292. package/dist/esm/review/execution/adapters.js +26 -9
  293. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  294. package/dist/esm/review/execution/output-policy.js +12 -0
  295. package/dist/esm/review/execution/review-checkout.js +87 -6
  296. package/dist/esm/review/execution/supervisor.js +5 -0
  297. package/dist/esm/review/execution/types.d.ts +8 -0
  298. package/dist/esm/review/opencode-account.d.ts +24 -0
  299. package/dist/esm/review/opencode-account.js +68 -0
  300. package/dist/esm/review/opencode-auth.d.ts +33 -0
  301. package/dist/esm/review/opencode-auth.js +79 -0
  302. package/dist/esm/review/subject.d.ts +27 -0
  303. package/dist/esm/review/subject.js +179 -32
  304. package/dist/esm/runtime/executor.d.ts +1 -0
  305. package/dist/esm/runtime/executor.js +2 -1
  306. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  307. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  308. package/dist/esm/secret-gate/runner.d.ts +1 -0
  309. package/dist/esm/secret-gate/runner.js +4 -1
  310. package/dist/esm/status/snapshot.js +30 -19
  311. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  312. package/dist/esm/test/duration-sequencer.js +54 -0
  313. package/dist/esm/test/shard-durations.json +922 -0
  314. package/dist/esm/test/worker-budget.d.ts +6 -0
  315. package/dist/esm/test/worker-budget.js +59 -1
  316. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  317. package/dist/esm/test-helpers/global-setup.js +68 -24
  318. package/dist/esm/ultragoal/service.d.ts +7 -0
  319. package/dist/esm/ultragoal/service.js +42 -2
  320. package/dist/esm/utils/write-json-file.js +3 -1
  321. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  322. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  323. package/dist/esm/worktrees/current.d.ts +1 -0
  324. package/dist/esm/worktrees/current.js +1 -0
  325. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  326. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  327. package/dist/esm/worktrees/registry.d.ts +8 -0
  328. package/dist/esm/worktrees/registry.js +18 -3
  329. package/package.json +18 -15
@@ -18,14 +18,17 @@
18
18
  * provisioned, exact-version binaries with environment-variable names
19
19
  * forwarded through Codex's stdio `env_vars` allow-list.
20
20
  */
21
- import { execFileSync, spawnSync } from "node:child_process";
22
- import { accessSync, constants, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
21
+ import { spawnSync } from "node:child_process";
22
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
  import { dirname, join } from "node:path";
25
- import { fileURLToPath } from "node:url";
25
+ import { agentKitMcpLaunchCommand, findWebpressoMcpEntry, resolveExecutableOnPath, resolveWebpressoMcpEntry, WEBPRESSO_MCP_SERVER_NAME, WEBPRESSO_MCP_SPEC, } from "#cli/commands/init/mcp-spec.js";
26
+ // Re-exported so existing importers (tests) keep a stable path; the canonical
27
+ // implementation now lives in `mcp-spec.ts`.
28
+ export { findWebpressoMcpEntry, WEBPRESSO_MCP_SERVER_NAME };
29
+ import { updateScaffolderTextFile } from "#cli/commands/init/scaffolders/file-transaction.js";
26
30
  import { isJsonRecord, isUnsafeRuntimeLauncher, parseJson, parseProjectMcpServers, parseTomlString, } from "#hooks/host-config-contract.js";
27
31
  import { appendGlobalCapableVpArgs, resolveGlobalCapableVpCommand, } from "#cli/global-vp.js";
28
- import { pathCandidates } from "#runtime/command-exists.js";
29
32
  import { writeFileAtomic } from "#shared-utils/write-json-file.js";
30
33
  export const PLAYWRIGHT_MCP_SERVER_NAME = "playwright";
31
34
  export const PLAYWRIGHT_MCP_VERSION = "0.0.78";
@@ -41,6 +44,9 @@ const PLAYWRIGHT_STDIO_MCP_DEFINITION = {
41
44
  command: "vp",
42
45
  args: ["dlx", PLAYWRIGHT_MCP_PACKAGE, ...PLAYWRIGHT_MCP_ARGS],
43
46
  };
47
+ function updateCodexConfig(configPath, transform) {
48
+ return updateScaffolderTextFile(configPath, transform);
49
+ }
44
50
  function tomlString(value) {
45
51
  return JSON.stringify(value);
46
52
  }
@@ -66,15 +72,254 @@ export function commandMcpCodexBlock(definition) {
66
72
  if (definition.codexEnvVars && definition.codexEnvVars.length > 0) {
67
73
  lines.push(`env_vars = ${tomlStringArray(definition.codexEnvVars)}`);
68
74
  }
75
+ if (definition.codexEnv && Object.keys(definition.codexEnv).length > 0) {
76
+ const pairs = Object.entries(definition.codexEnv).map(([key, value]) => `${key} = ${tomlString(value)}`);
77
+ lines.push(`env = { ${pairs.join(", ")} }`);
78
+ }
69
79
  lines.push("enabled = true");
70
80
  for (const [key, value] of Object.entries(definition.codexFields ?? {})) {
71
81
  lines.push(`${key} = ${tomlValue(value)}`);
72
82
  }
73
83
  return `${lines.join("\n")}\n`;
74
84
  }
85
+ function parseTomlTableHeader(line) {
86
+ const match = line.match(/^\s*\[\s*([\s\S]*?)\s*\]\s*(?:#.*)?$/u);
87
+ if (!match || line.trimStart().startsWith("[["))
88
+ return null;
89
+ return parseTomlDottedKey(match[1] ?? "");
90
+ }
91
+ function parseTomlArrayTableHeader(line) {
92
+ const match = line.match(/^\s*\[\[\s*([\s\S]*?)\s*\]\]\s*(?:#.*)?$/u);
93
+ if (!match)
94
+ return null;
95
+ return parseTomlDottedKey(match[1] ?? "");
96
+ }
97
+ function parseTomlDottedKey(raw) {
98
+ const segments = [];
99
+ let i = 0;
100
+ const skipSpace = () => {
101
+ while (/\s/u.test(raw[i] ?? ""))
102
+ i += 1;
103
+ };
104
+ while (i < raw.length) {
105
+ skipSpace();
106
+ const segment = parseTomlKeySegment(raw, i);
107
+ if (!segment)
108
+ return null;
109
+ segments.push(segment.value);
110
+ i = segment.next;
111
+ skipSpace();
112
+ if (i >= raw.length)
113
+ break;
114
+ if (raw[i] !== ".")
115
+ return null;
116
+ i += 1;
117
+ }
118
+ return segments.length > 0 ? segments : null;
119
+ }
120
+ function parseTomlKeySegment(raw, start) {
121
+ const first = raw[start];
122
+ if (first === "'") {
123
+ const end = raw.indexOf("'", start + 1);
124
+ if (end === -1)
125
+ return null;
126
+ return { value: raw.slice(start + 1, end), next: end + 1 };
127
+ }
128
+ if (first === '"') {
129
+ return parseBasicTomlKeySegment(raw, start);
130
+ }
131
+ let end = start;
132
+ while (/[A-Za-z0-9_-]/u.test(raw[end] ?? ""))
133
+ end += 1;
134
+ if (end === start)
135
+ return null;
136
+ return { value: raw.slice(start, end), next: end };
137
+ }
138
+ function parseBasicTomlKeySegment(raw, start) {
139
+ let value = "";
140
+ for (let i = start + 1; i < raw.length; i += 1) {
141
+ const ch = raw[i];
142
+ if (ch === '"')
143
+ return { value, next: i + 1 };
144
+ if (ch !== "\\") {
145
+ value += ch;
146
+ continue;
147
+ }
148
+ const escaped = raw[i + 1];
149
+ if (escaped === undefined)
150
+ return null;
151
+ const simple = {
152
+ b: "\b",
153
+ t: "\t",
154
+ n: "\n",
155
+ f: "\f",
156
+ r: "\r",
157
+ '"': '"',
158
+ "\\": "\\",
159
+ };
160
+ if (escaped in simple) {
161
+ value += simple[escaped];
162
+ i += 1;
163
+ continue;
164
+ }
165
+ if (escaped === "u" || escaped === "U") {
166
+ const width = escaped === "u" ? 4 : 8;
167
+ const hex = raw.slice(i + 2, i + 2 + width);
168
+ if (!new RegExp(`^[0-9A-Fa-f]{${width}}$`, "u").test(hex))
169
+ return null;
170
+ value += String.fromCodePoint(Number.parseInt(hex, 16));
171
+ i += 1 + width;
172
+ continue;
173
+ }
174
+ return null;
175
+ }
176
+ return null;
177
+ }
178
+ function isServerTableHeader(line, serverId) {
179
+ const segments = parseTomlTableHeader(line);
180
+ return segments?.length === 2 && segments[0] === "mcp_servers" && segments[1] === serverId;
181
+ }
75
182
  function isOwnedSubTableHeader(line, serverId) {
76
- const trimmed = line.trim();
77
- return trimmed.startsWith(`[mcp_servers.${serverId}.`) && trimmed.endsWith("]");
183
+ const segments = parseTomlTableHeader(line);
184
+ return (segments !== null &&
185
+ segments.length > 2 &&
186
+ segments[0] === "mcp_servers" &&
187
+ segments[1] === serverId);
188
+ }
189
+ function isOwnedServerOrDescendantTableHeader(line, serverId) {
190
+ const segments = parseTomlArrayTableHeader(line) ?? parseTomlTableHeader(line);
191
+ return (segments !== null &&
192
+ segments.length >= 2 &&
193
+ segments[0] === "mcp_servers" &&
194
+ segments[1] === serverId);
195
+ }
196
+ function tomlLinesStartingInsideMultilineString(lines) {
197
+ const insideAtStart = [];
198
+ let delimiter = null;
199
+ for (const line of lines) {
200
+ insideAtStart.push(delimiter !== null);
201
+ for (let i = 0; i < line.length; i += 1) {
202
+ if (delimiter !== null) {
203
+ const end = findTomlMultilineStringEnd(line, i, delimiter);
204
+ if (end === -1)
205
+ break;
206
+ i = end + delimiter.length - 1;
207
+ delimiter = null;
208
+ continue;
209
+ }
210
+ const ch = line[i];
211
+ if (ch === "#")
212
+ break;
213
+ if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
214
+ delimiter = line.startsWith('"""', i) ? '"""' : "'''";
215
+ i += delimiter.length - 1;
216
+ continue;
217
+ }
218
+ if (ch === '"') {
219
+ i += 1;
220
+ while (i < line.length) {
221
+ const inner = line[i];
222
+ if (inner === "\\") {
223
+ i += 2;
224
+ continue;
225
+ }
226
+ if (inner === '"')
227
+ break;
228
+ i += 1;
229
+ }
230
+ continue;
231
+ }
232
+ if (ch === "'") {
233
+ const end = line.indexOf("'", i + 1);
234
+ if (end === -1)
235
+ break;
236
+ i = end;
237
+ }
238
+ }
239
+ }
240
+ return insideAtStart;
241
+ }
242
+ function findTomlMultilineStringEnd(line, start, delimiter) {
243
+ if (delimiter === "'''")
244
+ return line.indexOf(delimiter, start);
245
+ for (let i = start; i < line.length; i += 1) {
246
+ if (!line.startsWith(delimiter, i))
247
+ continue;
248
+ let backslashes = 0;
249
+ for (let j = i - 1; j >= 0 && line[j] === "\\"; j -= 1)
250
+ backslashes += 1;
251
+ if (backslashes % 2 === 0)
252
+ return i;
253
+ }
254
+ return -1;
255
+ }
256
+ function tomlAssignmentKey(line) {
257
+ let quote = null;
258
+ let escaped = false;
259
+ for (let i = 0; i < line.length; i += 1) {
260
+ const ch = line[i];
261
+ if (quote !== null) {
262
+ if (quote === '"' && escaped) {
263
+ escaped = false;
264
+ }
265
+ else if (quote === '"' && ch === "\\") {
266
+ escaped = true;
267
+ }
268
+ else if (ch === quote) {
269
+ quote = null;
270
+ }
271
+ continue;
272
+ }
273
+ if (ch === '"' || ch === "'") {
274
+ quote = ch;
275
+ continue;
276
+ }
277
+ if (ch === "#")
278
+ return null;
279
+ if (ch === "=")
280
+ return parseTomlDottedKey(line.slice(0, i).trim());
281
+ }
282
+ return null;
283
+ }
284
+ function hasDottedServerDefinition(lines, startsInsideMultiline, serverId) {
285
+ let currentTable = null;
286
+ return lines.some((line, index) => {
287
+ if (startsInsideMultiline[index])
288
+ return false;
289
+ const arrayTable = parseTomlArrayTableHeader(line);
290
+ if (arrayTable !== null &&
291
+ arrayTable.length >= 2 &&
292
+ arrayTable[0] === "mcp_servers" &&
293
+ arrayTable[1] === serverId) {
294
+ return true;
295
+ }
296
+ const table = parseTomlTableHeader(line);
297
+ if (table !== null) {
298
+ currentTable = table;
299
+ return false;
300
+ }
301
+ const segments = tomlAssignmentKey(line);
302
+ return (segments !== null &&
303
+ ((segments.length >= 2 && segments[0] === "mcp_servers" && segments[1] === serverId) ||
304
+ (currentTable?.length === 1 &&
305
+ currentTable[0] === "mcp_servers" &&
306
+ segments[0] === serverId)));
307
+ });
308
+ }
309
+ export function hasAmbiguousMcpServerDottedDefinition(raw, serverId) {
310
+ const lines = raw.trimEnd().split(/\r?\n/);
311
+ const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
312
+ const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
313
+ return start === -1 && hasDottedServerDefinition(lines, startsInsideMultiline, serverId);
314
+ }
315
+ export function hasManagedMcpServerTableConflict(raw, serverId) {
316
+ const lines = raw.trimEnd().split(/\r?\n/);
317
+ const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
318
+ const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
319
+ if (start === -1)
320
+ return hasDottedServerDefinition(lines, startsInsideMultiline, serverId);
321
+ const end = managedSectionEnd(lines, startsInsideMultiline, start, serverId);
322
+ return hasOwnedServerTableOutsideManagedSection(lines, startsInsideMultiline, start, end, serverId);
78
323
  }
79
324
  /**
80
325
  * Find the exclusive end index of the managed section region: the parent block
@@ -84,9 +329,11 @@ function isOwnedSubTableHeader(line, serverId) {
84
329
  * sub-tables under foreign or absent parents are never touched (they remain
85
330
  * doctor-report-only territory).
86
331
  */
87
- function managedSectionEnd(lines, start, serverId) {
332
+ function managedSectionEnd(lines, startsInsideMultiline, start, serverId) {
88
333
  let end = lines.length;
89
334
  for (let i = start + 1; i < lines.length; i += 1) {
335
+ if (startsInsideMultiline[i])
336
+ continue;
90
337
  if (!lines[i].trim().startsWith("["))
91
338
  continue;
92
339
  if (isOwnedSubTableHeader(lines[i], serverId))
@@ -94,20 +341,43 @@ function managedSectionEnd(lines, start, serverId) {
94
341
  end = i;
95
342
  break;
96
343
  }
344
+ // Trailing comment/blank lines between the managed block and the next table
345
+ // belong to the surrounding document (often annotating the *next* section) —
346
+ // leave them in place rather than swallowing them into the regenerated block.
347
+ while (end > start + 1) {
348
+ const previous = lines[end - 1].trim();
349
+ if (previous.length === 0 || previous.startsWith("#")) {
350
+ end -= 1;
351
+ continue;
352
+ }
353
+ break;
354
+ }
97
355
  return end;
98
356
  }
99
- function upsertMcpServerBlock(raw, serverId, block) {
100
- const header = commandMcpHeader(serverId);
357
+ function hasOwnedServerTableOutsideManagedSection(lines, startsInsideMultiline, start, end, serverId) {
358
+ return lines.some((line, index) => !startsInsideMultiline[index] &&
359
+ (index < start || index >= end) &&
360
+ isOwnedServerOrDescendantTableHeader(line, serverId));
361
+ }
362
+ export function upsertMcpServerBlock(raw, serverId, block) {
101
363
  const lines = raw.trimEnd().split(/\r?\n/);
102
364
  const hasContent = raw.trim().length > 0;
103
- const start = lines.findIndex((line) => line.trim() === header);
365
+ const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
366
+ const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
104
367
  if (start === -1) {
368
+ if (hasDottedServerDefinition(lines, startsInsideMultiline, serverId))
369
+ return raw;
105
370
  const prefix = hasContent ? `${raw.trimEnd()}\n\n` : "";
106
371
  return `${prefix}${block}`;
107
372
  }
108
- const end = managedSectionEnd(lines, start, serverId);
373
+ const end = managedSectionEnd(lines, startsInsideMultiline, start, serverId);
374
+ if (hasOwnedServerTableOutsideManagedSection(lines, startsInsideMultiline, start, end, serverId)) {
375
+ return raw;
376
+ }
109
377
  const replacement = block.trimEnd().split("\n");
110
- if (end < lines.length)
378
+ // Separate the block from a preserved tail — unless the tail already starts
379
+ // with its own blank line (kept by the trailing-comment walk-back).
380
+ if (end < lines.length && lines[end].trim().length !== 0)
111
381
  replacement.push("");
112
382
  return [...lines.slice(0, start), ...replacement, ...lines.slice(end)].join("\n") + "\n";
113
383
  }
@@ -124,31 +394,11 @@ export function commandMcpClaudeServer(definition) {
124
394
  }
125
395
  return server;
126
396
  }
127
- export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
128
397
  export const WEBPRESSO_MCP_HEADER = `[mcp_servers.${WEBPRESSO_MCP_SERVER_NAME}]`;
129
398
  function defaultConfigPath() {
130
399
  const codexHome = process.env.CODEX_HOME || join(process.env.HOME || homedir(), ".codex");
131
400
  return join(codexHome, "config.toml");
132
401
  }
133
- function resolvePinnedMcpExecutable(binary, env) {
134
- for (const candidate of pathCandidates(binary, {
135
- pathEnv: env.PATH,
136
- pathExtEnv: env.PATHEXT,
137
- })) {
138
- try {
139
- if (!statSync(candidate).isFile())
140
- continue;
141
- if (process.platform !== "win32")
142
- accessSync(candidate, constants.X_OK);
143
- return candidate;
144
- }
145
- catch {
146
- // Continue scanning PATH. Returning the candidate, not its realpath, keeps
147
- // the generated config anchored to Vite+'s stable global-bin symlink.
148
- }
149
- }
150
- return null;
151
- }
152
402
  function installedGlobalPackageMetadata(vpCommand, packageName, spawn) {
153
403
  const listCommand = appendGlobalCapableVpArgs(vpCommand, [
154
404
  "list",
@@ -198,7 +448,7 @@ function selectPackageBinary(definition, metadata) {
198
448
  function provisionPinnedCodexMcp(input, definition) {
199
449
  const env = input.env ?? process.env;
200
450
  const spawn = input.spawn ?? spawnSync;
201
- const resolveExecutable = input.resolveExecutable ?? ((binary) => resolvePinnedMcpExecutable(binary, env));
451
+ const resolveExecutable = input.resolveExecutable ?? ((binary) => resolveExecutableOnPath(binary, env));
202
452
  const vpCommand = input.resolveVpCommand !== undefined
203
453
  ? input.resolveVpCommand()
204
454
  : resolveGlobalCapableVpCommand(env.PATH ?? "", process.platform, env);
@@ -271,13 +521,8 @@ export function ensurePinnedCodexMcp(input, definition) {
271
521
  reason: provisioned.reason,
272
522
  };
273
523
  }
274
- const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
275
- const next = upsertCommandMcpServer(existing, provisioned.definition);
276
- if (next === existing)
277
- return { kind: "unchanged", path: configPath };
278
- mkdirSync(dirname(configPath), { recursive: true });
279
- writeFileAtomic(configPath, next, "utf8");
280
- return { kind: "written", path: configPath };
524
+ const result = updateCodexConfig(configPath, (existing) => upsertCommandMcpServer(existing, provisioned.definition));
525
+ return { kind: result === "written" ? "written" : "unchanged", path: configPath };
281
526
  }
282
527
  function removeUnsafePackageManagerMcpBlocks(raw) {
283
528
  const lines = raw.split(/\r?\n/u);
@@ -329,15 +574,11 @@ function removeUnsafePackageManagerMcpBlocks(raw) {
329
574
  export function ensureCodexProjectMcpServers(input) {
330
575
  const configPath = input.configPath ?? defaultConfigPath();
331
576
  const sourcePath = input.sourcePath ?? join(input.repoRoot, ".mcp.json");
332
- const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
333
- const withoutUnsafeLaunchers = removeUnsafePackageManagerMcpBlocks(existing);
334
- const cleanedUnsafeLaunchers = withoutUnsafeLaunchers !== existing;
335
577
  const persistSafetyCleanup = () => {
336
- if (!cleanedUnsafeLaunchers || input.options.dryRun)
578
+ if (input.options.dryRun)
337
579
  return undefined;
338
- mkdirSync(dirname(configPath), { recursive: true });
339
- writeFileAtomic(configPath, withoutUnsafeLaunchers, "utf8");
340
- return true;
580
+ const result = updateCodexConfig(configPath, removeUnsafePackageManagerMcpBlocks);
581
+ return result === "written" ? true : undefined;
341
582
  };
342
583
  const parsed = existsSync(sourcePath)
343
584
  ? parseProjectMcpServers(readFileSync(sourcePath, "utf8"))
@@ -368,12 +609,10 @@ export function ensureCodexProjectMcpServers(input) {
368
609
  }
369
610
  provisioned.push(result.definition);
370
611
  }
371
- const next = provisioned.reduce(upsertCommandMcpServer, withoutUnsafeLaunchers);
372
- if (next === existing) {
612
+ const result = updateCodexConfig(configPath, (existing) => provisioned.reduce(upsertCommandMcpServer, removeUnsafePackageManagerMcpBlocks(existing)));
613
+ if (result === "unchanged") {
373
614
  return { kind: "codex-project-mcp-unchanged", path: configPath, servers };
374
615
  }
375
- mkdirSync(dirname(configPath), { recursive: true });
376
- writeFileAtomic(configPath, next, "utf8");
377
616
  return { kind: "codex-project-mcp-written", path: configPath, servers };
378
617
  }
379
618
  function claudePlaywrightServer() {
@@ -421,130 +660,13 @@ export function ensureClaudePlaywrightMcp(input) {
421
660
  // ────────────────────────────────────────────────────────────────────────────
422
661
  // Agent-kit MCP server registration
423
662
  // ────────────────────────────────────────────────────────────────────────────
424
- const WP_BIN_RELATIVE = join("bin", process.platform === "win32" ? "wp.cmd" : "wp");
425
- const WP_LAUNCHER_REQUIRED_RELATIVES = [
426
- WP_BIN_RELATIVE,
427
- join("bin", "_run.js"),
428
- join("bin", "runtime-lanes.js"),
429
- join("bin", "runtime-manifest.json"),
430
- ];
431
- /**
432
- * Resolve the absolute path to webpresso's global `bin/wp` launcher on this machine. Probes
433
- * the locations consumers use to install webpresso, in order of stability:
434
- *
435
- * 1. the stable global `wp` shim available on PATH
436
- * 2. the currently executing `@webpresso/agent-kit` package
437
- * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
438
- * (path-stable; updated by Claude Code's plugin manager)
439
- * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
440
- * 5. pnpm global — `$(pnpm root -g)/@webpresso/agent-kit/`
441
- * 6. npm global — `$(npm root -g)/@webpresso/agent-kit/`
442
- *
443
- * Returns `null` when none of the candidates contain `bin/wp`. The caller
444
- * surfaces a clear error rather than writing a broken codex config.
445
- */
446
- export function findWebpressoMcpEntry(probe = {}) {
447
- return resolveWebpressoMcpEntry(probe).entryPath;
448
- }
449
- function isUsableWebpressoMcpRoot(root) {
450
- return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
451
- }
452
- function resolveWebpressoMcpEntry(probe = {}) {
453
- const checked = [];
454
- if (probe.candidates === undefined) {
455
- const stableEntry = probe.stableEntry !== undefined
456
- ? probe.stableEntry()
457
- : resolvePinnedMcpExecutable("wp", process.env);
458
- if (stableEntry)
459
- return { entryPath: stableEntry, checked: [stableEntry] };
460
- }
461
- const candidateGroups = probe.candidates !== undefined ? [probe.candidates] : [defaultFixedCandidates()];
462
- for (const candidates of candidateGroups) {
463
- for (const root of candidates) {
464
- if (!root)
465
- continue;
466
- checked.push(root);
467
- const wpBin = join(root, WP_BIN_RELATIVE);
468
- if (isUsableWebpressoMcpRoot(root))
469
- return { entryPath: wpBin, checked };
470
- }
471
- }
472
- if (probe.candidates === undefined) {
473
- for (const root of defaultPackageManagerCandidates(probe)) {
474
- if (!root)
475
- continue;
476
- checked.push(root);
477
- const wpBin = join(root, WP_BIN_RELATIVE);
478
- if (isUsableWebpressoMcpRoot(root))
479
- return { entryPath: wpBin, checked };
480
- }
481
- }
482
- return { entryPath: null, checked };
483
- }
484
- function defaultFixedCandidates() {
485
- const home = process.env.HOME || homedir();
486
- const packageRoot = currentAgentKitPackageRoot();
487
- const claudePlugin = join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit");
488
- const bunGlobal = join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit");
489
- return [packageRoot ?? "", claudePlugin, bunGlobal];
490
- }
491
- function defaultPackageManagerCandidates(probe) {
492
- const pnpmRoot = (probe.pnpmGlobalRoot ?? probePnpmGlobalRoot)();
493
- const npmRoot = (probe.npmGlobalRoot ?? probeNpmGlobalRoot)();
494
- return [
495
- pnpmRoot ? join(pnpmRoot, "@webpresso", "agent-kit") : "",
496
- npmRoot ? join(npmRoot, "@webpresso", "agent-kit") : "",
497
- ];
498
- }
499
- function currentAgentKitPackageRoot() {
500
- let current = dirname(fileURLToPath(import.meta.url));
501
- for (let depth = 0; depth < 12; depth += 1) {
502
- const packagePath = join(current, "package.json");
503
- if (existsSync(packagePath)) {
504
- try {
505
- const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
506
- if (pkg.name === "@webpresso/agent-kit")
507
- return current;
508
- }
509
- catch {
510
- return null;
511
- }
512
- }
513
- const parent = dirname(current);
514
- if (parent === current)
515
- break;
516
- current = parent;
517
- }
518
- return null;
519
- }
520
- function probePnpmGlobalRoot() {
521
- return runQuiet("pnpm", ["root", "-g"]);
522
- }
523
- function probeNpmGlobalRoot() {
524
- return runQuiet("npm", ["root", "-g"]);
525
- }
526
- function runQuiet(cmd, args) {
527
- try {
528
- const output = execFileSync(cmd, [...args], {
529
- encoding: "utf8",
530
- stdio: ["ignore", "pipe", "ignore"],
531
- });
532
- const trimmed = output.trim();
533
- return trimmed.length > 0 ? trimmed : null;
534
- }
535
- catch {
536
- return null;
537
- }
538
- }
539
- export function agentKitMcpLaunchCommand(entryPath) {
540
- return { command: entryPath, args: ["mcp"] };
541
- }
542
663
  export function agentKitMcpBlock(entryPath) {
543
664
  const launch = agentKitMcpLaunchCommand(entryPath);
544
665
  return commandMcpCodexBlock({
545
666
  id: WEBPRESSO_MCP_SERVER_NAME,
546
667
  command: launch.command,
547
668
  args: launch.args,
669
+ codexEnv: WEBPRESSO_MCP_SPEC.env,
548
670
  });
549
671
  }
550
672
  export function upsertWebpressoMcpServer(raw, entryPath) {
@@ -562,11 +684,13 @@ export function ensureCodexWebpressoMcp(input) {
562
684
  return { kind: "codex-webpresso-mcp-not-installed", path: configPath, checked };
563
685
  }
564
686
  const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
565
- const next = upsertWebpressoMcpServer(existing, entryPath);
566
- if (next === existing) {
687
+ if (hasAmbiguousMcpServerDottedDefinition(existing, WEBPRESSO_MCP_SERVER_NAME) ||
688
+ hasManagedMcpServerTableConflict(existing, WEBPRESSO_MCP_SERVER_NAME)) {
689
+ return { kind: "codex-webpresso-mcp-ambiguous", path: configPath, entryPath };
690
+ }
691
+ const result = updateCodexConfig(configPath, (raw) => upsertWebpressoMcpServer(raw, entryPath));
692
+ if (result === "unchanged") {
567
693
  return { kind: "codex-webpresso-mcp-unchanged", path: configPath, entryPath };
568
694
  }
569
- mkdirSync(dirname(configPath), { recursive: true });
570
- writeFileAtomic(configPath, next, "utf8");
571
695
  return { kind: "codex-webpresso-mcp-written", path: configPath, entryPath };
572
696
  }
@@ -0,0 +1,7 @@
1
+ /** Serialize a complete read/modify/write transaction for a host config file. */
2
+ export declare function withScaffolderFileLock<T>(path: string, action: () => T): T;
3
+ /**
4
+ * Serialize a text-file read/modify/write and install the replacement strictly
5
+ * atomically without weakening an existing file's permissions.
6
+ */
7
+ export declare function updateScaffolderTextFile(path: string, transform: (existing: string) => string): "written" | "unchanged";
@@ -0,0 +1,58 @@
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import lockfile from "proper-lockfile";
4
+ import { writeFileAtomicStrict } from "#shared-utils/write-json-file.js";
5
+ const LOCK_TIMEOUT_MS = 5_000;
6
+ const LOCK_POLL_MS = 25;
7
+ const LOCK_STALE_MS = 30_000;
8
+ function sleepSync(ms) {
9
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
10
+ }
11
+ /** Serialize a complete read/modify/write transaction for a host config file. */
12
+ export function withScaffolderFileLock(path, action) {
13
+ mkdirSync(dirname(path), { recursive: true });
14
+ const startedAt = Date.now();
15
+ let release;
16
+ while (!release) {
17
+ try {
18
+ release = lockfile.lockSync(path, {
19
+ realpath: false,
20
+ stale: LOCK_STALE_MS,
21
+ update: LOCK_STALE_MS / 2,
22
+ });
23
+ }
24
+ catch (error) {
25
+ if (error.code !== "ELOCKED")
26
+ throw error;
27
+ if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {
28
+ throw new Error(`Timed out waiting for host config lock: ${path}`, { cause: error });
29
+ }
30
+ sleepSync(LOCK_POLL_MS);
31
+ }
32
+ }
33
+ try {
34
+ return action();
35
+ }
36
+ finally {
37
+ release();
38
+ }
39
+ }
40
+ /**
41
+ * Serialize a text-file read/modify/write and install the replacement strictly
42
+ * atomically without weakening an existing file's permissions.
43
+ */
44
+ export function updateScaffolderTextFile(path, transform) {
45
+ return withScaffolderFileLock(path, () => {
46
+ const existed = existsSync(path);
47
+ if (existed && lstatSync(path).isSymbolicLink()) {
48
+ throw new Error(`Refusing to replace symlinked host config: ${path}`);
49
+ }
50
+ const existing = existed ? readFileSync(path, "utf8") : "";
51
+ const next = transform(existing);
52
+ if (next === existing)
53
+ return "unchanged";
54
+ const mode = existed ? lstatSync(path).mode & 0o777 : 0o600;
55
+ writeFileAtomicStrict(path, next, { encoding: "utf8", mode });
56
+ return "written";
57
+ });
58
+ }