failproofai 1.0.12-beta.1 → 1.0.12

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.
Files changed (236) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +3 -3
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__167mipqmjprq5._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
  75. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_1d98fyfrcaqhy._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  90. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  91. package/.next/standalone/.next/server/pages/404.html +1 -1
  92. package/.next/standalone/.next/server/pages/500.html +1 -1
  93. package/.next/standalone/.next/server/preview-props.json +3 -3
  94. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  96. package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 0el9n3h9_zp5m.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 0umzct31duk5g.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 0wobqgosirs8p.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
  101. package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 1kduj74-715jh.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/2701amhrbsbot.js +1 -0
  103. package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 27cavoc8paelg.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 2ve23a7keupjw.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/3bv6xaoitjpnh.js +6 -0
  106. package/.next/standalone/app/actions/update-jev-config.ts +5 -3
  107. package/.next/standalone/app/settings/jev-panel.tsx +1 -1
  108. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +28 -18
  109. package/.next/standalone/fp-cloud-cli/README.md +127 -9
  110. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
  111. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +199 -34
  112. package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
  113. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
  114. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
  115. package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
  116. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
  118. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
  119. package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
  120. package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
  121. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
  122. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
  123. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
  124. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
  125. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
  126. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
  127. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
  128. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
  129. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
  130. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
  131. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
  132. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
  133. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
  134. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
  135. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
  136. package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
  137. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -152
  138. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
  139. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
  140. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
  141. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +173 -22
  142. package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
  143. package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
  144. package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
  145. package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
  146. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
  147. package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
  148. package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
  149. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
  150. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
  151. package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
  152. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
  153. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
  154. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
  155. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
  156. package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
  157. package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
  158. package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
  159. package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
  160. package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
  161. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
  162. package/.next/standalone/hermes-plugin/__init__.py +11 -0
  163. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  164. package/.next/standalone/package.json +11 -10
  165. package/.next/standalone/sdk/python/uv.lock +31 -8
  166. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
  167. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
  168. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
  169. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
  171. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
  172. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
  173. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
  174. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
  175. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
  176. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
  177. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
  178. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
  179. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
  180. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
  181. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
  182. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
  183. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
  184. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
  185. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
  186. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
  187. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
  188. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
  189. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
  190. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
  191. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
  192. package/.next/standalone/server.js +1 -1
  193. package/dist/cli.mjs +1760 -638
  194. package/dist/worker.mjs +426 -76
  195. package/hermes-plugin/__init__.py +11 -0
  196. package/hermes-plugin/plugin.yaml +1 -1
  197. package/package.json +11 -10
  198. package/src/audit/open-browser.ts +24 -7
  199. package/src/hooks/agent-roster.ts +9 -1
  200. package/src/hooks/agent-skill.ts +206 -0
  201. package/src/hooks/cloud-connection.ts +27 -51
  202. package/src/hooks/cloud-introspect.ts +5 -2
  203. package/src/hooks/cloud-login.ts +248 -0
  204. package/src/hooks/cloud-managed-policies.ts +54 -4
  205. package/src/hooks/codex-system-hooks.ts +10 -4
  206. package/src/hooks/config-status.ts +9 -3
  207. package/src/hooks/configure-wizard.ts +356 -32
  208. package/src/hooks/delivery-health.ts +47 -2
  209. package/src/hooks/effective-reviewers.ts +14 -0
  210. package/src/hooks/fp-config.ts +19 -0
  211. package/src/hooks/fp-home.ts +1 -1
  212. package/src/hooks/handler.ts +43 -5
  213. package/src/hooks/hook-activity-store.ts +7 -1
  214. package/src/hooks/integrations.ts +18 -3
  215. package/src/hooks/jev-activity.ts +117 -1
  216. package/src/hooks/jev-cli.ts +7 -9
  217. package/src/hooks/manager.ts +80 -46
  218. package/src/hooks/policy-evaluator.ts +17 -0
  219. package/src/hooks/policy-reviewability.ts +4 -2
  220. package/src/hooks/semantic/cloud-jev.ts +219 -28
  221. package/src/hooks/semantic/combine.ts +26 -1
  222. package/src/hooks/semantic/intent.ts +127 -24
  223. package/src/hooks/semantic/jev-client.ts +1 -1
  224. package/src/hooks/semantic/jev-config.ts +6 -27
  225. package/src/hooks/semantic/jev-review.ts +111 -4
  226. package/src/hooks/tui.ts +280 -0
  227. package/src/hooks/uninstall-cli.ts +12 -1
  228. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
  229. package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
  230. package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +0 -1
  231. package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
  232. package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
  233. package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
  234. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
  235. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
  236. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
