@elizaos/agent 2.0.0-alpha.421 → 2.0.0-alpha.425

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 (79) hide show
  1. package/package.json +4 -4
  2. package/packages/agent/src/api/accounts-routes.d.ts +11 -3
  3. package/packages/agent/src/api/accounts-routes.d.ts.map +1 -1
  4. package/packages/agent/src/api/accounts-routes.js +70 -123
  5. package/packages/agent/src/api/subscription-routes.js +27 -23
  6. package/packages/agent/src/runtime/plugin-role-gating.js +4 -3
  7. package/packages/app-core/src/api/client-agent.d.ts +63 -1
  8. package/packages/app-core/src/api/client-agent.d.ts.map +1 -1
  9. package/packages/app-core/src/api/client-agent.js +51 -0
  10. package/packages/app-core/src/components/accounts/AccountCard.d.ts +30 -0
  11. package/packages/app-core/src/components/accounts/AccountCard.d.ts.map +1 -0
  12. package/packages/app-core/src/components/accounts/AccountCard.js +183 -0
  13. package/packages/app-core/src/components/accounts/AccountList.d.ts +15 -0
  14. package/packages/app-core/src/components/accounts/AccountList.d.ts.map +1 -0
  15. package/packages/app-core/src/components/accounts/AccountList.js +80 -0
  16. package/packages/app-core/src/components/accounts/AddAccountDialog.d.ts +33 -0
  17. package/packages/app-core/src/components/accounts/AddAccountDialog.d.ts.map +1 -0
  18. package/packages/app-core/src/components/accounts/AddAccountDialog.js +277 -0
  19. package/packages/app-core/src/components/accounts/RotationStrategyPicker.d.ts +16 -0
  20. package/packages/app-core/src/components/accounts/RotationStrategyPicker.d.ts.map +1 -0
  21. package/packages/app-core/src/components/accounts/RotationStrategyPicker.js +50 -0
  22. package/packages/app-core/src/components/pages/PluginsView.d.ts.map +1 -1
  23. package/packages/app-core/src/components/pages/PluginsView.js +3 -34
  24. package/packages/app-core/src/components/settings/ProviderSwitcher.d.ts.map +1 -1
  25. package/packages/app-core/src/components/settings/ProviderSwitcher.js +2 -1
  26. package/packages/app-core/src/hooks/useAccounts.d.ts +41 -0
  27. package/packages/app-core/src/hooks/useAccounts.d.ts.map +1 -0
  28. package/packages/app-core/src/hooks/useAccounts.js +250 -0
  29. package/packages/app-core/src/i18n/locales/en.json +31 -31
  30. package/packages/app-core/src/i18n/locales/es.json +31 -31
  31. package/packages/app-core/src/i18n/locales/ko.json +31 -31
  32. package/packages/app-core/src/i18n/locales/pt.json +31 -31
  33. package/packages/app-core/src/i18n/locales/tl.json +31 -31
  34. package/packages/app-core/src/i18n/locales/vi.json +31 -31
  35. package/packages/app-core/src/i18n/locales/zh-CN.json +31 -31
  36. package/packages/typescript/src/features/basic-capabilities/index.d.ts +1 -0
  37. package/packages/typescript/src/features/basic-capabilities/index.d.ts.map +1 -1
  38. package/packages/typescript/src/features/basic-capabilities/index.js +4 -0
  39. package/packages/typescript/src/features/index.d.ts.map +1 -1
  40. package/packages/typescript/src/features/index.js +2 -7
  41. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/core-status.d.ts +14 -0
  42. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/core-status.d.ts.map +1 -0
  43. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/core-status.js +48 -0
  44. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/create.d.ts +32 -0
  45. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/create.d.ts.map +1 -0
  46. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/create.js +502 -0
  47. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.d.ts +16 -0
  48. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.d.ts.map +1 -0
  49. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.js +47 -0
  50. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.d.ts +18 -0
  51. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.d.ts.map +1 -0
  52. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.js +59 -0
  53. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.d.ts +14 -0
  54. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.d.ts.map +1 -0
  55. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.js +31 -0
  56. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list.d.ts +14 -0
  57. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list.d.ts.map +1 -0
  58. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list.js +51 -0
  59. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/reinject.d.ts +15 -0
  60. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/reinject.d.ts.map +1 -0
  61. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/reinject.js +43 -0
  62. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/search.d.ts +15 -0
  63. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/search.d.ts.map +1 -0
  64. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/search.js +52 -0
  65. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/sync.d.ts +15 -0
  66. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/sync.d.ts.map +1 -0
  67. package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/sync.js +48 -0
  68. package/packages/typescript/src/features/plugin-manager/actions/plugin.d.ts +25 -0
  69. package/packages/typescript/src/features/plugin-manager/actions/plugin.d.ts.map +1 -0
  70. package/packages/typescript/src/features/plugin-manager/actions/plugin.js +439 -0
  71. package/packages/typescript/src/features/plugin-manager/index.d.ts +2 -0
  72. package/packages/typescript/src/features/plugin-manager/index.d.ts.map +1 -1
  73. package/packages/typescript/src/features/plugin-manager/index.js +7 -10
  74. package/packages/typescript/src/features/plugin-manager/security.d.ts +33 -0
  75. package/packages/typescript/src/features/plugin-manager/security.d.ts.map +1 -0
  76. package/packages/typescript/src/features/plugin-manager/security.js +80 -0
  77. package/packages/app-core/src/runtime/plugin-manager-guard.d.ts +0 -12
  78. package/packages/app-core/src/runtime/plugin-manager-guard.d.ts.map +0 -1
  79. package/packages/app-core/src/runtime/plugin-manager-guard.js +0 -82
