@webpresso/agent-kit 3.1.30 → 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 (257) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  17. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  18. package/dist/esm/audit/ci-test-perf.js +30 -0
  19. package/dist/esm/audit/command-surface.js +4 -2
  20. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  21. package/dist/esm/audit/host-substitution-risk.js +256 -0
  22. package/dist/esm/audit/registry.d.ts +4 -4
  23. package/dist/esm/audit/registry.js +8 -1
  24. package/dist/esm/audit/toolchain-isolation.js +1 -2
  25. package/dist/esm/audit/typescript-version.d.ts +2 -0
  26. package/dist/esm/audit/typescript-version.js +123 -0
  27. package/dist/esm/blueprint/core/parser.js +43 -1
  28. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  29. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  30. package/dist/esm/blueprint/core/validation/state.js +25 -14
  31. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  32. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  33. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  34. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  35. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  36. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  37. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  38. package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
  39. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  40. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  41. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  42. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  43. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  44. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  45. package/dist/esm/blueprint/trust/gates.js +20 -0
  46. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  47. package/dist/esm/blueprint/trust/promotion.js +62 -112
  48. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  49. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  50. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  51. package/dist/esm/blueprint/trust/validator.js +7 -1
  52. package/dist/esm/blueprint/utils/archive.js +27 -5
  53. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  54. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  55. package/dist/esm/build/cli-mcp-parity.js +10 -0
  56. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  57. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  58. package/dist/esm/cli/cli.d.ts +1 -1
  59. package/dist/esm/cli/cli.js +34 -0
  60. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  61. package/dist/esm/cli/commands/agent-launch.js +82 -4
  62. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  63. package/dist/esm/cli/commands/audit-core.js +8 -1
  64. package/dist/esm/cli/commands/audit.js +17 -5
  65. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  66. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  67. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  68. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  69. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  70. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  71. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  72. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  73. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  74. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  75. package/dist/esm/cli/commands/compile.js +5 -31
  76. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  77. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  78. package/dist/esm/cli/commands/doctor.js +5 -0
  79. package/dist/esm/cli/commands/hook.d.ts +5 -0
  80. package/dist/esm/cli/commands/hook.js +15 -9
  81. package/dist/esm/cli/commands/init/config.js +2 -2
  82. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  83. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  84. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  85. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  86. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  87. package/dist/esm/cli/commands/init/convergence.js +57 -0
  88. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  89. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  90. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  91. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  92. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  93. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  94. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  95. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  96. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  97. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  98. package/dist/esm/cli/commands/init/index.js +326 -173
  99. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  100. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  101. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  102. package/dist/esm/cli/commands/init/merge.js +30 -1
  103. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  104. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  105. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  106. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  107. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  108. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  109. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  110. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  111. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  112. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  113. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  114. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  115. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  116. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  117. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  118. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  119. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  120. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  121. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  122. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  123. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  124. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  125. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  126. package/dist/esm/cli/commands/public-readiness.js +42 -0
  127. package/dist/esm/cli/commands/review.d.ts +35 -3
  128. package/dist/esm/cli/commands/review.js +729 -209
  129. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  130. package/dist/esm/cli/commands/run-wait.js +122 -0
  131. package/dist/esm/cli/commands/setup-help.js +1 -1
  132. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  133. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  134. package/dist/esm/cli/direct-provider-launch.js +9 -5
  135. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  136. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  137. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  138. package/dist/esm/daemon/domains/git-actions.js +136 -0
  139. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  140. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  141. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  142. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  143. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  144. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  145. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  146. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  147. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  148. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  149. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  150. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  151. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  152. package/dist/esm/daemon/handlers/product.js +10 -119
  153. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  154. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  155. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  156. package/dist/esm/daemon/handlers/shared.js +16 -0
  157. package/dist/esm/daemon/methods.d.ts +8 -1
  158. package/dist/esm/daemon/methods.js +7 -0
  159. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  160. package/dist/esm/daemon/protocol/contract.js +26 -0
  161. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  162. package/dist/esm/daemon/protocol/index.js +1 -1
  163. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  164. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  165. package/dist/esm/hooks/conformance/batch.js +5 -0
  166. package/dist/esm/hooks/doctor.d.ts +3 -3
  167. package/dist/esm/hooks/doctor.js +30 -15
  168. package/dist/esm/hooks/permission-request/index.js +9 -3
  169. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  170. package/dist/esm/hooks/permission-request/policy.js +13 -0
  171. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  172. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  173. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  174. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  175. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  176. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  177. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  178. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  179. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  180. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  181. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  182. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  183. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  184. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  185. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  186. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  187. package/dist/esm/hooks/shared/types.d.ts +8 -5
  188. package/dist/esm/hooks/shared/types.js +23 -8
  189. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  190. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  191. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  192. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  193. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  194. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  195. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  196. package/dist/esm/mcp/blueprint/registration.js +3 -2
  197. package/dist/esm/mcp/server.d.ts +1 -0
  198. package/dist/esm/mcp/server.js +27 -0
  199. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  200. package/dist/esm/mcp/tools/_names.js +5 -0
  201. package/dist/esm/mcp/tools/_registry.js +10 -0
  202. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  203. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  204. package/dist/esm/mcp/tools/audit.d.ts +4 -1
  205. package/dist/esm/mcp/tools/audit.js +30 -0
  206. package/dist/esm/mcp/tools/audits.d.ts +3 -0
  207. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  208. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  209. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  210. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  211. package/dist/esm/mcp/tools/review-run.js +216 -0
  212. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  213. package/dist/esm/mcp/tools/run-wait.js +593 -0
  214. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  215. package/dist/esm/mcp/tools/session-id.js +45 -0
  216. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  217. package/dist/esm/mcp/tools/session-info.js +49 -0
  218. package/dist/esm/output-transforms/audit.d.ts +23 -0
  219. package/dist/esm/output-transforms/audit.js +47 -0
  220. package/dist/esm/output-transforms/index.js +2 -0
  221. package/dist/esm/review/authority.js +13 -5
  222. package/dist/esm/review/availability.d.ts +22 -0
  223. package/dist/esm/review/availability.js +107 -20
  224. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  225. package/dist/esm/review/delivery-verifier.js +115 -0
  226. package/dist/esm/review/events.d.ts +3 -2
  227. package/dist/esm/review/events.js +23 -3
  228. package/dist/esm/review/execution/adapters.js +26 -9
  229. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  230. package/dist/esm/review/execution/output-policy.js +12 -0
  231. package/dist/esm/review/execution/review-checkout.js +87 -6
  232. package/dist/esm/review/execution/supervisor.js +5 -0
  233. package/dist/esm/review/execution/types.d.ts +8 -0
  234. package/dist/esm/review/opencode-account.d.ts +24 -0
  235. package/dist/esm/review/opencode-account.js +68 -0
  236. package/dist/esm/review/opencode-auth.d.ts +33 -0
  237. package/dist/esm/review/opencode-auth.js +79 -0
  238. package/dist/esm/review/subject.d.ts +12 -0
  239. package/dist/esm/review/subject.js +74 -22
  240. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  241. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  242. package/dist/esm/status/snapshot.js +30 -19
  243. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  244. package/dist/esm/test/duration-sequencer.js +54 -0
  245. package/dist/esm/test/shard-durations.json +922 -0
  246. package/dist/esm/test/worker-budget.d.ts +6 -0
  247. package/dist/esm/test/worker-budget.js +59 -1
  248. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  249. package/dist/esm/test-helpers/global-setup.js +68 -24
  250. package/dist/esm/ultragoal/service.d.ts +7 -0
  251. package/dist/esm/ultragoal/service.js +42 -2
  252. package/dist/esm/utils/write-json-file.js +3 -1
  253. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  254. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  255. package/dist/esm/worktrees/registry.d.ts +8 -0
  256. package/dist/esm/worktrees/registry.js +18 -3
  257. package/package.json +16 -13
