@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
@@ -0,0 +1,593 @@
1
+ import { z } from "zod";
2
+ import { createSummaryOutputSchema, createSummaryResult } from "#mcp/tools/_shared/result.js";
3
+ import { redactText } from "#mcp/tools/_shared/redact.js";
4
+ import { isMissingBinary, isRunFailure, runCommand } from "#mcp/tools/_shared/run-command.js";
5
+ import { readonlyOpsBaseSchema, resolveReadonlyCwd, runReadonlyCommand, } from "#mcp/tools/_readonly-ops.js";
6
+ export const RUN_WAIT_UNTIL_VALUES = ["run_terminal", "all_jobs_terminal"];
7
+ const SELECTOR_REQUIRED_MESSAGE = "wp run wait requires a selector: pass --sha <head-sha> or --run-id <id> (no implicit latest-run guessing)";
8
+ const SELECTOR_CONFLICT_MESSAGE = "wp run wait accepts exactly one selector: pass either --sha or --run-id, not both";
9
+ const runIdSchema = z.union([
10
+ z.number().int().positive(),
11
+ z
12
+ .string()
13
+ .regex(/^[1-9][0-9]*$/u, "runId must be a positive integer (no signs, decimals, or suffixes)"),
14
+ ]);
15
+ const inputSchema = readonlyOpsBaseSchema
16
+ .extend({
17
+ // Workflow-run JSON (jobs + steps) is much larger than PR metadata, so the
18
+ // effective default budget is the shared schema's ceiling rather than 4 KB.
19
+ maxOutputBytes: z.number().int().positive().max(64_000).optional().default(64_000),
20
+ sha: z.string().min(1).optional(),
21
+ runId: runIdSchema.optional(),
22
+ workflow: z.string().min(1).optional(),
23
+ until: z.enum(RUN_WAIT_UNTIL_VALUES).optional().default("run_terminal"),
24
+ pollIntervalMs: z.number().int().positive().max(300_000).optional().default(15_000),
25
+ })
26
+ .strict()
27
+ .superRefine((value, ctx) => {
28
+ const hasSha = value.sha !== undefined;
29
+ const hasRunId = value.runId !== undefined;
30
+ if (!hasSha && !hasRunId) {
31
+ ctx.addIssue({
32
+ code: z.ZodIssueCode.custom,
33
+ message: SELECTOR_REQUIRED_MESSAGE,
34
+ path: ["sha"],
35
+ });
36
+ }
37
+ if (hasSha && hasRunId) {
38
+ ctx.addIssue({
39
+ code: z.ZodIssueCode.custom,
40
+ message: SELECTOR_CONFLICT_MESSAGE,
41
+ path: ["runId"],
42
+ });
43
+ }
44
+ });
45
+ const runWaitJobSchema = z.object({
46
+ name: z.string(),
47
+ status: z.string(),
48
+ conclusion: z.string().nullable(),
49
+ failedStep: z.string().optional(),
50
+ errorTail: z.string().optional(),
51
+ upstreamCulprits: z.array(z.string()).optional(),
52
+ classification: z.literal("blocked_upstream").optional(),
53
+ transient: z.string().optional(),
54
+ });
55
+ const outputSchema = createSummaryOutputSchema({
56
+ counts: z.record(z.string(), z.number()),
57
+ details: z.object({
58
+ cwd: z.string(),
59
+ sha: z.string().optional(),
60
+ runId: z.number().optional(),
61
+ workflow: z.string().optional(),
62
+ until: z.enum(RUN_WAIT_UNTIL_VALUES),
63
+ timeoutMs: z.number(),
64
+ pollIntervalMs: z.number(),
65
+ pollCount: z.number(),
66
+ elapsedMs: z.number(),
67
+ commands: z.array(z.record(z.string(), z.unknown())),
68
+ }),
69
+ }).extend({
70
+ status: z.enum(["success", "failed", "timed_out", "no_run"]),
71
+ verdict: z.enum(["passed", "failed", "pending"]),
72
+ terminalReason: z.enum([
73
+ "run_terminal",
74
+ "all_jobs_terminal",
75
+ "run_failed",
76
+ "timeout",
77
+ "no_run",
78
+ "command_failed",
79
+ ]),
80
+ conclusion: z.string().nullable(),
81
+ runId: z.number().nullable(),
82
+ runUrl: z.string().optional(),
83
+ workflowName: z.string().optional(),
84
+ headSha: z.string().optional(),
85
+ until: z.enum(RUN_WAIT_UNTIL_VALUES),
86
+ jobs: z.array(runWaitJobSchema),
87
+ });
88
+ const RUN_VIEW_FIELDS = "databaseId,status,conclusion,workflowName,headSha,url,name,event,jobs";
89
+ const RUN_LIST_FIELDS = "databaseId,headSha,workflowName,status,conclusion,createdAt,url,name,event";
90
+ const PROBE_TIMEOUT_MS = 30_000;
91
+ const MAX_ERROR_TAIL_LINES = 50;
92
+ const MAX_ERROR_TAIL_BYTES = 2_000;
93
+ const MAX_FAILURE_SNIPPET_CHARS = 200;
94
+ /**
95
+ * Terminal job conclusions that warrant failure diagnostics (failed step,
96
+ * error tail, transient classification, upstream-culprit attribution).
97
+ * `skipped` and `neutral` are terminal but not unsuccessful.
98
+ */
99
+ const UNSUCCESSFUL_JOB_CONCLUSIONS = new Set([
100
+ "failure",
101
+ "timed_out",
102
+ "cancelled",
103
+ "action_required",
104
+ ]);
105
+ // Transient classifier v1 — regex on (failed-step name + errorTail).
106
+ const TRANSIENT_CLASSIFIERS = [
107
+ {
108
+ id: "pulumi-stack-lock",
109
+ pattern: /\[409\] Conflict: Another update is currently in progress/u,
110
+ },
111
+ { id: "node-gyp-cache-perms", pattern: /gyp .*Permission denied/u },
112
+ {
113
+ id: "playwright-browser-missing",
114
+ pattern: /Executable doesn't exist.*playwright|playwright.*Executable doesn't exist/iu,
115
+ },
116
+ ];
117
+ export function classifyTransient(text) {
118
+ for (const classifier of TRANSIENT_CLASSIFIERS) {
119
+ if (classifier.pattern.test(text))
120
+ return classifier.id;
121
+ }
122
+ return undefined;
123
+ }
124
+ /** Distinguish provider-level failures so they never collapse into a generic command_failed. */
125
+ function classifyGhFailure(id, output) {
126
+ if (!output)
127
+ return undefined;
128
+ if (/rate limit|HTTP 429|secondary rate/iu.test(output)) {
129
+ return `github-rate-limited: ${id} hit a GitHub API rate limit`;
130
+ }
131
+ if (/HTTP 401|Bad credentials|authentication|gh auth login/iu.test(output)) {
132
+ return `github-auth-failed: ${id} could not authenticate with GitHub`;
133
+ }
134
+ return undefined;
135
+ }
136
+ function skippedResult(id, args) {
137
+ return {
138
+ id,
139
+ command: { command: "gh", args },
140
+ passed: false,
141
+ skipped: true,
142
+ warnings: [`${id} skipped: timeout budget exhausted`],
143
+ };
144
+ }
145
+ /**
146
+ * Deadline-aware gh probe through the shared bounded read-only path
147
+ * (`runReadonlyCommand`): output is budget-clipped and redacted, spawn
148
+ * diagnostics (stderr, timedOut, aborted) are preserved, and no probe starts
149
+ * once the overall deadline is spent. Per-probe timeout is the remaining
150
+ * budget capped at PROBE_TIMEOUT_MS — never a fixed floor past the deadline.
151
+ */
152
+ async function runGhBounded(id, args, options) {
153
+ const remainingMs = options.deadline - Date.now();
154
+ if (remainingMs <= 0)
155
+ return skippedResult(id, args);
156
+ const result = await runReadonlyCommand(id, "gh", args, {
157
+ cwd: options.cwd,
158
+ timeoutMs: Math.min(remainingMs, PROBE_TIMEOUT_MS),
159
+ maxOutputBytes: options.maxOutputBytes,
160
+ signal: options.signal,
161
+ parseJson: options.parseJson,
162
+ });
163
+ if (result.passed || result.missingBinary)
164
+ return result;
165
+ const warnings = [...(result.warnings ?? [])];
166
+ const classified = classifyGhFailure(id, result.rawOutput);
167
+ if (classified)
168
+ warnings.push(classified);
169
+ if (result.timedOut)
170
+ warnings.push(`${id} timed out before completing`);
171
+ if (result.aborted)
172
+ warnings.push(`${id} aborted by cancellation`);
173
+ if (warnings.length === 0) {
174
+ const snippet = (result.rawOutput ?? "").slice(0, MAX_FAILURE_SNIPPET_CHARS).trim();
175
+ warnings.push(`${id} failed (exit ${result.exitCode ?? "unknown"})${snippet ? `: ${snippet}` : ""}`);
176
+ }
177
+ return { ...result, warnings };
178
+ }
179
+ function toSlimCommand(result) {
180
+ return {
181
+ id: result.id,
182
+ command: result.command.command,
183
+ args: result.command.args,
184
+ passed: result.passed,
185
+ ...(result.exitCode === undefined ? {} : { exitCode: result.exitCode }),
186
+ ...(result.timedOut ? { timedOut: true } : {}),
187
+ ...(result.aborted ? { aborted: true } : {}),
188
+ ...(result.missingBinary ? { missingBinary: true } : {}),
189
+ ...(result.skipped ? { skipped: true } : {}),
190
+ };
191
+ }
192
+ function asString(value) {
193
+ return typeof value === "string" && value.length > 0 ? value : undefined;
194
+ }
195
+ function asNumber(value) {
196
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
197
+ }
198
+ /** UTF-8-byte-bounded suffix; strips a split leading multibyte character. */
199
+ function tailBytes(text, maxBytes) {
200
+ const buffer = Buffer.from(text, "utf8");
201
+ if (buffer.byteLength <= maxBytes)
202
+ return text;
203
+ return buffer
204
+ .subarray(buffer.byteLength - maxBytes)
205
+ .toString("utf8")
206
+ .replace(/^�+/u, "");
207
+ }
208
+ function boundedLogTail(text) {
209
+ const lines = text.split(/\r?\n/u).filter((line) => line.length > 0);
210
+ const tail = lines.slice(-MAX_ERROR_TAIL_LINES).join("\n");
211
+ const redacted = redactText(tail) ?? "";
212
+ return tailBytes(redacted, MAX_ERROR_TAIL_BYTES);
213
+ }
214
+ /**
215
+ * Exact-match selection on top of the server-side `gh run list --commit
216
+ * <sha>` filter: entries missing headSha are NOT matches and there is no
217
+ * fallback pool. Because list output flows through the shared redactor
218
+ * (which masks 40+-char tokens, including full commit SHAs), equality also
219
+ * accepts the deterministic mask of the requested SHA — never any other
220
+ * value. Workflow filtering is delegated to `gh run list --workflow`, which
221
+ * accepts both file names and display names — a local name-equality
222
+ * re-filter would wrongly exclude file-name selectors.
223
+ */
224
+ function selectNewestRunId(entries, sha) {
225
+ const accepted = new Set([sha, redactText(sha) ?? sha]);
226
+ const matching = entries.filter((entry) => {
227
+ const headSha = asString(entry.headSha);
228
+ return headSha !== undefined && accepted.has(headSha);
229
+ });
230
+ const newest = [...matching].sort((a, b) => (asString(b.createdAt) ?? "").localeCompare(asString(a.createdAt) ?? ""));
231
+ for (const entry of newest) {
232
+ const runId = asNumber(entry.databaseId);
233
+ if (runId !== undefined)
234
+ return runId;
235
+ }
236
+ return undefined;
237
+ }
238
+ function firstUnsuccessfulStep(job) {
239
+ if (!Array.isArray(job.steps))
240
+ return undefined;
241
+ for (const step of job.steps) {
242
+ const conclusion = asString(step.conclusion);
243
+ if (conclusion !== undefined && UNSUCCESSFUL_JOB_CONCLUSIONS.has(conclusion)) {
244
+ const name = asString(step.name);
245
+ return name === undefined ? undefined : redactText(name);
246
+ }
247
+ }
248
+ return undefined;
249
+ }
250
+ function runIsTerminal(run) {
251
+ return asString(run.status) === "completed";
252
+ }
253
+ function jobsAllTerminal(jobs) {
254
+ return jobs.every((job) => asString(job.status) === "completed");
255
+ }
256
+ function isDone(until, run, jobs) {
257
+ if (!runIsTerminal(run))
258
+ return false;
259
+ if (until === "all_jobs_terminal")
260
+ return jobsAllTerminal(jobs);
261
+ return true;
262
+ }
263
+ async function fetchErrorTail(job, _run, context) {
264
+ const jobId = asNumber(job.databaseId);
265
+ if (jobId === undefined)
266
+ return undefined;
267
+ // gh substitutes {owner}/{repo} from the checkout's repository (and host),
268
+ // so no URL parsing is needed — job/run URLs pass through the shared
269
+ // redactor and are not reliable to parse.
270
+ const args = ["api", `repos/{owner}/{repo}/actions/jobs/${jobId}/logs`];
271
+ const remainingMs = context.deadline - Date.now();
272
+ if (remainingMs <= 0) {
273
+ context.commands.push(skippedResult("run_wait_job_logs", args));
274
+ context.warnings.push(`error-tail enrichment skipped for job ${String(asString(job.name) ?? jobId)}: timeout budget exhausted`);
275
+ return undefined;
276
+ }
277
+ // Deviates from runReadonlyCommand on purpose: its budget clip keeps the
278
+ // HEAD of the output, but failure diagnostics live at the TAIL of a job
279
+ // log. Spawn diagnostics are still preserved in the command record and the
280
+ // returned tail is line- and UTF-8-byte-bounded plus redacted; raw log
281
+ // bytes never enter the result payload.
282
+ const outcome = await runCommand("gh", args, {
283
+ cwd: context.cwd,
284
+ timeoutMs: Math.min(remainingMs, PROBE_TIMEOUT_MS),
285
+ signal: context.signal,
286
+ });
287
+ if (isRunFailure(outcome)) {
288
+ context.commands.push({
289
+ id: "run_wait_job_logs",
290
+ command: { command: "gh", args },
291
+ passed: false,
292
+ missingBinary: isMissingBinary(outcome),
293
+ warnings: [
294
+ isMissingBinary(outcome)
295
+ ? "missing binary: gh"
296
+ : `failed to spawn gh: ${outcome.error.message}`,
297
+ ],
298
+ });
299
+ return undefined;
300
+ }
301
+ const failureWarning = outcome.exitCode === 0
302
+ ? undefined
303
+ : (classifyGhFailure("run_wait_job_logs", outcome.stderr) ??
304
+ `run_wait_job_logs failed (exit ${outcome.exitCode})`);
305
+ context.commands.push({
306
+ id: "run_wait_job_logs",
307
+ command: { command: "gh", args },
308
+ passed: outcome.exitCode === 0,
309
+ exitCode: outcome.exitCode,
310
+ timedOut: outcome.timedOut || undefined,
311
+ aborted: outcome.aborted || undefined,
312
+ ...(failureWarning ? { warnings: [failureWarning] } : {}),
313
+ });
314
+ if (outcome.exitCode !== 0)
315
+ return undefined;
316
+ const tail = boundedLogTail(outcome.stdout);
317
+ return tail.length > 0 ? tail : undefined;
318
+ }
319
+ async function normalizeJobs(rawJobs, run, context, options) {
320
+ const unsuccessfulNames = rawJobs
321
+ .filter((job) => {
322
+ const conclusion = asString(job.conclusion);
323
+ return conclusion !== undefined && UNSUCCESSFUL_JOB_CONCLUSIONS.has(conclusion);
324
+ })
325
+ .map((job) => asString(job.name) ?? "")
326
+ .filter((name) => name.length > 0);
327
+ const jobs = [];
328
+ for (const raw of rawJobs) {
329
+ const name = asString(raw.name) ?? "";
330
+ const status = (asString(raw.status) ?? "");
331
+ const conclusion = (asString(raw.conclusion) ?? null);
332
+ const job = { name, status, conclusion };
333
+ if (conclusion !== null && UNSUCCESSFUL_JOB_CONCLUSIONS.has(conclusion)) {
334
+ const failedStep = firstUnsuccessfulStep(raw);
335
+ if (failedStep)
336
+ job.failedStep = failedStep;
337
+ const errorTail = options.fetchLogs ? await fetchErrorTail(raw, run, context) : undefined;
338
+ if (errorTail)
339
+ job.errorTail = errorTail;
340
+ const transient = classifyTransient(`${failedStep ?? ""}\n${errorTail ?? ""}`);
341
+ if (transient)
342
+ job.transient = transient;
343
+ }
344
+ if (conclusion === "skipped" && unsuccessfulNames.length > 0) {
345
+ job.classification = "blocked_upstream";
346
+ job.upstreamCulprits = unsuccessfulNames;
347
+ }
348
+ jobs.push(job);
349
+ }
350
+ return jobs;
351
+ }
352
+ function counts(pollCount, jobs) {
353
+ return {
354
+ pollCount,
355
+ jobCount: jobs.length,
356
+ failedJobCount: jobs.filter((job) => job.conclusion !== null && UNSUCCESSFUL_JOB_CONCLUSIONS.has(job.conclusion)).length,
357
+ };
358
+ }
359
+ function extractRawJobs(run, warnings) {
360
+ const rawValue = Array.isArray(run.jobs) ? run.jobs : [];
361
+ const jobs = rawValue.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.name === "string");
362
+ if (jobs.length !== rawValue.length) {
363
+ warnings.push("some job entries were omitted by the output-budget sanitizer");
364
+ }
365
+ return jobs;
366
+ }
367
+ async function sleep(ms, signal) {
368
+ if (ms <= 0)
369
+ return;
370
+ await new Promise((resolve) => {
371
+ let settled = false;
372
+ const finish = () => {
373
+ if (settled)
374
+ return;
375
+ settled = true;
376
+ clearTimeout(timer);
377
+ signal?.removeEventListener("abort", onAbort);
378
+ resolve();
379
+ };
380
+ const timer = setTimeout(finish, ms);
381
+ const onAbort = () => finish();
382
+ if (signal) {
383
+ if (signal.aborted)
384
+ finish();
385
+ else
386
+ signal.addEventListener("abort", onAbort, { once: true });
387
+ }
388
+ });
389
+ }
390
+ export async function runRunWait(raw, extra) {
391
+ const input = inputSchema.parse(raw ?? {});
392
+ const cwd = resolveReadonlyCwd(input);
393
+ const startedAt = Date.now();
394
+ const deadline = startedAt + input.timeoutMs;
395
+ const signal = extra?.signal;
396
+ const commands = [];
397
+ const warnings = [];
398
+ let pollCount = 0;
399
+ const finish = (outcome, jobs, run) => {
400
+ const allWarnings = [...commands.flatMap((command) => command.warnings ?? []), ...warnings];
401
+ const workflowName = asString(run?.workflowName);
402
+ const runUrl = asString(run?.url);
403
+ const headSha = asString(run?.headSha);
404
+ return {
405
+ ...outcome,
406
+ ...(runUrl ? { runUrl } : {}),
407
+ ...(workflowName ? { workflowName } : {}),
408
+ ...(headSha ? { headSha } : {}),
409
+ until: input.until,
410
+ jobs,
411
+ counts: counts(pollCount, jobs),
412
+ details: {
413
+ cwd,
414
+ sha: input.sha,
415
+ runId: typeof input.runId === "number" ? input.runId : undefined,
416
+ workflow: input.workflow,
417
+ until: input.until,
418
+ timeoutMs: input.timeoutMs,
419
+ pollIntervalMs: input.pollIntervalMs,
420
+ pollCount,
421
+ elapsedMs: Date.now() - startedAt,
422
+ commands: commands.map(toSlimCommand),
423
+ },
424
+ ...(allWarnings.length > 0 ? { warnings: allWarnings } : {}),
425
+ };
426
+ };
427
+ const commandFailed = (summary) => finish({
428
+ status: "failed",
429
+ passed: false,
430
+ verdict: "failed",
431
+ summary,
432
+ terminalReason: "command_failed",
433
+ conclusion: null,
434
+ runId: null,
435
+ }, []);
436
+ // Resolve the selector to a run id ONCE, then watch only that run: a rerun
437
+ // appearing mid-watch must never silently replace the run being watched.
438
+ let boundRunId;
439
+ if (input.runId !== undefined) {
440
+ boundRunId = typeof input.runId === "number" ? input.runId : Number.parseInt(input.runId, 10);
441
+ }
442
+ else {
443
+ const sha = input.sha;
444
+ const listArgs = [
445
+ "run",
446
+ "list",
447
+ "--commit",
448
+ sha,
449
+ ...(input.workflow ? ["--workflow", input.workflow] : []),
450
+ "--limit",
451
+ "20",
452
+ "--json",
453
+ RUN_LIST_FIELDS,
454
+ ];
455
+ const list = await runGhBounded("run_wait_list", listArgs, {
456
+ cwd,
457
+ deadline,
458
+ maxOutputBytes: input.maxOutputBytes,
459
+ signal,
460
+ });
461
+ commands.push(list);
462
+ if (list.missingBinary)
463
+ return commandFailed("run wait failed: missing binary gh");
464
+ if (!list.passed)
465
+ return commandFailed("run wait failed while listing workflow runs");
466
+ if (list.truncated) {
467
+ warnings.push("run list output exceeded the output budget; raise maxOutputBytes or use --run-id");
468
+ return commandFailed("run wait failed: run list output exceeded the output budget");
469
+ }
470
+ let parsed;
471
+ try {
472
+ parsed = JSON.parse(list.rawOutput?.trim() || "[]");
473
+ }
474
+ catch {
475
+ return commandFailed("run wait failed: run list returned invalid JSON");
476
+ }
477
+ const resolved = Array.isArray(parsed)
478
+ ? selectNewestRunId(parsed, sha)
479
+ : undefined;
480
+ if (resolved === undefined) {
481
+ return finish({
482
+ status: "no_run",
483
+ passed: false,
484
+ verdict: "failed",
485
+ summary: `run wait found no workflow run with head sha ${sha}${input.workflow ? ` for workflow ${input.workflow}` : ""}`,
486
+ terminalReason: "no_run",
487
+ conclusion: null,
488
+ runId: null,
489
+ }, []);
490
+ }
491
+ boundRunId = resolved;
492
+ }
493
+ const viewArgs = ["run", "view", String(boundRunId), "--json", RUN_VIEW_FIELDS];
494
+ let lastRun;
495
+ let lastJobs = [];
496
+ while (!signal?.aborted && Date.now() < deadline) {
497
+ pollCount += 1;
498
+ const view = await runGhBounded("run_wait_view", viewArgs, {
499
+ cwd,
500
+ deadline,
501
+ maxOutputBytes: input.maxOutputBytes,
502
+ signal,
503
+ parseJson: true,
504
+ });
505
+ commands.push(view);
506
+ if (view.skipped)
507
+ break;
508
+ if (view.missingBinary)
509
+ return commandFailed("run wait failed: missing binary gh");
510
+ if (!view.passed)
511
+ return commandFailed("run wait failed while reading workflow run state");
512
+ const run = (view.details ?? {});
513
+ const jobs = extractRawJobs(run, warnings);
514
+ lastRun = run;
515
+ lastJobs = jobs;
516
+ if (isDone(input.until, run, jobs)) {
517
+ const context = {
518
+ cwd,
519
+ deadline,
520
+ maxOutputBytes: input.maxOutputBytes,
521
+ signal,
522
+ commands,
523
+ warnings,
524
+ };
525
+ const enriched = await normalizeJobs(jobs, run, context, { fetchLogs: true });
526
+ const conclusion = (asString(run.conclusion) ?? null);
527
+ const green = conclusion === "success";
528
+ const terminalReason = green
529
+ ? input.until === "all_jobs_terminal"
530
+ ? "all_jobs_terminal"
531
+ : "run_terminal"
532
+ : "run_failed";
533
+ const workflowName = asString(run.workflowName);
534
+ return finish({
535
+ status: green ? "success" : "failed",
536
+ passed: green,
537
+ verdict: green ? "passed" : "failed",
538
+ summary: green
539
+ ? `run wait reached ${terminalReason} for run ${boundRunId}${workflowName ? ` (${workflowName})` : ""} with conclusion success`
540
+ : `run wait: run ${boundRunId}${workflowName ? ` (${workflowName})` : ""} finished with conclusion ${conclusion ?? "unknown"}`,
541
+ terminalReason,
542
+ conclusion,
543
+ runId: boundRunId,
544
+ }, enriched, run);
545
+ }
546
+ const sleepMs = Math.min(input.pollIntervalMs, deadline - Date.now());
547
+ if (sleepMs <= 0)
548
+ break;
549
+ await sleep(sleepMs, signal);
550
+ }
551
+ // Timeout / cancellation: report the last-observed job states (normalized,
552
+ // no log downloads) instead of discarding known partial progress.
553
+ const timeoutContext = {
554
+ cwd,
555
+ deadline,
556
+ maxOutputBytes: input.maxOutputBytes,
557
+ signal,
558
+ commands,
559
+ warnings,
560
+ };
561
+ const partialJobs = lastRun
562
+ ? await normalizeJobs(lastJobs, lastRun, timeoutContext, { fetchLogs: false })
563
+ : [];
564
+ return finish({
565
+ status: "timed_out",
566
+ passed: false,
567
+ verdict: "pending",
568
+ summary: `run wait timed out after ${input.timeoutMs}ms waiting for ${input.until}`,
569
+ terminalReason: "timeout",
570
+ conclusion: (asString(lastRun?.conclusion) ?? null),
571
+ runId: boundRunId,
572
+ }, partialJobs, lastRun);
573
+ }
574
+ const tool = {
575
+ name: "wp_run_wait",
576
+ description: "Read-only GitHub workflow-run waiter via the gh CLI: blocks until a push/dispatch run (e.g. a production deploy on main) reaches run-terminal or all-jobs-terminal state, then reports conclusion, failed-step tails, transient classification, and blocked-upstream culprits. Selector is required and exclusive (--sha newest exact-match run, or --run-id direct); timeoutMs (<=300000) is an overall deadline covering probes and log enrichment. Does not mutate run state.",
577
+ inputSchema,
578
+ outputSchema,
579
+ annotations: {
580
+ title: "Workflow run wait",
581
+ readOnlyHint: true,
582
+ destructiveHint: false,
583
+ idempotentHint: true,
584
+ openWorldHint: false,
585
+ },
586
+ handler: async (raw, extra) => {
587
+ const payload = await runRunWait(raw, extra);
588
+ return createSummaryResult(payload, {
589
+ isError: payload.status !== "success",
590
+ });
591
+ },
592
+ };
593
+ export default tool;
@@ -1,6 +1,6 @@
1
1
  import type { ToolDescriptor, ToolHandlerExtra, ToolHandlerResult } from "#mcp/auto-discover";
