aui-agent-builder 0.4.0-alpha.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +608 -114
  3. package/dist/api-client/apollo-client.d.ts +427 -0
  4. package/dist/api-client/apollo-client.d.ts.map +1 -0
  5. package/dist/api-client/apollo-client.js +347 -0
  6. package/dist/api-client/apollo-client.js.map +1 -0
  7. package/dist/api-client/index.d.ts +541 -21
  8. package/dist/api-client/index.d.ts.map +1 -1
  9. package/dist/api-client/index.js +956 -50
  10. package/dist/api-client/index.js.map +1 -1
  11. package/dist/api-client/kb-view-client.d.ts +94 -19
  12. package/dist/api-client/kb-view-client.d.ts.map +1 -1
  13. package/dist/api-client/kb-view-client.js +332 -35
  14. package/dist/api-client/kb-view-client.js.map +1 -1
  15. package/dist/api-client/rag-client.d.ts +28 -60
  16. package/dist/api-client/rag-client.d.ts.map +1 -1
  17. package/dist/api-client/rag-client.js +46 -42
  18. package/dist/api-client/rag-client.js.map +1 -1
  19. package/dist/commands/account.d.ts +11 -4
  20. package/dist/commands/account.d.ts.map +1 -1
  21. package/dist/commands/account.js +76 -59
  22. package/dist/commands/account.js.map +1 -1
  23. package/dist/commands/agents.d.ts +66 -2
  24. package/dist/commands/agents.d.ts.map +1 -1
  25. package/dist/commands/agents.js +706 -92
  26. package/dist/commands/agents.js.map +1 -1
  27. package/dist/commands/apollo.d.ts +185 -0
  28. package/dist/commands/apollo.d.ts.map +1 -0
  29. package/dist/commands/apollo.js +682 -0
  30. package/dist/commands/apollo.js.map +1 -0
  31. package/dist/commands/env.js +1 -1
  32. package/dist/commands/env.js.map +1 -1
  33. package/dist/commands/import-agent.d.ts +31 -0
  34. package/dist/commands/import-agent.d.ts.map +1 -1
  35. package/dist/commands/import-agent.js +1068 -283
  36. package/dist/commands/import-agent.js.map +1 -1
  37. package/dist/commands/index.d.ts +3 -3
  38. package/dist/commands/index.d.ts.map +1 -1
  39. package/dist/commands/index.js +3 -3
  40. package/dist/commands/index.js.map +1 -1
  41. package/dist/commands/init.d.ts.map +1 -1
  42. package/dist/commands/init.js +403 -94
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/integration-mcp-test.d.ts +68 -0
  45. package/dist/commands/integration-mcp-test.d.ts.map +1 -0
  46. package/dist/commands/integration-mcp-test.js +885 -0
  47. package/dist/commands/integration-mcp-test.js.map +1 -0
  48. package/dist/commands/integration-mcp-url.d.ts +40 -0
  49. package/dist/commands/integration-mcp-url.d.ts.map +1 -0
  50. package/dist/commands/integration-mcp-url.js +162 -0
  51. package/dist/commands/integration-mcp-url.js.map +1 -0
  52. package/dist/commands/integration-test.d.ts +108 -0
  53. package/dist/commands/integration-test.d.ts.map +1 -0
  54. package/dist/commands/integration-test.js +251 -0
  55. package/dist/commands/integration-test.js.map +1 -0
  56. package/dist/commands/integration-toolkits.d.ts +35 -0
  57. package/dist/commands/integration-toolkits.d.ts.map +1 -0
  58. package/dist/commands/integration-toolkits.js +101 -0
  59. package/dist/commands/integration-toolkits.js.map +1 -0
  60. package/dist/commands/integration-tools.d.ts +34 -0
  61. package/dist/commands/integration-tools.d.ts.map +1 -0
  62. package/dist/commands/integration-tools.js +108 -0
  63. package/dist/commands/integration-tools.js.map +1 -0
  64. package/dist/commands/integration.d.ts +83 -9
  65. package/dist/commands/integration.d.ts.map +1 -1
  66. package/dist/commands/integration.js +700 -252
  67. package/dist/commands/integration.js.map +1 -1
  68. package/dist/commands/legacy/push-records-mode.d.ts +166 -0
  69. package/dist/commands/legacy/push-records-mode.d.ts.map +1 -0
  70. package/dist/commands/legacy/push-records-mode.js +2621 -0
  71. package/dist/commands/legacy/push-records-mode.js.map +1 -0
  72. package/dist/commands/login.d.ts.map +1 -1
  73. package/dist/commands/login.js +34 -5
  74. package/dist/commands/login.js.map +1 -1
  75. package/dist/commands/pull-agent.d.ts +10 -0
  76. package/dist/commands/pull-agent.d.ts.map +1 -1
  77. package/dist/commands/pull-agent.js +713 -178
  78. package/dist/commands/pull-agent.js.map +1 -1
  79. package/dist/commands/push.d.ts +91 -6
  80. package/dist/commands/push.d.ts.map +1 -1
  81. package/dist/commands/push.js +1514 -1144
  82. package/dist/commands/push.js.map +1 -1
  83. package/dist/commands/rag.d.ts +1 -0
  84. package/dist/commands/rag.d.ts.map +1 -1
  85. package/dist/commands/rag.js +92 -36
  86. package/dist/commands/rag.js.map +1 -1
  87. package/dist/commands/report.d.ts +138 -0
  88. package/dist/commands/report.d.ts.map +1 -0
  89. package/dist/commands/report.js +205 -0
  90. package/dist/commands/report.js.map +1 -0
  91. package/dist/commands/serve.d.ts.map +1 -1
  92. package/dist/commands/serve.js +1 -4
  93. package/dist/commands/serve.js.map +1 -1
  94. package/dist/commands/sync-session.d.ts +13 -0
  95. package/dist/commands/sync-session.d.ts.map +1 -0
  96. package/dist/commands/sync-session.js +56 -0
  97. package/dist/commands/sync-session.js.map +1 -0
  98. package/dist/commands/upgrade.d.ts.map +1 -1
  99. package/dist/commands/upgrade.js +1 -1
  100. package/dist/commands/upgrade.js.map +1 -1
  101. package/dist/commands/util/agent-mode.d.ts +69 -0
  102. package/dist/commands/util/agent-mode.d.ts.map +1 -0
  103. package/dist/commands/util/agent-mode.js +159 -0
  104. package/dist/commands/util/agent-mode.js.map +1 -0
  105. package/dist/commands/util/agent-resolve.d.ts +102 -0
  106. package/dist/commands/util/agent-resolve.d.ts.map +1 -0
  107. package/dist/commands/util/agent-resolve.js +148 -0
  108. package/dist/commands/util/agent-resolve.js.map +1 -0
  109. package/dist/commands/util/apollo-agent.d.ts +70 -0
  110. package/dist/commands/util/apollo-agent.d.ts.map +1 -0
  111. package/dist/commands/util/apollo-agent.js +100 -0
  112. package/dist/commands/util/apollo-agent.js.map +1 -0
  113. package/dist/commands/validate.d.ts +129 -9
  114. package/dist/commands/validate.d.ts.map +1 -1
  115. package/dist/commands/validate.js +666 -804
  116. package/dist/commands/validate.js.map +1 -1
  117. package/dist/commands/version-snapshot.d.ts +21 -0
  118. package/dist/commands/version-snapshot.d.ts.map +1 -0
  119. package/dist/commands/version-snapshot.js +948 -0
  120. package/dist/commands/version-snapshot.js.map +1 -0
  121. package/dist/commands/version.d.ts +15 -2
  122. package/dist/commands/version.d.ts.map +1 -1
  123. package/dist/commands/version.js +439 -206
  124. package/dist/commands/version.js.map +1 -1
  125. package/dist/config/index.d.ts +96 -5
  126. package/dist/config/index.d.ts.map +1 -1
  127. package/dist/config/index.js +97 -59
  128. package/dist/config/index.js.map +1 -1
  129. package/dist/errors/index.d.ts +1 -12
  130. package/dist/errors/index.d.ts.map +1 -1
  131. package/dist/errors/index.js +90 -8
  132. package/dist/errors/index.js.map +1 -1
  133. package/dist/index.d.ts +2 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1066 -174
  136. package/dist/index.js.map +1 -1
  137. package/dist/services/account.service.js +1 -1
  138. package/dist/services/account.service.js.map +1 -1
  139. package/dist/services/agents.service.d.ts.map +1 -1
  140. package/dist/services/agents.service.js +11 -5
  141. package/dist/services/agents.service.js.map +1 -1
  142. package/dist/services/auth.service.d.ts +78 -0
  143. package/dist/services/auth.service.d.ts.map +1 -1
  144. package/dist/services/auth.service.js +290 -16
  145. package/dist/services/auth.service.js.map +1 -1
  146. package/dist/services/integration.service.d.ts +374 -22
  147. package/dist/services/integration.service.d.ts.map +1 -1
  148. package/dist/services/integration.service.js +643 -95
  149. package/dist/services/integration.service.js.map +1 -1
  150. package/dist/services/kb-view.service.d.ts +17 -30
  151. package/dist/services/kb-view.service.d.ts.map +1 -1
  152. package/dist/services/kb-view.service.js +191 -118
  153. package/dist/services/kb-view.service.js.map +1 -1
  154. package/dist/services/list-agents.service.d.ts +10 -0
  155. package/dist/services/list-agents.service.d.ts.map +1 -1
  156. package/dist/services/list-agents.service.js +16 -21
  157. package/dist/services/list-agents.service.js.map +1 -1
  158. package/dist/services/pull-schema.service.d.ts +20 -1
  159. package/dist/services/pull-schema.service.d.ts.map +1 -1
  160. package/dist/services/pull-schema.service.js +313 -121
  161. package/dist/services/pull-schema.service.js.map +1 -1
  162. package/dist/services/rag.service.d.ts +21 -8
  163. package/dist/services/rag.service.d.ts.map +1 -1
  164. package/dist/services/rag.service.js +43 -16
  165. package/dist/services/rag.service.js.map +1 -1
  166. package/dist/services/status.service.d.ts +8 -0
  167. package/dist/services/status.service.d.ts.map +1 -1
  168. package/dist/services/status.service.js +16 -1
  169. package/dist/services/status.service.js.map +1 -1
  170. package/dist/services/sync-session.service.d.ts +38 -0
  171. package/dist/services/sync-session.service.d.ts.map +1 -0
  172. package/dist/services/sync-session.service.js +93 -0
  173. package/dist/services/sync-session.service.js.map +1 -0
  174. package/dist/telemetry.d.ts +169 -2
  175. package/dist/telemetry.d.ts.map +1 -1
  176. package/dist/telemetry.js +878 -4
  177. package/dist/telemetry.js.map +1 -1
  178. package/dist/types/entity.d.ts +21 -0
  179. package/dist/types/entity.d.ts.map +1 -1
  180. package/dist/ui/components/Banner.js +1 -1
  181. package/dist/ui/components/Banner.js.map +1 -1
  182. package/dist/ui/components/EnvironmentBadge.d.ts +8 -0
  183. package/dist/ui/components/EnvironmentBadge.d.ts.map +1 -0
  184. package/dist/ui/components/EnvironmentBadge.js +11 -0
  185. package/dist/ui/components/EnvironmentBadge.js.map +1 -0
  186. package/dist/ui/components/index.d.ts +1 -0
  187. package/dist/ui/components/index.d.ts.map +1 -1
  188. package/dist/ui/components/index.js +1 -0
  189. package/dist/ui/components/index.js.map +1 -1
  190. package/dist/ui/theme.d.ts +13 -0
  191. package/dist/ui/theme.d.ts.map +1 -1
  192. package/dist/ui/theme.js +12 -0
  193. package/dist/ui/theme.js.map +1 -1
  194. package/dist/ui/views/AccountView.js +1 -1
  195. package/dist/ui/views/AccountView.js.map +1 -1
  196. package/dist/ui/views/EnvView.d.ts.map +1 -1
  197. package/dist/ui/views/EnvView.js +2 -2
  198. package/dist/ui/views/EnvView.js.map +1 -1
  199. package/dist/ui/views/ImportAgentView.d.ts +15 -0
  200. package/dist/ui/views/ImportAgentView.d.ts.map +1 -1
  201. package/dist/ui/views/ImportAgentView.js +8 -3
  202. package/dist/ui/views/ImportAgentView.js.map +1 -1
  203. package/dist/ui/views/IntegrationView.d.ts +3 -1
  204. package/dist/ui/views/IntegrationView.d.ts.map +1 -1
  205. package/dist/ui/views/IntegrationView.js +2 -2
  206. package/dist/ui/views/IntegrationView.js.map +1 -1
  207. package/dist/ui/views/LoginView.d.ts +2 -1
  208. package/dist/ui/views/LoginView.d.ts.map +1 -1
  209. package/dist/ui/views/LoginView.js +20 -14
  210. package/dist/ui/views/LoginView.js.map +1 -1
  211. package/dist/ui/views/PushView.d.ts +3 -1
  212. package/dist/ui/views/PushView.d.ts.map +1 -1
  213. package/dist/ui/views/PushView.js +8 -2
  214. package/dist/ui/views/PushView.js.map +1 -1
  215. package/dist/ui/views/RagView.d.ts +6 -1
  216. package/dist/ui/views/RagView.d.ts.map +1 -1
  217. package/dist/ui/views/RagView.js +23 -0
  218. package/dist/ui/views/RagView.js.map +1 -1
  219. package/dist/ui/views/StatusView.js +4 -4
  220. package/dist/ui/views/StatusView.js.map +1 -1
  221. package/dist/ui/views/SyncSessionView.d.ts +7 -0
  222. package/dist/ui/views/SyncSessionView.d.ts.map +1 -0
  223. package/dist/ui/views/SyncSessionView.js +10 -0
  224. package/dist/ui/views/SyncSessionView.js.map +1 -0
  225. package/dist/utils/agent-injection.d.ts +49 -0
  226. package/dist/utils/agent-injection.d.ts.map +1 -0
  227. package/dist/utils/agent-injection.js +83 -0
  228. package/dist/utils/agent-injection.js.map +1 -0
  229. package/dist/utils/fetch-with-timeout.d.ts +57 -0
  230. package/dist/utils/fetch-with-timeout.d.ts.map +1 -0
  231. package/dist/utils/fetch-with-timeout.js +125 -0
  232. package/dist/utils/fetch-with-timeout.js.map +1 -0
  233. package/dist/utils/help-json.d.ts +44 -0
  234. package/dist/utils/help-json.d.ts.map +1 -0
  235. package/dist/utils/help-json.js +62 -0
  236. package/dist/utils/help-json.js.map +1 -0
  237. package/dist/utils/index.d.ts +82 -18
  238. package/dist/utils/index.d.ts.map +1 -1
  239. package/dist/utils/index.js +387 -80
  240. package/dist/utils/index.js.map +1 -1
  241. package/dist/utils/json-output.d.ts +10 -0
  242. package/dist/utils/json-output.d.ts.map +1 -1
  243. package/dist/utils/json-output.js +14 -0
  244. package/dist/utils/json-output.js.map +1 -1
  245. package/dist/utils/payload-store.d.ts +40 -0
  246. package/dist/utils/payload-store.d.ts.map +1 -0
  247. package/dist/utils/payload-store.js +82 -0
  248. package/dist/utils/payload-store.js.map +1 -0
  249. package/dist/utils/request-capture.d.ts +26 -0
  250. package/dist/utils/request-capture.d.ts.map +1 -1
  251. package/dist/utils/request-capture.js +67 -0
  252. package/dist/utils/request-capture.js.map +1 -1
  253. package/dist/utils/select-prompt.d.ts +32 -0
  254. package/dist/utils/select-prompt.d.ts.map +1 -0
  255. package/dist/utils/select-prompt.js +71 -0
  256. package/dist/utils/select-prompt.js.map +1 -0
  257. package/dist/utils/update-notifier.d.ts.map +1 -1
  258. package/dist/utils/update-notifier.js +30 -3
  259. package/dist/utils/update-notifier.js.map +1 -1
  260. package/dist/web/assets/{index-DSg2xrPw.js → index-n4de71HW.js} +1 -1
  261. package/dist/web/index.html +1 -1
  262. package/package.json +10 -6
  263. package/dist/commands/add-integration.d.ts +0 -20
  264. package/dist/commands/add-integration.d.ts.map +0 -1
  265. package/dist/commands/add-integration.js +0 -385
  266. package/dist/commands/add-integration.js.map +0 -1
  267. package/dist/commands/add-tool.d.ts +0 -17
  268. package/dist/commands/add-tool.d.ts.map +0 -1
  269. package/dist/commands/add-tool.js +0 -225
  270. package/dist/commands/add-tool.js.map +0 -1
  271. package/dist/commands/chat.d.ts +0 -20
  272. package/dist/commands/chat.d.ts.map +0 -1
  273. package/dist/commands/chat.js +0 -545
  274. package/dist/commands/chat.js.map +0 -1
  275. package/dist/commands/create-agent.d.ts +0 -17
  276. package/dist/commands/create-agent.d.ts.map +0 -1
  277. package/dist/commands/create-agent.js +0 -18
  278. package/dist/commands/create-agent.js.map +0 -1
  279. package/dist/commands/lsp.d.ts +0 -13
  280. package/dist/commands/lsp.d.ts.map +0 -1
  281. package/dist/commands/lsp.js +0 -17
  282. package/dist/commands/lsp.js.map +0 -1
  283. package/dist/commands/widget.d.ts +0 -18
  284. package/dist/commands/widget.d.ts.map +0 -1
  285. package/dist/commands/widget.js +0 -153
  286. package/dist/commands/widget.js.map +0 -1
  287. package/dist/lsp/cross-refs.d.ts +0 -53
  288. package/dist/lsp/cross-refs.d.ts.map +0 -1
  289. package/dist/lsp/cross-refs.js +0 -330
  290. package/dist/lsp/cross-refs.js.map +0 -1
  291. package/dist/lsp/cross-refs.test.d.ts +0 -2
  292. package/dist/lsp/cross-refs.test.d.ts.map +0 -1
  293. package/dist/lsp/cross-refs.test.js +0 -130
  294. package/dist/lsp/cross-refs.test.js.map +0 -1
  295. package/dist/lsp/schemas.d.ts +0 -57
  296. package/dist/lsp/schemas.d.ts.map +0 -1
  297. package/dist/lsp/schemas.js +0 -126
  298. package/dist/lsp/schemas.js.map +0 -1
  299. package/dist/lsp/schemas.test.d.ts +0 -2
  300. package/dist/lsp/schemas.test.d.ts.map +0 -1
  301. package/dist/lsp/schemas.test.js +0 -74
  302. package/dist/lsp/schemas.test.js.map +0 -1
  303. package/dist/lsp/server.d.ts +0 -11
  304. package/dist/lsp/server.d.ts.map +0 -1
  305. package/dist/lsp/server.js +0 -205
  306. package/dist/lsp/server.js.map +0 -1
  307. package/dist/ui/views/ChatView.d.ts +0 -26
  308. package/dist/ui/views/ChatView.d.ts.map +0 -1
  309. package/dist/ui/views/ChatView.js +0 -96
  310. package/dist/ui/views/ChatView.js.map +0 -1
  311. package/dist/ui/views/__tests__/StatusView.test.d.ts +0 -2
  312. package/dist/ui/views/__tests__/StatusView.test.d.ts.map +0 -1
  313. package/dist/ui/views/__tests__/StatusView.test.js +0 -158
  314. package/dist/ui/views/__tests__/StatusView.test.js.map +0 -1
