@poolzin/pool-bot 2026.2.11 → 2026.2.18
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 +34 -0
- package/dist/agents/agent-scope.js +4 -0
- package/dist/agents/announce-idempotency.js +14 -0
- package/dist/agents/auth-profiles/usage.js +22 -0
- package/dist/agents/auth-profiles.js +1 -1
- package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +23 -0
- package/dist/agents/bash-tools.exec-runtime.js +438 -0
- package/dist/agents/bash-tools.shared.js +6 -0
- package/dist/agents/cli-runner/reliability.js +61 -0
- package/dist/agents/cli-watchdog-defaults.js +11 -0
- package/dist/agents/command-poll-backoff.js +63 -0
- package/dist/agents/current-time.js +16 -0
- package/dist/agents/glob-pattern.js +42 -0
- package/dist/agents/memory-search.js +33 -0
- package/dist/agents/model-alias-lines.js +18 -0
- package/dist/agents/model-auth-label.js +61 -0
- package/dist/agents/model-fallback.js +59 -8
- package/dist/agents/models-config.e2e-harness.js +115 -0
- package/dist/agents/ollama-stream.js +11 -3
- package/dist/agents/openclaw-tools.js +135 -0
- package/dist/agents/pi-auth-json.js +118 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +147 -0
- package/dist/agents/pi-embedded-subscribe.e2e-harness.js +90 -0
- package/dist/agents/pi-embedded-subscribe.handlers.compaction.js +63 -0
- package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +30 -0
- package/dist/agents/pi-extensions/session-manager-runtime-registry.js +23 -0
- package/dist/agents/pi-tools.before-tool-call.js +145 -4
- package/dist/agents/pi-tools.js +29 -9
- package/dist/agents/pi-tools.policy.js +85 -92
- package/dist/agents/pi-tools.schema.js +54 -27
- package/dist/agents/queued-file-writer.js +22 -0
- package/dist/agents/sandbox/docker.js +133 -40
- package/dist/agents/sandbox/fs-bridge.js +146 -0
- package/dist/agents/sandbox/fs-paths.js +205 -0
- package/dist/agents/sandbox/hash.js +4 -0
- package/dist/agents/sandbox/validate-sandbox-security.js +157 -0
- package/dist/agents/sandbox-paths.js +3 -0
- package/dist/agents/sandbox-tool-policy.js +26 -0
- package/dist/agents/sanitize-for-prompt.js +18 -0
- package/dist/agents/session-dirs.js +20 -0
- package/dist/agents/session-write-lock.js +203 -39
- package/dist/agents/skills/filter.js +24 -0
- package/dist/agents/skills/tools-dir.js +9 -0
- package/dist/agents/skills-install-download.js +290 -0
- package/dist/agents/skills-install-output.js +30 -0
- package/dist/agents/skills-install.download-test-utils.js +36 -0
- package/dist/agents/skills.e2e-test-helpers.js +13 -0
- package/dist/agents/subagent-announce-queue.js +59 -15
- package/dist/agents/subagent-depth.js +137 -0
- package/dist/agents/subagent-registry.js +448 -96
- package/dist/agents/subagent-spawn.js +262 -0
- package/dist/agents/system-prompt.js +52 -10
- package/dist/agents/test-helpers/fast-tool-stubs.js +18 -0
- package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +74 -0
- package/dist/agents/tool-display-common.js +782 -0
- package/dist/agents/tool-loop-detection.js +466 -0
- package/dist/agents/tool-policy.js +6 -0
- package/dist/agents/tools/image-tool.js +1 -1
- package/dist/agents/tools/sessions-access.js +178 -0
- package/dist/agents/tools/sessions-resolution.js +206 -0
- package/dist/agents/tools/subagents-tool.js +616 -0
- package/dist/agents/workspace-dir.js +18 -0
- package/dist/agents/workspace-dirs.js +14 -0
- package/dist/agents/workspace.js +70 -0
- package/dist/auto-reply/heartbeat-reply-payload.js +18 -0
- package/dist/auto-reply/reply/commands-export-session.js +163 -0
- package/dist/auto-reply/reply/commands-mesh.js +245 -0
- package/dist/auto-reply/reply/commands-setunset.js +28 -0
- package/dist/auto-reply/reply/commands-slash-parse.js +31 -0
- package/dist/auto-reply/reply/commands-system-prompt.js +117 -0
- package/dist/auto-reply/reply/directive-handling.levels.js +17 -0
- package/dist/auto-reply/reply/directive-handling.params.js +1 -0
- package/dist/auto-reply/reply/directive-parsing.js +36 -0
- package/dist/auto-reply/reply/dispatcher-registry.js +43 -0
- package/dist/auto-reply/reply/elevated-unavailable.js +20 -0
- package/dist/auto-reply/reply/post-compaction-audit.js +96 -0
- package/dist/auto-reply/reply/post-compaction-context.js +98 -0
- package/dist/auto-reply/reply/reply-delivery.js +92 -0
- package/dist/auto-reply/reply/session-reset-prompt.js +1 -0
- package/dist/auto-reply/reply/session-run-accounting.js +33 -0
- package/dist/auto-reply/reply.directive.directive-behavior.e2e-harness.js +115 -0
- package/dist/auto-reply/reply.directive.directive-behavior.e2e-mocks.js +12 -0
- package/dist/browser/bridge-auth-registry.js +26 -0
- package/dist/browser/client-actions-url.js +10 -0
- package/dist/browser/control-auth.js +73 -0
- package/dist/browser/csrf.js +64 -0
- package/dist/browser/http-auth.js +52 -0
- package/dist/browser/paths.js +37 -0
- package/dist/browser/proxy-files.js +32 -0
- package/dist/browser/pw-ai-state.js +7 -0
- package/dist/browser/resolved-config-refresh.js +42 -0
- package/dist/browser/routes/path-output.js +1 -0
- package/dist/browser/server-context.chrome-test-harness.js +20 -0
- package/dist/browser/server-middleware.js +31 -0
- package/dist/browser/test-port.js +16 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/file-resolver.js +43 -0
- package/dist/channels/account-summary.js +19 -0
- package/dist/channels/draft-stream-loop.js +77 -0
- package/dist/channels/plugins/account-helpers.js +26 -0
- package/dist/channels/telegram/allow-from.js +10 -0
- package/dist/cli/browser-cli-resize.js +22 -0
- package/dist/cli/browser-cli-shared.js +8 -0
- package/dist/cli/clawbot-cli.js +5 -0
- package/dist/cli/completion-cli.js +566 -0
- package/dist/cli/config-cli.js +63 -5
- package/dist/cli/daemon-cli/lifecycle-core.js +256 -0
- package/dist/cli/daemon-cli/register-service-commands.js +60 -0
- package/dist/cli/daemon-cli-compat.js +80 -0
- package/dist/cli/nodes-cli/pairing-render.js +26 -0
- package/dist/cli/program/action-reparse.js +17 -0
- package/dist/cli/program/command-registry.js +17 -0
- package/dist/cli/program/program-context.js +8 -0
- package/dist/cli/program/register.subclis.js +7 -0
- package/dist/cli/program/routes.js +233 -0
- package/dist/cli/qr-cli.js +132 -0
- package/dist/cli/requirements-test-fixtures.js +17 -0
- package/dist/cli/respawn-policy.js +4 -0
- package/dist/cli/shared/parse-port.js +18 -0
- package/dist/cli/skills-cli.format.js +241 -0
- package/dist/cli/update-cli/progress.js +121 -0
- package/dist/cli/update-cli/restart-helper.js +108 -0
- package/dist/cli/update-cli/shared.js +196 -0
- package/dist/cli/update-cli/status.js +97 -0
- package/dist/cli/update-cli/suppress-deprecations.js +17 -0
- package/dist/cli/update-cli/update-command.js +506 -0
- package/dist/cli/update-cli/wizard.js +130 -0
- package/dist/cli/update-cli.js +3 -9
- package/dist/cli/windows-argv.js +69 -0
- package/dist/commands/auth-choice-legacy.js +20 -0
- package/dist/commands/auth-choice.apply-helpers.js +8 -0
- package/dist/commands/channel-test-helpers.js +19 -0
- package/dist/commands/cleanup-plan.js +10 -0
- package/dist/commands/cleanup-utils.js +7 -0
- package/dist/commands/config-validation.js +15 -0
- package/dist/commands/doctor-completion.js +112 -0
- package/dist/commands/doctor-memory-search.js +119 -0
- package/dist/commands/doctor-session-locks.js +73 -0
- package/dist/commands/doctor.e2e-harness.js +364 -0
- package/dist/commands/gateway-presence.js +19 -0
- package/dist/commands/model-default.js +35 -0
- package/dist/commands/models/fallbacks-shared.js +102 -0
- package/dist/commands/models/shared.js +24 -0
- package/dist/commands/onboard-auth.config-gateways.js +64 -0
- package/dist/commands/onboard-auth.config-litellm.js +45 -0
- package/dist/commands/onboard-auth.config-shared.js +116 -0
- package/dist/commands/onboard-config.js +16 -0
- package/dist/commands/onboard-non-interactive.test-helpers.js +31 -0
- package/dist/commands/onboard-provider-auth-flags.js +136 -0
- package/dist/commands/openai-codex-oauth.js +40 -0
- package/dist/commands/test-runtime-config-helpers.js +21 -0
- package/dist/commands/test-wizard-helpers.js +68 -0
- package/dist/commands/vllm-setup.js +66 -0
- package/dist/compat/legacy-names.js +2 -0
- package/dist/config/backup-rotation.js +19 -0
- package/dist/config/env-preserve.js +122 -0
- package/dist/config/includes-scan.js +78 -0
- package/dist/config/plugins-allowlist.js +13 -0
- package/dist/config/schema.help.js +256 -0
- package/dist/config/schema.hints.js +189 -0
- package/dist/config/schema.irc.js +20 -0
- package/dist/config/schema.labels.js +317 -0
- package/dist/config/sessions/delivery-info.js +40 -0
- package/dist/config/types.irc.js +1 -0
- package/dist/config/zod-schema.agent-defaults.js +14 -0
- package/dist/config/zod-schema.agent-model.js +10 -0
- package/dist/config/zod-schema.agent-runtime.js +14 -0
- package/dist/config/zod-schema.allowdeny.js +35 -0
- package/dist/config/zod-schema.sensitive.js +4 -0
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/skills-snapshot.js +26 -0
- package/dist/cron/isolated-agent/subagent-followup.js +127 -0
- package/dist/cron/isolated-agent.mocks.js +12 -0
- package/dist/cron/isolated-agent.test-setup.js +22 -0
- package/dist/cron/legacy-delivery.js +43 -0
- package/dist/cron/webhook-url.js +22 -0
- package/dist/daemon/arg-split.js +40 -0
- package/dist/daemon/exec-file.js +23 -0
- package/dist/daemon/output.js +6 -0
- package/dist/daemon/runtime-format.js +31 -0
- package/dist/daemon/schtasks-exec.js +4 -0
- package/dist/daemon/service-audit.js +22 -0
- package/dist/discord/client.js +41 -0
- package/dist/discord/components-registry.js +57 -0
- package/dist/discord/components.js +816 -0
- package/dist/discord/guilds.js +12 -0
- package/dist/discord/monitor/gateway-plugin.js +48 -0
- package/dist/discord/monitor/presence.js +30 -0
- package/dist/discord/send.components.js +115 -0
- package/dist/discord/send.shared.js +4 -0
- package/dist/discord/ui.js +26 -0
- package/dist/discord/voice-message.js +254 -0
- package/dist/gateway/agent-event-assistant-text.js +5 -0
- package/dist/gateway/agent-prompt.js +33 -0
- package/dist/gateway/auth-rate-limit.js +136 -0
- package/dist/gateway/channel-health-monitor.js +114 -0
- package/dist/gateway/control-ui-contract.js +1 -0
- package/dist/gateway/control-ui-csp.js +15 -0
- package/dist/gateway/gateway-config-prompts.shared.js +25 -0
- package/dist/gateway/http-auth-helpers.js +18 -0
- package/dist/gateway/http-common.js +18 -0
- package/dist/gateway/http-endpoint-helpers.js +27 -0
- package/dist/gateway/node-invoke-sanitize.js +11 -0
- package/dist/gateway/node-invoke-system-run-approval.js +205 -0
- package/dist/gateway/probe-auth.js +21 -0
- package/dist/gateway/protocol/index.js +7 -2
- package/dist/gateway/protocol/schema/mesh.js +54 -0
- package/dist/gateway/protocol/schema/protocol-schemas.js +7 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server/ws-connection/auth-messages.js +54 -0
- package/dist/gateway/server-channels.js +11 -0
- package/dist/gateway/server-methods/attachment-normalize.js +16 -0
- package/dist/gateway/server-methods/base-hash.js +8 -0
- package/dist/gateway/server-methods/mesh.js +700 -0
- package/dist/gateway/server-methods/nodes.handlers.invoke-result.js +55 -0
- package/dist/gateway/server-methods/restart-request.js +13 -0
- package/dist/gateway/server-methods/validation.js +8 -0
- package/dist/gateway/server.agent.gateway-server-agent.mocks.js +35 -0
- package/dist/gateway/server.e2e-registry-helpers.js +1 -0
- package/dist/gateway/server.e2e-ws-harness.js +20 -0
- package/dist/gateway/test-helpers.js +2 -0
- package/dist/gateway/test-helpers.server.js +3 -1
- package/dist/gateway/test-http-response.js +12 -0
- package/dist/gateway/test-openai-responses-model.js +20 -0
- package/dist/gateway/test-temp-config.js +30 -0
- package/dist/gateway/test-with-server.js +32 -0
- package/dist/hooks/bundled/bootstrap-extra-files/handler.js +46 -0
- package/dist/imessage/monitor/abort-handler.js +23 -0
- package/dist/imessage/monitor/inbound-processing.js +346 -0
- package/dist/imessage/monitor/parse-notification.js +64 -0
- package/dist/imessage/target-parsing-helpers.js +92 -0
- package/dist/infra/archive.js +244 -20
- package/dist/infra/detect-package-manager.js +26 -0
- package/dist/infra/exec-approvals-allowlist.js +257 -0
- package/dist/infra/exec-approvals-analysis.js +770 -0
- package/dist/infra/exec-approvals.js +13 -0
- package/dist/infra/file-lock.js +1 -0
- package/dist/infra/gemini-auth.js +39 -0
- package/dist/infra/heartbeat-active-hours.js +85 -0
- package/dist/infra/heartbeat-events-filter.js +50 -0
- package/dist/infra/heartbeat-runner.test-utils.js +39 -0
- package/dist/infra/http-body.js +265 -0
- package/dist/infra/install-package-dir.js +50 -0
- package/dist/infra/install-safe-path.js +49 -0
- package/dist/infra/json-files.js +49 -0
- package/dist/infra/jsonl-socket.js +52 -0
- package/dist/infra/map-size.js +14 -0
- package/dist/infra/net/hostname.js +7 -0
- package/dist/infra/npm-registry-spec.js +39 -0
- package/dist/infra/openclaw-root.js +109 -0
- package/dist/infra/outbound/delivery-queue.js +214 -0
- package/dist/infra/outbound/identity.js +23 -0
- package/dist/infra/outbound/message-action-params.js +307 -0
- package/dist/infra/outbound/tool-payload.js +21 -0
- package/dist/infra/package-json.js +23 -0
- package/dist/infra/pairing-files.js +19 -0
- package/dist/infra/pairing-token.js +9 -0
- package/dist/infra/path-prepend.js +51 -0
- package/dist/infra/path-safety.js +16 -0
- package/dist/infra/process-respawn.js +49 -0
- package/dist/infra/runtime-status.js +16 -0
- package/dist/infra/session-cost-usage.types.js +1 -0
- package/dist/infra/session-maintenance-warning.js +89 -0
- package/dist/infra/system-run-command.js +78 -0
- package/dist/infra/tmp-openclaw-dir.js +81 -0
- package/dist/infra/tmp-poolbot-dir.js +2 -0
- package/dist/infra/update-channels.js +19 -0
- package/dist/line/actions.js +45 -0
- package/dist/line/channel-access-token.js +9 -0
- package/dist/line/flex-templates/basic-cards.js +332 -0
- package/dist/line/flex-templates/common.js +18 -0
- package/dist/line/flex-templates/media-control-cards.js +453 -0
- package/dist/line/flex-templates/message.js +10 -0
- package/dist/line/flex-templates/schedule-cards.js +399 -0
- package/dist/line/flex-templates/types.js +1 -0
- package/dist/line/webhook-node.js +100 -0
- package/dist/line/webhook-utils.js +11 -0
- package/dist/logging/diagnostic-session-state.js +73 -0
- package/dist/logging/diagnostic.js +22 -0
- package/dist/logging/timestamps.js +14 -0
- package/dist/markdown/whatsapp.js +62 -0
- package/dist/media/base64.js +34 -0
- package/dist/media/local-roots.js +32 -0
- package/dist/media/outbound-attachment.js +10 -0
- package/dist/media/read-response-with-limit.js +41 -0
- package/dist/media/sniff-mime-from-base64.js +19 -0
- package/dist/media-understanding/audio-preflight.js +67 -0
- package/dist/media-understanding/fs.js +13 -0
- package/dist/media-understanding/output-extract.js +26 -0
- package/dist/media-understanding/providers/audio.test-helpers.js +34 -0
- package/dist/media-understanding/providers/google/inline-data.js +64 -0
- package/dist/media-understanding/providers/shared.js +7 -0
- package/dist/media-understanding/runner.entries.js +459 -0
- package/dist/memory/batch-error-utils.js +11 -0
- package/dist/memory/batch-http.js +27 -0
- package/dist/memory/batch-output.js +29 -0
- package/dist/memory/batch-runner.js +22 -0
- package/dist/memory/batch-upload.js +23 -0
- package/dist/memory/batch-utils.js +26 -0
- package/dist/memory/embeddings-debug.js +11 -0
- package/dist/memory/embeddings-remote-client.js +22 -0
- package/dist/memory/embeddings-remote-fetch.js +14 -0
- package/dist/memory/embeddings.js +36 -9
- package/dist/memory/hybrid.js +24 -5
- package/dist/memory/manager-embedding-ops.js +616 -0
- package/dist/memory/manager-sync-ops.js +953 -0
- package/dist/memory/manager.js +76 -28
- package/dist/memory/mmr.js +164 -0
- package/dist/memory/qmd-manager.js +1061 -0
- package/dist/memory/qmd-query-parser.js +107 -0
- package/dist/memory/qmd-scope.js +93 -0
- package/dist/memory/query-expansion.js +331 -0
- package/dist/memory/search-manager.js +0 -1
- package/dist/memory/sync-index.js +21 -0
- package/dist/memory/sync-progress.js +22 -0
- package/dist/memory/sync-stale.js +30 -0
- package/dist/memory/temporal-decay.js +119 -0
- package/dist/memory/test-embeddings-mock.js +16 -0
- package/dist/memory/test-manager-helpers.js +14 -0
- package/dist/memory/test-runtime-mocks.js +11 -0
- package/dist/node-host/invoke-browser.js +177 -0
- package/dist/node-host/invoke.js +685 -0
- package/dist/pairing/setup-code.js +285 -0
- package/dist/plugin-sdk/account-id.js +1 -0
- package/dist/plugin-sdk/agent-media-payload.js +13 -0
- package/dist/plugin-sdk/allow-from.js +47 -0
- package/dist/plugin-sdk/command-auth.js +23 -0
- package/dist/plugin-sdk/config-paths.js +9 -0
- package/dist/plugin-sdk/file-lock.js +116 -0
- package/dist/plugin-sdk/json-store.js +31 -0
- package/dist/plugin-sdk/onboarding.js +28 -0
- package/dist/plugin-sdk/provider-auth-result.js +29 -0
- package/dist/plugin-sdk/slack-message-actions.js +133 -0
- package/dist/plugin-sdk/status-helpers.js +35 -0
- package/dist/plugin-sdk/text-chunking.js +31 -0
- package/dist/plugin-sdk/tool-send.js +12 -0
- package/dist/plugin-sdk/webhook-path.js +27 -0
- package/dist/plugin-sdk/webhook-targets.js +34 -0
- package/dist/plugins/hooks.test-helpers.js +21 -0
- package/dist/plugins/uninstall.js +171 -0
- package/dist/process/kill-tree.js +98 -0
- package/dist/process/supervisor/adapters/child.js +143 -0
- package/dist/process/supervisor/adapters/env.js +13 -0
- package/dist/process/supervisor/adapters/pty.js +148 -0
- package/dist/process/supervisor/index.js +10 -0
- package/dist/process/supervisor/registry.js +117 -0
- package/dist/process/supervisor/supervisor.js +244 -0
- package/dist/process/supervisor/types.js +1 -0
- package/dist/providers/google-shared.test-helpers.js +75 -0
- package/dist/security/audit-channel.js +419 -0
- package/dist/security/audit-tool-policy.js +1 -0
- package/dist/security/scan-paths.js +12 -0
- package/dist/sessions/input-provenance.js +55 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/shared/chat-content.js +31 -0
- package/dist/shared/chat-envelope.js +45 -0
- package/dist/shared/config-eval.js +117 -0
- package/dist/shared/device-auth.js +16 -0
- package/dist/shared/entry-metadata.js +9 -0
- package/dist/shared/entry-status.js +25 -0
- package/dist/shared/frontmatter.js +98 -0
- package/dist/shared/model-param-b.js +19 -0
- package/dist/shared/net/ipv4.js +17 -0
- package/dist/shared/node-match.js +53 -0
- package/dist/shared/pid-alive.js +12 -0
- package/dist/shared/process-scoped-map.js +10 -0
- package/dist/shared/requirements.js +128 -0
- package/dist/shared/subagents-format.js +84 -0
- package/dist/shared/usage-aggregates.js +28 -0
- package/dist/signal/monitor/mentions.js +45 -0
- package/dist/signal/rpc-context.js +19 -0
- package/dist/slack/blocks-fallback.js +76 -0
- package/dist/slack/blocks-input.js +40 -0
- package/dist/slack/draft-stream.js +106 -0
- package/dist/slack/message-actions.js +51 -0
- package/dist/slack/modal-metadata.js +32 -0
- package/dist/slack/monitor/events/interactions.js +462 -0
- package/dist/slack/monitor/room-context.js +17 -0
- package/dist/slack/stream-mode.js +41 -0
- package/dist/telegram/bot-native-command-menu.js +64 -0
- package/dist/telegram/bot.media.e2e-harness.js +81 -0
- package/dist/telegram/button-types.js +1 -0
- package/dist/telegram/group-access.js +65 -0
- package/dist/telegram/outbound-params.js +21 -0
- package/dist/telegram/poll-vote-cache.js +21 -0
- package/dist/terminal/health-style.js +36 -0
- package/dist/test-utils/chunk-test-helpers.js +21 -0
- package/dist/test-utils/env.js +72 -0
- package/dist/test-utils/exec-assertions.js +12 -0
- package/dist/test-utils/imessage-test-plugin.js +54 -0
- package/dist/test-utils/mock-http-response.js +17 -0
- package/dist/test-utils/vitest-mock-fn.js +1 -0
- package/dist/tts/tts-core.js +550 -0
- package/dist/utils/chunk-items.js +10 -0
- package/dist/utils/reaction-level.js +52 -0
- package/dist/utils/safe-json.js +22 -0
- package/dist/utils/with-timeout.js +14 -0
- package/dist/web/media.js +17 -5
- package/dist/whatsapp/resolve-outbound-target.js +42 -0
- package/dist/wizard/onboarding.completion.js +74 -0
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/bluebubbles/src/account-resolve.ts +29 -0
- package/extensions/bluebubbles/src/monitor-normalize.ts +796 -0
- package/extensions/bluebubbles/src/monitor-processing.ts +1007 -0
- package/extensions/bluebubbles/src/monitor-reply-cache.ts +185 -0
- package/extensions/bluebubbles/src/monitor-shared.ts +51 -0
- package/extensions/bluebubbles/src/multipart.ts +32 -0
- package/extensions/bluebubbles/src/send-helpers.ts +53 -0
- package/extensions/bluebubbles/src/test-harness.ts +50 -0
- package/extensions/bluebubbles/src/test-mocks.ts +11 -0
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/device-pair/index.ts +554 -0
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/discord/src/channel.js +366 -0
- package/extensions/discord/src/runtime.js +10 -0
- package/extensions/feishu/index.ts +63 -0
- package/extensions/feishu/src/accounts.ts +114 -0
- package/extensions/feishu/src/bitable.ts +739 -0
- package/extensions/feishu/src/bot.ts +965 -0
- package/extensions/feishu/src/channel.ts +351 -0
- package/extensions/feishu/src/client.ts +118 -0
- package/extensions/feishu/src/config-schema.ts +206 -0
- package/extensions/feishu/src/dedup.ts +33 -0
- package/extensions/feishu/src/directory.ts +177 -0
- package/extensions/feishu/src/doc-schema.ts +47 -0
- package/extensions/feishu/src/docx.ts +536 -0
- package/extensions/feishu/src/drive-schema.ts +46 -0
- package/extensions/feishu/src/drive.ts +227 -0
- package/extensions/feishu/src/dynamic-agent.ts +131 -0
- package/extensions/feishu/src/media.ts +449 -0
- package/extensions/feishu/src/mention.ts +126 -0
- package/extensions/feishu/src/monitor.ts +330 -0
- package/extensions/feishu/src/onboarding.ts +359 -0
- package/extensions/feishu/src/outbound.ts +55 -0
- package/extensions/feishu/src/perm-schema.ts +52 -0
- package/extensions/feishu/src/perm.ts +173 -0
- package/extensions/feishu/src/policy.ts +84 -0
- package/extensions/feishu/src/probe.ts +44 -0
- package/extensions/feishu/src/reactions.ts +160 -0
- package/extensions/feishu/src/reply-dispatcher.ts +239 -0
- package/extensions/feishu/src/runtime.ts +14 -0
- package/extensions/feishu/src/send-result.ts +29 -0
- package/extensions/feishu/src/send.ts +335 -0
- package/extensions/feishu/src/streaming-card.ts +223 -0
- package/extensions/feishu/src/targets.ts +78 -0
- package/extensions/feishu/src/tools-config.ts +21 -0
- package/extensions/feishu/src/types.ts +81 -0
- package/extensions/feishu/src/typing.ts +80 -0
- package/extensions/feishu/src/wiki-schema.ts +55 -0
- package/extensions/feishu/src/wiki.ts +232 -0
- 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/imessage/src/channel.js +253 -0
- package/extensions/imessage/src/runtime.js +10 -0
- package/extensions/irc/index.ts +17 -0
- package/extensions/irc/src/accounts.ts +268 -0
- package/extensions/irc/src/channel.ts +367 -0
- package/extensions/irc/src/client.ts +439 -0
- package/extensions/irc/src/config-schema.ts +97 -0
- package/extensions/irc/src/connect-options.ts +30 -0
- package/extensions/irc/src/control-chars.ts +22 -0
- package/extensions/irc/src/inbound.ts +334 -0
- package/extensions/irc/src/monitor.ts +147 -0
- package/extensions/irc/src/normalize.ts +117 -0
- package/extensions/irc/src/onboarding.ts +479 -0
- package/extensions/irc/src/policy.ts +157 -0
- package/extensions/irc/src/probe.ts +53 -0
- package/extensions/irc/src/protocol.ts +169 -0
- package/extensions/irc/src/runtime.ts +14 -0
- package/extensions/irc/src/send.ts +88 -0
- package/extensions/irc/src/types.ts +93 -0
- 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 +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/client-bootstrap.ts +39 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/monitor-onchar.ts +25 -0
- package/extensions/mattermost/src/mattermost/monitor-websocket.ts +221 -0
- package/extensions/mattermost/src/mattermost/reactions.ts +130 -0
- package/extensions/mattermost/src/mattermost/reconnect.ts +103 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/index.ts +161 -0
- package/extensions/minimax-portal-auth/oauth.ts +247 -0
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/msteams/src/file-lock.ts +1 -0
- package/extensions/msteams/src/graph.ts +92 -0
- package/extensions/msteams/src/mentions.ts +114 -0
- package/extensions/msteams/src/test-runtime.ts +16 -0
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/index.ts +177 -0
- package/extensions/phone-control/index.ts +421 -0
- package/extensions/shared/resolve-target-test-helpers.ts +66 -0
- package/extensions/signal/package.json +1 -1
- package/extensions/signal/src/channel.js +273 -0
- package/extensions/signal/src/runtime.js +10 -0
- package/extensions/slack/package.json +1 -1
- package/extensions/slack/src/channel.js +489 -0
- package/extensions/slack/src/runtime.js +10 -0
- package/extensions/talk-voice/index.ts +150 -0
- package/extensions/telegram/package.json +1 -1
- package/extensions/telegram/src/channel.js +424 -0
- package/extensions/telegram/src/runtime.js +10 -0
- package/extensions/thread-ownership/index.ts +133 -0
- package/extensions/tlon/package.json +1 -1
- package/extensions/tlon/src/account-fields.ts +25 -0
- package/extensions/tlon/src/urbit/base-url.ts +57 -0
- package/extensions/tlon/src/urbit/channel-client.ts +157 -0
- package/extensions/tlon/src/urbit/channel-ops.ts +164 -0
- package/extensions/tlon/src/urbit/context.ts +47 -0
- package/extensions/tlon/src/urbit/errors.ts +51 -0
- package/extensions/tlon/src/urbit/fetch.ts +39 -0
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/twitch/src/test-fixtures.ts +30 -0
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/voice-call/src/allowlist.ts +19 -0
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/whatsapp/src/channel.js +429 -0
- package/extensions/whatsapp/src/runtime.js +10 -0
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { fetchDiscord } from "./api.js";
|
|
2
|
+
import { normalizeDiscordSlug } from "./monitor/allow-list.js";
|
|
3
|
+
export async function listGuilds(token, fetcher) {
|
|
4
|
+
const raw = await fetchDiscord("/users/@me/guilds", token, fetcher);
|
|
5
|
+
return raw
|
|
6
|
+
.filter((guild) => typeof guild.id === "string" && typeof guild.name === "string")
|
|
7
|
+
.map((guild) => ({
|
|
8
|
+
id: guild.id,
|
|
9
|
+
name: guild.name,
|
|
10
|
+
slug: normalizeDiscordSlug(guild.name),
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
|
|
2
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
3
|
+
import WebSocket from "ws";
|
|
4
|
+
import { danger } from "../../globals.js";
|
|
5
|
+
export function resolveDiscordGatewayIntents(intentsConfig) {
|
|
6
|
+
let intents = GatewayIntents.Guilds |
|
|
7
|
+
GatewayIntents.GuildMessages |
|
|
8
|
+
GatewayIntents.MessageContent |
|
|
9
|
+
GatewayIntents.DirectMessages |
|
|
10
|
+
GatewayIntents.GuildMessageReactions |
|
|
11
|
+
GatewayIntents.DirectMessageReactions;
|
|
12
|
+
if (intentsConfig?.presence) {
|
|
13
|
+
intents |= GatewayIntents.GuildPresences;
|
|
14
|
+
}
|
|
15
|
+
if (intentsConfig?.guildMembers) {
|
|
16
|
+
intents |= GatewayIntents.GuildMembers;
|
|
17
|
+
}
|
|
18
|
+
return intents;
|
|
19
|
+
}
|
|
20
|
+
export function createDiscordGatewayPlugin(params) {
|
|
21
|
+
const intents = resolveDiscordGatewayIntents(params.discordConfig?.intents);
|
|
22
|
+
const proxy = params.discordConfig?.proxy?.trim();
|
|
23
|
+
const options = {
|
|
24
|
+
reconnect: { maxAttempts: 50 },
|
|
25
|
+
intents,
|
|
26
|
+
autoInteractions: true,
|
|
27
|
+
};
|
|
28
|
+
if (!proxy) {
|
|
29
|
+
return new GatewayPlugin(options);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const agent = new HttpsProxyAgent(proxy);
|
|
33
|
+
params.runtime.log?.("discord: gateway proxy enabled");
|
|
34
|
+
class ProxyGatewayPlugin extends GatewayPlugin {
|
|
35
|
+
constructor() {
|
|
36
|
+
super(options);
|
|
37
|
+
}
|
|
38
|
+
createWebSocket(url) {
|
|
39
|
+
return new WebSocket(url, { agent });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return new ProxyGatewayPlugin();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
params.runtime.error?.(danger(`discord: invalid gateway proxy: ${String(err)}`));
|
|
46
|
+
return new GatewayPlugin(options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DEFAULT_CUSTOM_ACTIVITY_TYPE = 4;
|
|
2
|
+
const CUSTOM_STATUS_NAME = "Custom Status";
|
|
3
|
+
export function resolveDiscordPresenceUpdate(config) {
|
|
4
|
+
const activityText = typeof config.activity === "string" ? config.activity.trim() : "";
|
|
5
|
+
const status = typeof config.status === "string" ? config.status.trim() : "";
|
|
6
|
+
const activityType = config.activityType;
|
|
7
|
+
const activityUrl = typeof config.activityUrl === "string" ? config.activityUrl.trim() : "";
|
|
8
|
+
const hasActivity = Boolean(activityText);
|
|
9
|
+
const hasStatus = Boolean(status);
|
|
10
|
+
if (!hasActivity && !hasStatus) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const activities = [];
|
|
14
|
+
if (hasActivity) {
|
|
15
|
+
const resolvedType = activityType ?? DEFAULT_CUSTOM_ACTIVITY_TYPE;
|
|
16
|
+
const activity = resolvedType === DEFAULT_CUSTOM_ACTIVITY_TYPE
|
|
17
|
+
? { name: CUSTOM_STATUS_NAME, type: resolvedType, state: activityText }
|
|
18
|
+
: { name: activityText, type: resolvedType };
|
|
19
|
+
if (resolvedType === 1 && activityUrl) {
|
|
20
|
+
activity.url = activityUrl;
|
|
21
|
+
}
|
|
22
|
+
activities.push(activity);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
since: null,
|
|
26
|
+
activities,
|
|
27
|
+
status: (status || "online"),
|
|
28
|
+
afk: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { serializePayload, } from "@buape/carbon";
|
|
2
|
+
import { ChannelType, Routes } from "discord-api-types/v10";
|
|
3
|
+
import { loadConfig } from "../config/config.js";
|
|
4
|
+
import { recordChannelActivity } from "../infra/channel-activity.js";
|
|
5
|
+
import { loadWebMedia } from "../web/media.js";
|
|
6
|
+
import { resolveDiscordAccount } from "./accounts.js";
|
|
7
|
+
import { registerDiscordComponentEntries } from "./components-registry.js";
|
|
8
|
+
import { buildDiscordComponentMessage, buildDiscordComponentMessageFlags, resolveDiscordComponentAttachmentName, } from "./components.js";
|
|
9
|
+
import { buildDiscordSendError, createDiscordClient, parseAndResolveRecipient, resolveChannelId, stripUndefinedFields, SUPPRESS_NOTIFICATIONS_FLAG, } from "./send.shared.js";
|
|
10
|
+
const DISCORD_FORUM_LIKE_TYPES = new Set([ChannelType.GuildForum, ChannelType.GuildMedia]);
|
|
11
|
+
function extractComponentAttachmentNames(spec) {
|
|
12
|
+
const names = [];
|
|
13
|
+
for (const block of spec.blocks ?? []) {
|
|
14
|
+
if (block.type === "file") {
|
|
15
|
+
names.push(resolveDiscordComponentAttachmentName(block.file));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return names;
|
|
19
|
+
}
|
|
20
|
+
export async function sendDiscordComponentMessage(to, spec, opts = {}) {
|
|
21
|
+
const cfg = loadConfig();
|
|
22
|
+
const accountInfo = resolveDiscordAccount({ cfg, accountId: opts.accountId });
|
|
23
|
+
const { token, rest, request } = createDiscordClient(opts, cfg);
|
|
24
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
25
|
+
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
26
|
+
let channelType;
|
|
27
|
+
try {
|
|
28
|
+
const channel = (await rest.get(Routes.channel(channelId)));
|
|
29
|
+
channelType = channel?.type;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
channelType = undefined;
|
|
33
|
+
}
|
|
34
|
+
if (channelType && DISCORD_FORUM_LIKE_TYPES.has(channelType)) {
|
|
35
|
+
throw new Error("Discord components are not supported in forum-style channels");
|
|
36
|
+
}
|
|
37
|
+
const buildResult = buildDiscordComponentMessage({
|
|
38
|
+
spec,
|
|
39
|
+
sessionKey: opts.sessionKey,
|
|
40
|
+
agentId: opts.agentId,
|
|
41
|
+
accountId: accountInfo.accountId,
|
|
42
|
+
});
|
|
43
|
+
const flags = buildDiscordComponentMessageFlags(buildResult.components);
|
|
44
|
+
const finalFlags = opts.silent
|
|
45
|
+
? (flags ?? 0) | SUPPRESS_NOTIFICATIONS_FLAG
|
|
46
|
+
: (flags ?? undefined);
|
|
47
|
+
const messageReference = opts.replyTo
|
|
48
|
+
? { message_id: opts.replyTo, fail_if_not_exists: false }
|
|
49
|
+
: undefined;
|
|
50
|
+
const attachmentNames = extractComponentAttachmentNames(spec);
|
|
51
|
+
const uniqueAttachmentNames = [...new Set(attachmentNames)];
|
|
52
|
+
if (uniqueAttachmentNames.length > 1) {
|
|
53
|
+
throw new Error("Discord component attachments currently support a single file. Use media-gallery for multiple files.");
|
|
54
|
+
}
|
|
55
|
+
const expectedAttachmentName = uniqueAttachmentNames[0];
|
|
56
|
+
let files;
|
|
57
|
+
if (opts.mediaUrl) {
|
|
58
|
+
const media = await loadWebMedia(opts.mediaUrl, { localRoots: opts.mediaLocalRoots });
|
|
59
|
+
const filenameOverride = opts.filename?.trim();
|
|
60
|
+
const fileName = filenameOverride || media.fileName || "upload";
|
|
61
|
+
if (expectedAttachmentName && expectedAttachmentName !== fileName) {
|
|
62
|
+
throw new Error(`Component file block expects attachment "${expectedAttachmentName}", but the uploaded file is "${fileName}". Update components.blocks[].file or provide a matching filename.`);
|
|
63
|
+
}
|
|
64
|
+
let fileData;
|
|
65
|
+
if (media.buffer instanceof Blob) {
|
|
66
|
+
fileData = media.buffer;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const arrayBuffer = new ArrayBuffer(media.buffer.byteLength);
|
|
70
|
+
new Uint8Array(arrayBuffer).set(media.buffer);
|
|
71
|
+
fileData = new Blob([arrayBuffer]);
|
|
72
|
+
}
|
|
73
|
+
files = [{ data: fileData, name: fileName }];
|
|
74
|
+
}
|
|
75
|
+
else if (expectedAttachmentName) {
|
|
76
|
+
throw new Error("Discord component file blocks require a media attachment (media/path/filePath).");
|
|
77
|
+
}
|
|
78
|
+
const payload = {
|
|
79
|
+
components: buildResult.components,
|
|
80
|
+
...(finalFlags ? { flags: finalFlags } : {}),
|
|
81
|
+
...(files ? { files } : {}),
|
|
82
|
+
};
|
|
83
|
+
const body = stripUndefinedFields({
|
|
84
|
+
...serializePayload(payload),
|
|
85
|
+
...(messageReference ? { message_reference: messageReference } : {}),
|
|
86
|
+
});
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
result = (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
90
|
+
body,
|
|
91
|
+
}), "components"));
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw await buildDiscordSendError(err, {
|
|
95
|
+
channelId,
|
|
96
|
+
rest,
|
|
97
|
+
token,
|
|
98
|
+
hasMedia: Boolean(files?.length),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
registerDiscordComponentEntries({
|
|
102
|
+
entries: buildResult.entries,
|
|
103
|
+
modals: buildResult.modals,
|
|
104
|
+
messageId: result.id,
|
|
105
|
+
});
|
|
106
|
+
recordChannelActivity({
|
|
107
|
+
channel: "discord",
|
|
108
|
+
accountId: accountInfo.accountId,
|
|
109
|
+
direction: "outbound",
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
messageId: result.id ?? "unknown",
|
|
113
|
+
channelId: result.channel_id ?? channelId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -17,6 +17,7 @@ const DISCORD_POLL_MAX_ANSWERS = 10;
|
|
|
17
17
|
const DISCORD_POLL_MAX_DURATION_HOURS = 32 * 24;
|
|
18
18
|
const DISCORD_MISSING_PERMISSIONS = 50013;
|
|
19
19
|
const DISCORD_CANNOT_DM = 50007;
|
|
20
|
+
export const SUPPRESS_NOTIFICATIONS_FLAG = 1 << 12;
|
|
20
21
|
function resolveToken(params) {
|
|
21
22
|
const explicit = normalizeDiscordToken(params.explicit);
|
|
22
23
|
if (explicit) {
|
|
@@ -283,4 +284,7 @@ function buildReactionIdentifier(emoji) {
|
|
|
283
284
|
function formatReactionEmoji(emoji) {
|
|
284
285
|
return buildReactionIdentifier(emoji);
|
|
285
286
|
}
|
|
287
|
+
export function stripUndefinedFields(obj) {
|
|
288
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
289
|
+
}
|
|
286
290
|
export { buildDiscordSendError, buildReactionIdentifier, createDiscordClient, formatReactionEmoji, normalizeDiscordPollInput, normalizeEmojiName, normalizeReactionEmoji, normalizeStickerIds, parseRecipient, resolveChannelId, resolveDiscordRest, sendDiscordMedia, sendDiscordText, };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Container } from "@buape/carbon";
|
|
2
|
+
import { resolveDiscordAccount } from "./accounts.js";
|
|
3
|
+
const DEFAULT_DISCORD_ACCENT_COLOR = "#5865F2";
|
|
4
|
+
export function normalizeDiscordAccentColor(raw) {
|
|
5
|
+
const trimmed = (raw ?? "").trim();
|
|
6
|
+
if (!trimmed) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const normalized = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
10
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return normalized.toUpperCase();
|
|
14
|
+
}
|
|
15
|
+
export function resolveDiscordAccentColor(params) {
|
|
16
|
+
const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
17
|
+
const configured = normalizeDiscordAccentColor(account.config.ui?.components?.accentColor);
|
|
18
|
+
return configured ?? DEFAULT_DISCORD_ACCENT_COLOR;
|
|
19
|
+
}
|
|
20
|
+
export class DiscordUiContainer extends Container {
|
|
21
|
+
constructor(params) {
|
|
22
|
+
const accentOverride = normalizeDiscordAccentColor(params.accentColor);
|
|
23
|
+
const accentColor = accentOverride ?? resolveDiscordAccentColor({ cfg: params.cfg, accountId: params.accountId });
|
|
24
|
+
super(params.components, { accentColor, spoiler: params.spoiler });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Voice Message Support
|
|
3
|
+
*
|
|
4
|
+
* Implements sending voice messages via Discord's API.
|
|
5
|
+
* Voice messages require:
|
|
6
|
+
* - OGG/Opus format audio
|
|
7
|
+
* - Waveform data (base64 encoded, up to 256 samples, 0-255 values)
|
|
8
|
+
* - Duration in seconds
|
|
9
|
+
* - Message flag 8192 (IS_VOICE_MESSAGE)
|
|
10
|
+
* - No other content (text, embeds, etc.)
|
|
11
|
+
*/
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
import crypto from "node:crypto";
|
|
14
|
+
import fs from "node:fs/promises";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { promisify } from "node:util";
|
|
17
|
+
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-poolbot-dir.js";
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
const DISCORD_VOICE_MESSAGE_FLAG = 1 << 13;
|
|
20
|
+
const SUPPRESS_NOTIFICATIONS_FLAG = 1 << 12;
|
|
21
|
+
const WAVEFORM_SAMPLES = 256;
|
|
22
|
+
/**
|
|
23
|
+
* Get audio duration using ffprobe
|
|
24
|
+
*/
|
|
25
|
+
export async function getAudioDuration(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
const { stdout } = await execFileAsync("ffprobe", [
|
|
28
|
+
"-v",
|
|
29
|
+
"error",
|
|
30
|
+
"-show_entries",
|
|
31
|
+
"format=duration",
|
|
32
|
+
"-of",
|
|
33
|
+
"csv=p=0",
|
|
34
|
+
filePath,
|
|
35
|
+
]);
|
|
36
|
+
const duration = parseFloat(stdout.trim());
|
|
37
|
+
if (isNaN(duration)) {
|
|
38
|
+
throw new Error("Could not parse duration");
|
|
39
|
+
}
|
|
40
|
+
return Math.round(duration * 100) / 100; // Round to 2 decimal places
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
44
|
+
throw new Error(`Failed to get audio duration: ${errMessage}`, { cause: err });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate waveform data from audio file using ffmpeg
|
|
49
|
+
* Returns base64 encoded byte array of amplitude samples (0-255)
|
|
50
|
+
*/
|
|
51
|
+
export async function generateWaveform(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
// Extract raw PCM and sample amplitude values
|
|
54
|
+
return await generateWaveformFromPcm(filePath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// If PCM extraction fails, generate a placeholder waveform
|
|
58
|
+
return generatePlaceholderWaveform();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate waveform by extracting raw PCM data and sampling amplitudes
|
|
63
|
+
*/
|
|
64
|
+
async function generateWaveformFromPcm(filePath) {
|
|
65
|
+
const tempDir = resolvePreferredOpenClawTmpDir();
|
|
66
|
+
const tempPcm = path.join(tempDir, `waveform-${crypto.randomUUID()}.raw`);
|
|
67
|
+
try {
|
|
68
|
+
// Convert to raw 16-bit signed PCM, mono, 8kHz
|
|
69
|
+
await execFileAsync("ffmpeg", [
|
|
70
|
+
"-y",
|
|
71
|
+
"-i",
|
|
72
|
+
filePath,
|
|
73
|
+
"-f",
|
|
74
|
+
"s16le",
|
|
75
|
+
"-acodec",
|
|
76
|
+
"pcm_s16le",
|
|
77
|
+
"-ac",
|
|
78
|
+
"1",
|
|
79
|
+
"-ar",
|
|
80
|
+
"8000",
|
|
81
|
+
tempPcm,
|
|
82
|
+
]);
|
|
83
|
+
const pcmData = await fs.readFile(tempPcm);
|
|
84
|
+
const samples = new Int16Array(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength / 2);
|
|
85
|
+
// Sample the PCM data to get WAVEFORM_SAMPLES points
|
|
86
|
+
const step = Math.max(1, Math.floor(samples.length / WAVEFORM_SAMPLES));
|
|
87
|
+
const waveform = [];
|
|
88
|
+
for (let i = 0; i < WAVEFORM_SAMPLES && i * step < samples.length; i++) {
|
|
89
|
+
// Get average absolute amplitude for this segment
|
|
90
|
+
let sum = 0;
|
|
91
|
+
let count = 0;
|
|
92
|
+
for (let j = 0; j < step && i * step + j < samples.length; j++) {
|
|
93
|
+
sum += Math.abs(samples[i * step + j]);
|
|
94
|
+
count++;
|
|
95
|
+
}
|
|
96
|
+
const avg = count > 0 ? sum / count : 0;
|
|
97
|
+
// Normalize to 0-255 (16-bit signed max is 32767)
|
|
98
|
+
const normalized = Math.min(255, Math.round((avg / 32767) * 255));
|
|
99
|
+
waveform.push(normalized);
|
|
100
|
+
}
|
|
101
|
+
// Pad with zeros if we don't have enough samples
|
|
102
|
+
while (waveform.length < WAVEFORM_SAMPLES) {
|
|
103
|
+
waveform.push(0);
|
|
104
|
+
}
|
|
105
|
+
return Buffer.from(waveform).toString("base64");
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
// Clean up temp file
|
|
109
|
+
try {
|
|
110
|
+
await fs.unlink(tempPcm);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Ignore cleanup errors
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generate a placeholder waveform (for when audio processing fails)
|
|
119
|
+
*/
|
|
120
|
+
function generatePlaceholderWaveform() {
|
|
121
|
+
// Generate a simple sine-wave-like pattern
|
|
122
|
+
const waveform = [];
|
|
123
|
+
for (let i = 0; i < WAVEFORM_SAMPLES; i++) {
|
|
124
|
+
const value = Math.round(128 + 64 * Math.sin((i / WAVEFORM_SAMPLES) * Math.PI * 8));
|
|
125
|
+
waveform.push(Math.min(255, Math.max(0, value)));
|
|
126
|
+
}
|
|
127
|
+
return Buffer.from(waveform).toString("base64");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Convert audio file to OGG/Opus format if needed
|
|
131
|
+
* Returns path to the OGG file (may be same as input if already OGG/Opus)
|
|
132
|
+
*/
|
|
133
|
+
export async function ensureOggOpus(filePath) {
|
|
134
|
+
const trimmed = filePath.trim();
|
|
135
|
+
// Defense-in-depth: callers should never hand ffmpeg/ffprobe a URL/protocol path.
|
|
136
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
|
|
137
|
+
throw new Error(`Voice message conversion requires a local file path; received a URL/protocol source: ${trimmed}`);
|
|
138
|
+
}
|
|
139
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
140
|
+
// Check if already OGG
|
|
141
|
+
if (ext === ".ogg") {
|
|
142
|
+
// Verify it's Opus codec, not Vorbis (Vorbis won't play on mobile)
|
|
143
|
+
try {
|
|
144
|
+
const { stdout } = await execFileAsync("ffprobe", [
|
|
145
|
+
"-v",
|
|
146
|
+
"error",
|
|
147
|
+
"-select_streams",
|
|
148
|
+
"a:0",
|
|
149
|
+
"-show_entries",
|
|
150
|
+
"stream=codec_name",
|
|
151
|
+
"-of",
|
|
152
|
+
"csv=p=0",
|
|
153
|
+
filePath,
|
|
154
|
+
]);
|
|
155
|
+
if (stdout.trim().toLowerCase() === "opus") {
|
|
156
|
+
return { path: filePath, cleanup: false };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// If probe fails, convert anyway
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Convert to OGG/Opus
|
|
164
|
+
const tempDir = resolvePreferredOpenClawTmpDir();
|
|
165
|
+
const outputPath = path.join(tempDir, `voice-${crypto.randomUUID()}.ogg`);
|
|
166
|
+
await execFileAsync("ffmpeg", [
|
|
167
|
+
"-y",
|
|
168
|
+
"-i",
|
|
169
|
+
filePath,
|
|
170
|
+
"-c:a",
|
|
171
|
+
"libopus",
|
|
172
|
+
"-b:a",
|
|
173
|
+
"64k",
|
|
174
|
+
outputPath,
|
|
175
|
+
]);
|
|
176
|
+
return { path: outputPath, cleanup: true };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get voice message metadata (duration and waveform)
|
|
180
|
+
*/
|
|
181
|
+
export async function getVoiceMessageMetadata(filePath) {
|
|
182
|
+
const [durationSecs, waveform] = await Promise.all([
|
|
183
|
+
getAudioDuration(filePath),
|
|
184
|
+
generateWaveform(filePath),
|
|
185
|
+
]);
|
|
186
|
+
return { durationSecs, waveform };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Send a voice message to Discord
|
|
190
|
+
*
|
|
191
|
+
* This follows Discord's voice message protocol:
|
|
192
|
+
* 1. Request upload URL from Discord
|
|
193
|
+
* 2. Upload the OGG file to the provided URL
|
|
194
|
+
* 3. Send the message with flag 8192 and attachment metadata
|
|
195
|
+
*/
|
|
196
|
+
export async function sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, replyTo, request, silent) {
|
|
197
|
+
const filename = "voice-message.ogg";
|
|
198
|
+
const fileSize = audioBuffer.byteLength;
|
|
199
|
+
// Step 1: Request upload URL from Discord
|
|
200
|
+
const uploadUrlResponse = await request(() => rest.post(`/channels/${channelId}/attachments`, {
|
|
201
|
+
body: {
|
|
202
|
+
files: [
|
|
203
|
+
{
|
|
204
|
+
filename,
|
|
205
|
+
file_size: fileSize,
|
|
206
|
+
id: "0",
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
}), "voice-upload-url");
|
|
211
|
+
if (!uploadUrlResponse.attachments?.[0]) {
|
|
212
|
+
throw new Error("Failed to get upload URL for voice message");
|
|
213
|
+
}
|
|
214
|
+
const { upload_url, upload_filename } = uploadUrlResponse.attachments[0];
|
|
215
|
+
// Step 2: Upload the file to Discord's CDN
|
|
216
|
+
// Note: Not wrapped in retry runner - upload URLs are single-use and CDN behavior differs
|
|
217
|
+
const uploadResponse = await fetch(upload_url, {
|
|
218
|
+
method: "PUT",
|
|
219
|
+
headers: {
|
|
220
|
+
"Content-Type": "audio/ogg",
|
|
221
|
+
},
|
|
222
|
+
body: new Uint8Array(audioBuffer),
|
|
223
|
+
});
|
|
224
|
+
if (!uploadResponse.ok) {
|
|
225
|
+
throw new Error(`Failed to upload voice message: ${uploadResponse.status}`);
|
|
226
|
+
}
|
|
227
|
+
// Step 3: Send the message with voice message flag and metadata
|
|
228
|
+
const flags = silent
|
|
229
|
+
? DISCORD_VOICE_MESSAGE_FLAG | SUPPRESS_NOTIFICATIONS_FLAG
|
|
230
|
+
: DISCORD_VOICE_MESSAGE_FLAG;
|
|
231
|
+
const messagePayload = {
|
|
232
|
+
flags,
|
|
233
|
+
attachments: [
|
|
234
|
+
{
|
|
235
|
+
id: "0",
|
|
236
|
+
filename,
|
|
237
|
+
uploaded_filename: upload_filename,
|
|
238
|
+
duration_secs: metadata.durationSecs,
|
|
239
|
+
waveform: metadata.waveform,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
// Note: Voice messages cannot have content, but can have message_reference for replies
|
|
244
|
+
if (replyTo) {
|
|
245
|
+
messagePayload.message_reference = {
|
|
246
|
+
message_id: replyTo,
|
|
247
|
+
fail_if_not_exists: false,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const res = (await request(() => rest.post(`/channels/${channelId}/messages`, {
|
|
251
|
+
body: messagePayload,
|
|
252
|
+
}), "voice-message"));
|
|
253
|
+
return res;
|
|
254
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { buildHistoryContextFromEntries } from "../auto-reply/reply/history.js";
|
|
2
|
+
export function buildAgentMessageFromConversationEntries(entries) {
|
|
3
|
+
if (entries.length === 0) {
|
|
4
|
+
return "";
|
|
5
|
+
}
|
|
6
|
+
// Prefer the last user/tool entry as "current message" so the agent responds to
|
|
7
|
+
// the latest user input or tool output, not the assistant's previous message.
|
|
8
|
+
let currentIndex = -1;
|
|
9
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
10
|
+
const role = entries[i]?.role;
|
|
11
|
+
if (role === "user" || role === "tool") {
|
|
12
|
+
currentIndex = i;
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (currentIndex < 0) {
|
|
17
|
+
currentIndex = entries.length - 1;
|
|
18
|
+
}
|
|
19
|
+
const currentEntry = entries[currentIndex]?.entry;
|
|
20
|
+
if (!currentEntry) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
const historyEntries = entries.slice(0, currentIndex).map((e) => e.entry);
|
|
24
|
+
if (historyEntries.length === 0) {
|
|
25
|
+
return currentEntry.body;
|
|
26
|
+
}
|
|
27
|
+
const formatEntry = (entry) => `${entry.sender}: ${entry.body}`;
|
|
28
|
+
return buildHistoryContextFromEntries({
|
|
29
|
+
entries: [...historyEntries, currentEntry],
|
|
30
|
+
currentMessage: formatEntry(currentEntry),
|
|
31
|
+
formatEntry,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory sliding-window rate limiter for gateway authentication attempts.
|
|
3
|
+
*
|
|
4
|
+
* Tracks failed auth attempts by {scope, clientIp}. A scope lets callers keep
|
|
5
|
+
* independent counters for different credential classes (for example, shared
|
|
6
|
+
* gateway token/password vs device-token auth) while still sharing one
|
|
7
|
+
* limiter instance.
|
|
8
|
+
*
|
|
9
|
+
* Design decisions:
|
|
10
|
+
* - Pure in-memory Map – no external dependencies; suitable for a single
|
|
11
|
+
* gateway process. The Map is periodically pruned to avoid unbounded
|
|
12
|
+
* growth.
|
|
13
|
+
* - Loopback addresses (127.0.0.1 / ::1) are exempt by default so that local
|
|
14
|
+
* CLI sessions are never locked out.
|
|
15
|
+
* - The module is side-effect-free: callers create an instance via
|
|
16
|
+
* {@link createAuthRateLimiter} and pass it where needed.
|
|
17
|
+
*/
|
|
18
|
+
import { isLoopbackAddress } from "./net.js";
|
|
19
|
+
export const AUTH_RATE_LIMIT_SCOPE_DEFAULT = "default";
|
|
20
|
+
export const AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET = "shared-secret";
|
|
21
|
+
export const AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN = "device-token";
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Defaults
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const DEFAULT_MAX_ATTEMPTS = 10;
|
|
26
|
+
const DEFAULT_WINDOW_MS = 60_000; // 1 minute
|
|
27
|
+
const DEFAULT_LOCKOUT_MS = 300_000; // 5 minutes
|
|
28
|
+
const PRUNE_INTERVAL_MS = 60_000; // prune stale entries every minute
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Implementation
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
export function createAuthRateLimiter(config) {
|
|
33
|
+
const maxAttempts = config?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
34
|
+
const windowMs = config?.windowMs ?? DEFAULT_WINDOW_MS;
|
|
35
|
+
const lockoutMs = config?.lockoutMs ?? DEFAULT_LOCKOUT_MS;
|
|
36
|
+
const exemptLoopback = config?.exemptLoopback ?? true;
|
|
37
|
+
const entries = new Map();
|
|
38
|
+
// Periodic cleanup to avoid unbounded map growth.
|
|
39
|
+
const pruneTimer = setInterval(() => prune(), PRUNE_INTERVAL_MS);
|
|
40
|
+
// Allow the Node.js process to exit even if the timer is still active.
|
|
41
|
+
if (pruneTimer.unref) {
|
|
42
|
+
pruneTimer.unref();
|
|
43
|
+
}
|
|
44
|
+
function normalizeScope(scope) {
|
|
45
|
+
return (scope ?? AUTH_RATE_LIMIT_SCOPE_DEFAULT).trim() || AUTH_RATE_LIMIT_SCOPE_DEFAULT;
|
|
46
|
+
}
|
|
47
|
+
function normalizeIp(ip) {
|
|
48
|
+
return (ip ?? "").trim() || "unknown";
|
|
49
|
+
}
|
|
50
|
+
function resolveKey(rawIp, rawScope) {
|
|
51
|
+
const ip = normalizeIp(rawIp);
|
|
52
|
+
const scope = normalizeScope(rawScope);
|
|
53
|
+
return { key: `${scope}:${ip}`, ip };
|
|
54
|
+
}
|
|
55
|
+
function isExempt(ip) {
|
|
56
|
+
return exemptLoopback && isLoopbackAddress(ip);
|
|
57
|
+
}
|
|
58
|
+
function slideWindow(entry, now) {
|
|
59
|
+
const cutoff = now - windowMs;
|
|
60
|
+
// Remove attempts that fell outside the window.
|
|
61
|
+
entry.attempts = entry.attempts.filter((ts) => ts > cutoff);
|
|
62
|
+
}
|
|
63
|
+
function check(rawIp, rawScope) {
|
|
64
|
+
const { key, ip } = resolveKey(rawIp, rawScope);
|
|
65
|
+
if (isExempt(ip)) {
|
|
66
|
+
return { allowed: true, remaining: maxAttempts, retryAfterMs: 0 };
|
|
67
|
+
}
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const entry = entries.get(key);
|
|
70
|
+
if (!entry) {
|
|
71
|
+
return { allowed: true, remaining: maxAttempts, retryAfterMs: 0 };
|
|
72
|
+
}
|
|
73
|
+
// Still locked out?
|
|
74
|
+
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
75
|
+
return {
|
|
76
|
+
allowed: false,
|
|
77
|
+
remaining: 0,
|
|
78
|
+
retryAfterMs: entry.lockedUntil - now,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Lockout expired – clear it.
|
|
82
|
+
if (entry.lockedUntil && now >= entry.lockedUntil) {
|
|
83
|
+
entry.lockedUntil = undefined;
|
|
84
|
+
entry.attempts = [];
|
|
85
|
+
}
|
|
86
|
+
slideWindow(entry, now);
|
|
87
|
+
const remaining = Math.max(0, maxAttempts - entry.attempts.length);
|
|
88
|
+
return { allowed: remaining > 0, remaining, retryAfterMs: 0 };
|
|
89
|
+
}
|
|
90
|
+
function recordFailure(rawIp, rawScope) {
|
|
91
|
+
const { key, ip } = resolveKey(rawIp, rawScope);
|
|
92
|
+
if (isExempt(ip)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
let entry = entries.get(key);
|
|
97
|
+
if (!entry) {
|
|
98
|
+
entry = { attempts: [] };
|
|
99
|
+
entries.set(key, entry);
|
|
100
|
+
}
|
|
101
|
+
// If currently locked, do nothing (already blocked).
|
|
102
|
+
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
slideWindow(entry, now);
|
|
106
|
+
entry.attempts.push(now);
|
|
107
|
+
if (entry.attempts.length >= maxAttempts) {
|
|
108
|
+
entry.lockedUntil = now + lockoutMs;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function reset(rawIp, rawScope) {
|
|
112
|
+
const { key } = resolveKey(rawIp, rawScope);
|
|
113
|
+
entries.delete(key);
|
|
114
|
+
}
|
|
115
|
+
function prune() {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
for (const [key, entry] of entries) {
|
|
118
|
+
// If locked out, keep the entry until the lockout expires.
|
|
119
|
+
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
slideWindow(entry, now);
|
|
123
|
+
if (entry.attempts.length === 0) {
|
|
124
|
+
entries.delete(key);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function size() {
|
|
129
|
+
return entries.size;
|
|
130
|
+
}
|
|
131
|
+
function dispose() {
|
|
132
|
+
clearInterval(pruneTimer);
|
|
133
|
+
entries.clear();
|
|
134
|
+
}
|
|
135
|
+
return { check, recordFailure, reset, size, prune, dispose };
|
|
136
|
+
}
|