failproofai 1.0.7-beta.1 → 1.0.7
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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +7 -8
- 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 +5 -7
- 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 +6 -6
- 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 +11 -13
- 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 +7 -8
- 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 +7 -7
- 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 +6 -7
- 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 +8 -41
- package/.next/standalone/.next/server/app/settings/page.js +9 -12
- 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]__1_bftcl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- 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/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0yxwl6j._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_214wgrp._.js → _1-i_gzc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_0bn2oo8._.js → _166t73i._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
- 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 +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_18_d8l1._.js → node_modules_next_dist_0drixxt._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.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/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 +23 -56
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
- package/.next/standalone/.next/static/chunks/0__8a7m868fvf.js +1 -0
- package/.next/standalone/.next/static/chunks/{12tvm75t5ffui.js → 0o6qlkgubtoex.js} +1 -1
- package/.next/standalone/.next/static/chunks/13i7-9is-vhys.js +1 -0
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
- package/.next/standalone/.next/static/chunks/{0vmd180qfntfb.js → 2klitrtzpaoe0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{258668t68du6b.js → 2mdh397ghgnvv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
- package/.next/standalone/.next/static/chunks/{0qrbdkv9qmvli.js → 3rh5o7e16irrm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{29fql3nbnfc9q.js → 43ufqrz8qo3h-.js} +1 -1
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/pack-actions.ts +0 -12
- package/.next/standalone/app/policies/hooks-client.tsx +0 -9
- package/.next/standalone/app/settings/page.tsx +1 -20
- package/.next/standalone/app/settings/settings-client.tsx +1 -27
- package/.next/standalone/app/settings/settings.css +0 -79
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/skill/SKILL.md +60 -14
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +2 -1
- package/.next/standalone/sdk/python/skill/references/evaluator.md +255 -0
- package/.next/standalone/sdk/python/skill/references/events.md +17 -8
- package/.next/standalone/sdk/python/skill/references/frameworks.md +3 -0
- package/.next/standalone/sdk/python/skill/references/install.md +3 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +6 -2
- package/.next/standalone/sdk/python/skill/references/typescript.md +568 -0
- package/.next/standalone/sdk/typescript/CHANGELOG.md +133 -0
- package/.next/standalone/sdk/typescript/LICENSE +42 -0
- package/.next/standalone/sdk/typescript/README.md +552 -0
- package/.next/standalone/sdk/typescript/eslint.config.mjs +59 -0
- package/.next/standalone/sdk/typescript/examples/research-agent.ts +197 -0
- package/.next/standalone/sdk/typescript/integration/ai.test.ts +920 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/agent.ts +337 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +261 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +16 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/surfaces.ts +605 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/agent.ts +342 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +156 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +16 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/surfaces.ts +628 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/agent.ts +346 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +156 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/surfaces.ts +651 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/agent.ts +350 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +153 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/surfaces.ts +651 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/agent.ts +623 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +344 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent-v1.ts +99 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent.ts +623 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +336 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/agent.ts +96 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +441 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.cjs +53 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.mjs +55 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/package.json +18 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/agent.ts +659 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +635 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/agent.ts +659 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +553 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/agent.ts +877 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/mcp-server.mjs +66 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +6797 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +24 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/agent.ts +872 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/mcp-server.mjs +66 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +2540 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/actions.ts +18 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/ai/route.ts +42 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/edge/route.ts +29 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/langgraph/route.ts +21 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/llamaindex/route.ts +11 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/mastra/route.ts +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/status/route.ts +7 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/layout.tsx +9 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/page.tsx +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/instrumentation.ts +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/ai.ts +61 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/langgraph.ts +68 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/llamaindex.ts +98 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/mastra.ts +90 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/next.config.ts +52 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +4343 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +28 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/agent.ts +51 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/deno-npm.ts +76 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +484 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/agent.ts +79 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/package-lock.json +740 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/package.json +11 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/agent.ts +197 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package-lock.json +70 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/global-setup.ts +29 -0
- package/.next/standalone/sdk/typescript/integration/harness.ts +451 -0
- package/.next/standalone/sdk/typescript/integration/langchain.test.ts +682 -0
- package/.next/standalone/sdk/typescript/integration/llamaindex.test.ts +709 -0
- package/.next/standalone/sdk/typescript/integration/mastra-coverage.test.ts +386 -0
- package/.next/standalone/sdk/typescript/integration/mastra.test.ts +311 -0
- package/.next/standalone/sdk/typescript/integration/nextjs.test.ts +341 -0
- package/.next/standalone/sdk/typescript/integration/runtime-parity.ts +180 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.bun.test.ts +15 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.core.test.ts +113 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.deno.test.ts +19 -0
- package/.next/standalone/sdk/typescript/integration/types.test.ts +141 -0
- package/.next/standalone/sdk/typescript/integration/vanilla.test.ts +255 -0
- package/.next/standalone/sdk/typescript/package-lock.json +2640 -0
- package/.next/standalone/sdk/typescript/package.json +401 -0
- package/.next/standalone/sdk/typescript/scripts/finalize-build.mjs +123 -0
- package/.next/standalone/sdk/typescript/scripts/release.mjs +177 -0
- package/.next/standalone/sdk/typescript/src/clock.ts +58 -0
- package/.next/standalone/sdk/typescript/src/context.ts +214 -0
- package/.next/standalone/sdk/typescript/src/edge/adapter.ts +18 -0
- package/.next/standalone/sdk/typescript/src/edge/ai.ts +116 -0
- package/.next/standalone/sdk/typescript/src/edge/index.ts +238 -0
- package/.next/standalone/sdk/typescript/src/edge/langchain.ts +18 -0
- package/.next/standalone/sdk/typescript/src/edge/llamaindex.ts +12 -0
- package/.next/standalone/sdk/typescript/src/edge/mastra.ts +17 -0
- package/.next/standalone/sdk/typescript/src/edge/notice.ts +33 -0
- package/.next/standalone/sdk/typescript/src/environment.ts +75 -0
- package/.next/standalone/sdk/typescript/src/evaluator/authoring.ts +480 -0
- package/.next/standalone/sdk/typescript/src/evaluator/cli.ts +96 -0
- package/.next/standalone/sdk/typescript/src/evaluator/client.ts +421 -0
- package/.next/standalone/sdk/typescript/src/evaluator/expression.ts +1292 -0
- package/.next/standalone/sdk/typescript/src/evaluator/index.ts +144 -0
- package/.next/standalone/sdk/typescript/src/evaluator/protocol.ts +747 -0
- package/.next/standalone/sdk/typescript/src/evaluator/runtime.ts +930 -0
- package/.next/standalone/sdk/typescript/src/evaluator/sandbox-worker.ts +171 -0
- package/.next/standalone/sdk/typescript/src/evaluator/source-limits.ts +27 -0
- package/.next/standalone/sdk/typescript/src/evaluator/source.ts +509 -0
- package/.next/standalone/sdk/typescript/src/events.ts +879 -0
- package/.next/standalone/sdk/typescript/src/exit.ts +117 -0
- package/.next/standalone/sdk/typescript/src/index.ts +186 -0
- package/.next/standalone/sdk/typescript/src/integrations/ai.ts +1566 -0
- package/.next/standalone/sdk/typescript/src/integrations/compat.ts +322 -0
- package/.next/standalone/sdk/typescript/src/integrations/core.ts +1321 -0
- package/.next/standalone/sdk/typescript/src/integrations/index.ts +355 -0
- package/.next/standalone/sdk/typescript/src/integrations/langchain.ts +2340 -0
- package/.next/standalone/sdk/typescript/src/integrations/llamaindex.ts +2111 -0
- package/.next/standalone/sdk/typescript/src/integrations/mastra.ts +1802 -0
- package/.next/standalone/sdk/typescript/src/logger.ts +98 -0
- package/.next/standalone/sdk/typescript/src/next.ts +115 -0
- package/.next/standalone/sdk/typescript/src/node-require.ts +446 -0
- package/.next/standalone/sdk/typescript/src/redact.ts +305 -0
- package/.next/standalone/sdk/typescript/src/resolver.ts +120 -0
- package/.next/standalone/sdk/typescript/src/runtime.ts +29 -0
- package/.next/standalone/sdk/typescript/src/schema.ts +410 -0
- package/.next/standalone/sdk/typescript/src/scopes.ts +701 -0
- package/.next/standalone/sdk/typescript/src/shared.ts +33 -0
- package/.next/standalone/sdk/typescript/src/version.ts +5 -0
- package/.next/standalone/sdk/typescript/src/writer.ts +934 -0
- package/.next/standalone/sdk/typescript/test/adapters.test.ts +397 -0
- package/.next/standalone/sdk/typescript/test/ai.test.ts +1076 -0
- package/.next/standalone/sdk/typescript/test/copies.test.ts +204 -0
- package/.next/standalone/sdk/typescript/test/edge.test.ts +183 -0
- package/.next/standalone/sdk/typescript/test/evaluator-client.test.ts +234 -0
- package/.next/standalone/sdk/typescript/test/evaluator-protocol.test.ts +225 -0
- package/.next/standalone/sdk/typescript/test/events.test.ts +193 -0
- package/.next/standalone/sdk/typescript/test/expression.test.ts +181 -0
- package/.next/standalone/sdk/typescript/test/global-setup.ts +26 -0
- package/.next/standalone/sdk/typescript/test/helpers.ts +130 -0
- package/.next/standalone/sdk/typescript/test/integrations.test.ts +369 -0
- package/.next/standalone/sdk/typescript/test/langchain-copies.test.ts +204 -0
- package/.next/standalone/sdk/typescript/test/langchain.test.ts +999 -0
- package/.next/standalone/sdk/typescript/test/llamaindex.test.ts +1760 -0
- package/.next/standalone/sdk/typescript/test/mastra-coverage.test.ts +501 -0
- package/.next/standalone/sdk/typescript/test/mastra-lifecycle.test.ts +479 -0
- package/.next/standalone/sdk/typescript/test/mastra.test.ts +285 -0
- package/.next/standalone/sdk/typescript/test/next.test.ts +109 -0
- package/.next/standalone/sdk/typescript/test/packaging.test.ts +312 -0
- package/.next/standalone/sdk/typescript/test/redaction.test.ts +171 -0
- package/.next/standalone/sdk/typescript/test/runtimes.test.ts +101 -0
- package/.next/standalone/sdk/typescript/test/sandbox.test.ts +189 -0
- package/.next/standalone/sdk/typescript/test/scopes.test.ts +271 -0
- package/.next/standalone/sdk/typescript/test/setup.ts +19 -0
- package/.next/standalone/sdk/typescript/test/skill-snippets.test.ts +73 -0
- package/.next/standalone/sdk/typescript/test/spool-contract.test.ts +124 -0
- package/.next/standalone/sdk/typescript/test/tracker-bounds.test.ts +191 -0
- package/.next/standalone/sdk/typescript/test/wire-format.test.ts +214 -0
- package/.next/standalone/sdk/typescript/test/writer.test.ts +407 -0
- package/.next/standalone/sdk/typescript/tsconfig.build.json +15 -0
- package/.next/standalone/sdk/typescript/tsconfig.cjs.json +19 -0
- package/.next/standalone/sdk/typescript/tsconfig.json +28 -0
- package/.next/standalone/sdk/typescript/vitest.config.ts +33 -0
- package/.next/standalone/sdk/typescript/vitest.integration.config.ts +23 -0
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +2 -115
- package/dist/cli.mjs +6538 -13686
- package/dist/index.js +1 -19
- package/dist/worker.mjs +2085 -8012
- package/package.json +9 -9
- package/pi-extension/index.ts +0 -11
- package/scripts/build-policy-pack.mjs +2 -39
- package/src/hooks/builtin-policies.ts +3 -21
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +0 -22
- package/src/hooks/custom-hooks-loader.ts +7 -45
- package/src/hooks/custom-hooks-registry.ts +1 -45
- package/src/hooks/first-run-gate.ts +0 -5
- package/src/hooks/fp-home.ts +0 -23
- package/src/hooks/handler.ts +6 -265
- package/src/hooks/hook-activity-store.ts +1 -105
- package/src/hooks/hook-telemetry.ts +0 -41
- package/src/hooks/loader-utils.ts +0 -6
- package/src/hooks/pack-cli.ts +17 -278
- package/src/hooks/pack-manifest.ts +7 -479
- package/src/hooks/pack-store.ts +10 -156
- package/src/hooks/policy-catalog.ts +0 -65
- package/src/hooks/policy-evaluator.ts +796 -940
- package/src/hooks/policy-registry.ts +0 -25
- package/src/hooks/policy-types.ts +0 -126
- package/src/hooks/worker-server.ts +26 -119
- package/src/index.ts +0 -6
- 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]__01bmjsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0w6mzq5._.js +0 -151
- 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/static/chunks/0cd-_8-c-m1ea.js +0 -6
- package/.next/standalone/.next/static/chunks/0uldbut9y2-e8.js +0 -1
- package/.next/standalone/.next/static/chunks/1u5zsejmgrir_.js +0 -1
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +0 -2
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
- package/.next/standalone/.next/static/chunks/2vkvu9-opa_1z.js +0 -1
- package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +0 -1
- package/.next/standalone/app/actions/get-jev-config.ts +0 -409
- package/.next/standalone/app/actions/update-jev-config.ts +0 -420
- package/.next/standalone/app/components/jev-notices.tsx +0 -96
- package/.next/standalone/app/settings/jev-panel.tsx +0 -469
- package/src/hooks/effective-reviewers.ts +0 -79
- package/src/hooks/jev-activity.ts +0 -385
- package/src/hooks/jev-cli.ts +0 -1193
- package/src/hooks/policy-authority.ts +0 -333
- package/src/hooks/policy-reviewability.ts +0 -229
- package/src/hooks/semantic/combine.ts +0 -541
- package/src/hooks/semantic/compile.ts +0 -176
- package/src/hooks/semantic/decide.ts +0 -392
- package/src/hooks/semantic/envelope.ts +0 -1296
- package/src/hooks/semantic/evaluator.ts +0 -547
- package/src/hooks/semantic/facts.ts +0 -292
- package/src/hooks/semantic/intent.ts +0 -1190
- package/src/hooks/semantic/jev-client.ts +0 -643
- package/src/hooks/semantic/jev-config.ts +0 -594
- package/src/hooks/semantic/jev-review.ts +0 -374
- package/src/hooks/semantic/jev-stats.ts +0 -289
- package/src/hooks/semantic/jev-throttle.ts +0 -421
- package/src/hooks/semantic/pack-policies.ts +0 -251
- package/src/hooks/semantic/policies.ts +0 -596
- package/src/hooks/semantic/precondition-names.ts +0 -60
- package/src/hooks/semantic/preconditions.ts +0 -58
- package/src/hooks/semantic/redact.ts +0 -2910
- package/src/hooks/semantic/types.ts +0 -145
- package/src/hooks/semver-precedence.ts +0 -128
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The async worker state machine for Evaluator v2.
|
|
3
|
+
*
|
|
4
|
+
* Claim -> fetch the transcript -> run each definition's condition -> submit a
|
|
5
|
+
* plan -> run the planned evaluations under a heartbeat -> submit each result.
|
|
6
|
+
*
|
|
7
|
+
* ## What is different from the Python worker, and why
|
|
8
|
+
*
|
|
9
|
+
* Python runs synchronous evaluations in a sized thread pool, because a
|
|
10
|
+
* blocking customer function would otherwise stall its event loop. JavaScript
|
|
11
|
+
* has no such option: a synchronous evaluation that does not return blocks the
|
|
12
|
+
* one thread there is, and no timeout can fire while it does. So the contract
|
|
13
|
+
* here is explicit — **an evaluation must yield.** An `async` function that
|
|
14
|
+
* awaits, or a sandboxed managed evaluation (which runs in its own Worker and
|
|
15
|
+
* IS killable), can always be bounded. A synchronous busy-loop cannot be, by
|
|
16
|
+
* anyone, and `DEFAULT_EVAL_TIMEOUT_SECONDS` will not save a worker from one.
|
|
17
|
+
*
|
|
18
|
+
* The bound that does hold everywhere: every evaluation is raced against a
|
|
19
|
+
* deadline, and a run that loses the race is reported `timed_out` and its
|
|
20
|
+
* result submitted, so the assignment does not sit unresolved. The evaluation
|
|
21
|
+
* itself may still be running — a promise cannot be cancelled — which is
|
|
22
|
+
* counted as `evaluations_orphaned` and logged with the eval key, so a hung one
|
|
23
|
+
* is findable.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { hostname } from "node:os";
|
|
27
|
+
import { randomUUID } from "node:crypto";
|
|
28
|
+
|
|
29
|
+
import { logException, logger } from "../logger.js";
|
|
30
|
+
import { VERSION } from "../version.js";
|
|
31
|
+
import { EvalResult, Evaluator, ConditionResult } from "./authoring.js";
|
|
32
|
+
import type { EvalDefinition } from "./authoring.js";
|
|
33
|
+
import { EvaluatorAPIError, EvaluatorClient } from "./client.js";
|
|
34
|
+
import {
|
|
35
|
+
DEFAULT_POLL_INTERVAL_SECONDS,
|
|
36
|
+
ExecutionMode,
|
|
37
|
+
MAX_CLAIM_CAPACITY,
|
|
38
|
+
MAX_ERROR_MESSAGE_BYTES,
|
|
39
|
+
MAX_WORKER_ID_BYTES,
|
|
40
|
+
TerminalRunStatus,
|
|
41
|
+
} from "./protocol.js";
|
|
42
|
+
import type {
|
|
43
|
+
Assignment,
|
|
44
|
+
AssignmentDefinition,
|
|
45
|
+
EvalSelection,
|
|
46
|
+
ResultItem,
|
|
47
|
+
SessionTranscript,
|
|
48
|
+
SkippedEval,
|
|
49
|
+
} from "./protocol.js";
|
|
50
|
+
import {
|
|
51
|
+
EvaluationTimeout,
|
|
52
|
+
UnsafeEvaluatorSource,
|
|
53
|
+
compileCondition,
|
|
54
|
+
compileEvaluator,
|
|
55
|
+
sourceChecksum,
|
|
56
|
+
} from "./source.js";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reserved out of the lease for the plan request (and network jitter) so the
|
|
60
|
+
* pre-plan condition phase always leaves time to submit the plan before the
|
|
61
|
+
* lease expires.
|
|
62
|
+
*/
|
|
63
|
+
const CONDITION_PHASE_SAFETY_MARGIN_SECONDS = 5;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fallback wall-clock bound for an evaluation whose definition declares no
|
|
67
|
+
* timeout. A LOCAL (customer-authored) eval has no sandbox backstop, so without
|
|
68
|
+
* this a hang — a wedged `await`, an unbounded judge HTTP call — would hold its
|
|
69
|
+
* worker slot and the assignment lease forever. Matches the storage contract's
|
|
70
|
+
* 5-minute per-eval default.
|
|
71
|
+
*/
|
|
72
|
+
export const DEFAULT_EVAL_TIMEOUT_SECONDS = 300;
|
|
73
|
+
|
|
74
|
+
function utcNow(): string {
|
|
75
|
+
return `${new Date().toISOString().slice(0, -1)}000Z`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function positiveIntEnv(name: string, fallback: number): number {
|
|
79
|
+
const raw = process.env[name];
|
|
80
|
+
if (raw === undefined) return fallback;
|
|
81
|
+
const value = Number(raw);
|
|
82
|
+
if (!Number.isInteger(value)) throw new Error(`${name} must be an integer`);
|
|
83
|
+
if (value <= 0) throw new Error(`${name} must be greater than zero`);
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function booleanEnv(name: string, fallback = false): boolean {
|
|
88
|
+
const raw = process.env[name];
|
|
89
|
+
if (raw === undefined) return fallback;
|
|
90
|
+
const normalized = raw.trim().toLowerCase();
|
|
91
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
92
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
93
|
+
throw new Error(`${name} must be a boolean`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface WorkerConfig {
|
|
97
|
+
serverUrl: string;
|
|
98
|
+
credential: string;
|
|
99
|
+
workerId: string;
|
|
100
|
+
maxConcurrency: number;
|
|
101
|
+
requestTimeoutSeconds: number;
|
|
102
|
+
drainTimeoutSeconds: number;
|
|
103
|
+
allowInsecureHttp: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function workerConfigFromEnv(): WorkerConfig {
|
|
107
|
+
const serverUrl = (process.env.FAILPROOFAI_EVALUATOR_URL ?? "").trim();
|
|
108
|
+
const credential = (process.env.FAILPROOFAI_EVALUATOR_TOKEN ?? "").trim();
|
|
109
|
+
if (!serverUrl) throw new Error("FAILPROOFAI_EVALUATOR_URL is required");
|
|
110
|
+
if (!credential) throw new Error("FAILPROOFAI_EVALUATOR_TOKEN is required");
|
|
111
|
+
|
|
112
|
+
let workerId = (process.env.FAILPROOFAI_EVALUATOR_WORKER_ID ?? "").trim();
|
|
113
|
+
if (!workerId) workerId = `${hostname()}-${process.pid}`;
|
|
114
|
+
if (Buffer.byteLength(workerId, "utf8") > MAX_WORKER_ID_BYTES) {
|
|
115
|
+
throw new Error(`FAILPROOFAI_EVALUATOR_WORKER_ID exceeds ${MAX_WORKER_ID_BYTES} bytes`);
|
|
116
|
+
}
|
|
117
|
+
for (const char of workerId) {
|
|
118
|
+
const code = char.codePointAt(0)!;
|
|
119
|
+
if (code < 32 || code === 127) {
|
|
120
|
+
throw new Error("FAILPROOFAI_EVALUATOR_WORKER_ID must not contain control characters");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const config: WorkerConfig = {
|
|
125
|
+
serverUrl,
|
|
126
|
+
credential,
|
|
127
|
+
workerId,
|
|
128
|
+
maxConcurrency: positiveIntEnv("FAILPROOFAI_EVALUATOR_CONCURRENCY", 1),
|
|
129
|
+
requestTimeoutSeconds: positiveIntEnv("FAILPROOFAI_EVALUATOR_REQUEST_TIMEOUT_SECONDS", 30),
|
|
130
|
+
drainTimeoutSeconds: positiveIntEnv("FAILPROOFAI_EVALUATOR_DRAIN_TIMEOUT_SECONDS", 60),
|
|
131
|
+
allowInsecureHttp: booleanEnv("FAILPROOFAI_EVALUATOR_ALLOW_INSECURE_HTTP"),
|
|
132
|
+
};
|
|
133
|
+
if (config.maxConcurrency > MAX_CLAIM_CAPACITY) {
|
|
134
|
+
throw new Error(`FAILPROOFAI_EVALUATOR_CONCURRENCY exceeds ${MAX_CLAIM_CAPACITY}`);
|
|
135
|
+
}
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A deadline race that reports which side won, so the caller can count it. */
|
|
140
|
+
async function withDeadline<T>(
|
|
141
|
+
work: Promise<T>,
|
|
142
|
+
timeoutMs: number,
|
|
143
|
+
): Promise<{ timedOut: false; value: T } | { timedOut: true }> {
|
|
144
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
145
|
+
const expiry = new Promise<{ timedOut: true }>((resolve) => {
|
|
146
|
+
timer = setTimeout(() => {
|
|
147
|
+
resolve({ timedOut: true });
|
|
148
|
+
}, timeoutMs);
|
|
149
|
+
timer.unref?.();
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
const outcome = await Promise.race([
|
|
153
|
+
work.then((value) => ({ timedOut: false as const, value })),
|
|
154
|
+
expiry,
|
|
155
|
+
]);
|
|
156
|
+
return outcome;
|
|
157
|
+
} finally {
|
|
158
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function sleep(ms: number, signal?: { aborted: boolean }): Promise<void> {
|
|
163
|
+
if (signal?.aborted) return;
|
|
164
|
+
await new Promise<void>((resolve) => {
|
|
165
|
+
const timer = setTimeout(resolve, ms);
|
|
166
|
+
timer.unref?.();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Compile server-authored source LAZILY, at invocation time.
|
|
172
|
+
*
|
|
173
|
+
* Compilation can reject unsafe or malformed source. Building the definition
|
|
174
|
+
* with this thunk instead of a pre-compiled function routes that failure
|
|
175
|
+
* through the same per-run `try/catch` that turns any evaluation error into a
|
|
176
|
+
* bounded FAILED result — so a poison definition dead-letters cleanly as one
|
|
177
|
+
* failed run instead of throwing out of assignment setup, crashing the task,
|
|
178
|
+
* and forcing the whole assignment to be reclaimed and retried until its
|
|
179
|
+
* attempt budget is exhausted.
|
|
180
|
+
*/
|
|
181
|
+
function deferredManagedEval(
|
|
182
|
+
source: string,
|
|
183
|
+
timeoutSeconds: number | null,
|
|
184
|
+
evalKey: string,
|
|
185
|
+
compiler: Evaluator["managedCompiler"],
|
|
186
|
+
): (session: SessionTranscript) => Promise<EvalResult> | EvalResult {
|
|
187
|
+
return (session: SessionTranscript) => {
|
|
188
|
+
const compile = compiler ?? ((s, o) => compileEvaluator(s, o));
|
|
189
|
+
return compile(source, { timeoutSeconds, evalKey })(session);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export class WorkerRuntime {
|
|
194
|
+
readonly evaluator: Evaluator;
|
|
195
|
+
readonly config: WorkerConfig;
|
|
196
|
+
readonly client: EvaluatorClient;
|
|
197
|
+
|
|
198
|
+
private stopping = false;
|
|
199
|
+
private stopWaiters: Array<() => void> = [];
|
|
200
|
+
private readonly activeAssignments = new Set<Promise<void>>();
|
|
201
|
+
private heartbeatInterval = 30;
|
|
202
|
+
private pollInterval: number = DEFAULT_POLL_INTERVAL_SECONDS;
|
|
203
|
+
private claimLimit: number;
|
|
204
|
+
private leaseDuration = 120;
|
|
205
|
+
private disabledDefinitions = new Set<string>();
|
|
206
|
+
private registered = false;
|
|
207
|
+
private lastServerContact: number | null = null;
|
|
208
|
+
private readonly metricCounts = new Map<string, number>();
|
|
209
|
+
private readonly slots: { inUse: number; waiters: Array<() => void> };
|
|
210
|
+
|
|
211
|
+
constructor(evaluator: Evaluator, config: WorkerConfig, options: { client?: EvaluatorClient } = {}) {
|
|
212
|
+
this.evaluator = evaluator;
|
|
213
|
+
this.config = config;
|
|
214
|
+
this.claimLimit = config.maxConcurrency;
|
|
215
|
+
this.client =
|
|
216
|
+
options.client ??
|
|
217
|
+
new EvaluatorClient({
|
|
218
|
+
baseUrl: config.serverUrl,
|
|
219
|
+
credential: config.credential,
|
|
220
|
+
timeoutSeconds: config.requestTimeoutSeconds,
|
|
221
|
+
allowInsecureHttp: config.allowInsecureHttp,
|
|
222
|
+
});
|
|
223
|
+
this.slots = { inUse: 0, waiters: [] };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async register(): Promise<void> {
|
|
227
|
+
let response;
|
|
228
|
+
try {
|
|
229
|
+
response = await this.call(() =>
|
|
230
|
+
this.client.register({
|
|
231
|
+
workerId: this.config.workerId,
|
|
232
|
+
sdkVersion: VERSION,
|
|
233
|
+
catalogRevision: this.evaluator.catalogRevision,
|
|
234
|
+
maxConcurrency: this.config.maxConcurrency,
|
|
235
|
+
definitions: this.evaluator.catalog(),
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.increment("registration_failure");
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
this.heartbeatInterval = response.heartbeatIntervalSeconds;
|
|
243
|
+
this.pollInterval = response.pollIntervalSeconds;
|
|
244
|
+
this.leaseDuration = response.leaseDurationSeconds;
|
|
245
|
+
this.claimLimit = Math.min(this.config.maxConcurrency, response.claimLimit);
|
|
246
|
+
if (
|
|
247
|
+
this.heartbeatInterval <= 0 ||
|
|
248
|
+
this.pollInterval <= 0 ||
|
|
249
|
+
this.leaseDuration <= this.heartbeatInterval ||
|
|
250
|
+
this.claimLimit <= 0
|
|
251
|
+
) {
|
|
252
|
+
this.increment("registration_failure");
|
|
253
|
+
throw new Error("server returned invalid evaluator timing or claim limits");
|
|
254
|
+
}
|
|
255
|
+
this.disabledDefinitions = new Set(response.disabledDefinitions);
|
|
256
|
+
this.registered = true;
|
|
257
|
+
this.increment("registration_success");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async runForever(): Promise<void> {
|
|
261
|
+
await this.register();
|
|
262
|
+
let retryDelay = 1;
|
|
263
|
+
try {
|
|
264
|
+
while (!this.stopping) {
|
|
265
|
+
const capacity = this.claimLimit - this.activeAssignments.size;
|
|
266
|
+
if (capacity <= 0) {
|
|
267
|
+
await this.waitForProgress();
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
let response;
|
|
271
|
+
try {
|
|
272
|
+
response = await this.call(() =>
|
|
273
|
+
this.client.claim({
|
|
274
|
+
workerId: this.config.workerId,
|
|
275
|
+
catalogRevision: this.evaluator.catalogRevision,
|
|
276
|
+
capacity,
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (!(error instanceof EvaluatorAPIError)) throw error;
|
|
281
|
+
this.increment("claim_failures");
|
|
282
|
+
logger.warn(`evaluator claim failed (code=${error.code}, retryable=${error.retryable})`);
|
|
283
|
+
if (!error.retryable) throw error;
|
|
284
|
+
// A transport failure with no status means the server is unreachable,
|
|
285
|
+
// not busy — back off by the whole lease rather than hammering it.
|
|
286
|
+
const delay = error.status === null ? this.leaseDuration : retryDelay;
|
|
287
|
+
await this.waitOrStop(delay * 1000);
|
|
288
|
+
retryDelay = Math.min(retryDelay * 2, 30);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
retryDelay = 1;
|
|
292
|
+
const assignments = validatedAssignments(response.assignments, capacity);
|
|
293
|
+
for (const assignment of assignments) this.spawn(assignment);
|
|
294
|
+
this.increment("assignments_claimed", assignments.length);
|
|
295
|
+
if (assignments.length === 0) {
|
|
296
|
+
// Normal short poll: the server returns immediately, so when nothing
|
|
297
|
+
// is queued we wait the advertised interval instead of hot-looping.
|
|
298
|
+
// When work IS returned we loop straight back to drain any backlog.
|
|
299
|
+
await this.waitOrStop(this.pollInterval * 1000);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} finally {
|
|
303
|
+
await this.drain();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Claim once and finish the returned assignments; useful for jobs and tests. */
|
|
308
|
+
async runOnce(): Promise<number> {
|
|
309
|
+
const response = await this.call(() =>
|
|
310
|
+
this.client.claim({
|
|
311
|
+
workerId: this.config.workerId,
|
|
312
|
+
catalogRevision: this.evaluator.catalogRevision,
|
|
313
|
+
capacity: this.claimLimit,
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
const assignments = validatedAssignments(response.assignments, this.claimLimit);
|
|
317
|
+
this.increment("assignments_claimed", assignments.length);
|
|
318
|
+
await Promise.all(assignments.map((assignment) => this.processAssignment(assignment)));
|
|
319
|
+
return assignments.length;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
stop(): void {
|
|
323
|
+
this.stopping = true;
|
|
324
|
+
const waiters = this.stopWaiters;
|
|
325
|
+
this.stopWaiters = [];
|
|
326
|
+
for (const waiter of waiters) waiter();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async drain(): Promise<void> {
|
|
330
|
+
if (this.activeAssignments.size === 0) return;
|
|
331
|
+
const outcome = await withDeadline(
|
|
332
|
+
Promise.allSettled([...this.activeAssignments]).then(() => undefined),
|
|
333
|
+
this.config.drainTimeoutSeconds * 1000,
|
|
334
|
+
);
|
|
335
|
+
if (outcome.timedOut) {
|
|
336
|
+
logger.warn(
|
|
337
|
+
`drain timed out with ${this.activeAssignments.size} assignment(s) still running; ` +
|
|
338
|
+
"their results may not be submitted",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
this.activeAssignments.clear();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private spawn(assignment: Assignment): void {
|
|
345
|
+
const task = this.processAssignment(assignment)
|
|
346
|
+
.catch((error: unknown) => {
|
|
347
|
+
logException("evaluator assignment failed", error);
|
|
348
|
+
})
|
|
349
|
+
.finally(() => {
|
|
350
|
+
this.activeAssignments.delete(task);
|
|
351
|
+
this.notifyProgress();
|
|
352
|
+
});
|
|
353
|
+
this.activeAssignments.add(task);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async processAssignment(assignment: Assignment): Promise<void> {
|
|
357
|
+
let session: SessionTranscript;
|
|
358
|
+
try {
|
|
359
|
+
session = await this.call(() => this.client.transcript(assignment, this.config.workerId));
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (error instanceof EvaluatorAPIError && error.code === "transcript_too_large") {
|
|
362
|
+
// No runs are planned yet, so there is nothing to submit a per-run
|
|
363
|
+
// result for, and the error is non-retryable — re-throwing would only
|
|
364
|
+
// wedge the poll loop and burn the assignment's whole retry budget
|
|
365
|
+
// against a transcript that can never shrink. The server terminalizes
|
|
366
|
+
// the assignment itself.
|
|
367
|
+
logger.warn(
|
|
368
|
+
`assignment ${assignment.assignmentId} transcript is too large to evaluate; skipping`,
|
|
369
|
+
);
|
|
370
|
+
this.increment("transcripts_too_large");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
if (session.sessionRevisionId !== assignment.sessionRevisionId) {
|
|
376
|
+
throw new Error("transcript session revision does not match assignment");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const descriptors = await this.assignmentDefinitions(assignment);
|
|
380
|
+
// Every descriptor the assignment carries, keyed for reconstruction: on an
|
|
381
|
+
// idempotent replay the server re-serves the first attempt's run set, which
|
|
382
|
+
// may include a run this attempt's re-derived plan would have skipped.
|
|
383
|
+
const descriptorByKey = new Map<string, AssignmentDefinition>(
|
|
384
|
+
descriptors.map((item) => [`${item.evalKey}@${item.evalVersion}`, item]),
|
|
385
|
+
);
|
|
386
|
+
const localDefinitions = new Map<string, EvalDefinition>(
|
|
387
|
+
this.evaluator.definitions.map((item) => [`${item.evalKey}@${item.evalVersion}`, item]),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const selected: Array<[AssignmentDefinition, EvalDefinition | null]> = [];
|
|
391
|
+
const skipped: SkippedEval[] = [];
|
|
392
|
+
// The assignment lease is fixed at claim time and cannot be renewed until
|
|
393
|
+
// the plan is submitted (the server only extends a lease for a *planned*
|
|
394
|
+
// assignment with running runs). A slow condition phase can therefore burn
|
|
395
|
+
// the whole lease and get the plan fenced as `lease_lost`, so every
|
|
396
|
+
// condition is bounded by the lease it must leave time to plan within.
|
|
397
|
+
const conditionDeadline = this.conditionPhaseDeadline(assignment);
|
|
398
|
+
|
|
399
|
+
for (const descriptor of descriptors) {
|
|
400
|
+
const key = `${descriptor.evalKey}@${descriptor.evalVersion}`;
|
|
401
|
+
const local = localDefinitions.get(key) ?? null;
|
|
402
|
+
if (descriptor.executionMode === ExecutionMode.LOCAL && local === null) {
|
|
403
|
+
throw new Error("server requested a definition absent from this worker");
|
|
404
|
+
}
|
|
405
|
+
if (this.disabledDefinitions.has(descriptor.evalKey)) {
|
|
406
|
+
skipped.push(skippedOf(descriptor, "disabled_by_server"));
|
|
407
|
+
this.increment("conditions_skipped");
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let applicable: boolean;
|
|
412
|
+
let reasonCode = "condition_false";
|
|
413
|
+
try {
|
|
414
|
+
// Whose condition decides applicability follows the EXECUTION MODE: a
|
|
415
|
+
// LOCAL definition's condition is client-authored; a sandboxed
|
|
416
|
+
// (server-authored) definition's is server-authored and MUST govern
|
|
417
|
+
// even when the worker also registered the same key/version locally.
|
|
418
|
+
// Keying `local` on key+version alone means a managed definition can
|
|
419
|
+
// collide with a local one; selecting the local condition there would
|
|
420
|
+
// let it override the server's rule and run the managed evaluator
|
|
421
|
+
// against the operator's intent.
|
|
422
|
+
let condition: ((s: SessionTranscript) => unknown) | null = null;
|
|
423
|
+
let managedConditionSource: string | null = null;
|
|
424
|
+
if (descriptor.executionMode === ExecutionMode.LOCAL) {
|
|
425
|
+
condition = local?.condition ?? null;
|
|
426
|
+
} else if (descriptor.conditionSource) {
|
|
427
|
+
managedConditionSource = descriptor.conditionSource;
|
|
428
|
+
}
|
|
429
|
+
if (condition === null && managedConditionSource === null) {
|
|
430
|
+
selected.push([descriptor, local]);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const budget = this.conditionBudget(conditionDeadline, descriptor.timeoutSeconds);
|
|
434
|
+
if (budget <= 0) {
|
|
435
|
+
// Not enough lease left to evaluate this condition and still submit
|
|
436
|
+
// the plan in time; skip it (and, as the loop proceeds, every later
|
|
437
|
+
// condition) rather than do work the server will fence as
|
|
438
|
+
// `lease_lost` and reclaim in a loop.
|
|
439
|
+
skipped.push(skippedOf(descriptor, "lease_exhausted"));
|
|
440
|
+
this.increment("conditions_skipped");
|
|
441
|
+
this.increment("conditions_lease_exhausted");
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
// Compiled INSIDE the try, and only once the lease budget is known: a
|
|
445
|
+
// managed condition the sandbox rejects must dead-letter as
|
|
446
|
+
// `condition_error`, not throw out of the plan loop and strand the
|
|
447
|
+
// whole assignment until its retry budget is exhausted.
|
|
448
|
+
if (managedConditionSource !== null) {
|
|
449
|
+
condition = compileCondition(managedConditionSource, { timeoutSeconds: budget });
|
|
450
|
+
}
|
|
451
|
+
const outcome = await withDeadline(
|
|
452
|
+
Promise.resolve(condition!(session)),
|
|
453
|
+
budget * 1000,
|
|
454
|
+
);
|
|
455
|
+
if (outcome.timedOut) throw new EvaluationTimeout("condition exceeded its budget");
|
|
456
|
+
const value = outcome.value;
|
|
457
|
+
if (value instanceof ConditionResult) {
|
|
458
|
+
applicable = value.applicable;
|
|
459
|
+
reasonCode = value.reasonCode;
|
|
460
|
+
} else if (typeof value === "boolean") {
|
|
461
|
+
applicable = value;
|
|
462
|
+
} else {
|
|
463
|
+
throw new TypeError("condition must return a boolean or a ConditionResult");
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
logger.warn(
|
|
467
|
+
`evaluator condition failed (assignment=${assignment.assignmentId}, ` +
|
|
468
|
+
`error=${error instanceof Error ? error.name : typeof error})`,
|
|
469
|
+
);
|
|
470
|
+
skipped.push(skippedOf(descriptor, "condition_error"));
|
|
471
|
+
this.increment("conditions_skipped");
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (applicable) {
|
|
476
|
+
selected.push([descriptor, local]);
|
|
477
|
+
this.increment("conditions_selected");
|
|
478
|
+
} else {
|
|
479
|
+
skipped.push(skippedOf(descriptor, reasonCode));
|
|
480
|
+
this.increment("conditions_skipped");
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const plan = await this.call(() =>
|
|
485
|
+
this.client.plan(assignment.assignmentId, {
|
|
486
|
+
workerId: this.config.workerId,
|
|
487
|
+
leaseGeneration: assignment.leaseGeneration,
|
|
488
|
+
selected: selected.map(
|
|
489
|
+
([item]): EvalSelection => ({ evalKey: item.evalKey, evalVersion: item.evalVersion }),
|
|
490
|
+
),
|
|
491
|
+
skipped,
|
|
492
|
+
}),
|
|
493
|
+
);
|
|
494
|
+
if (plan.assignmentId !== assignment.assignmentId) {
|
|
495
|
+
throw new Error("server returned a plan for a different assignment");
|
|
496
|
+
}
|
|
497
|
+
// On an idempotent replay the server's status is authoritative: this
|
|
498
|
+
// attempt may have selected a different set than the first, so a mismatch
|
|
499
|
+
// against our own `selected` is expected, not an error.
|
|
500
|
+
if (!plan.idempotentReplay) {
|
|
501
|
+
const expected = selected.length > 0 ? "planned" : "skipped";
|
|
502
|
+
if (plan.assignmentStatus !== expected) {
|
|
503
|
+
throw new Error("server returned an inconsistent assignment status");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const pending = new Map<string, [AssignmentDefinition, EvalDefinition | null]>(
|
|
508
|
+
selected.map(([item, local]) => [`${item.evalKey}@${item.evalVersion}`, [item, local]]),
|
|
509
|
+
);
|
|
510
|
+
const runDefinitions: Array<[string, EvalDefinition]> = [];
|
|
511
|
+
const runIds = new Set<string>();
|
|
512
|
+
for (const run of plan.runs) {
|
|
513
|
+
if (runIds.has(run.evaluationRunId)) {
|
|
514
|
+
throw new Error("server returned a duplicate evaluation run id");
|
|
515
|
+
}
|
|
516
|
+
runIds.add(run.evaluationRunId);
|
|
517
|
+
const key = `${run.evalKey}@${run.evalVersion}`;
|
|
518
|
+
let entry = pending.get(key);
|
|
519
|
+
pending.delete(key);
|
|
520
|
+
if (entry === undefined) {
|
|
521
|
+
// On an idempotent replay the server's run set is AUTHORITATIVE — it
|
|
522
|
+
// re-serves the first attempt's runs even for a definition this
|
|
523
|
+
// attempt's condition phase would have skipped. Reconstruct from the
|
|
524
|
+
// assignment's descriptors rather than throwing and dead-lettering an
|
|
525
|
+
// assignment that could otherwise never converge.
|
|
526
|
+
const replay = plan.idempotentReplay ? descriptorByKey.get(key) : undefined;
|
|
527
|
+
if (replay === undefined) throw new Error("server returned an unrequested evaluation run");
|
|
528
|
+
entry = [replay, localDefinitions.get(key) ?? null];
|
|
529
|
+
}
|
|
530
|
+
const [descriptor, local] = entry;
|
|
531
|
+
if (run.executionMode !== descriptor.executionMode) {
|
|
532
|
+
throw new Error("server changed the evaluation execution mode");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let definition: EvalDefinition;
|
|
536
|
+
if (run.executionMode === ExecutionMode.LOCAL) {
|
|
537
|
+
if (local === null) throw new Error("local evaluation definition is unavailable");
|
|
538
|
+
definition = local;
|
|
539
|
+
} else {
|
|
540
|
+
if (!run.evaluatorSource || !run.sourceChecksum) {
|
|
541
|
+
throw new Error("server omitted managed evaluation source");
|
|
542
|
+
}
|
|
543
|
+
const expected = sourceChecksum(descriptor.conditionSource, run.evaluatorSource);
|
|
544
|
+
if (
|
|
545
|
+
expected !== run.sourceChecksum ||
|
|
546
|
+
(descriptor.sourceChecksum && descriptor.sourceChecksum !== run.sourceChecksum)
|
|
547
|
+
) {
|
|
548
|
+
throw new Error("managed evaluation source checksum mismatch");
|
|
549
|
+
}
|
|
550
|
+
const timeoutSeconds = run.timeoutSeconds ?? descriptor.timeoutSeconds;
|
|
551
|
+
definition = {
|
|
552
|
+
evalKey: descriptor.evalKey,
|
|
553
|
+
displayName: descriptor.displayName,
|
|
554
|
+
evalVersion: descriptor.evalVersion,
|
|
555
|
+
resultKind: descriptor.resultKind,
|
|
556
|
+
labels: descriptor.labels,
|
|
557
|
+
function: deferredManagedEval(
|
|
558
|
+
run.evaluatorSource,
|
|
559
|
+
timeoutSeconds,
|
|
560
|
+
descriptor.evalKey,
|
|
561
|
+
this.evaluator.managedCompiler,
|
|
562
|
+
),
|
|
563
|
+
condition: null,
|
|
564
|
+
onCancel: null,
|
|
565
|
+
timeoutSeconds,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
runDefinitions.push([run.evaluationRunId, definition]);
|
|
569
|
+
}
|
|
570
|
+
if (pending.size > 0 && !plan.idempotentReplay) {
|
|
571
|
+
throw new Error("server omitted a selected evaluation run");
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const states = new Map<string, { done: boolean; cancelled: boolean }>();
|
|
575
|
+
const tasks = runDefinitions.map(([runId, definition]) => {
|
|
576
|
+
const state = { done: false, cancelled: false };
|
|
577
|
+
states.set(runId, state);
|
|
578
|
+
return this.executeRun(assignment, runId, definition, session, state).finally(() => {
|
|
579
|
+
state.done = true;
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
const heartbeat = this.heartbeat(assignment, states);
|
|
583
|
+
try {
|
|
584
|
+
const outcomes = await Promise.allSettled(tasks);
|
|
585
|
+
for (const outcome of outcomes) {
|
|
586
|
+
if (outcome.status === "rejected") throw outcome.reason as Error;
|
|
587
|
+
}
|
|
588
|
+
} finally {
|
|
589
|
+
heartbeat.cancel();
|
|
590
|
+
await heartbeat.finished;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private async acquireSlot(): Promise<void> {
|
|
595
|
+
if (this.slots.inUse < this.config.maxConcurrency) {
|
|
596
|
+
this.slots.inUse += 1;
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
await new Promise<void>((resolve) => this.slots.waiters.push(resolve));
|
|
600
|
+
this.slots.inUse += 1;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private releaseSlot(): void {
|
|
604
|
+
this.slots.inUse -= 1;
|
|
605
|
+
const waiter = this.slots.waiters.shift();
|
|
606
|
+
if (waiter) waiter();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private async executeRun(
|
|
610
|
+
assignment: Assignment,
|
|
611
|
+
runId: string,
|
|
612
|
+
definition: EvalDefinition,
|
|
613
|
+
session: SessionTranscript,
|
|
614
|
+
state: { done: boolean; cancelled: boolean },
|
|
615
|
+
): Promise<void> {
|
|
616
|
+
await this.acquireSlot();
|
|
617
|
+
try {
|
|
618
|
+
await this.executeRunInSlot(assignment, runId, definition, session, state);
|
|
619
|
+
} finally {
|
|
620
|
+
this.releaseSlot();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private async executeRunInSlot(
|
|
625
|
+
assignment: Assignment,
|
|
626
|
+
runId: string,
|
|
627
|
+
definition: EvalDefinition,
|
|
628
|
+
session: SessionTranscript,
|
|
629
|
+
state: { done: boolean; cancelled: boolean },
|
|
630
|
+
): Promise<void> {
|
|
631
|
+
const startedAt = utcNow();
|
|
632
|
+
const started = Date.now();
|
|
633
|
+
let items: readonly ResultItem[] = [];
|
|
634
|
+
let status: TerminalRunStatus;
|
|
635
|
+
let summary: string | null = null;
|
|
636
|
+
let errorCode: string | null = null;
|
|
637
|
+
let errorMessage: string | null = null;
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
if (state.cancelled) throw new EvaluationCancelled();
|
|
641
|
+
// Always bound the evaluation. A definition with no declared timeout
|
|
642
|
+
// falls back to the default rather than awaiting unbounded — an unbounded
|
|
643
|
+
// local eval that hangs would wedge its worker slot and hold the lease
|
|
644
|
+
// forever.
|
|
645
|
+
const timeoutSeconds = definition.timeoutSeconds ?? DEFAULT_EVAL_TIMEOUT_SECONDS;
|
|
646
|
+
const outcome = await withDeadline(
|
|
647
|
+
Promise.resolve(definition.function(session)),
|
|
648
|
+
timeoutSeconds * 1000,
|
|
649
|
+
);
|
|
650
|
+
if (outcome.timedOut) throw new EvaluationTimeout("evaluation exceeded its timeout");
|
|
651
|
+
const result = outcome.value;
|
|
652
|
+
if (!(result instanceof EvalResult)) throw new TypeError("evaluation must return an EvalResult");
|
|
653
|
+
items = result.resultItems(definition.evalKey);
|
|
654
|
+
if (
|
|
655
|
+
!items.some(
|
|
656
|
+
(item) => item.resultKey === definition.evalKey && item.resultKind === definition.resultKind,
|
|
657
|
+
)
|
|
658
|
+
) {
|
|
659
|
+
throw new Error("evaluation result does not contain its declared primary result");
|
|
660
|
+
}
|
|
661
|
+
status = TerminalRunStatus.SUCCEEDED;
|
|
662
|
+
summary = result.summary ?? null;
|
|
663
|
+
} catch (error) {
|
|
664
|
+
await this.cancelHook(definition, session);
|
|
665
|
+
if (error instanceof EvaluationCancelled || state.cancelled) {
|
|
666
|
+
status = TerminalRunStatus.CANCELLED;
|
|
667
|
+
errorCode = "lease_lost";
|
|
668
|
+
errorMessage = "the assignment lease was lost before this run finished";
|
|
669
|
+
} else if (error instanceof EvaluationTimeout) {
|
|
670
|
+
// A SANDBOXED evaluation that loses this race has already been
|
|
671
|
+
// terminated by the worker sandbox, so nothing is left running. A LOCAL
|
|
672
|
+
// one cannot be: a promise has no cancel, so the customer's function
|
|
673
|
+
// keeps going. Count it and name the eval so a hung one is findable.
|
|
674
|
+
this.increment("evaluations_orphaned");
|
|
675
|
+
logger.warn(
|
|
676
|
+
`evaluation ${JSON.stringify(definition.evalKey)} exceeded its timeout ` +
|
|
677
|
+
`(assignment=${assignment.assignmentId}). If it is a local evaluation it is still ` +
|
|
678
|
+
"running — a promise cannot be cancelled — and its worker slot is freed only when " +
|
|
679
|
+
"it settles.",
|
|
680
|
+
);
|
|
681
|
+
status = TerminalRunStatus.TIMED_OUT;
|
|
682
|
+
errorCode = "eval_timeout";
|
|
683
|
+
errorMessage = "evaluation exceeded its configured timeout";
|
|
684
|
+
} else if (error instanceof UnsafeEvaluatorSource) {
|
|
685
|
+
// Surface the REASON for a rejected server-authored definition. This is
|
|
686
|
+
// deliberately narrower than the generic branch below:
|
|
687
|
+
// `UnsafeEvaluatorSource` is raised by our own validator before any
|
|
688
|
+
// customer source runs, and its message is SDK-authored text about the
|
|
689
|
+
// source's shape — it embeds no transcript content, so it is safe to
|
|
690
|
+
// send back over the wire. Without it the author sees only "evaluation
|
|
691
|
+
// raised UnsafeEvaluatorSource" on every session, with no way to learn
|
|
692
|
+
// what was wrong.
|
|
693
|
+
status = TerminalRunStatus.FAILED;
|
|
694
|
+
errorCode = "eval_error";
|
|
695
|
+
const detail = error.message.trim();
|
|
696
|
+
errorMessage = truncateUtf8(
|
|
697
|
+
detail ? `evaluator source rejected: ${detail}` : "evaluator source rejected by the validator",
|
|
698
|
+
MAX_ERROR_MESSAGE_BYTES,
|
|
699
|
+
);
|
|
700
|
+
} else {
|
|
701
|
+
// Type name ONLY on the wire. A customer eval's error text can quote
|
|
702
|
+
// the transcript it was reading, and this field is persisted and shown
|
|
703
|
+
// in the dashboard. But log the FULL error LOCALLY: this runs on the
|
|
704
|
+
// customer's own pod over their own data, and without it an author
|
|
705
|
+
// whose eval throws sees only "evaluation raised TypeError" in the
|
|
706
|
+
// dashboard and nothing at all in their logs.
|
|
707
|
+
logException(
|
|
708
|
+
`evaluation ${JSON.stringify(definition.evalKey)} threw; reported to the server as a failed run`,
|
|
709
|
+
error,
|
|
710
|
+
);
|
|
711
|
+
status = TerminalRunStatus.FAILED;
|
|
712
|
+
errorCode = "eval_error";
|
|
713
|
+
errorMessage = `evaluation raised ${error instanceof Error ? error.name : typeof error}`;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
await this.call(() =>
|
|
718
|
+
this.client.submitResult(runId, {
|
|
719
|
+
submissionId: randomUUID(),
|
|
720
|
+
workerId: this.config.workerId,
|
|
721
|
+
leaseGeneration: assignment.leaseGeneration,
|
|
722
|
+
status,
|
|
723
|
+
startedAt,
|
|
724
|
+
finishedAt: utcNow(),
|
|
725
|
+
durationMs: Math.max(0, Math.round(Date.now() - started)),
|
|
726
|
+
summary,
|
|
727
|
+
results: items,
|
|
728
|
+
errorCode,
|
|
729
|
+
errorMessage,
|
|
730
|
+
}),
|
|
731
|
+
);
|
|
732
|
+
this.increment(`runs_${status}`);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private async cancelHook(definition: EvalDefinition, session: SessionTranscript): Promise<void> {
|
|
736
|
+
if (definition.onCancel === null) return;
|
|
737
|
+
try {
|
|
738
|
+
await definition.onCancel(session);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
logger.warn(
|
|
741
|
+
`evaluator cancellation hook failed (${error instanceof Error ? error.name : typeof error})`,
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private heartbeat(
|
|
747
|
+
assignment: Assignment,
|
|
748
|
+
states: Map<string, { done: boolean; cancelled: boolean }>,
|
|
749
|
+
): { cancel: () => void; finished: Promise<void> } {
|
|
750
|
+
let cancelled = false;
|
|
751
|
+
const finished = (async () => {
|
|
752
|
+
// Beat IMMEDIATELY, before the first sleep. The pre-plan condition phase
|
|
753
|
+
// may have consumed most of the claim-time lease, and the server only
|
|
754
|
+
// renews a planned assignment's lease on heartbeat — so sleeping a full
|
|
755
|
+
// interval here can let the lease expire after the runs have started,
|
|
756
|
+
// cancelling every one of them.
|
|
757
|
+
let first = true;
|
|
758
|
+
while (!cancelled) {
|
|
759
|
+
if (!first) await sleep(this.heartbeatInterval * 1000);
|
|
760
|
+
if (cancelled) return;
|
|
761
|
+
first = false;
|
|
762
|
+
const active = [...states.entries()]
|
|
763
|
+
.filter(([, state]) => !state.done)
|
|
764
|
+
.map(([runId]) => ({ evaluationRunId: runId, state: "running" }));
|
|
765
|
+
if (active.length === 0) return;
|
|
766
|
+
try {
|
|
767
|
+
const response = await this.call(() =>
|
|
768
|
+
this.client.heartbeat({
|
|
769
|
+
workerId: this.config.workerId,
|
|
770
|
+
leaseGeneration: assignment.leaseGeneration,
|
|
771
|
+
runs: active,
|
|
772
|
+
}),
|
|
773
|
+
);
|
|
774
|
+
const accepted = new Set(response.acceptedRunIds);
|
|
775
|
+
for (const [runId, state] of states) {
|
|
776
|
+
if (!state.done && !accepted.has(runId)) state.cancelled = true;
|
|
777
|
+
}
|
|
778
|
+
} catch (error) {
|
|
779
|
+
if (error instanceof EvaluatorAPIError && error.code === "lease_lost") {
|
|
780
|
+
this.increment("leases_lost");
|
|
781
|
+
for (const state of states.values()) state.cancelled = true;
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
logger.warn(
|
|
785
|
+
`evaluator heartbeat failed (assignment=${assignment.assignmentId}, ` +
|
|
786
|
+
`${error instanceof EvaluatorAPIError ? `code=${error.code}` : `error=${error instanceof Error ? error.name : typeof error}`})`,
|
|
787
|
+
);
|
|
788
|
+
this.increment("heartbeat_failures");
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
})();
|
|
792
|
+
return {
|
|
793
|
+
cancel: () => {
|
|
794
|
+
cancelled = true;
|
|
795
|
+
},
|
|
796
|
+
finished,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* The clock reading by which the pre-plan condition phase must end.
|
|
802
|
+
*
|
|
803
|
+
* The real `leaseExpiresAt` is used when it is in the future (production); a
|
|
804
|
+
* past or unparseable value (clock skew, or a replayed transcript in a test)
|
|
805
|
+
* falls back to the negotiated lease duration measured from now, so the bound
|
|
806
|
+
* never fires spuriously on a stale deadline.
|
|
807
|
+
*/
|
|
808
|
+
private conditionPhaseDeadline(assignment: Assignment): number {
|
|
809
|
+
let remaining = this.leaseDuration;
|
|
810
|
+
const expires = Date.parse(assignment.leaseExpiresAt);
|
|
811
|
+
if (Number.isFinite(expires)) {
|
|
812
|
+
const parsed = (expires - Date.now()) / 1000;
|
|
813
|
+
if (parsed > 0) remaining = parsed;
|
|
814
|
+
}
|
|
815
|
+
return Date.now() + remaining * 1000;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Seconds a single condition may run: the lease left before the
|
|
820
|
+
* plan-submission margin, capped by the definition's own timeout.
|
|
821
|
+
*/
|
|
822
|
+
private conditionBudget(deadline: number, timeoutSeconds: number | null): number {
|
|
823
|
+
let remaining = (deadline - Date.now()) / 1000 - CONDITION_PHASE_SAFETY_MARGIN_SECONDS;
|
|
824
|
+
if (timeoutSeconds !== null) remaining = Math.min(remaining, timeoutSeconds);
|
|
825
|
+
return remaining;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private async assignmentDefinitions(assignment: Assignment): Promise<AssignmentDefinition[]> {
|
|
829
|
+
if (assignment.definitionsUrl) {
|
|
830
|
+
const response = await this.call(() =>
|
|
831
|
+
this.client.definitions(assignment, this.config.workerId),
|
|
832
|
+
);
|
|
833
|
+
if (response.assignmentId !== assignment.assignmentId) {
|
|
834
|
+
throw new Error("server returned definitions for another assignment");
|
|
835
|
+
}
|
|
836
|
+
return [...response.definitions];
|
|
837
|
+
}
|
|
838
|
+
return this.evaluator.definitions.map((item) => ({
|
|
839
|
+
evalKey: item.evalKey,
|
|
840
|
+
displayName: item.displayName,
|
|
841
|
+
evalVersion: item.evalVersion,
|
|
842
|
+
resultKind: item.resultKind,
|
|
843
|
+
labels: item.labels,
|
|
844
|
+
executionMode: ExecutionMode.LOCAL,
|
|
845
|
+
conditionSource: null,
|
|
846
|
+
sourceChecksum: null,
|
|
847
|
+
timeoutSeconds: item.timeoutSeconds,
|
|
848
|
+
}));
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
private progressWaiters: Array<() => void> = [];
|
|
852
|
+
|
|
853
|
+
private notifyProgress(): void {
|
|
854
|
+
const waiters = this.progressWaiters;
|
|
855
|
+
this.progressWaiters = [];
|
|
856
|
+
for (const waiter of waiters) waiter();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private async waitForProgress(): Promise<void> {
|
|
860
|
+
if (this.activeAssignments.size === 0) return;
|
|
861
|
+
await new Promise<void>((resolve) => {
|
|
862
|
+
this.progressWaiters.push(resolve);
|
|
863
|
+
this.stopWaiters.push(resolve);
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
private async waitOrStop(ms: number): Promise<void> {
|
|
868
|
+
if (this.stopping) return;
|
|
869
|
+
await new Promise<void>((resolve) => {
|
|
870
|
+
const timer = setTimeout(resolve, ms);
|
|
871
|
+
timer.unref?.();
|
|
872
|
+
this.stopWaiters.push(() => {
|
|
873
|
+
clearTimeout(timer);
|
|
874
|
+
resolve();
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
private async call<T>(work: () => Promise<T>): Promise<T> {
|
|
880
|
+
const result = await work();
|
|
881
|
+
this.lastServerContact = Date.now();
|
|
882
|
+
return result;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
private increment(name: string, amount = 1): void {
|
|
886
|
+
this.metricCounts.set(name, (this.metricCounts.get(name) ?? 0) + amount);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
metrics(): Record<string, number> {
|
|
890
|
+
return Object.fromEntries(this.metricCounts);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
isReady(): boolean {
|
|
894
|
+
if (this.stopping || !this.registered || this.lastServerContact === null) return false;
|
|
895
|
+
return (Date.now() - this.lastServerContact) / 1000 <= Math.max(this.leaseDuration, 60);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
class EvaluationCancelled extends Error {
|
|
900
|
+
constructor() {
|
|
901
|
+
super("evaluation cancelled");
|
|
902
|
+
this.name = "EvaluationCancelled";
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function skippedOf(definition: AssignmentDefinition, reasonCode: string): SkippedEval {
|
|
907
|
+
return { evalKey: definition.evalKey, evalVersion: definition.evalVersion, reasonCode };
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function validatedAssignments(
|
|
911
|
+
assignments: readonly Assignment[],
|
|
912
|
+
capacity: number,
|
|
913
|
+
): Assignment[] {
|
|
914
|
+
if (assignments.length > capacity) {
|
|
915
|
+
throw new Error("server returned more assignments than requested");
|
|
916
|
+
}
|
|
917
|
+
const ids = assignments.map((item) => item.assignmentId);
|
|
918
|
+
if (new Set(ids).size !== ids.length) throw new Error("server returned duplicate assignments");
|
|
919
|
+
return [...assignments];
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/** Cut to `maximum` BYTES without splitting a multi-byte character. */
|
|
923
|
+
function truncateUtf8(value: string, maximum: number): string {
|
|
924
|
+
const encoded = Buffer.from(value, "utf8");
|
|
925
|
+
if (encoded.byteLength <= maximum) return value;
|
|
926
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(encoded.subarray(0, maximum)).replace(
|
|
927
|
+
/�$/,
|
|
928
|
+
"",
|
|
929
|
+
);
|
|
930
|
+
}
|