@poolzin/pool-bot 2026.2.23 → 2026.2.25

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 (235) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/secret-file.js +22 -0
  4. package/dist/agents/agent-scope.js +10 -0
  5. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  6. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  7. package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
  8. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  9. package/dist/agents/bash-tools.exec-types.js +1 -0
  10. package/dist/agents/bash-tools.process.js +224 -218
  11. package/dist/agents/content-blocks.js +16 -0
  12. package/dist/agents/model-fallback.js +96 -101
  13. package/dist/agents/models-config.providers.js +299 -182
  14. package/dist/agents/pi-embedded-payloads.js +1 -0
  15. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  16. package/dist/agents/skills.test-helpers.js +13 -0
  17. package/dist/agents/stable-stringify.js +12 -0
  18. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  19. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  20. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  21. package/dist/agents/tool-policy-shared.js +108 -0
  22. package/dist/agents/tools/browser-tool.js +160 -54
  23. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  24. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  25. package/dist/agents/tools/image-tool.js +214 -99
  26. package/dist/agents/tools/sessions-history-tool.js +140 -108
  27. package/dist/agents/workspace.js +222 -46
  28. package/dist/auto-reply/commands-registry.js +15 -18
  29. package/dist/auto-reply/fallback-state.js +114 -0
  30. package/dist/auto-reply/model-runtime.js +68 -0
  31. package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
  32. package/dist/auto-reply/reply/agent-runner.js +165 -39
  33. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  34. package/dist/browser/config.js +26 -0
  35. package/dist/browser/navigation-guard.js +31 -0
  36. package/dist/browser/routes/agent.act.js +431 -424
  37. package/dist/browser/routes/agent.shared.js +47 -3
  38. package/dist/browser/routes/agent.snapshot.js +122 -116
  39. package/dist/browser/routes/agent.storage.js +303 -297
  40. package/dist/browser/routes/tabs.js +154 -100
  41. package/dist/browser/server-lifecycle.js +37 -0
  42. package/dist/build-info.json +3 -3
  43. package/dist/channels/allow-from.js +25 -0
  44. package/dist/channels/plugins/account-action-gate.js +13 -0
  45. package/dist/channels/plugins/message-actions.js +10 -0
  46. package/dist/channels/telegram/api.js +18 -0
  47. package/dist/cli/argv.js +84 -21
  48. package/dist/cli/banner.js +2 -1
  49. package/dist/cli/exec-approvals-cli.js +92 -124
  50. package/dist/cli/memory-cli.js +158 -61
  51. package/dist/cli/nodes-cli/register.push.js +63 -0
  52. package/dist/cli/nodes-media-utils.js +21 -0
  53. package/dist/cli/plugins-cli.js +245 -61
  54. package/dist/cli/program/build-program.js +3 -1
  55. package/dist/cli/program/command-registry.js +223 -136
  56. package/dist/cli/program/help.js +43 -12
  57. package/dist/cli/route.js +1 -1
  58. package/dist/cli/test-runtime-capture.js +24 -0
  59. package/dist/commands/agent.js +163 -87
  60. package/dist/commands/channels.mock-harness.js +23 -0
  61. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  62. package/dist/commands/onboard-helpers.js +4 -4
  63. package/dist/commands/sessions.test-helpers.js +61 -0
  64. package/dist/compat/legacy-names.js +2 -2
  65. package/dist/config/commands.js +3 -0
  66. package/dist/config/config.js +1 -1
  67. package/dist/config/env-substitution.js +62 -34
  68. package/dist/config/env-vars.js +9 -0
  69. package/dist/config/io.js +571 -171
  70. package/dist/config/merge-patch.js +50 -4
  71. package/dist/config/redact-snapshot.js +404 -76
  72. package/dist/config/schema.js +58 -570
  73. package/dist/config/validation.js +140 -85
  74. package/dist/config/zod-schema.hooks.js +40 -11
  75. package/dist/config/zod-schema.installs.js +20 -0
  76. package/dist/config/zod-schema.js +8 -7
  77. package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
  78. package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
  79. package/dist/control-ui/index.html +1 -1
  80. package/dist/daemon/cmd-argv.js +21 -0
  81. package/dist/daemon/cmd-set.js +58 -0
  82. package/dist/daemon/service-types.js +1 -0
  83. package/dist/discord/monitor/exec-approvals.js +357 -162
  84. package/dist/gateway/auth.js +38 -3
  85. package/dist/gateway/call.js +149 -68
  86. package/dist/gateway/canvas-capability.js +75 -0
  87. package/dist/gateway/control-plane-audit.js +28 -0
  88. package/dist/gateway/control-plane-rate-limit.js +53 -0
  89. package/dist/gateway/events.js +1 -0
  90. package/dist/gateway/hooks.js +109 -54
  91. package/dist/gateway/http-common.js +22 -0
  92. package/dist/gateway/method-scopes.js +169 -0
  93. package/dist/gateway/net.js +23 -0
  94. package/dist/gateway/openresponses-http.js +120 -110
  95. package/dist/gateway/probe-auth.js +2 -0
  96. package/dist/gateway/protocol/index.js +3 -2
  97. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
  98. package/dist/gateway/protocol/schema/push.js +18 -0
  99. package/dist/gateway/protocol/schema.js +1 -0
  100. package/dist/gateway/server-http.js +236 -52
  101. package/dist/gateway/server-methods/agent.js +162 -24
  102. package/dist/gateway/server-methods/chat.js +461 -130
  103. package/dist/gateway/server-methods/config.js +193 -150
  104. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  105. package/dist/gateway/server-methods/nodes.js +251 -69
  106. package/dist/gateway/server-methods/push.js +53 -0
  107. package/dist/gateway/server-reload-handlers.js +2 -3
  108. package/dist/gateway/server-runtime-config.js +5 -0
  109. package/dist/gateway/server-runtime-state.js +2 -0
  110. package/dist/gateway/server-ws-runtime.js +1 -0
  111. package/dist/gateway/server.impl.js +296 -139
  112. package/dist/gateway/session-preview.test-helpers.js +11 -0
  113. package/dist/gateway/startup-auth.js +126 -0
  114. package/dist/gateway/test-helpers.agent-results.js +15 -0
  115. package/dist/gateway/test-helpers.mocks.js +37 -14
  116. package/dist/gateway/test-helpers.server.js +161 -77
  117. package/dist/hooks/bundled/session-memory/handler.js +165 -34
  118. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  119. package/dist/infra/archive-path.js +49 -0
  120. package/dist/infra/device-pairing.js +148 -167
  121. package/dist/infra/exec-approvals-allowlist.js +19 -70
  122. package/dist/infra/exec-approvals-analysis.js +44 -17
  123. package/dist/infra/exec-safe-bin-policy.js +269 -0
  124. package/dist/infra/fixed-window-rate-limit.js +33 -0
  125. package/dist/infra/git-root.js +61 -0
  126. package/dist/infra/heartbeat-active-hours.js +2 -2
  127. package/dist/infra/heartbeat-reason.js +40 -0
  128. package/dist/infra/heartbeat-runner.js +72 -32
  129. package/dist/infra/install-source-utils.js +91 -7
  130. package/dist/infra/node-pairing.js +50 -105
  131. package/dist/infra/npm-integrity.js +45 -0
  132. package/dist/infra/npm-pack-install.js +40 -0
  133. package/dist/infra/outbound/channel-adapters.js +20 -7
  134. package/dist/infra/outbound/message-action-runner.js +107 -327
  135. package/dist/infra/outbound/message.js +59 -36
  136. package/dist/infra/outbound/outbound-policy.js +52 -25
  137. package/dist/infra/outbound/outbound-send-service.js +58 -71
  138. package/dist/infra/pairing-files.js +10 -0
  139. package/dist/infra/plain-object.js +9 -0
  140. package/dist/infra/push-apns.js +365 -0
  141. package/dist/infra/restart-sentinel.js +16 -1
  142. package/dist/infra/restart.js +229 -26
  143. package/dist/infra/scp-host.js +54 -0
  144. package/dist/infra/update-startup.js +86 -9
  145. package/dist/media/inbound-path-policy.js +114 -0
  146. package/dist/media/input-files.js +16 -0
  147. package/dist/memory/test-manager.js +8 -0
  148. package/dist/plugin-sdk/temp-path.js +47 -0
  149. package/dist/plugins/discovery.js +217 -23
  150. package/dist/plugins/hook-runner-global.js +16 -0
  151. package/dist/plugins/loader.js +192 -26
  152. package/dist/plugins/logger.js +8 -0
  153. package/dist/plugins/manifest-registry.js +3 -0
  154. package/dist/plugins/path-safety.js +34 -0
  155. package/dist/plugins/registry.js +5 -2
  156. package/dist/plugins/runtime/index.js +271 -206
  157. package/dist/providers/github-copilot-models.js +4 -1
  158. package/dist/security/audit-channel.js +8 -19
  159. package/dist/security/audit-extra.async.js +354 -182
  160. package/dist/security/audit-extra.js +11 -1
  161. package/dist/security/audit-extra.sync.js +340 -33
  162. package/dist/security/audit-fs.js +31 -13
  163. package/dist/security/audit.js +145 -371
  164. package/dist/security/dm-policy-shared.js +24 -0
  165. package/dist/security/external-content.js +20 -8
  166. package/dist/security/fix.js +49 -85
  167. package/dist/security/scan-paths.js +20 -0
  168. package/dist/security/secret-equal.js +3 -7
  169. package/dist/security/windows-acl.js +30 -15
  170. package/dist/shared/node-list-parse.js +13 -0
  171. package/dist/shared/operator-scope-compat.js +37 -0
  172. package/dist/shared/text-chunking.js +29 -0
  173. package/dist/slack/blocks.test-helpers.js +31 -0
  174. package/dist/slack/monitor/mrkdwn.js +8 -0
  175. package/dist/telegram/bot-message-dispatch.js +366 -164
  176. package/dist/telegram/draft-stream.js +30 -7
  177. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  178. package/dist/terminal/prompt-select-styled.js +9 -0
  179. package/dist/test-utils/command-runner.js +6 -0
  180. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  181. package/dist/test-utils/model-auth-mock.js +12 -0
  182. package/dist/test-utils/provider-usage-fetch.js +14 -0
  183. package/dist/test-utils/temp-home.js +33 -0
  184. package/dist/tui/components/chat-log.js +9 -0
  185. package/dist/tui/tui-command-handlers.js +36 -27
  186. package/dist/tui/tui-event-handlers.js +122 -32
  187. package/dist/tui/tui.js +181 -45
  188. package/dist/utils/mask-api-key.js +10 -0
  189. package/dist/utils/run-with-concurrency.js +39 -0
  190. package/dist/web/media.js +4 -0
  191. package/docs/tools/slash-commands.md +5 -1
  192. package/extensions/bluebubbles/package.json +1 -1
  193. package/extensions/copilot-proxy/package.json +1 -1
  194. package/extensions/diagnostics-otel/package.json +1 -1
  195. package/extensions/discord/package.json +1 -1
  196. package/extensions/feishu/package.json +1 -1
  197. package/extensions/feishu/src/external-keys.ts +19 -0
  198. package/extensions/google-antigravity-auth/package.json +1 -1
  199. package/extensions/google-gemini-cli-auth/package.json +1 -1
  200. package/extensions/googlechat/package.json +1 -1
  201. package/extensions/imessage/package.json +1 -1
  202. package/extensions/irc/package.json +1 -1
  203. package/extensions/line/package.json +1 -1
  204. package/extensions/llm-task/package.json +1 -1
  205. package/extensions/lobster/package.json +1 -1
  206. package/extensions/lobster/src/windows-spawn.ts +193 -0
  207. package/extensions/matrix/CHANGELOG.md +5 -0
  208. package/extensions/matrix/package.json +1 -1
  209. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  210. package/extensions/mattermost/package.json +1 -1
  211. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  212. package/extensions/memory-core/package.json +1 -1
  213. package/extensions/memory-lancedb/package.json +1 -1
  214. package/extensions/minimax-portal-auth/package.json +1 -1
  215. package/extensions/msteams/CHANGELOG.md +5 -0
  216. package/extensions/msteams/package.json +1 -1
  217. package/extensions/nextcloud-talk/package.json +1 -1
  218. package/extensions/nostr/CHANGELOG.md +5 -0
  219. package/extensions/nostr/package.json +1 -1
  220. package/extensions/open-prose/package.json +1 -1
  221. package/extensions/openai-codex-auth/package.json +1 -1
  222. package/extensions/signal/package.json +1 -1
  223. package/extensions/slack/package.json +1 -1
  224. package/extensions/telegram/package.json +1 -1
  225. package/extensions/tlon/package.json +1 -1
  226. package/extensions/twitch/CHANGELOG.md +5 -0
  227. package/extensions/twitch/package.json +1 -1
  228. package/extensions/voice-call/CHANGELOG.md +5 -0
  229. package/extensions/voice-call/package.json +1 -1
  230. package/extensions/whatsapp/package.json +1 -1
  231. package/extensions/zalo/CHANGELOG.md +5 -0
  232. package/extensions/zalo/package.json +1 -1
  233. package/extensions/zalouser/CHANGELOG.md +5 -0
  234. package/extensions/zalouser/package.json +1 -1
  235. package/package.json +1 -1
