@webpresso/agent-kit 3.1.30 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (257) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  17. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  18. package/dist/esm/audit/ci-test-perf.js +30 -0
  19. package/dist/esm/audit/command-surface.js +4 -2
  20. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  21. package/dist/esm/audit/host-substitution-risk.js +256 -0
  22. package/dist/esm/audit/registry.d.ts +4 -4
  23. package/dist/esm/audit/registry.js +8 -1
  24. package/dist/esm/audit/toolchain-isolation.js +1 -2
  25. package/dist/esm/audit/typescript-version.d.ts +2 -0
  26. package/dist/esm/audit/typescript-version.js +123 -0
  27. package/dist/esm/blueprint/core/parser.js +43 -1
  28. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  29. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  30. package/dist/esm/blueprint/core/validation/state.js +25 -14
  31. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  32. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  33. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  34. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  35. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  36. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  37. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  38. package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
  39. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  40. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  41. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  42. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  43. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  44. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  45. package/dist/esm/blueprint/trust/gates.js +20 -0
  46. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  47. package/dist/esm/blueprint/trust/promotion.js +62 -112
  48. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  49. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  50. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  51. package/dist/esm/blueprint/trust/validator.js +7 -1
  52. package/dist/esm/blueprint/utils/archive.js +27 -5
  53. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  54. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  55. package/dist/esm/build/cli-mcp-parity.js +10 -0
  56. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  57. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  58. package/dist/esm/cli/cli.d.ts +1 -1
  59. package/dist/esm/cli/cli.js +34 -0
  60. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  61. package/dist/esm/cli/commands/agent-launch.js +82 -4
  62. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  63. package/dist/esm/cli/commands/audit-core.js +8 -1
  64. package/dist/esm/cli/commands/audit.js +17 -5
  65. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  66. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  67. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  68. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  69. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  70. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  71. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  72. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  73. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  74. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  75. package/dist/esm/cli/commands/compile.js +5 -31
  76. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  77. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  78. package/dist/esm/cli/commands/doctor.js +5 -0
  79. package/dist/esm/cli/commands/hook.d.ts +5 -0
  80. package/dist/esm/cli/commands/hook.js +15 -9
  81. package/dist/esm/cli/commands/init/config.js +2 -2
  82. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  83. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  84. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  85. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  86. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  87. package/dist/esm/cli/commands/init/convergence.js +57 -0
  88. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  89. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  90. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  91. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  92. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  93. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  94. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  95. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  96. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  97. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  98. package/dist/esm/cli/commands/init/index.js +326 -173
  99. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  100. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  101. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  102. package/dist/esm/cli/commands/init/merge.js +30 -1
  103. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  104. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  105. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  106. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  107. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  108. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  109. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  110. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  111. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  112. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  113. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  114. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  115. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  116. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  117. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  118. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  119. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  120. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  121. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  122. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  123. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  124. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  125. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  126. package/dist/esm/cli/commands/public-readiness.js +42 -0
  127. package/dist/esm/cli/commands/review.d.ts +35 -3
  128. package/dist/esm/cli/commands/review.js +729 -209
  129. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  130. package/dist/esm/cli/commands/run-wait.js +122 -0
  131. package/dist/esm/cli/commands/setup-help.js +1 -1
  132. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  133. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  134. package/dist/esm/cli/direct-provider-launch.js +9 -5
  135. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  136. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  137. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  138. package/dist/esm/daemon/domains/git-actions.js +136 -0
  139. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  140. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  141. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  142. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  143. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  144. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  145. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  146. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  147. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  148. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  149. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  150. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  151. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  152. package/dist/esm/daemon/handlers/product.js +10 -119
  153. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  154. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  155. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  156. package/dist/esm/daemon/handlers/shared.js +16 -0
  157. package/dist/esm/daemon/methods.d.ts +8 -1
  158. package/dist/esm/daemon/methods.js +7 -0
  159. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  160. package/dist/esm/daemon/protocol/contract.js +26 -0
  161. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  162. package/dist/esm/daemon/protocol/index.js +1 -1
  163. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  164. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  165. package/dist/esm/hooks/conformance/batch.js +5 -0
  166. package/dist/esm/hooks/doctor.d.ts +3 -3
  167. package/dist/esm/hooks/doctor.js +30 -15
  168. package/dist/esm/hooks/permission-request/index.js +9 -3
  169. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  170. package/dist/esm/hooks/permission-request/policy.js +13 -0
  171. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  172. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  173. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  174. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  175. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  176. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  177. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  178. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  179. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  180. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  181. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  182. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  183. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  184. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  185. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  186. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  187. package/dist/esm/hooks/shared/types.d.ts +8 -5
  188. package/dist/esm/hooks/shared/types.js +23 -8
  189. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  190. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  191. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  192. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  193. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  194. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  195. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  196. package/dist/esm/mcp/blueprint/registration.js +3 -2
  197. package/dist/esm/mcp/server.d.ts +1 -0
  198. package/dist/esm/mcp/server.js +27 -0
  199. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  200. package/dist/esm/mcp/tools/_names.js +5 -0
  201. package/dist/esm/mcp/tools/_registry.js +10 -0
  202. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  203. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  204. package/dist/esm/mcp/tools/audit.d.ts +4 -1
  205. package/dist/esm/mcp/tools/audit.js +30 -0
  206. package/dist/esm/mcp/tools/audits.d.ts +3 -0
  207. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  208. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  209. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  210. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  211. package/dist/esm/mcp/tools/review-run.js +216 -0
  212. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  213. package/dist/esm/mcp/tools/run-wait.js +593 -0
  214. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  215. package/dist/esm/mcp/tools/session-id.js +45 -0
  216. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  217. package/dist/esm/mcp/tools/session-info.js +49 -0
  218. package/dist/esm/output-transforms/audit.d.ts +23 -0
  219. package/dist/esm/output-transforms/audit.js +47 -0
  220. package/dist/esm/output-transforms/index.js +2 -0
  221. package/dist/esm/review/authority.js +13 -5
  222. package/dist/esm/review/availability.d.ts +22 -0
  223. package/dist/esm/review/availability.js +107 -20
  224. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  225. package/dist/esm/review/delivery-verifier.js +115 -0
  226. package/dist/esm/review/events.d.ts +3 -2
  227. package/dist/esm/review/events.js +23 -3
  228. package/dist/esm/review/execution/adapters.js +26 -9
  229. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  230. package/dist/esm/review/execution/output-policy.js +12 -0
  231. package/dist/esm/review/execution/review-checkout.js +87 -6
  232. package/dist/esm/review/execution/supervisor.js +5 -0
  233. package/dist/esm/review/execution/types.d.ts +8 -0
  234. package/dist/esm/review/opencode-account.d.ts +24 -0
  235. package/dist/esm/review/opencode-account.js +68 -0
  236. package/dist/esm/review/opencode-auth.d.ts +33 -0
  237. package/dist/esm/review/opencode-auth.js +79 -0
  238. package/dist/esm/review/subject.d.ts +12 -0
  239. package/dist/esm/review/subject.js +74 -22
  240. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  241. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  242. package/dist/esm/status/snapshot.js +30 -19
  243. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  244. package/dist/esm/test/duration-sequencer.js +54 -0
  245. package/dist/esm/test/shard-durations.json +922 -0
  246. package/dist/esm/test/worker-budget.d.ts +6 -0
  247. package/dist/esm/test/worker-budget.js +59 -1
  248. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  249. package/dist/esm/test-helpers/global-setup.js +68 -24
  250. package/dist/esm/ultragoal/service.d.ts +7 -0
  251. package/dist/esm/ultragoal/service.js +42 -2
  252. package/dist/esm/utils/write-json-file.js +3 -1
  253. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  254. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  255. package/dist/esm/worktrees/registry.d.ts +8 -0
  256. package/dist/esm/worktrees/registry.js +18 -3
  257. package/package.json +16 -13
