@poolzin/pool-bot 2026.2.0 → 2026.2.2
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 +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- package/dist/agents/model-selection.js +21 -0
- 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.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- 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-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- 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 +1 -1
- 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/envelope.js +74 -82
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- 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/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/mentions.js +1 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- 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 +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- 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.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/index.html +28 -28
- package/dist/channels/reply-prefix.js +8 -1
- 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/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/auth-choice.apply.oauth.js +1 -1
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.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.registry.js +1 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- 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 +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/compat/legacy-names.js +2 -0
- package/dist/config/defaults.js +3 -2
- 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 +2 -2
- package/dist/config/sessions/store.js +291 -23
- 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/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- 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 +172 -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/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/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- 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-http.js +109 -11
- 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 +5 -1
- 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.mocks.js +11 -7
- package/dist/gateway/test-helpers.server.js +35 -2
- 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/infra/bonjour-discovery.js +131 -70
- 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/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- 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/media/fetch.js +81 -58
- package/dist/media/store.js +2 -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 +3 -3
- 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/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- 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/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/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +54 -17
- 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/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/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -49,6 +49,64 @@ function normalizeAllowFromEntry(params) {
|
|
|
49
49
|
});
|
|
50
50
|
return normalized.filter((entry) => entry.trim().length > 0);
|
|
51
51
|
}
|
|
52
|
+
function resolveOwnerAllowFromList(params) {
|
|
53
|
+
const raw = params.allowFrom ?? params.cfg.commands?.ownerAllowFrom;
|
|
54
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const filtered = [];
|
|
58
|
+
for (const entry of raw) {
|
|
59
|
+
const trimmed = String(entry ?? "").trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
continue;
|
|
62
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
63
|
+
if (separatorIndex > 0) {
|
|
64
|
+
const prefix = trimmed.slice(0, separatorIndex);
|
|
65
|
+
const channel = normalizeAnyChannelId(prefix);
|
|
66
|
+
if (channel) {
|
|
67
|
+
if (params.providerId && channel !== params.providerId)
|
|
68
|
+
continue;
|
|
69
|
+
const remainder = trimmed.slice(separatorIndex + 1).trim();
|
|
70
|
+
if (remainder)
|
|
71
|
+
filtered.push(remainder);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
filtered.push(trimmed);
|
|
76
|
+
}
|
|
77
|
+
return formatAllowFromList({
|
|
78
|
+
dock: params.dock,
|
|
79
|
+
cfg: params.cfg,
|
|
80
|
+
accountId: params.accountId,
|
|
81
|
+
allowFrom: filtered,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolves the commands.allowFrom list for a given provider.
|
|
86
|
+
* Returns the provider-specific list if defined, otherwise the "*" global list.
|
|
87
|
+
* Returns null if commands.allowFrom is not configured at all (fall back to channel allowFrom).
|
|
88
|
+
*/
|
|
89
|
+
function resolveCommandsAllowFromList(params) {
|
|
90
|
+
const { dock, cfg, accountId, providerId } = params;
|
|
91
|
+
const commandsAllowFrom = cfg.commands?.allowFrom;
|
|
92
|
+
if (!commandsAllowFrom || typeof commandsAllowFrom !== "object") {
|
|
93
|
+
return null; // Not configured, fall back to channel allowFrom
|
|
94
|
+
}
|
|
95
|
+
// Check provider-specific list first, then fall back to global "*"
|
|
96
|
+
const providerKey = providerId ?? "";
|
|
97
|
+
const providerList = commandsAllowFrom[providerKey];
|
|
98
|
+
const globalList = commandsAllowFrom["*"];
|
|
99
|
+
const rawList = Array.isArray(providerList) ? providerList : globalList;
|
|
100
|
+
if (!Array.isArray(rawList)) {
|
|
101
|
+
return null; // No applicable list found
|
|
102
|
+
}
|
|
103
|
+
return formatAllowFromList({
|
|
104
|
+
dock,
|
|
105
|
+
cfg,
|
|
106
|
+
accountId,
|
|
107
|
+
allowFrom: rawList,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
52
110
|
function resolveSenderCandidates(params) {
|
|
53
111
|
const { dock, cfg, accountId } = params;
|
|
54
112
|
const candidates = [];
|
|
@@ -83,6 +141,13 @@ export function resolveCommandAuthorization(params) {
|
|
|
83
141
|
const dock = providerId ? getChannelDock(providerId) : undefined;
|
|
84
142
|
const from = (ctx.From ?? "").trim();
|
|
85
143
|
const to = (ctx.To ?? "").trim();
|
|
144
|
+
// Check if commands.allowFrom is configured (separate command authorization)
|
|
145
|
+
const commandsAllowFromList = resolveCommandsAllowFromList({
|
|
146
|
+
dock,
|
|
147
|
+
cfg,
|
|
148
|
+
accountId: ctx.AccountId,
|
|
149
|
+
providerId,
|
|
150
|
+
});
|
|
86
151
|
const allowFromRaw = dock?.config?.resolveAllowFrom
|
|
87
152
|
? dock.config.resolveAllowFrom({ cfg, accountId: ctx.AccountId })
|
|
88
153
|
: [];
|
|
@@ -92,9 +157,23 @@ export function resolveCommandAuthorization(params) {
|
|
|
92
157
|
accountId: ctx.AccountId,
|
|
93
158
|
allowFrom: Array.isArray(allowFromRaw) ? allowFromRaw : [],
|
|
94
159
|
});
|
|
160
|
+
const configOwnerAllowFromList = resolveOwnerAllowFromList({
|
|
161
|
+
dock,
|
|
162
|
+
cfg,
|
|
163
|
+
accountId: ctx.AccountId,
|
|
164
|
+
providerId,
|
|
165
|
+
allowFrom: cfg.commands?.ownerAllowFrom,
|
|
166
|
+
});
|
|
167
|
+
const contextOwnerAllowFromList = resolveOwnerAllowFromList({
|
|
168
|
+
dock,
|
|
169
|
+
cfg,
|
|
170
|
+
accountId: ctx.AccountId,
|
|
171
|
+
providerId,
|
|
172
|
+
allowFrom: ctx.OwnerAllowFrom,
|
|
173
|
+
});
|
|
95
174
|
const allowAll = allowFromList.length === 0 || allowFromList.some((entry) => entry.trim() === "*");
|
|
96
|
-
const
|
|
97
|
-
if (!allowAll &&
|
|
175
|
+
const ownerCandidatesForCommands = allowAll ? [] : allowFromList.filter((entry) => entry !== "*");
|
|
176
|
+
if (!allowAll && ownerCandidatesForCommands.length === 0 && to) {
|
|
98
177
|
const normalizedTo = normalizeAllowFromEntry({
|
|
99
178
|
dock,
|
|
100
179
|
cfg,
|
|
@@ -102,9 +181,18 @@ export function resolveCommandAuthorization(params) {
|
|
|
102
181
|
value: to,
|
|
103
182
|
});
|
|
104
183
|
if (normalizedTo.length > 0)
|
|
105
|
-
|
|
184
|
+
ownerCandidatesForCommands.push(...normalizedTo);
|
|
106
185
|
}
|
|
107
|
-
const
|
|
186
|
+
const ownerAllowAll = configOwnerAllowFromList.some((entry) => entry.trim() === "*");
|
|
187
|
+
const explicitOwners = configOwnerAllowFromList.filter((entry) => entry !== "*");
|
|
188
|
+
const explicitOverrides = contextOwnerAllowFromList.filter((entry) => entry !== "*");
|
|
189
|
+
const ownerList = Array.from(new Set(explicitOwners.length > 0
|
|
190
|
+
? explicitOwners
|
|
191
|
+
: ownerAllowAll
|
|
192
|
+
? []
|
|
193
|
+
: explicitOverrides.length > 0
|
|
194
|
+
? explicitOverrides
|
|
195
|
+
: ownerCandidatesForCommands));
|
|
108
196
|
const senderCandidates = resolveSenderCandidates({
|
|
109
197
|
dock,
|
|
110
198
|
providerId,
|
|
@@ -117,14 +205,41 @@ export function resolveCommandAuthorization(params) {
|
|
|
117
205
|
const matchedSender = ownerList.length
|
|
118
206
|
? senderCandidates.find((candidate) => ownerList.includes(candidate))
|
|
119
207
|
: undefined;
|
|
208
|
+
const matchedCommandOwner = ownerCandidatesForCommands.length
|
|
209
|
+
? senderCandidates.find((candidate) => ownerCandidatesForCommands.includes(candidate))
|
|
210
|
+
: undefined;
|
|
120
211
|
const senderId = matchedSender ?? senderCandidates[0];
|
|
121
212
|
const enforceOwner = Boolean(dock?.commands?.enforceOwnerForCommands);
|
|
122
|
-
const
|
|
123
|
-
const
|
|
213
|
+
const senderIsOwner = Boolean(matchedSender);
|
|
214
|
+
const ownerAllowlistConfigured = ownerAllowAll || explicitOwners.length > 0;
|
|
215
|
+
const requireOwner = enforceOwner || ownerAllowlistConfigured;
|
|
216
|
+
const isOwnerForCommands = !requireOwner
|
|
217
|
+
? true
|
|
218
|
+
: ownerAllowAll
|
|
219
|
+
? true
|
|
220
|
+
: ownerAllowlistConfigured
|
|
221
|
+
? senderIsOwner
|
|
222
|
+
: allowAll || ownerCandidatesForCommands.length === 0 || Boolean(matchedCommandOwner);
|
|
223
|
+
// If commands.allowFrom is configured, use it for command authorization
|
|
224
|
+
// Otherwise, fall back to existing behavior (channel allowFrom + owner checks)
|
|
225
|
+
let isAuthorizedSender;
|
|
226
|
+
if (commandsAllowFromList !== null) {
|
|
227
|
+
// commands.allowFrom is configured - use it for authorization
|
|
228
|
+
const commandsAllowAll = commandsAllowFromList.some((entry) => entry.trim() === "*");
|
|
229
|
+
const matchedCommandsAllowFrom = commandsAllowFromList.length
|
|
230
|
+
? senderCandidates.find((candidate) => commandsAllowFromList.includes(candidate))
|
|
231
|
+
: undefined;
|
|
232
|
+
isAuthorizedSender = commandsAllowAll || Boolean(matchedCommandsAllowFrom);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Fall back to existing behavior
|
|
236
|
+
isAuthorizedSender = commandAuthorized && isOwnerForCommands;
|
|
237
|
+
}
|
|
124
238
|
return {
|
|
125
239
|
providerId,
|
|
126
240
|
ownerList,
|
|
127
241
|
senderId: senderId || undefined,
|
|
242
|
+
senderIsOwner,
|
|
128
243
|
isAuthorizedSender,
|
|
129
244
|
from: from || undefined,
|
|
130
245
|
to: to || undefined,
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { resolveUserTimezone } from "../agents/date-time.js";
|
|
2
2
|
import { normalizeChatType } from "../channels/chat-type.js";
|
|
3
3
|
import { resolveSenderLabel } from "../channels/sender-label.js";
|
|
4
|
+
import { resolveTimezone, formatUtcTimestamp, formatZonedTimestamp, } from "../infra/format-time/format-datetime.js";
|
|
5
|
+
import { formatTimeAgo } from "../infra/format-time/format-relative.js";
|
|
6
|
+
function sanitizeEnvelopeHeaderPart(value) {
|
|
7
|
+
// Header parts are metadata and must not be able to break the bracketed prefix.
|
|
8
|
+
// Keep ASCII; collapse newlines/whitespace; neutralize brackets.
|
|
9
|
+
return value
|
|
10
|
+
.replace(/\r\n|\r|\n/g, " ")
|
|
11
|
+
.replaceAll("[", "(")
|
|
12
|
+
.replaceAll("]", ")")
|
|
13
|
+
.replace(/\s+/g, " ")
|
|
14
|
+
.trim();
|
|
15
|
+
}
|
|
4
16
|
export function resolveEnvelopeFormatOptions(cfg) {
|
|
5
17
|
const defaults = cfg?.agents?.defaults;
|
|
6
18
|
return {
|
|
@@ -20,125 +32,104 @@ function normalizeEnvelopeOptions(options) {
|
|
|
20
32
|
userTimezone: options?.userTimezone,
|
|
21
33
|
};
|
|
22
34
|
}
|
|
23
|
-
function resolveExplicitTimezone(value) {
|
|
24
|
-
try {
|
|
25
|
-
new Intl.DateTimeFormat("en-US", { timeZone: value }).format(new Date());
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
35
|
function resolveEnvelopeTimezone(options) {
|
|
33
36
|
const trimmed = options.timezone?.trim();
|
|
34
|
-
if (!trimmed)
|
|
37
|
+
if (!trimmed) {
|
|
35
38
|
return { mode: "local" };
|
|
39
|
+
}
|
|
36
40
|
const lowered = trimmed.toLowerCase();
|
|
37
|
-
if (lowered === "utc" || lowered === "gmt")
|
|
41
|
+
if (lowered === "utc" || lowered === "gmt") {
|
|
38
42
|
return { mode: "utc" };
|
|
39
|
-
|
|
43
|
+
}
|
|
44
|
+
if (lowered === "local" || lowered === "host") {
|
|
40
45
|
return { mode: "local" };
|
|
46
|
+
}
|
|
41
47
|
if (lowered === "user") {
|
|
42
48
|
return { mode: "iana", timeZone: resolveUserTimezone(options.userTimezone) };
|
|
43
49
|
}
|
|
44
|
-
const explicit =
|
|
50
|
+
const explicit = resolveTimezone(trimmed);
|
|
45
51
|
return explicit ? { mode: "iana", timeZone: explicit } : { mode: "utc" };
|
|
46
52
|
}
|
|
47
|
-
function formatUtcTimestamp(date) {
|
|
48
|
-
const yyyy = String(date.getUTCFullYear()).padStart(4, "0");
|
|
49
|
-
const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
50
|
-
const dd = String(date.getUTCDate()).padStart(2, "0");
|
|
51
|
-
const hh = String(date.getUTCHours()).padStart(2, "0");
|
|
52
|
-
const min = String(date.getUTCMinutes()).padStart(2, "0");
|
|
53
|
-
return `${yyyy}-${mm}-${dd}T${hh}:${min}Z`;
|
|
54
|
-
}
|
|
55
|
-
function formatZonedTimestamp(date, timeZone) {
|
|
56
|
-
const parts = new Intl.DateTimeFormat("en-US", {
|
|
57
|
-
timeZone,
|
|
58
|
-
year: "numeric",
|
|
59
|
-
month: "2-digit",
|
|
60
|
-
day: "2-digit",
|
|
61
|
-
hour: "2-digit",
|
|
62
|
-
minute: "2-digit",
|
|
63
|
-
hourCycle: "h23",
|
|
64
|
-
timeZoneName: "short",
|
|
65
|
-
}).formatToParts(date);
|
|
66
|
-
const pick = (type) => parts.find((part) => part.type === type)?.value;
|
|
67
|
-
const yyyy = pick("year");
|
|
68
|
-
const mm = pick("month");
|
|
69
|
-
const dd = pick("day");
|
|
70
|
-
const hh = pick("hour");
|
|
71
|
-
const min = pick("minute");
|
|
72
|
-
const tz = [...parts]
|
|
73
|
-
.reverse()
|
|
74
|
-
.find((part) => part.type === "timeZoneName")
|
|
75
|
-
?.value?.trim();
|
|
76
|
-
if (!yyyy || !mm || !dd || !hh || !min)
|
|
77
|
-
return undefined;
|
|
78
|
-
return `${yyyy}-${mm}-${dd} ${hh}:${min}${tz ? ` ${tz}` : ""}`;
|
|
79
|
-
}
|
|
80
53
|
function formatTimestamp(ts, options) {
|
|
81
|
-
if (!ts)
|
|
54
|
+
if (!ts) {
|
|
82
55
|
return undefined;
|
|
56
|
+
}
|
|
83
57
|
const resolved = normalizeEnvelopeOptions(options);
|
|
84
|
-
if (!resolved.includeTimestamp)
|
|
58
|
+
if (!resolved.includeTimestamp) {
|
|
85
59
|
return undefined;
|
|
60
|
+
}
|
|
86
61
|
const date = ts instanceof Date ? ts : new Date(ts);
|
|
87
|
-
if (Number.isNaN(date.getTime()))
|
|
62
|
+
if (Number.isNaN(date.getTime())) {
|
|
88
63
|
return undefined;
|
|
64
|
+
}
|
|
89
65
|
const zone = resolveEnvelopeTimezone(resolved);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
66
|
+
// Include a weekday prefix so models do not need to derive DOW from the date
|
|
67
|
+
// (small models are notoriously unreliable at that).
|
|
68
|
+
const weekday = (() => {
|
|
69
|
+
try {
|
|
70
|
+
if (zone.mode === "utc") {
|
|
71
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: "UTC", weekday: "short" }).format(date);
|
|
72
|
+
}
|
|
73
|
+
if (zone.mode === "local") {
|
|
74
|
+
return new Intl.DateTimeFormat("en-US", { weekday: "short" }).format(date);
|
|
75
|
+
}
|
|
76
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: zone.timeZone, weekday: "short" }).format(date);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
const formatted = zone.mode === "utc"
|
|
83
|
+
? formatUtcTimestamp(date)
|
|
84
|
+
: zone.mode === "local"
|
|
85
|
+
? formatZonedTimestamp(date)
|
|
86
|
+
: formatZonedTimestamp(date, { timeZone: zone.timeZone });
|
|
87
|
+
if (!formatted) {
|
|
99
88
|
return undefined;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return `${seconds}s`;
|
|
103
|
-
const minutes = Math.floor(seconds / 60);
|
|
104
|
-
if (minutes < 60)
|
|
105
|
-
return `${minutes}m`;
|
|
106
|
-
const hours = Math.floor(minutes / 60);
|
|
107
|
-
if (hours < 24)
|
|
108
|
-
return `${hours}h`;
|
|
109
|
-
const days = Math.floor(hours / 24);
|
|
110
|
-
return `${days}d`;
|
|
89
|
+
}
|
|
90
|
+
return weekday ? `${weekday} ${formatted}` : formatted;
|
|
111
91
|
}
|
|
112
92
|
export function formatAgentEnvelope(params) {
|
|
113
|
-
const channel = params.channel?.trim() || "Channel";
|
|
93
|
+
const channel = sanitizeEnvelopeHeaderPart(params.channel?.trim() || "Channel");
|
|
114
94
|
const parts = [channel];
|
|
115
95
|
const resolved = normalizeEnvelopeOptions(params.envelope);
|
|
116
|
-
|
|
117
|
-
|
|
96
|
+
let elapsed;
|
|
97
|
+
if (resolved.includeElapsed && params.timestamp && params.previousTimestamp) {
|
|
98
|
+
const currentMs = params.timestamp instanceof Date ? params.timestamp.getTime() : params.timestamp;
|
|
99
|
+
const previousMs = params.previousTimestamp instanceof Date
|
|
118
100
|
? params.previousTimestamp.getTime()
|
|
119
|
-
: params.previousTimestamp
|
|
120
|
-
|
|
101
|
+
: params.previousTimestamp;
|
|
102
|
+
const elapsedMs = currentMs - previousMs;
|
|
103
|
+
elapsed =
|
|
104
|
+
Number.isFinite(elapsedMs) && elapsedMs >= 0
|
|
105
|
+
? formatTimeAgo(elapsedMs, { suffix: false })
|
|
106
|
+
: undefined;
|
|
107
|
+
}
|
|
121
108
|
if (params.from?.trim()) {
|
|
122
|
-
const from = params.from.trim();
|
|
109
|
+
const from = sanitizeEnvelopeHeaderPart(params.from.trim());
|
|
123
110
|
parts.push(elapsed ? `${from} +${elapsed}` : from);
|
|
124
111
|
}
|
|
125
112
|
else if (elapsed) {
|
|
126
113
|
parts.push(`+${elapsed}`);
|
|
127
114
|
}
|
|
128
|
-
if (params.host?.trim())
|
|
129
|
-
parts.push(params.host.trim());
|
|
130
|
-
|
|
131
|
-
|
|
115
|
+
if (params.host?.trim()) {
|
|
116
|
+
parts.push(sanitizeEnvelopeHeaderPart(params.host.trim()));
|
|
117
|
+
}
|
|
118
|
+
if (params.ip?.trim()) {
|
|
119
|
+
parts.push(sanitizeEnvelopeHeaderPart(params.ip.trim()));
|
|
120
|
+
}
|
|
132
121
|
const ts = formatTimestamp(params.timestamp, resolved);
|
|
133
|
-
if (ts)
|
|
122
|
+
if (ts) {
|
|
134
123
|
parts.push(ts);
|
|
124
|
+
}
|
|
135
125
|
const header = `[${parts.join(" ")}]`;
|
|
136
126
|
return `${header} ${params.body}`;
|
|
137
127
|
}
|
|
138
128
|
export function formatInboundEnvelope(params) {
|
|
139
129
|
const chatType = normalizeChatType(params.chatType);
|
|
140
130
|
const isDirect = !chatType || chatType === "direct";
|
|
141
|
-
const
|
|
131
|
+
const resolvedSenderRaw = params.senderLabel?.trim() || resolveSenderLabel(params.sender ?? {});
|
|
132
|
+
const resolvedSender = resolvedSenderRaw ? sanitizeEnvelopeHeaderPart(resolvedSenderRaw) : "";
|
|
142
133
|
const body = !isDirect && resolvedSender ? `${resolvedSender}: ${params.body}` : params.body;
|
|
143
134
|
return formatAgentEnvelope({
|
|
144
135
|
channel: params.channel,
|
|
@@ -158,8 +149,9 @@ export function formatInboundFromLabel(params) {
|
|
|
158
149
|
}
|
|
159
150
|
const directLabel = params.directLabel.trim();
|
|
160
151
|
const directId = params.directId?.trim();
|
|
161
|
-
if (!directId || directId === directLabel)
|
|
152
|
+
if (!directId || directId === directLabel) {
|
|
162
153
|
return directLabel;
|
|
154
|
+
}
|
|
163
155
|
return `${directLabel} id:${directId}`;
|
|
164
156
|
}
|
|
165
157
|
export function formatThreadStarterEnvelope(params) {
|
|
@@ -70,6 +70,7 @@ export const handleCompactCommand = async (params) => {
|
|
|
70
70
|
defaultLevel: "off",
|
|
71
71
|
},
|
|
72
72
|
customInstructions,
|
|
73
|
+
senderIsOwner: params.command.senderIsOwner,
|
|
73
74
|
ownerNumbers: params.command.ownerList.length > 0 ? params.command.ownerList : undefined,
|
|
74
75
|
});
|
|
75
76
|
const compactLabel = result.ok
|
|
@@ -75,6 +75,7 @@ async function resolveContextReport(params) {
|
|
|
75
75
|
groupChannel: params.sessionEntry?.groupChannel ?? undefined,
|
|
76
76
|
groupSpace: params.sessionEntry?.space ?? undefined,
|
|
77
77
|
spawnedBy: params.sessionEntry?.spawnedBy ?? undefined,
|
|
78
|
+
senderIsOwner: params.command.senderIsOwner,
|
|
78
79
|
modelProvider: params.provider,
|
|
79
80
|
modelId: params.model,
|
|
80
81
|
});
|
|
@@ -1,75 +1,28 @@
|
|
|
1
1
|
import { loadModelCatalog } from "../../agents/model-catalog.js";
|
|
2
2
|
import { buildAllowedModelSet, buildModelAliasIndex, normalizeProviderId, resolveConfiguredModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js";
|
|
3
3
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
|
4
|
+
import { buildModelsKeyboard, buildProviderKeyboard, calculateTotalPages, getModelsPageSize, } from "../../telegram/model-buttons.js";
|
|
4
5
|
const PAGE_SIZE_DEFAULT = 20;
|
|
5
6
|
const PAGE_SIZE_MAX = 100;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (!trimmed) {
|
|
12
|
-
return { page: 1, pageSize: PAGE_SIZE_DEFAULT, all: false };
|
|
13
|
-
}
|
|
14
|
-
const tokens = trimmed.split(/\s+/g).filter(Boolean);
|
|
15
|
-
const provider = tokens[0]?.trim();
|
|
16
|
-
let page = 1;
|
|
17
|
-
let all = false;
|
|
18
|
-
for (const token of tokens.slice(1)) {
|
|
19
|
-
const lower = token.toLowerCase();
|
|
20
|
-
if (lower === "all" || lower === "--all") {
|
|
21
|
-
all = true;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (lower.startsWith("page=")) {
|
|
25
|
-
const value = Number.parseInt(lower.slice("page=".length), 10);
|
|
26
|
-
if (Number.isFinite(value) && value > 0)
|
|
27
|
-
page = value;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (/^[0-9]+$/.test(lower)) {
|
|
31
|
-
const value = Number.parseInt(lower, 10);
|
|
32
|
-
if (Number.isFinite(value) && value > 0)
|
|
33
|
-
page = value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
let pageSize = PAGE_SIZE_DEFAULT;
|
|
37
|
-
for (const token of tokens) {
|
|
38
|
-
const lower = token.toLowerCase();
|
|
39
|
-
if (lower.startsWith("limit=") || lower.startsWith("size=")) {
|
|
40
|
-
const rawValue = lower.slice(lower.indexOf("=") + 1);
|
|
41
|
-
const value = Number.parseInt(rawValue, 10);
|
|
42
|
-
if (Number.isFinite(value) && value > 0)
|
|
43
|
-
pageSize = Math.min(PAGE_SIZE_MAX, value);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
provider: provider ? normalizeProviderId(provider) : undefined,
|
|
48
|
-
page,
|
|
49
|
-
pageSize,
|
|
50
|
-
all,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
export async function resolveModelsCommandReply(params) {
|
|
54
|
-
const body = params.commandBodyNormalized.trim();
|
|
55
|
-
if (!body.startsWith("/models"))
|
|
56
|
-
return null;
|
|
57
|
-
const argText = body.replace(/^\/models\b/i, "").trim();
|
|
58
|
-
const { provider, page, pageSize, all } = parseModelsArgs(argText);
|
|
7
|
+
/**
|
|
8
|
+
* Build provider/model data from config and catalog.
|
|
9
|
+
* Exported for reuse by callback handlers.
|
|
10
|
+
*/
|
|
11
|
+
export async function buildModelsProviderData(cfg) {
|
|
59
12
|
const resolvedDefault = resolveConfiguredModelRef({
|
|
60
|
-
cfg
|
|
13
|
+
cfg,
|
|
61
14
|
defaultProvider: DEFAULT_PROVIDER,
|
|
62
15
|
defaultModel: DEFAULT_MODEL,
|
|
63
16
|
});
|
|
64
|
-
const catalog = await loadModelCatalog({ config:
|
|
17
|
+
const catalog = await loadModelCatalog({ config: cfg });
|
|
65
18
|
const allowed = buildAllowedModelSet({
|
|
66
|
-
cfg
|
|
19
|
+
cfg,
|
|
67
20
|
catalog,
|
|
68
21
|
defaultProvider: resolvedDefault.provider,
|
|
69
22
|
defaultModel: resolvedDefault.model,
|
|
70
23
|
});
|
|
71
24
|
const aliasIndex = buildModelAliasIndex({
|
|
72
|
-
cfg
|
|
25
|
+
cfg,
|
|
73
26
|
defaultProvider: resolvedDefault.provider,
|
|
74
27
|
});
|
|
75
28
|
const byProvider = new Map();
|
|
@@ -93,7 +46,7 @@ export async function resolveModelsCommandReply(params) {
|
|
|
93
46
|
add(resolved.ref.provider, resolved.ref.model);
|
|
94
47
|
};
|
|
95
48
|
const addModelConfigEntries = () => {
|
|
96
|
-
const modelConfig =
|
|
49
|
+
const modelConfig = cfg.agents?.defaults?.model;
|
|
97
50
|
if (typeof modelConfig === "string") {
|
|
98
51
|
addRawModelRef(modelConfig);
|
|
99
52
|
}
|
|
@@ -103,7 +56,7 @@ export async function resolveModelsCommandReply(params) {
|
|
|
103
56
|
addRawModelRef(fallback);
|
|
104
57
|
}
|
|
105
58
|
}
|
|
106
|
-
const imageConfig =
|
|
59
|
+
const imageConfig = cfg.agents?.defaults?.imageModel;
|
|
107
60
|
if (typeof imageConfig === "string") {
|
|
108
61
|
addRawModelRef(imageConfig);
|
|
109
62
|
}
|
|
@@ -118,7 +71,7 @@ export async function resolveModelsCommandReply(params) {
|
|
|
118
71
|
add(entry.provider, entry.id);
|
|
119
72
|
}
|
|
120
73
|
// Include config-only allowlist keys that aren't in the curated catalog.
|
|
121
|
-
for (const raw of Object.keys(
|
|
74
|
+
for (const raw of Object.keys(cfg.agents?.defaults?.models ?? {})) {
|
|
122
75
|
addRawModelRef(raw);
|
|
123
76
|
}
|
|
124
77
|
// Ensure configured defaults/fallbacks/image models show up even when the
|
|
@@ -126,7 +79,79 @@ export async function resolveModelsCommandReply(params) {
|
|
|
126
79
|
add(resolvedDefault.provider, resolvedDefault.model);
|
|
127
80
|
addModelConfigEntries();
|
|
128
81
|
const providers = [...byProvider.keys()].sort();
|
|
82
|
+
return { byProvider, providers, resolvedDefault };
|
|
83
|
+
}
|
|
84
|
+
function formatProviderLine(params) {
|
|
85
|
+
return `- ${params.provider} (${params.count})`;
|
|
86
|
+
}
|
|
87
|
+
function parseModelsArgs(raw) {
|
|
88
|
+
const trimmed = raw.trim();
|
|
89
|
+
if (!trimmed) {
|
|
90
|
+
return { page: 1, pageSize: PAGE_SIZE_DEFAULT, all: false };
|
|
91
|
+
}
|
|
92
|
+
const tokens = trimmed.split(/\s+/g).filter(Boolean);
|
|
93
|
+
const provider = tokens[0]?.trim();
|
|
94
|
+
let page = 1;
|
|
95
|
+
let all = false;
|
|
96
|
+
for (const token of tokens.slice(1)) {
|
|
97
|
+
const lower = token.toLowerCase();
|
|
98
|
+
if (lower === "all" || lower === "--all") {
|
|
99
|
+
all = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (lower.startsWith("page=")) {
|
|
103
|
+
const value = Number.parseInt(lower.slice("page=".length), 10);
|
|
104
|
+
if (Number.isFinite(value) && value > 0)
|
|
105
|
+
page = value;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (/^[0-9]+$/.test(lower)) {
|
|
109
|
+
const value = Number.parseInt(lower, 10);
|
|
110
|
+
if (Number.isFinite(value) && value > 0)
|
|
111
|
+
page = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let pageSize = PAGE_SIZE_DEFAULT;
|
|
115
|
+
for (const token of tokens) {
|
|
116
|
+
const lower = token.toLowerCase();
|
|
117
|
+
if (lower.startsWith("limit=") || lower.startsWith("size=")) {
|
|
118
|
+
const rawValue = lower.slice(lower.indexOf("=") + 1);
|
|
119
|
+
const value = Number.parseInt(rawValue, 10);
|
|
120
|
+
if (Number.isFinite(value) && value > 0)
|
|
121
|
+
pageSize = Math.min(PAGE_SIZE_MAX, value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
provider: provider ? normalizeProviderId(provider) : undefined,
|
|
126
|
+
page,
|
|
127
|
+
pageSize,
|
|
128
|
+
all,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export async function resolveModelsCommandReply(params) {
|
|
132
|
+
const body = params.commandBodyNormalized.trim();
|
|
133
|
+
if (!body.startsWith("/models"))
|
|
134
|
+
return null;
|
|
135
|
+
const argText = body.replace(/^\/models\b/i, "").trim();
|
|
136
|
+
const { provider, page, pageSize, all } = parseModelsArgs(argText);
|
|
137
|
+
const { byProvider, providers } = await buildModelsProviderData(params.cfg);
|
|
138
|
+
const isTelegram = params.surface === "telegram";
|
|
139
|
+
// Provider list (no provider specified)
|
|
129
140
|
if (!provider) {
|
|
141
|
+
// For Telegram: show buttons if there are providers
|
|
142
|
+
if (isTelegram && providers.length > 0) {
|
|
143
|
+
const providerInfos = providers.map((p) => ({
|
|
144
|
+
id: p,
|
|
145
|
+
count: byProvider.get(p)?.size ?? 0,
|
|
146
|
+
}));
|
|
147
|
+
const buttons = buildProviderKeyboard(providerInfos);
|
|
148
|
+
const text = "Select a provider:";
|
|
149
|
+
return {
|
|
150
|
+
text,
|
|
151
|
+
channelData: { telegram: { buttons } },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Text fallback for non-Telegram surfaces
|
|
130
155
|
const lines = [
|
|
131
156
|
"Providers:",
|
|
132
157
|
...providers.map((p) => formatProviderLine({ provider: p, count: byProvider.get(p)?.size ?? 0 })),
|
|
@@ -158,6 +183,26 @@ export async function resolveModelsCommandReply(params) {
|
|
|
158
183
|
];
|
|
159
184
|
return { text: lines.join("\n") };
|
|
160
185
|
}
|
|
186
|
+
// For Telegram: use button-based model list with inline keyboard pagination
|
|
187
|
+
if (isTelegram) {
|
|
188
|
+
const telegramPageSize = getModelsPageSize();
|
|
189
|
+
const totalPages = calculateTotalPages(total, telegramPageSize);
|
|
190
|
+
const safePage = Math.max(1, Math.min(page, totalPages));
|
|
191
|
+
const buttons = buildModelsKeyboard({
|
|
192
|
+
provider,
|
|
193
|
+
models,
|
|
194
|
+
currentModel: params.currentModel,
|
|
195
|
+
currentPage: safePage,
|
|
196
|
+
totalPages,
|
|
197
|
+
pageSize: telegramPageSize,
|
|
198
|
+
});
|
|
199
|
+
const text = `Models (${provider}) — ${total} available`;
|
|
200
|
+
return {
|
|
201
|
+
text,
|
|
202
|
+
channelData: { telegram: { buttons } },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Text fallback for non-Telegram surfaces
|
|
161
206
|
const effectivePageSize = all ? total : pageSize;
|
|
162
207
|
const pageCount = effectivePageSize > 0 ? Math.ceil(total / effectivePageSize) : 1;
|
|
163
208
|
const safePage = all ? 1 : Math.max(1, Math.min(page, pageCount));
|
|
@@ -194,6 +239,8 @@ export const handleModelsCommand = async (params, allowTextCommands) => {
|
|
|
194
239
|
const reply = await resolveModelsCommandReply({
|
|
195
240
|
cfg: params.cfg,
|
|
196
241
|
commandBodyNormalized: params.command.commandBodyNormalized,
|
|
242
|
+
surface: params.ctx.Surface,
|
|
243
|
+
currentModel: params.model ? `${params.provider}/${params.model}` : undefined,
|
|
197
244
|
});
|
|
198
245
|
if (!reply)
|
|
199
246
|
return null;
|