@chrysb/alphaclaw 0.5.6 → 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.
Files changed (84) hide show
  1. package/bin/alphaclaw.js +6 -1
  2. package/lib/public/css/agents.css +92 -0
  3. package/lib/public/css/explorer.css +101 -0
  4. package/lib/public/css/shell.css +15 -4
  5. package/lib/public/js/app.js +69 -3
  6. package/lib/public/js/components/action-button.js +5 -0
  7. package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +76 -0
  8. package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +490 -0
  9. package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +256 -0
  10. package/lib/public/js/components/agents-tab/agent-detail-panel.js +74 -0
  11. package/lib/public/js/components/agents-tab/agent-identity-section.js +175 -0
  12. package/lib/public/js/components/agents-tab/agent-overview/index.js +53 -0
  13. package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +44 -0
  14. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +158 -0
  15. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +169 -0
  16. package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +45 -0
  17. package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +47 -0
  18. package/lib/public/js/components/agents-tab/agent-pairing-section.js +265 -0
  19. package/lib/public/js/components/agents-tab/create-agent-modal.js +189 -0
  20. package/lib/public/js/components/agents-tab/create-channel-modal.js +323 -0
  21. package/lib/public/js/components/agents-tab/delete-agent-dialog.js +50 -0
  22. package/lib/public/js/components/agents-tab/edit-agent-modal.js +109 -0
  23. package/lib/public/js/components/agents-tab/index.js +148 -0
  24. package/lib/public/js/components/agents-tab/use-agents.js +89 -0
  25. package/lib/public/js/components/channel-account-status-badge.js +35 -0
  26. package/lib/public/js/components/channel-operations-panel.js +33 -0
  27. package/lib/public/js/components/channels.js +545 -60
  28. package/lib/public/js/components/envars.js +25 -4
  29. package/lib/public/js/components/general/index.js +21 -11
  30. package/lib/public/js/components/general/use-general-tab.js +78 -16
  31. package/lib/public/js/components/google/gmail-setup-wizard.js +1 -3
  32. package/lib/public/js/components/google/index.js +28 -30
  33. package/lib/public/js/components/icons.js +37 -0
  34. package/lib/public/js/components/models-tab/index.js +58 -224
  35. package/lib/public/js/components/models-tab/model-picker.js +212 -0
  36. package/lib/public/js/components/models-tab/use-models.js +17 -14
  37. package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -4
  38. package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
  39. package/lib/public/js/components/overflow-menu.js +122 -0
  40. package/lib/public/js/components/pairings.js +36 -8
  41. package/lib/public/js/components/routes/agents-route.js +27 -0
  42. package/lib/public/js/components/routes/general-route.js +2 -0
  43. package/lib/public/js/components/routes/index.js +1 -0
  44. package/lib/public/js/components/routes/telegram-route.js +2 -2
  45. package/lib/public/js/components/secret-input.js +8 -1
  46. package/lib/public/js/components/sidebar.js +64 -26
  47. package/lib/public/js/components/telegram-workspace/index.js +175 -74
  48. package/lib/public/js/components/telegram-workspace/manage.js +83 -10
  49. package/lib/public/js/components/telegram-workspace/onboarding.js +9 -8
  50. package/lib/public/js/components/webhooks.js +43 -18
  51. package/lib/public/js/hooks/use-app-shell-controller.js +7 -0
  52. package/lib/public/js/hooks/use-browse-navigation.js +8 -5
  53. package/lib/public/js/hooks/use-destination-session-selection.js +8 -1
  54. package/lib/public/js/lib/api.js +163 -9
  55. package/lib/public/js/lib/app-navigation.js +2 -1
  56. package/lib/public/js/lib/channel-create-operation.js +102 -0
  57. package/lib/public/js/lib/format.js +14 -0
  58. package/lib/public/js/lib/sse.js +51 -0
  59. package/lib/public/js/lib/telegram-api.js +38 -18
  60. package/lib/public/setup.html +1 -0
  61. package/lib/public/shared/browse-file-policies.json +0 -1
  62. package/lib/server/agents/service.js +1478 -0
  63. package/lib/server/constants.js +2 -2
  64. package/lib/server/env.js +3 -1
  65. package/lib/server/gateway.js +104 -20
  66. package/lib/server/gmail-watch.js +29 -2
  67. package/lib/server/onboarding/import/import-applier.js +0 -1
  68. package/lib/server/onboarding/index.js +0 -6
  69. package/lib/server/onboarding/workspace.js +73 -38
  70. package/lib/server/openclaw-config.js +23 -0
  71. package/lib/server/operation-events.js +141 -0
  72. package/lib/server/routes/agents.js +266 -0
  73. package/lib/server/routes/pairings.js +135 -25
  74. package/lib/server/routes/system.js +90 -10
  75. package/lib/server/routes/telegram.js +247 -51
  76. package/lib/server/telegram-workspace.js +61 -10
  77. package/lib/server/topic-registry.js +66 -7
  78. package/lib/server/watchdog.js +39 -1
  79. package/lib/server/webhooks.js +60 -12
  80. package/lib/server.js +21 -7
  81. package/lib/setup/core-prompts/AGENTS.md +6 -5
  82. package/lib/setup/core-prompts/TOOLS.md +1 -8
  83. package/package.json +1 -1
  84. package/lib/setup/skills/control-ui/SKILL.md +0 -62
