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/src/hooks/handler.ts
CHANGED
|
@@ -102,6 +102,21 @@ export interface HookEventOutcome {
|
|
|
102
102
|
exitCode: number;
|
|
103
103
|
stdout: string;
|
|
104
104
|
stderr: string;
|
|
105
|
+
/**
|
|
106
|
+
* Structured policy metadata for in-process adapters. Existing shell-hook
|
|
107
|
+
* callers intentionally ignore this and keep consuming stdout/stderr.
|
|
108
|
+
*/
|
|
109
|
+
evaluation?: HookEvaluationSummary;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface HookEvaluationSummary {
|
|
113
|
+
decision: "allow" | "deny" | "instruct";
|
|
114
|
+
policyName: string | null;
|
|
115
|
+
policyNames: string[];
|
|
116
|
+
reason: string | null;
|
|
117
|
+
matchedPolicies: string[];
|
|
118
|
+
durationMs: number;
|
|
119
|
+
toolName: string | null;
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
export interface EvaluateHookEventOptions {
|
|
@@ -757,7 +772,20 @@ export async function evaluateHookEvent(
|
|
|
757
772
|
}
|
|
758
773
|
}
|
|
759
774
|
|
|
760
|
-
return {
|
|
775
|
+
return {
|
|
776
|
+
exitCode: result.exitCode,
|
|
777
|
+
stdout: result.stdout,
|
|
778
|
+
stderr: result.stderr,
|
|
779
|
+
evaluation: {
|
|
780
|
+
decision: result.decision,
|
|
781
|
+
policyName: result.policyName,
|
|
782
|
+
policyNames: result.policyNames ?? (result.policyName ? [result.policyName] : []),
|
|
783
|
+
reason: result.reason,
|
|
784
|
+
matchedPolicies,
|
|
785
|
+
durationMs,
|
|
786
|
+
toolName: (parsed.tool_name as string) ?? null,
|
|
787
|
+
},
|
|
788
|
+
};
|
|
761
789
|
} finally {
|
|
762
790
|
if (opts?.awaitTelemetryFlush ?? true) {
|
|
763
791
|
// Await any un-awaited (`void trackHookEvent(...)`) events fired during
|
|
@@ -7,7 +7,17 @@
|
|
|
7
7
|
* is agent-agnostic — only install/uninstall plumbing varies.
|
|
8
8
|
*/
|
|
9
9
|
import { execSync } from "node:child_process";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
cpSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
lstatSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
renameSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
unlinkSync,
|
|
20
|
+
} from "node:fs";
|
|
11
21
|
import { resolve, dirname } from "node:path";
|
|
12
22
|
import { fileURLToPath } from "node:url";
|
|
13
23
|
import { homedir } from "node:os";
|
|
@@ -112,6 +122,12 @@ function binaryExists(name: string): boolean {
|
|
|
112
122
|
export interface Integration {
|
|
113
123
|
id: IntegrationType;
|
|
114
124
|
displayName: string;
|
|
125
|
+
/**
|
|
126
|
+
* This integration has no in-process/CLI fallback and cannot evaluate policy
|
|
127
|
+
* unless failproofaid answers an end-to-end probe. Installation must stop
|
|
128
|
+
* before writing any hook or plugin when the daemon is unavailable.
|
|
129
|
+
*/
|
|
130
|
+
requiresHealthyDaemon?: boolean;
|
|
115
131
|
/** Settings scopes this integration supports (e.g. claude: user/project/local; codex: user/project). */
|
|
116
132
|
scopes: readonly HookScope[];
|
|
117
133
|
/** Hook events this integration fires (Claude: PascalCase, Codex: snake_case stored as Pascal in settings). */
|
|
@@ -135,6 +151,9 @@ export interface Integration {
|
|
|
135
151
|
/** Read the raw settings/hooks file (returns {} when missing). */
|
|
136
152
|
readSettings(settingsPath: string): Record<string, unknown>;
|
|
137
153
|
|
|
154
|
+
/** Install integration-owned files before enabling them in settings. */
|
|
155
|
+
prepareInstall?(settingsPath: string): void;
|
|
156
|
+
|
|
138
157
|
/** Write the settings/hooks file. */
|
|
139
158
|
writeSettings(settingsPath: string, settings: Record<string, unknown>): void;
|
|
140
159
|
|
|
@@ -1363,14 +1382,14 @@ function makePiProjectRelativeEntry(extPath: string): string {
|
|
|
1363
1382
|
// works for the local user.
|
|
1364
1383
|
return extResolved;
|
|
1365
1384
|
}
|
|
1366
|
-
// ── Hermes (hermes-agent) —
|
|
1385
|
+
// ── Hermes (hermes-agent) — native plugin (Pillar 1) ────────────────────────
|
|
1367
1386
|
//
|
|
1368
|
-
//
|
|
1369
|
-
//
|
|
1370
|
-
//
|
|
1371
|
-
//
|
|
1372
|
-
//
|
|
1373
|
-
//
|
|
1387
|
+
// Hermes loads trusted Python plugins from `<HERMES_HOME>/plugins/<name>/`.
|
|
1388
|
+
// The shipped plugin registers Hermes-native hooks and talks directly to the
|
|
1389
|
+
// local failproofaid socket, avoiding a fresh CLI process per event. Config is
|
|
1390
|
+
// YAML and profile-scoped; install copies the plugin into every profile and
|
|
1391
|
+
// adds `failproofai` to `plugins.enabled`. Legacy shell-hook entries are removed
|
|
1392
|
+
// during migration so one tool call is never evaluated twice.
|
|
1374
1393
|
|
|
1375
1394
|
/** One hook entry as stored under a `hooks:` event key in config.yaml. */
|
|
1376
1395
|
interface HermesHookEntry {
|
|
@@ -1379,9 +1398,204 @@ interface HermesHookEntry {
|
|
|
1379
1398
|
[key: string]: unknown;
|
|
1380
1399
|
}
|
|
1381
1400
|
|
|
1401
|
+
interface HermesPluginsConfig {
|
|
1402
|
+
enabled?: unknown[];
|
|
1403
|
+
disabled?: unknown[];
|
|
1404
|
+
entries?: Record<string, unknown>;
|
|
1405
|
+
[key: string]: unknown;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const HERMES_PLUGIN_ID = "failproofai";
|
|
1409
|
+
const HERMES_PLUGIN_MARKER = ".failproofai-managed";
|
|
1410
|
+
const HERMES_PLUGIN_FILES = ["plugin.yaml", "__init__.py", "client.py", "ledger.py"] as const;
|
|
1411
|
+
|
|
1412
|
+
function getHermesPluginSourcePath(): string {
|
|
1413
|
+
const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
|
|
1414
|
+
if (fromEnv) return resolve(fromEnv, "hermes-plugin");
|
|
1415
|
+
return resolve(fileURLToPath(import.meta.url), "..", "..", "..", "hermes-plugin");
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
export function hermesPluginPathForSettings(settingsPath: string): string {
|
|
1419
|
+
return resolve(dirname(settingsPath), "plugins", HERMES_PLUGIN_ID);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function hasManagedHermesPlugin(settingsPath: string): boolean {
|
|
1423
|
+
const pluginPath = hermesPluginPathForSettings(settingsPath);
|
|
1424
|
+
try {
|
|
1425
|
+
if (lstatSync(pluginPath).isSymbolicLink()) return false;
|
|
1426
|
+
if (!existsSync(resolve(pluginPath, HERMES_PLUGIN_MARKER))) return false;
|
|
1427
|
+
return HERMES_PLUGIN_FILES.every((file) => existsSync(resolve(pluginPath, file)));
|
|
1428
|
+
} catch {
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function hasHermesPluginMarker(settingsPath: string): boolean {
|
|
1434
|
+
const pluginPath = hermesPluginPathForSettings(settingsPath);
|
|
1435
|
+
try {
|
|
1436
|
+
return !lstatSync(pluginPath).isSymbolicLink() && existsSync(resolve(pluginPath, HERMES_PLUGIN_MARKER));
|
|
1437
|
+
} catch {
|
|
1438
|
+
return false;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function installHermesPlugin(settingsPath: string): void {
|
|
1443
|
+
const source = getHermesPluginSourcePath();
|
|
1444
|
+
for (const file of HERMES_PLUGIN_FILES) {
|
|
1445
|
+
if (!existsSync(resolve(source, file))) {
|
|
1446
|
+
throw new Error(`Hermes plugin asset is missing from the failproofai package: ${file}`);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const destination = hermesPluginPathForSettings(settingsPath);
|
|
1451
|
+
if (existsSync(destination)) {
|
|
1452
|
+
if (lstatSync(destination).isSymbolicLink() || !existsSync(resolve(destination, HERMES_PLUGIN_MARKER))) {
|
|
1453
|
+
throw new Error(
|
|
1454
|
+
`Refusing to overwrite an unmanaged Hermes plugin at ${destination}. ` +
|
|
1455
|
+
`Move it aside or install FailproofAI under a different Hermes profile.`,
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
1461
|
+
const suffix = `${process.pid}-${Date.now()}`;
|
|
1462
|
+
const temporary = `${destination}.install-${suffix}`;
|
|
1463
|
+
const backup = `${destination}.backup-${suffix}`;
|
|
1464
|
+
let movedExisting = false;
|
|
1465
|
+
try {
|
|
1466
|
+
mkdirSync(temporary, { recursive: true });
|
|
1467
|
+
for (const file of HERMES_PLUGIN_FILES) {
|
|
1468
|
+
cpSync(resolve(source, file), resolve(temporary, file));
|
|
1469
|
+
}
|
|
1470
|
+
writeFileSync(
|
|
1471
|
+
resolve(temporary, HERMES_PLUGIN_MARKER),
|
|
1472
|
+
"Managed by failproofai. Remove with `failproofai policies --uninstall --cli hermes`.\n",
|
|
1473
|
+
{ encoding: "utf8", mode: 0o600 },
|
|
1474
|
+
);
|
|
1475
|
+
if (existsSync(destination)) {
|
|
1476
|
+
renameSync(destination, backup);
|
|
1477
|
+
movedExisting = true;
|
|
1478
|
+
}
|
|
1479
|
+
renameSync(temporary, destination);
|
|
1480
|
+
if (movedExisting) rmSync(backup, { recursive: true, force: true });
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
1483
|
+
if (movedExisting && !existsSync(destination) && existsSync(backup)) {
|
|
1484
|
+
renameSync(backup, destination);
|
|
1485
|
+
}
|
|
1486
|
+
throw err;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function removeManagedHermesPlugin(settingsPath: string): boolean {
|
|
1491
|
+
const pluginPath = hermesPluginPathForSettings(settingsPath);
|
|
1492
|
+
// The marker is the ownership boundary. Remove even an incomplete managed
|
|
1493
|
+
// install so an interrupted copy can always be repaired or uninstalled.
|
|
1494
|
+
if (!hasHermesPluginMarker(settingsPath)) return false;
|
|
1495
|
+
rmSync(pluginPath, { recursive: true, force: true });
|
|
1496
|
+
return true;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function removeLegacyHermesHooks(doc: Document): number {
|
|
1500
|
+
const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
|
|
1501
|
+
const hooks = js.hooks;
|
|
1502
|
+
let removed = 0;
|
|
1503
|
+
if (hooks && typeof hooks === "object") {
|
|
1504
|
+
for (const eventType of Object.keys(hooks)) {
|
|
1505
|
+
const entries = hooks[eventType];
|
|
1506
|
+
if (!Array.isArray(entries)) continue;
|
|
1507
|
+
const before = entries.length;
|
|
1508
|
+
const filtered = entries.filter((hook) => !isMarkedHook(hook));
|
|
1509
|
+
removed += before - filtered.length;
|
|
1510
|
+
if (filtered.length === 0) delete hooks[eventType];
|
|
1511
|
+
else hooks[eventType] = filtered;
|
|
1512
|
+
}
|
|
1513
|
+
if (Object.keys(hooks).length === 0) {
|
|
1514
|
+
doc.delete("hooks");
|
|
1515
|
+
// Older FailproofAI installs set this broad Hermes switch so headless
|
|
1516
|
+
// shell hooks could run. It is safe to remove only when no operator hook
|
|
1517
|
+
// remains that may rely on it.
|
|
1518
|
+
if (removed > 0) doc.delete("hooks_auto_accept");
|
|
1519
|
+
} else {
|
|
1520
|
+
doc.set("hooks", hooks);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return removed;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function hermesConfigState(settingsPath: string): {
|
|
1527
|
+
pluginEnabled: boolean;
|
|
1528
|
+
legacyShellHookPresent: boolean;
|
|
1529
|
+
} {
|
|
1530
|
+
if (!existsSync(settingsPath)) {
|
|
1531
|
+
return { pluginEnabled: false, legacyShellHookPresent: false };
|
|
1532
|
+
}
|
|
1533
|
+
const doc = readYamlDoc(settingsPath);
|
|
1534
|
+
const js = (doc.toJS() ?? {}) as {
|
|
1535
|
+
hooks?: Record<string, HermesHookEntry[]>;
|
|
1536
|
+
plugins?: HermesPluginsConfig;
|
|
1537
|
+
};
|
|
1538
|
+
const enabled = js.plugins?.enabled;
|
|
1539
|
+
const legacyShellHookPresent =
|
|
1540
|
+
!!js.hooks &&
|
|
1541
|
+
typeof js.hooks === "object" &&
|
|
1542
|
+
Object.values(js.hooks).some(
|
|
1543
|
+
(entries) => Array.isArray(entries) && entries.some((entry) => isMarkedHook(entry)),
|
|
1544
|
+
);
|
|
1545
|
+
return {
|
|
1546
|
+
pluginEnabled: Array.isArray(enabled) && enabled.includes(HERMES_PLUGIN_ID),
|
|
1547
|
+
legacyShellHookPresent,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
export interface HermesProfileHealth {
|
|
1552
|
+
name: string;
|
|
1553
|
+
home: string;
|
|
1554
|
+
settingsPath: string;
|
|
1555
|
+
pluginInstalled: boolean;
|
|
1556
|
+
pluginEnabled: boolean;
|
|
1557
|
+
legacyShellHookPresent: boolean;
|
|
1558
|
+
healthy: boolean;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/** Read-only, per-profile Hermes installation health for setup/status surfaces. */
|
|
1562
|
+
export function hermesProfileHealth(): HermesProfileHealth[] {
|
|
1563
|
+
return listHermesProfiles().map((profile) => {
|
|
1564
|
+
const settingsPath = resolve(profile.home, "config.yaml");
|
|
1565
|
+
const config = hermesConfigState(settingsPath);
|
|
1566
|
+
const pluginInstalled = hasManagedHermesPlugin(settingsPath);
|
|
1567
|
+
return {
|
|
1568
|
+
name: profile.name,
|
|
1569
|
+
home: profile.home,
|
|
1570
|
+
settingsPath,
|
|
1571
|
+
pluginInstalled,
|
|
1572
|
+
pluginEnabled: config.pluginEnabled,
|
|
1573
|
+
legacyShellHookPresent: config.legacyShellHookPresent,
|
|
1574
|
+
healthy: pluginInstalled && config.pluginEnabled && !config.legacyShellHookPresent,
|
|
1575
|
+
};
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/** Compact rows for the machine-status display; absent Hermes homes produce no noise. */
|
|
1580
|
+
export function hermesProfileStatusRows(): Array<[string, string]> {
|
|
1581
|
+
return hermesProfileHealth()
|
|
1582
|
+
.filter((profile) => existsSync(profile.home))
|
|
1583
|
+
.map((profile) => {
|
|
1584
|
+
const problems: string[] = [];
|
|
1585
|
+
if (!profile.pluginInstalled) problems.push("plugin files missing or incomplete");
|
|
1586
|
+
if (!profile.pluginEnabled) problems.push("plugin not enabled");
|
|
1587
|
+
if (profile.legacyShellHookPresent) problems.push("legacy shell hook also present");
|
|
1588
|
+
return [
|
|
1589
|
+
"hermes/" + profile.name,
|
|
1590
|
+
problems.length === 0 ? "native plugin enabled" : "UNHEALTHY — " + problems.join("; "),
|
|
1591
|
+
];
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1382
1595
|
export const hermes: Integration = {
|
|
1383
1596
|
id: "hermes",
|
|
1384
1597
|
displayName: "Hermes",
|
|
1598
|
+
requiresHealthyDaemon: true,
|
|
1385
1599
|
scopes: HERMES_HOOK_SCOPES,
|
|
1386
1600
|
eventTypes: HERMES_HOOK_EVENT_TYPES,
|
|
1387
1601
|
|
|
@@ -1410,75 +1624,66 @@ export const hermes: Integration = {
|
|
|
1410
1624
|
writeYamlDoc(settingsPath, settings as unknown as Document);
|
|
1411
1625
|
},
|
|
1412
1626
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
: `"${binaryPath}" --hook ${eventType} --cli hermes`;
|
|
1627
|
+
prepareInstall(settingsPath) {
|
|
1628
|
+
installHermesPlugin(settingsPath);
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1631
|
+
buildHookEntry(_binaryPath, _eventType, _scope) {
|
|
1632
|
+
// Native plugins are package-level registrations. This sentinel preserves
|
|
1633
|
+
// the common Integration interface; writeHookEntries does the real work.
|
|
1421
1634
|
return {
|
|
1422
|
-
command,
|
|
1423
|
-
timeout: 30,
|
|
1424
1635
|
[FAILPROOFAI_HOOK_MARKER]: true,
|
|
1636
|
+
_hermesPluginPath: getHermesPluginSourcePath(),
|
|
1425
1637
|
};
|
|
1426
1638
|
},
|
|
1427
1639
|
|
|
1428
1640
|
isFailproofaiHook: isMarkedHook,
|
|
1429
1641
|
|
|
1430
|
-
writeHookEntries(settings
|
|
1642
|
+
writeHookEntries(settings) {
|
|
1431
1643
|
const doc = settings as unknown as Document;
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1644
|
+
// A native plugin and the legacy shell bridge running together would
|
|
1645
|
+
// evaluate every event twice. Remove only failproofai-owned shell entries;
|
|
1646
|
+
// unrelated operator hooks remain intact.
|
|
1647
|
+
removeLegacyHermesHooks(doc);
|
|
1648
|
+
|
|
1649
|
+
const js = (doc.toJS() ?? {}) as { plugins?: HermesPluginsConfig };
|
|
1650
|
+
const plugins: HermesPluginsConfig =
|
|
1651
|
+
js.plugins && typeof js.plugins === "object" ? js.plugins : {};
|
|
1652
|
+
const enabled = Array.isArray(plugins.enabled) ? [...plugins.enabled] : [];
|
|
1653
|
+
if (!enabled.includes(HERMES_PLUGIN_ID)) enabled.push(HERMES_PLUGIN_ID);
|
|
1654
|
+
plugins.enabled = enabled;
|
|
1655
|
+
if (Array.isArray(plugins.disabled)) {
|
|
1656
|
+
plugins.disabled = plugins.disabled.filter((name) => name !== HERMES_PLUGIN_ID);
|
|
1657
|
+
if (plugins.disabled.length === 0) delete plugins.disabled;
|
|
1445
1658
|
}
|
|
1446
|
-
doc.set("
|
|
1447
|
-
// The headless gateway has no TTY to answer Hermes's first-use hook-consent
|
|
1448
|
-
// prompt, so auto-accept declared hooks. Tradeoff: also auto-accepts any
|
|
1449
|
-
// other hook the operator adds; a targeted `shell-hooks-allowlist.json`
|
|
1450
|
-
// pre-seed is a future refinement.
|
|
1451
|
-
doc.set("hooks_auto_accept", true);
|
|
1659
|
+
doc.set("plugins", plugins);
|
|
1452
1660
|
},
|
|
1453
1661
|
|
|
1454
1662
|
removeHooksFromFile(settingsPath) {
|
|
1455
|
-
if (!existsSync(settingsPath)) return 0;
|
|
1456
1663
|
const doc = readYamlDoc(settingsPath);
|
|
1457
|
-
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
let
|
|
1461
|
-
if (
|
|
1462
|
-
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1664
|
+
let removed = removeLegacyHermesHooks(doc);
|
|
1665
|
+
const js = (doc.toJS() ?? {}) as { plugins?: HermesPluginsConfig };
|
|
1666
|
+
const plugins = js.plugins;
|
|
1667
|
+
let configChanged = removed > 0;
|
|
1668
|
+
if (plugins && typeof plugins === "object") {
|
|
1669
|
+
if (Array.isArray(plugins.enabled)) {
|
|
1670
|
+
const before = plugins.enabled.length;
|
|
1671
|
+
plugins.enabled = plugins.enabled.filter((name) => name !== HERMES_PLUGIN_ID);
|
|
1672
|
+
if (plugins.enabled.length !== before) {
|
|
1673
|
+
removed += 1;
|
|
1674
|
+
configChanged = true;
|
|
1675
|
+
}
|
|
1676
|
+
if (plugins.enabled.length === 0) delete plugins.enabled;
|
|
1470
1677
|
}
|
|
1471
|
-
if (
|
|
1472
|
-
|
|
1473
|
-
|
|
1678
|
+
if (plugins.entries && HERMES_PLUGIN_ID in plugins.entries) {
|
|
1679
|
+
delete plugins.entries[HERMES_PLUGIN_ID];
|
|
1680
|
+
configChanged = true;
|
|
1474
1681
|
}
|
|
1682
|
+
if (Object.keys(plugins).length === 0) doc.delete("plugins");
|
|
1683
|
+
else doc.set("plugins", plugins);
|
|
1475
1684
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
// already removed manually — so it can't silently auto-accept future operator
|
|
1479
|
-
// hooks. `doc.delete` returns true iff the key was present.
|
|
1480
|
-
const droppedAutoAccept = doc.delete("hooks_auto_accept");
|
|
1481
|
-
if (removed > 0 || droppedAutoAccept) writeYamlDoc(settingsPath, doc);
|
|
1685
|
+
if (configChanged) writeYamlDoc(settingsPath, doc);
|
|
1686
|
+
if (removeManagedHermesPlugin(settingsPath)) removed += 1;
|
|
1482
1687
|
return removed;
|
|
1483
1688
|
},
|
|
1484
1689
|
|
|
@@ -1497,15 +1702,9 @@ export const hermes: Integration = {
|
|
|
1497
1702
|
};
|
|
1498
1703
|
|
|
1499
1704
|
function hermesConfigHasHooks(settingsPath: string): boolean {
|
|
1500
|
-
if (!existsSync(settingsPath)) return false;
|
|
1501
1705
|
try {
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
const hooks = js.hooks;
|
|
1505
|
-
if (!hooks || typeof hooks !== "object") return false;
|
|
1506
|
-
for (const entries of Object.values(hooks)) {
|
|
1507
|
-
if (Array.isArray(entries) && entries.some((h) => isMarkedHook(h))) return true;
|
|
1508
|
-
}
|
|
1706
|
+
const state = hermesConfigState(settingsPath);
|
|
1707
|
+
return state.pluginEnabled && hasManagedHermesPlugin(settingsPath) && !state.legacyShellHookPresent;
|
|
1509
1708
|
} catch {
|
|
1510
1709
|
// Corrupt config — treat as not installed.
|
|
1511
1710
|
}
|
|
@@ -1523,10 +1722,9 @@ function hermesConfigHasHooks(settingsPath: string): boolean {
|
|
|
1523
1722
|
* warning.
|
|
1524
1723
|
*/
|
|
1525
1724
|
export function unhookedHermesProfiles(): string[] {
|
|
1526
|
-
return
|
|
1527
|
-
.filter((
|
|
1528
|
-
.
|
|
1529
|
-
.map((p) => p.name);
|
|
1725
|
+
return hermesProfileHealth()
|
|
1726
|
+
.filter((profile) => existsSync(profile.home) && !profile.healthy)
|
|
1727
|
+
.map((profile) => profile.name);
|
|
1530
1728
|
}
|
|
1531
1729
|
|
|
1532
1730
|
// ── OpenClaw integration ────────────────────────────────────────────────────
|
package/src/hooks/manager.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { CORE_SOURCE, addPack, setPackPolicyEnabled } from "./pack-store";
|
|
|
29
29
|
import type { ResolvedPack } from "./pack-manifest";
|
|
30
30
|
import { hasInstalledPacks, readInstalledPacks } from "./pack-manifest";
|
|
31
31
|
import { packPolicyParamKey } from "./policy-evaluator";
|
|
32
|
+
import { probeDaemonEndToEnd } from "./daemon-service";
|
|
32
33
|
import {
|
|
33
34
|
chip,
|
|
34
35
|
note,
|
|
@@ -446,9 +447,13 @@ async function installHooksImpl(
|
|
|
446
447
|
// the user for multi-CLI selection before reaching here when --cli is omitted.
|
|
447
448
|
const selectedClis: IntegrationType[] = cli && cli.length > 0 ? [...new Set(cli)] : ["claude"];
|
|
448
449
|
|
|
450
|
+
const selectedIntegrations = selectedClis.map((cliId) => ({
|
|
451
|
+
cliId,
|
|
452
|
+
integration: getIntegration(cliId),
|
|
453
|
+
}));
|
|
454
|
+
|
|
449
455
|
// Per-CLI scope validation: Codex doesn't have a "local" scope.
|
|
450
|
-
for (const cliId of
|
|
451
|
-
const integration = getIntegration(cliId);
|
|
456
|
+
for (const { cliId, integration } of selectedIntegrations) {
|
|
452
457
|
if (!integration.scopes.includes(scope)) {
|
|
453
458
|
try {
|
|
454
459
|
await trackHookEvent(getInstanceId(), "scope_validation_failed", {
|
|
@@ -464,6 +469,23 @@ async function installHooksImpl(
|
|
|
464
469
|
}
|
|
465
470
|
}
|
|
466
471
|
|
|
472
|
+
// Daemon-only native integrations fail closed when their evaluator cannot be
|
|
473
|
+
// reached. Never enable one until a real policy request succeeds; otherwise
|
|
474
|
+
// a direct `policies --install` can lock every tool call. Shell-hook and CLI-
|
|
475
|
+
// backed integrations retain their local evaluator fallback and therefore do
|
|
476
|
+
// not opt into this requirement.
|
|
477
|
+
const daemonRequiredBy = selectedIntegrations
|
|
478
|
+
.map(({ integration }) => integration)
|
|
479
|
+
.filter((integration) => integration.requiresHealthyDaemon);
|
|
480
|
+
if (daemonRequiredBy.length > 0 && !(await probeDaemonEndToEnd())) {
|
|
481
|
+
const names = daemonRequiredBy.map((integration) => integration.displayName).join(", ");
|
|
482
|
+
const verb = daemonRequiredBy.length === 1 ? "requires" : "require";
|
|
483
|
+
throw new CliError(
|
|
484
|
+
`${names} ${verb} a healthy failproofaid daemon before FailproofAI enforcement can be enabled.\n` +
|
|
485
|
+
"Run `failproofai config` to install and configure the daemon, then retry.",
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
467
489
|
const binaryPath = resolveFailproofaiBinary();
|
|
468
490
|
|
|
469
491
|
// Capture existing config before overwriting (used for telemetry diff)
|
|
@@ -672,6 +694,7 @@ async function installHooksImpl(
|
|
|
672
694
|
const settingsPaths = settingsPathsFor(integration, scope, cwd);
|
|
673
695
|
try {
|
|
674
696
|
for (const settingsPath of settingsPaths) {
|
|
697
|
+
integration.prepareInstall?.(settingsPath);
|
|
675
698
|
const settings = integration.readSettings(settingsPath);
|
|
676
699
|
integration.writeHookEntries(settings, binaryPath, scope);
|
|
677
700
|
integration.writeSettings(settingsPath, settings);
|
|
@@ -738,10 +761,17 @@ async function installHooksImpl(
|
|
|
738
761
|
|
|
739
762
|
for (const { cli: cliId, path } of writtenSettingsPaths) {
|
|
740
763
|
const integration = getIntegration(cliId);
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
`
|
|
744
|
-
|
|
764
|
+
if (cliId === "hermes") {
|
|
765
|
+
console.log(
|
|
766
|
+
`Failproof AI native plugin installed for ${integration.displayName} ` +
|
|
767
|
+
`(8 registered hooks, scope: ${scope}).`
|
|
768
|
+
);
|
|
769
|
+
} else {
|
|
770
|
+
console.log(
|
|
771
|
+
`Failproof AI hooks installed for ${integration.displayName} ` +
|
|
772
|
+
`(${integration.eventTypes.length} event types, scope: ${scope}).`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
745
775
|
console.log(`Settings: ${path}`);
|
|
746
776
|
}
|
|
747
777
|
if (scope === "project") {
|
|
@@ -876,7 +906,11 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
876
906
|
for (const s of scopesToRemove) {
|
|
877
907
|
// Usually one path; Hermes returns one per profile.
|
|
878
908
|
const settingsPaths = settingsPathsFor(integration, s, cwd);
|
|
879
|
-
|
|
909
|
+
// A Hermes install copies its managed plugin before updating config.yaml.
|
|
910
|
+
// If the config write is interrupted, uninstall must still call the
|
|
911
|
+
// integration so it can remove that orphaned managed directory.
|
|
912
|
+
const existing =
|
|
913
|
+
cliId === "hermes" ? settingsPaths : settingsPaths.filter((p) => existsSync(p));
|
|
880
914
|
|
|
881
915
|
if (existing.length === 0) {
|
|
882
916
|
if (scope !== "all" && selectedClis.length === 1) {
|
package/src/hooks/types.ts
CHANGED
|
@@ -113,13 +113,13 @@ export const HERMES_TOOL_INPUT_MAP: Record<string, Record<string, string>> = {
|
|
|
113
113
|
Edit: { path: "file_path" },
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
// Hermes live-hook (Pillar 1) events + scopes.
|
|
117
|
-
// events
|
|
118
|
-
// `
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
116
|
+
// Hermes live-hook (Pillar 1) events + scopes. The native Python plugin sends
|
|
117
|
+
// these events to failproofaid's warm worker. It also handles `pre_llm_call`,
|
|
118
|
+
// `on_session_reset`, and `on_session_finalize` locally for instruction context
|
|
119
|
+
// and ledger cleanup, so those do not need canonical policy-event mappings.
|
|
120
|
+
// Config is USER-scope only (`~/.hermes/config.yaml`; Hermes has no project
|
|
121
|
+
// scope). `pre_tool_call` is the core gate and fires for tool calls from every
|
|
122
|
+
// source (Slack/Telegram/CLI/cron) and internal subagents.
|
|
123
123
|
//
|
|
124
124
|
// `pre_verify` IS a turn-end gate — the earlier claim here that Hermes has none
|
|
125
125
|
// was wrong. We deliberately do NOT install it (product decision, 2026-07-29);
|
|
@@ -143,7 +143,7 @@ export const HERMES_TOOL_INPUT_MAP: Record<string, Record<string, string>> = {
|
|
|
143
143
|
// 2. Capped at 3 nudges per turn (`DEFAULT_MAX_VERIFY_NUDGES`,
|
|
144
144
|
// `agent/verify_hooks.py:21`), operator-overridable; resets each turn.
|
|
145
145
|
// 3. It landed upstream ~2026-06-30. Older Hermes fails the key against
|
|
146
|
-
// VALID_HOOKS and warn-and-skips it
|
|
146
|
+
// VALID_HOOKS and warn-and-skips it silently.
|
|
147
147
|
//
|
|
148
148
|
// Until it is installed, `HERMES_EVENT_MAP` emits no `Stop` and those 5
|
|
149
149
|
// builtins remain inapplicable on Hermes.
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports=[78500,(e,o,r)=>{o.exports=e.x("node:async_hooks",()=>require("node:async_hooks"))},2157,(e,o,r)=>{o.exports=e.x("node:fs",()=>require("node:fs"))},12714,(e,o,r)=>{o.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},50227,(e,o,r)=>{o.exports=e.x("node:path",()=>require("node:path"))},1457,(e,o,r)=>{o.exports=e.x("node:readline",()=>require("node:readline"))},12057,(e,o,r)=>{o.exports=e.x("node:util",()=>require("node:util"))},27028,(e,o,r)=>{o.exports=e.x("node:zlib",()=>require("node:zlib"))},14747,(e,o,r)=>{o.exports=e.x("path",()=>require("path"))}];
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=%5Bexternals%5D__157pf8j._.js.map
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports=[44376,(a,b,c)=>{b.exports=a.x("node:module",()=>require("node:module"))},36119,81243,a=>{"use strict";var b=a.i(37936),c=a.i(21572),d=a.i(2157),e=a.i(75818),f=a.i(60526),g=a.i(50227),h=a.i(74533);function i(){return(0,f.userInfo)().username}function j(a=i()){return`failproofaid@${a}.service`}function k(a=i()){return(0,g.resolve)(process.env.FAILPROOFAI_SYSTEMD_DIR||"/etc/systemd/system",j(a))}function l(){try{var a;return a=(0,h.execFileSync)("systemctl",["show",j(),"--property=ConditionResult","--value"],{stdio:["ignore","pipe","ignore"],timeout:1e4}).toString(),"no"===a.trim()?"condition-failed":"stopped"}catch{return"stopped"}}a.i(10695),a.i(66680),a.i(44376),a.i(27028);var m=a.i(15501),n=a.i(8674),o=a.i(93328);function p(){return(process.env.FAILPROOF_API_URL??process.env.FAILPROOFAI_API_URL??"https://api.befailproof.ai").replace(/\/+$/,"")}class q extends Error{status;code;retryAfterSecs;constructor(a,b,c,d){super(c),this.status=a,this.code=b,this.retryAfterSecs=d,this.name="AuthApiError"}}async function r(a){let b={};try{b=await a.json()}catch{}let c=b.code??`http_${a.status}`,d=b.message??b.detail??a.statusText??"request failed",e=b.retry_after_secs;if(void 0===e){let b=a.headers.get("retry-after");if(b){let a=Number(b);Number.isFinite(a)&&(e=a)}}return void 0!==e&&(e=Math.max(0,Math.min(86400,e))),new q(a.status,c,d,e)}async function s(a,b){try{return await fetch(a,{...b,signal:function(a){let b=AbortSignal.timeout(1e4);if(!a)return b;let c=AbortSignal.any;if(c)return c([b,a]);let d=new AbortController;if(b.aborted)d.abort(b.reason);else b.addEventListener("abort",()=>d.abort(b.reason),{once:!0});if(a.aborted)d.abort(a.reason);else a.addEventListener("abort",()=>d.abort(a.reason),{once:!0});return d.signal}(b.signal??void 0)})}catch(d){let c=function(a){if(null===a||"object"!=typeof a)return!1;let b=a.name;return"AbortError"===b||"TimeoutError"===b}(d);if((0,o.trackEvent)("api_server_unreachable",{kind:c?"timeout":"network",path:function(a){try{return new URL(a).pathname}catch{return a}}(a),method:"string"==typeof b.method?b.method:"GET"}),c)throw new q(0,"timeout",`request to ${a} timed out after 10000ms`);throw d}}async function t(a,b,c){let d={"content-type":"application/json"};c?.accessToken&&(d.authorization=`Bearer ${c.accessToken}`);let e=await s(`${p()}${a}`,{method:"POST",headers:d,body:JSON.stringify(b)});if(204!==e.status){if(!e.ok)throw await r(e);return await e.json()}}async function u(a,b){let c=await s(`${p()}${a}`,{method:"GET",headers:{authorization:`Bearer ${b}`}});if(!c.ok)throw await r(c);return await c.json()}async function v(a){return t("/v0/auth/token/refresh",{refresh_token:a})}async function w(a){return u("/v0/auth/me",a)}function x(){let a=process.env.FAILPROOFAI_AUTH_DIR;return a?(0,g.join)(a,"session.json"):(0,e.auditSessionFile)()}function y(){let a=x();if(!(0,d.existsSync)(a))return null;try{let b=(0,d.readFileSync)(a,"utf-8"),c=JSON.parse(b);if("string"!=typeof c.access_token||"string"!=typeof c.refresh_token||"number"!=typeof c.access_expires_at||"object"!=typeof c.user||!c.user||"string"!=typeof c.user.id||"string"!=typeof c.user.email)return null;return{access_token:c.access_token,refresh_token:c.refresh_token,access_expires_at:c.access_expires_at,refresh_expires_at:"number"==typeof c.refresh_expires_at?c.refresh_expires_at:c.access_expires_at,user:{id:c.user.id,email:c.user.email}}}catch{return null}}function z(){let a=x();(0,d.existsSync)(a)&&(0,d.rmSync)(a,{force:!0});try{let a=(0,e.migrationsDir)();if(!(0,d.existsSync)(a))return;for(let b of(0,d.readdirSync)(a)){if(!b.startsWith("backup-layout"))continue;let c=(0,g.resolve)(a,b,"auth.json");(0,d.existsSync)(c)&&(0,d.rmSync)(c,{force:!0})}}catch{}}let A=new Map;async function B(a){let b=A.get(a.refresh_token);if(b)return b;let c=(async()=>{let b=function(a,b){let c=Math.floor(Date.now()/1e3),d=a.user??b;if(!d)throw Error("authFromTokenResponse: missing user identity");return{access_token:a.access_token,refresh_token:a.refresh_token,access_expires_at:c+a.access_expires_in,refresh_expires_at:c+a.refresh_expires_in,user:d}}(await v(a.refresh_token),a.user);return(0,n.writeJsonAtomically)(x(),b),b})();A.set(a.refresh_token,c);try{return await c}finally{A.delete(a.refresh_token)}}async function C(){let a=y();if(!a)return null;let b=Math.floor(Date.now()/1e3);if(a.access_expires_at-b>60)return a;try{return await B(a)}catch(a){return a instanceof q&&401===a.status&&z(),null}}async function D(){let a=await C();if(!a)return null;try{return{me:await w(a.access_token),auth:a}}catch(a){if(a instanceof q&&401===a.status){let a=y();if(!a)return null;try{let b=await B(a);return{me:await w(b.access_token),auth:b}}catch(a){a instanceof q&&401===a.status&&z()}}return null}}async function E(){let a=(0,c.readConfig)(),b=function(){let a,b;try{a=(0,d.readFileSync)((0,e.auditScheduleFile)(void 0),"utf8")}catch{return null}try{b=JSON.parse(a)}catch{return null}if(!b||"object"!=typeof b||Array.isArray(b))return null;let c=b,f=a=>"number"==typeof a&&Number.isFinite(a)?a:null,g=f(c.schema);return{schema:g,nextDueAtMs:f(c.next_due_at_ms),lastAttemptAtMs:f(c.last_attempt_at_ms),lastRunAtMs:f(c.last_run_at_ms),lastExitCode:f(c.last_exit_code),schemaAhead:null!==g&&g>1}}(),f=(0,m.readDashboardCacheMeta)(),g=y(),i=function(){if("linux"!==process.platform&&process.platform,!(0,d.existsSync)(k()))return"not-installed";try{let a=(0,h.execFileSync)("systemctl",["is-active",j()],{stdio:["ignore","pipe","ignore"],timeout:1e4}).toString().trim();return"active"===a?"running":l()}catch{return l()}}();return{auto:a.audit.auto,intervalDays:a.audit.intervalDays,signedInAs:g?{id:g.user.id,email:g.user.email}:null,daemon:i,schedule:b?{nextDueAtMs:b.nextDueAtMs,lastAttemptAtMs:b.lastAttemptAtMs,lastRunAtMs:b.lastRunAtMs,lastExitCode:b.lastExitCode,schemaAhead:b.schemaAhead}:null,lastResultAt:f?.cachedAt??null,lastScan:f?{finishedAt:f.cachedAt,findings:f.findings,sessionsScanned:f.sessionsScanned,eventsScanned:f.eventsScanned}:null,daemonStartedAtMs:"running"===i?function(){if(!(0,d.existsSync)(k()))return null;try{let a=(0,h.execFileSync)("systemctl",["show",j(),"-p","ActiveEnterTimestampMonotonic","--value"],{stdio:["ignore","pipe","ignore"],timeout:1e4}).toString().trim();return function(a,b,c){if(!Number.isFinite(a)||a<=0||!Number.isFinite(b)||b<=0)return null;let d=(b-a)/1e3;return d<0?null:Math.round(c-d)}(Number(a),Math.floor(Number(process.hrtime.bigint())/1e3),Date.now())}catch{return null}}():null}}a.s(["readAuth",0,y,"whoAmI",0,D],81243),(0,a.i(13095).ensureServerEntryExports)([E]),(0,b.registerServerReference)(E,"00063bb359b039d550f793409319d65c11c5b57371",null),a.s(["getScheduledAuditAction",0,E],36119)}];
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=%5Broot-of-the-server%5D__0oa1lav._.js.map
|