@@ -13,12 +13,13 @@
13
13
  * - x-aui-environment: "staging" | "production"
14
14
  * - x-aui-origin: "stores"
15
15
  */
16
- import fetch from "node-fetch";
16
+ import fetch, { FetchTimeoutError } from "../utils/fetch-with-timeout.js";
17
17
  import { randomUUID } from "crypto";
18
18
  import * as fs from "fs";
19
19
  import * as path from "path";
20
20
  import { getAgentSettingsBaseUrl, getAgentSettingsWriteUrl, getAgentManagementBaseUrl, } from "../config/index.js";
21
- import { captureRequest } from "../utils/request-capture.js";
21
+ import { captureRequest, formatAsCurlSafe } from "../utils/request-capture.js";
22
+ import { trace } from "@opentelemetry/api";
22
23
  // ─── Helpers ───
23
24
  function stripTestCurlResponse(settings) {
24
25
  if (!settings || typeof settings !== "object")
@@ -72,6 +73,22 @@ export class AUIClient {
72
73
  if (scope.organizationId)
73
74
  this.organizationId = scope.organizationId;
74
75
  }
76
+ /**
77
+ * Describe the request a given call WOULD send — the resolved URL, the
78
+ * exact headers, the method, and the body — without issuing it. Lets a
79
+ * caller attach an `aui.curl_repro` / `http.url` to its command span
80
+ * (the secrets in the headers are redacted by `formatAsCurlSafe`) so the
81
+ * endpoint a command hit is visible in Logfire, mirroring the preflight /
82
+ * validate telemetry. The URL is built the same way `request()` does.
83
+ */
84
+ previewRequest(method, endpoint, body) {
85
+ return {
86
+ method: method.toUpperCase(),
87
+ url: `${this.baseUrl}/${endpoint.replace(/^\/+/, "")}`,
88
+ headers: this.getHeaders(),
89
+ body,
90
+ };
91
+ }
75
92
  /**
76
93
  * Build request headers matching the real AUI API expectations
77
94
  */
@@ -91,9 +108,20 @@ export class AUIClient {
91
108
  }
92
109
  /**
93
110
  * Make an HTTP request to the AUI backend.
111
+ *
94
112
  * On 401, automatically attempts a token refresh and retries once.
113
+ *
114
+ * The internal options object explicitly carries `_isRetry` (this is the
115
+ * second attempt after a 401-triggered refresh) and `_isRefreshCall` (this
116
+ * IS the refresh call itself, so a 401 here must NOT trigger another
117
+ * refresh — that would cause stack-overflow). Previously the refresh call
118
+ * was identified by URL substring match on `"token/refresh"`, which is
119
+ * fragile against future endpoint renames; the explicit flag removes that
120
+ * footgun.
95
121
  */
96
- async request(method, endpoint, body, _isRetry = false) {
122
+ async request(method, endpoint, body, internal) {
123
+ const _isRetry = internal?._isRetry === true;
124
+ const _isRefreshCall = internal?._isRefreshCall === true;
97
125
  const url = `${this.baseUrl}/${endpoint.replace(/^\/+/, "")}`;
98
126
  const options = {
99
127
  method,
@@ -107,15 +135,18 @@ export class AUIClient {
107
135
  response = await fetch(url, options);
108
136
  }
109
137
  catch (error) {
138
+ // FetchTimeoutError already has a clear, structured message. Other
139
+ // errors get wrapped to keep the legacy "Network error: ..." prefix
140
+ // so existing callers / log scrapers don't break.
141
+ if (error instanceof FetchTimeoutError)
142
+ throw error;
110
143
  throw new Error(`Network error: ${error instanceof Error ? error.message : "Connection failed"}`);
111
144
  }
112
145
  if (!response.ok) {
113
- if (response.status === 401 &&
114
- !_isRetry &&
115
- !endpoint.includes("token/refresh")) {
146
+ if (response.status === 401 && !_isRetry && !_isRefreshCall) {
116
147
  const refreshed = await this.attemptTokenRefresh();
117
148
  if (refreshed) {
118
- return this.request(method, endpoint, body, true);
149
+ return this.request(method, endpoint, body, { _isRetry: true });
119
150
  }
120
151
  }
121
152
  let errorBody;
@@ -254,8 +285,32 @@ export class AUIClient {
254
285
  loginWithOtp: (email, otp) => this.post("identity/user/login", { email, password: otp, isOTP: true }),
255
286
  /** Logout (invalidate session server-side) */
256
287
  logout: () => this.post("identity/user/logout", {}),
257
- /** Exchange a refresh token for a new access + refresh token pair */
258
- refreshToken: (refreshToken) => this.post("identity/user/token/refresh", { refreshToken }),
288
+ /**
289
+ * Exchange a refresh token for a new access + refresh token pair.
290
+ *
291
+ * Marked `_isRefreshCall: true` so the generic `request()` 401 handler
292
+ * NEVER tries to refresh-and-retry the refresh call itself (would recurse
293
+ * indefinitely on an expired refresh token). Internal refresh inside
294
+ * `_doTokenRefresh` bypasses `request()` entirely via direct `fetch`, but
295
+ * this public surface needs the same guarantee for external callers.
296
+ */
297
+ refreshToken: (refreshToken) => this.request("POST", "identity/user/token/refresh", { refreshToken }, { _isRefreshCall: true }),
298
+ /**
299
+ * Switch the active organization and mint a NEW access token scoped to
300
+ * the target org. This is the only way to obtain a token whose
301
+ * `organizationId` claim matches an org other than the one the user
302
+ * logged in with — the org-id *header* alone is advisory and the
303
+ * gateway trusts the token claim (so org-scoped runtime endpoints like
304
+ * Apollo's create-task 404 with "Agent not found" when the two
305
+ * diverge).
306
+ *
307
+ * Mirrors the playground's org-switch call. Authenticated by the
308
+ * caller's CURRENT token (sent as `x-access-token` by `getHeaders`);
309
+ * the TARGET org goes in the body as `{ organization }`. The response
310
+ * shape is read defensively by callers because the field nesting
311
+ * (`data.token` vs `token`) is owned by the identity service.
312
+ */
313
+ switchOrganization: (organizationId) => this.post("identity/user/switch-organization", { organization: organizationId }),
259
314
  };
260
315
  // ─── Organizations ───
261
316
  /** List and fetch organizations the current user belongs to */
@@ -322,22 +377,95 @@ export class AUIClient {
322
377
  });
323
378
  if (filters?.network_id)
324
379
  qs.set("network_id", filters.network_id);
380
+ if (filters?.scope_type)
381
+ qs.set("scope_type", filters.scope_type);
382
+ if (filters?.kind)
383
+ qs.set("kind", filters.kind);
384
+ if (filters?.network_category_id)
385
+ qs.set("network_category_id", filters.network_category_id);
386
+ if (filters?.account_id)
387
+ qs.set("account_id", filters.account_id);
388
+ if (filters?.name)
389
+ qs.set("name", filters.name);
325
390
  const url = `${getAgentManagementBaseUrl()}/v1/agents?${qs}`;
326
- const resp = await this.agentManagementFetch(url, {
391
+ // `list_agents` is the fallback resolver used by `aui pull` /
392
+ // `aui import-agent` when `.auirc` has no `agent_management_id`
393
+ // (legacy projects pre-PR #54). Full preflight telemetry —
394
+ // curl repro + truncated response — so Logfire shows which
395
+ // sibling agents the resolver saw and which one it picked.
396
+ // Mirrors the contract used by `get_agent` and `get_version`.
397
+ const { resp, responseText } = await this.preflightFetch({
398
+ name: "list_agents",
399
+ label: "list agents",
400
+ url,
327
401
  method: "GET",
328
- headers: this.agentManagementHeaders(),
329
- }, "list agents");
402
+ extraAttrs: {
403
+ organization_id: orgId,
404
+ page,
405
+ size,
406
+ ...(filters?.network_id
407
+ ? { filter_network_id: filters.network_id }
408
+ : {}),
409
+ ...(filters?.network_category_id
410
+ ? { filter_network_category_id: filters.network_category_id }
411
+ : {}),
412
+ ...(filters?.scope_type ? { filter_scope_type: filters.scope_type } : {}),
413
+ ...(filters?.kind ? { filter_kind: filters.kind } : {}),
414
+ ...(filters?.account_id ? { filter_account_id: filters.account_id } : {}),
415
+ ...(filters?.name ? { filter_name: filters.name } : {}),
416
+ },
417
+ extractAttrs: (parsed) => {
418
+ const out = {};
419
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
420
+ const obj = parsed;
421
+ if (Array.isArray(obj.items))
422
+ out.items_count = obj.items.length;
423
+ if (typeof obj.total === "number")
424
+ out.total = obj.total;
425
+ if (typeof obj.pages === "number")
426
+ out.pages = obj.pages;
427
+ // Capture the bundle_mode breakdown for the page — useful
428
+ // when the resolver picks the "wrong" sibling on a
429
+ // bundle/records mixed account.
430
+ if (Array.isArray(obj.items)) {
431
+ let bundleTrue = 0;
432
+ let bundleFalse = 0;
433
+ let bundleUnset = 0;
434
+ for (const it of obj.items) {
435
+ const bm = it && typeof it === "object"
436
+ ? it.bundle_mode
437
+ : undefined;
438
+ if (bm === true)
439
+ bundleTrue++;
440
+ else if (bm === false)
441
+ bundleFalse++;
442
+ else
443
+ bundleUnset++;
444
+ }
445
+ out.items_bundle_mode_true = bundleTrue;
446
+ out.items_bundle_mode_false = bundleFalse;
447
+ out.items_bundle_mode_unset = bundleUnset;
448
+ }
449
+ }
450
+ return out;
451
+ },
452
+ });
330
453
  if (!resp.ok) {
331
454
  let errorBody;
332
455
  try {
333
- errorBody = await resp.json();
456
+ errorBody = JSON.parse(responseText);
334
457
  }
335
458
  catch {
336
- errorBody = await resp.text();
459
+ errorBody = responseText || null;
337
460
  }
338
461
  throw new AUIAPIError(resp.status, `agents: ${resp.statusText}`, errorBody);
339
462
  }
340
- return (await resp.json());
463
+ try {
464
+ return JSON.parse(responseText);
465
+ }
466
+ catch {
467
+ throw new AUIAPIError(resp.status, `agents: invalid JSON response`, responseText);
468
+ }
341
469
  },
342
470
  listVersions: async (agentId, page = 1, size = 50, filters) => {
343
471
  const qs = new URLSearchParams({
@@ -387,22 +515,69 @@ export class AUIClient {
387
515
  return (await resp.json());
388
516
  },
389
517
  getAgent: async (agentId) => {
518
+ // `GET /v1/agents/{id}` is the dispatch oracle for the whole CLI —
519
+ // every `aui import-agent`, `aui pull`, and `aui push` hits it
520
+ // before touching the per-entity / bundle endpoints (see
521
+ // `commands/util/agent-mode.ts → detectAgentBundleMode`). When
522
+ // a user reports "the CLI hit the wrong push endpoint" or
523
+ // "import keeps failing on this agent", the FIRST question is
524
+ // "what did the get-agent response say (especially bundle_mode)?".
525
+ //
526
+ // Telemetry is wired through `preflightFetch` (shared with
527
+ // `getVersion` and `listAgents`), which attaches a span event
528
+ // with curl repro, status, duration, and response body. Using
529
+ // `addEvent` (vs `setAttribute`) preserves multiple per-command
530
+ // calls: push's `lookupAgentManagementInfoForPush` +
531
+ // `resolveVersionDraft` can call `getAgent` more than once, and
532
+ // we want each call visible as its own log entry under the
533
+ // parent span.
390
534
  const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}`;
391
- const resp = await this.agentManagementFetch(url, {
535
+ const { resp, responseText } = await this.preflightFetch({
536
+ name: "get_agent",
537
+ label: "get agent",
538
+ url,
392
539
  method: "GET",
393
- headers: this.agentManagementHeaders(),
394
- }, "get agent");
540
+ extraAttrs: { agent_id: agentId },
541
+ extractAttrs: (parsed, spanAttrs) => {
542
+ const out = {};
543
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
544
+ const obj = parsed;
545
+ if (typeof obj.bundle_mode === "boolean") {
546
+ out.bundle_mode = obj.bundle_mode;
547
+ // Also pin the most-recent observed mode at the span level
548
+ // so it's filterable without expanding the event list.
549
+ // Multiple calls per command overwrite — the last call
550
+ // wins, which is the one whose decision actually drove
551
+ // the dispatch.
552
+ spanAttrs["agent.bundle_mode"] = obj.bundle_mode;
553
+ }
554
+ if (typeof obj.name === "string")
555
+ out.agent_name = obj.name;
556
+ if (typeof obj.active_version_id === "string") {
557
+ out.active_version_id = obj.active_version_id;
558
+ }
559
+ if (typeof obj.kind === "string")
560
+ out.agent_kind = obj.kind;
561
+ }
562
+ return out;
563
+ },
564
+ });
395
565
  if (!resp.ok) {
396
566
  let errorBody;
397
567
  try {
398
- errorBody = await resp.json();
568
+ errorBody = JSON.parse(responseText);
399
569
  }
400
570
  catch {
401
- errorBody = await resp.text();
571
+ errorBody = responseText || null;
402
572
  }
403
573
  throw new AUIAPIError(resp.status, `get agent: ${resp.statusText}`, errorBody);
404
574
  }
405
- return (await resp.json());
575
+ try {
576
+ return JSON.parse(responseText);
577
+ }
578
+ catch {
579
+ throw new AUIAPIError(resp.status, `get agent: invalid JSON response`, responseText);
580
+ }
406
581
  },
407
582
  activateVersion: async (agentId, versionId) => {
408
583
  const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/active-version`;
@@ -443,22 +618,58 @@ export class AUIClient {
443
618
  return (await resp.json());
444
619
  },
445
620
  getVersion: async (agentId, versionId) => {
621
+ // `GET /v1/agents/{id}/versions/{id}` is the second preflight read
622
+ // every push/pull issues — it resolves the draft target (and
623
+ // returns the version's `status`, `version_number`, etc.). When
624
+ // a user reports "push failed with 422 not-a-draft" or "pull
625
+ // returned 404", the diff between WHAT the CLI saw here and WHAT
626
+ // it then sent on /push or /pull is the smoking gun. Full
627
+ // preflight telemetry — curl repro + truncated response — keeps
628
+ // both pieces of evidence in one Logfire row.
446
629
  const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}`;
447
- const resp = await this.agentManagementFetch(url, {
630
+ const { resp, responseText } = await this.preflightFetch({
631
+ name: "get_version",
632
+ label: "get version",
633
+ url,
448
634
  method: "GET",
449
- headers: this.agentManagementHeaders(),
450
- }, "get version");
635
+ extraAttrs: { agent_id: agentId, version_id: versionId },
636
+ extractAttrs: (parsed) => {
637
+ const out = {};
638
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
639
+ const obj = parsed;
640
+ if (typeof obj.status === "string")
641
+ out.status = obj.status;
642
+ if (typeof obj.version_number === "number") {
643
+ out.version_number = obj.version_number;
644
+ }
645
+ if (typeof obj.version_revision_number === "number") {
646
+ out.version_revision_number = obj.version_revision_number;
647
+ }
648
+ if (typeof obj.label === "string")
649
+ out.label = obj.label;
650
+ if (typeof obj.is_active === "boolean") {
651
+ out.is_active = obj.is_active;
652
+ }
653
+ }
654
+ return out;
655
+ },
656
+ });
451
657
  if (!resp.ok) {
452
658
  let errorBody;
453
659
  try {
454
- errorBody = await resp.json();
660
+ errorBody = JSON.parse(responseText);
455
661
  }
456
662
  catch {
457
- errorBody = await resp.text();
663
+ errorBody = responseText || null;
458
664
  }
459
665
  throw new AUIAPIError(resp.status, `get version: ${resp.statusText}`, errorBody);
460
666
  }
461
- return (await resp.json());
667
+ try {
668
+ return JSON.parse(responseText);
669
+ }
670
+ catch {
671
+ throw new AUIAPIError(resp.status, `get version: invalid JSON response`, responseText);
672
+ }
462
673
  },
463
674
  updateVersion: async (agentId, versionId, data) => {
464
675
  const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}`;
@@ -626,6 +837,404 @@ export class AUIClient {
626
837
  }
627
838
  return resp.json();
628
839
  },
840
+ /**
841
+ * POST /v1/agents/{agentId}/versions/{versionId}/push
842
+ *
843
+ * JSON-only upload (multipart was retired on 2026-05-20 — see
844
+ * Notion "Agent Settings Push/Pull" and the OpenAPI under
845
+ * `PushRequestSchema`).
846
+ *
847
+ * Server flow:
848
+ * 1. Validates the assembled `bundle` through the v1 transcoder
849
+ * BEFORE acquiring the per-version Redis lock — fail-fast 422
850
+ * with a structured `BundleValidationResult` and no GCS /
851
+ * Mongo writes.
852
+ * 2. Canonicalizes the bundle bytes (deterministic key order,
853
+ * no extra whitespace), hashes with SHA-256, and writes a
854
+ * single `bundle.json` blob to GCS under
855
+ * `agent_id={id}/version_id={id}/version_tag={tag}/bundle.json`.
856
+ * 3. Bumps `version_revision_number` by exactly 1 and returns the
857
+ * new `version_tag`, `gcs_key`, `sha256`, and `size` so
858
+ * clients can verify integrity locally.
859
+ *
860
+ * Request body (`PushRequestSchema`):
861
+ * - `caller` : "agent_builder" | "ui" | "cli" (required)
862
+ * - `commit_message` : optional save note (≤ 4000 chars after trim)
863
+ * - `bundle` : `AgentSettingsBundle` — the CLI assembles
864
+ * this from local `.aui.json` files (see
865
+ * `assembleBundleFromFiles` in `utils/`).
866
+ *
867
+ * Auth: Bearer JWT + `X-Organization-ID` (same as other v1 routes).
868
+ * The legacy `x-api-key` fallback is NOT honoured here — Bearer only.
869
+ * Push requires `update` on `agent_settings` for the target agent.
870
+ */
871
+ pushVersionBlobs: async (agentId, versionId, body) => {
872
+ const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/push`;
873
+ // Defensive normalization of the request body:
874
+ // - `caller` always set (TS already enforces it, but be belt+suspenders)
875
+ // - `commit_message` trimmed + capped (server caps too, but the
876
+ // CLI shouldn't make the server do work on bad input)
877
+ // - `pushed_by` forwarded as-is when the caller supplies it
878
+ // (added 2026-05-25 per backend team — recorded on the
879
+ // blob revision row so revision provenance ties back to a
880
+ // real user, not just a surface). Only emitted when set so
881
+ // older server versions that don't read the field still
882
+ // accept the request body unchanged.
883
+ // - `bundle.schema_version` defaults to 1 if the caller forgot
884
+ const reqBody = {
885
+ caller: body.caller,
886
+ ...(body.commit_message && body.commit_message.trim()
887
+ ? { commit_message: body.commit_message.trim().slice(0, 4000) }
888
+ : {}),
889
+ ...(body.pushed_by ? { pushed_by: body.pushed_by } : {}),
890
+ bundle: {
891
+ schema_version: body.bundle.schema_version ?? 1,
892
+ ...body.bundle,
893
+ },
894
+ };
895
+ if (process.env.AUI_DEBUG) {
896
+ const b = reqBody.bundle;
897
+ const summary = {
898
+ schema_version: b.schema_version,
899
+ general_settings: b.general_settings ? "<object>" : null,
900
+ parameters: b.parameters ? b.parameters.length : null,
901
+ entities: b.entities ? b.entities.length : null,
902
+ integrations: b.integrations ? b.integrations.length : null,
903
+ rules: b.rules ? b.rules.length : null,
904
+ tools: b.tools ? b.tools.length : null,
905
+ };
906
+ console.log(`[debug] POST ${url} as caller=${reqBody.caller} pushed_by=${reqBody.pushed_by ?? "(absent)"}; bundle:`, JSON.stringify(summary));
907
+ }
908
+ const headers = {
909
+ Accept: "application/json",
910
+ "Content-Type": "application/json",
911
+ Authorization: `Bearer ${this.authToken}`,
912
+ "X-Organization-ID": this.organizationId,
913
+ };
914
+ // Bundle uploads can be moderately large (every parameter +
915
+ // entity + tool encoded as JSON). Cap the request at 5 minutes —
916
+ // long enough for a slow E2B sandbox cold-start, short enough
917
+ // that a truly hung request surfaces before the BFF's own
918
+ // pipeline timeout (BFF caps `aui push` at 5 min in
919
+ // `agent-builder-bff`'s `sandbox-command-client.ts:auiPush`).
920
+ // Override via `AUI_PUSH_TIMEOUT_MS` env var if needed (legacy
921
+ // `AUI_SNAPSHOT_TIMEOUT_MS` honoured too for back-compat).
922
+ const pushTimeoutMs = (() => {
923
+ const fromEnv = process.env.AUI_PUSH_TIMEOUT_MS ?? process.env.AUI_SNAPSHOT_TIMEOUT_MS;
924
+ if (fromEnv) {
925
+ const parsed = parseInt(fromEnv, 10);
926
+ if (!Number.isNaN(parsed) && parsed >= 0)
927
+ return parsed;
928
+ }
929
+ return 300_000;
930
+ })();
931
+ const pushController = new AbortController();
932
+ const pushTimer = pushTimeoutMs > 0
933
+ ? setTimeout(() => pushController.abort(), pushTimeoutMs)
934
+ : null;
935
+ const pushStart = Date.now();
936
+ const serialized = JSON.stringify(reqBody);
937
+ let resp;
938
+ try {
939
+ resp = await globalThis.fetch(url, {
940
+ method: "POST",
941
+ headers,
942
+ body: serialized,
943
+ signal: pushController.signal,
944
+ });
945
+ }
946
+ catch (err) {
947
+ if (pushTimer)
948
+ clearTimeout(pushTimer);
949
+ const elapsedMs = Date.now() - pushStart;
950
+ const isAbort = err instanceof Error &&
951
+ (err.name === "AbortError" || pushController.signal.aborted);
952
+ if (isAbort && pushTimeoutMs > 0 && elapsedMs >= pushTimeoutMs - 50) {
953
+ const msg = `Push blob upload timed out after ${elapsedMs}ms (limit ${pushTimeoutMs}ms): POST ${url}. ` +
954
+ `Override with AUI_PUSH_TIMEOUT_MS=<ms> if your agent is unusually large.`;
955
+ captureRequest({
956
+ timestamp: new Date().toISOString(),
957
+ method: "POST",
958
+ url,
959
+ headers,
960
+ body: {
961
+ caller: reqBody.caller,
962
+ commit_message: reqBody.commit_message,
963
+ pushed_by: reqBody.pushed_by,
964
+ bundle_size_bytes: serialized.length,
965
+ },
966
+ status: 0,
967
+ responseBody: msg,
968
+ label: "push version blobs",
969
+ success: false,
970
+ });
971
+ return { success: false, error: msg };
972
+ }
973
+ throw err;
974
+ }
975
+ if (pushTimer)
976
+ clearTimeout(pushTimer);
977
+ // Read the body ONCE — used for telemetry, the error envelope,
978
+ // and the success parse. (Web fetch bodies are single-shot.)
979
+ const responseText = await resp.text();
980
+ // Summary body for both captureRequest and the span curl. We do
981
+ // NOT inline the full bundle (it can be megabytes) — the
982
+ // reproducible signal for a push is the endpoint + the RESPONSE
983
+ // (new_version_tag on success, or the 422 BundleValidationResult
984
+ // on failure), both of which we capture in full (truncated 32KB).
985
+ const pushSummaryBody = {
986
+ caller: reqBody.caller,
987
+ commit_message: reqBody.commit_message,
988
+ pushed_by: reqBody.pushed_by,
989
+ bundle_size_bytes: serialized.length,
990
+ };
991
+ captureRequest({
992
+ timestamp: new Date().toISOString(),
993
+ method: "POST",
994
+ url,
995
+ headers,
996
+ body: pushSummaryBody,
997
+ status: resp.status,
998
+ responseBody: responseText,
999
+ label: "push version blobs",
1000
+ success: resp.ok,
1001
+ });
1002
+ // Attach the replayable curl + response to the active span
1003
+ // (`aui.push.task.bundle`) for BOTH success and failure, so the
1004
+ // bundle-mode push endpoint is fully visible in Logfire — the
1005
+ // counterpart to the records-mode per-entity write curls.
1006
+ this.attachHttpCurlTelemetry({
1007
+ name: "push_bundle",
1008
+ label: "push version blobs",
1009
+ method: "POST",
1010
+ url,
1011
+ headers,
1012
+ requestBody: pushSummaryBody,
1013
+ responseText,
1014
+ status: resp.status,
1015
+ success: resp.ok,
1016
+ durationMs: Date.now() - pushStart,
1017
+ });
1018
+ if (!resp.ok) {
1019
+ let errorBody;
1020
+ try {
1021
+ errorBody = JSON.parse(responseText);
1022
+ }
1023
+ catch {
1024
+ errorBody = responseText;
1025
+ }
1026
+ if (process.env.AUI_DEBUG) {
1027
+ console.log(`[debug] push version blobs failed: ${resp.status} ${JSON.stringify(errorBody)}`);
1028
+ }
1029
+ // 422 has two flavours per the doc:
1030
+ // - request validation: { detail: "string" } or { detail: [{ loc, msg, type }] }
1031
+ // - bundle validation : BundleValidationResult JSON
1032
+ // Surface whichever is present without losing the structured body.
1033
+ const detail = errorBody?.detail;
1034
+ let errorMsg;
1035
+ if (Array.isArray(detail)) {
1036
+ errorMsg = detail
1037
+ .map((d) => `${d.msg ?? d.message ?? ""} (${(d.loc ?? []).join(".")})`)
1038
+ .join("; ");
1039
+ }
1040
+ else if (typeof detail === "string") {
1041
+ errorMsg = detail;
1042
+ }
1043
+ else if (typeof errorBody === "string") {
1044
+ errorMsg = errorBody;
1045
+ }
1046
+ else {
1047
+ errorMsg = JSON.stringify(errorBody);
1048
+ }
1049
+ return {
1050
+ success: false,
1051
+ error: `${resp.status}: ${errorMsg}`,
1052
+ data: errorBody,
1053
+ };
1054
+ }
1055
+ let responseData;
1056
+ try {
1057
+ responseData = (responseText ? JSON.parse(responseText) : undefined);
1058
+ }
1059
+ catch {
1060
+ responseData = undefined;
1061
+ }
1062
+ if (process.env.AUI_DEBUG) {
1063
+ console.log(`[debug] push version blobs succeeded: ${JSON.stringify(responseData)}`);
1064
+ }
1065
+ return { success: true, data: responseData };
1066
+ },
1067
+ /**
1068
+ * GET /v1/agents/{agentId}/versions/{versionId}/pull
1069
+ *
1070
+ * JSON-only read (signed-URL + `return_type=json` modes were
1071
+ * retired on 2026-05-20 — the new server returns the assembled
1072
+ * bundle inline as `PullReadResponse`).
1073
+ *
1074
+ * Caching: every `version_tag` is an immutable snapshot — Pull
1075
+ * tries Redis first. `expires_at` on the response is the snapshot
1076
+ * row's TTL, not a per-request expiry.
1077
+ *
1078
+ * Auth: Bearer JWT + `X-Organization-ID`. The legacy `x-api-key`
1079
+ * fallback is NOT honoured here — Bearer only. Pull requires
1080
+ * `read` on `agent_settings`.
1081
+ */
1082
+ pullVersionBlobs: async (agentId, versionId, options = {}) => {
1083
+ const qs = new URLSearchParams();
1084
+ if (options.versionTag)
1085
+ qs.set("version_tag", options.versionTag);
1086
+ const search = qs.toString();
1087
+ const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/pull${search ? `?${search}` : ""}`;
1088
+ if (process.env.AUI_DEBUG) {
1089
+ console.log(`[debug] GET ${url}`);
1090
+ }
1091
+ const pullHeaders = {
1092
+ Accept: "application/json",
1093
+ Authorization: `Bearer ${this.authToken}`,
1094
+ "X-Organization-ID": this.organizationId,
1095
+ };
1096
+ const pullStart = Date.now();
1097
+ const resp = await this.agentManagementFetch(url, { method: "GET", headers: pullHeaders }, "pull version blobs");
1098
+ // Read the body ONCE — used for telemetry, the error envelope,
1099
+ // and the success parse.
1100
+ const responseText = await resp.text();
1101
+ // Attach the replayable curl + response to the active span
1102
+ // (`aui.pull` / `aui.import`) for the bundle-mode `/pull` read —
1103
+ // the counterpart to the records-mode `/view` read curls, so the
1104
+ // import endpoint is fully visible in Logfire for both modes.
1105
+ this.attachHttpCurlTelemetry({
1106
+ name: "pull_bundle",
1107
+ label: "pull version blobs",
1108
+ method: "GET",
1109
+ url,
1110
+ headers: pullHeaders,
1111
+ responseText,
1112
+ status: resp.status,
1113
+ success: resp.ok,
1114
+ durationMs: Date.now() - pullStart,
1115
+ });
1116
+ if (!resp.ok) {
1117
+ let errorBody;
1118
+ try {
1119
+ errorBody = JSON.parse(responseText);
1120
+ }
1121
+ catch {
1122
+ errorBody = responseText;
1123
+ }
1124
+ throw new AUIAPIError(resp.status, `pull version blobs: ${resp.statusText}`, errorBody);
1125
+ }
1126
+ return JSON.parse(responseText);
1127
+ },
1128
+ /**
1129
+ * @deprecated Multipart-files upload was removed on 2026-05-20. The
1130
+ * new server only accepts the JSON `PushBundleRequest` shape via
1131
+ * {@link pushVersionBlobs}. Callers must assemble the bundle from
1132
+ * local files (see `utils/assembleBundleFromFiles`) and pass it
1133
+ * in directly.
1134
+ */
1135
+ pushSnapshot: async () => {
1136
+ throw new AUIAPIError(410, "pushSnapshot is gone. The multipart /push endpoint was retired on 2026-05-20; use pushVersionBlobs with an assembled AgentSettingsBundle.");
1137
+ },
1138
+ /**
1139
+ * @deprecated Signed-URL pull mode was removed on 2026-05-20. The
1140
+ * new server returns the assembled bundle inline as
1141
+ * {@link PullReadResponse}; consumers should call
1142
+ * {@link pullVersionBlobs} directly and read `response.bundle`
1143
+ * instead of paging through per-file signed URLs.
1144
+ */
1145
+ getSnapshot: async () => {
1146
+ throw new AUIAPIError(410, "getSnapshot is gone. Signed-URL pull mode was retired on 2026-05-20; use pullVersionBlobs and read response.bundle.");
1147
+ },
1148
+ /**
1149
+ * POST /v1/agents/{agentId}/validate
1150
+ *
1151
+ * Server-side validation for an agent bundle. The CLI assembles every
1152
+ * `.aui.json` file into a flat payload and forwards it as-is — the
1153
+ * endpoint runs the canonical pydantic + cross-reference checks that
1154
+ * used to be duplicated in the CLI (AJV + JS, see commented-out
1155
+ * `_validate` in commands/validate.tsx).
1156
+ *
1157
+ * The endpoint always returns 200 on success even when `valid: false`
1158
+ * (the body carries per-section errors/warnings). A non-2xx response
1159
+ * means the request itself was malformed (422) or the backend is down
1160
+ * (5xx) and we surface it as an AUIAPIError so the caller can attach a
1161
+ * "validation infra failure" message to its span instead of pretending
1162
+ * the agent is valid.
1163
+ */
1164
+ validateAgent: async (agentId, payload) => {
1165
+ // Validate is the only gate between local files and a partial-state
1166
+ // push, so its span needs to be self-contained for triage: full curl
1167
+ // repro (redacted), HTTP method/url/status, and the response body —
1168
+ // on every call, not just failures. The push-task spans already do
1169
+ // this; we mirror the contract here so any "why did validate say
1170
+ // valid:false?" question can be answered straight from Logfire
1171
+ // without re-running the CLI.
1172
+ const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/validate`;
1173
+ const method = "POST";
1174
+ const headers = this.agentManagementWriteHeaders();
1175
+ const bodyStr = JSON.stringify(payload);
1176
+ const startedAt = Date.now();
1177
+ let resp;
1178
+ try {
1179
+ resp = await this.agentManagementFetch(url, { method, headers, body: bodyStr }, "validate agent");
1180
+ }
1181
+ catch (err) {
1182
+ // Network-level failure (ECONNREFUSED, DNS, abort, ...). The
1183
+ // upstream catch in `_validate` already records status / message;
1184
+ // we attach the request-side telemetry (curl + payload size +
1185
+ // duration) before re-throwing so the trace tells the full story.
1186
+ const networkErr = err instanceof Error ? err : new Error(String(err));
1187
+ this.attachValidateTelemetry({
1188
+ method,
1189
+ url,
1190
+ headers,
1191
+ payload,
1192
+ requestBodyStr: bodyStr,
1193
+ responseText: networkErr.message,
1194
+ status: 0,
1195
+ success: false,
1196
+ durationMs: Date.now() - startedAt,
1197
+ });
1198
+ throw networkErr;
1199
+ }
1200
+ // Read once — Response bodies can only be consumed a single time
1201
+ // and we need the same string for telemetry, error envelopes, and
1202
+ // the success JSON parse below.
1203
+ let responseText;
1204
+ try {
1205
+ responseText = await resp.text();
1206
+ }
1207
+ catch (e) {
1208
+ responseText = "";
1209
+ }
1210
+ this.attachValidateTelemetry({
1211
+ method,
1212
+ url,
1213
+ headers,
1214
+ payload,
1215
+ requestBodyStr: bodyStr,
1216
+ responseText,
1217
+ status: resp.status,
1218
+ success: resp.ok,
1219
+ durationMs: Date.now() - startedAt,
1220
+ });
1221
+ if (!resp.ok) {
1222
+ let errorBody;
1223
+ try {
1224
+ errorBody = JSON.parse(responseText);
1225
+ }
1226
+ catch {
1227
+ errorBody = responseText || null;
1228
+ }
1229
+ throw new AUIAPIError(resp.status, `validate agent: ${resp.statusText}`, errorBody);
1230
+ }
1231
+ try {
1232
+ return JSON.parse(responseText);
1233
+ }
1234
+ catch {
1235
+ throw new AUIAPIError(resp.status, `validate agent: invalid JSON response`, responseText);
1236
+ }
1237
+ },
629
1238
  };
630
1239
  async agentManagementFetch(url, init, label, _isRetry = false) {
631
1240
  const resp = await fetch(url, init);
@@ -674,6 +1283,268 @@ export class AUIClient {
674
1283
  });
675
1284
  return resp;
676
1285
  }
1286
+ /**
1287
+ * Attach validate-call telemetry to the currently-active span. Called on
1288
+ * every validate request — success, validation-fail, and infra-fail —
1289
+ * so Logfire shows the full story (curl repro, request size, response
1290
+ * body, duration) without us having to re-run the CLI. Mirrors the
1291
+ * shape the `writeV2` failure-path telemetry uses for push tasks, with
1292
+ * the one difference that we attach on success too: validate is the
1293
+ * push safety gate, so even a `valid: true` response deserves a curl
1294
+ * in case the verdict was wrong (false negative) and someone needs to
1295
+ * audit later.
1296
+ *
1297
+ * Always wrapped in a try/catch — telemetry must never break a call.
1298
+ */
1299
+ attachValidateTelemetry(input) {
1300
+ try {
1301
+ const span = trace.getActiveSpan();
1302
+ if (!span)
1303
+ return;
1304
+ const MAX = 32 * 1024;
1305
+ const truncatedResponse = input.responseText.length > MAX
1306
+ ? input.responseText.slice(0, MAX) +
1307
+ `... [truncated ${input.responseText.length - MAX} bytes]`
1308
+ : input.responseText;
1309
+ span.setAttribute("http.method", input.method);
1310
+ span.setAttribute("http.url", input.url);
1311
+ // status === 0 ⇒ no HTTP response (network failure). We deliberately
1312
+ // *omit* http.status_code in that case so dashboards filtering on
1313
+ // `status_code = X` don't conflate "couldn't reach backend" with
1314
+ // some real status.
1315
+ if (input.status > 0) {
1316
+ span.setAttribute("http.status_code", input.status);
1317
+ }
1318
+ span.setAttribute("validate.remote.request_payload_bytes", Buffer.byteLength(input.requestBodyStr, "utf8"));
1319
+ span.setAttribute("validate.remote.response_body", truncatedResponse);
1320
+ span.setAttribute("validate.remote.duration_ms", input.durationMs);
1321
+ span.setAttribute("aui.curl_repro", formatAsCurlSafe({
1322
+ timestamp: new Date().toISOString(),
1323
+ method: input.method,
1324
+ url: input.url,
1325
+ headers: input.headers,
1326
+ body: input.payload,
1327
+ status: input.status,
1328
+ responseBody: input.responseText,
1329
+ label: "validate agent",
1330
+ success: input.success,
1331
+ }, { maxBodyBytes: MAX }));
1332
+ }
1333
+ catch {
1334
+ // Telemetry must never break the call path.
1335
+ }
1336
+ }
1337
+ /**
1338
+ * Attach preflight HTTP telemetry to the currently-active span
1339
+ * (typically `aui.import` / `aui.pull` / `aui.push`).
1340
+ *
1341
+ * The CLI fires up to three preflight reads BEFORE issuing the
1342
+ * actual push/pull body:
1343
+ * 1. `GET /v1/agents/{id}` → reads `bundle_mode`
1344
+ * 2. `GET /v1/agents/{id}/versions/{id}` → resolves the draft/version
1345
+ * 3. `GET /v1/agents?network_id=…` → legacy-project fallback
1346
+ * when `.auirc` has no `agent_management_id`.
1347
+ *
1348
+ * All three are essential for triaging "push hit the wrong endpoint"
1349
+ * / "pull pulled the wrong version" reports. Each one is emitted as
1350
+ * its own span event with the full curl repro (auth redacted),
1351
+ * status, duration, response body (truncated), and a handful of
1352
+ * extracted highlights pulled by `extractAttrs` (e.g. `bundle_mode`,
1353
+ * `active_version_id`, version `status`, item counts).
1354
+ *
1355
+ * Best-effort throughout: telemetry must NEVER break the call path.
1356
+ */
1357
+ attachPreflightTelemetry(input) {
1358
+ try {
1359
+ const span = trace.getActiveSpan();
1360
+ if (!span)
1361
+ return;
1362
+ const MAX = 32 * 1024;
1363
+ const truncatedResponse = input.responseText.length > MAX
1364
+ ? input.responseText.slice(0, MAX) +
1365
+ `... [truncated ${input.responseText.length - MAX} bytes]`
1366
+ : input.responseText;
1367
+ const eventAttrs = {
1368
+ "http.method": input.method,
1369
+ "http.url": input.url,
1370
+ [`${input.name}.duration_ms`]: input.durationMs,
1371
+ [`${input.name}.success`]: input.success,
1372
+ [`${input.name}.response_body`]: truncatedResponse,
1373
+ "aui.curl_repro": formatAsCurlSafe({
1374
+ timestamp: new Date().toISOString(),
1375
+ method: input.method,
1376
+ url: input.url,
1377
+ headers: input.headers,
1378
+ body: input.requestBody,
1379
+ status: input.status,
1380
+ responseBody: input.responseText,
1381
+ label: input.label,
1382
+ success: input.success,
1383
+ }, { maxBodyBytes: MAX }),
1384
+ };
1385
+ // status === 0 ⇒ no HTTP response (network failure). Mirror the
1386
+ // validate-call convention and omit `http.status_code` so
1387
+ // dashboards filtering on status don't conflate "couldn't reach
1388
+ // backend" with a real status.
1389
+ if (input.status > 0) {
1390
+ eventAttrs["http.status_code"] = input.status;
1391
+ }
1392
+ if (input.extraAttrs) {
1393
+ for (const [k, v] of Object.entries(input.extraAttrs)) {
1394
+ eventAttrs[`${input.name}.${k}`] = v;
1395
+ }
1396
+ }
1397
+ // Parse the response body once for the per-endpoint highlight
1398
+ // extractor. The extractor can also push attributes onto the
1399
+ // parent span (`spanAttrs`) for things that are stable across
1400
+ // every call (e.g. `agent.bundle_mode`).
1401
+ if (input.extractAttrs) {
1402
+ const spanAttrs = {};
1403
+ try {
1404
+ const parsed = JSON.parse(input.responseText);
1405
+ const extracted = input.extractAttrs(parsed, spanAttrs) || {};
1406
+ for (const [k, v] of Object.entries(extracted)) {
1407
+ eventAttrs[`${input.name}.${k}`] = v;
1408
+ }
1409
+ }
1410
+ catch {
1411
+ // Response wasn't JSON (network failure stringified error,
1412
+ // 5xx HTML page, etc.) — fall through without highlights.
1413
+ }
1414
+ for (const [k, v] of Object.entries(spanAttrs)) {
1415
+ span.setAttribute(k, v);
1416
+ }
1417
+ }
1418
+ span.addEvent(input.name, eventAttrs);
1419
+ }
1420
+ catch {
1421
+ // Telemetry must never break the call path.
1422
+ }
1423
+ }
1424
+ /**
1425
+ * Run a preflight `agentManagementFetch` and attach full
1426
+ * curl+request+response telemetry to the active span. Use for
1427
+ * `GET` reads that drive the push/pull dispatch (getAgent,
1428
+ * getVersion, listAgents). Returns the response text so callers
1429
+ * still parse the body themselves — keeps the existing single-
1430
+ * read-once invariant from `getAgent` (response bodies are
1431
+ * single-shot).
1432
+ */
1433
+ async preflightFetch(input) {
1434
+ const headers = this.agentManagementHeaders();
1435
+ const startedAt = Date.now();
1436
+ let resp;
1437
+ try {
1438
+ resp = await this.agentManagementFetch(input.url, { method: input.method, headers }, input.label);
1439
+ }
1440
+ catch (err) {
1441
+ const networkErr = err instanceof Error ? err : new Error(String(err));
1442
+ this.attachPreflightTelemetry({
1443
+ name: input.name,
1444
+ label: input.label,
1445
+ method: input.method,
1446
+ url: input.url,
1447
+ headers,
1448
+ responseText: networkErr.message,
1449
+ status: 0,
1450
+ success: false,
1451
+ durationMs: Date.now() - startedAt,
1452
+ extraAttrs: input.extraAttrs,
1453
+ extractAttrs: input.extractAttrs,
1454
+ });
1455
+ throw networkErr;
1456
+ }
1457
+ let responseText;
1458
+ try {
1459
+ responseText = await resp.text();
1460
+ }
1461
+ catch {
1462
+ responseText = "";
1463
+ }
1464
+ this.attachPreflightTelemetry({
1465
+ name: input.name,
1466
+ label: input.label,
1467
+ method: input.method,
1468
+ url: input.url,
1469
+ headers,
1470
+ responseText,
1471
+ status: resp.status,
1472
+ success: resp.ok,
1473
+ durationMs: Date.now() - startedAt,
1474
+ extraAttrs: input.extraAttrs,
1475
+ extractAttrs: input.extractAttrs,
1476
+ });
1477
+ return { resp, responseText };
1478
+ }
1479
+ /**
1480
+ * Attach a redacted curl + request/response telemetry to the active
1481
+ * span for the core import/push endpoints (the `/pull` & `/push`
1482
+ * bundle routes and the records-mode `/view` reads & per-entity
1483
+ * writes). These already land in `aui curl` (via `captureRequest`)
1484
+ * and `.aui/push-logs/`, but were NOT visible in Logfire — so a
1485
+ * failed bundle push or a wrong-version pull showed only a span
1486
+ * with a bare error string. This makes the exact replayable curl +
1487
+ * (truncated) response body part of the trace, for BOTH success and
1488
+ * failure, in both records and bundle mode.
1489
+ *
1490
+ * Emits a `http.<name>` event (preserves history when several calls
1491
+ * share one span, e.g. the ~6 `/view` reads under a single
1492
+ * `aui.import`) AND mirrors the curl + HTTP fields onto the span
1493
+ * itself (so a dedicated per-call task span like
1494
+ * `aui.push.task.bundle` / `aui.push.task.<entity>` shows the curl
1495
+ * directly in its attributes). For multi-call spans the span-level
1496
+ * attrs are last-call-wins; the per-call events retain every curl.
1497
+ *
1498
+ * Best-effort throughout: telemetry must NEVER break the call path.
1499
+ */
1500
+ attachHttpCurlTelemetry(input) {
1501
+ try {
1502
+ const span = trace.getActiveSpan();
1503
+ if (!span)
1504
+ return;
1505
+ const MAX = 32 * 1024;
1506
+ const truncatedResponse = input.responseText.length > MAX
1507
+ ? input.responseText.slice(0, MAX) +
1508
+ `... [truncated ${input.responseText.length - MAX} bytes]`
1509
+ : input.responseText;
1510
+ const curl = formatAsCurlSafe({
1511
+ timestamp: new Date().toISOString(),
1512
+ method: input.method,
1513
+ url: input.url,
1514
+ headers: input.headers,
1515
+ body: input.requestBody,
1516
+ status: input.status,
1517
+ responseBody: input.responseText,
1518
+ label: input.label,
1519
+ success: input.success,
1520
+ }, { maxBodyBytes: MAX });
1521
+ const eventAttrs = {
1522
+ "http.method": input.method,
1523
+ "http.url": input.url,
1524
+ [`${input.name}.success`]: input.success,
1525
+ [`${input.name}.response_body`]: truncatedResponse,
1526
+ "aui.curl_repro": curl,
1527
+ };
1528
+ if (input.status > 0)
1529
+ eventAttrs["http.status_code"] = input.status;
1530
+ if (typeof input.durationMs === "number") {
1531
+ eventAttrs[`${input.name}.duration_ms`] = input.durationMs;
1532
+ }
1533
+ span.addEvent(`http.${input.name}`, eventAttrs);
1534
+ // Mirror onto the span for direct visibility on dedicated
1535
+ // per-call spans (push bundle / per-entity write task spans).
1536
+ span.setAttribute("aui.curl_repro", curl);
1537
+ span.setAttribute("http.method", input.method);
1538
+ span.setAttribute("http.url", input.url);
1539
+ span.setAttribute("http.response_body", truncatedResponse);
1540
+ if (input.status > 0) {
1541
+ span.setAttribute("http.status_code", input.status);
1542
+ }
1543
+ }
1544
+ catch {
1545
+ // Telemetry must never break the call path.
1546
+ }
1547
+ }
677
1548
  agentManagementHeaders() {
678
1549
  return {
679
1550
  Accept: "application/json",
@@ -917,20 +1788,40 @@ export class AUIClient {
917
1788
  console.log(`[debug] GET ${url}`);
918
1789
  }
919
1790
  const hdrs = this.agentSettingsReadHeaders();
1791
+ const startedAt = Date.now();
920
1792
  const resp = await fetch(url, { method: "GET", headers: hdrs });
1793
+ // Read the body ONCE — used for telemetry, the error envelope, and
1794
+ // the success parse. node-fetch bodies are single-shot.
1795
+ const responseText = await resp.text();
921
1796
  if (resp.status === 401 && !_isRetry) {
922
1797
  const refreshed = await this.attemptTokenRefresh();
923
1798
  if (refreshed) {
924
1799
  return this.fetchV2(path, params, true);
925
1800
  }
926
1801
  }
1802
+ // Attach the replayable curl + response to the active span for the
1803
+ // records-mode `/view` reads `aui import` / `aui pull` issue (the
1804
+ // ~6 per-entity reads run under one `aui.import` / `aui.pull`
1805
+ // span). Success + failure, so the import endpoints are fully
1806
+ // visible in Logfire — not just `aui curl`.
1807
+ this.attachHttpCurlTelemetry({
1808
+ name: "import_view",
1809
+ label: `GET ${path}`,
1810
+ method: "GET",
1811
+ url,
1812
+ headers: hdrs,
1813
+ responseText,
1814
+ status: resp.status,
1815
+ success: resp.ok,
1816
+ durationMs: Date.now() - startedAt,
1817
+ });
927
1818
  if (!resp.ok) {
928
1819
  let errorBody;
929
1820
  try {
930
- errorBody = await resp.json();
1821
+ errorBody = JSON.parse(responseText);
931
1822
  }
932
1823
  catch {
933
- errorBody = await resp.text();
1824
+ errorBody = responseText;
934
1825
  }
935
1826
  if (process.env.AUI_DEBUG) {
936
1827
  console.log(`[debug] ${path} → ${resp.status}: ${JSON.stringify(errorBody)}`);
@@ -949,7 +1840,7 @@ export class AUIClient {
949
1840
  err.statusCode = resp.status;
950
1841
  throw err;
951
1842
  }
952
- const data = await resp.json();
1843
+ const data = responseText ? JSON.parse(responseText) : {};
953
1844
  captureRequest({
954
1845
  timestamp: new Date().toISOString(),
955
1846
  method: "GET",
@@ -1017,11 +1908,22 @@ export class AUIClient {
1017
1908
  }
1018
1909
  return new URLSearchParams(filtered).toString();
1019
1910
  }
1911
+ /**
1912
+ * Add agent-management identifiers (`agent_version_id` + `agent_id`) onto a
1913
+ * write body when present on `params`. All agent-settings CRUD methods —
1914
+ * parameters, scope-entities, rules, integrations, tools, general settings —
1915
+ * funnel through here, so the CLI is the single source of truth for both
1916
+ * fields. The BFF should NOT inject either; if you see it doing so for a
1917
+ * subset of routes (e.g. parameters / scope-entities / rules but not
1918
+ * integrations), that is a BFF bug to remove rather than mirror here.
1919
+ */
1020
1920
  versionBody(params, body) {
1021
- if (params.version_id) {
1022
- return { ...body, agent_version_id: params.version_id };
1023
- }
1024
- return body;
1921
+ const enriched = { ...body };
1922
+ if (params.version_id)
1923
+ enriched.agent_version_id = params.version_id;
1924
+ if (params.agent_id)
1925
+ enriched.agent_id = params.agent_id;
1926
+ return enriched;
1025
1927
  }
1026
1928
  scopeBody(params) {
1027
1929
  const fields = {};
@@ -1083,6 +1985,22 @@ export class AUIClient {
1083
1985
  label: label || `${method} ${path}`,
1084
1986
  success: resp.ok,
1085
1987
  });
1988
+ // Attach the replayable curl + response to the active span for
1989
+ // BOTH success and failure (records-mode per-entity writes run
1990
+ // inside their own `aui.push.task.<entity>` span). Previously only
1991
+ // failures got a curl here; the owner asked for curl on every
1992
+ // pushed endpoint so triage never depends on `.aui/push-logs/`.
1993
+ this.attachHttpCurlTelemetry({
1994
+ name: "entity_write",
1995
+ label: label || `${method} ${path}`,
1996
+ method,
1997
+ url,
1998
+ headers,
1999
+ requestBody: body,
2000
+ responseText,
2001
+ status: resp.status,
2002
+ success: resp.ok,
2003
+ });
1086
2004
  if (this.pushLogDir) {
1087
2005
  this.writePushLog(method, url, headers, body, resp.status, responseText, label);
1088
2006
  }
@@ -1307,6 +2225,7 @@ export class AUIClient {
1307
2225
  const createBody = this.versionBody(params, {
1308
2226
  name,
1309
2227
  created_by: params.updated_by,
2228
+ type: item.type || "normal",
1310
2229
  description: item.description || "",
1311
2230
  identifier: item.identifier || null,
1312
2231
  parameters: item.parameters || [],
@@ -1324,6 +2243,7 @@ export class AUIClient {
1324
2243
  });
1325
2244
  const patchBody = this.versionBody(params, {
1326
2245
  updated_by: params.updated_by,
2246
+ type: item.type || null,
1327
2247
  description: item.description || null,
1328
2248
  identifier: item.identifier || null,
1329
2249
  parameters: item.parameters || null,
@@ -1353,6 +2273,7 @@ export class AUIClient {
1353
2273
  });
1354
2274
  const code = item.code;
1355
2275
  const createBody = this.versionBody(params, {
2276
+ code: code,
1356
2277
  name: item.name || code,
1357
2278
  type: item.type || "API",
1358
2279
  settings: stripTestCurlResponse(item.settings || {}),
@@ -1370,6 +2291,7 @@ export class AUIClient {
1370
2291
  enable_reduce_by_scope: "true",
1371
2292
  });
1372
2293
  const patchBody = this.versionBody(params, {
2294
+ name: item.name || null,
1373
2295
  updated_by: params.updated_by,
1374
2296
  description: item.description || null,
1375
2297
  settings: item.settings ? stripTestCurlResponse(item.settings) : null,
@@ -1679,22 +2601,6 @@ export class AUIClient {
1679
2601
  });
1680
2602
  return this.writeV2("PUT", "agent-tools/rules", qs, bodyWithUser, "PUT rules");
1681
2603
  }
1682
- // ─── Widget (Card Template View Mode) ───
1683
- /** POST /card-templates/view:generate — stateless server-side widget generation. */
1684
- async generateWidget(params, body) {
1685
- const enrichedBody = { ...body };
1686
- if (params.network_id)
1687
- enrichedBody.network_id = params.network_id;
1688
- if (params.account_id)
1689
- enrichedBody.account_id = params.account_id;
1690
- if (params.organization_id)
1691
- enrichedBody.organization_id = params.organization_id;
1692
- if (params.network_category_id)
1693
- enrichedBody.network_category_id = params.network_category_id;
1694
- if (params.version_id)
1695
- enrichedBody.agent_version_id = params.version_id;
1696
- return (await this.writeV2("POST", "card-templates/view:generate", "", enrichedBody, "generate widget"));
1697
- }
1698
2604
  }
1699
2605
  export function applyScopeLevel(params, level) {
1700
2606
  if (!level || level === "network")