@chrysb/alphaclaw 0.6.0-beta.1 → 0.6.0-beta.3
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/lib/public/js/components/agents-tab/agent-bindings-section/channel-item-trailing.js +203 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +10 -12
- package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +19 -287
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +1 -1
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-channel-items.js +211 -0
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +17 -4
- package/lib/public/js/components/agents-tab/create-channel-modal.js +29 -6
- package/lib/public/js/components/channels.js +19 -14
- package/lib/public/js/components/models-tab/provider-auth-card.js +18 -1
- package/lib/public/js/components/models-tab/use-models.js +15 -8
- package/lib/public/js/lib/channel-accounts.js +20 -0
- package/lib/public/js/lib/model-config.js +8 -4
- package/lib/server/agents/agents.js +207 -0
- package/lib/server/agents/bindings.js +74 -0
- package/lib/server/agents/channels.js +674 -0
- package/lib/server/agents/service.js +28 -1458
- package/lib/server/agents/shared.js +631 -0
- package/lib/server/constants.js +6 -0
- package/lib/server/db/usage/pricing.js +1 -0
- package/lib/server/openclaw-config.js +13 -0
- package/lib/server/routes/models.js +12 -1
- package/lib/server/routes/pairings.js +29 -3
- package/lib/server/routes/system.js +1 -6
- package/lib/server/routes/telegram.js +34 -16
- package/lib/server/telegram-workspace.js +22 -7
- package/lib/server/topic-registry.js +1 -4
- package/lib/server/utils/channels.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useEffect, useMemo, useState } from "https://esm.sh/preact/hooks";
|
|
3
|
+
import htm from "https://esm.sh/htm";
|
|
4
|
+
import {
|
|
5
|
+
canAgentBindAccount,
|
|
6
|
+
getChannelItemSortRank,
|
|
7
|
+
getResolvedAccountStatusInfo,
|
|
8
|
+
isImplicitDefaultAccount,
|
|
9
|
+
resolveChannelAccountLabel,
|
|
10
|
+
} from "./helpers.js";
|
|
11
|
+
|
|
12
|
+
const html = htm.bind(h);
|
|
13
|
+
|
|
14
|
+
export const useChannelItems = ({
|
|
15
|
+
agentId = "",
|
|
16
|
+
agentNameMap = new Map(),
|
|
17
|
+
channelStatus = {},
|
|
18
|
+
configuredChannelMap = new Map(),
|
|
19
|
+
configuredChannels = [],
|
|
20
|
+
defaultAgentId = "",
|
|
21
|
+
isDefaultAgent = false,
|
|
22
|
+
}) => {
|
|
23
|
+
const hasDiscordAccount = useMemo(() => {
|
|
24
|
+
const discordChannel = configuredChannelMap.get("discord");
|
|
25
|
+
return Array.isArray(discordChannel?.accounts) && discordChannel.accounts.length > 0;
|
|
26
|
+
}, [configuredChannelMap]);
|
|
27
|
+
|
|
28
|
+
const [showAssignedElsewhere, setShowAssignedElsewhere] = useState(false);
|
|
29
|
+
|
|
30
|
+
const channelItemData = useMemo(() => {
|
|
31
|
+
const channelOrderMap = new Map(
|
|
32
|
+
configuredChannels.map((entry, index) => [
|
|
33
|
+
String(entry?.channel || "").trim(),
|
|
34
|
+
index,
|
|
35
|
+
]),
|
|
36
|
+
);
|
|
37
|
+
const accountOrderMap = new Map(
|
|
38
|
+
configuredChannels.flatMap((entry) =>
|
|
39
|
+
(Array.isArray(entry?.accounts) ? entry.accounts : []).map(
|
|
40
|
+
(account, accountIndex) => [
|
|
41
|
+
`${String(entry?.channel || "").trim()}:${String(account?.id || "").trim() || "default"}`,
|
|
42
|
+
accountIndex,
|
|
43
|
+
],
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
const channelIds = Array.from(
|
|
48
|
+
new Set([
|
|
49
|
+
...configuredChannels.map((entry) => String(entry.channel || "").trim()),
|
|
50
|
+
]),
|
|
51
|
+
).filter(Boolean);
|
|
52
|
+
|
|
53
|
+
return channelIds
|
|
54
|
+
.flatMap((channelId) => {
|
|
55
|
+
const configuredChannel = configuredChannelMap.get(channelId);
|
|
56
|
+
const statusInfo = channelStatus?.[channelId] || null;
|
|
57
|
+
const accounts = Array.isArray(configuredChannel?.accounts)
|
|
58
|
+
? configuredChannel.accounts
|
|
59
|
+
: [];
|
|
60
|
+
|
|
61
|
+
if (!configuredChannel && !statusInfo) return [];
|
|
62
|
+
|
|
63
|
+
return accounts.map((account) => {
|
|
64
|
+
const accountId = String(account?.id || "").trim() || "default";
|
|
65
|
+
const boundAgentId = String(account?.boundAgentId || "").trim();
|
|
66
|
+
const accountStatusInfo = getResolvedAccountStatusInfo({
|
|
67
|
+
account,
|
|
68
|
+
statusInfo,
|
|
69
|
+
accountId,
|
|
70
|
+
});
|
|
71
|
+
const isImplicitDefaultOwned =
|
|
72
|
+
isDefaultAgent &&
|
|
73
|
+
isImplicitDefaultAccount({ accountId, boundAgentId });
|
|
74
|
+
const isOwned = boundAgentId === agentId || isImplicitDefaultOwned;
|
|
75
|
+
const isImplicitDefaultElsewhere =
|
|
76
|
+
!isDefaultAgent &&
|
|
77
|
+
isImplicitDefaultAccount({ accountId, boundAgentId });
|
|
78
|
+
const isAvailable = canAgentBindAccount({
|
|
79
|
+
accountId,
|
|
80
|
+
boundAgentId,
|
|
81
|
+
agentId,
|
|
82
|
+
isDefaultAgent,
|
|
83
|
+
});
|
|
84
|
+
const ownerAgentId =
|
|
85
|
+
boundAgentId ||
|
|
86
|
+
(isImplicitDefaultAccount({ accountId, boundAgentId })
|
|
87
|
+
? defaultAgentId
|
|
88
|
+
: "");
|
|
89
|
+
const ownerAgentName = String(
|
|
90
|
+
agentNameMap.get(ownerAgentId) || ownerAgentId || "",
|
|
91
|
+
).trim();
|
|
92
|
+
const canNavigateToOwnerAgent =
|
|
93
|
+
!!ownerAgentId && ownerAgentId !== agentId && !!ownerAgentName;
|
|
94
|
+
const canOpenWorkspace =
|
|
95
|
+
channelId === "telegram" &&
|
|
96
|
+
isOwned &&
|
|
97
|
+
accountStatusInfo?.status === "paired";
|
|
98
|
+
|
|
99
|
+
const accountData = {
|
|
100
|
+
id: accountId,
|
|
101
|
+
provider: channelId,
|
|
102
|
+
name: resolveChannelAccountLabel({ channelId, account }),
|
|
103
|
+
rawName: String(account?.name || "").trim(),
|
|
104
|
+
ownerAgentId,
|
|
105
|
+
ownerAgentName,
|
|
106
|
+
boundAgentId,
|
|
107
|
+
isOwned,
|
|
108
|
+
envKey: String(account?.envKey || "").trim(),
|
|
109
|
+
token: String(account?.token || "").trim(),
|
|
110
|
+
isAvailable,
|
|
111
|
+
isBoundElsewhere:
|
|
112
|
+
!isOwned &&
|
|
113
|
+
(!isAvailable || isImplicitDefaultElsewhere || !!ownerAgentId),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
id: `${channelId}:${accountId}`,
|
|
118
|
+
channel: channelId,
|
|
119
|
+
accountId,
|
|
120
|
+
channelOrder: Number(channelOrderMap.get(channelId) ?? 9999),
|
|
121
|
+
accountOrder: Number(
|
|
122
|
+
accountOrderMap.get(`${channelId}:${accountId}`) ?? 9999,
|
|
123
|
+
),
|
|
124
|
+
label: resolveChannelAccountLabel({ channelId, account }),
|
|
125
|
+
isAwaitingPairing: accountStatusInfo?.status !== "paired",
|
|
126
|
+
canOpenWorkspace,
|
|
127
|
+
canNavigateToOwnerAgent,
|
|
128
|
+
ownerAgentId,
|
|
129
|
+
ownerAgentName,
|
|
130
|
+
accountStatusInfo,
|
|
131
|
+
accountData,
|
|
132
|
+
isOwned,
|
|
133
|
+
isAvailable,
|
|
134
|
+
dimmedLabel: accountData.isBoundElsewhere,
|
|
135
|
+
isBoundElsewhere: accountData.isBoundElsewhere,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
})
|
|
139
|
+
.filter(Boolean)
|
|
140
|
+
.sort((a, b) => {
|
|
141
|
+
const rankDiff = getChannelItemSortRank(a) - getChannelItemSortRank(b);
|
|
142
|
+
if (rankDiff !== 0) return rankDiff;
|
|
143
|
+
const channelOrderDiff =
|
|
144
|
+
Number(a?.channelOrder ?? 9999) - Number(b?.channelOrder ?? 9999);
|
|
145
|
+
if (channelOrderDiff !== 0) return channelOrderDiff;
|
|
146
|
+
const accountOrderDiff =
|
|
147
|
+
Number(a?.accountOrder ?? 9999) - Number(b?.accountOrder ?? 9999);
|
|
148
|
+
if (accountOrderDiff !== 0) return accountOrderDiff;
|
|
149
|
+
return String(a?.label || "").localeCompare(String(b?.label || ""));
|
|
150
|
+
});
|
|
151
|
+
}, [
|
|
152
|
+
agentId,
|
|
153
|
+
agentNameMap,
|
|
154
|
+
channelStatus,
|
|
155
|
+
configuredChannelMap,
|
|
156
|
+
configuredChannels,
|
|
157
|
+
defaultAgentId,
|
|
158
|
+
isDefaultAgent,
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
const visibleChannelItems = channelItemData.filter(
|
|
162
|
+
(item) => !item?.isBoundElsewhere,
|
|
163
|
+
);
|
|
164
|
+
const assignedElsewhereItems = channelItemData.filter(
|
|
165
|
+
(item) => !!item?.isBoundElsewhere,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (assignedElsewhereItems.length === 0) {
|
|
170
|
+
setShowAssignedElsewhere(false);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (visibleChannelItems.length === 0) {
|
|
174
|
+
setShowAssignedElsewhere(true);
|
|
175
|
+
}
|
|
176
|
+
}, [agentId, assignedElsewhereItems.length, visibleChannelItems.length]);
|
|
177
|
+
|
|
178
|
+
const mergedChannelItems = useMemo(() => {
|
|
179
|
+
const baseItems = [...visibleChannelItems];
|
|
180
|
+
if (assignedElsewhereItems.length === 0) return baseItems;
|
|
181
|
+
baseItems.push({
|
|
182
|
+
id: "__assigned_elsewhere_toggle",
|
|
183
|
+
label: html`
|
|
184
|
+
<span class="inline-flex items-center gap-1.5">
|
|
185
|
+
<span class=${`arrow inline-block ${showAssignedElsewhere ? "" : "-rotate-90"}`}>▼</span>
|
|
186
|
+
<span>Assigned elsewhere</span>
|
|
187
|
+
</span>
|
|
188
|
+
`,
|
|
189
|
+
labelClassName: "text-xs",
|
|
190
|
+
clickable: true,
|
|
191
|
+
onClick: () => setShowAssignedElsewhere((current) => !current),
|
|
192
|
+
dimmedLabel: true,
|
|
193
|
+
trailing: html`
|
|
194
|
+
<span class="inline-flex items-center gap-1.5 text-gray-500">
|
|
195
|
+
<span class="text-[11px] px-2 py-0.5 rounded-full border border-border">
|
|
196
|
+
${assignedElsewhereItems.length}
|
|
197
|
+
</span>
|
|
198
|
+
</span>
|
|
199
|
+
`,
|
|
200
|
+
});
|
|
201
|
+
if (showAssignedElsewhere) {
|
|
202
|
+
baseItems.push(...assignedElsewhereItems);
|
|
203
|
+
}
|
|
204
|
+
return baseItems;
|
|
205
|
+
}, [assignedElsewhereItems, showAssignedElsewhere, visibleChannelItems]);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
hasDiscordAccount,
|
|
209
|
+
mergedChannelItems,
|
|
210
|
+
};
|
|
211
|
+
};
|
|
@@ -40,6 +40,7 @@ export const AgentPairingSection = ({ agent = {} }) => {
|
|
|
40
40
|
const [loadingBindings, setLoadingBindings] = useState(true);
|
|
41
41
|
const [pairingStatusRefreshing, setPairingStatusRefreshing] = useState(false);
|
|
42
42
|
const pairingRefreshTimerRef = useRef(null);
|
|
43
|
+
const pairingDelayedRefreshTimerRefs = useRef([]);
|
|
43
44
|
const agentId = String(agent?.id || "").trim();
|
|
44
45
|
const isDefaultAgent = !!agent?.default;
|
|
45
46
|
|
|
@@ -82,6 +83,10 @@ export const AgentPairingSection = ({ agent = {} }) => {
|
|
|
82
83
|
if (pairingRefreshTimerRef.current) {
|
|
83
84
|
clearTimeout(pairingRefreshTimerRef.current);
|
|
84
85
|
}
|
|
86
|
+
for (const timerId of pairingDelayedRefreshTimerRefs.current) {
|
|
87
|
+
clearTimeout(timerId);
|
|
88
|
+
}
|
|
89
|
+
pairingDelayedRefreshTimerRefs.current = [];
|
|
85
90
|
},
|
|
86
91
|
[],
|
|
87
92
|
);
|
|
@@ -217,19 +222,27 @@ export const AgentPairingSection = ({ agent = {} }) => {
|
|
|
217
222
|
setPairingStatusRefreshing(false);
|
|
218
223
|
pairingRefreshTimerRef.current = null;
|
|
219
224
|
}, 2800);
|
|
225
|
+
for (const timerId of pairingDelayedRefreshTimerRefs.current) {
|
|
226
|
+
clearTimeout(timerId);
|
|
227
|
+
}
|
|
228
|
+
pairingDelayedRefreshTimerRefs.current = [];
|
|
220
229
|
const refresh = () => {
|
|
221
230
|
pairingsPoll.refresh();
|
|
222
231
|
loadBindings();
|
|
223
232
|
announcePairingsChanged(agentId);
|
|
224
233
|
};
|
|
225
234
|
refresh();
|
|
226
|
-
setTimeout(refresh, 500);
|
|
227
|
-
setTimeout(refresh, 2000);
|
|
235
|
+
pairingDelayedRefreshTimerRefs.current.push(setTimeout(refresh, 500));
|
|
236
|
+
pairingDelayedRefreshTimerRefs.current.push(setTimeout(refresh, 2000));
|
|
228
237
|
}, [agentId, loadBindings, pairingsPoll]);
|
|
229
238
|
|
|
230
239
|
const handleApprove = async (id, channel, accountId = "") => {
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
try {
|
|
241
|
+
await approvePairing(id, channel, accountId);
|
|
242
|
+
refreshAfterPairingAction();
|
|
243
|
+
} catch (err) {
|
|
244
|
+
showToast(err.message || "Could not approve pairing", "error");
|
|
245
|
+
}
|
|
233
246
|
};
|
|
234
247
|
|
|
235
248
|
const handleReject = async (id, channel, accountId = "") => {
|
|
@@ -63,9 +63,19 @@ export const CreateChannelModal = ({
|
|
|
63
63
|
? initialProvider
|
|
64
64
|
: ALL_CHANNELS[0] || "telegram";
|
|
65
65
|
const providerLabel = getChannelMeta(nextProvider).label || "Channel";
|
|
66
|
+
const nextSelectedChannel =
|
|
67
|
+
existingChannels.find(
|
|
68
|
+
(entry) =>
|
|
69
|
+
String(entry?.channel || "").trim() === String(nextProvider || "").trim(),
|
|
70
|
+
) || null;
|
|
71
|
+
const nextProviderHasAccounts =
|
|
72
|
+
Array.isArray(nextSelectedChannel?.accounts)
|
|
73
|
+
&& nextSelectedChannel.accounts.length > 0;
|
|
66
74
|
const nextName = isEditMode
|
|
67
75
|
? String(account?.name || "").trim() || providerLabel
|
|
68
|
-
:
|
|
76
|
+
: nextProviderHasAccounts
|
|
77
|
+
? ""
|
|
78
|
+
: providerLabel;
|
|
69
79
|
const nextAgentId = isEditMode
|
|
70
80
|
? String(account?.ownerAgentId || "").trim()
|
|
71
81
|
|| String(initialAgentId || "").trim()
|
|
@@ -85,12 +95,16 @@ export const CreateChannelModal = ({
|
|
|
85
95
|
setAgentId(nextAgentId);
|
|
86
96
|
setError("");
|
|
87
97
|
setNameEditedManually(isEditMode);
|
|
88
|
-
}, [
|
|
98
|
+
}, [
|
|
99
|
+
visible,
|
|
100
|
+
initialAgentId,
|
|
101
|
+
initialProvider,
|
|
102
|
+
agents,
|
|
103
|
+
existingChannels,
|
|
104
|
+
isEditMode,
|
|
105
|
+
account,
|
|
106
|
+
]);
|
|
89
107
|
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (nameEditedManually) return;
|
|
92
|
-
setName(getChannelMeta(provider).label || "Channel");
|
|
93
|
-
}, [provider, nameEditedManually]);
|
|
94
108
|
const selectedChannel = useMemo(
|
|
95
109
|
() =>
|
|
96
110
|
existingChannels.find(
|
|
@@ -103,6 +117,15 @@ export const CreateChannelModal = ({
|
|
|
103
117
|
() => Array.isArray(selectedChannel?.accounts) && selectedChannel.accounts.length > 0,
|
|
104
118
|
[selectedChannel],
|
|
105
119
|
);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (nameEditedManually) return;
|
|
122
|
+
const providerLabel = getChannelMeta(provider).label || "Channel";
|
|
123
|
+
if (!isEditMode && providerHasAccounts) {
|
|
124
|
+
setName("");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
setName(providerLabel);
|
|
128
|
+
}, [provider, providerHasAccounts, nameEditedManually, isEditMode]);
|
|
106
129
|
const isSingleAccountProvider = String(provider || "").trim() === "discord";
|
|
107
130
|
|
|
108
131
|
const accountId = useMemo(() => {
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
fetchChannelAccounts,
|
|
17
17
|
updateChannelAccount,
|
|
18
18
|
} from "../lib/api.js";
|
|
19
|
+
import {
|
|
20
|
+
isImplicitDefaultAccount,
|
|
21
|
+
resolveChannelAccountLabel,
|
|
22
|
+
} from "../lib/channel-accounts.js";
|
|
19
23
|
import { createChannelAccountWithProgress } from "../lib/channel-create-operation.js";
|
|
20
24
|
import { CreateChannelModal } from "./agents-tab/create-channel-modal.js";
|
|
21
25
|
import { showToast } from "./toast.js";
|
|
@@ -40,18 +44,6 @@ const getChannelMeta = (channelId = "") => {
|
|
|
40
44
|
);
|
|
41
45
|
};
|
|
42
46
|
|
|
43
|
-
const resolveChannelAccountLabel = ({ channelId, account = {} }) => {
|
|
44
|
-
const providerLabel = getChannelMeta(channelId).label || "Channel";
|
|
45
|
-
const configuredName = String(account?.name || "").trim();
|
|
46
|
-
if (configuredName) return configuredName;
|
|
47
|
-
const accountId = String(account?.id || "").trim();
|
|
48
|
-
if (!accountId || accountId === "default") return providerLabel;
|
|
49
|
-
return `${providerLabel} ${accountId}`;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const isImplicitDefaultAccount = ({ accountId, boundAgentId }) =>
|
|
53
|
-
String(accountId || "").trim() === "default" &&
|
|
54
|
-
!String(boundAgentId || "").trim();
|
|
55
47
|
const announceRestartRequired = () =>
|
|
56
48
|
window.dispatchEvent(new CustomEvent("alphaclaw:restart-required"));
|
|
57
49
|
|
|
@@ -60,6 +52,7 @@ export const ChannelsCard = ({
|
|
|
60
52
|
items = [],
|
|
61
53
|
loadingLabel = "Loading...",
|
|
62
54
|
actions = null,
|
|
55
|
+
renderItem = null,
|
|
63
56
|
}) => html`
|
|
64
57
|
<div class="bg-surface border border-border rounded-xl p-4">
|
|
65
58
|
<div class="flex items-center justify-between gap-3 mb-3">
|
|
@@ -71,6 +64,10 @@ export const ChannelsCard = ({
|
|
|
71
64
|
? items.map((item) => {
|
|
72
65
|
const channelMeta = getChannelMeta(item.channel || item.id);
|
|
73
66
|
const clickable = !!item.clickable;
|
|
67
|
+
const customItem = renderItem
|
|
68
|
+
? renderItem({ item, channelMeta, clickable })
|
|
69
|
+
: null;
|
|
70
|
+
if (customItem) return customItem;
|
|
74
71
|
return html`
|
|
75
72
|
<div
|
|
76
73
|
key=${item.id || item.channel}
|
|
@@ -342,7 +339,11 @@ export const Channels = ({
|
|
|
342
339
|
const accountData = {
|
|
343
340
|
id: accountId,
|
|
344
341
|
provider: channelId,
|
|
345
|
-
name: resolveChannelAccountLabel({
|
|
342
|
+
name: resolveChannelAccountLabel({
|
|
343
|
+
channelId,
|
|
344
|
+
account,
|
|
345
|
+
providerLabel: getChannelMeta(channelId).label || "Channel",
|
|
346
|
+
}),
|
|
346
347
|
ownerAgentId,
|
|
347
348
|
envKey: String(account?.envKey || "").trim(),
|
|
348
349
|
token: String(account?.token || "").trim(),
|
|
@@ -422,7 +423,11 @@ export const Channels = ({
|
|
|
422
423
|
accountOrder: Number(
|
|
423
424
|
accountOrderMap.get(`${channelId}:${accountId}`) ?? 9999,
|
|
424
425
|
),
|
|
425
|
-
label: resolveChannelAccountLabel({
|
|
426
|
+
label: resolveChannelAccountLabel({
|
|
427
|
+
channelId,
|
|
428
|
+
account,
|
|
429
|
+
providerLabel: getChannelMeta(channelId).label || "Channel",
|
|
430
|
+
}),
|
|
426
431
|
isAwaitingPairing: accountStatus !== "paired",
|
|
427
432
|
detailText: isClickable ? "Workspace" : "",
|
|
428
433
|
detailChevron: isClickable,
|
|
@@ -100,6 +100,9 @@ const resolveProfileId = (mode, provider) => {
|
|
|
100
100
|
return `${p}:${mode.profileSuffix || "default"}`;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
const getCredentialValue = (value) =>
|
|
104
|
+
String(value?.key || value?.token || value?.access || "").trim();
|
|
105
|
+
|
|
103
106
|
const CodexOAuthSection = ({ codexStatus, onRefreshCodex }) => {
|
|
104
107
|
const [authStarted, setAuthStarted] = useState(false);
|
|
105
108
|
const [authWaiting, setAuthWaiting] = useState(false);
|
|
@@ -285,6 +288,18 @@ export const ProviderAuthCard = ({
|
|
|
285
288
|
|
|
286
289
|
const effectiveOrder = getEffectiveOrder(provider);
|
|
287
290
|
const activeProfileId = effectiveOrder?.[0] || null;
|
|
291
|
+
const savedOrder = authOrder[provider] || null;
|
|
292
|
+
|
|
293
|
+
const hasUnsavedProfileChanges = credentialModes.some((mode) => {
|
|
294
|
+
const profileId = resolveProfileId(mode, provider);
|
|
295
|
+
const savedValue = authProfiles.find((p) => p.id === profileId) || null;
|
|
296
|
+
const draftValue = getProfileValue(profileId);
|
|
297
|
+
return getCredentialValue(draftValue) !== getCredentialValue(savedValue);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const hasUnsavedOrderChanges =
|
|
301
|
+
JSON.stringify(effectiveOrder || null) !== JSON.stringify(savedOrder);
|
|
302
|
+
const hasUnsavedChanges = hasUnsavedProfileChanges || hasUnsavedOrderChanges;
|
|
288
303
|
|
|
289
304
|
const isConnected =
|
|
290
305
|
credentialModes.some((mode) => {
|
|
@@ -306,7 +321,9 @@ export const ProviderAuthCard = ({
|
|
|
306
321
|
<h3 class="card-label">${meta.label}</h3>
|
|
307
322
|
${showsInlineOauthStatus && credentialModes.length === 0
|
|
308
323
|
? null
|
|
309
|
-
:
|
|
324
|
+
: hasUnsavedChanges
|
|
325
|
+
? html`<${Badge} tone="warning">Unsaved</${Badge}>`
|
|
326
|
+
: isConnected
|
|
310
327
|
? html`<${Badge} tone="success">Connected</${Badge}>`
|
|
311
328
|
: html`<${Badge} tone="warning">Not configured</${Badge}>`}
|
|
312
329
|
</div>
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { showToast } from "../toast.js";
|
|
10
10
|
|
|
11
11
|
let kModelsTabCache = null;
|
|
12
|
+
const getCredentialValue = (value) =>
|
|
13
|
+
String(value?.key || value?.token || value?.access || "").trim();
|
|
12
14
|
|
|
13
15
|
export const useModels = (agentId) => {
|
|
14
16
|
const isScoped = !!agentId;
|
|
@@ -99,10 +101,7 @@ export const useModels = (agentId) => {
|
|
|
99
101
|
const hasProfileChanges = Object.entries(profileEdits).some(
|
|
100
102
|
([id, cred]) => {
|
|
101
103
|
const existing = authProfiles.find((p) => p.id === id);
|
|
102
|
-
|
|
103
|
-
const oldVal =
|
|
104
|
-
existing?.key || existing?.token || existing?.access || "";
|
|
105
|
-
return newVal !== oldVal && newVal !== "";
|
|
104
|
+
return getCredentialValue(cred) !== getCredentialValue(existing);
|
|
106
105
|
},
|
|
107
106
|
);
|
|
108
107
|
const hasOrderChanges = Object.entries(orderEdits).some(
|
|
@@ -199,9 +198,9 @@ export const useModels = (agentId) => {
|
|
|
199
198
|
setSaving(true);
|
|
200
199
|
try {
|
|
201
200
|
const changedProfiles = Object.entries(profileEdits)
|
|
202
|
-
.filter(([, cred]) => {
|
|
203
|
-
const
|
|
204
|
-
return
|
|
201
|
+
.filter(([id, cred]) => {
|
|
202
|
+
const existing = authProfiles.find((p) => p.id === id);
|
|
203
|
+
return getCredentialValue(cred) !== getCredentialValue(existing);
|
|
205
204
|
})
|
|
206
205
|
.map(([id, cred]) => ({ id, ...cred }));
|
|
207
206
|
|
|
@@ -225,7 +224,15 @@ export const useModels = (agentId) => {
|
|
|
225
224
|
} finally {
|
|
226
225
|
setSaving(false);
|
|
227
226
|
}
|
|
228
|
-
}, [
|
|
227
|
+
}, [
|
|
228
|
+
saving,
|
|
229
|
+
primary,
|
|
230
|
+
configuredModels,
|
|
231
|
+
profileEdits,
|
|
232
|
+
orderEdits,
|
|
233
|
+
authProfiles,
|
|
234
|
+
refresh,
|
|
235
|
+
]);
|
|
229
236
|
|
|
230
237
|
const refreshCodexStatus = useCallback(async () => {
|
|
231
238
|
try {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const resolveChannelAccountLabel = ({
|
|
2
|
+
channelId,
|
|
3
|
+
account = {},
|
|
4
|
+
providerLabel = "",
|
|
5
|
+
}) => {
|
|
6
|
+
const fallbackProviderLabel = channelId
|
|
7
|
+
? channelId.charAt(0).toUpperCase() + channelId.slice(1)
|
|
8
|
+
: "Channel";
|
|
9
|
+
const resolvedProviderLabel = String(providerLabel || "").trim()
|
|
10
|
+
|| fallbackProviderLabel;
|
|
11
|
+
const configuredName = String(account?.name || "").trim();
|
|
12
|
+
if (configuredName) return configuredName;
|
|
13
|
+
const accountId = String(account?.id || "").trim();
|
|
14
|
+
if (!accountId || accountId === "default") return resolvedProviderLabel;
|
|
15
|
+
return `${resolvedProviderLabel} ${accountId}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const isImplicitDefaultAccount = ({ accountId, boundAgentId }) =>
|
|
19
|
+
String(accountId || "").trim() === "default" &&
|
|
20
|
+
!String(boundAgentId || "").trim();
|
|
@@ -11,19 +11,23 @@ export const getAuthProviderFromModelProvider = (provider) => {
|
|
|
11
11
|
export const kFeaturedModelDefs = [
|
|
12
12
|
{
|
|
13
13
|
label: "Opus 4.6",
|
|
14
|
-
preferredKeys: ["anthropic/claude-opus-4-6"
|
|
14
|
+
preferredKeys: ["anthropic/claude-opus-4-6"],
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
label: "Sonnet 4.6",
|
|
18
|
-
preferredKeys: ["anthropic/claude-sonnet-4-6"
|
|
18
|
+
preferredKeys: ["anthropic/claude-sonnet-4-6"],
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
label: "Codex 5.3",
|
|
22
|
-
preferredKeys: ["openai-codex/gpt-5.3-codex"
|
|
22
|
+
preferredKeys: ["openai-codex/gpt-5.3-codex"],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: "GPT-5.4",
|
|
26
|
+
preferredKeys: ["openai-codex/gpt-5.4"],
|
|
23
27
|
},
|
|
24
28
|
{
|
|
25
29
|
label: "Gemini 3.1 Pro",
|
|
26
|
-
preferredKeys: ["google/gemini-3.1-pro-preview"
|
|
30
|
+
preferredKeys: ["google/gemini-3.1-pro-preview"],
|
|
27
31
|
},
|
|
28
32
|
];
|
|
29
33
|
|