@chrysb/alphaclaw 0.5.5 → 0.5.7-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.
- package/bin/alphaclaw.js +6 -1
- package/lib/public/css/agents.css +92 -0
- package/lib/public/css/explorer.css +101 -0
- package/lib/public/css/shell.css +15 -4
- package/lib/public/js/app.js +69 -3
- package/lib/public/js/components/action-button.js +5 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +76 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +490 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +256 -0
- package/lib/public/js/components/agents-tab/agent-detail-panel.js +74 -0
- package/lib/public/js/components/agents-tab/agent-identity-section.js +175 -0
- package/lib/public/js/components/agents-tab/agent-overview/index.js +53 -0
- package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +44 -0
- package/lib/public/js/components/agents-tab/agent-overview/model-card.js +158 -0
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +169 -0
- package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +45 -0
- package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +47 -0
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +265 -0
- package/lib/public/js/components/agents-tab/create-agent-modal.js +189 -0
- package/lib/public/js/components/agents-tab/create-channel-modal.js +323 -0
- package/lib/public/js/components/agents-tab/delete-agent-dialog.js +50 -0
- package/lib/public/js/components/agents-tab/edit-agent-modal.js +109 -0
- package/lib/public/js/components/agents-tab/index.js +148 -0
- package/lib/public/js/components/agents-tab/use-agents.js +89 -0
- package/lib/public/js/components/channel-account-status-badge.js +35 -0
- package/lib/public/js/components/channel-operations-panel.js +33 -0
- package/lib/public/js/components/channels.js +545 -60
- package/lib/public/js/components/envars.js +25 -4
- package/lib/public/js/components/general/index.js +21 -11
- package/lib/public/js/components/general/use-general-tab.js +78 -16
- package/lib/public/js/components/google/gmail-setup-wizard.js +1 -3
- package/lib/public/js/components/google/index.js +28 -30
- package/lib/public/js/components/icons.js +37 -0
- package/lib/public/js/components/models-tab/index.js +58 -224
- package/lib/public/js/components/models-tab/model-picker.js +212 -0
- package/lib/public/js/components/models-tab/use-models.js +17 -14
- package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -4
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
- package/lib/public/js/components/overflow-menu.js +122 -0
- package/lib/public/js/components/pairings.js +36 -8
- package/lib/public/js/components/routes/agents-route.js +27 -0
- package/lib/public/js/components/routes/general-route.js +2 -0
- package/lib/public/js/components/routes/index.js +1 -0
- package/lib/public/js/components/routes/telegram-route.js +2 -2
- package/lib/public/js/components/secret-input.js +8 -1
- package/lib/public/js/components/sidebar.js +65 -39
- package/lib/public/js/components/telegram-workspace/index.js +175 -74
- package/lib/public/js/components/telegram-workspace/manage.js +83 -10
- package/lib/public/js/components/telegram-workspace/onboarding.js +9 -8
- package/lib/public/js/components/webhooks.js +43 -18
- package/lib/public/js/hooks/use-app-shell-controller.js +7 -0
- package/lib/public/js/hooks/use-browse-navigation.js +8 -5
- package/lib/public/js/hooks/use-destination-session-selection.js +8 -1
- package/lib/public/js/lib/api.js +163 -9
- package/lib/public/js/lib/app-navigation.js +2 -1
- package/lib/public/js/lib/channel-create-operation.js +102 -0
- package/lib/public/js/lib/format.js +14 -0
- package/lib/public/js/lib/sse.js +51 -0
- package/lib/public/js/lib/telegram-api.js +38 -18
- package/lib/public/setup.html +1 -0
- package/lib/public/shared/browse-file-policies.json +0 -1
- package/lib/server/agents/service.js +1478 -0
- package/lib/server/constants.js +2 -2
- package/lib/server/env.js +3 -1
- package/lib/server/gateway.js +104 -20
- package/lib/server/gmail-serve.js +2 -12
- package/lib/server/gmail-watch.js +29 -2
- package/lib/server/onboarding/import/import-applier.js +0 -1
- package/lib/server/onboarding/index.js +0 -6
- package/lib/server/onboarding/workspace.js +74 -38
- package/lib/server/openclaw-config.js +23 -0
- package/lib/server/operation-events.js +141 -0
- package/lib/server/routes/agents.js +266 -0
- package/lib/server/routes/pairings.js +135 -25
- package/lib/server/routes/system.js +90 -10
- package/lib/server/routes/telegram.js +247 -51
- package/lib/server/startup.js +23 -0
- package/lib/server/telegram-workspace.js +61 -10
- package/lib/server/topic-registry.js +66 -7
- package/lib/server/watchdog.js +151 -27
- package/lib/server/webhooks.js +60 -12
- package/lib/server.js +40 -27
- package/lib/setup/core-prompts/AGENTS.md +6 -5
- package/lib/setup/core-prompts/TOOLS.md +1 -8
- package/package.json +1 -1
- package/lib/setup/skills/control-ui/SKILL.md +0 -62
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
createWebhook,
|
|
11
11
|
deleteWebhook,
|
|
12
|
+
fetchAgents,
|
|
12
13
|
fetchWebhookDetail,
|
|
13
14
|
fetchWebhookRequest,
|
|
14
15
|
fetchWebhookRequests,
|
|
@@ -74,6 +75,14 @@ const getRequestStatusTone = (status) => {
|
|
|
74
75
|
};
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
const formatAgentFallbackName = (agentId = "") =>
|
|
79
|
+
String(agentId || "")
|
|
80
|
+
.trim()
|
|
81
|
+
.split(/[-_\s]+/)
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
84
|
+
.join(" ") || "Main Agent";
|
|
85
|
+
|
|
77
86
|
const jsonPretty = (value) => {
|
|
78
87
|
if (typeof value === "string") {
|
|
79
88
|
try {
|
|
@@ -223,24 +232,6 @@ const CreateWebhookModal = ({
|
|
|
223
232
|
>
|
|
224
233
|
</td>
|
|
225
234
|
</tr>
|
|
226
|
-
${
|
|
227
|
-
selectedDestination
|
|
228
|
-
? html`
|
|
229
|
-
<tr class="border-t border-border">
|
|
230
|
-
<td class="w-24 px-3 py-2 text-gray-500">Channel</td>
|
|
231
|
-
<td class="px-3 py-2 text-gray-300 font-mono">
|
|
232
|
-
<code>${selectedDestination.channel}</code>
|
|
233
|
-
</td>
|
|
234
|
-
</tr>
|
|
235
|
-
<tr>
|
|
236
|
-
<td class="w-24 px-3 py-2 text-gray-500">To</td>
|
|
237
|
-
<td class="px-3 py-2 text-gray-300 font-mono">
|
|
238
|
-
<code>${selectedDestination.to}</code>
|
|
239
|
-
</td>
|
|
240
|
-
</tr>
|
|
241
|
-
`
|
|
242
|
-
: null
|
|
243
|
-
}
|
|
244
235
|
</tbody>
|
|
245
236
|
</table>
|
|
246
237
|
</div>
|
|
@@ -290,6 +281,21 @@ export const Webhooks = ({
|
|
|
290
281
|
|
|
291
282
|
const listPoll = usePolling(fetchWebhooks, 15000);
|
|
292
283
|
const webhooks = listPoll.data?.webhooks || [];
|
|
284
|
+
const agentsPoll = usePolling(fetchAgents, 20000);
|
|
285
|
+
const agents = Array.isArray(agentsPoll.data?.agents)
|
|
286
|
+
? agentsPoll.data.agents
|
|
287
|
+
: [];
|
|
288
|
+
const agentNameById = useMemo(
|
|
289
|
+
() =>
|
|
290
|
+
new Map(
|
|
291
|
+
agents.map((agent) => [
|
|
292
|
+
String(agent?.id || "").trim(),
|
|
293
|
+
String(agent?.name || "").trim() ||
|
|
294
|
+
formatAgentFallbackName(agent?.id),
|
|
295
|
+
]),
|
|
296
|
+
),
|
|
297
|
+
[agents],
|
|
298
|
+
);
|
|
293
299
|
|
|
294
300
|
const detailPoll = usePolling(
|
|
295
301
|
async () => {
|
|
@@ -317,6 +323,13 @@ export const Webhooks = ({
|
|
|
317
323
|
|
|
318
324
|
const selectedWebhook = detailPoll.data;
|
|
319
325
|
const selectedWebhookManaged = Boolean(selectedWebhook?.managed);
|
|
326
|
+
const selectedDeliveryAgentId =
|
|
327
|
+
String(selectedWebhook?.agentId || "main").trim() || "main";
|
|
328
|
+
const selectedDeliveryAgentName =
|
|
329
|
+
agentNameById.get(selectedDeliveryAgentId) ||
|
|
330
|
+
formatAgentFallbackName(selectedDeliveryAgentId);
|
|
331
|
+
const selectedDeliveryChannel =
|
|
332
|
+
String(selectedWebhook?.channel || "last").trim() || "last";
|
|
320
333
|
const requests = requestsPoll.data?.requests || [];
|
|
321
334
|
const webhookUrl =
|
|
322
335
|
selectedWebhook?.fullUrl || `.../hooks/${selectedHookName}`;
|
|
@@ -773,6 +786,18 @@ export const Webhooks = ({
|
|
|
773
786
|
`}
|
|
774
787
|
</div>
|
|
775
788
|
|
|
789
|
+
<div
|
|
790
|
+
class="bg-black/20 border border-border rounded-lg p-3 space-y-2"
|
|
791
|
+
>
|
|
792
|
+
<p class="text-xs text-gray-500">Deliver to</p>
|
|
793
|
+
<p class="text-xs text-gray-200 font-mono ">
|
|
794
|
+
${selectedDeliveryAgentName}${" "}
|
|
795
|
+
<span class="text-xs text-gray-500 font-mono">
|
|
796
|
+
(${selectedDeliveryChannel})</span
|
|
797
|
+
>
|
|
798
|
+
</p>
|
|
799
|
+
</div>
|
|
800
|
+
|
|
776
801
|
<div
|
|
777
802
|
class="bg-black/20 border border-border rounded-lg p-3 space-y-2"
|
|
778
803
|
>
|
|
@@ -136,6 +136,13 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
136
136
|
window.removeEventListener("alphaclaw:browse-file-saved", handleBrowseFileSaved);
|
|
137
137
|
};
|
|
138
138
|
}, []);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const handleRestartRequired = () => setRestartRequired(true);
|
|
141
|
+
window.addEventListener("alphaclaw:restart-required", handleRestartRequired);
|
|
142
|
+
return () => {
|
|
143
|
+
window.removeEventListener("alphaclaw:restart-required", handleRestartRequired);
|
|
144
|
+
};
|
|
145
|
+
}, []);
|
|
139
146
|
|
|
140
147
|
const handleGatewayRestart = useCallback(async () => {
|
|
141
148
|
if (restartingGateway) return;
|
|
@@ -11,9 +11,10 @@ export const useBrowseNavigation = ({
|
|
|
11
11
|
setLocation = () => {},
|
|
12
12
|
onCloseMobileSidebar = () => {},
|
|
13
13
|
} = {}) => {
|
|
14
|
-
const [sidebarTab, setSidebarTab] = useState(() =>
|
|
15
|
-
location.startsWith("/browse")
|
|
16
|
-
|
|
14
|
+
const [sidebarTab, setSidebarTab] = useState(() => {
|
|
15
|
+
if (location.startsWith("/browse")) return "browse";
|
|
16
|
+
return "menu";
|
|
17
|
+
});
|
|
17
18
|
const [lastBrowsePath, setLastBrowsePath] = useState(() => {
|
|
18
19
|
const settings = readUiSettings();
|
|
19
20
|
return typeof settings[kBrowseLastPathUiSettingKey] === "string"
|
|
@@ -26,7 +27,8 @@ export const useBrowseNavigation = ({
|
|
|
26
27
|
if (
|
|
27
28
|
typeof storedRoute === "string" &&
|
|
28
29
|
storedRoute.startsWith("/") &&
|
|
29
|
-
!storedRoute.startsWith("/browse")
|
|
30
|
+
!storedRoute.startsWith("/browse") &&
|
|
31
|
+
!storedRoute.startsWith("/agents")
|
|
30
32
|
) {
|
|
31
33
|
return storedRoute;
|
|
32
34
|
}
|
|
@@ -77,7 +79,7 @@ export const useBrowseNavigation = ({
|
|
|
77
79
|
|
|
78
80
|
useEffect(() => {
|
|
79
81
|
if (location.startsWith("/browse")) return;
|
|
80
|
-
if (location
|
|
82
|
+
if (location.startsWith("/telegram")) return;
|
|
81
83
|
setLastMenuRoute((currentRoute) =>
|
|
82
84
|
currentRoute === location ? currentRoute : location,
|
|
83
85
|
);
|
|
@@ -150,6 +152,7 @@ export const useBrowseNavigation = ({
|
|
|
150
152
|
}
|
|
151
153
|
if (nextTab === "browse" && !location.startsWith("/browse")) {
|
|
152
154
|
setLocation(buildBrowseRoute(lastBrowsePath));
|
|
155
|
+
return;
|
|
153
156
|
}
|
|
154
157
|
}, [lastBrowsePath, lastMenuRoute, location, setLocation]);
|
|
155
158
|
|
|
@@ -12,7 +12,14 @@ export const getDestinationFromSession = (sessionRow = null) => {
|
|
|
12
12
|
const channel = String(sessionRow?.replyChannel || "").trim();
|
|
13
13
|
const to = String(sessionRow?.replyTo || "").trim();
|
|
14
14
|
if (!channel || !to) return null;
|
|
15
|
-
|
|
15
|
+
const key = String(sessionRow?.key || "").trim();
|
|
16
|
+
const agentMatch = key.match(/^agent:([^:]+):/);
|
|
17
|
+
const agentId = String(agentMatch?.[1] || "").trim();
|
|
18
|
+
return {
|
|
19
|
+
channel,
|
|
20
|
+
to,
|
|
21
|
+
...(agentId ? { agentId } : {}),
|
|
22
|
+
};
|
|
16
23
|
};
|
|
17
24
|
|
|
18
25
|
export const useDestinationSessionSelection = ({
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { subscribeToSse } from "./sse.js";
|
|
2
|
+
|
|
1
3
|
const kClientTimeZoneHeader = "x-client-timezone";
|
|
2
4
|
|
|
3
5
|
const getBrowserTimeZone = () => {
|
|
@@ -39,22 +41,22 @@ export async function fetchPairings() {
|
|
|
39
41
|
return res.json();
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
export async function approvePairing(id, channel) {
|
|
44
|
+
export async function approvePairing(id, channel, accountId = "") {
|
|
43
45
|
const res = await authFetch(`/api/pairings/${id}/approve`, {
|
|
44
46
|
method: "POST",
|
|
45
47
|
headers: { "Content-Type": "application/json" },
|
|
46
|
-
body: JSON.stringify({ channel }),
|
|
48
|
+
body: JSON.stringify({ channel, accountId }),
|
|
47
49
|
});
|
|
48
50
|
return res.json();
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
export async function rejectPairing(id, channel) {
|
|
53
|
+
export async function rejectPairing(id, channel, accountId = "") {
|
|
52
54
|
const res = await authFetch(`/api/pairings/${id}/reject`, {
|
|
53
55
|
method: "POST",
|
|
54
56
|
headers: { "Content-Type": "application/json" },
|
|
55
|
-
body: JSON.stringify({ channel }),
|
|
57
|
+
body: JSON.stringify({ channel, accountId }),
|
|
56
58
|
});
|
|
57
|
-
return res
|
|
59
|
+
return parseJsonOrThrow(res, "Could not reject pairing");
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
export async function fetchGoogleAccounts() {
|
|
@@ -545,8 +547,9 @@ export const setPrimaryModel = async (modelKey) => {
|
|
|
545
547
|
return res.json();
|
|
546
548
|
};
|
|
547
549
|
|
|
548
|
-
export const fetchModelsConfig = async () => {
|
|
549
|
-
const
|
|
550
|
+
export const fetchModelsConfig = async ({ agentId } = {}) => {
|
|
551
|
+
const qs = agentId ? `?agentId=${encodeURIComponent(agentId)}` : "";
|
|
552
|
+
const res = await authFetch(`/api/models/config${qs}`);
|
|
550
553
|
return res.json();
|
|
551
554
|
};
|
|
552
555
|
|
|
@@ -555,8 +558,10 @@ export const saveModelsConfig = async ({
|
|
|
555
558
|
configuredModels,
|
|
556
559
|
profiles,
|
|
557
560
|
authOrder,
|
|
558
|
-
|
|
559
|
-
|
|
561
|
+
agentId,
|
|
562
|
+
} = {}) => {
|
|
563
|
+
const qs = agentId ? `?agentId=${encodeURIComponent(agentId)}` : "";
|
|
564
|
+
const res = await authFetch(`/api/models/config${qs}`, {
|
|
560
565
|
method: "PUT",
|
|
561
566
|
headers: { "Content-Type": "application/json" },
|
|
562
567
|
body: JSON.stringify({ primary, configuredModels, profiles, authOrder }),
|
|
@@ -591,6 +596,155 @@ export const deleteAuthProfile = async (profileId) => {
|
|
|
591
596
|
return res.json();
|
|
592
597
|
};
|
|
593
598
|
|
|
599
|
+
export const fetchAgents = async () => {
|
|
600
|
+
const res = await authFetch("/api/agents");
|
|
601
|
+
return parseJsonOrThrow(res, "Could not load agents");
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
export const fetchChannelAccounts = async () => {
|
|
605
|
+
const res = await authFetch("/api/channels/accounts");
|
|
606
|
+
return parseJsonOrThrow(res, "Could not load channel accounts");
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
export const fetchChannelAccountToken = async ({
|
|
610
|
+
provider = "",
|
|
611
|
+
accountId = "default",
|
|
612
|
+
} = {}) => {
|
|
613
|
+
const params = new URLSearchParams({
|
|
614
|
+
provider: String(provider || ""),
|
|
615
|
+
accountId: String(accountId || "default"),
|
|
616
|
+
});
|
|
617
|
+
const res = await authFetch(`/api/channels/accounts/token?${params.toString()}`);
|
|
618
|
+
return parseJsonOrThrow(res, "Could not load channel token");
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
export const createChannelAccount = async (payload) => {
|
|
622
|
+
const res = await authFetch("/api/channels/accounts", {
|
|
623
|
+
method: "POST",
|
|
624
|
+
headers: { "Content-Type": "application/json" },
|
|
625
|
+
body: JSON.stringify(payload || {}),
|
|
626
|
+
});
|
|
627
|
+
return parseJsonOrThrow(res, "Could not create channel account");
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
export const createChannelAccountJob = async (payload) => {
|
|
631
|
+
const res = await authFetch("/api/channels/accounts/jobs", {
|
|
632
|
+
method: "POST",
|
|
633
|
+
headers: { "Content-Type": "application/json" },
|
|
634
|
+
body: JSON.stringify(payload || {}),
|
|
635
|
+
});
|
|
636
|
+
return parseJsonOrThrow(res, "Could not start channel account operation");
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
export const subscribeOperationEvents = ({
|
|
640
|
+
operationId = "",
|
|
641
|
+
onMessage = () => {},
|
|
642
|
+
onError = () => {},
|
|
643
|
+
}) =>
|
|
644
|
+
subscribeToSse({
|
|
645
|
+
url: `/api/operations/${encodeURIComponent(String(operationId || ""))}/events`,
|
|
646
|
+
onMessage,
|
|
647
|
+
onError,
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
export const updateChannelAccount = async (payload) => {
|
|
651
|
+
const res = await authFetch("/api/channels/accounts", {
|
|
652
|
+
method: "PUT",
|
|
653
|
+
headers: { "Content-Type": "application/json" },
|
|
654
|
+
body: JSON.stringify(payload || {}),
|
|
655
|
+
});
|
|
656
|
+
return parseJsonOrThrow(res, "Could not update channel account");
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
export const deleteChannelAccount = async (payload) => {
|
|
660
|
+
const res = await authFetch("/api/channels/accounts", {
|
|
661
|
+
method: "DELETE",
|
|
662
|
+
headers: { "Content-Type": "application/json" },
|
|
663
|
+
body: JSON.stringify(payload || {}),
|
|
664
|
+
});
|
|
665
|
+
return parseJsonOrThrow(res, "Could not delete channel account");
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
export const fetchAgent = async (agentId) => {
|
|
669
|
+
const res = await authFetch(`/api/agents/${encodeURIComponent(String(agentId || ""))}`);
|
|
670
|
+
return parseJsonOrThrow(res, "Could not load agent");
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
export const fetchAgentWorkspaceSize = async (agentId) => {
|
|
674
|
+
const res = await authFetch(
|
|
675
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}/workspace-size`,
|
|
676
|
+
);
|
|
677
|
+
return parseJsonOrThrow(res, "Could not load workspace size");
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
export const fetchAgentBindings = async (agentId) => {
|
|
681
|
+
const res = await authFetch(
|
|
682
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}/bindings`,
|
|
683
|
+
);
|
|
684
|
+
return parseJsonOrThrow(res, "Could not load agent bindings");
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
export const createAgent = async (payload) => {
|
|
688
|
+
const res = await authFetch("/api/agents", {
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: { "Content-Type": "application/json" },
|
|
691
|
+
body: JSON.stringify(payload || {}),
|
|
692
|
+
});
|
|
693
|
+
return parseJsonOrThrow(res, "Could not create agent");
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
export const updateAgent = async (agentId, payload) => {
|
|
697
|
+
const res = await authFetch(`/api/agents/${encodeURIComponent(String(agentId || ""))}`, {
|
|
698
|
+
method: "PUT",
|
|
699
|
+
headers: { "Content-Type": "application/json" },
|
|
700
|
+
body: JSON.stringify(payload || {}),
|
|
701
|
+
});
|
|
702
|
+
return parseJsonOrThrow(res, "Could not update agent");
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
export const addAgentBinding = async (agentId, payload) => {
|
|
706
|
+
const res = await authFetch(
|
|
707
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}/bindings`,
|
|
708
|
+
{
|
|
709
|
+
method: "POST",
|
|
710
|
+
headers: { "Content-Type": "application/json" },
|
|
711
|
+
body: JSON.stringify(payload || {}),
|
|
712
|
+
},
|
|
713
|
+
);
|
|
714
|
+
return parseJsonOrThrow(res, "Could not add agent binding");
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
export const removeAgentBinding = async (agentId, payload) => {
|
|
718
|
+
const res = await authFetch(
|
|
719
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}/bindings`,
|
|
720
|
+
{
|
|
721
|
+
method: "DELETE",
|
|
722
|
+
headers: { "Content-Type": "application/json" },
|
|
723
|
+
body: JSON.stringify(payload || {}),
|
|
724
|
+
},
|
|
725
|
+
);
|
|
726
|
+
return parseJsonOrThrow(res, "Could not remove agent binding");
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
export const deleteAgent = async (agentId, { keepWorkspace = true } = {}) => {
|
|
730
|
+
const query = new URLSearchParams({
|
|
731
|
+
keepWorkspace: keepWorkspace ? "true" : "false",
|
|
732
|
+
});
|
|
733
|
+
const res = await authFetch(
|
|
734
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}?${query.toString()}`,
|
|
735
|
+
{ method: "DELETE" },
|
|
736
|
+
);
|
|
737
|
+
return parseJsonOrThrow(res, "Could not delete agent");
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
export const setDefaultAgent = async (agentId) => {
|
|
741
|
+
const res = await authFetch(
|
|
742
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}/default`,
|
|
743
|
+
{ method: "POST" },
|
|
744
|
+
);
|
|
745
|
+
return parseJsonOrThrow(res, "Could not set default agent");
|
|
746
|
+
};
|
|
747
|
+
|
|
594
748
|
export const fetchCodexStatus = async () => {
|
|
595
749
|
const res = await authFetch("/api/codex/status");
|
|
596
750
|
return res.json();
|
|
@@ -27,8 +27,9 @@ export const kNavSections = [
|
|
|
27
27
|
|
|
28
28
|
export const getSelectedNavId = ({ isBrowseRoute = false, location = "" } = {}) => {
|
|
29
29
|
if (isBrowseRoute) return "browse";
|
|
30
|
-
if (location
|
|
30
|
+
if (location.startsWith("/telegram")) return "";
|
|
31
31
|
if (location.startsWith("/models")) return "models";
|
|
32
|
+
if (location.startsWith("/agents")) return "agents";
|
|
32
33
|
if (location.startsWith("/providers")) return "models";
|
|
33
34
|
if (location.startsWith("/watchdog")) return "watchdog";
|
|
34
35
|
if (location.startsWith("/usage")) return "usage";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChannelAccount,
|
|
3
|
+
createChannelAccountJob,
|
|
4
|
+
subscribeOperationEvents,
|
|
5
|
+
} from "./api.js";
|
|
6
|
+
|
|
7
|
+
export const createChannelAccountWithProgress = async ({
|
|
8
|
+
payload = {},
|
|
9
|
+
onPhase = () => {},
|
|
10
|
+
}) => {
|
|
11
|
+
onPhase("Loading...");
|
|
12
|
+
if (typeof window?.EventSource !== "function") {
|
|
13
|
+
return createChannelAccount(payload);
|
|
14
|
+
}
|
|
15
|
+
const startResult = await createChannelAccountJob(payload);
|
|
16
|
+
const operationId = String(startResult?.operationId || "").trim();
|
|
17
|
+
if (!operationId) {
|
|
18
|
+
throw new Error("Could not start channel creation operation");
|
|
19
|
+
}
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let settleCalled = false;
|
|
22
|
+
let activePhase = "";
|
|
23
|
+
let activePhaseAtMs = 0;
|
|
24
|
+
let deferredPhase = null;
|
|
25
|
+
let deferredTimer = null;
|
|
26
|
+
const kPhaseMinimumVisibleMs = {
|
|
27
|
+
restarting: 1200,
|
|
28
|
+
};
|
|
29
|
+
const clearDeferredTimer = () => {
|
|
30
|
+
if (!deferredTimer) return;
|
|
31
|
+
clearTimeout(deferredTimer);
|
|
32
|
+
deferredTimer = null;
|
|
33
|
+
};
|
|
34
|
+
const applyPhase = ({ phase = "", label = "" } = {}) => {
|
|
35
|
+
const nextPhase = String(phase || "").trim();
|
|
36
|
+
const nextLabel = String(label || "").trim();
|
|
37
|
+
if (!nextLabel) return;
|
|
38
|
+
const minVisibleMs = Number(kPhaseMinimumVisibleMs[activePhase] || 0);
|
|
39
|
+
const elapsedMs = activePhaseAtMs > 0 ? Date.now() - activePhaseAtMs : 0;
|
|
40
|
+
if (
|
|
41
|
+
minVisibleMs > 0 &&
|
|
42
|
+
nextPhase &&
|
|
43
|
+
nextPhase !== activePhase &&
|
|
44
|
+
elapsedMs < minVisibleMs
|
|
45
|
+
) {
|
|
46
|
+
deferredPhase = { phase: nextPhase, label: nextLabel };
|
|
47
|
+
clearDeferredTimer();
|
|
48
|
+
deferredTimer = setTimeout(() => {
|
|
49
|
+
deferredTimer = null;
|
|
50
|
+
const next = deferredPhase;
|
|
51
|
+
deferredPhase = null;
|
|
52
|
+
if (!next) return;
|
|
53
|
+
applyPhase(next);
|
|
54
|
+
}, minVisibleMs - elapsedMs);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
clearDeferredTimer();
|
|
58
|
+
deferredPhase = null;
|
|
59
|
+
onPhase(nextLabel);
|
|
60
|
+
activePhase = nextPhase;
|
|
61
|
+
activePhaseAtMs = Date.now();
|
|
62
|
+
};
|
|
63
|
+
const closeWithCleanup = () => {
|
|
64
|
+
clearDeferredTimer();
|
|
65
|
+
close();
|
|
66
|
+
};
|
|
67
|
+
const close = subscribeOperationEvents({
|
|
68
|
+
operationId,
|
|
69
|
+
onMessage: (entry) => {
|
|
70
|
+
const eventName = String(entry?.event || "").trim();
|
|
71
|
+
if (eventName === "phase") {
|
|
72
|
+
applyPhase({
|
|
73
|
+
phase: String(entry?.data?.phase || "").trim(),
|
|
74
|
+
label: String(entry?.data?.label || "").trim(),
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (eventName === "done") {
|
|
79
|
+
if (settleCalled) return;
|
|
80
|
+
settleCalled = true;
|
|
81
|
+
closeWithCleanup();
|
|
82
|
+
resolve(entry?.data || {});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (eventName === "error") {
|
|
86
|
+
if (settleCalled) return;
|
|
87
|
+
settleCalled = true;
|
|
88
|
+
closeWithCleanup();
|
|
89
|
+
reject(
|
|
90
|
+
new Error(String(entry?.data?.error || "Could not create channel")),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onError: () => {
|
|
95
|
+
if (settleCalled) return;
|
|
96
|
+
settleCalled = true;
|
|
97
|
+
closeWithCleanup();
|
|
98
|
+
reject(new Error("Channel operation stream disconnected"));
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
};
|
|
@@ -37,6 +37,20 @@ export const formatCompactNumber = (value) => {
|
|
|
37
37
|
return kCompactNumberFormatter.format(numberValue);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
export const formatBytes = (value) => {
|
|
41
|
+
const bytes = Number(value || 0);
|
|
42
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
43
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
44
|
+
let unitIndex = 0;
|
|
45
|
+
let nextValue = bytes;
|
|
46
|
+
while (nextValue >= 1024 && unitIndex < units.length - 1) {
|
|
47
|
+
nextValue /= 1024;
|
|
48
|
+
unitIndex += 1;
|
|
49
|
+
}
|
|
50
|
+
const precision = nextValue >= 100 || unitIndex === 0 ? 0 : nextValue >= 10 ? 1 : 2;
|
|
51
|
+
return `${nextValue.toFixed(precision)} ${units[unitIndex]}`;
|
|
52
|
+
};
|
|
53
|
+
|
|
40
54
|
export const formatUsd = (value) => kUsdFormatter.format(Number(value || 0));
|
|
41
55
|
|
|
42
56
|
export const formatLocaleDateTime = (
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const parseEventPayload = (value) => {
|
|
2
|
+
if (typeof value !== "string" || !value.trim()) return {};
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(value);
|
|
5
|
+
} catch {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const subscribeToSse = ({
|
|
11
|
+
url = "",
|
|
12
|
+
onMessage = () => {},
|
|
13
|
+
onError = () => {},
|
|
14
|
+
}) => {
|
|
15
|
+
if (typeof window?.EventSource !== "function") {
|
|
16
|
+
throw new Error("Server events are not supported in this browser");
|
|
17
|
+
}
|
|
18
|
+
const source = new window.EventSource(String(url || ""), { withCredentials: true });
|
|
19
|
+
const handlePhase = (event) => {
|
|
20
|
+
onMessage({
|
|
21
|
+
event: "phase",
|
|
22
|
+
data: parseEventPayload(event?.data || ""),
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const handleDone = (event) => {
|
|
26
|
+
onMessage({
|
|
27
|
+
event: "done",
|
|
28
|
+
data: parseEventPayload(event?.data || ""),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const handleFailure = (event) => {
|
|
32
|
+
onMessage({
|
|
33
|
+
event: "error",
|
|
34
|
+
data: parseEventPayload(event?.data || ""),
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const handleError = (event) => {
|
|
38
|
+
onError(event);
|
|
39
|
+
};
|
|
40
|
+
source.addEventListener("phase", handlePhase);
|
|
41
|
+
source.addEventListener("done", handleDone);
|
|
42
|
+
source.addEventListener("error", handleFailure);
|
|
43
|
+
source.onerror = handleError;
|
|
44
|
+
return () => {
|
|
45
|
+
source.removeEventListener("phase", handlePhase);
|
|
46
|
+
source.removeEventListener("done", handleDone);
|
|
47
|
+
source.removeEventListener("error", handleFailure);
|
|
48
|
+
source.onerror = null;
|
|
49
|
+
source.close();
|
|
50
|
+
};
|
|
51
|
+
};
|