Files
dsh-telegram-channel/lib/index.js
T
RRRosmontis cde6979f2d feat: resilient delivery, live progress visibility, TG-side answering, /stop /mission /new
Delivery & visibility:
- per-chunk retry with capped backoff on rich/HTML sends; rich failure now
  falls back to HTML instead of dropping the reply
- typing heartbeat: refresh sendChatAction every 4s for the whole turn
  (a single action expires in ~5s, long turns looked dead)
- turn/end error / abort / crash-recovery notices reach Telegram
  (API failures were previously invisible)
- tool/call progress summaries, tool/result failure notices, todo progress
- thinking indicator (one notice per reasoning phase, status only)
- notices prefixed '> ' render as Telegram blockquotes; plain-text fallback
- non-text messages get a hint instead of being silently dropped
- busy feedback: a followup queued mid-turn gets an explicit ack

TG-side answering (dual-path race; UI path untouched, first answer wins):
- ask_user_question questions render with letter options on TG; reply A /
  [A] / A,B (multi) / free text / '1: A' per-line for multi-question
- approval requests (approval/request waterfall) answerable from TG:
  [A] allow once / [B] reject; /cancel closes only the TG side

Commands: /stop (cancel current turn), /mission (todo snapshot + busy state),
/new (create a session in the current workspace and attach)

Persistence: chat→session bindings stored at $DSH_HOME/
telegram-channel-bindings.json (override: DSH_TELEGRAM_BINDINGS_FILE) and
restored on start — hot reloads and restarts no longer drop attachments.

Host seams without public surface yet are reached with documented structural
casts (userQuestions provider wrap, approval/request waterfall).

Tests: 41/41 pass. Two pre-existing environment failures in bridge.test.ts
(/last history mirror, assistant mirror) now pass: the former was fixed by
the rich→HTML fallback, the latter needed an event-loop flush after the
(void) listener call since resilient delivery lengthens the chain.
2026-09-01 16:06:00 +08:00

53 lines
2.0 KiB
JavaScript

import Schema from '@deepseek-ai/schemastery';
import { TelegramBridge } from './bridge.js';
export const name = 'dsh-telegram-channel';
/** agents: followup; apiProxy: workspace/session catalog + /model; userQuestions: TG answer hook (Cordis requires inject). */
export const inject = ['agents', 'apiProxy', 'userQuestions'];
export const Config = Schema.object({
token: Schema.string().default(''),
allowedUserIds: Schema.array(Schema.number()).default([]),
allowAllUsers: Schema.boolean().default(false),
maxMessageLength: Schema.number().default(4096),
pollingTimeoutSec: Schema.number().default(30),
rendering: Schema.string().default('rich'),
});
function resolveAllowedUserIds(config) {
if (config.allowedUserIds && config.allowedUserIds.length > 0) {
return config.allowedUserIds;
}
const raw = process.env.DSH_TELEGRAM_ALLOWED_USER_IDS ?? '';
return raw
.split(/[,;\s]+/)
.map((part) => part.trim())
.filter(Boolean)
.map((part) => Number(part))
.filter((n) => Number.isFinite(n) && n > 0);
}
export function apply(ctx, config) {
const token = (config.token && config.token.length > 0)
? config.token
: (process.env.DSH_TELEGRAM_TOKEN ?? '');
if (!token) {
ctx.logger.error('dsh-telegram-channel: missing bot token (set config.token or DSH_TELEGRAM_TOKEN); polling not started');
return;
}
const bridge = new TelegramBridge(ctx, {
token,
allowedUserIds: resolveAllowedUserIds(config),
allowAllUsers: config.allowAllUsers ?? false,
maxMessageLength: config.maxMessageLength ?? 4096,
pollingTimeoutSec: config.pollingTimeoutSec ?? 30,
rendering: config.rendering === 'html' ? 'html' : 'rich',
});
ctx.effect(() => {
bridge.start();
return () => { void bridge.stop(); };
}, 'dsh-telegram-channel.serve');
}
export * from './format.js';
export * from './client.js';
export * from './auth.js';
export * from './commands.js';
export * from './label.js';
export * from './bridge.js';