@webpresso/agent-kit 3.1.30 → 3.3.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 (300) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +183 -20
  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/blueprint-trust.js +14 -10
  17. package/dist/esm/audit/changeset-evidence.d.ts +23 -0
  18. package/dist/esm/audit/changeset-evidence.js +30 -0
  19. package/dist/esm/audit/changeset-required.d.ts +10 -0
  20. package/dist/esm/audit/changeset-required.js +73 -0
  21. package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
  22. package/dist/esm/audit/ci-guardrails-detection.js +68 -0
  23. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  24. package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
  25. package/dist/esm/audit/ci-test-perf.js +30 -0
  26. package/dist/esm/audit/command-surface.js +4 -2
  27. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  28. package/dist/esm/audit/host-substitution-risk.js +256 -0
  29. package/dist/esm/audit/registry.d.ts +4 -4
  30. package/dist/esm/audit/registry.js +13 -1
  31. package/dist/esm/audit/toolchain-isolation.js +1 -2
  32. package/dist/esm/audit/typescript-version.d.ts +2 -0
  33. package/dist/esm/audit/typescript-version.js +123 -0
  34. package/dist/esm/blueprint/core/parser.js +101 -2
  35. package/dist/esm/blueprint/core/schema.d.ts +1 -0
  36. package/dist/esm/blueprint/core/schema.js +26 -0
  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 +87 -20
  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/local.d.ts +2 -1
  46. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  47. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  48. package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
  49. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
  50. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
  51. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  52. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  53. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  54. package/dist/esm/blueprint/sync/client.d.ts +2 -1
  55. package/dist/esm/blueprint/sync/client.js +1 -1
  56. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  57. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  58. package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
  59. package/dist/esm/blueprint/trust/dossier.js +40 -6
  60. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  61. package/dist/esm/blueprint/trust/gates.js +20 -0
  62. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  63. package/dist/esm/blueprint/trust/promotion.js +74 -112
  64. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  65. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  66. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  67. package/dist/esm/blueprint/trust/validator.js +7 -1
  68. package/dist/esm/blueprint/utils/archive.js +27 -5
  69. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  70. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  71. package/dist/esm/build/cli-mcp-parity.js +15 -0
  72. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  73. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  74. package/dist/esm/cli/cli.d.ts +1 -1
  75. package/dist/esm/cli/cli.js +43 -0
  76. package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
  77. package/dist/esm/cli/commands/agent-launch.js +93 -4
  78. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  79. package/dist/esm/cli/commands/audit-core.js +8 -1
  80. package/dist/esm/cli/commands/audit.js +21 -5
  81. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  82. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  83. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  84. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  85. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  86. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  87. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  88. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  89. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  90. package/dist/esm/cli/commands/ci-preflight.js +30 -6
  91. package/dist/esm/cli/commands/compile.js +5 -31
  92. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  93. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  94. package/dist/esm/cli/commands/doctor.js +5 -0
  95. package/dist/esm/cli/commands/hook.d.ts +5 -0
  96. package/dist/esm/cli/commands/hook.js +15 -9
  97. package/dist/esm/cli/commands/init/config.js +2 -2
  98. package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
  99. package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
  100. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  101. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  102. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  103. package/dist/esm/cli/commands/init/convergence.js +57 -0
  104. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  105. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  106. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  107. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  108. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  109. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  110. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  111. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  112. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  113. package/dist/esm/cli/commands/init/index.d.ts +3 -0
  114. package/dist/esm/cli/commands/init/index.js +326 -173
  115. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  116. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  117. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  118. package/dist/esm/cli/commands/init/merge.js +30 -1
  119. package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
  120. package/dist/esm/cli/commands/init/package-root.js +10 -2
  121. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  122. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  123. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  124. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  125. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
  126. package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
  127. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  128. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  129. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  130. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
  131. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  132. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  133. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
  134. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
  135. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  136. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  137. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  138. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  139. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  140. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  141. package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
  142. package/dist/esm/cli/commands/opencode-probe.js +323 -0
  143. package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
  144. package/dist/esm/cli/commands/opencode-rotate.js +147 -0
  145. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  146. package/dist/esm/cli/commands/public-readiness.js +42 -0
  147. package/dist/esm/cli/commands/quality-runner.js +3 -3
  148. package/dist/esm/cli/commands/review.d.ts +35 -3
  149. package/dist/esm/cli/commands/review.js +751 -209
  150. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  151. package/dist/esm/cli/commands/run-wait.js +122 -0
  152. package/dist/esm/cli/commands/rust-check.d.ts +24 -0
  153. package/dist/esm/cli/commands/rust-check.js +110 -0
  154. package/dist/esm/cli/commands/setup-help.js +1 -1
  155. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  156. package/dist/esm/cli/commands/typecheck.d.ts +1 -0
  157. package/dist/esm/cli/commands/typecheck.js +30 -1
  158. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
  159. package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
  160. package/dist/esm/cli/commands/worktree/router.js +10 -0
  161. package/dist/esm/cli/direct-provider-launch.js +9 -5
  162. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  163. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  164. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  165. package/dist/esm/daemon/domains/git-actions.js +136 -0
  166. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  167. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  168. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  169. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  170. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  171. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  172. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  173. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  174. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  175. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  176. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  177. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  178. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  179. package/dist/esm/daemon/handlers/product.js +10 -119
  180. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  181. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  182. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  183. package/dist/esm/daemon/handlers/shared.js +16 -0
  184. package/dist/esm/daemon/methods.d.ts +8 -1
  185. package/dist/esm/daemon/methods.js +7 -0
  186. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  187. package/dist/esm/daemon/protocol/contract.js +26 -0
  188. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  189. package/dist/esm/daemon/protocol/index.js +1 -1
  190. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  191. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  192. package/dist/esm/hooks/conformance/batch.js +5 -0
  193. package/dist/esm/hooks/doctor.d.ts +5 -3
  194. package/dist/esm/hooks/doctor.js +67 -16
  195. package/dist/esm/hooks/permission-request/index.js +9 -3
  196. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  197. package/dist/esm/hooks/permission-request/policy.js +13 -0
  198. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  199. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  200. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  201. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  202. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  203. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  204. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  205. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  206. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  207. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  208. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  209. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  210. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
  211. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  212. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  213. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  214. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  215. package/dist/esm/hooks/shared/types.d.ts +8 -5
  216. package/dist/esm/hooks/shared/types.js +50 -9
  217. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  218. package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
  219. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  220. package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
  221. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
  222. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  223. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  224. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  225. package/dist/esm/mcp/blueprint/registration.js +3 -2
  226. package/dist/esm/mcp/server.d.ts +1 -0
  227. package/dist/esm/mcp/server.js +27 -0
  228. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  229. package/dist/esm/mcp/tools/_names.js +5 -0
  230. package/dist/esm/mcp/tools/_registry.js +10 -0
  231. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  232. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  233. package/dist/esm/mcp/tools/audit.d.ts +5 -1
  234. package/dist/esm/mcp/tools/audit.js +42 -0
  235. package/dist/esm/mcp/tools/audits.d.ts +4 -0
  236. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  237. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  238. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  239. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  240. package/dist/esm/mcp/tools/review-run.js +216 -0
  241. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  242. package/dist/esm/mcp/tools/run-wait.js +593 -0
  243. package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
  244. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  245. package/dist/esm/mcp/tools/session-id.js +45 -0
  246. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  247. package/dist/esm/mcp/tools/session-info.js +49 -0
  248. package/dist/esm/output-transforms/audit.d.ts +23 -0
  249. package/dist/esm/output-transforms/audit.js +47 -0
  250. package/dist/esm/output-transforms/index.js +2 -0
  251. package/dist/esm/package.json +2 -0
  252. package/dist/esm/platform/client.d.ts +7 -1
  253. package/dist/esm/platform/github-contract-source.d.ts +2 -1
  254. package/dist/esm/pr-description/contract.js +24 -4
  255. package/dist/esm/review/authority.js +27 -14
  256. package/dist/esm/review/availability.d.ts +33 -0
  257. package/dist/esm/review/availability.js +122 -20
  258. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  259. package/dist/esm/review/delivery-verifier.js +115 -0
  260. package/dist/esm/review/events.d.ts +3 -2
  261. package/dist/esm/review/events.js +23 -3
  262. package/dist/esm/review/execution/adapters.d.ts +11 -1
  263. package/dist/esm/review/execution/adapters.js +36 -10
  264. package/dist/esm/review/execution/artifacts.d.ts +1 -1
  265. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  266. package/dist/esm/review/execution/output-policy.js +12 -0
  267. package/dist/esm/review/execution/review-checkout.js +121 -21
  268. package/dist/esm/review/execution/supervisor.js +36 -4
  269. package/dist/esm/review/execution/types.d.ts +9 -0
  270. package/dist/esm/review/opencode-account.d.ts +24 -0
  271. package/dist/esm/review/opencode-account.js +68 -0
  272. package/dist/esm/review/opencode-auth.d.ts +33 -0
  273. package/dist/esm/review/opencode-auth.js +79 -0
  274. package/dist/esm/review/subject.d.ts +25 -0
  275. package/dist/esm/review/subject.js +116 -26
  276. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  277. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  278. package/dist/esm/rust/affected-manifests.d.ts +38 -0
  279. package/dist/esm/rust/affected-manifests.js +69 -0
  280. package/dist/esm/session-memory/fetch-index.d.ts +1 -1
  281. package/dist/esm/status/snapshot.js +32 -25
  282. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  283. package/dist/esm/test/duration-sequencer.js +54 -0
  284. package/dist/esm/test/shard-durations.json +922 -0
  285. package/dist/esm/test/worker-budget.d.ts +6 -0
  286. package/dist/esm/test/worker-budget.js +59 -1
  287. package/dist/esm/test-helpers/global-setup.d.ts +1 -2
  288. package/dist/esm/test-helpers/global-setup.js +79 -32
  289. package/dist/esm/typecheck/planner.d.ts +9 -1
  290. package/dist/esm/typecheck/planner.js +31 -0
  291. package/dist/esm/ultragoal/service.d.ts +7 -0
  292. package/dist/esm/ultragoal/service.js +42 -2
  293. package/dist/esm/utils/write-json-file.js +3 -1
  294. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  295. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  296. package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
  297. package/dist/esm/worktrees/orphan-scan.js +211 -0
  298. package/dist/esm/worktrees/registry.d.ts +8 -0
  299. package/dist/esm/worktrees/registry.js +18 -3
  300. package/package.json +18 -13