@@ -0,0 +1,31 @@
1
+ import { defaultIndexDbPath } from "#mcp/tools/session-restore.js";
2
+ import { listDaemonSessions } from "#daemon/domains/run-registry-interop.js";
3
+ import { createSessionMemorySearchBackend, searchSessions, } from "#daemon/domains/sessions-search.js";
4
+ import { asRecord, requireString } from "./shared.js";
5
+ /**
6
+ * Build session-domain handlers scoped to `repoRoot`. `repoRoot` doubles as
7
+ * the dash run-registry cwd (sessions.list) and the session-memory index
8
+ * root (sessions.search) — both already resolve per-repo paths on their own.
9
+ */
10
+ export function createSessionsHandlers(repoRoot) {
11
+ return {
12
+ "sessions.list": async (_session, params) => {
13
+ const record = asRecord(params);
14
+ const limit = typeof record.limit === "number" ? record.limit : undefined;
15
+ return listDaemonSessions({ cwd: repoRoot, limit });
16
+ },
17
+ "sessions.search": (_session, params) => {
18
+ const record = asRecord(params);
19
+ const query = requireString(record, "query");
20
+ const limit = typeof record.limit === "number" ? record.limit : undefined;
21
+ const backend = createSessionMemorySearchBackend(defaultIndexDbPath(repoRoot));
22
+ const hits = searchSessions(backend, { query, limit });
23
+ return { hits };
24
+ },
25
+ "launch.permissionState": () => ({
26
+ mode: "safe",
27
+ yoloPreference: false,
28
+ isolationWarning: "permission state is advisory until full permission UX lands",
29
+ }),
30
+ };
31
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared parameter-parsing helpers for daemon product-domain handlers.
3
+ */
4
+ export declare function asRecord(params: unknown): Record<string, unknown>;
5
+ export declare function requireString(record: Record<string, unknown>, key: string): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared parameter-parsing helpers for daemon product-domain handlers.
3
+ */
4
+ export function asRecord(params) {
5
+ if (params === null || typeof params !== "object" || Array.isArray(params)) {
6
+ return {};
7
+ }
8
+ return params;
9
+ }
10
+ export function requireString(record, key) {
11
+ const v = record[key];
12
+ if (typeof v !== "string" || v.length === 0) {
13
+ throw new Error(`missing string param: ${key}`);
14
+ }
15
+ return v;
16
+ }
@@ -92,6 +92,13 @@ export declare const DAEMON_METHOD_REGISTRY: {
92
92
  readonly requireHandshake: true;
93
93
  readonly uiFacing: true;
94
94
  };
95
+ readonly "sessions.search": {
96
+ readonly privileged: true;
97
+ readonly requiresConfirmation: false;
98
+ readonly mutates: false;
99
+ readonly requireHandshake: true;
100
+ readonly uiFacing: true;
101
+ };
95
102
  readonly "git.diff": {
96
103
  readonly privileged: true;
97
104
  readonly requiresConfirmation: false;
@@ -164,7 +171,7 @@ export type UiRpcMethod = {
164
171
  [M in DaemonRpcMethod]: (typeof DAEMON_METHOD_REGISTRY)[M]["uiFacing"] extends true ? M : never;
165
172
  }[DaemonRpcMethod];
166
173
  export declare const UI_RPC_METHODS: UiRpcMethod[];
167
- export declare const DESTRUCTIVE_METHODS: ("auth.issueNonce" | "blueprints.exec" | "blueprints.list" | "daemon.envMap.push" | "deploy.run" | "fleet.status" | "git.commit" | "git.createPr" | "git.diff" | "handshake" | "launch.claim" | "launch.permissionState" | "launch.prepare" | "ping" | "sessions.list" | "worktrees.list")[];
174
+ export declare const DESTRUCTIVE_METHODS: ("auth.issueNonce" | "blueprints.exec" | "blueprints.list" | "daemon.envMap.push" | "deploy.run" | "fleet.status" | "git.commit" | "git.createPr" | "git.diff" | "handshake" | "launch.claim" | "launch.permissionState" | "launch.prepare" | "ping" | "sessions.list" | "sessions.search" | "worktrees.list")[];
168
175
  export declare function getMethodCapability(method: string): MethodCapability | null;
169
176
  export declare function isDaemonRpcMethod(method: string): method is DaemonRpcMethod;
170
177
  export declare function isUiRpcMethod(method: string): method is UiRpcMethod;
@@ -60,6 +60,13 @@ export const DAEMON_METHOD_REGISTRY = {
60
60
  requireHandshake: true,
61
61
  uiFacing: true,
62
62
  },
63
+ "sessions.search": {
64
+ privileged: true,
65
+ requiresConfirmation: false,
66
+ mutates: false,
67
+ requireHandshake: true,
68
+ uiFacing: true,
69
+ },
63
70
  "git.diff": {
64
71
  privileged: true,
65
72
  requiresConfirmation: false,
@@ -92,6 +92,7 @@ export declare const daemonRpcMethodSchema: z.ZodEnum<{
92
92
  "launch.prepare": "launch.prepare";
93
93
  ping: "ping";
94
94
  "sessions.list": "sessions.list";
95
+ "sessions.search": "sessions.search";
95
96
  "worktrees.list": "worktrees.list";
96
97
  }>;
97
98
  export declare const uiRpcMethodSchema: z.ZodEnum<{
@@ -108,6 +109,7 @@ export declare const uiRpcMethodSchema: z.ZodEnum<{
108
109
  "launch.prepare": "launch.prepare";
109
110
  ping: "ping";
110
111
  "sessions.list": "sessions.list";
112
+ "sessions.search": "sessions.search";
111
113
  "worktrees.list": "worktrees.list";
112
114
  }>;
113
115
  export declare const pingParamsSchema: z.ZodObject<{}, z.core.$strict>;
@@ -212,6 +214,32 @@ export declare const sessionsListResultSchema: z.ZodObject<{
212
214
  }, z.core.$strict>>;
213
215
  }, z.core.$strict>;
214
216
  export type SessionsListResult = z.infer<typeof sessionsListResultSchema>;
217
+ /**
218
+ * No db-path override fields: path safety requires the daemon resolve its
219
+ * own index db path from its configured repoRoot, never from a
220
+ * webview-supplied param.
221
+ */
222
+ export declare const sessionsSearchParamsSchema: z.ZodObject<{
223
+ query: z.ZodString;
224
+ limit: z.ZodOptional<z.ZodNumber>;
225
+ }, z.core.$strict>;
226
+ export type SessionsSearchParams = z.infer<typeof sessionsSearchParamsSchema>;
227
+ export declare const sessionSearchHitSchema: z.ZodObject<{
228
+ id: z.ZodString;
229
+ source: z.ZodString;
230
+ text: z.ZodString;
231
+ score: z.ZodNumber;
232
+ }, z.core.$strict>;
233
+ export type SessionSearchHitResult = z.infer<typeof sessionSearchHitSchema>;
234
+ export declare const sessionsSearchResultSchema: z.ZodObject<{
235
+ hits: z.ZodArray<z.ZodObject<{
236
+ id: z.ZodString;
237
+ source: z.ZodString;
238
+ text: z.ZodString;
239
+ score: z.ZodNumber;
240
+ }, z.core.$strict>>;
241
+ }, z.core.$strict>;
242
+ export type SessionsSearchResult = z.infer<typeof sessionsSearchResultSchema>;
215
243
  export declare const gitDiffParamsSchema: z.ZodObject<{
216
244
  cwd: z.ZodString;
217
245
  }, z.core.$strict>;
@@ -255,6 +283,7 @@ export declare const fleetStatusResultSchema: z.ZodObject<{
255
283
  label: z.ZodOptional<z.ZodString>;
256
284
  }, z.core.$strict>>;
257
285
  }, z.core.$strict>;
286
+ export type FleetStatusResult = z.infer<typeof fleetStatusResultSchema>;
258
287
  export declare const worktreesListParamsSchema: z.ZodObject<{
259
288
  repoRoot: z.ZodOptional<z.ZodString>;
260
289
  }, z.core.$strict>;
@@ -387,6 +416,20 @@ export declare const UI_METHOD_SCHEMAS: {
387
416
  }, z.core.$strict>>;
388
417
  }, z.core.$strict>;
389
418
  };
