@poolzin/pool-bot 2026.2.20 → 2026.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-auth.js +12 -0
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-fallback.js +24 -0
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -80
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/provider/config-loader.js +76 -0
- package/dist/agents/provider/index.js +15 -0
- package/dist/agents/provider/integration.js +136 -0
- package/dist/agents/provider/models-dev.js +129 -0
- package/dist/agents/provider/rate-limits.js +458 -0
- package/dist/agents/provider/request-monitor.js +449 -0
- package/dist/agents/provider/session-binding.js +376 -0
- package/dist/agents/provider/token-pool.js +541 -0
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/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/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- 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/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- 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
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/plcode-controller/SKILL.md +156 -0
- package/skills/plcode-controller/assets/operator-prompts.md +65 -0
- package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
- package/skills/plcode-controller/references/failure-handling.md +60 -0
- package/skills/plcode-controller/references/model-selection.md +57 -0
- package/skills/plcode-controller/references/plan-vs-build.md +52 -0
- package/skills/plcode-controller/references/question-handling.md +40 -0
- package/skills/plcode-controller/references/session-management.md +63 -0
- package/skills/plcode-controller/references/workflow.md +35 -0
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { isNumericTelegramUserId, normalizeTelegramAllowFromEntry, } from "../channels/telegram/allow-from.js";
|
|
3
4
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
4
5
|
import { PoolBotSchema, CONFIG_PATH, migrateLegacyConfig, readConfigFileSnapshot, } from "../config/config.js";
|
|
5
6
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
|
7
|
+
import { listTelegramAccountIds, resolveTelegramAccount } from "../telegram/accounts.js";
|
|
6
8
|
import { note } from "../terminal/note.js";
|
|
7
9
|
import { isRecord, resolveHomeDir } from "../utils.js";
|
|
8
10
|
import { normalizeLegacyConfigValues } from "./doctor-legacy-config.js";
|
|
@@ -14,8 +16,9 @@ function isUnrecognizedKeysIssue(issue) {
|
|
|
14
16
|
return issue.code === "unrecognized_keys";
|
|
15
17
|
}
|
|
16
18
|
function formatPath(parts) {
|
|
17
|
-
if (parts.length === 0)
|
|
19
|
+
if (parts.length === 0) {
|
|
18
20
|
return "<root>";
|
|
21
|
+
}
|
|
19
22
|
let out = "";
|
|
20
23
|
for (const part of parts) {
|
|
21
24
|
if (typeof part === "number") {
|
|
@@ -30,18 +33,22 @@ function resolvePathTarget(root, path) {
|
|
|
30
33
|
let current = root;
|
|
31
34
|
for (const part of path) {
|
|
32
35
|
if (typeof part === "number") {
|
|
33
|
-
if (!Array.isArray(current))
|
|
36
|
+
if (!Array.isArray(current)) {
|
|
34
37
|
return null;
|
|
35
|
-
|
|
38
|
+
}
|
|
39
|
+
if (part < 0 || part >= current.length) {
|
|
36
40
|
return null;
|
|
41
|
+
}
|
|
37
42
|
current = current[part];
|
|
38
43
|
continue;
|
|
39
44
|
}
|
|
40
|
-
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
45
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
41
46
|
return null;
|
|
47
|
+
}
|
|
42
48
|
const record = current;
|
|
43
|
-
if (!(part in record))
|
|
49
|
+
if (!(part in record)) {
|
|
44
50
|
return null;
|
|
51
|
+
}
|
|
45
52
|
current = record[part];
|
|
46
53
|
}
|
|
47
54
|
return current;
|
|
@@ -54,18 +61,22 @@ function stripUnknownConfigKeys(config) {
|
|
|
54
61
|
const next = structuredClone(config);
|
|
55
62
|
const removed = [];
|
|
56
63
|
for (const issue of parsed.error.issues) {
|
|
57
|
-
if (!isUnrecognizedKeysIssue(issue))
|
|
64
|
+
if (!isUnrecognizedKeysIssue(issue)) {
|
|
58
65
|
continue;
|
|
66
|
+
}
|
|
59
67
|
const path = normalizeIssuePath(issue.path);
|
|
60
68
|
const target = resolvePathTarget(next, path);
|
|
61
|
-
if (!target || typeof target !== "object" || Array.isArray(target))
|
|
69
|
+
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
62
70
|
continue;
|
|
71
|
+
}
|
|
63
72
|
const record = target;
|
|
64
73
|
for (const key of issue.keys) {
|
|
65
|
-
if (typeof key !== "string")
|
|
74
|
+
if (typeof key !== "string") {
|
|
66
75
|
continue;
|
|
67
|
-
|
|
76
|
+
}
|
|
77
|
+
if (!(key in record)) {
|
|
68
78
|
continue;
|
|
79
|
+
}
|
|
69
80
|
delete record[key];
|
|
70
81
|
removed.push(formatPath([...path, key]));
|
|
71
82
|
}
|
|
@@ -74,16 +85,20 @@ function stripUnknownConfigKeys(config) {
|
|
|
74
85
|
}
|
|
75
86
|
function noteOpencodeProviderOverrides(cfg) {
|
|
76
87
|
const providers = cfg.models?.providers;
|
|
77
|
-
if (!providers)
|
|
88
|
+
if (!providers) {
|
|
78
89
|
return;
|
|
90
|
+
}
|
|
79
91
|
// 2026-01-10: warn when OpenCode Zen overrides mask built-in routing/costs (8a194b4abc360c6098f157956bb9322576b44d51, 2d105d16f8a099276114173836d46b46cdfbdbae).
|
|
80
92
|
const overrides = [];
|
|
81
|
-
if (providers.opencode)
|
|
93
|
+
if (providers.opencode) {
|
|
82
94
|
overrides.push("opencode");
|
|
83
|
-
|
|
95
|
+
}
|
|
96
|
+
if (providers["opencode-zen"]) {
|
|
84
97
|
overrides.push("opencode-zen");
|
|
85
|
-
|
|
98
|
+
}
|
|
99
|
+
if (overrides.length === 0) {
|
|
86
100
|
return;
|
|
101
|
+
}
|
|
87
102
|
const lines = overrides.flatMap((id) => {
|
|
88
103
|
const providerEntry = providers[id];
|
|
89
104
|
const api = isRecord(providerEntry) && typeof providerEntry.api === "string"
|
|
@@ -97,6 +112,458 @@ function noteOpencodeProviderOverrides(cfg) {
|
|
|
97
112
|
lines.push("- Remove these entries to restore per-model API routing + costs (then re-run onboarding if needed).");
|
|
98
113
|
note(lines.join("\n"), "OpenCode Zen");
|
|
99
114
|
}
|
|
115
|
+
function noteIncludeConfinementWarning(snapshot) {
|
|
116
|
+
const issues = snapshot.issues ?? [];
|
|
117
|
+
const includeIssue = issues.find((issue) => issue.message.includes("Include path escapes config directory") ||
|
|
118
|
+
issue.message.includes("Include path resolves outside config directory"));
|
|
119
|
+
if (!includeIssue) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const configRoot = path.dirname(snapshot.path ?? CONFIG_PATH);
|
|
123
|
+
note([
|
|
124
|
+
`- $include paths must stay under: ${configRoot}`,
|
|
125
|
+
'- Move shared include files under that directory and update to relative paths like "./shared/common.json".',
|
|
126
|
+
`- Error: ${includeIssue.message}`,
|
|
127
|
+
].join("\n"), "Doctor warnings");
|
|
128
|
+
}
|
|
129
|
+
function asObjectRecord(value) {
|
|
130
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
function collectTelegramAccountScopes(cfg) {
|
|
136
|
+
const scopes = [];
|
|
137
|
+
const telegram = asObjectRecord(cfg.channels?.telegram);
|
|
138
|
+
if (!telegram) {
|
|
139
|
+
return scopes;
|
|
140
|
+
}
|
|
141
|
+
scopes.push({ prefix: "channels.telegram", account: telegram });
|
|
142
|
+
const accounts = asObjectRecord(telegram.accounts);
|
|
143
|
+
if (!accounts) {
|
|
144
|
+
return scopes;
|
|
145
|
+
}
|
|
146
|
+
for (const key of Object.keys(accounts)) {
|
|
147
|
+
const account = asObjectRecord(accounts[key]);
|
|
148
|
+
if (!account) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
scopes.push({ prefix: `channels.telegram.accounts.${key}`, account });
|
|
152
|
+
}
|
|
153
|
+
return scopes;
|
|
154
|
+
}
|
|
155
|
+
function collectTelegramAllowFromLists(prefix, account) {
|
|
156
|
+
const refs = [
|
|
157
|
+
{ pathLabel: `${prefix}.allowFrom`, holder: account, key: "allowFrom" },
|
|
158
|
+
{ pathLabel: `${prefix}.groupAllowFrom`, holder: account, key: "groupAllowFrom" },
|
|
159
|
+
];
|
|
160
|
+
const groups = asObjectRecord(account.groups);
|
|
161
|
+
if (!groups) {
|
|
162
|
+
return refs;
|
|
163
|
+
}
|
|
164
|
+
for (const groupId of Object.keys(groups)) {
|
|
165
|
+
const group = asObjectRecord(groups[groupId]);
|
|
166
|
+
if (!group) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
refs.push({
|
|
170
|
+
pathLabel: `${prefix}.groups.${groupId}.allowFrom`,
|
|
171
|
+
holder: group,
|
|
172
|
+
key: "allowFrom",
|
|
173
|
+
});
|
|
174
|
+
const topics = asObjectRecord(group.topics);
|
|
175
|
+
if (!topics) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
for (const topicId of Object.keys(topics)) {
|
|
179
|
+
const topic = asObjectRecord(topics[topicId]);
|
|
180
|
+
if (!topic) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
refs.push({
|
|
184
|
+
pathLabel: `${prefix}.groups.${groupId}.topics.${topicId}.allowFrom`,
|
|
185
|
+
holder: topic,
|
|
186
|
+
key: "allowFrom",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return refs;
|
|
191
|
+
}
|
|
192
|
+
function scanTelegramAllowFromUsernameEntries(cfg) {
|
|
193
|
+
const hits = [];
|
|
194
|
+
const scanList = (pathLabel, list) => {
|
|
195
|
+
if (!Array.isArray(list)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (const entry of list) {
|
|
199
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
200
|
+
if (!normalized || normalized === "*") {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (isNumericTelegramUserId(normalized)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
hits.push({ path: pathLabel, entry: String(entry).trim() });
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
for (const scope of collectTelegramAccountScopes(cfg)) {
|
|
210
|
+
for (const ref of collectTelegramAllowFromLists(scope.prefix, scope.account)) {
|
|
211
|
+
scanList(ref.pathLabel, ref.holder[ref.key]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return hits;
|
|
215
|
+
}
|
|
216
|
+
async function maybeRepairTelegramAllowFromUsernames(cfg) {
|
|
217
|
+
const hits = scanTelegramAllowFromUsernameEntries(cfg);
|
|
218
|
+
if (hits.length === 0) {
|
|
219
|
+
return { config: cfg, changes: [] };
|
|
220
|
+
}
|
|
221
|
+
const tokens = Array.from(new Set(listTelegramAccountIds(cfg)
|
|
222
|
+
.map((accountId) => resolveTelegramAccount({ cfg, accountId }))
|
|
223
|
+
.map((account) => (account.tokenSource === "none" ? "" : account.token))
|
|
224
|
+
.map((token) => token.trim())
|
|
225
|
+
.filter(Boolean)));
|
|
226
|
+
if (tokens.length === 0) {
|
|
227
|
+
return {
|
|
228
|
+
config: cfg,
|
|
229
|
+
changes: [
|
|
230
|
+
`- Telegram allowFrom contains @username entries, but no Telegram bot token is configured; cannot auto-resolve (run onboarding or replace with numeric sender IDs).`,
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const resolveUserId = async (raw) => {
|
|
235
|
+
const trimmed = raw.trim();
|
|
236
|
+
if (!trimmed) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const stripped = normalizeTelegramAllowFromEntry(trimmed);
|
|
240
|
+
if (!stripped || stripped === "*") {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
if (isNumericTelegramUserId(stripped)) {
|
|
244
|
+
return stripped;
|
|
245
|
+
}
|
|
246
|
+
if (/\s/.test(stripped)) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const username = stripped.startsWith("@") ? stripped : `@${stripped}`;
|
|
250
|
+
for (const token of tokens) {
|
|
251
|
+
const controller = new AbortController();
|
|
252
|
+
const timeout = setTimeout(() => controller.abort(), 4000);
|
|
253
|
+
try {
|
|
254
|
+
const url = `https://api.telegram.org/bot${token}/getChat?chat_id=${encodeURIComponent(username)}`;
|
|
255
|
+
const res = await fetch(url, { signal: controller.signal }).catch(() => null);
|
|
256
|
+
if (!res || !res.ok) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const data = (await res.json().catch(() => null));
|
|
260
|
+
const id = data?.ok ? data?.result?.id : undefined;
|
|
261
|
+
if (typeof id === "number" || typeof id === "string") {
|
|
262
|
+
return String(id);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// ignore and try next token
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
clearTimeout(timeout);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
};
|
|
274
|
+
const changes = [];
|
|
275
|
+
const next = structuredClone(cfg);
|
|
276
|
+
const repairList = async (pathLabel, holder, key) => {
|
|
277
|
+
const raw = holder[key];
|
|
278
|
+
if (!Array.isArray(raw)) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const out = [];
|
|
282
|
+
const replaced = [];
|
|
283
|
+
for (const entry of raw) {
|
|
284
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
285
|
+
if (!normalized) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (normalized === "*") {
|
|
289
|
+
out.push("*");
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (isNumericTelegramUserId(normalized)) {
|
|
293
|
+
out.push(normalized);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const resolved = await resolveUserId(String(entry));
|
|
297
|
+
if (resolved) {
|
|
298
|
+
out.push(resolved);
|
|
299
|
+
replaced.push({ from: String(entry).trim(), to: resolved });
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
out.push(String(entry).trim());
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const deduped = [];
|
|
306
|
+
const seen = new Set();
|
|
307
|
+
for (const entry of out) {
|
|
308
|
+
const k = String(entry).trim();
|
|
309
|
+
if (!k || seen.has(k)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
seen.add(k);
|
|
313
|
+
deduped.push(entry);
|
|
314
|
+
}
|
|
315
|
+
holder[key] = deduped;
|
|
316
|
+
if (replaced.length > 0) {
|
|
317
|
+
for (const rep of replaced.slice(0, 5)) {
|
|
318
|
+
changes.push(`- ${pathLabel}: resolved ${rep.from} -> ${rep.to}`);
|
|
319
|
+
}
|
|
320
|
+
if (replaced.length > 5) {
|
|
321
|
+
changes.push(`- ${pathLabel}: resolved ${replaced.length - 5} more @username entries`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const repairAccount = async (prefix, account) => {
|
|
326
|
+
for (const ref of collectTelegramAllowFromLists(prefix, account)) {
|
|
327
|
+
await repairList(ref.pathLabel, ref.holder, ref.key);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
for (const scope of collectTelegramAccountScopes(next)) {
|
|
331
|
+
await repairAccount(scope.prefix, scope.account);
|
|
332
|
+
}
|
|
333
|
+
if (changes.length === 0) {
|
|
334
|
+
return { config: cfg, changes: [] };
|
|
335
|
+
}
|
|
336
|
+
return { config: next, changes };
|
|
337
|
+
}
|
|
338
|
+
function collectDiscordAccountScopes(cfg) {
|
|
339
|
+
const scopes = [];
|
|
340
|
+
const discord = asObjectRecord(cfg.channels?.discord);
|
|
341
|
+
if (!discord) {
|
|
342
|
+
return scopes;
|
|
343
|
+
}
|
|
344
|
+
scopes.push({ prefix: "channels.discord", account: discord });
|
|
345
|
+
const accounts = asObjectRecord(discord.accounts);
|
|
346
|
+
if (!accounts) {
|
|
347
|
+
return scopes;
|
|
348
|
+
}
|
|
349
|
+
for (const key of Object.keys(accounts)) {
|
|
350
|
+
const account = asObjectRecord(accounts[key]);
|
|
351
|
+
if (!account) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
scopes.push({ prefix: `channels.discord.accounts.${key}`, account });
|
|
355
|
+
}
|
|
356
|
+
return scopes;
|
|
357
|
+
}
|
|
358
|
+
function collectDiscordIdLists(prefix, account) {
|
|
359
|
+
const refs = [
|
|
360
|
+
{ pathLabel: `${prefix}.allowFrom`, holder: account, key: "allowFrom" },
|
|
361
|
+
];
|
|
362
|
+
const dm = asObjectRecord(account.dm);
|
|
363
|
+
if (dm) {
|
|
364
|
+
refs.push({ pathLabel: `${prefix}.dm.allowFrom`, holder: dm, key: "allowFrom" });
|
|
365
|
+
refs.push({ pathLabel: `${prefix}.dm.groupChannels`, holder: dm, key: "groupChannels" });
|
|
366
|
+
}
|
|
367
|
+
const execApprovals = asObjectRecord(account.execApprovals);
|
|
368
|
+
if (execApprovals) {
|
|
369
|
+
refs.push({
|
|
370
|
+
pathLabel: `${prefix}.execApprovals.approvers`,
|
|
371
|
+
holder: execApprovals,
|
|
372
|
+
key: "approvers",
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const guilds = asObjectRecord(account.guilds);
|
|
376
|
+
if (!guilds) {
|
|
377
|
+
return refs;
|
|
378
|
+
}
|
|
379
|
+
for (const guildId of Object.keys(guilds)) {
|
|
380
|
+
const guild = asObjectRecord(guilds[guildId]);
|
|
381
|
+
if (!guild) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
refs.push({ pathLabel: `${prefix}.guilds.${guildId}.users`, holder: guild, key: "users" });
|
|
385
|
+
refs.push({ pathLabel: `${prefix}.guilds.${guildId}.roles`, holder: guild, key: "roles" });
|
|
386
|
+
const channels = asObjectRecord(guild.channels);
|
|
387
|
+
if (!channels) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
for (const channelId of Object.keys(channels)) {
|
|
391
|
+
const channel = asObjectRecord(channels[channelId]);
|
|
392
|
+
if (!channel) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
refs.push({
|
|
396
|
+
pathLabel: `${prefix}.guilds.${guildId}.channels.${channelId}.users`,
|
|
397
|
+
holder: channel,
|
|
398
|
+
key: "users",
|
|
399
|
+
});
|
|
400
|
+
refs.push({
|
|
401
|
+
pathLabel: `${prefix}.guilds.${guildId}.channels.${channelId}.roles`,
|
|
402
|
+
holder: channel,
|
|
403
|
+
key: "roles",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return refs;
|
|
408
|
+
}
|
|
409
|
+
function scanDiscordNumericIdEntries(cfg) {
|
|
410
|
+
const hits = [];
|
|
411
|
+
const scanList = (pathLabel, list) => {
|
|
412
|
+
if (!Array.isArray(list)) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
for (const [index, entry] of list.entries()) {
|
|
416
|
+
if (typeof entry !== "number") {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
hits.push({ path: `${pathLabel}[${index}]`, entry });
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
for (const scope of collectDiscordAccountScopes(cfg)) {
|
|
423
|
+
for (const ref of collectDiscordIdLists(scope.prefix, scope.account)) {
|
|
424
|
+
scanList(ref.pathLabel, ref.holder[ref.key]);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return hits;
|
|
428
|
+
}
|
|
429
|
+
function maybeRepairDiscordNumericIds(cfg) {
|
|
430
|
+
const hits = scanDiscordNumericIdEntries(cfg);
|
|
431
|
+
if (hits.length === 0) {
|
|
432
|
+
return { config: cfg, changes: [] };
|
|
433
|
+
}
|
|
434
|
+
const next = structuredClone(cfg);
|
|
435
|
+
const changes = [];
|
|
436
|
+
const repairList = (pathLabel, holder, key) => {
|
|
437
|
+
const raw = holder[key];
|
|
438
|
+
if (!Array.isArray(raw)) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
let converted = 0;
|
|
442
|
+
const updated = raw.map((entry) => {
|
|
443
|
+
if (typeof entry === "number") {
|
|
444
|
+
converted += 1;
|
|
445
|
+
return String(entry);
|
|
446
|
+
}
|
|
447
|
+
return entry;
|
|
448
|
+
});
|
|
449
|
+
if (converted === 0) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
holder[key] = updated;
|
|
453
|
+
changes.push(`- ${pathLabel}: converted ${converted} numeric ${converted === 1 ? "entry" : "entries"} to strings`);
|
|
454
|
+
};
|
|
455
|
+
for (const scope of collectDiscordAccountScopes(next)) {
|
|
456
|
+
for (const ref of collectDiscordIdLists(scope.prefix, scope.account)) {
|
|
457
|
+
repairList(ref.pathLabel, ref.holder, ref.key);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (changes.length === 0) {
|
|
461
|
+
return { config: cfg, changes: [] };
|
|
462
|
+
}
|
|
463
|
+
return { config: next, changes };
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Scan all channel configs for dmPolicy="open" without allowFrom including "*".
|
|
467
|
+
* This configuration is rejected by the schema validator but can easily occur when
|
|
468
|
+
* users (or integrations) set dmPolicy to "open" without realising that an explicit
|
|
469
|
+
* allowFrom wildcard is also required.
|
|
470
|
+
*/
|
|
471
|
+
function maybeRepairOpenPolicyAllowFrom(cfg) {
|
|
472
|
+
const channels = cfg.channels;
|
|
473
|
+
if (!channels || typeof channels !== "object") {
|
|
474
|
+
return { config: cfg, changes: [] };
|
|
475
|
+
}
|
|
476
|
+
const next = structuredClone(cfg);
|
|
477
|
+
const changes = [];
|
|
478
|
+
const resolveAllowFromMode = (channelName) => {
|
|
479
|
+
if (channelName === "googlechat") {
|
|
480
|
+
return "nestedOnly";
|
|
481
|
+
}
|
|
482
|
+
if (channelName === "discord" || channelName === "slack") {
|
|
483
|
+
return "topOrNested";
|
|
484
|
+
}
|
|
485
|
+
return "topOnly";
|
|
486
|
+
};
|
|
487
|
+
const hasWildcard = (list) => list?.some((v) => String(v).trim() === "*") ?? false;
|
|
488
|
+
const ensureWildcard = (account, prefix, mode) => {
|
|
489
|
+
const dmEntry = account.dm;
|
|
490
|
+
const dm = dmEntry && typeof dmEntry === "object" && !Array.isArray(dmEntry)
|
|
491
|
+
? dmEntry
|
|
492
|
+
: undefined;
|
|
493
|
+
const dmPolicy = account.dmPolicy ?? dm?.policy ?? undefined;
|
|
494
|
+
if (dmPolicy !== "open") {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const topAllowFrom = account.allowFrom;
|
|
498
|
+
const nestedAllowFrom = dm?.allowFrom;
|
|
499
|
+
if (mode === "nestedOnly") {
|
|
500
|
+
if (hasWildcard(nestedAllowFrom)) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (Array.isArray(nestedAllowFrom)) {
|
|
504
|
+
nestedAllowFrom.push("*");
|
|
505
|
+
changes.push(`- ${prefix}.dm.allowFrom: added "*" (required by dmPolicy="open")`);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const nextDm = dm ?? {};
|
|
509
|
+
nextDm.allowFrom = ["*"];
|
|
510
|
+
account.dm = nextDm;
|
|
511
|
+
changes.push(`- ${prefix}.dm.allowFrom: set to ["*"] (required by dmPolicy="open")`);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (mode === "topOrNested") {
|
|
515
|
+
if (hasWildcard(topAllowFrom) || hasWildcard(nestedAllowFrom)) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (Array.isArray(topAllowFrom)) {
|
|
519
|
+
topAllowFrom.push("*");
|
|
520
|
+
changes.push(`- ${prefix}.allowFrom: added "*" (required by dmPolicy="open")`);
|
|
521
|
+
}
|
|
522
|
+
else if (Array.isArray(nestedAllowFrom)) {
|
|
523
|
+
nestedAllowFrom.push("*");
|
|
524
|
+
changes.push(`- ${prefix}.dm.allowFrom: added "*" (required by dmPolicy="open")`);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
account.allowFrom = ["*"];
|
|
528
|
+
changes.push(`- ${prefix}.allowFrom: set to ["*"] (required by dmPolicy="open")`);
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (hasWildcard(topAllowFrom)) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (Array.isArray(topAllowFrom)) {
|
|
536
|
+
topAllowFrom.push("*");
|
|
537
|
+
changes.push(`- ${prefix}.allowFrom: added "*" (required by dmPolicy="open")`);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
account.allowFrom = ["*"];
|
|
541
|
+
changes.push(`- ${prefix}.allowFrom: set to ["*"] (required by dmPolicy="open")`);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
const nextChannels = next.channels;
|
|
545
|
+
for (const [channelName, channelConfig] of Object.entries(nextChannels)) {
|
|
546
|
+
if (!channelConfig || typeof channelConfig !== "object") {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const allowFromMode = resolveAllowFromMode(channelName);
|
|
550
|
+
// Check the top-level channel config
|
|
551
|
+
ensureWildcard(channelConfig, `channels.${channelName}`, allowFromMode);
|
|
552
|
+
// Check per-account configs (e.g. channels.discord.accounts.mybot)
|
|
553
|
+
const accounts = channelConfig.accounts;
|
|
554
|
+
if (accounts && typeof accounts === "object") {
|
|
555
|
+
for (const [accountName, accountConfig] of Object.entries(accounts)) {
|
|
556
|
+
if (accountConfig && typeof accountConfig === "object") {
|
|
557
|
+
ensureWildcard(accountConfig, `channels.${channelName}.accounts.${accountName}`, allowFromMode);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (changes.length === 0) {
|
|
563
|
+
return { config: cfg, changes: [] };
|
|
564
|
+
}
|
|
565
|
+
return { config: next, changes };
|
|
566
|
+
}
|
|
100
567
|
async function maybeMigrateLegacyConfig() {
|
|
101
568
|
const changes = [];
|
|
102
569
|
const home = resolveHomeDir();
|
|
@@ -114,8 +581,8 @@ async function maybeMigrateLegacyConfig() {
|
|
|
114
581
|
}
|
|
115
582
|
const legacyCandidates = [
|
|
116
583
|
path.join(home, ".clawdbot", "clawdbot.json"),
|
|
117
|
-
path.join(home, ".moltbot", "moltbot.json"),
|
|
118
584
|
path.join(home, ".moldbot", "moldbot.json"),
|
|
585
|
+
path.join(home, ".moltbot", "moltbot.json"),
|
|
119
586
|
];
|
|
120
587
|
let legacyPath = null;
|
|
121
588
|
for (const candidate of legacyCandidates) {
|
|
@@ -163,6 +630,7 @@ export async function loadAndMaybeMigrateDoctorConfig(params) {
|
|
|
163
630
|
const fixHints = [];
|
|
164
631
|
if (snapshot.exists && !snapshot.valid && snapshot.legacyIssues.length === 0) {
|
|
165
632
|
note("Config invalid; doctor will run with best-effort config.", "Config");
|
|
633
|
+
noteIncludeConfinementWarning(snapshot);
|
|
166
634
|
}
|
|
167
635
|
const warnings = snapshot.warnings ?? [];
|
|
168
636
|
if (warnings.length > 0) {
|
|
@@ -181,8 +649,9 @@ export async function loadAndMaybeMigrateDoctorConfig(params) {
|
|
|
181
649
|
}
|
|
182
650
|
if (shouldRepair) {
|
|
183
651
|
// Legacy migration (2026-01-02, commit: 16420e5b) — normalize per-provider allowlists; move WhatsApp gating into channels.whatsapp.allowFrom.
|
|
184
|
-
if (migrated)
|
|
652
|
+
if (migrated) {
|
|
185
653
|
cfg = migrated;
|
|
654
|
+
}
|
|
186
655
|
}
|
|
187
656
|
else {
|
|
188
657
|
fixHints.push(`Run "${formatCliCommand("poolbot doctor --fix")}" to apply legacy migrations.`);
|
|
@@ -212,6 +681,52 @@ export async function loadAndMaybeMigrateDoctorConfig(params) {
|
|
|
212
681
|
fixHints.push(`Run "${formatCliCommand("poolbot doctor --fix")}" to apply these changes.`);
|
|
213
682
|
}
|
|
214
683
|
}
|
|
684
|
+
if (shouldRepair) {
|
|
685
|
+
const repair = await maybeRepairTelegramAllowFromUsernames(candidate);
|
|
686
|
+
if (repair.changes.length > 0) {
|
|
687
|
+
note(repair.changes.join("\n"), "Doctor changes");
|
|
688
|
+
candidate = repair.config;
|
|
689
|
+
pendingChanges = true;
|
|
690
|
+
cfg = repair.config;
|
|
691
|
+
}
|
|
692
|
+
const discordRepair = maybeRepairDiscordNumericIds(candidate);
|
|
693
|
+
if (discordRepair.changes.length > 0) {
|
|
694
|
+
note(discordRepair.changes.join("\n"), "Doctor changes");
|
|
695
|
+
candidate = discordRepair.config;
|
|
696
|
+
pendingChanges = true;
|
|
697
|
+
cfg = discordRepair.config;
|
|
698
|
+
}
|
|
699
|
+
const allowFromRepair = maybeRepairOpenPolicyAllowFrom(candidate);
|
|
700
|
+
if (allowFromRepair.changes.length > 0) {
|
|
701
|
+
note(allowFromRepair.changes.join("\n"), "Doctor changes");
|
|
702
|
+
candidate = allowFromRepair.config;
|
|
703
|
+
pendingChanges = true;
|
|
704
|
+
cfg = allowFromRepair.config;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
const hits = scanTelegramAllowFromUsernameEntries(candidate);
|
|
709
|
+
if (hits.length > 0) {
|
|
710
|
+
note([
|
|
711
|
+
`- Telegram allowFrom contains ${hits.length} non-numeric entries (e.g. ${hits[0]?.entry ?? "@"}); Telegram authorization requires numeric sender IDs.`,
|
|
712
|
+
`- Run "${formatCliCommand("poolbot doctor --fix")}" to auto-resolve @username entries to numeric IDs (requires a Telegram bot token).`,
|
|
713
|
+
].join("\n"), "Doctor warnings");
|
|
714
|
+
}
|
|
715
|
+
const discordHits = scanDiscordNumericIdEntries(candidate);
|
|
716
|
+
if (discordHits.length > 0) {
|
|
717
|
+
note([
|
|
718
|
+
`- Discord allowlists contain ${discordHits.length} numeric entries (e.g. ${discordHits[0]?.path}=${discordHits[0]?.entry}).`,
|
|
719
|
+
`- Discord IDs must be strings; run "${formatCliCommand("poolbot doctor --fix")}" to convert numeric IDs to quoted strings.`,
|
|
720
|
+
].join("\n"), "Doctor warnings");
|
|
721
|
+
}
|
|
722
|
+
const allowFromScan = maybeRepairOpenPolicyAllowFrom(candidate);
|
|
723
|
+
if (allowFromScan.changes.length > 0) {
|
|
724
|
+
note([
|
|
725
|
+
...allowFromScan.changes,
|
|
726
|
+
`- Run "${formatCliCommand("poolbot doctor --fix")}" to add missing allowFrom wildcards.`,
|
|
727
|
+
].join("\n"), "Doctor warnings");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
215
730
|
const unknown = stripUnknownConfigKeys(candidate);
|
|
216
731
|
if (unknown.removed.length > 0) {
|
|
217
732
|
const lines = unknown.removed.map((path) => `- ${path}`).join("\n");
|
|
@@ -240,5 +755,10 @@ export async function loadAndMaybeMigrateDoctorConfig(params) {
|
|
|
240
755
|
}
|
|
241
756
|
}
|
|
242
757
|
noteOpencodeProviderOverrides(cfg);
|
|
243
|
-
return {
|
|
758
|
+
return {
|
|
759
|
+
cfg,
|
|
760
|
+
path: snapshot.path ?? CONFIG_PATH,
|
|
761
|
+
shouldWriteConfig,
|
|
762
|
+
sourceConfigValid: snapshot.valid,
|
|
763
|
+
};
|
|
244
764
|
}
|