@poolzin/pool-bot 2026.2.21 → 2026.2.23
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 +25 -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/device-pair/index.ts +2 -2
- 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/irc/src/accounts.ts +1 -1
- package/extensions/irc/src/onboarding.ts +4 -4
- 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 +10 -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 +10 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +10 -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 +10 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +10 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +10 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +10 -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
- package/dist/agents/openclaw-tools.js +0 -151
- package/dist/agents/tool-security.js +0 -96
- package/dist/gateway/url-validation.js +0 -94
- package/dist/infra/openclaw-root.js +0 -109
- package/dist/infra/tmp-openclaw-dir.js +0 -81
- package/dist/media/path-sanitization.js +0 -78
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
2
|
+
// Keep a conservative input budget to absorb tokenizer variance and provider framing overhead.
|
|
3
|
+
const CONTEXT_INPUT_HEADROOM_RATIO = 0.75;
|
|
4
|
+
const SINGLE_TOOL_RESULT_CONTEXT_SHARE = 0.5;
|
|
5
|
+
const TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE = 2;
|
|
6
|
+
const IMAGE_CHAR_ESTIMATE = 8_000;
|
|
7
|
+
export const CONTEXT_LIMIT_TRUNCATION_NOTICE = "[truncated: output exceeded context limit]";
|
|
8
|
+
const CONTEXT_LIMIT_TRUNCATION_SUFFIX = `\n${CONTEXT_LIMIT_TRUNCATION_NOTICE}`;
|
|
9
|
+
export const PREEMPTIVE_TOOL_RESULT_COMPACTION_PLACEHOLDER = "[compacted: tool output removed to free context]";
|
|
10
|
+
function isTextBlock(block) {
|
|
11
|
+
return !!block && typeof block === "object" && block.type === "text";
|
|
12
|
+
}
|
|
13
|
+
function isImageBlock(block) {
|
|
14
|
+
return !!block && typeof block === "object" && block.type === "image";
|
|
15
|
+
}
|
|
16
|
+
function estimateUnknownChars(value) {
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
return value.length;
|
|
19
|
+
}
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const serialized = JSON.stringify(value);
|
|
25
|
+
return typeof serialized === "string" ? serialized.length : 0;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return 256;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isToolResultMessage(msg) {
|
|
32
|
+
const role = msg.role;
|
|
33
|
+
const type = msg.type;
|
|
34
|
+
return role === "toolResult" || role === "tool" || type === "toolResult";
|
|
35
|
+
}
|
|
36
|
+
function getToolResultContent(msg) {
|
|
37
|
+
if (!isToolResultMessage(msg)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const content = msg.content;
|
|
41
|
+
if (typeof content === "string") {
|
|
42
|
+
return [{ type: "text", text: content }];
|
|
43
|
+
}
|
|
44
|
+
return Array.isArray(content) ? content : [];
|
|
45
|
+
}
|
|
46
|
+
function getToolResultText(msg) {
|
|
47
|
+
const content = getToolResultContent(msg);
|
|
48
|
+
const chunks = [];
|
|
49
|
+
for (const block of content) {
|
|
50
|
+
if (isTextBlock(block)) {
|
|
51
|
+
chunks.push(block.text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return chunks.join("\n");
|
|
55
|
+
}
|
|
56
|
+
function estimateMessageChars(msg) {
|
|
57
|
+
if (!msg || typeof msg !== "object") {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (msg.role === "user") {
|
|
61
|
+
const content = msg.content;
|
|
62
|
+
if (typeof content === "string") {
|
|
63
|
+
return content.length;
|
|
64
|
+
}
|
|
65
|
+
let chars = 0;
|
|
66
|
+
if (Array.isArray(content)) {
|
|
67
|
+
for (const block of content) {
|
|
68
|
+
if (isTextBlock(block)) {
|
|
69
|
+
chars += block.text.length;
|
|
70
|
+
}
|
|
71
|
+
else if (isImageBlock(block)) {
|
|
72
|
+
chars += IMAGE_CHAR_ESTIMATE;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
chars += estimateUnknownChars(block);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return chars;
|
|
80
|
+
}
|
|
81
|
+
if (msg.role === "assistant") {
|
|
82
|
+
let chars = 0;
|
|
83
|
+
const content = msg.content;
|
|
84
|
+
if (Array.isArray(content)) {
|
|
85
|
+
for (const block of content) {
|
|
86
|
+
if (!block || typeof block !== "object") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const typed = block;
|
|
90
|
+
if (typed.type === "text" && typeof typed.text === "string") {
|
|
91
|
+
chars += typed.text.length;
|
|
92
|
+
}
|
|
93
|
+
else if (typed.type === "thinking" && typeof typed.thinking === "string") {
|
|
94
|
+
chars += typed.thinking.length;
|
|
95
|
+
}
|
|
96
|
+
else if (typed.type === "toolCall") {
|
|
97
|
+
try {
|
|
98
|
+
chars += JSON.stringify(typed.arguments ?? {}).length;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
chars += 128;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
chars += estimateUnknownChars(block);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return chars;
|
|
110
|
+
}
|
|
111
|
+
if (isToolResultMessage(msg)) {
|
|
112
|
+
let chars = 0;
|
|
113
|
+
const content = getToolResultContent(msg);
|
|
114
|
+
for (const block of content) {
|
|
115
|
+
if (isTextBlock(block)) {
|
|
116
|
+
chars += block.text.length;
|
|
117
|
+
}
|
|
118
|
+
else if (isImageBlock(block)) {
|
|
119
|
+
chars += IMAGE_CHAR_ESTIMATE;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
chars += estimateUnknownChars(block);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const details = msg.details;
|
|
126
|
+
chars += estimateUnknownChars(details);
|
|
127
|
+
const weightedChars = Math.ceil(chars * (CHARS_PER_TOKEN_ESTIMATE / TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE));
|
|
128
|
+
return Math.max(chars, weightedChars);
|
|
129
|
+
}
|
|
130
|
+
return 256;
|
|
131
|
+
}
|
|
132
|
+
function estimateContextChars(messages) {
|
|
133
|
+
return messages.reduce((sum, msg) => sum + estimateMessageChars(msg), 0);
|
|
134
|
+
}
|
|
135
|
+
function truncateTextToBudget(text, maxChars) {
|
|
136
|
+
if (text.length <= maxChars) {
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
if (maxChars <= 0) {
|
|
140
|
+
return CONTEXT_LIMIT_TRUNCATION_NOTICE;
|
|
141
|
+
}
|
|
142
|
+
const bodyBudget = Math.max(0, maxChars - CONTEXT_LIMIT_TRUNCATION_SUFFIX.length);
|
|
143
|
+
if (bodyBudget <= 0) {
|
|
144
|
+
return CONTEXT_LIMIT_TRUNCATION_NOTICE;
|
|
145
|
+
}
|
|
146
|
+
let cutPoint = bodyBudget;
|
|
147
|
+
const newline = text.lastIndexOf("\n", bodyBudget);
|
|
148
|
+
if (newline > bodyBudget * 0.7) {
|
|
149
|
+
cutPoint = newline;
|
|
150
|
+
}
|
|
151
|
+
return text.slice(0, cutPoint) + CONTEXT_LIMIT_TRUNCATION_SUFFIX;
|
|
152
|
+
}
|
|
153
|
+
function replaceToolResultText(msg, text) {
|
|
154
|
+
const content = msg.content;
|
|
155
|
+
const replacementContent = typeof content === "string" || content === undefined ? text : [{ type: "text", text }];
|
|
156
|
+
const sourceRecord = msg;
|
|
157
|
+
const { details: _details, ...rest } = sourceRecord;
|
|
158
|
+
return {
|
|
159
|
+
...rest,
|
|
160
|
+
content: replacementContent,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function truncateToolResultToChars(msg, maxChars) {
|
|
164
|
+
if (!isToolResultMessage(msg)) {
|
|
165
|
+
return msg;
|
|
166
|
+
}
|
|
167
|
+
const estimatedChars = estimateMessageChars(msg);
|
|
168
|
+
if (estimatedChars <= maxChars) {
|
|
169
|
+
return msg;
|
|
170
|
+
}
|
|
171
|
+
const rawText = getToolResultText(msg);
|
|
172
|
+
if (!rawText) {
|
|
173
|
+
return replaceToolResultText(msg, CONTEXT_LIMIT_TRUNCATION_NOTICE);
|
|
174
|
+
}
|
|
175
|
+
const truncatedText = truncateTextToBudget(rawText, maxChars);
|
|
176
|
+
return replaceToolResultText(msg, truncatedText);
|
|
177
|
+
}
|
|
178
|
+
function compactExistingToolResultsInPlace(params) {
|
|
179
|
+
const { messages, charsNeeded } = params;
|
|
180
|
+
if (charsNeeded <= 0) {
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
let reduced = 0;
|
|
184
|
+
for (let i = 0; i < messages.length; i++) {
|
|
185
|
+
const msg = messages[i];
|
|
186
|
+
if (!isToolResultMessage(msg)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const before = estimateMessageChars(msg);
|
|
190
|
+
if (before <= PREEMPTIVE_TOOL_RESULT_COMPACTION_PLACEHOLDER.length) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const compacted = replaceToolResultText(msg, PREEMPTIVE_TOOL_RESULT_COMPACTION_PLACEHOLDER);
|
|
194
|
+
applyMessageMutationInPlace(msg, compacted);
|
|
195
|
+
const after = estimateMessageChars(msg);
|
|
196
|
+
if (after >= before) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
reduced += before - after;
|
|
200
|
+
if (reduced >= charsNeeded) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return reduced;
|
|
205
|
+
}
|
|
206
|
+
function applyMessageMutationInPlace(target, source) {
|
|
207
|
+
if (target === source) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const targetRecord = target;
|
|
211
|
+
const sourceRecord = source;
|
|
212
|
+
for (const key of Object.keys(targetRecord)) {
|
|
213
|
+
if (!(key in sourceRecord)) {
|
|
214
|
+
delete targetRecord[key];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
Object.assign(targetRecord, sourceRecord);
|
|
218
|
+
}
|
|
219
|
+
function enforceToolResultContextBudgetInPlace(params) {
|
|
220
|
+
const { messages, contextBudgetChars, maxSingleToolResultChars } = params;
|
|
221
|
+
// Ensure each tool result has an upper bound before considering total context usage.
|
|
222
|
+
for (const message of messages) {
|
|
223
|
+
if (!isToolResultMessage(message)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const truncated = truncateToolResultToChars(message, maxSingleToolResultChars);
|
|
227
|
+
applyMessageMutationInPlace(message, truncated);
|
|
228
|
+
}
|
|
229
|
+
let currentChars = estimateContextChars(messages);
|
|
230
|
+
if (currentChars <= contextBudgetChars) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// Compact oldest tool outputs first until the context is back under budget.
|
|
234
|
+
compactExistingToolResultsInPlace({
|
|
235
|
+
messages,
|
|
236
|
+
charsNeeded: currentChars - contextBudgetChars,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export function installToolResultContextGuard(params) {
|
|
240
|
+
const contextWindowTokens = Math.max(1, Math.floor(params.contextWindowTokens));
|
|
241
|
+
const contextBudgetChars = Math.max(1_024, Math.floor(contextWindowTokens * CHARS_PER_TOKEN_ESTIMATE * CONTEXT_INPUT_HEADROOM_RATIO));
|
|
242
|
+
const maxSingleToolResultChars = Math.max(1_024, Math.floor(contextWindowTokens * TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE * SINGLE_TOOL_RESULT_CONTEXT_SHARE));
|
|
243
|
+
// Agent.transformContext is private in pi-coding-agent, so access it via a
|
|
244
|
+
// narrow runtime view to keep callsites type-safe while preserving behavior.
|
|
245
|
+
const mutableAgent = params.agent;
|
|
246
|
+
const originalTransformContext = mutableAgent.transformContext;
|
|
247
|
+
mutableAgent.transformContext = (async (messages, signal) => {
|
|
248
|
+
const transformed = originalTransformContext
|
|
249
|
+
? await originalTransformContext.call(mutableAgent, messages, signal)
|
|
250
|
+
: messages;
|
|
251
|
+
const contextMessages = Array.isArray(transformed) ? transformed : messages;
|
|
252
|
+
enforceToolResultContextBudgetInPlace({
|
|
253
|
+
messages: contextMessages,
|
|
254
|
+
contextBudgetChars,
|
|
255
|
+
maxSingleToolResultChars,
|
|
256
|
+
});
|
|
257
|
+
return contextMessages;
|
|
258
|
+
});
|
|
259
|
+
return () => {
|
|
260
|
+
mutableAgent.transformContext = originalTransformContext;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { compactEmbeddedPiSession } from "./pi-embedded-runner/compact.js";
|
|
2
2
|
export { applyExtraParamsToAgent, resolveExtraParams } from "./pi-embedded-runner/extra-params.js";
|
|
3
3
|
export { applyGoogleTurnOrderingFix } from "./pi-embedded-runner/google.js";
|
|
4
|
-
export { getDmHistoryLimitFromSessionKey, limitHistoryTurns, } from "./pi-embedded-runner/history.js";
|
|
4
|
+
export { getDmHistoryLimitFromSessionKey, getHistoryLimitFromSessionKey, limitHistoryTurns, } from "./pi-embedded-runner/history.js";
|
|
5
5
|
export { resolveEmbeddedSessionLane } from "./pi-embedded-runner/lanes.js";
|
|
6
6
|
export { runEmbeddedPiAgent } from "./pi-embedded-runner/run.js";
|
|
7
7
|
export { abortEmbeddedPiRun, isEmbeddedPiRunActive, isEmbeddedPiRunStreaming, queueEmbeddedPiMessage, waitForEmbeddedPiRunEnd, } from "./pi-embedded-runner/runs.js";
|
|
@@ -1,26 +1,107 @@
|
|
|
1
1
|
import { emitAgentEvent } from "../infra/agent-events.js";
|
|
2
|
+
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
|
2
3
|
import { normalizeTextForComparison } from "./pi-embedded-helpers.js";
|
|
3
4
|
import { isMessagingTool, isMessagingToolSendAction } from "./pi-embedded-messaging.js";
|
|
4
|
-
import { extractToolErrorMessage, extractToolResultText, extractMessagingToolSend, isToolResultError, sanitizeToolResult, } from "./pi-embedded-subscribe.tools.js";
|
|
5
|
+
import { extractToolErrorMessage, extractToolResultMediaPaths, extractToolResultText, extractMessagingToolSend, isToolResultError, sanitizeToolResult, } from "./pi-embedded-subscribe.tools.js";
|
|
5
6
|
import { inferToolMetaFromArgs } from "./pi-embedded-utils.js";
|
|
7
|
+
import { buildToolMutationState, isSameToolMutationAction } from "./tool-mutation.js";
|
|
6
8
|
import { normalizeToolName } from "./tool-policy.js";
|
|
9
|
+
/** Track tool execution start times and args for after_tool_call hook */
|
|
10
|
+
const toolStartData = new Map();
|
|
11
|
+
function isCronAddAction(args) {
|
|
12
|
+
if (!args || typeof args !== "object") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const action = args.action;
|
|
16
|
+
return typeof action === "string" && action.trim().toLowerCase() === "add";
|
|
17
|
+
}
|
|
18
|
+
function buildToolCallSummary(toolName, args, meta) {
|
|
19
|
+
const mutation = buildToolMutationState(toolName, args, meta);
|
|
20
|
+
return {
|
|
21
|
+
meta,
|
|
22
|
+
mutatingAction: mutation.mutatingAction,
|
|
23
|
+
actionFingerprint: mutation.actionFingerprint,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
7
26
|
function extendExecMeta(toolName, args, meta) {
|
|
8
27
|
const normalized = toolName.trim().toLowerCase();
|
|
9
|
-
if (normalized !== "exec" && normalized !== "bash")
|
|
28
|
+
if (normalized !== "exec" && normalized !== "bash") {
|
|
10
29
|
return meta;
|
|
11
|
-
|
|
30
|
+
}
|
|
31
|
+
if (!args || typeof args !== "object") {
|
|
12
32
|
return meta;
|
|
33
|
+
}
|
|
13
34
|
const record = args;
|
|
14
35
|
const flags = [];
|
|
15
|
-
if (record.pty === true)
|
|
36
|
+
if (record.pty === true) {
|
|
16
37
|
flags.push("pty");
|
|
17
|
-
|
|
38
|
+
}
|
|
39
|
+
if (record.elevated === true) {
|
|
18
40
|
flags.push("elevated");
|
|
19
|
-
|
|
41
|
+
}
|
|
42
|
+
if (flags.length === 0) {
|
|
20
43
|
return meta;
|
|
44
|
+
}
|
|
21
45
|
const suffix = flags.join(" · ");
|
|
22
46
|
return meta ? `${meta} · ${suffix}` : suffix;
|
|
23
47
|
}
|
|
48
|
+
function pushUniqueMediaUrl(urls, seen, value) {
|
|
49
|
+
if (typeof value !== "string") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const normalized = value.trim();
|
|
53
|
+
if (!normalized || seen.has(normalized)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
seen.add(normalized);
|
|
57
|
+
urls.push(normalized);
|
|
58
|
+
}
|
|
59
|
+
function collectMessagingMediaUrlsFromRecord(record) {
|
|
60
|
+
const urls = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
pushUniqueMediaUrl(urls, seen, record.media);
|
|
63
|
+
pushUniqueMediaUrl(urls, seen, record.mediaUrl);
|
|
64
|
+
pushUniqueMediaUrl(urls, seen, record.path);
|
|
65
|
+
pushUniqueMediaUrl(urls, seen, record.filePath);
|
|
66
|
+
const mediaUrls = record.mediaUrls;
|
|
67
|
+
if (Array.isArray(mediaUrls)) {
|
|
68
|
+
for (const mediaUrl of mediaUrls) {
|
|
69
|
+
pushUniqueMediaUrl(urls, seen, mediaUrl);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return urls;
|
|
73
|
+
}
|
|
74
|
+
function collectMessagingMediaUrlsFromToolResult(result) {
|
|
75
|
+
const urls = [];
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
const appendFromRecord = (value) => {
|
|
78
|
+
if (!value || typeof value !== "object") {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const extracted = collectMessagingMediaUrlsFromRecord(value);
|
|
82
|
+
for (const url of extracted) {
|
|
83
|
+
if (seen.has(url)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
seen.add(url);
|
|
87
|
+
urls.push(url);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
appendFromRecord(result);
|
|
91
|
+
if (result && typeof result === "object") {
|
|
92
|
+
appendFromRecord(result.details);
|
|
93
|
+
}
|
|
94
|
+
const outputText = extractToolResultText(result);
|
|
95
|
+
if (outputText) {
|
|
96
|
+
try {
|
|
97
|
+
appendFromRecord(JSON.parse(outputText));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Ignore non-JSON tool output.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return urls;
|
|
104
|
+
}
|
|
24
105
|
export async function handleToolExecutionStart(ctx, evt) {
|
|
25
106
|
// Flush pending block replies to preserve message boundaries before tool execution.
|
|
26
107
|
ctx.flushBlockReplyBuffer();
|
|
@@ -31,16 +112,23 @@ export async function handleToolExecutionStart(ctx, evt) {
|
|
|
31
112
|
const toolName = normalizeToolName(rawToolName);
|
|
32
113
|
const toolCallId = String(evt.toolCallId);
|
|
33
114
|
const args = evt.args;
|
|
115
|
+
// Track start time and args for after_tool_call hook
|
|
116
|
+
toolStartData.set(toolCallId, { startTime: Date.now(), args });
|
|
34
117
|
if (toolName === "read") {
|
|
35
118
|
const record = args && typeof args === "object" ? args : {};
|
|
36
|
-
const
|
|
119
|
+
const filePathValue = typeof record.path === "string"
|
|
120
|
+
? record.path
|
|
121
|
+
: typeof record.file_path === "string"
|
|
122
|
+
? record.file_path
|
|
123
|
+
: "";
|
|
124
|
+
const filePath = filePathValue.trim();
|
|
37
125
|
if (!filePath) {
|
|
38
126
|
const argsPreview = typeof args === "string" ? args.slice(0, 200) : undefined;
|
|
39
127
|
ctx.log.warn(`read tool called without path: toolCallId=${toolCallId} argsType=${typeof args}${argsPreview ? ` argsPreview=${argsPreview}` : ""}`);
|
|
40
128
|
}
|
|
41
129
|
}
|
|
42
130
|
const meta = extendExecMeta(toolName, args, inferToolMetaFromArgs(toolName, args));
|
|
43
|
-
ctx.state.toolMetaById.set(toolCallId, meta);
|
|
131
|
+
ctx.state.toolMetaById.set(toolCallId, buildToolCallSummary(toolName, args, meta));
|
|
44
132
|
ctx.log.debug(`embedded run tool start: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`);
|
|
45
133
|
const shouldEmitToolEvents = ctx.shouldEmitToolResult();
|
|
46
134
|
emitAgentEvent({
|
|
@@ -79,6 +167,11 @@ export async function handleToolExecutionStart(ctx, evt) {
|
|
|
79
167
|
ctx.state.pendingMessagingTexts.set(toolCallId, text);
|
|
80
168
|
ctx.log.debug(`Tracking pending messaging text: tool=${toolName} len=${text.length}`);
|
|
81
169
|
}
|
|
170
|
+
// Track media URLs from messaging tool args (pending until tool_execution_end).
|
|
171
|
+
const mediaUrls = collectMessagingMediaUrlsFromRecord(argsRecord);
|
|
172
|
+
if (mediaUrls.length > 0) {
|
|
173
|
+
ctx.state.pendingMessagingMediaUrls.set(toolCallId, mediaUrls);
|
|
174
|
+
}
|
|
82
175
|
}
|
|
83
176
|
}
|
|
84
177
|
}
|
|
@@ -106,14 +199,17 @@ export function handleToolExecutionUpdate(ctx, evt) {
|
|
|
106
199
|
},
|
|
107
200
|
});
|
|
108
201
|
}
|
|
109
|
-
export function handleToolExecutionEnd(ctx, evt) {
|
|
202
|
+
export async function handleToolExecutionEnd(ctx, evt) {
|
|
110
203
|
const toolName = normalizeToolName(String(evt.toolName));
|
|
111
204
|
const toolCallId = String(evt.toolCallId);
|
|
112
205
|
const isError = Boolean(evt.isError);
|
|
113
206
|
const result = evt.result;
|
|
114
207
|
const isToolError = isError || isToolResultError(result);
|
|
115
208
|
const sanitizedResult = sanitizeToolResult(result);
|
|
116
|
-
const
|
|
209
|
+
const startData = toolStartData.get(toolCallId);
|
|
210
|
+
toolStartData.delete(toolCallId);
|
|
211
|
+
const callSummary = ctx.state.toolMetaById.get(toolCallId);
|
|
212
|
+
const meta = callSummary?.meta;
|
|
117
213
|
ctx.state.toolMetas.push({ toolName, meta });
|
|
118
214
|
ctx.state.toolMetaById.delete(toolCallId);
|
|
119
215
|
ctx.state.toolSummaryById.delete(toolCallId);
|
|
@@ -123,8 +219,25 @@ export function handleToolExecutionEnd(ctx, evt) {
|
|
|
123
219
|
toolName,
|
|
124
220
|
meta,
|
|
125
221
|
error: errorMessage,
|
|
222
|
+
mutatingAction: callSummary?.mutatingAction,
|
|
223
|
+
actionFingerprint: callSummary?.actionFingerprint,
|
|
126
224
|
};
|
|
127
225
|
}
|
|
226
|
+
else if (ctx.state.lastToolError) {
|
|
227
|
+
// Keep unresolved mutating failures until the same action succeeds.
|
|
228
|
+
if (ctx.state.lastToolError.mutatingAction) {
|
|
229
|
+
if (isSameToolMutationAction(ctx.state.lastToolError, {
|
|
230
|
+
toolName,
|
|
231
|
+
meta,
|
|
232
|
+
actionFingerprint: callSummary?.actionFingerprint,
|
|
233
|
+
})) {
|
|
234
|
+
ctx.state.lastToolError = undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
ctx.state.lastToolError = undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
128
241
|
// Commit messaging tool text on success, discard on error.
|
|
129
242
|
const pendingText = ctx.state.pendingMessagingTexts.get(toolCallId);
|
|
130
243
|
const pendingTarget = ctx.state.pendingMessagingTargets.get(toolCallId);
|
|
@@ -144,6 +257,27 @@ export function handleToolExecutionEnd(ctx, evt) {
|
|
|
144
257
|
ctx.trimMessagingToolSent();
|
|
145
258
|
}
|
|
146
259
|
}
|
|
260
|
+
const pendingMediaUrls = ctx.state.pendingMessagingMediaUrls.get(toolCallId) ?? [];
|
|
261
|
+
ctx.state.pendingMessagingMediaUrls.delete(toolCallId);
|
|
262
|
+
const startArgs = startData?.args && typeof startData.args === "object"
|
|
263
|
+
? startData.args
|
|
264
|
+
: {};
|
|
265
|
+
const isMessagingSend = pendingMediaUrls.length > 0 ||
|
|
266
|
+
(isMessagingTool(toolName) && isMessagingToolSendAction(toolName, startArgs));
|
|
267
|
+
if (!isToolError && isMessagingSend) {
|
|
268
|
+
const committedMediaUrls = [
|
|
269
|
+
...pendingMediaUrls,
|
|
270
|
+
...collectMessagingMediaUrlsFromToolResult(result),
|
|
271
|
+
];
|
|
272
|
+
if (committedMediaUrls.length > 0) {
|
|
273
|
+
ctx.state.messagingToolSentMediaUrls.push(...committedMediaUrls);
|
|
274
|
+
ctx.trimMessagingToolSent();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Track committed reminders only when cron.add completed successfully.
|
|
278
|
+
if (!isToolError && toolName === "cron" && isCronAddAction(startData?.args)) {
|
|
279
|
+
ctx.state.successfulCronAdds += 1;
|
|
280
|
+
}
|
|
147
281
|
emitAgentEvent({
|
|
148
282
|
runId: ctx.params.runId,
|
|
149
283
|
stream: "tool",
|
|
@@ -173,4 +307,40 @@ export function handleToolExecutionEnd(ctx, evt) {
|
|
|
173
307
|
ctx.emitToolOutput(toolName, meta, outputText);
|
|
174
308
|
}
|
|
175
309
|
}
|
|
310
|
+
// Deliver media from tool results when the verbose emitToolOutput path is off.
|
|
311
|
+
// When shouldEmitToolOutput() is true, emitToolOutput already delivers media
|
|
312
|
+
// via parseReplyDirectives (MEDIA: text extraction), so skip to avoid duplicates.
|
|
313
|
+
if (ctx.params.onToolResult && !isToolError && !ctx.shouldEmitToolOutput()) {
|
|
314
|
+
const mediaPaths = extractToolResultMediaPaths(result);
|
|
315
|
+
if (mediaPaths.length > 0) {
|
|
316
|
+
try {
|
|
317
|
+
void ctx.params.onToolResult({ mediaUrls: mediaPaths });
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// ignore delivery failures
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Run after_tool_call plugin hook (fire-and-forget)
|
|
325
|
+
const hookRunnerAfter = ctx.hookRunner ?? getGlobalHookRunner();
|
|
326
|
+
if (hookRunnerAfter?.hasHooks("after_tool_call")) {
|
|
327
|
+
const durationMs = startData?.startTime != null ? Date.now() - startData.startTime : undefined;
|
|
328
|
+
const toolArgs = startData?.args;
|
|
329
|
+
const hookEvent = {
|
|
330
|
+
toolName,
|
|
331
|
+
params: (toolArgs && typeof toolArgs === "object" ? toolArgs : {}),
|
|
332
|
+
result: sanitizedResult,
|
|
333
|
+
error: isToolError ? extractToolErrorMessage(sanitizedResult) : undefined,
|
|
334
|
+
durationMs,
|
|
335
|
+
};
|
|
336
|
+
void hookRunnerAfter
|
|
337
|
+
.runAfterToolCall(hookEvent, {
|
|
338
|
+
toolName,
|
|
339
|
+
agentId: undefined,
|
|
340
|
+
sessionKey: undefined,
|
|
341
|
+
})
|
|
342
|
+
.catch((err) => {
|
|
343
|
+
ctx.log.warn(`after_tool_call hook failed: tool=${toolName} error=${String(err)}`);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
176
346
|
}
|
|
@@ -53,8 +53,11 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
53
53
|
messagingToolSentTexts: [],
|
|
54
54
|
messagingToolSentTextsNormalized: [],
|
|
55
55
|
messagingToolSentTargets: [],
|
|
56
|
+
messagingToolSentMediaUrls: [],
|
|
56
57
|
pendingMessagingTexts: new Map(),
|
|
57
58
|
pendingMessagingTargets: new Map(),
|
|
59
|
+
successfulCronAdds: 0,
|
|
60
|
+
pendingMessagingMediaUrls: new Map(),
|
|
58
61
|
};
|
|
59
62
|
const usageTotals = {
|
|
60
63
|
input: 0,
|
|
@@ -71,6 +74,7 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
71
74
|
const messagingToolSentTexts = state.messagingToolSentTexts;
|
|
72
75
|
const messagingToolSentTextsNormalized = state.messagingToolSentTextsNormalized;
|
|
73
76
|
const messagingToolSentTargets = state.messagingToolSentTargets;
|
|
77
|
+
const messagingToolSentMediaUrls = state.messagingToolSentMediaUrls;
|
|
74
78
|
const pendingMessagingTexts = state.pendingMessagingTexts;
|
|
75
79
|
const pendingMessagingTargets = state.pendingMessagingTargets;
|
|
76
80
|
const replyDirectiveAccumulator = createStreamingDirectiveAccumulator();
|
|
@@ -158,6 +162,7 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
158
162
|
// These tools can send messages via sendMessage/threadReply actions (or sessions_send with message).
|
|
159
163
|
const MAX_MESSAGING_SENT_TEXTS = 200;
|
|
160
164
|
const MAX_MESSAGING_SENT_TARGETS = 200;
|
|
165
|
+
const MAX_MESSAGING_SENT_MEDIA_URLS = 200;
|
|
161
166
|
const trimMessagingToolSent = () => {
|
|
162
167
|
if (messagingToolSentTexts.length > MAX_MESSAGING_SENT_TEXTS) {
|
|
163
168
|
const overflow = messagingToolSentTexts.length - MAX_MESSAGING_SENT_TEXTS;
|
|
@@ -168,13 +173,20 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
168
173
|
const overflow = messagingToolSentTargets.length - MAX_MESSAGING_SENT_TARGETS;
|
|
169
174
|
messagingToolSentTargets.splice(0, overflow);
|
|
170
175
|
}
|
|
176
|
+
if (messagingToolSentMediaUrls.length > MAX_MESSAGING_SENT_MEDIA_URLS) {
|
|
177
|
+
const overflow = messagingToolSentMediaUrls.length - MAX_MESSAGING_SENT_MEDIA_URLS;
|
|
178
|
+
messagingToolSentMediaUrls.splice(0, overflow);
|
|
179
|
+
}
|
|
171
180
|
};
|
|
172
181
|
const ensureCompactionPromise = () => {
|
|
173
182
|
if (!state.compactionRetryPromise) {
|
|
183
|
+
// Create a single promise that resolves when ALL pending compactions complete
|
|
184
|
+
// (tracked by pendingCompactionRetry counter, decremented in resolveCompactionRetry)
|
|
174
185
|
state.compactionRetryPromise = new Promise((resolve, reject) => {
|
|
175
186
|
state.compactionRetryResolve = resolve;
|
|
176
187
|
state.compactionRetryReject = reject;
|
|
177
188
|
});
|
|
189
|
+
// Prevent unhandled rejection if rejected after all consumers have resolved
|
|
178
190
|
state.compactionRetryPromise.catch((err) => {
|
|
179
191
|
log.debug(`compaction promise rejected (no waiter): ${String(err)}`);
|
|
180
192
|
});
|
|
@@ -469,9 +481,12 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
469
481
|
if (formatted === state.lastStreamedReasoning) {
|
|
470
482
|
return;
|
|
471
483
|
}
|
|
484
|
+
// Compute delta: new text since the last emitted reasoning.
|
|
485
|
+
// Guard against non-prefix changes (e.g. trim/format altering earlier content).
|
|
472
486
|
const prior = state.lastStreamedReasoning ?? "";
|
|
473
487
|
const delta = formatted.startsWith(prior) ? formatted.slice(prior.length) : formatted;
|
|
474
488
|
state.lastStreamedReasoning = formatted;
|
|
489
|
+
// Broadcast thinking event to WebSocket clients in real-time
|
|
475
490
|
emitAgentEvent({
|
|
476
491
|
runId: params.runId,
|
|
477
492
|
stream: "thinking",
|
|
@@ -493,8 +508,11 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
493
508
|
messagingToolSentTexts.length = 0;
|
|
494
509
|
messagingToolSentTextsNormalized.length = 0;
|
|
495
510
|
messagingToolSentTargets.length = 0;
|
|
511
|
+
messagingToolSentMediaUrls.length = 0;
|
|
496
512
|
pendingMessagingTexts.clear();
|
|
497
513
|
pendingMessagingTargets.clear();
|
|
514
|
+
state.successfulCronAdds = 0;
|
|
515
|
+
state.pendingMessagingMediaUrls.clear();
|
|
498
516
|
resetAssistantMessageState(0);
|
|
499
517
|
};
|
|
500
518
|
const noteLastAssistant = (msg) => {
|
|
@@ -538,17 +556,33 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
538
556
|
if (state.unsubscribed) {
|
|
539
557
|
return;
|
|
540
558
|
}
|
|
559
|
+
// Mark as unsubscribed FIRST to prevent waitForCompactionRetry from creating
|
|
560
|
+
// new un-resolvable promises during teardown.
|
|
541
561
|
state.unsubscribed = true;
|
|
562
|
+
// Reject pending compaction wait to unblock awaiting code.
|
|
563
|
+
// Don't resolve, as that would incorrectly signal "compaction complete" when it's still in-flight.
|
|
542
564
|
if (state.compactionRetryPromise) {
|
|
543
565
|
log.debug(`unsubscribe: rejecting compaction wait runId=${params.runId}`);
|
|
544
566
|
const reject = state.compactionRetryReject;
|
|
545
567
|
state.compactionRetryResolve = undefined;
|
|
546
568
|
state.compactionRetryReject = undefined;
|
|
547
569
|
state.compactionRetryPromise = null;
|
|
570
|
+
// Reject with AbortError so it's caught by isAbortError() check in cleanup paths
|
|
548
571
|
const abortErr = new Error("Unsubscribed during compaction");
|
|
549
572
|
abortErr.name = "AbortError";
|
|
550
573
|
reject?.(abortErr);
|
|
551
574
|
}
|
|
575
|
+
// Cancel any in-flight compaction to prevent resource leaks when unsubscribing.
|
|
576
|
+
// Only abort if compaction is actually running to avoid unnecessary work.
|
|
577
|
+
if (params.session.isCompacting) {
|
|
578
|
+
log.debug(`unsubscribe: aborting in-flight compaction runId=${params.runId}`);
|
|
579
|
+
try {
|
|
580
|
+
params.session.abortCompaction();
|
|
581
|
+
}
|
|
582
|
+
catch (err) {
|
|
583
|
+
log.warn(`unsubscribe: compaction abort failed runId=${params.runId} err=${String(err)}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
552
586
|
sessionUnsubscribe();
|
|
553
587
|
};
|
|
554
588
|
return {
|
|
@@ -558,7 +592,9 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
558
592
|
isCompacting: () => state.compactionInFlight || state.pendingCompactionRetry > 0,
|
|
559
593
|
isCompactionInFlight: () => state.compactionInFlight,
|
|
560
594
|
getMessagingToolSentTexts: () => messagingToolSentTexts.slice(),
|
|
595
|
+
getMessagingToolSentMediaUrls: () => messagingToolSentMediaUrls.slice(),
|
|
561
596
|
getMessagingToolSentTargets: () => messagingToolSentTargets.slice(),
|
|
597
|
+
getSuccessfulCronAdds: () => state.successfulCronAdds,
|
|
562
598
|
// Returns true if any messaging tool successfully sent a message.
|
|
563
599
|
// Used to suppress agent's confirmation text (e.g., "Respondi no Telegram!")
|
|
564
600
|
// which is generated AFTER the tool sends the actual answer.
|
|
@@ -567,6 +603,7 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
567
603
|
getUsageTotals,
|
|
568
604
|
getCompactionCount: () => compactionCount,
|
|
569
605
|
waitForCompactionRetry: () => {
|
|
606
|
+
// Reject after unsubscribe so callers treat it as cancellation, not success
|
|
570
607
|
if (state.unsubscribed) {
|
|
571
608
|
const err = new Error("Unsubscribed during compaction wait");
|
|
572
609
|
err.name = "AbortError";
|