@chrysb/alphaclaw 0.8.3-beta.4 → 0.8.3-beta.5
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/css/explorer.css +65 -22
- package/lib/public/dist/app.bundle.js +1585 -1555
- package/lib/public/js/app.js +0 -7
- package/lib/public/js/components/cron-tab/cron-job-settings-card.js +9 -2
- package/lib/public/js/components/google/gmail-setup-wizard.js +6 -2
- package/lib/public/js/components/icons.js +13 -0
- package/lib/public/js/components/routes/chat-route.js +3 -21
- package/lib/public/js/components/session-select-field.js +9 -2
- package/lib/public/js/components/sidebar.js +128 -25
- package/lib/public/js/components/webhooks/webhook-detail/index.js +5 -2
- package/lib/public/js/lib/session-keys.js +74 -0
- package/lib/public/js/lib/storage-keys.js +2 -1
- package/lib/server/routes/system.js +36 -85
- package/package.json +1 -1
package/lib/public/js/app.js
CHANGED
|
@@ -217,13 +217,6 @@ const App = () => {
|
|
|
217
217
|
setSelectedChatSessionKey(sessionKey);
|
|
218
218
|
if (!isChatRoute) setLocation("/chat");
|
|
219
219
|
}}
|
|
220
|
-
onStartChat=${() => {
|
|
221
|
-
if (!isChatRoute) {
|
|
222
|
-
setLocation("/chat");
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
window.dispatchEvent(new Event("alphaclaw:chat-new"));
|
|
226
|
-
}}
|
|
227
220
|
/>
|
|
228
221
|
<div
|
|
229
222
|
class=${`sidebar-resizer ${shellState.isResizingSidebar ? "is-resizing" : ""}`}
|
|
@@ -4,6 +4,7 @@ import htm from "htm";
|
|
|
4
4
|
import { ActionButton } from "../action-button.js";
|
|
5
5
|
import { SegmentedControl } from "../segmented-control.js";
|
|
6
6
|
import { ToggleSwitch } from "../toggle-switch.js";
|
|
7
|
+
import { getSessionDisplayLabel } from "../../lib/session-keys.js";
|
|
7
8
|
import {
|
|
8
9
|
formatCronScheduleLabel,
|
|
9
10
|
formatNextRunRelativeMs,
|
|
@@ -89,7 +90,9 @@ export const CronJobSettingsCard = ({
|
|
|
89
90
|
if (!key) return;
|
|
90
91
|
if (key === selectedKey) selectedPresent = true;
|
|
91
92
|
const label = String(
|
|
92
|
-
sessionRow
|
|
93
|
+
getSessionDisplayLabel(sessionRow) ||
|
|
94
|
+
sessionRow?.key ||
|
|
95
|
+
"Session",
|
|
93
96
|
).trim();
|
|
94
97
|
const dedupeKey = label.toLowerCase();
|
|
95
98
|
if (seenLabels.has(dedupeKey)) return;
|
|
@@ -193,7 +196,11 @@ export const CronJobSettingsCard = ({
|
|
|
193
196
|
${deliverySessionOptions.map(
|
|
194
197
|
(sessionRow) => html`
|
|
195
198
|
<option value=${String(sessionRow?.key || "")}>
|
|
196
|
-
${String(
|
|
199
|
+
${String(
|
|
200
|
+
getSessionDisplayLabel(sessionRow) ||
|
|
201
|
+
sessionRow?.key ||
|
|
202
|
+
"Session",
|
|
203
|
+
)}
|
|
197
204
|
</option>
|
|
198
205
|
`,
|
|
199
206
|
)}
|
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
kNoDestinationSessionValue,
|
|
19
19
|
useDestinationSessionSelection,
|
|
20
20
|
} from "../../hooks/use-destination-session-selection.js";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
getSessionDisplayLabel,
|
|
23
|
+
kDestinationSessionFilter,
|
|
24
|
+
} from "../../lib/session-keys.js";
|
|
22
25
|
|
|
23
26
|
const html = htm.bind(h);
|
|
24
27
|
|
|
@@ -419,7 +422,8 @@ export const GmailSetupWizard = ({
|
|
|
419
422
|
${selectableAgentSessions.map(
|
|
420
423
|
(sessionRow) => html`
|
|
421
424
|
<option value=${sessionRow.key}>
|
|
422
|
-
${sessionRow
|
|
425
|
+
${getSessionDisplayLabel(sessionRow) ||
|
|
426
|
+
sessionRow.key}
|
|
423
427
|
</option>
|
|
424
428
|
`,
|
|
425
429
|
)}
|
|
@@ -208,6 +208,19 @@ export const ChatVoiceLineIcon = ({ className = "" }) => html`
|
|
|
208
208
|
</svg>
|
|
209
209
|
`;
|
|
210
210
|
|
|
211
|
+
export const Chat4LineIcon = ({ className = "" }) => html`
|
|
212
|
+
<svg
|
|
213
|
+
class=${className}
|
|
214
|
+
viewBox="0 0 24 24"
|
|
215
|
+
fill="currentColor"
|
|
216
|
+
aria-hidden="true"
|
|
217
|
+
>
|
|
218
|
+
<path
|
|
219
|
+
d="M5.76282 17H20V5H4V18.3851L5.76282 17ZM6.45455 19L2 22.5V4C2 3.44772 2.44772 3 3 3H21C21.5523 3 22 3.44772 22 4V18C22 18.5523 21.5523 19 21 19H6.45455Z"
|
|
220
|
+
/>
|
|
221
|
+
</svg>
|
|
222
|
+
`;
|
|
223
|
+
|
|
211
224
|
export const FileMusicLineIcon = ({ className = "" }) => html`
|
|
212
225
|
<svg
|
|
213
226
|
class=${className}
|
|
@@ -11,10 +11,10 @@ import htm from "htm";
|
|
|
11
11
|
import { marked } from "marked";
|
|
12
12
|
import { authFetch } from "../../lib/api.js";
|
|
13
13
|
import { kChatSessionDraftsStorageKey } from "../../lib/storage-keys.js";
|
|
14
|
+
import { getSessionDisplayLabel } from "../../lib/session-keys.js";
|
|
14
15
|
import { showToast } from "../toast.js";
|
|
15
16
|
|
|
16
17
|
const html = htm.bind(h);
|
|
17
|
-
const kNewChatEventName = "alphaclaw:chat-new";
|
|
18
18
|
const kWsReconnectMaxAttempts = 8;
|
|
19
19
|
const kAutoscrollBottomThresholdPx = 40;
|
|
20
20
|
const kChatDebugQueryFlag = "chatDebug";
|
|
@@ -267,25 +267,6 @@ export const ChatRoute = ({ sessions = [], selectedSessionKey = "" }) => {
|
|
|
267
267
|
[messagesBySession, selectedSessionKey],
|
|
268
268
|
);
|
|
269
269
|
|
|
270
|
-
useEffect(() => {
|
|
271
|
-
const handleNewChat = () => {
|
|
272
|
-
if (!selectedSessionKey) return;
|
|
273
|
-
setMessagesBySession((currentMap) => ({
|
|
274
|
-
...currentMap,
|
|
275
|
-
[selectedSessionKey]: [],
|
|
276
|
-
}));
|
|
277
|
-
setDraft("");
|
|
278
|
-
setDraftBySession((currentMap) => ({
|
|
279
|
-
...currentMap,
|
|
280
|
-
[selectedSessionKey]: "",
|
|
281
|
-
}));
|
|
282
|
-
};
|
|
283
|
-
window.addEventListener(kNewChatEventName, handleNewChat);
|
|
284
|
-
return () => {
|
|
285
|
-
window.removeEventListener(kNewChatEventName, handleNewChat);
|
|
286
|
-
};
|
|
287
|
-
}, [selectedSessionKey]);
|
|
288
|
-
|
|
289
270
|
useEffect(() => {
|
|
290
271
|
let mounted = true;
|
|
291
272
|
|
|
@@ -889,7 +870,8 @@ export const ChatRoute = ({ sessions = [], selectedSessionKey = "" }) => {
|
|
|
889
870
|
<div>
|
|
890
871
|
<div class="chat-route-title">Chat</div>
|
|
891
872
|
<div class="chat-route-subtitle">
|
|
892
|
-
${selectedSession
|
|
873
|
+
${getSessionDisplayLabel(selectedSession) ||
|
|
874
|
+
"Pick a session in the sidebar"}
|
|
893
875
|
</div>
|
|
894
876
|
${connectionError
|
|
895
877
|
? html`<div class="chat-route-warning">${connectionError}</div>`
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { h } from "preact";
|
|
2
2
|
import htm from "htm";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getSessionDisplayLabel,
|
|
5
|
+
getSessionRowKey,
|
|
6
|
+
} from "../lib/session-keys.js";
|
|
4
7
|
|
|
5
8
|
const html = htm.bind(h);
|
|
6
9
|
|
|
@@ -54,7 +57,11 @@ export const SessionSelectField = ({
|
|
|
54
57
|
${sessions.map(
|
|
55
58
|
(sessionRow) => html`
|
|
56
59
|
<option value=${getSessionRowKey(sessionRow)}>
|
|
57
|
-
${String(
|
|
60
|
+
${String(
|
|
61
|
+
getSessionDisplayLabel(sessionRow) ||
|
|
62
|
+
getSessionRowKey(sessionRow) ||
|
|
63
|
+
"Session",
|
|
64
|
+
)}
|
|
58
65
|
</option>
|
|
59
66
|
`,
|
|
60
67
|
)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { h } from "preact";
|
|
2
|
-
import { useEffect, useRef, useState } from "preact/hooks";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
3
3
|
import htm from "htm";
|
|
4
4
|
import {
|
|
5
5
|
AddLineIcon,
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
BarChartLineIcon,
|
|
8
8
|
Brain2LineIcon,
|
|
9
9
|
BracesLineIcon,
|
|
10
|
-
|
|
10
|
+
Chat4LineIcon,
|
|
11
|
+
ChevronDownIcon,
|
|
11
12
|
ComputerLineIcon,
|
|
12
13
|
EyeLineIcon,
|
|
13
14
|
FolderLineIcon,
|
|
@@ -21,7 +22,17 @@ import { OverflowMenu, OverflowMenuItem } from "./overflow-menu.js";
|
|
|
21
22
|
import { UpdateActionButton } from "./update-action-button.js";
|
|
22
23
|
import { SidebarGitPanel } from "./sidebar-git-panel.js";
|
|
23
24
|
import { UpdateModal } from "./update-modal.js";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
readUiSettings,
|
|
27
|
+
updateUiSettings,
|
|
28
|
+
writeUiSettings,
|
|
29
|
+
} from "../lib/ui-settings.js";
|
|
30
|
+
import {
|
|
31
|
+
getAgentIdFromSessionKey,
|
|
32
|
+
getSessionChannelForIcon,
|
|
33
|
+
getSessionDisplayLabel,
|
|
34
|
+
getSessionRowKey,
|
|
35
|
+
} from "../lib/session-keys.js";
|
|
25
36
|
|
|
26
37
|
const html = htm.bind(h);
|
|
27
38
|
const kBrowseBottomPanelUiSettingKey = "browseBottomPanelHeightPx";
|
|
@@ -29,6 +40,16 @@ const kBrowsePanelMinHeightPx = 120;
|
|
|
29
40
|
const kBrowseBottomMinHeightPx = 120;
|
|
30
41
|
const kBrowseResizerHeightPx = 6;
|
|
31
42
|
const kDefaultBrowseBottomPanelHeightPx = 260;
|
|
43
|
+
const kChatSidebarCollapsedAgentIdsKey = "chatSidebarCollapsedAgentIds";
|
|
44
|
+
const kChatChannelIconSrc = {
|
|
45
|
+
telegram: "/assets/icons/telegram.svg",
|
|
46
|
+
discord: "/assets/icons/discord.svg",
|
|
47
|
+
slack: "/assets/icons/slack.svg",
|
|
48
|
+
};
|
|
49
|
+
const readChatSidebarCollapsedAgentIds = () => {
|
|
50
|
+
const raw = readUiSettings()[kChatSidebarCollapsedAgentIdsKey];
|
|
51
|
+
return Array.isArray(raw) ? raw : [];
|
|
52
|
+
};
|
|
32
53
|
const kSidebarNavIconsById = {
|
|
33
54
|
cron: AlarmLineIcon,
|
|
34
55
|
usage: BarChartLineIcon,
|
|
@@ -97,7 +118,6 @@ export const AppSidebar = ({
|
|
|
97
118
|
chatSessions = [],
|
|
98
119
|
selectedChatSessionKey = "",
|
|
99
120
|
onSelectChatSession = () => {},
|
|
100
|
-
onStartChat = () => {},
|
|
101
121
|
}) => {
|
|
102
122
|
const browseLayoutRef = useRef(null);
|
|
103
123
|
const browseBottomPanelRef = useRef(null);
|
|
@@ -107,6 +127,53 @@ export const AppSidebar = ({
|
|
|
107
127
|
);
|
|
108
128
|
const [isResizingBrowsePanels, setIsResizingBrowsePanels] = useState(false);
|
|
109
129
|
const [updateModalOpen, setUpdateModalOpen] = useState(false);
|
|
130
|
+
const [collapsedChatAgentIds, setCollapsedChatAgentIds] = useState(() =>
|
|
131
|
+
readChatSidebarCollapsedAgentIds(),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const chatSessionGroups = useMemo(() => {
|
|
135
|
+
const rows = Array.isArray(chatSessions) ? chatSessions : [];
|
|
136
|
+
const order = [];
|
|
137
|
+
const byAgent = new Map();
|
|
138
|
+
for (const row of rows) {
|
|
139
|
+
const aid = String(
|
|
140
|
+
row.agentId ||
|
|
141
|
+
getAgentIdFromSessionKey(getSessionRowKey(row)) ||
|
|
142
|
+
"unknown",
|
|
143
|
+
);
|
|
144
|
+
if (!byAgent.has(aid)) {
|
|
145
|
+
byAgent.set(aid, {
|
|
146
|
+
agentId: aid,
|
|
147
|
+
agentLabel: String(row.agentLabel || "").trim() || aid,
|
|
148
|
+
sessions: [],
|
|
149
|
+
});
|
|
150
|
+
order.push(aid);
|
|
151
|
+
}
|
|
152
|
+
byAgent.get(aid).sessions.push(row);
|
|
153
|
+
}
|
|
154
|
+
const groups = order.map((aid) => byAgent.get(aid));
|
|
155
|
+
groups.sort((a, b) => {
|
|
156
|
+
if (a.agentId === "main" && b.agentId !== "main") return -1;
|
|
157
|
+
if (b.agentId === "main" && a.agentId !== "main") return 1;
|
|
158
|
+
return a.agentLabel.localeCompare(b.agentLabel);
|
|
159
|
+
});
|
|
160
|
+
return groups;
|
|
161
|
+
}, [chatSessions]);
|
|
162
|
+
|
|
163
|
+
const toggleChatAgentCollapsed = (agentId) => {
|
|
164
|
+
const id = String(agentId || "");
|
|
165
|
+
setCollapsedChatAgentIds((prev) => {
|
|
166
|
+
const next = new Set(prev);
|
|
167
|
+
if (next.has(id)) next.delete(id);
|
|
168
|
+
else next.add(id);
|
|
169
|
+
const arr = Array.from(next);
|
|
170
|
+
updateUiSettings((s) => ({
|
|
171
|
+
...s,
|
|
172
|
+
[kChatSidebarCollapsedAgentIdsKey]: arr,
|
|
173
|
+
}));
|
|
174
|
+
return arr;
|
|
175
|
+
});
|
|
176
|
+
};
|
|
110
177
|
|
|
111
178
|
useEffect(() => {
|
|
112
179
|
const settings = readUiSettings();
|
|
@@ -219,7 +286,7 @@ export const AppSidebar = ({
|
|
|
219
286
|
title="Chat"
|
|
220
287
|
onclick=${() => onSelectSidebarTab("chat")}
|
|
221
288
|
>
|
|
222
|
-
<${
|
|
289
|
+
<${Chat4LineIcon} className="sidebar-tab-icon" />
|
|
223
290
|
</button>
|
|
224
291
|
</div>
|
|
225
292
|
<div
|
|
@@ -306,31 +373,67 @@ export const AppSidebar = ({
|
|
|
306
373
|
>
|
|
307
374
|
<div class="sidebar-chat-header">
|
|
308
375
|
<div class="sidebar-label sidebar-chat-label">Sessions</div>
|
|
309
|
-
<button
|
|
310
|
-
type="button"
|
|
311
|
-
class="sidebar-chat-new-button"
|
|
312
|
-
onclick=${onStartChat}
|
|
313
|
-
title="New chat"
|
|
314
|
-
aria-label="New chat"
|
|
315
|
-
>
|
|
316
|
-
<${AddLineIcon} className="sidebar-chat-new-icon" />
|
|
317
|
-
</button>
|
|
318
376
|
</div>
|
|
319
377
|
<div class="sidebar-chat-sessions-list">
|
|
320
378
|
${chatSessions.length === 0
|
|
321
379
|
? html`<div class="sidebar-chat-empty">No sessions found</div>`
|
|
322
|
-
:
|
|
323
|
-
(
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
380
|
+
: chatSessionGroups.map(
|
|
381
|
+
(group) => html`
|
|
382
|
+
<div key=${group.agentId} class="sidebar-chat-agent-group">
|
|
383
|
+
<button
|
|
384
|
+
type="button"
|
|
385
|
+
class="sidebar-chat-agent-toggle"
|
|
386
|
+
onclick=${() => toggleChatAgentCollapsed(group.agentId)}
|
|
387
|
+
aria-expanded=${!collapsedChatAgentIds.includes(
|
|
388
|
+
group.agentId,
|
|
389
|
+
)}
|
|
332
390
|
>
|
|
333
|
-
|
|
391
|
+
<span
|
|
392
|
+
class=${`sidebar-chat-agent-chevron ${collapsedChatAgentIds.includes(group.agentId) ? "is-collapsed" : ""}`}
|
|
393
|
+
aria-hidden="true"
|
|
394
|
+
>
|
|
395
|
+
<${ChevronDownIcon} className="sidebar-chat-agent-chevron-icon" />
|
|
396
|
+
</span>
|
|
397
|
+
<span class="sidebar-chat-agent-label">${group.agentLabel}</span>
|
|
398
|
+
</button>
|
|
399
|
+
${collapsedChatAgentIds.includes(group.agentId)
|
|
400
|
+
? null
|
|
401
|
+
: html`
|
|
402
|
+
<div class="sidebar-chat-agent-sessions">
|
|
403
|
+
${group.sessions.map((sessionRow) => {
|
|
404
|
+
const displayLabel = getSessionDisplayLabel(sessionRow);
|
|
405
|
+
const channelIconSrc =
|
|
406
|
+
kChatChannelIconSrc[
|
|
407
|
+
String(
|
|
408
|
+
getSessionChannelForIcon(sessionRow) || "",
|
|
409
|
+
).toLowerCase()
|
|
410
|
+
] || "";
|
|
411
|
+
return html`
|
|
412
|
+
<button
|
|
413
|
+
key=${sessionRow.key}
|
|
414
|
+
class=${`sidebar-chat-session-item ${selectedChatSessionKey === sessionRow.key ? "active" : ""}`}
|
|
415
|
+
onclick=${() =>
|
|
416
|
+
onSelectChatSession(sessionRow.key)}
|
|
417
|
+
title=${displayLabel}
|
|
418
|
+
>
|
|
419
|
+
${channelIconSrc
|
|
420
|
+
? html`<img
|
|
421
|
+
src=${channelIconSrc}
|
|
422
|
+
alt=""
|
|
423
|
+
width="12"
|
|
424
|
+
height="12"
|
|
425
|
+
class="sidebar-chat-session-channel-icon"
|
|
426
|
+
/>`
|
|
427
|
+
: null}
|
|
428
|
+
<span class="sidebar-chat-session-name"
|
|
429
|
+
>${displayLabel}</span
|
|
430
|
+
>
|
|
431
|
+
</button>
|
|
432
|
+
`;
|
|
433
|
+
})}
|
|
434
|
+
</div>
|
|
435
|
+
`}
|
|
436
|
+
</div>
|
|
334
437
|
`,
|
|
335
438
|
)}
|
|
336
439
|
</div>
|
|
@@ -6,7 +6,10 @@ import { Badge } from "../../badge.js";
|
|
|
6
6
|
import { ConfirmDialog } from "../../confirm-dialog.js";
|
|
7
7
|
import { showToast } from "../../toast.js";
|
|
8
8
|
import { kNoDestinationSessionValue } from "../../../hooks/use-destination-session-selection.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getSessionDisplayLabel,
|
|
11
|
+
getSessionRowKey,
|
|
12
|
+
} from "../../../lib/session-keys.js";
|
|
10
13
|
import { formatDateTime } from "../helpers.js";
|
|
11
14
|
import { RequestHistory } from "../request-history/index.js";
|
|
12
15
|
import { useWebhookDetail } from "./use-webhook-detail.js";
|
|
@@ -267,7 +270,7 @@ export const WebhookDetail = ({
|
|
|
267
270
|
(sessionRow) => html`
|
|
268
271
|
<option value=${getSessionRowKey(sessionRow)}>
|
|
269
272
|
${String(
|
|
270
|
-
sessionRow
|
|
273
|
+
getSessionDisplayLabel(sessionRow) ||
|
|
271
274
|
getSessionRowKey(sessionRow) ||
|
|
272
275
|
"Session",
|
|
273
276
|
)}
|
|
@@ -55,3 +55,77 @@ export const getDestinationFromSession = (sessionRow = null) => {
|
|
|
55
55
|
...(agentId ? { agentId } : {}),
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
|
+
|
|
59
|
+
/** Matches server `parseChannelFromSessionKey` for icon routing when `channel` is absent (cached rows). */
|
|
60
|
+
export const parseChannelFromSessionKey = (sessionKey = "") => {
|
|
61
|
+
const k = String(sessionKey || "");
|
|
62
|
+
if (k.includes(":telegram:")) return "telegram";
|
|
63
|
+
if (k.includes(":discord:")) return "discord";
|
|
64
|
+
if (k.includes(":slack:")) return "slack";
|
|
65
|
+
return "";
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const getTopicIdsFromSessionKey = (sessionKey = "") => {
|
|
69
|
+
const normalizedSessionKey = getNormalizedSessionKey(sessionKey);
|
|
70
|
+
const topicMatch = normalizedSessionKey.match(
|
|
71
|
+
/:telegram:group:([^:]+):topic:([^:]+)$/,
|
|
72
|
+
);
|
|
73
|
+
return {
|
|
74
|
+
groupId: String(topicMatch?.[1] || "").trim(),
|
|
75
|
+
topicId: String(topicMatch?.[2] || "").trim(),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const getSessionKind = (sessionKey = "") => {
|
|
80
|
+
const normalizedSessionKey = getNormalizedSessionKey(sessionKey);
|
|
81
|
+
if (!normalizedSessionKey) return "other";
|
|
82
|
+
if (normalizedSessionKey === "main" || normalizedSessionKey.endsWith(":main")) {
|
|
83
|
+
return "main";
|
|
84
|
+
}
|
|
85
|
+
if (/:telegram:group:([^:]+):topic:([^:]+)$/.test(normalizedSessionKey)) {
|
|
86
|
+
return "topic";
|
|
87
|
+
}
|
|
88
|
+
if (normalizedSessionKey.includes(":slash:")) return "slash";
|
|
89
|
+
if (normalizedSessionKey.includes(":subagent:")) return "subagent";
|
|
90
|
+
if (/:direct:([^:]+)$/.test(normalizedSessionKey)) return "direct";
|
|
91
|
+
return "other";
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const getSessionDisplayLabel = (sessionRow = null) => {
|
|
95
|
+
const key = getSessionRowKey(sessionRow);
|
|
96
|
+
const kind = getSessionKind(key);
|
|
97
|
+
if (kind === "main") return "Main Thread";
|
|
98
|
+
|
|
99
|
+
const doctorMatch = key.match(/(?:^|:)doctor:(\d+)$/);
|
|
100
|
+
if (doctorMatch) return `Doctor Run #${doctorMatch[1]}`;
|
|
101
|
+
if (/(?:^|:)doctor(?::|$)/.test(key)) return "Doctor Run";
|
|
102
|
+
|
|
103
|
+
if (kind === "topic") {
|
|
104
|
+
const { groupId, topicId } = getTopicIdsFromSessionKey(key);
|
|
105
|
+
const topicName = String(sessionRow?.topicName || "").trim();
|
|
106
|
+
const groupName = String(sessionRow?.groupName || "").trim();
|
|
107
|
+
const topicLabel = topicName || (topicId ? `Topic ${topicId}` : "Topic");
|
|
108
|
+
const groupLabel = groupName || groupId;
|
|
109
|
+
return groupLabel ? `${topicLabel} - ${groupLabel}` : topicLabel;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (kind === "direct") {
|
|
113
|
+
const directMatch = key.match(/:direct:([^:]+)$/);
|
|
114
|
+
const directTarget = String(directMatch?.[1] || "").trim();
|
|
115
|
+
if (parseChannelFromSessionKey(key) === "telegram") {
|
|
116
|
+
return "Direct message";
|
|
117
|
+
}
|
|
118
|
+
return directTarget ? `Direct ${directTarget}` : "Direct";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return key || "Session";
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Channel id for platform icons; prefers API `channel`, else parses from key / replyChannel. */
|
|
125
|
+
export const getSessionChannelForIcon = (sessionRow = null) => {
|
|
126
|
+
const fromRow = String(sessionRow?.channel || "").trim();
|
|
127
|
+
if (fromRow) return fromRow;
|
|
128
|
+
const fromReply = String(sessionRow?.replyChannel || "").trim();
|
|
129
|
+
if (fromReply) return fromReply;
|
|
130
|
+
return parseChannelFromSessionKey(getSessionRowKey(sessionRow));
|
|
131
|
+
};
|
|
@@ -24,7 +24,8 @@ export const kTelegramWorkspaceStorageKey = "alphaclaw.telegram.workspaceState";
|
|
|
24
24
|
export const kTelegramWorkspaceCacheKey = "alphaclaw.telegram.workspaceCache";
|
|
25
25
|
|
|
26
26
|
// --- Agent sessions (shared across session pickers) ---
|
|
27
|
-
|
|
27
|
+
// Bump version when session row shape changes so stale cache is not reused.
|
|
28
|
+
export const kAgentSessionsCacheKey = "alphaclaw.agent.sessionsCache.v3";
|
|
28
29
|
export const kAgentLastSessionKey = "alphaclaw.agent.lastSessionKey";
|
|
29
30
|
|
|
30
31
|
// --- Chat ---
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const { buildManagedPaths } = require("../internal-files-migration");
|
|
2
2
|
const { readOpenclawConfig } = require("../openclaw-config");
|
|
3
|
-
const { hasScopedBindingFields } = require("../utils/channels");
|
|
4
3
|
const https = require("https");
|
|
5
4
|
|
|
6
5
|
const registerSystemRoutes = ({
|
|
@@ -117,40 +116,6 @@ const registerSystemRoutes = ({
|
|
|
117
116
|
.filter(Boolean)
|
|
118
117
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
119
118
|
.join(" ");
|
|
120
|
-
const resolveTelegramAccountIdForAgent = ({ config, agentId }) => {
|
|
121
|
-
const normalizedAgentId = String(agentId || "").trim();
|
|
122
|
-
if (!normalizedAgentId) return "default";
|
|
123
|
-
const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
|
|
124
|
-
for (const binding of bindings) {
|
|
125
|
-
if (String(binding?.agentId || "").trim() !== normalizedAgentId) continue;
|
|
126
|
-
const match = binding?.match || {};
|
|
127
|
-
if (String(match.channel || "").trim() !== "telegram") continue;
|
|
128
|
-
if (hasScopedBindingFields(match)) continue;
|
|
129
|
-
return String(match.accountId || "").trim() || "default";
|
|
130
|
-
}
|
|
131
|
-
return normalizedAgentId === "main" ? "default" : "";
|
|
132
|
-
};
|
|
133
|
-
const resolveTelegramChannelNameForAgent = ({ config, agentId }) => {
|
|
134
|
-
const telegramConfig =
|
|
135
|
-
config?.channels?.telegram && typeof config.channels.telegram === "object"
|
|
136
|
-
? config.channels.telegram
|
|
137
|
-
: {};
|
|
138
|
-
const accountId = resolveTelegramAccountIdForAgent({ config, agentId });
|
|
139
|
-
const hasAccounts =
|
|
140
|
-
telegramConfig.accounts && typeof telegramConfig.accounts === "object";
|
|
141
|
-
if (hasAccounts) {
|
|
142
|
-
const accountConfig =
|
|
143
|
-
accountId && telegramConfig.accounts?.[accountId]
|
|
144
|
-
? telegramConfig.accounts[accountId]
|
|
145
|
-
: {};
|
|
146
|
-
const accountName = String(accountConfig?.name || "").trim();
|
|
147
|
-
if (accountName) return accountName;
|
|
148
|
-
} else if (accountId === "default") {
|
|
149
|
-
const legacyName = String(telegramConfig?.name || "").trim();
|
|
150
|
-
if (legacyName) return legacyName;
|
|
151
|
-
}
|
|
152
|
-
return "Telegram";
|
|
153
|
-
};
|
|
154
119
|
const getDefaultAgentLabel = (config = {}) => {
|
|
155
120
|
return "Main Agent";
|
|
156
121
|
};
|
|
@@ -182,44 +147,31 @@ const registerSystemRoutes = ({
|
|
|
182
147
|
if (!agentId) return "Agent";
|
|
183
148
|
return getConfiguredAgentLabel(config, agentId);
|
|
184
149
|
};
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/:telegram:group:([^:]+):topic:([^:]+)$/,
|
|
201
|
-
);
|
|
202
|
-
if (telegramTopicMatch) {
|
|
203
|
-
const [, groupId, topicId] = telegramTopicMatch;
|
|
204
|
-
let groupEntry = null;
|
|
205
|
-
try {
|
|
206
|
-
groupEntry = topicRegistry?.getGroup?.(groupId) || null;
|
|
207
|
-
} catch {}
|
|
208
|
-
const groupName = String(groupEntry?.name || "").trim();
|
|
209
|
-
const topicName = String(
|
|
210
|
-
groupEntry?.topics?.[topicId]?.name || "",
|
|
211
|
-
).trim();
|
|
212
|
-
if (groupName && topicName) {
|
|
213
|
-
return `${agentLabel} - ${telegramChannelName} ${groupName} · ${topicName}`;
|
|
214
|
-
}
|
|
215
|
-
if (topicName) return `${agentLabel} - ${telegramChannelName} Topic ${topicName}`;
|
|
216
|
-
return `${agentLabel} - ${telegramChannelName} Topic ${topicId}`;
|
|
217
|
-
}
|
|
218
|
-
const directMatch = key.match(/:direct:([^:]+)$/);
|
|
219
|
-
if (directMatch) {
|
|
220
|
-
return `${agentLabel} - Direct ${directMatch[1]}`;
|
|
150
|
+
const parseChannelFromSessionKey = (key = "") => {
|
|
151
|
+
const k = String(key || "");
|
|
152
|
+
if (k.includes(":telegram:")) return "telegram";
|
|
153
|
+
if (k.includes(":discord:")) return "discord";
|
|
154
|
+
if (k.includes(":slack:")) return "slack";
|
|
155
|
+
return "";
|
|
156
|
+
};
|
|
157
|
+
const getSessionTopicContext = (sessionKey = "") => {
|
|
158
|
+
const key = String(sessionKey || "");
|
|
159
|
+
const topicMatch = key.match(/:telegram:group:([^:]+):topic:([^:]+)$/);
|
|
160
|
+
if (!topicMatch) {
|
|
161
|
+
return {
|
|
162
|
+
groupName: "",
|
|
163
|
+
topicName: "",
|
|
164
|
+
};
|
|
221
165
|
}
|
|
222
|
-
|
|
166
|
+
const [, groupId, topicId] = topicMatch;
|
|
167
|
+
let groupEntry = null;
|
|
168
|
+
try {
|
|
169
|
+
groupEntry = topicRegistry?.getGroup?.(groupId) || null;
|
|
170
|
+
} catch {}
|
|
171
|
+
return {
|
|
172
|
+
groupName: String(groupEntry?.name || "").trim(),
|
|
173
|
+
topicName: String(groupEntry?.topics?.[topicId]?.name || "").trim(),
|
|
174
|
+
};
|
|
223
175
|
};
|
|
224
176
|
const syncApiKeyAuthProfilesFromEnvVars = (nextEnvVars) => {
|
|
225
177
|
if (!authProfiles) return;
|
|
@@ -280,21 +232,15 @@ const registerSystemRoutes = ({
|
|
|
280
232
|
fallback: {},
|
|
281
233
|
});
|
|
282
234
|
return sessions
|
|
283
|
-
.filter((sessionRow) => {
|
|
284
|
-
const key = getRawSessionKey(sessionRow).toLowerCase();
|
|
285
|
-
if (!key) return false;
|
|
286
|
-
if (
|
|
287
|
-
key.includes(":hook:") ||
|
|
288
|
-
key.includes(":cron:") ||
|
|
289
|
-
key.includes(":doctor:")
|
|
290
|
-
) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
return true;
|
|
294
|
-
})
|
|
295
235
|
.map((sessionRow) => {
|
|
296
236
|
const key = getRawSessionKey(sessionRow);
|
|
237
|
+
if (!key) return null;
|
|
297
238
|
const replyTarget = getSessionReplyTarget(key);
|
|
239
|
+
const agentKeyMatch = key.match(/^agent:([^:]+):/);
|
|
240
|
+
const agentId = String(agentKeyMatch?.[1] || "").trim();
|
|
241
|
+
const channel =
|
|
242
|
+
parseChannelFromSessionKey(key) || replyTarget.replyChannel || "";
|
|
243
|
+
const topicContext = getSessionTopicContext(key);
|
|
298
244
|
return {
|
|
299
245
|
key,
|
|
300
246
|
sessionId: String(sessionRow?.sessionId || sessionRow?.id || ""),
|
|
@@ -304,11 +250,16 @@ const registerSystemRoutes = ({
|
|
|
304
250
|
sessionRow?.lastActivityAt ||
|
|
305
251
|
sessionRow?.lastActiveAt,
|
|
306
252
|
) || 0,
|
|
307
|
-
|
|
253
|
+
agentId,
|
|
254
|
+
agentLabel: getAgentLabelFromSessionKey(key, config),
|
|
255
|
+
channel,
|
|
256
|
+
groupName: topicContext.groupName,
|
|
257
|
+
topicName: topicContext.topicName,
|
|
308
258
|
replyChannel: replyTarget.replyChannel,
|
|
309
259
|
replyTo: replyTarget.replyTo,
|
|
310
260
|
};
|
|
311
261
|
})
|
|
262
|
+
.filter(Boolean)
|
|
312
263
|
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
313
264
|
};
|
|
314
265
|
const readSystemCronConfig = () => {
|