failproofai 0.0.13-beta.0 → 0.0.13-beta.2
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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +1 -1
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cefjd9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ekj92q._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hv08mx._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0obaznk._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xvsnvt._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12v-lma._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__136y39g._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h8pzr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hkdkmz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1i9rknc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0cycwg6._.js → [root-of-the-server]__1tvn4wf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1z062h5._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04bqvos._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bbpv2d._.js → [root-of-the-server]__09u1pcy._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dqgs96._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1uvfwgr._.js → [root-of-the-server]__0hdsupo._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q5rfxg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qukut7._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ehe24g._.js → [root-of-the-server]__0ynf7tx._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +245 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ql-mch._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-m1rhq._.js → [root-of-the-server]__1rlmypf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0ul5rf_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_11v0g3z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_hermes-projects_ts_05b8-it._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1u91ynr.js → node_modules_html-to-image_es_index_09fe3kb.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/static/chunks/{39rn602wszcbd.js → 06cw8nbn5c4_r.js} +1 -1
- package/.next/standalone/.next/static/chunks/0fa7j157kg9_d.js +1 -0
- package/.next/standalone/.next/static/chunks/{03zg3ms7uaoq3.js → 0i0joc040j999.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1h569gia53ant.js → 0l2n5ftar65ox.js} +1 -1
- package/.next/standalone/.next/static/chunks/1c8i78p7t1i-5.js +1 -0
- package/.next/standalone/.next/static/chunks/{2ks4pzu5b-l_q.js → 1sg8kx5szx42y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3jka0hvhqouyv.js → 23t-i9s-q--nu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0byua6ng1f_ka.js → 25qvspmcbb_5y.js} +1 -1
- package/.next/standalone/.next/static/chunks/25s52un3k_wht.css +2 -0
- package/.next/standalone/.next/static/chunks/{41mhnhfkmglpp.js → 2a-k91nsa1lgf.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0azkpcy8eb1gr.js → 2oz8ok8fzxaj3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1zxtyef-iqede.js → 2rkvn4znvuy4o.js} +1 -1
- package/.next/standalone/app/components/sessions-list.tsx +19 -0
- package/.next/standalone/app/project/[name]/page.tsx +9 -4
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +26 -5
- package/.next/standalone/assets/logos/hermes-dark.svg +1 -0
- package/.next/standalone/assets/logos/hermes-light.svg +1 -0
- package/.next/standalone/lib/cli-registry.ts +6 -1
- package/.next/standalone/lib/download-session.ts +16 -0
- package/.next/standalone/lib/hermes-projects.ts +164 -0
- package/.next/standalone/lib/hermes-sessions.ts +273 -0
- package/.next/standalone/lib/projects.ts +14 -3
- package/.next/standalone/lib/sqlite-reader.ts +102 -0
- package/.next/standalone/package.json +5 -3
- package/.next/standalone/server.js +1 -1
- package/README.md +10 -1
- package/bin/failproofai.mjs +17 -16
- package/dist/cli.mjs +7774 -225
- package/lib/cli-registry.ts +6 -1
- package/lib/download-session.ts +16 -0
- package/lib/hermes-projects.ts +164 -0
- package/lib/hermes-sessions.ts +273 -0
- package/lib/projects.ts +14 -3
- package/lib/sql-js-asm.d.ts +26 -0
- package/lib/sqlite-reader.ts +102 -0
- package/package.json +5 -3
- package/src/audit/cli-adapters/hermes.ts +58 -0
- package/src/audit/cli-adapters/index.ts +6 -0
- package/src/hooks/builtin-policies.ts +16 -0
- package/src/hooks/handler.ts +8 -0
- package/src/hooks/install-prompt.ts +7 -3
- package/src/hooks/integrations.ts +179 -5
- package/src/hooks/policy-evaluator.ts +42 -0
- package/src/hooks/resolve-transcript-path.ts +4 -0
- package/src/hooks/tool-name-canonicalize.ts +6 -0
- package/src/hooks/types.ts +67 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dwpg-h._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lnenda._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13i_sva._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m2_4t0._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_0ttxbz7._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_1epycqa._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_1wpdcgo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e4-6d8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f62vu9._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0k65l27._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1e-x7j4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +0 -221
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +0 -4
- package/.next/standalone/.next/static/chunks/0f7d7hnbh4djs.js +0 -1
- package/.next/standalone/.next/static/chunks/1o0xa47736gi9.css +0 -2
- package/.next/standalone/.next/static/chunks/2wbuxnsvux4di.js +0 -1
- /package/.next/standalone/.next/static/{W6ELw5VwHTFy2amS2px6e → HgG0mizCfOM3XyN1EW-sL}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{W6ELw5VwHTFy2amS2px6e → HgG0mizCfOM3XyN1EW-sL}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{W6ELw5VwHTFy2amS2px6e → HgG0mizCfOM3XyN1EW-sL}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes (hermes-agent) session transcript loader + parser.
|
|
3
|
+
*
|
|
4
|
+
* AUDIT-ONLY (Pillar 2). Hermes stores every gateway user's sessions in one
|
|
5
|
+
* SQLite DB (`~/.hermes/state.db`). We read it DIRECTLY via the bundled sql.js
|
|
6
|
+
* driver (lib/sqlite-reader.ts) — a reusable path for SQLite-backed agents.
|
|
7
|
+
* (opencode and the already-shipped CLIs keep their CLI shell-out unchanged.)
|
|
8
|
+
*
|
|
9
|
+
* Message shape is OpenAI Chat-Completions style (verified live): assistant tool
|
|
10
|
+
* calls are `tool_calls[].function.{name, arguments}` (stored as a JSON string
|
|
11
|
+
* column) and results are separate `role:"tool"` rows keyed by `tool_call_id`.
|
|
12
|
+
* `hermesRowsToLogEntries` pairs them (mirrors `lib/codex-sessions.ts`) and is a
|
|
13
|
+
* PURE function of the message rows, so it is unit-testable without a DB.
|
|
14
|
+
*
|
|
15
|
+
* DB path override: set `HERMES_DB_PATH` (used by tests and to point at a copied
|
|
16
|
+
* or remote state.db).
|
|
17
|
+
*/
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { openSqliteReadonly } from "./sqlite-reader";
|
|
21
|
+
import { runtimeCache } from "./runtime-cache";
|
|
22
|
+
import {
|
|
23
|
+
baseEntry,
|
|
24
|
+
formatTimestamp,
|
|
25
|
+
type LogEntry,
|
|
26
|
+
type UserEntry,
|
|
27
|
+
type AssistantEntry,
|
|
28
|
+
type GenericEntry,
|
|
29
|
+
type ContentBlock,
|
|
30
|
+
type ToolUseBlock,
|
|
31
|
+
type LogSource,
|
|
32
|
+
} from "./log-entries";
|
|
33
|
+
import { formatDuration } from "./format-duration";
|
|
34
|
+
|
|
35
|
+
/** Absolute path to Hermes's SQLite DB (override with HERMES_DB_PATH). */
|
|
36
|
+
export function hermesDbPath(): string {
|
|
37
|
+
return process.env.HERMES_DB_PATH || join(homedir(), ".hermes", "state.db");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Coerce a Hermes epoch value (seconds or ms) to epoch ms. Hermes stores
|
|
41
|
+
* `started_at`/`ended_at`/`timestamp` as REAL epoch seconds. */
|
|
42
|
+
export function epochToMs(value: unknown): number {
|
|
43
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
44
|
+
if (value > 1e12) return value; // already ms
|
|
45
|
+
if (value > 1e9) return value * 1000; // seconds
|
|
46
|
+
}
|
|
47
|
+
return Date.now();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Parsing helpers ──
|
|
51
|
+
|
|
52
|
+
function safeJsonParse(s: unknown): unknown {
|
|
53
|
+
if (typeof s !== "string" || s.length === 0) return undefined;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(s);
|
|
56
|
+
} catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
62
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Text from a `content` field that may be a string or an array of
|
|
66
|
+
* `{ type:"text", text }` blocks. */
|
|
67
|
+
function extractText(content: unknown): string {
|
|
68
|
+
if (typeof content === "string") return content;
|
|
69
|
+
if (Array.isArray(content)) {
|
|
70
|
+
return content
|
|
71
|
+
.map((c) => (isPlainObject(c) && typeof c.text === "string" ? (c.text as string) : ""))
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.join("\n");
|
|
74
|
+
}
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function toDate(value: unknown, fallbackMs: number): Date {
|
|
79
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
80
|
+
if (value > 1e12) return new Date(value);
|
|
81
|
+
if (value > 1e9) return new Date(value * 1000);
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === "string") {
|
|
84
|
+
const ms = Date.parse(value);
|
|
85
|
+
if (!Number.isNaN(ms)) return new Date(ms);
|
|
86
|
+
}
|
|
87
|
+
return new Date(fallbackMs);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface NormalizedToolCall {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
input: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Normalize `tool_calls` (array, or the JSON-string column Hermes stores) into
|
|
97
|
+
* `{ id, name, input }[]`. OpenAI shape: `{ id|call_id, function:{ name,
|
|
98
|
+
* arguments } }` where `arguments` is itself a JSON string. */
|
|
99
|
+
function normalizeToolCalls(raw: unknown): NormalizedToolCall[] {
|
|
100
|
+
const arr = Array.isArray(raw) ? raw : safeJsonParse(raw);
|
|
101
|
+
if (!Array.isArray(arr)) return [];
|
|
102
|
+
const out: NormalizedToolCall[] = [];
|
|
103
|
+
for (const tc of arr) {
|
|
104
|
+
if (!isPlainObject(tc)) continue;
|
|
105
|
+
const fn = isPlainObject(tc.function) ? tc.function : {};
|
|
106
|
+
const name =
|
|
107
|
+
typeof fn.name === "string"
|
|
108
|
+
? fn.name
|
|
109
|
+
: typeof tc.name === "string"
|
|
110
|
+
? (tc.name as string)
|
|
111
|
+
: "tool";
|
|
112
|
+
const id =
|
|
113
|
+
(typeof tc.id === "string" && tc.id) ||
|
|
114
|
+
(typeof tc.call_id === "string" && tc.call_id) ||
|
|
115
|
+
`${name}-${out.length}`;
|
|
116
|
+
const parsedArgs = isPlainObject(fn.arguments) ? fn.arguments : safeJsonParse(fn.arguments);
|
|
117
|
+
out.push({ id, name, input: isPlainObject(parsedArgs) ? parsedArgs : {} });
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Pure parser: message rows → LogEntry[] ──
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Convert `messages`-table rows (ordered by timestamp) into `LogEntry[]`.
|
|
126
|
+
* Pairs each assistant `tool_calls` entry with its later `role:"tool"` result
|
|
127
|
+
* by `tool_call_id`. Roles other than user/assistant/tool (e.g. Hermes's
|
|
128
|
+
* `session_meta`) become generic system entries so nothing is dropped. Pure —
|
|
129
|
+
* unit-testable with plain row objects.
|
|
130
|
+
*/
|
|
131
|
+
export function hermesRowsToLogEntries(
|
|
132
|
+
rows: Record<string, unknown>[],
|
|
133
|
+
source: LogSource = "session",
|
|
134
|
+
): LogEntry[] {
|
|
135
|
+
const entries: LogEntry[] = [];
|
|
136
|
+
const toolUseById = new Map<string, ToolUseBlock>();
|
|
137
|
+
const toolUseStartMs = new Map<string, number>();
|
|
138
|
+
const baseMs = Date.now();
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < rows.length; i++) {
|
|
141
|
+
const m = rows[i];
|
|
142
|
+
if (!isPlainObject(m)) continue;
|
|
143
|
+
|
|
144
|
+
const role = typeof m.role === "string" ? m.role : "system";
|
|
145
|
+
const date = toDate(m.timestamp ?? m.time_created, baseMs + i);
|
|
146
|
+
const timestamp = date.toISOString();
|
|
147
|
+
const raw: Record<string, unknown> = {
|
|
148
|
+
uuid: m.id != null ? String(m.id) : `hermes-${i}`,
|
|
149
|
+
parentUuid: null,
|
|
150
|
+
};
|
|
151
|
+
const base = baseEntry(raw, timestamp, date, source);
|
|
152
|
+
|
|
153
|
+
if (role === "user") {
|
|
154
|
+
entries.push({
|
|
155
|
+
type: "user",
|
|
156
|
+
...base,
|
|
157
|
+
message: { role: "user", content: extractText(m.content) },
|
|
158
|
+
} satisfies UserEntry);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (role === "assistant") {
|
|
163
|
+
const blocks: ContentBlock[] = [];
|
|
164
|
+
const text = extractText(m.content);
|
|
165
|
+
if (text) blocks.push({ type: "text", text });
|
|
166
|
+
for (const tc of normalizeToolCalls(m.tool_calls)) {
|
|
167
|
+
const block: ToolUseBlock = { type: "tool_use", id: tc.id, name: tc.name, input: tc.input };
|
|
168
|
+
blocks.push(block);
|
|
169
|
+
toolUseById.set(tc.id, block);
|
|
170
|
+
toolUseStartMs.set(tc.id, date.getTime());
|
|
171
|
+
}
|
|
172
|
+
if (blocks.length === 0) continue; // empty assistant turn
|
|
173
|
+
entries.push({
|
|
174
|
+
type: "assistant",
|
|
175
|
+
...base,
|
|
176
|
+
message: { role: "assistant", content: blocks },
|
|
177
|
+
} satisfies AssistantEntry);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (role === "tool") {
|
|
182
|
+
const callId = typeof m.tool_call_id === "string" ? m.tool_call_id : undefined;
|
|
183
|
+
const block = callId ? toolUseById.get(callId) : undefined;
|
|
184
|
+
if (block) {
|
|
185
|
+
const startMs = (callId && toolUseStartMs.get(callId)) || date.getTime();
|
|
186
|
+
const durationMs = Math.max(0, date.getTime() - startMs);
|
|
187
|
+
block.result = {
|
|
188
|
+
timestamp,
|
|
189
|
+
timestampFormatted: formatTimestamp(date),
|
|
190
|
+
content: extractText(m.content),
|
|
191
|
+
durationMs,
|
|
192
|
+
durationFormatted: formatDuration(durationMs),
|
|
193
|
+
};
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
// Orphan tool result — fall through to a system entry.
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
entries.push({ type: "system", ...base, raw } satisfies GenericEntry);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
entries.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
203
|
+
return entries;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── DB loader ──
|
|
207
|
+
|
|
208
|
+
export interface HermesSessionLogData {
|
|
209
|
+
entries: LogEntry[];
|
|
210
|
+
rawLines: Record<string, unknown>[];
|
|
211
|
+
cwd?: string;
|
|
212
|
+
filePath: string; // synthetic — hermes keeps sessions in a DB; we use hermes://<id>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
interface HermesMessageRow {
|
|
216
|
+
id: number | null;
|
|
217
|
+
role: string | null;
|
|
218
|
+
content: string | null;
|
|
219
|
+
tool_call_id: string | null;
|
|
220
|
+
tool_calls: string | null;
|
|
221
|
+
tool_name: string | null;
|
|
222
|
+
timestamp: number | null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Load one session by ID from `state.db`. Returns `null` when the DB is
|
|
227
|
+
* unavailable or the session doesn't exist.
|
|
228
|
+
*/
|
|
229
|
+
export async function getHermesSessionLog(
|
|
230
|
+
sessionId: string,
|
|
231
|
+
): Promise<HermesSessionLogData | null> {
|
|
232
|
+
if (!sessionId || !/^[A-Za-z0-9_-]+$/.test(sessionId)) return null;
|
|
233
|
+
const db = await openSqliteReadonly(hermesDbPath());
|
|
234
|
+
if (!db) return null;
|
|
235
|
+
try {
|
|
236
|
+
const sessionRows = db.query<{ source: string | null; cwd: string | null }>(
|
|
237
|
+
"SELECT source, cwd FROM sessions WHERE id = ?",
|
|
238
|
+
[sessionId],
|
|
239
|
+
);
|
|
240
|
+
if (sessionRows.length === 0) return null;
|
|
241
|
+
const { source, cwd: realCwd } = sessionRows[0];
|
|
242
|
+
|
|
243
|
+
const msgRows = db.query<HermesMessageRow>(
|
|
244
|
+
"SELECT id, role, content, tool_call_id, tool_calls, tool_name, timestamp " +
|
|
245
|
+
"FROM messages WHERE session_id = ? ORDER BY timestamp ASC",
|
|
246
|
+
[sessionId],
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const entries = hermesRowsToLogEntries(
|
|
250
|
+
msgRows as unknown as Record<string, unknown>[],
|
|
251
|
+
"session",
|
|
252
|
+
);
|
|
253
|
+
// Gateway sessions have no cwd → group by source (slack/telegram/cli/cron).
|
|
254
|
+
const cwd =
|
|
255
|
+
realCwd && realCwd.length > 0 ? realCwd : source ? `hermes:${source}` : undefined;
|
|
256
|
+
return {
|
|
257
|
+
entries,
|
|
258
|
+
rawLines: msgRows as unknown as Record<string, unknown>[],
|
|
259
|
+
cwd,
|
|
260
|
+
filePath: `hermes://${sessionId}`,
|
|
261
|
+
};
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
} finally {
|
|
265
|
+
db.close();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export const getCachedHermesSessionLog = runtimeCache(
|
|
270
|
+
(sessionId: string) => getHermesSessionLog(sessionId),
|
|
271
|
+
2,
|
|
272
|
+
{ maxSize: 50 },
|
|
273
|
+
);
|
|
@@ -16,7 +16,7 @@ import { formatDate } from "./format-date";
|
|
|
16
16
|
export const UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
17
17
|
export const PATH_TRAVERSAL_RE = /(^|[\\/])\.\.($|[\\/])/;
|
|
18
18
|
|
|
19
|
-
export type ProjectCli = "claude" | "codex" | "copilot" | "cursor" | "opencode" | "pi" | "gemini";
|
|
19
|
+
export type ProjectCli = "claude" | "codex" | "copilot" | "cursor" | "opencode" | "pi" | "gemini" | "hermes";
|
|
20
20
|
|
|
21
21
|
export interface ProjectFolder {
|
|
22
22
|
name: string;
|
|
@@ -40,6 +40,11 @@ export interface SessionFile {
|
|
|
40
40
|
/** Originating agent CLI. Set when the session list mixes sources from more
|
|
41
41
|
* than one CLI, so the table can render a per-row CLI badge. */
|
|
42
42
|
cli?: ProjectCli;
|
|
43
|
+
/** Gateway session metadata (Hermes only today): who drove it and where.
|
|
44
|
+
* Other CLIs leave these undefined; the list renders them only when present. */
|
|
45
|
+
userId?: string;
|
|
46
|
+
channelId?: string;
|
|
47
|
+
channelType?: string; // e.g. "group" | "dm"
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
/** Stats a path and returns mtime. Falls back to epoch (1970-01-01) on error
|
|
@@ -139,6 +144,7 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
139
144
|
{ getOpenCodeProjects },
|
|
140
145
|
{ getPiProjects },
|
|
141
146
|
{ getGeminiProjects },
|
|
147
|
+
{ getHermesProjects },
|
|
142
148
|
] = await Promise.all([
|
|
143
149
|
import("./codex-projects"),
|
|
144
150
|
import("./copilot-projects"),
|
|
@@ -146,8 +152,9 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
146
152
|
import("./opencode-projects"),
|
|
147
153
|
import("./pi-projects"),
|
|
148
154
|
import("./gemini-projects"),
|
|
155
|
+
import("./hermes-projects"),
|
|
149
156
|
]);
|
|
150
|
-
const [claude, codex, copilot, cursor, opencode, pi, gemini] = await Promise.all([
|
|
157
|
+
const [claude, codex, copilot, cursor, opencode, pi, gemini, hermes] = await Promise.all([
|
|
151
158
|
getClaudeProjectFolders(),
|
|
152
159
|
getCodexProjects().catch((error) => {
|
|
153
160
|
logError("Error reading Codex projects:", error);
|
|
@@ -173,8 +180,12 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
173
180
|
logError("Error reading Gemini projects:", error);
|
|
174
181
|
return [] as ProjectFolder[];
|
|
175
182
|
}),
|
|
183
|
+
getHermesProjects().catch((error) => {
|
|
184
|
+
logError("Error reading Hermes projects:", error);
|
|
185
|
+
return [] as ProjectFolder[];
|
|
186
|
+
}),
|
|
176
187
|
]);
|
|
177
|
-
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi, gemini);
|
|
188
|
+
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi, gemini, hermes);
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
/**
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable read-only SQLite access for audit adapters of SQLite-backed agents.
|
|
3
|
+
*
|
|
4
|
+
* Tiered so we read LIVE data (including the write-ahead log) wherever possible,
|
|
5
|
+
* and still run anywhere:
|
|
6
|
+
* 1. `node:sqlite` — a real connection that reads main DB + WAL together, so
|
|
7
|
+
* brand-new rows are visible immediately. Built into Node ≥ 22.5 (no native
|
|
8
|
+
* module, no flag on recent 22.x). Preferred.
|
|
9
|
+
* 2. `sql.js` (pure-JS/asm) — portable fallback for older Node. Reads the main
|
|
10
|
+
* DB file's bytes only, so it reflects a snapshot up to the last WAL
|
|
11
|
+
* checkpoint (very recent rows may lag). No native module — survives
|
|
12
|
+
* `npm install --ignore-scripts`.
|
|
13
|
+
*
|
|
14
|
+
* Either way: read-only, and `null` on any failure (fail-open, so an absent or
|
|
15
|
+
* locked DB makes the audit skip that agent rather than crash).
|
|
16
|
+
*
|
|
17
|
+
* NOTE: opencode and every CLI that already ships keep their existing CLI
|
|
18
|
+
* shell-out — this layer is only for new SQLite integrations.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
+
import initSqlJs, { type SqlJsStatic } from "sql.js/dist/sql-asm.js";
|
|
22
|
+
|
|
23
|
+
export interface SqliteReader {
|
|
24
|
+
/** Run a parameterized read-only query; returns rows as plain objects. */
|
|
25
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Tier 1: node:sqlite (WAL-aware, Node ≥ 22.5) ──
|
|
30
|
+
|
|
31
|
+
interface NodeSqliteStmt {
|
|
32
|
+
all(...params: unknown[]): Record<string, unknown>[];
|
|
33
|
+
}
|
|
34
|
+
interface NodeSqliteDb {
|
|
35
|
+
prepare(sql: string): NodeSqliteStmt;
|
|
36
|
+
close(): void;
|
|
37
|
+
}
|
|
38
|
+
interface NodeSqliteModule {
|
|
39
|
+
DatabaseSync: new (path: string, options?: { readOnly?: boolean }) => NodeSqliteDb;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function tryNodeSqlite(dbPath: string): Promise<SqliteReader | null> {
|
|
43
|
+
try {
|
|
44
|
+
// Dynamic import: on Node < 22.5 this rejects and we fall through to sql.js.
|
|
45
|
+
const mod = (await import("node:sqlite")) as unknown as NodeSqliteModule;
|
|
46
|
+
const db = new mod.DatabaseSync(dbPath, { readOnly: true });
|
|
47
|
+
return {
|
|
48
|
+
query<T = Record<string, unknown>>(sql: string, params: unknown[] = []): T[] {
|
|
49
|
+
const stmt = db.prepare(sql);
|
|
50
|
+
return (params.length ? stmt.all(...params) : stmt.all()) as T[];
|
|
51
|
+
},
|
|
52
|
+
close() {
|
|
53
|
+
db.close();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Tier 2: sql.js (portable snapshot) ──
|
|
62
|
+
|
|
63
|
+
let sqlPromise: Promise<SqlJsStatic> | null = null;
|
|
64
|
+
function loadSqlJs(): Promise<SqlJsStatic> {
|
|
65
|
+
if (!sqlPromise) sqlPromise = initSqlJs();
|
|
66
|
+
return sqlPromise;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function trySqlJs(dbPath: string): Promise<SqliteReader | null> {
|
|
70
|
+
try {
|
|
71
|
+
const SQL = await loadSqlJs();
|
|
72
|
+
const db = new SQL.Database(readFileSync(dbPath));
|
|
73
|
+
return {
|
|
74
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[] {
|
|
75
|
+
const stmt = db.prepare(sql);
|
|
76
|
+
try {
|
|
77
|
+
if (params && params.length) stmt.bind(params);
|
|
78
|
+
const rows: T[] = [];
|
|
79
|
+
while (stmt.step()) rows.push(stmt.getAsObject() as T);
|
|
80
|
+
return rows;
|
|
81
|
+
} finally {
|
|
82
|
+
stmt.free();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
close() {
|
|
86
|
+
db.close();
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Open a SQLite database file READ-ONLY. Prefers a WAL-aware `node:sqlite`
|
|
96
|
+
* connection (live data), falls back to a `sql.js` snapshot. Returns `null` on
|
|
97
|
+
* any failure. Always `close()` the reader when done.
|
|
98
|
+
*/
|
|
99
|
+
export async function openSqliteReadonly(dbPath: string): Promise<SqliteReader | null> {
|
|
100
|
+
if (!existsSync(dbPath)) return null;
|
|
101
|
+
return (await tryNodeSqlite(dbPath)) ?? (await trySqlJs(dbPath));
|
|
102
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.13-beta.
|
|
3
|
+
"version": "0.0.13-beta.2",
|
|
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"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"predev": "bun run build:cli && bun link",
|
|
24
24
|
"dev": "FAILPROOFAI_TELEMETRY_DISABLED=1 bun scripts/dev.ts --port 8020",
|
|
25
|
-
"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',''))\"",
|
|
25
|
+
"build:cli": "bun build --target=node --format=esm --outfile=dist/cli.mjs bin/failproofai.mjs --external posthog-node --external sql.js && 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',''))\"",
|
|
26
26
|
"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",
|
|
27
27
|
"prestart": "bun run build:cli && bun link",
|
|
28
28
|
"start": "FAILPROOFAI_TELEMETRY_DISABLED=1 bun scripts/start.ts",
|
|
@@ -99,7 +99,9 @@
|
|
|
99
99
|
"dependencies": {
|
|
100
100
|
"html-to-image": "^1.11.13",
|
|
101
101
|
"html2canvas": "^1.4.1",
|
|
102
|
-
"posthog-node": "^5.37.1"
|
|
102
|
+
"posthog-node": "^5.37.1",
|
|
103
|
+
"sql.js": "^1.14.1",
|
|
104
|
+
"yaml": "^2.9.0"
|
|
103
105
|
},
|
|
104
106
|
"overrides": {
|
|
105
107
|
"postcss": "8.5.14",
|
|
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
9
9
|
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
|
10
10
|
|
|
11
11
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
12
|
-
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.13-beta.
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.13-beta.2"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|
package/README.md
CHANGED
|
@@ -72,9 +72,18 @@ before they become incidents. Zero latency. Runs locally.
|
|
|
72
72
|
<img src="assets/logos/gemini-light.svg" alt="Gemini CLI" width="64" height="64" />
|
|
73
73
|
</picture>
|
|
74
74
|
</a>
|
|
75
|
+
|
|
76
|
+
<a href="https://github.com/FailproofAI/failproofai/blob/main/docs/configuration.mdx" title="Hermes (hermes-agent)">
|
|
77
|
+
<picture>
|
|
78
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logos/hermes-dark.svg" />
|
|
79
|
+
<img src="assets/logos/hermes-light.svg" alt="Hermes" width="64" height="64" />
|
|
80
|
+
</picture>
|
|
81
|
+
</a>
|
|
75
82
|
</p>
|
|
76
83
|
|
|
77
|
-
> Install hooks for one or any combination: `failproofai policies --install --cli opencode pi gemini` (or `--cli claude codex copilot cursor opencode pi gemini`). Omit `--cli` to auto-detect installed CLIs and prompt.
|
|
84
|
+
> Install hooks for one or any combination: `failproofai policies --install --cli opencode pi gemini` (or `--cli claude codex copilot cursor opencode pi gemini hermes`). Omit `--cli` to auto-detect installed CLIs and prompt.
|
|
85
|
+
>
|
|
86
|
+
> **Hermes** (hermes-agent, a Slack/Telegram gateway) is supported for both **live-hook enforcement** (`--cli hermes` — one install intercepts tool calls from every platform and subagent) and offline **audit** replay of its gateway sessions from the single `~/.hermes/state.db`.
|
|
78
87
|
|
|
79
88
|
---
|
|
80
89
|
|
package/bin/failproofai.mjs
CHANGED
|
@@ -65,7 +65,7 @@ const hookIdx = args.indexOf("--hook");
|
|
|
65
65
|
if (hookIdx >= 0) {
|
|
66
66
|
if (!args[hookIdx + 1]) {
|
|
67
67
|
console.error("Error: Missing event type after --hook");
|
|
68
|
-
console.error("Usage: failproofai --hook <event> [--cli <claude|codex|copilot|cursor|opencode|pi|gemini>]");
|
|
68
|
+
console.error("Usage: failproofai --hook <event> [--cli <claude|codex|copilot|cursor|opencode|pi|gemini|hermes>]");
|
|
69
69
|
process.exit(1);
|
|
70
70
|
}
|
|
71
71
|
const eventType = args[hookIdx + 1];
|
|
@@ -82,6 +82,7 @@ if (hookIdx >= 0) {
|
|
|
82
82
|
|| cliArg === "opencode"
|
|
83
83
|
|| cliArg === "pi"
|
|
84
84
|
|| cliArg === "gemini"
|
|
85
|
+
|| cliArg === "hermes"
|
|
85
86
|
)
|
|
86
87
|
? cliArg
|
|
87
88
|
: "claude";
|
|
@@ -129,9 +130,9 @@ COMMANDS
|
|
|
129
130
|
policies, p List all available policies and their status
|
|
130
131
|
policies --install, -i Enable policies in agent CLI settings
|
|
131
132
|
[names...] Specific policy names to enable
|
|
132
|
-
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
133
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini|hermes
|
|
133
134
|
Agent CLI(s) to install for; space-separated
|
|
134
|
-
(e.g. --cli claude codex copilot cursor opencode pi gemini) or repeated.
|
|
135
|
+
(e.g. --cli claude codex copilot cursor opencode pi gemini hermes) or repeated.
|
|
135
136
|
Default: detect installed CLIs and prompt.
|
|
136
137
|
--scope user|project|local Config scope to write to (default: user)
|
|
137
138
|
(Codex / Copilot / Cursor / OpenCode / Pi / Gemini support user|project only)
|
|
@@ -140,7 +141,7 @@ COMMANDS
|
|
|
140
141
|
|
|
141
142
|
policies --uninstall, -u Disable policies or remove hooks
|
|
142
143
|
[names...] Specific policy names to disable
|
|
143
|
-
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
144
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini|hermes
|
|
144
145
|
Agent CLI(s) to uninstall from
|
|
145
146
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
146
147
|
--beta Remove only beta policies
|
|
@@ -176,7 +177,7 @@ EXAMPLES
|
|
|
176
177
|
failproofai policies --install --cli opencode --scope project
|
|
177
178
|
failproofai policies --install --cli pi --scope project
|
|
178
179
|
failproofai policies --install --cli gemini --scope project
|
|
179
|
-
failproofai policies --install --cli claude codex copilot cursor opencode pi gemini
|
|
180
|
+
failproofai policies --install --cli claude codex copilot cursor opencode pi gemini hermes
|
|
180
181
|
failproofai policies --install --custom ./my-policies.js
|
|
181
182
|
failproofai policies -i -c ./my-policies.js
|
|
182
183
|
failproofai policies --uninstall block-sudo
|
|
@@ -225,9 +226,9 @@ USAGE
|
|
|
225
226
|
|
|
226
227
|
OPTIONS (install)
|
|
227
228
|
[names...] Specific policy names to enable (omit for interactive)
|
|
228
|
-
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
229
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini|hermes
|
|
229
230
|
Agent CLI(s) to install for; space-separated
|
|
230
|
-
(e.g. --cli claude codex copilot cursor opencode pi gemini) or repeated.
|
|
231
|
+
(e.g. --cli claude codex copilot cursor opencode pi gemini hermes) or repeated.
|
|
231
232
|
Omit to detect installed CLIs and prompt (or
|
|
232
233
|
auto-pick if only one is found).
|
|
233
234
|
--scope user|project|local Config scope to write to (default: user)
|
|
@@ -238,7 +239,7 @@ OPTIONS (install)
|
|
|
238
239
|
|
|
239
240
|
OPTIONS (uninstall)
|
|
240
241
|
[names...] Specific policy names to disable (omit to remove hooks)
|
|
241
|
-
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
242
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini|hermes
|
|
242
243
|
Agent CLI(s) to uninstall from
|
|
243
244
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
244
245
|
--beta Remove only beta policies
|
|
@@ -254,7 +255,7 @@ EXAMPLES
|
|
|
254
255
|
failproofai policies --install --cli opencode --scope project
|
|
255
256
|
failproofai policies --install --cli pi --scope project
|
|
256
257
|
failproofai policies --install --cli gemini --scope project
|
|
257
|
-
failproofai policies --install --cli claude codex copilot cursor opencode pi gemini
|
|
258
|
+
failproofai policies --install --cli claude codex copilot cursor opencode pi gemini hermes
|
|
258
259
|
failproofai policies --install --custom ./my-policies.js
|
|
259
260
|
failproofai policies -i -c ./my-policies.js
|
|
260
261
|
failproofai policies --uninstall block-sudo
|
|
@@ -295,7 +296,7 @@ EXAMPLES
|
|
|
295
296
|
// --cli claude codex copilot
|
|
296
297
|
// --cli claude --cli codex
|
|
297
298
|
// Values are consumed greedily until the next flag or end of argv.
|
|
298
|
-
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"]);
|
|
299
|
+
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini", "hermes"]);
|
|
299
300
|
const cliFlagValues = [];
|
|
300
301
|
const cliConsumedIdxs = new Set();
|
|
301
302
|
const cliFlagIdxs = subArgs.map((a, i) => (a === "--cli" ? i : -1)).filter((i) => i >= 0);
|
|
@@ -312,7 +313,7 @@ EXAMPLES
|
|
|
312
313
|
consumed++;
|
|
313
314
|
}
|
|
314
315
|
if (consumed === 0) {
|
|
315
|
-
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini (or any subset)");
|
|
316
|
+
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini hermes (or any subset)");
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
|
|
@@ -384,7 +385,7 @@ EXAMPLES
|
|
|
384
385
|
}
|
|
385
386
|
|
|
386
387
|
// --cli accepts one or more space-separated values; same parser as install.
|
|
387
|
-
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"]);
|
|
388
|
+
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini", "hermes"]);
|
|
388
389
|
const cliFlagValues = [];
|
|
389
390
|
const cliConsumedIdxs = new Set();
|
|
390
391
|
const cliFlagIdxs = subArgs.map((a, i) => (a === "--cli" ? i : -1)).filter((i) => i >= 0);
|
|
@@ -401,7 +402,7 @@ EXAMPLES
|
|
|
401
402
|
consumed++;
|
|
402
403
|
}
|
|
403
404
|
if (consumed === 0) {
|
|
404
|
-
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini (or any subset)");
|
|
405
|
+
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini hermes (or any subset)");
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
|
|
@@ -510,7 +511,7 @@ USAGE
|
|
|
510
511
|
failproofai policy remove <name> Disable one policy
|
|
511
512
|
|
|
512
513
|
OPTIONS
|
|
513
|
-
--cli claude|codex|copilot|cursor|opencode|pi|gemini
|
|
514
|
+
--cli claude|codex|copilot|cursor|opencode|pi|gemini|hermes
|
|
514
515
|
Agent CLI(s) to apply to; space-separated or repeated.
|
|
515
516
|
Omit to detect installed CLIs and prompt.
|
|
516
517
|
--scope user|project|local Config scope (default: user)
|
|
@@ -548,7 +549,7 @@ EXAMPLES
|
|
|
548
549
|
}
|
|
549
550
|
|
|
550
551
|
// --cli accepts one or more space-separated values, optionally repeated.
|
|
551
|
-
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"]);
|
|
552
|
+
const VALID_CLIS = new Set(["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini", "hermes"]);
|
|
552
553
|
const cliFlagValues = [];
|
|
553
554
|
const cliConsumedIdxs = new Set();
|
|
554
555
|
const cliFlagIdxs = rest.map((a, i) => (a === "--cli" ? i : -1)).filter((i) => i >= 0);
|
|
@@ -563,7 +564,7 @@ EXAMPLES
|
|
|
563
564
|
consumed++;
|
|
564
565
|
}
|
|
565
566
|
if (consumed === 0) {
|
|
566
|
-
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini (or any subset)");
|
|
567
|
+
throw new CliError("Missing value(s) for --cli. Usage: --cli claude codex copilot cursor opencode pi gemini hermes (or any subset)");
|
|
567
568
|
}
|
|
568
569
|
}
|
|
569
570
|
|