failproofai 1.0.7-beta.2 → 1.0.8-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.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/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 +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -5
- 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/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +4 -4
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +6 -7
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +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/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/server-reference-manifest.json +18 -7
- package/.next/standalone/.next/server/app/settings/page.js +9 -7
- 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]__1j-zsg5._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
- 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/_1ek68ln._.js +9 -9
- package/.next/standalone/.next/server/chunks/_1q5i8mb._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qxnccm._.js → [root-of-the-server]__06pflha._.js} +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0soxz2z._.js → [root-of-the-server]__12e7nhs._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _0o4xkpl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-7sqrb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.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 +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +33 -22
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
- package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 0ahfwmbkpgfw4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{043j99m8ykg__.css → 0bhidk90e-07f.css} +1 -1
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
- package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 1v_tp3hm8wyhe.js} +1 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
- package/.next/standalone/.next/static/chunks/{3yxro_r2_o9ad.js → 2vo7qbdbvnc0o.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 2z7i-yg59w4pn.js} +1 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
- package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 3bgot5v6f6s3c.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3otmypm6j_xfo.js → 3o3f1ibfci0p7.js} +2 -2
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/get-jev-config.ts +194 -14
- package/.next/standalone/app/actions/update-jev-config.ts +160 -11
- package/.next/standalone/app/policies/hooks-client.tsx +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +169 -12
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
- package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +1 -0
- package/bin/failproofai.mjs +37 -6
- package/dist/cli.mjs +2861 -1145
- package/dist/worker.mjs +815 -197
- package/package.json +9 -9
- package/src/hooks/builtin-policies.ts +24 -6
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +56 -7
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +58 -8
- package/src/hooks/effective-reviewers.ts +172 -8
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +44 -7
- package/src/hooks/hook-activity-store.ts +13 -5
- package/src/hooks/jev-cli.ts +1009 -86
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/manager.ts +44 -6
- package/src/hooks/pack-cli.ts +175 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +11 -1
- package/src/hooks/pack-store.ts +47 -10
- package/src/hooks/policy-authority.ts +73 -20
- package/src/hooks/policy-catalog.ts +10 -8
- package/src/hooks/policy-evaluator.ts +31 -8
- package/src/hooks/policy-registry.ts +3 -2
- package/src/hooks/policy-reviewability.ts +41 -12
- package/src/hooks/policy-types.ts +5 -3
- package/src/hooks/semantic/combine.ts +124 -84
- package/src/hooks/semantic/decide.ts +16 -0
- package/src/hooks/semantic/envelope.ts +18 -59
- package/src/hooks/semantic/evaluator.ts +11 -2
- package/src/hooks/semantic/facts.ts +7 -1
- package/src/hooks/semantic/intent.ts +8 -8
- package/src/hooks/semantic/jev-client.ts +540 -28
- package/src/hooks/semantic/jev-config.ts +586 -38
- package/src/hooks/semantic/jev-review.ts +26 -11
- package/src/hooks/semantic/jev-throttle.ts +8 -2
- package/src/hooks/semantic/pack-policies.ts +58 -15
- package/src/hooks/semantic/policies.ts +4 -3
- package/src/hooks/semantic/redact.ts +28 -1
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +11 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/worker-server.ts +2 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bf34x4._.js +0 -22
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_1w_5l7t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/1qd741hzlmjbo.js +0 -1
- package/.next/standalone/.next/static/chunks/2_pltstd8-xgs.js +0 -1
- package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
|
@@ -53,6 +53,7 @@ import * as intentStore from "./intent";
|
|
|
53
53
|
import { JevError, transportForConfig, type JevTransport } from "./jev-client";
|
|
54
54
|
import { DEFAULT_JEV_MODE, type JevConfig } from "./jev-config";
|
|
55
55
|
import * as jevThrottle from "./jev-throttle";
|
|
56
|
+
import { sessionProjectRoot } from "./session-root";
|
|
56
57
|
import type { SemanticInput } from "./types";
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -177,12 +178,15 @@ export function resolveTimeout(cfg: JevConfig): number {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
/** The semantic policy an attributable Jev verdict is filed under. */
|
|
181
|
+
function jevDecider(outcome: Extract<SemanticOutcome, { status: "ok" }>) {
|
|
182
|
+
const wanted = outcome.verdict.decision === "deny" ? "deny" : "instruct";
|
|
183
|
+
return outcome.verdict.outcomes.find((o) => o.verdict === wanted);
|
|
184
|
+
}
|
|
185
|
+
|
|
180
186
|
function jevPolicyName(outcome: Extract<SemanticOutcome, { status: "ok" }>): string {
|
|
181
|
-
const
|
|
182
|
-
const wanted = verdict.decision === "deny" ? "deny" : "instruct";
|
|
183
|
-
const first = verdict.outcomes.find((o) => o.verdict === wanted);
|
|
187
|
+
const first = jevDecider(outcome);
|
|
184
188
|
if (first) return `semantic/${first.policy}`;
|
|
185
|
-
return verdict.beyondTask ? "semantic/beyond-task" : "semantic/jev";
|
|
189
|
+
return outcome.verdict.beyondTask ? "semantic/beyond-task" : "semantic/jev";
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
/**
|
|
@@ -215,24 +219,30 @@ export function toReview(outcome: SemanticOutcome, cached = false): JevReview {
|
|
|
215
219
|
// only that no request was made: v1 asks it on every call it sends, with or
|
|
216
220
|
// without a recorded human message (see `compile.ts`).
|
|
217
221
|
const injection = sent ? outcome.verdict.injectionSuspected : null;
|
|
222
|
+
// Fired, and no consent softened it: an `instruct` the human's own task did
|
|
223
|
+
// not downgrade from a deny (see "A check that fired without consent keeps
|
|
224
|
+
// the floor" in `combine.ts`).
|
|
225
|
+
const warned = (o: (typeof outcomes)[number]) => o.verdict === "instruct" && o.intent !== "downgraded-task-step";
|
|
218
226
|
return {
|
|
219
227
|
kind: "answered",
|
|
220
228
|
decision: outcome.verdict.decision,
|
|
221
229
|
reason: outcome.verdict.reason,
|
|
222
230
|
policyName: jevPolicyName(outcome),
|
|
231
|
+
...(jevDecider(outcome)?.origin ? { origin: jevDecider(outcome)?.origin } : {}),
|
|
223
232
|
// Every selected policy's questions were in the request, and `readAnswers`
|
|
224
233
|
// refuses a response missing any of them — so selected == asked, and only
|
|
225
234
|
// when a request was actually made.
|
|
226
235
|
asked: sent ? outcomes.map((o) => o.policy) : [],
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
// most-severe merge — see "A warning-level answer clears the deny, and
|
|
232
|
-
// leaves the warning" there. A `deny` is what keeps the block.
|
|
233
|
-
notDenied: sent ? outcomes.filter((o) => o.verdict !== "deny").map((o) => o.policy) : [],
|
|
236
|
+
// `none` found nothing, `overridden` is what the human asked for, and a
|
|
237
|
+
// deny the human's task softened to a warning is that task's consent — all
|
|
238
|
+
// three clear. A `deny`, or a warning nobody consented to, keeps the block.
|
|
239
|
+
notDenied: sent ? outcomes.filter((o) => o.verdict !== "deny" && !warned(o)).map((o) => o.policy) : [],
|
|
234
240
|
injectionAsked: injection !== null,
|
|
235
241
|
injected: injection !== null && injection >= DEFAULT_THRESHOLDS_V1.injection,
|
|
242
|
+
// A check that CAN deny warned without consent: nothing may be cleared on
|
|
243
|
+
// this call (see "A check that fired without consent keeps the floor" in
|
|
244
|
+
// `combine.ts`).
|
|
245
|
+
unclearableWarned: outcomes.some((o) => o.mode === "deny" && warned(o)),
|
|
236
246
|
// Something did not fit — a human turn, the agent's message, or what T4's
|
|
237
247
|
// store had already capped before the envelope saw it (it caps to fit the
|
|
238
248
|
// envelope, so the envelope cannot see that cut; only the evaluator's
|
|
@@ -300,11 +310,15 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
|
|
|
300
310
|
intent = { userSaid: [], agentLastMessage: null };
|
|
301
311
|
}
|
|
302
312
|
|
|
313
|
+
// Never throws; null only without a cwd, and then there is nothing to pin.
|
|
314
|
+
const projectRoot = sessionProjectRoot(call.sessionId, call.cwd);
|
|
315
|
+
|
|
303
316
|
const input: SemanticInput = {
|
|
304
317
|
eventType: call.eventType,
|
|
305
318
|
toolName: call.toolName,
|
|
306
319
|
toolInput: toolInputRecord(call.toolInput),
|
|
307
320
|
...(call.cwd ? { cwd: call.cwd } : {}),
|
|
321
|
+
...(projectRoot ? { projectRoot } : {}),
|
|
308
322
|
...(call.permissionMode ? { permissionMode: call.permissionMode } : {}),
|
|
309
323
|
userSaid: intent.userSaid,
|
|
310
324
|
agentLastMessage: intent.agentLastMessage,
|
|
@@ -323,6 +337,7 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
|
|
|
323
337
|
let abandoned = false;
|
|
324
338
|
const answered = evaluateSemantic(input, {
|
|
325
339
|
transport,
|
|
340
|
+
cli: call.cli,
|
|
326
341
|
via: route.via,
|
|
327
342
|
model: route.model,
|
|
328
343
|
timeoutMs,
|
|
@@ -37,8 +37,14 @@
|
|
|
37
37
|
* the only evaluator on a configured machine and lives for hours, so the
|
|
38
38
|
* process is the cache and the bucket is the process's upstream budget: a
|
|
39
39
|
* caller may build a new wrapper per hook event and every wrapper shares both.
|
|
40
|
-
* The worker
|
|
41
|
-
*
|
|
40
|
+
* The worker serializes the registry part of `evaluateHookEvent` but hands the
|
|
41
|
+
* queue back while a two-tier review waits on Jev (`worker-server.ts`
|
|
42
|
+
* `release`), so several Jev calls can be in flight at once. The cache fills
|
|
43
|
+
* only when an answer arrives: identical requests in flight together each
|
|
44
|
+
* miss, each take a token and each go upstream (and are billed).
|
|
45
|
+
* ponytail: no in-flight de-duplication; add a single-flight map keyed by the
|
|
46
|
+
* digest if duplicate spend shows up (the joiner's abort must not cancel the
|
|
47
|
+
* leader, and a leader's own abort must not fail the joiner).
|
|
42
48
|
*
|
|
43
49
|
* **No on-disk cache**, deliberately. It would only help the one-shot
|
|
44
50
|
* in-process path, which on a configured machine does not exist (CLAUDE.md,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ## The replacement rule
|
|
5
5
|
*
|
|
6
|
-
* A pack that declares at least ONE `semantic` entry replaces
|
|
6
|
+
* A FailproofAI pack that declares at least ONE `semantic` entry replaces
|
|
7
7
|
* `SEMANTIC_POLICIES` wholesale. It mirrors the rule already in force for the
|
|
8
8
|
* regex builtins — installing a pack stops the compiled builtins registering —
|
|
9
9
|
* and it exists for the same reason: one source of truth, so a name collision
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* arise, and `reviewedBy: ["destructive-deletion"]` in a pack manifest cannot
|
|
12
12
|
* silently mean the builtin's question set instead of the pack's.
|
|
13
13
|
*
|
|
14
|
+
* Anyone else's checks are ADDED to the compiled-in set instead. Their names
|
|
15
|
+
* cannot collide with it (a third party's claim to a builtin name is void:
|
|
16
|
+
* `isReservedClaim`),
|
|
17
|
+
* and replacing it would drop deny-mode checks like `credential-exfiltration`
|
|
18
|
+
* that add denies the regex tier does not have — weaker, not noisier.
|
|
19
|
+
*
|
|
14
20
|
* Two packs that both declare entries CONCATENATE; a name declared by two packs
|
|
15
21
|
* keeps the first and drops the later one, because the answer map is keyed by
|
|
16
22
|
* policy name and the second would overwrite the first's questions in the
|
|
@@ -33,6 +39,7 @@
|
|
|
33
39
|
* regex policies cover — a machine locked out over a typo in the half of the
|
|
34
40
|
* system whose job is to let more real work through.
|
|
35
41
|
*/
|
|
42
|
+
import { contestedSemanticNames, isFirstPartyPack, isReservedClaim, jevPacks, replacesBuiltinChecks } from "../effective-reviewers";
|
|
36
43
|
import { hookLogWarn } from "../hook-logger";
|
|
37
44
|
import {
|
|
38
45
|
packSemantic,
|
|
@@ -88,9 +95,9 @@ const GLOBAL_QUESTION_CHARS = Math.max(
|
|
|
88
95
|
* "somebody installed a big pack", and the flag that was supposed to be
|
|
89
96
|
* unreachable from ordinary work becomes routine.
|
|
90
97
|
*
|
|
91
|
-
* The real sixteen use
|
|
92
|
-
* constraint on the set we ship.
|
|
93
|
-
* ask for.
|
|
98
|
+
* The real sixteen use `BUILTIN_QUESTION_CHARS` of it, so this is not a
|
|
99
|
+
* constraint on the set we ship. A stranger's pack is added to them, so what it
|
|
100
|
+
* may ask for is what they leave.
|
|
94
101
|
*/
|
|
95
102
|
export const MAX_PACK_QUESTION_CHARS = MAX_REQUEST_CHARS - MAX_STATE_CHARS - GLOBAL_QUESTION_CHARS;
|
|
96
103
|
|
|
@@ -116,16 +123,19 @@ export function questionChars(entry: SemanticManifestEntry): number {
|
|
|
116
123
|
return JSON.stringify(questions).length;
|
|
117
124
|
}
|
|
118
125
|
|
|
126
|
+
/** What the compiled-in set spends of that budget when third-party checks join it. */
|
|
127
|
+
export const BUILTIN_QUESTION_CHARS = SEMANTIC_POLICIES.reduce((n, p) => n + questionChars(p as SemanticManifestEntry), 0);
|
|
128
|
+
|
|
119
129
|
export interface ResolvedSemanticPolicies {
|
|
120
130
|
policies: ReadonlyArray<SemanticPolicy>;
|
|
121
|
-
/** True when a pack supplied
|
|
131
|
+
/** True when a pack supplied any of the set. */
|
|
122
132
|
fromPack: boolean;
|
|
123
133
|
/** One line per dropped policy. Diagnostics; nothing here changes a verdict. */
|
|
124
134
|
errors: string[];
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
/** Turn one validated manifest entry into a policy the compiler can use. */
|
|
128
|
-
function toSemanticPolicy(entry: SemanticManifestEntry): SemanticPolicy {
|
|
138
|
+
function toSemanticPolicy(entry: SemanticManifestEntry, pack: { id: string; version?: string }): SemanticPolicy {
|
|
129
139
|
// An unknown precondition name DROPS the policy (the caller catches this),
|
|
130
140
|
// rather than compiling it with no gate at all. Ungating would be the wider
|
|
131
141
|
// direction, not the weaker one, but it is not what the author asked for: a
|
|
@@ -151,6 +161,7 @@ function toSemanticPolicy(entry: SemanticManifestEntry): SemanticPolicy {
|
|
|
151
161
|
// to the identical policy.
|
|
152
162
|
...(precondition ? { precondition } : {}),
|
|
153
163
|
guidance: entry.guidance,
|
|
164
|
+
origin: { packId: pack.id, ...(pack.version ? { packVersion: pack.version } : {}) },
|
|
154
165
|
};
|
|
155
166
|
}
|
|
156
167
|
|
|
@@ -163,18 +174,50 @@ function toSemanticPolicy(entry: SemanticManifestEntry): SemanticPolicy {
|
|
|
163
174
|
* a filesystem.
|
|
164
175
|
*/
|
|
165
176
|
export function semanticPoliciesFromPacks(
|
|
166
|
-
packs: ReadonlyArray<Pick<ResolvedPack, "id" | "semantic"
|
|
177
|
+
packs: ReadonlyArray<Pick<ResolvedPack, "id" | "semantic"> & { source?: string; version?: string }>,
|
|
167
178
|
): ResolvedSemanticPolicies {
|
|
168
179
|
const declared = packs.filter((p) => packSemantic(p).length > 0);
|
|
169
180
|
if (declared.length === 0) return { policies: SEMANTIC_POLICIES, fromPack: false, errors: [] };
|
|
170
181
|
|
|
171
|
-
|
|
182
|
+
// FailproofAI's own checks replace the compiled-in set; anyone else's join it
|
|
183
|
+
// (`replacesBuiltinChecks`, which the reviewer set applies too). First-party
|
|
184
|
+
// packs spend the budget first, so install order cannot starve them.
|
|
185
|
+
const replace = replacesBuiltinChecks(declared);
|
|
186
|
+
const ordered = [...declared.filter((p) => isFirstPartyPack(p)), ...declared.filter((p) => !isFirstPartyPack(p))];
|
|
187
|
+
const policies: SemanticPolicy[] = replace ? [] : [...SEMANTIC_POLICIES];
|
|
172
188
|
const errors: string[] = [];
|
|
173
|
-
const seen = new Set<string>();
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
const seen = new Set<string>(policies.map((p) => p.name));
|
|
190
|
+
// A name two packs declare DIFFERENTLY is asked for nobody. Keeping the first
|
|
191
|
+
// was the escalation: the question that decides another pack's policies came
|
|
192
|
+
// from whichever pack was listed first, so installing a permissive
|
|
193
|
+
// `production-infra-change` beside a real one cleared every policy reviewable
|
|
194
|
+
// by that name. Computed by `effectiveReviewerNames`'s own function, because
|
|
195
|
+
// the name it refuses in the reviewer set and the question refused here have to
|
|
196
|
+
// be the same name — a check in the set with nobody's question, or a question
|
|
197
|
+
// nobody may name, are both worse than neither.
|
|
198
|
+
const contested = contestedSemanticNames(declared);
|
|
199
|
+
let spent = replace ? 0 : BUILTIN_QUESTION_CHARS;
|
|
200
|
+
for (const pack of ordered) {
|
|
176
201
|
for (const entry of packSemantic(pack)) {
|
|
202
|
+
if (isReservedClaim(pack, entry.name)) {
|
|
203
|
+
errors.push(
|
|
204
|
+
`pack ${pack.id} declares semantic policy ${entry.name}, a name reserved for FailproofAI's own Jev checks, ` +
|
|
205
|
+
`so that pack's version of it is never asked`,
|
|
206
|
+
);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const claimants = contested.get(entry.name);
|
|
210
|
+
if (claimants) {
|
|
211
|
+
// Once per name: every claimant reaches here with the same message.
|
|
212
|
+
const message =
|
|
213
|
+
`packs ${claimants.join(" and ")} declare different semantic policies named ${entry.name}, so it is asked ` +
|
|
214
|
+
`for neither of them and no policy can be cleared by that name`;
|
|
215
|
+
if (!errors.includes(message)) errors.push(message);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
177
218
|
if (seen.has(entry.name)) {
|
|
219
|
+
// Same name, same declaration — a fork or a re-publish of one pack, where
|
|
220
|
+
// the question is identical either way. Kept once, said once.
|
|
178
221
|
errors.push(`pack ${pack.id} declares semantic policy ${entry.name}, which another pack already declared`);
|
|
179
222
|
continue;
|
|
180
223
|
}
|
|
@@ -188,7 +231,7 @@ export function semanticPoliciesFromPacks(
|
|
|
188
231
|
continue;
|
|
189
232
|
}
|
|
190
233
|
try {
|
|
191
|
-
policies.push(toSemanticPolicy(entry));
|
|
234
|
+
policies.push(toSemanticPolicy(entry, pack));
|
|
192
235
|
} catch (err) {
|
|
193
236
|
errors.push(`pack ${pack.id} semantic policy ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
194
237
|
continue;
|
|
@@ -203,7 +246,7 @@ export function semanticPoliciesFromPacks(
|
|
|
203
246
|
// the safe one: what a pack's regex half names in `reviewedBy` will not match
|
|
204
247
|
// it, so those policies stay hard rather than being cleared by questions
|
|
205
248
|
// nobody validated.
|
|
206
|
-
if (policies.length === 0) return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
|
|
249
|
+
if (policies.length === (replace ? 0 : SEMANTIC_POLICIES.length)) return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
|
|
207
250
|
return { policies, fromPack: true, errors };
|
|
208
251
|
}
|
|
209
252
|
|
|
@@ -224,7 +267,7 @@ const warned = new Set<string>();
|
|
|
224
267
|
* fails in the safe direction: the set a pack's `reviewedBy` names is not the
|
|
225
268
|
* builtin set, so nothing gets cleared by a policy nobody could read.
|
|
226
269
|
*/
|
|
227
|
-
export function resolveSemanticPolicies(): ReadonlyArray<SemanticPolicy> {
|
|
270
|
+
export function resolveSemanticPolicies(cli?: string): ReadonlyArray<SemanticPolicy> {
|
|
228
271
|
let packs: ReadonlyArray<ResolvedPack> = [];
|
|
229
272
|
let manifestErrors: string[] = [];
|
|
230
273
|
try {
|
|
@@ -234,7 +277,7 @@ export function resolveSemanticPolicies(): ReadonlyArray<SemanticPolicy> {
|
|
|
234
277
|
} catch {
|
|
235
278
|
return SEMANTIC_POLICIES;
|
|
236
279
|
}
|
|
237
|
-
const resolved = semanticPoliciesFromPacks(packs);
|
|
280
|
+
const resolved = semanticPoliciesFromPacks(jevPacks(packs, cli));
|
|
238
281
|
// Once per process per message, like `warnAuthority`: this runs on every gate
|
|
239
282
|
// event, and in the warm worker that is every tool call of every session.
|
|
240
283
|
for (const message of [...manifestErrors, ...resolved.errors]) {
|
|
@@ -53,8 +53,9 @@ const OUTSIDE_PROJECT: ReadonlySet<PathFact["relation"]> = new Set<PathFact["rel
|
|
|
53
53
|
* question has to be asked. Two tests, because the two tiers measure "the
|
|
54
54
|
* project" from different roots:
|
|
55
55
|
*
|
|
56
|
-
* - `relation` is computed against the
|
|
57
|
-
*
|
|
56
|
+
* - `relation` is computed against `facts.projectRoot`: the git root of the
|
|
57
|
+
* directory the session started in, pinned by `session-root.ts` so a later
|
|
58
|
+
* `cd` cannot move it. Anything but `inside_project` / `project_root` is
|
|
58
59
|
* outside it.
|
|
59
60
|
* - the partner compares against `$CLAUDE_PROJECT_DIR` or, when that is unset,
|
|
60
61
|
* the session's LIVE cwd, which drifts below the git root as the agent `cd`s.
|
|
@@ -260,7 +261,7 @@ export const SEMANTIC_POLICIES: ReadonlyArray<SemanticPolicy> = [
|
|
|
260
261
|
},
|
|
261
262
|
},
|
|
262
263
|
],
|
|
263
|
-
guidance: "This sends secrets or private files to a remote destination
|
|
264
|
+
guidance: "This sends secrets or private files to a remote destination, which the user cannot approve. Do not send them.",
|
|
264
265
|
},
|
|
265
266
|
{
|
|
266
267
|
name: "remote-code-execution",
|
|
@@ -2017,6 +2017,25 @@ function literalValue(
|
|
|
2017
2017
|
|
|
2018
2018
|
/** Long runs of token characters: candidates for the high-entropy rule. Greedy, so a match is a whole run. */
|
|
2019
2019
|
const LONG_TOKEN_RE = /[A-Za-z0-9_-]{32,}/g;
|
|
2020
|
+
/**
|
|
2021
|
+
* The same, in standard base64: `+` and `/` split an AWS secret access key
|
|
2022
|
+
* (40 characters, ~72% hold one) into runs under 32, so `LONG_TOKEN_RE` never
|
|
2023
|
+
* saw it and the key went out beside its redacted `AKIA` id.
|
|
2024
|
+
*/
|
|
2025
|
+
const LONG_B64_TOKEN_RE = /[A-Za-z0-9+/_-]{32,}={0,2}/g;
|
|
2026
|
+
|
|
2027
|
+
/**
|
|
2028
|
+
* A standard-base64 run that looks generated. A path or URL is a run of `/`
|
|
2029
|
+
* too, so any `+`/`/`-separated segment that is a word rejects it — the
|
|
2030
|
+
* word-built run then falls through to `LONG_TOKEN_RE`, which still takes a
|
|
2031
|
+
* random token sitting inside it.
|
|
2032
|
+
*/
|
|
2033
|
+
function looksRandomB64(t: string): boolean {
|
|
2034
|
+
if (!/[+/]/.test(t)) return false;
|
|
2035
|
+
const bare = t.replace(/=+$/, "");
|
|
2036
|
+
if (bare.split(/[+/]/).some((seg) => /^[A-Z]?[a-z]{3,}$/.test(seg) || /^[A-Z]{4,}$/.test(seg))) return false;
|
|
2037
|
+
return looksRandomToken(bare.replace(/[+/]/g, ""));
|
|
2038
|
+
}
|
|
2020
2039
|
|
|
2021
2040
|
// ── Secret names ─────────────────────────────────────────────────────────────
|
|
2022
2041
|
|
|
@@ -2801,7 +2820,15 @@ export function redactSecretsDetailed(text: string, opts: RedactOptions = {}): R
|
|
|
2801
2820
|
|
|
2802
2821
|
out = redactNamedSecrets(out, c);
|
|
2803
2822
|
|
|
2804
|
-
// 8. Anything left that looks generated
|
|
2823
|
+
// 8. Anything left that looks generated: standard base64 first, whole, so
|
|
2824
|
+
// no piece of it is left for the narrower pass to miss.
|
|
2825
|
+
out = replaceCounting(
|
|
2826
|
+
out,
|
|
2827
|
+
LONG_B64_TOKEN_RE,
|
|
2828
|
+
(m, _g, offset, whole) => (looksRandomB64(m) && !insideDigest(whole, offset) ? marker("high-entropy token") : null),
|
|
2829
|
+
c,
|
|
2830
|
+
"skip",
|
|
2831
|
+
);
|
|
2805
2832
|
out = replaceCounting(
|
|
2806
2833
|
out,
|
|
2807
2834
|
LONG_TOKEN_RE,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project a session started in, pinned the first time the session is seen.
|
|
3
|
+
*
|
|
4
|
+
* The hook payload's `cwd` is the agent's LIVE shell directory. Claude Code
|
|
5
|
+
* keeps a `cd` across calls, so a session started in `~/work` that once ran
|
|
6
|
+
* `cd ~/work/api` reports `~/work/api` from then on — and `facts.projectRoot`,
|
|
7
|
+
* derived from that, shrank with it. Every file elsewhere in `~/work` then read
|
|
8
|
+
* as `outside_project_in_home`, and `read-outside-workspace` fired on the
|
|
9
|
+
* project the human opened.
|
|
10
|
+
*
|
|
11
|
+
* Letting the root FOLLOW the `cd` instead would be a bypass: `cd ~/.ssh` in
|
|
12
|
+
* one call and `cat id_rsa` in the next would make `~/.ssh` the project. So the
|
|
13
|
+
* root is fixed at the session's first reviewed call and never moves after.
|
|
14
|
+
* A `cd` can change how a relative path resolves (that is still the live cwd's
|
|
15
|
+
* job, in `extractPaths`) but never what counts as inside the project.
|
|
16
|
+
*
|
|
17
|
+
* Failure is never stricter or looser than before this file existed: no
|
|
18
|
+
* session id, an unreadable store, or a store someone else can write to all
|
|
19
|
+
* fall back to the live cwd's root — exactly the old behaviour. A session whose
|
|
20
|
+
* first reviewed call came after a `cd` pins that later directory; same again.
|
|
21
|
+
*/
|
|
22
|
+
import { mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { isAbsolute, resolve } from "node:path";
|
|
24
|
+
import { semanticDir } from "../fp-home";
|
|
25
|
+
import { findProjectRoot } from "./facts";
|
|
26
|
+
import { looseIntentStoreDir } from "./intent";
|
|
27
|
+
|
|
28
|
+
const SESSION_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
|
|
29
|
+
const ROOT_FILE_RE = /^[A-Za-z0-9._-]{1,128}\.json$/;
|
|
30
|
+
/** Sessions outlive the intent store's six hours; a week covers any real one. */
|
|
31
|
+
export const SESSION_ROOT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
32
|
+
const DIR_WRITABLE_BY_OTHERS = 0o022;
|
|
33
|
+
|
|
34
|
+
const rootsDir = (): string => resolve(semanticDir(), "roots");
|
|
35
|
+
const rootFile = (sessionId: string): string => resolve(rootsDir(), `${sessionId}.json`);
|
|
36
|
+
|
|
37
|
+
/** A stored root is only believed when it is a plain absolute path, and never `/`. */
|
|
38
|
+
function validRoot(v: unknown): v is string {
|
|
39
|
+
return typeof v === "string" && isAbsolute(v) && resolve(v) === v && v !== "/";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether the store may be trusted. A directory anyone else can write to lets
|
|
44
|
+
* them swap in a root of their choosing — a wider root is fewer paths outside
|
|
45
|
+
* it — so a loose one is ignored, after the same tightening the intent store
|
|
46
|
+
* applies (`looseIntentStoreDir`).
|
|
47
|
+
*/
|
|
48
|
+
function storeIsOurs(): boolean {
|
|
49
|
+
if (process.platform === "win32") return true;
|
|
50
|
+
if (looseIntentStoreDir() !== null) return false;
|
|
51
|
+
try {
|
|
52
|
+
const mode = statSync(rootsDir()).mode & 0o777;
|
|
53
|
+
return (mode & DIR_WRITABLE_BY_OTHERS) === 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return true; // Not there yet: nothing to read out of it.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readPinned(sessionId: string): string | null {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(readFileSync(rootFile(sessionId), "utf8")) as { root?: unknown };
|
|
62
|
+
return validRoot(parsed?.root) ? parsed.root : null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Delete root files older than {@link SESSION_ROOT_MAX_AGE_MS}. Best effort. */
|
|
69
|
+
export function pruneSessionRoots(now: number = Date.now()): number {
|
|
70
|
+
let removed = 0;
|
|
71
|
+
try {
|
|
72
|
+
const dir = rootsDir();
|
|
73
|
+
for (const name of readdirSync(dir)) {
|
|
74
|
+
if (!ROOT_FILE_RE.test(name)) continue;
|
|
75
|
+
const path = resolve(dir, name);
|
|
76
|
+
try {
|
|
77
|
+
const st = statSync(path);
|
|
78
|
+
if (st.isFile() && now - st.mtimeMs > SESSION_ROOT_MAX_AGE_MS) {
|
|
79
|
+
unlinkSync(path);
|
|
80
|
+
removed++;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Raced with another writer, or already gone.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// No directory yet.
|
|
88
|
+
}
|
|
89
|
+
return removed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The project root to judge this call's paths against: the one pinned for the
|
|
94
|
+
* session, or — on its first reviewed call — the live cwd's root, pinned now.
|
|
95
|
+
* Null only when there is no cwd at all. Never throws.
|
|
96
|
+
*/
|
|
97
|
+
export function sessionProjectRoot(sessionId: string | undefined, cwd: string | undefined, now: number = Date.now()): string | null {
|
|
98
|
+
if (!cwd) return null;
|
|
99
|
+
const live = findProjectRoot(cwd);
|
|
100
|
+
if (!sessionId || !SESSION_ID_RE.test(sessionId)) return live;
|
|
101
|
+
try {
|
|
102
|
+
if (!storeIsOurs()) return live;
|
|
103
|
+
const pinned = readPinned(sessionId);
|
|
104
|
+
if (pinned !== null) return pinned;
|
|
105
|
+
if (!validRoot(live)) return live;
|
|
106
|
+
mkdirSync(rootsDir(), { recursive: true, mode: 0o700 });
|
|
107
|
+
// `wx`: the first writer wins. Two calls racing on a new session both
|
|
108
|
+
// computed a root from the same moment; whichever lands is the pin, and
|
|
109
|
+
// the loser's EEXIST just means it answers with the live root this once.
|
|
110
|
+
writeFileSync(rootFile(sessionId), JSON.stringify({ root: live, at: now }), { mode: 0o600, flag: "wx" });
|
|
111
|
+
pruneSessionRoots(now);
|
|
112
|
+
} catch {
|
|
113
|
+
// Unwritable store: the old behaviour, for this call.
|
|
114
|
+
}
|
|
115
|
+
return live;
|
|
116
|
+
}
|
|
@@ -68,6 +68,8 @@ export interface SemanticPolicy {
|
|
|
68
68
|
precondition?: (facts: Facts) => boolean;
|
|
69
69
|
/** Shown to the agent when the policy fires. */
|
|
70
70
|
guidance: string;
|
|
71
|
+
/** The pack that declared it; absent for the compiled-in set. Attribution only. */
|
|
72
|
+
origin?: { packId: string; packVersion?: string };
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export interface PathFact {
|
|
@@ -94,6 +96,11 @@ export interface SemanticInput {
|
|
|
94
96
|
toolName: string;
|
|
95
97
|
toolInput: Record<string, unknown>;
|
|
96
98
|
cwd?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The project root pinned for the session (`session-root.ts`), so a `cd`
|
|
101
|
+
* cannot move what counts as inside the project. Absent: derived from `cwd`.
|
|
102
|
+
*/
|
|
103
|
+
projectRoot?: string;
|
|
97
104
|
permissionMode?: string;
|
|
98
105
|
/** What the human typed recently, oldest first. Trusted channel only. */
|
|
99
106
|
userSaid: string[];
|
|
@@ -121,6 +128,10 @@ export type IntentMode = "v0" | "v1";
|
|
|
121
128
|
export interface PolicyOutcome {
|
|
122
129
|
policy: string;
|
|
123
130
|
mode: "deny" | "instruct";
|
|
131
|
+
/** The policy's own `userCanOverride`: false is a check no consent can clear. */
|
|
132
|
+
userCanOverride?: boolean;
|
|
133
|
+
/** The pack that declared the check, when one did (`SemanticPolicy.origin`). */
|
|
134
|
+
origin?: { packId: string; packVersion?: string };
|
|
124
135
|
/** min over probe probabilities — one weak probe spoils the evidence. */
|
|
125
136
|
evidence: number;
|
|
126
137
|
exempt: number | null;
|
package/src/hooks/tui.ts
CHANGED
|
@@ -96,6 +96,10 @@ export interface MultiSelectOptions<T> {
|
|
|
96
96
|
|
|
97
97
|
const ESC = "\x1B";
|
|
98
98
|
|
|
99
|
+
/** How every "the key was on argv" warning opens, whichever command took it (`jev setup --token`, `config --token`). */
|
|
100
|
+
export const TOKEN_ON_ARGV =
|
|
101
|
+
"--token was on the command line: your shell history has it, and while this command ran any process of yours could read it from the process list.";
|
|
102
|
+
|
|
99
103
|
// ── glyphs ────────────────────────────────────────────────────────────────
|
|
100
104
|
// Exported so the other branded prompts (install-prompt.ts) share the exact
|
|
101
105
|
// same set instead of hand-syncing copies.
|
|
@@ -77,7 +77,8 @@ const TASK_DEADLINE_MS = 60_000;
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Serializes every request across every connection through one chain, so
|
|
80
|
-
* `evaluateHookEvent` calls never overlap
|
|
80
|
+
* `evaluateHookEvent` calls never overlap while reading the registry (see
|
|
81
|
+
* `release` below). `.catch()` on each link keeps the
|
|
81
82
|
* chain alive even if a handler throws unexpectedly — one bad request must
|
|
82
83
|
* never wedge every request queued behind it.
|
|
83
84
|
*
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports=[24868,(e,t,r)=>{t.exports=e.x("fs/promises",()=>require("fs/promises"))},12714,(e,t,r)=>{t.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},46786,(e,t,r)=>{t.exports=e.x("os",()=>require("os"))},14747,(e,t,r)=>{t.exports=e.x("path",()=>require("path"))},71809,e=>{"use strict";async function t(e,t){let r=Array(e.length),i=0;async function n(){for(;i<e.length;){let t=i++;try{r[t]={status:"fulfilled",value:await e[t]()}}catch(e){r[t]={status:"rejected",reason:e}}}}let a=Array.from({length:Math.min(t,e.length)},()=>n());return await Promise.all(a),r}e.s(["batchAll",0,t])},28557,e=>{"use strict";e.s(["formatDate",0,function(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).format(e)}])},54214,67405,e=>{"use strict";var t=e.i(24868),r=e.i(14747),i=e.i(81580),n=e.i(56936);async function a(e,i,n,a){let o=`agent-${a}.jsonl`;for(let a of[(0,r.join)(e,i,o),(0,r.join)(e,i,n,o),(0,r.join)(e,i,n,"subagents",o)])if(!(0,r.relative)(e,a).startsWith(".."))try{return await (0,t.access)(a),a}catch(e){if("ENOENT"===e.code)continue;break}return null}var o=e.i(83534),s=e.i(71809),l=e.i(28557);function u(e){if(e<1e3)return`${e}ms`;let t=Math.round(e/100);if(t<600)return`${(t/10).toFixed(1)}s`;let r=Math.round(e/1e3);if(r<3600){let e=Math.floor(r/60);return`${e}m ${r%60}s`}let i=Math.round(e/6e4),n=Math.floor(i/60);return`${n}h ${i%60}m`}function c(e){let t=(0,l.formatDate)(e),r=e.getMilliseconds().toString().padStart(3,"0");return`${t}.${r}`}function f(e,t,r,i){return{_source:i,uuid:e.uuid||"",parentUuid:e.parentUuid??null,timestamp:t,timestampMs:r.getTime(),timestampFormatted:c(r)}}async function m(e,t){let r=e.split("\n").filter(e=>""!==e.trim()),i=new Map,n=[],a=[],o=new Set,s=!1;for(let e=0;e<r.length;e++){let l;e>0&&e%200==0&&await new Promise(e=>setImmediate(e));let u=r[e];try{l=JSON.parse(u)}catch{continue}let c={...l,_source:t};a.push(c);let m=l.type,d=l.timestamp;if(!d)continue;let p=new Date(d),g=p.getTime();if("user"===m){let e=l.message;if(Array.isArray(e?.content)){let t=e.content;if(t.some(e=>"tool_result"===e.type)){let e=l.toolUseResult,r="string"==typeof e?.agentId?e.agentId:void 0;for(let e of(r&&/^[a-f0-9]+$/.test(r)&&o.add(r),t)){if("tool_result"!==e.type)continue;let t=e.tool_use_id;if(!t)continue;let{text:n,images:a}=function(e){let t=e.content;if("string"==typeof t)return{text:t};if(Array.isArray(t)){let e=t.filter(e=>"text"===e.type).map(e=>e.text),r=t.filter(e=>"image"===e.type).map(e=>{let t=e.source;return{base64:t.data,mediaType:t.media_type}}).filter(e=>e.base64&&e.mediaType);return{text:e.length>0?e.join("\n"):void 0,images:r.length>0?r:void 0}}return{}}(e);i.set(t,{timestamp:d,timestampMs:g,content:n,images:a,agentId:r})}continue}}let r="string"==typeof e?.content?e.content:"";n.push({type:"user",...f(l,d,p,t),message:{role:"user",content:r}});continue}if("assistant"===m){let e=l.message,r=[];if(Array.isArray(e?.content)&&(r=e.content.filter(e=>["text","tool_use","thinking"].includes(e.type)).map(e=>{if("text"===e.type)return{type:"text",text:e.text};if("tool_use"===e.type){let t=e.input;return{type:"tool_use",id:e.id,name:e.name,input:e.input??{},..."Task"===e.name&&t?{subagentType:t.subagent_type,subagentDescription:t.description}:{}}}return{type:"thinking",thinking:e.thinking,signature:e.signature}})),0===r.length)continue;n.push({type:"assistant",...f(l,d,p,t),message:{role:"assistant",content:r,model:e?.model}});continue}if("file-history-snapshot"===m||"progress"===m||"system"===m){n.push({type:m,...f(l,d,p,t),raw:{...l}});continue}if("queue-operation"===m){let e=s?"Session Resumed":"Session Started";s=!0,n.push({type:"queue-operation",...f(l,d,p,t),label:e});continue}}let l=0;for(let e of n)if("assistant"===e.type)for(let t of(++l%200==0&&await new Promise(e=>setImmediate(e)),e.message.content)){if("tool_use"!==t.type)continue;let r=i.get(t.id);if(!r)continue;let n=new Date(r.timestamp),a=Math.max(0,r.timestampMs-e.timestampMs);t.result={timestamp:r.timestamp,timestampFormatted:c(n),content:r.content,images:r.images,durationMs:a,durationFormatted:u(a)},r.agentId&&(t.subagentId=r.agentId)}return n.length>500&&await new Promise(e=>setImmediate(e)),n.sort((e,t)=>e.timestampMs-t.timestampMs),{entries:n,rawLines:a,subagentIds:Array.from(o)}}async function d(e,t="session"){return(await m(e,t)).entries}async function p(e,o){let l=(0,n.resolveProjectPath)(e),u=(0,i.getClaudeProjectsPath)(),c=(0,r.join)(l,`${o}.jsonl`),f=await (0,t.readFile)(c,"utf-8"),{entries:d,rawLines:p,subagentIds:g}=await m(f,"session");if(0===g.length)return{entries:d,rawLines:p,subagentIds:[]};let h=await (0,s.batchAll)(g.map(r=>async()=>{let i=`agent-${r}`,n=await a(u,e,o,r);if(!n)return null;let s=await (0,t.readFile)(n,"utf-8"),{entries:l,rawLines:c}=await m(s,i);return{entries:l,rawLines:c}}),4),y=[...d],w=[...p];for(let e of h)"fulfilled"===e.status&&e.value&&(y.push(...e.value.entries),w.push(...e.value.rawLines));return y.length>500&&await new Promise(e=>setImmediate(e)),y.sort((e,t)=>e.timestampMs-t.timestampMs),{entries:y,rawLines:w,subagentIds:g}}e.s(["formatDuration",0,u],67405),(0,o.runtimeCache)((e,t)=>p(e,t),60,{maxSize:50}),e.s(["baseEntry",0,f,"formatTimestamp",0,c,"parseLogContent",0,d,"parseRawLines",0,function(e,t){return e.split("\n").filter(e=>""!==e.trim()).flatMap(e=>{try{let r=JSON.parse(e);return void 0!==t&&(r._source=t),[r]}catch{return[]}})}],54214)},40911,e=>{"use strict";let t={info:0,warn:1,error:2};function r(e){return t[e]>=t.warn}function i(e){return`[failproofai${new Date().toISOString()}] ${e}`}e.s(["logActivity",0,function(e,t,n){if(!r("info"))return;let a=[`user=${e}`,`action=${t}`];n&&a.push(n),console.log(i("ACTIVITY"),a.join(" "))},"logError",0,function(e,t){void 0!==t?console.error(i("ERROR"),e,t):console.error(i("ERROR"),e)},"logWarn",0,function(e,t){r("warn")&&(void 0!==t?console.warn(i("WARN"),e,t):console.warn(i("WARN"),e))}])},81580,e=>{"use strict";var t=e.i(46786),r=e.i(14747);e.s(["decodeFolderName",0,function(e){return/^[A-Za-z]--/.test(e)?e[0]+":/"+e.slice(3).replace(/-/g,"/"):e.replace(/-/g,"/")},"encodeFolderName",0,function(e){let t=/^([A-Za-z]):[\\/](.*)$/.exec(e);return t?t[1]+"--"+t[2].replace(/[\\/]/g,"-"):e.replace(/[\\/]/g,"-")},"getClaudeProjectsPath",0,function(){let e=process.env.CLAUDE_PROJECTS_PATH;return e||(0,r.join)((0,t.homedir)(),".claude","projects")}])},56936,e=>{"use strict";var t=e.i(12714),r=e.i(50227),i=e.i(81580),n=e.i(83534),a=e.i(71809),o=e.i(40911),s=e.i(28557);let l=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;async function u(e,r){try{return(await (0,t.stat)(e)).mtime}catch(e){return(0,o.logWarn)(`Failed to stat ${r}:`,e),new Date(0)}}async function c(e){try{if(!(await (0,t.stat)(e)).isDirectory())return null;return await (0,t.readdir)(e,{withFileTypes:!0})}catch{return null}}async function f(){try{let e=(0,i.getClaudeProjectsPath)(),t=await c(e);if(!t)return[];let n=(await (0,a.batchAll)(t.filter(e=>e.isDirectory()).map(t=>async()=>{let i=(0,r.join)(e,t.name),n=await u(i,t.name);return{name:t.name,path:i,isDirectory:!0,lastModified:n,lastModifiedFormatted:(0,s.formatDate)(n),cli:["claude"]}}),16)).filter(e=>"fulfilled"===e.status).map(e=>e.value);return n.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),n}catch(e){return(0,o.logError)("Error reading Claude project folders:",e),[]}}async function m(){let[{getCodexProjects:t},{getCopilotProjects:r},{getCursorProjects:i},{getOpenCodeProjects:n},{getPiProjects:a},{getHermesProjects:s},{getOpenClawProjects:l},{getFactoryProjects:u},{getDevinProjects:c},{getAntigravityProjects:m},{getGooseProjects:d}]=await Promise.all([e.A(37352),e.A(5807),e.A(97452),e.A(82978),e.A(45093),e.A(67347),e.A(38978),e.A(62998),e.A(52400),e.A(91301),e.A(31854)]),[p,g,h,y,w,E,j,M,A,v,x,P]=await Promise.all([f(),t().catch(e=>((0,o.logError)("Error reading Codex projects:",e),[])),r().catch(e=>((0,o.logError)("Error reading Copilot projects:",e),[])),i().catch(e=>((0,o.logError)("Error reading Cursor projects:",e),[])),n().catch(e=>((0,o.logError)("Error reading OpenCode projects:",e),[])),a().catch(e=>((0,o.logError)("Error reading Pi projects:",e),[])),s().catch(e=>((0,o.logError)("Error reading Hermes projects:",e),[])),l().catch(e=>((0,o.logError)("Error reading OpenClaw projects:",e),[])),u().catch(e=>((0,o.logError)("Error reading Factory projects:",e),[])),c().catch(e=>((0,o.logError)("Error reading Devin projects:",e),[])),m().catch(e=>((0,o.logError)("Error reading Antigravity projects:",e),[])),d().catch(e=>((0,o.logError)("Error reading Goose projects:",e),[]))]);return function(...e){let t=new Map;for(let r of e)for(let e of r){let r=t.get(e.name);if(!r){t.set(e.name,{...e,cli:[...e.cli]});continue}let i=[...r.cli];for(let t of e.cli)i.includes(t)||i.push(t);let n=e.lastModified.getTime()>r.lastModified.getTime()?e:r;t.set(e.name,{...r,cli:i,lastModified:n.lastModified,lastModifiedFormatted:n.lastModifiedFormatted,sessionCount:void 0===r.sessionCount&&void 0===e.sessionCount?void 0:(r.sessionCount??0)+(e.sessionCount??0)})}let r=Array.from(t.values());return r.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),r}(p,g,h,y,w,E,j,M,A,v,x,P)}function d(e){if(!e)throw RangeError("Empty project name");if(/^[/\\]/.test(e))throw RangeError("Absolute project name");let t=(0,r.resolve)((0,i.getClaudeProjectsPath)()),n=(0,r.resolve)((0,r.join)(t,e));if(!n.startsWith(t+r.sep))throw RangeError("Project path escapes root");return n}function p(e){let t=e.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);return t?t[0]:void 0}async function g(e){try{let t=await c(e);if(!t)return[];let i=t.filter(e=>e.isFile()&&e.name.endsWith(".jsonl")&&p(e.name)),n=(await (0,a.batchAll)(i.map(t=>async()=>{let i=(0,r.join)(e,t.name),n=await u(i,t.name);return{name:t.name,path:i,lastModified:n,lastModifiedFormatted:(0,s.formatDate)(n),sessionId:p(t.name),cli:"claude"}}),16)).filter(e=>"fulfilled"===e.status).map(e=>e.value);return n.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),n}catch(e){return(0,o.logError)("Error reading session files:",e),[]}}(0,n.runtimeCache)(m,30),(0,n.runtimeCache)(e=>g(e),30),e.s(["UUID_RE",0,l,"resolveProjectPath",0,d,"resolveSessionFilePath",0,function(e,t){if(!l.test(t))throw RangeError("Invalid session ID");let i=d(e);return(0,r.join)(i,`${t}.jsonl`)}])},83534,e=>{"use strict";e.s(["runtimeCache",0,function(e,t,r){let i=new Map,n=new Map,a=r?.maxSize;return async(...r)=>{let o=JSON.stringify(r),s=i.get(o);if(s&&Date.now()<s.expiry)return s.data;let l=n.get(o);if(l)return l;let u=e(...r).then(e=>{if(n.delete(o),a&&i.size>=a){let e=i.keys().next().value;i.delete(e)}return i.set(o,{data:e,expiry:Date.now()+1e3*t}),e},e=>{throw n.delete(o),e});return n.set(o,u),u}}])}];
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=%5Broot-of-the-server%5D__0cuho4x._.js.map
|