@@ -1316,3 +1316,54 @@ ElizaClient.prototype.saveStreamSettings = async function (settings) {
1316
1316
  body: JSON.stringify({ settings }),
1317
1317
  });
1318
1318
  };
1319
+ // ---------------------------------------------------------------------------
1320
+ // Multi-account routes (WS3)
1321
+ // ---------------------------------------------------------------------------
1322
+ ElizaClient.prototype.listAccounts = async function () {
1323
+ return this.fetch("/api/accounts");
1324
+ };
1325
+ ElizaClient.prototype.createApiKeyAccount = async function (providerId, body) {
1326
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}`, {
1327
+ method: "POST",
1328
+ body: JSON.stringify({ source: "api-key", ...body }),
1329
+ });
1330
+ };
1331
+ ElizaClient.prototype.patchAccount = async function (providerId, accountId, body) {
1332
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/${encodeURIComponent(accountId)}`, {
1333
+ method: "PATCH",
1334
+ body: JSON.stringify(body),
1335
+ });
1336
+ };
1337
+ ElizaClient.prototype.deleteAccount = async function (providerId, accountId) {
1338
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/${encodeURIComponent(accountId)}`, { method: "DELETE" });
1339
+ };
1340
+ ElizaClient.prototype.testAccount = async function (providerId, accountId) {
1341
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/${encodeURIComponent(accountId)}/test`, { method: "POST" });
1342
+ };
1343
+ ElizaClient.prototype.refreshAccountUsage = async function (providerId, accountId) {
1344
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/${encodeURIComponent(accountId)}/refresh-usage`, { method: "POST" });
1345
+ };
1346
+ ElizaClient.prototype.startAccountOAuth = async function (providerId, body) {
1347
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/oauth/start`, {
1348
+ method: "POST",
1349
+ body: JSON.stringify(body),
1350
+ });
1351
+ };
1352
+ ElizaClient.prototype.submitAccountOAuthCode = async function (providerId, body) {
1353
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/oauth/submit-code`, {
1354
+ method: "POST",
1355
+ body: JSON.stringify(body),
1356
+ });
1357
+ };
1358
+ ElizaClient.prototype.cancelAccountOAuth = async function (providerId, body) {
1359
+ return this.fetch(`/api/accounts/${encodeURIComponent(providerId)}/oauth/cancel`, {
1360
+ method: "POST",
1361
+ body: JSON.stringify(body),
1362
+ });
1363
+ };
1364
+ ElizaClient.prototype.patchProviderStrategy = async function (providerId, body) {
1365
+ return this.fetch(`/api/providers/${encodeURIComponent(providerId)}/strategy`, {
1366
+ method: "PATCH",
1367
+ body: JSON.stringify(body),
1368
+ });
1369
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * AccountCard — single account row inside an AccountList.
3
+ *
4
+ * Renders the credential's health glyph, label (inline-editable), source
5
+ * badge, priority controls (up/down arrows — no drag-drop dependency),
6
+ * usage bars (Anthropic shows session + weekly, Codex shows session
7
+ * only), enabled toggle, Test/Refresh/Delete actions, and a confirm
8
+ * dialog for delete.
9
+ */
10
+ import type { AccountWithCredentialFlag } from "../../api/client-agent";
11
+ export interface AccountCardProps {
12
+ account: AccountWithCredentialFlag;
13
+ isFirst: boolean;
14
+ isLast: boolean;
15
+ saving: boolean;
16
+ onPatch: (body: Partial<{
17
+ label: string;
18
+ enabled: boolean;
19
+ priority: number;
20
+ }>) => Promise<void>;
21
+ onMoveUp: () => Promise<void>;
22
+ onMoveDown: () => Promise<void>;
23
+ onTest: () => Promise<void>;
24
+ onRefreshUsage: () => Promise<void>;
25
+ onDelete: () => Promise<void>;
26
+ testBusy?: boolean;
27
+ refreshBusy?: boolean;
28
+ }
29
+ export declare function AccountCard({ account, isFirst, isLast, saving, onPatch, onMoveUp, onMoveDown, onTest, onRefreshUsage, onDelete, testBusy, refreshBusy, }: AccountCardProps): import("react/jsx-runtime").JSX.Element;
30
+ //# sourceMappingURL=AccountCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountCard.d.ts","sourceRoot":"","sources":["../../../../../../../app-core/src/components/accounts/AccountCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0BH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,yBAAyB,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,CACP,IAAI,EAAE,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KACjE,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAqHD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,OAAO,EACP,MAAM,EACN,MAAM,EACN,OAAO,EACP,QAAQ,EACR,UAAU,EACV,MAAM,EACN,cAAc,EACd,QAAQ,EACR,QAAgB,EAChB,WAAmB,GACpB,EAAE,gBAAgB,2CAoSlB"}
@@ -0,0 +1,183 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * AccountCard — single account row inside an AccountList.
4
+ *
5
+ * Renders the credential's health glyph, label (inline-editable), source
6
+ * badge, priority controls (up/down arrows — no drag-drop dependency),
7
+ * usage bars (Anthropic shows session + weekly, Codex shows session
8
+ * only), enabled toggle, Test/Refresh/Delete actions, and a confirm
9
+ * dialog for delete.
10
+ */
11
+ import { Badge, Button, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Spinner, StatusBadge, cn, } from "@elizaos/ui";
12
+ import { ChevronDown, ChevronUp, Pencil, Trash2 } from "lucide-react";
13
+ import { useCallback, useEffect, useState, } from "react";
14
+ import { useApp } from "../../state";
15
+ function formatRelativeTime(epochMs) {
16
+ if (!epochMs)
17
+ return "—";
18
+ const diff = Date.now() - epochMs;
19
+ if (diff < 60_000)
20
+ return "just now";
21
+ if (diff < 3_600_000)
22
+ return `${Math.floor(diff / 60_000)}m ago`;
23
+ if (diff < 86_400_000)
24
+ return `${Math.floor(diff / 3_600_000)}h ago`;
25
+ return `${Math.floor(diff / 86_400_000)}d ago`;
26
+ }
27
+ function formatResetIn(epochMs) {
28
+ if (!epochMs)
29
+ return null;
30
+ const diff = epochMs - Date.now();
31
+ if (diff <= 0)
32
+ return null;
33
+ if (diff < 3_600_000)
34
+ return `${Math.max(1, Math.floor(diff / 60_000))}m`;
35
+ if (diff < 86_400_000)
36
+ return `${Math.floor(diff / 3_600_000)}h`;
37
+ return `${Math.floor(diff / 86_400_000)}d`;
38
+ }
39
+ function clampPct(value) {
40
+ if (value == null || Number.isNaN(value))
41
+ return undefined;
42
+ return Math.max(0, Math.min(100, value));
43
+ }
44
+ function UsageBar({ label, pct, resetsAt }) {
45
+ const clamped = clampPct(pct);
46
+ const resetIn = formatResetIn(resetsAt);
47
+ const tone = clamped == null
48
+ ? "bg-muted/30"
49
+ : clamped >= 85
50
+ ? "bg-destructive"
51
+ : clamped >= 60
52
+ ? "bg-warn"
53
+ : "bg-ok";
54
+ const titleParts = [
55
+ `${label}: ${clamped == null ? "—" : `${Math.round(clamped)}%`}`,
56
+ ];
57
+ if (resetIn)
58
+ titleParts.push(`resets in ${resetIn}`);
59
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", title: titleParts.join(" · "), children: [_jsx("span", { className: "w-9 shrink-0 text-[10px] font-medium uppercase tracking-wider text-muted", children: label }), _jsx("div", { className: "relative h-1.5 min-w-[48px] flex-1 overflow-hidden rounded-full bg-bg-accent", children: _jsx("div", { className: cn("h-full transition-all", tone), style: { width: `${clamped ?? 0}%` } }) }), _jsx("span", { className: "w-8 shrink-0 text-right text-[10px] tabular-nums text-muted", children: clamped == null ? "—" : `${Math.round(clamped)}%` })] }));
60
+ }
61
+ function deriveHealthLabel(account, t) {
62
+ switch (account.health) {
63
+ case "ok":
64
+ return {
65
+ label: t("accounts.health.ok", { defaultValue: "Healthy" }),
66
+ tone: "success",
67
+ };
68
+ case "rate-limited": {
69
+ const resetIn = formatResetIn(account.healthDetail?.until);
70
+ return {
71
+ label: resetIn
72
+ ? t("accounts.health.rateLimitedWithReset", {
73
+ defaultValue: `Rate-limited (resets in ${resetIn})`,
74
+ resetIn,
75
+ })
76
+ : t("accounts.health.rateLimited", {
77
+ defaultValue: "Rate-limited",
78
+ }),
79
+ tone: "warning",
80
+ };
81
+ }
82
+ case "needs-reauth":
83
+ return {
84
+ label: t("accounts.health.needsReauth", {
85
+ defaultValue: "Needs reauth",
86
+ }),
87
+ tone: "danger",
88
+ };
89
+ case "invalid":
90
+ return {
91
+ label: t("accounts.health.invalid", {
92
+ defaultValue: "Invalid credential",
93
+ }),
94
+ tone: "danger",
95
+ };
96
+ default:
97
+ return {
98
+ label: t("accounts.health.unknown", { defaultValue: "Unknown" }),
99
+ tone: "muted",
100
+ };
101
+ }
102
+ }
103
+ export function AccountCard({ account, isFirst, isLast, saving, onPatch, onMoveUp, onMoveDown, onTest, onRefreshUsage, onDelete, testBusy = false, refreshBusy = false, }) {
104
+ const { t } = useApp();
105
+ const [labelEditing, setLabelEditing] = useState(false);
106
+ const [labelDraft, setLabelDraft] = useState(account.label);
107
+ const [confirmingDelete, setConfirmingDelete] = useState(false);
108
+ const [deleteBusy, setDeleteBusy] = useState(false);
109
+ useEffect(() => {
110
+ if (!labelEditing)
111
+ setLabelDraft(account.label);
112
+ }, [account.label, labelEditing]);
113
+ const submitLabel = useCallback(async (event) => {
114
+ event?.preventDefault();
115
+ const trimmed = labelDraft.trim();
116
+ setLabelEditing(false);
117
+ if (!trimmed || trimmed === account.label) {
118
+ setLabelDraft(account.label);
119
+ return;
120
+ }
121
+ try {
122
+ await onPatch({ label: trimmed });
123
+ }
124
+ catch {
125
+ setLabelDraft(account.label);
126
+ }
127
+ }, [account.label, labelDraft, onPatch]);
128
+ const handleLabelKey = (event) => {
129
+ if (event.key === "Escape") {
130
+ event.preventDefault();
131
+ setLabelDraft(account.label);
132
+ setLabelEditing(false);
133
+ }
134
+ else if (event.key === "Enter") {
135
+ event.preventDefault();
136
+ void submitLabel();
137
+ }
138
+ };
139
+ const handleConfirmDelete = useCallback(async () => {
140
+ setDeleteBusy(true);
141
+ try {
142
+ await onDelete();
143
+ setConfirmingDelete(false);
144
+ }
145
+ finally {
146
+ setDeleteBusy(false);
147
+ }
148
+ }, [onDelete]);
149
+ const health = deriveHealthLabel(account, t);
150
+ const isAnthropic = account.providerId === "anthropic-subscription";
151
+ const isCodex = account.providerId === "openai-codex";
152
+ const usage = account.usage;
153
+ const lastUsed = formatRelativeTime(account.lastUsedAt);
154
+ return (_jsxs("div", { className: cn("flex flex-col gap-2 rounded-lg border border-border/45 bg-card/35 px-3 py-2.5 transition-opacity", !account.enabled && "opacity-60"), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx(StatusBadge, { label: health.label, tone: health.tone, withDot: true }), labelEditing ? (_jsx("form", { onSubmit: submitLabel, className: "min-w-0 flex-1", children: _jsx(Input, { value: labelDraft, onChange: (e) => setLabelDraft(e.target.value), onBlur: () => void submitLabel(), onKeyDown: handleLabelKey, autoFocus: true, className: "h-7 max-w-[240px] text-sm", "aria-label": t("accounts.label.edit", {
155
+ defaultValue: "Account label",
156
+ }) }) })) : (_jsxs("button", { type: "button", onClick: () => setLabelEditing(true), title: t("accounts.label.editTooltip", {
157
+ defaultValue: "Click to rename",
158
+ }), className: "inline-flex min-w-0 items-center gap-1 truncate rounded text-sm font-medium text-txt hover:text-accent", children: [_jsx("span", { className: "truncate", children: account.label }), _jsx(Pencil, { className: "h-3 w-3 shrink-0 opacity-0 transition-opacity group-hover:opacity-100", "aria-hidden": true })] })), _jsx(Badge, { variant: "outline", className: "shrink-0 text-[10px] uppercase", children: account.source === "oauth"
159
+ ? t("accounts.source.oauth", { defaultValue: "OAuth" })
160
+ : t("accounts.source.apiKey", { defaultValue: "API key" }) }), _jsxs("span", { className: "shrink-0 text-[10px] tabular-nums text-muted", title: t("accounts.priority.tooltip", {
161
+ defaultValue: "Lower priority value runs first",
162
+ }), children: ["#", account.priority] }), _jsx("span", { className: "shrink-0 text-[10px] text-muted", children: t("accounts.lastUsed", {
163
+ defaultValue: `Last used ${lastUsed}`,
164
+ lastUsed,
165
+ }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: isFirst || saving, onClick: () => void onMoveUp(), "aria-label": t("accounts.moveUp", { defaultValue: "Move up" }), title: t("accounts.moveUp", { defaultValue: "Move up" }), className: "h-7 w-7 p-0", children: _jsx(ChevronUp, { className: "h-3.5 w-3.5", "aria-hidden": true }) }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: isLast || saving, onClick: () => void onMoveDown(), "aria-label": t("accounts.moveDown", { defaultValue: "Move down" }), title: t("accounts.moveDown", { defaultValue: "Move down" }), className: "h-7 w-7 p-0", children: _jsx(ChevronDown, { className: "h-3.5 w-3.5", "aria-hidden": true }) }), _jsxs("label", { className: "ml-1 inline-flex items-center gap-1.5 text-xs text-muted", children: [_jsx(Checkbox, { checked: account.enabled, disabled: saving, onCheckedChange: (value) => {
166
+ void onPatch({ enabled: value === true });
167
+ }, "aria-label": t("accounts.enabledToggle", {
168
+ defaultValue: "Account enabled",
169
+ }) }), t("accounts.enabled", { defaultValue: "Enabled" })] }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: testBusy || saving, onClick: () => void onTest(), className: "h-7 px-2 text-xs", children: testBusy ? (_jsx(Spinner, { className: "h-3 w-3" })) : (t("accounts.test", { defaultValue: "Test" })) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: refreshBusy || saving, onClick: () => void onRefreshUsage(), className: "h-7 px-2 text-xs", children: refreshBusy ? (_jsx(Spinner, { className: "h-3 w-3" })) : (t("accounts.refresh", { defaultValue: "Refresh" })) }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: saving, onClick: () => setConfirmingDelete(true), "aria-label": t("accounts.delete", { defaultValue: "Delete account" }), title: t("accounts.delete", { defaultValue: "Delete account" }), className: "h-7 w-7 p-0 text-destructive hover:bg-destructive/10", children: _jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true }) })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1.5", children: [isAnthropic ? (_jsxs(_Fragment, { children: [_jsx(UsageBar, { label: t("accounts.usage.session5h", { defaultValue: "5h" }), pct: usage?.sessionPct, resetsAt: usage?.resetsAt }), _jsx(UsageBar, { label: t("accounts.usage.weekly", { defaultValue: "7d" }), pct: usage?.weeklyPct, resetsAt: usage?.resetsAt })] })) : isCodex ? (_jsx(UsageBar, { label: t("accounts.usage.session", { defaultValue: "Session" }), pct: usage?.sessionPct, resetsAt: usage?.resetsAt })) : usage ? (_jsx(UsageBar, { label: t("accounts.usage.session", { defaultValue: "Session" }), pct: usage.sessionPct, resetsAt: usage.resetsAt })) : (_jsx("span", { className: "text-xs text-muted", children: t("accounts.usage.none", {
170
+ defaultValue: "No usage data yet — click Refresh to probe.",
171
+ }) })), !account.hasCredential ? (_jsx("span", { className: "text-[10px] text-warn", title: t("accounts.orphan.tooltip", {
172
+ defaultValue: "Pool metadata exists but no on-disk credential was found.",
173
+ }), children: t("accounts.orphan.label", {
174
+ defaultValue: "Orphan metadata",
175
+ }) })) : null] }), _jsx(Dialog, { open: confirmingDelete, onOpenChange: (open) => {
176
+ if (!deleteBusy)
177
+ setConfirmingDelete(open);
178
+ }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t("accounts.deleteConfirm.title", {
179
+ defaultValue: "Remove this account?",
180
+ }) }), _jsx(DialogDescription, { children: t("accounts.deleteConfirm.description", {
181
+ defaultValue: "Removing the account deletes its stored credential and pool metadata. This cannot be undone.",
182
+ }) })] }), _jsxs(DialogFooter, { className: "gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", disabled: deleteBusy, onClick: () => setConfirmingDelete(false), children: t("accounts.cancel", { defaultValue: "Cancel" }) }), _jsx(Button, { type: "button", variant: "destructive", disabled: deleteBusy, onClick: () => void handleConfirmDelete(), children: deleteBusy ? (_jsx(Spinner, { className: "h-3 w-3" })) : (t("accounts.delete.confirm", { defaultValue: "Remove account" })) })] })] }) })] }));
183
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * AccountList — provider-scoped multi-account UI.
3
+ *
4
+ * Renders the rotation strategy picker, "Add account" button, and a
5
+ * priority-ordered stack of `AccountCard`s for the given providerId.
6
+ * Up/down reordering swaps priorities with the neighbour via two
7
+ * sequential PATCH calls (no drag-drop dependency).
8
+ */
9
+ import type { LinkedAccountProviderId } from "@elizaos/shared";
10
+ interface AccountListProps {
11
+ providerId: LinkedAccountProviderId;
12
+ }
13
+ export declare function AccountList({ providerId }: AccountListProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=AccountList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountList.d.ts","sourceRoot":"","sources":["../../../../../../../app-core/src/components/accounts/AccountList.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAU/D,UAAU,gBAAgB;IACxB,UAAU,EAAE,uBAAuB,CAAC;CACrC;AAOD,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,gBAAgB,2CA8I3D"}
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * AccountList — provider-scoped multi-account UI.
4
+ *
5
+ * Renders the rotation strategy picker, "Add account" button, and a
6
+ * priority-ordered stack of `AccountCard`s for the given providerId.
7
+ * Up/down reordering swaps priorities with the neighbour via two
8
+ * sequential PATCH calls (no drag-drop dependency).
9
+ */
10
+ import { Button, Spinner } from "@elizaos/ui";
11
+ import { Plus } from "lucide-react";
12
+ import { useCallback, useMemo, useState } from "react";
13
+ import { useApp } from "../../state";
14
+ import { useAccounts } from "../../hooks/useAccounts";
15
+ import { AccountCard } from "./AccountCard";
16
+ import { AddAccountDialog } from "./AddAccountDialog";
17
+ import { RotationStrategyPicker } from "./RotationStrategyPicker";
18
+ const SUBSCRIPTION_PROVIDERS = new Set([
19
+ "anthropic-subscription",
20
+ "openai-codex",
21
+ ]);
22
+ export function AccountList({ providerId }) {
23
+ const { t } = useApp();
24
+ const accounts = useAccounts();
25
+ const [addDialogOpen, setAddDialogOpen] = useState(false);
26
+ const providerEntry = useMemo(() => accounts.data?.providers.find((p) => p.providerId === providerId), [accounts.data, providerId]);
27
+ const sorted = useMemo(() => providerEntry
28
+ ? [...providerEntry.accounts].sort((a, b) => a.priority - b.priority)
29
+ : [], [providerEntry]);
30
+ const subscriptionProvider = SUBSCRIPTION_PROVIDERS.has(providerId);
31
+ // The accounts API currently 501s on POST for *-api providers (the
32
+ // credential-storage layer doesn't yet write API-key records for
33
+ // those). The pool / GET still work for any pre-existing entries, so
34
+ // render the list either way and gate the Add button.
35
+ const addDisabled = !subscriptionProvider;
36
+ const handleMove = useCallback(async (accountId, direction) => {
37
+ const index = sorted.findIndex((a) => a.id === accountId);
38
+ if (index < 0)
39
+ return;
40
+ const neighbourIndex = direction === "up" ? index - 1 : index + 1;
41
+ if (neighbourIndex < 0 || neighbourIndex >= sorted.length)
42
+ return;
43
+ const self = sorted[index];
44
+ const neighbour = sorted[neighbourIndex];
45
+ if (!self || !neighbour || self.priority === neighbour.priority)
46
+ return;
47
+ // Swap priorities. We patch the lower-priority one to a temp
48
+ // sentinel first to avoid the unique-priority assumption later
49
+ // consumers might add — for the current pool there's no uniqueness
50
+ // constraint, so two sequential patches are enough.
51
+ await accounts.patch(providerId, self.id, {
52
+ priority: neighbour.priority,
53
+ });
54
+ await accounts.patch(providerId, neighbour.id, {
55
+ priority: self.priority,
56
+ });
57
+ }, [accounts, providerId, sorted]);
58
+ if (accounts.loading && !accounts.data) {
59
+ return (_jsxs("div", { className: "mt-3 flex items-center gap-2 text-xs text-muted", children: [_jsx(Spinner, { className: "h-3 w-3" }), t("accounts.loading", { defaultValue: "Loading accounts…" })] }));
60
+ }
61
+ return (_jsxs("div", { className: "mt-3 flex flex-col gap-2 rounded-xl border border-border/40 bg-bg-accent/40 p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("h3", { className: "text-xs font-semibold uppercase tracking-wider text-muted", children: t("accounts.heading", {
62
+ defaultValue: "Accounts ({{count}})",
63
+ count: sorted.length,
64
+ }) }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(RotationStrategyPicker, { providerId: providerId, value: providerEntry?.strategy, onChange: (strategy) => {
65
+ void accounts.setStrategy(providerId, strategy);
66
+ }, disabled: accounts.saving.has(`strategy:${providerId}`) }), _jsxs(Button, { type: "button", variant: "default", size: "sm", disabled: addDisabled, onClick: () => setAddDialogOpen(true), title: addDisabled
67
+ ? t("accounts.add.disabledHint", {
68
+ defaultValue: "API-key accounts for this provider are not yet supported.",
69
+ })
70
+ : undefined, className: "h-8 gap-1 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5", "aria-hidden": true }), t("accounts.add.button", { defaultValue: "Add account" })] })] })] }), sorted.length === 0 ? (_jsx("div", { className: "rounded-lg border border-dashed border-border/50 px-3 py-6 text-center text-xs text-muted", children: t("accounts.empty", {
71
+ defaultValue: "No accounts yet — add one to start using this provider.",
72
+ }) })) : (_jsx("div", { className: "flex flex-col gap-2", children: sorted.map((account, index) => (_jsx(AccountCard, { account: account, isFirst: index === 0, isLast: index === sorted.length - 1, saving: accounts.saving.has(account.id), testBusy: accounts.saving.has(`test:${account.id}`), refreshBusy: accounts.saving.has(`usage:${account.id}`), onPatch: (body) => accounts.patch(providerId, account.id, body), onMoveUp: () => handleMove(account.id, "up"), onMoveDown: () => handleMove(account.id, "down"), onTest: async () => {
73
+ await accounts.test(providerId, account.id);
74
+ }, onRefreshUsage: () => accounts.refreshUsage(providerId, account.id), onDelete: () => accounts.remove(providerId, account.id) }, account.id))) })), _jsx(AddAccountDialog, { open: addDialogOpen, providerId: providerId, onClose: () => setAddDialogOpen(false), onCreated: () => {
75
+ // useAccounts already injects the new entry on success, so
76
+ // there's nothing to do here. Refresh anyway in case the
77
+ // optimistic insert missed a server-side default.
78
+ void accounts.refresh();
79
+ } })] }));
80
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * AddAccountDialog — modal that walks the user through adding a new
3
+ * credential to a provider's account pool.
4
+ *
5
+ * Two paths:
6
+ * - **OAuth** (subscription providers): start the server-side OAuth
7
+ * flow, open the auth URL in a real browser window via
8
+ * `preOpenWindow` + `navigatePreOpenedWindow` (preserves the user
9
+ * gesture so popup blockers don't fire), then subscribe to the
10
+ * SSE stream at `/api/accounts/:provider/oauth/status` for terminal
11
+ * state. On `success`, hand the new `LinkedAccountConfig` to the
12
+ * parent. On error / timeout / cancel, surface the message inline
13
+ * and let the user retry. If the dialog closes mid-flow we cancel
14
+ * the server-side listener so it doesn't leak.
15
+ * - **API key**: simple label + key form, immediate POST.
16
+ *
17
+ * The dialog is provider-aware: OAuth-only providers
18
+ * (`anthropic-subscription`, `openai-codex`) skip the chooser screen.
19
+ * Direct API providers (`anthropic-api`, `openai-api`) show only the
20
+ * API-key form. The server still 501s on POST for the api-key path on
21
+ * subscription providers, so the chooser is mostly forward-looking
22
+ * defence against future shapes.
23
+ */
24
+ import type { LinkedAccountConfig, LinkedAccountProviderId } from "@elizaos/shared";
25
+ interface AddAccountDialogProps {
26
+ open: boolean;
27
+ providerId: LinkedAccountProviderId;
28
+ onClose: () => void;
29
+ onCreated: (account: LinkedAccountConfig) => void;
30
+ }
31
+ export declare function AddAccountDialog({ open, providerId, onClose, onCreated, }: AddAccountDialogProps): import("react/jsx-runtime").JSX.Element;
32
+ export {};
33
+ //# sourceMappingURL=AddAccountDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddAccountDialog.d.ts","sourceRoot":"","sources":["../../../../../../../app-core/src/components/accounts/AddAccountDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAeH,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,iBAAiB,CAAC;AAYzB,UAAU,qBAAqB;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,uBAAuB,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACnD;AAoDD,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,UAAU,EACV,OAAO,EACP,SAAS,GACV,EAAE,qBAAqB,2CAkZvB"}