2
2
  export interface SessionFetchAndIndexDeps {
3
- readonly fetchImpl?: typeof fetch;
3
+ readonly fetchImpl?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
4
4
  }
5
5
  export declare function handleSessionFetchAndIndex(raw: unknown, extra?: ToolHandlerExtra, deps?: SessionFetchAndIndexDeps): Promise<ToolHandlerResult>;
6
6
  declare const tool: ToolDescriptor;
@@ -0,0 +1,3 @@
1
+ import type { ToolDescriptor } from "#mcp/auto-discover";
2
+ declare const tool: ToolDescriptor;
3
+ export default tool;
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { readCurrentSessionId } from "#session-memory/current-session.js";
3
+ import { createSummaryOutputSchema, createSummaryResult } from "./_shared/result.js";
4
+ const inputSchema = z
5
+ .object({
6
+ cwd: z.string().optional(),
7
+ })
8
+ .strict();
9
+ const outputSchema = createSummaryOutputSchema({
10
+ details: z.object({
11
+ sessionId: z.string(),
12
+ }),
13
+ }).extend({
14
+ sessionId: z.string(),
15
+ });
16
+ function effectiveCwd(cwd) {
17
+ return cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
18
+ }
19
+ const tool = {
20
+ name: "wp_session_id",
21
+ description: "Report only the current agent session id (empty if unknown), skipping the cross-worktree execution-model scan `wp_session_info` performs. Cheaper for hot paths — this is the lookup `wp_pr_upsert` uses internally to auto-resolve an omitted `description.sessionId`. Prefer `wp_session_info` when the observed execution model(s) are also needed.",
22
+ inputSchema,
23
+ outputSchema,
24
+ annotations: {
25
+ title: "Session id",
26
+ readOnlyHint: true,
27
+ destructiveHint: false,
28
+ idempotentHint: true,
29
+ openWorldHint: false,
30
+ },
31
+ handler: async (raw) => {
32
+ const input = inputSchema.parse(raw ?? {});
33
+ const sessionId = readCurrentSessionId(effectiveCwd(input.cwd), process.env);
34
+ const payload = {
35
+ passed: true,
36
+ summary: sessionId
37
+ ? `session id ${sessionId}`
38
+ : "no live or captured session id found; use Session-skip in the PR disclosure",
39
+ sessionId,
40
+ details: { sessionId },
41
+ };
42
+ return createSummaryResult(payload);
43
+ },
44
+ };
45
+ export default tool;
@@ -0,0 +1,3 @@
1
+ import type { ToolDescriptor } from "#mcp/auto-discover";
2
+ declare const tool: ToolDescriptor;
3
+ export default tool;