aui-agent-builder 0.3.102 → 0.3.104

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 (65) hide show
  1. package/README.md +1 -1
  2. package/dist/api-client/index.d.ts +304 -19
  3. package/dist/api-client/index.d.ts.map +1 -1
  4. package/dist/api-client/index.js +337 -69
  5. package/dist/api-client/index.js.map +1 -1
  6. package/dist/api-client/kb-view-client.d.ts +26 -12
  7. package/dist/api-client/kb-view-client.d.ts.map +1 -1
  8. package/dist/api-client/kb-view-client.js.map +1 -1
  9. package/dist/commands/agents.d.ts +55 -1
  10. package/dist/commands/agents.d.ts.map +1 -1
  11. package/dist/commands/agents.js +193 -50
  12. package/dist/commands/agents.js.map +1 -1
  13. package/dist/commands/import-agent.d.ts +24 -0
  14. package/dist/commands/import-agent.d.ts.map +1 -1
  15. package/dist/commands/import-agent.js +807 -153
  16. package/dist/commands/import-agent.js.map +1 -1
  17. package/dist/commands/legacy/push-records-mode.d.ts +166 -0
  18. package/dist/commands/legacy/push-records-mode.d.ts.map +1 -0
  19. package/dist/commands/legacy/push-records-mode.js +2536 -0
  20. package/dist/commands/legacy/push-records-mode.js.map +1 -0
  21. package/dist/commands/pull-agent.d.ts +9 -0
  22. package/dist/commands/pull-agent.d.ts.map +1 -1
  23. package/dist/commands/pull-agent.js +571 -127
  24. package/dist/commands/pull-agent.js.map +1 -1
  25. package/dist/commands/push.d.ts +78 -107
  26. package/dist/commands/push.d.ts.map +1 -1
  27. package/dist/commands/push.js +1014 -1807
  28. package/dist/commands/push.js.map +1 -1
  29. package/dist/commands/rag.js +1 -1
  30. package/dist/commands/rag.js.map +1 -1
  31. package/dist/commands/util/agent-mode.d.ts +69 -0
  32. package/dist/commands/util/agent-mode.d.ts.map +1 -0
  33. package/dist/commands/util/agent-mode.js +101 -0
  34. package/dist/commands/util/agent-mode.js.map +1 -0
  35. package/dist/commands/validate.d.ts.map +1 -1
  36. package/dist/commands/validate.js +23 -7
  37. package/dist/commands/validate.js.map +1 -1
  38. package/dist/commands/version-snapshot.d.ts.map +1 -1
  39. package/dist/commands/version-snapshot.js +253 -49
  40. package/dist/commands/version-snapshot.js.map +1 -1
  41. package/dist/commands/version.d.ts +15 -1
  42. package/dist/commands/version.d.ts.map +1 -1
  43. package/dist/commands/version.js +102 -7
  44. package/dist/commands/version.js.map +1 -1
  45. package/dist/config/index.d.ts +16 -1
  46. package/dist/config/index.d.ts.map +1 -1
  47. package/dist/config/index.js.map +1 -1
  48. package/dist/index.js +22 -5
  49. package/dist/index.js.map +1 -1
  50. package/dist/services/kb-view.service.d.ts +12 -27
  51. package/dist/services/kb-view.service.d.ts.map +1 -1
  52. package/dist/services/kb-view.service.js +134 -106
  53. package/dist/services/kb-view.service.js.map +1 -1
  54. package/dist/telemetry.d.ts +2 -1
  55. package/dist/telemetry.d.ts.map +1 -1
  56. package/dist/telemetry.js.map +1 -1
  57. package/dist/ui/views/ImportAgentView.d.ts +15 -0
  58. package/dist/ui/views/ImportAgentView.d.ts.map +1 -1
  59. package/dist/ui/views/ImportAgentView.js +8 -3
  60. package/dist/ui/views/ImportAgentView.js.map +1 -1
  61. package/dist/utils/index.d.ts +80 -0
  62. package/dist/utils/index.d.ts.map +1 -1
  63. package/dist/utils/index.js +330 -0
  64. package/dist/utils/index.js.map +1 -1
  65. package/package.json +1 -1
@@ -345,6 +345,12 @@ export class AUIClient {
345
345
  });
346
346
  if (filters?.network_id)
