aui-agent-builder 0.3.67 → 0.3.69

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.
@@ -3,7 +3,7 @@ import { render, Box, Text } from "ink";
3
3
  import inquirer from "inquirer";
4
4
  import chalk from "chalk";
5
5
  import { getAuthenticatedSession, listAgents as listAgentsSvc, listVersions as listVersionsSvc, switchToAgent, } from "../services/agents.service.js";
6
- import { AgentsMenuView, AgentsListView, AgentSwitchView, VersionsListView, } from "../ui/views/AgentsView.js";
6
+ import { AgentsMenuView, AgentsListView, AgentSwitchView, VersionsListView, AgentDeletePreviewView, AgentDeleteResultView, } from "../ui/views/AgentsView.js";
7
7
  import { Spinner, ErrorDisplay, StatusLine } from "../ui/components/index.js";
8
8
  import { AUIClient } from "../api-client/index.js";
9
9
  import { isJsonMode, outputJson, stderrLog } from "../utils/json-output.js";
@@ -12,6 +12,73 @@ function renderView(element) {
12
12
  const instance = render(element);
13
13
  instance.unmount();
14
14
  }
15
+ /**
16
+ * Best-effort follow-up DELETE on the legacy network record after the
17
+ * agent-settings DELETE succeeds.
18
+ *
19
+ * Agent creation is a 3-step process (network → settings → agent-management).
20
+ * The agent-settings DELETE is the primary record removal; this safety-net
21
+ * also removes the network shell so the agent disappears from the legacy
22
+ * `networks.list` immediately. If it fails (e.g. already cascaded) we
23
+ * surface a warning but never let it fail the overall operation.
24
+ */
25
+ async function deleteNetworkBestEffort(client, networkId) {
26
+ try {
27
+ const resp = await client.networks.delete(networkId);
28
+ if (resp && resp.status === false) {
29
+ return {
30
+ deleted: false,
31
+ errorMessage: resp.message || "backend returned status=false",
32
+ };
33
+ }
34
+ return { deleted: true };
35
+ }
36
+ catch (err) {
37
+ return { deleted: false, errorMessage: formatApiErrorBody(err) };
38
+ }
39
+ }
40
+ /**
41
+ * Build a human-readable summary from an API error.
42
+ *
43
+ * Surfaces FastAPI-style validation errors so the user sees
44
+ * "Field required: query.deleted_by" instead of the generic
45
+ * "API request failed (422 Unprocessable Entity)".
46
+ */
47
+ function formatApiErrorBody(error) {
48
+ if (!error || typeof error !== "object")
49
+ return String(error);
50
+ const e = error;
51
+ const status = e.status;
52
+ const body = e.body;
53
+ if (body && typeof body === "object") {
54
+ const detail = body.detail;
55
+ if (Array.isArray(detail) && detail.length > 0) {
56
+ const items = detail.slice(0, 3).map((d) => {
57
+ if (!d || typeof d !== "object")
58
+ return String(d);
59
+ const dd = d;
60
+ const where = Array.isArray(dd.loc) ? dd.loc.filter(Boolean).join(".") : "";
61
+ const msg = dd.msg ?? dd.type ?? "invalid";
62
+ return where ? `${msg} (${where})` : String(msg);
63
+ });
64
+ const more = detail.length > items.length ? ` (+${detail.length - items.length} more)` : "";
65
+ return `${status ?? "?"} — ${items.join("; ")}${more}`;
66
+ }
67
+ if (typeof detail === "string")
68
+ return `${status ?? "?"} — ${detail}`;
69
+ const msg = body.msg ??
70
+ body.message ??
71
+ body.error;
72
+ if (typeof msg === "string")
73
+ return `${status ?? "?"} — ${msg}`;
74
+ }
75
+ if (typeof body === "string" && body.length > 0) {
76
+ return `${status ?? "?"} — ${body.slice(0, 300)}`;
77
+ }
78
+ if (e.message)
79
+ return e.message;
80
+ return String(error);
81
+ }
15
82
  export async function agents(options = {}) {
16
83
  const session = await getAuthenticatedSession();
17
84
  if (options.list) {
@@ -35,6 +102,10 @@ export async function agents(options = {}) {
35
102
  await handleCreateAgent(options);
36
103
  return;
37
104
  }
105
+ if (options.delete) {
106
+ await handleDeleteAgent(options);
107
+ return;
108
+ }
38
109
  if (options.import) {
39
110
  const { importAgent } = await import("./import-agent.js");
40
111
  await importAgent();
@@ -61,6 +132,11 @@ export async function agents(options = {}) {
61
132
  { name: "Import from another account", value: "import-other-account" },
62
133
  { name: "Import from another organization", value: "import-other-org" },
63
134
  new inquirer.Separator(),
135
+ {
136
+ name: chalk.red("Delete agent or version") + chalk.gray(" (irreversible)"),
137
+ value: "delete",
138
+ },
139
+ new inquirer.Separator(),
64
140
  { name: "Back", value: "back" },
65
141
  ],
66
142
  },
@@ -79,6 +155,9 @@ export async function agents(options = {}) {
79
155
  else if (action === "create") {
80
156
  await handleCreateAgent({});
81
157
  }
158
+ else if (action === "delete") {
159
+ await handleDeleteAgent({});
160
+ }
82
161
  else if (action === "import") {
83
162
  const { importAgent } = await import("./import-agent.js");
84
163
  await importAgent();
@@ -289,6 +368,710 @@ export async function agents(options = {}) {
289
368
  renderView(_jsx(ErrorDisplay, { error: error, message: "Failed to switch agent." }));
290
369
  }
291
370
  }
371
+ async function handleDeleteAgent(opts = {}) {
372
+ const config = getConfig();
373
+ const client = new AUIClient({
374
+ baseUrl: config.apiUrl,
375
+ authToken: config.authToken,
376
+ accountId: config.accountId,
377
+ organizationId: config.organizationId,
378
+ environment: config.environment,
379
+ });
380
+ const apiKey = loadAgentSettingsApiKey();
381
+ if (apiKey)
382
+ client.setAgentSettingsApiKey(apiKey);
383
+ // ─── Non-interactive: --network-id given → delete entire agent ───
384
+ // (also covers --network-id + --all-versions explicitly)
385
+ if (opts.networkId && !opts.version) {
386
+ await runDeleteEntireAgent(client, {
387
+ networkId: opts.networkId,
388
+ skipConfirm: !!opts.yes,
389
+ });
390
+ return;
391
+ }
392
+ // ─── Non-interactive: --version given → delete specific version ───
393
+ if (opts.version) {
394
+ const agentMgmtId = opts.agentId || opts.use;
395
+ await runDeleteVersion(client, {
396
+ agentManagementId: agentMgmtId,
397
+ networkId: opts.networkId,
398
+ versionId: opts.version,
399
+ skipConfirm: !!opts.yes,
400
+ });
401
+ return;
402
+ }
403
+ // ─── Non-interactive: --agent-id + --all-versions → loop per-version DELETEs ───
404
+ // (kept distinct from --network-id which does a single full-agent DELETE)
405
+ if (opts.agentId && opts.allVersions) {
406
+ const agent = await client.agentManagement.getAgent(opts.agentId).catch(() => null);
407
+ if (!agent) {
408
+ renderView(_jsx(ErrorDisplay, { message: `Agent not found: ${opts.agentId}`, suggestion: "Run `aui agent --list` to see available agents." }));
409
+ return;
410
+ }
411
+ await runDeleteAllVersions(client, {
412
+ agentManagementId: agent.id,
413
+ networkId: agent.scope.network_id || undefined,
414
+ agentName: agent.name,
415
+ activeVersionId: agent.active_version_id,
416
+ skipConfirm: !!opts.yes,
417
+ });
418
+ return;
419
+ }
420
+ // ─── Interactive flow: org → account → agent → scope ───
421
+ logView(_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "red", bold: true, children: "⚠ " }), _jsx(Text, { bold: true, children: "Delete an agent or a specific version" })] }), _jsx(Box, { children: _jsx(Text, { color: "gray", children: " This is permanent and cannot be undone." }) })] }));
422
+ // Step 1: Select organization
423
+ logView(_jsx(Text, { color: "gray", children: " \u250C Step 1 \u2192 Select Organization" }));
424
+ const org = await pickOrganization(client);
425
+ if (!org)
426
+ return;
427
+ client.setScope({ organizationId: org.id });
428
+ logView(_jsx(StatusLine, { kind: "success", label: `Organization: ${org.name}` }));
429
+ // Step 2: Select account
430
+ logView(_jsx(Text, { color: "gray", children: " \u251C Step 2 \u2192 Select Account" }));
431
+ const account = await pickAccount(client);
432
+ if (!account)
433
+ return;
434
+ client.setScope({ accountId: account.id });
435
+ logView(_jsx(StatusLine, { kind: "success", label: `Account: ${account.name}` }));
436
+ // Step 3: Select agent
437
+ logView(_jsx(Text, { color: "gray", children: " \u251C Step 3 \u2192 Select Agent" }));
438
+ const picked = await pickAgentInAccount(client);
439
+ if (!picked)
440
+ return;
441
+ const { network } = picked;
442
+ const networkId = network._id || network.id;
443
+ const agentName = network.name;
444
+ logView(_jsx(StatusLine, { kind: "success", label: `Agent: ${agentName}` }));
445
+ // Step 4: Choose what to delete
446
+ logView(_jsx(Text, { color: "gray", children: " \u2514 Step 4 \u2192 Choose what to delete" }));
447
+ const { scope } = await inquirer.prompt([
448
+ {
449
+ type: "list",
450
+ name: "scope",
451
+ message: "What do you want to delete?",
452
+ choices: [
453
+ {
454
+ name: `${chalk.red("Delete entire agent")} ${chalk.gray("(single API call — removes ALL versions)")}`,
455
+ value: "agent",
456
+ },
457
+ {
458
+ name: `${chalk.yellow("Delete a specific version")} ${chalk.gray("(keeps the agent)")}`,
459
+ value: "version",
460
+ },
461
+ {
462
+ name: `${chalk.magenta("Delete all versions")} ${chalk.gray("(loops per-version DELETE — keeps the agent shell)")}`,
463
+ value: "all-versions",
464
+ },
465
+ new inquirer.Separator(),
466
+ { name: "Cancel", value: "cancel" },
467
+ ],
468
+ },
469
+ ]);
470
+ if (scope === "cancel") {
471
+ logView(_jsx(StatusLine, { kind: "muted", label: "Cancelled." }));
472
+ return;
473
+ }
474
+ if (scope === "agent") {
475
+ // Entire-agent delete only needs the network_id; the cascade + the
476
+ // safety-net network DELETE handle everything else. Skip the lookup.
477
+ await runDeleteEntireAgent(client, {
478
+ networkId,
479
+ agentName,
480
+ skipConfirm: false,
481
+ });
482
+ return;
483
+ }
484
+ // For version-related scopes we need the agent-management ID (and the
485
+ // active_version_id). Resolve it once via a targeted lookup by network_id.
486
+ const lookupSpinner = render(_jsx(Spinner, { label: "Resolving agent record..." }));
487
+ let agentManagementId;
488
+ let activeVersionId = null;
489
+ try {
490
+ const resp = await client.agentManagement.listAgents(client.getOrganizationId(), 1, 50, { network_id: networkId });
491
+ const match = resp.items.find((a) => a.scope.network_id === networkId || a.id === networkId);
492
+ if (match) {
493
+ agentManagementId = match.id;
494
+ activeVersionId = match.active_version_id;
495
+ }
496
+ }
497
+ catch {
498
+ // best-effort; handled below
499
+ }
500
+ finally {
501
+ lookupSpinner.unmount();
502
+ }
503
+ if (!agentManagementId) {
504
+ renderView(_jsx(ErrorDisplay, { message: "This agent has no agent-management record, so per-version operations aren't available.", suggestion: "Use 'Delete entire agent' instead, or pass --agent-id explicitly." }));
505
+ return;
506
+ }
507
+ if (scope === "all-versions") {
508
+ await runDeleteAllVersions(client, {
509
+ agentManagementId,
510
+ networkId,
511
+ agentName,
512
+ activeVersionId,
513
+ skipConfirm: false,
514
+ });
515
+ return;
516
+ }
517
+ // scope === "version"
518
+ const versionsSpinner = render(_jsx(Spinner, { label: `Fetching versions for "${agentName}"...` }));
519
+ try {
520
+ const versions = await fetchAllVersionsForAgent(client, agentManagementId);
521
+ versionsSpinner.unmount();
522
+ if (versions.length === 0) {
523
+ renderView(_jsx(ErrorDisplay, { message: "No versions found for this agent.", suggestion: "Nothing to delete. Use the entire-agent option instead." }));
524
+ return;
525
+ }
526
+ const { selectedVersion } = await inquirer.prompt([
527
+ {
528
+ type: "list",
529
+ name: "selectedVersion",
530
+ message: "Select version to delete:",
531
+ choices: versions.map((v) => {
532
+ const isActive = v.id === activeVersionId;
533
+ const vLabel = `v${v.version_number}.${v.version_revision_number}`;
534
+ const statusBadge = v.status === "published"
535
+ ? chalk.green(v.status)
536
+ : v.status === "archived"
537
+ ? chalk.gray(v.status)
538
+ : chalk.yellow(v.status);
539
+ const activeBadge = isActive ? chalk.green(" ← live") : "";
540
+ return {
541
+ name: `${vLabel} [${statusBadge}]${activeBadge}`,
542
+ value: v,
543
+ };
544
+ }),
545
+ pageSize: 15,
546
+ },
547
+ ]);
548
+ await runDeleteVersion(client, {
549
+ agentManagementId,
550
+ networkId,
551
+ versionId: selectedVersion.id,
552
+ agentName,
553
+ versionLabel: `v${selectedVersion.version_number}.${selectedVersion.version_revision_number}`,
554
+ versionStatus: selectedVersion.status,
555
+ isActiveVersion: selectedVersion.id === activeVersionId,
556
+ skipConfirm: false,
557
+ });
558
+ }
559
+ catch (error) {
560
+ versionsSpinner.unmount();
561
+ renderView(_jsx(ErrorDisplay, { error: error, message: "Failed to fetch versions." }));
562
+ }
563
+ }
564
+ async function pickOrganization(client) {
565
+ const spinner = render(_jsx(Spinner, { label: "Fetching organizations..." }));
566
+ try {
567
+ // Fetch page 1 to learn totalPages, then fetch the rest in parallel.
568
+ const first = await client.organizations.listMy(1, 50);
569
+ const allOrgs = [...first.data.docs];
570
+ const totalPages = first.data.totalPages ?? 1;
571
+ if (totalPages > 1) {
572
+ const restPages = Array.from({ length: totalPages - 1 }, (_, i) => i + 2);
573
+ const rest = await Promise.all(restPages.map((p) => client.organizations.listMy(p, 50)));
574
+ for (const r of rest)
575
+ allOrgs.push(...r.data.docs);
576
+ }
577
+ spinner.unmount();
578
+ if (allOrgs.length === 0) {
579
+ const fallbackId = session.organization_id || client.getOrganizationId();
580
+ if (!fallbackId) {
581
+ renderView(_jsx(ErrorDisplay, { message: "No organizations available.", suggestion: "Run `aui login` to refresh your session." }));
582
+ return null;
583
+ }
584
+ return { id: fallbackId, name: session.organization_name || fallbackId };
585
+ }
586
+ if (allOrgs.length === 1) {
587
+ const o = allOrgs[0];
588
+ return { id: o._id || o.id, name: o.name };
589
+ }
590
+ const currentOrgId = session.organization_id;
591
+ const { chosen } = await inquirer.prompt([
592
+ {
593
+ type: "list",
594
+ name: "chosen",
595
+ message: "Select organization:",
596
+ choices: allOrgs.map((o) => {
597
+ const isCurrent = o._id === currentOrgId || o.id === currentOrgId;
598
+ const meta = chalk.gray(`(${o.niceName || o._id})`);
599
+ const label = isCurrent
600
+ ? `${o.name} ${meta} ${chalk.green("← current")}`
601
+ : `${o.name} ${meta}`;
602
+ return { name: label, value: o };
603
+ }),
604
+ pageSize: 15,
605
+ },
606
+ ]);
607
+ const o = chosen;
608
+ return { id: o._id || o.id, name: o.name };
609
+ }
610
+ catch (error) {
611
+ spinner.unmount();
612
+ renderView(_jsx(ErrorDisplay, { error: error, message: "Failed to fetch organizations." }));
613
+ return null;
614
+ }
615
+ }
616
+ async function pickAccount(client) {
617
+ const spinner = render(_jsx(Spinner, { label: "Fetching accounts..." }));
618
+ try {
619
+ // Fetch page 1 to learn totalPages, then fetch the rest in parallel.
620
+ const first = await client.accounts.list(1, 50);
621
+ const allAccounts = [...first.data.docs];
622
+ const totalPages = first.data.totalPages ?? 1;
623
+ if (totalPages > 1) {
624
+ const restPages = Array.from({ length: totalPages - 1 }, (_, i) => i + 2);
625
+ const rest = await Promise.all(restPages.map((p) => client.accounts.list(p, 50)));
626
+ for (const r of rest)
627
+ allAccounts.push(...r.data.docs);
628
+ }
629
+ spinner.unmount();
630
+ if (allAccounts.length === 0) {
631
+ renderView(_jsx(ErrorDisplay, { message: "No accounts found in this organization.", suggestion: "Nothing to delete here." }));
632
+ return null;
633
+ }
634
+ if (allAccounts.length === 1) {
635
+ const a = allAccounts[0];
636
+ return { id: a._id || a.id, name: a.name };
637
+ }
638
+ const currentAccountId = session.account_id;
639
+ const { chosen } = await inquirer.prompt([
640
+ {
641
+ type: "list",
642
+ name: "chosen",
643
+ message: "Select account:",
644
+ choices: allAccounts.map((a) => {
645
+ const isCurrent = a._id === currentAccountId || a.id === currentAccountId;
646
+ const meta = chalk.gray(`(${a.niceName})`);
647
+ const label = isCurrent
648
+ ? `${a.name} ${meta} ${chalk.green("← current")}`
649
+ : `${a.name} ${meta}`;
650
+ return { name: label, value: a };
651
+ }),
652
+ pageSize: 15,
653
+ },
654
+ ]);
655
+ const a = chosen;
656
+ return { id: a._id || a.id, name: a.name };
657
+ }
658
+ catch (error) {
659
+ spinner.unmount();
660
+ renderView(_jsx(ErrorDisplay, { error: error, message: "Failed to fetch accounts." }));
661
+ return null;
662
+ }
663
+ }
664
+ async function pickAgentInAccount(client) {
665
+ const spinner = render(_jsx(Spinner, { label: "Fetching agents..." }));
666
+ try {
667
+ // Use the legacy networks endpoint — it's already filtered server-side
668
+ // by org+account, which is what we want for an account-scoped picker.
669
+ // The agent-management lookup is deferred to AFTER selection (single
670
+ // targeted call by network_id) and only when the chosen scope needs it.
671
+ const netResp = await client.networks.list();
672
+ const networks = netResp.data;
673
+ spinner.unmount();
674
+ if (networks.length === 0) {
675
+ renderView(_jsx(ErrorDisplay, { message: "No agents found in this account.", suggestion: "Nothing to delete here." }));
676
+ return null;
677
+ }
678
+ const currentNetworkId = session.network_id;
679
+ const { chosen } = await inquirer.prompt([
680
+ {
681
+ type: "list",
682
+ name: "chosen",
683
+ message: "Select agent to delete from:",
684
+ choices: networks.map((n) => {
685
+ const id = n._id || n.id;
686
+ const isCurrent = id === currentNetworkId;
687
+ const label = isCurrent
688
+ ? `${n.name} ${chalk.green("← current")}`
689
+ : n.name;
690
+ return { name: label, value: { network: n } };
691
+ }),
692
+ pageSize: 15,
693
+ },
694
+ ]);
695
+ return chosen;
696
+ }
697
+ catch (error) {
698
+ spinner.unmount();
699
+ renderView(_jsx(ErrorDisplay, { error: error, message: "Failed to fetch agents." }));
700
+ return null;
701
+ }
702
+ }
703
+ async function fetchAllVersionsForAgent(client, agentManagementId) {
704
+ const all = [];
705
+ let page = 1;
706
+ let hasMore = true;
707
+ while (hasMore) {
708
+ const resp = await client.agentManagement.listVersions(agentManagementId, page, 50);
709
+ all.push(...resp.items);
710
+ hasMore = page < resp.pages;
711
+ page++;
712
+ }
713
+ return all;
714
+ }
715
+ async function runDeleteAllVersions(client, args) {
716
+ const { agentManagementId, networkId, agentName, activeVersionId } = args;
717
+ const fetchSpinner = render(_jsx(Spinner, { label: "Fetching versions..." }));
718
+ let versions;
719
+ try {
720
+ versions = await fetchAllVersionsForAgent(client, agentManagementId);
721
+ fetchSpinner.unmount();
722
+ }
723
+ catch (error) {
724
+ fetchSpinner.unmount();
725
+ renderView(_jsx(ErrorDisplay, { error: error, message: "Failed to fetch versions." }));
726
+ return;
727
+ }
728
+ if (versions.length === 0) {
729
+ renderView(_jsx(Box, { flexDirection: "column", paddingX: 1, children: _jsx(StatusLine, { kind: "muted", label: "No versions to delete." }) }));
730
+ return;
731
+ }
732
+ const activeCount = versions.filter((v) => v.id === activeVersionId).length;
733
+ const deletableCount = versions.length - activeCount;
734
+ if (isJsonMode()) {
735
+ if (!args.skipConfirm) {
736
+ outputJson({
737
+ requires_confirmation: true,
738
+ message: "Refusing to delete in JSON mode without --yes. Re-run with --yes to confirm.",
739
+ agent: { name: agentName, agent_management_id: agentManagementId, network_id: networkId },
740
+ versions_total: versions.length,
741
+ versions_deletable: deletableCount,
742
+ versions_active_blocked: activeCount,
743
+ });
744
+ return;
745
+ }
746
+ stderrLog(`Deleting ${versions.length} version(s) of "${agentName}" one-by-one...`);
747
+ const results = await loopDeleteVersions(client, agentManagementId, versions, activeVersionId);
748
+ const cleared = clearSessionIfMatchesAgentVersionsAfterAll(versions, results);
749
+ outputJson({
750
+ scope: "all-versions",
751
+ agent: { name: agentName, agent_management_id: agentManagementId, network_id: networkId },
752
+ attempted: versions.length,
753
+ deleted: results.filter((r) => r.success).length,
754
+ failed: results.filter((r) => !r.success).map((r) => ({
755
+ version_id: r.version.id,
756
+ version: `v${r.version.version_number}.${r.version.version_revision_number}`,
757
+ error: r.errorMessage,
758
+ })),
759
+ session_cleared: cleared,
760
+ });
761
+ return;
762
+ }
763
+ logView(_jsxs(Box, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "red", bold: true, children: "⚠ " }), _jsxs(Text, { bold: true, children: ["You are about to delete every version of \"", agentName, "\""] })] }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [" ", versions.length, " version(s) total \u00B7 ", deletableCount, " deletable", activeCount > 0 ? ` · ${activeCount} active (will be blocked by backend)` : ""] }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [" ", "This keeps the agent shell. Use 'Delete entire agent' for a full wipe."] }) })] }));
764
+ if (!args.skipConfirm) {
765
+ const { typed } = await inquirer.prompt([
766
+ {
767
+ type: "input",
768
+ name: "typed",
769
+ message: `Type the agent name (${agentName}) to confirm:`,
770
+ validate: (v) => v.trim() === agentName ? true : `Type exactly: ${agentName}`,
771
+ },
772
+ ]);
773
+ if (typed.trim() !== agentName) {
774
+ logView(_jsx(StatusLine, { kind: "muted", label: "Cancelled." }));
775
+ return;
776
+ }
777
+ }
778
+ const loopSpinner = render(_jsx(Spinner, { label: `Deleting ${versions.length} version(s)...` }));
779
+ let results;
780
+ try {
781
+ results = await loopDeleteVersions(client, agentManagementId, versions, activeVersionId);
782
+ }
783
+ finally {
784
+ loopSpinner.unmount();
785
+ }
786
+ const succeeded = results.filter((r) => r.success);
787
+ const failed = results.filter((r) => !r.success);
788
+ logView(_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(StatusLine, { kind: failed.length === 0 ? "success" : "warning", label: `Deleted ${succeeded.length}/${versions.length} version(s) of "${agentName}".` }), failed.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "gray", children: " Failed:" }), failed.slice(0, 10).map((r) => (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: " " }), _jsxs(Text, { color: "red", children: ["v", r.version.version_number, ".", r.version.version_revision_number] }), _jsxs(Text, { color: "gray", children: [" — ", r.errorMessage] })] }, r.version.id))), failed.length > 10 && (_jsxs(Text, { color: "gray", children: [" ", "... and ", failed.length - 10, " more"] }))] }))] }));
789
+ const cleared = clearSessionIfMatchesAgentVersionsAfterAll(versions, results);
790
+ if (cleared) {
791
+ logView(_jsx(StatusLine, { kind: "muted", label: "Session version pointer cleared." }));
792
+ }
793
+ }
794
+ async function loopDeleteVersions(client, agentManagementId, versions, activeVersionId) {
795
+ const results = [];
796
+ // Delete non-active versions first; the active one (if any) attempts last.
797
+ const ordered = [
798
+ ...versions.filter((v) => v.id !== activeVersionId),
799
+ ...versions.filter((v) => v.id === activeVersionId),
800
+ ];
801
+ for (const v of ordered) {
802
+ try {
803
+ await client.agentManagement.deleteVersion(agentManagementId, v.id, session.user_id);
804
+ results.push({ version: v, success: true });
805
+ }
806
+ catch (err) {
807
+ results.push({ version: v, success: false, errorMessage: formatApiErrorBody(err) });
808
+ }
809
+ }
810
+ return results;
811
+ }
812
+ function clearSessionIfMatchesAgentVersionsAfterAll(versions, results) {
813
+ const current = loadSession();
814
+ if (!current?.version_id)
815
+ return false;
816
+ const currentDeleted = results.find((r) => r.success && r.version.id === current.version_id);
817
+ if (!currentDeleted)
818
+ return false;
819
+ delete current.version_id;
820
+ delete current.version_number;
821
+ delete current.version_revision_number;
822
+ saveSession(current);
823
+ return true;
824
+ }
825
+ async function runDeleteEntireAgent(client, args) {
826
+ let { networkId, agentName, agentManagementId } = args;
827
+ // Resolve a friendly name + version count for the preview
828
+ let totalVersions;
829
+ try {
830
+ if (!agentManagementId) {
831
+ const resp = await client.agentManagement.listAgents(client.getOrganizationId(), 1, 50, { network_id: networkId });
832
+ const match = resp.items.find((a) => a.scope.network_id === networkId || a.id === networkId);
833
+ if (match) {
834
+ agentManagementId = match.id;
835
+ if (!agentName)
836
+ agentName = match.name;
837
+ }
838
+ }
839
+ if (agentManagementId) {
840
+ const versionsResp = await client.agentManagement.listVersions(agentManagementId, 1, 1);
841
+ totalVersions = versionsResp.total;
842
+ }
843
+ }
844
+ catch {
845
+ // best-effort enrichment
846
+ }
847
+ if (!agentName)
848
+ agentName = networkId;
849
+ if (isJsonMode()) {
850
+ if (!args.skipConfirm) {
851
+ outputJson({
852
+ requires_confirmation: true,
853
+ message: "Refusing to delete in JSON mode without --yes. Re-run with --yes to confirm.",
854
+ agent: { name: agentName, network_id: networkId, agent_management_id: agentManagementId },
855
+ total_versions: totalVersions,
856
+ });
857
+ return;
858
+ }
859
+ stderrLog(`Deleting agent "${agentName}" (network ${networkId})...`);
860
+ try {
861
+ const result = await client.agentManagement.deleteAgentByNetwork(networkId, session.user_id);
862
+ const networkResult = await deleteNetworkBestEffort(client, networkId);
863
+ const cleared = clearSessionIfMatchesAgent(networkId, agentManagementId);
864
+ outputJson({
865
+ deleted: true,
866
+ scope: "agent",
867
+ agent: { name: agentName, network_id: networkId, agent_management_id: agentManagementId },
868
+ versions_deleted: result.versions_deleted ?? totalVersions,
869
+ network_deleted: networkResult.deleted,
870
+ ...(networkResult.deleted ? {} : { network_delete_error: networkResult.errorMessage }),
871
+ session_cleared: cleared,
872
+ });
873
+ }
874
+ catch (error) {
875
+ const code = error instanceof Error && "status" in error
876
+ ? `HTTP_${error.status}`
877
+ : "DELETE_AGENT_FAILED";
878
+ outputJson({
879
+ deleted: false,
880
+ error: {
881
+ code,
882
+ message: error instanceof Error ? error.message : String(error),
883
+ },
884
+ });
885
+ }
886
+ return;
887
+ }
888
+ renderView(_jsx(AgentDeletePreviewView, { scope: "agent", agentName: agentName, agentId: agentManagementId || networkId, networkId: networkId, totalVersions: totalVersions }));
889
+ if (!args.skipConfirm) {
890
+ const { typed } = await inquirer.prompt([
891
+ {
892
+ type: "input",
893
+ name: "typed",
894
+ message: `Type the agent name (${agentName}) to confirm:`,
895
+ validate: (v) => v.trim() === agentName
896
+ ? true
897
+ : `Type exactly: ${agentName}`,
898
+ },
899
+ ]);
900
+ if (typed.trim() !== agentName) {
901
+ logView(_jsx(StatusLine, { kind: "muted", label: "Cancelled." }));
902
+ return;
903
+ }
904
+ }
905
+ // Two-call deletion (safer):
906
+ // 1. agent-settings DELETE → removes versions, settings, agent record
907
+ // 2. networks DELETE → safety net so the agent disappears from
908
+ // the legacy networks listing immediately
909
+ const deleteSpinner = render(_jsx(Spinner, { label: "Deleting agent..." }));
910
+ try {
911
+ await client.agentManagement.deleteAgentByNetwork(networkId, session.user_id);
912
+ deleteSpinner.unmount();
913
+ logView(_jsx(StatusLine, { kind: "success", label: "Agent-management record deleted (versions, settings)." }));
914
+ const networkSpinner = render(_jsx(Spinner, { label: "Removing network shell..." }));
915
+ const networkResult = await deleteNetworkBestEffort(client, networkId);
916
+ networkSpinner.unmount();
917
+ if (networkResult.deleted) {
918
+ logView(_jsx(StatusLine, { kind: "success", label: "Network record removed." }));
919
+ }
920
+ else {
921
+ logView(_jsx(StatusLine, { kind: "warning", label: `Network shell could not be removed automatically: ${networkResult.errorMessage}. The agent may still appear in some legacy listings.` }));
922
+ }
923
+ const cleared = clearSessionIfMatchesAgent(networkId, agentManagementId);
924
+ renderView(_jsx(AgentDeleteResultView, { scope: "agent", agentName: agentName, cleared: cleared }));
925
+ }
926
+ catch (error) {
927
+ deleteSpinner.unmount();
928
+ renderView(_jsx(ErrorDisplay, { error: error, message: `Failed to delete agent: ${formatApiErrorBody(error)}` }));
929
+ }
930
+ }
931
+ async function runDeleteVersion(client, args) {
932
+ let { agentManagementId, networkId, versionId, agentName, versionLabel, versionStatus, isActiveVersion, } = args;
933
+ // Resolve agent_id from session/network if not provided
934
+ if (!agentManagementId) {
935
+ const networkLookup = networkId || session.network_id;
936
+ if (!networkLookup) {
937
+ renderView(_jsx(ErrorDisplay, { message: "Could not resolve which agent the version belongs to.", suggestion: "Pass --agent-id <id> (or --network-id <id>), or switch to an agent first with: aui agent --switch" }));
938
+ return;
939
+ }
940
+ try {
941
+ const resp = await client.agentManagement.listAgents(client.getOrganizationId(), 1, 50, { network_id: networkLookup });
942
+ const match = resp.items.find((a) => a.scope.network_id === networkLookup || a.id === networkLookup);
943
+ if (match) {
944
+ agentManagementId = match.id;
945
+ if (!agentName)
946
+ agentName = match.name;
947
+ if (!networkId)
948
+ networkId = match.scope.network_id || undefined;
949
+ }
950
+ }
951
+ catch {
952
+ // ignore
953
+ }
954
+ if (!agentManagementId) {
955
+ renderView(_jsx(ErrorDisplay, { message: "Agent not found for the given identifiers.", suggestion: "Run `aui agent --list` to see available agents." }));
956
+ return;
957
+ }
958
+ }
959
+ // Enrich version info from backend if missing
960
+ if (!versionLabel || !versionStatus || isActiveVersion === undefined) {
961
+ try {
962
+ const v = await client.agentManagement.getVersion(agentManagementId, versionId);
963
+ if (!versionLabel)
964
+ versionLabel = `v${v.version_number}.${v.version_revision_number}`;
965
+ if (!versionStatus)
966
+ versionStatus = v.status;
967
+ const agent = await client.agentManagement.getAgent(agentManagementId).catch(() => null);
968
+ if (agent && isActiveVersion === undefined) {
969
+ isActiveVersion = agent.active_version_id === versionId;
970
+ }
971
+ if (agent && !agentName)
972
+ agentName = agent.name;
973
+ }
974
+ catch {
975
+ // best-effort enrichment
976
+ }
977
+ }
978
+ if (!agentName)
979
+ agentName = agentManagementId;
980
+ if (isJsonMode()) {
981
+ if (!args.skipConfirm) {
982
+ outputJson({
983
+ requires_confirmation: true,
984
+ message: "Refusing to delete in JSON mode without --yes. Re-run with --yes to confirm.",
985
+ agent: { name: agentName, agent_management_id: agentManagementId },
986
+ version: { id: versionId, label: versionLabel, status: versionStatus, is_active: isActiveVersion },
987
+ });
988
+ return;
989
+ }
990
+ stderrLog(`Deleting version ${versionLabel ?? versionId} of "${agentName}"...`);
991
+ try {
992
+ await client.agentManagement.deleteVersion(agentManagementId, versionId, session.user_id);
993
+ const cleared = clearSessionIfMatchesVersion(versionId);
994
+ outputJson({
995
+ deleted: true,
996
+ scope: "version",
997
+ agent: { name: agentName, agent_management_id: agentManagementId },
998
+ version: { id: versionId, label: versionLabel },
999
+ session_cleared: cleared,
1000
+ });
1001
+ }
1002
+ catch (error) {
1003
+ const code = error instanceof Error && "status" in error
1004
+ ? `HTTP_${error.status}`
1005
+ : "DELETE_VERSION_FAILED";
1006
+ outputJson({
1007
+ deleted: false,
1008
+ error: {
1009
+ code,
1010
+ message: error instanceof Error ? error.message : String(error),
1011
+ },
1012
+ });
1013
+ }
1014
+ return;
1015
+ }
1016
+ renderView(_jsx(AgentDeletePreviewView, { scope: "version", agentName: agentName, agentId: agentManagementId, networkId: networkId, versionLabel: versionLabel, versionId: versionId, versionStatus: versionStatus, isActiveVersion: isActiveVersion }));
1017
+ if (!args.skipConfirm) {
1018
+ const { confirm } = await inquirer.prompt([
1019
+ {
1020
+ type: "confirm",
1021
+ name: "confirm",
1022
+ message: isActiveVersion
1023
+ ? `${chalk.red("WARNING:")} this version is currently live. Continue?`
1024
+ : `Delete ${versionLabel ?? versionId} permanently?`,
1025
+ default: false,
1026
+ },
1027
+ ]);
1028
+ if (!confirm) {
1029
+ logView(_jsx(StatusLine, { kind: "muted", label: "Cancelled." }));
1030
+ return;
1031
+ }
1032
+ }
1033
+ const deleteSpinner = render(_jsx(Spinner, { label: "Deleting version..." }));
1034
+ try {
1035
+ await client.agentManagement.deleteVersion(agentManagementId, versionId, session.user_id);
1036
+ deleteSpinner.unmount();
1037
+ const cleared = clearSessionIfMatchesVersion(versionId);
1038
+ renderView(_jsx(AgentDeleteResultView, { scope: "version", agentName: agentName, versionLabel: versionLabel, cleared: cleared }));
1039
+ }
1040
+ catch (error) {
1041
+ deleteSpinner.unmount();
1042
+ renderView(_jsx(ErrorDisplay, { error: error, message: `Failed to delete version: ${formatApiErrorBody(error)}` }));
1043
+ }
1044
+ }
1045
+ function clearSessionIfMatchesAgent(deletedNetworkId, deletedAgentManagementId) {
1046
+ const current = loadSession();
1047
+ if (!current)
1048
+ return false;
1049
+ const matchesNetwork = current.network_id === deletedNetworkId;
1050
+ const matchesAgentMgmt = !!deletedAgentManagementId &&
1051
+ current.agent_management_id === deletedAgentManagementId;
1052
+ if (!matchesNetwork && !matchesAgentMgmt)
1053
+ return false;
1054
+ delete current.network_id;
1055
+ delete current.network_name;
1056
+ delete current.agent_management_id;
1057
+ delete current.version_id;
1058
+ delete current.version_number;
1059
+ delete current.version_revision_number;
1060
+ saveSession(current);
1061
+ return true;
1062
+ }
1063
+ function clearSessionIfMatchesVersion(deletedVersionId) {
1064
+ const current = loadSession();
1065
+ if (!current)
1066
+ return false;
1067
+ if (current.version_id !== deletedVersionId)
1068
+ return false;
1069
+ delete current.version_id;
1070
+ delete current.version_number;
1071
+ delete current.version_revision_number;
1072
+ saveSession(current);
1073
+ return true;
1074
+ }
292
1075
  async function handleCreateAgent(opts = {}) {
293
1076
  const config = getConfig();
294
1077
  const client = new AUIClient({