@poolzin/pool-bot 2026.2.24 → 2026.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/event-mapper.js +87 -22
- package/dist/acp/meta.js +12 -6
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-paths.js +8 -9
- package/dist/agents/agent-scope.js +17 -5
- package/dist/agents/auth-profiles/oauth.js +148 -64
- package/dist/agents/auth-profiles/session-override.js +13 -7
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +240 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-runtime.js +2 -25
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/bedrock-discovery.js +3 -1
- package/dist/agents/byteplus-models.js +97 -0
- package/dist/agents/chutes-oauth.js +1 -0
- package/dist/agents/cli-runner/helpers.js +4 -0
- package/dist/agents/compaction.js +41 -14
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/doubao-models.js +121 -0
- package/dist/agents/failover-error.js +2 -0
- package/dist/agents/huggingface-models.js +5 -3
- package/dist/agents/live-model-filter.js +5 -0
- package/dist/agents/minimax-vlm.js +10 -8
- package/dist/agents/model-auth.js +6 -0
- package/dist/agents/model-catalog.js +3 -1
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/model-selection.js +7 -1
- package/dist/agents/models-config.providers.js +364 -165
- package/dist/agents/ollama-stream.js +117 -4
- package/dist/agents/opencode-zen-models.js +22 -11
- package/dist/agents/pi-embedded-helpers/errors.js +55 -33
- package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
- package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/compact.js +29 -7
- package/dist/agents/pi-embedded-runner/extensions.js +28 -26
- package/dist/agents/pi-embedded-runner/google.js +20 -8
- package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
- package/dist/agents/pi-embedded-runner/run.js +71 -12
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
- package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
- package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
- package/dist/agents/pi-embedded-runner/thinking.js +42 -0
- package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
- package/dist/agents/pi-embedded-runner/utils.js +7 -10
- package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
- package/dist/agents/pi-embedded-subscribe.js +9 -4
- package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
- package/dist/agents/pi-embedded-utils.js +3 -0
- package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
- package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
- package/dist/agents/pi-settings.js +40 -0
- package/dist/agents/pi-tools.policy.js +2 -1
- package/dist/agents/provider/config-loader.js +1 -1
- package/dist/agents/sandbox/browser.js +170 -33
- package/dist/agents/sandbox/config-hash.js +14 -27
- package/dist/agents/sandbox/config.js +21 -2
- package/dist/agents/sandbox/constants.js +2 -0
- package/dist/agents/sandbox/docker.js +16 -2
- package/dist/agents/sandbox/novnc-auth.js +62 -0
- package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
- package/dist/agents/sandbox/shared.js +10 -6
- package/dist/agents/sandbox-paths.js +24 -11
- package/dist/agents/schema/clean-for-gemini.js +132 -85
- package/dist/agents/session-slug.js +10 -5
- package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
- package/dist/agents/session-tool-result-guard.js +3 -1
- package/dist/agents/session-transcript-repair.js +40 -6
- package/dist/agents/skills/bundled-dir.js +19 -5
- package/dist/agents/skills/env-overrides.js +124 -43
- package/dist/agents/skills/frontmatter.js +6 -6
- package/dist/agents/skills/plugin-skills.js +14 -7
- package/dist/agents/skills/workspace.js +1 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-announce.js +251 -49
- package/dist/agents/subagent-lifecycle-events.js +19 -0
- package/dist/agents/subagent-registry-cleanup.js +31 -0
- package/dist/agents/subagent-registry-completion.js +68 -0
- package/dist/agents/subagent-registry-queries.js +117 -0
- package/dist/agents/subagent-registry-state.js +46 -0
- package/dist/agents/subagent-registry.js +252 -221
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/subagent-registry.store.js +1 -0
- package/dist/agents/subagent-registry.types.js +1 -0
- package/dist/agents/subagent-spawn.js +195 -7
- package/dist/agents/system-prompt.js +22 -6
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
- package/dist/agents/test-helpers/fast-core-tools.js +1 -17
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/timeout.js +18 -6
- package/dist/agents/tool-call-id.js +1 -1
- package/dist/agents/tool-display-common.js +162 -29
- package/dist/agents/tool-images.js +82 -9
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tool-policy.js +51 -26
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/canvas-tool.js +27 -1
- package/dist/agents/tools/common.js +45 -0
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-guild.js +4 -1
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/gateway-tool.js +3 -1
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/nodes-utils.js +1 -10
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/tools/sessions-send-helpers.js +12 -6
- package/dist/agents/tools/sessions-spawn-tool.js +8 -2
- package/dist/agents/tools/subagents-tool.js +2 -1
- package/dist/agents/tools/whatsapp-actions.js +10 -2
- package/dist/agents/tools/whatsapp-target-auth.js +18 -0
- package/dist/agents/transcript-policy.js +22 -8
- package/dist/agents/venice-models.js +11 -3
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.data.js +51 -0
- package/dist/auto-reply/commands-registry.js +19 -21
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/group-activation.js +10 -5
- package/dist/auto-reply/inbound-debounce.js +10 -5
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/abort.js +1 -1
- package/dist/auto-reply/reply/agent-runner-execution.js +40 -5
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/bash-command.js +41 -39
- package/dist/auto-reply/reply/command-gates.js +25 -0
- package/dist/auto-reply/reply/commands-allowlist.js +111 -72
- package/dist/auto-reply/reply/commands-bash.js +6 -5
- package/dist/auto-reply/reply/commands-config.js +30 -28
- package/dist/auto-reply/reply/commands-core.js +2 -1
- package/dist/auto-reply/reply/commands-info.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +65 -14
- package/dist/auto-reply/reply/commands-session.js +237 -82
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/auto-reply/reply/commands-setunset.js +45 -0
- package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
- package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
- package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
- package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
- package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
- package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
- package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
- package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
- package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
- package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
- package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
- package/dist/auto-reply/reply/commands-subagents.js +51 -587
- package/dist/auto-reply/reply/commands-tts.js +10 -5
- package/dist/auto-reply/reply/config-value.js +10 -5
- package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
- package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
- package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
- package/dist/auto-reply/reply/followup-runner.js +1 -0
- package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
- package/dist/auto-reply/reply/get-reply-directives.js +17 -28
- package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
- package/dist/auto-reply/reply/get-reply.js +71 -12
- package/dist/auto-reply/reply/model-selection.js +80 -39
- package/dist/auto-reply/reply/queue/enqueue.js +10 -5
- package/dist/auto-reply/reply/queue/state.js +13 -12
- package/dist/auto-reply/reply/reply-payloads.js +67 -36
- package/dist/auto-reply/reply/reply-reference.js +9 -8
- package/dist/auto-reply/reply/route-reply.js +15 -8
- package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
- package/dist/auto-reply/reply/session.js +22 -6
- package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
- package/dist/auto-reply/reply/subagents-utils.js +56 -30
- package/dist/auto-reply/reply/typing.js +46 -21
- package/dist/auto-reply/send-policy.js +14 -7
- package/dist/auto-reply/status.js +140 -16
- package/dist/auto-reply/templating.js +10 -5
- package/dist/auto-reply/thinking.js +7 -16
- package/dist/auto-reply/tokens.js +21 -5
- package/dist/browser/bridge-server.js +36 -20
- package/dist/browser/cdp.helpers.js +7 -14
- package/dist/browser/cdp.js +35 -15
- package/dist/browser/chrome.profile-decoration.js +7 -4
- package/dist/browser/config.js +30 -0
- package/dist/browser/extension-relay-auth.js +55 -0
- package/dist/browser/extension-relay.js +74 -29
- package/dist/browser/navigation-guard.js +39 -0
- package/dist/browser/paths.js +77 -0
- package/dist/browser/profiles.js +13 -8
- package/dist/browser/pw-ai-module.js +10 -5
- package/dist/browser/pw-session.js +76 -39
- package/dist/browser/pw-tools-core.interactions.js +14 -7
- package/dist/browser/pw-tools-core.state.js +12 -6
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-context.js +7 -0
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +26 -0
- package/dist/channels/allowlists/resolve-utils.js +43 -19
- package/dist/channels/channel-config.js +14 -7
- package/dist/channels/draft-stream-loop.js +7 -0
- package/dist/channels/model-overrides.js +82 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/plugins/normalize/imessage.js +14 -7
- package/dist/channels/plugins/normalize/slack.js +10 -5
- package/dist/channels/plugins/normalize/telegram.js +14 -7
- package/dist/channels/plugins/outbound/discord.js +80 -8
- package/dist/channels/plugins/outbound/signal.js +11 -11
- package/dist/channels/plugins/setup-helpers.js +10 -5
- package/dist/channels/sender-label.js +14 -7
- package/dist/channels/session.js +4 -2
- package/dist/channels/status-reactions.js +297 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +3 -2
- package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
- package/dist/cli/cli-name.js +11 -11
- package/dist/cli/cli-utils.js +13 -3
- package/dist/cli/command-format.js +1 -1
- package/dist/cli/config-cli.js +1 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
- package/dist/cli/daemon-cli/lifecycle.js +64 -2
- package/dist/cli/daemon-cli/restart-health.js +126 -0
- package/dist/cli/daemon-cli/status.gather.js +9 -13
- package/dist/cli/daemon-cli/status.print.js +2 -10
- package/dist/cli/deps.js +27 -22
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/gateway-cli/run-loop.js +23 -5
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/node-cli/register.js +14 -5
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +26 -0
- package/dist/cli/outbound-send-deps.js +2 -9
- package/dist/cli/outbound-send-mapping.js +11 -0
- package/dist/cli/pairing-cli.js +40 -14
- package/dist/cli/plugins-cli.js +250 -73
- package/dist/cli/ports.js +11 -10
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +214 -136
- package/dist/cli/program/command-tree.js +16 -0
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/program/preaction.js +13 -9
- package/dist/cli/program/register.configure.js +3 -18
- package/dist/cli/program/register.maintenance.js +2 -2
- package/dist/cli/program/register.onboard.js +2 -0
- package/dist/cli/program/register.status-health-sessions.js +16 -17
- package/dist/cli/program/register.subclis.js +93 -52
- package/dist/cli/route.js +12 -8
- package/dist/cli/system-cli.js +36 -46
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/cli/update-cli/shared.js +22 -9
- package/dist/cli/update-cli/update-command.js +89 -14
- package/dist/cli/update-cli/wizard.js +6 -12
- package/dist/commands/agent/run-context.js +18 -5
- package/dist/commands/agent/session-store.js +17 -4
- package/dist/commands/agent.js +185 -89
- package/dist/commands/agents.bindings.js +14 -7
- package/dist/commands/agents.commands.add.js +13 -9
- package/dist/commands/agents.commands.identity.js +12 -6
- package/dist/commands/agents.commands.list.js +11 -6
- package/dist/commands/agents.config.js +8 -10
- package/dist/commands/agents.providers.js +12 -6
- package/dist/commands/auth-choice-options.js +103 -75
- package/dist/commands/auth-choice.apply.byteplus.js +55 -0
- package/dist/commands/auth-choice.apply.js +4 -0
- package/dist/commands/auth-choice.apply.minimax.js +61 -13
- package/dist/commands/auth-choice.apply.openai.js +3 -1
- package/dist/commands/auth-choice.apply.volcengine.js +55 -0
- package/dist/commands/auth-choice.preferred-provider.js +2 -0
- package/dist/commands/channels/remove.js +13 -6
- package/dist/commands/channels/shared.js +4 -14
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/configure.commands.js +14 -0
- package/dist/commands/configure.gateway.js +2 -4
- package/dist/commands/configure.js +1 -1
- package/dist/commands/configure.shared.js +11 -0
- package/dist/commands/daemon-install-helpers.js +2 -2
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/dashboard.js +12 -10
- package/dist/commands/docs.js +14 -8
- package/dist/commands/doctor-config-flow.js +11 -9
- package/dist/commands/doctor-legacy-config.js +281 -0
- package/dist/commands/doctor-state-integrity.js +99 -23
- package/dist/commands/doctor-update.js +12 -9
- package/dist/commands/models/list.list-command.js +7 -5
- package/dist/commands/models/set-image.js +2 -21
- package/dist/commands/node-daemon-install-helpers.js +10 -8
- package/dist/commands/onboard-auth.config-minimax.js +54 -80
- package/dist/commands/onboard-auth.config-opencode.js +2 -18
- package/dist/commands/onboard-auth.credentials.js +90 -13
- package/dist/commands/onboard-auth.js +1 -1
- package/dist/commands/onboard-auth.models.js +6 -5
- package/dist/commands/onboard-hooks.js +1 -1
- package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
- package/dist/commands/onboard-provider-auth-flags.js +14 -0
- package/dist/commands/onboard-remote.js +14 -7
- package/dist/commands/onboard.js +11 -13
- package/dist/commands/sandbox-display.js +6 -5
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/commands/status-all/diagnosis.js +14 -10
- package/dist/commands/status-all/format.js +1 -0
- package/dist/commands/status.gateway-probe.js +1 -16
- package/dist/commands/systemd-linger.js +12 -6
- package/dist/config/agent-limits.js +2 -0
- package/dist/config/commands.js +32 -15
- package/dist/config/config-paths.js +9 -11
- package/dist/config/config.js +1 -1
- package/dist/config/defaults.js +22 -2
- package/dist/config/discord-preview-streaming.js +104 -0
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +45 -7
- package/dist/config/includes.js +4 -0
- package/dist/config/io.js +656 -171
- package/dist/config/legacy.migrations.part-1.js +189 -78
- package/dist/config/legacy.shared.js +3 -1
- package/dist/config/merge-patch.js +54 -4
- package/dist/config/prototype-keys.js +4 -0
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.help.js +44 -7
- package/dist/config/schema.js +58 -570
- package/dist/config/schema.labels.js +38 -6
- package/dist/config/sessions/delivery-info.js +10 -3
- package/dist/config/sessions/main-session.js +10 -5
- package/dist/config/sessions/session-file.js +33 -0
- package/dist/config/sessions/session-key.js +10 -5
- package/dist/config/sessions/store.js +1 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.agent-runtime.js +11 -0
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +156 -20
- package/dist/config/zod-schema.providers-core.js +78 -4
- package/dist/config/zod-schema.providers.js +6 -1
- package/dist/config/zod-schema.session.js +41 -2
- package/dist/cron/run-log.js +3 -0
- package/dist/cron/schedule.js +21 -10
- package/dist/cron/service/ops.js +35 -21
- package/dist/cron/service/timer.js +116 -16
- package/dist/cron/stagger.js +3 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/api.js +12 -6
- package/dist/discord/draft-chunking.js +22 -0
- package/dist/discord/draft-stream.js +124 -0
- package/dist/discord/monitor/agent-components.js +1 -1
- package/dist/discord/monitor/commands.js +5 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/discord/monitor/gateway-plugin.js +2 -1
- package/dist/discord/monitor/listeners.js +37 -27
- package/dist/discord/monitor/message-handler.js +4 -1
- package/dist/discord/monitor/message-handler.preflight.js +65 -8
- package/dist/discord/monitor/message-handler.process.js +246 -217
- package/dist/discord/monitor/message-utils.js +143 -6
- package/dist/discord/monitor/model-picker-preferences.js +143 -0
- package/dist/discord/monitor/model-picker.js +651 -0
- package/dist/discord/monitor/native-command.js +573 -16
- package/dist/discord/monitor/provider.allowlist.js +223 -0
- package/dist/discord/monitor/provider.js +275 -347
- package/dist/discord/monitor/provider.lifecycle.js +100 -0
- package/dist/discord/monitor/reply-delivery.js +123 -16
- package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
- package/dist/discord/monitor/thread-bindings.js +4 -0
- package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
- package/dist/discord/monitor/thread-bindings.manager.js +423 -0
- package/dist/discord/monitor/thread-bindings.messages.js +55 -0
- package/dist/discord/monitor/thread-bindings.state.js +358 -0
- package/dist/discord/monitor/thread-bindings.types.js +6 -0
- package/dist/discord/resolve-users.js +33 -21
- package/dist/discord/send.channels.js +15 -0
- package/dist/discord/send.js +3 -2
- package/dist/discord/send.outbound.js +82 -26
- package/dist/discord/send.permissions.js +83 -30
- package/dist/discord/send.reactions.js +8 -4
- package/dist/discord/token.js +10 -5
- package/dist/discord/voice/command.js +263 -0
- package/dist/discord/voice/manager.js +531 -0
- package/dist/gateway/auth.js +72 -13
- package/dist/gateway/call.js +152 -83
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/client.js +28 -4
- package/dist/gateway/config-reload.js +3 -4
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/control-ui.js +219 -96
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks-mapping.js +88 -38
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-auth-helpers.js +3 -2
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/http-endpoint-helpers.js +1 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +74 -9
- package/dist/gateway/node-invoke-system-run-approval.js +14 -35
- package/dist/gateway/node-registry.js +10 -5
- package/dist/gateway/openai-http.js +1 -0
- package/dist/gateway/openresponses-http.js +121 -110
- package/dist/gateway/origin-check.js +1 -18
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +4 -2
- package/dist/gateway/protocol/schema/cron.js +1 -0
- package/dist/gateway/protocol/schema/devices.js +1 -0
- package/dist/gateway/protocol/schema/protocol-schemas.js +4 -1
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema/sessions.js +6 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/role-policy.js +17 -0
- package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
- package/dist/gateway/server/ws-connection/message-handler.js +175 -148
- package/dist/gateway/server-chat.js +83 -25
- package/dist/gateway/server-constants.js +10 -9
- package/dist/gateway/server-cron.js +1 -0
- package/dist/gateway/server-http.js +247 -54
- package/dist/gateway/server-maintenance.js +20 -5
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +465 -130
- package/dist/gateway/server-methods/config.js +193 -152
- package/dist/gateway/server-methods/devices.js +17 -3
- package/dist/gateway/server-methods/models.js +11 -1
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-methods/sessions.js +64 -8
- package/dist/gateway/server-methods/usage.js +162 -75
- package/dist/gateway/server-node-events.js +29 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +39 -13
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-startup-memory.js +17 -11
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/session-utils.fs.js +32 -34
- package/dist/gateway/sessions-resolve.js +17 -5
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.openai-mock.js +14 -7
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/gateway/tools-invoke-http.js +21 -10
- package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
- package/dist/hooks/bundled/command-logger/handler.js +7 -2
- package/dist/hooks/bundled/session-memory/handler.js +170 -38
- package/dist/hooks/frontmatter.js +6 -6
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/hooks/gmail-watcher.js +11 -6
- package/dist/hooks/internal-hooks.js +11 -1
- package/dist/hooks/llm-slug-generator.js +4 -1
- package/dist/hooks/workspace.js +47 -17
- package/dist/imessage/accounts.js +9 -20
- package/dist/imessage/monitor/inbound-processing.js +2 -1
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/archive.js +174 -73
- package/dist/infra/control-ui-assets.js +14 -6
- package/dist/infra/device-pairing.js +204 -144
- package/dist/infra/env.js +10 -5
- package/dist/infra/exec-approvals-allowlist.js +141 -70
- package/dist/infra/exec-approvals-analysis.js +78 -20
- package/dist/infra/exec-approvals.js +5 -17
- package/dist/infra/exec-safe-bin-policy.js +277 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/fs-safe.js +71 -39
- package/dist/infra/gateway-lock.js +6 -2
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/heartbeat-wake.js +6 -12
- package/dist/infra/host-env-security-policy.json +19 -0
- package/dist/infra/host-env-security.js +66 -0
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/net/ssrf.js +131 -38
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/bound-delivery-router.js +88 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/channel-selection.js +12 -6
- package/dist/infra/outbound/envelope.js +1 -1
- package/dist/infra/outbound/format.js +12 -6
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/outbound/payloads.js +14 -7
- package/dist/infra/outbound/session-binding-service.js +123 -0
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/path-guards.js +25 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/provider-usage.fetch.codex.js +7 -15
- package/dist/infra/provider-usage.fetch.gemini.js +14 -11
- package/dist/infra/provider-usage.fetch.shared.js +30 -1
- package/dist/infra/provider-usage.fetch.zai.js +10 -9
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/retry-policy.js +4 -2
- package/dist/infra/retry.js +9 -5
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/session-cost-usage.js +107 -59
- package/dist/infra/session-maintenance-warning.js +3 -1
- package/dist/infra/shell-env.js +98 -34
- package/dist/infra/ssh-config.js +12 -6
- package/dist/infra/system-run-command.js +49 -4
- package/dist/infra/update-channels.js +10 -5
- package/dist/infra/update-startup.js +86 -9
- package/dist/line/accounts.js +5 -7
- package/dist/line/bot-access.js +8 -20
- package/dist/line/bot-handlers.js +3 -1
- package/dist/link-understanding/detect.js +15 -7
- package/dist/media/constants.js +15 -6
- package/dist/media/image-ops.js +7 -0
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/media/local-roots.js +3 -2
- package/dist/media-understanding/apply.js +4 -1
- package/dist/media-understanding/concurrency.js +8 -20
- package/dist/memory/backend-config.js +45 -6
- package/dist/memory/embeddings.js +10 -4
- package/dist/memory/fs-utils.js +23 -0
- package/dist/memory/manager-search.js +12 -6
- package/dist/memory/manager-sync-ops.js +12 -2
- package/dist/memory/qmd-manager.js +466 -53
- package/dist/memory/query-expansion.js +167 -3
- package/dist/memory/status-format.js +10 -5
- package/dist/memory/sync-memory-files.js +1 -1
- package/dist/memory/test-manager.js +8 -0
- package/dist/node-host/invoke-system-run.js +281 -0
- package/dist/node-host/invoke.js +55 -337
- package/dist/pairing/pairing-store.js +22 -0
- package/dist/plugin-sdk/allow-from.js +1 -1
- package/dist/plugin-sdk/command-auth.js +3 -1
- package/dist/plugin-sdk/index.js +6 -3
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugin-sdk/webhook-targets.js +32 -0
- package/dist/plugins/bundled-dir.js +9 -6
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/hooks.js +50 -0
- package/dist/plugins/install.js +28 -16
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/plugins/runtime.js +3 -17
- package/dist/plugins/update.js +78 -12
- package/dist/process/spawn-utils.js +14 -7
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/providers/github-copilot-token.js +11 -6
- package/dist/providers/qwen-portal-oauth.js +14 -6
- package/dist/routing/account-id.js +30 -0
- package/dist/routing/resolve-route.js +3 -7
- package/dist/routing/session-key.js +2 -16
- package/dist/security/audit-channel.js +100 -20
- package/dist/security/audit-extra.async.js +505 -179
- package/dist/security/audit-extra.js +12 -2
- package/dist/security/audit-extra.sync.js +421 -35
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +180 -370
- package/dist/security/dm-policy-shared.js +68 -0
- package/dist/security/external-content.js +46 -14
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/entry-status.js +6 -0
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/node-match.js +11 -4
- package/dist/shared/operator-scope-compat.js +42 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/signal/accounts.js +7 -20
- package/dist/signal/monitor/event-handler.js +3 -1
- package/dist/slack/accounts.js +6 -19
- package/dist/slack/actions.js +11 -3
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/auth.js +1 -1
- package/dist/slack/monitor/message-handler/dispatch.js +50 -29
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/slack/monitor/replies.js +15 -7
- package/dist/slack/monitor/slash.js +22 -13
- package/dist/slack/resolve-channels.js +10 -5
- package/dist/slack/send.js +102 -12
- package/dist/slack/stream-mode.js +10 -0
- package/dist/slack/streaming.js +4 -2
- package/dist/telegram/accounts.js +19 -14
- package/dist/telegram/bot/helpers.js +3 -5
- package/dist/telegram/bot-access.js +35 -36
- package/dist/telegram/bot-handlers.js +120 -148
- package/dist/telegram/bot-message-context.js +68 -9
- package/dist/telegram/bot-message-dispatch.js +477 -210
- package/dist/telegram/bot-native-commands.js +16 -0
- package/dist/telegram/draft-stream.js +44 -8
- package/dist/telegram/inline-buttons.js +5 -15
- package/dist/telegram/monitor.js +11 -7
- package/dist/telegram/network-config.js +19 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/telegram/send.js +3 -2
- package/dist/telegram/sent-message-cache.js +5 -6
- package/dist/telegram/status-reaction-variants.js +208 -0
- package/dist/telegram/sticker-cache.js +11 -9
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/terminal/theme.js +12 -12
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tts/tts.js +80 -567
- package/dist/tui/components/chat-log.js +50 -8
- package/dist/tui/theme/theme.js +10 -12
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui-local-shell.js +16 -6
- package/dist/tui/tui.js +236 -48
- package/dist/utils/account-id.js +2 -4
- package/dist/utils/boolean.js +10 -5
- package/dist/utils/directive-tags.js +11 -0
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/queue-helpers.js +67 -12
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/auto-reply/deliver-reply.js +8 -4
- package/dist/web/auto-reply/mentions.js +10 -5
- package/dist/web/auto-reply/monitor/group-members.js +14 -7
- package/dist/web/auto-reply/monitor/process-message.js +45 -24
- package/dist/web/inbound/access-control.js +5 -2
- package/dist/web/login-qr.js +12 -6
- package/dist/web/media.js +126 -15
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
- package/extensions/bluebubbles/src/monitor.ts +208 -1950
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/package.json +1 -1
|
@@ -1,35 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveControlUiRootSync } from "../infra/control-ui-assets.js";
|
|
4
|
+
import { isWithinDir } from "../infra/path-safety.js";
|
|
4
5
|
import { DEFAULT_ASSISTANT_IDENTITY, resolveAssistantIdentity } from "./assistant-identity.js";
|
|
6
|
+
import { CONTROL_UI_BOOTSTRAP_CONFIG_PATH, } from "./control-ui-contract.js";
|
|
7
|
+
import { buildControlUiCspHeader } from "./control-ui-csp.js";
|
|
5
8
|
import { buildControlUiAvatarUrl, CONTROL_UI_AVATAR_PREFIX, normalizeControlUiBasePath, resolveAssistantAvatarUrl, } from "./control-ui-shared.js";
|
|
6
9
|
const ROOT_PREFIX = "/";
|
|
7
|
-
function resolveControlUiRoot() {
|
|
8
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const execDir = (() => {
|
|
10
|
-
try {
|
|
11
|
-
return path.dirname(fs.realpathSync(process.execPath));
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
})();
|
|
17
|
-
const candidates = [
|
|
18
|
-
// Packaged app: control-ui lives alongside the executable.
|
|
19
|
-
execDir ? path.resolve(execDir, "control-ui") : null,
|
|
20
|
-
// Running from dist: dist/gateway/control-ui.js -> dist/control-ui
|
|
21
|
-
path.resolve(here, "../control-ui"),
|
|
22
|
-
// Running from source: src/gateway/control-ui.ts -> dist/control-ui
|
|
23
|
-
path.resolve(here, "../../dist/control-ui"),
|
|
24
|
-
// Fallback to cwd (dev)
|
|
25
|
-
path.resolve(process.cwd(), "dist", "control-ui"),
|
|
26
|
-
].filter((dir) => Boolean(dir));
|
|
27
|
-
for (const dir of candidates) {
|
|
28
|
-
if (fs.existsSync(path.join(dir, "index.html")))
|
|
29
|
-
return dir;
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
10
|
function contentTypeForExt(ext) {
|
|
34
11
|
switch (ext) {
|
|
35
12
|
case ".html":
|
|
@@ -60,6 +37,33 @@ function contentTypeForExt(ext) {
|
|
|
60
37
|
return "application/octet-stream";
|
|
61
38
|
}
|
|
62
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Extensions recognised as static assets. Missing files with these extensions
|
|
42
|
+
* return 404 instead of the SPA index.html fallback. `.html` is intentionally
|
|
43
|
+
* excluded — actual HTML files on disk are served earlier, and missing `.html`
|
|
44
|
+
* paths should fall through to the SPA router (client-side routers may use
|
|
45
|
+
* `.html`-suffixed routes).
|
|
46
|
+
*/
|
|
47
|
+
const STATIC_ASSET_EXTENSIONS = new Set([
|
|
48
|
+
".js",
|
|
49
|
+
".css",
|
|
50
|
+
".json",
|
|
51
|
+
".map",
|
|
52
|
+
".svg",
|
|
53
|
+
".png",
|
|
54
|
+
".jpg",
|
|
55
|
+
".jpeg",
|
|
56
|
+
".gif",
|
|
57
|
+
".webp",
|
|
58
|
+
".ico",
|
|
59
|
+
".txt",
|
|
60
|
+
]);
|
|
61
|
+
function applyControlUiSecurityHeaders(res) {
|
|
62
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
63
|
+
res.setHeader("Content-Security-Policy", buildControlUiCspHeader());
|
|
64
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
65
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
66
|
+
}
|
|
63
67
|
function sendJson(res, status, body) {
|
|
64
68
|
res.statusCode = status;
|
|
65
69
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -71,18 +75,22 @@ function isValidAgentId(agentId) {
|
|
|
71
75
|
}
|
|
72
76
|
export function handleControlUiAvatarRequest(req, res, opts) {
|
|
73
77
|
const urlRaw = req.url;
|
|
74
|
-
if (!urlRaw)
|
|
78
|
+
if (!urlRaw) {
|
|
75
79
|
return false;
|
|
76
|
-
|
|
80
|
+
}
|
|
81
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
77
82
|
return false;
|
|
83
|
+
}
|
|
78
84
|
const url = new URL(urlRaw, "http://localhost");
|
|
79
85
|
const basePath = normalizeControlUiBasePath(opts.basePath);
|
|
80
86
|
const pathname = url.pathname;
|
|
81
87
|
const pathWithBase = basePath
|
|
82
88
|
? `${basePath}${CONTROL_UI_AVATAR_PREFIX}/`
|
|
83
89
|
: `${CONTROL_UI_AVATAR_PREFIX}/`;
|
|
84
|
-
if (!pathname.startsWith(pathWithBase))
|
|
90
|
+
if (!pathname.startsWith(pathWithBase)) {
|
|
85
91
|
return false;
|
|
92
|
+
}
|
|
93
|
+
applyControlUiSecurityHeaders(res);
|
|
86
94
|
const agentIdParts = pathname.slice(pathWithBase.length).split("/").filter(Boolean);
|
|
87
95
|
const agentId = agentIdParts[0] ?? "";
|
|
88
96
|
if (agentIdParts.length !== 1 || !agentId || !isValidAgentId(agentId)) {
|
|
@@ -119,66 +127,93 @@ function respondNotFound(res) {
|
|
|
119
127
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
120
128
|
res.end("Not Found");
|
|
121
129
|
}
|
|
122
|
-
function
|
|
130
|
+
function setStaticFileHeaders(res, filePath) {
|
|
123
131
|
const ext = path.extname(filePath).toLowerCase();
|
|
124
132
|
res.setHeader("Content-Type", contentTypeForExt(ext));
|
|
125
133
|
// Static UI should never be cached aggressively while iterating; allow the
|
|
126
134
|
// browser to revalidate.
|
|
127
135
|
res.setHeader("Cache-Control", "no-cache");
|
|
136
|
+
}
|
|
137
|
+
function serveFile(res, filePath) {
|
|
138
|
+
setStaticFileHeaders(res, filePath);
|
|
128
139
|
res.end(fs.readFileSync(filePath));
|
|
129
140
|
}
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`window.__CLAWDBOT_CONTROL_UI_BASE_PATH__=${JSON.stringify(basePath)};` +
|
|
134
|
-
`window.__CLAWDBOT_ASSISTANT_NAME__=${JSON.stringify(assistantName ?? DEFAULT_ASSISTANT_IDENTITY.name)};` +
|
|
135
|
-
`window.__CLAWDBOT_ASSISTANT_AVATAR__=${JSON.stringify(assistantAvatar ?? DEFAULT_ASSISTANT_IDENTITY.avatar)};` +
|
|
136
|
-
`</script>`;
|
|
137
|
-
// Check if already injected
|
|
138
|
-
if (html.includes("__CLAWDBOT_ASSISTANT_NAME__"))
|
|
139
|
-
return html;
|
|
140
|
-
const headClose = html.indexOf("</head>");
|
|
141
|
-
if (headClose !== -1) {
|
|
142
|
-
return `${html.slice(0, headClose)}${script}${html.slice(headClose)}`;
|
|
143
|
-
}
|
|
144
|
-
return `${script}${html}`;
|
|
141
|
+
function serveResolvedFile(res, filePath, body) {
|
|
142
|
+
setStaticFileHeaders(res, filePath);
|
|
143
|
+
res.end(body);
|
|
145
144
|
}
|
|
146
|
-
function
|
|
147
|
-
const { basePath, config, agentId } = opts;
|
|
148
|
-
const identity = config
|
|
149
|
-
? resolveAssistantIdentity({ cfg: config, agentId })
|
|
150
|
-
: DEFAULT_ASSISTANT_IDENTITY;
|
|
151
|
-
const resolvedAgentId = typeof identity.agentId === "string"
|
|
152
|
-
? identity.agentId
|
|
153
|
-
: agentId;
|
|
154
|
-
const avatarValue = resolveAssistantAvatarUrl({
|
|
155
|
-
avatar: identity.avatar,
|
|
156
|
-
agentId: resolvedAgentId,
|
|
157
|
-
basePath,
|
|
158
|
-
}) ?? identity.avatar;
|
|
145
|
+
function serveResolvedIndexHtml(res, body) {
|
|
159
146
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
160
147
|
res.setHeader("Cache-Control", "no-cache");
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
148
|
+
res.end(body);
|
|
149
|
+
}
|
|
150
|
+
function isContainedPath(baseDir, targetPath) {
|
|
151
|
+
const relative = path.relative(baseDir, targetPath);
|
|
152
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
153
|
+
}
|
|
154
|
+
function isExpectedSafePathError(error) {
|
|
155
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
156
|
+
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
157
|
+
}
|
|
158
|
+
function areSameFileIdentity(preOpen, opened) {
|
|
159
|
+
return preOpen.dev === opened.dev && preOpen.ino === opened.ino;
|
|
160
|
+
}
|
|
161
|
+
function resolveSafeControlUiFile(rootReal, filePath) {
|
|
162
|
+
let fd = null;
|
|
163
|
+
try {
|
|
164
|
+
const fileReal = fs.realpathSync(filePath);
|
|
165
|
+
if (!isContainedPath(rootReal, fileReal)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const preOpenStat = fs.lstatSync(fileReal);
|
|
169
|
+
if (!preOpenStat.isFile()) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const openFlags = fs.constants.O_RDONLY |
|
|
173
|
+
(typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0);
|
|
174
|
+
fd = fs.openSync(fileReal, openFlags);
|
|
175
|
+
const openedStat = fs.fstatSync(fd);
|
|
176
|
+
// Compare inode identity so swaps between validation and open are rejected.
|
|
177
|
+
if (!openedStat.isFile() || !areSameFileIdentity(preOpenStat, openedStat)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const resolved = { path: fileReal, fd };
|
|
181
|
+
fd = null;
|
|
182
|
+
return resolved;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (isExpectedSafePathError(error)) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
if (fd !== null) {
|
|
192
|
+
fs.closeSync(fd);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
167
195
|
}
|
|
168
196
|
function isSafeRelativePath(relPath) {
|
|
169
|
-
if (!relPath)
|
|
197
|
+
if (!relPath) {
|
|
170
198
|
return false;
|
|
199
|
+
}
|
|
171
200
|
const normalized = path.posix.normalize(relPath);
|
|
172
|
-
if (
|
|
201
|
+
if (path.posix.isAbsolute(normalized) || path.win32.isAbsolute(normalized)) {
|
|
173
202
|
return false;
|
|
174
|
-
|
|
203
|
+
}
|
|
204
|
+
if (normalized.startsWith("../") || normalized === "..") {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (normalized.includes("\0")) {
|
|
175
208
|
return false;
|
|
209
|
+
}
|
|
176
210
|
return true;
|
|
177
211
|
}
|
|
178
212
|
export function handleControlUiHttpRequest(req, res, opts) {
|
|
179
213
|
const urlRaw = req.url;
|
|
180
|
-
if (!urlRaw)
|
|
214
|
+
if (!urlRaw) {
|
|
181
215
|
return false;
|
|
216
|
+
}
|
|
182
217
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
183
218
|
res.statusCode = 405;
|
|
184
219
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
@@ -190,41 +225,104 @@ export function handleControlUiHttpRequest(req, res, opts) {
|
|
|
190
225
|
const pathname = url.pathname;
|
|
191
226
|
if (!basePath) {
|
|
192
227
|
if (pathname === "/ui" || pathname.startsWith("/ui/")) {
|
|
228
|
+
applyControlUiSecurityHeaders(res);
|
|
193
229
|
respondNotFound(res);
|
|
194
230
|
return true;
|
|
195
231
|
}
|
|
196
232
|
}
|
|
197
233
|
if (basePath) {
|
|
198
234
|
if (pathname === basePath) {
|
|
235
|
+
applyControlUiSecurityHeaders(res);
|
|
199
236
|
res.statusCode = 302;
|
|
200
237
|
res.setHeader("Location", `${basePath}/${url.search}`);
|
|
201
238
|
res.end();
|
|
202
239
|
return true;
|
|
203
240
|
}
|
|
204
|
-
if (!pathname.startsWith(`${basePath}/`))
|
|
241
|
+
if (!pathname.startsWith(`${basePath}/`)) {
|
|
205
242
|
return false;
|
|
243
|
+
}
|
|
206
244
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
245
|
+
applyControlUiSecurityHeaders(res);
|
|
246
|
+
const bootstrapConfigPath = basePath
|
|
247
|
+
? `${basePath}${CONTROL_UI_BOOTSTRAP_CONFIG_PATH}`
|
|
248
|
+
: CONTROL_UI_BOOTSTRAP_CONFIG_PATH;
|
|
249
|
+
if (pathname === bootstrapConfigPath) {
|
|
250
|
+
const config = opts?.config;
|
|
251
|
+
const identity = config
|
|
252
|
+
? resolveAssistantIdentity({ cfg: config, agentId: opts?.agentId })
|
|
253
|
+
: DEFAULT_ASSISTANT_IDENTITY;
|
|
254
|
+
const avatarValue = resolveAssistantAvatarUrl({
|
|
255
|
+
avatar: identity.avatar,
|
|
256
|
+
agentId: identity.agentId,
|
|
257
|
+
basePath,
|
|
258
|
+
});
|
|
259
|
+
if (req.method === "HEAD") {
|
|
260
|
+
res.statusCode = 200;
|
|
261
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
262
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
263
|
+
res.end();
|
|
264
|
+
return true;
|
|
212
265
|
}
|
|
213
|
-
|
|
214
|
-
|
|
266
|
+
sendJson(res, 200, {
|
|
267
|
+
basePath,
|
|
268
|
+
assistantName: identity.name,
|
|
269
|
+
assistantAvatar: avatarValue ?? identity.avatar,
|
|
270
|
+
assistantAgentId: identity.agentId,
|
|
271
|
+
});
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
const rootState = opts?.root;
|
|
275
|
+
if (rootState?.kind === "invalid") {
|
|
276
|
+
res.statusCode = 503;
|
|
277
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
278
|
+
res.end(`Control UI assets not found at ${rootState.path}. Build them with \`pnpm ui:build\` (auto-installs UI deps), or update gateway.controlUi.root.`);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (rootState?.kind === "missing") {
|
|
282
|
+
res.statusCode = 503;
|
|
283
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
284
|
+
res.end("Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.");
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
const root = rootState?.kind === "resolved"
|
|
288
|
+
? rootState.path
|
|
289
|
+
: resolveControlUiRootSync({
|
|
290
|
+
moduleUrl: import.meta.url,
|
|
291
|
+
argv1: process.argv[1],
|
|
292
|
+
cwd: process.cwd(),
|
|
293
|
+
});
|
|
215
294
|
if (!root) {
|
|
216
295
|
res.statusCode = 503;
|
|
217
296
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
218
297
|
res.end("Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.");
|
|
219
298
|
return true;
|
|
220
299
|
}
|
|
300
|
+
const rootReal = (() => {
|
|
301
|
+
try {
|
|
302
|
+
return fs.realpathSync(root);
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
if (isExpectedSafePathError(error)) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
})();
|
|
311
|
+
if (!rootReal) {
|
|
312
|
+
res.statusCode = 503;
|
|
313
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
314
|
+
res.end("Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.");
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
221
317
|
const uiPath = basePath && pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) : pathname;
|
|
222
318
|
const rel = (() => {
|
|
223
|
-
if (uiPath === ROOT_PREFIX)
|
|
319
|
+
if (uiPath === ROOT_PREFIX) {
|
|
224
320
|
return "";
|
|
321
|
+
}
|
|
225
322
|
const assetsIndex = uiPath.indexOf("/assets/");
|
|
226
|
-
if (assetsIndex >= 0)
|
|
323
|
+
if (assetsIndex >= 0) {
|
|
227
324
|
return uiPath.slice(assetsIndex + 1);
|
|
325
|
+
}
|
|
228
326
|
return uiPath.slice(1);
|
|
229
327
|
})();
|
|
230
328
|
const requested = rel && !rel.endsWith("/") ? rel : `${rel}index.html`;
|
|
@@ -233,32 +331,57 @@ export function handleControlUiHttpRequest(req, res, opts) {
|
|
|
233
331
|
respondNotFound(res);
|
|
234
332
|
return true;
|
|
235
333
|
}
|
|
236
|
-
const filePath = path.
|
|
237
|
-
if (!
|
|
334
|
+
const filePath = path.resolve(root, fileRel);
|
|
335
|
+
if (!isWithinDir(root, filePath)) {
|
|
238
336
|
respondNotFound(res);
|
|
239
337
|
return true;
|
|
240
338
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
339
|
+
const safeFile = resolveSafeControlUiFile(rootReal, filePath);
|
|
340
|
+
if (safeFile) {
|
|
341
|
+
try {
|
|
342
|
+
if (req.method === "HEAD") {
|
|
343
|
+
res.statusCode = 200;
|
|
344
|
+
setStaticFileHeaders(res, safeFile.path);
|
|
345
|
+
res.end();
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
if (path.basename(safeFile.path) === "index.html") {
|
|
349
|
+
serveResolvedIndexHtml(res, fs.readFileSync(safeFile.fd, "utf8"));
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
serveResolvedFile(res, safeFile.path, fs.readFileSync(safeFile.fd));
|
|
248
353
|
return true;
|
|
249
354
|
}
|
|
250
|
-
|
|
355
|
+
finally {
|
|
356
|
+
fs.closeSync(safeFile.fd);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// If the requested path looks like a static asset (known extension), return
|
|
360
|
+
// 404 rather than falling through to the SPA index.html fallback. We check
|
|
361
|
+
// against the same set of extensions that contentTypeForExt() recognises so
|
|
362
|
+
// that dotted SPA routes (e.g. /user/jane.doe, /v2.0) still get the
|
|
363
|
+
// client-side router fallback.
|
|
364
|
+
if (STATIC_ASSET_EXTENSIONS.has(path.extname(fileRel).toLowerCase())) {
|
|
365
|
+
respondNotFound(res);
|
|
251
366
|
return true;
|
|
252
367
|
}
|
|
253
368
|
// SPA fallback (client-side router): serve index.html for unknown paths.
|
|
254
369
|
const indexPath = path.join(root, "index.html");
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
370
|
+
const safeIndex = resolveSafeControlUiFile(rootReal, indexPath);
|
|
371
|
+
if (safeIndex) {
|
|
372
|
+
try {
|
|
373
|
+
if (req.method === "HEAD") {
|
|
374
|
+
res.statusCode = 200;
|
|
375
|
+
setStaticFileHeaders(res, safeIndex.path);
|
|
376
|
+
res.end();
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
serveResolvedIndexHtml(res, fs.readFileSync(safeIndex.fd, "utf8"));
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
finally {
|
|
383
|
+
fs.closeSync(safeIndex.fd);
|
|
384
|
+
}
|
|
262
385
|
}
|
|
263
386
|
respondNotFound(res);
|
|
264
387
|
return true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const GATEWAY_EVENT_UPDATE_AVAILABLE = "update.available";
|
|
@@ -15,16 +15,18 @@ const hookPresetMappings = {
|
|
|
15
15
|
],
|
|
16
16
|
};
|
|
17
17
|
const transformCache = new Map();
|
|
18
|
-
export function resolveHookMappings(hooks) {
|
|
18
|
+
export function resolveHookMappings(hooks, opts) {
|
|
19
19
|
const presets = hooks?.presets ?? [];
|
|
20
20
|
const gmailAllowUnsafe = hooks?.gmail?.allowUnsafeExternalContent;
|
|
21
21
|
const mappings = [];
|
|
22
|
-
if (hooks?.mappings)
|
|
22
|
+
if (hooks?.mappings) {
|
|
23
23
|
mappings.push(...hooks.mappings);
|
|
24
|
+
}
|
|
24
25
|
for (const preset of presets) {
|
|
25
26
|
const presetMappings = hookPresetMappings[preset];
|
|
26
|
-
if (!presetMappings)
|
|
27
|
+
if (!presetMappings) {
|
|
27
28
|
continue;
|
|
29
|
+
}
|
|
28
30
|
if (preset === "gmail" && typeof gmailAllowUnsafe === "boolean") {
|
|
29
31
|
mappings.push(...presetMappings.map((mapping) => ({
|
|
30
32
|
...mapping,
|
|
@@ -34,23 +36,26 @@ export function resolveHookMappings(hooks) {
|
|
|
34
36
|
}
|
|
35
37
|
mappings.push(...presetMappings);
|
|
36
38
|
}
|
|
37
|
-
if (mappings.length === 0)
|
|
39
|
+
if (mappings.length === 0) {
|
|
38
40
|
return [];
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
}
|
|
42
|
+
const configDir = path.resolve(opts?.configDir ?? path.dirname(CONFIG_PATH));
|
|
43
|
+
const transformsRootDir = path.join(configDir, "hooks", "transforms");
|
|
44
|
+
const transformsDir = resolveOptionalContainedPath(transformsRootDir, hooks?.transformsDir, "Hook transformsDir");
|
|
43
45
|
return mappings.map((mapping, index) => normalizeHookMapping(mapping, index, transformsDir));
|
|
44
46
|
}
|
|
45
47
|
export async function applyHookMappings(mappings, ctx) {
|
|
46
|
-
if (mappings.length === 0)
|
|
48
|
+
if (mappings.length === 0) {
|
|
47
49
|
return null;
|
|
50
|
+
}
|
|
48
51
|
for (const mapping of mappings) {
|
|
49
|
-
if (!mappingMatches(mapping, ctx))
|
|
52
|
+
if (!mappingMatches(mapping, ctx)) {
|
|
50
53
|
continue;
|
|
54
|
+
}
|
|
51
55
|
const base = buildActionFromMapping(mapping, ctx);
|
|
52
|
-
if (!base.ok)
|
|
56
|
+
if (!base.ok) {
|
|
53
57
|
return base;
|
|
58
|
+
}
|
|
54
59
|
let override = null;
|
|
55
60
|
if (mapping.transform) {
|
|
56
61
|
const transform = await loadTransform(mapping.transform);
|
|
@@ -59,11 +64,13 @@ export async function applyHookMappings(mappings, ctx) {
|
|
|
59
64
|
return { ok: true, action: null, skipped: true };
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
|
-
if (!base.action)
|
|
67
|
+
if (!base.action) {
|
|
63
68
|
return { ok: true, action: null, skipped: true };
|
|
69
|
+
}
|
|
64
70
|
const merged = mergeAction(base.action, override, mapping.action);
|
|
65
|
-
if (!merged.ok)
|
|
71
|
+
if (!merged.ok) {
|
|
66
72
|
return merged;
|
|
73
|
+
}
|
|
67
74
|
return merged;
|
|
68
75
|
}
|
|
69
76
|
return null;
|
|
@@ -76,7 +83,7 @@ function normalizeHookMapping(mapping, index, transformsDir) {
|
|
|
76
83
|
const wakeMode = mapping.wakeMode ?? "now";
|
|
77
84
|
const transform = mapping.transform
|
|
78
85
|
? {
|
|
79
|
-
modulePath:
|
|
86
|
+
modulePath: resolveContainedPath(transformsDir, mapping.transform.module, "Hook transform"),
|
|
80
87
|
exportName: mapping.transform.export?.trim() || undefined,
|
|
81
88
|
}
|
|
82
89
|
: undefined;
|
|
@@ -103,13 +110,15 @@ function normalizeHookMapping(mapping, index, transformsDir) {
|
|
|
103
110
|
}
|
|
104
111
|
function mappingMatches(mapping, ctx) {
|
|
105
112
|
if (mapping.matchPath) {
|
|
106
|
-
if (mapping.matchPath !== normalizeMatchPath(ctx.path))
|
|
113
|
+
if (mapping.matchPath !== normalizeMatchPath(ctx.path)) {
|
|
107
114
|
return false;
|
|
115
|
+
}
|
|
108
116
|
}
|
|
109
117
|
if (mapping.matchSource) {
|
|
110
118
|
const source = typeof ctx.payload.source === "string" ? ctx.payload.source : undefined;
|
|
111
|
-
if (!source || source !== mapping.matchSource)
|
|
119
|
+
if (!source || source !== mapping.matchSource) {
|
|
112
120
|
return false;
|
|
121
|
+
}
|
|
113
122
|
}
|
|
114
123
|
return true;
|
|
115
124
|
}
|
|
@@ -149,7 +158,7 @@ function mergeAction(base, override, defaultAction) {
|
|
|
149
158
|
if (!override) {
|
|
150
159
|
return validateAction(base);
|
|
151
160
|
}
|
|
152
|
-
const kind =
|
|
161
|
+
const kind = override.kind ?? base.kind ?? defaultAction;
|
|
153
162
|
if (kind === "wake") {
|
|
154
163
|
const baseWake = base.kind === "wake" ? base : undefined;
|
|
155
164
|
const text = typeof override.text === "string" ? override.text : (baseWake?.text ?? "");
|
|
@@ -190,13 +199,15 @@ function validateAction(action) {
|
|
|
190
199
|
return { ok: true, action };
|
|
191
200
|
}
|
|
192
201
|
async function loadTransform(transform) {
|
|
193
|
-
const
|
|
194
|
-
|
|
202
|
+
const cacheKey = `${transform.modulePath}::${transform.exportName ?? "default"}`;
|
|
203
|
+
const cached = transformCache.get(cacheKey);
|
|
204
|
+
if (cached) {
|
|
195
205
|
return cached;
|
|
206
|
+
}
|
|
196
207
|
const url = pathToFileURL(transform.modulePath).href;
|
|
197
208
|
const mod = (await import(url));
|
|
198
209
|
const fn = resolveTransformFn(mod, transform.exportName);
|
|
199
|
-
transformCache.set(
|
|
210
|
+
transformCache.set(cacheKey, fn);
|
|
200
211
|
return fn;
|
|
201
212
|
}
|
|
202
213
|
function resolveTransformFn(mod, exportName) {
|
|
@@ -207,45 +218,73 @@ function resolveTransformFn(mod, exportName) {
|
|
|
207
218
|
return candidate;
|
|
208
219
|
}
|
|
209
220
|
function resolvePath(baseDir, target) {
|
|
210
|
-
if (!target)
|
|
211
|
-
return baseDir;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
if (!target) {
|
|
222
|
+
return path.resolve(baseDir);
|
|
223
|
+
}
|
|
224
|
+
return path.isAbsolute(target) ? path.resolve(target) : path.resolve(baseDir, target);
|
|
225
|
+
}
|
|
226
|
+
function resolveContainedPath(baseDir, target, label) {
|
|
227
|
+
const base = path.resolve(baseDir);
|
|
228
|
+
const trimmed = target?.trim();
|
|
229
|
+
if (!trimmed) {
|
|
230
|
+
throw new Error(`${label} module path is required`);
|
|
231
|
+
}
|
|
232
|
+
const resolved = resolvePath(base, trimmed);
|
|
233
|
+
const relative = path.relative(base, resolved);
|
|
234
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
235
|
+
throw new Error(`${label} module path must be within ${base}: ${target}`);
|
|
236
|
+
}
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
239
|
+
function resolveOptionalContainedPath(baseDir, target, label) {
|
|
240
|
+
const trimmed = target?.trim();
|
|
241
|
+
if (!trimmed) {
|
|
242
|
+
return path.resolve(baseDir);
|
|
243
|
+
}
|
|
244
|
+
return resolveContainedPath(baseDir, trimmed, label);
|
|
215
245
|
}
|
|
216
246
|
function normalizeMatchPath(raw) {
|
|
217
|
-
if (!raw)
|
|
247
|
+
if (!raw) {
|
|
218
248
|
return undefined;
|
|
249
|
+
}
|
|
219
250
|
const trimmed = raw.trim();
|
|
220
|
-
if (!trimmed)
|
|
251
|
+
if (!trimmed) {
|
|
221
252
|
return undefined;
|
|
253
|
+
}
|
|
222
254
|
return trimmed.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
223
255
|
}
|
|
224
256
|
function renderOptional(value, ctx) {
|
|
225
|
-
if (!value)
|
|
257
|
+
if (!value) {
|
|
226
258
|
return undefined;
|
|
259
|
+
}
|
|
227
260
|
const rendered = renderTemplate(value, ctx).trim();
|
|
228
261
|
return rendered ? rendered : undefined;
|
|
229
262
|
}
|
|
230
263
|
function renderTemplate(template, ctx) {
|
|
231
|
-
if (!template)
|
|
264
|
+
if (!template) {
|
|
232
265
|
return "";
|
|
266
|
+
}
|
|
233
267
|
return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
|
|
234
268
|
const value = resolveTemplateExpr(expr.trim(), ctx);
|
|
235
|
-
if (value === undefined || value === null)
|
|
269
|
+
if (value === undefined || value === null) {
|
|
236
270
|
return "";
|
|
237
|
-
|
|
271
|
+
}
|
|
272
|
+
if (typeof value === "string") {
|
|
238
273
|
return value;
|
|
239
|
-
|
|
274
|
+
}
|
|
275
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
240
276
|
return String(value);
|
|
277
|
+
}
|
|
241
278
|
return JSON.stringify(value);
|
|
242
279
|
});
|
|
243
280
|
}
|
|
244
281
|
function resolveTemplateExpr(expr, ctx) {
|
|
245
|
-
if (expr === "path")
|
|
282
|
+
if (expr === "path") {
|
|
246
283
|
return ctx.path;
|
|
247
|
-
|
|
284
|
+
}
|
|
285
|
+
if (expr === "now") {
|
|
248
286
|
return new Date().toISOString();
|
|
287
|
+
}
|
|
249
288
|
if (expr.startsWith("headers.")) {
|
|
250
289
|
return getByPath(ctx.headers, expr.slice("headers.".length));
|
|
251
290
|
}
|
|
@@ -257,9 +296,14 @@ function resolveTemplateExpr(expr, ctx) {
|
|
|
257
296
|
}
|
|
258
297
|
return getByPath(ctx.payload, expr);
|
|
259
298
|
}
|
|
299
|
+
// Block traversal into prototype-chain properties on attacker-controlled
|
|
300
|
+
// webhook payloads. Mirrors the same blocklist used by config-paths.ts
|
|
301
|
+
// for config path traversal.
|
|
302
|
+
const BLOCKED_PATH_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
|
260
303
|
function getByPath(input, pathExpr) {
|
|
261
|
-
if (!pathExpr)
|
|
304
|
+
if (!pathExpr) {
|
|
262
305
|
return undefined;
|
|
306
|
+
}
|
|
263
307
|
const parts = [];
|
|
264
308
|
const re = /([^.[\]]+)|(\[(\d+)\])/g;
|
|
265
309
|
let match = re.exec(pathExpr);
|
|
@@ -274,16 +318,22 @@ function getByPath(input, pathExpr) {
|
|
|
274
318
|
}
|
|
275
319
|
let current = input;
|
|
276
320
|
for (const part of parts) {
|
|
277
|
-
if (current === null || current === undefined)
|
|
321
|
+
if (current === null || current === undefined) {
|
|
278
322
|
return undefined;
|
|
323
|
+
}
|
|
279
324
|
if (typeof part === "number") {
|
|
280
|
-
if (!Array.isArray(current))
|
|
325
|
+
if (!Array.isArray(current)) {
|
|
281
326
|
return undefined;
|
|
327
|
+
}
|
|
282
328
|
current = current[part];
|
|
283
329
|
continue;
|
|
284
330
|
}
|
|
285
|
-
if (
|
|
331
|
+
if (BLOCKED_PATH_KEYS.has(part)) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
if (typeof current !== "object") {
|
|
286
335
|
return undefined;
|
|
336
|
+
}
|
|
287
337
|
current = current[part];
|
|
288
338
|
}
|
|
289
339
|
return current;
|