@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
|
@@ -1,71 +1,121 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { acquireSessionWriteLock } from "../session-write-lock.js";
|
|
2
5
|
import { SANDBOX_BROWSER_REGISTRY_PATH, SANDBOX_REGISTRY_PATH, SANDBOX_STATE_DIR, } from "./constants.js";
|
|
3
|
-
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return Boolean(value) && typeof value === "object";
|
|
8
|
+
}
|
|
9
|
+
function isRegistryEntry(value) {
|
|
10
|
+
return isRecord(value) && typeof value.containerName === "string";
|
|
11
|
+
}
|
|
12
|
+
function isRegistryFile(value) {
|
|
13
|
+
if (!isRecord(value)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const maybeEntries = value.entries;
|
|
17
|
+
return Array.isArray(maybeEntries) && maybeEntries.every(isRegistryEntry);
|
|
18
|
+
}
|
|
19
|
+
async function withRegistryLock(registryPath, fn) {
|
|
20
|
+
const lock = await acquireSessionWriteLock({ sessionFile: registryPath, allowReentrant: false });
|
|
21
|
+
try {
|
|
22
|
+
return await fn();
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await lock.release();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function readRegistryFromFile(registryPath, mode) {
|
|
4
29
|
try {
|
|
5
|
-
const raw = await fs.readFile(
|
|
30
|
+
const raw = await fs.readFile(registryPath, "utf-8");
|
|
6
31
|
const parsed = JSON.parse(raw);
|
|
7
|
-
if (
|
|
32
|
+
if (isRegistryFile(parsed)) {
|
|
8
33
|
return parsed;
|
|
34
|
+
}
|
|
35
|
+
if (mode === "fallback") {
|
|
36
|
+
return { entries: [] };
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Invalid sandbox registry format: ${registryPath}`);
|
|
9
39
|
}
|
|
10
|
-
catch {
|
|
11
|
-
|
|
40
|
+
catch (error) {
|
|
41
|
+
const code = error?.code;
|
|
42
|
+
if (code === "ENOENT") {
|
|
43
|
+
return { entries: [] };
|
|
44
|
+
}
|
|
45
|
+
if (mode === "fallback") {
|
|
46
|
+
return { entries: [] };
|
|
47
|
+
}
|
|
48
|
+
if (error instanceof Error) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Failed to read sandbox registry file: ${registryPath}`, { cause: error });
|
|
12
52
|
}
|
|
13
|
-
return { entries: [] };
|
|
14
53
|
}
|
|
15
|
-
async function
|
|
54
|
+
async function writeRegistryFile(registryPath, registry) {
|
|
16
55
|
await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
|
|
17
|
-
|
|
56
|
+
const payload = `${JSON.stringify(registry, null, 2)}\n`;
|
|
57
|
+
const registryDir = path.dirname(registryPath);
|
|
58
|
+
const tempPath = path.join(registryDir, `${path.basename(registryPath)}.${crypto.randomUUID()}.tmp`);
|
|
59
|
+
await fs.writeFile(tempPath, payload, "utf-8");
|
|
60
|
+
try {
|
|
61
|
+
await fs.rename(tempPath, registryPath);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
await fs.rm(tempPath, { force: true });
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
18
67
|
}
|
|
19
|
-
export async function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
68
|
+
export async function readRegistry() {
|
|
69
|
+
return await readRegistryFromFile(SANDBOX_REGISTRY_PATH, "fallback");
|
|
70
|
+
}
|
|
71
|
+
function upsertEntry(entries, entry) {
|
|
72
|
+
const existing = entries.find((item) => item.containerName === entry.containerName);
|
|
73
|
+
const next = entries.filter((item) => item.containerName !== entry.containerName);
|
|
23
74
|
next.push({
|
|
24
75
|
...entry,
|
|
25
76
|
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
|
|
26
77
|
image: existing?.image ?? entry.image,
|
|
27
78
|
configHash: entry.configHash ?? existing?.configHash,
|
|
28
79
|
});
|
|
29
|
-
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
function removeEntry(entries, containerName) {
|
|
83
|
+
return entries.filter((entry) => entry.containerName !== containerName);
|
|
84
|
+
}
|
|
85
|
+
async function withRegistryMutation(registryPath, mutate) {
|
|
86
|
+
await withRegistryLock(registryPath, async () => {
|
|
87
|
+
const registry = await readRegistryFromFile(registryPath, "strict");
|
|
88
|
+
const next = mutate(registry.entries);
|
|
89
|
+
if (next === null) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await writeRegistryFile(registryPath, { entries: next });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export async function updateRegistry(entry) {
|
|
96
|
+
await withRegistryMutation(SANDBOX_REGISTRY_PATH, (entries) => upsertEntry(entries, entry));
|
|
30
97
|
}
|
|
31
98
|
export async function removeRegistryEntry(containerName) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
99
|
+
await withRegistryMutation(SANDBOX_REGISTRY_PATH, (entries) => {
|
|
100
|
+
const next = removeEntry(entries, containerName);
|
|
101
|
+
if (next.length === entries.length) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return next;
|
|
105
|
+
});
|
|
37
106
|
}
|
|
38
107
|
export async function readBrowserRegistry() {
|
|
39
|
-
|
|
40
|
-
const raw = await fs.readFile(SANDBOX_BROWSER_REGISTRY_PATH, "utf-8");
|
|
41
|
-
const parsed = JSON.parse(raw);
|
|
42
|
-
if (parsed && Array.isArray(parsed.entries))
|
|
43
|
-
return parsed;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// ignore
|
|
47
|
-
}
|
|
48
|
-
return { entries: [] };
|
|
49
|
-
}
|
|
50
|
-
async function writeBrowserRegistry(registry) {
|
|
51
|
-
await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
|
|
52
|
-
await fs.writeFile(SANDBOX_BROWSER_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
|
|
108
|
+
return await readRegistryFromFile(SANDBOX_BROWSER_REGISTRY_PATH, "fallback");
|
|
53
109
|
}
|
|
54
110
|
export async function updateBrowserRegistry(entry) {
|
|
55
|
-
|
|
56
|
-
const existing = registry.entries.find((item) => item.containerName === entry.containerName);
|
|
57
|
-
const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
|
|
58
|
-
next.push({
|
|
59
|
-
...entry,
|
|
60
|
-
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
|
|
61
|
-
image: existing?.image ?? entry.image,
|
|
62
|
-
});
|
|
63
|
-
await writeBrowserRegistry({ entries: next });
|
|
111
|
+
await withRegistryMutation(SANDBOX_BROWSER_REGISTRY_PATH, (entries) => upsertEntry(entries, entry));
|
|
64
112
|
}
|
|
65
113
|
export async function removeBrowserRegistryEntry(containerName) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
await withRegistryMutation(SANDBOX_BROWSER_REGISTRY_PATH, (entries) => {
|
|
115
|
+
const next = removeEntry(entries, containerName);
|
|
116
|
+
if (next.length === entries.length) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return next;
|
|
120
|
+
});
|
|
71
121
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const BLOCKED_ENV_VAR_PATTERNS = [
|
|
2
|
+
/^ANTHROPIC_API_KEY$/i,
|
|
3
|
+
/^OPENAI_API_KEY$/i,
|
|
4
|
+
/^GEMINI_API_KEY$/i,
|
|
5
|
+
/^OPENROUTER_API_KEY$/i,
|
|
6
|
+
/^MINIMAX_API_KEY$/i,
|
|
7
|
+
/^ELEVENLABS_API_KEY$/i,
|
|
8
|
+
/^SYNTHETIC_API_KEY$/i,
|
|
9
|
+
/^TELEGRAM_BOT_TOKEN$/i,
|
|
10
|
+
/^DISCORD_BOT_TOKEN$/i,
|
|
11
|
+
/^SLACK_(BOT|APP)_TOKEN$/i,
|
|
12
|
+
/^LINE_CHANNEL_SECRET$/i,
|
|
13
|
+
/^LINE_CHANNEL_ACCESS_TOKEN$/i,
|
|
14
|
+
/^POOLBOT_GATEWAY_(TOKEN|PASSWORD)$/i,
|
|
15
|
+
/^AWS_(SECRET_ACCESS_KEY|SECRET_KEY|SESSION_TOKEN)$/i,
|
|
16
|
+
/^(GH|GITHUB)_TOKEN$/i,
|
|
17
|
+
/^(AZURE|AZURE_OPENAI|COHERE|AI_GATEWAY|OPENROUTER)_API_KEY$/i,
|
|
18
|
+
/_?(API_KEY|TOKEN|PASSWORD|PRIVATE_KEY|SECRET)$/i,
|
|
19
|
+
];
|
|
20
|
+
const ALLOWED_ENV_VAR_PATTERNS = [
|
|
21
|
+
/^LANG$/,
|
|
22
|
+
/^LC_.*$/i,
|
|
23
|
+
/^PATH$/i,
|
|
24
|
+
/^HOME$/i,
|
|
25
|
+
/^USER$/i,
|
|
26
|
+
/^SHELL$/i,
|
|
27
|
+
/^TERM$/i,
|
|
28
|
+
/^TZ$/i,
|
|
29
|
+
/^NODE_ENV$/i,
|
|
30
|
+
];
|
|
31
|
+
function validateEnvVarValue(value) {
|
|
32
|
+
if (value.includes("\0")) {
|
|
33
|
+
return "Contains null bytes";
|
|
34
|
+
}
|
|
35
|
+
if (value.length > 32768) {
|
|
36
|
+
return "Value exceeds maximum length";
|
|
37
|
+
}
|
|
38
|
+
if (/^[A-Za-z0-9+/=]{80,}$/.test(value)) {
|
|
39
|
+
return "Value looks like base64-encoded credential data";
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function matchesAnyPattern(value, patterns) {
|
|
44
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
45
|
+
}
|
|
46
|
+
export function sanitizeEnvVars(envVars, options = {}) {
|
|
47
|
+
const allowed = {};
|
|
48
|
+
const blocked = [];
|
|
49
|
+
const warnings = [];
|
|
50
|
+
const blockedPatterns = [...BLOCKED_ENV_VAR_PATTERNS, ...(options.customBlockedPatterns ?? [])];
|
|
51
|
+
const allowedPatterns = [...ALLOWED_ENV_VAR_PATTERNS, ...(options.customAllowedPatterns ?? [])];
|
|
52
|
+
for (const [rawKey, value] of Object.entries(envVars)) {
|
|
53
|
+
const key = rawKey.trim();
|
|
54
|
+
if (!key) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (matchesAnyPattern(key, blockedPatterns)) {
|
|
58
|
+
blocked.push(key);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (options.strictMode && !matchesAnyPattern(key, allowedPatterns)) {
|
|
62
|
+
blocked.push(key);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const warning = validateEnvVarValue(value);
|
|
66
|
+
if (warning) {
|
|
67
|
+
if (warning === "Contains null bytes") {
|
|
68
|
+
blocked.push(key);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
warnings.push(`${key}: ${warning}`);
|
|
72
|
+
}
|
|
73
|
+
allowed[key] = value;
|
|
74
|
+
}
|
|
75
|
+
return { allowed, blocked, warnings };
|
|
76
|
+
}
|
|
77
|
+
export function getBlockedPatterns() {
|
|
78
|
+
return BLOCKED_ENV_VAR_PATTERNS.map((pattern) => pattern.source);
|
|
79
|
+
}
|
|
80
|
+
export function getAllowedPatterns() {
|
|
81
|
+
return ALLOWED_ENV_VAR_PATTERNS.map((pattern) => pattern.source);
|
|
82
|
+
}
|
|
@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
5
6
|
const HTTP_URL_RE = /^https?:\/\//i;
|
|
6
7
|
const DATA_URL_RE = /^data:/i;
|
|
7
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
8
8
|
function normalizeUnicodeSpaces(str) {
|
|
9
9
|
return str.replace(UNICODE_SPACES, " ");
|
|
10
10
|
}
|
|
@@ -20,15 +20,16 @@ function expandPath(filePath) {
|
|
|
20
20
|
}
|
|
21
21
|
function resolveToCwd(filePath, cwd) {
|
|
22
22
|
const expanded = expandPath(filePath);
|
|
23
|
-
if (path.isAbsolute(expanded))
|
|
23
|
+
if (path.isAbsolute(expanded)) {
|
|
24
24
|
return expanded;
|
|
25
|
+
}
|
|
25
26
|
return path.resolve(cwd, expanded);
|
|
26
27
|
}
|
|
27
28
|
export function resolveSandboxInputPath(filePath, cwd) {
|
|
28
29
|
return resolveToCwd(filePath, cwd);
|
|
29
30
|
}
|
|
30
31
|
export function resolveSandboxPath(params) {
|
|
31
|
-
const resolved =
|
|
32
|
+
const resolved = resolveSandboxInputPath(params.filePath, params.cwd);
|
|
32
33
|
const rootResolved = path.resolve(params.root);
|
|
33
34
|
const relative = path.relative(rootResolved, resolved);
|
|
34
35
|
if (!relative || relative === "") {
|
|
@@ -41,7 +42,9 @@ export function resolveSandboxPath(params) {
|
|
|
41
42
|
}
|
|
42
43
|
export async function assertSandboxPath(params) {
|
|
43
44
|
const resolved = resolveSandboxPath(params);
|
|
44
|
-
await
|
|
45
|
+
await assertNoSymlinkEscape(resolved.relative, path.resolve(params.root), {
|
|
46
|
+
allowFinalSymlink: params.allowFinalSymlink,
|
|
47
|
+
});
|
|
45
48
|
return resolved;
|
|
46
49
|
}
|
|
47
50
|
export function assertMediaNotDataUrl(media) {
|
|
@@ -52,10 +55,12 @@ export function assertMediaNotDataUrl(media) {
|
|
|
52
55
|
}
|
|
53
56
|
export async function resolveSandboxedMediaSource(params) {
|
|
54
57
|
const raw = params.media.trim();
|
|
55
|
-
if (!raw)
|
|
58
|
+
if (!raw) {
|
|
56
59
|
return raw;
|
|
57
|
-
|
|
60
|
+
}
|
|
61
|
+
if (HTTP_URL_RE.test(raw)) {
|
|
58
62
|
return raw;
|
|
63
|
+
}
|
|
59
64
|
let candidate = raw;
|
|
60
65
|
if (/^file:\/\//i.test(candidate)) {
|
|
61
66
|
try {
|
|
@@ -72,17 +77,30 @@ export async function resolveSandboxedMediaSource(params) {
|
|
|
72
77
|
});
|
|
73
78
|
return resolved.resolved;
|
|
74
79
|
}
|
|
75
|
-
async function
|
|
76
|
-
if (!relative)
|
|
80
|
+
async function assertNoSymlinkEscape(relative, root, options) {
|
|
81
|
+
if (!relative) {
|
|
77
82
|
return;
|
|
83
|
+
}
|
|
84
|
+
const rootReal = await tryRealpath(root);
|
|
78
85
|
const parts = relative.split(path.sep).filter(Boolean);
|
|
79
86
|
let current = root;
|
|
80
|
-
for (
|
|
87
|
+
for (let idx = 0; idx < parts.length; idx += 1) {
|
|
88
|
+
const part = parts[idx];
|
|
89
|
+
const isLast = idx === parts.length - 1;
|
|
81
90
|
current = path.join(current, part);
|
|
82
91
|
try {
|
|
83
92
|
const stat = await fs.lstat(current);
|
|
84
93
|
if (stat.isSymbolicLink()) {
|
|
85
|
-
|
|
94
|
+
// Unlinking a symlink itself is safe even if it points outside the root. What we
|
|
95
|
+
// must prevent is traversing through a symlink to reach targets outside root.
|
|
96
|
+
if (options?.allowFinalSymlink && isLast) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const target = await tryRealpath(current);
|
|
100
|
+
if (!isPathInside(rootReal, target)) {
|
|
101
|
+
throw new Error(`Symlink escapes sandbox root (${shortPath(rootReal)}): ${shortPath(current)}`);
|
|
102
|
+
}
|
|
103
|
+
current = target;
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
106
|
catch (err) {
|
|
@@ -94,6 +112,21 @@ async function assertNoSymlink(relative, root) {
|
|
|
94
112
|
}
|
|
95
113
|
}
|
|
96
114
|
}
|
|
115
|
+
async function tryRealpath(value) {
|
|
116
|
+
try {
|
|
117
|
+
return await fs.realpath(value);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return path.resolve(value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function isPathInside(root, target) {
|
|
124
|
+
const relative = path.relative(root, target);
|
|
125
|
+
if (!relative || relative === "") {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return !(relative.startsWith("..") || path.isAbsolute(relative));
|
|
129
|
+
}
|
|
97
130
|
function shortPath(value) {
|
|
98
131
|
if (value.startsWith(os.homedir())) {
|
|
99
132
|
return `~${value.slice(os.homedir().length)}`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
|
2
|
+
import { applyInputProvenanceToUserMessage, } from "../sessions/input-provenance.js";
|
|
2
3
|
import { installSessionToolResultGuard } from "./session-tool-result-guard.js";
|
|
3
4
|
/**
|
|
4
5
|
* Apply the tool-result guard to a SessionManager exactly once and expose
|
|
@@ -9,25 +10,36 @@ export function guardSessionManager(sessionManager, opts) {
|
|
|
9
10
|
return sessionManager;
|
|
10
11
|
}
|
|
11
12
|
const hookRunner = getGlobalHookRunner();
|
|
12
|
-
const
|
|
13
|
-
? (
|
|
14
|
-
|
|
15
|
-
toolName: meta.toolName,
|
|
16
|
-
toolCallId: meta.toolCallId,
|
|
17
|
-
message,
|
|
18
|
-
isSynthetic: meta.isSynthetic,
|
|
19
|
-
}, {
|
|
13
|
+
const beforeMessageWrite = hookRunner?.hasHooks("before_message_write")
|
|
14
|
+
? (event) => {
|
|
15
|
+
return hookRunner.runBeforeMessageWrite(event, {
|
|
20
16
|
agentId: opts?.agentId,
|
|
21
17
|
sessionKey: opts?.sessionKey,
|
|
22
|
-
toolName: meta.toolName,
|
|
23
|
-
toolCallId: meta.toolCallId,
|
|
24
18
|
});
|
|
25
|
-
return out?.message ?? message;
|
|
26
19
|
}
|
|
27
20
|
: undefined;
|
|
21
|
+
const transform = hookRunner?.hasHooks("tool_result_persist")
|
|
22
|
+
? // oxlint-disable-next-line typescript/no-explicit-any
|
|
23
|
+
(message, meta) => {
|
|
24
|
+
const out = hookRunner.runToolResultPersist({
|
|
25
|
+
toolName: meta.toolName,
|
|
26
|
+
toolCallId: meta.toolCallId,
|
|
27
|
+
message,
|
|
28
|
+
isSynthetic: meta.isSynthetic,
|
|
29
|
+
}, {
|
|
30
|
+
agentId: opts?.agentId,
|
|
31
|
+
sessionKey: opts?.sessionKey,
|
|
32
|
+
toolName: meta.toolName,
|
|
33
|
+
toolCallId: meta.toolCallId,
|
|
34
|
+
});
|
|
35
|
+
return out?.message ?? message;
|
|
36
|
+
}
|
|
37
|
+
: undefined;
|
|
28
38
|
const guard = installSessionToolResultGuard(sessionManager, {
|
|
39
|
+
transformMessageForPersistence: (message) => applyInputProvenanceToUserMessage(message, opts?.inputProvenance),
|
|
29
40
|
transformToolResultForPersistence: transform,
|
|
30
41
|
allowSyntheticToolResults: opts?.allowSyntheticToolResults,
|
|
42
|
+
beforeMessageWriteHook: beforeMessageWrite,
|
|
31
43
|
});
|
|
32
44
|
sessionManager.flushPendingToolResults = guard.flushPendingToolResults;
|
|
33
45
|
return sessionManager;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
|
2
2
|
import { HARD_MAX_TOOL_RESULT_CHARS } from "./pi-embedded-runner/tool-result-truncation.js";
|
|
3
3
|
import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
|
|
4
|
+
import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
|
|
4
5
|
const GUARD_TRUNCATION_SUFFIX = "\n\n⚠️ [Content truncated during persistence — original exceeded size limit. " +
|
|
5
6
|
"Use offset/limit parameters or request specific sections for large content.]";
|
|
6
7
|
/**
|
|
@@ -57,48 +58,36 @@ function capToolResultSize(msg) {
|
|
|
57
58
|
});
|
|
58
59
|
return { ...msg, content: newContent };
|
|
59
60
|
}
|
|
60
|
-
function extractAssistantToolCalls(msg) {
|
|
61
|
-
const content = msg.content;
|
|
62
|
-
if (!Array.isArray(content)) {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
const toolCalls = [];
|
|
66
|
-
for (const block of content) {
|
|
67
|
-
if (!block || typeof block !== "object") {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const rec = block;
|
|
71
|
-
if (typeof rec.id !== "string" || !rec.id) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
|
|
75
|
-
toolCalls.push({
|
|
76
|
-
id: rec.id,
|
|
77
|
-
name: typeof rec.name === "string" ? rec.name : undefined,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return toolCalls;
|
|
82
|
-
}
|
|
83
|
-
function extractToolResultId(msg) {
|
|
84
|
-
const toolCallId = msg.toolCallId;
|
|
85
|
-
if (typeof toolCallId === "string" && toolCallId) {
|
|
86
|
-
return toolCallId;
|
|
87
|
-
}
|
|
88
|
-
const toolUseId = msg.toolUseId;
|
|
89
|
-
if (typeof toolUseId === "string" && toolUseId) {
|
|
90
|
-
return toolUseId;
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
61
|
export function installSessionToolResultGuard(sessionManager, opts) {
|
|
95
62
|
const originalAppend = sessionManager.appendMessage.bind(sessionManager);
|
|
96
63
|
const pending = new Map();
|
|
64
|
+
const persistMessage = (message) => {
|
|
65
|
+
const transformer = opts?.transformMessageForPersistence;
|
|
66
|
+
return transformer ? transformer(message) : message;
|
|
67
|
+
};
|
|
97
68
|
const persistToolResult = (message, meta) => {
|
|
98
69
|
const transformer = opts?.transformToolResultForPersistence;
|
|
99
70
|
return transformer ? transformer(message, meta) : message;
|
|
100
71
|
};
|
|
101
72
|
const allowSyntheticToolResults = opts?.allowSyntheticToolResults ?? true;
|
|
73
|
+
const beforeWrite = opts?.beforeMessageWriteHook;
|
|
74
|
+
/**
|
|
75
|
+
* Run the before_message_write hook. Returns the (possibly modified) message,
|
|
76
|
+
* or null if the message should be blocked.
|
|
77
|
+
*/
|
|
78
|
+
const applyBeforeWriteHook = (msg) => {
|
|
79
|
+
if (!beforeWrite) {
|
|
80
|
+
return msg;
|
|
81
|
+
}
|
|
82
|
+
const result = beforeWrite({ message: msg });
|
|
83
|
+
if (result?.block) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (result?.message) {
|
|
87
|
+
return result.message;
|
|
88
|
+
}
|
|
89
|
+
return msg;
|
|
90
|
+
};
|
|
102
91
|
const flushPendingToolResults = () => {
|
|
103
92
|
if (pending.size === 0) {
|
|
104
93
|
return;
|
|
@@ -106,11 +95,14 @@ export function installSessionToolResultGuard(sessionManager, opts) {
|
|
|
106
95
|
if (allowSyntheticToolResults) {
|
|
107
96
|
for (const [id, name] of pending.entries()) {
|
|
108
97
|
const synthetic = makeMissingToolResult({ toolCallId: id, toolName: name });
|
|
109
|
-
|
|
98
|
+
const flushed = applyBeforeWriteHook(persistToolResult(persistMessage(synthetic), {
|
|
110
99
|
toolCallId: id,
|
|
111
100
|
toolName: name,
|
|
112
101
|
isSynthetic: true,
|
|
113
102
|
}));
|
|
103
|
+
if (flushed) {
|
|
104
|
+
originalAppend(flushed);
|
|
105
|
+
}
|
|
114
106
|
}
|
|
115
107
|
}
|
|
116
108
|
pending.clear();
|
|
@@ -137,15 +129,19 @@ export function installSessionToolResultGuard(sessionManager, opts) {
|
|
|
137
129
|
}
|
|
138
130
|
// Apply hard size cap before persistence to prevent oversized tool results
|
|
139
131
|
// from consuming the entire context window on subsequent LLM calls.
|
|
140
|
-
const capped = capToolResultSize(nextMessage);
|
|
141
|
-
|
|
132
|
+
const capped = capToolResultSize(persistMessage(nextMessage));
|
|
133
|
+
const persisted = applyBeforeWriteHook(persistToolResult(capped, {
|
|
142
134
|
toolCallId: id ?? undefined,
|
|
143
135
|
toolName,
|
|
144
136
|
isSynthetic: false,
|
|
145
137
|
}));
|
|
138
|
+
if (!persisted) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
return originalAppend(persisted);
|
|
146
142
|
}
|
|
147
143
|
const toolCalls = nextRole === "assistant"
|
|
148
|
-
?
|
|
144
|
+
? extractToolCallsFromAssistant(nextMessage)
|
|
149
145
|
: [];
|
|
150
146
|
if (allowSyntheticToolResults) {
|
|
151
147
|
// If previous tool calls are still pending, flush before non-tool results.
|
|
@@ -157,7 +153,11 @@ export function installSessionToolResultGuard(sessionManager, opts) {
|
|
|
157
153
|
flushPendingToolResults();
|
|
158
154
|
}
|
|
159
155
|
}
|
|
160
|
-
const
|
|
156
|
+
const finalMessage = applyBeforeWriteHook(persistMessage(nextMessage));
|
|
157
|
+
if (!finalMessage) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const result = originalAppend(finalMessage);
|
|
161
161
|
const sessionFile = sessionManager.getSessionFile?.();
|
|
162
162
|
if (sessionFile) {
|
|
163
163
|
emitSessionTranscriptUpdate(sessionFile);
|
|
@@ -1,44 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
function extractToolCallsFromAssistant(msg) {
|
|
3
|
-
const content = msg.content;
|
|
4
|
-
if (!Array.isArray(content))
|
|
5
|
-
return [];
|
|
6
|
-
const toolCalls = [];
|
|
7
|
-
for (const block of content) {
|
|
8
|
-
if (!block || typeof block !== "object")
|
|
9
|
-
continue;
|
|
10
|
-
const rec = block;
|
|
11
|
-
if (typeof rec.id !== "string" || !rec.id)
|
|
12
|
-
continue;
|
|
13
|
-
if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
|
|
14
|
-
toolCalls.push({
|
|
15
|
-
id: rec.id,
|
|
16
|
-
name: typeof rec.name === "string" ? rec.name : undefined,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return toolCalls;
|
|
21
|
-
}
|
|
1
|
+
import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
|
|
22
2
|
function isToolCallBlock(block) {
|
|
23
3
|
if (!block || typeof block !== "object") {
|
|
24
4
|
return false;
|
|
25
5
|
}
|
|
26
6
|
const type = block.type;
|
|
27
|
-
return typeof type === "string" &&
|
|
7
|
+
return (typeof type === "string" &&
|
|
8
|
+
(type === "toolCall" || type === "toolUse" || type === "functionCall"));
|
|
28
9
|
}
|
|
29
10
|
function hasToolCallInput(block) {
|
|
30
11
|
const hasInput = "input" in block ? block.input !== undefined && block.input !== null : false;
|
|
31
12
|
const hasArguments = "arguments" in block ? block.arguments !== undefined && block.arguments !== null : false;
|
|
32
13
|
return hasInput || hasArguments;
|
|
33
14
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return
|
|
15
|
+
function hasNonEmptyStringField(value) {
|
|
16
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
17
|
+
}
|
|
18
|
+
function hasToolCallId(block) {
|
|
19
|
+
return hasNonEmptyStringField(block.id);
|
|
20
|
+
}
|
|
21
|
+
function hasToolCallName(block) {
|
|
22
|
+
return hasNonEmptyStringField(block.name);
|
|
42
23
|
}
|
|
43
24
|
function makeMissingToolResult(params) {
|
|
44
25
|
return {
|
|
@@ -56,6 +37,24 @@ function makeMissingToolResult(params) {
|
|
|
56
37
|
};
|
|
57
38
|
}
|
|
58
39
|
export { makeMissingToolResult };
|
|
40
|
+
export function stripToolResultDetails(messages) {
|
|
41
|
+
let touched = false;
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const msg of messages) {
|
|
44
|
+
if (!msg || typeof msg !== "object" || msg.role !== "toolResult") {
|
|
45
|
+
out.push(msg);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!("details" in msg)) {
|
|
49
|
+
out.push(msg);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const { details: _details, ...rest } = msg;
|
|
53
|
+
touched = true;
|
|
54
|
+
out.push(rest);
|
|
55
|
+
}
|
|
56
|
+
return touched ? out : messages;
|
|
57
|
+
}
|
|
59
58
|
export function repairToolCallInputs(messages) {
|
|
60
59
|
let droppedToolCalls = 0;
|
|
61
60
|
let droppedAssistantMessages = 0;
|
|
@@ -73,7 +72,8 @@ export function repairToolCallInputs(messages) {
|
|
|
73
72
|
const nextContent = [];
|
|
74
73
|
let droppedInMessage = 0;
|
|
75
74
|
for (const block of msg.content) {
|
|
76
|
-
if (isToolCallBlock(block) &&
|
|
75
|
+
if (isToolCallBlock(block) &&
|
|
76
|
+
(!hasToolCallInput(block) || !hasToolCallId(block) || !hasToolCallName(block))) {
|
|
77
77
|
droppedToolCalls += 1;
|
|
78
78
|
droppedInMessage += 1;
|
|
79
79
|
changed = true;
|
|
@@ -125,8 +125,9 @@ export function repairToolUseResultPairing(messages) {
|
|
|
125
125
|
changed = true;
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
|
-
if (id)
|
|
128
|
+
if (id) {
|
|
129
129
|
seenToolResultIds.add(id);
|
|
130
|
+
}
|
|
130
131
|
out.push(msg);
|
|
131
132
|
};
|
|
132
133
|
for (let i = 0; i < messages.length; i += 1) {
|
|
@@ -155,6 +156,7 @@ export function repairToolUseResultPairing(messages) {
|
|
|
155
156
|
// (e.g., partialJson: true) and should not have synthetic tool_results created.
|
|
156
157
|
// Creating synthetic results for incomplete tool calls causes API 400 errors:
|
|
157
158
|
// "unexpected tool_use_id found in tool_result blocks"
|
|
159
|
+
// See: https://github.com/poolbot/poolbot/issues/4597
|
|
158
160
|
const stopReason = assistant.stopReason;
|
|
159
161
|
if (stopReason === "error" || stopReason === "aborted") {
|
|
160
162
|
out.push(msg);
|
|
@@ -176,8 +178,9 @@ export function repairToolUseResultPairing(messages) {
|
|
|
176
178
|
continue;
|
|
177
179
|
}
|
|
178
180
|
const nextRole = next.role;
|
|
179
|
-
if (nextRole === "assistant")
|
|
181
|
+
if (nextRole === "assistant") {
|
|
180
182
|
break;
|
|
183
|
+
}
|
|
181
184
|
if (nextRole === "toolResult") {
|
|
182
185
|
const toolResult = next;
|
|
183
186
|
const id = extractToolResultId(toolResult);
|