failproofai 0.0.10-beta.2 → 0.0.10-beta.4

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.
Files changed (123) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  21. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  24. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  27. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -2
  29. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/index.html +1 -1
  31. package/.next/standalone/.next/server/app/index.rsc +15 -15
  32. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  34. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  35. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js +2 -2
  39. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  42. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  43. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  47. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -3
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/projects/page.js +2 -2
  56. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06.arfm._.js +3 -0
  59. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0__i0h0._.js +3 -0
  60. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0dtn9lr._.js → [root-of-the-server]__0d_ob4n._.js} +2 -2
  61. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fe7_q_._.js +3 -0
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fjhqi9._.js +3 -0
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fw.e.h._.js +3 -0
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kjo7d_._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0pxn0e1._.js +3 -0
  66. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xv0jh2._.js +3 -0
  67. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__010i6f5._.js → [root-of-the-server]__0z-180.._.js} +2 -2
  68. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0dl0kgt._.js → [root-of-the-server]__01hj~sd._.js} +3 -3
  70. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0s~gy6y._.js → [root-of-the-server]__02r6nu-._.js} +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bz245.._.js → [root-of-the-server]__04dywib._.js} +3 -3
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0gmhxyo._.js → [root-of-the-server]__06sb2gn._.js} +3 -3
  73. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0tt8-uq._.js → [root-of-the-server]__092oklx._.js} +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0-2wr.c._.js → [root-of-the-server]__09jpajs._.js} +3 -3
  75. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ils1oq._.js → [root-of-the-server]__0bemgh7._.js} +2 -2
  76. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qbpe_v._.js → [root-of-the-server]__0jm6jnh._.js} +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0709m8.._.js → [root-of-the-server]__0joez.y._.js} +3 -3
  78. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ohb3gc._.js → [root-of-the-server]__0mi5ejy._.js} +3 -3
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u1i~9~._.js +4 -0
  80. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09y9okz._.js → [root-of-the-server]__0x5limi._.js} +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +5 -3
  82. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  86. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  87. package/.next/standalone/.next/server/pages/404.html +2 -2
  88. package/.next/standalone/.next/server/pages/500.html +1 -1
  89. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  90. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  91. package/.next/standalone/.next/static/chunks/{0yzl~f-qji0...js → 0csfsu88oqir8.js} +2 -2
  92. package/.next/standalone/.next/static/chunks/{0y8oyen~jnjl3.js → 0fvemi0q_n349.js} +1 -1
  93. package/.next/standalone/.next/static/chunks/{0a_w-lcg.0tl7.js → 0ilx6hm27porg.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/0kxi65pnos15e.js +1 -0
  95. package/.next/standalone/.next/static/chunks/{0-dltnti0ew4y.js → 12mx5dzs_vf1w.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{17o7-pn_xzt-t.js → 14p_4ajxz0ifs.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{0lscbfo_2_h14.js → 16ydic9u8kagt.js} +2 -2
  98. package/.next/standalone/.next/static/chunks/{02h_cfxpk4x9..js → 18daqyhykpw8z.js} +1 -1
  99. package/.next/standalone/app/api/download/[project]/[session]/route.ts +26 -10
  100. package/.next/standalone/app/components/session-hooks-panel.tsx +3 -0
  101. package/.next/standalone/app/policies/hooks-client.tsx +3 -0
  102. package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +8 -10
  103. package/.next/standalone/lib/download-session.ts +92 -0
  104. package/.next/standalone/lib/opencode-sessions.ts +42 -0
  105. package/.next/standalone/package.json +1 -1
  106. package/.next/standalone/server.js +1 -1
  107. package/dist/cli.mjs +1281 -305
  108. package/lib/download-session.ts +92 -0
  109. package/lib/opencode-sessions.ts +42 -0
  110. package/package.json +1 -1
  111. package/src/hooks/builtin-policies.ts +4 -3
  112. package/src/hooks/handler.ts +22 -12
  113. package/src/hooks/integrations.ts +2 -0
  114. package/src/hooks/policy-evaluator.ts +47 -6
  115. package/src/hooks/resolve-transcript-path.ts +69 -0
  116. package/src/hooks/types.ts +73 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b57.gk._.js +0 -3
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z4c5dj._.js +0 -3
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0.~m-w2._.js +0 -4
  120. package/.next/standalone/.next/static/chunks/16k5y9ruha2v4.js +0 -1
  121. /package/.next/standalone/.next/static/{EBbXdrT2rHib3zGLSuY7Q → Z3pK8z9QuEdJFE28LwB5Y}/_buildManifest.js +0 -0
  122. /package/.next/standalone/.next/static/{EBbXdrT2rHib3zGLSuY7Q → Z3pK8z9QuEdJFE28LwB5Y}/_clientMiddlewareManifest.js +0 -0
  123. /package/.next/standalone/.next/static/{EBbXdrT2rHib3zGLSuY7Q → Z3pK8z9QuEdJFE28LwB5Y}/_ssgManifest.js +0 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Per-CLI dispatcher for the dashboard's Download Logs endpoint.
3
+ *
4
+ * Returns either a real on-disk path (so the route can `createReadStream` and
5
+ * stream the bytes verbatim) or a synthesized JSONL body (used only by
6
+ * OpenCode, whose transcripts live in SQLite rather than on disk).
7
+ *
8
+ * Per-CLI session loaders / transcript finders already exist in their own
9
+ * files; this module is the thin glue that picks the right one. Imports of
10
+ * the per-CLI modules are dynamic so callers that only need the dispatch
11
+ * type can avoid pulling Node-only deps into client bundles.
12
+ */
13
+ import { resolveSessionFilePath, UUID_RE } from "./projects";
14
+ import type { CliId } from "./cli-registry";
15
+
16
+ export const OPENCODE_SESSION_RE = /^ses_[A-Za-z0-9]+$/;
17
+
18
+ export type DownloadSource =
19
+ | { kind: "file"; path: string }
20
+ | { kind: "synthesized"; body: string; contentType: string; extension: string };
21
+
22
+ /** Validate a session ID against the per-CLI shape. OpenCode uses `ses_*`
23
+ * prefixes; everyone else is a UUID. */
24
+ export function isValidSessionId(cli: CliId, sessionId: string): boolean {
25
+ if (cli === "opencode") return OPENCODE_SESSION_RE.test(sessionId);
26
+ return UUID_RE.test(sessionId);
27
+ }
28
+
29
+ /**
30
+ * Resolve a download source for `(cli, sessionId)`. `project` is only
31
+ * meaningful for Claude (path-traversal validation under the projects root);
32
+ * the other CLIs key sessions by ID alone.
33
+ *
34
+ * Returns `null` when the session can't be located. Throws RangeError when
35
+ * the inputs fail validation (caller should map to 400).
36
+ */
37
+ export async function resolveDownloadSource(
38
+ cli: CliId,
39
+ project: string,
40
+ sessionId: string,
41
+ ): Promise<DownloadSource | null> {
42
+ if (!isValidSessionId(cli, sessionId)) {
43
+ throw new RangeError("Invalid session ID");
44
+ }
45
+
46
+ if (cli === "claude") {
47
+ // resolveSessionFilePath validates project and joins under the Claude root.
48
+ return { kind: "file", path: resolveSessionFilePath(project, sessionId) };
49
+ }
50
+
51
+ if (cli === "codex") {
52
+ const { findCodexTranscript } = await import("./codex-sessions");
53
+ const path = findCodexTranscript(sessionId);
54
+ return path ? { kind: "file", path } : null;
55
+ }
56
+ if (cli === "copilot") {
57
+ const { findCopilotTranscript } = await import("./copilot-sessions");
58
+ const path = findCopilotTranscript(sessionId);
59
+ return path ? { kind: "file", path } : null;
60
+ }
61
+ if (cli === "cursor") {
62
+ const { findCursorTranscript } = await import("./cursor-sessions");
63
+ const path = findCursorTranscript(sessionId);
64
+ return path ? { kind: "file", path } : null;
65
+ }
66
+ if (cli === "pi") {
67
+ const { findPiTranscript } = await import("./pi-sessions");
68
+ const path = findPiTranscript(sessionId);
69
+ return path ? { kind: "file", path } : null;
70
+ }
71
+ if (cli === "gemini") {
72
+ const { findGeminiTranscript } = await import("./gemini-sessions");
73
+ const path = findGeminiTranscript(sessionId);
74
+ return path ? { kind: "file", path } : null;
75
+ }
76
+ if (cli === "opencode") {
77
+ // OpenCode keeps sessions in SQLite (~/.local/share/opencode/opencode.db)
78
+ // across three tables: session / message / part. Export all three so
79
+ // users get a faithful snapshot of the underlying structure rather than
80
+ // a collapsed single-stream that loses parts.
81
+ const { getOpenCodeSessionExport } = await import("./opencode-sessions");
82
+ const result = await getOpenCodeSessionExport(sessionId);
83
+ if (!result) return null;
84
+ const body = JSON.stringify(result, null, 2) + "\n";
85
+ return { kind: "synthesized", body, contentType: "application/json", extension: "json" };
86
+ }
87
+
88
+ // Exhaustive — but TypeScript can't always see CliId is exhausted across the
89
+ // if-chain above, so guard with a runtime fallback.
90
+ const _exhaustive: never = cli;
91
+ return _exhaustive;
92
+ }
@@ -235,3 +235,45 @@ export const getCachedOpenCodeSessionLog = runtimeCache(
235
235
  30,
236
236
  { maxSize: 50 },
237
237
  );
238
+
239
+ // ── Export shape ──
240
+
241
+ /**
242
+ * Snapshot of an OpenCode session as it lives in the SQLite store: one
243
+ * `session` row, all of its `message` rows, all of its `part` rows. The JSON
244
+ * `data` column on each message/part is parsed (when valid) so consumers
245
+ * don't have to double-parse; malformed JSON survives as the raw string.
246
+ *
247
+ * Used by the dashboard's Download Logs feature so the export mirrors the
248
+ * underlying schema rather than collapsing parts into a single per-message
249
+ * record (which is what the viewer-facing `getOpenCodeSessionLog` does).
250
+ */
251
+ export interface OpenCodeSessionExportData {
252
+ session: OpenCodeSessionRow;
253
+ messages: Array<Omit<OpenCodeMessageRow, "data"> & { data: Record<string, unknown> | string | null }>;
254
+ parts: Array<Omit<OpenCodePartRow, "data"> & { data: Record<string, unknown> | string | null }>;
255
+ }
256
+
257
+ export async function getOpenCodeSessionExport(sessionId: string): Promise<OpenCodeSessionExportData | null> {
258
+ if (!sessionId || !/^[A-Za-z0-9_-]+$/.test(sessionId)) return null;
259
+ const sessions = runOpenCodeDb<OpenCodeSessionRow>(
260
+ `SELECT id, project_id, slug, directory, title, time_created, time_updated FROM session WHERE id = '${sessionId}'`,
261
+ );
262
+ if (!sessions || sessions.length === 0) return null;
263
+ // Don't coalesce a `null` return (query failure) into `[]` — that would
264
+ // silently serve an "empty session" export. A genuinely empty session has
265
+ // an `[]` from the DB; a query failure has `null`. Treat the latter as
266
+ // not-found so the route returns 404 rather than a misleading 200.
267
+ const messages = runOpenCodeDb<OpenCodeMessageRow>(
268
+ `SELECT id, session_id, time_created, time_updated, data FROM message WHERE session_id = '${sessionId}' ORDER BY time_created ASC`,
269
+ );
270
+ const parts = runOpenCodeDb<OpenCodePartRow>(
271
+ `SELECT id, message_id, session_id, time_created, time_updated, data FROM part WHERE session_id = '${sessionId}' ORDER BY time_created ASC`,
272
+ );
273
+ if (messages === null || parts === null) return null;
274
+ return {
275
+ session: sessions[0],
276
+ messages: messages.map((m) => ({ ...m, data: parseDataColumn(m.data) ?? m.data })),
277
+ parts: parts.map((p) => ({ ...p, data: parseDataColumn(p.data) ?? p.data })),
278
+ };
279
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.10-beta.2",
3
+ "version": "0.0.10-beta.4",
4
4
  "description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs"
@@ -180,7 +180,7 @@ const SECRET_FILE_ID_RSA_RE = /id_rsa/;
180
180
  const SECRET_FILE_CREDENTIALS_RE = /credentials/;
181
181
 
182
182
  // blockWorkOnMain
183
- const GIT_COMMIT_MERGE_RE = /git\s+(?:commit|merge|rebase|cherry-pick)\b/;
183
+ const GIT_COMMIT_MERGE_RE = /git\s+(commit|merge|rebase|cherry-pick)\b/;
184
184
 
185
185
  // blockFailproofaiCommands
186
186
  const FAILPROOFAI_CLI_RE = /(?:^|;|&&|\|\||\|)\s*failproofai(?:\s|$)/;
@@ -822,7 +822,8 @@ function blockReadOutsideCwd(ctx: PolicyContext): PolicyResult {
822
822
  function blockWorkOnMain(ctx: PolicyContext): PolicyResult {
823
823
  if (ctx.toolName !== "Bash") return allow();
824
824
  const cmd = getCommand(ctx);
825
- if (!GIT_COMMIT_MERGE_RE.test(cmd)) return allow();
825
+ const match = cmd.match(GIT_COMMIT_MERGE_RE);
826
+ if (!match) return allow();
826
827
 
827
828
  const cwd = ctx.session?.cwd;
828
829
  if (!cwd) return allow();
@@ -833,7 +834,7 @@ function blockWorkOnMain(ctx: PolicyContext): PolicyResult {
833
834
  const protectedBranches = ((ctx.params?.protectedBranches ?? ["main", "master"]) as string[]);
834
835
  if (protectedBranches.includes(branch)) {
835
836
  return deny(
836
- `Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`,
837
+ `Git ${match[1]} on ${branch} is blocked. Create a feature branch first.`,
837
838
  );
838
839
  }
839
840
  return allow();
@@ -14,7 +14,16 @@ import type {
14
14
  PiHookEventType,
15
15
  GeminiHookEventType,
16
16
  } from "./types";
17
- import { CODEX_EVENT_MAP, CURSOR_EVENT_MAP, PI_EVENT_MAP, GEMINI_EVENT_MAP, GEMINI_TOOL_MAP, COPILOT_TOOL_MAP } from "./types";
17
+ import {
18
+ CODEX_EVENT_MAP,
19
+ CURSOR_EVENT_MAP,
20
+ PI_EVENT_MAP,
21
+ GEMINI_EVENT_MAP,
22
+ GEMINI_TOOL_MAP,
23
+ COPILOT_TOOL_MAP,
24
+ CURSOR_TOOL_MAP,
25
+ CODEX_TOOL_MAP,
26
+ } from "./types";
18
27
  import type { PolicyFunction, PolicyResult } from "./policy-types";
19
28
  import { readMergedHooksConfig } from "./hooks-config";
20
29
  import { registerBuiltinPolicies } from "./builtin-policies";
@@ -25,6 +34,7 @@ import type { CustomHook } from "./policy-types";
25
34
  import { persistHookActivity } from "./hook-activity-store";
26
35
  import { trackHookEvent } from "./hook-telemetry";
27
36
  import { resolvePermissionMode } from "./resolve-permission-mode";
37
+ import { resolveTranscriptPath } from "./resolve-transcript-path";
28
38
  import { getInstanceId } from "../../lib/telemetry-id";
29
39
  import { hookLogInfo, hookLogWarn } from "./hook-logger";
30
40
 
@@ -70,10 +80,12 @@ function canonicalizeEventType(raw: string, cli: IntegrationType): HookEventType
70
80
  *
71
81
  * Per-CLI tool-name shapes (verified from in-repo evidence and vendor docs):
72
82
  * • Claude: PascalCase native — passthrough
73
- * • Codex: PascalCase per current install — passthrough (no map until
74
- * empirical evidence shows otherwise)
75
- * • Copilot: lowercase IDs (`bash`, `read`, …) — COPILOT_TOOL_MAP
76
- * • Cursor: PascalCase per Cursor docs passthrough
83
+ * • Codex: `Bash` PascalCase passthrough; `apply_patch` `Edit`,
84
+ * `write_stdin` `Bash` via CODEX_TOOL_MAP
85
+ * • Copilot: lowercase IDs (`bash`, `read`, `view`, …) — COPILOT_TOOL_MAP
86
+ * • Cursor: PascalCase per Cursor docs but uses `Shell` for the bash-
87
+ * equivalent — CURSOR_TOOL_MAP rewrites `Shell → Bash`; other
88
+ * tool names already canonical and pass through
77
89
  * • OpenCode: handled in the OpenCode plugin shim (in-process,
78
90
  * self-contained) before the JSON crosses to this binary
79
91
  * • Pi: handled in the Pi extension shim (same)
@@ -84,12 +96,10 @@ function canonicalizeEventType(raw: string, cli: IntegrationType): HookEventType
84
96
  */
85
97
  function canonicalizeToolName(raw: string | undefined, cli: IntegrationType): string | undefined {
86
98
  if (!raw) return raw;
87
- if (cli === "copilot") {
88
- return COPILOT_TOOL_MAP[raw] ?? raw;
89
- }
90
- if (cli === "gemini") {
91
- return GEMINI_TOOL_MAP[raw] ?? raw;
92
- }
99
+ if (cli === "copilot") return COPILOT_TOOL_MAP[raw] ?? raw;
100
+ if (cli === "cursor") return CURSOR_TOOL_MAP[raw] ?? raw;
101
+ if (cli === "codex") return CODEX_TOOL_MAP[raw] ?? raw;
102
+ if (cli === "gemini") return GEMINI_TOOL_MAP[raw] ?? raw;
93
103
  return raw;
94
104
  }
95
105
 
@@ -153,7 +163,7 @@ export async function handleHookEvent(
153
163
  const sessionId = parsed.session_id as string | undefined;
154
164
  const session: SessionMetadata = {
155
165
  sessionId,
156
- transcriptPath: parsed.transcript_path as string | undefined,
166
+ transcriptPath: resolveTranscriptPath(cli, parsed, sessionId),
157
167
  cwd: parsed.cwd as string | undefined,
158
168
  permissionMode: resolvePermissionMode(cli, parsed, sessionId),
159
169
  hookEventName: parsed.hook_event_name as string | undefined,
@@ -736,10 +736,12 @@ const TOOL_NAME_MAP = {
736
736
  read: "Read",
737
737
  write: "Write",
738
738
  edit: "Edit",
739
+ apply_patch: "Edit",
739
740
  glob: "Glob",
740
741
  grep: "Grep",
741
742
  list: "LS",
742
743
  webfetch: "WebFetch",
744
+ websearch: "WebSearch",
743
745
  todowrite: "TodoWrite",
744
746
  todoread: "TodoRead",
745
747
  };
@@ -264,11 +264,32 @@ export async function evaluatePolicies(
264
264
  };
265
265
  }
266
266
 
267
- if (eventType === "Stop") {
267
+ if (eventType === "Stop" || eventType === "SubagentStop") {
268
+ const reasonText = `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}\n\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation — execute the required action, then attempt to finish your task again.`;
269
+ // Copilot CLI: `agentStop` and `subagentStop` both honor
270
+ // `{decision: "block", reason}` JSON on stdout — the reason becomes the
271
+ // next-turn prompt and the agent (or subagent) retries. Exit-2 is logged
272
+ // as `[WARNING] Hook warning: ...` (verified empirically against Copilot
273
+ // CLI 1.0.41 events.jsonl) but does NOT trigger retry. We branch on both
274
+ // event types so that custom policies matching SubagentStop also enforce
275
+ // on Copilot subagent boundaries; the 5 builtin require-*-before-stop
276
+ // policies still match Stop only by design — they are session-completion
277
+ // gates (commit/push/PR/conflicts/CI), not subagent-return gates.
278
+ // Ref: https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-hooks-reference
279
+ if (session?.cli === "copilot") {
280
+ return {
281
+ exitCode: 0,
282
+ stdout: JSON.stringify({ decision: "block", reason: reasonText }),
283
+ stderr: "",
284
+ policyName: policy.name,
285
+ reason,
286
+ decision: "deny",
287
+ };
288
+ }
268
289
  return {
269
290
  exitCode: 2,
270
291
  stdout: "",
271
- stderr: `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}\n\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation — execute the required action, then attempt to finish your task again.`,
292
+ stderr: reasonText,
272
293
  policyName: policy.name,
273
294
  reason,
274
295
  decision: "deny",
@@ -433,16 +454,36 @@ export async function evaluatePolicies(
433
454
  };
434
455
  }
435
456
 
436
- if (eventType === "Stop") {
437
- // Stop hook: exitCode 2 blocks Claude from stopping.
438
- // Reason goes to stderr so Claude Code receives it as context.
457
+ if (eventType === "Stop" || eventType === "SubagentStop") {
458
+ // Stop/SubagentStop instruct: exitCode 2 + stderr forces Claude to retry
459
+ // the agent (or subagent) loop with the reason as context. Same widening
460
+ // as the deny branch above — custom policies subscribing to
461
+ // SubagentStop need the same retry semantics; the 5 builtin
462
+ // require-*-before-stop policies still match Stop only by design.
439
463
  const policyAttribution = policyNames.length === 1
440
464
  ? `policy: ${policyNames[0]}`
441
465
  : `policies: ${policyNames.join(", ")}`;
466
+ const reasonText = `MANDATORY ACTION REQUIRED from failproofai (${policyAttribution}): ${combined}\n\nYou MUST complete the above action(s) NOW. Do NOT ask the user for confirmation — execute the required action(s), then attempt to finish your task again.`;
467
+ // Copilot CLI: exit-2 from agentStop / subagentStop is logged as
468
+ // `[WARNING] Hook warning: ...` but does NOT trigger retry. The
469
+ // documented retry shape is `{decision: "block", reason}` JSON on
470
+ // stdout (exit 0). Mirrors the cli==="copilot" branch in the deny
471
+ // path at line ~279 so custom instruct policies enforce on Copilot.
472
+ if (session?.cli === "copilot") {
473
+ return {
474
+ exitCode: 0,
475
+ stdout: JSON.stringify({ decision: "block", reason: reasonText }),
476
+ stderr: "",
477
+ policyName: policyNames[0],
478
+ policyNames,
479
+ reason: combined,
480
+ decision: "instruct",
481
+ };
482
+ }
442
483
  return {
443
484
  exitCode: 2,
444
485
  stdout: "",
445
- stderr: `MANDATORY ACTION REQUIRED from failproofai (${policyAttribution}): ${combined}\n\nYou MUST complete the above action(s) NOW. Do NOT ask the user for confirmation — execute the required action(s), then attempt to finish your task again.`,
486
+ stderr: reasonText,
446
487
  policyName: policyNames[0],
447
488
  policyNames,
448
489
  reason: combined,
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Per-CLI transcript-path resolver.
3
+ *
4
+ * • Claude Code: `transcript_path` arrives on the hook stdin payload —
5
+ * passthrough.
6
+ *
7
+ * • Codex: stdin doesn't carry transcript_path. Discover via
8
+ * ~/.codex/sessions/<YYYY>/<MM>/<DD>/<file containing sessionId>.jsonl.
9
+ *
10
+ * • Copilot: stdin doesn't carry transcript_path. Discover at
11
+ * ~/.copilot/session-state/<sessionId>/events.jsonl.
12
+ *
13
+ * • Cursor: stdin doesn't carry transcript_path. Discover under
14
+ * ~/.cursor/projects/<encoded-cwd>/agent-transcripts/<sessionId>/<sessionId>.jsonl
15
+ * (with legacy fallbacks).
16
+ *
17
+ * • OpenCode: transcripts live in SQLite at
18
+ * ~/.local/share/opencode/opencode.db, not on disk. Synthesize an
19
+ * `opencode-db://<sessionId>` marker so the dashboard renders something
20
+ * meaningful and the value is distinguishable from a genuine miss.
21
+ *
22
+ * • Pi: shim doesn't forward transcript_path. Discover at
23
+ * ~/.pi/agent/sessions/<encodedCwd>/<isoTimestamp>_<sessionId>.jsonl.
24
+ *
25
+ * • Gemini: docs say stdin carries transcript_path, but coverage is uneven
26
+ * across versions. Trust stdin first; fall back to discovery under
27
+ * ~/.gemini/tmp/<projectHash>/chats/<sessionId>.json.
28
+ *
29
+ * Mirrors the dispatch pattern of `resolve-permission-mode.ts`. Each
30
+ * `find*Transcript` helper performs its own existsSync + path-traversal
31
+ * containment check, so passing in a malformed sessionId is safe (returns
32
+ * null → undefined).
33
+ */
34
+ import { findCodexTranscript } from "../../lib/codex-sessions";
35
+ import { findCopilotTranscript } from "../../lib/copilot-sessions";
36
+ import { findCursorTranscript } from "../../lib/cursor-sessions";
37
+ import { findPiTranscript } from "../../lib/pi-sessions";
38
+ import { findGeminiTranscript } from "../../lib/gemini-sessions";
39
+ import type { IntegrationType } from "./types";
40
+
41
+ export function resolveTranscriptPath(
42
+ integration: IntegrationType,
43
+ parsed: Record<string, unknown>,
44
+ sessionId: string | undefined,
45
+ ): string | undefined {
46
+ const stdinPath =
47
+ typeof parsed.transcript_path === "string" ? parsed.transcript_path : undefined;
48
+ if (stdinPath) return stdinPath;
49
+ if (typeof sessionId !== "string" || sessionId.length === 0) return undefined;
50
+
51
+ switch (integration) {
52
+ case "claude":
53
+ return undefined;
54
+ case "codex":
55
+ return findCodexTranscript(sessionId) ?? undefined;
56
+ case "copilot":
57
+ return findCopilotTranscript(sessionId) ?? undefined;
58
+ case "cursor":
59
+ return findCursorTranscript(sessionId) ?? undefined;
60
+ case "pi":
61
+ return findPiTranscript(sessionId) ?? undefined;
62
+ case "gemini":
63
+ return findGeminiTranscript(sessionId) ?? undefined;
64
+ case "opencode":
65
+ return `opencode-db://${sessionId}`;
66
+ default:
67
+ return undefined;
68
+ }
69
+ }
@@ -30,6 +30,21 @@ export const CODEX_EVENT_MAP: Record<CodexHookEventType, HookEventType> = {
30
30
  stop: "Stop",
31
31
  };
32
32
 
33
+ /**
34
+ * Codex's per-tool canonicalization. Per
35
+ * https://developers.openai.com/codex/hooks the hook payload reports
36
+ * `tool_name: "Bash"` already PascalCase (passthrough) and `tool_name:
37
+ * "apply_patch"` for file edits even when matchers say `Edit`/`Write`.
38
+ * Local Codex sessions also expose `write_stdin` (sends input to a running
39
+ * shell — same risk class as Bash). Map the two non-canonical names so
40
+ * builtin policies fire; everything else (MCP `mcp__*`, future tools)
41
+ * passes through.
42
+ */
43
+ export const CODEX_TOOL_MAP: Record<string, string> = {
44
+ apply_patch: "Edit",
45
+ write_stdin: "Bash",
46
+ };
47
+
33
48
  // ── GitHub Copilot CLI ─────────────────────────────────────────────────────
34
49
  //
35
50
  // Copilot CLI accepts two payload formats. We install with PascalCase event
@@ -37,6 +52,13 @@ export const CODEX_EVENT_MAP: Record<CodexHookEventType, HookEventType> = {
37
52
  // `hook_event_name` plus snake_case fields — same shape Claude already uses
38
53
  // at the WRAPPER level (no event-name canonicalization needed).
39
54
  //
55
+ // Empirically verified (Copilot CLI 1.0.41 against
56
+ // `~/.copilot/session-state/<id>/events.jsonl`): the user-scope PascalCase
57
+ // `Stop` entry IS dispatched on Copilot's native camelCase `agentStop` event
58
+ // — Copilot performs the alias mapping and case-fold internally so failproofai's
59
+ // `--hook Stop --cli copilot` invocation is what actually receives `agentStop`
60
+ // firings. Same alias rule applies to `SubagentStop` ↔ `subagentStop`.
61
+ //
40
62
  // Tool names are a separate matter: Copilot's tool registry uses lowercase
41
63
  // IDs (`bash`, `read`, `write`, `edit`, …) — confirmed by the session-log
42
64
  // shape at `lib/copilot-sessions.ts:257` and the unit-test fixture at
@@ -45,6 +67,15 @@ export const CODEX_EVENT_MAP: Record<CodexHookEventType, HookEventType> = {
45
67
  // without canonicalization every Bash/Read/Write/Edit builtin silently
46
68
  // no-ops under Copilot. COPILOT_TOOL_MAP below is the source of truth.
47
69
  //
70
+ // **Stop block semantics** (verified against Copilot CLI 1.0.41 + docs at
71
+ // https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-hooks-reference):
72
+ // `agentStop` accepts `{decision: "block", reason}` JSON on stdout — the reason
73
+ // becomes the next-turn prompt and the agent retries. **Exit-2 + stderr is NOT
74
+ // honored** — the session log shows it surfaced as `[WARNING] Hook warning: ...`
75
+ // to the user but the agent stops cleanly without retrying. policy-evaluator.ts
76
+ // has a `cli === "copilot"` Stop branch that emits the JSON-block shape so the
77
+ // 5 require-*-before-stop builtins actually enforce on Copilot sessions.
78
+ //
48
79
  // Settings paths:
49
80
  // user → ~/.copilot/hooks/failproofai.json
50
81
  // project → <cwd>/.github/hooks/failproofai.json (also where the cloud agent reads)
@@ -60,6 +91,7 @@ export const COPILOT_HOOK_EVENT_TYPES = [
60
91
  "PreToolUse",
61
92
  "PostToolUse",
62
93
  "Stop",
94
+ "SubagentStop",
63
95
  ] as const;
64
96
  export type CopilotHookEventType = (typeof COPILOT_HOOK_EVENT_TYPES)[number];
65
97
 
@@ -76,13 +108,39 @@ export type CopilotHookEventType = (typeof COPILOT_HOOK_EVENT_TYPES)[number];
76
108
  */
77
109
  export const COPILOT_TOOL_MAP: Record<string, string> = {
78
110
  bash: "Bash",
111
+ // Windows shell + the *_bash / *_powershell session-management tools all
112
+ // execute or interact with shell commands, so they map to the same risk
113
+ // class as bash. Without this `block-sudo`, `block-rm-rf`,
114
+ // `block-read-outside-cwd` (Bash branch), etc. silently no-op for any
115
+ // command Copilot routes through powershell or a long-lived shell session.
116
+ powershell: "Bash",
117
+ list_bash: "Bash",
118
+ read_bash: "Bash",
119
+ stop_bash: "Bash",
120
+ write_bash: "Bash",
121
+ list_powershell: "Bash",
122
+ read_powershell: "Bash",
123
+ stop_powershell: "Bash",
124
+ write_powershell: "Bash",
79
125
  read: "Read",
126
+ // `view` reads files OR lists directories
127
+ // (`{"toolName":"view","arguments":{"path":"/some/dir"}}` — verified
128
+ // empirically against Copilot CLI 1.0.39). Mapping to Read makes
129
+ // block-read-outside-cwd fire on `view` calls; the policy reads
130
+ // toolInput.path as a fallback to file_path so directory listings get
131
+ // covered by the same path check.
132
+ view: "Read",
133
+ show_file: "Read",
80
134
  write: "Write",
135
+ create: "Write",
81
136
  edit: "Edit",
137
+ apply_patch: "Edit",
82
138
  str_replace_editor: "Edit",
83
139
  glob: "Glob",
84
140
  grep: "Grep",
141
+ rg: "Grep",
85
142
  ls: "LS",
143
+ web_fetch: "WebFetch",
86
144
  };
87
145
 
88
146
  // ── Cursor Agent CLI ───────────────────────────────────────────────────────
@@ -122,6 +180,19 @@ export const CURSOR_EVENT_MAP: Record<CursorHookEventType, HookEventType> = {
122
180
  stop: "Stop",
123
181
  };
124
182
 
183
+ /**
184
+ * Cursor delivers PascalCase tool names per https://cursor.com/docs/hooks
185
+ * (`Shell | Read | Write | Grep | Delete | Task | MCP:*`). All but `Shell`
186
+ * are already canonical (`Read`, `Write`, `Grep` match Claude verbatim) or
187
+ * have no Claude equivalent (`Delete`, `Task`, `MCP:*`) so passthrough is
188
+ * fine. `Shell` is Cursor's name for what Claude calls `Bash`; without this
189
+ * map every Bash builtin (`block-sudo`, `block-rm-rf`,
190
+ * `block-read-outside-cwd`, …) silently no-ops on Cursor sessions.
191
+ */
192
+ export const CURSOR_TOOL_MAP: Record<string, string> = {
193
+ Shell: "Bash",
194
+ };
195
+
125
196
  // ── OpenCode (sst/opencode) ─────────────────────────────────────────────────
126
197
  //
127
198
  // OpenCode's plugin model is fundamentally different from the other four CLIs:
@@ -193,10 +264,12 @@ export const OPENCODE_TOOL_MAP: Record<string, string> = {
193
264
  read: "Read",
194
265
  write: "Write",
195
266
  edit: "Edit",
267
+ apply_patch: "Edit",
196
268
  glob: "Glob",
197
269
  grep: "Grep",
198
270
  list: "LS",
199
271
  webfetch: "WebFetch",
272
+ websearch: "WebSearch",
200
273
  todowrite: "TodoWrite",
201
274
  todoread: "TodoRead",
202
275
  };
@@ -1,3 +0,0 @@
1
- module.exports=[88947,(e,t,r)=>{t.exports=e.x("stream",()=>require("stream"))},9311,e=>{"use strict";var t=e.i(47909),r=e.i(74017),a=e.i(96250),n=e.i(59756),o=e.i(61916),i=e.i(74677),s=e.i(69741),l=e.i(16795),d=e.i(87718),u=e.i(95169),c=e.i(47587),p=e.i(66012),f=e.i(70101),h=e.i(26937),m=e.i(10372),g=e.i(93695);e.i(20232);var R=e.i(220),E=e.i(89171),w=e.i(22734),v=e.i(24868),y=e.i(88947),C=e.i(40911),A=e.i(12714),x=e.i(50227),T=e.i(81580),j=e.i(83534),M=e.i(71809),P=e.i(28557);let N=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;async function b(e,t){try{return(await (0,A.stat)(e)).mtime}catch(e){return(0,C.logWarn)(`Failed to stat ${t}:`,e),new Date(0)}}async function S(e){try{if(!(await (0,A.stat)(e)).isDirectory())return null;return await (0,A.readdir)(e,{withFileTypes:!0})}catch{return null}}async function O(){try{let e=(0,T.getClaudeProjectsPath)(),t=await S(e);if(!t)return[];let r=(await (0,M.batchAll)(t.filter(e=>e.isDirectory()).map(t=>async()=>{let r=(0,x.join)(e,t.name),a=await b(r,t.name);return{name:t.name,path:r,isDirectory:!0,lastModified:a,lastModifiedFormatted:(0,P.formatDate)(a),cli:["claude"]}}),16)).filter(e=>"fulfilled"===e.status).map(e=>e.value);return r.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),r}catch(e){return(0,C.logError)("Error reading Claude project folders:",e),[]}}async function _(){let[{getCodexProjects:t},{getCopilotProjects:r},{getCursorProjects:a},{getOpenCodeProjects:n},{getPiProjects:o},{getGeminiProjects:i}]=await Promise.all([e.A(37352),e.A(5807),e.A(97452),e.A(82978),e.A(45093),e.A(61489)]),[s,l,d,u,c,p,f]=await Promise.all([O(),t().catch(e=>((0,C.logError)("Error reading Codex projects:",e),[])),r().catch(e=>((0,C.logError)("Error reading Copilot projects:",e),[])),a().catch(e=>((0,C.logError)("Error reading Cursor projects:",e),[])),n().catch(e=>((0,C.logError)("Error reading OpenCode projects:",e),[])),o().catch(e=>((0,C.logError)("Error reading Pi projects:",e),[])),i().catch(e=>((0,C.logError)("Error reading Gemini projects:",e),[]))]);return function(...e){let t=new Map;for(let r of e)for(let e of r){let r=t.get(e.name);if(!r){t.set(e.name,{...e,cli:[...e.cli]});continue}let a=[...r.cli];for(let t of e.cli)a.includes(t)||a.push(t);let n=e.lastModified.getTime()>r.lastModified.getTime()?e:r;t.set(e.name,{...r,cli:a,lastModified:n.lastModified,lastModifiedFormatted:n.lastModifiedFormatted})}let r=Array.from(t.values());return r.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),r}(s,l,d,u,c,p,f)}function D(e){let t=e.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);return t?t[0]:void 0}async function I(e){try{let t=await S(e);if(!t)return[];let r=t.filter(e=>e.isFile()&&e.name.endsWith(".jsonl")&&D(e.name)),a=(await (0,M.batchAll)(r.map(t=>async()=>{let r=(0,x.join)(e,t.name),a=await b(r,t.name);return{name:t.name,path:r,lastModified:a,lastModifiedFormatted:(0,P.formatDate)(a),sessionId:D(t.name),cli:"claude"}}),16)).filter(e=>"fulfilled"===e.status).map(e=>e.value);return a.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),a}catch(e){return(0,C.logError)("Error reading session files:",e),[]}}function q(e,t){return E.NextResponse.json({error:e},{status:t})}async function H(e,{params:t}){let r,{project:a,session:n}=await t;try{r=function(e,t){if(!N.test(t))throw RangeError("Invalid session ID");let r=function(e){if(!e)throw RangeError("Empty project name");if(/^[/\\]/.test(e))throw RangeError("Absolute project name");let t=(0,x.resolve)((0,T.getClaudeProjectsPath)()),r=(0,x.resolve)((0,x.join)(t,e));if(!r.startsWith(t+x.sep))throw RangeError("Project path escapes root");return r}(e);return(0,x.join)(r,`${t}.jsonl`)}(a,n)}catch{return q("Invalid project or session",400)}(0,C.logActivity)("anonymous","download-log",`project=${a} session=${n}`);try{await (0,v.stat)(r);let e=(0,w.createReadStream)(r),t=y.Readable.toWeb(e);return new E.NextResponse(t,{headers:{"Content-Type":"application/x-ndjson","Content-Disposition":`attachment; filename="${n}.jsonl"`}})}catch(e){if("ENOENT"===e.code||e instanceof RangeError)return q("Session log not found",404);return q("Failed to read session log",500)}}(0,j.runtimeCache)(_,30),(0,j.runtimeCache)(e=>I(e),30),e.s(["GET",0,H],32525);var F=e.i(32525);let U=new t.AppRouteRouteModule({definition:{kind:r.RouteKind.APP_ROUTE,page:"/api/download/[project]/[session]/route",pathname:"/api/download/[project]/[session]",filename:"route",bundlePath:""},distDir:".next",relativeProjectDir:"",resolvedPagePath:"[project]/app/api/download/[project]/[session]/route.ts",nextConfigOutput:"standalone",userland:F,...{}}),{workAsyncStorage:$,workUnitAsyncStorage:k,serverHooks:K}=U;async function W(e,t,a){a.requestMeta&&(0,n.setRequestMeta)(e,a.requestMeta),U.isDev&&(0,n.addRequestMeta)(e,"devRequestTimingInternalsEnd",process.hrtime.bigint());let E="/api/download/[project]/[session]/route";E=E.replace(/\/index$/,"")||"/";let w=await U.prepare(e,t,{srcPage:E,multiZoneDraftMode:!1});if(!w)return t.statusCode=400,t.end("Bad Request"),null==a.waitUntil||a.waitUntil.call(a,Promise.resolve()),null;let{buildId:v,params:y,nextConfig:C,parsedUrl:A,isDraftMode:x,prerenderManifest:T,routerServerContext:j,isOnDemandRevalidate:M,revalidateOnlyGenerated:P,resolvedPathname:N,clientReferenceManifest:b,serverActionsManifest:S}=w,O=(0,s.normalizeAppPath)(E),_=!!(T.dynamicRoutes[O]||T.routes[N]),D=async()=>((null==j?void 0:j.render404)?await j.render404(e,t,A,!1):t.end("This page could not be found"),null);if(_&&!x){let e=!!T.routes[N],t=T.dynamicRoutes[O];if(t&&!1===t.fallback&&!e){if(C.adapterPath)return await D();throw new g.NoFallbackError}}let I=null;!_||U.isDev||x||(I="/index"===(I=N)?"/":I);let q=!0===U.isDev||!_,H=_&&!q;S&&b&&(0,i.setManifestsSingleton)({page:E,clientReferenceManifest:b,serverActionsManifest:S});let F=e.method||"GET",$=(0,o.getTracer)(),k=$.getActiveScopeSpan(),K=!!(null==j?void 0:j.isWrappedByNextServer),W=!!(0,n.getRequestMeta)(e,"minimalMode"),B=(0,n.getRequestMeta)(e,"incrementalCache")||await U.getIncrementalCache(e,C,T,W);null==B||B.resetRequestCache(),globalThis.__incrementalCache=B;let G={params:y,previewProps:T.preview,renderOpts:{experimental:{authInterrupts:!!C.experimental.authInterrupts},cacheComponents:!!C.cacheComponents,supportsDynamicResponse:q,incrementalCache:B,cacheLifeProfiles:C.cacheLife,waitUntil:a.waitUntil,onClose:e=>{t.on("close",e)},onAfterTaskError:void 0,onInstrumentationRequestError:(t,r,a,n)=>U.onRequestError(e,t,a,n,j)},sharedContext:{buildId:v}},L=new l.NodeNextRequest(e),V=new l.NodeNextResponse(t),X=d.NextRequestAdapter.fromNodeNextRequest(L,(0,d.signalFromNodeResponse)(t));try{let n,i=async e=>U.handle(X,G).finally(()=>{if(!e)return;e.setAttributes({"http.status_code":t.statusCode,"next.rsc":!1});let r=$.getRootSpanAttributes();if(!r)return;if(r.get("next.span_type")!==u.BaseServerSpan.handleRequest)return void console.warn(`Unexpected root span type '${r.get("next.span_type")}'. Please report this Next.js issue https://github.com/vercel/next.js`);let a=r.get("next.route");if(a){let t=`${F} ${a}`;e.setAttributes({"next.route":a,"http.route":a,"next.span_name":t}),e.updateName(t),n&&n!==e&&(n.setAttribute("http.route",a),n.updateName(t))}else e.updateName(`${F} ${E}`)}),s=async n=>{var o,s;let l=async({previousCacheEntry:r})=>{try{if(!W&&M&&P&&!r)return t.statusCode=404,t.setHeader("x-nextjs-cache","REVALIDATED"),t.end("This page could not be found"),null;let o=await i(n);e.fetchMetrics=G.renderOpts.fetchMetrics;let s=G.renderOpts.pendingWaitUntil;s&&a.waitUntil&&(a.waitUntil(s),s=void 0);let l=G.renderOpts.collectedTags;if(!_)return await (0,p.sendResponse)(L,V,o,G.renderOpts.pendingWaitUntil),null;{let e=await o.blob(),t=(0,f.toNodeOutgoingHttpHeaders)(o.headers);l&&(t[m.NEXT_CACHE_TAGS_HEADER]=l),!t["content-type"]&&e.type&&(t["content-type"]=e.type);let r=void 0!==G.renderOpts.collectedRevalidate&&!(G.renderOpts.collectedRevalidate>=m.INFINITE_CACHE)&&G.renderOpts.collectedRevalidate,a=void 0===G.renderOpts.collectedExpire||G.renderOpts.collectedExpire>=m.INFINITE_CACHE?void 0:G.renderOpts.collectedExpire;return{value:{kind:R.CachedRouteKind.APP_ROUTE,status:o.status,body:Buffer.from(await e.arrayBuffer()),headers:t},cacheControl:{revalidate:r,expire:a}}}}catch(t){throw(null==r?void 0:r.isStale)&&await U.onRequestError(e,t,{routerKind:"App Router",routePath:E,routeType:"route",revalidateReason:(0,c.getRevalidateReason)({isStaticGeneration:H,isOnDemandRevalidate:M})},!1,j),t}},d=await U.handleResponse({req:e,nextConfig:C,cacheKey:I,routeKind:r.RouteKind.APP_ROUTE,isFallback:!1,prerenderManifest:T,isRoutePPREnabled:!1,isOnDemandRevalidate:M,revalidateOnlyGenerated:P,responseGenerator:l,waitUntil:a.waitUntil,isMinimalMode:W});if(!_)return null;if((null==d||null==(o=d.value)?void 0:o.kind)!==R.CachedRouteKind.APP_ROUTE)throw Object.defineProperty(Error(`Invariant: app-route received invalid cache entry ${null==d||null==(s=d.value)?void 0:s.kind}`),"__NEXT_ERROR_CODE",{value:"E701",enumerable:!1,configurable:!0});W||t.setHeader("x-nextjs-cache",M?"REVALIDATED":d.isMiss?"MISS":d.isStale?"STALE":"HIT"),x&&t.setHeader("Cache-Control","private, no-cache, no-store, max-age=0, must-revalidate");let u=(0,f.fromNodeOutgoingHttpHeaders)(d.value.headers);return W&&_||u.delete(m.NEXT_CACHE_TAGS_HEADER),!d.cacheControl||t.getHeader("Cache-Control")||u.get("Cache-Control")||u.set("Cache-Control",(0,h.getCacheControlHeader)(d.cacheControl)),await (0,p.sendResponse)(L,V,new Response(d.value.body,{headers:u,status:d.value.status||200})),null};K&&k?await s(k):(n=$.getActiveScopeSpan(),await $.withPropagatedContext(e.headers,()=>$.trace(u.BaseServerSpan.handleRequest,{spanName:`${F} ${E}`,kind:o.SpanKind.SERVER,attributes:{"http.method":F,"http.target":e.url}},s),void 0,!K))}catch(t){if(t instanceof g.NoFallbackError||await U.onRequestError(e,t,{routerKind:"App Router",routePath:O,routeType:"route",revalidateReason:(0,c.getRevalidateReason)({isStaticGeneration:H,isOnDemandRevalidate:M})},!1,j),_)throw t;return await (0,p.sendResponse)(L,V,new Response(null,{status:500})),null}}e.s(["handler",0,W,"patchFetch",0,function(){return(0,a.patchFetch)({workAsyncStorage:$,workUnitAsyncStorage:k})},"routeModule",0,U,"serverHooks",0,K,"workAsyncStorage",0,$,"workUnitAsyncStorage",0,k],9311)}];
2
-
3
- //# sourceMappingURL=%5Broot-of-the-server%5D__0b57.gk._.js.map
@@ -1,3 +0,0 @@
1
- module.exports=[18622,(e,r,t)=>{r.exports=e.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js",()=>require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"))},56704,(e,r,t)=>{r.exports=e.x("next/dist/server/app-render/work-async-storage.external.js",()=>require("next/dist/server/app-render/work-async-storage.external.js"))},32319,(e,r,t)=>{r.exports=e.x("next/dist/server/app-render/work-unit-async-storage.external.js",()=>require("next/dist/server/app-render/work-unit-async-storage.external.js"))},24725,(e,r,t)=>{r.exports=e.x("next/dist/server/app-render/after-task-async-storage.external.js",()=>require("next/dist/server/app-render/after-task-async-storage.external.js"))},70406,(e,r,t)=>{r.exports=e.x("next/dist/compiled/@opentelemetry/api",()=>require("next/dist/compiled/@opentelemetry/api"))},93695,(e,r,t)=>{r.exports=e.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},50227,(e,r,t)=>{r.exports=e.x("node:path",()=>require("node:path"))},14747,(e,r,t)=>{r.exports=e.x("path",()=>require("path"))},46786,(e,r,t)=>{r.exports=e.x("os",()=>require("os"))},12714,(e,r,t)=>{r.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},40911,e=>{"use strict";let r={info:0,warn:1,error:2};function t(e){return r[e]>=r.warn}function s(e){return`[failproofai${new Date().toISOString()}] ${e}`}e.s(["logActivity",0,function(e,r,n){if(!t("info"))return;let o=[`user=${e}`,`action=${r}`];n&&o.push(n),console.log(s("ACTIVITY"),o.join(" "))},"logError",0,function(e,r){void 0!==r?console.error(s("ERROR"),e,r):console.error(s("ERROR"),e)},"logWarn",0,function(e,r){t("warn")&&(void 0!==r?console.warn(s("WARN"),e,r):console.warn(s("WARN"),e))}])},81580,e=>{"use strict";var r=e.i(46786),t=e.i(14747);e.s(["decodeFolderName",0,function(e){return/^[A-Za-z]--/.test(e)?e[0]+":/"+e.slice(3).replace(/-/g,"/"):e.replace(/-/g,"/")},"encodeFolderName",0,function(e){let r=/^([A-Za-z]):[\\/](.*)$/.exec(e);return r?r[1]+"--"+r[2].replace(/[\\/]/g,"-"):e.replace(/[\\/]/g,"-")},"getClaudeProjectsPath",0,function(){let e=process.env.CLAUDE_PROJECTS_PATH;return e||(0,t.join)((0,r.homedir)(),".claude","projects")}])},83534,28557,e=>{"use strict";e.s(["runtimeCache",0,function(e,r,t){let s=new Map,n=new Map,o=t?.maxSize;return async(...t)=>{let a=JSON.stringify(t),i=s.get(a);if(i&&Date.now()<i.expiry)return i.data;let l=n.get(a);if(l)return l;let u=e(...t).then(e=>{if(n.delete(a),o&&s.size>=o){let e=s.keys().next().value;s.delete(e)}return s.set(a,{data:e,expiry:Date.now()+1e3*r}),e},e=>{throw n.delete(a),e});return n.set(a,u),u}}],83534),e.s(["formatDate",0,function(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).format(e)}],28557)},71809,e=>{"use strict";async function r(e,r){let t=Array(e.length),s=0;async function n(){for(;s<e.length;){let r=s++;try{t[r]={status:"fulfilled",value:await e[r]()}}catch(e){t[r]={status:"rejected",reason:e}}}}let o=Array.from({length:Math.min(r,e.length)},()=>n());return await Promise.all(o),t}e.s(["batchAll",0,r])},24868,(e,r,t)=>{r.exports=e.x("fs/promises",()=>require("fs/promises"))},22734,(e,r,t)=>{r.exports=e.x("fs",()=>require("fs"))},37352,e=>{e.v(r=>Promise.all(["server/chunks/[root-of-the-server]__0wu7fr7._.js"].map(r=>e.l(r))).then(()=>r(36203)))},5807,e=>{e.v(r=>Promise.all(["server/chunks/[root-of-the-server]__0.~nmr9._.js"].map(r=>e.l(r))).then(()=>r(55851)))},97452,e=>{e.v(r=>Promise.all(["server/chunks/[root-of-the-server]__0zso~62._.js"].map(r=>e.l(r))).then(()=>r(46413)))},82978,e=>{e.v(r=>Promise.all(["server/chunks/[root-of-the-server]__0yfq1yr._.js"].map(r=>e.l(r))).then(()=>r(35435)))},45093,e=>{e.v(r=>Promise.all(["server/chunks/[root-of-the-server]__08px0ym._.js"].map(r=>e.l(r))).then(()=>r(19791)))},61489,e=>{e.v(r=>Promise.all(["server/chunks/[root-of-the-server]__0vlhtkc._.js"].map(r=>e.l(r))).then(()=>r(90172)))}];
2
-
3
- //# sourceMappingURL=%5Broot-of-the-server%5D__0z4c5dj._.js.map
@@ -1,4 +0,0 @@
1
- module.exports=[50227,(a,b,c)=>{b.exports=a.x("node:path",()=>require("node:path"))},60526,(a,b,c)=>{b.exports=a.x("node:os",()=>require("node:os"))},12714,(a,b,c)=>{b.exports=a.x("node:fs/promises",()=>require("node:fs/promises"))},74533,(a,b,c)=>{b.exports=a.x("node:child_process",()=>require("node:child_process"))},2157,(a,b,c)=>{b.exports=a.x("node:fs",()=>require("node:fs"))},66680,(a,b,c)=>{b.exports=a.x("node:crypto",()=>require("node:crypto"))},37936,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"registerServerReference",{enumerable:!0,get:function(){return d.registerServerReference}});let d=a.r(11857)},12072,(a,b,c)=>{b.exports={name:"failproofai",version:"0.0.10-beta.2",description:"The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",bin:{failproofai:"./dist/cli.mjs"},files:["bin/","src/","scripts/","lib/","pi-extension/",".next/standalone/","dist/","README.md"],engines:{node:">=20.9.0",bun:">=1.3.0"},scripts:{predev:"bun run build:cli && bun link",dev:"FAILPROOFAI_TELEMETRY_DISABLED=1 bun scripts/dev.ts --port 8020","build:cli":"bun build --target=node --format=esm --outfile=dist/cli.mjs bin/failproofai.mjs --external posthog-node && node -e \"const fs=require('fs');const c=fs.readFileSync('dist/cli.mjs','utf8');fs.writeFileSync('dist/cli.mjs',c.replace('#!/usr/bin/env bun','#!/usr/bin/env node').replace('// @bun\\n',''))\"",build:"bun build --target=node --format=cjs --outfile=dist/index.js src/index.ts && bun run build:cli && bun --bun next build && node -e \"const {cpSync}=require('fs');cpSync('.next/static','.next/standalone/.next/static',{recursive:true});\" && node scripts/prune-standalone.mjs",prestart:"bun run build:cli && bun link",start:"FAILPROOFAI_TELEMETRY_DISABLED=1 bun scripts/start.ts",test:"vitest","test:run":"vitest run",lint:"eslint . --config eslint.config.mjs",postinstall:"node scripts/postinstall.mjs",preuninstall:"node scripts/preuninstall.mjs",prepare:"bun run build","test:e2e":"vitest run --config vitest.config.e2e.mts","test:e2e:watch":"vitest --config vitest.config.e2e.mts",translate:"bun scripts/translate-docs/cli.ts","translate:readme":"bun scripts/translate-docs/cli.ts --readme-only","translate:docs":"bun scripts/translate-docs/cli.ts --docs-only","translate:dry-run":"bun scripts/translate-docs/cli.ts --dry-run","translate:validate":"bun scripts/translate-docs/cli.ts --validate"},keywords:["claude","claude-code","claude-agents-sdk","anthropic","ai-agent","agent-reliability","agent-monitoring","autonomous-agent","failure-prevention","developer-tools","devtools","cli","local-first","hooks","policies"],author:"ExosphereHost Inc. <failproofai@exosphere.host>",license:"SEE LICENSE IN LICENSE",homepage:"https://github.com/exospherehost/failproofai",repository:{type:"git",url:"https://github.com/exospherehost/failproofai.git"},bugs:{url:"https://github.com/exospherehost/failproofai/issues"},publishConfig:{access:"public"},devDependencies:{"@tailwindcss/postcss":"^4.1.18","@tanstack/react-virtual":"^3.13.18","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.2","@testing-library/user-event":"^14.6.1","@types/node":"^25.5.2","@types/react":"^19.2.13","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1",clsx:"^2.1.1",eslint:"^10.2.0","eslint-config-next":"^16.2.2","happy-dom":"^20.7.0","lucide-react":"^1.0.1",next:"^16.2.2",react:"^19.2.4","react-dom":"^19.2.4","tailwind-merge":"^3.4.0",tailwindcss:"^4.1.18",typescript:"^6.0.2","@anthropic-ai/sdk":"^0.93.0",vitest:"^4.0.18"},dependencies:{"posthog-node":"^5.28.11"}}},13095,(a,b,c)=>{"use strict";function d(a){for(let b=0;b<a.length;b++){let c=a[b];if("function"!=typeof c)throw Object.defineProperty(Error(`A "use server" file can only export async functions, found ${typeof c}.
2
- Read more: https://nextjs.org/docs/messages/invalid-use-server-value`),"__NEXT_ERROR_CODE",{value:"E352",enumerable:!1,configurable:!0})}}Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"ensureServerEntryExports",{enumerable:!0,get:function(){return d}})},36359,69839,79779,a=>{"use strict";let b;var c=a.i(37936),d=a.i(2157),e=a.i(50227),f=a.i(60526),g=a.i(66680);a.i(74533);let h=e.default.join(f.default.homedir(),".failproofai"),i=e.default.join(h,"instance-id");function j(a){return g.default.createHmac("sha256","failproofai-telemetry-v1").update(a).digest("hex")}function k(){if(b)return b;let a=function(){try{for(let a of(f.default.platform(),["/etc/machine-id","/var/lib/dbus/machine-id"]))try{let b=d.default.readFileSync(a,"utf-8").trim();if(b)return b}catch{}}catch{}}();if(a)return b=j(a);let c=[f.default.hostname(),f.default.homedir(),f.default.platform(),f.default.arch(),f.default.cpus()[0]?.model??""].join(":");return b=c?j(c):function(){try{let a=d.default.readFileSync(i,"utf-8").trim();if(a)return a}catch{}let a=g.default.randomUUID();try{d.default.mkdirSync(h,{recursive:!0}),d.default.writeFileSync(i,a,"utf-8")}catch{}return a}()}a.s(["getInstanceId",0,k,"hashToId",0,j],69839),a.s(["POSTHOG_API_KEY",0,"phc_Ac1Ww1GqKc0z1SyrRWbmatEeQdlOQIsDEEdP8l8JRgX"],79779);var l=a.i(12072);async function m(){let a="1"!==process.env.FAILPROOFAI_TELEMETRY_DISABLED;return{enabled:a,distinctId:a?k():"",apiKey:process.env.FAILPROOFAI_POSTHOG_KEY??"phc_Ac1Ww1GqKc0z1SyrRWbmatEeQdlOQIsDEEdP8l8JRgX",host:process.env.FAILPROOFAI_POSTHOG_HOST??"https://us.i.posthog.com",version:l.version}}(0,a.i(13095).ensureServerEntryExports)([m]),(0,c.registerServerReference)(m,"0093ba850168e5577955a082a23dfc9130f1ac1407",null),a.s(["getTelemetryConfig",0,m],36359)},24868,(a,b,c)=>{b.exports=a.x("fs/promises",()=>require("fs/promises"))},1457,(a,b,c)=>{b.exports=a.x("node:readline",()=>require("node:readline"))},7292,a=>{"use strict";var b=a.i(37936),c=a.i(2157),d=a.i(50227),e=a.i(60526);let f=(0,d.join)((0,e.homedir)(),".failproofai","cache","hook-activity"),g="current.jsonl";function h(){(0,c.existsSync)(f)||(0,c.mkdirSync)(f,{recursive:!0})}function i(){let a=function(){try{return JSON.parse((0,c.readFileSync)((0,d.join)(f,"stats.json"),"utf-8"))}catch{return{totalEvents:0,denyCount:0,policyMap:{}}}}(),b=null,e=0;for(let[c,d]of Object.entries(a.policyMap))d>e&&(b=c,e=d);return{totalEvents:a.totalEvents,denyCount:a.denyCount,topPolicy:b,topPolicyCount:e}}function j(a){let b=function(a){try{return(0,c.readFileSync)(a,"utf-8")}catch{return""}}(a);if(!b.trim())return[];let d=[];for(let a of b.trim().split("\n"))try{d.push(JSON.parse(a))}catch{}return d}function k(){try{return(0,c.readdirSync)(f).filter(a=>a.startsWith("page-")&&a.endsWith(".jsonl")).sort((a,b)=>{let c=a.slice(5,-6).split("-"),d=b.slice(5,-6).split("-"),e=parseInt(c[0],10),f=parseInt(d[0],10);if(e!==f)return f-e;let g=c.length>1?parseInt(c[1],10):0;return(d.length>1?parseInt(d[1],10):0)-g})}catch{return[]}}async function l(a){let b;return b=function(a){if(h(),a<1)return[];if(1===a)return j((0,d.join)(f,g)).reverse();let b=k(),c=a-2;return c>=b.length?[]:j((0,d.join)(f,b[c])).reverse()}(a),h(),{entries:b,totalPages:1+k().length,page:a,stats:i()}}async function m(a,b){let c,e,l;return e=Math.max(1,Math.ceil((c=(function(){h();let a=j((0,d.join)(f,g)).reverse(),b=k(),c=[];for(let a of b){let b=j((0,d.join)(f,a));c.push(...b.reverse())}return[...a,...c]})().filter(b=>(!a.decision||b.decision===a.decision)&&(!a.eventType||b.eventType===a.eventType)&&(!a.policyName||!!b.policyName&&!!b.policyName.toLowerCase().includes(a.policyName.toLowerCase()))&&(!a.sessionId||!!b.sessionId&&!!b.sessionId.toLowerCase().includes(a.sessionId.toLowerCase()))&&(!a.integration||b.integration===a.integration))).length/25)),l=(b-1)*25,{entries:c.slice(l,l+25),totalPages:e,page:b,stats:i()}}(0,a.i(13095).ensureServerEntryExports)([l,m]),(0,b.registerServerReference)(l,"403eae75122453abcf4043a6ec0d83422ce1d566d8",null),(0,b.registerServerReference)(m,"60139a5cfb4041468f75d94ed2d3844ae7e54239ef",null),a.s(["getHookActivityAction",0,l,"searchHookActivityAction",0,m],7292)},2726,a=>{a.v(b=>Promise.all(["server/chunks/ssr/[externals]_node_async_hooks_0v0ln8c._.js","server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js"].map(b=>a.l(b))).then(()=>b(74493)))}];
3
-
4
- //# sourceMappingURL=%5Broot-of-the-server%5D__0.~m-w2._.js.map
@@ -1 +0,0 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,53348,e=>{"use strict";var r=e.i(43476),t=e.i(71645),n=e.i(23328),i=e.i(9969);e.s(["default",0,function({error:e,reset:o}){return(0,t.useEffect)(()=>{(0,n.getTelemetryConfig)().then(r=>{(0,i.setClientTelemetryConfig)(r),(0,i.captureClientEvent)("client_error",{error_message:e.message,error_name:e.name,error_digest:e.digest,boundary:"global"})}).catch(()=>{})},[e]),(0,r.jsx)("html",{children:(0,r.jsx)("body",{children:(0,r.jsx)("main",{style:{minHeight:"100vh",display:"flex",alignItems:"center",justifyContent:"center",background:"#031035",color:"#f8fafc",fontFamily:"system-ui, sans-serif"},children:(0,r.jsxs)("div",{style:{textAlign:"center",padding:"2rem",border:"1px solid rgba(239,68,68,0.4)",borderRadius:"0.5rem",maxWidth:"500px"},children:[(0,r.jsx)("h2",{style:{color:"#ef4444",marginBottom:"0.5rem",fontSize:"1.25rem"},children:"Something went wrong"}),(0,r.jsx)("p",{style:{color:"#94a3b8",marginBottom:"1.5rem"},children:e.message||"An unexpected error occurred."}),(0,r.jsx)("button",{onClick:o,style:{padding:"0.5rem 1.25rem",background:"#3b82f6",color:"white",border:"none",borderRadius:"0.375rem",cursor:"pointer",fontSize:"0.875rem"},children:"Try again"})]})})})})}])}]);