@@ -51,6 +51,78 @@ const buildTelegramGitSyncCommand = (action, target = "") => {
51
51
  return `alphaclaw git-sync -m ${quoteShellArg(message, { strategy: "single" })}`;
52
52
  };
53
53
 
54
+ const { createTelegramApi } = require("../telegram-api");
55
+
56
+ const kTelegramEnvKeyBase = "TELEGRAM_BOT_TOKEN";
57
+ const normalizeAccountId = (value) => String(value || "").trim() || "default";
58
+
59
+ const deriveAccountEnvKey = (accountId) => {
60
+ const normalized = normalizeAccountId(accountId);
61
+ if (normalized === "default") return kTelegramEnvKeyBase;
62
+ return `${kTelegramEnvKeyBase}_${normalized.replace(/-/g, "_").toUpperCase()}`;
63
+ };
64
+
65
+ const resolveAccountTelegramApi = (accountId, defaultApi) => {
66
+ const normalized = normalizeAccountId(accountId);
67
+ if (normalized === "default") return defaultApi;
68
+ const envKey = deriveAccountEnvKey(normalized);
69
+ const token = process.env[envKey];
70
+ if (!token) {
71
+ console.log(
72
+ `[alphaclaw] Telegram account "${normalized}": env var ${envKey} not found, falling back to default token`,
73
+ );
74
+ return defaultApi;
75
+ }
76
+ return createTelegramApi(() => process.env[envKey]);
77
+ };
78
+
79
+ const resolveAccountId = (req) =>
80
+ normalizeAccountId(req.query?.accountId || req.body?.accountId || "");
81
+
82
+ const resolveTelegramConfigForAccount = ({ telegramConfig, accountId }) => {
83
+ const normalizedAccountId = normalizeAccountId(accountId);
84
+ const accounts =
85
+ telegramConfig?.accounts && typeof telegramConfig.accounts === "object"
86
+ ? telegramConfig.accounts
87
+ : null;
88
+ const hasAccounts = !!accounts && Object.keys(accounts).length > 0;
89
+ if (hasAccounts) {
90
+ const accountConfig =
91
+ accounts[normalizedAccountId] &&
92
+ typeof accounts[normalizedAccountId] === "object"
93
+ ? accounts[normalizedAccountId]
94
+ : {};
95
+ return { normalizedAccountId, hasAccounts, accountConfig };
96
+ }
97
+ return {
98
+ normalizedAccountId,
99
+ hasAccounts: false,
100
+ accountConfig: telegramConfig || {},
101
+ };
102
+ };
103
+
104
+ const hasScopedBindingFields = (match) =>
105
+ !!match.peer ||
106
+ !!match.parentPeer ||
107
+ !!String(match.guildId || "").trim() ||
108
+ !!String(match.teamId || "").trim() ||
109
+ (Array.isArray(match.roles) && match.roles.length > 0);
110
+
111
+ const resolveBoundAgentIdForAccount = ({ cfg, accountId }) => {
112
+ const bindings = Array.isArray(cfg?.bindings) ? cfg.bindings : [];
113
+ const normalizedAccountId = normalizeAccountId(accountId);
114
+ for (const binding of bindings) {
115
+ const match = binding?.match || {};
116
+ if (hasScopedBindingFields(match)) continue;
117
+ if (String(match.channel || "").trim() !== "telegram") continue;
118
+ const bindingAccountId = normalizeAccountId(match.accountId);
119
+ if (bindingAccountId !== normalizedAccountId) continue;
120
+ const boundAgentId = String(binding?.agentId || "").trim();
121
+ if (boundAgentId) return boundAgentId;
122
+ }
123
+ return normalizedAccountId === "default" ? "default" : "";
124
+ };
125
+
54
126
  const registerTelegramRoutes = ({
55
127
  app,
56
128
  telegramApi,
@@ -59,18 +131,24 @@ const registerTelegramRoutes = ({
59
131
  }) => {
60
132
  const repairGroupAllowFromIfMissing = async ({
61
133
  cfg,
134
+ accountId = "default",
62
135
  groupId,
63
136
  requireMention = false,
137
+ tgApi = telegramApi,
64
138
  }) => {
65
139
  const telegramConfig = cfg?.channels?.telegram || {};
140
+ const { accountConfig } = resolveTelegramConfigForAccount({
141
+ telegramConfig,
142
+ accountId,
143
+ });
66
144
  if (
67
- Array.isArray(telegramConfig.groupAllowFrom) &&
68
- telegramConfig.groupAllowFrom.length > 0
145
+ Array.isArray(accountConfig.groupAllowFrom) &&
146
+ accountConfig.groupAllowFrom.length > 0
69
147
  ) {
70
148
  return { repaired: false, resolvedUserId: "", syncWarning: null };
71
149
  }
72
150
  const resolvedUserId = await resolveAllowUserId({
73
- telegramApi,
151
+ telegramApi: tgApi,
74
152
  groupId,
75
153
  preferredUserId: "",
76
154
  });
@@ -79,6 +157,7 @@ const registerTelegramRoutes = ({
79
157
  openclawDir: OPENCLAW_DIR,
80
158
  topicRegistry,
81
159
  groupId,
160
+ accountId,
82
161
  requireMention,
83
162
  resolvedUserId,
84
163
  });
@@ -104,8 +183,10 @@ const registerTelegramRoutes = ({
104
183
  // Verify bot token
105
184
  app.get("/api/telegram/bot", async (req, res) => {
106
185
  try {
107
- const me = await telegramApi.getMe();
108
- res.json({ ok: true, bot: me });
186
+ const reqAccountId = resolveAccountId(req);
187
+ const tgApi = resolveAccountTelegramApi(reqAccountId, telegramApi);
188
+ const me = await tgApi.getMe();
189
+ res.json({ ok: true, bot: me, accountId: reqAccountId || "default" });
109
190
  } catch (e) {
110
191
  res.json({ ok: false, error: e.message });
111
192
  }
@@ -118,11 +199,15 @@ const registerTelegramRoutes = ({
118
199
  return res.status(400).json({ ok: false, error: "groupId is required" });
119
200
 
120
201
  try {
121
- const chat = await telegramApi.getChat(groupId);
122
- const me = await telegramApi.getMe();
123
- const member = await telegramApi.getChatMember(groupId, me.id);
124
- const suggestedUserId = await resolveAllowUserId({
202
+ const tgApi = resolveAccountTelegramApi(
203
+ resolveAccountId(req),
125
204
  telegramApi,
205
+ );
206
+ const chat = await tgApi.getChat(groupId);
207
+ const me = await tgApi.getMe();
208
+ const member = await tgApi.getChatMember(groupId, me.id);
209
+ const suggestedUserId = await resolveAllowUserId({
210
+ telegramApi: tgApi,
126
211
  groupId,
127
212
  preferredUserId: "",
128
213
  });
@@ -166,6 +251,8 @@ const registerTelegramRoutes = ({
166
251
  const systemInstructions = String(
167
252
  body.systemInstructions ?? body.systemPrompt ?? "",
168
253
  ).trim();
254
+ const hasAgentId = Object.prototype.hasOwnProperty.call(body, "agentId");
255
+ const agentId = String(body.agentId ?? "").trim();
169
256
  const iconColorValue =
170
257
  rawIconColor == null ? null : Number.parseInt(String(rawIconColor), 10);
171
258
  const iconColor = Number.isFinite(iconColorValue)
@@ -175,7 +262,11 @@ const registerTelegramRoutes = ({
175
262
  return res.status(400).json({ ok: false, error: "name is required" });
176
263
 
177
264
  try {
178
- const result = await telegramApi.createForumTopic(groupId, name, {
265
+ const tgApi = resolveAccountTelegramApi(
266
+ resolveAccountId(req),
267
+ telegramApi,
268
+ );
269
+ const result = await tgApi.createForumTopic(groupId, name, {
179
270
  iconColor,
180
271
  });
181
272
  const threadId = result.message_thread_id;
@@ -183,12 +274,14 @@ const registerTelegramRoutes = ({
183
274
  name: result.name,
184
275
  iconColor: result.icon_color,
185
276
  ...(systemInstructions ? { systemInstructions } : {}),
277
+ ...(hasAgentId ? { agentId: agentId || undefined } : {}),
186
278
  });
187
279
  syncConfigForTelegram({
188
280
  fs,
189
281
  openclawDir: OPENCLAW_DIR,
190
282
  topicRegistry,
191
283
  groupId,
284
+ accountId: resolveAccountId(req),
192
285
  requireMention: false,
193
286
  resolvedUserId: "",
194
287
  });
@@ -200,6 +293,7 @@ const registerTelegramRoutes = ({
200
293
  threadId,
201
294
  name: result.name,
202
295
  iconColor: result.icon_color,
296
+ ...(hasAgentId ? { agentId } : {}),
203
297
  },
204
298
  syncWarning,
205
299
  });
@@ -219,6 +313,7 @@ const registerTelegramRoutes = ({
219
313
  .json({ ok: false, error: "topics array is required" });
220
314
  }
221
315
 
316
+ const tgApi = resolveAccountTelegramApi(resolveAccountId(req), telegramApi);
222
317
  const results = [];
223
318
  for (const t of topics) {
224
319
  if (!t.name) {
@@ -226,17 +321,20 @@ const registerTelegramRoutes = ({
226
321
  continue;
227
322
  }
228
323
  try {
229
- const result = await telegramApi.createForumTopic(groupId, t.name, {
324
+ const result = await tgApi.createForumTopic(groupId, t.name, {
230
325
  iconColor: t.iconColor || undefined,
231
326
  });
232
327
  const threadId = result.message_thread_id;
233
328
  const systemInstructions = String(
234
329
  t.systemInstructions ?? t.systemPrompt ?? "",
235
330
  ).trim();
331
+ const hasAgentId = Object.prototype.hasOwnProperty.call(t, "agentId");
332
+ const agentId = String(t.agentId ?? "").trim();
236
333
  topicRegistry.addTopic(groupId, threadId, {
237
334
  name: result.name,
238
335
  iconColor: result.icon_color,
239
336
  ...(systemInstructions ? { systemInstructions } : {}),
337
+ ...(hasAgentId ? { agentId: agentId || undefined } : {}),
240
338
  });
241
339
  results.push({ name: result.name, threadId, ok: true });
242
340
  } catch (e) {
@@ -248,6 +346,7 @@ const registerTelegramRoutes = ({
248
346
  openclawDir: OPENCLAW_DIR,
249
347
  topicRegistry,
250
348
  groupId,
349
+ accountId: resolveAccountId(req),
251
350
  requireMention: false,
252
351
  resolvedUserId: "",
253
352
  });
@@ -262,13 +361,18 @@ const registerTelegramRoutes = ({
262
361
  async (req, res) => {
263
362
  const { groupId, topicId } = req.params;
264
363
  try {
265
- await telegramApi.deleteForumTopic(groupId, parseInt(topicId, 10));
364
+ const tgApi = resolveAccountTelegramApi(
365
+ resolveAccountId(req),
366
+ telegramApi,
367
+ );
368
+ await tgApi.deleteForumTopic(groupId, parseInt(topicId, 10));
266
369
  topicRegistry.removeTopic(groupId, topicId);
267
370
  syncConfigForTelegram({
268
371
  fs,
269
372
  openclawDir: OPENCLAW_DIR,
270
373
  topicRegistry,
271
374
  groupId,
375
+ accountId: resolveAccountId(req),
272
376
  requireMention: false,
273
377
  resolvedUserId: "",
274
378
  });
@@ -285,6 +389,7 @@ const registerTelegramRoutes = ({
285
389
  openclawDir: OPENCLAW_DIR,
286
390
  topicRegistry,
287
391
  groupId,
392
+ accountId: resolveAccountId(req),
288
393
  requireMention: false,
289
394
  resolvedUserId: "",
290
395
  });
@@ -304,7 +409,7 @@ const registerTelegramRoutes = ({
304
409
  },
305
410
  );
306
411
 
307
- // Rename a topic
412
+ // Update a topic (rename, system instructions, agent routing)
308
413
  app.put("/api/telegram/groups/:groupId/topics/:topicId", async (req, res) => {
309
414
  const { groupId, topicId } = req.params;
310
415
  const body = req.body || {};
@@ -315,6 +420,8 @@ const registerTelegramRoutes = ({
315
420
  const systemInstructions = String(
316
421
  body.systemInstructions ?? body.systemPrompt ?? "",
317
422
  ).trim();
423
+ const hasAgentId = Object.prototype.hasOwnProperty.call(body, "agentId");
424
+ const agentId = String(body.agentId ?? "").trim();
318
425
  if (!name)
319
426
  return res.status(400).json({ ok: false, error: "name is required" });
320
427
  try {
@@ -324,13 +431,17 @@ const registerTelegramRoutes = ({
324
431
  .status(400)
325
432
  .json({ ok: false, error: "topicId must be numeric" });
326
433
  }
434
+ const tgApi = resolveAccountTelegramApi(
435
+ resolveAccountId(req),
436
+ telegramApi,
437
+ );
327
438
  const existingTopic =
328
439
  topicRegistry.getGroup(groupId)?.topics?.[String(threadId)] || {};
329
440
  const existingName = String(existingTopic.name || "").trim();
330
441
  const shouldRename = !existingName || existingName !== name;
331
442
  if (shouldRename) {
332
443
  try {
333
- await telegramApi.editForumTopic(groupId, threadId, { name });
444
+ await tgApi.editForumTopic(groupId, threadId, { name });
334
445
  } catch (e) {
335
446
  // Telegram returns TOPIC_NOT_MODIFIED when the name is unchanged.
336
447
  if (!String(e.message || "").includes("TOPIC_NOT_MODIFIED")) {
@@ -342,23 +453,26 @@ const registerTelegramRoutes = ({
342
453
  ...existingTopic,
343
454
  name,
344
455
  ...(hasSystemInstructions ? { systemInstructions } : {}),
456
+ ...(hasAgentId ? { agentId: agentId || undefined } : {}),
345
457
  });
346
458
  syncConfigForTelegram({
347
459
  fs,
348
460
  openclawDir: OPENCLAW_DIR,
349
461
  topicRegistry,
350
462
  groupId,
463
+ accountId: resolveAccountId(req),
351
464
  requireMention: false,
352
465
  resolvedUserId: "",
353
466
  });
354
467
  syncPromptFiles();
355
- const syncWarning = await runTelegramGitSync("rename-topic", name);
468
+ const syncWarning = await runTelegramGitSync("update-topic", name);
356
469
  return res.json({
357
470
  ok: true,
358
471
  topic: {
359
472
  threadId,
360
473
  name,
361
474
  ...(hasSystemInstructions ? { systemInstructions } : {}),
475
+ ...(hasAgentId ? { agentId } : {}),
362
476
  },
363
477
  syncWarning,
364
478
  });
@@ -373,10 +487,15 @@ const registerTelegramRoutes = ({
373
487
  const body = req.body || {};
374
488
  const userId = body.userId ?? "";
375
489
  const groupName = body.groupName ?? "";
490
+ const accountId = resolveAccountId(req);
376
491
  const requireMention = parseBooleanValue(body.requireMention, false);
377
492
  try {
378
- const resolvedUserId = await resolveAllowUserId({
493
+ const tgApi = resolveAccountTelegramApi(
494
+ accountId,
379
495
  telegramApi,
496
+ );
497
+ const resolvedUserId = await resolveAllowUserId({
498
+ telegramApi: tgApi,
380
499
  groupId,
381
500
  preferredUserId: userId,
382
501
  });
@@ -385,15 +504,21 @@ const registerTelegramRoutes = ({
385
504
  openclawDir: OPENCLAW_DIR,
386
505
  topicRegistry,
387
506
  groupId,
507
+ accountId,
388
508
  requireMention,
389
509
  resolvedUserId,
390
510
  });
391
511
 
392
512
  // Save metadata in local topic registry only.
393
- if (groupName) {
394
- topicRegistry.setGroup(groupId, { name: groupName });
395
- syncPromptFiles();
396
- }
513
+ const configPath = `${OPENCLAW_DIR}/openclaw.json`;
514
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
515
+ const boundAgentId = resolveBoundAgentIdForAccount({ cfg, accountId });
516
+ topicRegistry.setGroup(groupId, {
517
+ ...(groupName ? { name: groupName } : {}),
518
+ accountId,
519
+ ...(boundAgentId ? { agentId: boundAgentId } : {}),
520
+ });
521
+ syncPromptFiles();
397
522
  const syncWarning = await runTelegramGitSync("configure-group", groupId);
398
523
 
399
524
  res.json({ ok: true, userId: resolvedUserId || null, syncWarning });
@@ -410,47 +535,95 @@ const registerTelegramRoutes = ({
410
535
  // Workspace bootstrap info (lets UI jump straight to management)
411
536
  app.get("/api/telegram/workspace", async (req, res) => {
412
537
  try {
538
+ const accountId = resolveAccountId(req);
413
539
  const debugEnabled = isDebugEnabled();
414
540
  const configPath = `${OPENCLAW_DIR}/openclaw.json`;
415
541
  let cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
416
542
  let telegramConfig = cfg.channels?.telegram || {};
417
- const configuredGroups = telegramConfig.groups || {};
543
+ const { accountConfig } = resolveTelegramConfigForAccount({
544
+ telegramConfig,
545
+ accountId,
546
+ });
547
+ const configuredGroups =
548
+ accountConfig?.groups && typeof accountConfig.groups === "object"
549
+ ? accountConfig.groups
550
+ : {};
418
551
  const groupIds = Object.keys(configuredGroups);
419
- if (groupIds.length === 0) {
420
- return res.json({ ok: true, configured: false, debugEnabled });
552
+ const registryFallbackGroups = topicRegistry.getGroupsForAccount(accountId);
553
+ const registryFallbackGroupIds = Object.keys(registryFallbackGroups);
554
+ const useRegistryFallback =
555
+ groupIds.length === 0 && registryFallbackGroupIds.length > 0;
556
+ if (groupIds.length === 0 && !useRegistryFallback) {
557
+ return res.json({
558
+ ok: true,
559
+ configured: false,
560
+ groups: [],
561
+ debugEnabled,
562
+ });
421
563
  }
422
- const groupId = String(groupIds[0]);
423
- const groupConfig = configuredGroups[groupId] || {};
424
- const repairResult = await repairGroupAllowFromIfMissing({
425
- cfg,
426
- groupId,
427
- requireMention: !!groupConfig.requireMention,
428
- });
429
- if (repairResult.repaired) {
430
- cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
431
- telegramConfig = cfg.channels?.telegram || {};
564
+
565
+ const tgApi = resolveAccountTelegramApi(accountId, telegramApi);
566
+ let activeGroupIds = useRegistryFallback ? registryFallbackGroupIds : groupIds;
567
+ if (!useRegistryFallback) {
568
+ let anyRepaired = false;
569
+ for (const gId of groupIds) {
570
+ const gConfig = configuredGroups[gId] || {};
571
+ const repairResult = await repairGroupAllowFromIfMissing({
572
+ cfg,
573
+ accountId,
574
+ groupId: gId,
575
+ requireMention: !!gConfig.requireMention,
576
+ tgApi,
577
+ });
578
+ if (repairResult.repaired) {
579
+ anyRepaired = true;
580
+ }
581
+ }
582
+ if (anyRepaired) {
583
+ cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
584
+ telegramConfig = cfg.channels?.telegram || {};
585
+ }
586
+ const refreshedAccountConfig = resolveTelegramConfigForAccount({
587
+ telegramConfig,
588
+ accountId,
589
+ }).accountConfig;
590
+ const refreshedGroups =
591
+ refreshedAccountConfig?.groups &&
592
+ typeof refreshedAccountConfig.groups === "object"
593
+ ? refreshedAccountConfig.groups
594
+ : {};
595
+ activeGroupIds = Object.keys(refreshedGroups);
432
596
  }
433
- const registryGroup = topicRegistry.getGroup(groupId);
434
- let groupName = registryGroup?.name || groupId;
435
- try {
436
- const chat = await telegramApi.getChat(groupId);
437
- if (chat?.title) groupName = chat.title;
438
- } catch {}
597
+
598
+ const groups = [];
599
+ for (const gId of activeGroupIds) {
600
+ const registryGroup = topicRegistry.getGroup(gId);
601
+ let gName = registryGroup?.name || gId;
602
+ try {
603
+ const chat = await tgApi.getChat(gId);
604
+ if (chat?.title) gName = chat.title;
605
+ } catch {}
606
+ groups.push({
607
+ groupId: gId,
608
+ groupName: gName,
609
+ topics: registryGroup?.topics || {},
610
+ });
611
+ }
612
+
613
+ const first = groups[0] || {};
439
614
  return res.json({
440
615
  ok: true,
441
616
  configured: true,
442
- groupId,
443
- groupName,
444
- topics: registryGroup?.topics || {},
617
+ groups,
618
+ groupId: first.groupId,
619
+ groupName: first.groupName,
620
+ topics: first.topics,
445
621
  debugEnabled,
446
622
  concurrency: {
447
623
  agentMaxConcurrent: cfg.agents?.defaults?.maxConcurrent ?? null,
448
624
  subagentMaxConcurrent:
449
625
  cfg.agents?.defaults?.subagents?.maxConcurrent ?? null,
450
626
  },
451
- repairedGroupAllowFrom: !!repairResult.repaired,
452
- repairedUserId: repairResult.resolvedUserId || null,
453
- syncWarning: repairResult.syncWarning || null,
454
627
  });
455
628
  } catch (e) {
456
629
  return res.json({ ok: false, error: e.message });
@@ -460,26 +633,49 @@ const registerTelegramRoutes = ({
460
633
  // Reset Telegram workspace onboarding state
461
634
  app.post("/api/telegram/workspace/reset", async (req, res) => {
462
635
  try {
636
+ const accountId = resolveAccountId(req);
463
637
  const configPath = `${OPENCLAW_DIR}/openclaw.json`;
464
638
  const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
465
- const telegramGroups = Object.keys(cfg.channels?.telegram?.groups || {});
466
- if (cfg.channels?.telegram) {
467
- delete cfg.channels.telegram.groups;
468
- delete cfg.channels.telegram.groupAllowFrom;
639
+ const telegramConfig = cfg.channels?.telegram;
640
+ if (!telegramConfig || typeof telegramConfig !== "object") {
641
+ return res.json({ ok: true, syncWarning: null });
642
+ }
643
+ const { normalizedAccountId, hasAccounts, accountConfig } =
644
+ resolveTelegramConfigForAccount({
645
+ telegramConfig,
646
+ accountId,
647
+ });
648
+ const telegramGroups = Object.keys(accountConfig?.groups || {});
649
+ const groupsToRemove =
650
+ telegramGroups.length > 0
651
+ ? telegramGroups
652
+ : Object.keys(topicRegistry.getGroupsForAccount(accountId));
653
+ if (hasAccounts) {
654
+ const accountEntry = telegramConfig.accounts?.[normalizedAccountId];
655
+ if (accountEntry && typeof accountEntry === "object") {
656
+ delete accountEntry.groups;
657
+ delete accountEntry.groupAllowFrom;
658
+ }
659
+ } else {
660
+ delete telegramConfig.groups;
661
+ delete telegramConfig.groupAllowFrom;
469
662
  }
470
663
  fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
471
664
 
472
665
  // Remove corresponding groups from topic registry
473
666
  const registry = topicRegistry.readRegistry();
474
667
  if (registry && registry.groups) {
475
- for (const groupId of telegramGroups) {
668
+ for (const groupId of groupsToRemove) {
476
669
  delete registry.groups[groupId];
477
670
  }
478
671
  topicRegistry.writeRegistry(registry);
479
672
  }
480
673
 
481
674
  syncPromptFiles();
482
- const syncWarning = await runTelegramGitSync("reset-workspace", "telegram");
675
+ const syncWarning = await runTelegramGitSync(
676
+ "reset-workspace",
677
+ "telegram",
678
+ );
483
679
  return res.json({ ok: true, syncWarning });
484
680
  } catch (e) {
485
681
  return res.json({ ok: false, error: e.message });
@@ -2,11 +2,39 @@ const kTelegramTopicConcurrencyMultiplier = 3;
2
2
  const kAgentConcurrencyFloor = 8;
3
3
  const kSubagentConcurrencyFloor = 4;
4
4
 
5
+ const normalizeAccountId = (value) => String(value || "").trim() || "default";
6
+
7
+ const resolveTelegramAccountConfig = ({ telegramConfig, accountId }) => {
8
+ const normalizedAccountId = normalizeAccountId(accountId);
9
+ const accounts =
10
+ telegramConfig?.accounts && typeof telegramConfig.accounts === "object"
11
+ ? telegramConfig.accounts
12
+ : null;
13
+ const hasAccounts = !!accounts && Object.keys(accounts).length > 0;
14
+ if (hasAccounts) {
15
+ const nextAccountConfig =
16
+ accounts[normalizedAccountId] && typeof accounts[normalizedAccountId] === "object"
17
+ ? accounts[normalizedAccountId]
18
+ : {};
19
+ return {
20
+ normalizedAccountId,
21
+ hasAccounts,
22
+ accountConfig: nextAccountConfig,
23
+ };
24
+ }
25
+ return {
26
+ normalizedAccountId,
27
+ hasAccounts: false,
28
+ accountConfig: telegramConfig,
29
+ };
30
+ };
31
+
5
32
  const syncConfigForTelegram = ({
6
33
  fs,
7
34
  openclawDir,
8
35
  topicRegistry,
9
36
  groupId,
37
+ accountId = "default",
10
38
  requireMention = false,
11
39
  resolvedUserId = "",
12
40
  }) => {
@@ -20,9 +48,32 @@ const syncConfigForTelegram = ({
20
48
 
21
49
  if (!cfg.channels) cfg.channels = {};
22
50
  if (!cfg.channels.telegram) cfg.channels.telegram = {};
23
- if (!cfg.channels.telegram.groups) cfg.channels.telegram.groups = {};
24
- const existingGroupConfig = cfg.channels.telegram.groups[groupId] || {};
25
- cfg.channels.telegram.groups[groupId] = {
51
+ const telegramConfig = cfg.channels.telegram;
52
+ const { normalizedAccountId, hasAccounts, accountConfig } =
53
+ resolveTelegramAccountConfig({
54
+ telegramConfig,
55
+ accountId,
56
+ });
57
+ if (hasAccounts) {
58
+ if (!telegramConfig.accounts || typeof telegramConfig.accounts !== "object") {
59
+ telegramConfig.accounts = {};
60
+ }
61
+ if (
62
+ !telegramConfig.accounts[normalizedAccountId]
63
+ || typeof telegramConfig.accounts[normalizedAccountId] !== "object"
64
+ ) {
65
+ telegramConfig.accounts[normalizedAccountId] = {};
66
+ }
67
+ }
68
+ const targetConfig = hasAccounts
69
+ ? telegramConfig.accounts[normalizedAccountId]
70
+ : telegramConfig;
71
+
72
+ if (!targetConfig.groups || typeof targetConfig.groups !== "object") {
73
+ targetConfig.groups = {};
74
+ }
75
+ const existingGroupConfig = targetConfig.groups[groupId] || {};
76
+ targetConfig.groups[groupId] = {
26
77
  ...existingGroupConfig,
27
78
  requireMention,
28
79
  };
@@ -35,20 +86,20 @@ const syncConfigForTelegram = ({
35
86
  promptTopics[threadId] = { systemPrompt };
36
87
  }
37
88
  if (Object.keys(promptTopics).length > 0) {
38
- cfg.channels.telegram.groups[groupId].topics = promptTopics;
89
+ targetConfig.groups[groupId].topics = promptTopics;
39
90
  } else {
40
- delete cfg.channels.telegram.groups[groupId].topics;
91
+ delete targetConfig.groups[groupId].topics;
41
92
  }
42
93
 
43
- cfg.channels.telegram.groupPolicy = "allowlist";
44
- if (!Array.isArray(cfg.channels.telegram.groupAllowFrom)) {
45
- cfg.channels.telegram.groupAllowFrom = [];
94
+ targetConfig.groupPolicy = "allowlist";
95
+ if (!Array.isArray(targetConfig.groupAllowFrom)) {
96
+ targetConfig.groupAllowFrom = [];
46
97
  }
47
98
  if (
48
99
  resolvedUserId
49
- && !cfg.channels.telegram.groupAllowFrom.includes(String(resolvedUserId))
100
+ && !targetConfig.groupAllowFrom.includes(String(resolvedUserId))
50
101
  ) {
51
- cfg.channels.telegram.groupAllowFrom.push(String(resolvedUserId));
102
+ targetConfig.groupAllowFrom.push(String(resolvedUserId));
52
103
  }
53
104
 
54
105
  // Persist thread sessions and keep concurrency in schema-valid agent defaults.