@chrysb/alphaclaw 0.5.6 → 0.6.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/bin/alphaclaw.js +6 -1
  2. package/lib/public/css/agents.css +92 -0
  3. package/lib/public/css/explorer.css +101 -0
  4. package/lib/public/css/shell.css +15 -4
  5. package/lib/public/js/app.js +69 -3
  6. package/lib/public/js/components/action-button.js +5 -0
  7. package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +76 -0
  8. package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +490 -0
  9. package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +256 -0
  10. package/lib/public/js/components/agents-tab/agent-detail-panel.js +74 -0
  11. package/lib/public/js/components/agents-tab/agent-identity-section.js +175 -0
  12. package/lib/public/js/components/agents-tab/agent-overview/index.js +53 -0
  13. package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +44 -0
  14. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +158 -0
  15. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +169 -0
  16. package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +45 -0
  17. package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +47 -0
  18. package/lib/public/js/components/agents-tab/agent-pairing-section.js +265 -0
  19. package/lib/public/js/components/agents-tab/create-agent-modal.js +189 -0
  20. package/lib/public/js/components/agents-tab/create-channel-modal.js +323 -0
  21. package/lib/public/js/components/agents-tab/delete-agent-dialog.js +50 -0
  22. package/lib/public/js/components/agents-tab/edit-agent-modal.js +109 -0
  23. package/lib/public/js/components/agents-tab/index.js +148 -0
  24. package/lib/public/js/components/agents-tab/use-agents.js +89 -0
  25. package/lib/public/js/components/channel-account-status-badge.js +35 -0
  26. package/lib/public/js/components/channel-operations-panel.js +33 -0
  27. package/lib/public/js/components/channels.js +545 -60
  28. package/lib/public/js/components/envars.js +25 -4
  29. package/lib/public/js/components/general/index.js +21 -11
  30. package/lib/public/js/components/general/use-general-tab.js +78 -16
  31. package/lib/public/js/components/google/gmail-setup-wizard.js +1 -3
  32. package/lib/public/js/components/google/index.js +28 -30
  33. package/lib/public/js/components/icons.js +37 -0
  34. package/lib/public/js/components/models-tab/index.js +58 -224
  35. package/lib/public/js/components/models-tab/model-picker.js +212 -0
  36. package/lib/public/js/components/models-tab/use-models.js +17 -14
  37. package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -4
  38. package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
  39. package/lib/public/js/components/overflow-menu.js +122 -0
  40. package/lib/public/js/components/pairings.js +36 -8
  41. package/lib/public/js/components/routes/agents-route.js +27 -0
  42. package/lib/public/js/components/routes/general-route.js +2 -0
  43. package/lib/public/js/components/routes/index.js +1 -0
  44. package/lib/public/js/components/routes/telegram-route.js +2 -2
  45. package/lib/public/js/components/secret-input.js +8 -1
  46. package/lib/public/js/components/sidebar.js +64 -26
  47. package/lib/public/js/components/telegram-workspace/index.js +175 -74
  48. package/lib/public/js/components/telegram-workspace/manage.js +83 -10
  49. package/lib/public/js/components/telegram-workspace/onboarding.js +9 -8
  50. package/lib/public/js/components/webhooks.js +43 -18
  51. package/lib/public/js/hooks/use-app-shell-controller.js +7 -0
  52. package/lib/public/js/hooks/use-browse-navigation.js +8 -5
  53. package/lib/public/js/hooks/use-destination-session-selection.js +8 -1
  54. package/lib/public/js/lib/api.js +163 -9
  55. package/lib/public/js/lib/app-navigation.js +2 -1
  56. package/lib/public/js/lib/channel-create-operation.js +102 -0
  57. package/lib/public/js/lib/format.js +14 -0
  58. package/lib/public/js/lib/sse.js +51 -0
  59. package/lib/public/js/lib/telegram-api.js +38 -18
  60. package/lib/public/setup.html +1 -0
  61. package/lib/public/shared/browse-file-policies.json +0 -1
  62. package/lib/server/agents/service.js +1478 -0
  63. package/lib/server/constants.js +2 -2
  64. package/lib/server/env.js +3 -1
  65. package/lib/server/gateway.js +104 -20
  66. package/lib/server/gmail-watch.js +29 -2
  67. package/lib/server/onboarding/import/import-applier.js +0 -1
  68. package/lib/server/onboarding/index.js +0 -6
  69. package/lib/server/onboarding/workspace.js +73 -38
  70. package/lib/server/openclaw-config.js +23 -0
  71. package/lib/server/operation-events.js +141 -0
  72. package/lib/server/routes/agents.js +266 -0
  73. package/lib/server/routes/pairings.js +135 -25
  74. package/lib/server/routes/system.js +90 -10
  75. package/lib/server/routes/telegram.js +247 -51
  76. package/lib/server/telegram-workspace.js +61 -10
  77. package/lib/server/topic-registry.js +66 -7
  78. package/lib/server/watchdog.js +39 -1
  79. package/lib/server/webhooks.js +60 -12
  80. package/lib/server.js +21 -7
  81. package/lib/setup/core-prompts/AGENTS.md +6 -5
  82. package/lib/setup/core-prompts/TOOLS.md +1 -8
  83. package/package.json +1 -1
  84. package/lib/setup/skills/control-ui/SKILL.md +0 -62
