negotium 0.7.1 → 0.8.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 (44) hide show
  1. package/README.md +3 -4
  2. package/dist/agent-helpers.js +17 -8
  3. package/dist/agent-helpers.js.map +10 -9
  4. package/dist/background-bash.js.map +2 -2
  5. package/dist/browser-runtime.js.map +2 -2
  6. package/dist/{chunk-4y98yvq8.js → chunk-1bfta5qh.js} +2 -2
  7. package/dist/{chunk-4y98yvq8.js.map → chunk-1bfta5qh.js.map} +2 -2
  8. package/dist/chunk-pp6dvrzq.js.map +2 -2
  9. package/dist/hosted-agent.js +2 -2
  10. package/dist/hosted-agent.js.map +4 -4
  11. package/dist/main.js +355 -148
  12. package/dist/main.js.map +16 -15
  13. package/dist/mcp-factories.js +17 -8
  14. package/dist/mcp-factories.js.map +10 -9
  15. package/dist/media.js.map +2 -2
  16. package/dist/prompts.js.map +2 -2
  17. package/dist/query-runtime.js +7 -2
  18. package/dist/query-runtime.js.map +7 -6
  19. package/dist/registry.js +1 -1
  20. package/dist/runtime/src/identifiers.ts +28 -0
  21. package/dist/runtime/src/index.ts +12 -0
  22. package/dist/runtime/src/query/index.ts +0 -1
  23. package/dist/runtime/src/query/public-runtime.ts +1 -5
  24. package/dist/runtime/src/query/room-query-registry.ts +4 -2
  25. package/dist/runtime/src/query/types.ts +0 -76
  26. package/dist/runtime/src/runtime/turn-runner.ts +4 -2
  27. package/dist/runtime/src/storage/api-topics.ts +32 -12
  28. package/dist/runtime/src/storage/storage-public.ts +4 -39
  29. package/dist/runtime/src/topics/personal-general.ts +3 -3
  30. package/dist/runtime/src/types.ts +0 -11
  31. package/dist/runtime/src/version.ts +1 -1
  32. package/dist/runtime-helpers.js.map +2 -2
  33. package/dist/storage.js +11 -35
  34. package/dist/storage.js.map +4 -4
  35. package/dist/types/packages/core/src/identifiers.d.ts +21 -0
  36. package/dist/types/packages/core/src/query/public-runtime.d.ts +1 -1
  37. package/dist/types/packages/core/src/query/room-query-registry.d.ts +2 -1
  38. package/dist/types/packages/core/src/query/types.d.ts +0 -59
  39. package/dist/types/packages/core/src/storage/api-topics.d.ts +10 -4
  40. package/dist/types/packages/core/src/storage/storage-public.d.ts +2 -3
  41. package/dist/types/packages/core/src/types.d.ts +0 -10
  42. package/dist/types/packages/core/src/version.d.ts +1 -1
  43. package/dist/vault.js.map +2 -2
  44. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -3708,7 +3708,7 @@ var exports_version = {};
3708
3708
  __export(exports_version, {
3709
3709
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
3710
3710
  });
3711
- var NEGOTIUM_VERSION = "0.7.1";
3711
+ var NEGOTIUM_VERSION = "0.8.0";
3712
3712
 
3713
3713
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3714
3714
  import { spawn as spawn2 } from "child_process";