@@ -0,0 +1,2 @@
1
+ export declare const RUN_WAIT_HELP = "wp run wait \u2014 read-only GitHub workflow-run waiter (push/dispatch runs, e.g. production deploys on main)\n\nUsage:\n wp run wait --run-id <id> [options]\n wp run wait --sha <head-sha> [--workflow <file-or-name>] [options]\n\nSelector (exactly one required):\n --run-id <id> Watch a specific workflow run by database id\n --sha <sha> Watch the newest run for a head commit SHA\n --workflow <file|name> Filter --sha selection to one workflow\n\nOptions:\n --until <target> run-terminal (default) | all-jobs-terminal\n --poll-interval-ms <n> Poll interval (default 15000)\n --timeout-ms <n> Overall watch deadline (max 300000, default 120000)\n --max-output-bytes <n> Per-command output budget\n --cwd <path> Project root or descendant\n --json Emit exactly one JSON document (default output)\n -h, --help Show this help\n\nRead-only: issues only `gh` GET calls; never mutates run state.";
2
+ export declare function runRunWaitCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,122 @@
1
+ import { RUN_WAIT_UNTIL_VALUES, runRunWait, } from "#mcp/tools/run-wait.js";
2
+ // `wp run wait` is intercepted in cli.ts before the package-manager `run`
3
+ // facade (which maps `wp run <script>` to `vp run <script>`). This module owns
4
+ // only the `wait` subcommand and keeps arg parsing dependency-free so the
5
+ // interception path stays cheap.
6
+ export const RUN_WAIT_HELP = `wp run wait — read-only GitHub workflow-run waiter (push/dispatch runs, e.g. production deploys on main)
7
+
8
+ Usage:
9
+ wp run wait --run-id <id> [options]
10
+ wp run wait --sha <head-sha> [--workflow <file-or-name>] [options]
11
+
12
+ Selector (exactly one required):
13
+ --run-id <id> Watch a specific workflow run by database id
14
+ --sha <sha> Watch the newest run for a head commit SHA
15
+ --workflow <file|name> Filter --sha selection to one workflow
16
+
17
+ Options:
18
+ --until <target> run-terminal (default) | all-jobs-terminal
19
+ --poll-interval-ms <n> Poll interval (default 15000)
20
+ --timeout-ms <n> Overall watch deadline (max 300000, default 120000)
21
+ --max-output-bytes <n> Per-command output budget
22
+ --cwd <path> Project root or descendant
23
+ --json Emit exactly one JSON document (default output)
24
+ -h, --help Show this help
25
+
26
+ Read-only: issues only \`gh\` GET calls; never mutates run state.`;
27
+ function parsePositiveInteger(value, name) {
28
+ if (value === undefined)
29
+ return undefined;
30
+ const parsed = Number.parseInt(value, 10);
31
+ if (!Number.isFinite(parsed) || parsed <= 0) {
32
+ throw new Error(`${name} must be a positive integer`);
33
+ }
34
+ return parsed;
35
+ }
36
+ function normalizeUntil(value) {
37
+ if (value === undefined)
38
+ return undefined;
39
+ const normalized = value.replaceAll("-", "_");
40
+ if (!RUN_WAIT_UNTIL_VALUES.includes(normalized)) {
41
+ throw new Error(`Unknown run wait target: ${value}`);
42
+ }
43
+ return normalized;
44
+ }
45
+ const VALUE_FLAGS = new Set([
46
+ "--run-id",
47
+ "--sha",
48
+ "--workflow",
49
+ "--until",
50
+ "--poll-interval-ms",
51
+ "--timeout-ms",
52
+ "--max-output-bytes",
53
+ "--cwd",
54
+ ]);
55
+ function parseFlags(args) {
56
+ const values = {};
57
+ let help = false;
58
+ for (let index = 0; index < args.length; index += 1) {
59
+ const arg = args[index];
60
+ if (arg === undefined)
61
+ continue;
62
+ if (arg === "--help" || arg === "-h") {
63
+ help = true;
64
+ continue;
65
+ }
66
+ if (arg === "--json")
67
+ continue; // JSON is the only output shape
68
+ const eq = arg.indexOf("=");
69
+ if (eq !== -1 && arg.startsWith("--")) {
70
+ const key = arg.slice(0, eq);
71
+ if (VALUE_FLAGS.has(key)) {
72
+ values[key] = arg.slice(eq + 1);
73
+ continue;
74
+ }
75
+ }
76
+ if (VALUE_FLAGS.has(arg)) {
77
+ const next = args[index + 1];
78
+ if (next === undefined)
79
+ throw new Error(`${arg} requires a value`);
80
+ values[arg] = next;
81
+ index += 1;
82
+ continue;
83
+ }
84
+ throw new Error(`Unknown run wait option: ${arg}`);
85
+ }
86
+ return { values, help };
87
+ }
88
+ export async function runRunWaitCommand(args) {
89
+ const { values, help } = parseFlags(args);
90
+ if (help) {
91
+ console.log(RUN_WAIT_HELP);
92
+ return 0;
93
+ }
94
+ const runId = values["--run-id"];
95
+ const input = {
96
+ sha: values["--sha"],
97
+ runId: runId === undefined ? undefined : runId,
98
+ workflow: values["--workflow"],
99
+ until: normalizeUntil(values["--until"]),
100
+ pollIntervalMs: parsePositiveInteger(values["--poll-interval-ms"], "--poll-interval-ms"),
101
+ timeoutMs: parsePositiveInteger(values["--timeout-ms"], "--timeout-ms"),
102
+ maxOutputBytes: parsePositiveInteger(values["--max-output-bytes"], "--max-output-bytes"),
103
+ cwd: values["--cwd"],
104
+ };
105
+ let payload;
106
+ try {
107
+ payload = await runRunWait(input);
108
+ }
109
+ catch (error) {
110
+ const issues = error.issues;
111
+ if (Array.isArray(issues)) {
112
+ const message = issues
113
+ .map((issue) => (typeof issue.message === "string" ? issue.message : String(issue.message)))
114
+ .join("; ");
115
+ console.error(`wp run wait: ${message}`);
116
+ return 1;
117
+ }
118
+ throw error;
119
+ }
120
+ console.log(JSON.stringify(payload, null, 2));
121
+ return payload.status === "success" ? 0 : 1;
122
+ }
@@ -8,7 +8,7 @@ export function getSetupHelpText(version) {
8
8
  "Options:",
9
9
  " --with <skills> Comma-separated opt-in skills and/or presets to install (non-interactive). Presets: example-skill, lore-commits, playwright-mcp, rtk, vision plus the special token `all`. Legacy compatibility presets are intentionally omitted; use wp install oh-my opencode when you want WP-owned refreshes. Opt-in skills are listed by 'wp skill list'. ",
10
10
  " --without <skills> Comma-separated opt-in skills and/or presets to opt out of for this run. ",
11
- " --host <hosts> Comma-separated host targets: codex, claude, opencode, all, none ",
11
+ " --host <hosts> Comma-separated host targets: codex, claude, opencode, grok, all, none ",
12
12
  " --dry-run Show what would change without writing anything ",
13
13
  " --cwd <dir> Working tree to scaffold into (default: process.cwd()) ",
14
14
  " -h, --help Display this message ",
@@ -5,7 +5,7 @@
5
5
  const PRIMARY_SETUP_OPTIONS = [
6
6
  " --with <skills> Comma-separated opt-in skills and/or presets to install (non-interactive). Presets: example-skill, lore-commits, playwright-mcp, rtk, vision plus the special token `all`. Legacy compatibility presets are intentionally omitted; use wp install oh-my opencode when you want WP-owned refreshes. Opt-in skills are listed by 'wp skill list'. ",
7
7
  " --without <skills> Comma-separated opt-in skills and/or presets to opt out of for this run. ",
8
- " --host <hosts> Comma-separated host targets: codex, claude, opencode, all, none ",
8
+ " --host <hosts> Comma-separated host targets: codex, claude, opencode, grok, all, none ",
9
9
  " --prune Widen stale agent-kit plugin-cache pruning from the default host set (claude, codex, opencode) to every supported host while retaining recent versions ",
10
10
  " --dry-run Show what would change without writing anything ",
11
11
  " --cwd <dir> Working tree to scaffold into (default: process.cwd()) ",
@@ -5,6 +5,7 @@ import { execFileSync, spawnSync } from "node:child_process";
5
5
  import { existsSync, mkdirSync } from "node:fs";
6
6
  import { basename, dirname, join, relative, resolve } from "node:path";
7
7
  import { findGitRoot } from "#cli/commands/init/detect-consumer.js";
8
+ import { removeCodexProjectTrust, removeCodexProjectTrusts, } from "#worktrees/codex-project-trust.js";
8
9
  import { shortId } from "#shared-utils/short-id.js";
9
10
  import { readConfig } from "#cli/commands/init/config";
10
11
  import { getProjectRoot } from "#cli/utils";
@@ -15,7 +16,7 @@ import { hydrateWorktreeDependencies, reconcileWorktreeDependencies, } from "#wo
15
16
  import { projectWorktreeAgentSurfaces } from "#worktrees/project-agent-surfaces.js";
16
17
  import { assertNotLocalMainBranch } from "#worktrees/main-ownership.js";
17
18
  import { buildWorktreeInventory, canonicalizeWorktreePath, classifyMembership, isManagedWorktreePath, resolveRepoIdentity, } from "#worktrees/identity.js";
18
- import { findRegistryCandidatesByPath, pruneStaleWorktreeRegistryEntries, readWorktreeRegistry, removeWorktreeRegistryEntries, repoScopedPathPredicate, upsertWorktreeRegistryEntry, } from "#worktrees/registry.js";
19
+ import { findRegistryCandidatesByPath, planStaleWorktreeRegistryPrune, readWorktreeRegistry, removeWorktreeRegistryEntries, repoScopedPathPredicate, upsertWorktreeRegistryEntry, } from "#worktrees/registry.js";
19
20
  function parseDepsOption(value) {
20
21
  if (value === undefined || value === null || value === "")
21
22
  return undefined;
@@ -443,7 +444,7 @@ function handleRefresh(opts) {
443
444
  }
444
445
  console.log(`Refreshed ${updated} managed worktree entr${updated === 1 ? "y" : "ies"} for ${repoRoot}.`);
445
446
  }
446
- function handlePrune(opts) {
447
+ async function handlePrune(opts) {
447
448
  if (!opts.all)
448
449
  throw new Error("Usage: wp worktree prune --all");
449
450
  // Repository-scoped, three-state prune: only THIS repo's entries that are
@@ -451,10 +452,12 @@ function handlePrune(opts) {
451
452
  // other-repo, and live-legacy entries are retained.
452
453
  const repoRoot = resolveWorktreeRepoRoot(opts.repo ?? opts.cwd ?? process.cwd());
453
454
  const inventory = buildWorktreeInventory(repoRoot);
454
- const result = pruneStaleWorktreeRegistryEntries({
455
+ const result = planStaleWorktreeRegistryPrune({
455
456
  inventory,
456
457
  scope: { authoritativeRepoKey: resolveRepoIdentity(repoRoot) },
457
458
  });
459
+ await removeCodexProjectTrusts(result.removed.map((entry) => entry.path));
460
+ result.commit();
458
461
  console.log(`Pruned ${result.removed.length} stale managed registry entr${result.removed.length === 1 ? "y" : "ies"}.`);
459
462
  for (const warning of result.warnings)
460
463
  console.log(`warning: ${warning}`);
@@ -527,7 +530,7 @@ function handleRebind(args, opts) {
527
530
  console.log(` worktree_owner_branch: ${binding.branch}`);
528
531
  console.log(` path: ${binding.path}`);
529
532
  }
530
- function handleRemove(nameOrPath, opts) {
533
+ async function handleRemove(nameOrPath, opts) {
531
534
  const cwd = opts.cwd ?? process.cwd();
532
535
  const callerRoot = resolveWorktreeRepoRoot(cwd);
533
536
  const entries = listEntries(callerRoot);
@@ -548,6 +551,7 @@ function handleRemove(nameOrPath, opts) {
548
551
  if (result.status !== 0) {
549
552
  throw new Error("git worktree remove failed");
550
553
  }
554
+ await removeCodexProjectTrust(resolved);
551
555
  removeWorktreeRegistryEntries(repoScopedPathPredicate(resolved, { authoritativeRepoKey: resolveRepoIdentity(repoRoot) }));
552
556
  }
553
557
  function isDirty(path) {
@@ -563,7 +567,7 @@ function currentBranch(repoRoot) {
563
567
  });
564
568
  return result.status === 0 ? String(result.stdout ?? "").trim() : "";
565
569
  }
566
- function handleMergeCleanup(nameOrPath, opts) {
570
+ async function handleMergeCleanup(nameOrPath, opts) {
567
571
  const cwd = opts.cwd ?? process.cwd();
568
572
  const callerRoot = resolveWorktreeRepoRoot(cwd);
569
573
  const entries = listEntries(callerRoot);
@@ -628,6 +632,7 @@ function handleMergeCleanup(nameOrPath, opts) {
628
632
  if (removeResult.status !== 0) {
629
633
  throw new Error("git worktree remove failed");
630
634
  }
635
+ await removeCodexProjectTrust(resolved);
631
636
  removeWorktreeRegistryEntries(repoScopedPathPredicate(resolved, { authoritativeRepoKey: resolveRepoIdentity(repoRoot) }));
632
637
  const fetchResult = spawnSync("git", [...plan.fetchArgs], { cwd: repoRoot, stdio: "inherit" });
633
638
  if (fetchResult.status !== 0) {
@@ -665,7 +670,7 @@ export async function executeWorktreeSubcommand(subcommand, args, opts) {
665
670
  handleRefresh(opts);
666
671
  return;
667
672
  case "prune":
668
- handlePrune(opts);
673
+ await handlePrune(opts);
669
674
  return;
670
675
  case "migrate":
671
676
  handleMigrate(opts);
@@ -682,7 +687,7 @@ export async function executeWorktreeSubcommand(subcommand, args, opts) {
682
687
  if (!nameOrPath) {
683
688
  throw new Error("Usage: wp worktree remove <branch-or-path> [--force]");
684
689
  }
685
- handleRemove(nameOrPath, opts);
690
+ await handleRemove(nameOrPath, opts);
686
691
  return;
687
692
  }
688
693
  case "merge-cleanup": {
@@ -690,7 +695,7 @@ export async function executeWorktreeSubcommand(subcommand, args, opts) {
690
695
  if (!nameOrPath) {
691
696
  throw new Error("Usage: wp worktree merge-cleanup <branch-or-path> [--base <ref>] [--dry-run]");
692
697
  }
693
- handleMergeCleanup(nameOrPath, opts);
698
+ await handleMergeCleanup(nameOrPath, opts);
694
699
  return;
695
700
  }
696
701
  default: {
@@ -79,8 +79,8 @@ function validateDirectProviderLaunch(provider, cwd) {
79
79
  ? `Run \`${installCommand}\` and retry.`
80
80
  : `Install ${ownershipName} and retry.`);
81
81
  }
82
- // External PATH hosts (Grok) do not run optional-tool freshness — skip vp.
83
- const vpCommand = resolveGlobalCapableVpCommand();
82
+ // External PATH hosts (Grok) do not run optional-tool freshness.
83
+ const vpCommand = adapter ? resolveGlobalCapableVpCommand() : null;
84
84
  if (adapter && !vpCommand) {
85
85
  throw new DirectProviderLaunchPreparationError("No global-capable Vite+ runner found.", "Reinstall @webpresso/agent-kit and retry.");
86
86
  }
@@ -105,10 +105,14 @@ function ensureCodexSessionTooling(repoRoot) {
105
105
  throw new DirectProviderLaunchPreparationError(`Codex project MCP server ${project.server} is unavailable: ${project.reason}`, "Run `wp setup --yes-global` from this repo to provision project MCP servers, then retry.");
106
106
  }
107
107
  }
108
+ const PROVIDER_SESSION_PREFLIGHTS = {
109
+ claude: () => undefined,
110
+ codex: ensureCodexSessionTooling,
111
+ grok: () => undefined,
112
+ opencode: () => undefined,
113
+ };
108
114
  function ensureProviderSessionTooling(provider, cwd) {
109
- if (provider !== "codex")
110
- return;
111
- ensureCodexSessionTooling(cwd);
115
+ PROVIDER_SESSION_PREFLIGHTS[provider](cwd);
112
116
  }
113
117
  export function isBareInteractiveProviderLaunch(args) {
114
118
  return parseDashPermissionMode(args).args.length === 0;
@@ -15,6 +15,16 @@ export interface OptionalToolFreshnessCache {
15
15
  readonly version: 1;
16
16
  readonly tools: Partial<Record<ManagedToolName, OptionalToolFreshnessRecord>>;
17
17
  }
18
+ export interface OptionalToolRefreshLeaseRecord {
19
+ readonly pid: number | null;
20
+ readonly startedAt: number;
21
+ readonly heartbeatAt: number;
22
+ readonly expiresAt: number;
23
+ }
24
+ export interface OptionalToolRefreshLeaseCache {
25
+ readonly version: 1;
26
+ readonly tools: Partial<Record<ManagedToolName, OptionalToolRefreshLeaseRecord>>;
27
+ }
18
28
  export type OptionalToolFreshnessMode = "force" | "if-needed";
19
29
  export type OptionalToolFreshnessResult = {
20
30
  readonly kind: "missing";
@@ -66,9 +76,22 @@ export interface OptionalToolFreshnessDeps {
66
76
  }) => SpawnSyncReturns<string>;
67
77
  readonly readCache?: () => OptionalToolFreshnessCache;
68
78
  readonly writeCache?: (cache: OptionalToolFreshnessCache) => void;
79
+ readonly readLeaseCache?: () => OptionalToolRefreshLeaseCache;
80
+ readonly writeLeaseCache?: (cache: OptionalToolRefreshLeaseCache) => void;
81
+ readonly clearLease?: (tool: ManagedToolName) => void;
82
+ readonly spawnDetached?: (command: string, args: readonly string[], options: {
83
+ readonly cwd: string;
84
+ }) => {
85
+ readonly pid?: number | undefined;
86
+ readonly unref?: () => void;
87
+ };
69
88
  }
70
89
  export declare function readOptionalToolFreshnessCache(path?: string): OptionalToolFreshnessCache;
71
90
  export declare function writeOptionalToolFreshnessCache(cache: OptionalToolFreshnessCache, path?: string): void;
91
+ export declare function readOptionalToolRefreshLeaseCache(path?: string): OptionalToolRefreshLeaseCache;
92
+ export declare function writeOptionalToolRefreshLeaseCache(cache: OptionalToolRefreshLeaseCache, path?: string): void;
93
+ export declare function clearOptionalToolRefreshLease(tool: ManagedToolName, path?: string): void;
94
+ export declare function optionalToolRefreshLeaseRecord(pid: number | null, now: number): OptionalToolRefreshLeaseRecord;
72
95
  export declare function supportsOptionalToolFreshness(adapter: OptionalToolAdapter): boolean;
73
96
  /**
74
97
  * Resolve a WP-managed optional-tool adapter for a dash provider.
@@ -78,4 +101,8 @@ export declare function tryResolveProviderToolAdapter(provider: DashProvider): O
78
101
  export declare function resolveProviderToolAdapter(provider: DashProvider): OptionalToolAdapter;
79
102
  export declare function refreshOptionalToolSync(input: OptionalToolFreshnessInput, deps?: OptionalToolFreshnessDeps): OptionalToolFreshnessResult;
80
103
  export declare function ensureOptionalToolFreshnessForLaunch(input: Omit<OptionalToolFreshnessInput, "mode">, deps?: OptionalToolFreshnessDeps): Promise<OptionalToolFreshnessResult>;
104
+ export declare function runOptionalToolRefreshWorker(args: readonly string[], deps?: OptionalToolFreshnessDeps & {
105
+ readonly readOwnershipState?: () => ToolingOwnershipState;
106
+ readonly resolveVpCommand?: () => GlobalCapableVpCommandInput | null;
107
+ }): number;
81
108
  export {};
@@ -1,16 +1,19 @@
1
- import { mkdirSync, existsSync } from "node:fs";
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { mkdirSync, existsSync, rmSync } from "node:fs";
2
3
  import { dirname } from "node:path";
3
- import { spawnSync } from "node:child_process";
4
4
  import { UPDATE_CHECK_INTERVAL_MS } from "#cli/auto-update/cache.js";
5
+ import { buildSelfInvocationCommand } from "#cli/auto-update/self-invocation.js";
5
6
  import { compareVersions } from "#cli/auto-update/version.js";
6
- import { appendGlobalCapableVpArgs } from "#cli/global-vp.js";
7
- import { optionalToolCanonicalCommand, resolveOptionalTool, } from "#cli/optional-tools.js";
8
- import { isUserOwnedTool, } from "#cli/tooling-ownership.js";
7
+ import { appendGlobalCapableVpArgs, resolveGlobalCapableVpCommand, } from "#cli/global-vp.js";
8
+ import { optionalToolCanonicalCommand, OPTIONAL_TOOL_ADAPTERS, resolveOptionalTool, } from "#cli/optional-tools.js";
9
+ import { isUserOwnedTool, readToolingOwnershipState, } from "#cli/tooling-ownership.js";
9
10
  import { getSurfacePath, withLock } from "#paths/state-root.js";
10
11
  import { readTrustedJsonFile } from "#shared-utils/read-json-file.js";
11
12
  import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
12
13
  const OPTIONAL_TOOL_FRESHNESS_CACHE = "optional-tool-freshness.json";
14
+ const OPTIONAL_TOOL_REFRESH_LEASE = "optional-tool-refresh-lease.json";
13
15
  const VERSION_RE = /v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/u;
16
+ const UNKNOWN_VERSION = "unknown";
14
17
  /**
15
18
  * Hot-path budget for version probes (`<tool> --version`) and registry
16
19
  * latest-version queries (`vp info <pkg>`). These run on the dash/launch turn
@@ -30,9 +33,15 @@ export const OPTIONAL_TOOL_UPDATE_TIMEOUT_MS = 600_000;
30
33
  function emptyCache() {
31
34
  return { version: 1, tools: {} };
32
35
  }
36
+ function emptyLeaseCache() {
37
+ return { version: 1, tools: {} };
38
+ }
33
39
  function cachePath() {
34
40
  return getSurfacePath(OPTIONAL_TOOL_FRESHNESS_CACHE, "user");
35
41
  }
42
+ function leasePath() {
43
+ return getSurfacePath(OPTIONAL_TOOL_REFRESH_LEASE, "user");
44
+ }
36
45
  export function readOptionalToolFreshnessCache(path = cachePath()) {
37
46
  if (!existsSync(path))
38
47
  return emptyCache();
@@ -61,6 +70,44 @@ export function writeOptionalToolFreshnessCache(cache, path = cachePath()) {
61
70
  mkdirSync(dirname(path), { recursive: true });
62
71
  writeJsonFileAtomic(path, cache);
63
72
  }
73
+ export function readOptionalToolRefreshLeaseCache(path = leasePath()) {
74
+ if (!existsSync(path))
75
+ return emptyLeaseCache();
76
+ try {
77
+ const parsed = readTrustedJsonFile(path);
78
+ if (parsed.version !== 1 || parsed.tools === null || typeof parsed.tools !== "object") {
79
+ return emptyLeaseCache();
80
+ }
81
+ return {
82
+ version: 1,
83
+ tools: Object.fromEntries(Object.entries(parsed.tools).filter((entry) => {
84
+ const record = entry[1];
85
+ return (record !== undefined &&
86
+ (typeof record.pid === "number" || record.pid === null) &&
87
+ typeof record.startedAt === "number" &&
88
+ typeof record.heartbeatAt === "number" &&
89
+ typeof record.expiresAt === "number");
90
+ })),
91
+ };
92
+ }
93
+ catch {
94
+ return emptyLeaseCache();
95
+ }
96
+ }
97
+ export function writeOptionalToolRefreshLeaseCache(cache, path = leasePath()) {
98
+ mkdirSync(dirname(path), { recursive: true });
99
+ writeJsonFileAtomic(path, cache);
100
+ }
101
+ export function clearOptionalToolRefreshLease(tool, path = leasePath()) {
102
+ const cache = readOptionalToolRefreshLeaseCache(path);
103
+ const tools = { ...cache.tools };
104
+ delete tools[tool];
105
+ if (Object.keys(tools).length === 0) {
106
+ rmSync(path, { force: true });
107
+ return;
108
+ }
109
+ writeOptionalToolRefreshLeaseCache({ version: 1, tools }, path);
110
+ }
64
111
  function nextCache(cache, tool, record) {
65
112
  return {
66
113
  version: 1,
@@ -70,6 +117,15 @@ function nextCache(cache, tool, record) {
70
117
  },
71
118
  };
72
119
  }
120
+ function nextLeaseCache(cache, tool, record) {
121
+ return {
122
+ version: 1,
123
+ tools: {
124
+ ...cache.tools,
125
+ [tool]: record,
126
+ },
127
+ };
128
+ }
73
129
  function readFreshRecord(cache, tool, observedVersion, now) {
74
130
  const record = cache.tools[tool];
75
131
  if (!record)
@@ -80,6 +136,14 @@ function readFreshRecord(cache, tool, observedVersion, now) {
80
136
  return null;
81
137
  return record;
82
138
  }
139
+ function readFreshLaunchRecord(cache, tool, now) {
140
+ const record = cache.tools[tool];
141
+ if (!record)
142
+ return null;
143
+ if (now - record.checkedAt > UPDATE_CHECK_INTERVAL_MS)
144
+ return null;
145
+ return record;
146
+ }
83
147
  function parseVersion(output) {
84
148
  const match = VERSION_RE.exec(output.trim());
85
149
  return match?.[0] ?? null;
@@ -142,6 +206,72 @@ function persistRecord(deps, adapter, record) {
142
206
  function cachedWarnings(record) {
143
207
  return record.status === "warning" && record.warning ? [record.warning] : [];
144
208
  }
209
+ function launchCacheResult(adapter, record, fromCache) {
210
+ return {
211
+ kind: "ready",
212
+ adapter,
213
+ observedVersion: record?.observedVersion ?? UNKNOWN_VERSION,
214
+ latestVersion: record?.latestVersion ?? null,
215
+ updated: false,
216
+ warnings: record ? cachedWarnings(record) : [],
217
+ fromCache,
218
+ };
219
+ }
220
+ function refreshLeaseIsActive(record, now) {
221
+ return record !== undefined && record.expiresAt > now;
222
+ }
223
+ export function optionalToolRefreshLeaseRecord(pid, now) {
224
+ return {
225
+ pid,
226
+ startedAt: now,
227
+ heartbeatAt: now,
228
+ // The worker may run a real package update with the foreground update
229
+ // deadline, so the launch lease must cover that blocking child.
230
+ expiresAt: now + OPTIONAL_TOOL_UPDATE_TIMEOUT_MS + 60_000,
231
+ };
232
+ }
233
+ function defaultSpawnDetached(command, args, options) {
234
+ const child = spawn(command, [...args], {
235
+ cwd: options.cwd,
236
+ detached: true,
237
+ env: process.env,
238
+ stdio: "ignore",
239
+ windowsHide: true,
240
+ });
241
+ child.unref();
242
+ return child;
243
+ }
244
+ function spawnOptionalToolRefreshWorker(input, deps, now) {
245
+ const readLease = deps.readLeaseCache ?? readOptionalToolRefreshLeaseCache;
246
+ const writeLease = deps.writeLeaseCache ?? writeOptionalToolRefreshLeaseCache;
247
+ const clearLease = deps.clearLease ?? clearOptionalToolRefreshLease;
248
+ const lease = readLease();
249
+ if (refreshLeaseIsActive(lease.tools[input.adapter.id], now))
250
+ return;
251
+ const command = buildSelfInvocationCommand([
252
+ "optional-tool-refresh",
253
+ input.adapter.id,
254
+ "--cwd",
255
+ input.cwd,
256
+ ]);
257
+ try {
258
+ writeLease(nextLeaseCache(lease, input.adapter.id, optionalToolRefreshLeaseRecord(null, now)));
259
+ const child = (deps.spawnDetached ?? defaultSpawnDetached)(command[0], command.slice(1), {
260
+ cwd: input.cwd,
261
+ });
262
+ child.unref?.();
263
+ }
264
+ catch (error) {
265
+ clearLease(input.adapter.id);
266
+ persistRecord(deps, input.adapter, {
267
+ checkedAt: now,
268
+ observedVersion: UNKNOWN_VERSION,
269
+ latestVersion: null,
270
+ status: "warning",
271
+ warning: `${input.adapter.ownershipName} optional refresh could not start; launching without freshness proof.${error instanceof Error ? ` ${error.message}.` : ""}`,
272
+ });
273
+ }
274
+ }
145
275
  export function supportsOptionalToolFreshness(adapter) {
146
276
  return adapter.namespace === "base" && Boolean(adapter.packageName && adapter.executableName);
147
277
  }
@@ -312,8 +442,88 @@ export function refreshOptionalToolSync(input, deps = {}) {
312
442
  };
313
443
  }
314
444
  export async function ensureOptionalToolFreshnessForLaunch(input, deps = {}) {
315
- return withLock("user", () => refreshOptionalToolSync({
316
- ...input,
317
- mode: "if-needed",
318
- }, deps));
445
+ if (!isUserOwnedTool(input.ownershipState, input.adapter.id)) {
446
+ return {
447
+ kind: "unmanaged",
448
+ adapter: input.adapter,
449
+ observedVersion: UNKNOWN_VERSION,
450
+ warning: `${input.adapter.ownershipName} is installed but not WP-owned. Run \`${optionalToolCanonicalCommand(input.adapter)}\` to let WP keep it current.`,
451
+ };
452
+ }
453
+ return withLock("user", () => {
454
+ const now = deps.now?.() ?? Date.now();
455
+ const readCache = deps.readCache ?? readOptionalToolFreshnessCache;
456
+ const cache = readCache();
457
+ const fresh = readFreshLaunchRecord(cache, input.adapter.id, now);
458
+ if (fresh)
459
+ return launchCacheResult(input.adapter, fresh, true);
460
+ const stale = cache.tools[input.adapter.id] ?? null;
461
+ spawnOptionalToolRefreshWorker(input, deps, now);
462
+ return launchCacheResult(input.adapter, stale, stale !== null);
463
+ });
464
+ }
465
+ export function runOptionalToolRefreshWorker(args, deps = {}) {
466
+ const [tool, ...rest] = args;
467
+ const adapter = OPTIONAL_TOOL_ADAPTERS.find((candidate) => candidate.id === tool);
468
+ if (!adapter || !supportsOptionalToolFreshness(adapter)) {
469
+ console.error(`optional-tool-refresh: unsupported tool ${tool ?? "<missing>"}`);
470
+ return 2;
471
+ }
472
+ let cwd = process.cwd();
473
+ for (let index = 0; index < rest.length; index += 1) {
474
+ const arg = rest[index];
475
+ if (arg === "--cwd") {
476
+ const value = rest[index + 1];
477
+ if (!value) {
478
+ console.error("optional-tool-refresh: --cwd requires a value");
479
+ return 2;
480
+ }
481
+ cwd = value;
482
+ index += 1;
483
+ continue;
484
+ }
485
+ console.error(`optional-tool-refresh: unsupported argument ${arg}`);
486
+ return 2;
487
+ }
488
+ const now = deps.now?.() ?? Date.now();
489
+ const writeLease = deps.writeLeaseCache ?? writeOptionalToolRefreshLeaseCache;
490
+ const readLease = deps.readLeaseCache ?? readOptionalToolRefreshLeaseCache;
491
+ const clearLease = deps.clearLease ?? clearOptionalToolRefreshLease;
492
+ writeLease(nextLeaseCache(readLease(), adapter.id, optionalToolRefreshLeaseRecord(typeof process.pid === "number" ? process.pid : null, now)));
493
+ try {
494
+ const vpCommand = deps.resolveVpCommand !== undefined
495
+ ? deps.resolveVpCommand()
496
+ : resolveGlobalCapableVpCommand();
497
+ if (!vpCommand) {
498
+ persistRecord(deps, adapter, {
499
+ checkedAt: now,
500
+ observedVersion: UNKNOWN_VERSION,
501
+ latestVersion: null,
502
+ status: "warning",
503
+ warning: `${adapter.ownershipName} optional refresh skipped because no global-capable Vite+ runner was found.`,
504
+ });
505
+ return 0;
506
+ }
507
+ const result = refreshOptionalToolSync({
508
+ adapter,
509
+ mode: "if-needed",
510
+ ownershipState: deps.readOwnershipState?.() ?? readToolingOwnershipState(),
511
+ vpCommand,
512
+ cwd,
513
+ }, deps);
514
+ if (result.kind === "missing") {
515
+ persistRecord(deps, adapter, {
516
+ checkedAt: now,
517
+ observedVersion: UNKNOWN_VERSION,
518
+ latestVersion: null,
519
+ status: "warning",
520
+ warning: result.warning,
521
+ });
522
+ return 0;
523
+ }
524
+ return 0;
525
+ }
526
+ finally {
527
+ clearLease(adapter.id);
528
+ }
319
529
  }
@@ -1,7 +1,16 @@
1
1
  /**
2
2
  * Daemon-side git helpers for review panel RPCs (Task 1.5).
3
3
  * Runs real git via child_process — never re-implements porcelain.
4
+ *
5
+ * `gitCreatePr` additionally spawns `gh` (same class of utility-binary spawn
6
+ * as the `git` calls above — see Trust Dossier Material Decision MD1 on the
7
+ * desktop-daemon-rpc-completion blueprint overview for why this does not
8
+ * cross the daemon's "no agent-child spawn" invariant). It adapts
9
+ * the gh-wrapping logic from `src/mcp/tools/pr-upsert.ts` (preflight,
10
+ * collision detection, canonical re-fetch, output redaction) to this
11
+ * module's synchronous-git / injectable-deps pattern.
4
12
  */
13
+ import { type RunOutcome } from "#mcp/tools/_shared/run-command.js";
5
14
  export type DiffFile = {
6
15
  readonly path: string;
7
16
  readonly status: string;
@@ -16,3 +25,23 @@ export declare function gitCommit(cwd: string, message: string, paths?: readonly
16
25
  }): {
17
26
  readonly commitSha: string;
18
27
  };
28
+ export type CreatePrResult = {
29
+ readonly url: string;
30
+ readonly number?: number;
31
+ };
32
+ export interface GitCreatePrDeps {
33
+ /** Resolve the current branch name; throws on detached HEAD. Injectable for tests. */
34
+ readonly currentBranch?: (cwd: string) => string;
35
+ /** Invoke `gh` with args in `cwd`. Injectable for tests (no real `gh` binary required). */
36
+ readonly gh?: (args: readonly string[], cwd: string) => Promise<RunOutcome>;
37
+ }
38
+ /**
39
+ * Upsert semantics (the frozen daemon contract has no create/update `mode`
40
+ * field, see Trust Dossier MD2): edit the PR for the current branch if one
41
+ * already exists, else create it against the repo's default branch. Every
42
+ * success path re-fetches the canonical PR view rather than trusting
43
+ * `gh pr create`'s stdout alone (matches `src/mcp/tools/pr-upsert.ts`'s
44
+ * canonical-refetch pattern) so a create-collision (two callers racing)
45
+ * still returns the real PR instead of throwing.
46
+ */
47
+ export declare function gitCreatePr(cwd: string, title: string, body: string | undefined, deps?: GitCreatePrDeps): Promise<CreatePrResult>;