package/bin/alphaclaw.js CHANGED
@@ -105,6 +105,7 @@ telegram topic add options:
105
105
  --thread <id> Telegram thread ID
106
106
  --name <text> Topic name
107
107
  --system <text> Optional system instructions
108
+ --agent <id> Optional agent ID for per-topic routing
108
109
  --group <id> Optional group ID override (auto-resolves when one group exists)
109
110
 
110
111
  Examples:
@@ -112,6 +113,7 @@ Examples:
112
113
  alphaclaw git-sync --message "update config" --file "workspace/app/config.json"
113
114
  alphaclaw telegram topic add --thread 12 --name "Testing"
114
115
  alphaclaw telegram topic add --thread 12 --name "Testing" --system "Handle QA requests"
116
+ alphaclaw telegram topic add --thread 12 --name "Ops" --agent ops
115
117
  `);
116
118
  process.exit(0);
117
119
  }
@@ -375,6 +377,7 @@ const runTelegramTopicAdd = () => {
375
377
  const systemInstructions = String(
376
378
  flagValue(commandArgs, "--system") || "",
377
379
  ).trim();
380
+ const agentId = String(flagValue(commandArgs, "--agent") || "").trim();
378
381
  const requestedGroupId = String(
379
382
  flagValue(commandArgs, "--group") || "",
380
383
  ).trim();
@@ -423,6 +426,7 @@ const runTelegramTopicAdd = () => {
423
426
  topicRegistry.updateTopic(groupId, threadId, {
424
427
  name: topicName,
425
428
  ...(systemInstructions ? { systemInstructions } : {}),
429
+ ...(agentId ? { agentId } : {}),
426
430
  });
427
431
 
428
432
  const requireMention =
@@ -440,8 +444,9 @@ const runTelegramTopicAdd = () => {
440
444
  workspaceDir: path.join(openclawDir, "workspace"),
441
445
  });
442
446
 
447
+ const agentSuffix = agentId ? ` agent=${agentId}` : "";
443
448
  console.log(
444
- `[alphaclaw] Topic mapped: group=${groupId} thread=${threadId} name=${topicName}`,
449
+ `[alphaclaw] Topic mapped: group=${groupId} thread=${threadId} name=${topicName}${agentSuffix}`,
445
450
  );
446
451
  console.log(
447
452
  `[alphaclaw] Concurrency updated: agent=${syncResult.maxConcurrent} subagents=${syncResult.subagentMaxConcurrent} topics=${syncResult.totalTopics}`,
@@ -0,0 +1,92 @@
1
+ /* ── Agents detail layout ────────────────────── */
2
+
3
+ .app-content-pane.agents-pane {
4
+ padding: 0;
5
+ }
6
+
7
+ /* ── Detail panel ────────────────────────────── */
8
+
9
+ .agents-detail-panel {
10
+ height: 100%;
11
+ overflow-y: auto;
12
+ padding: 0 32px;
13
+ }
14
+
15
+ .agents-detail-inner {
16
+ max-width: 42rem;
17
+ width: 100%;
18
+ margin: 0 auto;
19
+ display: flex;
20
+ flex-direction: column;
21
+ min-height: 100%;
22
+ }
23
+
24
+ .agents-detail-header {
25
+ padding-top: 16px;
26
+ display: flex;
27
+ align-items: flex-start;
28
+ justify-content: space-between;
29
+ gap: 12px;
30
+ }
31
+
32
+ .agents-detail-header-title {
33
+ font-size: 16px;
34
+ font-weight: 600;
35
+ color: var(--text);
36
+ min-width: 0;
37
+ overflow: hidden;
38
+ text-overflow: ellipsis;
39
+ white-space: nowrap;
40
+ }
41
+
42
+ /* ── Sub-tabs ────────────────────────────────── */
43
+
44
+ .agents-sub-tabs {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 2px;
48
+ padding-top: 12px;
49
+ border-bottom: 1px solid var(--border);
50
+ }
51
+
52
+ .agents-sub-tab {
53
+ padding: 8px 14px;
54
+ font-size: 13px;
55
+ font-weight: 500;
56
+ color: var(--text-muted);
57
+ background: transparent;
58
+ border: none;
59
+ border-bottom: 2px solid transparent;
60
+ cursor: pointer;
61
+ transition: color 0.12s, border-color 0.12s;
62
+ font-family: inherit;
63
+ margin-bottom: -1px;
64
+ }
65
+
66
+ .agents-sub-tab:hover {
67
+ color: var(--text);
68
+ }
69
+
70
+ .agents-sub-tab.active {
71
+ color: var(--accent);
72
+ border-bottom-color: var(--accent);
73
+ }
74
+
75
+ .agents-detail-content {
76
+ flex: 1;
77
+ padding: 24px 0;
78
+ }
79
+
80
+ /* ── Empty state ─────────────────────────────── */
81
+
82
+ .agents-empty-state {
83
+ display: flex;
84
+ flex-direction: column;
85
+ align-items: center;
86
+ justify-content: center;
87
+ height: 100%;
88
+ color: var(--text-dim);
89
+ gap: 12px;
90
+ padding: 32px;
91
+ text-align: center;
92
+ }
@@ -95,6 +95,107 @@
95
95
  overflow: hidden;
96
96
  }
97
97
 
98
+ /* ── Sidebar agents list ─────────────────────── */
99
+
100
+ .sidebar-agents-header {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: space-between;
104
+ gap: 8px;
105
+ padding: 12px 16px 4px;
106
+ }
107
+
108
+ .sidebar-agents-label {
109
+ padding: 0;
110
+ }
111
+
112
+ .sidebar-agents-add-button {
113
+ border: none;
114
+ background: transparent;
115
+ color: var(--text-muted);
116
+ font: inherit;
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ line-height: 1;
121
+ padding: 6px;
122
+ margin: -6px;
123
+ cursor: pointer;
124
+ opacity: 0.9;
125
+ transition: color 0.1s, opacity 0.1s;
126
+ }
127
+
128
+ .sidebar-agents-add-icon {
129
+ width: 16px;
130
+ height: 16px;
131
+ display: block;
132
+ }
133
+
134
+ .sidebar-agents-add-button:hover {
135
+ color: var(--text);
136
+ opacity: 1;
137
+ }
138
+
139
+ .sidebar-agents-list {
140
+ flex: 0 0 auto;
141
+ overflow: visible;
142
+ padding: 2px 0 0;
143
+ }
144
+
145
+ .sidebar-agent-item {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 6px;
149
+ padding: 6px 16px 6px 24px;
150
+ color: var(--text-muted);
151
+ font-size: 13px;
152
+ line-height: 1.4;
153
+ cursor: pointer;
154
+ transition: background 0.1s, color 0.1s;
155
+ position: relative;
156
+ user-select: none;
157
+ border: none;
158
+ background: transparent;
159
+ width: 100%;
160
+ text-align: left;
161
+ font: inherit;
162
+ }
163
+
164
+ .sidebar-agent-item:hover {
165
+ background: var(--bg-hover);
166
+ color: var(--text);
167
+ }
168
+
169
+ .sidebar-agent-item.active {
170
+ background: var(--bg-active);
171
+ color: var(--accent);
172
+ }
173
+
174
+ .sidebar-agent-item.active::before {
175
+ content: "";
176
+ position: absolute;
177
+ left: 0;
178
+ top: 0;
179
+ bottom: 0;
180
+ width: 2px;
181
+ background: var(--accent);
182
+ }
183
+
184
+ .sidebar-agent-name {
185
+ flex: 1;
186
+ min-width: 0;
187
+ overflow: hidden;
188
+ text-overflow: ellipsis;
189
+ white-space: nowrap;
190
+ }
191
+
192
+ .sidebar-agents-add {
193
+ margin: 0;
194
+ padding: 16px 14px 8px 20px;
195
+ border-top: 1px solid var(--border);
196
+ margin-top: 8px;
197
+ }
198
+
98
199
  .file-tree-wrap {
99
200
  width: 100%;
100
201
  display: flex;
@@ -174,7 +174,7 @@
174
174
  .sidebar-footer:empty { display: none; }
175
175
 
176
176
  .sidebar-footer:not(:empty) {
177
- padding: 0 16px 12px 16px;
177
+ padding: 12px 16px;
178
178
  border-top: 1px solid var(--border);
179
179
  }
180
180
 
@@ -222,7 +222,9 @@
222
222
  position: absolute;
223
223
  top: calc(100% + 4px);
224
224
  right: 0;
225
- min-width: 120px;
225
+ min-width: max-content;
226
+ width: max-content;
227
+ max-width: min(280px, calc(100vw - 24px));
226
228
  background: var(--bg-sidebar);
227
229
  border: 1px solid var(--border-strong);
228
230
  border-radius: 8px;
@@ -231,16 +233,25 @@
231
233
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
232
234
  }
233
235
 
234
- .brand-dropdown a {
236
+ .brand-dropdown a,
237
+ .brand-dropdown-item {
235
238
  display: block;
239
+ width: 100%;
236
240
  padding: 6px 10px;
241
+ font: inherit;
237
242
  font-size: 12px;
238
243
  color: var(--text-muted);
239
244
  text-decoration: none;
240
245
  border-radius: 5px;
241
246
  transition: background 0.1s, color 0.1s;
247
+ background: transparent;
248
+ border: none;
249
+ text-align: left;
250
+ white-space: nowrap;
251
+ cursor: pointer;
242
252
  }
243
- .brand-dropdown a:hover { background: var(--bg-hover); color: var(--text); }
253
+ .brand-dropdown a:hover,
254
+ .brand-dropdown-item:hover { background: var(--bg-hover); color: var(--text); }
244
255
 
245
256
  /* ── Statusbar ─────────────────────────────────── */
246
257
 
@@ -14,6 +14,7 @@ import { GlobalRestartBanner } from "./components/global-restart-banner.js";
14
14
  import { LoadingSpinner } from "./components/loading-spinner.js";
15
15
  import { AppSidebar } from "./components/sidebar.js";
16
16
  import {
17
+ AgentsRoute,
17
18
  BrowseRoute,
18
19
  DoctorRoute,
19
20
  EnvarsRoute,
@@ -25,6 +26,7 @@ import {
25
26
  WatchdogRoute,
26
27
  WebhooksRoute,
27
28
  } from "./components/routes/index.js";
29
+ import { useAgents } from "./components/agents-tab/use-agents.js";
28
30
  import { useAppShellController } from "./hooks/use-app-shell-controller.js";
29
31
  import { useAppShellUi } from "./hooks/use-app-shell-ui.js";
30
32
  import { useBrowseNavigation } from "./hooks/use-browse-navigation.js";
@@ -38,6 +40,7 @@ const html = htm.bind(h);
38
40
  const kDoctorWarningDismissedUntilUiSettingKey =
39
41
  "doctorWarningDismissedUntilMs";
40
42
  const kOneWeekMs = 7 * 24 * 60 * 60 * 1000;
43
+ const kPendingCreateAgentWindowFlag = "__alphaclawPendingCreateAgent";
41
44
 
42
45
  const App = () => {
43
46
  const [location, setLocation] = useLocation();
@@ -66,6 +69,34 @@ const App = () => {
66
69
  onCloseMobileSidebar: shellActions.closeMobileSidebar,
67
70
  });
68
71
 
72
+ const {
73
+ state: agentsState,
74
+ actions: agentsActions,
75
+ } = useAgents();
76
+
77
+ const isAgentsRoute = location.startsWith("/agents");
78
+ const selectedAgentId = (() => {
79
+ const match = location.match(/^\/agents\/([^/]+)/);
80
+ return match ? decodeURIComponent(match[1]) : "";
81
+ })();
82
+
83
+ useEffect(() => {
84
+ if (!isAgentsRoute) return;
85
+ if (window[kPendingCreateAgentWindowFlag]) return;
86
+ if (selectedAgentId) return;
87
+ if (agentsState.loading || agentsState.agents.length === 0) return;
88
+ setLocation(`/agents/${encodeURIComponent(agentsState.agents[0].id)}`);
89
+ }, [isAgentsRoute, selectedAgentId, agentsState.loading, agentsState.agents, setLocation]);
90
+
91
+ useEffect(() => {
92
+ if (!isAgentsRoute) return;
93
+ if (!window[kPendingCreateAgentWindowFlag]) return;
94
+ window[kPendingCreateAgentWindowFlag] = false;
95
+ window.setTimeout(() => {
96
+ window.dispatchEvent(new Event("alphaclaw:create-agent"));
97
+ }, 0);
98
+ }, [isAgentsRoute]);
99
+
69
100
  useEffect(() => {
70
101
  const settings = readUiSettings();
71
102
  settings[kDoctorWarningDismissedUntilUiSettingKey] =
@@ -143,6 +174,17 @@ const App = () => {
143
174
  acLatest=${controllerState.acLatest}
144
175
  acUpdating=${controllerState.acUpdating}
145
176
  onAcUpdate=${controllerActions.handleAcUpdate}
177
+ agents=${agentsState.agents}
178
+ selectedAgentId=${selectedAgentId}
179
+ onSelectAgent=${(agentId) => setLocation(`/agents/${encodeURIComponent(agentId)}`)}
180
+ onAddAgent=${() => {
181
+ if (isAgentsRoute) {
182
+ window.dispatchEvent(new Event("alphaclaw:create-agent"));
183
+ return;
184
+ }
185
+ window[kPendingCreateAgentWindowFlag] = true;
186
+ setLocation("/agents");
187
+ }}
146
188
  />
147
189
  <div
148
190
  class=${`sidebar-resizer ${shellState.isResizingSidebar ? "is-resizing" : ""}`}
@@ -181,10 +223,25 @@ const App = () => {
181
223
  }}
182
224
  />
183
225
  </div>
226
+ <div
227
+ class="app-content-pane agents-pane"
228
+ style=${{ display: isAgentsRoute ? "block" : "none" }}
229
+ >
230
+ <${AgentsRoute}
231
+ agents=${agentsState.agents}
232
+ loading=${agentsState.loading}
233
+ saving=${agentsState.saving}
234
+ agentsActions=${agentsActions}
235
+ selectedAgentId=${selectedAgentId}
236
+ onSelectAgent=${(agentId) => setLocation(`/agents/${encodeURIComponent(agentId)}`)}
237
+ onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
238
+ onSetLocation=${setLocation}
239
+ />
240
+ </div>
184
241
  <div
185
242
  class="app-content-pane"
186
243
  onscroll=${shellActions.handlePaneScroll}
187
- style=${{ display: browseState.isBrowseRoute ? "none" : "block" }}
244
+ style=${{ display: browseState.isBrowseRoute || isAgentsRoute ? "none" : "block" }}
188
245
  >
189
246
  <div
190
247
  class=${`mobile-topbar ${shellState.mobileTopbarScrolled ? "is-scrolled" : ""}`}
@@ -212,7 +269,7 @@ const App = () => {
212
269
  </span>
213
270
  </div>
214
271
  <div class="max-w-2xl w-full mx-auto">
215
- ${!browseState.isBrowseRoute
272
+ ${!browseState.isBrowseRoute && !isAgentsRoute
216
273
  ? html`
