feat: /last and bind button for previous Q/A context (v0.3.4)

After attaching a session, show a button and /last command that load the latest human/assistant turn via session.history so phone users can continue with context.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
owo
2026-08-14 20:25:07 +08:00
co-authored by Cursor
parent 3566df945b
commit fb5a334edb
16 changed files with 489 additions and 11 deletions
+34 -2
View File
@@ -3,8 +3,9 @@ import { SessionId } from '@deepseek-ai/dsh-session/types';
import { isAuthorized } from './auth.js';
import { catalogFromLiveAgents, loadCatalog, truncateButton, visibleSessionsForWorkspace, workspacesWithVisibleSessions, } from './catalog.js';
import { TelegramClient, } from './client.js';
import { MSG, parseCommand } from './commands.js';
import { MSG, LAST_CB, parseCommand } from './commands.js';
import { markdownToHtml, splitMessage } from './format.js';
import { formatLastTurn, loadLastTurn } from './history.js';
import { describeAgent, displayLabel } from './label.js';
import { formatModel, loadSessionModels, selectSessionModel, } from './models.js';
const WS_CB = 'ws:';
@@ -14,6 +15,11 @@ const MODEL_CB = 'mdl:';
const EFFORT_CB = 'me:';
const BACK_MODEL_CB = 'mb';
const MAX_BUTTONS = 40;
function lastContextKeyboard() {
return {
inline_keyboard: [[{ text: '查看上次对话', callback_data: LAST_CB }]],
};
}
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function contentToText(content) {
return content
@@ -57,6 +63,7 @@ export class TelegramBridge {
void this.client.setMyCommands([
{ command: 'start', description: '欢迎与用法' },
{ command: 'sessions', description: '按工作区列出并附着会话' },
{ command: 'last', description: '查看上次问答(续接上下文)' },
{ command: 'model', description: '切换当前绑定会话的模型' },
{ command: 'status', description: '查看当前绑定' },
{ command: 'unbind', description: '断开手机绑定(不关闭本机会话)' },
@@ -112,6 +119,9 @@ export class TelegramBridge {
case 'sessions':
await this.sendWorkspacePicker(chatId);
return;
case 'last':
await this.sendLastTurn(chatId);
return;
case 'model':
await this.sendModelPicker(chatId);
return;
@@ -146,6 +156,11 @@ export class TelegramBridge {
}
const data = cq.data ?? '';
const picker = this.pickers.get(String(chatId));
if (data === LAST_CB) {
await this.client.answerCallbackQuery(cq.id);
await this.sendLastTurn(chatId);
return;
}
if (data === BACK_WS_CB) {
await this.client.answerCallbackQuery(cq.id);
await this.sendWorkspacePicker(chatId, picker?.catalog);
@@ -317,7 +332,24 @@ export class TelegramBridge {
: displayLabel(parts);
this.bindings.set(String(chatId), { chatId, sessionId: String(agent.id), label });
await this.client.answerCallbackQuery(callbackId, '已附着');
await this.client.sendMessage(chatId, MSG.BOUND(label));
await this.client.sendMessage(chatId, MSG.BOUND(label), undefined, lastContextKeyboard());
}
async sendLastTurn(chatId) {
const binding = this.bindings.get(String(chatId));
if (!binding) {
await this.client.sendMessage(chatId, MSG.NEED_BIND);
return;
}
const agent = await this.ensureLiveAgent(binding.sessionId);
try {
const turn = await loadLastTurn(this.ctx, binding.sessionId, agent);
const text = formatLastTurn(turn);
await this.deliver(chatId, text);
}
catch (err) {
this.ctx.logger.warn(`dsh-telegram-channel: /last failed: ${this.redact(err)}`);
await this.client.sendMessage(chatId, MSG.LAST_FAILED);
}
}
async sendModelPicker(chatId) {
const binding = this.bindings.get(String(chatId));