@@ -31,6 +31,7 @@ from .errors import (
31
31
  ApiError,
32
32
  AuthError,
33
33
  ForbiddenError,
34
+ FpCliError,
34
35
  KeyModeUnsupportedError,
35
36
  NetworkError,
36
37
  NotFoundError,
@@ -134,6 +135,14 @@ _V1_MECHANICAL_FAMILIES = frozenset(
134
135
  "alerts",
135
136
  "audits",
136
137
  "evaluations",
138
+ "evaluator-definitions",
139
+ "evaluator-instances",
140
+ "evaluator-schema-profile",
141
+ "evaluation-jobs",
142
+ "dashboards",
143
+ "otel",
144
+ "offers",
145
+ "team-trial",
137
146
  "events",
138
147
  "issues",
139
148
  "keys",
@@ -152,6 +161,7 @@ _V1_MECHANICAL_FAMILIES = frozenset(
152
161
  # family rule, which is why this must stay exact-match.
153
162
  _V1_RENAMED = {
154
163
  "/api/evaluations/score-keys": "/v1/evaluations/score_keys",
164
+ "/api/evaluations/metric-keys": "/v1/evaluations/metric_keys",
155
165
  }
156
166
 
157
167
  # Families with no `/v1` route, and why — the message a user actually sees.
@@ -173,6 +183,8 @@ _V1_NO_EQUIVALENT = {
173
183
  "cloud-managed policies are an operator surface — the fleet routes are "
174
184
  "deliberately absent from /v1, which is internet-facing"
175
185
  ),
186
+ "integrations": "dashboard integration routes need a signed-in session",
187
+ "orgs": "organization creation needs a signed-in user",
176
188
  }
177
189
 
178
190
 
@@ -375,6 +387,40 @@ def _required_permission(response: httpx.Response) -> Optional[str]:
375
387
  return None
376
388
 
377
389
 
390
+ def _unknown_route_response(response: httpx.Response) -> Optional[bool]:
391
+ """Use explicit 404 details first; an empty/generic reply is ambiguous."""
392
+ try:
393
+ data = response.json()
394
+ except ValueError:
395
+ return None
396
+ if not isinstance(data, dict):
397
+ return None
398
+ code = data.get("code") or data.get("error_code") or data.get("error") or ""
399
+ if isinstance(code, dict):
400
+ code = code.get("code", "")
401
+ code = str(code).lower()
402
+ if code in ("unknown_route", "route_not_found", "unknown_endpoint", "endpoint_not_found"):
403
+ return True
404
+ if code in ("record_not_found", "resource_not_found", "entity_not_found", "session_not_found", "query_not_found"):
405
+ return False
406
+ message = _extract_error(response)
407
+ if not message:
408
+ return None
409
+ lower = message.lower().strip().rstrip(".")
410
+ if re.search(
411
+ r"\b(?:unknown|unrecognized|unsupported|missing|no such)\s+(?:api\s+)?(?:route|endpoint)\b"
412
+ r"|\b(?:route|endpoint)\b.*\b(?:not found|not available|does not exist|absent|unknown)\b"
413
+ r"|\bcannot (?:get|post|put|patch|delete)\s+/",
414
+ lower,
415
+ ):
416
+ return True
417
+ if lower in ("not found", "not_found", "404", "404 not found"):
418
+ return None
419
+ # A specific response ("session not found", "offer expired", etc.) came
420
+ # from a handler, rather than the router. Do not replace its useful detail.
421
+ return False
422
+
423
+
378
424
  def _raise_for_status(response: httpx.Response, ctx: ClientContext) -> None:
379
425
  key_mode = ctx.auth_mode is AuthMode.API_KEY
380
426
  if response.status_code < 400:
@@ -474,7 +520,9 @@ def _raise_for_status(response: httpx.Response, ctx: ClientContext) -> None:
474
520
  request_id=request_id,
475
521
  hint="point --base-url at the server itself, e.g. http://localhost:8080",
476
522
  )
477
- raise NotFoundError(message or "Not found.", request_id=request_id)
523
+ error = NotFoundError(message or "Not found.", request_id=request_id)
524
+ error.route_unknown = _unknown_route_response(response)
525
+ raise error
478
526
  if response.status_code == 429:
479
527
  retry_after = response.headers.get("retry-after")
480
528
  wait = f" Retry after {retry_after}s." if retry_after else " Please wait a moment and try again."
@@ -606,6 +654,121 @@ def _delete(ctx: ClientContext, path: str) -> Any:
606
654
  return _request_json(ctx, "DELETE", path)
607
655
 
608
656
 
657
+ def cloud_request(
658
+ ctx: ClientContext, method: str, path: str, *,
659
+ body: Any = None, params: Optional[Dict[str, Any]] = None,
660
+ ) -> Any:
661
+ """New Cloud capabilities, with a predictable older-deployment failure.
662
+
663
+ Existing callers retain their response and error contracts. Paths still pass
664
+ through the credential and identifier guards; this is not a second transport.
665
+ """
666
+ try:
667
+ if method == "GET":
668
+ return _get_json(ctx, path, params)
669
+ return _request_json(ctx, method, path, json_body=body, params=params)
670
+ except (NotFoundError, ApiError) as exc:
671
+ if isinstance(exc, ApiError) and exc.status != 404:
672
+ raise
673
+ raise _cloud_not_found(exc, path, body=body, params=params) from exc
674
+
675
+
676
+ def _cloud_not_found(exc: FpCliError, path: str, *, body=None, params=None) -> NotFoundError:
677
+ """Classify ambiguous 404s from the request; never claim a record is a route.
678
+
679
+ Top-level collections and named capability endpoints have no record segment.
680
+ Scoped collections (/dashboards/ID/tiles) and actions on records default to a
681
+ missing record unless the response explicitly says the route is unknown.
682
+ """
683
+ route_unknown = getattr(exc, "route_unknown", None)
684
+ if isinstance(exc, ApiError):
685
+ # The transport only raises HTTP 404 ApiError for an HTML front door.
686
+ route_unknown = True
687
+ if route_unknown is None:
688
+ segments = path.removeprefix("/api/").split("/")
689
+ family = segments[0]
690
+ capability = len(segments) == 1 or (
691
+ family == "agent" and len(segments) == 2
692
+ or family == "otel" and segments == ["otel", "status"]
693
+ or family == "offers" and segments[1:] in (["me"], ["claim"])
694
+ or family == "team-trial" and segments == ["team-trial", "me"]
695
+ or family == "integrations" and segments == ["integrations", "slack"]
696
+ or family == "events" and segments in (
697
+ ["events", "summary"], ["events", "latency_aggregate"],
698
+ ["events", "sentiment"], ["events", "sentiment", "messages"],
699
+ ["events", "sentiment", "sessions"],
700
+ )
701
+ or family == "evaluator-definitions" and segments == ["evaluator-definitions", "dry-run"]
702
+ or family == "evaluations" and segments == ["evaluations", "backfill"]
703
+ or family == "queries" and segments[1:] in (["run"], ["schema"])
704
+ or family == "audits" and segments == ["audits", "contracts"]
705
+ or family == "alerts" and segments == ["alerts", "recipients"]
706
+ or family == "users" and segments == ["users", "defaults"]
707
+ or family == "usage" and segments == ["usage", "windows"]
708
+ or family == "settings" and segments == ["settings", "model-context-windows"]
709
+ or family == "settings" and len(segments) == 2
710
+ )
711
+ # Some collection operations name their target in a query or body.
712
+ request_body = body if isinstance(body, dict) else {}
713
+ record_selected = (
714
+ (params or {}).get("session_id") or (params or {}).get("model")
715
+ or request_body.get("query_id") or request_body.get("callId")
716
+ or request_body.get("session_ids") or request_body.get("offer_key")
717
+ or request_body.get("conversationId")
718
+ )
719
+ route_unknown = capability and not record_selected
720
+ if route_unknown:
721
+ return NotFoundError(
722
+ "not available on this FailproofAI Cloud yet",
723
+ hint="upgrade your Cloud deployment to use this route",
724
+ request_id=exc.request_id,
725
+ )
726
+ message = exc.message
727
+ if message.lower().strip().rstrip(".") in (
728
+ "not found", "not_found", "record_not_found", "resource_not_found",
729
+ "entity_not_found", "session_not_found", "query_not_found",
730
+ ):
731
+ message = f"Record not found at {path}"
732
+ return NotFoundError(message, request_id=exc.request_id)
733
+
734
+
735
+ def import_events_file(
736
+ ctx: ClientContext, data: bytes, params: Dict[str, Any],
737
+ ) -> Any:
738
+ """POST /api/events/import — one file's bytes (gzip accepted), as the dashboard's
739
+ import dialog sends it. ``params``: format, path, environment, dry_run, existing_before."""
740
+ clean = {k: v for k, v in params.items() if v is not None} or None
741
+ url = _path(ctx, "/api/events/import")
742
+ try:
743
+ with _client(ctx) as client:
744
+ response = client.request(
745
+ "POST", url, content=data, params=clean,
746
+ headers={"Content-Type": "application/octet-stream"},
747
+ )
748
+ except httpx.RequestError as exc:
749
+ raise NetworkError(
750
+ f"Cannot reach FailproofAI Cloud at {ctx.base_url}: {exc}",
751
+ request_id=_sent_request_id(exc),
752
+ )
753
+ _raise_for_status(response, ctx)
754
+ if not response.content:
755
+ return {}
756
+ try:
757
+ return response.json()
758
+ except ValueError:
759
+ raise ApiError("The dashboard returned an invalid import response.")
760
+
761
+
762
+ def cloud_stream(ctx: ClientContext, path: str, body: Dict[str, Any]) -> Iterator[Dict[str, Any]]:
763
+ """Dashboard SSE with the same additive-capability 404 contract."""
764
+ try:
765
+ yield from _stream_sse(ctx, path, body)
766
+ except (NotFoundError, ApiError) as exc:
767
+ if isinstance(exc, ApiError) and exc.status != 404:
768
+ raise
769
+ raise _cloud_not_found(exc, path, body=body) from exc
770
+
771
+
609
772
  # --- Auth / identity --------------------------------------------------------
610
773
 
611
774
 
@@ -793,6 +956,10 @@ _FACET_PATHS = {
793
956
  # Evaluation score keys (a distinct endpoint, not /api/events) — the source for
794
957
  # the sessions-page score-filter dropdown; needs `evaluations:read`.
795
958
  "score_filters": "/api/evaluations/score-keys",
959
+ # Evaluation-side facets: environments with evaluation results, and metric
960
+ # keys (the server returns {name, unit, count}; `list` shows the names).
961
+ "eval_environments": "/api/evaluations/environments",
962
+ "metric_keys": "/api/evaluations/metric-keys",
796
963
  }
797
964
  FACET_KINDS = tuple(_FACET_PATHS.keys())
798
965
 
@@ -800,7 +967,9 @@ FACET_KINDS = tuple(_FACET_PATHS.keys())
800
967
  def list_facet(ctx: ClientContext, kind: str) -> List[str]:
801
968
  """GET /api/events/<kind> — distinct facet values (a bare JSON array)."""
802
969
  data = _get_json(ctx, _FACET_PATHS[kind])
803
- return [str(x) for x in data] if isinstance(data, list) else []
970
+ if not isinstance(data, list):
971
+ return []
972
+ return [str(x.get("name", "")) if isinstance(x, dict) else str(x) for x in data]
804
973
 
805
974
 
806
975
  def get_usage(ctx: ClientContext) -> Dict[str, Any]:
@@ -1195,20 +1364,28 @@ def list_incidents(
1195
1364
  state: Optional[str] = None,
1196
1365
  alert_id: Optional[str] = None,
1197
1366
  limit: Optional[int] = None,
1367
+ filters: Optional[Dict[str, Any]] = None,
1198
1368
  ) -> List[Incident]:
1199
1369
  """GET /api/issues (or /api/alerts/{id}/issues when alert_id given). The
1200
1370
  alert-scoped path honours ``state``/``limit`` too — pass them so the filters aren't silently
1201
1371
  dropped on that path (``_get_json`` omits None params)."""
1202
- if alert_id:
1372
+ extra = {k: v for k, v in (filters or {}).items() if v is not None}
1373
+ if alert_id and not extra:
1203
1374
  data = _get_json(ctx, f"/api/alerts/{alert_id}/issues", {"state": state, "limit": limit})
1375
+ elif alert_id:
1376
+ # The board's own filters live on /issues, which takes alert_id as one of them.
1377
+ data = _get_json(ctx, "/api/issues", {"state": state, "limit": limit, "alert_id": alert_id, **extra})
1204
1378
  else:
1205
- data = _get_json(ctx, "/api/issues", {"state": state, "limit": limit})
1379
+ data = _get_json(ctx, "/api/issues", {"state": state, "limit": limit, **extra})
1206
1380
  return [Incident.from_dict(i) for i in (data if isinstance(data, list) else [])]
1207
1381
 
1208
1382
 
1209
- def count_incidents(ctx: ClientContext, *, state: Optional[str] = None) -> int:
1383
+ def count_incidents(
1384
+ ctx: ClientContext, *, state: Optional[str] = None, filters: Optional[Dict[str, Any]] = None,
1385
+ ) -> int:
1210
1386
  """GET /api/issues/count — {count}."""
1211
- data = _get_json(ctx, "/api/issues/count", {"state": state})
1387
+ extra = {k: v for k, v in (filters or {}).items() if v is not None}
1388
+ data = _get_json(ctx, "/api/issues/count", {"state": state, **extra})
1212
1389
  if not isinstance(data, dict):
1213
1390
  return 0
1214
1391
  try:
@@ -1532,6 +1709,8 @@ def agent_chat_oneshot(
1532
1709
  conversation_id: Optional[str] = None,
1533
1710
  page_context: Optional[str] = None,
1534
1711
  model: Optional[str] = None,
1712
+ on_ask_user: Optional[Callable[[Dict[str, Any]], None]] = None,
1713
+ on_artifact: Optional[Callable[[Dict[str, Any]], None]] = None,
1535
1714
  ) -> Dict[str, Any]:
1536
1715
  """POST /api/agent/chat — accumulate the streamed answer.
1537
1716
 
@@ -1551,20 +1730,32 @@ def agent_chat_oneshot(
1551
1730
  body["pageContext"] = page_context
1552
1731
  if model:
1553
1732
  body["model"] = model
1733
+ if on_artifact:
1734
+ body["pageActions"] = [{"action": "open_artifact"}, {"action": "home"}]
1554
1735
  parts: List[str] = []
1555
1736
  tools: List[str] = []
1556
1737
  interrupted = False
1557
1738
  error: Optional[str] = None
1558
- for ev in _stream_sse(ctx, "/api/agent/chat", body):
1739
+ for ev in cloud_stream(ctx, "/api/agent/chat", body):
1559
1740
  kind = ev.get("type")
1560
1741
  if kind == "text-delta":
1561
1742
  parts.append(str(ev.get("text", "")))
1562
1743
  elif kind == "tool-start":
1563
1744
  tools.append(str(ev.get("tool", "")))
1564
1745
  elif kind == "ask-user":
1746
+ if on_ask_user and ev.get("callId"):
1747
+ on_ask_user(ev)
1748
+ continue
1565
1749
  interrupted = True
1566
1750
  error = str(ev.get("question") or "the assistant needs interactive input")
1567
1751
  break
1752
+ elif kind == "ui-action" and on_artifact:
1753
+ if ev.get("action") == "open_artifact" and isinstance(ev.get("artifact"), dict):
1754
+ on_artifact(ev["artifact"])
1755
+ elif ev.get("action") == "home" and isinstance(ev.get("home"), dict):
1756
+ for block in ev["home"].get("blocks", []):
1757
+ if isinstance(block, dict):
1758
+ on_artifact(block)
1568
1759
  elif kind == "error":
1569
1760
  error = str(ev.get("message", "assistant error"))
1570
1761
  elif kind == "done":
@@ -1785,42 +1976,16 @@ def deploy_policies(
1785
1976
  ctx: ClientContext,
1786
1977
  machine_id: str,
1787
1978
  policies: Sequence[PolicyRef],
1788
- jev_mode: Optional[str] = None,
1789
1979
  ) -> Deployment:
1790
1980
  """PUT /api/enforcement/deployments/{id} — REPLACES the machine's whole set.
1791
1981
 
1792
- ``jev_mode`` (``off|observe|enforce|local``) is sent only when given: the
1793
- server reads an ABSENT ``jevMode`` as "keep the machine's current one", so a
1794
- deploy that says nothing about Jev never changes it. ``local`` stops
1795
- FailproofAI Cloud overriding the machine's own mode.
1982
+ Never carries a Jev mode: FailproofAI Cloud derives it from the set.
1796
1983
  """
1797
1984
  path = f"/api/enforcement/deployments/{machine_id}"
1798
1985
  body: Dict[str, Any] = {"policies": [p.to_dict() for p in policies]}
1799
- if jev_mode is not None:
1800
- body["jevMode"] = jev_mode
1801
1986
  return Deployment.from_dict(_request_json(ctx, "PUT", path, json_body=body) or {})
1802
1987
 
1803
1988
 
1804
- def set_jev_mode(ctx: ClientContext, machine_id: str, jev_mode: str) -> Tuple[Deployment, bool]:
1805
- """PUT /api/enforcement/deployments/{id}/jev-mode — change ONLY the Jev mode.
1806
-
1807
- ``jev_mode`` is ``off|observe|enforce|local`` (``local`` stops FailproofAI
1808
- Cloud overriding the machine's own mode). Unlike a deploy, this never
1809
- touches the machine's policy set: a deploy is a full replace of the set, and
1810
- the set a client can read leaves out disabled policies' assignments, so a
1811
- mode change sent as a deploy deleted them — and reverted any edit made since
1812
- the set was read. It still mints a new generation, which is how the machine
1813
- picks the mode up.
1814
-
1815
- Returns the deployment and whether anything changed: setting the mode the
1816
- machine already has changes nothing and mints nothing (``changed: false``).
1817
- A machine with no deployment is a 404 — a deploy is how it gets its first.
1818
- """
1819
- path = f"/api/enforcement/deployments/{machine_id}/jev-mode"
1820
- data = _request_json(ctx, "PUT", path, json_body={"jevMode": jev_mode}) or {}
1821
- return Deployment.from_dict(data), bool(data.get("changed", True))
1822
-
1823
-
1824
1989
  def deployment_history(ctx: ClientContext, machine_id: str) -> List[Dict[str, Any]]:
1825
1990
  """GET /api/enforcement/deployments/{id}/history — every generation, newest first."""
1826
1991
  path = f"/api/enforcement/deployments/{machine_id}/history"