217
274
  <${Switch}>
218
275
  <${Route} path="/general">
@@ -220,6 +277,7 @@ const App = () => {
220
277
  statusData=${controllerState.sharedStatus}
221
278
  watchdogData=${controllerState.sharedWatchdogStatus}
222
279
  doctorStatusData=${controllerState.sharedDoctorStatus}
280
+ agents=${agentsState.agents}
223
281
  doctorWarningDismissedUntilMs=${doctorWarningDismissedUntilMs}
224
282
  onRefreshStatuses=${controllerActions.refreshSharedStatuses}
225
283
  onSetLocation=${setLocation}
@@ -240,8 +298,16 @@ const App = () => {
240
298
  <${Route} path="/doctor">
241
299
  <${DoctorRoute} onNavigateToBrowseFile=${browseActions.navigateToBrowseFile} />
242
300
  </${Route}>
301
+ <${Route} path="/telegram/:accountId">
302
+ ${(params) => html`
303
+ <${TelegramRoute}
304
+ accountId=${decodeURIComponent(params.accountId || "default")}
305
+ onBack=${browseActions.exitSubScreen}
306
+ />
307
+ `}
308
+ </${Route}>
243
309
  <${Route} path="/telegram">
244
- <${TelegramRoute} onBack=${browseActions.exitSubScreen} />
310
+ <${RouteRedirect} to="/telegram/default" />
245
311
  </${Route}>
246
312
  <${Route} path="/models">
247
313
  <${ModelsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
@@ -13,6 +13,11 @@ const kStaticToneClassByTone = {
13
13
  };
14
14
 
15
15
  const getToneClass = (tone, isInteractive) => {
16
+ if (tone === "subtle") {
17
+ return isInteractive
18
+ ? "border border-border text-gray-500 hover:text-gray-300 hover:border-gray-500"
19
+ : "border border-border text-gray-500";
20
+ }
16
21
  if (tone === "neutral") {
17
22
  return isInteractive
18
23
  ? "border border-border text-gray-500 hover:text-gray-300 hover:border-gray-500"
@@ -0,0 +1,76 @@
1
+ export const announceBindingsChanged = (agentId) => {
2
+ window.dispatchEvent(
3
+ new CustomEvent("alphaclaw:agent-bindings-changed", {
4
+ detail: { agentId: String(agentId || "").trim() },
5
+ }),
6
+ );
7
+ };
8
+
9
+ export const resolveChannelAccountLabel = ({ channelId, account = {} }) => {
10
+ const providerLabel = channelId
11
+ ? channelId.charAt(0).toUpperCase() + channelId.slice(1)
12
+ : "Channel";
13
+ const configuredName = String(account?.name || "").trim();
14
+ if (configuredName) return configuredName;
15
+ const accountId = String(account?.id || "").trim();
16
+ if (!accountId || accountId === "default") return providerLabel;
17
+ return `${providerLabel} ${accountId}`;
18
+ };
19
+
20
+ export const getChannelItemSortRank = (item = {}) => {
21
+ if (item.isAwaitingPairing) return 99;
22
+ if (item.isOwned) return 0;
23
+ if (item.isUnconfigured) return 3;
24
+ if (item.isAvailable) return 1;
25
+ return 2;
26
+ };
27
+
28
+ export const getAccountStatusInfo = ({ statusInfo, accountId }) => {
29
+ const normalizedAccountId = String(accountId || "").trim() || "default";
30
+ const accountStatuses =
31
+ statusInfo?.accounts && typeof statusInfo.accounts === "object"
32
+ ? statusInfo.accounts
33
+ : null;
34
+ if (accountStatuses?.[normalizedAccountId]) {
35
+ return accountStatuses[normalizedAccountId];
36
+ }
37
+ if (normalizedAccountId === "default" && statusInfo) {
38
+ return statusInfo;
39
+ }
40
+ return null;
41
+ };
42
+
43
+ export const getResolvedAccountStatusInfo = ({
44
+ account,
45
+ statusInfo,
46
+ accountId,
47
+ }) => {
48
+ const accountStatus = String(account?.status || "").trim();
49
+ if (accountStatus) {
50
+ return {
51
+ status: accountStatus,
52
+ paired: Number(account?.paired || 0),
53
+ };
54
+ }
55
+ return getAccountStatusInfo({ statusInfo, accountId });
56
+ };
57
+
58
+ export const isImplicitDefaultAccount = ({ accountId, boundAgentId }) =>
59
+ String(accountId || "").trim() === "default" &&
60
+ !String(boundAgentId || "").trim();
61
+
62
+ export const canAgentBindAccount = ({
63
+ accountId,
64
+ boundAgentId,
65
+ agentId,
66
+ isDefaultAgent,
67
+ }) => {
68
+ const normalizedBoundAgentId = String(boundAgentId || "").trim();
69
+ if (normalizedBoundAgentId) {
70
+ return normalizedBoundAgentId === String(agentId || "").trim();
71
+ }
72
+ if (isImplicitDefaultAccount({ accountId, boundAgentId })) {
73
+ return !!isDefaultAgent;
74
+ }
75
+ return true;
76
+ };