419
+ readonly "sessions.search": {
420
+ readonly params: z.ZodObject<{
421
+ query: z.ZodString;
422
+ limit: z.ZodOptional<z.ZodNumber>;
423
+ }, z.core.$strict>;
424
+ readonly result: z.ZodObject<{
425
+ hits: z.ZodArray<z.ZodObject<{
426
+ id: z.ZodString;
427
+ source: z.ZodString;
428
+ text: z.ZodString;
429
+ score: z.ZodNumber;
430
+ }, z.core.$strict>>;
431
+ }, z.core.$strict>;
432
+ };
390
433
  readonly "git.diff": {
391
434
  readonly params: z.ZodObject<{
392
435
  cwd: z.ZodString;
@@ -189,6 +189,31 @@ export const sessionsListResultSchema = z
189
189
  sessions: z.array(sessionSummarySchema),
190
190
  })
191
191
  .strict();
192
+ // --- sessions.search ---
193
+ /**
194
+ * No db-path override fields: path safety requires the daemon resolve its
195
+ * own index db path from its configured repoRoot, never from a
196
+ * webview-supplied param.
197
+ */
198
+ export const sessionsSearchParamsSchema = z
199
+ .object({
200
+ query: z.string().min(1).max(4_096),
201
+ limit: z.number().int().positive().max(100).optional(),
202
+ })
203
+ .strict();
204
+ export const sessionSearchHitSchema = z
205
+ .object({
206
+ id: z.string().min(1),
207
+ source: z.string().min(1),
208
+ text: z.string(),
209
+ score: z.number(),
210
+ })
211
+ .strict();
212
+ export const sessionsSearchResultSchema = z
213
+ .object({
214
+ hits: z.array(sessionSearchHitSchema),
215
+ })
216
+ .strict();
192
217
  // --- git (stubs) ---
193
218
  export const gitDiffParamsSchema = z
