@poolzin/pool-bot 2026.2.24 → 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.
- package/CHANGELOG.md +21 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/package.json +1 -1
|
@@ -1,55 +1,46 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { readNumberParam, readStringArrayParam, readStringParam, } from "../../agents/tools/common.js";
|
|
4
1
|
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
|
|
2
|
+
import { readNumberParam, readStringArrayParam, readStringParam, } from "../../agents/tools/common.js";
|
|
5
3
|
import { parseReplyDirectives } from "../../auto-reply/reply/reply-directives.js";
|
|
6
4
|
import { dispatchChannelMessageAction } from "../../channels/plugins/message-actions.js";
|
|
7
5
|
import { isDeliverableMessageChannel, normalizeMessageChannel, } from "../../utils/message-channel.js";
|
|
6
|
+
import { throwIfAborted } from "./abort.js";
|
|
8
7
|
import { listConfiguredMessageChannels, resolveMessageChannelSelection, } from "./channel-selection.js";
|
|
9
8
|
import { applyTargetToParams } from "./channel-target.js";
|
|
10
|
-
import {
|
|
9
|
+
import { hydrateSendAttachmentParams, hydrateSetGroupIconParams, normalizeSandboxMediaList, normalizeSandboxMediaParams, parseButtonsParam, parseCardParam, parseComponentsParam, readBooleanParam, resolveSlackAutoThreadId, resolveTelegramAutoThreadId, } from "./message-action-params.js";
|
|
10
|
+
import { actionHasTarget, actionRequiresTarget } from "./message-action-spec.js";
|
|
11
11
|
import { applyCrossContextDecoration, buildCrossContextDecoration, enforceCrossContextPolicy, shouldApplyCrossContextMarker, } from "./outbound-policy.js";
|
|
12
12
|
import { executePollAction, executeSendAction } from "./outbound-send-service.js";
|
|
13
|
-
import {
|
|
13
|
+
import { ensureOutboundSessionEntry, resolveOutboundSessionRoute } from "./outbound-session.js";
|
|
14
14
|
import { resolveChannelTarget } from "./target-resolver.js";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import { throwIfAborted } from "./abort.js";
|
|
21
|
-
export function getToolResult(result) {
|
|
22
|
-
return "toolResult" in result ? result.toolResult : undefined;
|
|
23
|
-
}
|
|
24
|
-
function extractToolPayload(result) {
|
|
25
|
-
if (result.details !== undefined)
|
|
26
|
-
return result.details;
|
|
27
|
-
const textBlock = Array.isArray(result.content)
|
|
28
|
-
? result.content.find((block) => block &&
|
|
29
|
-
typeof block === "object" &&
|
|
30
|
-
block.type === "text" &&
|
|
31
|
-
typeof block.text === "string")
|
|
15
|
+
import { extractToolPayload } from "./tool-payload.js";
|
|
16
|
+
function resolveAndApplyOutboundThreadId(params, ctx) {
|
|
17
|
+
const threadId = readStringParam(params, "threadId");
|
|
18
|
+
const slackAutoThreadId = ctx.allowSlackAutoThread && ctx.channel === "slack" && !threadId
|
|
19
|
+
? resolveSlackAutoThreadId({ to: ctx.to, toolContext: ctx.toolContext })
|
|
32
20
|
: undefined;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
const telegramAutoThreadId = ctx.channel === "telegram" && !threadId
|
|
22
|
+
? resolveTelegramAutoThreadId({ to: ctx.to, toolContext: ctx.toolContext })
|
|
23
|
+
: undefined;
|
|
24
|
+
const resolved = threadId ?? slackAutoThreadId ?? telegramAutoThreadId;
|
|
25
|
+
// Write auto-resolved threadId back into params so downstream dispatch
|
|
26
|
+
// (plugin `readStringParam(params, "threadId")`) picks it up.
|
|
27
|
+
if (resolved && !params.threadId) {
|
|
28
|
+
params.threadId = resolved;
|
|
41
29
|
}
|
|
42
|
-
return
|
|
30
|
+
return resolved ?? undefined;
|
|
31
|
+
}
|
|
32
|
+
export function getToolResult(result) {
|
|
33
|
+
return "toolResult" in result ? result.toolResult : undefined;
|
|
43
34
|
}
|
|
44
|
-
function applyCrossContextMessageDecoration({ params, message, decoration,
|
|
35
|
+
function applyCrossContextMessageDecoration({ params, message, decoration, preferComponents, }) {
|
|
45
36
|
const applied = applyCrossContextDecoration({
|
|
46
37
|
message,
|
|
47
38
|
decoration,
|
|
48
|
-
|
|
39
|
+
preferComponents,
|
|
49
40
|
});
|
|
50
41
|
params.message = applied.message;
|
|
51
|
-
if (applied.
|
|
52
|
-
params.
|
|
42
|
+
if (applied.componentsBuilder) {
|
|
43
|
+
params.components = applied.componentsBuilder;
|
|
53
44
|
}
|
|
54
45
|
return applied.message;
|
|
55
46
|
}
|
|
@@ -64,279 +55,16 @@ async function maybeApplyCrossContextMarker(params) {
|
|
|
64
55
|
toolContext: params.toolContext,
|
|
65
56
|
accountId: params.accountId ?? undefined,
|
|
66
57
|
});
|
|
67
|
-
if (!decoration)
|
|
58
|
+
if (!decoration) {
|
|
68
59
|
return params.message;
|
|
60
|
+
}
|
|
69
61
|
return applyCrossContextMessageDecoration({
|
|
70
62
|
params: params.args,
|
|
71
63
|
message: params.message,
|
|
72
64
|
decoration,
|
|
73
|
-
|
|
65
|
+
preferComponents: params.preferComponents,
|
|
74
66
|
});
|
|
75
67
|
}
|
|
76
|
-
function readBooleanParam(params, key) {
|
|
77
|
-
const raw = params[key];
|
|
78
|
-
if (typeof raw === "boolean")
|
|
79
|
-
return raw;
|
|
80
|
-
if (typeof raw === "string") {
|
|
81
|
-
const trimmed = raw.trim().toLowerCase();
|
|
82
|
-
if (trimmed === "true")
|
|
83
|
-
return true;
|
|
84
|
-
if (trimmed === "false")
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
function resolveSlackAutoThreadId(params) {
|
|
90
|
-
const context = params.toolContext;
|
|
91
|
-
if (!context?.currentThreadTs || !context.currentChannelId)
|
|
92
|
-
return undefined;
|
|
93
|
-
// Only mirror auto-threading when Slack would reply in the active thread for this channel.
|
|
94
|
-
if (context.replyToMode !== "all" && context.replyToMode !== "first")
|
|
95
|
-
return undefined;
|
|
96
|
-
const parsedTarget = parseSlackTarget(params.to, { defaultKind: "channel" });
|
|
97
|
-
if (!parsedTarget || parsedTarget.kind !== "channel")
|
|
98
|
-
return undefined;
|
|
99
|
-
if (parsedTarget.id.toLowerCase() !== context.currentChannelId.toLowerCase())
|
|
100
|
-
return undefined;
|
|
101
|
-
if (context.replyToMode === "first" && context.hasRepliedRef?.value)
|
|
102
|
-
return undefined;
|
|
103
|
-
return context.currentThreadTs;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Auto-inject Telegram forum topic thread ID when the message tool targets
|
|
107
|
-
* the same chat the session originated from.
|
|
108
|
-
*/
|
|
109
|
-
function resolveTelegramAutoThreadId(params) {
|
|
110
|
-
const context = params.toolContext;
|
|
111
|
-
if (!context?.currentThreadTs || !context.currentChannelId)
|
|
112
|
-
return undefined;
|
|
113
|
-
const parsedTo = parseTelegramTarget(params.to);
|
|
114
|
-
const parsedChannel = parseTelegramTarget(context.currentChannelId);
|
|
115
|
-
if (parsedTo.chatId.toLowerCase() !== parsedChannel.chatId.toLowerCase())
|
|
116
|
-
return undefined;
|
|
117
|
-
return context.currentThreadTs;
|
|
118
|
-
}
|
|
119
|
-
function resolveAttachmentMaxBytes(params) {
|
|
120
|
-
const accountId = typeof params.accountId === "string" ? params.accountId.trim() : "";
|
|
121
|
-
const channelCfg = params.cfg.channels?.[params.channel];
|
|
122
|
-
const channelObj = channelCfg && typeof channelCfg === "object"
|
|
123
|
-
? channelCfg
|
|
124
|
-
: undefined;
|
|
125
|
-
const channelMediaMax = typeof channelObj?.mediaMaxMb === "number" ? channelObj.mediaMaxMb : undefined;
|
|
126
|
-
const accountsObj = channelObj?.accounts && typeof channelObj.accounts === "object"
|
|
127
|
-
? channelObj.accounts
|
|
128
|
-
: undefined;
|
|
129
|
-
const accountCfg = accountId && accountsObj ? accountsObj[accountId] : undefined;
|
|
130
|
-
const accountMediaMax = accountCfg && typeof accountCfg === "object"
|
|
131
|
-
? accountCfg.mediaMaxMb
|
|
132
|
-
: undefined;
|
|
133
|
-
const limitMb = (typeof accountMediaMax === "number" ? accountMediaMax : undefined) ??
|
|
134
|
-
channelMediaMax ??
|
|
135
|
-
params.cfg.agents?.defaults?.mediaMaxMb;
|
|
136
|
-
return typeof limitMb === "number" ? limitMb * 1024 * 1024 : undefined;
|
|
137
|
-
}
|
|
138
|
-
function inferAttachmentFilename(params) {
|
|
139
|
-
const mediaHint = params.mediaHint?.trim();
|
|
140
|
-
if (mediaHint) {
|
|
141
|
-
try {
|
|
142
|
-
if (mediaHint.startsWith("file://")) {
|
|
143
|
-
const filePath = fileURLToPath(mediaHint);
|
|
144
|
-
const base = path.basename(filePath);
|
|
145
|
-
if (base)
|
|
146
|
-
return base;
|
|
147
|
-
}
|
|
148
|
-
else if (/^https?:\/\//i.test(mediaHint)) {
|
|
149
|
-
const url = new URL(mediaHint);
|
|
150
|
-
const base = path.basename(url.pathname);
|
|
151
|
-
if (base)
|
|
152
|
-
return base;
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
const base = path.basename(mediaHint);
|
|
156
|
-
if (base)
|
|
157
|
-
return base;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// fall through to content-type based default
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const ext = params.contentType ? extensionForMime(params.contentType) : undefined;
|
|
165
|
-
return ext ? `attachment${ext}` : "attachment";
|
|
166
|
-
}
|
|
167
|
-
function normalizeBase64Payload(params) {
|
|
168
|
-
if (!params.base64)
|
|
169
|
-
return { base64: params.base64, contentType: params.contentType };
|
|
170
|
-
const match = /^data:([^;]+);base64,(.*)$/i.exec(params.base64.trim());
|
|
171
|
-
if (!match)
|
|
172
|
-
return { base64: params.base64, contentType: params.contentType };
|
|
173
|
-
const [, mime, payload] = match;
|
|
174
|
-
return {
|
|
175
|
-
base64: payload,
|
|
176
|
-
contentType: params.contentType ?? mime,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
async function normalizeSandboxMediaParams(params) {
|
|
180
|
-
const sandboxRoot = params.sandboxRoot?.trim();
|
|
181
|
-
const mediaKeys = ["media", "path", "filePath"];
|
|
182
|
-
for (const key of mediaKeys) {
|
|
183
|
-
const raw = readStringParam(params.args, key, { trim: false });
|
|
184
|
-
if (!raw)
|
|
185
|
-
continue;
|
|
186
|
-
assertMediaNotDataUrl(raw);
|
|
187
|
-
if (!sandboxRoot)
|
|
188
|
-
continue;
|
|
189
|
-
const normalized = await resolveSandboxedMediaSource({ media: raw, sandboxRoot });
|
|
190
|
-
if (normalized !== raw) {
|
|
191
|
-
params.args[key] = normalized;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
async function normalizeSandboxMediaList(params) {
|
|
196
|
-
const sandboxRoot = params.sandboxRoot?.trim();
|
|
197
|
-
const normalized = [];
|
|
198
|
-
const seen = new Set();
|
|
199
|
-
for (const value of params.values) {
|
|
200
|
-
const raw = value?.trim();
|
|
201
|
-
if (!raw)
|
|
202
|
-
continue;
|
|
203
|
-
assertMediaNotDataUrl(raw);
|
|
204
|
-
const resolved = sandboxRoot
|
|
205
|
-
? await resolveSandboxedMediaSource({ media: raw, sandboxRoot })
|
|
206
|
-
: raw;
|
|
207
|
-
if (seen.has(resolved))
|
|
208
|
-
continue;
|
|
209
|
-
seen.add(resolved);
|
|
210
|
-
normalized.push(resolved);
|
|
211
|
-
}
|
|
212
|
-
return normalized;
|
|
213
|
-
}
|
|
214
|
-
async function hydrateSetGroupIconParams(params) {
|
|
215
|
-
if (params.action !== "setGroupIcon")
|
|
216
|
-
return;
|
|
217
|
-
const mediaHint = readStringParam(params.args, "media", { trim: false });
|
|
218
|
-
const fileHint = readStringParam(params.args, "path", { trim: false }) ??
|
|
219
|
-
readStringParam(params.args, "filePath", { trim: false });
|
|
220
|
-
const contentTypeParam = readStringParam(params.args, "contentType") ?? readStringParam(params.args, "mimeType");
|
|
221
|
-
const rawBuffer = readStringParam(params.args, "buffer", { trim: false });
|
|
222
|
-
const normalized = normalizeBase64Payload({
|
|
223
|
-
base64: rawBuffer,
|
|
224
|
-
contentType: contentTypeParam ?? undefined,
|
|
225
|
-
});
|
|
226
|
-
if (normalized.base64 !== rawBuffer && normalized.base64) {
|
|
227
|
-
params.args.buffer = normalized.base64;
|
|
228
|
-
if (normalized.contentType && !contentTypeParam) {
|
|
229
|
-
params.args.contentType = normalized.contentType;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const filename = readStringParam(params.args, "filename");
|
|
233
|
-
const mediaSource = mediaHint ?? fileHint;
|
|
234
|
-
if (!params.dryRun && !readStringParam(params.args, "buffer", { trim: false }) && mediaSource) {
|
|
235
|
-
const maxBytes = resolveAttachmentMaxBytes({
|
|
236
|
-
cfg: params.cfg,
|
|
237
|
-
channel: params.channel,
|
|
238
|
-
accountId: params.accountId,
|
|
239
|
-
});
|
|
240
|
-
const media = await loadWebMedia(mediaSource, maxBytes);
|
|
241
|
-
params.args.buffer = media.buffer.toString("base64");
|
|
242
|
-
if (!contentTypeParam && media.contentType) {
|
|
243
|
-
params.args.contentType = media.contentType;
|
|
244
|
-
}
|
|
245
|
-
if (!filename) {
|
|
246
|
-
params.args.filename = inferAttachmentFilename({
|
|
247
|
-
mediaHint: media.fileName ?? mediaSource,
|
|
248
|
-
contentType: media.contentType ?? contentTypeParam ?? undefined,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else if (!filename) {
|
|
253
|
-
params.args.filename = inferAttachmentFilename({
|
|
254
|
-
mediaHint: mediaSource,
|
|
255
|
-
contentType: contentTypeParam ?? undefined,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
async function hydrateSendAttachmentParams(params) {
|
|
260
|
-
if (params.action !== "sendAttachment")
|
|
261
|
-
return;
|
|
262
|
-
const mediaHint = readStringParam(params.args, "media", { trim: false });
|
|
263
|
-
const fileHint = readStringParam(params.args, "path", { trim: false }) ??
|
|
264
|
-
readStringParam(params.args, "filePath", { trim: false });
|
|
265
|
-
const contentTypeParam = readStringParam(params.args, "contentType") ?? readStringParam(params.args, "mimeType");
|
|
266
|
-
const caption = readStringParam(params.args, "caption", { allowEmpty: true })?.trim();
|
|
267
|
-
const message = readStringParam(params.args, "message", { allowEmpty: true })?.trim();
|
|
268
|
-
if (!caption && message)
|
|
269
|
-
params.args.caption = message;
|
|
270
|
-
const rawBuffer = readStringParam(params.args, "buffer", { trim: false });
|
|
271
|
-
const normalized = normalizeBase64Payload({
|
|
272
|
-
base64: rawBuffer,
|
|
273
|
-
contentType: contentTypeParam ?? undefined,
|
|
274
|
-
});
|
|
275
|
-
if (normalized.base64 !== rawBuffer && normalized.base64) {
|
|
276
|
-
params.args.buffer = normalized.base64;
|
|
277
|
-
if (normalized.contentType && !contentTypeParam) {
|
|
278
|
-
params.args.contentType = normalized.contentType;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
const filename = readStringParam(params.args, "filename");
|
|
282
|
-
const mediaSource = mediaHint ?? fileHint;
|
|
283
|
-
if (!params.dryRun && !readStringParam(params.args, "buffer", { trim: false }) && mediaSource) {
|
|
284
|
-
const maxBytes = resolveAttachmentMaxBytes({
|
|
285
|
-
cfg: params.cfg,
|
|
286
|
-
channel: params.channel,
|
|
287
|
-
accountId: params.accountId,
|
|
288
|
-
});
|
|
289
|
-
const media = await loadWebMedia(mediaSource, maxBytes);
|
|
290
|
-
params.args.buffer = media.buffer.toString("base64");
|
|
291
|
-
if (!contentTypeParam && media.contentType) {
|
|
292
|
-
params.args.contentType = media.contentType;
|
|
293
|
-
}
|
|
294
|
-
if (!filename) {
|
|
295
|
-
params.args.filename = inferAttachmentFilename({
|
|
296
|
-
mediaHint: media.fileName ?? mediaSource,
|
|
297
|
-
contentType: media.contentType ?? contentTypeParam ?? undefined,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else if (!filename) {
|
|
302
|
-
params.args.filename = inferAttachmentFilename({
|
|
303
|
-
mediaHint: mediaSource,
|
|
304
|
-
contentType: contentTypeParam ?? undefined,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
function parseButtonsParam(params) {
|
|
309
|
-
const raw = params.buttons;
|
|
310
|
-
if (typeof raw !== "string")
|
|
311
|
-
return;
|
|
312
|
-
const trimmed = raw.trim();
|
|
313
|
-
if (!trimmed) {
|
|
314
|
-
delete params.buttons;
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
try {
|
|
318
|
-
params.buttons = JSON.parse(trimmed);
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
throw new Error("--buttons must be valid JSON");
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
function parseCardParam(params) {
|
|
325
|
-
const raw = params.card;
|
|
326
|
-
if (typeof raw !== "string")
|
|
327
|
-
return;
|
|
328
|
-
const trimmed = raw.trim();
|
|
329
|
-
if (!trimmed) {
|
|
330
|
-
delete params.card;
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
try {
|
|
334
|
-
params.card = JSON.parse(trimmed);
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
throw new Error("--card must be valid JSON");
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
68
|
async function resolveChannel(cfg, params) {
|
|
341
69
|
const channelHint = readStringParam(params, "channel");
|
|
342
70
|
const selection = await resolveMessageChannelSelection({
|
|
@@ -385,8 +113,9 @@ async function resolveActionTarget(params) {
|
|
|
385
113
|
return resolvedTarget;
|
|
386
114
|
}
|
|
387
115
|
function resolveGateway(input) {
|
|
388
|
-
if (!input.gateway)
|
|
116
|
+
if (!input.gateway) {
|
|
389
117
|
return undefined;
|
|
118
|
+
}
|
|
390
119
|
return {
|
|
391
120
|
url: input.gateway.url,
|
|
392
121
|
token: input.gateway.token,
|
|
@@ -426,8 +155,9 @@ async function handleBroadcastAction(input, params) {
|
|
|
426
155
|
channel: targetChannel,
|
|
427
156
|
input: target,
|
|
428
157
|
});
|
|
429
|
-
if (!resolved.ok)
|
|
158
|
+
if (!resolved.ok) {
|
|
430
159
|
throw resolved.error;
|
|
160
|
+
}
|
|
431
161
|
const sendResult = await runMessageAction({
|
|
432
162
|
...input,
|
|
433
163
|
action: "send",
|
|
@@ -445,8 +175,9 @@ async function handleBroadcastAction(input, params) {
|
|
|
445
175
|
});
|
|
446
176
|
}
|
|
447
177
|
catch (err) {
|
|
448
|
-
if (isAbortError(err))
|
|
178
|
+
if (isAbortError(err)) {
|
|
449
179
|
throw err;
|
|
180
|
+
}
|
|
450
181
|
results.push({
|
|
451
182
|
channel: targetChannel,
|
|
452
183
|
to: target,
|
|
@@ -458,7 +189,7 @@ async function handleBroadcastAction(input, params) {
|
|
|
458
189
|
}
|
|
459
190
|
return {
|
|
460
191
|
kind: "broadcast",
|
|
461
|
-
channel:
|
|
192
|
+
channel: targetChannels[0] ?? "discord",
|
|
462
193
|
action: "broadcast",
|
|
463
194
|
handledBy: input.dryRun ? "dry-run" : "core",
|
|
464
195
|
payload: { results },
|
|
@@ -475,31 +206,37 @@ async function handleSendAction(ctx) {
|
|
|
475
206
|
readStringParam(params, "path", { trim: false }) ??
|
|
476
207
|
readStringParam(params, "filePath", { trim: false });
|
|
477
208
|
const hasCard = params.card != null && typeof params.card === "object";
|
|
209
|
+
const hasComponents = params.components != null && typeof params.components === "object";
|
|
210
|
+
const caption = readStringParam(params, "caption", { allowEmpty: true }) ?? "";
|
|
478
211
|
let message = readStringParam(params, "message", {
|
|
479
|
-
required: !mediaHint && !hasCard,
|
|
212
|
+
required: !mediaHint && !hasCard && !hasComponents,
|
|
480
213
|
allowEmpty: true,
|
|
481
214
|
}) ?? "";
|
|
482
|
-
// Unescape literal \\n sequences that models sometimes emit in tool arguments.
|
|
483
215
|
if (message.includes("\\n")) {
|
|
484
216
|
message = message.replaceAll("\\n", "\n");
|
|
485
217
|
}
|
|
218
|
+
if (!message.trim() && caption.trim()) {
|
|
219
|
+
message = caption;
|
|
220
|
+
}
|
|
486
221
|
const parsed = parseReplyDirectives(message);
|
|
487
222
|
const mergedMediaUrls = [];
|
|
488
223
|
const seenMedia = new Set();
|
|
489
224
|
const pushMedia = (value) => {
|
|
490
225
|
const trimmed = value?.trim();
|
|
491
|
-
if (!trimmed)
|
|
226
|
+
if (!trimmed) {
|
|
492
227
|
return;
|
|
493
|
-
|
|
228
|
+
}
|
|
229
|
+
if (seenMedia.has(trimmed)) {
|
|
494
230
|
return;
|
|
231
|
+
}
|
|
495
232
|
seenMedia.add(trimmed);
|
|
496
233
|
mergedMediaUrls.push(trimmed);
|
|
497
234
|
};
|
|
498
235
|
pushMedia(mediaHint);
|
|
499
|
-
for (const url of parsed.mediaUrls ?? [])
|
|
236
|
+
for (const url of parsed.mediaUrls ?? []) {
|
|
500
237
|
pushMedia(url);
|
|
238
|
+
}
|
|
501
239
|
pushMedia(parsed.mediaUrl);
|
|
502
|
-
// Sandbox-validate and deduplicate media URLs.
|
|
503
240
|
const normalizedMediaUrls = await normalizeSandboxMediaList({
|
|
504
241
|
values: mergedMediaUrls,
|
|
505
242
|
sandboxRoot: input.sandboxRoot,
|
|
@@ -508,8 +245,9 @@ async function handleSendAction(ctx) {
|
|
|
508
245
|
mergedMediaUrls.push(...normalizedMediaUrls);
|
|
509
246
|
message = parsed.text;
|
|
510
247
|
params.message = message;
|
|
511
|
-
if (!params.replyTo && parsed.replyToId)
|
|
248
|
+
if (!params.replyTo && parsed.replyToId) {
|
|
512
249
|
params.replyTo = parsed.replyToId;
|
|
250
|
+
}
|
|
513
251
|
if (!params.media) {
|
|
514
252
|
// Use path/filePath if media not set, then fall back to parsed directives
|
|
515
253
|
params.media = mergedMediaUrls[0] || undefined;
|
|
@@ -523,25 +261,29 @@ async function handleSendAction(ctx) {
|
|
|
523
261
|
accountId,
|
|
524
262
|
args: params,
|
|
525
263
|
message,
|
|
526
|
-
|
|
264
|
+
preferComponents: true,
|
|
527
265
|
});
|
|
528
266
|
const mediaUrl = readStringParam(params, "media", { trim: false });
|
|
267
|
+
if (channel === "whatsapp") {
|
|
268
|
+
message = message.replace(/^(?:[ \t]*\r?\n)+/, "");
|
|
269
|
+
if (!message.trim()) {
|
|
270
|
+
message = "";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!message.trim() && !mediaUrl && mergedMediaUrls.length === 0 && !hasCard && !hasComponents) {
|
|
274
|
+
throw new Error("send requires text or media");
|
|
275
|
+
}
|
|
276
|
+
params.message = message;
|
|
529
277
|
const gifPlayback = readBooleanParam(params, "gifPlayback") ?? false;
|
|
530
278
|
const bestEffort = readBooleanParam(params, "bestEffort");
|
|
279
|
+
const silent = readBooleanParam(params, "silent");
|
|
531
280
|
const replyToId = readStringParam(params, "replyTo");
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
:
|
|
537
|
-
|
|
538
|
-
const telegramAutoThreadId = channel === "telegram" && !threadId
|
|
539
|
-
? resolveTelegramAutoThreadId({ to, toolContext: input.toolContext })
|
|
540
|
-
: undefined;
|
|
541
|
-
const resolvedThreadId = threadId ?? slackAutoThreadId ?? telegramAutoThreadId;
|
|
542
|
-
if (resolvedThreadId && !params.threadId) {
|
|
543
|
-
params.threadId = resolvedThreadId;
|
|
544
|
-
}
|
|
281
|
+
const resolvedThreadId = resolveAndApplyOutboundThreadId(params, {
|
|
282
|
+
channel,
|
|
283
|
+
to,
|
|
284
|
+
toolContext: input.toolContext,
|
|
285
|
+
allowSlackAutoThread: channel === "slack" && !replyToId,
|
|
286
|
+
});
|
|
545
287
|
const outboundRoute = agentId && !dryRun
|
|
546
288
|
? await resolveOutboundSessionRoute({
|
|
547
289
|
cfg,
|
|
@@ -563,6 +305,12 @@ async function handleSendAction(ctx) {
|
|
|
563
305
|
route: outboundRoute,
|
|
564
306
|
});
|
|
565
307
|
}
|
|
308
|
+
if (outboundRoute && !dryRun) {
|
|
309
|
+
params.__sessionKey = outboundRoute.sessionKey;
|
|
310
|
+
}
|
|
311
|
+
if (agentId) {
|
|
312
|
+
params.__agentId = agentId;
|
|
313
|
+
}
|
|
566
314
|
const mirrorMediaUrls = mergedMediaUrls.length > 0 ? mergedMediaUrls : mediaUrl ? [mediaUrl] : undefined;
|
|
567
315
|
throwIfAborted(abortSignal);
|
|
568
316
|
const send = await executeSendAction({
|
|
@@ -570,6 +318,7 @@ async function handleSendAction(ctx) {
|
|
|
570
318
|
cfg,
|
|
571
319
|
channel,
|
|
572
320
|
params,
|
|
321
|
+
agentId,
|
|
573
322
|
accountId: accountId ?? undefined,
|
|
574
323
|
gateway,
|
|
575
324
|
toolContext: input.toolContext,
|
|
@@ -584,6 +333,7 @@ async function handleSendAction(ctx) {
|
|
|
584
333
|
}
|
|
585
334
|
: undefined,
|
|
586
335
|
abortSignal,
|
|
336
|
+
silent: silent ?? undefined,
|
|
587
337
|
},
|
|
588
338
|
to,
|
|
589
339
|
message,
|
|
@@ -591,6 +341,8 @@ async function handleSendAction(ctx) {
|
|
|
591
341
|
mediaUrls: mergedMediaUrls.length ? mergedMediaUrls : undefined,
|
|
592
342
|
gifPlayback,
|
|
593
343
|
bestEffort: bestEffort ?? undefined,
|
|
344
|
+
replyToId: replyToId ?? undefined,
|
|
345
|
+
threadId: resolvedThreadId ?? undefined,
|
|
594
346
|
});
|
|
595
347
|
return {
|
|
596
348
|
kind: "send",
|
|
@@ -616,11 +368,33 @@ async function handlePollAction(ctx) {
|
|
|
616
368
|
if (options.length < 2) {
|
|
617
369
|
throw new Error("pollOption requires at least two values");
|
|
618
370
|
}
|
|
371
|
+
const silent = readBooleanParam(params, "silent");
|
|
619
372
|
const allowMultiselect = readBooleanParam(params, "pollMulti") ?? false;
|
|
373
|
+
const pollAnonymous = readBooleanParam(params, "pollAnonymous");
|
|
374
|
+
const pollPublic = readBooleanParam(params, "pollPublic");
|
|
375
|
+
if (pollAnonymous && pollPublic) {
|
|
376
|
+
throw new Error("pollAnonymous and pollPublic are mutually exclusive");
|
|
377
|
+
}
|
|
378
|
+
const isAnonymous = pollAnonymous ? true : pollPublic ? false : undefined;
|
|
620
379
|
const durationHours = readNumberParam(params, "pollDurationHours", {
|
|
621
380
|
integer: true,
|
|
622
381
|
});
|
|
382
|
+
const durationSeconds = readNumberParam(params, "pollDurationSeconds", {
|
|
383
|
+
integer: true,
|
|
384
|
+
});
|
|
623
385
|
const maxSelections = allowMultiselect ? Math.max(2, options.length) : 1;
|
|
386
|
+
if (durationSeconds !== undefined && channel !== "telegram") {
|
|
387
|
+
throw new Error("pollDurationSeconds is only supported for Telegram polls");
|
|
388
|
+
}
|
|
389
|
+
if (isAnonymous !== undefined && channel !== "telegram") {
|
|
390
|
+
throw new Error("pollAnonymous/pollPublic are only supported for Telegram polls");
|
|
391
|
+
}
|
|
392
|
+
const resolvedThreadId = resolveAndApplyOutboundThreadId(params, {
|
|
393
|
+
channel,
|
|
394
|
+
to,
|
|
395
|
+
toolContext: input.toolContext,
|
|
396
|
+
allowSlackAutoThread: channel === "slack",
|
|
397
|
+
});
|
|
624
398
|
const base = typeof params.message === "string" ? params.message : "";
|
|
625
399
|
await maybeApplyCrossContextMarker({
|
|
626
400
|
cfg,
|
|
@@ -631,7 +405,7 @@ async function handlePollAction(ctx) {
|
|
|
631
405
|
accountId,
|
|
632
406
|
args: params,
|
|
633
407
|
message: base,
|
|
634
|
-
|
|
408
|
+
preferComponents: false,
|
|
635
409
|
});
|
|
636
410
|
const poll = await executePollAction({
|
|
637
411
|
ctx: {
|
|
@@ -642,12 +416,16 @@ async function handlePollAction(ctx) {
|
|
|
642
416
|
gateway,
|
|
643
417
|
toolContext: input.toolContext,
|
|
644
418
|
dryRun,
|
|
419
|
+
silent: silent ?? undefined,
|
|
645
420
|
},
|
|
646
421
|
to,
|
|
647
422
|
question,
|
|
648
423
|
options,
|
|
649
424
|
maxSelections,
|
|
425
|
+
durationSeconds: durationSeconds ?? undefined,
|
|
650
426
|
durationHours: durationHours ?? undefined,
|
|
427
|
+
threadId: resolvedThreadId ?? undefined,
|
|
428
|
+
isAnonymous,
|
|
651
429
|
});
|
|
652
430
|
return {
|
|
653
431
|
kind: "poll",
|
|
@@ -681,6 +459,7 @@ async function handlePluginAction(ctx) {
|
|
|
681
459
|
cfg,
|
|
682
460
|
params,
|
|
683
461
|
accountId: accountId ?? undefined,
|
|
462
|
+
requesterSenderId: input.requesterSenderId ?? undefined,
|
|
684
463
|
gateway,
|
|
685
464
|
toolContext: input.toolContext,
|
|
686
465
|
dryRun,
|
|
@@ -707,6 +486,7 @@ export async function runMessageAction(input) {
|
|
|
707
486
|
: undefined);
|
|
708
487
|
parseButtonsParam(params);
|
|
709
488
|
parseCardParam(params);
|
|
489
|
+
parseComponentsParam(params);
|
|
710
490
|
const action = input.action;
|
|
711
491
|
if (action === "broadcast") {
|
|
712
492
|
return handleBroadcastAction(input, params);
|