failproofai 1.0.0-beta.2 → 1.0.0-beta.21
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/app-path-routes-manifest.json +2 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- 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 +3 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- 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 +3 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -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 +4 -3
- 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 +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 +3 -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 +3 -2
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +5 -3
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +3 -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 +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +5 -3
- 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 +3 -3
- 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 +4 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- 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 +6 -4
- 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 +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +4 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
- package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
- package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
- package/.next/standalone/.next/server/app/settings/page.js +18 -0
- package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
- package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
- 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]__1rv-sc9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- 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/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +2 -2
- 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 +74 -20
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
- package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/SECURITY.md +8 -2
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
- package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
- package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
- package/.next/standalone/app/api/audit/run/route.ts +28 -0
- package/.next/standalone/app/api/audit/status/route.ts +13 -1
- package/.next/standalone/app/api/auth/status/route.ts +1 -1
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
- package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
- package/.next/standalone/app/components/toast.tsx +1 -1
- package/.next/standalone/app/policies/hooks-client.tsx +84 -8
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +487 -0
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/lib/auth/api-server-client.ts +2 -3
- package/.next/standalone/lib/auth/auth-store.ts +4 -5
- package/.next/standalone/lib/codex-sessions.ts +2 -1
- package/.next/standalone/lib/dashboard-host.ts +101 -0
- package/.next/standalone/lib/install-check.ts +22 -7
- package/.next/standalone/lib/pi-sessions.ts +99 -14
- package/.next/standalone/lib/telemetry-enabled.ts +54 -0
- package/.next/standalone/lib/telemetry-id.ts +52 -1
- package/.next/standalone/lib/telemetry.ts +8 -4
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +23 -12
- package/.next/standalone/proxy.ts +111 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai-worker.mjs +1 -1
- package/bin/failproofai.mjs +1041 -81
- package/bin/failproofaid-shim.mjs +4 -4
- package/dist/cli.mjs +18513 -10474
- package/dist/worker.mjs +1223 -299
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -5
- package/lib/codex-sessions.ts +2 -1
- package/lib/dashboard-host.ts +101 -0
- package/lib/install-check.ts +22 -7
- package/lib/pi-sessions.ts +99 -14
- package/lib/telemetry-enabled.ts +54 -0
- package/lib/telemetry-id.ts +52 -1
- package/lib/telemetry.ts +8 -4
- package/package.json +23 -12
- package/scripts/build-daemon-packages.mjs +247 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/dev-hook.mjs +46 -5
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +27 -1
- package/scripts/prune-standalone.mjs +26 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/scripts/repro-npm-install.sh +155 -0
- package/scripts/translate-docs/cache.ts +1 -1
- package/scripts/translate-docs/cli.ts +2 -2
- package/scripts/translate-docs/mintlify-nav.ts +1 -1
- package/scripts/translate-docs/readme-translator.ts +163 -4
- package/scripts/validate-mdx.ts +153 -13
- package/src/audit/audit-lock.ts +285 -0
- package/src/audit/audit-schedule.ts +111 -0
- package/src/audit/cache.ts +18 -5
- package/src/audit/cli-adapters/goose.ts +2 -1
- package/src/audit/cli-adapters/hermes.ts +28 -10
- package/src/audit/cli.ts +191 -35
- package/src/audit/dashboard-cache.ts +2 -3
- package/src/audit/report.ts +0 -1
- package/src/hooks/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +391 -3
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +439 -0
- package/src/hooks/cloud-enrollment.ts +284 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +206 -0
- package/src/hooks/collector-config.ts +218 -0
- package/src/hooks/configure-wizard.ts +1211 -233
- package/src/hooks/custom-hooks-loader.ts +243 -37
- package/src/hooks/daemon-client.ts +136 -33
- package/src/hooks/daemon-download.ts +264 -31
- package/src/hooks/daemon-service.ts +1124 -76
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/flush-cli.ts +180 -0
- package/src/hooks/fp-config.ts +835 -0
- package/src/hooks/fp-home.ts +714 -0
- package/src/hooks/fp-reset.ts +1363 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/harness-cli.ts +386 -0
- package/src/hooks/hook-activity-store.ts +171 -24
- package/src/hooks/hook-logger.ts +2 -2
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/integrations.ts +0 -1
- package/src/hooks/loader-utils.ts +172 -9
- package/src/hooks/manager.ts +41 -5
- package/src/hooks/migrations.ts +450 -0
- package/src/hooks/onboarding-attempt.ts +195 -0
- package/src/hooks/onboarding-lock.ts +118 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +267 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +209 -9
- package/src/hooks/uninstall-cli.ts +418 -0
- package/src/hooks/worker-server.ts +134 -15
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- package/.next/standalone/Cargo.lock +0 -428
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
- package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
- package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
- package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
- package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
- package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
- package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
- package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
- package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
- package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
- package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
- package/.next/standalone/integration-suite/Dockerfile +0 -47
- package/.next/standalone/integration-suite/README.md +0 -129
- package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
- package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
- package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
- package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
- package/.next/standalone/integration-suite/install-clis.sh +0 -136
- package/.next/standalone/integration-suite/probe-cli.sh +0 -248
- package/.next/standalone/integration-suite/report.js +0 -120
- package/.next/standalone/integration-suite/run.sh +0 -159
- package/.next/standalone/openclaw-plugin/index.js +0 -223
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
- package/.next/standalone/openclaw-plugin/package.json +0 -12
- package/.next/standalone/osv-scanner.toml +0 -17
- package/.next/standalone/pi-extension/index.ts +0 -524
- package/.next/standalone/pi-extension/package.json +0 -12
- package/.next/standalone/postcss.config.mjs +0 -8
- package/.next/standalone/readme-arch-hq.gif +0 -0
- package/.next/standalone/rust-toolchain.toml +0 -4
- package/src/auth/cli.ts +0 -371
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Low-level HTTP client for the FailproofAI api-server's /v0/auth/* endpoints.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `./auth-store.ts`.
|
|
4
|
+
* Used by the dashboard's Next.js API route proxies. Has no filesystem access
|
|
5
|
+
* — token persistence lives in `./auth-store.ts`.
|
|
7
6
|
*
|
|
8
7
|
* The base URL is resolved from FAILPROOF_API_URL (preferred) or the legacy
|
|
9
8
|
* FAILPROOFAI_API_URL, falling back to the hosted api-server. Local-dev
|
package/lib/auth/auth-store.ts
CHANGED
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
* Persistence layer for the FailproofAI auth.json file.
|
|
3
3
|
*
|
|
4
4
|
* Tokens live at ~/.failproofai/auth.json with mode 0600. The dashboard's
|
|
5
|
-
* Next.js API routes and
|
|
6
|
-
*
|
|
7
|
-
* (CLI) invocations.
|
|
5
|
+
* Next.js API routes read and write through here, so a session survives across
|
|
6
|
+
* dashboard runs.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
10
|
import { join } from "node:path";
|
|
13
11
|
|
|
14
12
|
import { writeJsonAtomically } from "../atomic-write";
|
|
13
|
+
import { failproofaiHome } from "../../src/hooks/fp-home";
|
|
15
14
|
import {
|
|
16
15
|
AuthApiError,
|
|
17
16
|
decodeJwt,
|
|
@@ -31,7 +30,7 @@ export interface StoredAuth {
|
|
|
31
30
|
export function getAuthDir(): string {
|
|
32
31
|
const override = process.env.FAILPROOFAI_AUTH_DIR;
|
|
33
32
|
if (override) return override;
|
|
34
|
-
return
|
|
33
|
+
return failproofaiHome();
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
export function getAuthFilePath(): string {
|
package/lib/codex-sessions.ts
CHANGED
|
@@ -33,10 +33,11 @@ import {
|
|
|
33
33
|
type LogSource,
|
|
34
34
|
} from "./log-entries";
|
|
35
35
|
import { formatDuration } from "./format-duration";
|
|
36
|
+
import { codexSessionPathsFile } from "../src/hooks/fp-home";
|
|
36
37
|
|
|
37
38
|
// ── Transcript discovery ──
|
|
38
39
|
|
|
39
|
-
const CACHE_PATH =
|
|
40
|
+
const CACHE_PATH = codexSessionPathsFile();
|
|
40
41
|
|
|
41
42
|
function readCache(): Record<string, string> {
|
|
42
43
|
try {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the local dashboard binds, and which `Host` headers it will answer to.
|
|
3
|
+
*
|
|
4
|
+
* Shared by `scripts/launch.ts` (which chooses the bind address) and `proxy.ts`
|
|
5
|
+
* (which enforces it per request), because the two drifting apart is exactly how
|
|
6
|
+
* a lockdown quietly stops working.
|
|
7
|
+
*
|
|
8
|
+
* ## Why this exists
|
|
9
|
+
*
|
|
10
|
+
* The dashboard is a WRITE surface for this machine's security configuration —
|
|
11
|
+
* it can toggle policies and uninstall failproofai's hooks from every agent CLI
|
|
12
|
+
* — and it has no authentication of any kind. It used to bind `0.0.0.0`
|
|
13
|
+
* unconditionally, so every peer on the network could do all of that.
|
|
14
|
+
*
|
|
15
|
+
* Binding loopback is necessary but NOT sufficient. A malicious page can still
|
|
16
|
+
* reach `http://localhost:8020` from the victim's own browser, and DNS
|
|
17
|
+
* rebinding (a domain that resolves to 127.0.0.1 after first load) defeats an
|
|
18
|
+
* `Origin === Host` comparison, because both then read as the attacker's
|
|
19
|
+
* domain. So the defence is three layers, and all three are load-bearing:
|
|
20
|
+
*
|
|
21
|
+
* 1. bind loopback by default (kills every off-machine caller);
|
|
22
|
+
* 2. answer only to a loopback `Host` (kills DNS rebinding, whose whole trick
|
|
23
|
+
* is arriving with an attacker-controlled Host);
|
|
24
|
+
* 3. reject cross-origin mutating requests by `Origin` (kills the ordinary
|
|
25
|
+
* drive-by, which arrives with a perfectly valid `Host: localhost:8020`
|
|
26
|
+
* and an `Origin` of the attacker's site).
|
|
27
|
+
*
|
|
28
|
+
* Layer 2 is what makes layer 3 trustworthy: without it, a rebound page is
|
|
29
|
+
* same-origin by the browser's own reckoning and layer 3 waves it through.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** The default bind address. Loopback — never the wildcard. */
|
|
33
|
+
export const DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Hostnames that mean "this machine". `[::1]` is the bracketed form as it
|
|
37
|
+
* appears in a `Host` header; `::1` is the bare form as it appears in a bind
|
|
38
|
+
* address.
|
|
39
|
+
*/
|
|
40
|
+
const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0:0:0:0:0:0:0:1"]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True for any address that only this machine can reach.
|
|
44
|
+
*
|
|
45
|
+
* The whole 127.0.0.0/8 block is loopback, not just 127.0.0.1 — some setups
|
|
46
|
+
* bind 127.0.0.2 to separate services, and treating that as external would
|
|
47
|
+
* refuse a request the user deliberately arranged.
|
|
48
|
+
*/
|
|
49
|
+
export function isLoopbackHostname(hostname: string): boolean {
|
|
50
|
+
const h = hostname.trim().toLowerCase();
|
|
51
|
+
if (!h) return false;
|
|
52
|
+
if (LOOPBACK_HOSTNAMES.has(h)) return true;
|
|
53
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The hostname part of a `Host` header, with the port removed.
|
|
58
|
+
*
|
|
59
|
+
* IPv6 literals are bracketed (`[::1]:8020`), so the port cannot be found by
|
|
60
|
+
* looking for the last colon without handling the brackets first — `[::1]`
|
|
61
|
+
* is all colons.
|
|
62
|
+
*/
|
|
63
|
+
export function hostnameFromHostHeader(hostHeader: string): string {
|
|
64
|
+
const raw = hostHeader.trim();
|
|
65
|
+
if (raw.startsWith("[")) {
|
|
66
|
+
const end = raw.indexOf("]");
|
|
67
|
+
return end === -1 ? raw : raw.slice(0, end + 1);
|
|
68
|
+
}
|
|
69
|
+
const colon = raw.indexOf(":");
|
|
70
|
+
return colon === -1 ? raw : raw.slice(0, colon);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The hostname part of an `Origin` header (`http://localhost:8020` -> `localhost`). */
|
|
74
|
+
export function hostnameFromOrigin(origin: string): string | null {
|
|
75
|
+
const raw = origin.trim();
|
|
76
|
+
// "null" is a real Origin value (sandboxed iframes, some redirects). It is
|
|
77
|
+
// never ours, so it must not parse into something permissive.
|
|
78
|
+
if (!raw || raw === "null") return null;
|
|
79
|
+
try {
|
|
80
|
+
const url = new URL(raw);
|
|
81
|
+
// `URL.hostname` KEEPS the brackets on an IPv6 literal ("[::1]"), which is
|
|
82
|
+
// already the form a Host header carries — so it passes through untouched.
|
|
83
|
+
// Bracketing it again would produce "[[::1]]" and never match anything.
|
|
84
|
+
return url.hostname || null;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the bind address from an explicit flag, then the environment, then
|
|
92
|
+
* the safe default.
|
|
93
|
+
*
|
|
94
|
+
* An explicit non-loopback value is honoured — someone running the dashboard in
|
|
95
|
+
* a container or on a remote dev box has a real need — but it is a deliberate
|
|
96
|
+
* act, and `launch.ts` says so out loud when it happens.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveDashboardHost(flagHost?: string, envHost?: string): string {
|
|
99
|
+
const chosen = (flagHost ?? envHost ?? "").trim();
|
|
100
|
+
return chosen || DEFAULT_DASHBOARD_HOST;
|
|
101
|
+
}
|
package/lib/install-check.ts
CHANGED
|
@@ -19,10 +19,12 @@
|
|
|
19
19
|
* real command.
|
|
20
20
|
*/
|
|
21
21
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
22
|
-
import { resolve } from "node:path";
|
|
22
|
+
import { dirname, resolve } from "node:path";
|
|
23
23
|
import { platform, arch, release, homedir, hostname } from "node:os";
|
|
24
24
|
import { createHmac } from "node:crypto";
|
|
25
25
|
import { trackInstallEvent } from "../scripts/install-telemetry.mjs";
|
|
26
|
+
import { isTelemetryEnabled } from "./telemetry-enabled";
|
|
27
|
+
import { globalPolicyConfigFile, lastVersionFile } from "../src/hooks/fp-home";
|
|
26
28
|
|
|
27
29
|
const FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
28
30
|
const NAMESPACE = "failproofai-telemetry-v1";
|
|
@@ -47,7 +49,7 @@ function hashToId(raw: string): string {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function lastVersionPath(): string {
|
|
50
|
-
return
|
|
52
|
+
return lastVersionFile();
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -110,7 +112,13 @@ export interface HooksState {
|
|
|
110
112
|
* script so the event keeps its historical meaning.
|
|
111
113
|
*/
|
|
112
114
|
export function checkHooks(): HooksState {
|
|
113
|
-
|
|
115
|
+
// Layout 2's global policy config, via the one module allowed to join a path
|
|
116
|
+
// onto the home. This read `~/.failproofai/policies-config.json` — layout 1's
|
|
117
|
+
// location — so on every layout-2 machine it found nothing and reported the
|
|
118
|
+
// install as unconfigured with zero policies, which is what
|
|
119
|
+
// `package_installed` telemetry has been recording for every user since the
|
|
120
|
+
// reorganisation.
|
|
121
|
+
const hooksConfigPath = globalPolicyConfigFile();
|
|
114
122
|
if (!existsSync(hooksConfigPath)) {
|
|
115
123
|
return { configured: false, registered: false, policyCount: 0 };
|
|
116
124
|
}
|
|
@@ -177,6 +185,13 @@ export async function maybeReportInstall(version: string): Promise<void> {
|
|
|
177
185
|
// Steady state: same version already reported. Costs one stat() per command.
|
|
178
186
|
if (previousVersion === version) return;
|
|
179
187
|
|
|
188
|
+
// The install dispatcher is a dependency-free .mjs and cannot import the
|
|
189
|
+
// TOML config, so it is told the verdict rather than resolving it. Passed
|
|
190
|
+
// per call rather than checked here, because the version marker below must
|
|
191
|
+
// still be written when telemetry is off — otherwise this whole check would
|
|
192
|
+
// re-run on every single CLI invocation, forever.
|
|
193
|
+
const telemetryEnabled = isTelemetryEnabled();
|
|
194
|
+
|
|
180
195
|
const hooks = checkHooks();
|
|
181
196
|
|
|
182
197
|
const events: Promise<void>[] = [];
|
|
@@ -187,7 +202,7 @@ export async function maybeReportInstall(version: string): Promise<void> {
|
|
|
187
202
|
arch: arch(),
|
|
188
203
|
os_release: release(),
|
|
189
204
|
node_version: process.versions.node,
|
|
190
|
-
}, { version, timeoutMs: REPORT_TIMEOUT_MS }),
|
|
205
|
+
}, { version, timeoutMs: REPORT_TIMEOUT_MS, enabled: telemetryEnabled }),
|
|
191
206
|
);
|
|
192
207
|
} else {
|
|
193
208
|
const cmp = compareSemver(previousVersion, version);
|
|
@@ -198,7 +213,7 @@ export async function maybeReportInstall(version: string): Promise<void> {
|
|
|
198
213
|
direction: cmp < 0 ? "upgrade" : "downgrade",
|
|
199
214
|
platform: platform(),
|
|
200
215
|
arch: arch(),
|
|
201
|
-
}, { version, timeoutMs: REPORT_TIMEOUT_MS }),
|
|
216
|
+
}, { version, timeoutMs: REPORT_TIMEOUT_MS, enabled: telemetryEnabled }),
|
|
202
217
|
);
|
|
203
218
|
}
|
|
204
219
|
|
|
@@ -212,13 +227,13 @@ export async function maybeReportInstall(version: string): Promise<void> {
|
|
|
212
227
|
hooks_configured: hooks.configured,
|
|
213
228
|
hooks_registered: hooks.registered,
|
|
214
229
|
enabled_policy_count: hooks.policyCount,
|
|
215
|
-
}, { version, timeoutMs: REPORT_TIMEOUT_MS }),
|
|
230
|
+
}, { version, timeoutMs: REPORT_TIMEOUT_MS, enabled: telemetryEnabled }),
|
|
216
231
|
);
|
|
217
232
|
|
|
218
233
|
// Record before awaiting: a dropped event is cheaper than re-reporting the
|
|
219
234
|
// same version on every command because delivery kept failing.
|
|
220
235
|
try {
|
|
221
|
-
mkdirSync(
|
|
236
|
+
mkdirSync(dirname(versionFile), { recursive: true });
|
|
222
237
|
writeFileSync(versionFile, version, "utf8");
|
|
223
238
|
} catch {
|
|
224
239
|
// best-effort
|
package/lib/pi-sessions.ts
CHANGED
|
@@ -17,12 +17,33 @@
|
|
|
17
17
|
* {type: "message", id, parentId, timestamp,
|
|
18
18
|
* message: {role, content[], timestamp}}
|
|
19
19
|
*
|
|
20
|
-
* `message.content[]` items can be `{type: "text", text}
|
|
21
|
-
* `{type: "thinking", thinking, thinkingSignature}
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
20
|
+
* `message.content[]` items can be `{type: "text", text}`,
|
|
21
|
+
* `{type: "thinking", thinking, thinkingSignature}` or `{type: "toolCall",
|
|
22
|
+
* id, name, arguments}`.
|
|
23
|
+
*
|
|
24
|
+
* Tool calls WERE previously believed not to exist here — the header used to
|
|
25
|
+
* say no tool-using run had been captured, so `toolCall` blocks fell through
|
|
26
|
+
* to the generic "system" branch and every tool event Pi emits was silently
|
|
27
|
+
* dropped from the audit path. A live capture (pi 0.73.1 and 0.83.0, driven
|
|
28
|
+
* against a real provider) shows Pi emits them in full:
|
|
29
|
+
*
|
|
30
|
+
* assistant turn content[] contains {type:"toolCall", id, name, arguments}
|
|
31
|
+
* and the message carries stopReason:"toolUse"
|
|
32
|
+
* tool result its own record with a THIRD role —
|
|
33
|
+
* message.role === "toolResult", carrying toolCallId,
|
|
34
|
+
* toolName, content[], isError, timestamp (epoch ms)
|
|
35
|
+
*
|
|
36
|
+
* `toolResult.toolCallId` pairs exactly with `toolCall.id`, one result record
|
|
37
|
+
* per call, emitted in call order — so results attach by id, never by
|
|
38
|
+
* position. Pi supplies no duration, so it is derived from the gap between
|
|
39
|
+
* the call and its result, the same way the OpenClaw parser does it.
|
|
40
|
+
*
|
|
41
|
+
* The format is identical across the 0.73.1 (`@mariozechner/pi-coding-agent`)
|
|
42
|
+
* and 0.83.0 (`@earendil-works/pi-coding-agent`) packages — same `version: 3`
|
|
43
|
+
* header, same record types — so one parser covers both. One difference worth
|
|
44
|
+
* guarding: 0.83.0 emits leading prose alongside the calls
|
|
45
|
+
* (`["text","toolCall","toolCall"]`) where 0.73.1 emitted only the calls, so
|
|
46
|
+
* assistant content must not be assumed homogeneous.
|
|
26
47
|
*/
|
|
27
48
|
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
28
49
|
import { readFile } from "node:fs/promises";
|
|
@@ -38,8 +59,10 @@ import {
|
|
|
38
59
|
type GenericEntry,
|
|
39
60
|
type QueueOperationEntry,
|
|
40
61
|
type ContentBlock,
|
|
62
|
+
type ToolUseBlock,
|
|
41
63
|
type LogSource,
|
|
42
64
|
} from "./log-entries";
|
|
65
|
+
import { formatDuration } from "./format-duration";
|
|
43
66
|
|
|
44
67
|
// ── Paths ──
|
|
45
68
|
|
|
@@ -107,6 +130,10 @@ interface PiSessionRecord {
|
|
|
107
130
|
role?: string;
|
|
108
131
|
content?: Array<Record<string, unknown>>;
|
|
109
132
|
timestamp?: number;
|
|
133
|
+
/** Present on `role: "toolResult"` records — pairs with a `toolCall.id`. */
|
|
134
|
+
toolCallId?: string;
|
|
135
|
+
toolName?: string;
|
|
136
|
+
isError?: boolean;
|
|
110
137
|
};
|
|
111
138
|
}
|
|
112
139
|
|
|
@@ -129,9 +156,19 @@ function extractMessageText(content: Array<Record<string, unknown>> | undefined)
|
|
|
129
156
|
return parts.join("\n\n");
|
|
130
157
|
}
|
|
131
158
|
|
|
132
|
-
/** Build a list of ContentBlocks for the assistant entry, preserving text
|
|
133
|
-
* thinking blocks. Skips blocks with non-string payloads
|
|
134
|
-
|
|
159
|
+
/** Build a list of ContentBlocks for the assistant entry, preserving text,
|
|
160
|
+
* thinking and tool-call blocks. Skips blocks with non-string payloads
|
|
161
|
+
* (typeof guards).
|
|
162
|
+
*
|
|
163
|
+
* Every `tool_use` block built here is also handed to `onToolUse` so the
|
|
164
|
+
* caller can index it by id and attach the matching `toolResult` record when
|
|
165
|
+
* it arrives on a later line. The block is passed by reference and mutated
|
|
166
|
+
* in place, exactly as the OpenClaw parser does — the entry has already been
|
|
167
|
+
* pushed by then, so there is nothing else to attach it to. */
|
|
168
|
+
function buildAssistantContent(
|
|
169
|
+
content: Array<Record<string, unknown>> | undefined,
|
|
170
|
+
onToolUse?: (block: ToolUseBlock) => void,
|
|
171
|
+
): ContentBlock[] {
|
|
135
172
|
if (!Array.isArray(content)) return [];
|
|
136
173
|
const blocks: ContentBlock[] = [];
|
|
137
174
|
for (const block of content) {
|
|
@@ -143,6 +180,22 @@ function buildAssistantContent(content: Array<Record<string, unknown>> | undefin
|
|
|
143
180
|
if (block?.type === "thinking" && typeof block.thinking === "string" && block.thinking.length > 0) {
|
|
144
181
|
blocks.push({ type: "text", text: `[thinking] ${block.thinking}` });
|
|
145
182
|
}
|
|
183
|
+
if (block?.type === "toolCall") {
|
|
184
|
+
// Fall back to a positional id only when Pi omits one. A synthetic id
|
|
185
|
+
// still renders, but it can never pair with a result — so it must not
|
|
186
|
+
// collide with a real one, hence the index suffix.
|
|
187
|
+
const id = typeof block.id === "string" && block.id.length > 0
|
|
188
|
+
? block.id
|
|
189
|
+
: `${typeof block.name === "string" ? block.name : "tool"}-${blocks.length}`;
|
|
190
|
+
const name = typeof block.name === "string" ? block.name : "tool";
|
|
191
|
+
const input =
|
|
192
|
+
block.arguments && typeof block.arguments === "object" && !Array.isArray(block.arguments)
|
|
193
|
+
? (block.arguments as Record<string, unknown>)
|
|
194
|
+
: {};
|
|
195
|
+
const toolUse: ToolUseBlock = { type: "tool_use", id, name, input };
|
|
196
|
+
blocks.push(toolUse);
|
|
197
|
+
onToolUse?.(toolUse);
|
|
198
|
+
}
|
|
146
199
|
}
|
|
147
200
|
return blocks;
|
|
148
201
|
}
|
|
@@ -161,6 +214,12 @@ export async function parsePiLog(
|
|
|
161
214
|
const rawLines: Record<string, unknown>[] = [];
|
|
162
215
|
let cwd: string | undefined;
|
|
163
216
|
let seenSessionStart = false;
|
|
217
|
+
// In-flight tool calls, so a later `toolResult` record can attach its output
|
|
218
|
+
// to the block it belongs to. Keyed by the provider's own call id, never by
|
|
219
|
+
// position — Pi emits results in call order today, but pairing by order
|
|
220
|
+
// would break silently the first time it does not.
|
|
221
|
+
const toolUseById = new Map<string, ToolUseBlock>();
|
|
222
|
+
const toolUseStartMs = new Map<string, number>();
|
|
164
223
|
|
|
165
224
|
for (let i = 0; i < lines.length; i++) {
|
|
166
225
|
if (i > 0 && i % 200 === 0) await new Promise<void>((r) => setImmediate(r));
|
|
@@ -215,7 +274,10 @@ export async function parsePiLog(
|
|
|
215
274
|
}
|
|
216
275
|
|
|
217
276
|
if (role === "assistant") {
|
|
218
|
-
const blocks = buildAssistantContent(content)
|
|
277
|
+
const blocks = buildAssistantContent(content, (block) => {
|
|
278
|
+
toolUseById.set(block.id, block);
|
|
279
|
+
toolUseStartMs.set(block.id, date.getTime());
|
|
280
|
+
});
|
|
219
281
|
if (blocks.length === 0) {
|
|
220
282
|
entries.push({
|
|
221
283
|
type: "system",
|
|
@@ -232,6 +294,33 @@ export async function parsePiLog(
|
|
|
232
294
|
continue;
|
|
233
295
|
}
|
|
234
296
|
|
|
297
|
+
// Pi's third role: a tool result, on its own record, pairing back to an
|
|
298
|
+
// assistant turn's toolCall by id. Attaching it to that block is what
|
|
299
|
+
// makes the tool's OUTPUT visible — without this the call renders with
|
|
300
|
+
// no result and the audit path sees no `toolResultText` at all.
|
|
301
|
+
if (role === "toolResult") {
|
|
302
|
+
const callId = raw.message.toolCallId;
|
|
303
|
+
const block = typeof callId === "string" ? toolUseById.get(callId) : undefined;
|
|
304
|
+
if (block) {
|
|
305
|
+
// Pi records no duration on the result, so derive it from the gap
|
|
306
|
+
// between the call and its result. `startMs` is always present for
|
|
307
|
+
// a block we indexed; the fallback keeps the arithmetic total.
|
|
308
|
+
const startMs = (typeof callId === "string" && toolUseStartMs.get(callId)) || date.getTime();
|
|
309
|
+
const durationMs = Math.max(0, date.getTime() - startMs);
|
|
310
|
+
block.result = {
|
|
311
|
+
timestamp,
|
|
312
|
+
timestampFormatted: formatTimestamp(date),
|
|
313
|
+
content: extractMessageText(content),
|
|
314
|
+
durationMs,
|
|
315
|
+
durationFormatted: formatDuration(durationMs),
|
|
316
|
+
};
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// Orphan result — the call was never seen (truncated file, or a
|
|
320
|
+
// resumed session whose earlier half is in another file). Fall
|
|
321
|
+
// through so the record is preserved rather than dropped.
|
|
322
|
+
}
|
|
323
|
+
|
|
235
324
|
// Unknown role — preserve raw so nothing is dropped.
|
|
236
325
|
entries.push({
|
|
237
326
|
type: "system",
|
|
@@ -319,7 +408,3 @@ export function readPiTranscriptSync(sessionId: string): string | null {
|
|
|
319
408
|
return null;
|
|
320
409
|
}
|
|
321
410
|
}
|
|
322
|
-
|
|
323
|
-
/** Suppress unused-import warning for formatTimestamp; reserved for tool-call
|
|
324
|
-
* rendering once Pi emits it (see header comment). */
|
|
325
|
-
void formatTimestamp;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single answer to "may we send telemetry?", shared by every dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* There are four of them — the CLI and hook path (`src/hooks/hook-telemetry.ts`),
|
|
5
|
+
* the Next.js server (`lib/telemetry.ts`), the browser (`lib/client-telemetry.ts`,
|
|
6
|
+
* which inherits this via `getTelemetryConfig`), and the install script
|
|
7
|
+
* (`scripts/install-telemetry.mjs`, gated by its TypeScript caller). A dispatcher
|
|
8
|
+
* that resolved this differently would be an opt-out that silently does not hold,
|
|
9
|
+
* which is worse than having no opt-out at all.
|
|
10
|
+
*
|
|
11
|
+
* ## Two sources, and why the file is the important one
|
|
12
|
+
*
|
|
13
|
+
* `FAILPROOFAI_TELEMETRY_DISABLED=1` is read from `process.env`. That works for
|
|
14
|
+
* anything the user launches from a shell, and it is kept because people already
|
|
15
|
+
* rely on it. It cannot work for **failproofaid**: the daemon is a system-scope
|
|
16
|
+
* service unit whose environment carries essentially nothing, so an exported
|
|
17
|
+
* variable is structurally incapable of reaching it. `[telemetry] enabled` in
|
|
18
|
+
* config.toml is a file both the CLI and the daemon read, which is why it is the
|
|
19
|
+
* off-switch the documentation points at.
|
|
20
|
+
*
|
|
21
|
+
* They resolve to the **more restrictive** of the two: either says stop, and we
|
|
22
|
+
* stop. The environment can never re-enable something the file switched off —
|
|
23
|
+
* an env var that could override a written preference is not an opt-out.
|
|
24
|
+
*/
|
|
25
|
+
import { readConfig } from "../src/hooks/fp-config";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolved fresh on every call, deliberately.
|
|
29
|
+
*
|
|
30
|
+
* Memoising this was the obvious optimisation and it is the wrong one. An
|
|
31
|
+
* opt-out that a long-lived process (the dashboard server, the warm worker)
|
|
32
|
+
* keeps ignoring until it restarts is an opt-out that does not hold, and
|
|
33
|
+
* "telemetry stopped only after a reboot" is precisely the bug that destroys
|
|
34
|
+
* trust in a switch like this.
|
|
35
|
+
*
|
|
36
|
+
* The cost is a small `readFileSync` + TOML parse per event. That is affordable
|
|
37
|
+
* even on the hook path's 150ms fail-closed budget: the same `readConfig()` is
|
|
38
|
+
* already called there by `isDaemonConfigured()`, so this adds a repeat of work
|
|
39
|
+
* the path was already paying for, on a file of a few hundred bytes.
|
|
40
|
+
*/
|
|
41
|
+
export function isTelemetryEnabled(): boolean {
|
|
42
|
+
// Checked first, and cheaply: an env lookup costs nothing, and letting it
|
|
43
|
+
// short-circuit means an operator who exported the variable never pays for a
|
|
44
|
+
// file read at all.
|
|
45
|
+
if (process.env.FAILPROOFAI_TELEMETRY_DISABLED === "1") return false;
|
|
46
|
+
try {
|
|
47
|
+
return readConfig().telemetry.enabled;
|
|
48
|
+
} catch {
|
|
49
|
+
// readConfig already resolves an unreadable or malformed file to the
|
|
50
|
+
// defaults, so reaching here means something more unusual. Fall back to the
|
|
51
|
+
// shipped default rather than inventing a third answer.
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/lib/telemetry-id.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { execSync } from "node:child_process";
|
|
6
|
+
import { telemetryIdFile } from "../src/hooks/fp-home";
|
|
6
7
|
|
|
7
8
|
const NAMESPACE = "failproofai-telemetry-v1";
|
|
8
9
|
const ID_DIR = path.join(os.homedir(), ".failproofai");
|
|
@@ -66,6 +67,52 @@ function getFileBasedId(): string {
|
|
|
66
67
|
return id;
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Publish the resolved id where **failproofaid** can read it.
|
|
72
|
+
*
|
|
73
|
+
* The daemon cannot re-derive this. Tier 2 below hashes `os.arch()` and
|
|
74
|
+
* `os.cpus()[0].model`, which are Node's own formatting — Node says `x64` where
|
|
75
|
+
* Rust says `x86_64` — and a near-miss there does not fail, it silently files
|
|
76
|
+
* one machine under two different PostHog persons with nothing in the data to
|
|
77
|
+
* say so. So the CLI writes what it resolved and the daemon reads it
|
|
78
|
+
* (`telemetry_id_path()` in `crates/failproofaid/src/paths.rs`).
|
|
79
|
+
*
|
|
80
|
+
* Deliberately NOT gated on the telemetry opt-out. This sends nothing — it
|
|
81
|
+
* writes one anonymous HMAC to a local file — and gating it would mean a machine
|
|
82
|
+
* that opted out and later opted back in reported under a different identity
|
|
83
|
+
* until the next CLI run. The gate belongs on the sending, which is where both
|
|
84
|
+
* this process and the daemon apply it.
|
|
85
|
+
*
|
|
86
|
+
* Every failure is swallowed. This runs on the hook path, and a telemetry id
|
|
87
|
+
* that could not be published is worth exactly nothing next to a tool call that
|
|
88
|
+
* did not complete. The read-and-compare is what keeps the common case to one
|
|
89
|
+
* small `readFileSync` rather than a write per process.
|
|
90
|
+
*/
|
|
91
|
+
function publishInstanceId(id: string): void {
|
|
92
|
+
let target: string;
|
|
93
|
+
try {
|
|
94
|
+
target = telemetryIdFile();
|
|
95
|
+
if (fs.readFileSync(target, "utf-8").trim() === id) return;
|
|
96
|
+
} catch {
|
|
97
|
+
// Absent or unreadable — fall through and try to write it.
|
|
98
|
+
try {
|
|
99
|
+
target = telemetryIdFile();
|
|
100
|
+
} catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
106
|
+
// tmp → rename, and the tmp name carries the pid: two CLI processes racing
|
|
107
|
+
// here is ordinary (every hook call resolves an id), and a shared temp name
|
|
108
|
+
// would let one truncate the file the other is renaming into place, leaving
|
|
109
|
+
// a torn id that the daemon would then adopt as a permanent, wrong person.
|
|
110
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
111
|
+
fs.writeFileSync(tmp, id, { encoding: "utf-8", mode: 0o600 });
|
|
112
|
+
fs.renameSync(tmp, target);
|
|
113
|
+
} catch {}
|
|
114
|
+
}
|
|
115
|
+
|
|
69
116
|
/**
|
|
70
117
|
* Returns a stable, anonymous machine ID for telemetry.
|
|
71
118
|
*
|
|
@@ -75,7 +122,8 @@ function getFileBasedId(): string {
|
|
|
75
122
|
* 3. File-based random UUID at `~/.failproofai/instance-id` (final fallback)
|
|
76
123
|
*
|
|
77
124
|
* All raw values are HMAC-hashed with an app-specific namespace so no PII
|
|
78
|
-
* is transmitted. The result is cached in-process to avoid repeated I/O
|
|
125
|
+
* is transmitted. The result is cached in-process to avoid repeated I/O, and
|
|
126
|
+
* published once per process for the daemon (see `publishInstanceId`).
|
|
79
127
|
*/
|
|
80
128
|
export function getInstanceId(): string {
|
|
81
129
|
if (cachedId) return cachedId;
|
|
@@ -83,15 +131,18 @@ export function getInstanceId(): string {
|
|
|
83
131
|
const machineId = getPlatformMachineId();
|
|
84
132
|
if (machineId) {
|
|
85
133
|
cachedId = hashToId(machineId);
|
|
134
|
+
publishInstanceId(cachedId);
|
|
86
135
|
return cachedId;
|
|
87
136
|
}
|
|
88
137
|
|
|
89
138
|
const sysProps = getSystemPropertiesId();
|
|
90
139
|
if (sysProps) {
|
|
91
140
|
cachedId = hashToId(sysProps);
|
|
141
|
+
publishInstanceId(cachedId);
|
|
92
142
|
return cachedId;
|
|
93
143
|
}
|
|
94
144
|
|
|
95
145
|
cachedId = getFileBasedId();
|
|
146
|
+
publishInstanceId(cachedId);
|
|
96
147
|
return cachedId;
|
|
97
148
|
}
|
package/lib/telemetry.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { getInstanceId } from "./telemetry-id";
|
|
12
|
+
import { isTelemetryEnabled } from "./telemetry-enabled";
|
|
12
13
|
import { version } from "../package.json";
|
|
13
14
|
import { POSTHOG_API_KEY, POSTHOG_PRODUCT } from "../src/posthog-key";
|
|
14
15
|
|
|
@@ -74,10 +75,13 @@ async function resilientFetch(
|
|
|
74
75
|
return new Response("{}", { status: 200 });
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Returns true unless the user has explicitly opted out, via either
|
|
80
|
+
* `FAILPROOFAI_TELEMETRY_DISABLED=1` or `[telemetry] enabled = false` in
|
|
81
|
+
* config.toml. Re-exported from the shared resolver so all four dispatchers
|
|
82
|
+
* cannot drift — see lib/telemetry-enabled.ts.
|
|
83
|
+
*/
|
|
84
|
+
export { isTelemetryEnabled };
|
|
81
85
|
|
|
82
86
|
/**
|
|
83
87
|
* Lazily import posthog-node and create a client.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.21",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -76,19 +76,19 @@
|
|
|
76
76
|
"@anthropic-ai/sdk": "^0.115.0",
|
|
77
77
|
"@mdx-js/mdx": "^3.1.1",
|
|
78
78
|
"@tailwindcss/postcss": "^4.3.1",
|
|
79
|
-
"@tanstack/react-virtual": "^3.14.
|
|
79
|
+
"@tanstack/react-virtual": "^3.14.9",
|
|
80
80
|
"@testing-library/jest-dom": "^7.0.0",
|
|
81
81
|
"@testing-library/react": "^16.3.2",
|
|
82
82
|
"@testing-library/user-event": "^14.6.1",
|
|
83
|
-
"@types/node": "26.1.
|
|
84
|
-
"@types/react": "19.2.
|
|
83
|
+
"@types/node": "26.1.2",
|
|
84
|
+
"@types/react": "19.2.18",
|
|
85
85
|
"@types/react-dom": "^19.2.3",
|
|
86
|
-
"@vitejs/plugin-react": "^6.0.
|
|
86
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
87
87
|
"clsx": "^2.1.1",
|
|
88
88
|
"eslint": "^10.5.0",
|
|
89
89
|
"eslint-config-next": "^16.2.9",
|
|
90
|
-
"jsdom": "^30.0.
|
|
91
|
-
"lucide-react": "^1.
|
|
90
|
+
"jsdom": "^30.0.1",
|
|
91
|
+
"lucide-react": "^1.28.0",
|
|
92
92
|
"next": "^16.2.11",
|
|
93
93
|
"react": "^19.2.4",
|
|
94
94
|
"react-dom": "^19.2.4",
|
|
@@ -99,17 +99,28 @@
|
|
|
99
99
|
},
|
|
100
100
|
"dependencies": {
|
|
101
101
|
"html-to-image": "^1.11.13",
|
|
102
|
-
"
|
|
103
|
-
"posthog-node": "^5.37.1",
|
|
102
|
+
"posthog-node": "^5.47.7",
|
|
104
103
|
"sql.js": "^1.14.1",
|
|
105
104
|
"yaml": "^2.9.0"
|
|
106
105
|
},
|
|
107
106
|
"overrides": {
|
|
108
|
-
"postcss": "8.5.
|
|
107
|
+
"postcss": "8.5.26",
|
|
109
108
|
"eslint-plugin-react-hooks": "7.0.1",
|
|
110
109
|
"vite": "8.0.16",
|
|
111
|
-
"undici": "7.
|
|
112
|
-
"brace-expansion": "5.0.
|
|
110
|
+
"undici": "7.29.0",
|
|
111
|
+
"brace-expansion": "5.0.9",
|
|
113
112
|
"sharp": "0.35.0"
|
|
113
|
+
},
|
|
114
|
+
"optionalDependencies": {
|
|
115
|
+
"@failproofai/failproofaid-linux-x64": "1.0.0-beta.21",
|
|
116
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.0-beta.21",
|
|
117
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.0-beta.21",
|
|
118
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.0-beta.21"
|
|
119
|
+
},
|
|
120
|
+
"failproofaidBinaries": {
|
|
121
|
+
"linux-x64": "78848fa69407c528ee9fea02bdd64c2f95aad6a84a4d3973780200c5644fe61c",
|
|
122
|
+
"linux-arm64": "eef4b4a435c44ba913610f213436bd34a873299ef774961a1af70cf76da934a2",
|
|
123
|
+
"darwin-x64": "abdfbdac60f06db20c69b11ff477123481622da3e03bd531e1896b8757ce0884",
|
|
124
|
+
"darwin-arm64": "49fc9dc590c38ece592aa46e734bb6c0743559e3dbb90996e8381056abb2f5da"
|
|
114
125
|
}
|
|
115
126
|
}
|