347
347
  qs.set("network_id", filters.network_id);
348
+ if (filters?.scope_type)
349
+ qs.set("scope_type", filters.scope_type);
350
+ if (filters?.kind)
351
+ qs.set("kind", filters.kind);
352
+ if (filters?.network_category_id)
353
+ qs.set("network_category_id", filters.network_category_id);
348
354
  const url = `${getAgentManagementBaseUrl()}/v1/agents?${qs}`;
349
355
  const resp = await this.agentManagementFetch(url, {
350
356
  method: "GET",
@@ -410,22 +416,84 @@ export class AUIClient {
410
416
  return (await resp.json());
411
417
  },
412
418
  getAgent: async (agentId) => {
419
+ // `GET /v1/agents/{id}` is the dispatch oracle for the whole CLI —
420
+ // every `aui import-agent`, `aui pull`, and `aui push` hits it
421
+ // before touching the per-entity / bundle endpoints (see
422
+ // `commands/util/agent-mode.ts → detectAgentBundleMode`). When
423
+ // a user reports "the CLI hit the wrong push endpoint" or
424
+ // "import keeps failing on this agent", the FIRST question is
425
+ // "what did the get-agent response say (especially bundle_mode)?".
426
+ //
427
+ // To answer that straight from Logfire — without re-running the
428
+ // CLI with `AUI_DEBUG=1` — we attach an event on the active span
429
+ // (`aui.import` / `aui.pull` / `aui.push`) carrying the full
430
+ // curl repro (auth redacted), HTTP method/url/status, duration,
431
+ // and the response body. Using `addEvent` (vs `setAttribute`)
432
+ // preserves multiple per-command calls: push's
433
+ // `lookupAgentManagementInfoForPush` + `resolveVersionDraft` can
434
+ // call `getAgent` more than once, and we want each call visible
435
+ // as its own log entry under the parent span.
413
436
  const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}`;
414
- const resp = await this.agentManagementFetch(url, {
415
- method: "GET",
416
- headers: this.agentManagementHeaders(),
417
- }, "get agent");
437
+ const method = "GET";
438
+ const headers = this.agentManagementHeaders();
439
+ const startedAt = Date.now();
440
+ let resp;
441
+ try {
442
+ resp = await this.agentManagementFetch(url, { method, headers }, "get agent");
443
+ }
444
+ catch (err) {
445
+ // Network-level failure (DNS, ECONNREFUSED, timeout, abort, ...).
446
+ // Attach the request-side telemetry before re-throwing so the
447
+ // span tells the full story even when there's no HTTP response.
448
+ const networkErr = err instanceof Error ? err : new Error(String(err));
449
+ this.attachGetAgentTelemetry({
450
+ method,
451
+ url,
452
+ headers,
453
+ agentId,
454
+ responseText: networkErr.message,
455
+ status: 0,
456
+ success: false,
457
+ durationMs: Date.now() - startedAt,
458
+ });
459
+ throw networkErr;
460
+ }
461
+ // Read the body ONCE — Response bodies are single-shot and we
462
+ // need the same string for telemetry, error envelopes, and the
463
+ // success JSON parse below. Mirrors the validate-call path.
464
+ let responseText;
465
+ try {
466
+ responseText = await resp.text();
467
+ }
468
+ catch {
469
+ responseText = "";
470
+ }
471
+ this.attachGetAgentTelemetry({
472
+ method,
473
+ url,
474
+ headers,
475
+ agentId,
476
+ responseText,
477
+ status: resp.status,
478
+ success: resp.ok,
479
+ durationMs: Date.now() - startedAt,
480
+ });
418
481
  if (!resp.ok) {
419
482
  let errorBody;
420
483
  try {
421
- errorBody = await resp.json();
484
+ errorBody = JSON.parse(responseText);
422
485
  }
423
486
  catch {
424
- errorBody = await resp.text();
487
+ errorBody = responseText || null;
425
488
  }
426
489
  throw new AUIAPIError(resp.status, `get agent: ${resp.statusText}`, errorBody);
427
490
  }
428
- return (await resp.json());
491
+ try {
492
+ return JSON.parse(responseText);
493
+ }
494
+ catch {
495
+ throw new AUIAPIError(resp.status, `get agent: invalid JSON response`, responseText);
496
+ }
429
497
  },
430
498
  activateVersion: async (agentId, versionId) => {
431
499
  const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/active-version`;
@@ -650,34 +718,89 @@ export class AUIClient {
650
718
  return resp.json();
651
719
  },
652
720
  /**
653
- * POST /v1/agents/{agentId}/versions/{versionId}/snapshot
654
- * Pushes local files as a version snapshot via multipart form data.
655
- * Uses JWT auth (Bearer token). Bump is automatic on success.
721
+ * POST /v1/agents/{agentId}/versions/{versionId}/push
722
+ *
723
+ * JSON-only upload (multipart was retired on 2026-05-20 see
724
+ * Notion "Agent Settings Push/Pull" and the OpenAPI under
725
+ * `PushRequestSchema`).
726
+ *
727
+ * Server flow:
728
+ * 1. Validates the assembled `bundle` through the v1 transcoder
729
+ * BEFORE acquiring the per-version Redis lock — fail-fast 422
730
+ * with a structured `BundleValidationResult` and no GCS /
731
+ * Mongo writes.
732
+ * 2. Canonicalizes the bundle bytes (deterministic key order,
733
+ * no extra whitespace), hashes with SHA-256, and writes a
734
+ * single `bundle.json` blob to GCS under
735
+ * `agent_id={id}/version_id={id}/version_tag={tag}/bundle.json`.
736
+ * 3. Bumps `version_revision_number` by exactly 1 and returns the
737
+ * new `version_tag`, `gcs_key`, `sha256`, and `size` so
738
+ * clients can verify integrity locally.
739
+ *
740
+ * Request body (`PushRequestSchema`):
741
+ * - `caller` : "agent_builder" | "ui" | "cli" (required)
742
+ * - `commit_message` : optional save note (≤ 4000 chars after trim)
743
+ * - `bundle` : `AgentSettingsBundle` — the CLI assembles
744
+ * this from local `.aui.json` files (see
745
+ * `assembleBundleFromFiles` in `utils/`).
746
+ *
747
+ * Auth: Bearer JWT + `X-Organization-ID` (same as other v1 routes).
748
+ * The legacy `x-api-key` fallback is NOT honoured here — Bearer only.
749
+ * Push requires `update` on `agent_settings` for the target agent.
656
750
  */
657
- pushSnapshot: async (agentId, versionId, files) => {
658
- const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/snapshot`;
659
- const fd = new FormData();
660
- for (const file of files) {
661
- fd.append("files", new File([file.content], file.fileName, { type: "application/json" }));
662
- }
751
+ pushVersionBlobs: async (agentId, versionId, body) => {
752
+ const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/push`;
753
+ // Defensive normalization of the request body:
754
+ // - `caller` always set (TS already enforces it, but be belt+suspenders)
755
+ // - `commit_message` trimmed + capped (server caps too, but the
756
+ // CLI shouldn't make the server do work on bad input)
757
+ // - `pushed_by` forwarded as-is when the caller supplies it
758
+ // (added 2026-05-25 per backend team — recorded on the
759
+ // blob revision row so revision provenance ties back to a
760
+ // real user, not just a surface). Only emitted when set so
761
+ // older server versions that don't read the field still
762
+ // accept the request body unchanged.
763
+ // - `bundle.schema_version` defaults to 1 if the caller forgot
764
+ const reqBody = {
765
+ caller: body.caller,
766
+ ...(body.commit_message && body.commit_message.trim()
767
+ ? { commit_message: body.commit_message.trim().slice(0, 4000) }
768
+ : {}),
769
+ ...(body.pushed_by ? { pushed_by: body.pushed_by } : {}),
770
+ bundle: {
771
+ schema_version: body.bundle.schema_version ?? 1,
772
+ ...body.bundle,
773
+ },
774
+ };
663
775
  if (process.env.AUI_DEBUG) {
664
- console.log(`[debug] POST ${url}`);
665
- console.log(`[debug] Uploading ${files.length} file(s): ${files.map((f) => f.fileName).join(", ")}`);
776
+ const b = reqBody.bundle;
777
+ const summary = {
778
+ schema_version: b.schema_version,
779
+ general_settings: b.general_settings ? "<object>" : null,
780
+ parameters: b.parameters ? b.parameters.length : null,
781
+ entities: b.entities ? b.entities.length : null,
782
+ integrations: b.integrations ? b.integrations.length : null,
783
+ rules: b.rules ? b.rules.length : null,
784
+ tools: b.tools ? b.tools.length : null,
785
+ };
786
+ console.log(`[debug] POST ${url} as caller=${reqBody.caller} pushed_by=${reqBody.pushed_by ?? "(absent)"}; bundle:`, JSON.stringify(summary));
666
787
  }
667
788
  const headers = {
668
789
  Accept: "application/json",
790
+ "Content-Type": "application/json",
669
791
  Authorization: `Bearer ${this.authToken}`,
670
792
  "X-Organization-ID": this.organizationId,
671
793
  };
672
- // Snapshot uploads can legitimately be large (entire agent file set
673
- // multipart-uploaded). Cap at 5 minutes — long enough for a 50-file
674
- // snapshot on a slow E2B sandbox cold-start, short enough that a
675
- // truly hung request surfaces before the BFF's own pipeline timeout
676
- // (BFF caps `aui push` at 5 min in `agent-builder-bff`'s
677
- // `sandbox-command-client.ts:auiPush`). Override per-call via
678
- // `AUI_SNAPSHOT_TIMEOUT_MS` env var if needed.
679
- const snapshotTimeoutMs = (() => {
680
- const fromEnv = process.env.AUI_SNAPSHOT_TIMEOUT_MS;
794
+ // Bundle uploads can be moderately large (every parameter +
795
+ // entity + tool encoded as JSON). Cap the request at 5 minutes —
796
+ // long enough for a slow E2B sandbox cold-start, short enough
797
+ // that a truly hung request surfaces before the BFF's own
798
+ // pipeline timeout (BFF caps `aui push` at 5 min in
799
+ // `agent-builder-bff`'s `sandbox-command-client.ts:auiPush`).
800
+ // Override via `AUI_PUSH_TIMEOUT_MS` env var if needed (legacy
801
+ // `AUI_SNAPSHOT_TIMEOUT_MS` honoured too for back-compat).
802
+ const pushTimeoutMs = (() => {
803
+ const fromEnv = process.env.AUI_PUSH_TIMEOUT_MS ?? process.env.AUI_SNAPSHOT_TIMEOUT_MS;
681
804
  if (fromEnv) {
682
805
  const parsed = parseInt(fromEnv, 10);
683
806
  if (!Number.isNaN(parsed) && parsed >= 0)
@@ -685,56 +808,65 @@ export class AUIClient {
685
808
  }
686
809
  return 300_000;
687
810
  })();
688
- const snapshotController = new AbortController();
689
- const snapshotTimer = snapshotTimeoutMs > 0
690
- ? setTimeout(() => snapshotController.abort(), snapshotTimeoutMs)
811
+ const pushController = new AbortController();
812
+ const pushTimer = pushTimeoutMs > 0
813
+ ? setTimeout(() => pushController.abort(), pushTimeoutMs)
691
814
  : null;
692
- const snapshotStart = Date.now();
815
+ const pushStart = Date.now();
816
+ const serialized = JSON.stringify(reqBody);
693
817
  let resp;
694
818
  try {
695
819
  resp = await globalThis.fetch(url, {
696
820
  method: "POST",
697
821
  headers,
698
- body: fd,
699
- signal: snapshotController.signal,
822
+ body: serialized,
823
+ signal: pushController.signal,
700
824
  });
701
825
  }
702
826
  catch (err) {
703
- if (snapshotTimer)
704
- clearTimeout(snapshotTimer);
705
- const elapsedMs = Date.now() - snapshotStart;
827
+ if (pushTimer)
828
+ clearTimeout(pushTimer);
829
+ const elapsedMs = Date.now() - pushStart;
706
830
  const isAbort = err instanceof Error &&
707
- (err.name === "AbortError" || snapshotController.signal.aborted);
708
- if (isAbort && snapshotTimeoutMs > 0 && elapsedMs >= snapshotTimeoutMs - 50) {
709
- // Loud, structured failure so Logfire / push-logs / BFF logs all
710
- // see the same wording never silently retry forever.
711
- const msg = `Snapshot upload timed out after ${elapsedMs}ms (limit ${snapshotTimeoutMs}ms): POST ${url}. ` +
712
- `Override with AUI_SNAPSHOT_TIMEOUT_MS=<ms> if your agent is unusually large.`;
831
+ (err.name === "AbortError" || pushController.signal.aborted);
832
+ if (isAbort && pushTimeoutMs > 0 && elapsedMs >= pushTimeoutMs - 50) {
833
+ const msg = `Push blob upload timed out after ${elapsedMs}ms (limit ${pushTimeoutMs}ms): POST ${url}. ` +
834
+ `Override with AUI_PUSH_TIMEOUT_MS=<ms> if your agent is unusually large.`;
713
835
  captureRequest({
714
836
  timestamp: new Date().toISOString(),
715
837
  method: "POST",
716
838
  url,
717
839
  headers,
718
- body: { files: files.map((f) => f.fileName) },
840
+ body: {
841
+ caller: reqBody.caller,
842
+ commit_message: reqBody.commit_message,
843
+ pushed_by: reqBody.pushed_by,
844
+ bundle_size_bytes: serialized.length,
845
+ },
719
846
  status: 0,
720
847
  responseBody: msg,
721
- label: "push snapshot",
848
+ label: "push version blobs",
722
849
  success: false,
723
850
  });
724
- return { success: false, failed: files.map((f) => f.fileName), error: msg };
851
+ return { success: false, error: msg };
725
852
  }
726
853
  throw err;
727
854
  }
728
- if (snapshotTimer)
729
- clearTimeout(snapshotTimer);
855
+ if (pushTimer)
856
+ clearTimeout(pushTimer);
730
857
  captureRequest({
731
858
  timestamp: new Date().toISOString(),
732
859
  method: "POST",
733
860
  url,
734
861
  headers,
735
- body: { files: files.map((f) => f.fileName) },
862
+ body: {
863
+ caller: reqBody.caller,
864
+ commit_message: reqBody.commit_message,
865
+ pushed_by: reqBody.pushed_by,
866
+ bundle_size_bytes: serialized.length,
867
+ },
736
868
  status: resp.status,
737
- label: "push snapshot",
869
+ label: "push version blobs",
738
870
  success: resp.ok,
739
871
  });
740
872
  if (!resp.ok) {
@@ -746,37 +878,67 @@ export class AUIClient {
746
878
  errorBody = await resp.text();
747
879
  }
748
880
  if (process.env.AUI_DEBUG) {
749
- console.log(`[debug] snapshot push failed: ${resp.status} ${JSON.stringify(errorBody)}`);
881
+ console.log(`[debug] push version blobs failed: ${resp.status} ${JSON.stringify(errorBody)}`);
750
882
  }
883
+ // 422 has two flavours per the doc:
884
+ // - request validation: { detail: "string" } or { detail: [{ loc, msg, type }] }
885
+ // - bundle validation : BundleValidationResult JSON
886
+ // Surface whichever is present without losing the structured body.
751
887
  const detail = errorBody?.detail;
752
- const errorMsg = Array.isArray(detail)
753
- ? detail.map((d) => `${d.msg} (${d.loc?.join(".")})`).join("; ")
754
- : typeof errorBody === "string" ? errorBody : JSON.stringify(errorBody);
755
- const failedFiles = errorBody?.failed ?? files.map((f) => f.fileName);
756
- return { success: false, failed: failedFiles, error: `${resp.status}: ${errorMsg}`, data: errorBody };
888
+ let errorMsg;
889
+ if (Array.isArray(detail)) {
890
+ errorMsg = detail
891
+ .map((d) => `${d.msg ?? d.message ?? ""} (${(d.loc ?? []).join(".")})`)
892
+ .join("; ");
893
+ }
894
+ else if (typeof detail === "string") {
895
+ errorMsg = detail;
896
+ }
897
+ else if (typeof errorBody === "string") {
898
+ errorMsg = errorBody;
899
+ }
900
+ else {
901
+ errorMsg = JSON.stringify(errorBody);
902
+ }
903
+ return {
904
+ success: false,
905
+ error: `${resp.status}: ${errorMsg}`,
906
+ data: errorBody,
907
+ };
757
908
  }
758
909
  let responseData;
759
910
  try {
760
- responseData = await resp.json();
911
+ responseData = (await resp.json());
761
912
  }
762
913
  catch {
763
- responseData = null;
914
+ responseData = undefined;
764
915
  }
765
916
  if (process.env.AUI_DEBUG) {
766
- console.log(`[debug] snapshot push succeeded: ${JSON.stringify(responseData)}`);
917
+ console.log(`[debug] push version blobs succeeded: ${JSON.stringify(responseData)}`);
767
918
  }
768
- return { success: true, failed: [], data: responseData };
919
+ return { success: true, data: responseData };
769
920
  },
770
921
  /**
771
- * GET /v1/agents/{agentId}/versions/{versionId}/snapshot
772
- * Retrieves the snapshot manifest + signed download URLs for a version_tag.
922
+ * GET /v1/agents/{agentId}/versions/{versionId}/pull
923
+ *
924
+ * JSON-only read (signed-URL + `return_type=json` modes were
925
+ * retired on 2026-05-20 — the new server returns the assembled
926
+ * bundle inline as `PullReadResponse`).
927
+ *
928
+ * Caching: every `version_tag` is an immutable snapshot — Pull
929
+ * tries Redis first. `expires_at` on the response is the snapshot
930
+ * row's TTL, not a per-request expiry.
931
+ *
932
+ * Auth: Bearer JWT + `X-Organization-ID`. The legacy `x-api-key`
933
+ * fallback is NOT honoured here — Bearer only. Pull requires
934
+ * `read` on `agent_settings`.
773
935
  */
774
- getSnapshot: async (agentId, versionId, options) => {
936
+ pullVersionBlobs: async (agentId, versionId, options = {}) => {
775
937
  const qs = new URLSearchParams();
776
- qs.set("version_tag", options.versionTag);
777
- if (options.expiresIn)
778
- qs.set("expires_in", String(options.expiresIn));
779
- const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/snapshot?${qs}`;
938
+ if (options.versionTag)
939
+ qs.set("version_tag", options.versionTag);
940
+ const search = qs.toString();
941
+ const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/pull${search ? `?${search}` : ""}`;
780
942
  if (process.env.AUI_DEBUG) {
781
943
  console.log(`[debug] GET ${url}`);
782
944
  }
@@ -787,7 +949,7 @@ export class AUIClient {
787
949
  Authorization: `Bearer ${this.authToken}`,
788
950
  "X-Organization-ID": this.organizationId,
789
951
  },
790
- }, "get snapshot");
952
+ }, "pull version blobs");
791
953
  if (!resp.ok) {
792
954
  let errorBody;
793
955
  try {
@@ -796,9 +958,29 @@ export class AUIClient {
796
958
  catch {
797
959
  errorBody = await resp.text();
798
960
  }
799
- throw new AUIAPIError(resp.status, `get snapshot: ${resp.statusText}`, errorBody);
961
+ throw new AUIAPIError(resp.status, `pull version blobs: ${resp.statusText}`, errorBody);
800
962
  }
801
- return resp.json();
963
+ return (await resp.json());
964
+ },
965
+ /**
966
+ * @deprecated Multipart-files upload was removed on 2026-05-20. The
967
+ * new server only accepts the JSON `PushBundleRequest` shape via
968
+ * {@link pushVersionBlobs}. Callers must assemble the bundle from
969
+ * local files (see `utils/assembleBundleFromFiles`) and pass it
970
+ * in directly.
971
+ */
972
+ pushSnapshot: async () => {
973
+ throw new AUIAPIError(410, "pushSnapshot is gone. The multipart /push endpoint was retired on 2026-05-20; use pushVersionBlobs with an assembled AgentSettingsBundle.");
974
+ },
975
+ /**
976
+ * @deprecated Signed-URL pull mode was removed on 2026-05-20. The
977
+ * new server returns the assembled bundle inline as
978
+ * {@link PullReadResponse}; consumers should call
979
+ * {@link pullVersionBlobs} directly and read `response.bundle`
980
+ * instead of paging through per-file signed URLs.
981
+ */
982
+ getSnapshot: async () => {
983
+ throw new AUIAPIError(410, "getSnapshot is gone. Signed-URL pull mode was retired on 2026-05-20; use pullVersionBlobs and read response.bundle.");
802
984
  },
803
985
  /**
804
986
  * POST /v1/agents/{agentId}/validate
@@ -989,6 +1171,92 @@ export class AUIClient {
989
1171
  // Telemetry must never break the call path.
990
1172
  }
991
1173
  }
1174
+ /**
1175
+ * Attach `GET /v1/agents/{id}` telemetry to the currently-active span
1176
+ * (typically `aui.import` / `aui.pull` / `aui.push`). The CLI calls
1177
+ * this endpoint before every import/pull/push to read `bundle_mode`
1178
+ * and decide which surface to use — so making every call self-
1179
+ * describing in Logfire (curl repro, status, response body, duration)
1180
+ * answers "did the CLI even know the right mode?" without re-running
1181
+ * locally. See the comment block in `getAgent` for the rationale.
1182
+ *
1183
+ * Best-effort throughout: telemetry must NEVER break the call path.
1184
+ */
1185
+ attachGetAgentTelemetry(input) {
1186
+ try {
1187
+ const span = trace.getActiveSpan();
1188
+ if (!span)
1189
+ return;
1190
+ const MAX = 32 * 1024;
1191
+ const truncatedResponse = input.responseText.length > MAX
1192
+ ? input.responseText.slice(0, MAX) +
1193
+ `... [truncated ${input.responseText.length - MAX} bytes]`
1194
+ : input.responseText;
1195
+ // Best-effort: surface `bundle_mode` as its own event attribute
1196
+ // so dashboards can filter "show me every push where the agent
1197
+ // reported bundle_mode=false" without grepping response bodies.
1198
+ let bundleMode;
1199
+ let agentName;
1200
+ let activeVersionId;
1201
+ try {
1202
+ const parsed = JSON.parse(input.responseText);
1203
+ if (typeof parsed.bundle_mode === "boolean") {
1204
+ bundleMode = parsed.bundle_mode;
1205
+ }
1206
+ if (typeof parsed.name === "string")
1207
+ agentName = parsed.name;
1208
+ if (typeof parsed.active_version_id === "string") {
1209
+ activeVersionId = parsed.active_version_id;
1210
+ }
1211
+ }
1212
+ catch {
1213
+ // Response wasn't JSON (network failure stringified error,
1214
+ // 5xx HTML page, etc.) — fall through with bundleMode unset.
1215
+ }
1216
+ const eventAttrs = {
1217
+ "http.method": input.method,
1218
+ "http.url": input.url,
1219
+ "get_agent.agent_id": input.agentId,
1220
+ "get_agent.duration_ms": input.durationMs,
1221
+ "get_agent.success": input.success,
1222
+ "get_agent.response_body": truncatedResponse,
1223
+ "aui.curl_repro": formatAsCurlSafe({
1224
+ timestamp: new Date().toISOString(),
1225
+ method: input.method,
1226
+ url: input.url,
1227
+ headers: input.headers,
1228
+ status: input.status,
1229
+ responseBody: input.responseText,
1230
+ label: "get agent",
1231
+ success: input.success,
1232
+ }, { maxBodyBytes: MAX }),
1233
+ };
1234
+ // status === 0 ⇒ no HTTP response (network failure). Mirror the
1235
+ // validate-call convention and omit `http.status_code` so
1236
+ // dashboards filtering on status don't conflate "couldn't reach
1237
+ // backend" with a real status.
1238
+ if (input.status > 0) {
1239
+ eventAttrs["http.status_code"] = input.status;
1240
+ }
1241
+ if (typeof bundleMode === "boolean") {
1242
+ eventAttrs["get_agent.bundle_mode"] = bundleMode;
1243
+ // Also pin the most-recent observed mode at the span level so
1244
+ // it's filterable without expanding the event list. Multiple
1245
+ // calls per command overwrite — the last call wins, which is
1246
+ // the one whose decision actually drove the dispatch.
1247
+ span.setAttribute("agent.bundle_mode", bundleMode);
1248
+ }
1249
+ if (agentName)
1250
+ eventAttrs["get_agent.agent_name"] = agentName;
1251
+ if (activeVersionId) {
1252
+ eventAttrs["get_agent.active_version_id"] = activeVersionId;
1253
+ }
1254
+ span.addEvent("get_agent", eventAttrs);
1255
+ }
1256
+ catch {
1257
+ // Telemetry must never break the call path.
1258
+ }
1259
+ }
992
1260
  agentManagementHeaders() {
993
1261
  return {
994
1262
  Accept: "application/json",