Skip to content

解决方案设计

方案 A:流式输出监听(推荐)

架构概述

使用 Bun 的 spawn API 启动 OpenCode 进程,通过流式读取 stdout/stderr 来实时捕获输出,并根据策略将信息发送到 Telegram。

数据流图

核心组件设计

1. 进程管理器 (ProcessManager)

职责:管理 OpenCode 进程的生命周期,包括启动、追踪、终止和清理。

typescript
interface ProcessInfo {
  pid: number;
  chatId: number;  // 关联的 Telegram 聊天 ID
  messageId?: number;  // 用于回复的消息 ID
  startTime: number;
  status: 'running' | 'stopped' | 'failed';
}

class ProcessManager {
  private processes: Map<number, ProcessInfo> = new Map();
  private store: ProcessStore;  // 持久化存储

  async start(prompt: string, chatId: number, messageId: number): Promise<number>;
  async stop(chatId: number): Promise<boolean>;
  async getStatus(chatId: number): Promise<ProcessInfo | null>;
  async cleanup(): Promise<void>;
}

2. 流监听器 (StreamListener)

职责:实时监听 OpenCode 的 stdout/stderr 流,并将原始数据转发给解析器。

typescript
class StreamListener {
  private buffer: string = '';

  async listen(stdout: ReadableStream<Uint8Array>, stderr: ReadableStream<Uint8Array>): void;

  private async readStream(stream: ReadableStream<Uint8Array>, type: 'stdout' | 'stderr'): Promise<void>;

  private processChunk(chunk: string, type: 'stdout' | 'stderr'): void;
}

3. 输出解析器 (OutputParser)

职责:解析 OpenCode 的输出,识别关键事件(工具调用、文件操作、错误等)。

typescript
interface ParsedEvent {
  type: 'tool_call' | 'file_edit' | 'error' | 'progress' | 'completion' | 'other';
  content: string;
  timestamp: number;
  rawOutput: string;
}

class OutputParser {
  parse(output: string): ParsedEvent[];
}

4. 通知策略 (NotifyStrategy)

职责:根据预定义规则决定何时发送通知以及如何格式化消息。

typescript
interface NotificationRule {
  eventType: ParsedEvent['type'];
  throttleMs: number;  // 节流时间
  includeFullOutput: boolean;
  priority: 'high' | 'medium' | 'low';
}

class NotifyStrategy {
  private rules: NotificationRule[];

  shouldNotify(event: ParsedEvent): boolean;
  formatMessage(event: ParsedEvent, fullOutput: string): string;
  splitLongMessage(message: string): string[];
}

5. Telegram 通知器 (TelegramNotifier)

职责:与 Telegram API 交互,发送消息、编辑消息、处理限流。

typescript
class TelegramNotifier {
  private bot: Bot;
  private messageQueue: Map<number, QueuedMessage[]>;

  async sendMessage(chatId: number, text: string, options?: SendMessageOptions): Promise<void>;
  async editMessage(chatId: number, messageId: number, text: string): Promise<void>;
  async replyToMessage(chatId: number, messageId: number, text: string): Promise<void>;

  private async enforceRateLimit(): Promise<void>;
}

通知策略设计

策略等级

等级触发条件通知方式示例
紧急错误、异常、关键失败立即发送、标记OpenCode 崩溃、编译失败
重要工具调用完成、文件修改实时发送、编辑更新文件写入完成、测试通过
普通进度信息、状态变化节流发送(如每 30 秒)正在执行某操作
调试详细日志、中间状态可选发送(用户配置)工具调用详情、参数

消息格式化

typescript
// 紧急消息
🚨 错误:OpenCode 执行失败
原因:编译错误
详情:...

// 重要消息
✅ 文件已修改:src/index.ts
变更:+10 行,-5

// 进度消息
⏳ 正在执行:分析代码库...
已扫描 45/100 文件

// 最终结果
🎉 任务完成!
耗时:2m 30s
修改文件:3

进程终止流程

方案 B:日志文件监听(备选)

架构概述

配置 OpenCode 将输出写入日志文件,通过文件监听器(如 chokidar)实时读取文件变化并发送通知。

优缺点对比

方面方案 A(流式监听)方案 B(日志文件监听)
实时性高(实时流)中(文件轮询)
复杂度低(依赖文件系统)
资源占用中(磁盘 I/O)
可靠性中(可能丢失缓冲内容)
部署难度中(需要配置日志路径)
容错性中(进程断开需重连)高(文件可重读)

推荐选择

推荐方案 A,原因:

  1. 更好的实时性和用户体验
  2. 不需要额外的文件系统依赖
  3. 更符合 Unix 管道哲学
  4. 易于调试和维护

方案 B 可作为备选,适用于:

  • OpenCode 必须以特定模式运行(如 daemon 模式)
  • 需要事后查询历史输出
  • 进程和网络不稳定的环境

数据持久化方案

进程信息存储

选项实现方式优点缺点
内存存储Map<string, ProcessInfo>简单、快速机器人重启后丢失
JSON 文件fs.writeFileSync简单、易调试并发问题
SQLitebetter-sqlite3成熟、支持查询额外依赖
Redis客户端库高性能、支持过期需要额外服务

推荐:JSON 文件 + 内存缓存,对于单机器人实例足够使用。

配置存储

typescript
interface BotConfig {
  telegramToken: string;
  notificationRules: NotificationRule[];
  maxConcurrentProcesses: number;
  processTimeoutMs: number;
  autoKillTimeoutMs: number;
}

安全性设计

权限控制

  1. 用户白名单:只允许特定用户 ID 使用机器人
  2. 指令限制:kill 指令需要额外验证(如仅限发起 OpenCode 请求的用户)
  3. 速率限制:防止恶意用户频繁请求

敏感信息过滤

typescript
function sanitizeOutput(output: string): string {
  // 移除 API Keys、密码等敏感信息
  return output
    .replace(/Bearer\s+[A-Za-z0-9\-_]+/g, 'Bearer ***')
    .replace(/password["\s]*[:=]["\s]*[^\s"]+/gi, 'password: ***')
    .replace(/token["\s]*[:=]["\s]*[^\s"]+/gi, 'token: ***');
}

错误处理策略

进程启动失败

  • 返回错误消息给用户
  • 记录日志
  • 不影响其他功能

输出解析失败

  • 跳过当前事件
  • 记录错误日志
  • 继续监听后续输出

Telegram API 失败

  • 重试机制(最多 3 次)
  • 退避策略(指数退避)
  • 失败后记录日志,继续执行

实施建议

分阶段实施

第一阶段:基础功能

  1. 实现进程启动和基本的 stdout 监听
  2. 简单的通知(完整输出作为一条消息)
  3. 基础的 kill 指令

第二阶段:智能通知

  1. 实现输出解析和事件识别
  2. 应用通知策略和节流
  3. 消息格式化和分割

第三阶段:增强功能

  1. 多进程管理
  2. 进程持久化和恢复
  3. 高级通知(编辑消息、文件上传)

测试策略

  1. 单元测试:解析器、通知策略等独立组件
  2. 集成测试:完整的 OpenCode 执行流程
  3. 负载测试:多个并发进程、大量输出
  4. 故障测试:进程崩溃、网络中断等场景

参考资料