@@ -7884,6 +7884,7 @@ __export(exports_api_topics, {
7884
7884
  upsertTopic: () => upsertTopic,
7885
7885
  stampUnscopedOtiumTopics: () => stampUnscopedOtiumTopics,
7886
7886
  setTopicSurfaces: () => setTopicSurfaces,
7887
+ setTopicSurfaceScope: () => setTopicSurfaceScope,
7887
7888
  setTopicSessionId: () => setTopicSessionId,
7888
7889
  setSurfaceScopeRequired: () => setSurfaceScopeRequired,
7889
7890
  setDefaultSurfaceScope: () => setDefaultSurfaceScope,
@@ -8412,11 +8413,9 @@ function normalizedTitle(title) {
8412
8413
  return title.trim().toLowerCase();
8413
8414
  }
8414
8415
  function surfaceScopeForWrite(t) {
8415
- if (normalizeTopicSurface(t.surface ?? defaultTopicSurface()) !== "otium")
8416
- return null;
8417
8416
  if (t.surfaceScope !== undefined)
8418
8417
  return normalizeSurfaceScope(t.surfaceScope);
8419
- return defaultSurfaceScope();
8418
+ return normalizeTopicSurface(t.surface ?? defaultTopicSurface()) === "otium" ? defaultSurfaceScope() : null;
8420
8419
  }
8421
8420
  function upsertTopic(t) {
8422
8421
  const normalized = normalizeTopicState({
@@ -8561,6 +8560,13 @@ function setTopicSurfaces(topicIds, surface) {
8561
8560
  })();
8562
8561
  return changed;
8563
8562
  }
8563
+ function setTopicSurfaceScope(topicId, surface, surfaceScope) {
8564
+ const normalizedSurface = normalizeTopicSurface(surface);
8565
+ const normalizedScope = normalizeSurfaceScope(surfaceScope);
8566
+ const result = db.query(`UPDATE api_topics SET surface = ?, surface_scope = ?
8567
+ WHERE id = ? AND (surface IS NOT ? OR surface_scope IS NOT ?)`).run(normalizedSurface, normalizedScope, topicId, normalizedSurface, normalizedScope);
8568
+ return Number(result.changes ?? 0) > 0;
8569
+ }
8564
8570
  function getTopicByNameForUser(title, userId, lookupScope) {
8565
8571
  const opts = "scope" in lookupScope ? {} : lookupScope;
8566
8572
  const trimmed = title.trim();
@@ -11511,9 +11517,17 @@ var init_manager2 = __esm(async () => {
11511
11517
  }, 30 * 60 * 1000).unref();
11512
11518
  });
11513
11519
 
11520
+ // ../../packages/core/src/identifiers.ts
11521
+ function asTurnId(value) {
11522
+ return value;
11523
+ }
11524
+ function asTurnSlotKey(value) {
11525
+ return value;
11526
+ }
11527
+
11514
11528
  // ../../packages/core/src/query/room-query-registry.ts
11515
11529
  function isolatedTurnRoomId(topicId, queryId) {
11516
- return `${topicId}${ISOLATED_TURN_ROOM_MARKER}${queryId}`;
11530
+ return asTurnSlotKey(`${topicId}${ISOLATED_TURN_ROOM_MARKER}${queryId}`);
11517
11531
  }
11518
11532
  function isIsolatedTurnRoomId(roomId) {
11519
11533
  return roomId.includes(ISOLATED_TURN_ROOM_MARKER);
@@ -11642,6 +11656,7 @@ function createRoomQueryRegistry(host) {
11642
11656
  };
11643
11657
  }
11644
11658
  var ISOLATED_TURN_ROOM_MARKER = "::isolated-turn::";
11659
+ var init_room_query_registry = () => {};
11645
11660
 
11646
11661
  // ../../packages/core/src/query/types.ts
11647
11662
  var AbortReason;
@@ -11867,7 +11882,9 @@ var roomQueryRegistry, interSessionQueue;
11867
11882
  var init_active_rooms = __esm(async () => {
11868
11883
  init_logger();
11869
11884
  await init_runtime_leases();
11885
+ init_room_query_registry();
11870
11886
  init_types2();
11887
+ init_room_query_registry();
11871
11888
  roomQueryRegistry = createRoomQueryRegistry({
11872
11889
  instanceId: RUNTIME_INSTANCE_ID,
11873
11890
  internalAbortReason: "internal" /* Internal */,
@@ -19713,8 +19730,8 @@ function startAiTurn(params) {
19713
19730
  const askReplySources = params.askReplySources;
19714
19731
  const sessionRetried = params._sessionRetried === true;
19715
19732
  const emptyResponseRetried = params._emptyResponseRetried === true;
19716
- const queryId = params._queryId ?? randomUUID17();
19717
- const roomId = turnConcurrency === "isolated" ? isolatedTurnRoomId(topicId, queryId) : topicId;
19733
+ const queryId = asTurnId(params._queryId ?? randomUUID17());
19734
+ const roomId = turnConcurrency === "isolated" ? isolatedTurnRoomId(topicId, queryId) : asTurnSlotKey(topicId);
19718
19735
  const currentRuntimeEpoch = getRuntimeTopicEpoch(topic.id);
19719
19736
  const runtimeEpoch = params._runtimeEpoch ?? currentRuntimeEpoch;
19720
19737
  if (params._runtimeEpoch !== undefined && params._runtimeEpoch !== currentRuntimeEpoch) {
@@ -24134,6 +24151,7 @@ __export(exports_src, {
24134
24151
  startAiTurn: () => startAiTurn,
24135
24152
  stampUnscopedOtiumTopics: () => stampUnscopedOtiumTopics,
24136
24153
  setTopicSurfaces: () => setTopicSurfaces,
24154
+ setTopicSurfaceScope: () => setTopicSurfaceScope,
24137
24155
  setTopicSessionId: () => setTopicSessionId,
24138
24156
  setSurfaceScopeRequired: () => setSurfaceScopeRequired,
24139
24157
  setRuntimeMcpPort: () => setRuntimeMcpPort,
@@ -29407,7 +29425,6 @@ __export(exports_storage_public, {
29407
29425
  waitForRequiredRuntimeProcessLease: () => waitForRequiredRuntimeProcessLease,
29408
29426
  validateDecisionGraph: () => validateDecisionGraph,
29409
29427
  upsertTopic: () => upsertTopic,
29410
- updateTopicThreadId: () => updateTopicThreadId,
29411
29428
  updateTasks: () => updateTasks,
29412
29429
  updateDecisions: () => updateDecisions,
29413
29430
  updateApiMessageUsage: () => updateApiMessageUsage,
@@ -29429,21 +29446,12 @@ __export(exports_storage_public, {
29429
29446
  softDeleteApiMessage: () => softDeleteApiMessage,
29430
29447
  settleTopicArchiveJob: () => settleTopicArchiveJob,
29431
29448
  setTopicSurfaces: () => setTopicSurfaces,
29449
+ setTopicSurfaceScope: () => setTopicSurfaceScope,
29432
29450
  setTopicSessionId: () => setTopicSessionId,
29433
- setTopicMcpExtra: () => setTopicMcpExtra,
29434
- setTopicMcpEnabled: () => setTopicMcpEnabled,
29435
- setTopicForkOrigin: () => setTopicForkOrigin,
29436
- setTopicDescription: () => setTopicDescription,
29437
29451
  setTopicBrief: () => setTopicBrief,
29438
29452
  setTopicArchiveState: () => setTopicArchiveState,
29439
- setTopicAgentAndSession: () => setTopicAgentAndSession,
29440
- setTopicAgentAndClearSession: () => setTopicAgentAndClearSession,
29441
- setTopicAgent: () => setTopicAgent,
29442
29453
  setSurfaceScopeRequired: () => setSurfaceScopeRequired,
29443
- setSessionForTopic: () => setSessionForTopic,
29444
- setLastShownConfig: () => setLastShownConfig,
29445
29454
  setGlobalAiName: () => setGlobalAiName,
29446
- setDmSessionId: () => setDmSessionId,
29447
29455
  setDefaultSurfaceScope: () => setDefaultSurfaceScope,
29448
29456
  setApiTopicConfig: () => setApiTopicConfig,
29449
29457
  setApiTopicAgent: () => setApiTopicAgent,
@@ -29459,8 +29467,6 @@ __export(exports_storage_public, {
29459
29467
  reparentTopicChildren: () => reparentTopicChildren,
29460
29468
  renderTaskList: () => renderTaskList,
29461
29469
  renderDecisionList: () => renderDecisionList,
29462
- renameTopic: () => renameTopic,
29463
- removeTopic: () => removeTopic,
29464
29470
  removeSentFilesForTopic: () => removeSentFilesForTopic,
29465
29471
  removeParticipantFromDB: () => removeParticipantFromDB,
29466
29472
  removeDeadRuntimeProcessLeases: () => removeDeadRuntimeProcessLeases,
@@ -29503,49 +29509,34 @@ __export(exports_storage_public, {
29503
29509
  hasActiveConversation: () => hasActiveConversation,
29504
29510
  grantSubagentTellTarget: () => grantSubagentTellTarget,
29505
29511
  getWikiDir: () => getWikiDir,
29506
- getUserConfig: () => getUserConfig,
29507
29512
  getTopicStats: () => getTopicStats,
29508
29513
  getTopicSessionId: () => getTopicSessionId,
29509
- getTopicNames: () => getTopicNames,
29510
29514
  getTopicMemoryOrigin: () => getTopicMemoryOrigin,
29511
29515
  getTopicMemoryFilePaths: () => getTopicMemoryFilePaths,
29512
- getTopicMcpConfig: () => getTopicMcpConfig,
29513
- getTopicDescription: () => getTopicDescription,
29514
29516
  getTopicByNameForUser: () => getTopicByNameForUser,
29515
29517
  getTopicByNameAndKind: () => getTopicByNameAndKind,
29516
29518
  getTopicByName: () => getTopicByName,
29517
29519
  getTopicBrief: () => getTopicBrief,
29518
29520
  getTopicArchiveState: () => getTopicArchiveState,
29519
- getTopicAgentSwitchState: () => getTopicAgentSwitchState,
29520
- getTopicAgent: () => getTopicAgent,
29521
29521
  getTopic: () => getTopic,
29522
29522
  getThreadSummaries: () => getThreadSummaries,
29523
29523
  getTaskFilePath: () => getTaskFilePath,
29524
29524
  getStats: () => getStats,
29525
29525
  getSharedWikiDir: () => getSharedWikiDir,
29526
- getSessionForTopic: () => getSessionForTopic,
29527
29526
  getRuntimeProcessLease: () => getRuntimeProcessLease,
29528
29527
  getMessagesForTopicAfterRowid: () => getMessagesForTopicAfterRowid,
29529
29528
  getManagerTopicForUser: () => getManagerTopicForUser,
29530
- getLastShownConfig: () => getLastShownConfig,
29531
29529
  getLastMessagePreviews: () => getLastMessagePreviews,
29532
29530
  getGlobalAiName: () => getGlobalAiName,
29533
- getForumTopicByName: () => getTopicByName2,
29534
- getDmSessionId: () => getDmSessionId,
29535
29531
  getDecisionGraphSvgPath: () => getDecisionGraphSvgPath,
29536
29532
  getDecisionFilePath: () => getDecisionFilePath,
29537
29533
  getConversationPath: () => getConversationPath,
29538
- getCommunicateThreadId: () => getCommunicateThreadId,
29539
29534
  getApiTopicConfig: () => getApiTopicConfig,
29540
29535
  getApiMessage: () => getApiMessage,
29541
- getAllUserIds: () => getAllUserIds,
29542
- getAllTopics: () => getAllTopics,
29543
29536
  getAllMessagesForTopic: () => getAllMessagesForTopic,
29544
29537
  getActiveConversationPath: () => getActiveConversationPath,
29545
29538
  forum: () => exports_forum,
29546
29539
  formatTopicArchiveTranscriptRecord: () => formatTopicArchiveTranscriptRecord,
29547
- flushSessionCache: () => flushSessionCache,
29548
- findUserByThread: () => findUserByThread,
29549
29540
  findTopicTitleConflict: () => findTopicTitleConflict,
29550
29541
  findRecentUserMessage: () => findRecentUserMessage,
29551
29542
  findLastSessionIdForAgent: () => findLastSessionIdForAgent,
@@ -29573,10 +29564,8 @@ __export(exports_storage_public, {
29573
29564
  configureStorageHost: () => configureStorageHost2,
29574
29565
  cloneConversationLog: () => cloneConversationLog,
29575
29566
  clearTopicSessionId: () => clearTopicSessionId,
29576
- clearSessionForTopic: () => clearSessionForTopic,
29577
29567
  clearPendingAskSources: () => clearPendingAskSources,
29578
29568
  clearPendingAsk: () => clearPendingAsk,
29579
- clearDmSessionId: () => clearDmSessionId,
29580
29569
  claimTopicArchiveJob: () => claimTopicArchiveJob,
29581
29570
  claimAskUserGateAndSelect: () => claimAskUserGateAndSelect,
29582
29571
  captureMessageSnapshotForTopic: () => captureMessageSnapshotForTopic,
@@ -29596,7 +29585,6 @@ __export(exports_storage_public, {
29596
29585
  apiMessages: () => exports_api_messages,
29597
29586
  anonymizeApiMessages: () => anonymizeApiMessages,
29598
29587
  aiMentionFromMode: () => aiMentionFromMode,
29599
- addTopic: () => addTopic,
29600
29588
  addParticipantToDB: () => addParticipantToDB,
29601
29589
  activityLog: () => exports_activity_log,
29602
29590
  acquireRuntimeProcessLease: () => acquireRuntimeProcessLease,
@@ -29628,7 +29616,6 @@ var init_storage_public = __esm(async () => {
29628
29616
  await init_decisions();
29629
29617
  init_decisions();
29630
29618
  await init_forum();
29631
- await init_forum();
29632
29619
  await init_runtime_process_leases();
29633
29620
  init_runtime_process_leases();
29634
29621
  await init_session_asks();
@@ -29643,7 +29630,6 @@ var init_storage_public = __esm(async () => {
29643
29630
  await init_topic_archive_state();
29644
29631
  init_topic_archive_state();
29645
29632
  await init_topic_settings();
29646
- init_topic_settings();
29647
29633
  await init_wiki();
29648
29634
  init_wiki();
29649
29635
  init_wiki_summary_names();
@@ -42719,12 +42705,15 @@ var init_telegram_api = __esm(() => {
42719
42705
  });
42720
42706
 
42721
42707
  // ../../adapters/telegram/src/commands.ts
42722
- function resolveTelegramTopicArg(argument, userId) {
42723
- const byName = getTopicByNameForUser(argument, userId, { surface: "telegram" });
42708
+ function resolveTelegramTopicArg(argument, userId, surfaceScope) {
42709
+ const byName = getTopicByNameForUser(argument, userId, {
42710
+ surface: "telegram",
42711
+ surfaceScope
42712
+ });
42724
42713
  if (byName)
42725
42714
  return byName;
42726
42715
  const byId = getTopic(argument);
42727
- return byId?.surface === "telegram" ? byId : null;
42716
+ return byId?.surface === "telegram" && (byId.surfaceScope ?? null) === surfaceScope ? byId : null;
42728
42717
  }
42729
42718
  function createTelegramCommandRouter(context) {
42730
42719
  const { userId, reply, currentTopicId, bindMapping, loadTopic, unloadTopic, abortTurn } = context;
@@ -42762,7 +42751,7 @@ function createTelegramCommandRouter(context) {
42762
42751
  return;
42763
42752
  }
42764
42753
  try {
42765
- const topic = context.getOrCreateTopic(`${context.titleFor(chatId, threadId)}-${argument}`, argument);
42754
+ const topic = context.getOrCreateTopic(chatId, `${context.titleFor(chatId, threadId)}-${argument}`, argument);
42766
42755
  bindMapping(chatId, threadId, topic.id);
42767
42756
  reply(chatId, threadId, `agent set to ${argument} \u2014 topic "${topic.title}"`);
42768
42757
  } catch (err2) {
@@ -42771,7 +42760,10 @@ function createTelegramCommandRouter(context) {
42771
42760
  return;
42772
42761
  }
42773
42762
  case "/topics": {
42774
- const topics = listTopics({ surface: "telegram" }).filter((topic) => isTopicVisible(topic) && topic.participants.some((person) => person.userId === userId));
42763
+ const topics = listTopics({
42764
+ surface: "telegram",
42765
+ surfaceScope: context.surfaceScopeFor(chatId)
42766
+ }).filter((topic) => isTopicVisible(topic) && topic.participants.some((person) => person.userId === userId));
42775
42767
  reply(chatId, threadId, topics.length ? topics.map((topic) => `- ${topic.title}${topic.agent ? ` (${topic.agent})` : ""}`).join(`
42776
42768
  `) : "no topics");
42777
42769
  return;
@@ -42802,9 +42794,10 @@ function createTelegramCommandRouter(context) {
42802
42794
  userId,
42803
42795
  kind: "agent",
42804
42796
  surface: "telegram",
42797
+ surfaceScope: context.surfaceScopeFor(chatId),
42805
42798
  ...context.defaultAgent ? { agent: context.defaultAgent } : {}
42806
42799
  };
42807
- const fromForumGeneral = context.forum?.enabled === true && chatId === context.forum.chatId && threadId === undefined;
42800
+ const fromForumGeneral = context.isForumGeneral(chatId, threadId);
42808
42801
  const topic = fromForumGeneral ? topicService.create(createOptions) : context.registerTopic(createOptions);
42809
42802
  if (fromForumGeneral) {
42810
42803
  reply(chatId, threadId, `creating new topic "${topic.title}"`);
@@ -42822,7 +42815,7 @@ function createTelegramCommandRouter(context) {
42822
42815
  reply(chatId, threadId, "usage: /load <topic>");
42823
42816
  return;
42824
42817
  }
42825
- const candidate = resolveTelegramTopicArg(argument, userId);
42818
+ const candidate = resolveTelegramTopicArg(argument, userId, context.surfaceScopeFor(chatId));
42826
42819
  const topic = candidate && isTopicVisible(candidate) ? candidate : null;
42827
42820
  if (!topic || !loadTopic(chatId, topic.id, threadId)) {
42828
42821
  reply(chatId, threadId, `no visible topic matching "${argument}"`);
@@ -42867,7 +42860,7 @@ function createTelegramCommandRouter(context) {
42867
42860
  case "/del!": {
42868
42861
  const force = command === "/del!";
42869
42862
  const topicId = currentTopicId(chatId, threadId);
42870
- const topic = argument ? resolveTelegramTopicArg(argument, userId) : topicId ? getTopic(topicId) : null;
42863
+ const topic = argument ? resolveTelegramTopicArg(argument, userId, context.surfaceScopeFor(chatId)) : topicId ? getTopic(topicId) : null;
42871
42864
  if (!topic) {
42872
42865
  reply(chatId, threadId, argument ? `no topic named "${argument}"` : "this chat has no topic \u2014 usage: /del [name]");
42873
42866
  return;
@@ -42952,6 +42945,12 @@ function createTables(db4) {
42952
42945
  key TEXT PRIMARY KEY,
42953
42946
  value TEXT NOT NULL
42954
42947
  )`);
42948
+ db4.run(`CREATE TABLE IF NOT EXISTS telegram_groups (
42949
+ chat_id INTEGER PRIMARY KEY,
42950
+ title TEXT,
42951
+ owner_telegram_user_id INTEGER,
42952
+ created_at TEXT NOT NULL
42953
+ )`);
42955
42954
  db4.run(`CREATE TABLE IF NOT EXISTS runtime_inbox (
42956
42955
  seq INTEGER PRIMARY KEY,
42957
42956
  runtime_message_id TEXT,
@@ -42981,6 +42980,14 @@ function openMappingStore(path) {
42981
42980
  const db4 = new Database2(dbPath);
42982
42981
  migrateLegacySchema(db4);
42983
42982
  createTables(db4);
42983
+ const legacyForum = db4.query("SELECT value FROM settings WHERE key = 'forum_chat_id'").get();
42984
+ if (legacyForum) {
42985
+ const chatId = Number.parseInt(legacyForum.value, 10);
42986
+ if (Number.isSafeInteger(chatId)) {
42987
+ db4.run(`INSERT OR IGNORE INTO telegram_groups (chat_id, created_at)
42988
+ VALUES (?, ?)`, [chatId, new Date().toISOString()]);
42989
+ }
42990
+ }
42984
42991
  migrateOutboxSchema(db4);
42985
42992
  migrateRuntimeInboxSchema(db4);
42986
42993
  return {
@@ -43033,6 +43040,32 @@ function openMappingStore(path) {
43033
43040
  clearForumChatId() {
43034
43041
  db4.run("DELETE FROM settings WHERE key = 'forum_chat_id'");
43035
43042
  },
43043
+ loadGroups() {
43044
+ const rows = db4.query("SELECT chat_id, title, owner_telegram_user_id FROM telegram_groups ORDER BY created_at").all();
43045
+ return rows.map((row) => ({
43046
+ chatId: row.chat_id,
43047
+ ...row.title ? { title: row.title } : {},
43048
+ ...row.owner_telegram_user_id !== null ? { ownerTelegramUserId: row.owner_telegram_user_id } : {}
43049
+ }));
43050
+ },
43051
+ saveGroup(group) {
43052
+ db4.run(`INSERT INTO telegram_groups (chat_id, title, owner_telegram_user_id, created_at)
43053
+ VALUES (?, ?, ?, ?)
43054
+ ON CONFLICT(chat_id) DO UPDATE SET
43055
+ title = COALESCE(excluded.title, telegram_groups.title),
43056
+ owner_telegram_user_id = COALESCE(
43057
+ excluded.owner_telegram_user_id,
43058
+ telegram_groups.owner_telegram_user_id
43059
+ )`, [
43060
+ group.chatId,
43061
+ group.title ?? null,
43062
+ group.ownerTelegramUserId ?? null,
43063
+ new Date().toISOString()
43064
+ ]);
43065
+ },
43066
+ deleteGroup(chatId) {
43067
+ db4.run("DELETE FROM telegram_groups WHERE chat_id = ?", [chatId]);
43068
+ },
43036
43069
  isFlagSet(key) {
43037
43070
  return db4.query("SELECT 1 FROM settings WHERE key = ?").get(key) !== null;
43038
43071
  },
@@ -43372,6 +43405,15 @@ function renderOutbound(text2, maxLen = 4096) {
43372
43405
  // ../../adapters/telegram/src/adapter.ts
43373
43406
  import { existsSync as existsSync37 } from "fs";
43374
43407
  import { basename as basename13, extname as extname6 } from "path";
43408
+ function telegramGroupScope(chatId) {
43409
+ return `tg:${chatId}`;
43410
+ }
43411
+ function telegramGroupIdFromScope(scope) {
43412
+ if (!scope?.startsWith("tg:"))
43413
+ return null;
43414
+ const chatId = Number(scope.slice(3));
43415
+ return Number.isSafeInteger(chatId) ? chatId : null;
43416
+ }
43375
43417
  function startTelegramAdapter(opts) {
43376
43418
  const { client, forumChatId } = opts;
43377
43419
  const userId = opts.userId ?? "local";
@@ -43390,16 +43432,42 @@ function startTelegramAdapter(opts) {
43390
43432
  const dispatchTurn = opts.startTurn ?? startAiTurn;
43391
43433
  const store = openMappingStore(opts.mappingDbPath);
43392
43434
  const restoredForumChatId = store.loadForumChatId();
43393
- const initialForumChatId = forumChatId ?? restoredForumChatId;
43394
- let forumChat = initialForumChatId ?? 0;
43395
- let forumMode = initialForumChatId !== undefined && typeof client.createForumTopic === "function";
43396
- let forumManageTopicsAvailable = forumMode && forumChatId !== undefined;
43397
- if (forumChatId !== undefined)
43435
+ if (forumChatId !== undefined) {
43436
+ store.saveGroup({ chatId: forumChatId });
43398
43437
  store.saveForumChatId(forumChatId);
43399
- if (initialForumChatId !== undefined && !forumMode) {
43400
- logger.warn({ forumChatId: initialForumChatId }, "telegram adapter: forumChatId set but client lacks createForumTopic \u2014 forum mode disabled");
43438
+ }
43439
+ const forumGroups = new Map;
43440
+ for (const group of store.loadGroups()) {
43441
+ forumGroups.set(group.chatId, {
43442
+ chatId: group.chatId,
43443
+ manageTopicsAvailable: group.chatId === forumChatId,
43444
+ configured: group.chatId === forumChatId,
43445
+ permissionRevision: 0
43446
+ });
43447
+ }
43448
+ if (restoredForumChatId !== undefined && !forumGroups.has(restoredForumChatId)) {
43449
+ forumGroups.set(restoredForumChatId, {
43450
+ chatId: restoredForumChatId,
43451
+ manageTopicsAvailable: false,
43452
+ configured: false,
43453
+ permissionRevision: 0
43454
+ });
43455
+ }
43456
+ if (forumGroups.size > 0 && typeof client.createForumTopic !== "function") {
43457
+ logger.warn({ forumChatIds: [...forumGroups.keys()] }, "telegram adapter: forum groups configured but client lacks createForumTopic");
43401
43458
  }
43402
43459
  const personalGeneral = ensurePersonalGeneral(userId, "telegram");
43460
+ const groupGenerals = new Map;
43461
+ const generalForGroup = (chatId) => {
43462
+ const existing = groupGenerals.get(chatId);
43463
+ if (existing)
43464
+ return existing;
43465
+ const general = ensurePersonalGeneral(userId, "telegram", {
43466
+ surfaceScope: telegramGroupScope(chatId)
43467
+ });
43468
+ groupGenerals.set(chatId, general);
43469
+ return general;
43470
+ };
43403
43471
  const byKey = new Map;
43404
43472
  const byTopic = new Map;
43405
43473
  const targetByQueryId = new Map;
@@ -43573,6 +43641,22 @@ function startTelegramAdapter(opts) {
43573
43641
  }
43574
43642
  function bindMapping(chatId, threadId, topicId, { persist = true } = {}) {
43575
43643
  const key = mappingKey(chatId, threadId);
43644
+ if (forumGroups.has(chatId)) {
43645
+ const topic = getTopic(topicId);
43646
+ const expectedScope = telegramGroupScope(chatId);
43647
+ if (!topic || topic.surface !== "telegram" || (topic.surfaceScope ?? null) !== expectedScope) {
43648
+ throw new Error(`refusing to bind Telegram group ${chatId} to topic ${topicId} outside ${expectedScope}`);
43649
+ }
43650
+ for (const existing of [...byTopic.get(topicId) ?? []]) {
43651
+ if (existing.chatId !== chatId || mappingKey(existing.chatId, existing.threadId) === key) {
43652
+ continue;
43653
+ }
43654
+ detachFromTopic(existing);
43655
+ byKey.delete(mappingKey(existing.chatId, existing.threadId));
43656
+ if (persist)
43657
+ store.deleteByChat(existing.chatId, existing.threadId);
43658
+ }
43659
+ }
43576
43660
  const prev = byKey.get(key);
43577
43661
  if (prev) {
43578
43662
  if (prev.topicId === topicId)
@@ -43639,7 +43723,8 @@ function startTelegramAdapter(opts) {
43639
43723
  }
43640
43724
  function loadExistingTopic(chatId, topicId, threadId) {
43641
43725
  const topic = getTopic(topicId);
43642
- if (!topic?.participants.some((participant) => participant.userId === userId)) {
43726
+ const expectedScope = forumGroups.has(chatId) ? telegramGroupScope(chatId) : null;
43727
+ if (!topic?.participants.some((participant) => participant.userId === userId) || topic.surface !== "telegram" || (topic.surfaceScope ?? null) !== expectedScope) {
43643
43728
  return false;
43644
43729
  }
43645
43730
  bindMapping(chatId, threadId, topic.id);
@@ -43647,7 +43732,10 @@ function startTelegramAdapter(opts) {
43647
43732
  }
43648
43733
  if (!store.isFlagSet(SURFACE_BACKFILL_FLAG)) {
43649
43734
  const mappedIds = [
43650
- ...new Set(store.load().map((mapping) => mapping.topicId).filter((topicId) => getTopic(topicId)?.kind !== "manager"))
43735
+ ...new Set(store.load().flatMap((mapping) => {
43736
+ const topic = getTopic(mapping.topicId);
43737
+ return topic && topic.kind !== "manager" && topic.participants.some((participant) => participant.userId === userId) ? [topic.id] : [];
43738
+ }))
43651
43739
  ];
43652
43740
  const moved = setTopicSurfaces(mappedIds, "telegram");
43653
43741
  store.setFlag(SURFACE_BACKFILL_FLAG);
@@ -43655,12 +43743,70 @@ function startTelegramAdapter(opts) {
43655
43743
  logger.info({ moved, mapped: mappedIds.length }, "telegram adapter: moved mapped topics onto the telegram surface");
43656
43744
  }
43657
43745
  }
43746
+ if (!store.isFlagSet(GROUP_SCOPE_BACKFILL_FLAG)) {
43747
+ const groupIds = new Set(forumGroups.keys());
43748
+ const groupsByTopic = new Map;
43749
+ for (const mapping of store.load()) {
43750
+ if (!groupIds.has(mapping.chatId))
43751
+ continue;
43752
+ const groups = groupsByTopic.get(mapping.topicId) ?? new Set;
43753
+ groups.add(mapping.chatId);
43754
+ groupsByTopic.set(mapping.topicId, groups);
43755
+ }
43756
+ let filed = 0;
43757
+ for (const [topicId, groups] of groupsByTopic) {
43758
+ if (groups.size !== 1) {
43759
+ logger.warn({ topicId, groupIds: [...groups] }, "telegram adapter: legacy topic spans multiple groups; scope migration skipped");
43760
+ continue;
43761
+ }
43762
+ const topic = getTopic(topicId);
43763
+ if (!topic || topic.kind === "manager" || topic.surface !== "telegram" || !topic.participants.some((participant) => participant.userId === userId)) {
43764
+ continue;
43765
+ }
43766
+ const [chatId] = groups;
43767
+ if (chatId !== undefined) {
43768
+ const targetScope = telegramGroupScope(chatId);
43769
+ const currentScope = topic.surfaceScope ?? null;
43770
+ if (currentScope !== null && currentScope !== targetScope) {
43771
+ logger.warn({ topicId, currentScope, targetScope }, "telegram adapter: legacy mapping conflicts with an existing topic namespace");
43772
+ continue;
43773
+ }
43774
+ const titleConflict = findTopicTitleConflict(topic.title, topic.kind ?? "agent", {
43775
+ excludeTopicId: topic.id,
43776
+ surface: "telegram",
43777
+ surfaceScope: targetScope
43778
+ });
43779
+ if (titleConflict) {
43780
+ logger.warn({ topicId, conflictTopicId: titleConflict.id, targetScope }, "telegram adapter: legacy topic title conflicts in target namespace");
43781
+ continue;
43782
+ }
43783
+ filed += Number(setTopicSurfaceScope(topicId, "telegram", targetScope));
43784
+ }
43785
+ }
43786
+ store.setFlag(GROUP_SCOPE_BACKFILL_FLAG);
43787
+ if (filed > 0) {
43788
+ logger.info({ filed }, "telegram adapter: filed legacy topics into group namespaces");
43789
+ }
43790
+ }
43658
43791
  for (const persisted of store.load()) {
43659
43792
  const topic = getTopic(persisted.topicId);
43660
- if (topic?.participants.some((participant) => participant.userId === userId)) {
43793
+ const expectedScope = forumGroups.has(persisted.chatId) ? telegramGroupScope(persisted.chatId) : null;
43794
+ if (topic?.participants.some((participant) => participant.userId === userId) && topic.surface === "telegram" && (topic.surfaceScope ?? null) === expectedScope) {
43661
43795
  bindMapping(persisted.chatId, persisted.threadId, persisted.topicId, { persist: false });
43662
43796
  continue;
43663
43797
  }
43798
+ if (topic) {
43799
+ store.deleteByChat(persisted.chatId, persisted.threadId);
43800
+ logger.warn({
43801
+ chatId: persisted.chatId,
43802
+ threadId: persisted.threadId,
43803
+ topicId: persisted.topicId,
43804
+ topicSurface: topic.surface,
43805
+ topicSurfaceScope: topic.surfaceScope ?? null,
43806
+ expectedScope
43807
+ }, "telegram adapter: quarantined mapping outside its topic namespace");
43808
+ continue;
43809
+ }
43664
43810
  cleanupPersistedMapping(persisted, "telegram adapter: stale forum thread cleanup failed");
43665
43811
  }
43666
43812
  function reconcileStaleMappings() {
@@ -43680,8 +43826,15 @@ function startTelegramAdapter(opts) {
43680
43826
  suppressMaterialize--;
43681
43827
  }
43682
43828
  }
43683
- function getOrCreateTopic(title, agent) {
43684
- return getTopicByNameForUser(title, userId, { surface: "telegram" }) ?? registerTopicLocal({ title, userId, kind: "agent", ...agent ? { agent } : {} });
43829
+ function getOrCreateTopic(chatId, title, agent) {
43830
+ const surfaceScope = forumGroups.has(chatId) ? telegramGroupScope(chatId) : null;
43831
+ return getTopicByNameForUser(title, userId, { surface: "telegram", surfaceScope }) ?? registerTopicLocal({
43832
+ title,
43833
+ userId,
43834
+ kind: "agent",
43835
+ surfaceScope,
43836
+ ...agent ? { agent } : {}
43837
+ });
43685
43838
  }
43686
43839
  function resolveMapping(chatId, threadId) {
43687
43840
  const cached = byKey.get(mappingKey(chatId, threadId));
@@ -43691,7 +43844,7 @@ function startTelegramAdapter(opts) {
43691
43844
  return topic2;
43692
43845
  unbindTopic(cached.topicId);
43693
43846
  }
43694
- const topic = getOrCreateTopic(titleFor(chatId, threadId), opts.defaultAgent);
43847
+ const topic = getOrCreateTopic(chatId, titleFor(chatId, threadId), opts.defaultAgent);
43695
43848
  bindMapping(chatId, threadId, topic.id);
43696
43849
  return topic;
43697
43850
  }
@@ -44009,12 +44162,16 @@ function startTelegramAdapter(opts) {
44009
44162
  const pendingByTopic = new Map;
44010
44163
  const permissionBlockedTopics = new Map;
44011
44164
  const materializeTombstones = new Map;
44012
- if (forumMode) {
44165
+ if (forumGroups.size > 0) {
44013
44166
  for (const t of store.loadTombstones())
44014
44167
  materializeTombstones.set(t.topicId, t.title);
44015
44168
  }
44016
44169
  function deliverFallback(topicId, title, payload) {
44017
- return deliverToTargets(topicId, [{ chatId: forumChat, topicId }], {
44170
+ const topic = getTopic(topicId);
44171
+ const chatId = telegramGroupIdFromScope(topic?.surfaceScope);
44172
+ if (chatId === null)
44173
+ return Promise.resolve();
44174
+ return deliverToTargets(topicId, [{ chatId, topicId }], {
44018
44175
  text: payload.text ? `[${title}] ${payload.text}` : "",
44019
44176
  files: payload.files,
44020
44177
  runtimeMessageId: payload.runtimeMessageId,
@@ -44027,16 +44184,18 @@ function startTelegramAdapter(opts) {
44027
44184
  }
44028
44185
  }
44029
44186
  function materializeTopic(topic) {
44030
- if (!forumManageTopicsAvailable || suppressMaterialize > 0 || byTopic.has(topic.id) || pendingByTopic.has(topic.id) || permissionBlockedTopics.has(topic.id) || materializeTombstones.has(topic.id)) {
44187
+ if (topic.surface !== "telegram" || !topic.participants?.some((participant) => participant.userId === userId)) {
44031
44188
  return false;
44032
44189
  }
44033
- if (!isTopicVisible(topic))
44190
+ const materializationChat = telegramGroupIdFromScope(topic.surfaceScope);
44191
+ if (materializationChat === null)
44034
44192
  return false;
44035
- if (!topic.participants?.some((p) => p.userId === userId))
44193
+ const group = forumGroups.get(materializationChat);
44194
+ if (!group?.manageTopicsAvailable || suppressMaterialize > 0 || byTopic.has(topic.id) || pendingByTopic.has(topic.id) || permissionBlockedTopics.has(topic.id) || materializeTombstones.has(topic.id)) {
44036
44195
  return false;
44037
- if (topic.surface !== "telegram")
44196
+ }
44197
+ if (!isTopicVisible(topic))
44038
44198
  return false;
44039
- const materializationChat = forumChat;
44040
44199
  const pending2 = { buffer: [], cancelled: false };
44041
44200
  pendingByTopic.set(topic.id, pending2);
44042
44201
  (async () => {
@@ -44050,12 +44209,12 @@ function startTelegramAdapter(opts) {
44050
44209
  return;
44051
44210
  const errorInfo = telegramErrorInfo(err2);
44052
44211
  if (isManageTopicsPermissionError(errorInfo)) {
44053
- const permissionWasAvailable = forumManageTopicsAvailable;
44054
- forumManageTopicsAvailable = false;
44212
+ const permissionWasAvailable = group.manageTopicsAvailable;
44213
+ group.manageTopicsAvailable = false;
44055
44214
  permissionBlockedTopics.set(topic.id, topic.title);
44056
44215
  logger.warn({ err: err2, topicId: topic.id, title: topic.title }, "telegram adapter: Manage Topics unavailable \u2014 waiting for permission recovery");
44057
44216
  if (permissionWasAvailable) {
44058
- reply(forumChat, undefined, 'Forum topic creation is paused. Enable the bot administrator permission "Manage Topics"; pending topics will be retried automatically.');
44217
+ reply(materializationChat, undefined, 'Forum topic creation is paused. Enable the bot administrator permission "Manage Topics"; pending topics will be retried automatically.');
44059
44218
  }
44060
44219
  flushBuffered(topic.id, pending2.buffer, (payload) => deliverFallback(topic.id, topic.title, payload));
44061
44220
  return;
@@ -44144,8 +44303,10 @@ function startTelegramAdapter(opts) {
44144
44303
  const topic = getTopic(topicId);
44145
44304
  if (!topic)
44146
44305
  return false;
44147
- if (forumMode) {
44148
- if (!forumManageTopicsAvailable || permissionBlockedTopics.has(topicId)) {
44306
+ const groupId = telegramGroupIdFromScope(topic.surfaceScope);
44307
+ const group = groupId === null ? undefined : forumGroups.get(groupId);
44308
+ if (group) {
44309
+ if (!group.manageTopicsAvailable || permissionBlockedTopics.has(topicId)) {
44149
44310
  enqueueSend(topicId, () => deliverFallback(topicId, topic.title, payload)).then(payload.onSettled);
44150
44311
  return true;
44151
44312
  }
@@ -44291,32 +44452,52 @@ ${suffix}`;
44291
44452
  return;
44292
44453
  await client.sendMessage(chatId, onboardingGuide(bot?.username), threadOpts(threadId)).catch((err2) => logger.warn({ err: err2, chatId }, "telegram adapter: onboarding guide failed"));
44293
44454
  }
44294
- function materializeVisibleTopics() {
44295
- if (!forumMode || !forumManageTopicsAvailable)
44455
+ function materializeVisibleTopics(chatId) {
44456
+ const group = forumGroups.get(chatId);
44457
+ if (!group?.manageTopicsAvailable)
44296
44458
  return;
44297
- for (const topic of listTopics({ surface: "telegram" })) {
44459
+ const topics = listTopics({
44460
+ surface: "telegram",
44461
+ surfaceScope: telegramGroupScope(chatId)
44462
+ });
44463
+ for (const topic of topics) {
44464
+ if (topic.kind === "manager")
44465
+ continue;
44298
44466
  if (isTopicVisible(topic) && topic.participants.some((participant) => participant.userId === userId)) {
44299
44467
  materializeTopic(topic);
44300
44468
  }
44301
44469
  }
44302
44470
  }
44303
- function restoreForumTopicCreation({ retryPermanent = false } = {}) {
44304
- forumManageTopicsAvailable = true;
44305
- permissionBlockedTopics.clear();
44471
+ function restoreForumTopicCreation(chatId, { retryPermanent = false } = {}) {
44472
+ const group = forumGroups.get(chatId);
44473
+ if (!group)
44474
+ return;
44475
+ group.manageTopicsAvailable = true;
44476
+ for (const topicId of [...permissionBlockedTopics.keys()]) {
44477
+ if (telegramGroupIdFromScope(getTopic(topicId)?.surfaceScope) === chatId) {
44478
+ permissionBlockedTopics.delete(topicId);
44479
+ }
44480
+ }
44306
44481
  if (retryPermanent) {
44307
- materializeTombstones.clear();
44308
- store.clearTombstones();
44482
+ for (const topicId of [...materializeTombstones.keys()]) {
44483
+ if (telegramGroupIdFromScope(getTopic(topicId)?.surfaceScope) === chatId) {
44484
+ materializeTombstones.delete(topicId);
44485
+ store.deleteTombstone(topicId);
44486
+ }
44487
+ }
44309
44488
  }
44310
44489
  reconcileStaleMappings();
44311
- materializeVisibleTopics();
44490
+ materializeVisibleTopics(chatId);
44312
44491
  }
44313
44492
  async function notifyOwnerDms(text2) {
44314
44493
  await Promise.allSettled([...ownerDmChatIds].map((chatId) => client.sendMessage(chatId, text2)));
44315
44494
  }
44316
44495
  async function disconnectForum(chatId) {
44317
- if (!forumMode || forumChat !== chatId)
44496
+ if (!forumGroups.has(chatId))
44318
44497
  return false;
44319
44498
  for (const [topicId, pending2] of pendingByTopic) {
44499
+ if (telegramGroupIdFromScope(getTopic(topicId)?.surfaceScope) !== chatId)
44500
+ continue;
44320
44501
  pending2.cancelled = true;
44321
44502
  if (pendingByTopic.get(topicId) === pending2)
44322
44503
  pendingByTopic.delete(topicId);
@@ -44329,14 +44510,23 @@ ${suffix}`;
44329
44510
  if (target.chatId === chatId)
44330
44511
  targetByQueryId.delete(queryId);
44331
44512
  }
44332
- permissionBlockedTopics.clear();
44333
- materializeTombstones.clear();
44334
- store.clearTombstones();
44513
+ for (const topicId of [...permissionBlockedTopics.keys()]) {
44514
+ if (telegramGroupIdFromScope(getTopic(topicId)?.surfaceScope) === chatId) {
44515
+ permissionBlockedTopics.delete(topicId);
44516
+ }
44517
+ }
44518
+ for (const topicId of [...materializeTombstones.keys()]) {
44519
+ if (telegramGroupIdFromScope(getTopic(topicId)?.surfaceScope) === chatId) {
44520
+ materializeTombstones.delete(topicId);
44521
+ store.deleteTombstone(topicId);
44522
+ }
44523
+ }
44335
44524
  store.outboxDeleteByChat(chatId);
44336
- store.clearForumChatId();
44337
- forumMode = false;
44338
- forumManageTopicsAvailable = false;
44339
- forumChat = 0;
44525
+ store.deleteGroup(chatId);
44526
+ if (store.loadForumChatId() === chatId)
44527
+ store.clearForumChatId();
44528
+ forumGroups.delete(chatId);
44529
+ groupGenerals.delete(chatId);
44340
44530
  await notifyOwnerDms("The Telegram forum was disconnected because the bot left or was removed. Your Negotium topics were preserved; promote the bot in a forum group to reconnect.");
44341
44531
  logger.info({ userId, forumChatId: chatId }, "telegram adapter: forum disconnected");
44342
44532
  return true;
@@ -44346,16 +44536,14 @@ ${suffix}`;
44346
44536
  reply(ownerTelegramId, undefined, "This Telegram client cannot create forum topics, so the group was not connected.");
44347
44537
  return false;
44348
44538
  }
44349
- if (forumMode && forumChat !== chat.id) {
44350
- reply(ownerTelegramId, undefined, `A different forum group (${forumChat}) is already connected to this Negotium node.`);
44351
- return false;
44352
- }
44353
44539
  const hasManageTopics = canManageTopics(botMember);
44354
- if (forumMode && forumChat === chat.id) {
44355
- const recovered = hasManageTopics && !forumManageTopicsAvailable;
44356
- const lostPermission = !hasManageTopics && forumManageTopicsAvailable;
44540
+ const existingGroup = forumGroups.get(chat.id);
44541
+ if (existingGroup) {
44542
+ existingGroup.permissionRevision += 1;
44543
+ const recovered = hasManageTopics && !existingGroup.manageTopicsAvailable;
44544
+ const lostPermission = !hasManageTopics && existingGroup.manageTopicsAvailable;
44357
44545
  if (hasManageTopics) {
44358
- restoreForumTopicCreation({ retryPermanent: recovered });
44546
+ restoreForumTopicCreation(chat.id, { retryPermanent: recovered });
44359
44547
  if (recovered) {
44360
44548
  await Promise.allSettled([
44361
44549
  client.sendMessage(chat.id, "Manage Topics permission confirmed. Pending Negotium topics are being created now."),
@@ -44363,23 +44551,29 @@ ${suffix}`;
44363
44551
  ]);
44364
44552
  }
44365
44553
  } else {
44366
- forumManageTopicsAvailable = false;
44554
+ existingGroup.manageTopicsAvailable = false;
44367
44555
  if (lostPermission) {
44368
44556
  await notifyOwnerDms(`Manage Topics permission was removed from \u201C${chat.title?.trim() || chat.id}\u201D. Existing topics are preserved; restore the permission to resume topic creation.`);
44369
44557
  }
44370
44558
  }
44371
44559
  return true;
44372
44560
  }
44373
- forumChat = chat.id;
44374
- forumMode = true;
44375
- forumManageTopicsAvailable = hasManageTopics;
44376
- permissionBlockedTopics.clear();
44377
- materializeTombstones.clear();
44378
- store.clearTombstones();
44379
- store.saveForumChatId(chat.id);
44380
- bindMapping(chat.id, undefined, personalGeneral.id);
44561
+ forumGroups.set(chat.id, {
44562
+ chatId: chat.id,
44563
+ manageTopicsAvailable: hasManageTopics,
44564
+ configured: chat.id === forumChatId,
44565
+ permissionRevision: 1
44566
+ });
44567
+ store.saveGroup({
44568
+ chatId: chat.id,
44569
+ title: chat.title?.trim(),
44570
+ ownerTelegramUserId: ownerTelegramId
44571
+ });
44572
+ if (store.loadForumChatId() === undefined)
44573
+ store.saveForumChatId(chat.id);
44574
+ bindMapping(chat.id, undefined, generalForGroup(chat.id).id);
44381
44575
  ownerDmChatIds.add(ownerTelegramId);
44382
- materializeVisibleTopics();
44576
+ materializeVisibleTopics(chat.id);
44383
44577
  const title = chat.title?.trim() || String(chat.id);
44384
44578
  const permissionWarning = hasManageTopics ? "" : `
44385
44579
 
@@ -44401,9 +44595,8 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44401
44595
  if (msg.chat.type !== "supergroup" || !msg.chat.is_forum || senderId === undefined || !isAllowed(senderId)) {
44402
44596
  return false;
44403
44597
  }
44404
- if (forumMode && msg.chat.id !== forumChat)
44405
- return false;
44406
- if (forumMode && forumManageTopicsAvailable)
44598
+ const connected = forumGroups.get(msg.chat.id);
44599
+ if (connected?.manageTopicsAvailable)
44407
44600
  return true;
44408
44601
  if (typeof client.getMe !== "function" || typeof client.getChatMember !== "function") {
44409
44602
  return false;
@@ -44419,38 +44612,49 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44419
44612
  if (!isChatAdmin(botMember) || !isChatAdmin(senderMember))
44420
44613
  return false;
44421
44614
  await linkForumAndAnnounce(senderId, msg.chat, botMember);
44422
- return forumMode && forumChat === msg.chat.id;
44615
+ return forumGroups.has(msg.chat.id);
44423
44616
  } catch (err2) {
44424
44617
  logger.debug({ err: err2, groupId: msg.chat.id, senderId }, "telegram adapter: lazy forum auto-connect check failed");
44425
44618
  return false;
44426
44619
  }
44427
44620
  }
44428
44621
  async function verifyInitialForumPermissions() {
44429
- if (!forumMode)
44430
- return;
44431
- if (forumChatId !== undefined)
44622
+ const initialGroups = [...forumGroups.values()];
44623
+ if (initialGroups.length === 0)
44432
44624
  return;
44433
44625
  if (typeof client.getMe !== "function" || typeof client.getChatMember !== "function") {
44434
- forumManageTopicsAvailable = true;
44626
+ for (const group of initialGroups) {
44627
+ if (forumGroups.get(group.chatId) === group)
44628
+ group.manageTopicsAvailable = true;
44629
+ }
44435
44630
  return;
44436
44631
  }
44437
- try {
44438
- const bot = await resolveBotIdentity();
44439
- if (!bot || stopped || !forumMode)
44440
- return;
44441
- const member = await client.getChatMember(forumChat, bot.id);
44442
- if (member.status === "left" || member.status === "kicked") {
44443
- await disconnectForum(forumChat);
44444
- return;
44445
- }
44446
- if (canManageTopics(member)) {
44447
- const restoredAutoConnection = forumChatId === undefined && restoredForumChatId !== undefined;
44448
- restoreForumTopicCreation({ retryPermanent: restoredAutoConnection });
44449
- } else {
44450
- forumManageTopicsAvailable = false;
44632
+ const bot = await resolveBotIdentity();
44633
+ if (!bot || stopped)
44634
+ return;
44635
+ for (const group of initialGroups) {
44636
+ if (forumGroups.get(group.chatId) !== group)
44637
+ continue;
44638
+ if (group.configured)
44639
+ continue;
44640
+ try {
44641
+ const permissionRevision = group.permissionRevision;
44642
+ const member = await client.getChatMember(group.chatId, bot.id);
44643
+ if (forumGroups.get(group.chatId) !== group || group.permissionRevision !== permissionRevision) {
44644
+ continue;
44645
+ }
44646
+ if (member.status === "left" || member.status === "kicked") {
44647
+ await disconnectForum(group.chatId);
44648
+ continue;
44649
+ }
44650
+ if (canManageTopics(member)) {
44651
+ restoreForumTopicCreation(group.chatId, { retryPermanent: !group.configured });
44652
+ } else {
44653
+ group.manageTopicsAvailable = false;
44654
+ }
44655
+ } catch (err2) {
44656
+ logger.warn({ err: err2, forumChatId: group.chatId }, "telegram adapter: initial forum permission check failed");
44451
44657
  }
44452
- } catch (err2) {
44453
- logger.warn({ err: err2, forumChatId: forumChat }, "telegram adapter: initial forum permission check failed");
44454
44658
  }
44455
44659
  }
44456
44660
  verifyInitialForumPermissions();
@@ -44502,7 +44706,8 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44502
44706
  const handleCommand = createTelegramCommandRouter({
44503
44707
  userId,
44504
44708
  defaultAgent: opts.defaultAgent,
44505
- forum: forumMode ? { enabled: true, chatId: forumChat } : undefined,
44709
+ surfaceScopeFor: (chatId) => forumGroups.has(chatId) ? telegramGroupScope(chatId) : null,
44710
+ isForumGeneral: (chatId, threadId) => forumGroups.has(chatId) && threadId === undefined,
44506
44711
  resolveBotUsername: async () => (await resolveBotIdentity())?.username,
44507
44712
  isVaultOwner,
44508
44713
  reply,
@@ -44528,8 +44733,10 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44528
44733
  const firstPrivateContact = privateDm && !byKey.has(mappingKey(chatId));
44529
44734
  if (privateDm)
44530
44735
  ownerDmChatIds.add(chatId);
44531
- if (privateDm || forumMode && chatId === forumChat && threadId === undefined) {
44736
+ if (privateDm) {
44532
44737
  bindMapping(chatId, threadId, personalGeneral.id);
44738
+ } else if (forumGroups.has(chatId) && threadId === undefined) {
44739
+ bindMapping(chatId, threadId, generalForGroup(chatId).id);
44533
44740
  }
44534
44741
  if (firstPrivateContact && text2 !== "/start") {
44535
44742
  sendOnboardingGuide(chatId);
@@ -44561,9 +44768,7 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44561
44768
  if (!isAllowed(msg.from?.id))
44562
44769
  return;
44563
44770
  if (msg.chat.type === "supergroup" && msg.chat.is_forum) {
44564
- if (forumMode && msg.chat.id !== forumChat)
44565
- return;
44566
- if (!forumMode || !forumManageTopicsAvailable) {
44771
+ if (!forumGroups.get(msg.chat.id)?.manageTopicsAvailable) {
44567
44772
  tryAutoConnectFromMessage(msg).then((connected) => {
44568
44773
  if (connected && !stopped)
44569
44774
  handleIncomingMessage(msg);
@@ -44577,19 +44782,21 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44577
44782
  if (stopped || update.chat.type !== "supergroup" || !update.chat.is_forum)
44578
44783
  return;
44579
44784
  const status = update.new_chat_member?.status;
44580
- if ((status === "left" || status === "kicked") && forumChat === update.chat.id) {
44785
+ if ((status === "left" || status === "kicked") && forumGroups.has(update.chat.id)) {
44581
44786
  disconnectForum(update.chat.id).catch((err2) => logger.warn({ err: err2, groupId: update.chat.id }, "telegram adapter: forum disconnect failed"));
44582
44787
  return;
44583
44788
  }
44584
- if (forumMode && forumChat === update.chat.id && status !== "administrator" && status !== "creator") {
44585
- const permissionWasAvailable = forumManageTopicsAvailable;
44586
- forumManageTopicsAvailable = false;
44789
+ if (forumGroups.has(update.chat.id) && status !== "administrator" && status !== "creator") {
44790
+ const group = forumGroups.get(update.chat.id);
44791
+ group.permissionRevision += 1;
44792
+ const permissionWasAvailable = group.manageTopicsAvailable;
44793
+ group.manageTopicsAvailable = false;
44587
44794
  if (permissionWasAvailable) {
44588
44795
  notifyOwnerDms('The bot no longer has forum administrator access. Existing topics are preserved; restore administrator + "Manage Topics" to resume topic creation.');
44589
44796
  }
44590
44797
  return;
44591
44798
  }
44592
- if (status !== "administrator" && status !== "creator" || update.from?.id === undefined || !isAllowed(update.from.id) && (!forumMode || forumChat !== update.chat.id)) {
44799
+ if (status !== "administrator" && status !== "creator" || update.from?.id === undefined || !isAllowed(update.from.id) && !forumGroups.has(update.chat.id)) {
44593
44800
  return;
44594
44801
  }
44595
44802
  linkForumAndAnnounce(update.from.id, update.chat, update.new_chat_member).catch((err2) => logger.warn({ err: err2, groupId: update.chat.id }, "telegram adapter: forum auto-connect failed"));
@@ -44704,7 +44911,7 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44704
44911
  const unsubscribe = runtimeBus().subscribe((event) => {
44705
44912
  if (stopped)
44706
44913
  return;
44707
- if (event.type === "topic-created" && forumMode) {
44914
+ if (event.type === "topic-created") {
44708
44915
  materializeTopic(event.payload);
44709
44916
  return;
44710
44917
  }
@@ -44755,9 +44962,9 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44755
44962
  pollRuntimeEvents();
44756
44963
  const runtimeEventPollTimer = setInterval(pollRuntimeEvents, 100);
44757
44964
  runtimeEventPollTimer.unref?.();
44758
- if (forumMode) {
44759
- bindMapping(forumChat, undefined, personalGeneral.id);
44760
- materializeVisibleTopics();
44965
+ for (const group of forumGroups.values()) {
44966
+ bindMapping(group.chatId, undefined, generalForGroup(group.chatId).id);
44967
+ materializeVisibleTopics(group.chatId);
44761
44968
  }
44762
44969
  return {
44763
44970
  name: "telegram",
@@ -44780,7 +44987,7 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
44780
44987
  }
44781
44988
  };
44782
44989
  }
44783
- var SURFACE_BACKFILL_FLAG = "surface_backfill_20260808", FORUM_TOPIC_NAME_MAX = 128, MAX_PARENT_HOPS = 5, DEFAULT_SEND_TIMEOUT_MS = 60000, PHOTO_EXTS;
44990
+ var SURFACE_BACKFILL_FLAG = "surface_backfill_20260808", GROUP_SCOPE_BACKFILL_FLAG = "telegram_group_scope_backfill_20260829", FORUM_TOPIC_NAME_MAX = 128, MAX_PARENT_HOPS = 5, DEFAULT_SEND_TIMEOUT_MS = 60000, PHOTO_EXTS;
44784
44991
  var init_adapter = __esm(async () => {
44785
44992
  await init_src();
44786
44993
  await init_commands2();
@@ -49743,4 +49950,4 @@ switch (command) {
49743
49950
  }
49744
49951
  }
49745
49952
 
49746
- //# debugId=3B82619F70BCB0E864756E2164756E21
49953
+ //# debugId=37C7DCC041D0DB9F64756E2164756E21