@poolzin/pool-bot 2026.3.25 → 2026.3.26

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 +10 -276
  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,436 @@
1
+ import { resolveChannelGroupRequireMention, resolveChannelGroupToolsPolicy, } from "../config/group-policy.js";
2
+ import { resolveDiscordAccount } from "../discord/accounts.js";
3
+ import { resolveIMessageAccount } from "../imessage/accounts.js";
4
+ import { requireActivePluginRegistry } from "../plugins/runtime.js";
5
+ import { normalizeAccountId } from "../routing/session-key.js";
6
+ import { resolveSignalAccount } from "../signal/accounts.js";
7
+ import { resolveSlackAccount, resolveSlackReplyToMode } from "../slack/accounts.js";
8
+ import { buildSlackThreadingToolContext } from "../slack/threading-tool-context.js";
9
+ import { resolveTelegramAccount } from "../telegram/accounts.js";
10
+ import { escapeRegExp, normalizeE164 } from "../utils.js";
11
+ import { resolveWhatsAppAccount } from "../web/accounts.js";
12
+ import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
13
+ import { resolveDiscordGroupRequireMention, resolveDiscordGroupToolPolicy, resolveGoogleChatGroupRequireMention, resolveGoogleChatGroupToolPolicy, resolveIMessageGroupRequireMention, resolveIMessageGroupToolPolicy, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, resolveTelegramGroupRequireMention, resolveTelegramGroupToolPolicy, resolveWhatsAppGroupRequireMention, resolveWhatsAppGroupToolPolicy, } from "./plugins/group-mentions.js";
14
+ import { normalizeSignalMessagingTarget } from "./plugins/normalize/signal.js";
15
+ import { CHAT_CHANNEL_ORDER, getChatChannelMeta } from "./registry.js";
16
+ const formatLower = (allowFrom) => allowFrom
17
+ .map((entry) => String(entry).trim())
18
+ .filter(Boolean)
19
+ .map((entry) => entry.toLowerCase());
20
+ const formatDiscordAllowFrom = (allowFrom) => allowFrom
21
+ .map((entry) => String(entry)
22
+ .trim()
23
+ .replace(/^<@!?/, "")
24
+ .replace(/>$/, "")
25
+ .replace(/^discord:/i, "")
26
+ .replace(/^user:/i, "")
27
+ .replace(/^pk:/i, "")
28
+ .trim()
29
+ .toLowerCase())
30
+ .filter(Boolean);
31
+ function resolveDirectOrGroupChannelId(context) {
32
+ const isDirect = context.ChatType?.toLowerCase() === "direct";
33
+ return (isDirect ? (context.From ?? context.To) : context.To)?.trim() || undefined;
34
+ }
35
+ function buildSignalThreadToolContext(params) {
36
+ const currentChannelIdRaw = resolveDirectOrGroupChannelId(params.context);
37
+ const currentChannelId = currentChannelIdRaw
38
+ ? (normalizeSignalMessagingTarget(currentChannelIdRaw) ?? currentChannelIdRaw.trim())
39
+ : undefined;
40
+ return {
41
+ currentChannelId,
42
+ currentThreadTs: params.context.ReplyToId,
43
+ hasRepliedRef: params.hasRepliedRef,
44
+ };
45
+ }
46
+ function buildIMessageThreadToolContext(params) {
47
+ return {
48
+ currentChannelId: resolveDirectOrGroupChannelId(params.context),
49
+ currentThreadTs: params.context.ReplyToId,
50
+ hasRepliedRef: params.hasRepliedRef,
51
+ };
52
+ }
53
+ // Channel docks: lightweight channel metadata/behavior for shared code paths.
54
+ //
55
+ // Rules:
56
+ // - keep this module *light* (no monitors, probes, puppeteer/web login, etc)
57
+ // - OK: config readers, allowFrom formatting, mention stripping patterns, threading defaults
58
+ // - shared code should import from here (and from `src/channels/registry.ts`), not from the plugins registry
59
+ //
60
+ // Adding a channel:
61
+ // - add a new entry to `DOCKS`
62
+ // - keep it cheap; push heavy logic into `src/channels/plugins/<id>.ts` or channel modules
63
+ const DOCKS = {
64
+ telegram: {
65
+ id: "telegram",
66
+ capabilities: {
67
+ chatTypes: ["direct", "group", "channel", "thread"],
68
+ nativeCommands: true,
69
+ blockStreaming: true,
70
+ },
71
+ outbound: { textChunkLimit: 4000 },
72
+ config: {
73
+ resolveAllowFrom: ({ cfg, accountId }) => (resolveTelegramAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)),
74
+ formatAllowFrom: ({ allowFrom }) => allowFrom
75
+ .map((entry) => String(entry).trim())
76
+ .filter(Boolean)
77
+ .map((entry) => entry.replace(/^(telegram|tg):/i, ""))
78
+ .map((entry) => entry.toLowerCase()),
79
+ },
80
+ groups: {
81
+ resolveRequireMention: resolveTelegramGroupRequireMention,
82
+ resolveToolPolicy: resolveTelegramGroupToolPolicy,
83
+ },
84
+ threading: {
85
+ resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "off",
86
+ buildToolContext: ({ context, hasRepliedRef }) => {
87
+ const threadId = context.MessageThreadId ?? context.ReplyToId;
88
+ return {
89
+ currentChannelId: context.To?.trim() || undefined,
90
+ currentThreadTs: threadId != null ? String(threadId) : undefined,
91
+ hasRepliedRef,
92
+ };
93
+ },
94
+ },
95
+ },
96
+ whatsapp: {
97
+ id: "whatsapp",
98
+ capabilities: {
99
+ chatTypes: ["direct", "group"],
100
+ polls: true,
101
+ reactions: true,
102
+ media: true,
103
+ },
104
+ commands: {
105
+ enforceOwnerForCommands: true,
106
+ skipWhenConfigEmpty: true,
107
+ },
108
+ outbound: { textChunkLimit: 4000 },
109
+ config: {
110
+ resolveAllowFrom: ({ cfg, accountId }) => resolveWhatsAppAccount({ cfg, accountId }).allowFrom ?? [],
111
+ formatAllowFrom: ({ allowFrom }) => allowFrom
112
+ .map((entry) => String(entry).trim())
113
+ .filter((entry) => Boolean(entry))
114
+ .map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
115
+ .filter((entry) => Boolean(entry)),
116
+ },
117
+ groups: {
118
+ resolveRequireMention: resolveWhatsAppGroupRequireMention,
119
+ resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
120
+ resolveGroupIntroHint: () => "WhatsApp IDs: SenderId is the participant JID (group participant id).",
121
+ },
122
+ mentions: {
123
+ stripPatterns: ({ ctx }) => {
124
+ const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, "");
125
+ if (!selfE164) {
126
+ return [];
127
+ }
128
+ const escaped = escapeRegExp(selfE164);
129
+ return [escaped, `@${escaped}`];
130
+ },
131
+ },
132
+ threading: {
133
+ buildToolContext: ({ context, hasRepliedRef }) => {
134
+ const channelId = context.From?.trim() || context.To?.trim() || undefined;
135
+ return {
136
+ currentChannelId: channelId,
137
+ currentThreadTs: context.ReplyToId,
138
+ hasRepliedRef,
139
+ };
140
+ },
141
+ },
142
+ },
143
+ discord: {
144
+ id: "discord",
145
+ capabilities: {
146
+ chatTypes: ["direct", "channel", "thread"],
147
+ polls: true,
148
+ reactions: true,
149
+ media: true,
150
+ nativeCommands: true,
151
+ threads: true,
152
+ },
153
+ outbound: { textChunkLimit: 2000 },
154
+ streaming: {
155
+ blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
156
+ },
157
+ elevated: {
158
+ allowFromFallback: ({ cfg }) => cfg.channels?.discord?.allowFrom ?? cfg.channels?.discord?.dm?.allowFrom,
159
+ },
160
+ config: {
161
+ resolveAllowFrom: ({ cfg, accountId }) => {
162
+ const account = resolveDiscordAccount({ cfg, accountId });
163
+ return (account.config.allowFrom ?? account.config.dm?.allowFrom ?? []).map((entry) => String(entry));
164
+ },
165
+ formatAllowFrom: ({ allowFrom }) => formatDiscordAllowFrom(allowFrom),
166
+ },
167
+ groups: {
168
+ resolveRequireMention: resolveDiscordGroupRequireMention,
169
+ resolveToolPolicy: resolveDiscordGroupToolPolicy,
170
+ },
171
+ mentions: {
172
+ stripPatterns: () => ["<@!?\\d+>"],
173
+ },
174
+ threading: {
175
+ resolveReplyToMode: ({ cfg }) => cfg.channels?.discord?.replyToMode ?? "off",
176
+ buildToolContext: ({ context, hasRepliedRef }) => ({
177
+ currentChannelId: context.To?.trim() || undefined,
178
+ currentThreadTs: context.ReplyToId,
179
+ hasRepliedRef,
180
+ }),
181
+ },
182
+ },
183
+ irc: {
184
+ id: "irc",
185
+ capabilities: {
186
+ chatTypes: ["direct", "group"],
187
+ media: true,
188
+ blockStreaming: true,
189
+ },
190
+ outbound: { textChunkLimit: 350 },
191
+ streaming: {
192
+ blockStreamingCoalesceDefaults: { minChars: 300, idleMs: 1000 },
193
+ },
194
+ config: {
195
+ resolveAllowFrom: ({ cfg, accountId }) => {
196
+ const channel = cfg.channels?.irc;
197
+ const normalized = normalizeAccountId(accountId);
198
+ const account = channel?.accounts?.[normalized] ??
199
+ channel?.accounts?.[Object.keys(channel?.accounts ?? {}).find((key) => key.toLowerCase() === normalized.toLowerCase()) ?? ""];
200
+ return (account?.allowFrom ?? channel?.allowFrom ?? []).map((entry) => String(entry));
201
+ },
202
+ formatAllowFrom: ({ allowFrom }) => allowFrom
203
+ .map((entry) => String(entry).trim())
204
+ .filter(Boolean)
205
+ .map((entry) => entry
206
+ .replace(/^irc:/i, "")
207
+ .replace(/^user:/i, "")
208
+ .toLowerCase()),
209
+ },
210
+ groups: {
211
+ resolveRequireMention: ({ cfg, accountId, groupId }) => {
212
+ if (!groupId) {
213
+ return true;
214
+ }
215
+ return resolveChannelGroupRequireMention({
216
+ cfg,
217
+ channel: "irc",
218
+ groupId,
219
+ accountId,
220
+ groupIdCaseInsensitive: true,
221
+ });
222
+ },
223
+ resolveToolPolicy: ({ cfg, accountId, groupId, senderId, senderName, senderUsername }) => {
224
+ if (!groupId) {
225
+ return undefined;
226
+ }
227
+ // IRC supports per-channel tool policies. Prefer the shared resolver so
228
+ // toolsBySender is honored consistently across surfaces.
229
+ return resolveChannelGroupToolsPolicy({
230
+ cfg,
231
+ channel: "irc",
232
+ groupId,
233
+ accountId,
234
+ groupIdCaseInsensitive: true,
235
+ senderId,
236
+ senderName,
237
+ senderUsername,
238
+ });
239
+ },
240
+ },
241
+ },
242
+ googlechat: {
243
+ id: "googlechat",
244
+ capabilities: {
245
+ chatTypes: ["direct", "group", "thread"],
246
+ reactions: true,
247
+ media: true,
248
+ threads: true,
249
+ blockStreaming: true,
250
+ },
251
+ outbound: { textChunkLimit: 4000 },
252
+ config: {
253
+ resolveAllowFrom: ({ cfg, accountId }) => {
254
+ const channel = cfg.channels?.googlechat;
255
+ const normalized = normalizeAccountId(accountId);
256
+ const account = channel?.accounts?.[normalized] ??
257
+ channel?.accounts?.[Object.keys(channel?.accounts ?? {}).find((key) => key.toLowerCase() === normalized.toLowerCase()) ?? ""];
258
+ return (account?.dm?.allowFrom ?? channel?.dm?.allowFrom ?? []).map((entry) => String(entry));
259
+ },
260
+ formatAllowFrom: ({ allowFrom }) => allowFrom
261
+ .map((entry) => String(entry).trim())
262
+ .filter(Boolean)
263
+ .map((entry) => entry
264
+ .replace(/^(googlechat|google-chat|gchat):/i, "")
265
+ .replace(/^user:/i, "")
266
+ .replace(/^users\//i, "")
267
+ .toLowerCase()),
268
+ },
269
+ groups: {
270
+ resolveRequireMention: resolveGoogleChatGroupRequireMention,
271
+ resolveToolPolicy: resolveGoogleChatGroupToolPolicy,
272
+ },
273
+ threading: {
274
+ resolveReplyToMode: ({ cfg }) => cfg.channels?.googlechat?.replyToMode ?? "off",
275
+ buildToolContext: ({ context, hasRepliedRef }) => {
276
+ const threadId = context.MessageThreadId ?? context.ReplyToId;
277
+ return {
278
+ currentChannelId: context.To?.trim() || undefined,
279
+ currentThreadTs: threadId != null ? String(threadId) : undefined,
280
+ hasRepliedRef,
281
+ };
282
+ },
283
+ },
284
+ },
285
+ slack: {
286
+ id: "slack",
287
+ capabilities: {
288
+ chatTypes: ["direct", "channel", "thread"],
289
+ reactions: true,
290
+ media: true,
291
+ nativeCommands: true,
292
+ threads: true,
293
+ },
294
+ outbound: { textChunkLimit: 4000 },
295
+ streaming: {
296
+ blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
297
+ },
298
+ config: {
299
+ resolveAllowFrom: ({ cfg, accountId }) => {
300
+ const account = resolveSlackAccount({ cfg, accountId });
301
+ return (account.config.allowFrom ?? account.dm?.allowFrom ?? []).map((entry) => String(entry));
302
+ },
303
+ formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom),
304
+ },
305
+ groups: {
306
+ resolveRequireMention: resolveSlackGroupRequireMention,
307
+ resolveToolPolicy: resolveSlackGroupToolPolicy,
308
+ },
309
+ mentions: {
310
+ stripPatterns: () => ["<@[^>]+>"],
311
+ },
312
+ threading: {
313
+ resolveReplyToMode: ({ cfg, accountId, chatType }) => resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType),
314
+ allowExplicitReplyTagsWhenOff: true,
315
+ buildToolContext: (params) => buildSlackThreadingToolContext(params),
316
+ },
317
+ },
318
+ signal: {
319
+ id: "signal",
320
+ capabilities: {
321
+ chatTypes: ["direct", "group"],
322
+ reactions: true,
323
+ media: true,
324
+ },
325
+ outbound: { textChunkLimit: 4000 },
326
+ streaming: {
327
+ blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
328
+ },
329
+ config: {
330
+ resolveAllowFrom: ({ cfg, accountId }) => (resolveSignalAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)),
331
+ formatAllowFrom: ({ allowFrom }) => allowFrom
332
+ .map((entry) => String(entry).trim())
333
+ .filter(Boolean)
334
+ .map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
335
+ .filter(Boolean),
336
+ },
337
+ threading: {
338
+ buildToolContext: ({ context, hasRepliedRef }) => buildSignalThreadToolContext({ context, hasRepliedRef }),
339
+ },
340
+ },
341
+ imessage: {
342
+ id: "imessage",
343
+ capabilities: {
344
+ chatTypes: ["direct", "group"],
345
+ reactions: true,
346
+ media: true,
347
+ },
348
+ outbound: { textChunkLimit: 4000 },
349
+ config: {
350
+ resolveAllowFrom: ({ cfg, accountId }) => (resolveIMessageAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)),
351
+ formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean),
352
+ },
353
+ groups: {
354
+ resolveRequireMention: resolveIMessageGroupRequireMention,
355
+ resolveToolPolicy: resolveIMessageGroupToolPolicy,
356
+ },
357
+ threading: {
358
+ buildToolContext: ({ context, hasRepliedRef }) => buildIMessageThreadToolContext({ context, hasRepliedRef }),
359
+ },
360
+ },
361
+ };
362
+ function buildDockFromPlugin(plugin) {
363
+ return {
364
+ id: plugin.id,
365
+ capabilities: plugin.capabilities,
366
+ commands: plugin.commands,
367
+ outbound: plugin.outbound?.textChunkLimit
368
+ ? { textChunkLimit: plugin.outbound.textChunkLimit }
369
+ : undefined,
370
+ streaming: plugin.streaming
371
+ ? { blockStreamingCoalesceDefaults: plugin.streaming.blockStreamingCoalesceDefaults }
372
+ : undefined,
373
+ elevated: plugin.elevated,
374
+ config: plugin.config
375
+ ? {
376
+ resolveAllowFrom: plugin.config.resolveAllowFrom,
377
+ formatAllowFrom: plugin.config.formatAllowFrom,
378
+ }
379
+ : undefined,
380
+ groups: plugin.groups,
381
+ mentions: plugin.mentions,
382
+ threading: plugin.threading,
383
+ agentPrompt: plugin.agentPrompt,
384
+ };
385
+ }
386
+ function listPluginDockEntries() {
387
+ const registry = requireActivePluginRegistry();
388
+ const entries = [];
389
+ const seen = new Set();
390
+ for (const entry of registry.channels) {
391
+ const plugin = entry.plugin;
392
+ const id = String(plugin.id).trim();
393
+ if (!id || seen.has(id)) {
394
+ continue;
395
+ }
396
+ seen.add(id);
397
+ if (CHAT_CHANNEL_ORDER.includes(plugin.id)) {
398
+ continue;
399
+ }
400
+ const dock = entry.dock ?? buildDockFromPlugin(plugin);
401
+ entries.push({ id: plugin.id, dock, order: plugin.meta.order });
402
+ }
403
+ return entries;
404
+ }
405
+ export function listChannelDocks() {
406
+ const baseEntries = CHAT_CHANNEL_ORDER.map((id) => ({
407
+ id,
408
+ dock: DOCKS[id],
409
+ order: getChatChannelMeta(id).order,
410
+ }));
411
+ const pluginEntries = listPluginDockEntries();
412
+ const combined = [...baseEntries, ...pluginEntries];
413
+ combined.sort((a, b) => {
414
+ const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id);
415
+ const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id);
416
+ const orderA = a.order ?? (indexA === -1 ? 999 : indexA);
417
+ const orderB = b.order ?? (indexB === -1 ? 999 : indexB);
418
+ if (orderA !== orderB) {
419
+ return orderA - orderB;
420
+ }
421
+ return String(a.id).localeCompare(String(b.id));
422
+ });
423
+ return combined.map((entry) => entry.dock);
424
+ }
425
+ export function getChannelDock(id) {
426
+ const core = DOCKS[id];
427
+ if (core) {
428
+ return core;
429
+ }
430
+ const registry = requireActivePluginRegistry();
431
+ const pluginEntry = registry.channels.find((entry) => entry.plugin.id === id);
432
+ if (!pluginEntry) {
433
+ return undefined;
434
+ }
435
+ return pluginEntry.dock ?? buildDockFromPlugin(pluginEntry.plugin);
436
+ }
@@ -0,0 +1,51 @@
1
+ import { CHAT_CHANNEL_ORDER, normalizeAnyChannelId } from "../registry.js";
2
+ import { requireActivePluginRegistry } from "../../plugins/runtime.js";
3
+ // Channel plugins registry (runtime).
4
+ //
5
+ // This module is intentionally "heavy" (plugins may import channel monitors, web login, etc).
6
+ // Shared code paths (reply flow, command auth, sandbox explain) should depend on `src/channels/dock.ts`
7
+ // instead, and only call `getChannelPlugin()` at execution boundaries.
8
+ //
9
+ // Channel plugins are registered by the plugin loader (extensions/ or configured paths).
10
+ function listPluginChannels() {
11
+ const registry = requireActivePluginRegistry();
12
+ return registry.channels.map((entry) => entry.plugin);
13
+ }
14
+ function dedupeChannels(channels) {
15
+ const seen = new Set();
16
+ const resolved = [];
17
+ for (const plugin of channels) {
18
+ const id = String(plugin.id).trim();
19
+ if (!id || seen.has(id))
20
+ continue;
21
+ seen.add(id);
22
+ resolved.push(plugin);
23
+ }
24
+ return resolved;
25
+ }
26
+ export function listChannelPlugins() {
27
+ const combined = dedupeChannels(listPluginChannels());
28
+ return combined.sort((a, b) => {
29
+ const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id);
30
+ const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id);
31
+ const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
32
+ const orderB = b.meta.order ?? (indexB === -1 ? 999 : indexB);
33
+ if (orderA !== orderB)
34
+ return orderA - orderB;
35
+ return a.id.localeCompare(b.id);
36
+ });
37
+ }
38
+ export function getChannelPlugin(id) {
39
+ const resolvedId = String(id).trim();
40
+ if (!resolvedId)
41
+ return undefined;
42
+ return listChannelPlugins().find((plugin) => plugin.id === resolvedId);
43
+ }
44
+ export function normalizeChannelId(raw) {
45
+ // Channel docking: keep input normalization centralized in src/channels/registry.ts.
46
+ // Plugin registry must be initialized before calling.
47
+ return normalizeAnyChannelId(raw);
48
+ }
49
+ export { listDiscordDirectoryGroupsFromConfig, listDiscordDirectoryPeersFromConfig, listSlackDirectoryGroupsFromConfig, listSlackDirectoryPeersFromConfig, listTelegramDirectoryGroupsFromConfig, listTelegramDirectoryPeersFromConfig, listWhatsAppDirectoryGroupsFromConfig, listWhatsAppDirectoryPeersFromConfig, } from "./directory-config.js";
50
+ export { applyChannelMatchMeta, buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatch, resolveChannelEntryMatchWithFallback, resolveChannelMatchConfig, resolveNestedAllowlistDecision, } from "./channel-config.js";
51
+ export { formatAllowlistMatchMeta, } from "./allowlist-match.js";
@@ -0,0 +1,101 @@
1
+ import { getThreadBindingManager, } from "../../../discord/monitor/thread-bindings.js";
2
+ import { sendMessageDiscord, sendPollDiscord, sendWebhookMessageDiscord, } from "../../../discord/send.js";
3
+ import { normalizeDiscordOutboundTarget } from "../normalize/discord.js";
4
+ function resolveDiscordOutboundTarget(params) {
5
+ if (params.threadId == null) {
6
+ return params.to;
7
+ }
8
+ const threadId = String(params.threadId).trim();
9
+ if (!threadId) {
10
+ return params.to;
11
+ }
12
+ return `channel:${threadId}`;
13
+ }
14
+ function resolveDiscordWebhookIdentity(params) {
15
+ const usernameRaw = params.identity?.name?.trim();
16
+ const fallbackUsername = params.binding.label?.trim() || params.binding.agentId;
17
+ const username = (usernameRaw || fallbackUsername || "").slice(0, 80) || undefined;
18
+ const avatarUrl = params.identity?.avatarUrl?.trim() || undefined;
19
+ return { username, avatarUrl };
20
+ }
21
+ async function maybeSendDiscordWebhookText(params) {
22
+ if (params.threadId == null) {
23
+ return null;
24
+ }
25
+ const threadId = String(params.threadId).trim();
26
+ if (!threadId) {
27
+ return null;
28
+ }
29
+ const manager = getThreadBindingManager(params.accountId ?? undefined);
30
+ if (!manager) {
31
+ return null;
32
+ }
33
+ const binding = manager.getByThreadId(threadId);
34
+ if (!binding?.webhookId || !binding?.webhookToken) {
35
+ return null;
36
+ }
37
+ const persona = resolveDiscordWebhookIdentity({
38
+ identity: params.identity,
39
+ binding,
40
+ });
41
+ const result = await sendWebhookMessageDiscord(params.text, {
42
+ webhookId: binding.webhookId,
43
+ webhookToken: binding.webhookToken,
44
+ accountId: binding.accountId,
45
+ threadId: binding.threadId,
46
+ replyTo: params.replyToId ?? undefined,
47
+ username: persona.username,
48
+ avatarUrl: persona.avatarUrl,
49
+ });
50
+ return result;
51
+ }
52
+ export const discordOutbound = {
53
+ deliveryMode: "direct",
54
+ chunker: null,
55
+ textChunkLimit: 2000,
56
+ pollMaxOptions: 10,
57
+ resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to),
58
+ sendText: async ({ to, text, accountId, deps, replyToId, threadId, identity, silent }) => {
59
+ if (!silent) {
60
+ const webhookResult = await maybeSendDiscordWebhookText({
61
+ text,
62
+ threadId,
63
+ accountId,
64
+ identity,
65
+ replyToId,
66
+ }).catch(() => null);
67
+ if (webhookResult) {
68
+ return { channel: "discord", ...webhookResult };
69
+ }
70
+ }
71
+ const send = deps?.sendDiscord ?? sendMessageDiscord;
72
+ const target = resolveDiscordOutboundTarget({ to, threadId });
73
+ const result = await send(target, text, {
74
+ verbose: false,
75
+ replyTo: replyToId ?? undefined,
76
+ accountId: accountId ?? undefined,
77
+ silent: silent ?? undefined,
78
+ });
79
+ return { channel: "discord", ...result };
80
+ },
81
+ sendMedia: async ({ to, text, mediaUrl, mediaLocalRoots, accountId, deps, replyToId, threadId, silent, }) => {
82
+ const send = deps?.sendDiscord ?? sendMessageDiscord;
83
+ const target = resolveDiscordOutboundTarget({ to, threadId });
84
+ const result = await send(target, text, {
85
+ verbose: false,
86
+ mediaUrl,
87
+ mediaLocalRoots,
88
+ replyTo: replyToId ?? undefined,
89
+ accountId: accountId ?? undefined,
90
+ silent: silent ?? undefined,
91
+ });
92
+ return { channel: "discord", ...result };
93
+ },
94
+ sendPoll: async ({ to, poll, accountId, threadId, silent }) => {
95
+ const target = resolveDiscordOutboundTarget({ to, threadId });
96
+ return await sendPollDiscord(target, poll, {
97
+ accountId: accountId ?? undefined,
98
+ silent: silent ?? undefined,
99
+ });
100
+ },
101
+ };
@@ -0,0 +1,17 @@
1
+ import { normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
2
+ export function normalizeWhatsAppMessagingTarget(raw) {
3
+ const trimmed = raw.trim();
4
+ if (!trimmed)
5
+ return undefined;
6
+ return normalizeWhatsAppTarget(trimmed) ?? undefined;
7
+ }
8
+ export function looksLikeWhatsAppTargetId(raw) {
9
+ const trimmed = raw.trim();
10
+ if (!trimmed)
11
+ return false;
12
+ if (/^whatsapp:/i.test(trimmed))
13
+ return true;
14
+ if (trimmed.includes("@"))
15
+ return true;
16
+ return /^\+?\d{3,}$/.test(trimmed);
17
+ }
@@ -1 +1 @@
1
- export { CHANNEL_MESSAGE_ACTION_NAMES } from "./message-action-names.js";
1
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stub for run-state-machine
3
+ * TODO: Implement proper state machine
4
+ */
5
+ export function runStateMachine() {
6
+ throw new Error("runStateMachine not implemented");
7
+ }
@@ -1,5 +1,5 @@
1
1
  import { confirm as clackConfirm, select as clackSelect, text as clackText } from "@clack/prompts";
2
- import { upsertAuthProfile } from "../../agents/auth-profiles.js";
2
+ import { clearAuthProfileCooldown, loadAuthProfileStore, upsertAuthProfile, } from "../../agents/auth-profiles.js";
3
3
  import { normalizeProviderId } from "../../agents/model-selection.js";
4
4
  import { resolveAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, } from "../../agents/agent-scope.js";
5
5
  import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
@@ -275,3 +275,49 @@ export async function modelsAuthLoginCommand(opts, runtime) {
275
275
  await prompter.note(result.notes.join("\n"), "Provider notes");
276
276
  }
277
277
  }