@@ -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>;
@@ -1,8 +1,18 @@
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
  */
5
13
  import { execFileSync } from "node:child_process";
14
+ import { runCommand, isRunFailure } from "#mcp/tools/_shared/run-command.js";
15
+ import { redactText } from "#mcp/tools/_shared/redact.js";
6
16
  export function gitDiff(cwd) {
7
17
  const out = execFileSync("git", ["status", "--porcelain=v1", "-uall", "--no-renames"], {
8
18
  cwd,
@@ -45,3 +55,129 @@ export function gitCommit(cwd, message, paths, options) {
45
55
  }).trim();
46
56
  return { commitSha };
47
57
  }
58
+ /** Same default timeoutMs `src/mcp/tools/pr-upsert.ts` already uses for `gh` calls. */
59
+ const GH_TIMEOUT_MS = 120_000;
60
+ function defaultCurrentBranch(cwd) {
61
+ const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
62
+ cwd,
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "pipe"],
65
+ }).trim();
66
+ if (!branch || branch === "HEAD") {
67
+ throw new Error("git.createPr requires a named branch (HEAD is detached)");
68
+ }
69
+ return branch;
70
+ }
71
+ function defaultGh(args, cwd) {
72
+ return runCommand("gh", args, { cwd, timeoutMs: GH_TIMEOUT_MS });
73
+ }
74
+ function outcomeText(outcome) {
75
+ if (isRunFailure(outcome))
76
+ return outcome.error.message;
77
+ return [outcome.stdout, outcome.stderr].filter(Boolean).join("\n");
78
+ }
79
+ function outcomeOk(outcome) {
80
+ return !isRunFailure(outcome) && outcome.exitCode === 0;
81
+ }
82
+ function parseTarget(outcome) {
83
+ if (!outcomeOk(outcome))
84
+ return undefined;
85
+ try {
86
+ const parsed = JSON.parse(outcome.stdout);
87
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
88
+ return undefined;
89
+ const value = parsed;
90
+ return {
91
+ number: typeof value.number === "number" ? value.number : undefined,
92
+ url: typeof value.url === "string" ? value.url : undefined,
93
+ title: typeof value.title === "string" ? value.title : undefined,
94
+ state: typeof value.state === "string" ? value.state : undefined,
95
+ };
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
101
+ /** Mirrors `src/mcp/tools/pr-upsert.ts`'s `isNoPr` string-matching (do not reinvent). */
102
+ function isNoPr(outcome) {
103
+ if (outcomeOk(outcome))
104
+ return false;
105
+ const text = outcomeText(outcome).toLowerCase();
106
+ return (text.includes("no pull requests found") ||
107
+ text.includes("no pull request found") ||
108
+ text.includes("could not find pull request"));
109
+ }
110
+ /** Mirrors `src/mcp/tools/pr-upsert.ts`'s `isCreateCollision` string-matching (do not reinvent). */
111
+ function isCreateCollision(outcome) {
112
+ if (outcomeOk(outcome))
113
+ return false;
114
+ const text = outcomeText(outcome).toLowerCase();
115
+ return text.includes("already exists") || text.includes("a pull request already");
116
+ }
117
+ function throwGhFailure(action, outcome) {
118
+ const detail = redactText(outcomeText(outcome)) || "unknown error";
119
+ throw new Error(`git.createPr: gh ${action} failed: ${detail}`);
120
+ }
121
+ function requireUrl(target, context) {
122
+ if (!target.url) {
123
+ throw new Error(`git.createPr: ${context} did not return a PR url`);
124
+ }
125
+ return target.url;
126
+ }
127
+ function buildEditArgs(branch, title, body) {
128
+ const args = ["pr", "edit", branch, "--title", title];
129
+ if (body !== undefined)
130
+ args.push("--body", body);
131
+ return args;
132
+ }
133
+ function buildCreateArgs(branch, base, title, body) {
134
+ const args = ["pr", "create", "--head", branch, "--base", base, "--title", title];
135
+ if (body !== undefined)
136
+ args.push("--body", body);
137
+ return args;
138
+ }
139
+ /**
140
+ * Upsert semantics (the frozen daemon contract has no create/update `mode`
141
+ * field, see Trust Dossier MD2): edit the PR for the current branch if one
142
+ * already exists, else create it against the repo's default branch. Every
143
+ * success path re-fetches the canonical PR view rather than trusting
144
+ * `gh pr create`'s stdout alone (matches `src/mcp/tools/pr-upsert.ts`'s
145
+ * canonical-refetch pattern) so a create-collision (two callers racing)
146
+ * still returns the real PR instead of throwing.
147
+ */
148
+ export async function gitCreatePr(cwd, title, body, deps) {
149
+ const currentBranch = deps?.currentBranch ?? defaultCurrentBranch;
150
+ const gh = deps?.gh ?? defaultGh;
151
+ const branch = currentBranch(cwd);
152
+ const preflight = await gh(["pr", "view", branch, "--json", "number,url,title,state"], cwd);
153
+ if (outcomeOk(preflight)) {
154
+ const target = parseTarget(preflight);
155
+ if (!target)
156
+ throwGhFailure("pr view (preflight)", preflight);
157
+ const edit = await gh(buildEditArgs(branch, title, body), cwd);
158
+ if (!outcomeOk(edit))
159
+ throwGhFailure("pr edit", edit);
160
+ return { url: requireUrl(target, "pr view (preflight)"), number: target.number };
161
+ }
162
+ if (!isNoPr(preflight))
163
+ throwGhFailure("pr view (preflight)", preflight);
164
+ const repoView = await gh(["repo", "view", "--json", "defaultBranchRef", "-q", ".defaultBranchRef.name"], cwd);
165
+ if (!outcomeOk(repoView))
166
+ throwGhFailure("repo view (default branch)", repoView);
167
+ const base = repoView.stdout.trim();
168
+ if (!base) {
169
+ throw new Error("git.createPr: could not resolve the repository default branch");
170
+ }
171
+ const create = await gh(buildCreateArgs(branch, base, title, body), cwd);
172
+ if (!outcomeOk(create) && !isCreateCollision(create)) {
173
+ throwGhFailure("pr create", create);
174
+ }
175
+ const canonical = await gh(["pr", "view", branch, "--json", "number,url,title,state"], cwd);
176
+ const canonicalTarget = parseTarget(canonical);
177
+ if (!canonicalTarget)
178
+ throwGhFailure("pr view (canonical)", canonical);
179
+ return {
180
+ url: requireUrl(canonicalTarget, "pr view (canonical)"),
181
+ number: canonicalTarget.number,
182
+ };
183
+ }
@@ -2,6 +2,8 @@
2
2
  * Cross-process run-registry interop between wp dash and the desktop app (1.6).
3
3
  * Reuses the existing dash run-registry store paths — no second registry.
4
4
  */
5
+ import type { DashRun } from "#cli/commands/dash/run-types.js";
6
+ import type { SessionsListResult, SessionSummary } from "#daemon/protocol/contract.js";
5
7
  export type RunRegistrySnapshot = {
6
8
  readonly root: string;
7
9
  readonly runIds: readonly string[];
@@ -12,3 +14,17 @@ export type RunRegistrySnapshot = {
12
14
  * `root` should be the directory that contains per-run JSON sidecars.
13
15
  */
14
16
  export declare function listRunRegistry(root: string): RunRegistrySnapshot;
17
+ /** Pure mapping from a dash run to the daemon's UI-facing session shape. */
18
+ export declare function toSessionSummary(run: DashRun): SessionSummary;
19
+ export interface ListDaemonSessionsOptions {
20
+ readonly cwd?: string;
21
+ readonly limit?: number;
22
+ }
23
+ /**
24
+ * Daemon `sessions.list` backing implementation: reads the real dash
25
+ * run-registry (no second registry) and maps it to the daemon wire shape.
26
+ * Complexity: O(n log n) for the updatedAt-descending sort (n = dash runs at
27
+ * one repo root — small/bounded); no additional filesystem scans beyond the
28
+ * single locked registry read `readDashRuns` already performs.
29
+ */
30
+ export declare function listDaemonSessions(options?: ListDaemonSessionsOptions): Promise<SessionsListResult>;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { existsSync, readdirSync, readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
+ import { readDashRuns } from "#cli/commands/dash/run-registry.js";
7
8
  /**
8
9
  * List run ids under a dash-compatible registry root.
9
10
  * `root` should be the directory that contains per-run JSON sidecars.
@@ -27,3 +28,47 @@ export function listRunRegistry(root) {
27
28
  }
28
29
  return { root, runIds: runIds.sort(), readable: true };
29
30
  }
31
+ /**
32
+ * Daemon `providerIdSchema` (src/daemon/protocol/contract.ts) only accepts
33
+ * 'claude' | 'codex' | 'opencode' | 'fleet'. `DashRun.provider` can also be
34
+ * 'grok' (src/cli/commands/dash/provider-capabilities.ts DASH_PROVIDERS).
35
+ * `parseUiMethodResult` is `.strict()` and would throw on the whole response
36
+ * for one out-of-enum row, so an unrecognized provider is omitted (the field
37
+ * is optional on `SessionSummary`) rather than passed through or fabricated.
38
+ */
39
+ const KNOWN_DAEMON_PROVIDERS = new Set([
40
+ "claude",
41
+ "codex",
42
+ "opencode",
43
+ "fleet",
44
+ ]);
45
+ function isKnownDaemonProvider(provider) {
46
+ return KNOWN_DAEMON_PROVIDERS.has(provider);
47
+ }
48
+ /** Pure mapping from a dash run to the daemon's UI-facing session shape. */
49
+ export function toSessionSummary(run) {
50
+ return {
51
+ id: run.id,
52
+ ...(isKnownDaemonProvider(run.provider) ? { provider: run.provider } : {}),
53
+ label: run.label,
54
+ state: run.state,
55
+ cwd: run.cwd,
56
+ permissionMode: run.permissionMode,
57
+ updatedAt: run.updatedAt,
58
+ };
59
+ }
60
+ /**
61
+ * Daemon `sessions.list` backing implementation: reads the real dash
62
+ * run-registry (no second registry) and maps it to the daemon wire shape.
63
+ * Complexity: O(n log n) for the updatedAt-descending sort (n = dash runs at
64
+ * one repo root — small/bounded); no additional filesystem scans beyond the
65
+ * single locked registry read `readDashRuns` already performs.
66
+ */
67
+ export async function listDaemonSessions(options = {}) {
68
+ const { runs } = await readDashRuns({ cwd: options.cwd });
69
+ const sorted = [...runs].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
70
+ const limited = typeof options.limit === "number" && options.limit >= 0
71
+ ? sorted.slice(0, options.limit)
72
+ : sorted;
73
+ return { sessions: limited.map(toSessionSummary) };
74
+ }
@@ -18,3 +18,14 @@ export declare function searchSessions(backend: SessionSearchBackend, options: {
18
18
  readonly query: string;
19
19
  readonly limit?: number;
20
20
  }): readonly SessionSearchHit[];
21
+ /**
22
+ * Production backend: opens the real `SessionMemoryStore` FTS index (porter
23
+ * → trigram → Levenshtein fallback, same engine behind `wp_session_search`)
24
+ * and reuses its indexed search — never a table scan. A fresh store is
25
+ * opened and closed per call, mirroring the exact pattern
26
+ * `src/mcp/tools/session-restore.ts`'s `buildRecallPayload` already uses for
27
+ * the identical class (avoids a stale open SQLite/WAL handle). Caching a
28
+ * store across daemon-lifetime calls is intentionally deferred (YAGNI) until
29
+ * there is evidence of per-RPC latency pressure.
30
+ */
31
+ export declare function createSessionMemorySearchBackend(indexDbPath: string): SessionSearchBackend;
@@ -2,6 +2,7 @@
2
2
  * Session search domain (Task 3.7).
3
3
  * Pure ranking/filter helpers + injectable FTS backend (session-memory store).
4
4
  */
5
+ import { SessionMemoryStore } from "#session-memory/store.js";
5
6
  /** Normalize and bound a search query; empty → no search. */
6
7
  export function normalizeSearchQuery(query) {
7
8
  return query.trim().replace(/\s+/gu, " ").slice(0, 200);
@@ -14,3 +15,31 @@ export function searchSessions(backend, options) {
14
15
  const limit = Math.min(Math.max(options.limit ?? 20, 1), 100);
15
16
  return backend.search(q, limit);
16
17
  }
18
+ /**
19
+ * Production backend: opens the real `SessionMemoryStore` FTS index (porter
20
+ * → trigram → Levenshtein fallback, same engine behind `wp_session_search`)
21
+ * and reuses its indexed search — never a table scan. A fresh store is
22
+ * opened and closed per call, mirroring the exact pattern
23
+ * `src/mcp/tools/session-restore.ts`'s `buildRecallPayload` already uses for
24
+ * the identical class (avoids a stale open SQLite/WAL handle). Caching a
25
+ * store across daemon-lifetime calls is intentionally deferred (YAGNI) until
26
+ * there is evidence of per-RPC latency pressure.
27
+ */
28
+ export function createSessionMemorySearchBackend(indexDbPath) {
29
+ return {
30
+ search(query, limit) {
31
+ const store = new SessionMemoryStore(indexDbPath);
32
+ try {
33
+ return store.search({ query, limit }).map((hit) => ({
34
+ id: hit.id,
35
+ source: hit.source,
36
+ text: hit.text,
37
+ score: hit.score,
38
+ }));
39
+ }
40
+ finally {
41
+ store.close();
42
+ }
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,5 @@
1
+ import type { MethodHandler } from "#daemon/rpc.js";
2
+ /**
3
+ * Build blueprint-domain handlers scoped to `repoRoot`.
4
+ */
5
+ export declare function createBlueprintsHandlers(repoRoot: string): Readonly<Partial<Record<string, MethodHandler>>>;