@poolzin/pool-bot 2026.3.25 → 2026.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/model-fallback.js +5 -4
- package/dist/agents/tools/common.js +16 -201
- package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
- package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
- package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
- package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
- package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
- package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
- package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
- package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
- package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
- package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
- package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
- package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
- package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
- package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
- package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
- package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
- package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
- package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
- package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
- package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
- package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
- package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
- package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
- package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
- package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
- package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
- package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
- package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
- package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
- package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
- package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
- package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
- package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
- package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
- package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
- package/dist/canvas-host/a2ui/index.html +1 -307
- package/dist/channels/channels/directory-config.js +185 -0
- package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
- package/dist/channels/channels/discord/handle-action.js +165 -0
- package/dist/channels/channels/discord.js +413 -0
- package/dist/channels/channels/dock.js +436 -0
- package/dist/channels/channels/index.js +51 -0
- package/dist/channels/channels/plugins/outbound/discord.js +101 -0
- package/dist/channels/channels/whatsapp.js +17 -0
- package/dist/channels/plugins/types.js +1 -1
- package/dist/channels/run-state-machine.js +7 -0
- package/dist/commands/models/auth.js +47 -1
- package/dist/commands-subagents/action-agents.js +44 -0
- package/dist/commands-subagents/action-focus.js +64 -0
- package/dist/commands-subagents/action-help.js +4 -0
- package/dist/commands-subagents/action-info.js +45 -0
- package/dist/commands-subagents/action-kill.js +60 -0
- package/dist/commands-subagents/action-list.js +44 -0
- package/dist/commands-subagents/action-log.js +29 -0
- package/dist/commands-subagents/action-send.js +119 -0
- package/dist/commands-subagents/action-spawn.js +52 -0
- package/dist/commands-subagents/action-unfocus.js +30 -0
- package/dist/commands-subagents/shared.js +303 -0
- package/dist/config/config.js +1 -8
- package/dist/config/types.secrets.js +61 -0
- package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
- package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
- package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
- package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
- package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
- package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
- package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
- package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/method-scopes.js +9 -1
- package/dist/gateway/node-pending-work.js +142 -0
- package/dist/gateway/protocol/index.js +5 -1
- package/dist/gateway/protocol/schema/nodes.js +18 -0
- package/dist/gateway/server-methods/nodes-pending.js +96 -0
- package/dist/gateway/server-methods-list.js +4 -0
- package/dist/gateway/server-methods.js +2 -0
- package/dist/imessage/channel.js +253 -0
- package/dist/imessage/monitor/echo-cache.js +70 -0
- package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
- package/dist/imessage/monitor/reflection-guard.js +50 -0
- package/dist/imessage/monitor/sanitize-outbound.js +25 -0
- package/dist/imessage/monitor/self-chat-cache.js +75 -0
- package/dist/imessage/runtime.js +3 -0
- package/dist/infra/exec-approval-reply.js +7 -0
- package/dist/infra/tmp-openclaw-dir.js +84 -0
- package/dist/pairing/pairing-challenge.js +15 -0
- package/dist/plugin-sdk/account-id.d.ts +1 -0
- package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
- package/dist/plugin-sdk/allow-from.d.ts +27 -0
- package/dist/plugin-sdk/command-auth.d.ts +25 -0
- package/dist/plugin-sdk/command-auth.js +3 -1
- package/dist/plugin-sdk/config-paths.d.ts +6 -0
- package/dist/plugin-sdk/file-lock.d.ts +16 -0
- package/dist/plugin-sdk/index.d.ts +428 -0
- package/dist/plugin-sdk/index.js +237 -103
- package/dist/plugin-sdk/json-store.d.ts +5 -0
- package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
- package/dist/plugin-sdk/onboarding.d.ts +11 -0
- package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
- package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
- package/dist/plugin-sdk/status-helpers.d.ts +25 -0
- package/dist/plugin-sdk/temp-path.d.ts +12 -0
- package/dist/plugin-sdk/text-chunking.d.ts +1 -0
- package/dist/plugin-sdk/tool-send.d.ts +4 -0
- package/dist/plugin-sdk/webhook-path.d.ts +6 -0
- package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
- package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
- package/dist/plugin-sdk-internal/accounts.js +6 -0
- package/dist/plugin-sdk-internal/discord.js +23 -0
- package/dist/plugin-sdk-internal/imessage.js +13 -0
- package/dist/plugin-sdk-internal/setup.js +9 -0
- package/dist/plugin-sdk-internal/signal.js +13 -0
- package/dist/plugin-sdk-internal/slack.js +22 -0
- package/dist/plugin-sdk-internal/telegram.js +32 -0
- package/dist/plugin-sdk-internal/whatsapp.js +29 -0
- package/dist/routing/session-key.js +4 -185
- package/dist/shared/pid-alive.js +2 -61
- package/dist/shared/process-scoped-map.js +5 -7
- package/dist/signal/channel.js +264 -0
- package/dist/signal/monitor/access-policy.js +60 -0
- package/dist/signal/runtime.js +3 -0
- package/dist/slack/account-inspect.js +135 -0
- package/dist/slack/blocks-input.js +7 -38
- package/dist/slack/channel.js +394 -0
- package/dist/slack/interactive-replies.js +28 -0
- package/dist/slack/monitor/channel-type.js +31 -0
- package/dist/slack/monitor/dm-auth.js +49 -0
- package/dist/slack/monitor/events/interactions.modal.js +137 -0
- package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
- package/dist/slack/monitor/events/system-event-context.js +29 -0
- package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
- package/dist/slack/monitor/external-arg-menu-store.js +46 -0
- package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
- package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
- package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
- package/dist/slack/monitor/reconnect-policy.js +78 -0
- package/dist/slack/monitor/slash-commands.runtime.js +1 -0
- package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
- package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
- package/dist/slack/resolve-allowlist-common.js +36 -0
- package/dist/slack/runtime.js +3 -0
- package/dist/slack/sent-thread-cache.js +61 -0
- package/dist/slack/truncate.js +10 -0
- package/dist/telegram/account-inspect.js +175 -0
- package/dist/telegram/allow-from.js +10 -0
- package/dist/telegram/api-fetch.js +18 -0
- package/dist/telegram/approval-buttons.js +30 -0
- package/dist/telegram/audit-membership-runtime.js +61 -0
- package/dist/telegram/bot/delivery.replies.js +508 -0
- package/dist/telegram/bot/delivery.resolve-media.js +227 -0
- package/dist/telegram/bot/delivery.send.js +132 -0
- package/dist/telegram/bot/reply-threading.js +46 -0
- package/dist/telegram/bot-message-context.body.js +186 -0
- package/dist/telegram/bot-message-context.session.js +207 -0
- package/dist/telegram/bot-message-context.types.js +1 -0
- package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
- package/dist/telegram/bot.media.e2e-harness.js +81 -0
- package/dist/telegram/bot.media.test-utils.js +81 -0
- package/dist/telegram/channel-actions.js +225 -0
- package/dist/telegram/channel.js +515 -0
- package/dist/telegram/conversation-route.js +107 -0
- package/dist/telegram/delivery.js +2 -0
- package/dist/telegram/delivery.replies.js +508 -0
- package/dist/telegram/dm-access.js +86 -0
- package/dist/telegram/draft-stream.test-helpers.js +62 -0
- package/dist/telegram/exec-approvals-handler.js +281 -0
- package/dist/telegram/exec-approvals.js +62 -0
- package/dist/telegram/forum-service-message.js +22 -0
- package/dist/telegram/group-config-helpers.js +10 -0
- package/dist/telegram/lane-delivery-state.js +19 -0
- package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
- package/dist/telegram/lane-delivery.js +2 -0
- package/dist/telegram/normalize.js +37 -0
- package/dist/telegram/onboarding.js +192 -0
- package/dist/telegram/outbound-adapter.js +100 -0
- package/dist/telegram/polling-session.js +275 -0
- package/dist/telegram/runtime.js +3 -0
- package/dist/telegram/sendchataction-401-backoff.js +71 -0
- package/dist/telegram/sequential-key.js +46 -0
- package/dist/telegram/status-issues.js +105 -0
- package/dist/telegram/target-writeback.js +165 -0
- package/dist/telegram/thread-bindings.js +560 -0
- package/dist/utils.js +10 -276
- package/dist/wizard/prompts.js +5 -5
- package/extensions/feishu/src/policy.ts +1 -1
- package/extensions/firecrawl/index.test.ts +82 -0
- package/extensions/firecrawl/index.ts +20 -0
- package/extensions/firecrawl/openclaw.plugin.json +8 -0
- package/extensions/firecrawl/package.json +12 -0
- package/extensions/firecrawl/src/config.ts +159 -0
- package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
- package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
- package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
- package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
- package/package.json +1 -1
- package/dist/.buildstamp +0 -1
- package/dist/acp/bindings-store.js +0 -209
- package/dist/acp/control-plane/runtime-cache.js +0 -54
- package/dist/acp/control-plane/runtime-options.js +0 -215
- package/dist/acp/control-plane/session-actor-queue.js +0 -36
- package/dist/acp/index.js +0 -2
- package/dist/acp/runtime/errors.js +0 -47
- package/dist/acp/runtime/registry.js +0 -86
- package/dist/acp/secret-file.js +0 -22
- package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
- package/dist/agents/bash-process-registry.test-helpers.js +0 -29
- package/dist/agents/bash-tools.exec-approval-request.js +0 -20
- package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
- package/dist/agents/bash-tools.exec-host-node.js +0 -235
- package/dist/agents/checkpoint-manager.js +0 -290
- package/dist/agents/claude-cli-runner.js +0 -3
- package/dist/agents/error-classifier.js +0 -251
- package/dist/agents/live-model-filter.js +0 -84
- package/dist/agents/nvidia-models.js +0 -228
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
- package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
- package/dist/agents/provider/config-loader.js +0 -76
- package/dist/agents/provider/index.js +0 -15
- package/dist/agents/provider/models-dev.js +0 -129
- package/dist/agents/provider/session-binding.js +0 -376
- package/dist/agents/queued-file-writer.js +0 -22
- package/dist/agents/skills/bundled-context.js +0 -23
- package/dist/agents/skills/security.js +0 -211
- package/dist/agents/skills/tools-dir.js +0 -9
- package/dist/agents/skills-install-download.js +0 -290
- package/dist/agents/skills-install-output.js +0 -30
- package/dist/agents/skills-install.download-test-utils.js +0 -36
- package/dist/agents/skills.test-helpers.js +0 -13
- package/dist/agents/subagent-announce-reliability.js +0 -160
- package/dist/agents/subagent-registry.mocks.shared.js +0 -12
- package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
- package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
- package/dist/agents/test-helpers/fast-core-tools.js +0 -8
- package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
- package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
- package/dist/agents/tool-display-common.js +0 -915
- package/dist/agents/tool-policy-shared.js +0 -108
- package/dist/agents/tool-policy.conformance.js +0 -14
- package/dist/agents/tool-result-truncation.js +0 -299
- package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
- package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
- package/dist/agents/tools/discord-actions-presence.js +0 -78
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
- package/dist/discord/discord-improvements.js +0 -167
- package/dist/discord/index.js +0 -2
- package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
- package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
- package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
- package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
- package/dist/whatsapp/normalize.js +0 -66
- package/dist/whatsapp/resolve-outbound-target.js +0 -42
- /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
- /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { abortEmbeddedPiRun, isEmbeddedPiRunActive, isEmbeddedPiRunStreaming, resolveEmbeddedSessionLane, } from "../../agents/pi-embedded.js";
|
|
3
|
+
import { resolveSessionAuthProfileOverride } from "../../agents/auth-profiles/session-override.js";
|
|
4
|
+
import { resolveGroupSessionKey, resolveSessionFilePath, updateSessionStore, } from "../../config/sessions.js";
|
|
5
|
+
import { logVerbose } from "../../globals.js";
|
|
6
|
+
import { clearCommandLane, getQueueSize } from "../../process/command-queue.js";
|
|
7
|
+
import { normalizeMainKey } from "../../routing/session-key.js";
|
|
8
|
+
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
|
|
9
|
+
import { hasControlCommand } from "../command-detection.js";
|
|
10
|
+
import { buildInboundMediaNote } from "../media-note.js";
|
|
11
|
+
import { formatXHighModelHint, normalizeThinkLevel, supportsXHighThinking, } from "../thinking.js";
|
|
12
|
+
import { SILENT_REPLY_TOKEN } from "../tokens.js";
|
|
13
|
+
import { runReplyAgent } from "./agent-runner.js";
|
|
14
|
+
import { applySessionHints } from "./body.js";
|
|
15
|
+
import { routeReply } from "./route-reply.js";
|
|
16
|
+
import { buildGroupIntro } from "./groups.js";
|
|
17
|
+
import { resolveQueueSettings } from "./queue.js";
|
|
18
|
+
import { ensureSkillSnapshot, prependSystemEvents } from "./session-updates.js";
|
|
19
|
+
import { resolveTypingMode } from "./typing-mode.js";
|
|
20
|
+
import { buildInboundMetaSystemPrompt, buildInboundUserContextPrefix } from "./inbound-meta.js";
|
|
21
|
+
import { appendUntrustedContext } from "./untrusted-context.js";
|
|
22
|
+
const BARE_SESSION_RESET_PROMPT = "A new session was started via /new or /reset. Greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.";
|
|
23
|
+
export async function runPreparedReply(params) {
|
|
24
|
+
const { ctx, sessionCtx, cfg, agentId, agentDir, agentCfg, sessionCfg, commandAuthorized, command, commandSource, allowTextCommands, directives, defaultActivation, elevatedEnabled, elevatedAllowed, blockStreamingEnabled, blockReplyChunking, resolvedBlockStreamingBreak, modelState, provider, model, perMessageQueueMode, perMessageQueueOptions, typing, opts, defaultProvider, defaultModel, timeoutMs, isNewSession, resetTriggered, systemSent, sessionKey, sessionId, storePath, workspaceDir, sessionStore, } = params;
|
|
25
|
+
let { sessionEntry, resolvedThinkLevel, resolvedVerboseLevel, resolvedReasoningLevel, resolvedElevatedLevel, execOverrides, abortedLastRun, } = params;
|
|
26
|
+
let currentSystemSent = systemSent;
|
|
27
|
+
const isFirstTurnInSession = isNewSession || !currentSystemSent;
|
|
28
|
+
const isGroupChat = sessionCtx.ChatType === "group";
|
|
29
|
+
const wasMentioned = ctx.WasMentioned === true;
|
|
30
|
+
const isHeartbeat = opts?.isHeartbeat === true;
|
|
31
|
+
const typingMode = resolveTypingMode({
|
|
32
|
+
configured: sessionCfg?.typingMode ?? agentCfg?.typingMode,
|
|
33
|
+
isGroupChat,
|
|
34
|
+
wasMentioned,
|
|
35
|
+
isHeartbeat,
|
|
36
|
+
});
|
|
37
|
+
const shouldInjectGroupIntro = Boolean(isGroupChat && (isFirstTurnInSession || sessionEntry?.groupActivationNeedsSystemIntro));
|
|
38
|
+
const groupIntro = shouldInjectGroupIntro
|
|
39
|
+
? buildGroupIntro({
|
|
40
|
+
cfg,
|
|
41
|
+
sessionCtx,
|
|
42
|
+
sessionEntry,
|
|
43
|
+
defaultActivation,
|
|
44
|
+
silentToken: SILENT_REPLY_TOKEN,
|
|
45
|
+
})
|
|
46
|
+
: "";
|
|
47
|
+
const groupSystemPrompt = sessionCtx.GroupSystemPrompt?.trim() ?? "";
|
|
48
|
+
const inboundMetaPrompt = buildInboundMetaSystemPrompt(isNewSession ? sessionCtx : { ...sessionCtx, ThreadStarterBody: undefined });
|
|
49
|
+
const extraSystemPrompt = [inboundMetaPrompt, groupIntro, groupSystemPrompt]
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.join("\n\n");
|
|
52
|
+
const baseBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? "";
|
|
53
|
+
// Use CommandBody/RawBody for bare reset detection (clean message without structural context).
|
|
54
|
+
const rawBodyTrimmed = (ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "").trim();
|
|
55
|
+
const baseBodyTrimmedRaw = baseBody.trim();
|
|
56
|
+
if (allowTextCommands &&
|
|
57
|
+
(!commandAuthorized || !command.isAuthorizedSender) &&
|
|
58
|
+
!baseBodyTrimmedRaw &&
|
|
59
|
+
hasControlCommand(commandSource, cfg)) {
|
|
60
|
+
typing.cleanup();
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const isBareNewOrReset = rawBodyTrimmed === "/new" || rawBodyTrimmed === "/reset";
|
|
64
|
+
const isBareSessionReset = isNewSession &&
|
|
65
|
+
((baseBodyTrimmedRaw.length === 0 && rawBodyTrimmed.length > 0) || isBareNewOrReset);
|
|
66
|
+
const baseBodyFinal = isBareSessionReset ? BARE_SESSION_RESET_PROMPT : baseBody;
|
|
67
|
+
const inboundUserContext = buildInboundUserContextPrefix(isNewSession ? sessionCtx : { ...sessionCtx, ThreadStarterBody: undefined });
|
|
68
|
+
const baseBodyForPrompt = isBareSessionReset
|
|
69
|
+
? baseBodyFinal
|
|
70
|
+
: [inboundUserContext, baseBodyFinal].filter(Boolean).join("\n\n");
|
|
71
|
+
const baseBodyTrimmed = baseBodyForPrompt.trim();
|
|
72
|
+
if (!baseBodyTrimmed) {
|
|
73
|
+
await typing.onReplyStart();
|
|
74
|
+
logVerbose("Inbound body empty after normalization; skipping agent run");
|
|
75
|
+
typing.cleanup();
|
|
76
|
+
return {
|
|
77
|
+
text: "I didn't receive any text in your message. Please resend or add a caption.",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
let prefixedBodyBase = await applySessionHints({
|
|
81
|
+
baseBody: baseBodyForPrompt,
|
|
82
|
+
abortedLastRun,
|
|
83
|
+
sessionEntry,
|
|
84
|
+
sessionStore,
|
|
85
|
+
sessionKey,
|
|
86
|
+
storePath,
|
|
87
|
+
abortKey: command.abortKey,
|
|
88
|
+
messageId: sessionCtx.MessageSid,
|
|
89
|
+
});
|
|
90
|
+
const isGroupSession = sessionEntry?.chatType === "group" || sessionEntry?.chatType === "channel";
|
|
91
|
+
const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey);
|
|
92
|
+
prefixedBodyBase = await prependSystemEvents({
|
|
93
|
+
cfg,
|
|
94
|
+
sessionKey,
|
|
95
|
+
isMainSession,
|
|
96
|
+
isNewSession,
|
|
97
|
+
prefixedBodyBase,
|
|
98
|
+
});
|
|
99
|
+
prefixedBodyBase = appendUntrustedContext(prefixedBodyBase, sessionCtx.UntrustedContext);
|
|
100
|
+
const threadStarterBody = ctx.ThreadStarterBody?.trim();
|
|
101
|
+
const threadStarterNote = isNewSession && threadStarterBody
|
|
102
|
+
? `[Thread starter - for context]\n${threadStarterBody}`
|
|
103
|
+
: undefined;
|
|
104
|
+
const skillResult = await ensureSkillSnapshot({
|
|
105
|
+
sessionEntry,
|
|
106
|
+
sessionStore,
|
|
107
|
+
sessionKey,
|
|
108
|
+
storePath,
|
|
109
|
+
sessionId,
|
|
110
|
+
isFirstTurnInSession,
|
|
111
|
+
workspaceDir,
|
|
112
|
+
cfg,
|
|
113
|
+
skillFilter: opts?.skillFilter,
|
|
114
|
+
});
|
|
115
|
+
sessionEntry = skillResult.sessionEntry ?? sessionEntry;
|
|
116
|
+
currentSystemSent = skillResult.systemSent;
|
|
117
|
+
const skillsSnapshot = skillResult.skillsSnapshot;
|
|
118
|
+
const prefixedBody = [threadStarterNote, prefixedBodyBase].filter(Boolean).join("\n\n");
|
|
119
|
+
const mediaNote = buildInboundMediaNote(ctx);
|
|
120
|
+
const mediaReplyHint = mediaNote
|
|
121
|
+
? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:https://example.com/image.jpg (spaces ok, quote if needed) or a safe relative path like MEDIA:./image.jpg. Avoid absolute paths (MEDIA:/...) and ~ paths — they are blocked for security. Keep caption in the text body."
|
|
122
|
+
: undefined;
|
|
123
|
+
let prefixedCommandBody = mediaNote
|
|
124
|
+
? [mediaNote, mediaReplyHint, prefixedBody ?? ""].filter(Boolean).join("\n").trim()
|
|
125
|
+
: prefixedBody;
|
|
126
|
+
if (!resolvedThinkLevel && prefixedCommandBody) {
|
|
127
|
+
const parts = prefixedCommandBody.split(/\s+/);
|
|
128
|
+
const maybeLevel = normalizeThinkLevel(parts[0]);
|
|
129
|
+
if (maybeLevel && (maybeLevel !== "xhigh" || supportsXHighThinking(provider, model))) {
|
|
130
|
+
resolvedThinkLevel = maybeLevel;
|
|
131
|
+
prefixedCommandBody = parts.slice(1).join(" ").trim();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!resolvedThinkLevel) {
|
|
135
|
+
resolvedThinkLevel = await modelState.resolveDefaultThinkingLevel();
|
|
136
|
+
}
|
|
137
|
+
if (resolvedThinkLevel === "xhigh" && !supportsXHighThinking(provider, model)) {
|
|
138
|
+
const explicitThink = directives.hasThinkDirective && directives.thinkLevel !== undefined;
|
|
139
|
+
if (explicitThink) {
|
|
140
|
+
typing.cleanup();
|
|
141
|
+
return {
|
|
142
|
+
text: `Thinking level "xhigh" is only supported for ${formatXHighModelHint()}. Use /think high or switch to one of those models.`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
resolvedThinkLevel = "high";
|
|
146
|
+
if (sessionEntry && sessionStore && sessionKey && sessionEntry.thinkingLevel === "xhigh") {
|
|
147
|
+
sessionEntry.thinkingLevel = "high";
|
|
148
|
+
sessionEntry.updatedAt = Date.now();
|
|
149
|
+
sessionStore[sessionKey] = sessionEntry;
|
|
150
|
+
if (storePath) {
|
|
151
|
+
await updateSessionStore(storePath, (store) => {
|
|
152
|
+
store[sessionKey] = sessionEntry;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (resetTriggered && command.isAuthorizedSender) {
|
|
158
|
+
const channel = ctx.OriginatingChannel || command.channel;
|
|
159
|
+
const to = ctx.OriginatingTo || command.from || command.to;
|
|
160
|
+
if (channel && to) {
|
|
161
|
+
const modelLabel = `${provider}/${model}`;
|
|
162
|
+
const defaultLabel = `${defaultProvider}/${defaultModel}`;
|
|
163
|
+
const text = modelLabel === defaultLabel
|
|
164
|
+
? `✅ New session started · model: ${modelLabel}`
|
|
165
|
+
: `✅ New session started · model: ${modelLabel} (default: ${defaultLabel})`;
|
|
166
|
+
await routeReply({
|
|
167
|
+
payload: { text },
|
|
168
|
+
channel,
|
|
169
|
+
to,
|
|
170
|
+
sessionKey,
|
|
171
|
+
accountId: ctx.AccountId,
|
|
172
|
+
threadId: ctx.MessageThreadId,
|
|
173
|
+
cfg,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const sessionIdFinal = sessionId ?? crypto.randomUUID();
|
|
178
|
+
const sessionFile = resolveSessionFilePath(sessionIdFinal, sessionEntry);
|
|
179
|
+
const queueBodyBase = [threadStarterNote, baseBodyFinal].filter(Boolean).join("\n\n");
|
|
180
|
+
const queueMessageId = sessionCtx.MessageSid?.trim();
|
|
181
|
+
const queueMessageIdHint = queueMessageId ? `[message_id: ${queueMessageId}]` : "";
|
|
182
|
+
const queueBodyWithId = queueMessageIdHint
|
|
183
|
+
? `${queueBodyBase}\n${queueMessageIdHint}`
|
|
184
|
+
: queueBodyBase;
|
|
185
|
+
const queuedBody = mediaNote
|
|
186
|
+
? [mediaNote, mediaReplyHint, queueBodyWithId].filter(Boolean).join("\n").trim()
|
|
187
|
+
: queueBodyWithId;
|
|
188
|
+
const resolvedQueue = resolveQueueSettings({
|
|
189
|
+
cfg,
|
|
190
|
+
channel: sessionCtx.Provider,
|
|
191
|
+
sessionEntry,
|
|
192
|
+
inlineMode: perMessageQueueMode,
|
|
193
|
+
inlineOptions: perMessageQueueOptions,
|
|
194
|
+
});
|
|
195
|
+
const sessionLaneKey = resolveEmbeddedSessionLane(sessionKey ?? sessionIdFinal);
|
|
196
|
+
const laneSize = getQueueSize(sessionLaneKey);
|
|
197
|
+
if (resolvedQueue.mode === "interrupt" && laneSize > 0) {
|
|
198
|
+
const cleared = clearCommandLane(sessionLaneKey);
|
|
199
|
+
const aborted = abortEmbeddedPiRun(sessionIdFinal);
|
|
200
|
+
logVerbose(`Interrupting ${sessionLaneKey} (cleared ${cleared}, aborted=${aborted})`);
|
|
201
|
+
}
|
|
202
|
+
const queueKey = sessionKey ?? sessionIdFinal;
|
|
203
|
+
const isActive = isEmbeddedPiRunActive(sessionIdFinal);
|
|
204
|
+
const isStreaming = isEmbeddedPiRunStreaming(sessionIdFinal);
|
|
205
|
+
const shouldSteer = resolvedQueue.mode === "steer" || resolvedQueue.mode === "steer-backlog";
|
|
206
|
+
const shouldFollowup = resolvedQueue.mode === "followup" ||
|
|
207
|
+
resolvedQueue.mode === "collect" ||
|
|
208
|
+
resolvedQueue.mode === "steer-backlog";
|
|
209
|
+
const authProfileId = await resolveSessionAuthProfileOverride({
|
|
210
|
+
cfg,
|
|
211
|
+
provider,
|
|
212
|
+
agentDir,
|
|
213
|
+
sessionEntry,
|
|
214
|
+
sessionStore,
|
|
215
|
+
sessionKey,
|
|
216
|
+
storePath,
|
|
217
|
+
isNewSession,
|
|
218
|
+
});
|
|
219
|
+
const authProfileIdSource = sessionEntry?.authProfileOverrideSource;
|
|
220
|
+
const followupRun = {
|
|
221
|
+
prompt: queuedBody,
|
|
222
|
+
messageId: sessionCtx.MessageSidFull ?? sessionCtx.MessageSid,
|
|
223
|
+
summaryLine: baseBodyTrimmedRaw,
|
|
224
|
+
enqueuedAt: Date.now(),
|
|
225
|
+
// Originating channel for reply routing.
|
|
226
|
+
originatingChannel: ctx.OriginatingChannel,
|
|
227
|
+
originatingTo: ctx.OriginatingTo,
|
|
228
|
+
originatingAccountId: ctx.AccountId,
|
|
229
|
+
originatingThreadId: ctx.MessageThreadId,
|
|
230
|
+
originatingChatType: ctx.ChatType,
|
|
231
|
+
run: {
|
|
232
|
+
agentId,
|
|
233
|
+
agentDir,
|
|
234
|
+
sessionId: sessionIdFinal,
|
|
235
|
+
sessionKey,
|
|
236
|
+
messageProvider: sessionCtx.Provider?.trim().toLowerCase() || undefined,
|
|
237
|
+
agentAccountId: sessionCtx.AccountId,
|
|
238
|
+
groupId: resolveGroupSessionKey(sessionCtx)?.id ?? undefined,
|
|
239
|
+
groupChannel: sessionCtx.GroupChannel?.trim() ?? sessionCtx.GroupSubject?.trim(),
|
|
240
|
+
groupSpace: sessionCtx.GroupSpace?.trim() ?? undefined,
|
|
241
|
+
senderId: sessionCtx.SenderId?.trim() || undefined,
|
|
242
|
+
senderName: sessionCtx.SenderName?.trim() || undefined,
|
|
243
|
+
senderUsername: sessionCtx.SenderUsername?.trim() || undefined,
|
|
244
|
+
senderE164: sessionCtx.SenderE164?.trim() || undefined,
|
|
245
|
+
senderIsOwner: command.senderIsOwner,
|
|
246
|
+
sessionFile,
|
|
247
|
+
workspaceDir,
|
|
248
|
+
config: cfg,
|
|
249
|
+
skillsSnapshot,
|
|
250
|
+
provider,
|
|
251
|
+
model,
|
|
252
|
+
authProfileId,
|
|
253
|
+
authProfileIdSource,
|
|
254
|
+
thinkLevel: resolvedThinkLevel,
|
|
255
|
+
verboseLevel: resolvedVerboseLevel,
|
|
256
|
+
reasoningLevel: resolvedReasoningLevel,
|
|
257
|
+
elevatedLevel: resolvedElevatedLevel,
|
|
258
|
+
execOverrides,
|
|
259
|
+
bashElevated: {
|
|
260
|
+
enabled: elevatedEnabled,
|
|
261
|
+
allowed: elevatedAllowed,
|
|
262
|
+
defaultLevel: resolvedElevatedLevel ?? "off",
|
|
263
|
+
},
|
|
264
|
+
timeoutMs,
|
|
265
|
+
blockReplyBreak: resolvedBlockStreamingBreak,
|
|
266
|
+
ownerNumbers: command.ownerList.length > 0 ? command.ownerList : undefined,
|
|
267
|
+
extraSystemPrompt: extraSystemPrompt || undefined,
|
|
268
|
+
...(isReasoningTagProvider(provider) ? { enforceFinalTag: true } : {}),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
return runReplyAgent({
|
|
272
|
+
commandBody: prefixedCommandBody,
|
|
273
|
+
followupRun,
|
|
274
|
+
queueKey,
|
|
275
|
+
resolvedQueue,
|
|
276
|
+
shouldSteer,
|
|
277
|
+
shouldFollowup,
|
|
278
|
+
isActive,
|
|
279
|
+
isStreaming,
|
|
280
|
+
opts,
|
|
281
|
+
typing,
|
|
282
|
+
sessionEntry,
|
|
283
|
+
sessionStore,
|
|
284
|
+
sessionKey,
|
|
285
|
+
storePath,
|
|
286
|
+
defaultModel,
|
|
287
|
+
agentCfgContextTokens: agentCfg?.contextTokens,
|
|
288
|
+
resolvedVerboseLevel: resolvedVerboseLevel ?? "off",
|
|
289
|
+
isNewSession,
|
|
290
|
+
blockStreamingEnabled,
|
|
291
|
+
blockReplyChunking,
|
|
292
|
+
resolvedBlockStreamingBreak,
|
|
293
|
+
sessionCtx,
|
|
294
|
+
shouldInjectGroupIntro,
|
|
295
|
+
typingMode,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { getChannelDock } from "../../channels/dock.js";
|
|
2
|
+
import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
|
|
3
|
+
import { isInternalMessageChannel } from "../../utils/message-channel.js";
|
|
4
|
+
import { normalizeGroupActivation } from "../group-activation.js";
|
|
5
|
+
function extractGroupId(raw) {
|
|
6
|
+
const trimmed = (raw ?? "").trim();
|
|
7
|
+
if (!trimmed)
|
|
8
|
+
return undefined;
|
|
9
|
+
const parts = trimmed.split(":").filter(Boolean);
|
|
10
|
+
if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) {
|
|
11
|
+
return parts.slice(2).join(":") || undefined;
|
|
12
|
+
}
|
|
13
|
+
if (parts.length >= 2 &&
|
|
14
|
+
parts[0]?.toLowerCase() === "whatsapp" &&
|
|
15
|
+
trimmed.toLowerCase().includes("@g.us")) {
|
|
16
|
+
return parts.slice(1).join(":") || undefined;
|
|
17
|
+
}
|
|
18
|
+
if (parts.length >= 2 && (parts[0] === "group" || parts[0] === "channel")) {
|
|
19
|
+
return parts.slice(1).join(":") || undefined;
|
|
20
|
+
}
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
export function resolveGroupRequireMention(params) {
|
|
24
|
+
const { cfg, ctx, groupResolution } = params;
|
|
25
|
+
const rawChannel = groupResolution?.channel ?? ctx.Provider?.trim();
|
|
26
|
+
const channel = normalizeChannelId(rawChannel);
|
|
27
|
+
if (!channel)
|
|
28
|
+
return true;
|
|
29
|
+
const groupId = groupResolution?.id ?? extractGroupId(ctx.From);
|
|
30
|
+
const groupChannel = ctx.GroupChannel?.trim() ?? ctx.GroupSubject?.trim();
|
|
31
|
+
const groupSpace = ctx.GroupSpace?.trim();
|
|
32
|
+
const requireMention = getChannelDock(channel)?.groups?.resolveRequireMention?.({
|
|
33
|
+
cfg,
|
|
34
|
+
groupId,
|
|
35
|
+
groupChannel,
|
|
36
|
+
groupSpace,
|
|
37
|
+
accountId: ctx.AccountId,
|
|
38
|
+
});
|
|
39
|
+
if (typeof requireMention === "boolean")
|
|
40
|
+
return requireMention;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
export function defaultGroupActivation(requireMention) {
|
|
44
|
+
return requireMention === false ? "always" : "mention";
|
|
45
|
+
}
|
|
46
|
+
export function buildGroupIntro(params) {
|
|
47
|
+
const activation = normalizeGroupActivation(params.sessionEntry?.groupActivation) ?? params.defaultActivation;
|
|
48
|
+
const subject = params.sessionCtx.GroupSubject?.trim();
|
|
49
|
+
const members = params.sessionCtx.GroupMembers?.trim();
|
|
50
|
+
const rawProvider = params.sessionCtx.Provider?.trim();
|
|
51
|
+
const providerKey = rawProvider?.toLowerCase() ?? "";
|
|
52
|
+
const providerId = normalizeChannelId(rawProvider);
|
|
53
|
+
const providerLabel = (() => {
|
|
54
|
+
if (!providerKey)
|
|
55
|
+
return "chat";
|
|
56
|
+
if (isInternalMessageChannel(providerKey))
|
|
57
|
+
return "WebChat";
|
|
58
|
+
if (providerId)
|
|
59
|
+
return getChannelPlugin(providerId)?.meta.label ?? providerId;
|
|
60
|
+
return `${providerKey.at(0)?.toUpperCase() ?? ""}${providerKey.slice(1)}`;
|
|
61
|
+
})();
|
|
62
|
+
const subjectLine = subject
|
|
63
|
+
? `You are replying inside the ${providerLabel} group "${subject}".`
|
|
64
|
+
: `You are replying inside a ${providerLabel} group chat.`;
|
|
65
|
+
const membersLine = members ? `Group members: ${members}.` : undefined;
|
|
66
|
+
const activationLine = activation === "always"
|
|
67
|
+
? "Activation: always-on (you receive every group message)."
|
|
68
|
+
: "Activation: trigger-only (you are invoked only when explicitly mentioned; recent context may be included).";
|
|
69
|
+
const groupId = params.sessionEntry?.groupId ?? extractGroupId(params.sessionCtx.From);
|
|
70
|
+
const groupChannel = params.sessionCtx.GroupChannel?.trim() ?? subject;
|
|
71
|
+
const groupSpace = params.sessionCtx.GroupSpace?.trim();
|
|
72
|
+
const providerIdsLine = providerId
|
|
73
|
+
? getChannelDock(providerId)?.groups?.resolveGroupIntroHint?.({
|
|
74
|
+
cfg: params.cfg,
|
|
75
|
+
groupId,
|
|
76
|
+
groupChannel,
|
|
77
|
+
groupSpace,
|
|
78
|
+
accountId: params.sessionCtx.AccountId,
|
|
79
|
+
})
|
|
80
|
+
: undefined;
|
|
81
|
+
const silenceLine = activation === "always"
|
|
82
|
+
? `If no response is needed, reply with exactly "${params.silentToken}" (and nothing else) so Poolbot stays silent. Do not add any other words, punctuation, tags, markdown/code blocks, or explanations.`
|
|
83
|
+
: undefined;
|
|
84
|
+
const cautionLine = activation === "always"
|
|
85
|
+
? "Be extremely selective: reply only when directly addressed or clearly helpful. Otherwise stay silent."
|
|
86
|
+
: undefined;
|
|
87
|
+
const lurkLine = "Be a good group participant: mostly lurk and follow the conversation; reply only when directly addressed or you can add clear value. Emoji reactions are welcome when available.";
|
|
88
|
+
const styleLine = "Write like a human. Avoid Markdown tables. Don't type literal \\n sequences; use real line breaks sparingly.";
|
|
89
|
+
return [
|
|
90
|
+
subjectLine,
|
|
91
|
+
membersLine,
|
|
92
|
+
activationLine,
|
|
93
|
+
providerIdsLine,
|
|
94
|
+
silenceLine,
|
|
95
|
+
cautionLine,
|
|
96
|
+
lurkLine,
|
|
97
|
+
styleLine,
|
|
98
|
+
]
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join(" ")
|
|
101
|
+
.concat(" Address the specific sender noted in the message context.");
|
|
102
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { resolveAgentConfig } from "../../agents/agent-scope.js";
|
|
2
|
+
import { getChannelDock } from "../../channels/dock.js";
|
|
3
|
+
import { normalizeChannelId } from "../../channels/plugins/index.js";
|
|
4
|
+
import { escapeRegExp } from "../../utils.js";
|
|
5
|
+
function deriveMentionPatterns(identity) {
|
|
6
|
+
const patterns = [];
|
|
7
|
+
const name = identity?.name?.trim();
|
|
8
|
+
if (name) {
|
|
9
|
+
const parts = name.split(/\s+/).filter(Boolean).map(escapeRegExp);
|
|
10
|
+
const re = parts.length ? parts.join(String.raw `\s+`) : escapeRegExp(name);
|
|
11
|
+
patterns.push(String.raw `\b@?${re}\b`);
|
|
12
|
+
}
|
|
13
|
+
const emoji = identity?.emoji?.trim();
|
|
14
|
+
if (emoji) {
|
|
15
|
+
patterns.push(escapeRegExp(emoji));
|
|
16
|
+
}
|
|
17
|
+
return patterns;
|
|
18
|
+
}
|
|
19
|
+
const BACKSPACE_CHAR = "\u0008";
|
|
20
|
+
export const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
21
|
+
function normalizeMentionPattern(pattern) {
|
|
22
|
+
if (!pattern.includes(BACKSPACE_CHAR)) {
|
|
23
|
+
return pattern;
|
|
24
|
+
}
|
|
25
|
+
return pattern.split(BACKSPACE_CHAR).join("\\b");
|
|
26
|
+
}
|
|
27
|
+
function normalizeMentionPatterns(patterns) {
|
|
28
|
+
return patterns.map(normalizeMentionPattern);
|
|
29
|
+
}
|
|
30
|
+
function resolveMentionPatterns(cfg, agentId) {
|
|
31
|
+
if (!cfg) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined;
|
|
35
|
+
const agentGroupChat = agentConfig?.groupChat;
|
|
36
|
+
if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
|
|
37
|
+
return agentGroupChat.mentionPatterns ?? [];
|
|
38
|
+
}
|
|
39
|
+
const globalGroupChat = cfg.messages?.groupChat;
|
|
40
|
+
if (globalGroupChat && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
|
|
41
|
+
return globalGroupChat.mentionPatterns ?? [];
|
|
42
|
+
}
|
|
43
|
+
const derived = deriveMentionPatterns(agentConfig?.identity);
|
|
44
|
+
return derived.length > 0 ? derived : [];
|
|
45
|
+
}
|
|
46
|
+
export function buildMentionRegexes(cfg, agentId) {
|
|
47
|
+
const patterns = normalizeMentionPatterns(resolveMentionPatterns(cfg, agentId));
|
|
48
|
+
return patterns
|
|
49
|
+
.map((pattern) => {
|
|
50
|
+
try {
|
|
51
|
+
return new RegExp(pattern, "i");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.filter((value) => Boolean(value));
|
|
58
|
+
}
|
|
59
|
+
export function normalizeMentionText(text) {
|
|
60
|
+
return (text ?? "").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "").toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
export function matchesMentionPatterns(text, mentionRegexes) {
|
|
63
|
+
if (mentionRegexes.length === 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const cleaned = normalizeMentionText(text ?? "");
|
|
67
|
+
if (!cleaned) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return mentionRegexes.some((re) => re.test(cleaned));
|
|
71
|
+
}
|
|
72
|
+
export function matchesMentionWithExplicit(params) {
|
|
73
|
+
const cleaned = normalizeMentionText(params.text ?? "");
|
|
74
|
+
const explicit = params.explicit?.isExplicitlyMentioned === true;
|
|
75
|
+
const explicitAvailable = params.explicit?.canResolveExplicit === true;
|
|
76
|
+
const hasAnyMention = params.explicit?.hasAnyMention === true;
|
|
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) {
|
|
84
|
+
return explicit;
|
|
85
|
+
}
|
|
86
|
+
return explicit || params.mentionRegexes.some((re) => re.test(textToCheck));
|
|
87
|
+
}
|
|
88
|
+
export function stripStructuralPrefixes(text) {
|
|
89
|
+
// Ignore wrapper labels, timestamps, and sender prefixes so directive-only
|
|
90
|
+
// detection still works in group batches that include history/context.
|
|
91
|
+
const afterMarker = text.includes(CURRENT_MESSAGE_MARKER)
|
|
92
|
+
? text.slice(text.indexOf(CURRENT_MESSAGE_MARKER) + CURRENT_MESSAGE_MARKER.length).trimStart()
|
|
93
|
+
: text;
|
|
94
|
+
return afterMarker
|
|
95
|
+
.replace(/\[[^\]]+\]\s*/g, "")
|
|
96
|
+
.replace(/^[ \t]*[A-Za-z0-9+()\-_. ]+:\s*/gm, "")
|
|
97
|
+
.replace(/\\n/g, " ")
|
|
98
|
+
.replace(/\s+/g, " ")
|
|
99
|
+
.trim();
|
|
100
|
+
}
|
|
101
|
+
export function stripMentions(text, ctx, cfg, agentId) {
|
|
102
|
+
let result = text;
|
|
103
|
+
const providerId = ctx.Provider ? normalizeChannelId(ctx.Provider) : null;
|
|
104
|
+
const providerMentions = providerId ? getChannelDock(providerId)?.mentions : undefined;
|
|
105
|
+
const patterns = normalizeMentionPatterns([
|
|
106
|
+
...resolveMentionPatterns(cfg, agentId),
|
|
107
|
+
...(providerMentions?.stripPatterns?.({ ctx, cfg, agentId }) ?? []),
|
|
108
|
+
]);
|
|
109
|
+
for (const p of patterns) {
|
|
110
|
+
try {
|
|
111
|
+
const re = new RegExp(p, "gi");
|
|
112
|
+
result = result.replace(re, " ");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// ignore invalid regex
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (providerMentions?.stripMentions) {
|
|
119
|
+
result = providerMentions.stripMentions({
|
|
120
|
+
text: result,
|
|
121
|
+
ctx,
|
|
122
|
+
cfg,
|
|
123
|
+
agentId,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Generic mention patterns like @123456789 or plain digits
|
|
127
|
+
result = result.replace(/@[0-9+]{5,}/g, " ");
|
|
128
|
+
return result.replace(/\s+/g, " ").trim();
|
|
129
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { logVerbose } from "../../globals.js";
|
|
2
|
+
import { SILENT_REPLY_TOKEN } from "../tokens.js";
|
|
3
|
+
import { createBlockReplyPayloadKey } from "./block-reply-pipeline.js";
|
|
4
|
+
import { parseReplyDirectives } from "./reply-directives.js";
|
|
5
|
+
import { applyReplyTagsToPayload, isRenderablePayload } from "./reply-payloads.js";
|
|
6
|
+
export function normalizeReplyPayloadDirectives(params) {
|
|
7
|
+
const parseMode = params.parseMode ?? "always";
|
|
8
|
+
const silentToken = params.silentToken ?? SILENT_REPLY_TOKEN;
|
|
9
|
+
const sourceText = params.payload.text ?? "";
|
|
10
|
+
const shouldParse = parseMode === "always" ||
|
|
11
|
+
(parseMode === "auto" &&
|
|
12
|
+
(sourceText.includes("[[") ||
|
|
13
|
+
sourceText.includes("MEDIA:") ||
|
|
14
|
+
sourceText.includes(silentToken)));
|
|
15
|
+
const parsed = shouldParse
|
|
16
|
+
? parseReplyDirectives(sourceText, {
|
|
17
|
+
currentMessageId: params.currentMessageId,
|
|
18
|
+
silentToken,
|
|
19
|
+
})
|
|
20
|
+
: undefined;
|
|
21
|
+
let text = parsed ? parsed.text || undefined : params.payload.text || undefined;
|
|
22
|
+
if (params.trimLeadingWhitespace && text) {
|
|
23
|
+
text = text.trimStart() || undefined;
|
|
24
|
+
}
|
|
25
|
+
const mediaUrls = params.payload.mediaUrls ?? parsed?.mediaUrls;
|
|
26
|
+
const mediaUrl = params.payload.mediaUrl ?? parsed?.mediaUrl ?? mediaUrls?.[0];
|
|
27
|
+
return {
|
|
28
|
+
payload: {
|
|
29
|
+
...params.payload,
|
|
30
|
+
text,
|
|
31
|
+
mediaUrls,
|
|
32
|
+
mediaUrl,
|
|
33
|
+
replyToId: params.payload.replyToId ?? parsed?.replyToId,
|
|
34
|
+
replyToTag: params.payload.replyToTag || parsed?.replyToTag,
|
|
35
|
+
replyToCurrent: params.payload.replyToCurrent || parsed?.replyToCurrent,
|
|
36
|
+
audioAsVoice: Boolean(params.payload.audioAsVoice || parsed?.audioAsVoice),
|
|
37
|
+
},
|
|
38
|
+
isSilent: parsed?.isSilent ?? false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const hasRenderableMedia = (payload) => Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
|
42
|
+
export function createBlockReplyDeliveryHandler(params) {
|
|
43
|
+
return async (payload) => {
|
|
44
|
+
const { text, skip } = params.normalizeStreamingText(payload);
|
|
45
|
+
if (skip && !hasRenderableMedia(payload)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const taggedPayload = applyReplyTagsToPayload({
|
|
49
|
+
...payload,
|
|
50
|
+
text,
|
|
51
|
+
mediaUrl: payload.mediaUrl ?? payload.mediaUrls?.[0],
|
|
52
|
+
replyToId: payload.replyToId ??
|
|
53
|
+
(payload.replyToCurrent === false ? undefined : params.currentMessageId),
|
|
54
|
+
}, params.currentMessageId);
|
|
55
|
+
// Let through payloads with audioAsVoice flag even if empty (need to track it).
|
|
56
|
+
if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const normalized = normalizeReplyPayloadDirectives({
|
|
60
|
+
payload: taggedPayload,
|
|
61
|
+
currentMessageId: params.currentMessageId,
|
|
62
|
+
silentToken: SILENT_REPLY_TOKEN,
|
|
63
|
+
trimLeadingWhitespace: true,
|
|
64
|
+
parseMode: "auto",
|
|
65
|
+
});
|
|
66
|
+
const blockPayload = params.applyReplyToMode(normalized.payload);
|
|
67
|
+
const blockHasMedia = hasRenderableMedia(blockPayload);
|
|
68
|
+
// Skip empty payloads unless they have audioAsVoice flag (need to track it).
|
|
69
|
+
if (!blockPayload.text && !blockHasMedia && !blockPayload.audioAsVoice) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (normalized.isSilent && !blockHasMedia) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (blockPayload.text) {
|
|
76
|
+
void params.typingSignals.signalTextDelta(blockPayload.text).catch((err) => {
|
|
77
|
+
logVerbose(`block reply typing signal failed: ${String(err)}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Use pipeline if available (block streaming enabled), otherwise send directly.
|
|
81
|
+
if (params.blockStreamingEnabled && params.blockReplyPipeline) {
|
|
82
|
+
params.blockReplyPipeline.enqueue(blockPayload);
|
|
83
|
+
}
|
|
84
|
+
else if (params.blockStreamingEnabled) {
|
|
85
|
+
// Send directly when flushing before tool execution (no pipeline but streaming enabled).
|
|
86
|
+
// Track sent key to avoid duplicate in final payloads.
|
|
87
|
+
params.directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
|
|
88
|
+
await params.onBlockReply(blockPayload);
|
|
89
|
+
}
|
|
90
|
+
// When streaming is disabled entirely, blocks are accumulated in final text instead.
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { splitMediaFromOutput } from "../../media/parse.js";
|
|
2
|
+
import { parseInlineDirectives } from "../../utils/directive-tags.js";
|
|
3
|
+
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
|
|
4
|
+
export function parseReplyDirectives(raw, options = {}) {
|
|
5
|
+
const split = splitMediaFromOutput(raw);
|
|
6
|
+
let text = split.text ?? "";
|
|
7
|
+
const replyParsed = parseInlineDirectives(text, {
|
|
8
|
+
currentMessageId: options.currentMessageId,
|
|
9
|
+
stripAudioTag: false,
|
|
10
|
+
stripReplyTags: true,
|
|
11
|
+
});
|
|
12
|
+
if (replyParsed.hasReplyTag) {
|
|
13
|
+
text = replyParsed.text;
|
|
14
|
+
}
|
|
15
|
+
const silentToken = options.silentToken ?? SILENT_REPLY_TOKEN;
|
|
16
|
+
const isSilent = isSilentReplyText(text, silentToken);
|
|
17
|
+
if (isSilent) {
|
|
18
|
+
text = "";
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
text,
|
|
22
|
+
mediaUrls: split.mediaUrls,
|
|
23
|
+
mediaUrl: split.mediaUrl,
|
|
24
|
+
replyToId: replyParsed.replyToId,
|
|
25
|
+
replyToCurrent: replyParsed.replyToCurrent,
|
|
26
|
+
replyToTag: replyParsed.hasReplyTag,
|
|
27
|
+
audioAsVoice: split.audioAsVoice,
|
|
28
|
+
isSilent,
|
|
29
|
+
};
|
|
30
|
+
}
|