@@ -1,15 +1,37 @@
1
1
  import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
2
2
  import { loadConfig } from "../../config/config.js";
3
- import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
3
+ import { callGatewayLeastPrivilege, randomIdempotencyKey } from "../../gateway/call.js";
4
4
  import { normalizePollInput } from "../../polls.js";
5
5
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../../utils/message-channel.js";
6
6
  import { resolveMessageChannelSelection } from "./channel-selection.js";
7
7
  import { deliverOutboundPayloads, } from "./deliver.js";
8
8
  import { normalizeReplyPayloadsForDelivery } from "./payloads.js";
9
9
  import { resolveOutboundTarget } from "./targets.js";
10
+ async function resolveRequiredChannel(params) {
11
+ const channel = params.channel?.trim()
12
+ ? normalizeChannelId(params.channel)
13
+ : (await resolveMessageChannelSelection({ cfg: params.cfg })).channel;
14
+ if (!channel) {
15
+ throw new Error(`Unknown channel: ${params.channel}`);
16
+ }
17
+ return channel;
18
+ }
19
+ function resolveRequiredPlugin(channel) {
20
+ const plugin = getChannelPlugin(channel);
21
+ if (!plugin) {
22
+ throw new Error(`Unknown channel: ${channel}`);
23
+ }
24
+ return plugin;
25
+ }
10
26
  function resolveGatewayOptions(opts) {
27
+ // Security: backend callers (tools/agents) must not accept user-controlled gateway URLs.
28
+ // Use config-derived gateway target only.
29
+ const url = opts?.mode === GATEWAY_CLIENT_MODES.BACKEND ||
30
+ opts?.clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT
31
+ ? undefined
32
+ : opts?.url;
11
33
  return {
12
- url: opts?.url,
34
+ url,
13
35
  token: opts?.token,
14
36
  timeoutMs: typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
15
37
  ? Math.max(1, Math.floor(opts.timeoutMs))
@@ -19,18 +41,23 @@ function resolveGatewayOptions(opts) {
19
41
  mode: opts?.mode ?? GATEWAY_CLIENT_MODES.CLI,
20
42
  };
21
43
  }
44
+ async function callMessageGateway(params) {
45
+ const gateway = resolveGatewayOptions(params.gateway);
46
+ return await callGatewayLeastPrivilege({
47
+ url: gateway.url,
48
+ token: gateway.token,
49
+ method: params.method,
50
+ params: params.params,
51
+ timeoutMs: gateway.timeoutMs,
52
+ clientName: gateway.clientName,
53
+ clientDisplayName: gateway.clientDisplayName,
54
+ mode: gateway.mode,
55
+ });
56
+ }
22
57
  export async function sendMessage(params) {
23
58
  const cfg = params.cfg ?? loadConfig();
24
- const channel = params.channel?.trim()
25
- ? normalizeChannelId(params.channel)
26
- : (await resolveMessageChannelSelection({ cfg })).channel;
27
- if (!channel) {
28
- throw new Error(`Unknown channel: ${params.channel}`);
29
- }
30
- const plugin = getChannelPlugin(channel);
31
- if (!plugin) {
32
- throw new Error(`Unknown channel: ${channel}`);
33
- }
59
+ const channel = await resolveRequiredChannel({ cfg, channel: params.channel });
60
+ const plugin = resolveRequiredPlugin(channel);
34
61
  const deliveryMode = plugin.outbound?.deliveryMode ?? "direct";
35
62
  const normalizedPayloads = normalizeReplyPayloadsForDelivery([
36
63
  {
@@ -64,18 +91,23 @@ export async function sendMessage(params) {
64
91
  accountId: params.accountId,
65
92
  mode: "explicit",
66
93
  });
67
- if (!resolvedTarget.ok)
94
+ if (!resolvedTarget.ok) {
68
95
  throw resolvedTarget.error;
96
+ }
69
97
  const results = await deliverOutboundPayloads({
70
98
  cfg,
71
99
  channel: outboundChannel,
72
100
  to: resolvedTarget.to,
101
+ agentId: params.agentId,
73
102
  accountId: params.accountId,
74
103
  payloads: normalizedPayloads,
104
+ replyToId: params.replyToId,
105
+ threadId: params.threadId,
75
106
  gifPlayback: params.gifPlayback,
76
107
  deps: params.deps,
77
108
  bestEffort: params.bestEffort,
78
109
  abortSignal: params.abortSignal,
110
+ silent: params.silent,
79
111
  mirror: params.mirror
80
112
  ? {
81
113
  ...params.mirror,
@@ -93,10 +125,8 @@ export async function sendMessage(params) {
93
125
  result: results.at(-1),
94
126
  };
95
127
  }
96
- const gateway = resolveGatewayOptions(params.gateway);
97
- const result = await callGateway({
98
- url: gateway.url,
99
- token: gateway.token,
128
+ const result = await callMessageGateway({
129
+ gateway: params.gateway,
100
130
  method: "send",
101
131
  params: {
102
132
  to: params.to,
@@ -109,10 +139,6 @@ export async function sendMessage(params) {
109
139
  sessionKey: params.mirror?.sessionKey,
110
140
  idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey(),
111
141
  },
112
- timeoutMs: gateway.timeoutMs,
113
- clientName: gateway.clientName,
114
- clientDisplayName: gateway.clientDisplayName,
115
- mode: gateway.mode,
116
142
  });
117
143
  return {
118
144
  channel,
@@ -125,19 +151,15 @@ export async function sendMessage(params) {
125
151
  }
126
152
  export async function sendPoll(params) {
127
153
  const cfg = params.cfg ?? loadConfig();
128
- const channel = params.channel?.trim()
129
- ? normalizeChannelId(params.channel)
130
- : (await resolveMessageChannelSelection({ cfg })).channel;
131
- if (!channel) {
132
- throw new Error(`Unknown channel: ${params.channel}`);
133
- }
154
+ const channel = await resolveRequiredChannel({ cfg, channel: params.channel });
134
155
  const pollInput = {
135
156
  question: params.question,
136
157
  options: params.options,
137
158
  maxSelections: params.maxSelections,
159
+ durationSeconds: params.durationSeconds,
138
160
  durationHours: params.durationHours,
139
161
  };
140
- const plugin = getChannelPlugin(channel);
162
+ const plugin = resolveRequiredPlugin(channel);
141
163
  const outbound = plugin?.outbound;
142
164
  if (!outbound?.sendPoll) {
143
165
  throw new Error(`Unsupported poll channel: ${channel}`);
@@ -152,29 +174,29 @@ export async function sendPoll(params) {
152
174
  question: normalized.question,
153
175
  options: normalized.options,
154
176
  maxSelections: normalized.maxSelections,
177
+ durationSeconds: normalized.durationSeconds ?? null,
155
178
  durationHours: normalized.durationHours ?? null,
156
179
  via: "gateway",
157
180
  dryRun: true,
158
181
  };
159
182
  }
160
- const gateway = resolveGatewayOptions(params.gateway);
161
- const result = await callGateway({
162
- url: gateway.url,
163
- token: gateway.token,
183
+ const result = await callMessageGateway({
184
+ gateway: params.gateway,
164
185
  method: "poll",
165
186
  params: {
166
187
  to: params.to,
167
188
  question: normalized.question,
168
189
  options: normalized.options,
169
190
  maxSelections: normalized.maxSelections,
191
+ durationSeconds: normalized.durationSeconds,
170
192
  durationHours: normalized.durationHours,
193
+ threadId: params.threadId,
194
+ silent: params.silent,
195
+ isAnonymous: params.isAnonymous,
171
196
  channel,
197
+ accountId: params.accountId,
172
198
  idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey(),
173
199
  },
174
- timeoutMs: gateway.timeoutMs,
175
- clientName: gateway.clientName,
176
- clientDisplayName: gateway.clientDisplayName,
177
- mode: gateway.mode,
178
200
  });
179
201
  return {
180
202
  channel,
@@ -182,6 +204,7 @@ export async function sendPoll(params) {
182
204
  question: normalized.question,
183
205
  options: normalized.options,
184
206
  maxSelections: normalized.maxSelections,
207
+ durationSeconds: normalized.durationSeconds ?? null,
185
208
  durationHours: normalized.durationHours ?? null,
186
209
  via: "gateway",
187
210
  result,
@@ -1,5 +1,5 @@
1
+ import { getChannelMessageAdapter, } from "./channel-adapters.js";
1
2
  import { normalizeTargetForProvider } from "./target-normalization.js";
2
- import { getChannelMessageAdapter } from "./channel-adapters.js";
3
3
  import { formatTargetDisplay, lookupDirectoryDisplay } from "./target-resolver.js";
4
4
  const CONTEXT_GUARDED_ACTIONS = new Set([
5
5
  "send",
@@ -21,42 +21,52 @@ const CONTEXT_MARKER_ACTIONS = new Set([
21
21
  "sticker",
22
22
  ]);
23
23
  function resolveContextGuardTarget(action, params) {
24
- if (!CONTEXT_GUARDED_ACTIONS.has(action))
24
+ if (!CONTEXT_GUARDED_ACTIONS.has(action)) {
25
25
  return undefined;
26
+ }
26
27
  if (action === "thread-reply" || action === "thread-create") {
27
- if (typeof params.channelId === "string")
28
+ if (typeof params.channelId === "string") {
28
29
  return params.channelId;
29
- if (typeof params.to === "string")
30
+ }
31
+ if (typeof params.to === "string") {
30
32
  return params.to;
33
+ }
31
34
  return undefined;
32
35
  }
33
- if (typeof params.to === "string")
36
+ if (typeof params.to === "string") {
34
37
  return params.to;
35
- if (typeof params.channelId === "string")
38
+ }
39
+ if (typeof params.channelId === "string") {
36
40
  return params.channelId;
41
+ }
37
42
  return undefined;
38
43
  }
39
44
  function normalizeTarget(channel, raw) {
40
- return normalizeTargetForProvider(channel, raw) ?? raw.trim().toLowerCase();
45
+ return normalizeTargetForProvider(channel, raw) ?? raw.trim();
41
46
  }
42
47
  function isCrossContextTarget(params) {
43
48
  const currentTarget = params.toolContext?.currentChannelId?.trim();
44
- if (!currentTarget)
49
+ if (!currentTarget) {
45
50
  return false;
51
+ }
46
52
  const normalizedTarget = normalizeTarget(params.channel, params.target);
47
53
  const normalizedCurrent = normalizeTarget(params.channel, currentTarget);
48
- if (!normalizedTarget || !normalizedCurrent)
54
+ if (!normalizedTarget || !normalizedCurrent) {
49
55
  return false;
56
+ }
50
57
  return normalizedTarget !== normalizedCurrent;
51
58
  }
52
59
  export function enforceCrossContextPolicy(params) {
53
60
  const currentTarget = params.toolContext?.currentChannelId?.trim();
54
- if (!currentTarget)
61
+ if (!currentTarget) {
55
62
  return;
56
- if (!CONTEXT_GUARDED_ACTIONS.has(params.action))
63
+ }
64
+ if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) {
57
65
  return;
58
- if (params.cfg.tools?.message?.allowCrossContextSend)
66
+ }
67
+ if (params.cfg.tools?.message?.allowCrossContextSend) {
59
68
  return;
69
+ }
60
70
  const currentProvider = params.toolContext?.currentChannelProvider;
61
71
  const allowWithinProvider = params.cfg.tools?.message?.crossContext?.allowWithinProvider !== false;
62
72
  const allowAcrossProviders = params.cfg.tools?.message?.crossContext?.allowAcrossProviders === true;
@@ -66,27 +76,33 @@ export function enforceCrossContextPolicy(params) {
66
76
  }
67
77
  return;
68
78
  }
69
- if (allowWithinProvider)
79
+ if (allowWithinProvider) {
70
80
  return;
81
+ }
71
82
  const target = resolveContextGuardTarget(params.action, params.args);
72
- if (!target)
83
+ if (!target) {
73
84
  return;
85
+ }
74
86
  if (!isCrossContextTarget({ channel: params.channel, target, toolContext: params.toolContext })) {
75
87
  return;
76
88
  }
77
89
  throw new Error(`Cross-context messaging denied: action=${params.action} target="${target}" while bound to "${currentTarget}" (channel=${params.channel}).`);
78
90
  }
79
91
  export async function buildCrossContextDecoration(params) {
80
- if (!params.toolContext?.currentChannelId)
92
+ if (!params.toolContext?.currentChannelId) {
81
93
  return null;
94
+ }
82
95
  // Skip decoration for direct tool sends (agent composing, not forwarding)
83
- if (params.toolContext.skipCrossContextDecoration)
96
+ if (params.toolContext.skipCrossContextDecoration) {
84
97
  return null;
85
- if (!isCrossContextTarget(params))
98
+ }
99
+ if (!isCrossContextTarget(params)) {
86
100
  return null;
101
+ }
87
102
  const markerConfig = params.cfg.tools?.message?.crossContext?.marker;
88
- if (markerConfig?.enabled === false)
103
+ if (markerConfig?.enabled === false) {
89
104
  return null;
105
+ }
90
106
  const currentName = (await lookupDirectoryDisplay({
91
107
  cfg: params.cfg,
92
108
  channel: params.channel,
@@ -104,19 +120,30 @@ export async function buildCrossContextDecoration(params) {
104
120
  const prefix = prefixTemplate.replaceAll("{channel}", originLabel);
105
121
  const suffix = suffixTemplate.replaceAll("{channel}", originLabel);
106
122
  const adapter = getChannelMessageAdapter(params.channel);
107
- const embeds = adapter.supportsEmbeds
108
- ? (adapter.buildCrossContextEmbeds?.(originLabel) ?? undefined)
123
+ const componentsBuilder = adapter.supportsComponentsV2
124
+ ? adapter.buildCrossContextComponents
125
+ ? (message) => adapter.buildCrossContextComponents({
126
+ originLabel,
127
+ message,
128
+ cfg: params.cfg,
129
+ accountId: params.accountId ?? undefined,
130
+ })
131
+ : undefined
109
132
  : undefined;
110
- return { prefix, suffix, embeds };
133
+ return { prefix, suffix, componentsBuilder };
111
134
  }
112
135
  export function shouldApplyCrossContextMarker(action) {
113
136
  return CONTEXT_MARKER_ACTIONS.has(action);
114
137
  }
115
138
  export function applyCrossContextDecoration(params) {
116
- const useEmbeds = params.preferEmbeds && params.decoration.embeds?.length;
117
- if (useEmbeds) {
118
- return { message: params.message, embeds: params.decoration.embeds, usedEmbeds: true };
139
+ const useComponents = params.preferComponents && params.decoration.componentsBuilder;
140
+ if (useComponents) {
141
+ return {
142
+ message: params.message,
143
+ componentsBuilder: params.decoration.componentsBuilder,
144
+ usedComponents: true,
145
+ };
119
146
  }
120
147
  const message = `${params.decoration.prefix}${params.message}${params.decoration.suffix}`;
121
- return { message, usedEmbeds: false };
148
+ return { message, usedComponents: false };
122
149
  }
@@ -1,75 +1,68 @@
1
1
  import { dispatchChannelMessageAction } from "../../channels/plugins/message-actions.js";
2
2
  import { appendAssistantMessageToSessionTranscript } from "../../config/sessions.js";
3
+ import { throwIfAborted } from "./abort.js";
3
4
  import { sendMessage, sendPoll } from "./message.js";
4
- function extractToolPayload(result) {
5
- if (result.details !== undefined)
6
- return result.details;
7
- const textBlock = Array.isArray(result.content)
8
- ? result.content.find((block) => block &&
9
- typeof block === "object" &&
10
- block.type === "text" &&
11
- typeof block.text === "string")
12
- : undefined;
13
- const text = textBlock?.text;
14
- if (text) {
15
- try {
16
- return JSON.parse(text);
17
- }
18
- catch {
19
- return text;
20
- }
5
+ import { extractToolPayload } from "./tool-payload.js";
6
+ async function tryHandleWithPluginAction(params) {
7
+ if (params.ctx.dryRun) {
8
+ return null;
21
9
  }
22
- return result.content ?? result;
23
- }
24
- function throwIfAborted(abortSignal) {
25
- if (abortSignal?.aborted) {
26
- const err = new Error("Message send aborted");
27
- err.name = "AbortError";
28
- throw err;
10
+ const handled = await dispatchChannelMessageAction({
11
+ channel: params.ctx.channel,
12
+ action: params.action,
13
+ cfg: params.ctx.cfg,
14
+ params: params.ctx.params,
15
+ accountId: params.ctx.accountId ?? undefined,
16
+ gateway: params.ctx.gateway,
17
+ toolContext: params.ctx.toolContext,
18
+ dryRun: params.ctx.dryRun,
19
+ });
20
+ if (!handled) {
21
+ return null;
29
22
  }
23
+ await params.onHandled?.();
24
+ return {
25
+ handledBy: "plugin",
26
+ payload: extractToolPayload(handled),
27
+ toolResult: handled,
28
+ };
30
29
  }
31
30
  export async function executeSendAction(params) {
32
31
  throwIfAborted(params.ctx.abortSignal);
33
- if (!params.ctx.dryRun) {
34
- const handled = await dispatchChannelMessageAction({
35
- channel: params.ctx.channel,
36
- action: "send",
37
- cfg: params.ctx.cfg,
38
- params: params.ctx.params,
39
- accountId: params.ctx.accountId ?? undefined,
40
- gateway: params.ctx.gateway,
41
- toolContext: params.ctx.toolContext,
42
- dryRun: params.ctx.dryRun,
43
- });
44
- if (handled) {
45
- if (params.ctx.mirror) {
46
- const mirrorText = params.ctx.mirror.text ?? params.message;
47
- const mirrorMediaUrls = params.ctx.mirror.mediaUrls ??
48
- params.mediaUrls ??
49
- (params.mediaUrl ? [params.mediaUrl] : undefined);
50
- await appendAssistantMessageToSessionTranscript({
51
- agentId: params.ctx.mirror.agentId,
52
- sessionKey: params.ctx.mirror.sessionKey,
53
- text: mirrorText,
54
- mediaUrls: mirrorMediaUrls,
55
- });
32
+ const pluginHandled = await tryHandleWithPluginAction({
33
+ ctx: params.ctx,
34
+ action: "send",
35
+ onHandled: async () => {
36
+ if (!params.ctx.mirror) {
37
+ return;
56
38
  }
57
- return {
58
- handledBy: "plugin",
59
- payload: extractToolPayload(handled),
60
- toolResult: handled,
61
- };
62
- }
39
+ const mirrorText = params.ctx.mirror.text ?? params.message;
40
+ const mirrorMediaUrls = params.ctx.mirror.mediaUrls ??
41
+ params.mediaUrls ??
42
+ (params.mediaUrl ? [params.mediaUrl] : undefined);
43
+ await appendAssistantMessageToSessionTranscript({
44
+ agentId: params.ctx.mirror.agentId,
45
+ sessionKey: params.ctx.mirror.sessionKey,
46
+ text: mirrorText,
47
+ mediaUrls: mirrorMediaUrls,
48
+ });
49
+ },
50
+ });
51
+ if (pluginHandled) {
52
+ return pluginHandled;
63
53
  }
64
54
  throwIfAborted(params.ctx.abortSignal);
65
55
  const result = await sendMessage({
66
56
  cfg: params.ctx.cfg,
67
57
  to: params.to,
68
58
  content: params.message,
59
+ agentId: params.ctx.agentId,
69
60
  mediaUrl: params.mediaUrl || undefined,
70
61
  mediaUrls: params.mediaUrls,
71
62
  channel: params.ctx.channel || undefined,
72
63
  accountId: params.ctx.accountId ?? undefined,
64
+ replyToId: params.replyToId,
65
+ threadId: params.threadId,
73
66
  gifPlayback: params.gifPlayback,
74
67
  dryRun: params.ctx.dryRun,
75
68
  bestEffort: params.bestEffort ?? undefined,
@@ -77,6 +70,7 @@ export async function executeSendAction(params) {
77
70
  gateway: params.ctx.gateway,
78
71
  mirror: params.ctx.mirror,
79
72
  abortSignal: params.ctx.abortSignal,
73
+ silent: params.ctx.silent,
80
74
  });
81
75
  return {
82
76
  handledBy: "core",
@@ -85,24 +79,12 @@ export async function executeSendAction(params) {
85
79
  };
86
80
  }
87
81
  export async function executePollAction(params) {
88
- if (!params.ctx.dryRun) {
89
- const handled = await dispatchChannelMessageAction({
90
- channel: params.ctx.channel,
91
- action: "poll",
92
- cfg: params.ctx.cfg,
93
- params: params.ctx.params,
94
- accountId: params.ctx.accountId ?? undefined,
95
- gateway: params.ctx.gateway,
96
- toolContext: params.ctx.toolContext,
97
- dryRun: params.ctx.dryRun,
98
- });
99
- if (handled) {
100
- return {
101
- handledBy: "plugin",
102
- payload: extractToolPayload(handled),
103
- toolResult: handled,
104
- };
105
- }
82
+ const pluginHandled = await tryHandleWithPluginAction({
83
+ ctx: params.ctx,
84
+ action: "poll",
85
+ });
86
+ if (pluginHandled) {
87
+ return pluginHandled;
106
88
  }
107
89
  const result = await sendPoll({
108
90
  cfg: params.ctx.cfg,
@@ -110,8 +92,13 @@ export async function executePollAction(params) {
110
92
  question: params.question,
111
93
  options: params.options,
112
94
  maxSelections: params.maxSelections,
95
+ durationSeconds: params.durationSeconds ?? undefined,
113
96
  durationHours: params.durationHours ?? undefined,
114
97
  channel: params.ctx.channel,
98
+ accountId: params.ctx.accountId ?? undefined,
99
+ threadId: params.threadId ?? undefined,
100
+ silent: params.ctx.silent ?? undefined,
101
+ isAnonymous: params.isAnonymous ?? undefined,
115
102
  dryRun: params.ctx.dryRun,
116
103
  gateway: params.ctx.gateway,
117
104
  });
@@ -17,3 +17,13 @@ export function pruneExpiredPending(pendingById, nowMs, ttlMs) {
17
17
  }
18
18
  }
19
19
  }
20
+ export async function upsertPendingPairingRequest(params) {
21
+ const existing = Object.values(params.pendingById).find(params.isExisting);
22
+ if (existing) {
23
+ return { status: "pending", request: existing, created: false };
24
+ }
25
+ const request = params.createRequest(params.isRepair);
26
+ params.pendingById[request.requestId] = request;
27
+ await params.persist();
28
+ return { status: "pending", request, created: true };
29
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Strict plain-object guard (excludes arrays and host objects).
3
+ */
4
+ export function isPlainObject(value) {
5
+ return (typeof value === "object" &&
6
+ value !== null &&
7
+ !Array.isArray(value) &&
8
+ Object.prototype.toString.call(value) === "[object Object]");
9
+ }