failproofai 1.0.12-beta.2 → 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 (196) 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.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  51. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  54. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00l2u6j86cb8y._.js → [root-of-the-server]__167mipqmjprq5._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_1fw0_nzkae5ao._.js → _1d98fyfrcaqhy._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.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 +1 -1
  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_1fv03uigni8s5.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  87. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  88. package/.next/standalone/.next/server/pages/404.html +1 -1
  89. package/.next/standalone/.next/server/pages/500.html +1 -1
  90. package/.next/standalone/.next/server/preview-props.json +3 -3
  91. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  93. package/.next/standalone/.next/static/chunks/{1c_jf6g5wnslk.js → 0el9n3h9_zp5m.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
  95. package/.next/standalone/.next/static/chunks/{2o6d37j425uci.js → 0umzct31duk5g.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{42xptz88mtq3x.js → 0wobqgosirs8p.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{0ay__-d2h1xb5.js → 1kduj74-715jh.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{40oworn_-x12p.js → 2701amhrbsbot.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/{0tbm3vjm62h-2.js → 27cavoc8paelg.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{1my3-5p47t7r-.js → 2ve23a7keupjw.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/{21hckpmsc4g70.js → 3bv6xaoitjpnh.js} +2 -2
  103. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +19 -0
  104. package/.next/standalone/fp-cloud-cli/README.md +120 -0
  105. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
  106. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +198 -7
  107. package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
  108. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
  109. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
  110. package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
  111. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
  112. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
  113. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
  114. package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
  115. package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
  116. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
  118. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
  119. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
  120. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
  121. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
  122. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
  123. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
  124. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
  125. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
  126. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
  127. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
  128. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
  129. package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
  130. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +187 -81
  131. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
  132. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
  133. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
  134. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +168 -16
  135. package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
  136. package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
  137. package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
  138. package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
  139. package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
  140. package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
  141. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
  142. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
  143. package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
  144. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
  145. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
  146. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
  147. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
  148. package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
  149. package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
  150. package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
  151. package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
  152. package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
  153. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
  154. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  155. package/.next/standalone/package.json +9 -9
  156. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
  157. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
  158. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
  159. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  160. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
  161. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
  162. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
  163. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
  164. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
  165. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
  166. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
  167. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
  168. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
  169. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
  171. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
  172. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
  173. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
  174. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
  175. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
  176. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
  177. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
  178. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
  179. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
  180. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
  181. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
  182. package/.next/standalone/server.js +1 -1
  183. package/dist/cli.mjs +85 -37
  184. package/dist/worker.mjs +73 -28
  185. package/hermes-plugin/plugin.yaml +1 -1
  186. package/package.json +9 -9
  187. package/src/hooks/agent-skill.ts +3 -0
  188. package/src/hooks/fp-home.ts +1 -1
  189. package/src/hooks/handler.ts +22 -3
  190. package/src/hooks/semantic/intent.ts +127 -24
  191. package/src/hooks/semantic/jev-review.ts +21 -2
  192. package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +0 -1
  193. package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +0 -1
  194. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
  195. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
  196. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
@@ -17,12 +17,15 @@ from . import orgs as orgsmod
17
17
  from ._context import AppState
18
18
  from ._version import __version__
19
19
  from .commands import (
20
+ account_cmds,
20
21
  agent_cmds,
21
22
  alerts_cmds,
22
23
  audits_cmds,
23
24
  auth_cmds,
25
+ dashboards_cmds,
24
26
  errors_cmds,
25
27
  evals_cmds,
28
+ evaluators_cmds,
26
29
  events_cmds,
27
30
  fleet_cmds,
28
31
  guardrails_cmds,
@@ -33,6 +36,7 @@ from .commands import (
33
36
  policies_cmds,
34
37
  queries_cmds,
35
38
  sessions_cmds,
39
+ sentiment_cmds,
36
40
  settings_cmds,
37
41
  usage_cmds,
38
42
  users_cmds,
@@ -128,6 +132,12 @@ app = typer.Typer(
128
132
  # function Typer calls for this; falls back silently if its internals change.
129
133
  try: # pragma: no cover - exercised via real command output, not unit assertions
130
134
  import typer.rich_utils as _rich_utils
135
+ # Keep generated subcommand help on the same shared screen primitives.
136
+ _rich_utils.Panel = output.Panel
137
+ _rich_utils.Table = output.Table
138
+ _rich_utils.STYLE_OPTION = output.theme.PINK
139
+ _rich_utils.STYLE_COMMANDS_TABLE_FIRST_COLUMN = output.theme.PINK
140
+ _rich_utils.STYLE_OPTION_ENVVAR = output.theme.LABEL
131
141
 
132
142
  import re as _re
133
143
 
@@ -327,7 +337,7 @@ def main(
327
337
  "must be a positive number of seconds.", param_hint="--timeout"
328
338
  )
329
339
  cfg = cfgmod.load_config()
330
- output.configure(no_color=no_color, quiet=quiet, json=json_output)
340
+ output.configure(no_color=no_color, quiet=quiet, json=json_output, context=ctx.invoked_subcommand or "Cloud")
331
341
  # Resolve the credential BEFORE telemetry starts: the distinct-id depends on it.
332
342
  # `get_parameter_source` is what separates a flag from its env var — the values
333
343
  # alone cannot, and the precedence rules differ between the two (see resolve_auth).
@@ -452,6 +462,9 @@ orgs_cmds.register(app)
452
462
  events_cmds.register(app)
453
463
  sessions_cmds.register(app)
454
464
  evals_cmds.register(app)
465
+ evaluators_cmds.register(app)
466
+ dashboards_cmds.register(app)
467
+ sentiment_cmds.register(app)
455
468
  errors_cmds.register(app)
456
469
  usage_cmds.register(app)
457
470
  list_cmds.register(app)
@@ -466,6 +479,7 @@ agent_cmds.register(app)
466
479
  policies_cmds.register(app)
467
480
  fleet_cmds.register(app)
468
481
  guardrails_cmds.register(app)
482
+ account_cmds.register(app)
469
483
 
470
484
 
471
485
  def _elapsed_ms(start: float) -> int:
@@ -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":