failproofai 1.0.5 → 1.0.6-beta.1
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 +5 -5
- package/.next/standalone/.next/prerender-manifest.json +5 -5
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- 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.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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- 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.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 +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- 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 +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- 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 +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +6 -5
- 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/build-manifest.json +2 -2
- 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 +1 -1
- 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/build-manifest.json +2 -2
- 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 +2 -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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- 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/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +3 -2
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0sds_16._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zlqo2t._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__112b4go._.js → [root-of-the-server]__1_r2rbg._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1anxb5k._.js +1 -1
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +3 -3
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- 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]__0-_ki57._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bym1os._.js → [root-of-the-server]__0cg-bgc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__0u3w0ll._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0sz_5na._.js → [root-of-the-server]__1ouobn4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__15k-dqb._.js → [root-of-the-server]__1x3j9mj._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0guaevg._.js → _0qlygw6._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1b78kcp._.js → _1_ddpm3._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1_qswah._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -11
- 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_components_project-list_tsx_1ke6anr._.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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- 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 +19 -19
- package/.next/standalone/.next/static/chunks/{3w46euc60dx39.js → 02vwr5js1x28j.js} +1 -1
- package/.next/standalone/.next/static/chunks/06fjx8-0_qufs.js +1 -0
- package/.next/standalone/.next/static/chunks/{2zafgs90r_leq.js → 06jxjnw8bfczc.js} +1 -1
- package/.next/standalone/.next/static/chunks/0cbvytf8epn34.js +1 -0
- package/.next/standalone/.next/static/chunks/0y227_ohdqwvn.js +1 -0
- package/.next/standalone/.next/static/chunks/{252xreaecy80h.js → 1p4xauf-zu6zk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{021ydrq6rhpsi.js → 1qf2uiipatr80.js} +1 -1
- package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +6 -0
- package/.next/standalone/.next/static/chunks/1xacnwjkbekn7.js +1 -0
- package/.next/standalone/.next/static/chunks/{11s_v03ub6nss.js → 27_bwbg8sv6vj.js} +1 -1
- package/.next/standalone/.next/static/chunks/2hg_bp8z_vm2g.js +1 -0
- package/.next/standalone/.next/static/chunks/{3bleyy40s99f2.js → 2x776s0x3n_tu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1vb33xa6j9y2q.js → 3ht0xsco46pul.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-3p73obe0xmrxm.js → turbopack-38nzuvp95_2rv.js} +1 -1
- package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
- package/.next/standalone/hermes-plugin/README.md +138 -0
- package/.next/standalone/hermes-plugin/__init__.py +248 -0
- package/.next/standalone/hermes-plugin/client.py +150 -0
- package/.next/standalone/hermes-plugin/ledger.py +187 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +37 -0
- package/.next/standalone/lib/hermes-profiles.ts +67 -19
- package/.next/standalone/lib/hermes-sessions.ts +3 -2
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/app-render/create-component-styles-and-scripts.js +2 -1
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +14 -0
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/disk-lru-cache.external.js +7 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +6 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/node_modules/react/cjs/react.development.js +92 -46
- package/.next/standalone/node_modules/react/cjs/react.production.js +112 -89
- package/.next/standalone/node_modules/react/package.json +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1165 -790
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1171 -791
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +1294 -862
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +1305 -868
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +1315 -896
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +24 -5
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +13 -12
- package/.next/standalone/sdk/python/uv.lock +18 -18
- package/.next/standalone/server.js +1 -1
- package/README.md +29 -13
- package/bin/failproofai.mjs +3 -1
- package/dist/cli.mjs +655 -360
- package/dist/worker.mjs +78 -34
- package/hermes-plugin/README.md +138 -0
- package/hermes-plugin/__init__.py +248 -0
- package/hermes-plugin/client.py +150 -0
- package/hermes-plugin/ledger.py +187 -0
- package/hermes-plugin/plugin.yaml +37 -0
- package/lib/hermes-profiles.ts +67 -19
- package/lib/hermes-sessions.ts +3 -2
- package/package.json +13 -12
- package/scripts/translate-docs/validate-translation.ts +48 -0
- package/src/hooks/handler.ts +29 -1
- package/src/hooks/integrations.ts +271 -73
- package/src/hooks/manager.ts +41 -7
- package/src/hooks/types.ts +8 -8
- package/src/hooks/worker-server.ts +1 -0
- package/.next/standalone/.next/server/chunks/[externals]__157pf8j._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +0 -3
- package/.next/standalone/.next/static/chunks/0y8_1s45gbqmn.js +0 -6
- package/.next/standalone/.next/static/chunks/14b3ezufe5411.js +0 -1
- package/.next/standalone/.next/static/chunks/1oskk7pdo8aon.js +0 -1
- package/.next/standalone/.next/static/chunks/2xycd6mjg00fb.js +0 -1
- package/.next/standalone/.next/static/chunks/3l36d73kj18zw.js +0 -1
- package/.next/standalone/.next/static/chunks/40lpt2o2b283e.js +0 -1
- /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → QCNYz2yCxxdeggg1k0iEq}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → QCNYz2yCxxdeggg1k0iEq}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → QCNYz2yCxxdeggg1k0iEq}/_ssgManifest.js +0 -0
package/dist/worker.mjs
CHANGED
|
@@ -3483,7 +3483,7 @@ function parsePackPolicyName(canonicalName) {
|
|
|
3483
3483
|
}
|
|
3484
3484
|
|
|
3485
3485
|
// package.json
|
|
3486
|
-
var version = "1.0.
|
|
3486
|
+
var version = "1.0.6-beta.1";
|
|
3487
3487
|
var init_package = () => {};
|
|
3488
3488
|
|
|
3489
3489
|
// src/posthog-key.ts
|
|
@@ -6388,36 +6388,66 @@ var init_pi_projects = __esm(() => {
|
|
|
6388
6388
|
});
|
|
6389
6389
|
|
|
6390
6390
|
// lib/hermes-profiles.ts
|
|
6391
|
-
import { readdirSync as readdirSync3 } from "node:fs";
|
|
6391
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
6392
6392
|
import { homedir as homedir10 } from "node:os";
|
|
6393
6393
|
import { basename as basename2, dirname as dirname3, join as join11, resolve as resolve8 } from "node:path";
|
|
6394
6394
|
function hermesRoot() {
|
|
6395
6395
|
const env = (process.env.HERMES_HOME || "").trim();
|
|
6396
6396
|
if (env) {
|
|
6397
|
-
|
|
6397
|
+
let home = resolve8(env);
|
|
6398
6398
|
const parent = dirname3(home);
|
|
6399
6399
|
if (basename2(parent) === PROFILES_DIR)
|
|
6400
|
-
|
|
6400
|
+
home = dirname3(parent);
|
|
6401
|
+
const base = basename2(home);
|
|
6402
|
+
if (base === STANDARD_HOME || base.startsWith(SIBLING_HOME_PREFIX)) {
|
|
6403
|
+
return join11(dirname3(home), STANDARD_HOME);
|
|
6404
|
+
}
|
|
6401
6405
|
return home;
|
|
6402
6406
|
}
|
|
6403
|
-
return join11(homedir10(),
|
|
6407
|
+
return join11(homedir10(), STANDARD_HOME);
|
|
6408
|
+
}
|
|
6409
|
+
function uniqueProfileName(preferred, seen) {
|
|
6410
|
+
if (!seen.has(preferred))
|
|
6411
|
+
return preferred;
|
|
6412
|
+
const base = `${preferred}-home`;
|
|
6413
|
+
if (!seen.has(base))
|
|
6414
|
+
return base;
|
|
6415
|
+
let suffix = 2;
|
|
6416
|
+
while (seen.has(`${base}-${suffix}`))
|
|
6417
|
+
suffix += 1;
|
|
6418
|
+
return `${base}-${suffix}`;
|
|
6404
6419
|
}
|
|
6405
6420
|
function listHermesProfiles() {
|
|
6406
6421
|
const root = hermesRoot();
|
|
6407
6422
|
const out = [{ name: HERMES_DEFAULT_PROFILE, home: root }];
|
|
6408
6423
|
const seen = new Set([HERMES_DEFAULT_PROFILE]);
|
|
6409
|
-
let names;
|
|
6424
|
+
let names = [];
|
|
6410
6425
|
try {
|
|
6411
6426
|
names = readdirSync3(join11(root, PROFILES_DIR), { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
6412
|
-
} catch {
|
|
6413
|
-
return out;
|
|
6414
|
-
}
|
|
6427
|
+
} catch {}
|
|
6415
6428
|
for (const name of names) {
|
|
6416
6429
|
if (seen.has(name))
|
|
6417
6430
|
continue;
|
|
6418
6431
|
seen.add(name);
|
|
6419
6432
|
out.push({ name, home: join11(root, PROFILES_DIR, name) });
|
|
6420
6433
|
}
|
|
6434
|
+
if (basename2(root) !== STANDARD_HOME)
|
|
6435
|
+
return out;
|
|
6436
|
+
let siblings = [];
|
|
6437
|
+
const parent = dirname3(root);
|
|
6438
|
+
try {
|
|
6439
|
+
siblings = readdirSync3(parent, { withFileTypes: true }).filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && entry.name.startsWith(SIBLING_HOME_PREFIX) && entry.name.length > SIBLING_HOME_PREFIX.length && existsSync9(join11(parent, entry.name, "config.yaml"))).map((entry) => ({
|
|
6440
|
+
name: entry.name.slice(SIBLING_HOME_PREFIX.length),
|
|
6441
|
+
home: join11(parent, entry.name)
|
|
6442
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
6443
|
+
} catch {
|
|
6444
|
+
return out;
|
|
6445
|
+
}
|
|
6446
|
+
for (const sibling of siblings) {
|
|
6447
|
+
const name = uniqueProfileName(sibling.name, seen);
|
|
6448
|
+
seen.add(name);
|
|
6449
|
+
out.push({ name, home: sibling.home });
|
|
6450
|
+
}
|
|
6421
6451
|
return out;
|
|
6422
6452
|
}
|
|
6423
6453
|
function hermesProjectName(profile, source) {
|
|
@@ -6443,7 +6473,7 @@ function hermesProjectNameCandidates(name) {
|
|
|
6443
6473
|
out.push({ profile: HERMES_DEFAULT_PROFILE, source: rest });
|
|
6444
6474
|
return out;
|
|
6445
6475
|
}
|
|
6446
|
-
var PROFILES_DIR = "profiles", HERMES_DEFAULT_PROFILE = "default";
|
|
6476
|
+
var PROFILES_DIR = "profiles", STANDARD_HOME = ".hermes", SIBLING_HOME_PREFIX = ".hermes-", HERMES_DEFAULT_PROFILE = "default";
|
|
6447
6477
|
var init_hermes_profiles = () => {};
|
|
6448
6478
|
|
|
6449
6479
|
// lib/format-duration.ts
|
|
@@ -6765,7 +6795,7 @@ var init_hermes_projects = __esm(() => {
|
|
|
6765
6795
|
});
|
|
6766
6796
|
|
|
6767
6797
|
// lib/openclaw-db.ts
|
|
6768
|
-
import { existsSync as
|
|
6798
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
6769
6799
|
import { join as join13 } from "node:path";
|
|
6770
6800
|
function str(value) {
|
|
6771
6801
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
@@ -6785,7 +6815,7 @@ function listOpenClawDatabases(home, agentIds) {
|
|
|
6785
6815
|
const out = [];
|
|
6786
6816
|
for (const agentId of agentIds) {
|
|
6787
6817
|
const dbPath = join13(home, "agents", agentId, "agent", DB_NAME);
|
|
6788
|
-
if (
|
|
6818
|
+
if (existsSync10(dbPath))
|
|
6789
6819
|
out.push({ agentId, dbPath });
|
|
6790
6820
|
}
|
|
6791
6821
|
return out;
|
|
@@ -8962,13 +8992,13 @@ var init_log_entries = __esm(() => {
|
|
|
8962
8992
|
});
|
|
8963
8993
|
|
|
8964
8994
|
// lib/codex-sessions.ts
|
|
8965
|
-
import { readFileSync as readFileSync8, readdirSync as readdirSync7, existsSync as
|
|
8995
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync7, existsSync as existsSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, statSync as statSync10 } from "node:fs";
|
|
8966
8996
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
8967
8997
|
import { dirname as dirname4, join as join24 } from "node:path";
|
|
8968
8998
|
import { homedir as homedir16 } from "node:os";
|
|
8969
8999
|
function readCache() {
|
|
8970
9000
|
try {
|
|
8971
|
-
if (!
|
|
9001
|
+
if (!existsSync11(CACHE_PATH))
|
|
8972
9002
|
return {};
|
|
8973
9003
|
return JSON.parse(readFileSync8(CACHE_PATH, "utf-8"));
|
|
8974
9004
|
} catch {
|
|
@@ -8996,7 +9026,7 @@ function dirSearch(dir, sessionId) {
|
|
|
8996
9026
|
function findCodexTranscript(sessionId) {
|
|
8997
9027
|
const cache = readCache();
|
|
8998
9028
|
const cached = cache[sessionId];
|
|
8999
|
-
if (cached &&
|
|
9029
|
+
if (cached && existsSync11(cached))
|
|
9000
9030
|
return cached;
|
|
9001
9031
|
const root = join24(homedir16(), ".codex", "sessions");
|
|
9002
9032
|
const today = new Date;
|
|
@@ -9290,7 +9320,7 @@ var init_resolve_permission_mode = __esm(() => {
|
|
|
9290
9320
|
});
|
|
9291
9321
|
|
|
9292
9322
|
// lib/copilot-sessions.ts
|
|
9293
|
-
import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as
|
|
9323
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync12, statSync as statSync11 } from "node:fs";
|
|
9294
9324
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
9295
9325
|
import { join as join25, resolve as resolve10, sep as sep2 } from "node:path";
|
|
9296
9326
|
import { homedir as homedir17 } from "node:os";
|
|
@@ -9319,13 +9349,13 @@ function findCopilotTranscript(sessionId) {
|
|
|
9319
9349
|
const candidate = resolveSessionFile(sessionId, "events.jsonl");
|
|
9320
9350
|
if (!candidate)
|
|
9321
9351
|
return null;
|
|
9322
|
-
return
|
|
9352
|
+
return existsSync12(candidate) ? candidate : null;
|
|
9323
9353
|
}
|
|
9324
9354
|
function findCopilotWorkspace(sessionId) {
|
|
9325
9355
|
const candidate = resolveSessionFile(sessionId, "workspace.yaml");
|
|
9326
9356
|
if (!candidate)
|
|
9327
9357
|
return null;
|
|
9328
|
-
return
|
|
9358
|
+
return existsSync12(candidate) ? candidate : null;
|
|
9329
9359
|
}
|
|
9330
9360
|
function readYamlScalar(path, key) {
|
|
9331
9361
|
try {
|
|
@@ -9507,7 +9537,7 @@ var init_copilot_sessions = __esm(() => {
|
|
|
9507
9537
|
});
|
|
9508
9538
|
|
|
9509
9539
|
// lib/cursor-sessions.ts
|
|
9510
|
-
import { readFileSync as readFileSync11, readdirSync as readdirSync9, existsSync as
|
|
9540
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync9, existsSync as existsSync13, statSync as statSync12 } from "node:fs";
|
|
9511
9541
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
9512
9542
|
import { basename as basename3, join as join26, resolve as resolve11, sep as sep3 } from "node:path";
|
|
9513
9543
|
import { homedir as homedir18 } from "node:os";
|
|
@@ -9530,7 +9560,7 @@ function getCursorSessionDir(sessionId) {
|
|
|
9530
9560
|
const transcriptRoot = resolve11(projectsRoot, entry.name, NEW_AGENT_TRANSCRIPTS_DIR2);
|
|
9531
9561
|
if (candidate === transcriptRoot || !candidate.startsWith(`${transcriptRoot}${sep3}`))
|
|
9532
9562
|
continue;
|
|
9533
|
-
if (
|
|
9563
|
+
if (existsSync13(candidate))
|
|
9534
9564
|
return candidate;
|
|
9535
9565
|
}
|
|
9536
9566
|
for (const sub of LEGACY_SESSION_ROOT_CANDIDATES2) {
|
|
@@ -9538,7 +9568,7 @@ function getCursorSessionDir(sessionId) {
|
|
|
9538
9568
|
const candidate = resolve11(root, sessionId);
|
|
9539
9569
|
if (candidate === root || !candidate.startsWith(`${root}${sep3}`))
|
|
9540
9570
|
continue;
|
|
9541
|
-
if (
|
|
9571
|
+
if (existsSync13(candidate))
|
|
9542
9572
|
return candidate;
|
|
9543
9573
|
}
|
|
9544
9574
|
return null;
|
|
@@ -9548,11 +9578,11 @@ function findCursorTranscript(sessionId) {
|
|
|
9548
9578
|
if (!dir)
|
|
9549
9579
|
return null;
|
|
9550
9580
|
const newCandidate = join26(dir, `${basename3(dir)}.jsonl`);
|
|
9551
|
-
if (
|
|
9581
|
+
if (existsSync13(newCandidate))
|
|
9552
9582
|
return newCandidate;
|
|
9553
9583
|
for (const name of LEGACY_TRANSCRIPT_FILE_CANDIDATES2) {
|
|
9554
9584
|
const candidate = join26(dir, name);
|
|
9555
|
-
if (
|
|
9585
|
+
if (existsSync13(candidate))
|
|
9556
9586
|
return candidate;
|
|
9557
9587
|
}
|
|
9558
9588
|
return null;
|
|
@@ -9763,7 +9793,7 @@ var init_cursor_sessions = __esm(() => {
|
|
|
9763
9793
|
});
|
|
9764
9794
|
|
|
9765
9795
|
// lib/pi-sessions.ts
|
|
9766
|
-
import { readFileSync as readFileSync12, readdirSync as readdirSync10, existsSync as
|
|
9796
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync10, existsSync as existsSync14, statSync as statSync13 } from "node:fs";
|
|
9767
9797
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
9768
9798
|
import { join as join27, resolve as resolve12, sep as sep4 } from "node:path";
|
|
9769
9799
|
import { homedir as homedir19 } from "node:os";
|
|
@@ -9800,7 +9830,7 @@ function findPiTranscript(sessionId) {
|
|
|
9800
9830
|
const candidate = resolve12(cwdPath, f);
|
|
9801
9831
|
if (!candidate.startsWith(`${cwdPath}${sep4}`))
|
|
9802
9832
|
continue;
|
|
9803
|
-
if (
|
|
9833
|
+
if (existsSync14(candidate))
|
|
9804
9834
|
return candidate;
|
|
9805
9835
|
}
|
|
9806
9836
|
}
|
|
@@ -10160,7 +10190,7 @@ var init_pack_failclosed = __esm(() => {
|
|
|
10160
10190
|
var init_atomic_write = () => {};
|
|
10161
10191
|
|
|
10162
10192
|
// src/hooks/session-pause.ts
|
|
10163
|
-
import { existsSync as
|
|
10193
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync11, readFileSync as readFileSync13, rmSync as rmSync2 } from "node:fs";
|
|
10164
10194
|
import { createHash as createHash5 } from "node:crypto";
|
|
10165
10195
|
import { join as join28, resolve as resolve13 } from "node:path";
|
|
10166
10196
|
function pauseStateDir() {
|
|
@@ -10200,7 +10230,7 @@ function readActivePause(sessionId, now = Date.now()) {
|
|
|
10200
10230
|
return null;
|
|
10201
10231
|
try {
|
|
10202
10232
|
const path = pauseFilePath(sessionId);
|
|
10203
|
-
if (!
|
|
10233
|
+
if (!existsSync15(path))
|
|
10204
10234
|
return null;
|
|
10205
10235
|
const stored = parseStored(JSON.parse(readFileSync13(path, "utf8")));
|
|
10206
10236
|
if (!stored)
|
|
@@ -10241,7 +10271,7 @@ var init_daemon_service = __esm(() => {
|
|
|
10241
10271
|
// src/hooks/fp-reset.ts
|
|
10242
10272
|
import {
|
|
10243
10273
|
copyFileSync,
|
|
10244
|
-
existsSync as
|
|
10274
|
+
existsSync as existsSync16,
|
|
10245
10275
|
mkdirSync as mkdirSync7,
|
|
10246
10276
|
readFileSync as readFileSync14,
|
|
10247
10277
|
readdirSync as readdirSync12,
|
|
@@ -10258,7 +10288,7 @@ function layoutWarningForHook() {
|
|
|
10258
10288
|
if (state.kind === "future") {
|
|
10259
10289
|
return `[failproofai] this directory was written by a newer version (layout ${state.found} vs ${LAYOUT_VERSION}) — policies are NOT being enforced. ` + `Upgrade failproofai.`;
|
|
10260
10290
|
}
|
|
10261
|
-
if (
|
|
10291
|
+
if (existsSync16(globalPolicyConfigFile()))
|
|
10262
10292
|
return null;
|
|
10263
10293
|
return `[failproofai] setup predates this version — global policies are NOT being enforced. ` + `Run \`failproofai config\` to re-create it.`;
|
|
10264
10294
|
}
|
|
@@ -10605,7 +10635,20 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
10605
10635
|
}
|
|
10606
10636
|
} catch {}
|
|
10607
10637
|
}
|
|
10608
|
-
return {
|
|
10638
|
+
return {
|
|
10639
|
+
exitCode: result.exitCode,
|
|
10640
|
+
stdout: result.stdout,
|
|
10641
|
+
stderr: result.stderr,
|
|
10642
|
+
evaluation: {
|
|
10643
|
+
decision: result.decision,
|
|
10644
|
+
policyName: result.policyName,
|
|
10645
|
+
policyNames: result.policyNames ?? (result.policyName ? [result.policyName] : []),
|
|
10646
|
+
reason: result.reason,
|
|
10647
|
+
matchedPolicies,
|
|
10648
|
+
durationMs,
|
|
10649
|
+
toolName: parsed.tool_name ?? null
|
|
10650
|
+
}
|
|
10651
|
+
};
|
|
10609
10652
|
} finally {
|
|
10610
10653
|
if (opts?.awaitTelemetryFlush ?? true) {
|
|
10611
10654
|
await flushHookTelemetry();
|
|
@@ -10635,7 +10678,7 @@ var init_handler = __esm(() => {
|
|
|
10635
10678
|
|
|
10636
10679
|
// src/hooks/worker-server.ts
|
|
10637
10680
|
import { createConnection, createServer } from "node:net";
|
|
10638
|
-
import { existsSync as
|
|
10681
|
+
import { existsSync as existsSync17, unlinkSync as unlinkSync2 } from "node:fs";
|
|
10639
10682
|
function isWorkerHookRequest(msg) {
|
|
10640
10683
|
if (!msg || typeof msg !== "object")
|
|
10641
10684
|
return false;
|
|
@@ -10709,7 +10752,8 @@ function handleConnection(socket, shutdown) {
|
|
|
10709
10752
|
type: "hookResult",
|
|
10710
10753
|
exitCode: result.exitCode,
|
|
10711
10754
|
stdout: result.stdout,
|
|
10712
|
-
stderr: result.stderr
|
|
10755
|
+
stderr: result.stderr,
|
|
10756
|
+
evaluation: result.evaluation
|
|
10713
10757
|
}));
|
|
10714
10758
|
} catch (err) {
|
|
10715
10759
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -10768,14 +10812,14 @@ function requestExistingWorkerShutdown(socketPath) {
|
|
|
10768
10812
|
function startWorkerServer(socketPath, shutdown) {
|
|
10769
10813
|
const server = createServer((socket) => handleConnection(socket, shutdown ?? (() => server.close())));
|
|
10770
10814
|
const listen = () => {
|
|
10771
|
-
if (
|
|
10815
|
+
if (existsSync17(socketPath)) {
|
|
10772
10816
|
try {
|
|
10773
10817
|
unlinkSync2(socketPath);
|
|
10774
10818
|
} catch {}
|
|
10775
10819
|
}
|
|
10776
10820
|
server.listen(socketPath);
|
|
10777
10821
|
};
|
|
10778
|
-
if (!
|
|
10822
|
+
if (!existsSync17(socketPath))
|
|
10779
10823
|
listen();
|
|
10780
10824
|
else
|
|
10781
10825
|
requestExistingWorkerShutdown(socketPath).then(listen, (err) => server.emit("error", err));
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# FailproofAI for Hermes
|
|
2
|
+
|
|
3
|
+
This directory is the native Hermes adapter shipped inside the `failproofai`
|
|
4
|
+
npm package. It keeps policy evaluation in FailproofAI's TypeScript worker and
|
|
5
|
+
translates structured verdicts into Hermes-native hook behavior.
|
|
6
|
+
|
|
7
|
+
Validated contract: Hermes 0.21.3 at
|
|
8
|
+
`4d55ca91656ac5f83e1506679b7f81e0238e5e16`. The plugin uses only documented
|
|
9
|
+
manifest v2 fields, `PluginContext` methods/state, and keyword hook payloads.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Hermes policy evaluation requires a configured, healthy local `failproofaid`
|
|
14
|
+
daemon. If this machine has not been configured yet, run `failproofai config`
|
|
15
|
+
first. The standalone installer refuses to enable the fail-closed plugin until
|
|
16
|
+
the daemon answers an end-to-end health probe.
|
|
17
|
+
|
|
18
|
+
Then run:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
failproofai policies --install --cli hermes --scope user
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
FailproofAI copies this directory to every discovered profile at
|
|
25
|
+
`<HERMES_HOME>/plugins/failproofai/` and adds `failproofai` to
|
|
26
|
+
`plugins.enabled` in that profile's `config.yaml`. Reinstall replaces only a
|
|
27
|
+
directory carrying `.failproofai-managed`; an unrelated plugin with the same
|
|
28
|
+
directory name is never overwritten.
|
|
29
|
+
|
|
30
|
+
Legacy FailproofAI shell hooks are removed during migration. Operator-owned
|
|
31
|
+
hooks and unrelated plugin settings are preserved. No dashboard deployment or
|
|
32
|
+
backtest is part of installation.
|
|
33
|
+
|
|
34
|
+
## Runtime path
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
Hermes pre_tool_call
|
|
38
|
+
-> native Python plugin
|
|
39
|
+
-> owner-only failproofaid Unix socket
|
|
40
|
+
-> warm TypeScript policy worker
|
|
41
|
+
-> structured allow | deny | instruct verdict
|
|
42
|
+
-> Hermes-native return value
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
There is no cloud request and no new CLI process in the tool-call path.
|
|
46
|
+
|
|
47
|
+
## Decision behavior
|
|
48
|
+
|
|
49
|
+
- `allow`: return no directive; Hermes runs the tool.
|
|
50
|
+
- `deny`: always return `{"action":"block","message":"..."}`.
|
|
51
|
+
- `instruct`: persist delivery state, block the first attempt with a
|
|
52
|
+
model-visible `FAILPROOF INSTRUCTION`, keep the same API request blocked, then
|
|
53
|
+
allow a later API iteration for the same instruction scope.
|
|
54
|
+
|
|
55
|
+
The default instruction scope is profile + session + task + turn + policy
|
|
56
|
+
fingerprint + canonical tool. The persistent SQLite ledger lives below Hermes'
|
|
57
|
+
profile-scoped plugin data directory. Two distinct instruction interruptions
|
|
58
|
+
are allowed per turn by default; after that, advisory instructions fail open so
|
|
59
|
+
they cannot create an infinite retry loop. A real deny is never bypassed by the
|
|
60
|
+
ledger.
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
Settings live under `plugins.entries.failproofai.settings`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
plugins:
|
|
68
|
+
enabled:
|
|
69
|
+
- failproofai
|
|
70
|
+
entries:
|
|
71
|
+
failproofai:
|
|
72
|
+
settings:
|
|
73
|
+
failure_mode: deny
|
|
74
|
+
connect_timeout_ms: 250
|
|
75
|
+
evaluation_timeout_ms: 12000
|
|
76
|
+
instruction_ttl_seconds: 3600
|
|
77
|
+
max_instruction_rounds: 2
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`failure_mode` controls evaluator/protocol failures only. `deny` is the safe
|
|
81
|
+
default. `allow` favors availability when the local daemon cannot return a
|
|
82
|
+
trusted verdict. Ledger failures always allow only the advisory `instruct`
|
|
83
|
+
decision, because otherwise corrupted retry state could block a turn forever.
|
|
84
|
+
|
|
85
|
+
## Local protocol
|
|
86
|
+
|
|
87
|
+
Requests and responses use the existing length-prefixed failproofaid Unix
|
|
88
|
+
socket protocol, version 1.
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"type": "policyEvaluation",
|
|
93
|
+
"protocolVersion": 1,
|
|
94
|
+
"integration": "hermes",
|
|
95
|
+
"event": "pre_tool_call",
|
|
96
|
+
"payload": {},
|
|
97
|
+
"cwd": "/workspace/project"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"type": "policyResult",
|
|
104
|
+
"protocolVersion": 1,
|
|
105
|
+
"decision": "instruct",
|
|
106
|
+
"policyNames": ["custom/approved-write-route"],
|
|
107
|
+
"reason": "Use the approved write route.",
|
|
108
|
+
"matchedPolicies": ["custom/approved-write-route"],
|
|
109
|
+
"durationMs": 3,
|
|
110
|
+
"toolName": "Write"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Requests are limited to 1 MiB and responses to 16 MiB. Invalid JSON, unknown
|
|
115
|
+
message types, version mismatch, timeout, and socket failure are treated as
|
|
116
|
+
untrusted evaluation failures.
|
|
117
|
+
|
|
118
|
+
## Diagnostics and rollback
|
|
119
|
+
|
|
120
|
+
`failproofai config --status` reports each existing Hermes profile as healthy,
|
|
121
|
+
disabled, incomplete, or duplicated with a legacy shell hook. Hermes-side load
|
|
122
|
+
errors are available through:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
HERMES_PLUGINS_DEBUG=1 hermes plugins list
|
|
126
|
+
hermes plugins doctor ~/.hermes/plugins/failproofai --ci
|
|
127
|
+
hermes logs --level WARNING
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use a temporary `HERMES_HOME` for development and compatibility tests. Remove
|
|
131
|
+
the integration with:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
failproofai policies --uninstall --cli hermes --scope user
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Uninstall removes the config registration and only the plugin directory marked
|
|
138
|
+
as FailproofAI-managed.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Native Hermes bridge for FailproofAI policy enforcement."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Mapping
|
|
9
|
+
|
|
10
|
+
from .client import PolicyVerdict, evaluate_policy
|
|
11
|
+
from .ledger import InstructionLedger, LedgerError
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_PROTOCOL_CONTEXT = (
|
|
16
|
+
"When a tool result starts with FAILPROOF INSTRUCTION, treat it as policy guidance. "
|
|
17
|
+
"Reconsider the attempted action before retrying. Do not evade or ignore the instruction; "
|
|
18
|
+
"change tools, arguments, targets, or side effects only when the instruction requires it. "
|
|
19
|
+
"If you cannot follow the instruction, stop and explain why."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _bounded_int(value: object, default: int, minimum: int, maximum: int) -> int:
|
|
24
|
+
try:
|
|
25
|
+
parsed = int(value)
|
|
26
|
+
except (TypeError, ValueError):
|
|
27
|
+
return default
|
|
28
|
+
return min(max(parsed, minimum), maximum)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _profile_name() -> str:
|
|
32
|
+
explicit = os.environ.get("HERMES_PROFILE", "").strip()
|
|
33
|
+
if explicit:
|
|
34
|
+
return explicit
|
|
35
|
+
configured_home = os.environ.get("HERMES_HOME", "").strip()
|
|
36
|
+
home = Path(configured_home).expanduser() if configured_home else Path.home() / ".hermes"
|
|
37
|
+
if home.parent.name == "profiles":
|
|
38
|
+
return home.name
|
|
39
|
+
return "default"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _string(value: object) -> str:
|
|
43
|
+
return value if isinstance(value, str) else ""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FailproofAIPlugin:
|
|
47
|
+
def __init__(self, ctx: Any) -> None:
|
|
48
|
+
self.ctx = ctx
|
|
49
|
+
self.profile = _profile_name()
|
|
50
|
+
self.failure_mode = str(ctx.get_config("failure_mode", "deny")).strip().lower()
|
|
51
|
+
self.connect_timeout_ms = _bounded_int(
|
|
52
|
+
ctx.get_config("connect_timeout_ms", 250), 250, 25, 5_000
|
|
53
|
+
)
|
|
54
|
+
self.evaluation_timeout_ms = _bounded_int(
|
|
55
|
+
ctx.get_config("evaluation_timeout_ms", 12_000), 12_000, 100, 29_000
|
|
56
|
+
)
|
|
57
|
+
ttl_seconds = _bounded_int(
|
|
58
|
+
ctx.get_config("instruction_ttl_seconds", 3_600), 3_600, 60, 86_400
|
|
59
|
+
)
|
|
60
|
+
max_rounds = _bounded_int(
|
|
61
|
+
ctx.get_config("max_instruction_rounds", 2), 2, 0, 10
|
|
62
|
+
)
|
|
63
|
+
self.ledger = InstructionLedger(
|
|
64
|
+
Path(ctx.state.data_dir) / "instructions.db",
|
|
65
|
+
ttl_seconds=ttl_seconds,
|
|
66
|
+
max_rounds=max_rounds,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def _fallback(self, error: Exception) -> dict[str, str] | None:
|
|
70
|
+
logger.error("FailproofAI evaluator unavailable: %s", error)
|
|
71
|
+
if self.failure_mode == "allow":
|
|
72
|
+
return None
|
|
73
|
+
return {
|
|
74
|
+
"action": "block",
|
|
75
|
+
"message": (
|
|
76
|
+
"FailproofAI could not verify this tool call because the local policy "
|
|
77
|
+
"evaluator is unavailable. Check `failproofai status` before retrying."
|
|
78
|
+
),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def _evaluate(self, event: str, payload: Mapping[str, Any]) -> PolicyVerdict:
|
|
82
|
+
cwd = _string(payload.get("cwd")) or os.getcwd()
|
|
83
|
+
return evaluate_policy(
|
|
84
|
+
event=event,
|
|
85
|
+
payload=payload,
|
|
86
|
+
cwd=cwd,
|
|
87
|
+
connect_timeout_ms=self.connect_timeout_ms,
|
|
88
|
+
evaluation_timeout_ms=self.evaluation_timeout_ms,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def pre_tool_call(
|
|
92
|
+
self,
|
|
93
|
+
tool_name: str = "",
|
|
94
|
+
args: Mapping[str, Any] | None = None,
|
|
95
|
+
session_id: str = "",
|
|
96
|
+
task_id: str = "",
|
|
97
|
+
tool_call_id: str = "",
|
|
98
|
+
turn_id: str = "",
|
|
99
|
+
api_request_id: str = "",
|
|
100
|
+
**kwargs: Any,
|
|
101
|
+
) -> dict[str, str] | None:
|
|
102
|
+
payload = {
|
|
103
|
+
"hook_event_name": "pre_tool_call",
|
|
104
|
+
"tool_name": tool_name,
|
|
105
|
+
"tool_input": dict(args or {}),
|
|
106
|
+
"session_id": session_id,
|
|
107
|
+
"cwd": _string(kwargs.get("cwd")) or os.getcwd(),
|
|
108
|
+
"hermes": {
|
|
109
|
+
"profile": self.profile,
|
|
110
|
+
"task_id": task_id,
|
|
111
|
+
"tool_call_id": tool_call_id,
|
|
112
|
+
"turn_id": turn_id,
|
|
113
|
+
"api_request_id": api_request_id,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
try:
|
|
117
|
+
verdict = self._evaluate("pre_tool_call", payload)
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
return self._fallback(exc)
|
|
120
|
+
|
|
121
|
+
if verdict.decision == "allow":
|
|
122
|
+
return None
|
|
123
|
+
if verdict.decision == "deny":
|
|
124
|
+
return {
|
|
125
|
+
"action": "block",
|
|
126
|
+
"message": verdict.reason or "Blocked by FailproofAI policy.",
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
reason = verdict.reason or "Reconsider this tool call before retrying."
|
|
130
|
+
try:
|
|
131
|
+
action = self.ledger.decide(
|
|
132
|
+
profile=self.profile,
|
|
133
|
+
session_id=session_id,
|
|
134
|
+
task_id=task_id,
|
|
135
|
+
turn_id=turn_id,
|
|
136
|
+
api_request_id=api_request_id,
|
|
137
|
+
policy_names=verdict.policy_names,
|
|
138
|
+
reason=reason,
|
|
139
|
+
scope_key=verdict.tool_name or tool_name or "unknown-tool",
|
|
140
|
+
)
|
|
141
|
+
except LedgerError as exc:
|
|
142
|
+
# An advisory instruction must never become an indefinite denial
|
|
143
|
+
# because its local retry state is unavailable.
|
|
144
|
+
logger.error("FailproofAI instruction state unavailable; allowing: %s", exc)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
if not action.block:
|
|
148
|
+
if action.status in {"missing-correlation-id", "turn-cap"}:
|
|
149
|
+
logger.warning("FailproofAI instruction allowed in degraded state: %s", action.status)
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
policies = ", ".join(verdict.policy_names) or "unknown policy"
|
|
153
|
+
return {
|
|
154
|
+
"action": "block",
|
|
155
|
+
"message": (
|
|
156
|
+
f"FAILPROOF INSTRUCTION ({policies})\n\n{reason}\n\n"
|
|
157
|
+
"Reconsider the attempted action, then make a corrected tool call. "
|
|
158
|
+
"Do not route around this instruction with an equivalent ungoverned action."
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def _observe(self, event: str, payload: Mapping[str, Any]) -> None:
|
|
163
|
+
try:
|
|
164
|
+
self._evaluate(event, payload)
|
|
165
|
+
except Exception as exc:
|
|
166
|
+
logger.warning("FailproofAI observation failed for %s: %s", event, exc)
|
|
167
|
+
|
|
168
|
+
def post_tool_call(
|
|
169
|
+
self,
|
|
170
|
+
tool_name: str = "",
|
|
171
|
+
args: Mapping[str, Any] | None = None,
|
|
172
|
+
result: Any = None,
|
|
173
|
+
session_id: str = "",
|
|
174
|
+
**kwargs: Any,
|
|
175
|
+
) -> None:
|
|
176
|
+
self._observe(
|
|
177
|
+
"post_tool_call",
|
|
178
|
+
{
|
|
179
|
+
"hook_event_name": "post_tool_call",
|
|
180
|
+
"tool_name": tool_name,
|
|
181
|
+
"tool_input": dict(args or {}),
|
|
182
|
+
"tool_response": result,
|
|
183
|
+
"session_id": session_id,
|
|
184
|
+
"cwd": _string(kwargs.get("cwd")) or os.getcwd(),
|
|
185
|
+
"hermes": {"profile": self.profile, **kwargs},
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def on_session_start(self, session_id: str = "", **kwargs: Any) -> None:
|
|
190
|
+
self._observe(
|
|
191
|
+
"on_session_start",
|
|
192
|
+
{
|
|
193
|
+
"hook_event_name": "on_session_start",
|
|
194
|
+
"session_id": session_id,
|
|
195
|
+
"cwd": _string(kwargs.get("cwd")) or os.getcwd(),
|
|
196
|
+
"hermes": {"profile": self.profile, **kwargs},
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def on_session_end(self, session_id: str = "", **kwargs: Any) -> None:
|
|
201
|
+
self._observe(
|
|
202
|
+
"on_session_end",
|
|
203
|
+
{
|
|
204
|
+
"hook_event_name": "on_session_end",
|
|
205
|
+
"session_id": session_id,
|
|
206
|
+
"cwd": _string(kwargs.get("cwd")) or os.getcwd(),
|
|
207
|
+
"hermes": {"profile": self.profile, **kwargs},
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
self._clear_session(session_id)
|
|
211
|
+
|
|
212
|
+
def subagent_stop(self, session_id: str = "", **kwargs: Any) -> None:
|
|
213
|
+
self._observe(
|
|
214
|
+
"subagent_stop",
|
|
215
|
+
{
|
|
216
|
+
"hook_event_name": "subagent_stop",
|
|
217
|
+
"session_id": session_id,
|
|
218
|
+
"cwd": _string(kwargs.get("cwd")) or os.getcwd(),
|
|
219
|
+
"hermes": {"profile": self.profile, **kwargs},
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def pre_llm_call(self, **kwargs: Any) -> dict[str, str]:
|
|
224
|
+
return {"context": _PROTOCOL_CONTEXT}
|
|
225
|
+
|
|
226
|
+
def on_session_reset(self, session_id: str = "", **kwargs: Any) -> None:
|
|
227
|
+
self._clear_session(session_id)
|
|
228
|
+
|
|
229
|
+
def on_session_finalize(self, session_id: str = "", **kwargs: Any) -> None:
|
|
230
|
+
self._clear_session(session_id)
|
|
231
|
+
|
|
232
|
+
def _clear_session(self, session_id: str) -> None:
|
|
233
|
+
try:
|
|
234
|
+
self.ledger.clear_session(session_id)
|
|
235
|
+
except LedgerError as exc:
|
|
236
|
+
logger.warning("FailproofAI instruction cleanup failed: %s", exc)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def register(ctx: Any) -> None:
|
|
240
|
+
plugin = FailproofAIPlugin(ctx)
|
|
241
|
+
ctx.register_hook("pre_tool_call", plugin.pre_tool_call)
|
|
242
|
+
ctx.register_hook("post_tool_call", plugin.post_tool_call)
|
|
243
|
+
ctx.register_hook("pre_llm_call", plugin.pre_llm_call)
|
|
244
|
+
ctx.register_hook("on_session_start", plugin.on_session_start)
|
|
245
|
+
ctx.register_hook("on_session_end", plugin.on_session_end)
|
|
246
|
+
ctx.register_hook("on_session_reset", plugin.on_session_reset)
|
|
247
|
+
ctx.register_hook("on_session_finalize", plugin.on_session_finalize)
|
|
248
|
+
ctx.register_hook("subagent_stop", plugin.subagent_stop)
|