@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.
@@ -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?.label || sessionRow?.key || "Session",
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(sessionRow?.label || sessionRow?.key || "Session")}
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 { kDestinationSessionFilter } from "../../lib/session-keys.js";
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.label || sessionRow.key}
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?.label || "Pick a session in the sidebar"}
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 { getSessionRowKey } from "../lib/session-keys.js";
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(sessionRow?.label || getSessionRowKey(sessionRow) || "Session")}
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
- ChatVoiceLineIcon,
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 { readUiSettings, writeUiSettings } from "../lib/ui-settings.js";
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
- <${ChatVoiceLineIcon} className="sidebar-tab-icon" />
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
- : chatSessions.map(
323
- (sessionRow) => html`
324
- <button
325
- key=${sessionRow.key}
326
- class=${`sidebar-chat-session-item ${selectedChatSessionKey === sessionRow.key ? "active" : ""}`}
327
- onclick=${() => onSelectChatSession(sessionRow.key)}
328
- title=${sessionRow.label || sessionRow.key}
329
- >
330
- <span class="sidebar-chat-session-name"
331
- >${sessionRow.label || sessionRow.key}</span
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
- </button>
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 { getSessionRowKey } from "../../../lib/session-keys.js";
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?.label ||
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
- export const kAgentSessionsCacheKey = "alphaclaw.agent.sessionsCache";
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 buildSessionLabel = (sessionRow = {}, config = {}) => {
186
- const key = String(sessionRow?.key || "");
187
- const agentLabel = getAgentLabelFromSessionKey(key, config);
188
- const agentKeyMatch = key.match(/^agent:([^:]+):/);
189
- const agentId = String(agentKeyMatch?.[1] || "").trim();
190
- const telegramChannelName = resolveTelegramChannelNameForAgent({
191
- config,
192
- agentId,
193
- });
194
- if (key.endsWith(":main")) return `${agentLabel} - Main Thread`;
195
- const telegramMatch = key.match(/:telegram:direct:([^:]+)$/);
196
- if (telegramMatch) {
197
- return `${agentLabel} - Telegram DM (${telegramChannelName})`;
198
- }
199
- const telegramTopicMatch = key.match(
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
- return key || "Session";
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
- label: buildSessionLabel(sessionRow, config),
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 = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.8.3-beta.4",
3
+ "version": "0.8.3-beta.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },