@poolzin/pool-bot 2026.2.21 → 2026.2.22
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 +17 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
|
@@ -688,7 +688,7 @@ function summarizeKnownExec(words) {
|
|
|
688
688
|
}
|
|
689
689
|
if (bin === "poolbot") {
|
|
690
690
|
const sub = firstPositional(words, 1);
|
|
691
|
-
return sub ? `run
|
|
691
|
+
return sub ? `run poolbot ${sub}` : "run poolbot";
|
|
692
692
|
}
|
|
693
693
|
const arg = firstPositional(words, 1);
|
|
694
694
|
if (!arg || arg.length > 48) {
|
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
2
2
|
import { getImageMetadata, resizeToJpeg } from "../media/image-ops.js";
|
|
3
|
-
|
|
3
|
+
import { DEFAULT_IMAGE_MAX_BYTES, DEFAULT_IMAGE_MAX_DIMENSION_PX, } from "./image-sanitization.js";
|
|
4
|
+
// Anthropic Messages API limitations (observed in Pool Bot sessions):
|
|
4
5
|
// - Images over ~2000px per side can fail in multi-image requests.
|
|
5
6
|
// - Images over 5MB are rejected by the API.
|
|
6
7
|
//
|
|
7
8
|
// To keep sessions resilient (and avoid "silent" WhatsApp non-replies), we auto-downscale
|
|
8
9
|
// and recompress base64 image blocks when they exceed these limits.
|
|
9
|
-
const MAX_IMAGE_DIMENSION_PX =
|
|
10
|
-
const MAX_IMAGE_BYTES =
|
|
10
|
+
const MAX_IMAGE_DIMENSION_PX = DEFAULT_IMAGE_MAX_DIMENSION_PX;
|
|
11
|
+
const MAX_IMAGE_BYTES = DEFAULT_IMAGE_MAX_BYTES;
|
|
11
12
|
const log = createSubsystemLogger("agents/tool-images");
|
|
12
13
|
function isImageBlock(block) {
|
|
13
|
-
if (!block || typeof block !== "object")
|
|
14
|
+
if (!block || typeof block !== "object") {
|
|
14
15
|
return false;
|
|
16
|
+
}
|
|
15
17
|
const rec = block;
|
|
16
18
|
return rec.type === "image" && typeof rec.data === "string" && typeof rec.mimeType === "string";
|
|
17
19
|
}
|
|
18
20
|
function isTextBlock(block) {
|
|
19
|
-
if (!block || typeof block !== "object")
|
|
21
|
+
if (!block || typeof block !== "object") {
|
|
20
22
|
return false;
|
|
23
|
+
}
|
|
21
24
|
const rec = block;
|
|
22
25
|
return rec.type === "text" && typeof rec.text === "string";
|
|
23
26
|
}
|
|
24
27
|
function inferMimeTypeFromBase64(base64) {
|
|
25
28
|
const trimmed = base64.trim();
|
|
26
|
-
if (!trimmed)
|
|
29
|
+
if (!trimmed) {
|
|
27
30
|
return undefined;
|
|
28
|
-
|
|
31
|
+
}
|
|
32
|
+
if (trimmed.startsWith("/9j/")) {
|
|
29
33
|
return "image/jpeg";
|
|
30
|
-
|
|
34
|
+
}
|
|
35
|
+
if (trimmed.startsWith("iVBOR")) {
|
|
31
36
|
return "image/png";
|
|
32
|
-
|
|
37
|
+
}
|
|
38
|
+
if (trimmed.startsWith("R0lGOD")) {
|
|
33
39
|
return "image/gif";
|
|
40
|
+
}
|
|
34
41
|
return undefined;
|
|
35
42
|
}
|
|
43
|
+
function formatBytesShort(bytes) {
|
|
44
|
+
if (!Number.isFinite(bytes) || bytes < 1024) {
|
|
45
|
+
return `${Math.max(0, Math.round(bytes))}B`;
|
|
46
|
+
}
|
|
47
|
+
if (bytes < 1024 * 1024) {
|
|
48
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
49
|
+
}
|
|
50
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
|
|
51
|
+
}
|
|
36
52
|
async function resizeImageBase64IfNeeded(params) {
|
|
37
53
|
const buf = Buffer.from(params.base64, "base64");
|
|
38
54
|
const meta = await getImageMetadata(buf);
|
|
@@ -40,6 +56,7 @@ async function resizeImageBase64IfNeeded(params) {
|
|
|
40
56
|
const height = meta?.height;
|
|
41
57
|
const overBytes = buf.byteLength > params.maxBytes;
|
|
42
58
|
const hasDimensions = typeof width === "number" && typeof height === "number";
|
|
59
|
+
const overDimensions = hasDimensions && (width > params.maxDimensionPx || height > params.maxDimensionPx);
|
|
43
60
|
if (hasDimensions &&
|
|
44
61
|
!overBytes &&
|
|
45
62
|
width <= params.maxDimensionPx &&
|
|
@@ -52,23 +69,13 @@ async function resizeImageBase64IfNeeded(params) {
|
|
|
52
69
|
height,
|
|
53
70
|
};
|
|
54
71
|
}
|
|
55
|
-
if (hasDimensions &&
|
|
56
|
-
(width > params.maxDimensionPx || height > params.maxDimensionPx || overBytes)) {
|
|
57
|
-
log.warn("Image exceeds limits; resizing", {
|
|
58
|
-
label: params.label,
|
|
59
|
-
width,
|
|
60
|
-
height,
|
|
61
|
-
maxDimensionPx: params.maxDimensionPx,
|
|
62
|
-
maxBytes: params.maxBytes,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
72
|
const qualities = [85, 75, 65, 55, 45, 35];
|
|
66
73
|
const maxDim = hasDimensions ? Math.max(width ?? 0, height ?? 0) : params.maxDimensionPx;
|
|
67
74
|
const sideStart = maxDim > 0 ? Math.min(params.maxDimensionPx, maxDim) : params.maxDimensionPx;
|
|
68
75
|
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
|
69
|
-
.
|
|
76
|
+
.filter((v) => v > 0 && v <= params.maxDimensionPx)
|
|
70
77
|
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
|
71
|
-
.
|
|
78
|
+
.toSorted((a, b) => b - a);
|
|
72
79
|
let smallest = null;
|
|
73
80
|
for (const side of sideGrid) {
|
|
74
81
|
for (const quality of qualities) {
|
|
@@ -82,16 +89,27 @@ async function resizeImageBase64IfNeeded(params) {
|
|
|
82
89
|
smallest = { buffer: out, size: out.byteLength };
|
|
83
90
|
}
|
|
84
91
|
if (out.byteLength <= params.maxBytes) {
|
|
85
|
-
|
|
92
|
+
const sourcePixels = typeof width === "number" && typeof height === "number"
|
|
93
|
+
? `${width}x${height}px`
|
|
94
|
+
: "unknown";
|
|
95
|
+
const byteReductionPct = buf.byteLength > 0
|
|
96
|
+
? Number((((buf.byteLength - out.byteLength) / buf.byteLength) * 100).toFixed(1))
|
|
97
|
+
: 0;
|
|
98
|
+
log.info(`Image resized to fit limits: ${sourcePixels} ${formatBytesShort(buf.byteLength)} -> ${formatBytesShort(out.byteLength)} (-${byteReductionPct}%)`, {
|
|
86
99
|
label: params.label,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
sourceMimeType: params.mimeType,
|
|
101
|
+
sourceWidth: width,
|
|
102
|
+
sourceHeight: height,
|
|
103
|
+
sourceBytes: buf.byteLength,
|
|
90
104
|
maxBytes: params.maxBytes,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
maxDimensionPx: params.maxDimensionPx,
|
|
106
|
+
triggerOverBytes: overBytes,
|
|
107
|
+
triggerOverDimensions: overDimensions,
|
|
108
|
+
outputMimeType: "image/jpeg",
|
|
109
|
+
outputBytes: out.byteLength,
|
|
110
|
+
outputQuality: quality,
|
|
111
|
+
outputMaxSide: side,
|
|
112
|
+
byteReductionPct,
|
|
95
113
|
});
|
|
96
114
|
return {
|
|
97
115
|
base64: out.toString("base64"),
|
|
@@ -106,6 +124,19 @@ async function resizeImageBase64IfNeeded(params) {
|
|
|
106
124
|
const best = smallest?.buffer ?? buf;
|
|
107
125
|
const maxMb = (params.maxBytes / (1024 * 1024)).toFixed(0);
|
|
108
126
|
const gotMb = (best.byteLength / (1024 * 1024)).toFixed(2);
|
|
127
|
+
const sourcePixels = typeof width === "number" && typeof height === "number" ? `${width}x${height}px` : "unknown";
|
|
128
|
+
log.warn(`Image resize failed to fit limits: ${sourcePixels} best=${formatBytesShort(best.byteLength)} limit=${formatBytesShort(params.maxBytes)}`, {
|
|
129
|
+
label: params.label,
|
|
130
|
+
sourceMimeType: params.mimeType,
|
|
131
|
+
sourceWidth: width,
|
|
132
|
+
sourceHeight: height,
|
|
133
|
+
sourceBytes: buf.byteLength,
|
|
134
|
+
maxDimensionPx: params.maxDimensionPx,
|
|
135
|
+
maxBytes: params.maxBytes,
|
|
136
|
+
smallestCandidateBytes: best.byteLength,
|
|
137
|
+
triggerOverBytes: overBytes,
|
|
138
|
+
triggerOverDimensions: overDimensions,
|
|
139
|
+
});
|
|
109
140
|
throw new Error(`Image could not be reduced below ${maxMb}MB (got ${gotMb}MB)`);
|
|
110
141
|
}
|
|
111
142
|
export async function sanitizeContentBlocksImages(blocks, label, opts = {}) {
|
|
@@ -151,16 +182,18 @@ export async function sanitizeContentBlocksImages(blocks, label, opts = {}) {
|
|
|
151
182
|
return out;
|
|
152
183
|
}
|
|
153
184
|
export async function sanitizeImageBlocks(images, label, opts = {}) {
|
|
154
|
-
if (images.length === 0)
|
|
185
|
+
if (images.length === 0) {
|
|
155
186
|
return { images, dropped: 0 };
|
|
187
|
+
}
|
|
156
188
|
const sanitized = await sanitizeContentBlocksImages(images, label, opts);
|
|
157
189
|
const next = sanitized.filter(isImageBlock);
|
|
158
190
|
return { images: next, dropped: Math.max(0, images.length - next.length) };
|
|
159
191
|
}
|
|
160
192
|
export async function sanitizeToolResultImages(result, label, opts = {}) {
|
|
161
193
|
const content = Array.isArray(result.content) ? result.content : [];
|
|
162
|
-
if (!content.some((b) => isImageBlock(b) || isTextBlock(b)))
|
|
194
|
+
if (!content.some((b) => isImageBlock(b) || isTextBlock(b))) {
|
|
163
195
|
return result;
|
|
196
|
+
}
|
|
164
197
|
const next = await sanitizeContentBlocksImages(content, label, opts);
|
|
165
198
|
return { ...result, content: next };
|
|
166
199
|
}
|
|
@@ -4,9 +4,10 @@ import { Type } from "@sinclair/typebox";
|
|
|
4
4
|
import { writeBase64ToFile } from "../../cli/nodes-camera.js";
|
|
5
5
|
import { canvasSnapshotTempPath, parseCanvasSnapshotPayload } from "../../cli/nodes-canvas.js";
|
|
6
6
|
import { imageMimeFromFormat } from "../../media/mime.js";
|
|
7
|
+
import { resolveImageSanitizationLimits } from "../image-sanitization.js";
|
|
7
8
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
|
8
9
|
import { imageResult, jsonResult, readStringParam } from "./common.js";
|
|
9
|
-
import { callGatewayTool } from "./gateway.js";
|
|
10
|
+
import { callGatewayTool, readGatewayCallOptions } from "./gateway.js";
|
|
10
11
|
import { resolveNodeId } from "./nodes-utils.js";
|
|
11
12
|
const CANVAS_ACTIONS = [
|
|
12
13
|
"present",
|
|
@@ -44,7 +45,8 @@ const CanvasToolSchema = Type.Object({
|
|
|
44
45
|
jsonl: Type.Optional(Type.String()),
|
|
45
46
|
jsonlPath: Type.Optional(Type.String()),
|
|
46
47
|
});
|
|
47
|
-
export function createCanvasTool() {
|
|
48
|
+
export function createCanvasTool(options) {
|
|
49
|
+
const imageSanitization = resolveImageSanitizationLimits(options?.config);
|
|
48
50
|
return {
|
|
49
51
|
label: "Canvas",
|
|
50
52
|
name: "canvas",
|
|
@@ -53,11 +55,7 @@ export function createCanvasTool() {
|
|
|
53
55
|
execute: async (_toolCallId, args) => {
|
|
54
56
|
const params = args;
|
|
55
57
|
const action = readStringParam(params, "action", { required: true });
|
|
56
|
-
const gatewayOpts =
|
|
57
|
-
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
|
|
58
|
-
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
|
|
59
|
-
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : undefined,
|
|
60
|
-
};
|
|
58
|
+
const gatewayOpts = readGatewayCallOptions(params);
|
|
61
59
|
const nodeId = await resolveNodeId(gatewayOpts, readStringParam(params, "node", { trim: true }), true);
|
|
62
60
|
const invoke = async (command, invokeParams) => await callGatewayTool("node.invoke", gatewayOpts, {
|
|
63
61
|
nodeId,
|
|
@@ -74,8 +72,12 @@ export function createCanvasTool() {
|
|
|
74
72
|
height: typeof params.height === "number" ? params.height : undefined,
|
|
75
73
|
};
|
|
76
74
|
const invokeParams = {};
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
// Accept both `target` and `url` for present to match common caller expectations.
|
|
76
|
+
// `target` remains the canonical field for CLI compatibility.
|
|
77
|
+
const presentTarget = readStringParam(params, "target", { trim: true }) ??
|
|
78
|
+
readStringParam(params, "url", { trim: true });
|
|
79
|
+
if (presentTarget) {
|
|
80
|
+
invokeParams.url = presentTarget;
|
|
79
81
|
}
|
|
80
82
|
if (Number.isFinite(placement.x) ||
|
|
81
83
|
Number.isFinite(placement.y) ||
|
|
@@ -90,7 +92,9 @@ export function createCanvasTool() {
|
|
|
90
92
|
await invoke("canvas.hide", undefined);
|
|
91
93
|
return jsonResult({ ok: true });
|
|
92
94
|
case "navigate": {
|
|
93
|
-
|
|
95
|
+
// Support `target` as an alias so callers can reuse the same field across present/navigate.
|
|
96
|
+
const url = readStringParam(params, "url", { trim: true }) ??
|
|
97
|
+
readStringParam(params, "target", { required: true, trim: true, label: "url" });
|
|
94
98
|
await invoke("canvas.navigate", { url });
|
|
95
99
|
return jsonResult({ ok: true });
|
|
96
100
|
}
|
|
@@ -134,6 +138,7 @@ export function createCanvasTool() {
|
|
|
134
138
|
base64: payload.base64,
|
|
135
139
|
mimeType,
|
|
136
140
|
details: { format: payload.format },
|
|
141
|
+
imageSanitization,
|
|
137
142
|
});
|
|
138
143
|
}
|
|
139
144
|
case "a2ui_push": {
|
|
@@ -142,8 +147,9 @@ export function createCanvasTool() {
|
|
|
142
147
|
: typeof params.jsonlPath === "string" && params.jsonlPath.trim()
|
|
143
148
|
? await fs.readFile(params.jsonlPath.trim(), "utf8")
|
|
144
149
|
: "";
|
|
145
|
-
if (!jsonl.trim())
|
|
150
|
+
if (!jsonl.trim()) {
|
|
146
151
|
throw new Error("jsonl or jsonlPath required");
|
|
152
|
+
}
|
|
147
153
|
await invoke("canvas.a2ui.pushJSONL", { jsonl });
|
|
148
154
|
return jsonResult({ ok: true });
|
|
149
155
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { detectMime } from "../../media/mime.js";
|
|
3
3
|
import { sanitizeToolResultImages } from "../tool-images.js";
|
|
4
|
+
export class ToolInputError extends Error {
|
|
5
|
+
status = 400;
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ToolInputError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
4
11
|
export function createActionGate(actions) {
|
|
5
12
|
return (key, defaultValue = true) => {
|
|
6
13
|
const value = actions?.[key];
|
|
7
|
-
if (value === undefined)
|
|
14
|
+
if (value === undefined) {
|
|
8
15
|
return defaultValue;
|
|
16
|
+
}
|
|
9
17
|
return value !== false;
|
|
10
18
|
};
|
|
11
19
|
}
|
|
@@ -13,14 +21,16 @@ export function readStringParam(params, key, options = {}) {
|
|
|
13
21
|
const { required = false, trim = true, label = key, allowEmpty = false } = options;
|
|
14
22
|
const raw = params[key];
|
|
15
23
|
if (typeof raw !== "string") {
|
|
16
|
-
if (required)
|
|
17
|
-
throw new
|
|
24
|
+
if (required) {
|
|
25
|
+
throw new ToolInputError(`${label} required`);
|
|
26
|
+
}
|
|
18
27
|
return undefined;
|
|
19
28
|
}
|
|
20
29
|
const value = trim ? raw.trim() : raw;
|
|
21
30
|
if (!value && !allowEmpty) {
|
|
22
|
-
if (required)
|
|
23
|
-
throw new
|
|
31
|
+
if (required) {
|
|
32
|
+
throw new ToolInputError(`${label} required`);
|
|
33
|
+
}
|
|
24
34
|
return undefined;
|
|
25
35
|
}
|
|
26
36
|
return value;
|
|
@@ -33,11 +43,13 @@ export function readStringOrNumberParam(params, key, options = {}) {
|
|
|
33
43
|
}
|
|
34
44
|
if (typeof raw === "string") {
|
|
35
45
|
const value = raw.trim();
|
|
36
|
-
if (value)
|
|
46
|
+
if (value) {
|
|
37
47
|
return value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (required) {
|
|
51
|
+
throw new ToolInputError(`${label} required`);
|
|
38
52
|
}
|
|
39
|
-
if (required)
|
|
40
|
-
throw new Error(`${label} required`);
|
|
41
53
|
return undefined;
|
|
42
54
|
}
|
|
43
55
|
export function readNumberParam(params, key, options = {}) {
|
|
@@ -51,13 +63,15 @@ export function readNumberParam(params, key, options = {}) {
|
|
|
51
63
|
const trimmed = raw.trim();
|
|
52
64
|
if (trimmed) {
|
|
53
65
|
const parsed = Number.parseFloat(trimmed);
|
|
54
|
-
if (Number.isFinite(parsed))
|
|
66
|
+
if (Number.isFinite(parsed)) {
|
|
55
67
|
value = parsed;
|
|
68
|
+
}
|
|
56
69
|
}
|
|
57
70
|
}
|
|
58
71
|
if (value === undefined) {
|
|
59
|
-
if (required)
|
|
60
|
-
throw new
|
|
72
|
+
if (required) {
|
|
73
|
+
throw new ToolInputError(`${label} required`);
|
|
74
|
+
}
|
|
61
75
|
return undefined;
|
|
62
76
|
}
|
|
63
77
|
return integer ? Math.trunc(value) : value;
|
|
@@ -71,8 +85,9 @@ export function readStringArrayParam(params, key, options = {}) {
|
|
|
71
85
|
.map((entry) => entry.trim())
|
|
72
86
|
.filter(Boolean);
|
|
73
87
|
if (values.length === 0) {
|
|
74
|
-
if (required)
|
|
75
|
-
throw new
|
|
88
|
+
if (required) {
|
|
89
|
+
throw new ToolInputError(`${label} required`);
|
|
90
|
+
}
|
|
76
91
|
return undefined;
|
|
77
92
|
}
|
|
78
93
|
return values;
|
|
@@ -80,14 +95,16 @@ export function readStringArrayParam(params, key, options = {}) {
|
|
|
80
95
|
if (typeof raw === "string") {
|
|
81
96
|
const value = raw.trim();
|
|
82
97
|
if (!value) {
|
|
83
|
-
if (required)
|
|
84
|
-
throw new
|
|
98
|
+
if (required) {
|
|
99
|
+
throw new ToolInputError(`${label} required`);
|
|
100
|
+
}
|
|
85
101
|
return undefined;
|
|
86
102
|
}
|
|
87
103
|
return [value];
|
|
88
104
|
}
|
|
89
|
-
if (required)
|
|
90
|
-
throw new
|
|
105
|
+
if (required) {
|
|
106
|
+
throw new ToolInputError(`${label} required`);
|
|
107
|
+
}
|
|
91
108
|
return undefined;
|
|
92
109
|
}
|
|
93
110
|
export function readReactionParams(params, options) {
|
|
@@ -99,7 +116,7 @@ export function readReactionParams(params, options) {
|
|
|
99
116
|
allowEmpty: true,
|
|
100
117
|
});
|
|
101
118
|
if (remove && !emoji) {
|
|
102
|
-
throw new
|
|
119
|
+
throw new ToolInputError(options.removeErrorMessage);
|
|
103
120
|
}
|
|
104
121
|
return { emoji, remove, isEmpty: !emoji };
|
|
105
122
|
}
|
|
@@ -130,7 +147,7 @@ export async function imageResult(params) {
|
|
|
130
147
|
content,
|
|
131
148
|
details: { path: params.path, ...params.details },
|
|
132
149
|
};
|
|
133
|
-
return await sanitizeToolResultImages(result, params.label);
|
|
150
|
+
return await sanitizeToolResultImages(result, params.label, params.imageSanitization);
|
|
134
151
|
}
|
|
135
152
|
export async function imageResultFromFile(params) {
|
|
136
153
|
const buf = await fs.readFile(params.path);
|
|
@@ -142,5 +159,6 @@ export async function imageResultFromFile(params) {
|
|
|
142
159
|
mimeType,
|
|
143
160
|
extraText: params.extraText,
|
|
144
161
|
details: params.details,
|
|
162
|
+
imageSanitization: params.imageSanitization,
|
|
145
163
|
});
|
|
146
164
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { loadConfig } from "../../config/config.js";
|
|
3
3
|
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
|
|
4
|
+
import { normalizeHttpWebhookUrl } from "../../cron/webhook-url.js";
|
|
4
5
|
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
|
|
6
|
+
import { extractTextFromChatContent } from "../../shared/chat-content.js";
|
|
5
7
|
import { isRecord, truncateUtf16Safe } from "../../utils.js";
|
|
6
8
|
import { resolveSessionAgentId } from "../agent-scope.js";
|
|
7
9
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
|
@@ -49,37 +51,13 @@ function truncateText(input, maxLen) {
|
|
|
49
51
|
const truncated = truncateUtf16Safe(input, Math.max(0, maxLen - 3)).trimEnd();
|
|
50
52
|
return `${truncated}...`;
|
|
51
53
|
}
|
|
52
|
-
function normalizeContextText(raw) {
|
|
53
|
-
return raw.replace(/\s+/g, " ").trim();
|
|
54
|
-
}
|
|
55
54
|
function extractMessageText(message) {
|
|
56
55
|
const role = typeof message.role === "string" ? message.role : "";
|
|
57
56
|
if (role !== "user" && role !== "assistant") {
|
|
58
57
|
return null;
|
|
59
58
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const normalized = normalizeContextText(content);
|
|
63
|
-
return normalized ? { role, text: normalized } : null;
|
|
64
|
-
}
|
|
65
|
-
if (!Array.isArray(content)) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
const chunks = [];
|
|
69
|
-
for (const block of content) {
|
|
70
|
-
if (!block || typeof block !== "object") {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (block.type !== "text") {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const text = block.text;
|
|
77
|
-
if (typeof text === "string" && text.trim()) {
|
|
78
|
-
chunks.push(text);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const joined = normalizeContextText(chunks.join(" "));
|
|
82
|
-
return joined ? { role, text: joined } : null;
|
|
59
|
+
const text = extractTextFromChatContent(message.content);
|
|
60
|
+
return text ? { role, text } : null;
|
|
83
61
|
}
|
|
84
62
|
async function buildReminderContextLines(params) {
|
|
85
63
|
const maxMessages = Math.min(REMINDER_CONTEXT_MESSAGES_MAX, Math.max(0, Math.floor(params.contextMessages)));
|
|
@@ -201,7 +179,7 @@ JOB SCHEMA (for add action):
|
|
|
201
179
|
"name": "string (optional)",
|
|
202
180
|
"schedule": { ... }, // Required: when to run
|
|
203
181
|
"payload": { ... }, // Required: what to execute
|
|
204
|
-
"delivery": { ... }, // Optional: announce summary
|
|
182
|
+
"delivery": { ... }, // Optional: announce summary or webhook POST
|
|
205
183
|
"sessionTarget": "main" | "isolated", // Required
|
|
206
184
|
"enabled": true | false // Optional, default true
|
|
207
185
|
}
|
|
@@ -220,16 +198,19 @@ PAYLOAD TYPES (payload.kind):
|
|
|
220
198
|
- "systemEvent": Injects text as system event into session
|
|
221
199
|
{ "kind": "systemEvent", "text": "<message>" }
|
|
222
200
|
- "agentTurn": Runs agent with message (isolated sessions only)
|
|
223
|
-
{ "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional> }
|
|
201
|
+
{ "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional, 0 means no timeout> }
|
|
224
202
|
|
|
225
|
-
DELIVERY (
|
|
226
|
-
{ "mode": "none|announce", "channel": "<optional>", "to": "<optional>", "bestEffort": <optional-bool> }
|
|
203
|
+
DELIVERY (top-level):
|
|
204
|
+
{ "mode": "none|announce|webhook", "channel": "<optional>", "to": "<optional>", "bestEffort": <optional-bool> }
|
|
227
205
|
- Default for isolated agentTurn jobs (when delivery omitted): "announce"
|
|
228
|
-
-
|
|
206
|
+
- announce: send to chat channel (optional channel/to target)
|
|
207
|
+
- webhook: send finished-run event as HTTP POST to delivery.to (URL required)
|
|
208
|
+
- If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.
|
|
229
209
|
|
|
230
210
|
CRITICAL CONSTRAINTS:
|
|
231
211
|
- sessionTarget="main" REQUIRES payload.kind="systemEvent"
|
|
232
212
|
- sessionTarget="isolated" REQUIRES payload.kind="agentTurn"
|
|
213
|
+
- For webhook callbacks, use delivery.mode="webhook" with delivery.to set to a URL.
|
|
233
214
|
Default: prefer isolated agentTurn jobs unless the user explicitly wants a main-session system event.
|
|
234
215
|
|
|
235
216
|
WAKE MODES (for wake action):
|
|
@@ -258,7 +239,7 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
258
239
|
// job properties to the top level alongside `action` instead of nesting
|
|
259
240
|
// them inside `job`. When `params.job` is missing or empty, reconstruct
|
|
260
241
|
// a synthetic job object from any recognised top-level job fields.
|
|
261
|
-
// See: https://github.com/
|
|
242
|
+
// See: https://github.com/poolbot/poolbot/issues/11310
|
|
262
243
|
if (!params.job ||
|
|
263
244
|
(typeof params.job === "object" &&
|
|
264
245
|
params.job !== null &&
|
|
@@ -274,6 +255,7 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
274
255
|
"description",
|
|
275
256
|
"deleteAfterRun",
|
|
276
257
|
"agentId",
|
|
258
|
+
"sessionKey",
|
|
277
259
|
"message",
|
|
278
260
|
"text",
|
|
279
261
|
"model",
|
|
@@ -304,13 +286,22 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
304
286
|
throw new Error("job required");
|
|
305
287
|
}
|
|
306
288
|
const job = normalizeCronJobCreate(params.job) ?? params.job;
|
|
307
|
-
if (job && typeof job === "object"
|
|
289
|
+
if (job && typeof job === "object") {
|
|
308
290
|
const cfg = loadConfig();
|
|
309
|
-
const
|
|
310
|
-
|
|
291
|
+
const { mainKey, alias } = resolveMainSessionAlias(cfg);
|
|
292
|
+
const resolvedSessionKey = opts?.agentSessionKey
|
|
293
|
+
? resolveInternalSessionKey({ key: opts.agentSessionKey, alias, mainKey })
|
|
311
294
|
: undefined;
|
|
312
|
-
if (agentId) {
|
|
313
|
-
|
|
295
|
+
if (!("agentId" in job)) {
|
|
296
|
+
const agentId = opts?.agentSessionKey
|
|
297
|
+
? resolveSessionAgentId({ sessionKey: opts.agentSessionKey, config: cfg })
|
|
298
|
+
: undefined;
|
|
299
|
+
if (agentId) {
|
|
300
|
+
job.agentId = agentId;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (!("sessionKey" in job) && resolvedSessionKey) {
|
|
304
|
+
job.sessionKey = resolvedSessionKey;
|
|
314
305
|
}
|
|
315
306
|
}
|
|
316
307
|
if (opts?.agentSessionKey &&
|
|
@@ -322,9 +313,20 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
322
313
|
const delivery = isRecord(deliveryValue) ? deliveryValue : undefined;
|
|
323
314
|
const modeRaw = typeof delivery?.mode === "string" ? delivery.mode : "";
|
|
324
315
|
const mode = modeRaw.trim().toLowerCase();
|
|
316
|
+
if (mode === "webhook") {
|
|
317
|
+
const webhookUrl = normalizeHttpWebhookUrl(delivery?.to);
|
|
318
|
+
if (!webhookUrl) {
|
|
319
|
+
throw new Error('delivery.mode="webhook" requires delivery.to to be a valid http(s) URL');
|
|
320
|
+
}
|
|
321
|
+
if (delivery) {
|
|
322
|
+
delivery.to = webhookUrl;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
325
|
const hasTarget = (typeof delivery?.channel === "string" && delivery.channel.trim()) ||
|
|
326
326
|
(typeof delivery?.to === "string" && delivery.to.trim());
|
|
327
|
-
const shouldInfer = (deliveryValue == null || delivery) &&
|
|
327
|
+
const shouldInfer = (deliveryValue == null || delivery) &&
|
|
328
|
+
(mode === "" || mode === "announce") &&
|
|
329
|
+
!hasTarget;
|
|
328
330
|
if (shouldInfer) {
|
|
329
331
|
const inferred = inferDeliveryFromSessionKey(opts.agentSessionKey);
|
|
330
332
|
if (inferred) {
|
|
@@ -1,17 +1,85 @@
|
|
|
1
|
+
import { loadConfig, resolveGatewayPort } from "../../config/config.js";
|
|
1
2
|
import { callGateway } from "../../gateway/call.js";
|
|
2
3
|
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
|
|
4
|
+
import { readStringParam } from "./common.js";
|
|
3
5
|
export const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
|
|
6
|
+
export function readGatewayCallOptions(params) {
|
|
7
|
+
return {
|
|
8
|
+
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
|
|
9
|
+
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
|
|
10
|
+
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : undefined,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function canonicalizeToolGatewayWsUrl(raw) {
|
|
14
|
+
const input = raw.trim();
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(input);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
throw new Error(`invalid gatewayUrl: ${input} (${message})`, { cause: error });
|
|
22
|
+
}
|
|
23
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
24
|
+
throw new Error(`invalid gatewayUrl protocol: ${url.protocol} (expected ws:// or wss://)`);
|
|
25
|
+
}
|
|
26
|
+
if (url.username || url.password) {
|
|
27
|
+
throw new Error("invalid gatewayUrl: credentials are not allowed");
|
|
28
|
+
}
|
|
29
|
+
if (url.search || url.hash) {
|
|
30
|
+
throw new Error("invalid gatewayUrl: query/hash not allowed");
|
|
31
|
+
}
|
|
32
|
+
// Agents/tools expect the gateway websocket on the origin, not arbitrary paths.
|
|
33
|
+
if (url.pathname && url.pathname !== "/") {
|
|
34
|
+
throw new Error("invalid gatewayUrl: path not allowed");
|
|
35
|
+
}
|
|
36
|
+
const origin = url.origin;
|
|
37
|
+
// Key: protocol + host only, lowercased. (host includes IPv6 brackets + port when present)
|
|
38
|
+
const key = `${url.protocol}//${url.host.toLowerCase()}`;
|
|
39
|
+
return { origin, key };
|
|
40
|
+
}
|
|
41
|
+
function validateGatewayUrlOverrideForAgentTools(urlOverride) {
|
|
42
|
+
const cfg = loadConfig();
|
|
43
|
+
const port = resolveGatewayPort(cfg);
|
|
44
|
+
const allowed = new Set([
|
|
45
|
+
`ws://127.0.0.1:${port}`,
|
|
46
|
+
`wss://127.0.0.1:${port}`,
|
|
47
|
+
`ws://localhost:${port}`,
|
|
48
|
+
`wss://localhost:${port}`,
|
|
49
|
+
`ws://[::1]:${port}`,
|
|
50
|
+
`wss://[::1]:${port}`,
|
|
51
|
+
]);
|
|
52
|
+
const remoteUrl = typeof cfg.gateway?.remote?.url === "string" ? cfg.gateway.remote.url.trim() : "";
|
|
53
|
+
if (remoteUrl) {
|
|
54
|
+
try {
|
|
55
|
+
const remote = canonicalizeToolGatewayWsUrl(remoteUrl);
|
|
56
|
+
allowed.add(remote.key);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// ignore: misconfigured remote url; tools should fall back to default resolution.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const parsed = canonicalizeToolGatewayWsUrl(urlOverride);
|
|
63
|
+
if (!allowed.has(parsed.key)) {
|
|
64
|
+
throw new Error([
|
|
65
|
+
"gatewayUrl override rejected.",
|
|
66
|
+
`Allowed: ws(s) loopback on port ${port} (127.0.0.1/localhost/[::1])`,
|
|
67
|
+
"Or: configure gateway.remote.url and omit gatewayUrl to use the configured remote gateway.",
|
|
68
|
+
].join(" "));
|
|
69
|
+
}
|
|
70
|
+
return parsed.origin;
|
|
71
|
+
}
|
|
4
72
|
export function resolveGatewayOptions(opts) {
|
|
5
73
|
// Prefer an explicit override; otherwise let callGateway choose based on config.
|
|
6
74
|
const url = typeof opts?.gatewayUrl === "string" && opts.gatewayUrl.trim()
|
|
7
|
-
? opts.gatewayUrl
|
|
75
|
+
? validateGatewayUrlOverrideForAgentTools(opts.gatewayUrl)
|
|
8
76
|
: undefined;
|
|
9
77
|
const token = typeof opts?.gatewayToken === "string" && opts.gatewayToken.trim()
|
|
10
78
|
? opts.gatewayToken.trim()
|
|
11
79
|
: undefined;
|
|
12
80
|
const timeoutMs = typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
|
|
13
81
|
? Math.max(1, Math.floor(opts.timeoutMs))
|
|
14
|
-
:
|
|
82
|
+
: 30_000;
|
|
15
83
|
return { url, token, timeoutMs };
|
|
16
84
|
}
|
|
17
85
|
export async function callGatewayTool(method, opts, params, extra) {
|