194
219
  .object({
@@ -311,6 +336,7 @@ export const UI_METHOD_SCHEMAS = {
311
336
  result: launchPermissionStateResultSchema,
312
337
  },
313
338
  "sessions.list": { params: sessionsListParamsSchema, result: sessionsListResultSchema },
339
+ "sessions.search": { params: sessionsSearchParamsSchema, result: sessionsSearchResultSchema },
314
340
  "git.diff": { params: gitDiffParamsSchema, result: gitDiffResultSchema },
315
341
  "git.commit": { params: gitCommitParamsSchema, result: gitCommitResultSchema },
316
342
  "git.createPr": { params: gitCreatePrParamsSchema, result: gitCreatePrResultSchema },
@@ -5,4 +5,4 @@
5
5
  * - `#daemon/protocol` (repo-internal package imports)
6
6
  * - `@webpresso/agent-kit/daemon/protocol` (published subpath)
7
7
  */
8
- export { DAEMON_PROTOCOL_VERSION, DAEMON_SESSION_MEMORY_SCHEMA_VERSION, DAEMON_RPC_METHODS, RUST_CHANNEL_METHODS, FORBIDDEN_UI_PAYLOAD_KEYS, UI_METHOD_SCHEMAS, opaqueHandleSchema, permissionModeSchema, providerIdSchema, peerRoleSchema, daemonRpcMethodSchema, handshakeParamsSchema, handshakeResultSchema, pingParamsSchema, pingResultSchema, launchPrepareParamsSchema, launchPrepareResultSchema, launchPermissionStateParamsSchema, launchPermissionStateResultSchema, sessionsListParamsSchema, sessionSummarySchema, sessionsListResultSchema, gitDiffParamsSchema, gitDiffResultSchema, gitCommitParamsSchema, gitCommitResultSchema, gitCreatePrParamsSchema, gitCreatePrResultSchema, fleetStatusParamsSchema, fleetStatusResultSchema, worktreesListParamsSchema, worktreesListResultSchema, blueprintsListParamsSchema, blueprintsListResultSchema, blueprintsExecParamsSchema, blueprintsExecResultSchema, deployRunParamsSchema, deployRunResultSchema, rustSpawnEnvMapSchema, rustSpawnEnvDeliverySchema, sessionExitNotificationSchema, jsonRpcIdSchema, jsonRpcRequestSchema, jsonRpcErrorObjectSchema, jsonRpcSuccessResponseSchema, jsonRpcErrorResponseSchema, jsonRpcResponseSchema, isDaemonRpcMethod, isUiRpcMethod, isRustChannelMethod, UI_RPC_METHODS, parseUiMethodParams, parseUiMethodResult, safeParseUiMethodParams, safeParseUiMethodResult, buildHandshakeResult, payloadHasForbiddenUiKeys, type OpaqueHandle, type PermissionMode, type ProviderId, type PeerRole, type ForbiddenUiPayloadKey, type HandshakeParams, type HandshakeResult, type DaemonRpcMethod, type PingParams, type PingResult, type LaunchPrepareParams, type LaunchPrepareResult, type LaunchPermissionStateParams, type LaunchPermissionStateResult, type SessionsListParams, type SessionSummary, type SessionsListResult, type GitDiffParams, type GitDiffResult, type GitCommitParams, type GitCommitResult, type GitCreatePrParams, type GitCreatePrResult, type RustSpawnEnvMap, type RustSpawnEnvDelivery, type RustChannelMethod, type JsonRpcId, type JsonRpcRequest, type JsonRpcResponse, type MethodSchemaPair, type UiMethodSchemas, } from "./contract.js";
8
+ export { DAEMON_PROTOCOL_VERSION, DAEMON_SESSION_MEMORY_SCHEMA_VERSION, DAEMON_RPC_METHODS, RUST_CHANNEL_METHODS, FORBIDDEN_UI_PAYLOAD_KEYS, UI_METHOD_SCHEMAS, opaqueHandleSchema, permissionModeSchema, providerIdSchema, peerRoleSchema, daemonRpcMethodSchema, handshakeParamsSchema, handshakeResultSchema, pingParamsSchema, pingResultSchema, launchPrepareParamsSchema, launchPrepareResultSchema, launchPermissionStateParamsSchema, launchPermissionStateResultSchema, sessionsListParamsSchema, sessionSummarySchema, sessionsListResultSchema, sessionsSearchParamsSchema, sessionSearchHitSchema, sessionsSearchResultSchema, gitDiffParamsSchema, gitDiffResultSchema, gitCommitParamsSchema, gitCommitResultSchema, gitCreatePrParamsSchema, gitCreatePrResultSchema, fleetStatusParamsSchema, fleetStatusResultSchema, worktreesListParamsSchema, worktreesListResultSchema, blueprintsListParamsSchema, blueprintsListResultSchema, blueprintsExecParamsSchema, blueprintsExecResultSchema, deployRunParamsSchema, deployRunResultSchema, rustSpawnEnvMapSchema, rustSpawnEnvDeliverySchema, sessionExitNotificationSchema, jsonRpcIdSchema, jsonRpcRequestSchema, jsonRpcErrorObjectSchema, jsonRpcSuccessResponseSchema, jsonRpcErrorResponseSchema, jsonRpcResponseSchema, isDaemonRpcMethod, isUiRpcMethod, isRustChannelMethod, UI_RPC_METHODS, parseUiMethodParams, parseUiMethodResult, safeParseUiMethodParams, safeParseUiMethodResult, buildHandshakeResult, payloadHasForbiddenUiKeys, type OpaqueHandle, type PermissionMode, type ProviderId, type PeerRole, type ForbiddenUiPayloadKey, type HandshakeParams, type HandshakeResult, type DaemonRpcMethod, type PingParams, type PingResult, type LaunchPrepareParams, type LaunchPrepareResult, type LaunchPermissionStateParams, type LaunchPermissionStateResult, type SessionsListParams, type SessionSummary, type SessionsListResult, type SessionsSearchParams, type SessionSearchHitResult, type SessionsSearchResult, type GitDiffParams, type GitDiffResult, type GitCommitParams, type GitCommitResult, type GitCreatePrParams, type GitCreatePrResult, type FleetStatusResult, type RustSpawnEnvMap, type RustSpawnEnvDelivery, type RustChannelMethod, type JsonRpcId, type JsonRpcRequest, type JsonRpcResponse, type MethodSchemaPair, type UiMethodSchemas, } from "./contract.js";
@@ -5,4 +5,4 @@
5
5
  * - `#daemon/protocol` (repo-internal package imports)
6
6
  * - `@webpresso/agent-kit/daemon/protocol` (published subpath)
7
7
  */
8
- export { DAEMON_PROTOCOL_VERSION, DAEMON_SESSION_MEMORY_SCHEMA_VERSION, DAEMON_RPC_METHODS, RUST_CHANNEL_METHODS, FORBIDDEN_UI_PAYLOAD_KEYS, UI_METHOD_SCHEMAS, opaqueHandleSchema, permissionModeSchema, providerIdSchema, peerRoleSchema, daemonRpcMethodSchema, handshakeParamsSchema, handshakeResultSchema, pingParamsSchema, pingResultSchema, launchPrepareParamsSchema, launchPrepareResultSchema, launchPermissionStateParamsSchema, launchPermissionStateResultSchema, sessionsListParamsSchema, sessionSummarySchema, sessionsListResultSchema, gitDiffParamsSchema, gitDiffResultSchema, gitCommitParamsSchema, gitCommitResultSchema, gitCreatePrParamsSchema, gitCreatePrResultSchema, fleetStatusParamsSchema, fleetStatusResultSchema, worktreesListParamsSchema, worktreesListResultSchema, blueprintsListParamsSchema, blueprintsListResultSchema, blueprintsExecParamsSchema, blueprintsExecResultSchema, deployRunParamsSchema, deployRunResultSchema, rustSpawnEnvMapSchema, rustSpawnEnvDeliverySchema, sessionExitNotificationSchema, jsonRpcIdSchema, jsonRpcRequestSchema, jsonRpcErrorObjectSchema, jsonRpcSuccessResponseSchema, jsonRpcErrorResponseSchema, jsonRpcResponseSchema, isDaemonRpcMethod, isUiRpcMethod, isRustChannelMethod, UI_RPC_METHODS, parseUiMethodParams, parseUiMethodResult, safeParseUiMethodParams, safeParseUiMethodResult, buildHandshakeResult, payloadHasForbiddenUiKeys, } from "./contract.js";
8
+ export { DAEMON_PROTOCOL_VERSION, DAEMON_SESSION_MEMORY_SCHEMA_VERSION, DAEMON_RPC_METHODS, RUST_CHANNEL_METHODS, FORBIDDEN_UI_PAYLOAD_KEYS, UI_METHOD_SCHEMAS, opaqueHandleSchema, permissionModeSchema, providerIdSchema, peerRoleSchema, daemonRpcMethodSchema, handshakeParamsSchema, handshakeResultSchema, pingParamsSchema, pingResultSchema, launchPrepareParamsSchema, launchPrepareResultSchema, launchPermissionStateParamsSchema, launchPermissionStateResultSchema, sessionsListParamsSchema, sessionSummarySchema, sessionsListResultSchema, sessionsSearchParamsSchema, sessionSearchHitSchema, sessionsSearchResultSchema, gitDiffParamsSchema, gitDiffResultSchema, gitCommitParamsSchema, gitCommitResultSchema, gitCreatePrParamsSchema, gitCreatePrResultSchema, fleetStatusParamsSchema, fleetStatusResultSchema, worktreesListParamsSchema, worktreesListResultSchema, blueprintsListParamsSchema, blueprintsListResultSchema, blueprintsExecParamsSchema, blueprintsExecResultSchema, deployRunParamsSchema, deployRunResultSchema, rustSpawnEnvMapSchema, rustSpawnEnvDeliverySchema, sessionExitNotificationSchema, jsonRpcIdSchema, jsonRpcRequestSchema, jsonRpcErrorObjectSchema, jsonRpcSuccessResponseSchema, jsonRpcErrorResponseSchema, jsonRpcResponseSchema, isDaemonRpcMethod, isUiRpcMethod, isRustChannelMethod, UI_RPC_METHODS, parseUiMethodParams, parseUiMethodResult, safeParseUiMethodParams, safeParseUiMethodResult, buildHandshakeResult, payloadHasForbiddenUiKeys, } from "./contract.js";
@@ -24,6 +24,12 @@ export type WebpressoHookBin = (typeof WEBPRESSO_HOOK_BINS)[number];
24
24
  export type HookRunResult = {
25
25
  readonly stdout: string;
26
26
  readonly exitCode: number | null;
27
+ /**
28
+ * Captured stderr, when the runner provides it. Claude reads stderr as the
29
+ * blocking feedback on an exit-2 PreToolUse deny, so the coded reason is
30
+ * asserted here when the stdout envelope carries none.
31
+ */
32
+ readonly stderr?: string;
27
33
  };
28
34
  type BaseRow = {
29
35
  readonly name: string;
@@ -110,6 +110,96 @@ function denyDecision(obj) {
110
110
  return "deny";
111
111
  return null;
112
112
  }
113
+ /** Extract the host-displayed deny reason from either envelope shape. */
114
+ function denyReasonText(obj) {
115
+ const hookSpecific = obj?.hookSpecificOutput;
116
+ if (hookSpecific && typeof hookSpecific === "object") {
117
+ const reason = hookSpecific.permissionDecisionReason;
118
+ if (typeof reason === "string")
119
+ return reason;
120
+ }
121
+ // Grok dual envelope carries a top-level reason.
122
+ if (typeof obj?.reason === "string")
123
+ return obj.reason;
124
+ return null;
125
+ }
126
+ const GUARD_CODED_REASON_RE = /^GUARD_[A-Z0-9_]+: /u;
127
+ /**
128
+ * Every PreToolUse deny reason on a parseable stdout envelope carries a stable
129
+ * `GUARD_<CODE>: ` prefix within the 80-char permissionDecisionReason budget.
130
+ * The code is the cross-host contract; the prose is truncated to fit.
131
+ *
132
+ * This reads `result.stdout` directly (NOT the decision-path `obj`, which is
133
+ * null for a Claude exit-2 deny) so the coded reason is asserted on BOTH hosts —
134
+ * pretool-guard always prints the deny envelope to stdout (dual for claude/grok,
135
+ * hookSpecificOutput-only for codex) even when Claude reads the exit code for the
136
+ * decision itself. Non-JSON stdout (the synthetic "stdout is ignored" fixture)
137
+ * and the empty-stdout exit-code-deny convention carry no reason and are skipped.
138
+ */
139
+ /** Parse an optional deny reason out of a JSON stdout deny envelope. */
140
+ function stdoutDenyReason(stdout) {
141
+ const text = stdout.trim();
142
+ if (text === "")
143
+ return null;
144
+ let parsed;
145
+ try {
146
+ parsed = JSON.parse(text);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ if (parsed === null || typeof parsed !== "object")
152
+ return null;
153
+ return denyReasonText(parsed);
154
+ }
155
+ /** First GUARD_-coded line in a captured stderr blob (Claude's exit-2 channel). */
156
+ function stderrCodedReason(stderr) {
157
+ if (!stderr)
158
+ return null;
159
+ for (const line of stderr.split("\n")) {
160
+ if (GUARD_CODED_REASON_RE.test(line.trim()))
161
+ return line.trim();
162
+ }
163
+ return null;
164
+ }
165
+ /** Assert a single reason is GUARD_-coded and within the 80-code-point budget. */
166
+ function assertCodedWithinBudget(row, label, reason) {
167
+ if (!GUARD_CODED_REASON_RE.test(reason)) {
168
+ throw new Error(`[${row.name}] ${label} must start with a GUARD_ code prefix, got ${JSON.stringify(reason)}`);
169
+ }
170
+ const points = [...reason].length;
171
+ if (points > 80) {
172
+ throw new Error(`[${row.name}] ${label} exceeds the 80-code-point budget (${points}): ${JSON.stringify(reason)}`);
173
+ }
174
+ }
175
+ function assertGuardCodedDenyReason(row, result) {
176
+ // The coded reason must reach the host on the channel it reads: the stdout
177
+ // deny envelope (Codex/Grok) or stderr (Claude reads stderr on an exit-2
178
+ // PreToolUse deny). Single-spawn tests capture the host boundary directly;
179
+ // the batch runner installs a row-scoped intentional-stderr capture so this
180
+ // same assertion also applies to source/compiled parity.
181
+ const reason = stdoutDenyReason(result.stdout) ?? stderrCodedReason(result.stderr);
182
+ if (reason === null) {
183
+ throw new Error(`[${row.name}] a PreToolUse deny must carry a GUARD_-coded reason on the stdout envelope or stderr; found none`);
184
+ }
185
+ assertCodedWithinBudget(row, "deny reason", reason);
186
+ }
187
+ function assertCodexExitTwoBlockingReason(row, result) {
188
+ if (row.host !== "codex" ||
189
+ row.event !== "PreToolUse" ||
190
+ result.exitCode !== 2 ||
191
+ result.stderr === undefined) {
192
+ return;
193
+ }
194
+ const stderrReason = stderrCodedReason(result.stderr);
195
+ if (stderrReason === null) {
196
+ throw new Error(`[${row.name}] Codex exit-2 PreToolUse deny must write a GUARD_-coded blocking reason to stderr`);
197
+ }
198
+ const stdoutReason = stdoutDenyReason(result.stdout);
199
+ if (stdoutReason !== null && stderrReason !== stdoutReason) {
200
+ throw new Error(`[${row.name}] Codex stdout and stderr deny reasons must match exactly`);
201
+ }
202
+ }
113
203
  /** PermissionRequest golden wire: hookSpecificOutput.decision.behavior allow|deny. */
114
204
  function permissionRequestBehavior(obj) {
115
205
  const hookSpecific = obj?.hookSpecificOutput;
@@ -121,6 +211,45 @@ function permissionRequestBehavior(obj) {
121
211
  const behavior = decision.behavior;
122
212
  return typeof behavior === "string" ? behavior : null;
123
213
  }
214
+ /** PermissionRequest golden wire message: hookSpecificOutput.decision.message. */
215
+ function permissionRequestMessage(obj) {
216
+ const hookSpecific = obj?.hookSpecificOutput;
217
+ if (!hookSpecific || typeof hookSpecific !== "object")
218
+ return null;
219
+ const decision = hookSpecific.decision;
220
+ if (!decision || typeof decision !== "object")
221
+ return null;
222
+ const message = decision.message;
223
+ return typeof message === "string" ? message : null;
224
+ }
225
+ /**
226
+ * A PermissionRequest allow/deny message on a parseable envelope carries the
227
+ * same `GUARD_<CODE>: ` prefix within the 80-char budget as PreToolUse denies —
228
+ * the vocabulary is central, not PreToolUse-only. Skipped when there is no
229
+ * envelope message (e.g. a pure exit-2 deny with no stdout).
230
+ */
231
+ function assertGuardCodedPermissionMessage(row, result, expect) {
232
+ let stdoutMessage = null;
233
+ const text = result.stdout.trim();
234
+ if (text !== "") {
235
+ try {
236
+ const parsed = JSON.parse(text);
237
+ if (parsed !== null && typeof parsed === "object") {
238
+ stdoutMessage = permissionRequestMessage(parsed);
239
+ }
240
+ }
241
+ catch {
242
+ stdoutMessage = null;
243
+ }
244
+ }
245
+ // A deny exits 2 and also carries the coded message on stderr; an allow exits 0
246
+ // with the coded message on the stdout envelope.
247
+ const message = stdoutMessage ?? stderrCodedReason(result.stderr);
248
+ if (message === null) {
249
+ throw new Error(`[${row.name}] PermissionRequest ${expect} must carry a GUARD_-coded message on the stdout envelope or stderr; found none`);
250
+ }
251
+ assertCodedWithinBudget(row, "PermissionRequest message", message);
252
+ }
124
253
  function assertNoCodexUnsupportedFields(row, obj) {
125
254
  if (row.host !== "codex" || obj === null)
126
255
  return;
@@ -170,6 +299,11 @@ export function assertConformance(row, result) {
170
299
  // exit-code deny convention). A deny envelope printed just before the hook
171
300
  // crashes (exit 1) is not a valid decision.
172
301
  assertExitCode(row, result, true);
302
+ // Every deny reason is GUARD_-coded and within the 80-char budget on
303
+ // both Tier-1 hosts (read from stdout, which carries the envelope even
304
+ // when Claude uses the exit code for the decision).
305
+ assertGuardCodedDenyReason(row, result);
306
+ assertCodexExitTwoBlockingReason(row, result);
173
307
  }
174
308
  else {
175
309
  if (decision === "deny") {
@@ -196,6 +330,8 @@ export function assertConformance(row, result) {
196
330
  if (result.exitCode !== 2) {
197
331
  throw new Error(`[${row.name}] expected PermissionRequest deny via exit 2, got exit ${result.exitCode ?? "null"}`);
198
332
  }
333
+ // The deny message carries the central GUARD_ code contract too.
334
+ assertGuardCodedPermissionMessage(row, result, "deny");
199
335
  return;
200
336
  }
201
337
  if (row.expect === "allow") {
@@ -203,6 +339,7 @@ export function assertConformance(row, result) {
203
339
  throw new Error(`[${row.name}] expected PermissionRequest allow envelope, got behavior=${behavior ?? "none"} (stdout: ${result.stdout.slice(0, 160)})`);
204
340
  }
205
341
  assertExitCode(row, result, false);
342
+ assertGuardCodedPermissionMessage(row, result, "allow");
206
343
  return;
207
344
  }
208
345
  // pass-through: host default UI — empty object (or empty stdout), no decision, exit 0.
@@ -85,6 +85,7 @@ export async function runHookConformanceBatch() {
85
85
  }
86
86
  const raw = await readBatchStdin();
87
87
  const { isHookName, runHookCommand } = await import("#cli/commands/hook.js");
88
+ const { installGuardStderrCapture } = await import("#hooks/shared/hook-bootstrap");
88
89
  const payload = parseBatchPayload(raw, isHookName);
89
90
  const protocolWrite = process.stdout.write.bind(process.stdout);
90
91
  for (const row of payload.rows) {
@@ -92,6 +93,9 @@ export async function runHookConformanceBatch() {
92
93
  let stdout = "";
93
94
  let stderr = "";
94
95
  const restoreEnv = setRowEnvironment(row);
96
+ const restoreGuardStderrCapture = installGuardStderrCapture((text) => {
97
+ stderr += text;
98
+ });
95
99
  const originalStdoutWrite = process.stdout.write;
96
100
  const originalStderrWrite = process.stderr.write;
97
101
  process.stdout.write = (chunk) => {
@@ -112,6 +116,7 @@ export async function runHookConformanceBatch() {
112
116
  finally {
113
117
  process.stdout.write = originalStdoutWrite;
114
118
  process.stderr.write = originalStderrWrite;
119
+ restoreGuardStderrCapture();
115
120
  restoreEnv();
116
121
  }
117
122
  protocolWrite(`${JSON.stringify({ type: "result", id: row.id, stdout, stderr, exitCode })}\n`);
@@ -124,9 +124,9 @@ export interface ClaudeMcpDuplicateRegistrationOptions {
124
124
  /**
125
125
  * `claude-mcp-duplicate-registration` — advisory. A machine can register the
126
126
  * webpresso MCP server twice for the same Claude Code session: a manual
127
- * `claude mcp add webpresso` entry in `~/.claude.json` (user scope or a
128
- * project scope) plus the agent-kit marketplace plugin's bundled server
129
- * (plugin cache under `~/.claude/plugins/`). Both spawn `wp mcp`, so affected
127
+ * `claude mcp add webpresso` entry in Claude Code's home JSON config (user
128
+ * scope or a project scope) plus the agent-kit marketplace plugin's bundled
129
+ * server from the Claude plugin cache. Both spawn `wp mcp`, so affected
130
130
  * sessions run two server processes and carry a duplicated wp_* tool list.
131
131
  * Degrades to ok on missing/unreadable config — loud only on real duplication.
132
132
  */
@@ -658,11 +658,15 @@ function probeDecisionRow(wpCli, row) {
658
658
  detached: process.platform !== "win32",
659
659
  });
660
660
  let stdout = "";
661
+ let stderr = "";
661
662
  const timeoutMs = hookProbeTimeoutMs();
662
663
  const settle = createOwnedProbeSettler(child, timeoutMs, () => ({ ok: false, detail: `decision probe timed out after ${timeoutMs}ms` }), resolve);
663
664
  child.stdout.on("data", (chunk) => {
664
665
  stdout += String(chunk);
665
666
  });
667
+ child.stderr.on("data", (chunk) => {
668
+ stderr += String(chunk);
669
+ });
666
670
  child.stdin.on?.("error", (err) => {
667
671
  settle({ ok: false, detail: `stdin write failed: ${err.message}` });
668
672
  });
@@ -674,7 +678,7 @@ function probeDecisionRow(wpCli, row) {
674
678
  });
675
679
  child.on("close", (code) => {
676
680
  try {
677
- assertConformance(row, { stdout, exitCode: code });
681
+ assertConformance(row, { stdout, stderr, exitCode: code });
678
682
  settle({ ok: true });
679
683
  }
680
684
  catch (error) {
@@ -1256,13 +1260,15 @@ async function checkClaudeHost() {
1256
1260
  }
1257
1261
  async function checkGrokHost(cwd = process.cwd()) {
1258
1262
  const available = await runCommand("grok", ["--version"]);
1259
- const hooksPath = join(cwd, ".grok", "hooks.json");
1263
+ const hooksPath = join(cwd, ".grok", "hooks", "webpresso.json");
1260
1264
  let hooksPresent = false;
1261
1265
  let hostLabeled = false;
1262
1266
  try {
1263
1267
  const body = readFileSync(hooksPath, "utf8");
1264
1268
  hooksPresent = true;
1265
- hostLabeled = body.includes("WP_HOOK_HOST=grok");
1269
+ // The generated launcher chain shell-quotes the value (WP_HOOK_HOST='grok');
1270
+ // accept any quoting variant so the doctor recognizes its own output.
1271
+ hostLabeled = /WP_HOOK_HOST=(['"]?)grok\1/u.test(body);
1266
1272
  }
1267
1273
  catch {
1268
1274
  // Missing project hooks is advisory when binary absent; fail when binary present.
@@ -1273,8 +1279,8 @@ async function checkGrokHost(cwd = process.cwd()) {
1273
1279
  ok: true,
1274
1280
  detail: hooksPresent
1275
1281
  ? hostLabeled
1276
- ? "skipped (grok not on PATH); native .grok/hooks.json present with WP_HOOK_HOST=grok"
1277
- : "skipped (grok not on PATH); .grok/hooks.json present but missing WP_HOOK_HOST=grok"
1282
+ ? "skipped (grok not on PATH); native .grok/hooks/webpresso.json present with WP_HOOK_HOST=grok"
1283
+ : "skipped (grok not on PATH); .grok/hooks/webpresso.json present but missing WP_HOOK_HOST=grok"
1278
1284
  : "skipped (grok not on PATH)",
1279
1285
  };
1280
1286
  }
@@ -1282,20 +1288,20 @@ async function checkGrokHost(cwd = process.cwd()) {
1282
1288
  return {
1283
1289
  name: "Grok host integration",
1284
1290
  ok: false,
1285
- detail: "grok on PATH but missing project .grok/hooks.json — run wp setup to scaffold native hooks",
1291
+ detail: "grok on PATH but missing project .grok/hooks/webpresso.json — run wp setup to scaffold native hooks, then run /hooks-trust once in Grok",
1286
1292
  };
1287
1293
  }
1288
1294
  if (!hostLabeled) {
1289
1295
  return {
1290
1296
  name: "Grok host integration",
1291
1297
  ok: false,
1292
- detail: ".grok/hooks.json present but managed commands lack WP_HOOK_HOST=grok",
1298
+ detail: ".grok/hooks/webpresso.json present but managed commands lack WP_HOOK_HOST=grok",
1293
1299
  };
1294
1300
  }
1295
1301
  return {
1296
1302
  name: "Grok host integration",
1297
1303
  ok: true,
1298
- detail: "native .grok/hooks.json with WP_HOOK_HOST=grok (PreToolUse fail-open residual applies)",
1304
+ detail: "native .grok/hooks/webpresso.json with WP_HOOK_HOST=grok (trust-gated: run /hooks-trust once; PreToolUse fail-open residual applies)",
1299
1305
  };
1300
1306
  }
1301
1307
  // Markers for the direct hook commands `wp setup` writes into
@@ -1793,9 +1799,9 @@ export function checkProjectMcpPins(cwd = process.cwd(), deps = {}) {
1793
1799
  /**
1794
1800
  * `claude-mcp-duplicate-registration` — advisory. A machine can register the
1795
1801
  * webpresso MCP server twice for the same Claude Code session: a manual
1796
- * `claude mcp add webpresso` entry in `~/.claude.json` (user scope or a
1797
- * project scope) plus the agent-kit marketplace plugin's bundled server
1798
- * (plugin cache under `~/.claude/plugins/`). Both spawn `wp mcp`, so affected
1802
+ * `claude mcp add webpresso` entry in Claude Code's home JSON config (user
1803
+ * scope or a project scope) plus the agent-kit marketplace plugin's bundled
1804
+ * server from the Claude plugin cache. Both spawn `wp mcp`, so affected
1799
1805
  * sessions run two server processes and carry a duplicated wp_* tool list.
1800
1806
  * Degrades to ok on missing/unreadable config — loud only on real duplication.
1801
1807
  */
@@ -1810,17 +1816,17 @@ export function checkClaudeMcpDuplicateRegistration(options = {}) {
1810
1816
  }
1811
1817
  const claudeJsonPath = join(home, ".claude.json");
1812
1818
  if (!exists(claudeJsonPath)) {
1813
- return { name, ok: true, detail: "skipped (no ~/.claude.json)" };
1819
+ return { name, ok: true, detail: "skipped (no Claude Code home JSON config)" };
1814
1820
  }
1815
1821
  let parsed;
1816
1822
  try {
1817
1823
  parsed = JSON.parse(readFile(claudeJsonPath));
1818
1824
  }
1819
1825
  catch {
1820
- return { name, ok: true, detail: "skipped (~/.claude.json unreadable)" };
1826
+ return { name, ok: true, detail: "skipped (Claude Code home JSON config unreadable)" };
1821
1827
  }
1822
1828
  if (parsed === null || typeof parsed !== "object") {
1823
- return { name, ok: true, detail: "skipped (~/.claude.json is not an object)" };
1829
+ return { name, ok: true, detail: "skipped (Claude Code home JSON config is not an object)" };
1824
1830
  }
1825
1831
  const config = parsed;
1826
1832
  const manualSources = [];
@@ -1839,7 +1845,7 @@ export function checkClaudeMcpDuplicateRegistration(options = {}) {
1839
1845
  name,
1840
1846
  ok: false,
1841
1847
  advisory: true,
1842
- detail: `the agent-kit Claude plugin bundles the webpresso MCP server, but ~/.claude.json also ` +
1848
+ detail: `the agent-kit Claude plugin bundles the webpresso MCP server, but Claude Code's home JSON config also ` +
1843
1849
  `registers "webpresso" manually (${manualSources.join(", ")}). Affected sessions run two ` +
1844
1850
  `wp mcp processes with a duplicated wp_* tool list. Remove the manual entry: ` +
1845
1851
  `claude mcp remove webpresso (run inside the project directory for project-scope entries).`,
@@ -2142,6 +2148,15 @@ export async function runHooksDoctor(opts = {}) {
2142
2148
  detail: "claude required but not on PATH",
2143
2149
  });
2144
2150
  }
2151
+ if (host === "grok") {
2152
+ const available = await runCommand("grok", ["--version"]);
2153
+ if (!available.ok)
2154
+ checks.push({
2155
+ name: "Grok host integration",
2156
+ ok: false,
2157
+ detail: "grok required but not on PATH",
2158
+ });
2159
+ }
2145
2160
  }
2146
2161
  }
2147
2162
  checks.push(checkHooksManifest(opts.cwd));
@@ -9,7 +9,8 @@
9
9
  * Stdin: uses shared `readStdinJson` so conformance-batch can inject payloads via
10
10
  * `WP_CONFORMANCE_STDIN` (same contract as other managed hooks).
11
11
  */
12
- import { readStdinJson, writeHookOutput } from "#hooks/shared/hook-bootstrap";
12
+ import { formatGuardReason } from "#hooks/shared/guard-outcome";
13
+ import { readStdinJson, writeGuardStderr, writeHookOutput } from "#hooks/shared/hook-bootstrap";
13
14
  import { buildPermissionRequestEnvelope } from "./envelope.js";
14
15
  import { evaluatePermissionRequest } from "./policy.js";
15
16
  function isRecord(value) {
@@ -53,9 +54,14 @@ export async function main() {
53
54
  return;
54
55
  }
55
56
  // Claude + Codex share the golden PermissionRequest wire shape
56
- // (hookSpecificOutput.hookEventName + decision.behavior).
57
- writeHookOutput(JSON.stringify(buildPermissionRequestEnvelope(result.decision, result.reason)));
57
+ // (hookSpecificOutput.hookEventName + decision.behavior). The wire message
58
+ // carries the GUARD_* code so allow/deny decisions are keyable across hosts.
59
+ const codedMessage = formatGuardReason(result.code, result.reason);
60
+ writeHookOutput(JSON.stringify(buildPermissionRequestEnvelope(result.decision, codedMessage)));
58
61
  if (result.decision === "deny") {
62
+ // A deny exits 2; deliver the coded message to the real stderr too so it is
63
+ // observable even when a host prefers the exit-code channel.
64
+ writeGuardStderr(`${codedMessage}\n`);
59
65
  // Must use process.exit so runHookCommand's synthetic-exit trap records exit 2.
60
66
  process.exit(2);
61
67
  }
@@ -5,6 +5,7 @@
5
5
  * authoritative when policy is uncertain. Explicit deny only for high-confidence
6
6
  * unsafe escalations; explicit allow only for clearly safe read-only escalations.
7
7
  */
8
+ import type { GuardReasonCode } from "#hooks/shared/guard-outcome";
8
9
  export type PermissionRequestDecision = "allow" | "deny";
9
10
  export type PermissionRequestPolicyInput = {
10
11
  readonly toolName?: string;
@@ -15,13 +16,21 @@ export type PermissionRequestPolicyInput = {
15
16
  export type PermissionRequestPolicyResult = {
16
17
  readonly decision: "allow";
17
18
  readonly reason: string;
19
+ readonly code: GuardReasonCode;
18
20
  } | {
19
21
  readonly decision: "deny";
20
22
  readonly reason: string;
23
+ readonly code: GuardReasonCode;
21
24
  } | {
22
25
  readonly decision: undefined;
23
26
  readonly reason: string;
27
+ readonly code: GuardReasonCode;
24
28
  };
29
+ /**
30
+ * Every GUARD_* code the permission-request policy can emit. Consumed by the
31
+ * guard-outcome registry-completeness test.
32
+ */
33
+ export declare const PERMISSION_GUARD_CODES: readonly ["GUARD_PERMISSION_ALLOWED", "GUARD_PERMISSION_DENIED", "GUARD_HOST_DEFAULT"];
25
34
  /**
26
35
  * Decide allow / deny / pass-through for a PermissionRequest payload.
27
36
  */