@poolzin/pool-bot 2026.1.39 → 2026.2.1
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/assets/chrome-extension/README.md +3 -3
- package/assets/chrome-extension/background.js +5 -5
- package/assets/chrome-extension/manifest.json +3 -3
- package/assets/chrome-extension/options.html +4 -4
- package/assets/chrome-extension/options.js +1 -1
- package/dist/acp/client.js +3 -3
- package/dist/acp/types.js +1 -1
- package/dist/agents/agent-paths.js +3 -3
- package/dist/agents/auth-profiles/paths.js +3 -3
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +10 -12
- package/dist/agents/cli-runner.js +2 -2
- package/dist/agents/cloudflare-ai-gateway.js +31 -0
- package/dist/agents/compaction.js +16 -2
- package/dist/agents/context-window-guard.js +13 -10
- package/dist/agents/context.js +4 -4
- package/dist/agents/docs-path.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/minimax-vlm.js +1 -1
- package/dist/agents/model-auth.js +12 -1
- package/dist/agents/model-catalog.js +4 -4
- package/dist/agents/model-selection.js +31 -4
- package/dist/agents/models-config.js +3 -3
- package/dist/agents/models-config.providers.js +147 -39
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers/openai.js +1 -1
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +9 -8
- package/dist/agents/pi-embedded-runner/model.js +63 -4
- package/dist/agents/pi-embedded-runner/run/attempt.js +27 -17
- package/dist/agents/pi-embedded-runner/run.js +203 -50
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-runner/tool-result-truncation.js +275 -0
- package/dist/agents/pi-embedded-runner/utils.js +1 -1
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-model-discovery.js +10 -0
- package/dist/agents/pi-tool-definition-adapter.js +50 -9
- package/dist/agents/pi-tools.before-tool-call.js +67 -0
- package/dist/agents/pi-tools.js +20 -10
- package/dist/agents/pi-tools.read.js +2 -2
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-file-repair.js +83 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/session-transcript-repair.js +68 -0
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills/frontmatter.js +1 -1
- package/dist/agents/skills/workspace.js +2 -2
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/system-prompt.js +28 -4
- package/dist/agents/together-models.js +127 -0
- package/dist/agents/tool-images.js +1 -1
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +25 -1
- package/dist/agents/tools/browser-tool.js +3 -3
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/image-tool.js +2 -2
- package/dist/agents/tools/memory-tool.js +93 -5
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/commands-registry.data.js +1 -1
- package/dist/auto-reply/envelope.js +50 -72
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +3 -2
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/commands-session.js +2 -2
- package/dist/auto-reply/reply/get-reply-run.js +16 -5
- package/dist/auto-reply/reply/groups.js +1 -1
- package/dist/auto-reply/reply/inbound-context.js +9 -1
- package/dist/auto-reply/reply/inbound-meta.js +130 -0
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/reply/untrusted-context.js +15 -0
- package/dist/auto-reply/status.js +1 -1
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +51 -8
- package/dist/browser/config.js +2 -11
- package/dist/browser/extension-relay.js +104 -43
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server-context.js +2 -2
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui.js +3 -3
- package/dist/channels/plugins/catalog.js +2 -2
- package/dist/channels/plugins/onboarding/imessage.js +1 -1
- package/dist/channels/plugins/onboarding/signal.js +1 -1
- package/dist/channels/plugins/onboarding/slack.js +4 -4
- package/dist/channels/plugins/onboarding/whatsapp.js +3 -3
- package/dist/channels/plugins/pairing-message.js +1 -1
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/browser-cli-extension.js +2 -2
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/docs-cli.js +1 -1
- package/dist/cli/gateway-cli/dev.js +1 -1
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +30 -20
- package/dist/cli/nodes-cli/register.canvas.js +1 -1
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/plugins-cli.js +1 -1
- package/dist/cli/run-main.js +2 -2
- package/dist/cli/security-cli.js +1 -1
- package/dist/cli/tagline.js +1 -1
- package/dist/cli/update-cli.js +173 -52
- package/dist/cli/webhooks-cli.js +5 -5
- package/dist/commands/agent.js +1 -0
- package/dist/commands/agents.commands.add.js +1 -1
- package/dist/commands/auth-choice.apply.api-providers.js +305 -17
- package/dist/commands/auth-choice.apply.js +4 -1
- package/dist/commands/auth-choice.apply.plugin-provider.js +2 -2
- package/dist/commands/auth-choice.apply.xai.js +63 -0
- package/dist/commands/auth-choice.preferred-provider.js +7 -1
- package/dist/commands/configure.wizard.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-gateway-services.js +3 -3
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/doctor-update.js +3 -3
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.probe.js +2 -2
- package/dist/commands/models/list.registry.js +4 -4
- package/dist/commands/models/list.status-command.js +44 -24
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-auth.config-core.js +366 -28
- package/dist/commands/onboard-auth.credentials.js +71 -9
- package/dist/commands/onboard-auth.js +3 -3
- package/dist/commands/onboard-auth.models.js +26 -24
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +146 -9
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/commands/status-all/report-lines.js +1 -1
- package/dist/commands/status.command.js +1 -1
- package/dist/commands/uninstall.js +3 -3
- package/dist/compat/legacy-names.js +1 -1
- package/dist/config/defaults.js +3 -2
- package/dist/config/io.js +3 -3
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +3 -3
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/types.memory.js +1 -0
- package/dist/config/version.js +4 -4
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +171 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/daemon/constants.js +7 -7
- package/dist/daemon/inspect.js +6 -6
- package/dist/daemon/systemd-unit.js +1 -1
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/live-image-probe.js +1 -66
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/openai-http.js +2 -2
- package/dist/gateway/openresponses-http.js +4 -4
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-discovery.js +2 -2
- package/dist/gateway/server-http.js +110 -12
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +7 -3
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/hooks/frontmatter.js +1 -1
- package/dist/hooks/hooks-status.js +1 -1
- package/dist/hooks/install.js +2 -2
- package/dist/hooks/loader.js +1 -1
- package/dist/hooks/workspace.js +3 -3
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/index.js +2 -2
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/bonjour.js +3 -3
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/path-env.js +3 -3
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/provider-usage.fetch.minimax.js +1 -1
- package/dist/infra/restart.js +1 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/tailscale.js +1 -1
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/macos/relay.js +2 -2
- package/dist/media/fetch.js +81 -58
- package/dist/media/input-files.js +1 -1
- package/dist/media/mime.js +4 -0
- package/dist/media/png-encode.js +74 -0
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +4 -4
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/backend-config.js +207 -0
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +29 -17
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +155 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/memory/types.js +1 -0
- package/dist/node-host/runner.js +36 -26
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/pairing/pairing-messages.js +1 -1
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/discovery.js +1 -1
- package/dist/plugins/install.js +2 -2
- package/dist/plugins/source-display.js +51 -0
- package/dist/plugins/update.js +1 -1
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/audit.js +2 -2
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/shared/text/reasoning-tags.js +52 -7
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +145 -0
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +38 -11
- package/dist/telegram/bot-message-dispatch.js +48 -15
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/test-helpers/workspace.js +11 -0
- package/dist/test-utils/channel-plugins.js +82 -0
- package/dist/test-utils/ports.js +73 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/shell-argv.js +61 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +55 -14
- package/dist/version.js +42 -5
- package/dist/web/qr-image.js +1 -61
- package/dist/wizard/onboarding.finalize.js +7 -7
- package/dist/wizard/onboarding.js +3 -3
- package/docs/RELEASE_WORKFOTS_COMPARISON.md +3 -3
- package/docs/_config.yml +2 -2
- package/docs/_layouts/default.html +9 -9
- package/docs/concepts/typebox.md +1 -1
- package/docs/docs.json +1 -1
- package/docs/northflank.mdx +7 -7
- package/docs/railway.mdx +3 -3
- package/docs/render.mdx +5 -5
- package/docs/start/lore.md +2 -2
- package/extensions/bluebubbles/index.ts +2 -2
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/bluebubbles/src/accounts.ts +8 -8
- package/extensions/bluebubbles/src/actions.test.ts +22 -22
- package/extensions/bluebubbles/src/actions.ts +5 -5
- package/extensions/bluebubbles/src/attachments.ts +2 -2
- package/extensions/bluebubbles/src/channel.ts +16 -16
- package/extensions/bluebubbles/src/chat.ts +2 -2
- package/extensions/bluebubbles/src/media-send.ts +2 -2
- package/extensions/bluebubbles/src/monitor.test.ts +46 -46
- package/extensions/bluebubbles/src/monitor.ts +5 -5
- package/extensions/bluebubbles/src/onboarding.ts +7 -7
- package/extensions/bluebubbles/src/reactions.ts +2 -2
- package/extensions/bluebubbles/src/send.ts +2 -2
- package/extensions/copilot-proxy/README.md +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/index.ts +2 -2
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/diagnostics-otel/src/service.ts +3 -3
- package/extensions/discord/index.ts +2 -2
- package/extensions/discord/package.json +1 -1
- package/extensions/google-antigravity-auth/README.md +1 -1
- package/extensions/google-antigravity-auth/index.ts +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/README.md +1 -1
- package/extensions/google-gemini-cli-auth/oauth.ts +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/index.ts +3 -3
- package/extensions/googlechat/package.json +1 -1
- package/extensions/googlechat/src/accounts.ts +8 -8
- package/extensions/googlechat/src/actions.ts +6 -6
- package/extensions/googlechat/src/channel.ts +21 -21
- package/extensions/googlechat/src/monitor.ts +8 -8
- package/extensions/googlechat/src/onboarding.ts +10 -10
- package/extensions/imessage/index.ts +2 -2
- package/extensions/imessage/package.json +1 -1
- package/extensions/line/index.ts +2 -2
- package/extensions/line/package.json +1 -1
- package/extensions/line/src/card-command.ts +2 -2
- package/extensions/line/src/channel.logout.test.ts +4 -4
- package/extensions/line/src/channel.sendPayload.test.ts +8 -8
- package/extensions/line/src/channel.ts +3 -3
- package/extensions/llm-task/README.md +3 -3
- package/extensions/llm-task/index.ts +2 -2
- package/extensions/llm-task/package.json +1 -1
- package/extensions/llm-task/src/llm-task-tool.ts +4 -4
- package/extensions/lobster/README.md +6 -6
- package/extensions/lobster/index.ts +2 -2
- package/extensions/lobster/src/lobster-tool.test.ts +4 -4
- package/extensions/lobster/src/lobster-tool.ts +2 -2
- package/extensions/matrix/index.ts +2 -2
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/client/config.ts +1 -1
- package/extensions/matrix/src/matrix/monitor/handler.ts +1 -1
- package/extensions/matrix/src/onboarding.ts +1 -1
- package/extensions/mattermost/index.ts +2 -2
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/accounts.ts +8 -8
- package/extensions/mattermost/src/mattermost/monitor-helpers.ts +5 -5
- package/extensions/mattermost/src/mattermost/monitor.ts +2 -2
- package/extensions/mattermost/src/onboarding-helpers.ts +3 -3
- package/extensions/mattermost/src/onboarding.ts +2 -2
- package/extensions/memory-core/index.ts +2 -2
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/index.ts +3 -3
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/msteams/index.ts +2 -2
- package/extensions/msteams/package.json +1 -1
- package/extensions/msteams/src/channel.directory.test.ts +2 -2
- package/extensions/msteams/src/channel.ts +2 -2
- package/extensions/msteams/src/graph-upload.ts +4 -4
- package/extensions/msteams/src/monitor-handler.ts +2 -2
- package/extensions/msteams/src/monitor.ts +2 -2
- package/extensions/msteams/src/onboarding.ts +9 -9
- package/extensions/msteams/src/reply-dispatcher.ts +2 -2
- package/extensions/msteams/src/send-context.ts +2 -2
- package/extensions/msteams/src/send.ts +4 -4
- package/extensions/nextcloud-talk/index.ts +2 -2
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nextcloud-talk/src/channel.ts +7 -7
- package/extensions/nextcloud-talk/src/inbound.ts +7 -7
- package/extensions/nextcloud-talk/src/onboarding.ts +1 -1
- package/extensions/nostr/README.md +2 -2
- package/extensions/nostr/index.ts +5 -5
- package/extensions/nostr/package.json +1 -1
- package/extensions/nostr/src/types.ts +4 -4
- package/extensions/open-prose/index.ts +2 -2
- package/extensions/qwen-portal-auth/README.md +1 -1
- package/extensions/signal/index.ts +2 -2
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/index.ts +2 -2
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/index.ts +2 -2
- package/extensions/telegram/package.json +1 -1
- package/extensions/telegram/src/channel.ts +2 -2
- package/extensions/tlon/README.md +2 -2
- package/extensions/tlon/index.ts +2 -2
- package/extensions/tlon/package.json +1 -1
- package/extensions/tlon/src/channel.ts +13 -13
- package/extensions/tlon/src/monitor/index.ts +3 -3
- package/extensions/tlon/src/onboarding.ts +3 -3
- package/extensions/tlon/src/types.ts +3 -3
- package/extensions/twitch/README.md +1 -1
- package/extensions/twitch/index.ts +2 -2
- package/extensions/twitch/package.json +1 -1
- package/extensions/twitch/src/config.ts +3 -3
- package/extensions/twitch/src/monitor.ts +3 -3
- package/extensions/twitch/src/onboarding.ts +9 -9
- package/extensions/twitch/src/outbound.test.ts +2 -2
- package/extensions/twitch/src/plugin.test.ts +2 -2
- package/extensions/twitch/src/plugin.ts +8 -8
- package/extensions/twitch/src/send.test.ts +2 -2
- package/extensions/twitch/src/send.ts +4 -4
- package/extensions/twitch/src/token.test.ts +8 -8
- package/extensions/twitch/src/token.ts +3 -3
- package/extensions/twitch/src/twitch-client.ts +3 -3
- package/extensions/twitch/src/types.ts +3 -3
- package/extensions/twitch/src/utils/markdown.ts +1 -1
- package/extensions/voice-call/README.md +3 -3
- package/extensions/voice-call/package.json +1 -1
- package/extensions/voice-call/src/core-bridge.ts +2 -2
- package/extensions/voice-call/src/response-generator.ts +1 -1
- package/extensions/whatsapp/index.ts +2 -2
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/README.md +1 -1
- package/extensions/zalo/index.ts +2 -2
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalo/src/accounts.ts +8 -8
- package/extensions/zalo/src/actions.ts +4 -4
- package/extensions/zalo/src/channel.directory.test.ts +2 -2
- package/extensions/zalo/src/channel.ts +18 -18
- package/extensions/zalo/src/monitor.ts +9 -9
- package/extensions/zalo/src/monitor.webhook.test.ts +2 -2
- package/extensions/zalo/src/onboarding.ts +24 -24
- package/extensions/zalo/src/send.ts +2 -2
- package/extensions/zalouser/README.md +2 -2
- package/extensions/zalouser/index.ts +2 -2
- package/extensions/zalouser/package.json +1 -1
- package/extensions/zalouser/src/accounts.ts +9 -9
- package/extensions/zalouser/src/channel.ts +24 -24
- package/extensions/zalouser/src/monitor.ts +4 -4
- package/extensions/zalouser/src/onboarding.ts +28 -28
- package/package.json +13 -251
- package/skills/nano-banana-pro/scripts/generate_image.py +1 -1
- package/skills/tmux/scripts/find-sessions.sh +1 -1
- package/CHANGELOG.md +0 -102
- package/README-header.png +0 -0
- package/git-hooks/pre-commit +0 -4
- package/scripts/format-staged.js +0 -148
- package/scripts/postinstall.js +0 -300
- package/scripts/setup-git-hooks.js +0 -96
|
@@ -1,14 +1,77 @@
|
|
|
1
1
|
import { fetchRemoteMedia } from "../../media/fetch.js";
|
|
2
2
|
import { saveMediaBuffer } from "../../media/store.js";
|
|
3
|
+
function normalizeHostname(hostname) {
|
|
4
|
+
const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
5
|
+
if (normalized.startsWith("[") && normalized.endsWith("]")) {
|
|
6
|
+
return normalized.slice(1, -1);
|
|
7
|
+
}
|
|
8
|
+
return normalized;
|
|
9
|
+
}
|
|
10
|
+
function isSlackHostname(hostname) {
|
|
11
|
+
const normalized = normalizeHostname(hostname);
|
|
12
|
+
if (!normalized) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
// Slack-hosted files typically come from *.slack.com and redirect to Slack CDN domains.
|
|
16
|
+
// Include a small allowlist of known Slack domains to avoid leaking tokens if a file URL
|
|
17
|
+
// is ever spoofed or mishandled.
|
|
18
|
+
const allowedSuffixes = ["slack.com", "slack-edge.com", "slack-files.com"];
|
|
19
|
+
return allowedSuffixes.some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`));
|
|
20
|
+
}
|
|
21
|
+
function assertSlackFileUrl(rawUrl) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = new URL(rawUrl);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error(`Invalid Slack file URL: ${rawUrl}`);
|
|
28
|
+
}
|
|
29
|
+
if (parsed.protocol !== "https:") {
|
|
30
|
+
throw new Error(`Refusing Slack file URL with non-HTTPS protocol: ${parsed.protocol}`);
|
|
31
|
+
}
|
|
32
|
+
if (!isSlackHostname(parsed.hostname)) {
|
|
33
|
+
throw new Error(`Refusing to send Slack token to non-Slack host "${parsed.hostname}" (url: ${rawUrl})`);
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
function resolveRequestUrl(input) {
|
|
38
|
+
if (typeof input === "string") {
|
|
39
|
+
return input;
|
|
40
|
+
}
|
|
41
|
+
if (input instanceof URL) {
|
|
42
|
+
return input.toString();
|
|
43
|
+
}
|
|
44
|
+
if ("url" in input && typeof input.url === "string") {
|
|
45
|
+
return input.url;
|
|
46
|
+
}
|
|
47
|
+
throw new Error("Unsupported fetch input: expected string, URL, or Request");
|
|
48
|
+
}
|
|
49
|
+
function createSlackMediaFetch(token) {
|
|
50
|
+
let includeAuth = true;
|
|
51
|
+
return async (input, init) => {
|
|
52
|
+
const url = resolveRequestUrl(input);
|
|
53
|
+
const { headers: initHeaders, redirect: _redirect, ...rest } = init ?? {};
|
|
54
|
+
const headers = new Headers(initHeaders);
|
|
55
|
+
if (includeAuth) {
|
|
56
|
+
includeAuth = false;
|
|
57
|
+
const parsed = assertSlackFileUrl(url);
|
|
58
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
59
|
+
return fetch(parsed.href, { ...rest, headers, redirect: "manual" });
|
|
60
|
+
}
|
|
61
|
+
headers.delete("Authorization");
|
|
62
|
+
return fetch(url, { ...rest, headers, redirect: "manual" });
|
|
63
|
+
};
|
|
64
|
+
}
|
|
3
65
|
/**
|
|
4
66
|
* Fetches a URL with Authorization header, handling cross-origin redirects.
|
|
5
67
|
* Node.js fetch strips Authorization headers on cross-origin redirects for security.
|
|
6
|
-
* Slack's
|
|
7
|
-
*
|
|
68
|
+
* Slack's file URLs redirect to CDN domains with pre-signed URLs that don't need the
|
|
69
|
+
* Authorization header, so we handle the initial auth request manually.
|
|
8
70
|
*/
|
|
9
71
|
export async function fetchWithSlackAuth(url, token) {
|
|
72
|
+
const parsed = assertSlackFileUrl(url);
|
|
10
73
|
// Initial request with auth and manual redirect handling
|
|
11
|
-
const initialRes = await fetch(
|
|
74
|
+
const initialRes = await fetch(parsed.href, {
|
|
12
75
|
headers: { Authorization: `Bearer ${token}` },
|
|
13
76
|
redirect: "manual",
|
|
14
77
|
});
|
|
@@ -22,31 +85,36 @@ export async function fetchWithSlackAuth(url, token) {
|
|
|
22
85
|
return initialRes;
|
|
23
86
|
}
|
|
24
87
|
// Resolve relative URLs against the original
|
|
25
|
-
const resolvedUrl = new URL(redirectUrl,
|
|
88
|
+
const resolvedUrl = new URL(redirectUrl, parsed.href);
|
|
89
|
+
// Only follow safe protocols (we do NOT include Authorization on redirects).
|
|
90
|
+
if (resolvedUrl.protocol !== "https:") {
|
|
91
|
+
return initialRes;
|
|
92
|
+
}
|
|
26
93
|
// Follow the redirect without the Authorization header
|
|
27
94
|
// (Slack's CDN URLs are pre-signed and don't need it)
|
|
28
|
-
return fetch(resolvedUrl, { redirect: "follow" });
|
|
95
|
+
return fetch(resolvedUrl.toString(), { redirect: "follow" });
|
|
29
96
|
}
|
|
30
97
|
export async function resolveSlackMedia(params) {
|
|
31
98
|
const files = params.files ?? [];
|
|
32
99
|
for (const file of files) {
|
|
33
100
|
const url = file.url_private_download ?? file.url_private;
|
|
34
|
-
if (!url)
|
|
101
|
+
if (!url) {
|
|
35
102
|
continue;
|
|
103
|
+
}
|
|
36
104
|
try {
|
|
37
|
-
// Note:
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return fetchWithSlackAuth(inputUrl, params.token);
|
|
42
|
-
};
|
|
105
|
+
// Note: fetchRemoteMedia calls fetchImpl(url) with the URL string today and
|
|
106
|
+
// handles size limits internally. Provide a fetcher that uses auth once, then lets
|
|
107
|
+
// the redirect chain continue without credentials.
|
|
108
|
+
const fetchImpl = createSlackMediaFetch(params.token);
|
|
43
109
|
const fetched = await fetchRemoteMedia({
|
|
44
110
|
url,
|
|
45
111
|
fetchImpl,
|
|
46
112
|
filePathHint: file.name,
|
|
113
|
+
maxBytes: params.maxBytes,
|
|
47
114
|
});
|
|
48
|
-
if (fetched.buffer.byteLength > params.maxBytes)
|
|
115
|
+
if (fetched.buffer.byteLength > params.maxBytes) {
|
|
49
116
|
continue;
|
|
117
|
+
}
|
|
50
118
|
const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType ?? file.mimetype, "inbound", params.maxBytes);
|
|
51
119
|
const label = fetched.fileName ?? file.name;
|
|
52
120
|
return {
|
|
@@ -65,8 +133,9 @@ const THREAD_STARTER_CACHE = new Map();
|
|
|
65
133
|
export async function resolveSlackThreadStarter(params) {
|
|
66
134
|
const cacheKey = `${params.channelId}:${params.threadTs}`;
|
|
67
135
|
const cached = THREAD_STARTER_CACHE.get(cacheKey);
|
|
68
|
-
if (cached)
|
|
136
|
+
if (cached) {
|
|
69
137
|
return cached;
|
|
138
|
+
}
|
|
70
139
|
try {
|
|
71
140
|
const response = (await params.client.conversations.replies({
|
|
72
141
|
channel: params.channelId,
|
|
@@ -76,8 +145,9 @@ export async function resolveSlackThreadStarter(params) {
|
|
|
76
145
|
}));
|
|
77
146
|
const message = response?.messages?.[0];
|
|
78
147
|
const text = (message?.text ?? "").trim();
|
|
79
|
-
if (!message || !text)
|
|
148
|
+
if (!message || !text) {
|
|
80
149
|
return null;
|
|
150
|
+
}
|
|
81
151
|
const starter = {
|
|
82
152
|
text,
|
|
83
153
|
userId: message.user,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
/**
|
|
5
|
+
* Check if Tailscale is installed by looking for the binary.
|
|
6
|
+
*/
|
|
7
|
+
async function isTailscaleInstalled() {
|
|
8
|
+
try {
|
|
9
|
+
const platform = os.platform();
|
|
10
|
+
const possiblePaths = [];
|
|
11
|
+
if (platform === "linux") {
|
|
12
|
+
possiblePaths.push("/usr/bin/tailscale", "/usr/local/bin/tailscale");
|
|
13
|
+
}
|
|
14
|
+
else if (platform === "darwin") {
|
|
15
|
+
possiblePaths.push("/Applications/Tailscale.app/Contents/MacOS/Tailscale", "/usr/local/bin/tailscale");
|
|
16
|
+
}
|
|
17
|
+
else if (platform === "win32") {
|
|
18
|
+
possiblePaths.push("C:\\Program Files\\Tailscale\\tailscale.exe", "C:\\ProgramData\\chocolatey\\lib\\tailscale\\tools\\tailscale.exe");
|
|
19
|
+
}
|
|
20
|
+
for (const binPath of possiblePaths) {
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(binPath);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run tailscale status command and parse output.
|
|
37
|
+
*/
|
|
38
|
+
async function getTailscaleStatus() {
|
|
39
|
+
try {
|
|
40
|
+
const platform = os.platform();
|
|
41
|
+
const binPath = platform === "win32"
|
|
42
|
+
? "tailscale.exe"
|
|
43
|
+
: platform === "darwin"
|
|
44
|
+
? "/Applications/Tailscale.app/Contents/MacOS/Tailscale"
|
|
45
|
+
: "tailscale";
|
|
46
|
+
const timeout = 5000;
|
|
47
|
+
const result = execSync(`"${binPath}" status --json`, {
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
50
|
+
timeout,
|
|
51
|
+
});
|
|
52
|
+
const status = JSON.parse(result.trim());
|
|
53
|
+
// Extract useful info from Tailscale status JSON
|
|
54
|
+
const self = status.Self || {};
|
|
55
|
+
const backendState = status.BackendState || "";
|
|
56
|
+
return {
|
|
57
|
+
installed: true,
|
|
58
|
+
running: backendState === "Running",
|
|
59
|
+
ip: self.TailscaleIPs?.[0],
|
|
60
|
+
dnsName: self.DNSName,
|
|
61
|
+
hostname: self.HostName,
|
|
62
|
+
tailnet: status.MagicsockName || status.TailnetName,
|
|
63
|
+
online: self.Online ?? false,
|
|
64
|
+
canServe: true, // If running, can serve
|
|
65
|
+
canFunnel: self.Capabilities?.map.includes("funnel") ?? false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return {
|
|
70
|
+
installed: await isTailscaleInstalled(),
|
|
71
|
+
running: false,
|
|
72
|
+
canServe: false,
|
|
73
|
+
canFunnel: false,
|
|
74
|
+
online: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Auto-detect Tailscale configuration and recommend settings.
|
|
80
|
+
*
|
|
81
|
+
* This is a SAFE detection function - it only reads status and never
|
|
82
|
+
* modifies configuration. Recommendations are purely advisory.
|
|
83
|
+
*
|
|
84
|
+
* @returns Tailscale auto-detection result
|
|
85
|
+
*/
|
|
86
|
+
export async function detectTailscaleConfig() {
|
|
87
|
+
const info = {
|
|
88
|
+
installed: await isTailscaleInstalled(),
|
|
89
|
+
running: false,
|
|
90
|
+
canServe: false,
|
|
91
|
+
canFunnel: false,
|
|
92
|
+
online: false,
|
|
93
|
+
};
|
|
94
|
+
if (!info.installed) {
|
|
95
|
+
return { detected: false };
|
|
96
|
+
}
|
|
97
|
+
// Get detailed status if installed
|
|
98
|
+
const status = await getTailscaleStatus();
|
|
99
|
+
Object.assign(info, status);
|
|
100
|
+
// Generate recommendations based on detected state
|
|
101
|
+
const recommended = info.online
|
|
102
|
+
? {
|
|
103
|
+
mode: info.canFunnel ? "funnel" : "serve",
|
|
104
|
+
bind: "tailnet",
|
|
105
|
+
port: 18789,
|
|
106
|
+
reason: info.canFunnel
|
|
107
|
+
? "Tailscale is online and supports funnel (public access)"
|
|
108
|
+
: "Tailscale is online - use serve for private tailnet access",
|
|
109
|
+
}
|
|
110
|
+
: info.running
|
|
111
|
+
? {
|
|
112
|
+
mode: "off",
|
|
113
|
+
bind: "loopback",
|
|
114
|
+
port: 18789,
|
|
115
|
+
reason: "Tailscale is installed but offline - use loopback bind",
|
|
116
|
+
}
|
|
117
|
+
: undefined;
|
|
118
|
+
return {
|
|
119
|
+
detected: true,
|
|
120
|
+
info,
|
|
121
|
+
recommended,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get a human-readable summary of Tailscale detection.
|
|
126
|
+
*/
|
|
127
|
+
export function formatTailscaleDetection(result) {
|
|
128
|
+
if (!result.detected || !result.info) {
|
|
129
|
+
return "Tailscale: Not installed";
|
|
130
|
+
}
|
|
131
|
+
const { info, recommended } = result;
|
|
132
|
+
const parts = [];
|
|
133
|
+
parts.push(`Tailscale: ${info.installed ? "Installed" : "Not installed"}`);
|
|
134
|
+
parts.push(`Status: ${info.running ? (info.online ? "Online" : "Offline") : "Not running"}`);
|
|
135
|
+
if (info.online && info.ip) {
|
|
136
|
+
parts.push(`IP: ${info.ip}`);
|
|
137
|
+
}
|
|
138
|
+
if (info.dnsName) {
|
|
139
|
+
parts.push(`DNS: ${info.dnsName}`);
|
|
140
|
+
}
|
|
141
|
+
if (recommended) {
|
|
142
|
+
parts.push(`\nRecommendation: ${recommended.mode} (${recommended.reason})`);
|
|
143
|
+
}
|
|
144
|
+
return parts.join(" | ");
|
|
145
|
+
}
|
|
@@ -1,22 +1,67 @@
|
|
|
1
1
|
import { formatLocationText } from "../../channels/location.js";
|
|
2
2
|
const TELEGRAM_GENERAL_TOPIC_ID = 1;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the thread ID for Telegram forum topics.
|
|
5
|
+
* For non-forum groups, returns undefined even if messageThreadId is present
|
|
6
|
+
* (reply threads in regular groups should not create separate sessions).
|
|
7
|
+
* For forum groups, returns the topic ID (or General topic ID=1 if unspecified).
|
|
8
|
+
*/
|
|
3
9
|
export function resolveTelegramForumThreadId(params) {
|
|
4
|
-
|
|
10
|
+
// Non-forum groups: ignore message_thread_id (reply threads are not real topics)
|
|
11
|
+
if (!params.isForum) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
// Forum groups: use the topic ID, defaulting to General topic
|
|
15
|
+
if (params.messageThreadId == null) {
|
|
5
16
|
return TELEGRAM_GENERAL_TOPIC_ID;
|
|
6
17
|
}
|
|
7
|
-
return params.messageThreadId
|
|
18
|
+
return params.messageThreadId;
|
|
19
|
+
}
|
|
20
|
+
export function resolveTelegramThreadSpec(params) {
|
|
21
|
+
if (params.isGroup) {
|
|
22
|
+
const id = resolveTelegramForumThreadId({
|
|
23
|
+
isForum: params.isForum,
|
|
24
|
+
messageThreadId: params.messageThreadId,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
id,
|
|
28
|
+
scope: params.isForum ? "forum" : "none",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (params.messageThreadId == null) {
|
|
32
|
+
return { scope: "dm" };
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
id: params.messageThreadId,
|
|
36
|
+
scope: "dm",
|
|
37
|
+
};
|
|
8
38
|
}
|
|
9
39
|
/**
|
|
10
40
|
* Build thread params for Telegram API calls (messages, media).
|
|
11
41
|
* General forum topic (id=1) must be treated like a regular supergroup send:
|
|
12
42
|
* Telegram rejects sendMessage/sendMedia with message_thread_id=1 ("thread not found").
|
|
43
|
+
*
|
|
44
|
+
* Accepts either a TelegramThreadSpec (preferred) or a raw messageThreadId number
|
|
45
|
+
* for backward compatibility with callers that haven't migrated yet.
|
|
13
46
|
*/
|
|
14
|
-
export function buildTelegramThreadParams(
|
|
15
|
-
if (
|
|
47
|
+
export function buildTelegramThreadParams(threadOrId) {
|
|
48
|
+
if (threadOrId == null) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
// Legacy call: raw number
|
|
52
|
+
if (typeof threadOrId === "number") {
|
|
53
|
+
const normalized = Math.trunc(threadOrId);
|
|
54
|
+
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return { message_thread_id: normalized };
|
|
58
|
+
}
|
|
59
|
+
// New call: TelegramThreadSpec
|
|
60
|
+
if (!threadOrId.id) {
|
|
16
61
|
return undefined;
|
|
17
62
|
}
|
|
18
|
-
const normalized = Math.trunc(
|
|
19
|
-
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
|
|
63
|
+
const normalized = Math.trunc(threadOrId.id);
|
|
64
|
+
if (normalized === TELEGRAM_GENERAL_TOPIC_ID && threadOrId.scope === "forum") {
|
|
20
65
|
return undefined;
|
|
21
66
|
}
|
|
22
67
|
return { message_thread_id: normalized };
|
|
@@ -43,6 +88,19 @@ export function buildTelegramGroupPeerId(chatId, messageThreadId) {
|
|
|
43
88
|
export function buildTelegramGroupFrom(chatId, messageThreadId) {
|
|
44
89
|
return `telegram:group:${buildTelegramGroupPeerId(chatId, messageThreadId)}`;
|
|
45
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Build parentPeer for forum topic binding inheritance.
|
|
93
|
+
* When a message comes from a forum topic, the peer ID includes the topic suffix
|
|
94
|
+
* (e.g., `-1001234567890:topic:99`). To allow bindings configured for the base
|
|
95
|
+
* group ID to match, we provide the parent group as `parentPeer` so the routing
|
|
96
|
+
* layer can fall back to it when the exact peer doesn't match.
|
|
97
|
+
*/
|
|
98
|
+
export function buildTelegramParentPeer(params) {
|
|
99
|
+
if (!params.isGroup || params.resolvedThreadId == null) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return { kind: "group", id: String(params.chatId) };
|
|
103
|
+
}
|
|
46
104
|
export function buildSenderName(msg) {
|
|
47
105
|
const name = [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ").trim() ||
|
|
48
106
|
msg.from?.username;
|
|
@@ -93,7 +151,7 @@ export function expandTextLinks(text, entities) {
|
|
|
93
151
|
return text;
|
|
94
152
|
const textLinks = entities
|
|
95
153
|
.filter((entity) => entity.type === "text_link" && Boolean(entity.url))
|
|
96
|
-
.
|
|
154
|
+
.toSorted((a, b) => b.offset - a.offset);
|
|
97
155
|
if (textLinks.length === 0)
|
|
98
156
|
return text;
|
|
99
157
|
let result = text;
|
|
@@ -115,33 +173,53 @@ export function resolveTelegramReplyId(raw) {
|
|
|
115
173
|
}
|
|
116
174
|
export function describeReplyTarget(msg) {
|
|
117
175
|
const reply = msg.reply_to_message;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
body = "<media:audio>";
|
|
129
|
-
else if (reply.document)
|
|
130
|
-
body = "<media:document>";
|
|
131
|
-
else {
|
|
132
|
-
const locationData = extractTelegramLocation(reply);
|
|
133
|
-
if (locationData)
|
|
134
|
-
body = formatLocationText(locationData);
|
|
176
|
+
const externalReply = msg
|
|
177
|
+
.external_reply;
|
|
178
|
+
const quoteText = msg.quote?.text ??
|
|
179
|
+
externalReply?.quote?.text;
|
|
180
|
+
let body = "";
|
|
181
|
+
let kind = "reply";
|
|
182
|
+
if (typeof quoteText === "string") {
|
|
183
|
+
body = quoteText.trim();
|
|
184
|
+
if (body) {
|
|
185
|
+
kind = "quote";
|
|
135
186
|
}
|
|
136
187
|
}
|
|
137
|
-
|
|
188
|
+
const replyLike = reply ?? externalReply;
|
|
189
|
+
if (!body && replyLike) {
|
|
190
|
+
const replyBody = (replyLike.text ?? replyLike.caption ?? "").trim();
|
|
191
|
+
body = replyBody;
|
|
192
|
+
if (!body) {
|
|
193
|
+
if (replyLike.photo) {
|
|
194
|
+
body = "<media:image>";
|
|
195
|
+
}
|
|
196
|
+
else if (replyLike.video) {
|
|
197
|
+
body = "<media:video>";
|
|
198
|
+
}
|
|
199
|
+
else if (replyLike.audio || replyLike.voice) {
|
|
200
|
+
body = "<media:audio>";
|
|
201
|
+
}
|
|
202
|
+
else if (replyLike.document) {
|
|
203
|
+
body = "<media:document>";
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const locationData = extractTelegramLocation(replyLike);
|
|
207
|
+
if (locationData) {
|
|
208
|
+
body = formatLocationText(locationData);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!body) {
|
|
138
214
|
return null;
|
|
139
|
-
|
|
140
|
-
const
|
|
215
|
+
}
|
|
216
|
+
const sender = replyLike ? buildSenderName(replyLike) : undefined;
|
|
217
|
+
const senderLabel = sender ?? "unknown sender";
|
|
141
218
|
return {
|
|
142
|
-
id:
|
|
219
|
+
id: replyLike?.message_id ? String(replyLike.message_id) : undefined,
|
|
143
220
|
sender: senderLabel,
|
|
144
221
|
body,
|
|
222
|
+
kind,
|
|
145
223
|
};
|
|
146
224
|
}
|
|
147
225
|
function normalizeForwardedUserLabel(user) {
|
|
@@ -191,6 +269,7 @@ function buildForwardedContextFromChat(params) {
|
|
|
191
269
|
return null;
|
|
192
270
|
const signature = params.signature?.trim() || undefined;
|
|
193
271
|
const from = signature ? `${display} (${signature})` : display;
|
|
272
|
+
const chatType = (params.chat.type?.trim() || undefined);
|
|
194
273
|
return {
|
|
195
274
|
from,
|
|
196
275
|
date: params.date,
|
|
@@ -199,6 +278,8 @@ function buildForwardedContextFromChat(params) {
|
|
|
199
278
|
fromUsername: username,
|
|
200
279
|
fromTitle: title,
|
|
201
280
|
fromSignature: signature,
|
|
281
|
+
fromChatType: chatType,
|
|
282
|
+
fromMessageId: params.messageId,
|
|
202
283
|
};
|
|
203
284
|
}
|
|
204
285
|
function resolveForwardOrigin(origin, signature) {
|
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
|
3
3
|
import { createInboundDebouncer, resolveInboundDebounceMs, } from "../auto-reply/inbound-debounce.js";
|
|
4
4
|
import { buildCommandsPaginationKeyboard } from "../auto-reply/reply/commands-info.js";
|
|
5
|
+
import { buildModelsProviderData } from "../auto-reply/reply/commands-models.js";
|
|
6
|
+
import { resolveStoredModelOverride } from "../auto-reply/reply/model-selection.js";
|
|
5
7
|
import { buildCommandsMessagePaginated } from "../auto-reply/status.js";
|
|
6
8
|
import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
|
|
7
9
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
8
10
|
import { loadConfig } from "../config/config.js";
|
|
11
|
+
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
|
12
|
+
import { resolveAgentRoute } from "../routing/resolve-route.js";
|
|
13
|
+
import { resolveThreadSessionKeys } from "../routing/session-key.js";
|
|
9
14
|
import { writeConfigFile } from "../config/io.js";
|
|
10
15
|
import { danger, logVerbose, warn } from "../globals.js";
|
|
11
16
|
import { resolveMedia } from "./bot/delivery.js";
|
|
12
17
|
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
|
13
|
-
import { resolveTelegramForumThreadId } from "./bot/helpers.js";
|
|
18
|
+
import { buildTelegramGroupPeerId, buildTelegramParentPeer, resolveTelegramForumThreadId, } from "./bot/helpers.js";
|
|
14
19
|
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
|
15
20
|
import { MEDIA_GROUP_TIMEOUT_MS } from "./bot-updates.js";
|
|
16
21
|
import { migrateTelegramGroupConfig } from "./group-migration.js";
|
|
17
22
|
import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js";
|
|
18
23
|
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
|
19
24
|
import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js";
|
|
25
|
+
import { buildModelsKeyboard, buildProviderKeyboard, calculateTotalPages, getModelsPageSize, parseModelCallbackData, } from "./model-buttons.js";
|
|
20
26
|
import { buildInlineKeyboard } from "./send.js";
|
|
21
27
|
export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, mediaMaxBytes, telegramCfg, groupAllowFrom, resolveGroupPolicy, resolveTelegramGroupConfig, shouldSkipUpdate, processMessage, logger, }) => {
|
|
22
28
|
const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000;
|
|
@@ -72,6 +78,57 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
|
|
|
72
78
|
runtime.error?.(danger(`telegram debounce flush failed: ${String(err)}`));
|
|
73
79
|
},
|
|
74
80
|
});
|
|
81
|
+
const resolveTelegramSessionModel = (params) => {
|
|
82
|
+
const resolvedThreadId = params.resolvedThreadId ??
|
|
83
|
+
resolveTelegramForumThreadId({
|
|
84
|
+
isForum: params.isForum,
|
|
85
|
+
messageThreadId: params.messageThreadId,
|
|
86
|
+
});
|
|
87
|
+
const peerId = params.isGroup
|
|
88
|
+
? buildTelegramGroupPeerId(params.chatId, resolvedThreadId)
|
|
89
|
+
: String(params.chatId);
|
|
90
|
+
const parentPeer = buildTelegramParentPeer({
|
|
91
|
+
isGroup: params.isGroup,
|
|
92
|
+
resolvedThreadId,
|
|
93
|
+
chatId: params.chatId,
|
|
94
|
+
});
|
|
95
|
+
const route = resolveAgentRoute({
|
|
96
|
+
cfg,
|
|
97
|
+
channel: "telegram",
|
|
98
|
+
accountId,
|
|
99
|
+
peer: {
|
|
100
|
+
kind: params.isGroup ? "group" : "dm",
|
|
101
|
+
id: peerId,
|
|
102
|
+
},
|
|
103
|
+
parentPeer,
|
|
104
|
+
});
|
|
105
|
+
const baseSessionKey = route.sessionKey;
|
|
106
|
+
const dmThreadId = !params.isGroup ? params.messageThreadId : undefined;
|
|
107
|
+
const threadKeys = dmThreadId != null
|
|
108
|
+
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
|
|
109
|
+
: null;
|
|
110
|
+
const sessionKey = threadKeys?.sessionKey ?? baseSessionKey;
|
|
111
|
+
const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
|
|
112
|
+
const store = loadSessionStore(storePath);
|
|
113
|
+
const entry = store[sessionKey];
|
|
114
|
+
const storedOverride = resolveStoredModelOverride({
|
|
115
|
+
sessionEntry: entry,
|
|
116
|
+
sessionStore: store,
|
|
117
|
+
sessionKey,
|
|
118
|
+
});
|
|
119
|
+
if (storedOverride) {
|
|
120
|
+
return storedOverride.provider
|
|
121
|
+
? `${storedOverride.provider}/${storedOverride.model}`
|
|
122
|
+
: storedOverride.model;
|
|
123
|
+
}
|
|
124
|
+
const provider = entry?.modelProvider?.trim();
|
|
125
|
+
const model = entry?.model?.trim();
|
|
126
|
+
if (provider && model) {
|
|
127
|
+
return `${provider}/${model}`;
|
|
128
|
+
}
|
|
129
|
+
const modelCfg = cfg.agents?.defaults?.model;
|
|
130
|
+
return typeof modelCfg === "string" ? modelCfg : modelCfg?.primary;
|
|
131
|
+
};
|
|
75
132
|
const processMediaGroup = async (entry) => {
|
|
76
133
|
try {
|
|
77
134
|
entry.messages.sort((a, b) => a.msg.message_id - b.msg.message_id);
|
|
@@ -205,7 +262,7 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
|
|
|
205
262
|
}
|
|
206
263
|
}
|
|
207
264
|
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
208
|
-
const groupPolicy = telegramCfg.groupPolicy
|
|
265
|
+
const groupPolicy = firstDefined(topicConfig?.groupPolicy, groupConfig?.groupPolicy, telegramCfg.groupPolicy, defaultGroupPolicy, "open");
|
|
209
266
|
if (groupPolicy === "disabled") {
|
|
210
267
|
logVerbose(`Blocked telegram group message (groupPolicy: disabled)`);
|
|
211
268
|
return;
|
|
@@ -293,6 +350,90 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
|
|
|
293
350
|
}
|
|
294
351
|
return;
|
|
295
352
|
}
|
|
353
|
+
// Model selection callback handler (mdl_prov, mdl_list_*, mdl_sel_*, mdl_back)
|
|
354
|
+
const modelCallback = parseModelCallbackData(data);
|
|
355
|
+
if (modelCallback) {
|
|
356
|
+
const modelData = await buildModelsProviderData(cfg);
|
|
357
|
+
const { byProvider, providers } = modelData;
|
|
358
|
+
const editMessageWithButtons = async (text, buttons) => {
|
|
359
|
+
const keyboard = buildInlineKeyboard(buttons);
|
|
360
|
+
try {
|
|
361
|
+
await bot.api.editMessageText(callbackMessage.chat.id, callbackMessage.message_id, text, keyboard ? { reply_markup: keyboard } : undefined);
|
|
362
|
+
}
|
|
363
|
+
catch (editErr) {
|
|
364
|
+
const errStr = String(editErr);
|
|
365
|
+
if (!errStr.includes("message is not modified")) {
|
|
366
|
+
throw editErr;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
if (modelCallback.type === "providers" || modelCallback.type === "back") {
|
|
371
|
+
if (providers.length === 0) {
|
|
372
|
+
await editMessageWithButtons("No providers available.", []);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const providerInfos = providers.map((p) => ({
|
|
376
|
+
id: p,
|
|
377
|
+
count: byProvider.get(p)?.size ?? 0,
|
|
378
|
+
}));
|
|
379
|
+
const buttons = buildProviderKeyboard(providerInfos);
|
|
380
|
+
await editMessageWithButtons("Select a provider:", buttons);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (modelCallback.type === "list") {
|
|
384
|
+
const { provider, page } = modelCallback;
|
|
385
|
+
const modelSet = byProvider.get(provider);
|
|
386
|
+
if (!modelSet || modelSet.size === 0) {
|
|
387
|
+
const providerInfos = providers.map((p) => ({
|
|
388
|
+
id: p,
|
|
389
|
+
count: byProvider.get(p)?.size ?? 0,
|
|
390
|
+
}));
|
|
391
|
+
const buttons = buildProviderKeyboard(providerInfos);
|
|
392
|
+
await editMessageWithButtons(`Unknown provider: ${provider}\n\nSelect a provider:`, buttons);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const models = [...modelSet].toSorted();
|
|
396
|
+
const pageSize = getModelsPageSize();
|
|
397
|
+
const totalPages = calculateTotalPages(models.length, pageSize);
|
|
398
|
+
const safePage = Math.max(1, Math.min(page, totalPages));
|
|
399
|
+
const currentModel = resolveTelegramSessionModel({
|
|
400
|
+
chatId,
|
|
401
|
+
isGroup,
|
|
402
|
+
isForum,
|
|
403
|
+
messageThreadId,
|
|
404
|
+
resolvedThreadId,
|
|
405
|
+
});
|
|
406
|
+
const buttons = buildModelsKeyboard({
|
|
407
|
+
provider,
|
|
408
|
+
models,
|
|
409
|
+
currentModel,
|
|
410
|
+
currentPage: safePage,
|
|
411
|
+
totalPages,
|
|
412
|
+
pageSize,
|
|
413
|
+
});
|
|
414
|
+
const text = `Models (${provider}) — ${models.length} available`;
|
|
415
|
+
await editMessageWithButtons(text, buttons);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (modelCallback.type === "select") {
|
|
419
|
+
const { provider, model } = modelCallback;
|
|
420
|
+
const syntheticMessage = {
|
|
421
|
+
...callbackMessage,
|
|
422
|
+
from: callback.from,
|
|
423
|
+
text: `/model ${provider}/${model}`,
|
|
424
|
+
caption: undefined,
|
|
425
|
+
caption_entities: undefined,
|
|
426
|
+
entities: undefined,
|
|
427
|
+
};
|
|
428
|
+
const getFile = typeof ctx.getFile === "function" ? ctx.getFile.bind(ctx) : async () => ({});
|
|
429
|
+
await processMessage({ message: syntheticMessage, me: ctx.me, getFile }, [], storeAllowFrom, {
|
|
430
|
+
forceWasMentioned: true,
|
|
431
|
+
messageIdOverride: callback.id,
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
296
437
|
const syntheticMessage = {
|
|
297
438
|
...callbackMessage,
|
|
298
439
|
from: callback.from,
|
|
@@ -403,7 +544,7 @@ export const registerTelegramHandlers = ({ cfg, accountId, bot, opts, runtime, m
|
|
|
403
544
|
// - "disabled": block all group messages entirely
|
|
404
545
|
// - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
|
|
405
546
|
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
406
|
-
const groupPolicy = telegramCfg.groupPolicy
|
|
547
|
+
const groupPolicy = firstDefined(topicConfig?.groupPolicy, groupConfig?.groupPolicy, telegramCfg.groupPolicy, defaultGroupPolicy, "open");
|
|
407
548
|
if (groupPolicy === "disabled") {
|
|
408
549
|
logVerbose(`Blocked telegram group message (groupPolicy: disabled)`);
|
|
409
550
|
return;
|