@poolzin/pool-bot 2026.3.25 → 2026.3.27

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 (271) hide show
  1. package/dist/agents/model-fallback.js +5 -4
  2. package/dist/agents/tools/common.js +16 -201
  3. package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
  4. package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
  5. package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
  6. package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
  7. package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
  8. package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
  9. package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
  10. package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
  11. package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
  12. package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
  13. package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
  14. package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
  15. package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
  16. package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
  17. package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
  18. package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
  19. package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
  20. package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
  21. package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
  22. package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
  23. package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
  24. package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
  25. package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
  26. package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
  27. package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
  28. package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
  29. package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
  30. package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
  31. package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
  32. package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
  33. package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
  34. package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
  35. package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
  36. package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
  37. package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
  38. package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
  39. package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
  40. package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
  41. package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
  42. package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
  43. package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
  44. package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
  45. package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
  46. package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
  47. package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
  48. package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
  49. package/dist/build-info.json +3 -3
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
  52. package/dist/canvas-host/a2ui/index.html +1 -307
  53. package/dist/channels/channels/directory-config.js +185 -0
  54. package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
  55. package/dist/channels/channels/discord/handle-action.js +165 -0
  56. package/dist/channels/channels/discord.js +413 -0
  57. package/dist/channels/channels/dock.js +436 -0
  58. package/dist/channels/channels/index.js +51 -0
  59. package/dist/channels/channels/plugins/outbound/discord.js +101 -0
  60. package/dist/channels/channels/whatsapp.js +17 -0
  61. package/dist/channels/plugins/types.js +1 -1
  62. package/dist/channels/run-state-machine.js +7 -0
  63. package/dist/commands/models/auth.js +47 -1
  64. package/dist/commands-subagents/action-agents.js +44 -0
  65. package/dist/commands-subagents/action-focus.js +64 -0
  66. package/dist/commands-subagents/action-help.js +4 -0
  67. package/dist/commands-subagents/action-info.js +45 -0
  68. package/dist/commands-subagents/action-kill.js +60 -0
  69. package/dist/commands-subagents/action-list.js +44 -0
  70. package/dist/commands-subagents/action-log.js +29 -0
  71. package/dist/commands-subagents/action-send.js +119 -0
  72. package/dist/commands-subagents/action-spawn.js +52 -0
  73. package/dist/commands-subagents/action-unfocus.js +30 -0
  74. package/dist/commands-subagents/shared.js +303 -0
  75. package/dist/config/config.js +1 -8
  76. package/dist/config/types.secrets.js +61 -0
  77. package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
  78. package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
  79. package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
  80. package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
  81. package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
  82. package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
  83. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
  84. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
  85. package/dist/control-ui/index.html +1 -1
  86. package/dist/gateway/method-scopes.js +9 -1
  87. package/dist/gateway/node-pending-work.js +142 -0
  88. package/dist/gateway/protocol/index.js +5 -1
  89. package/dist/gateway/protocol/schema/nodes.js +18 -0
  90. package/dist/gateway/server-methods/nodes-pending.js +96 -0
  91. package/dist/gateway/server-methods-list.js +4 -0
  92. package/dist/gateway/server-methods.js +2 -0
  93. package/dist/imessage/channel.js +253 -0
  94. package/dist/imessage/monitor/echo-cache.js +70 -0
  95. package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
  96. package/dist/imessage/monitor/reflection-guard.js +50 -0
  97. package/dist/imessage/monitor/sanitize-outbound.js +25 -0
  98. package/dist/imessage/monitor/self-chat-cache.js +75 -0
  99. package/dist/imessage/runtime.js +3 -0
  100. package/dist/infra/exec-approval-reply.js +7 -0
  101. package/dist/infra/tmp-openclaw-dir.js +84 -0
  102. package/dist/pairing/pairing-challenge.js +15 -0
  103. package/dist/plugin-sdk/account-id.d.ts +1 -0
  104. package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
  105. package/dist/plugin-sdk/allow-from.d.ts +27 -0
  106. package/dist/plugin-sdk/command-auth.d.ts +25 -0
  107. package/dist/plugin-sdk/command-auth.js +3 -1
  108. package/dist/plugin-sdk/config-paths.d.ts +6 -0
  109. package/dist/plugin-sdk/file-lock.d.ts +16 -0
  110. package/dist/plugin-sdk/index.d.ts +428 -0
  111. package/dist/plugin-sdk/index.js +237 -103
  112. package/dist/plugin-sdk/json-store.d.ts +5 -0
  113. package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
  114. package/dist/plugin-sdk/onboarding.d.ts +11 -0
  115. package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
  116. package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
  117. package/dist/plugin-sdk/status-helpers.d.ts +25 -0
  118. package/dist/plugin-sdk/temp-path.d.ts +12 -0
  119. package/dist/plugin-sdk/text-chunking.d.ts +1 -0
  120. package/dist/plugin-sdk/tool-send.d.ts +4 -0
  121. package/dist/plugin-sdk/webhook-path.d.ts +6 -0
  122. package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
  123. package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
  124. package/dist/plugin-sdk-internal/accounts.js +6 -0
  125. package/dist/plugin-sdk-internal/discord.js +23 -0
  126. package/dist/plugin-sdk-internal/imessage.js +13 -0
  127. package/dist/plugin-sdk-internal/setup.js +9 -0
  128. package/dist/plugin-sdk-internal/signal.js +13 -0
  129. package/dist/plugin-sdk-internal/slack.js +22 -0
  130. package/dist/plugin-sdk-internal/telegram.js +32 -0
  131. package/dist/plugin-sdk-internal/whatsapp.js +29 -0
  132. package/dist/routing/session-key.js +4 -185
  133. package/dist/shared/pid-alive.js +2 -61
  134. package/dist/shared/process-scoped-map.js +5 -7
  135. package/dist/signal/channel.js +264 -0
  136. package/dist/signal/monitor/access-policy.js +60 -0
  137. package/dist/signal/runtime.js +3 -0
  138. package/dist/slack/account-inspect.js +135 -0
  139. package/dist/slack/blocks-input.js +7 -38
  140. package/dist/slack/channel.js +394 -0
  141. package/dist/slack/interactive-replies.js +28 -0
  142. package/dist/slack/monitor/channel-type.js +31 -0
  143. package/dist/slack/monitor/dm-auth.js +49 -0
  144. package/dist/slack/monitor/events/interactions.modal.js +137 -0
  145. package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
  146. package/dist/slack/monitor/events/system-event-context.js +29 -0
  147. package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
  148. package/dist/slack/monitor/external-arg-menu-store.js +46 -0
  149. package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
  150. package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
  151. package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
  152. package/dist/slack/monitor/reconnect-policy.js +78 -0
  153. package/dist/slack/monitor/slash-commands.runtime.js +1 -0
  154. package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
  155. package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
  156. package/dist/slack/resolve-allowlist-common.js +36 -0
  157. package/dist/slack/runtime.js +3 -0
  158. package/dist/slack/sent-thread-cache.js +61 -0
  159. package/dist/slack/truncate.js +10 -0
  160. package/dist/telegram/account-inspect.js +175 -0
  161. package/dist/telegram/allow-from.js +10 -0
  162. package/dist/telegram/api-fetch.js +18 -0
  163. package/dist/telegram/approval-buttons.js +30 -0
  164. package/dist/telegram/audit-membership-runtime.js +61 -0
  165. package/dist/telegram/bot/delivery.replies.js +508 -0
  166. package/dist/telegram/bot/delivery.resolve-media.js +227 -0
  167. package/dist/telegram/bot/delivery.send.js +132 -0
  168. package/dist/telegram/bot/reply-threading.js +46 -0
  169. package/dist/telegram/bot-message-context.body.js +186 -0
  170. package/dist/telegram/bot-message-context.session.js +207 -0
  171. package/dist/telegram/bot-message-context.types.js +1 -0
  172. package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
  173. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  174. package/dist/telegram/bot.media.test-utils.js +81 -0
  175. package/dist/telegram/channel-actions.js +225 -0
  176. package/dist/telegram/channel.js +515 -0
  177. package/dist/telegram/conversation-route.js +107 -0
  178. package/dist/telegram/delivery.js +2 -0
  179. package/dist/telegram/delivery.replies.js +508 -0
  180. package/dist/telegram/dm-access.js +86 -0
  181. package/dist/telegram/draft-stream.test-helpers.js +62 -0
  182. package/dist/telegram/exec-approvals-handler.js +281 -0
  183. package/dist/telegram/exec-approvals.js +62 -0
  184. package/dist/telegram/forum-service-message.js +22 -0
  185. package/dist/telegram/group-config-helpers.js +10 -0
  186. package/dist/telegram/lane-delivery-state.js +19 -0
  187. package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
  188. package/dist/telegram/lane-delivery.js +2 -0
  189. package/dist/telegram/normalize.js +37 -0
  190. package/dist/telegram/onboarding.js +192 -0
  191. package/dist/telegram/outbound-adapter.js +100 -0
  192. package/dist/telegram/polling-session.js +275 -0
  193. package/dist/telegram/runtime.js +3 -0
  194. package/dist/telegram/sendchataction-401-backoff.js +71 -0
  195. package/dist/telegram/sequential-key.js +46 -0
  196. package/dist/telegram/status-issues.js +105 -0
  197. package/dist/telegram/target-writeback.js +165 -0
  198. package/dist/telegram/thread-bindings.js +560 -0
  199. package/dist/utils.js +32 -257
  200. package/dist/wizard/prompts.js +5 -5
  201. package/extensions/feishu/src/policy.ts +1 -1
  202. package/extensions/firecrawl/index.test.ts +82 -0
  203. package/extensions/firecrawl/index.ts +20 -0
  204. package/extensions/firecrawl/openclaw.plugin.json +8 -0
  205. package/extensions/firecrawl/package.json +12 -0
  206. package/extensions/firecrawl/src/config.ts +159 -0
  207. package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
  208. package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
  209. package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
  210. package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
  211. package/package.json +1 -1
  212. package/dist/.buildstamp +0 -1
  213. package/dist/acp/bindings-store.js +0 -209
  214. package/dist/acp/control-plane/runtime-cache.js +0 -54
  215. package/dist/acp/control-plane/runtime-options.js +0 -215
  216. package/dist/acp/control-plane/session-actor-queue.js +0 -36
  217. package/dist/acp/index.js +0 -2
  218. package/dist/acp/runtime/errors.js +0 -47
  219. package/dist/acp/runtime/registry.js +0 -86
  220. package/dist/acp/secret-file.js +0 -22
  221. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
  222. package/dist/agents/bash-process-registry.test-helpers.js +0 -29
  223. package/dist/agents/bash-tools.exec-approval-request.js +0 -20
  224. package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
  225. package/dist/agents/bash-tools.exec-host-node.js +0 -235
  226. package/dist/agents/checkpoint-manager.js +0 -290
  227. package/dist/agents/claude-cli-runner.js +0 -3
  228. package/dist/agents/error-classifier.js +0 -251
  229. package/dist/agents/live-model-filter.js +0 -84
  230. package/dist/agents/nvidia-models.js +0 -228
  231. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
  232. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
  233. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
  234. package/dist/agents/provider/config-loader.js +0 -76
  235. package/dist/agents/provider/index.js +0 -15
  236. package/dist/agents/provider/models-dev.js +0 -129
  237. package/dist/agents/provider/session-binding.js +0 -376
  238. package/dist/agents/queued-file-writer.js +0 -22
  239. package/dist/agents/skills/bundled-context.js +0 -23
  240. package/dist/agents/skills/security.js +0 -211
  241. package/dist/agents/skills/tools-dir.js +0 -9
  242. package/dist/agents/skills-install-download.js +0 -290
  243. package/dist/agents/skills-install-output.js +0 -30
  244. package/dist/agents/skills-install.download-test-utils.js +0 -36
  245. package/dist/agents/skills.test-helpers.js +0 -13
  246. package/dist/agents/subagent-announce-reliability.js +0 -160
  247. package/dist/agents/subagent-registry.mocks.shared.js +0 -12
  248. package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
  249. package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
  250. package/dist/agents/test-helpers/fast-core-tools.js +0 -8
  251. package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
  252. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
  253. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
  254. package/dist/agents/tool-display-common.js +0 -915
  255. package/dist/agents/tool-policy-shared.js +0 -108
  256. package/dist/agents/tool-policy.conformance.js +0 -14
  257. package/dist/agents/tool-result-truncation.js +0 -299
  258. package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
  259. package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
  260. package/dist/agents/tools/discord-actions-presence.js +0 -78
  261. package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
  262. package/dist/discord/discord-improvements.js +0 -167
  263. package/dist/discord/index.js +0 -2
  264. package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
  265. package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
  266. package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
  267. package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
  268. package/dist/whatsapp/normalize.js +0 -66
  269. package/dist/whatsapp/resolve-outbound-target.js +0 -42
  270. /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
  271. /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
