@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveSessionAgentIds } from "../../agents/agent-scope.js";
|
|
2
2
|
import { resolveBootstrapContextForRun } from "../../agents/bootstrap-files.js";
|
|
3
3
|
import { resolveDefaultModelForAgent } from "../../agents/model-selection.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createPoolbotCodingTools } from "../../agents/pi-tools.js";
|
|
5
5
|
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
|
6
6
|
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
|
|
7
7
|
import { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js";
|
|
@@ -37,7 +37,7 @@ export async function resolveCommandsSystemPromptBundle(params) {
|
|
|
37
37
|
});
|
|
38
38
|
const tools = (() => {
|
|
39
39
|
try {
|
|
40
|
-
return
|
|
40
|
+
return createPoolbotCodingTools({
|
|
41
41
|
config: params.cfg,
|
|
42
42
|
workspaceDir,
|
|
43
43
|
sessionKey: params.sessionKey,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
|
|
2
2
|
import { loadSessionStore, resolveStorePath } from "../../config/sessions.js";
|
|
3
3
|
import { logVerbose } from "../../globals.js";
|
|
4
|
+
import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
|
|
4
5
|
import { isDiagnosticsEnabled } from "../../infra/diagnostic-events.js";
|
|
5
6
|
import { logMessageProcessed, logMessageQueued, logSessionStateChange, } from "../../logging/diagnostic.js";
|
|
6
7
|
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
|
|
8
|
+
import { maybeApplyTtsToPayload, normalizeTtsAutoMode, resolveTtsConfig } from "../../tts/tts.js";
|
|
7
9
|
import { getReplyFromConfig } from "../reply.js";
|
|
8
10
|
import { formatAbortReplyText, tryFastAbortFromMessage } from "./abort.js";
|
|
9
11
|
import { shouldSkipDuplicateInbound } from "./inbound-dedupe.js";
|
|
10
12
|
import { isRoutableChannel, routeReply } from "./route-reply.js";
|
|
11
|
-
import { maybeApplyTtsToPayload, normalizeTtsAutoMode, resolveTtsConfig } from "../../tts/tts.js";
|
|
12
13
|
const AUDIO_PLACEHOLDER_RE = /^<media:audio>(\s*\([^)]*\))?$/i;
|
|
13
14
|
const AUDIO_HEADER_RE = /^\[Audio\b/i;
|
|
14
15
|
const normalizeMediaType = (value) => value.split(";")[0]?.trim().toLowerCase();
|
|
@@ -18,8 +19,9 @@ const isInboundAudioContext = (ctx) => {
|
|
|
18
19
|
...(Array.isArray(ctx.MediaTypes) ? ctx.MediaTypes : []),
|
|
19
20
|
].filter(Boolean);
|
|
20
21
|
const types = rawTypes.map((type) => normalizeMediaType(type));
|
|
21
|
-
if (types.some((type) => type === "audio" || type.startsWith("audio/")))
|
|
22
|
+
if (types.some((type) => type === "audio" || type.startsWith("audio/"))) {
|
|
22
23
|
return true;
|
|
24
|
+
}
|
|
23
25
|
const body = typeof ctx.BodyForCommands === "string"
|
|
24
26
|
? ctx.BodyForCommands
|
|
25
27
|
: typeof ctx.CommandBody === "string"
|
|
@@ -30,17 +32,20 @@ const isInboundAudioContext = (ctx) => {
|
|
|
30
32
|
? ctx.Body
|
|
31
33
|
: "";
|
|
32
34
|
const trimmed = body.trim();
|
|
33
|
-
if (!trimmed)
|
|
35
|
+
if (!trimmed) {
|
|
34
36
|
return false;
|
|
35
|
-
|
|
37
|
+
}
|
|
38
|
+
if (AUDIO_PLACEHOLDER_RE.test(trimmed)) {
|
|
36
39
|
return true;
|
|
40
|
+
}
|
|
37
41
|
return AUDIO_HEADER_RE.test(trimmed);
|
|
38
42
|
};
|
|
39
43
|
const resolveSessionTtsAuto = (ctx, cfg) => {
|
|
40
44
|
const targetSessionKey = ctx.CommandSource === "native" ? ctx.CommandTargetSessionKey?.trim() : undefined;
|
|
41
45
|
const sessionKey = (targetSessionKey ?? ctx.SessionKey)?.trim();
|
|
42
|
-
if (!sessionKey)
|
|
46
|
+
if (!sessionKey) {
|
|
43
47
|
return undefined;
|
|
48
|
+
}
|
|
44
49
|
const agentId = resolveSessionAgentId({ sessionKey, config: cfg });
|
|
45
50
|
const storePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
46
51
|
try {
|
|
@@ -62,8 +67,9 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
62
67
|
const startTime = diagnosticsEnabled ? Date.now() : 0;
|
|
63
68
|
const canTrackSession = diagnosticsEnabled && Boolean(sessionKey);
|
|
64
69
|
const recordProcessed = (outcome, opts) => {
|
|
65
|
-
if (!diagnosticsEnabled)
|
|
70
|
+
if (!diagnosticsEnabled) {
|
|
66
71
|
return;
|
|
72
|
+
}
|
|
67
73
|
logMessageProcessed({
|
|
68
74
|
channel,
|
|
69
75
|
chatId,
|
|
@@ -76,8 +82,9 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
76
82
|
});
|
|
77
83
|
};
|
|
78
84
|
const markProcessing = () => {
|
|
79
|
-
if (!canTrackSession || !sessionKey)
|
|
85
|
+
if (!canTrackSession || !sessionKey) {
|
|
80
86
|
return;
|
|
87
|
+
}
|
|
81
88
|
logMessageQueued({ sessionKey, channel, source: "dispatch" });
|
|
82
89
|
logSessionStateChange({
|
|
83
90
|
sessionKey,
|
|
@@ -86,8 +93,9 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
86
93
|
});
|
|
87
94
|
};
|
|
88
95
|
const markIdle = (reason) => {
|
|
89
|
-
if (!canTrackSession || !sessionKey)
|
|
96
|
+
if (!canTrackSession || !sessionKey) {
|
|
90
97
|
return;
|
|
98
|
+
}
|
|
91
99
|
logSessionStateChange({
|
|
92
100
|
sessionKey,
|
|
93
101
|
state: "idle",
|
|
@@ -101,20 +109,20 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
101
109
|
const inboundAudio = isInboundAudioContext(ctx);
|
|
102
110
|
const sessionTtsAuto = resolveSessionTtsAuto(ctx, cfg);
|
|
103
111
|
const hookRunner = getGlobalHookRunner();
|
|
112
|
+
// Extract message context for hooks (plugin and internal)
|
|
113
|
+
const timestamp = typeof ctx.Timestamp === "number" && Number.isFinite(ctx.Timestamp) ? ctx.Timestamp : undefined;
|
|
114
|
+
const messageIdForHook = ctx.MessageSidFull ?? ctx.MessageSid ?? ctx.MessageSidFirst ?? ctx.MessageSidLast;
|
|
115
|
+
const content = typeof ctx.BodyForCommands === "string"
|
|
116
|
+
? ctx.BodyForCommands
|
|
117
|
+
: typeof ctx.RawBody === "string"
|
|
118
|
+
? ctx.RawBody
|
|
119
|
+
: typeof ctx.Body === "string"
|
|
120
|
+
? ctx.Body
|
|
121
|
+
: "";
|
|
122
|
+
const channelId = (ctx.OriginatingChannel ?? ctx.Surface ?? ctx.Provider ?? "").toLowerCase();
|
|
123
|
+
const conversationId = ctx.OriginatingTo ?? ctx.To ?? ctx.From ?? undefined;
|
|
124
|
+
// Trigger plugin hooks (fire-and-forget)
|
|
104
125
|
if (hookRunner?.hasHooks("message_received")) {
|
|
105
|
-
const timestamp = typeof ctx.Timestamp === "number" && Number.isFinite(ctx.Timestamp)
|
|
106
|
-
? ctx.Timestamp
|
|
107
|
-
: undefined;
|
|
108
|
-
const messageIdForHook = ctx.MessageSidFull ?? ctx.MessageSid ?? ctx.MessageSidFirst ?? ctx.MessageSidLast;
|
|
109
|
-
const content = typeof ctx.BodyForCommands === "string"
|
|
110
|
-
? ctx.BodyForCommands
|
|
111
|
-
: typeof ctx.RawBody === "string"
|
|
112
|
-
? ctx.RawBody
|
|
113
|
-
: typeof ctx.Body === "string"
|
|
114
|
-
? ctx.Body
|
|
115
|
-
: "";
|
|
116
|
-
const channelId = (ctx.OriginatingChannel ?? ctx.Surface ?? ctx.Provider ?? "").toLowerCase();
|
|
117
|
-
const conversationId = ctx.OriginatingTo ?? ctx.To ?? ctx.From ?? undefined;
|
|
118
126
|
void hookRunner
|
|
119
127
|
.runMessageReceived({
|
|
120
128
|
from: ctx.From ?? "",
|
|
@@ -139,7 +147,31 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
139
147
|
conversationId,
|
|
140
148
|
})
|
|
141
149
|
.catch((err) => {
|
|
142
|
-
logVerbose(`dispatch-from-config: message_received hook failed: ${String(err)}`);
|
|
150
|
+
logVerbose(`dispatch-from-config: message_received plugin hook failed: ${String(err)}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Bridge to internal hooks (HOOK.md discovery system) - refs #8807
|
|
154
|
+
if (sessionKey) {
|
|
155
|
+
void triggerInternalHook(createInternalHookEvent("message", "received", sessionKey, {
|
|
156
|
+
from: ctx.From ?? "",
|
|
157
|
+
content,
|
|
158
|
+
timestamp,
|
|
159
|
+
channelId,
|
|
160
|
+
accountId: ctx.AccountId,
|
|
161
|
+
conversationId,
|
|
162
|
+
messageId: messageIdForHook,
|
|
163
|
+
metadata: {
|
|
164
|
+
to: ctx.To,
|
|
165
|
+
provider: ctx.Provider,
|
|
166
|
+
surface: ctx.Surface,
|
|
167
|
+
threadId: ctx.MessageThreadId,
|
|
168
|
+
senderId: ctx.SenderId,
|
|
169
|
+
senderName: ctx.SenderName,
|
|
170
|
+
senderUsername: ctx.SenderUsername,
|
|
171
|
+
senderE164: ctx.SenderE164,
|
|
172
|
+
},
|
|
173
|
+
})).catch((err) => {
|
|
174
|
+
logVerbose(`dispatch-from-config: message_received internal hook failed: ${String(err)}`);
|
|
143
175
|
});
|
|
144
176
|
}
|
|
145
177
|
// Check if we should route replies to originating channel instead of dispatcher.
|
|
@@ -163,10 +195,12 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
163
195
|
const sendPayloadAsync = async (payload, abortSignal, mirror) => {
|
|
164
196
|
// TypeScript doesn't narrow these from the shouldRouteToOriginating check,
|
|
165
197
|
// but they're guaranteed non-null when this function is called.
|
|
166
|
-
if (!originatingChannel || !originatingTo)
|
|
198
|
+
if (!originatingChannel || !originatingTo) {
|
|
167
199
|
return;
|
|
168
|
-
|
|
200
|
+
}
|
|
201
|
+
if (abortSignal?.aborted) {
|
|
169
202
|
return;
|
|
203
|
+
}
|
|
170
204
|
const result = await routeReply({
|
|
171
205
|
payload,
|
|
172
206
|
channel: originatingChannel,
|
|
@@ -202,8 +236,9 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
202
236
|
cfg,
|
|
203
237
|
});
|
|
204
238
|
queuedFinal = result.ok;
|
|
205
|
-
if (result.ok)
|
|
239
|
+
if (result.ok) {
|
|
206
240
|
routedFinalCount += 1;
|
|
241
|
+
}
|
|
207
242
|
if (!result.ok) {
|
|
208
243
|
logVerbose(`dispatch-from-config: route-reply (abort) failed: ${result.error ?? "unknown error"}`);
|
|
209
244
|
}
|
|
@@ -211,7 +246,6 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
211
246
|
else {
|
|
212
247
|
queuedFinal = dispatcher.sendFinalReply(payload);
|
|
213
248
|
}
|
|
214
|
-
await dispatcher.waitForIdle();
|
|
215
249
|
const counts = dispatcher.getQueuedCounts();
|
|
216
250
|
counts.final += routedFinalCount;
|
|
217
251
|
recordProcessed("completed", { reason: "fast_abort" });
|
|
@@ -223,8 +257,44 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
223
257
|
// TTS audio separately from the accumulated block content.
|
|
224
258
|
let accumulatedBlockText = "";
|
|
225
259
|
let blockCount = 0;
|
|
260
|
+
const shouldSendToolSummaries = ctx.ChatType !== "group" && ctx.CommandSource !== "native";
|
|
261
|
+
const resolveToolDeliveryPayload = (payload) => {
|
|
262
|
+
if (shouldSendToolSummaries) {
|
|
263
|
+
return payload;
|
|
264
|
+
}
|
|
265
|
+
// Group/native flows intentionally suppress tool summary text, but media-only
|
|
266
|
+
// tool results (for example TTS audio) must still be delivered.
|
|
267
|
+
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
|
268
|
+
if (!hasMedia) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
return { ...payload, text: undefined };
|
|
272
|
+
};
|
|
226
273
|
const replyResult = await (params.replyResolver ?? getReplyFromConfig)(ctx, {
|
|
227
274
|
...params.replyOptions,
|
|
275
|
+
onToolResult: (payload) => {
|
|
276
|
+
const run = async () => {
|
|
277
|
+
const ttsPayload = await maybeApplyTtsToPayload({
|
|
278
|
+
payload,
|
|
279
|
+
cfg,
|
|
280
|
+
channel: ttsChannel,
|
|
281
|
+
kind: "tool",
|
|
282
|
+
inboundAudio,
|
|
283
|
+
ttsAuto: sessionTtsAuto,
|
|
284
|
+
});
|
|
285
|
+
const deliveryPayload = resolveToolDeliveryPayload(ttsPayload);
|
|
286
|
+
if (!deliveryPayload) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (shouldRouteToOriginating) {
|
|
290
|
+
await sendPayloadAsync(deliveryPayload, undefined, false);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
dispatcher.sendToolResult(deliveryPayload);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
return run();
|
|
297
|
+
},
|
|
228
298
|
onBlockReply: (payload, context) => {
|
|
229
299
|
const run = async () => {
|
|
230
300
|
// Accumulate block text for TTS generation after streaming
|
|
@@ -280,8 +350,9 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
280
350
|
logVerbose(`dispatch-from-config: route-reply (final) failed: ${result.error ?? "unknown error"}`);
|
|
281
351
|
}
|
|
282
352
|
queuedFinal = result.ok || queuedFinal;
|
|
283
|
-
if (result.ok)
|
|
353
|
+
if (result.ok) {
|
|
284
354
|
routedFinalCount += 1;
|
|
355
|
+
}
|
|
285
356
|
}
|
|
286
357
|
else {
|
|
287
358
|
queuedFinal = dispatcher.sendFinalReply(ttsReply) || queuedFinal;
|
|
@@ -322,8 +393,9 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
322
393
|
cfg,
|
|
323
394
|
});
|
|
324
395
|
queuedFinal = result.ok || queuedFinal;
|
|
325
|
-
if (result.ok)
|
|
396
|
+
if (result.ok) {
|
|
326
397
|
routedFinalCount += 1;
|
|
398
|
+
}
|
|
327
399
|
if (!result.ok) {
|
|
328
400
|
logVerbose(`dispatch-from-config: route-reply (tts-only) failed: ${result.error ?? "unknown error"}`);
|
|
329
401
|
}
|
|
@@ -338,7 +410,6 @@ export async function dispatchReplyFromConfig(params) {
|
|
|
338
410
|
logVerbose(`dispatch-from-config: accumulated block TTS failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
339
411
|
}
|
|
340
412
|
}
|
|
341
|
-
await dispatcher.waitForIdle();
|
|
342
413
|
const counts = dispatcher.getQueuedCounts();
|
|
343
414
|
counts.final += routedFinalCount;
|
|
344
415
|
recordProcessed("completed");
|
|
@@ -14,7 +14,7 @@ export function formatElevatedUnavailableMessage(params) {
|
|
|
14
14
|
lines.push("- agents.list[].tools.elevated.enabled");
|
|
15
15
|
lines.push("- agents.list[].tools.elevated.allowFrom.<provider>");
|
|
16
16
|
if (params.sessionKey) {
|
|
17
|
-
lines.push(`See: ${formatCliCommand(`
|
|
17
|
+
lines.push(`See: ${formatCliCommand(`poolbot sandbox explain --session ${params.sessionKey}`)}`);
|
|
18
18
|
}
|
|
19
19
|
return lines.join("\n");
|
|
20
20
|
}
|
|
@@ -10,10 +10,19 @@ function safeTrim(value) {
|
|
|
10
10
|
export function buildInboundMetaSystemPrompt(ctx) {
|
|
11
11
|
const chatType = normalizeChatType(ctx.ChatType);
|
|
12
12
|
const isDirect = !chatType || chatType === "direct";
|
|
13
|
+
const messageId = safeTrim(ctx.MessageSid);
|
|
14
|
+
const messageIdFull = safeTrim(ctx.MessageSidFull);
|
|
15
|
+
const replyToId = safeTrim(ctx.ReplyToId);
|
|
16
|
+
const chatId = safeTrim(ctx.OriginatingTo);
|
|
13
17
|
// Keep system metadata strictly free of attacker-controlled strings (sender names, group subjects, etc.).
|
|
14
18
|
// Those belong in the user-role "untrusted context" blocks.
|
|
15
19
|
const payload = {
|
|
16
20
|
schema: "poolbot.inbound_meta.v1",
|
|
21
|
+
message_id: messageId,
|
|
22
|
+
message_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : undefined,
|
|
23
|
+
sender_id: safeTrim(ctx.SenderId),
|
|
24
|
+
chat_id: chatId,
|
|
25
|
+
reply_to_id: replyToId,
|
|
17
26
|
channel: safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface) ?? safeTrim(ctx.Provider),
|
|
18
27
|
provider: safeTrim(ctx.Provider),
|
|
19
28
|
surface: safeTrim(ctx.Surface),
|
|
@@ -45,7 +54,9 @@ export function buildInboundUserContextPrefix(ctx) {
|
|
|
45
54
|
const chatType = normalizeChatType(ctx.ChatType);
|
|
46
55
|
const isDirect = !chatType || chatType === "direct";
|
|
47
56
|
const conversationInfo = {
|
|
48
|
-
|
|
57
|
+
message_id: safeTrim(ctx.MessageSid),
|
|
58
|
+
conversation_label: isDirect ? undefined : safeTrim(ctx.ConversationLabel),
|
|
59
|
+
sender: safeTrim(ctx.SenderE164) ?? safeTrim(ctx.SenderId) ?? safeTrim(ctx.SenderUsername),
|
|
49
60
|
group_subject: safeTrim(ctx.GroupSubject),
|
|
50
61
|
group_channel: safeTrim(ctx.GroupChannel),
|
|
51
62
|
group_space: safeTrim(ctx.GroupSpace),
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { resolveAgentConfig } from "../../agents/agent-scope.js";
|
|
2
2
|
import { getChannelDock } from "../../channels/dock.js";
|
|
3
3
|
import { normalizeChannelId } from "../../channels/plugins/index.js";
|
|
4
|
-
|
|
5
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
|
-
}
|
|
4
|
+
import { escapeRegExp } from "../../utils.js";
|
|
7
5
|
function deriveMentionPatterns(identity) {
|
|
8
6
|
const patterns = [];
|
|
9
7
|
const name = identity?.name?.trim();
|
|
@@ -21,16 +19,18 @@ function deriveMentionPatterns(identity) {
|
|
|
21
19
|
const BACKSPACE_CHAR = "\u0008";
|
|
22
20
|
export const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
23
21
|
function normalizeMentionPattern(pattern) {
|
|
24
|
-
if (!pattern.includes(BACKSPACE_CHAR))
|
|
22
|
+
if (!pattern.includes(BACKSPACE_CHAR)) {
|
|
25
23
|
return pattern;
|
|
24
|
+
}
|
|
26
25
|
return pattern.split(BACKSPACE_CHAR).join("\\b");
|
|
27
26
|
}
|
|
28
27
|
function normalizeMentionPatterns(patterns) {
|
|
29
28
|
return patterns.map(normalizeMentionPattern);
|
|
30
29
|
}
|
|
31
30
|
function resolveMentionPatterns(cfg, agentId) {
|
|
32
|
-
if (!cfg)
|
|
31
|
+
if (!cfg) {
|
|
33
32
|
return [];
|
|
33
|
+
}
|
|
34
34
|
const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined;
|
|
35
35
|
const agentGroupChat = agentConfig?.groupChat;
|
|
36
36
|
if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
|
|
@@ -60,11 +60,13 @@ export function normalizeMentionText(text) {
|
|
|
60
60
|
return (text ?? "").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "").toLowerCase();
|
|
61
61
|
}
|
|
62
62
|
export function matchesMentionPatterns(text, mentionRegexes) {
|
|
63
|
-
if (mentionRegexes.length === 0)
|
|
63
|
+
if (mentionRegexes.length === 0) {
|
|
64
64
|
return false;
|
|
65
|
+
}
|
|
65
66
|
const cleaned = normalizeMentionText(text ?? "");
|
|
66
|
-
if (!cleaned)
|
|
67
|
+
if (!cleaned) {
|
|
67
68
|
return false;
|
|
69
|
+
}
|
|
68
70
|
return mentionRegexes.some((re) => re.test(cleaned));
|
|
69
71
|
}
|
|
70
72
|
export function matchesMentionWithExplicit(params) {
|
|
@@ -72,11 +74,16 @@ export function matchesMentionWithExplicit(params) {
|
|
|
72
74
|
const explicit = params.explicit?.isExplicitlyMentioned === true;
|
|
73
75
|
const explicitAvailable = params.explicit?.canResolveExplicit === true;
|
|
74
76
|
const hasAnyMention = params.explicit?.hasAnyMention === true;
|
|
75
|
-
if
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
// Check transcript if text is empty and transcript is provided
|
|
78
|
+
const transcriptCleaned = params.transcript ? normalizeMentionText(params.transcript) : "";
|
|
79
|
+
const textToCheck = cleaned || transcriptCleaned;
|
|
80
|
+
if (hasAnyMention && explicitAvailable) {
|
|
81
|
+
return explicit || params.mentionRegexes.some((re) => re.test(textToCheck));
|
|
82
|
+
}
|
|
83
|
+
if (!textToCheck) {
|
|
78
84
|
return explicit;
|
|
79
|
-
|
|
85
|
+
}
|
|
86
|
+
return explicit || params.mentionRegexes.some((re) => re.test(textToCheck));
|
|
80
87
|
}
|
|
81
88
|
export function stripStructuralPrefixes(text) {
|
|
82
89
|
// Ignore wrapper labels, timestamps, and sender prefixes so directive-only
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
+
import { sanitizeUserFacingText } from "../../agents/pi-embedded-helpers.js";
|
|
1
2
|
import { stripHeartbeatToken } from "../heartbeat.js";
|
|
2
3
|
import { HEARTBEAT_TOKEN, isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
|
|
3
|
-
import { sanitizeUserFacingText } from "../../agents/pi-embedded-helpers.js";
|
|
4
|
-
import { resolveResponsePrefixTemplate, } from "./response-prefix-template.js";
|
|
5
4
|
import { hasLineDirectives, parseLineDirectives } from "./line-directives.js";
|
|
5
|
+
import { resolveResponsePrefixTemplate, } from "./response-prefix-template.js";
|
|
6
6
|
export function normalizeReplyPayload(payload, opts = {}) {
|
|
7
7
|
const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
|
|
8
8
|
const hasChannelData = Boolean(payload.channelData && Object.keys(payload.channelData).length > 0);
|
|
9
9
|
const trimmed = payload.text?.trim() ?? "";
|
|
10
|
-
if (!trimmed && !hasMedia && !hasChannelData)
|
|
10
|
+
if (!trimmed && !hasMedia && !hasChannelData) {
|
|
11
|
+
opts.onSkip?.("empty");
|
|
11
12
|
return null;
|
|
13
|
+
}
|
|
12
14
|
const silentToken = opts.silentToken ?? SILENT_REPLY_TOKEN;
|
|
13
15
|
let text = payload.text ?? undefined;
|
|
14
16
|
if (text && isSilentReplyText(text, silentToken)) {
|
|
15
|
-
if (!hasMedia && !hasChannelData)
|
|
17
|
+
if (!hasMedia && !hasChannelData) {
|
|
18
|
+
opts.onSkip?.("silent");
|
|
16
19
|
return null;
|
|
20
|
+
}
|
|
17
21
|
text = "";
|
|
18
22
|
}
|
|
19
23
|
if (text && !trimmed) {
|
|
@@ -23,17 +27,22 @@ export function normalizeReplyPayload(payload, opts = {}) {
|
|
|
23
27
|
const shouldStripHeartbeat = opts.stripHeartbeat ?? true;
|
|
24
28
|
if (shouldStripHeartbeat && text?.includes(HEARTBEAT_TOKEN)) {
|
|
25
29
|
const stripped = stripHeartbeatToken(text, { mode: "message" });
|
|
26
|
-
if (stripped.didStrip)
|
|
30
|
+
if (stripped.didStrip) {
|
|
27
31
|
opts.onHeartbeatStrip?.();
|
|
28
|
-
|
|
32
|
+
}
|
|
33
|
+
if (stripped.shouldSkip && !hasMedia && !hasChannelData) {
|
|
34
|
+
opts.onSkip?.("heartbeat");
|
|
29
35
|
return null;
|
|
36
|
+
}
|
|
30
37
|
text = stripped.text;
|
|
31
38
|
}
|
|
32
39
|
if (text) {
|
|
33
|
-
text = sanitizeUserFacingText(text);
|
|
40
|
+
text = sanitizeUserFacingText(text, { errorContext: Boolean(payload.isError) });
|
|
34
41
|
}
|
|
35
|
-
if (!text?.trim() && !hasMedia && !hasChannelData)
|
|
42
|
+
if (!text?.trim() && !hasMedia && !hasChannelData) {
|
|
43
|
+
opts.onSkip?.("empty");
|
|
36
44
|
return null;
|
|
45
|
+
}
|
|
37
46
|
// Parse LINE-specific directives from text (quick_replies, location, confirm, buttons)
|
|
38
47
|
let enrichedPayload = { ...payload, text };
|
|
39
48
|
if (text && hasLineDirectives(text)) {
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
+
import { sleep } from "../../utils.js";
|
|
2
|
+
import { registerDispatcher } from "./dispatcher-registry.js";
|
|
1
3
|
import { normalizeReplyPayload } from "./normalize-reply.js";
|
|
2
4
|
const DEFAULT_HUMAN_DELAY_MIN_MS = 800;
|
|
3
5
|
const DEFAULT_HUMAN_DELAY_MAX_MS = 2500;
|
|
4
6
|
/** Generate a random delay within the configured range. */
|
|
5
7
|
function getHumanDelay(config) {
|
|
6
8
|
const mode = config?.mode ?? "off";
|
|
7
|
-
if (mode === "off")
|
|
9
|
+
if (mode === "off") {
|
|
8
10
|
return 0;
|
|
11
|
+
}
|
|
9
12
|
const min = mode === "custom" ? (config?.minMs ?? DEFAULT_HUMAN_DELAY_MIN_MS) : DEFAULT_HUMAN_DELAY_MIN_MS;
|
|
10
13
|
const max = mode === "custom" ? (config?.maxMs ?? DEFAULT_HUMAN_DELAY_MAX_MS) : DEFAULT_HUMAN_DELAY_MAX_MS;
|
|
11
|
-
if (max <= min)
|
|
14
|
+
if (max <= min) {
|
|
12
15
|
return min;
|
|
16
|
+
}
|
|
13
17
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
14
18
|
}
|
|
15
|
-
/** Sleep for a given number of milliseconds. */
|
|
16
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
19
|
function normalizeReplyPayloadInternal(payload, opts) {
|
|
18
20
|
// Prefer dynamic context provider over static context
|
|
19
21
|
const prefixContext = opts.responsePrefixContextProvider?.() ?? opts.responsePrefixContext;
|
|
@@ -21,12 +23,16 @@ function normalizeReplyPayloadInternal(payload, opts) {
|
|
|
21
23
|
responsePrefix: opts.responsePrefix,
|
|
22
24
|
responsePrefixContext: prefixContext,
|
|
23
25
|
onHeartbeatStrip: opts.onHeartbeatStrip,
|
|
26
|
+
onSkip: opts.onSkip,
|
|
24
27
|
});
|
|
25
28
|
}
|
|
26
29
|
export function createReplyDispatcher(options) {
|
|
27
30
|
let sendChain = Promise.resolve();
|
|
28
31
|
// Track in-flight deliveries so we can emit a reliable "idle" signal.
|
|
29
|
-
|
|
32
|
+
// Start with pending=1 as a "reservation" to prevent premature gateway restart.
|
|
33
|
+
// This is decremented when markComplete() is called to signal no more replies will come.
|
|
34
|
+
let pending = 1;
|
|
35
|
+
let completeCalled = false;
|
|
30
36
|
// Track whether we've sent a block reply (for human delay - skip delay on first block).
|
|
31
37
|
let sentFirstBlock = false;
|
|
32
38
|
// Serialize outbound replies to preserve tool/block/final order.
|
|
@@ -35,24 +41,40 @@ export function createReplyDispatcher(options) {
|
|
|
35
41
|
block: 0,
|
|
36
42
|
final: 0,
|
|
37
43
|
};
|
|
44
|
+
// Register this dispatcher globally for gateway restart coordination.
|
|
45
|
+
const { unregister } = registerDispatcher({
|
|
46
|
+
pending: () => pending,
|
|
47
|
+
waitForIdle: () => sendChain,
|
|
48
|
+
});
|
|
38
49
|
const enqueue = (kind, payload) => {
|
|
39
|
-
const normalized = normalizeReplyPayloadInternal(payload,
|
|
40
|
-
|
|
50
|
+
const normalized = normalizeReplyPayloadInternal(payload, {
|
|
51
|
+
responsePrefix: options.responsePrefix,
|
|
52
|
+
responsePrefixContext: options.responsePrefixContext,
|
|
53
|
+
responsePrefixContextProvider: options.responsePrefixContextProvider,
|
|
54
|
+
onHeartbeatStrip: options.onHeartbeatStrip,
|
|
55
|
+
onSkip: (reason) => options.onSkip?.(payload, { kind, reason }),
|
|
56
|
+
});
|
|
57
|
+
if (!normalized) {
|
|
41
58
|
return false;
|
|
59
|
+
}
|
|
42
60
|
queuedCounts[kind] += 1;
|
|
43
61
|
pending += 1;
|
|
44
62
|
// Determine if we should add human-like delay (only for block replies after the first).
|
|
45
63
|
const shouldDelay = kind === "block" && sentFirstBlock;
|
|
46
|
-
if (kind === "block")
|
|
64
|
+
if (kind === "block") {
|
|
47
65
|
sentFirstBlock = true;
|
|
66
|
+
}
|
|
48
67
|
sendChain = sendChain
|
|
49
68
|
.then(async () => {
|
|
50
69
|
// Add human-like delay between block replies for natural rhythm.
|
|
51
70
|
if (shouldDelay) {
|
|
52
71
|
const delayMs = getHumanDelay(options.humanDelay);
|
|
53
|
-
if (delayMs > 0)
|
|
72
|
+
if (delayMs > 0) {
|
|
54
73
|
await sleep(delayMs);
|
|
74
|
+
}
|
|
55
75
|
}
|
|
76
|
+
// Safe: deliver is called inside an async .then() callback, so even a synchronous
|
|
77
|
+
// throw becomes a rejection that flows through .catch()/.finally(), ensuring cleanup.
|
|
56
78
|
await options.deliver(normalized, { kind });
|
|
57
79
|
})
|
|
58
80
|
.catch((err) => {
|
|
@@ -60,22 +82,51 @@ export function createReplyDispatcher(options) {
|
|
|
60
82
|
})
|
|
61
83
|
.finally(() => {
|
|
62
84
|
pending -= 1;
|
|
85
|
+
// Clear reservation if:
|
|
86
|
+
// 1. pending is now 1 (just the reservation left)
|
|
87
|
+
// 2. markComplete has been called
|
|
88
|
+
// 3. No more replies will be enqueued
|
|
89
|
+
if (pending === 1 && completeCalled) {
|
|
90
|
+
pending -= 1; // Clear the reservation
|
|
91
|
+
}
|
|
63
92
|
if (pending === 0) {
|
|
93
|
+
// Unregister from global tracking when idle.
|
|
94
|
+
unregister();
|
|
64
95
|
options.onIdle?.();
|
|
65
96
|
}
|
|
66
97
|
});
|
|
67
98
|
return true;
|
|
68
99
|
};
|
|
100
|
+
const markComplete = () => {
|
|
101
|
+
if (completeCalled) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
completeCalled = true;
|
|
105
|
+
// If no replies were enqueued (pending is still 1 = just the reservation),
|
|
106
|
+
// schedule clearing the reservation after current microtasks complete.
|
|
107
|
+
// This gives any in-flight enqueue() calls a chance to increment pending.
|
|
108
|
+
void Promise.resolve().then(() => {
|
|
109
|
+
if (pending === 1 && completeCalled) {
|
|
110
|
+
// Still just the reservation, no replies were enqueued
|
|
111
|
+
pending -= 1;
|
|
112
|
+
if (pending === 0) {
|
|
113
|
+
unregister();
|
|
114
|
+
options.onIdle?.();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
};
|
|
69
119
|
return {
|
|
70
120
|
sendToolResult: (payload) => enqueue("tool", payload),
|
|
71
121
|
sendBlockReply: (payload) => enqueue("block", payload),
|
|
72
122
|
sendFinalReply: (payload) => enqueue("final", payload),
|
|
73
123
|
waitForIdle: () => sendChain,
|
|
74
124
|
getQueuedCounts: () => ({ ...queuedCounts }),
|
|
125
|
+
markComplete,
|
|
75
126
|
};
|
|
76
127
|
}
|
|
77
128
|
export function createReplyDispatcherWithTyping(options) {
|
|
78
|
-
const { onReplyStart, onIdle, ...dispatcherOptions } = options;
|
|
129
|
+
const { onReplyStart, onIdle, onCleanup, ...dispatcherOptions } = options;
|
|
79
130
|
let typingController;
|
|
80
131
|
const dispatcher = createReplyDispatcher({
|
|
81
132
|
...dispatcherOptions,
|
|
@@ -88,6 +139,7 @@ export function createReplyDispatcherWithTyping(options) {
|
|
|
88
139
|
dispatcher,
|
|
89
140
|
replyOptions: {
|
|
90
141
|
onReplyStart,
|
|
142
|
+
onTypingCleanup: onCleanup,
|
|
91
143
|
onTypingController: (typing) => {
|
|
92
144
|
typingController = typing;
|
|
93
145
|
},
|