@poolzin/pool-bot 2026.3.25 → 2026.3.27
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 +32 -257
- 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,502 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { resolveAgentModelFallbacksOverride } from "../../agents/agent-scope.js";
|
|
4
|
+
import { runCliAgent } from "../../agents/cli-runner.js";
|
|
5
|
+
import { getCliSessionId } from "../../agents/cli-session.js";
|
|
6
|
+
import { runWithModelFallback } from "../../agents/model-fallback.js";
|
|
7
|
+
import { isCliProvider } from "../../agents/model-selection.js";
|
|
8
|
+
import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
|
|
9
|
+
import { isCompactionFailureError, isContextOverflowError, isLikelyContextOverflowError, isTransientHttpError, sanitizeUserFacingText, } from "../../agents/pi-embedded-helpers.js";
|
|
10
|
+
import { resolveAgentIdFromSessionKey, resolveGroupSessionKey, resolveSessionTranscriptPath, updateSessionStore, } from "../../config/sessions.js";
|
|
11
|
+
import { logVerbose } from "../../globals.js";
|
|
12
|
+
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
|
13
|
+
import { defaultRuntime } from "../../runtime.js";
|
|
14
|
+
import { isMarkdownCapableMessageChannel, resolveMessageChannel, } from "../../utils/message-channel.js";
|
|
15
|
+
import { stripHeartbeatToken } from "../heartbeat.js";
|
|
16
|
+
import { isSilentReplyPrefixText, isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
|
|
17
|
+
import { buildThreadingToolContext, resolveEnforceFinalTag } from "./agent-runner-utils.js";
|
|
18
|
+
import { createBlockReplyPayloadKey } from "./block-reply-pipeline.js";
|
|
19
|
+
import { parseReplyDirectives } from "./reply-directives.js";
|
|
20
|
+
import { applyReplyTagsToPayload, isRenderablePayload } from "./reply-payloads.js";
|
|
21
|
+
export async function runAgentTurnWithFallback(params) {
|
|
22
|
+
const TRANSIENT_HTTP_RETRY_DELAY_MS = 2_500;
|
|
23
|
+
let didLogHeartbeatStrip = false;
|
|
24
|
+
let autoCompactionCompleted = false;
|
|
25
|
+
let didRetryTransientHttpError = false;
|
|
26
|
+
// Track payloads sent directly (not via pipeline) during tool flush to avoid duplicates.
|
|
27
|
+
const directlySentBlockKeys = new Set();
|
|
28
|
+
const runId = params.opts?.runId ?? crypto.randomUUID();
|
|
29
|
+
params.opts?.onAgentRunStart?.(runId);
|
|
30
|
+
if (params.sessionKey) {
|
|
31
|
+
registerAgentRunContext(runId, {
|
|
32
|
+
sessionKey: params.sessionKey,
|
|
33
|
+
verboseLevel: params.resolvedVerboseLevel,
|
|
34
|
+
isHeartbeat: params.isHeartbeat,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
let runResult;
|
|
38
|
+
let fallbackProvider = params.followupRun.run.provider;
|
|
39
|
+
let fallbackModel = params.followupRun.run.model;
|
|
40
|
+
let didResetAfterCompactionFailure = false;
|
|
41
|
+
let fallbackAttempts = [];
|
|
42
|
+
while (true) {
|
|
43
|
+
try {
|
|
44
|
+
const allowPartialStream = !(params.followupRun.run.reasoningLevel === "stream" && params.opts?.onReasoningStream);
|
|
45
|
+
const normalizeStreamingText = (payload) => {
|
|
46
|
+
if (!allowPartialStream)
|
|
47
|
+
return { skip: true };
|
|
48
|
+
let text = payload.text;
|
|
49
|
+
if (!params.isHeartbeat && text?.includes("HEARTBEAT_OK")) {
|
|
50
|
+
const stripped = stripHeartbeatToken(text, {
|
|
51
|
+
mode: "message",
|
|
52
|
+
});
|
|
53
|
+
if (stripped.didStrip && !didLogHeartbeatStrip) {
|
|
54
|
+
didLogHeartbeatStrip = true;
|
|
55
|
+
logVerbose("Stripped stray HEARTBEAT_OK token from reply");
|
|
56
|
+
}
|
|
57
|
+
if (stripped.shouldSkip && (payload.mediaUrls?.length ?? 0) === 0) {
|
|
58
|
+
return { skip: true };
|
|
59
|
+
}
|
|
60
|
+
text = stripped.text;
|
|
61
|
+
}
|
|
62
|
+
if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) {
|
|
63
|
+
return { skip: true };
|
|
64
|
+
}
|
|
65
|
+
if (!text) {
|
|
66
|
+
// Allow media-only payloads through (no text but has media)
|
|
67
|
+
if (payload.mediaUrls?.length)
|
|
68
|
+
return { text: undefined, skip: false };
|
|
69
|
+
return { skip: true };
|
|
70
|
+
}
|
|
71
|
+
const sanitized = sanitizeUserFacingText(text, { errorContext: Boolean(payload.isError) });
|
|
72
|
+
if (!sanitized.trim())
|
|
73
|
+
return { skip: true };
|
|
74
|
+
return { text: sanitized, skip: false };
|
|
75
|
+
};
|
|
76
|
+
const handlePartialForTyping = async (payload) => {
|
|
77
|
+
if (isSilentReplyPrefixText(payload.text, SILENT_REPLY_TOKEN)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const { text, skip } = normalizeStreamingText(payload);
|
|
81
|
+
if (skip || !text)
|
|
82
|
+
return undefined;
|
|
83
|
+
await params.typingSignals.signalTextDelta(text);
|
|
84
|
+
return text;
|
|
85
|
+
};
|
|
86
|
+
const blockReplyPipeline = params.blockReplyPipeline;
|
|
87
|
+
const onToolResult = params.opts?.onToolResult;
|
|
88
|
+
const fallbackResult = await runWithModelFallback({
|
|
89
|
+
cfg: params.followupRun.run.config,
|
|
90
|
+
provider: params.followupRun.run.provider,
|
|
91
|
+
model: params.followupRun.run.model,
|
|
92
|
+
agentDir: params.followupRun.run.agentDir,
|
|
93
|
+
fallbacksOverride: resolveAgentModelFallbacksOverride(params.followupRun.run.config, resolveAgentIdFromSessionKey(params.followupRun.run.sessionKey)),
|
|
94
|
+
run: (provider, model) => {
|
|
95
|
+
// Notify that model selection is complete (including after fallback).
|
|
96
|
+
// This allows responsePrefix template interpolation with the actual model.
|
|
97
|
+
params.opts?.onModelSelected?.({
|
|
98
|
+
provider,
|
|
99
|
+
model,
|
|
100
|
+
thinkLevel: params.followupRun.run.thinkLevel,
|
|
101
|
+
});
|
|
102
|
+
if (isCliProvider(provider, params.followupRun.run.config)) {
|
|
103
|
+
const startedAt = Date.now();
|
|
104
|
+
emitAgentEvent({
|
|
105
|
+
runId,
|
|
106
|
+
stream: "lifecycle",
|
|
107
|
+
data: {
|
|
108
|
+
phase: "start",
|
|
109
|
+
startedAt,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
const cliSessionId = getCliSessionId(params.getActiveSessionEntry(), provider);
|
|
113
|
+
return runCliAgent({
|
|
114
|
+
sessionId: params.followupRun.run.sessionId,
|
|
115
|
+
sessionKey: params.sessionKey,
|
|
116
|
+
sessionFile: params.followupRun.run.sessionFile,
|
|
117
|
+
workspaceDir: params.followupRun.run.workspaceDir,
|
|
118
|
+
config: params.followupRun.run.config,
|
|
119
|
+
prompt: params.commandBody,
|
|
120
|
+
provider,
|
|
121
|
+
model,
|
|
122
|
+
thinkLevel: params.followupRun.run.thinkLevel,
|
|
123
|
+
timeoutMs: params.followupRun.run.timeoutMs,
|
|
124
|
+
runId,
|
|
125
|
+
agentId: resolveAgentIdFromSessionKey(params.followupRun.run.sessionKey),
|
|
126
|
+
extraSystemPrompt: params.followupRun.run.extraSystemPrompt,
|
|
127
|
+
ownerNumbers: params.followupRun.run.ownerNumbers,
|
|
128
|
+
cliSessionId,
|
|
129
|
+
images: params.opts?.images,
|
|
130
|
+
})
|
|
131
|
+
.then((result) => {
|
|
132
|
+
// CLI backends don't emit streaming assistant events, so we need to
|
|
133
|
+
// emit one with the final text so server-chat can populate its buffer
|
|
134
|
+
// and send the response to TUI/WebSocket clients.
|
|
135
|
+
const cliText = result.payloads?.[0]?.text?.trim();
|
|
136
|
+
if (cliText) {
|
|
137
|
+
emitAgentEvent({
|
|
138
|
+
runId,
|
|
139
|
+
stream: "assistant",
|
|
140
|
+
data: { text: cliText },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
emitAgentEvent({
|
|
144
|
+
runId,
|
|
145
|
+
stream: "lifecycle",
|
|
146
|
+
data: {
|
|
147
|
+
phase: "end",
|
|
148
|
+
startedAt,
|
|
149
|
+
endedAt: Date.now(),
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
return result;
|
|
153
|
+
})
|
|
154
|
+
.catch((err) => {
|
|
155
|
+
emitAgentEvent({
|
|
156
|
+
runId,
|
|
157
|
+
stream: "lifecycle",
|
|
158
|
+
data: {
|
|
159
|
+
phase: "error",
|
|
160
|
+
startedAt,
|
|
161
|
+
endedAt: Date.now(),
|
|
162
|
+
error: err instanceof Error ? err.message : String(err),
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
throw err;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const authProfileId = provider === params.followupRun.run.provider
|
|
169
|
+
? params.followupRun.run.authProfileId
|
|
170
|
+
: undefined;
|
|
171
|
+
return runEmbeddedPiAgent({
|
|
172
|
+
sessionId: params.followupRun.run.sessionId,
|
|
173
|
+
sessionKey: params.sessionKey,
|
|
174
|
+
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
|
|
175
|
+
agentAccountId: params.sessionCtx.AccountId,
|
|
176
|
+
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
|
|
177
|
+
messageThreadId: params.sessionCtx.MessageThreadId ?? undefined,
|
|
178
|
+
groupId: resolveGroupSessionKey(params.sessionCtx)?.id,
|
|
179
|
+
groupChannel: params.sessionCtx.GroupChannel?.trim() ?? params.sessionCtx.GroupSubject?.trim(),
|
|
180
|
+
groupSpace: params.sessionCtx.GroupSpace?.trim() ?? undefined,
|
|
181
|
+
senderId: params.sessionCtx.SenderId?.trim() || undefined,
|
|
182
|
+
senderName: params.sessionCtx.SenderName?.trim() || undefined,
|
|
183
|
+
senderUsername: params.sessionCtx.SenderUsername?.trim() || undefined,
|
|
184
|
+
senderE164: params.sessionCtx.SenderE164?.trim() || undefined,
|
|
185
|
+
// Provider threading context for tool auto-injection
|
|
186
|
+
...buildThreadingToolContext({
|
|
187
|
+
sessionCtx: params.sessionCtx,
|
|
188
|
+
config: params.followupRun.run.config,
|
|
189
|
+
hasRepliedRef: params.opts?.hasRepliedRef,
|
|
190
|
+
}),
|
|
191
|
+
sessionFile: params.followupRun.run.sessionFile,
|
|
192
|
+
workspaceDir: params.followupRun.run.workspaceDir,
|
|
193
|
+
agentDir: params.followupRun.run.agentDir,
|
|
194
|
+
config: params.followupRun.run.config,
|
|
195
|
+
skillsSnapshot: params.followupRun.run.skillsSnapshot,
|
|
196
|
+
prompt: params.commandBody,
|
|
197
|
+
extraSystemPrompt: params.followupRun.run.extraSystemPrompt,
|
|
198
|
+
ownerNumbers: params.followupRun.run.ownerNumbers,
|
|
199
|
+
enforceFinalTag: resolveEnforceFinalTag(params.followupRun.run, provider),
|
|
200
|
+
provider,
|
|
201
|
+
model,
|
|
202
|
+
authProfileId,
|
|
203
|
+
authProfileIdSource: authProfileId
|
|
204
|
+
? params.followupRun.run.authProfileIdSource
|
|
205
|
+
: undefined,
|
|
206
|
+
thinkLevel: params.followupRun.run.thinkLevel,
|
|
207
|
+
verboseLevel: params.followupRun.run.verboseLevel,
|
|
208
|
+
reasoningLevel: params.followupRun.run.reasoningLevel,
|
|
209
|
+
execOverrides: params.followupRun.run.execOverrides,
|
|
210
|
+
toolResultFormat: (() => {
|
|
211
|
+
const channel = resolveMessageChannel(params.sessionCtx.Surface, params.sessionCtx.Provider);
|
|
212
|
+
if (!channel)
|
|
213
|
+
return "markdown";
|
|
214
|
+
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
|
215
|
+
})(),
|
|
216
|
+
bashElevated: params.followupRun.run.bashElevated,
|
|
217
|
+
timeoutMs: params.followupRun.run.timeoutMs,
|
|
218
|
+
runId,
|
|
219
|
+
images: params.opts?.images,
|
|
220
|
+
abortSignal: params.opts?.abortSignal,
|
|
221
|
+
blockReplyBreak: params.resolvedBlockStreamingBreak,
|
|
222
|
+
blockReplyChunking: params.blockReplyChunking,
|
|
223
|
+
onPartialReply: allowPartialStream
|
|
224
|
+
? async (payload) => {
|
|
225
|
+
const textForTyping = await handlePartialForTyping(payload);
|
|
226
|
+
if (!params.opts?.onPartialReply || textForTyping === undefined)
|
|
227
|
+
return;
|
|
228
|
+
await params.opts.onPartialReply({
|
|
229
|
+
text: textForTyping,
|
|
230
|
+
mediaUrls: payload.mediaUrls,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
: undefined,
|
|
234
|
+
onAssistantMessageStart: async () => {
|
|
235
|
+
await params.typingSignals.signalMessageStart();
|
|
236
|
+
},
|
|
237
|
+
onReasoningStream: params.typingSignals.shouldStartOnReasoning || params.opts?.onReasoningStream
|
|
238
|
+
? async (payload) => {
|
|
239
|
+
await params.typingSignals.signalReasoningDelta();
|
|
240
|
+
await params.opts?.onReasoningStream?.({
|
|
241
|
+
text: payload.text,
|
|
242
|
+
mediaUrls: payload.mediaUrls,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
: undefined,
|
|
246
|
+
onAgentEvent: async (evt) => {
|
|
247
|
+
// Trigger typing when tools start executing.
|
|
248
|
+
// Must await to ensure typing indicator starts before tool summaries are emitted.
|
|
249
|
+
if (evt.stream === "tool") {
|
|
250
|
+
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
|
251
|
+
if (phase === "start" || phase === "update") {
|
|
252
|
+
await params.typingSignals.signalToolStart();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Track auto-compaction completion
|
|
256
|
+
if (evt.stream === "compaction") {
|
|
257
|
+
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
|
258
|
+
const willRetry = Boolean(evt.data.willRetry);
|
|
259
|
+
if (phase === "end" && !willRetry) {
|
|
260
|
+
autoCompactionCompleted = true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
// Always pass onBlockReply so flushBlockReplyBuffer works before tool execution,
|
|
265
|
+
// even when regular block streaming is disabled. The handler sends directly
|
|
266
|
+
// via opts.onBlockReply when the pipeline isn't available.
|
|
267
|
+
onBlockReply: params.opts?.onBlockReply
|
|
268
|
+
? async (payload) => {
|
|
269
|
+
const { text, skip } = normalizeStreamingText(payload);
|
|
270
|
+
const hasPayloadMedia = (payload.mediaUrls?.length ?? 0) > 0;
|
|
271
|
+
if (skip && !hasPayloadMedia)
|
|
272
|
+
return;
|
|
273
|
+
const currentMessageId = params.sessionCtx.MessageSidFull ?? params.sessionCtx.MessageSid;
|
|
274
|
+
const taggedPayload = applyReplyTagsToPayload({
|
|
275
|
+
text,
|
|
276
|
+
mediaUrls: payload.mediaUrls,
|
|
277
|
+
mediaUrl: payload.mediaUrls?.[0],
|
|
278
|
+
replyToId: payload.replyToId,
|
|
279
|
+
replyToTag: payload.replyToTag,
|
|
280
|
+
replyToCurrent: payload.replyToCurrent,
|
|
281
|
+
}, currentMessageId);
|
|
282
|
+
// Let through payloads with audioAsVoice flag even if empty (need to track it)
|
|
283
|
+
if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice)
|
|
284
|
+
return;
|
|
285
|
+
const parsed = parseReplyDirectives(taggedPayload.text ?? "", {
|
|
286
|
+
currentMessageId,
|
|
287
|
+
silentToken: SILENT_REPLY_TOKEN,
|
|
288
|
+
});
|
|
289
|
+
const cleaned = parsed.text || undefined;
|
|
290
|
+
const hasRenderableMedia = Boolean(taggedPayload.mediaUrl) || (taggedPayload.mediaUrls?.length ?? 0) > 0;
|
|
291
|
+
// Skip empty payloads unless they have audioAsVoice flag (need to track it)
|
|
292
|
+
if (!cleaned &&
|
|
293
|
+
!hasRenderableMedia &&
|
|
294
|
+
!payload.audioAsVoice &&
|
|
295
|
+
!parsed.audioAsVoice)
|
|
296
|
+
return;
|
|
297
|
+
if (parsed.isSilent && !hasRenderableMedia)
|
|
298
|
+
return;
|
|
299
|
+
const blockPayload = params.applyReplyToMode({
|
|
300
|
+
...taggedPayload,
|
|
301
|
+
text: cleaned,
|
|
302
|
+
audioAsVoice: Boolean(parsed.audioAsVoice || payload.audioAsVoice),
|
|
303
|
+
replyToId: taggedPayload.replyToId ?? parsed.replyToId,
|
|
304
|
+
replyToTag: taggedPayload.replyToTag || parsed.replyToTag,
|
|
305
|
+
replyToCurrent: taggedPayload.replyToCurrent || parsed.replyToCurrent,
|
|
306
|
+
});
|
|
307
|
+
void params.typingSignals
|
|
308
|
+
.signalTextDelta(cleaned ?? taggedPayload.text)
|
|
309
|
+
.catch((err) => {
|
|
310
|
+
logVerbose(`block reply typing signal failed: ${String(err)}`);
|
|
311
|
+
});
|
|
312
|
+
// Use pipeline if available (block streaming enabled), otherwise send directly
|
|
313
|
+
if (params.blockStreamingEnabled && params.blockReplyPipeline) {
|
|
314
|
+
params.blockReplyPipeline.enqueue(blockPayload);
|
|
315
|
+
}
|
|
316
|
+
else if (params.blockStreamingEnabled) {
|
|
317
|
+
// Send directly when flushing before tool execution (no pipeline but streaming enabled).
|
|
318
|
+
// Track sent key to avoid duplicate in final payloads.
|
|
319
|
+
directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
|
|
320
|
+
await params.opts?.onBlockReply?.(blockPayload);
|
|
321
|
+
}
|
|
322
|
+
// When streaming is disabled entirely, blocks are accumulated in final text instead.
|
|
323
|
+
}
|
|
324
|
+
: undefined,
|
|
325
|
+
onBlockReplyFlush: params.blockStreamingEnabled && blockReplyPipeline
|
|
326
|
+
? async () => {
|
|
327
|
+
await blockReplyPipeline.flush({ force: true });
|
|
328
|
+
}
|
|
329
|
+
: undefined,
|
|
330
|
+
shouldEmitToolResult: params.shouldEmitToolResult,
|
|
331
|
+
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
|
332
|
+
onToolResult: onToolResult
|
|
333
|
+
? (payload) => {
|
|
334
|
+
// `subscribeEmbeddedPiSession` may invoke tool callbacks without awaiting them.
|
|
335
|
+
// If a tool callback starts typing after the run finalized, we can end up with
|
|
336
|
+
// a typing loop that never sees a matching markRunComplete(). Track and drain.
|
|
337
|
+
const task = (async () => {
|
|
338
|
+
const { text, skip } = normalizeStreamingText(payload);
|
|
339
|
+
if (skip)
|
|
340
|
+
return;
|
|
341
|
+
await params.typingSignals.signalTextDelta(text);
|
|
342
|
+
await onToolResult({
|
|
343
|
+
text,
|
|
344
|
+
mediaUrls: payload.mediaUrls,
|
|
345
|
+
});
|
|
346
|
+
})()
|
|
347
|
+
.catch((err) => {
|
|
348
|
+
logVerbose(`tool result delivery failed: ${String(err)}`);
|
|
349
|
+
})
|
|
350
|
+
.finally(() => {
|
|
351
|
+
params.pendingToolTasks.delete(task);
|
|
352
|
+
});
|
|
353
|
+
params.pendingToolTasks.add(task);
|
|
354
|
+
}
|
|
355
|
+
: undefined,
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
runResult = fallbackResult.result;
|
|
360
|
+
fallbackProvider = fallbackResult.provider;
|
|
361
|
+
fallbackModel = fallbackResult.model;
|
|
362
|
+
fallbackAttempts = Array.isArray(fallbackResult.attempts)
|
|
363
|
+
? fallbackResult.attempts.map((attempt) => ({
|
|
364
|
+
provider: String(attempt.provider ?? ""),
|
|
365
|
+
model: String(attempt.model ?? ""),
|
|
366
|
+
error: String(attempt.error ?? ""),
|
|
367
|
+
reason: attempt.reason ? String(attempt.reason) : undefined,
|
|
368
|
+
status: typeof attempt.status === "number" ? attempt.status : undefined,
|
|
369
|
+
code: attempt.code ? String(attempt.code) : undefined,
|
|
370
|
+
}))
|
|
371
|
+
: [];
|
|
372
|
+
// Some embedded runs surface context overflow as an error payload instead of throwing.
|
|
373
|
+
// Treat those as a session-level failure and auto-recover by starting a fresh session.
|
|
374
|
+
const embeddedError = runResult.meta?.error;
|
|
375
|
+
if (embeddedError &&
|
|
376
|
+
isContextOverflowError(embeddedError.message) &&
|
|
377
|
+
!didResetAfterCompactionFailure &&
|
|
378
|
+
(await params.resetSessionAfterCompactionFailure(embeddedError.message))) {
|
|
379
|
+
didResetAfterCompactionFailure = true;
|
|
380
|
+
return {
|
|
381
|
+
kind: "final",
|
|
382
|
+
payload: {
|
|
383
|
+
text: "⚠️ Context limit exceeded. I've reset our conversation to start fresh - please try again.\n\nTo prevent this, increase your compaction buffer by setting `agents.defaults.compaction.reserveTokensFloor` to 4000 or higher in your config.",
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (embeddedError?.kind === "role_ordering") {
|
|
388
|
+
const didReset = await params.resetSessionAfterRoleOrderingConflict(embeddedError.message);
|
|
389
|
+
if (didReset) {
|
|
390
|
+
return {
|
|
391
|
+
kind: "final",
|
|
392
|
+
payload: {
|
|
393
|
+
text: "⚠️ Message ordering conflict. I've reset the conversation - please try again.",
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
402
|
+
const isContextOverflow = isLikelyContextOverflowError(message);
|
|
403
|
+
const isCompactionFailure = isCompactionFailureError(message);
|
|
404
|
+
const isSessionCorruption = /function call turn comes immediately after/i.test(message);
|
|
405
|
+
const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message);
|
|
406
|
+
const isTransientHttp = isTransientHttpError(message);
|
|
407
|
+
if (isCompactionFailure &&
|
|
408
|
+
!didResetAfterCompactionFailure &&
|
|
409
|
+
(await params.resetSessionAfterCompactionFailure(message))) {
|
|
410
|
+
didResetAfterCompactionFailure = true;
|
|
411
|
+
return {
|
|
412
|
+
kind: "final",
|
|
413
|
+
payload: {
|
|
414
|
+
text: "⚠️ Context limit exceeded during compaction. I've reset our conversation to start fresh - please try again.\n\nTo prevent this, increase your compaction buffer by setting `agents.defaults.compaction.reserveTokensFloor` to 4000 or higher in your config.",
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (isRoleOrderingError) {
|
|
419
|
+
const didReset = await params.resetSessionAfterRoleOrderingConflict(message);
|
|
420
|
+
if (didReset) {
|
|
421
|
+
return {
|
|
422
|
+
kind: "final",
|
|
423
|
+
payload: {
|
|
424
|
+
text: "⚠️ Message ordering conflict. I've reset the conversation - please try again.",
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (isTransientHttp && !didRetryTransientHttpError) {
|
|
430
|
+
didRetryTransientHttpError = true;
|
|
431
|
+
defaultRuntime.error(`Transient HTTP provider error before reply (${message}). Retrying once in ${TRANSIENT_HTTP_RETRY_DELAY_MS}ms.`);
|
|
432
|
+
await new Promise((resolve) => {
|
|
433
|
+
setTimeout(resolve, TRANSIENT_HTTP_RETRY_DELAY_MS);
|
|
434
|
+
});
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
// Auto-recover from Gemini session corruption by resetting the session
|
|
438
|
+
if (isSessionCorruption &&
|
|
439
|
+
params.sessionKey &&
|
|
440
|
+
params.activeSessionStore &&
|
|
441
|
+
params.storePath) {
|
|
442
|
+
const sessionKey = params.sessionKey;
|
|
443
|
+
const corruptedSessionId = params.getActiveSessionEntry()?.sessionId;
|
|
444
|
+
defaultRuntime.error(`Session history corrupted (Gemini function call ordering). Resetting session: ${params.sessionKey}`);
|
|
445
|
+
try {
|
|
446
|
+
// Delete transcript file if it exists
|
|
447
|
+
if (corruptedSessionId) {
|
|
448
|
+
const transcriptPath = resolveSessionTranscriptPath(corruptedSessionId);
|
|
449
|
+
try {
|
|
450
|
+
fs.unlinkSync(transcriptPath);
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
// Ignore if file doesn't exist
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// Keep the in-memory snapshot consistent with the on-disk store reset.
|
|
457
|
+
delete params.activeSessionStore[sessionKey];
|
|
458
|
+
// Remove session entry from store using a fresh, locked snapshot.
|
|
459
|
+
await updateSessionStore(params.storePath, (store) => {
|
|
460
|
+
delete store[sessionKey];
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
catch (cleanupErr) {
|
|
464
|
+
defaultRuntime.error(`Failed to reset corrupted session ${params.sessionKey}: ${String(cleanupErr)}`);
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
kind: "final",
|
|
468
|
+
payload: {
|
|
469
|
+
text: "⚠️ Session history was corrupted. I've reset the conversation - please try again!",
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
|
|
474
|
+
const safeMessage = isTransientHttp
|
|
475
|
+
? sanitizeUserFacingText(message, { errorContext: true })
|
|
476
|
+
: message;
|
|
477
|
+
const trimmedMessage = safeMessage.replace(/\.\s*$/, "");
|
|
478
|
+
const fallbackText = isContextOverflow
|
|
479
|
+
? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."
|
|
480
|
+
: isRoleOrderingError
|
|
481
|
+
? "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session."
|
|
482
|
+
: `⚠️ Agent failed before reply: ${trimmedMessage}.\nLogs: poolbot logs --follow`;
|
|
483
|
+
return {
|
|
484
|
+
kind: "final",
|
|
485
|
+
payload: {
|
|
486
|
+
text: fallbackText,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
kind: "success",
|
|
493
|
+
runResult,
|
|
494
|
+
fallbackProvider,
|
|
495
|
+
fallbackModel,
|
|
496
|
+
runId,
|
|
497
|
+
fallbackAttempts,
|
|
498
|
+
didLogHeartbeatStrip,
|
|
499
|
+
autoCompactionCompleted,
|
|
500
|
+
directlySentBlockKeys: directlySentBlockKeys.size > 0 ? directlySentBlockKeys : undefined,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { loadSessionStore } from "../../config/sessions.js";
|
|
2
|
+
import { isAudioFileName } from "../../media/mime.js";
|
|
3
|
+
import { normalizeVerboseLevel } from "../thinking.js";
|
|
4
|
+
import { scheduleFollowupDrain } from "./queue.js";
|
|
5
|
+
const hasAudioMedia = (urls) => Boolean(urls?.some((url) => isAudioFileName(url)));
|
|
6
|
+
export const isAudioPayload = (payload) => hasAudioMedia(payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : undefined));
|
|
7
|
+
export const createShouldEmitToolResult = (params) => {
|
|
8
|
+
// Normalize verbose values from session store/config so false/"false" still means off.
|
|
9
|
+
const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off";
|
|
10
|
+
return () => {
|
|
11
|
+
if (!params.sessionKey || !params.storePath) {
|
|
12
|
+
return fallbackVerbose !== "off";
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const store = loadSessionStore(params.storePath);
|
|
16
|
+
const entry = store[params.sessionKey];
|
|
17
|
+
const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
|
|
18
|
+
if (current)
|
|
19
|
+
return current !== "off";
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// ignore store read failures
|
|
23
|
+
}
|
|
24
|
+
return fallbackVerbose !== "off";
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export const createShouldEmitToolOutput = (params) => {
|
|
28
|
+
// Normalize verbose values from session store/config so false/"false" still means off.
|
|
29
|
+
const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off";
|
|
30
|
+
return () => {
|
|
31
|
+
if (!params.sessionKey || !params.storePath) {
|
|
32
|
+
return fallbackVerbose === "full";
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const store = loadSessionStore(params.storePath);
|
|
36
|
+
const entry = store[params.sessionKey];
|
|
37
|
+
const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
|
|
38
|
+
if (current)
|
|
39
|
+
return current === "full";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// ignore store read failures
|
|
43
|
+
}
|
|
44
|
+
return fallbackVerbose === "full";
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export const finalizeWithFollowup = (value, queueKey, runFollowupTurn) => {
|
|
48
|
+
scheduleFollowupDrain(queueKey, runFollowupTurn);
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
export const signalTypingIfNeeded = async (payloads, typingSignals) => {
|
|
52
|
+
const shouldSignalTyping = payloads.some((payload) => {
|
|
53
|
+
const trimmed = payload.text?.trim();
|
|
54
|
+
if (trimmed)
|
|
55
|
+
return true;
|
|
56
|
+
if (payload.mediaUrl)
|
|
57
|
+
return true;
|
|
58
|
+
if (payload.mediaUrls && payload.mediaUrls.length > 0)
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
if (shouldSignalTyping) {
|
|
63
|
+
await typingSignals.signalRunStart();
|
|
64
|
+
}
|
|
65
|
+
};
|