@@ -0,0 +1,560 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { resolveThreadBindingConversationIdFromBindingId } from "../../../src/channels/thread-binding-id.js";
5
+ import { formatThreadBindingDurationLabel } from "../../../src/channels/thread-bindings-messages.js";
6
+ import { resolveStateDir } from "../../../src/config/paths.js";
7
+ import { logVerbose } from "../../../src/globals.js";
8
+ import { writeJsonAtomic } from "../../../src/infra/json-files.js";
9
+ import { registerSessionBindingAdapter, unregisterSessionBindingAdapter, } from "../../../src/infra/outbound/session-binding-service.js";
10
+ import { normalizeAccountId } from "../../../src/routing/session-key.js";
11
+ import { resolveGlobalSingleton } from "../../../src/shared/global-singleton.js";
12
+ const DEFAULT_THREAD_BINDING_IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1000;
13
+ const DEFAULT_THREAD_BINDING_MAX_AGE_MS = 0;
14
+ const THREAD_BINDINGS_SWEEP_INTERVAL_MS = 60_000;
15
+ const STORE_VERSION = 1;
16
+ /**
17
+ * Keep Telegram thread binding state shared across bundled chunks so routing,
18
+ * binding lookups, and binding mutations all observe the same live registry.
19
+ */
20
+ const TELEGRAM_THREAD_BINDINGS_STATE_KEY = Symbol.for("openclaw.telegramThreadBindingsState");
21
+ const threadBindingsState = resolveGlobalSingleton(TELEGRAM_THREAD_BINDINGS_STATE_KEY, () => ({
22
+ managersByAccountId: new Map(),
23
+ bindingsByAccountConversation: new Map(),
24
+ }));
25
+ const MANAGERS_BY_ACCOUNT_ID = threadBindingsState.managersByAccountId;
26
+ const BINDINGS_BY_ACCOUNT_CONVERSATION = threadBindingsState.bindingsByAccountConversation;
27
+ function normalizeDurationMs(raw, fallback) {
28
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
29
+ return fallback;
30
+ }
31
+ return Math.max(0, Math.floor(raw));
32
+ }
33
+ function normalizeConversationId(raw) {
34
+ if (typeof raw !== "string") {
35
+ return undefined;
36
+ }
37
+ const trimmed = raw.trim();
38
+ return trimmed || undefined;
39
+ }
40
+ function resolveBindingKey(params) {
41
+ return `${params.accountId}:${params.conversationId}`;
42
+ }
43
+ function toSessionBindingTargetKind(raw) {
44
+ return raw === "subagent" ? "subagent" : "session";
45
+ }
46
+ function toTelegramTargetKind(raw) {
47
+ return raw === "subagent" ? "subagent" : "acp";
48
+ }
49
+ function resolveEffectiveBindingExpiresAt(params) {
50
+ const idleTimeoutMs = typeof params.record.idleTimeoutMs === "number"
51
+ ? Math.max(0, Math.floor(params.record.idleTimeoutMs))
52
+ : params.defaultIdleTimeoutMs;
53
+ const maxAgeMs = typeof params.record.maxAgeMs === "number"
54
+ ? Math.max(0, Math.floor(params.record.maxAgeMs))
55
+ : params.defaultMaxAgeMs;
56
+ const inactivityExpiresAt = idleTimeoutMs > 0
57
+ ? Math.max(params.record.lastActivityAt, params.record.boundAt) + idleTimeoutMs
58
+ : undefined;
59
+ const maxAgeExpiresAt = maxAgeMs > 0 ? params.record.boundAt + maxAgeMs : undefined;
60
+ if (inactivityExpiresAt != null && maxAgeExpiresAt != null) {
61
+ return Math.min(inactivityExpiresAt, maxAgeExpiresAt);
62
+ }
63
+ return inactivityExpiresAt ?? maxAgeExpiresAt;
64
+ }
65
+ function toSessionBindingRecord(record, defaults) {
66
+ return {
67
+ bindingId: resolveBindingKey({
68
+ accountId: record.accountId,
69
+ conversationId: record.conversationId,
70
+ }),
71
+ targetSessionKey: record.targetSessionKey,
72
+ targetKind: toSessionBindingTargetKind(record.targetKind),
73
+ conversation: {
74
+ channel: "telegram",
75
+ accountId: record.accountId,
76
+ conversationId: record.conversationId,
77
+ },
78
+ status: "active",
79
+ boundAt: record.boundAt,
80
+ expiresAt: resolveEffectiveBindingExpiresAt({
81
+ record,
82
+ defaultIdleTimeoutMs: defaults.idleTimeoutMs,
83
+ defaultMaxAgeMs: defaults.maxAgeMs,
84
+ }),
85
+ metadata: {
86
+ agentId: record.agentId,
87
+ label: record.label,
88
+ boundBy: record.boundBy,
89
+ lastActivityAt: record.lastActivityAt,
90
+ idleTimeoutMs: typeof record.idleTimeoutMs === "number"
91
+ ? Math.max(0, Math.floor(record.idleTimeoutMs))
92
+ : defaults.idleTimeoutMs,
93
+ maxAgeMs: typeof record.maxAgeMs === "number"
94
+ ? Math.max(0, Math.floor(record.maxAgeMs))
95
+ : defaults.maxAgeMs,
96
+ },
97
+ };
98
+ }
99
+ function fromSessionBindingInput(params) {
100
+ const now = Date.now();
101
+ const metadata = params.input.metadata ?? {};
102
+ const existing = BINDINGS_BY_ACCOUNT_CONVERSATION.get(resolveBindingKey({
103
+ accountId: params.accountId,
104
+ conversationId: params.input.conversationId,
105
+ }));
106
+ const record = {
107
+ accountId: params.accountId,
108
+ conversationId: params.input.conversationId,
109
+ targetKind: toTelegramTargetKind(params.input.targetKind),
110
+ targetSessionKey: params.input.targetSessionKey,
111
+ agentId: typeof metadata.agentId === "string" && metadata.agentId.trim()
112
+ ? metadata.agentId.trim()
113
+ : existing?.agentId,
114
+ label: typeof metadata.label === "string" && metadata.label.trim()
115
+ ? metadata.label.trim()
116
+ : existing?.label,
117
+ boundBy: typeof metadata.boundBy === "string" && metadata.boundBy.trim()
118
+ ? metadata.boundBy.trim()
119
+ : existing?.boundBy,
120
+ boundAt: now,
121
+ lastActivityAt: now,
122
+ };
123
+ if (typeof metadata.idleTimeoutMs === "number" && Number.isFinite(metadata.idleTimeoutMs)) {
124
+ record.idleTimeoutMs = Math.max(0, Math.floor(metadata.idleTimeoutMs));
125
+ }
126
+ else if (typeof existing?.idleTimeoutMs === "number") {
127
+ record.idleTimeoutMs = existing.idleTimeoutMs;
128
+ }
129
+ if (typeof metadata.maxAgeMs === "number" && Number.isFinite(metadata.maxAgeMs)) {
130
+ record.maxAgeMs = Math.max(0, Math.floor(metadata.maxAgeMs));
131
+ }
132
+ else if (typeof existing?.maxAgeMs === "number") {
133
+ record.maxAgeMs = existing.maxAgeMs;
134
+ }
135
+ return record;
136
+ }
137
+ function resolveBindingsPath(accountId, env = process.env) {
138
+ const stateDir = resolveStateDir(env, os.homedir);
139
+ return path.join(stateDir, "telegram", `thread-bindings-${accountId}.json`);
140
+ }
141
+ function summarizeLifecycleForLog(record, defaults) {
142
+ const idleTimeoutMs = typeof record.idleTimeoutMs === "number" ? record.idleTimeoutMs : defaults.idleTimeoutMs;
143
+ const maxAgeMs = typeof record.maxAgeMs === "number" ? record.maxAgeMs : defaults.maxAgeMs;
144
+ const idleLabel = formatThreadBindingDurationLabel(Math.max(0, Math.floor(idleTimeoutMs)));
145
+ const maxAgeLabel = formatThreadBindingDurationLabel(Math.max(0, Math.floor(maxAgeMs)));
146
+ return `idle=${idleLabel} maxAge=${maxAgeLabel}`;
147
+ }
148
+ function loadBindingsFromDisk(accountId) {
149
+ const filePath = resolveBindingsPath(accountId);
150
+ try {
151
+ const raw = fs.readFileSync(filePath, "utf-8");
152
+ const parsed = JSON.parse(raw);
153
+ if (parsed?.version !== STORE_VERSION || !Array.isArray(parsed.bindings)) {
154
+ return [];
155
+ }
156
+ const bindings = [];
157
+ for (const entry of parsed.bindings) {
158
+ const conversationId = normalizeConversationId(entry?.conversationId);
159
+ const targetSessionKey = typeof entry?.targetSessionKey === "string" ? entry.targetSessionKey.trim() : "";
160
+ const targetKind = entry?.targetKind === "subagent" ? "subagent" : "acp";
161
+ if (!conversationId || !targetSessionKey) {
162
+ continue;
163
+ }
164
+ const boundAt = typeof entry?.boundAt === "number" && Number.isFinite(entry.boundAt)
165
+ ? Math.floor(entry.boundAt)
166
+ : Date.now();
167
+ const lastActivityAt = typeof entry?.lastActivityAt === "number" && Number.isFinite(entry.lastActivityAt)
168
+ ? Math.floor(entry.lastActivityAt)
169
+ : boundAt;
170
+ const record = {
171
+ accountId,
172
+ conversationId,
173
+ targetSessionKey,
174
+ targetKind,
175
+ boundAt,
176
+ lastActivityAt,
177
+ };
178
+ if (typeof entry?.idleTimeoutMs === "number" && Number.isFinite(entry.idleTimeoutMs)) {
179
+ record.idleTimeoutMs = Math.max(0, Math.floor(entry.idleTimeoutMs));
180
+ }
181
+ if (typeof entry?.maxAgeMs === "number" && Number.isFinite(entry.maxAgeMs)) {
182
+ record.maxAgeMs = Math.max(0, Math.floor(entry.maxAgeMs));
183
+ }
184
+ if (typeof entry?.agentId === "string" && entry.agentId.trim()) {
185
+ record.agentId = entry.agentId.trim();
186
+ }
187
+ if (typeof entry?.label === "string" && entry.label.trim()) {
188
+ record.label = entry.label.trim();
189
+ }
190
+ if (typeof entry?.boundBy === "string" && entry.boundBy.trim()) {
191
+ record.boundBy = entry.boundBy.trim();
192
+ }
193
+ bindings.push(record);
194
+ }
195
+ return bindings;
196
+ }
197
+ catch (err) {
198
+ const code = err.code;
199
+ if (code !== "ENOENT") {
200
+ logVerbose(`telegram thread bindings load failed (${accountId}): ${String(err)}`);
201
+ }
202
+ return [];
203
+ }
204
+ }
205
+ async function persistBindingsToDisk(params) {
206
+ if (!params.persist) {
207
+ return;
208
+ }
209
+ const bindings = [...BINDINGS_BY_ACCOUNT_CONVERSATION.values()].filter((entry) => entry.accountId === params.accountId);
210
+ const payload = {
211
+ version: STORE_VERSION,
212
+ bindings,
213
+ };
214
+ await writeJsonAtomic(resolveBindingsPath(params.accountId), payload, {
215
+ mode: 0o600,
216
+ trailingNewline: true,
217
+ ensureDirMode: 0o700,
218
+ });
219
+ }
220
+ function normalizeTimestampMs(raw) {
221
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
222
+ return Date.now();
223
+ }
224
+ return Math.max(0, Math.floor(raw));
225
+ }
226
+ function shouldExpireByIdle(params) {
227
+ const idleTimeoutMs = typeof params.record.idleTimeoutMs === "number"
228
+ ? Math.max(0, Math.floor(params.record.idleTimeoutMs))
229
+ : params.defaultIdleTimeoutMs;
230
+ if (idleTimeoutMs <= 0) {
231
+ return false;
232
+ }
233
+ return (params.now >= Math.max(params.record.lastActivityAt, params.record.boundAt) + idleTimeoutMs);
234
+ }
235
+ function shouldExpireByMaxAge(params) {
236
+ const maxAgeMs = typeof params.record.maxAgeMs === "number"
237
+ ? Math.max(0, Math.floor(params.record.maxAgeMs))
238
+ : params.defaultMaxAgeMs;
239
+ if (maxAgeMs <= 0) {
240
+ return false;
241
+ }
242
+ return params.now >= params.record.boundAt + maxAgeMs;
243
+ }
244
+ export function createTelegramThreadBindingManager(params = {}) {
245
+ const accountId = normalizeAccountId(params.accountId);
246
+ const existing = MANAGERS_BY_ACCOUNT_ID.get(accountId);
247
+ if (existing) {
248
+ return existing;
249
+ }
250
+ const persist = params.persist ?? true;
251
+ const idleTimeoutMs = normalizeDurationMs(params.idleTimeoutMs, DEFAULT_THREAD_BINDING_IDLE_TIMEOUT_MS);
252
+ const maxAgeMs = normalizeDurationMs(params.maxAgeMs, DEFAULT_THREAD_BINDING_MAX_AGE_MS);
253
+ const loaded = loadBindingsFromDisk(accountId);
254
+ for (const entry of loaded) {
255
+ const key = resolveBindingKey({
256
+ accountId,
257
+ conversationId: entry.conversationId,
258
+ });
259
+ BINDINGS_BY_ACCOUNT_CONVERSATION.set(key, {
260
+ ...entry,
261
+ accountId,
262
+ });
263
+ }
264
+ const listBindingsForAccount = () => [...BINDINGS_BY_ACCOUNT_CONVERSATION.values()].filter((entry) => entry.accountId === accountId);
265
+ let sweepTimer = null;
266
+ const manager = {
267
+ accountId,
268
+ shouldPersistMutations: () => persist,
269
+ getIdleTimeoutMs: () => idleTimeoutMs,
270
+ getMaxAgeMs: () => maxAgeMs,
271
+ getByConversationId: (conversationIdRaw) => {
272
+ const conversationId = normalizeConversationId(conversationIdRaw);
273
+ if (!conversationId) {
274
+ return undefined;
275
+ }
276
+ return BINDINGS_BY_ACCOUNT_CONVERSATION.get(resolveBindingKey({
277
+ accountId,
278
+ conversationId,
279
+ }));
280
+ },
281
+ listBySessionKey: (targetSessionKeyRaw) => {
282
+ const targetSessionKey = targetSessionKeyRaw.trim();
283
+ if (!targetSessionKey) {
284
+ return [];
285
+ }
286
+ return listBindingsForAccount().filter((entry) => entry.targetSessionKey === targetSessionKey);
287
+ },
288
+ listBindings: () => listBindingsForAccount(),
289
+ touchConversation: (conversationIdRaw, at) => {
290
+ const conversationId = normalizeConversationId(conversationIdRaw);
291
+ if (!conversationId) {
292
+ return null;
293
+ }
294
+ const key = resolveBindingKey({ accountId, conversationId });
295
+ const existing = BINDINGS_BY_ACCOUNT_CONVERSATION.get(key);
296
+ if (!existing) {
297
+ return null;
298
+ }
299
+ const nextRecord = {
300
+ ...existing,
301
+ lastActivityAt: normalizeTimestampMs(at ?? Date.now()),
302
+ };
303
+ BINDINGS_BY_ACCOUNT_CONVERSATION.set(key, nextRecord);
304
+ void persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() });
305
+ return nextRecord;
306
+ },
307
+ unbindConversation: (unbindParams) => {
308
+ const conversationId = normalizeConversationId(unbindParams.conversationId);
309
+ if (!conversationId) {
310
+ return null;
311
+ }
312
+ const key = resolveBindingKey({ accountId, conversationId });
313
+ const removed = BINDINGS_BY_ACCOUNT_CONVERSATION.get(key) ?? null;
314
+ if (!removed) {
315
+ return null;
316
+ }
317
+ BINDINGS_BY_ACCOUNT_CONVERSATION.delete(key);
318
+ void persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() });
319
+ return removed;
320
+ },
321
+ unbindBySessionKey: (unbindParams) => {
322
+ const targetSessionKey = unbindParams.targetSessionKey.trim();
323
+ if (!targetSessionKey) {
324
+ return [];
325
+ }
326
+ const removed = [];
327
+ for (const entry of listBindingsForAccount()) {
328
+ if (entry.targetSessionKey !== targetSessionKey) {
329
+ continue;
330
+ }
331
+ const key = resolveBindingKey({
332
+ accountId,
333
+ conversationId: entry.conversationId,
334
+ });
335
+ BINDINGS_BY_ACCOUNT_CONVERSATION.delete(key);
336
+ removed.push(entry);
337
+ }
338
+ if (removed.length > 0) {
339
+ void persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() });
340
+ }
341
+ return removed;
342
+ },
343
+ stop: () => {
344
+ if (sweepTimer) {
345
+ clearInterval(sweepTimer);
346
+ sweepTimer = null;
347
+ }
348
+ unregisterSessionBindingAdapter({ channel: "telegram", accountId });
349
+ const existingManager = MANAGERS_BY_ACCOUNT_ID.get(accountId);
350
+ if (existingManager === manager) {
351
+ MANAGERS_BY_ACCOUNT_ID.delete(accountId);
352
+ }
353
+ },
354
+ };
355
+ registerSessionBindingAdapter({
356
+ channel: "telegram",
357
+ accountId,
358
+ capabilities: {
359
+ placements: ["current"],
360
+ },
361
+ bind: async (input) => {
362
+ if (input.conversation.channel !== "telegram") {
363
+ return null;
364
+ }
365
+ if (input.placement === "child") {
366
+ return null;
367
+ }
368
+ const conversationId = normalizeConversationId(input.conversation.conversationId);
369
+ const targetSessionKey = input.targetSessionKey.trim();
370
+ if (!conversationId || !targetSessionKey) {
371
+ return null;
372
+ }
373
+ const record = fromSessionBindingInput({
374
+ accountId,
375
+ input: {
376
+ targetSessionKey,
377
+ targetKind: input.targetKind,
378
+ conversationId,
379
+ metadata: input.metadata,
380
+ },
381
+ });
382
+ BINDINGS_BY_ACCOUNT_CONVERSATION.set(resolveBindingKey({ accountId, conversationId }), record);
383
+ void persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() });
384
+ logVerbose(`telegram: bound conversation ${conversationId} -> ${targetSessionKey} (${summarizeLifecycleForLog(record, {
385
+ idleTimeoutMs,
386
+ maxAgeMs,
387
+ })})`);
388
+ return toSessionBindingRecord(record, {
389
+ idleTimeoutMs,
390
+ maxAgeMs,
391
+ });
392
+ },
393
+ listBySession: (targetSessionKeyRaw) => {
394
+ const targetSessionKey = targetSessionKeyRaw.trim();
395
+ if (!targetSessionKey) {
396
+ return [];
397
+ }
398
+ return manager.listBySessionKey(targetSessionKey).map((entry) => toSessionBindingRecord(entry, {
399
+ idleTimeoutMs,
400
+ maxAgeMs,
401
+ }));
402
+ },
403
+ resolveByConversation: (ref) => {
404
+ if (ref.channel !== "telegram") {
405
+ return null;
406
+ }
407
+ const conversationId = normalizeConversationId(ref.conversationId);
408
+ if (!conversationId) {
409
+ return null;
410
+ }
411
+ const record = manager.getByConversationId(conversationId);
412
+ return record
413
+ ? toSessionBindingRecord(record, {
414
+ idleTimeoutMs,
415
+ maxAgeMs,
416
+ })
417
+ : null;
418
+ },
419
+ touch: (bindingId, at) => {
420
+ const conversationId = resolveThreadBindingConversationIdFromBindingId({
421
+ accountId,
422
+ bindingId,
423
+ });
424
+ if (!conversationId) {
425
+ return;
426
+ }
427
+ manager.touchConversation(conversationId, at);
428
+ },
429
+ unbind: async (input) => {
430
+ if (input.targetSessionKey?.trim()) {
431
+ const removed = manager.unbindBySessionKey({
432
+ targetSessionKey: input.targetSessionKey,
433
+ reason: input.reason,
434
+ sendFarewell: false,
435
+ });
436
+ return removed.map((entry) => toSessionBindingRecord(entry, {
437
+ idleTimeoutMs,
438
+ maxAgeMs,
439
+ }));
440
+ }
441
+ const conversationId = resolveThreadBindingConversationIdFromBindingId({
442
+ accountId,
443
+ bindingId: input.bindingId,
444
+ });
445
+ if (!conversationId) {
446
+ return [];
447
+ }
448
+ const removed = manager.unbindConversation({
449
+ conversationId,
450
+ reason: input.reason,
451
+ sendFarewell: false,
452
+ });
453
+ return removed
454
+ ? [
455
+ toSessionBindingRecord(removed, {
456
+ idleTimeoutMs,
457
+ maxAgeMs,
458
+ }),
459
+ ]
460
+ : [];
461
+ },
462
+ });
463
+ const sweeperEnabled = params.enableSweeper !== false;
464
+ if (sweeperEnabled) {
465
+ sweepTimer = setInterval(() => {
466
+ const now = Date.now();
467
+ for (const record of listBindingsForAccount()) {
468
+ const idleExpired = shouldExpireByIdle({
469
+ now,
470
+ record,
471
+ defaultIdleTimeoutMs: idleTimeoutMs,
472
+ });
473
+ const maxAgeExpired = shouldExpireByMaxAge({
474
+ now,
475
+ record,
476
+ defaultMaxAgeMs: maxAgeMs,
477
+ });
478
+ if (!idleExpired && !maxAgeExpired) {
479
+ continue;
480
+ }
481
+ manager.unbindConversation({
482
+ conversationId: record.conversationId,
483
+ reason: idleExpired ? "idle-expired" : "max-age-expired",
484
+ sendFarewell: false,
485
+ });
486
+ }
487
+ }, THREAD_BINDINGS_SWEEP_INTERVAL_MS);
488
+ sweepTimer.unref?.();
489
+ }
490
+ MANAGERS_BY_ACCOUNT_ID.set(accountId, manager);
491
+ return manager;
492
+ }
493
+ export function getTelegramThreadBindingManager(accountId) {
494
+ return MANAGERS_BY_ACCOUNT_ID.get(normalizeAccountId(accountId)) ?? null;
495
+ }
496
+ function updateTelegramBindingsBySessionKey(params) {
497
+ const targetSessionKey = params.targetSessionKey.trim();
498
+ if (!targetSessionKey) {
499
+ return [];
500
+ }
501
+ const now = Date.now();
502
+ const updated = [];
503
+ for (const entry of params.manager.listBySessionKey(targetSessionKey)) {
504
+ const key = resolveBindingKey({
505
+ accountId: params.manager.accountId,
506
+ conversationId: entry.conversationId,
507
+ });
508
+ const next = params.update(entry, now);
509
+ BINDINGS_BY_ACCOUNT_CONVERSATION.set(key, next);
510
+ updated.push(next);
511
+ }
512
+ if (updated.length > 0) {
513
+ void persistBindingsToDisk({
514
+ accountId: params.manager.accountId,
515
+ persist: params.manager.shouldPersistMutations(),
516
+ });
517
+ }
518
+ return updated;
519
+ }
520
+ export function setTelegramThreadBindingIdleTimeoutBySessionKey(params) {
521
+ const manager = getTelegramThreadBindingManager(params.accountId);
522
+ if (!manager) {
523
+ return [];
524
+ }
525
+ const idleTimeoutMs = normalizeDurationMs(params.idleTimeoutMs, 0);
526
+ return updateTelegramBindingsBySessionKey({
527
+ manager,
528
+ targetSessionKey: params.targetSessionKey,
529
+ update: (entry, now) => ({
530
+ ...entry,
531
+ idleTimeoutMs,
532
+ lastActivityAt: now,
533
+ }),
534
+ });
535
+ }
536
+ export function setTelegramThreadBindingMaxAgeBySessionKey(params) {
537
+ const manager = getTelegramThreadBindingManager(params.accountId);
538
+ if (!manager) {
539
+ return [];
540
+ }
541
+ const maxAgeMs = normalizeDurationMs(params.maxAgeMs, 0);
542
+ return updateTelegramBindingsBySessionKey({
543
+ manager,
544
+ targetSessionKey: params.targetSessionKey,
545
+ update: (entry, now) => ({
546
+ ...entry,
547
+ maxAgeMs,
548
+ lastActivityAt: now,
549
+ }),
550
+ });
551
+ }
552
+ export const __testing = {
553
+ resetTelegramThreadBindingsForTests() {
554
+ for (const manager of MANAGERS_BY_ACCOUNT_ID.values()) {
555
+ manager.stop();
556
+ }
557
+ MANAGERS_BY_ACCOUNT_ID.clear();
558
+ BINDINGS_BY_ACCOUNT_CONVERSATION.clear();
559
+ },
560
+ };