278
+ export async function modelsAuthClearCooldownsCommand(opts, runtime) {
279
+ const snapshot = await readConfigFileSnapshot();
280
+ if (!snapshot.valid) {
281
+ const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
282
+ throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
283
+ }
284
+ const config = snapshot.config;
285
+ const agentId = opts.agent ?? resolveDefaultAgentId(config);
286
+ const agentDir = resolveAgentDir(config, agentId);
287
+ const store = loadAuthProfileStore(agentDir);
288
+ const profileIds = Object.keys(store.profiles);
289
+ if (profileIds.length === 0) {
290
+ runtime.log("No auth profiles found.");
291
+ return;
292
+ }
293
+ const profilesWithCooldowns = profileIds.filter((id) => {
294
+ const stats = store.usageStats?.[id];
295
+ if (!stats)
296
+ return false;
297
+ return stats.cooldownUntil || stats.disabledUntil;
298
+ });
299
+ if (profilesWithCooldowns.length === 0) {
300
+ runtime.log("No profiles currently in cooldown.");
301
+ return;
302
+ }
303
+ if (!opts.yes) {
304
+ runtime.log(`Found ${profilesWithCooldowns.length} profile(s) with cooldowns:`);
305
+ for (const id of profilesWithCooldowns) {
306
+ const stats = store.usageStats?.[id];
307
+ const until = stats?.cooldownUntil ?? stats?.disabledUntil;
308
+ const remaining = until ? Math.max(0, until - Date.now()) : 0;
309
+ const minutes = Math.ceil(remaining / 60000);
310
+ runtime.log(` - ${id}: ${minutes}m remaining`);
311
+ }
312
+ const proceed = await confirm({
313
+ message: "Clear cooldowns for these profiles?",
314
+ initialValue: true,
315
+ });
316
+ if (!proceed)
317
+ return;
318
+ }
319
+ for (const profileId of profilesWithCooldowns) {
320
+ await clearAuthProfileCooldown({ store, profileId, agentDir });
321
+ }
322
+ runtime.log(`Cleared cooldowns for ${profilesWithCooldowns.length} profile(s).`);
323
+ }