# DSL Agent Logs 扩展实现说明

## 项目结构

```
extensions/dsl-agent-logs/
├── package.json                  # 扩展配置和依赖
├── tsconfig.json                 # TypeScript 配置
├── README.md                     # 使用说明
├── IMPLEMENTATION.md             # 实现说明（本文档）
├── assets/
│   └── icon.png                  # 扩展图标
└── src/
    ├── extension.ts              # 扩展激活入口
    ├── types.ts                  # 类型定义
    ├── multiStreamService.ts     # 多路日志流服务
    ├── logTreeDataProvider.ts    # 日志视图数据提供者
    ├── logTreeItem.ts            # 日志节点定义
    ├── streamListDataProvider.ts # Stream 列表数据提供者
    ├── streamListItem.ts         # Stream 列表节点定义
    └── logDecorationProvider.ts  # 文件装饰提供者
```

## 核心组件

### 1. MultiStreamService (multiStreamService.ts)

**职责**:
- 管理多个日志流（按 session_id 分组）
- 存储和管理日志条目
- 管理活跃 Stream（当前选中的 Stream）
- 为每个 Stream 维护独立的过滤条件
- 提供日志变更事件

**关键方法**:
- `addLogEntry(streamId, streamName, log)`: 添加日志条目（自动创建 Stream 并设为活跃）
- `removeStream(streamId)`: 移除指定 Stream（自动切换活跃 Stream）
- `toggleStream(streamId, enabled)`: 启用/禁用 Stream
- `getStreams()`: 获取所有 Stream 信息
- `getLogsByStream(streamId)`: 获取指定 Stream 的日志
- `clearAllLogs(streamId)`: 清空指定 Stream 的日志
- `getActiveStreamId()`: 获取当前活跃的 Stream ID
- `setActiveStreamId(streamId)`: 设置活跃的 Stream
- `getFilter(streamId)`: 获取指定 Stream 的过滤条件
- `setFilter(streamId, filter)`: 设置指定 Stream 的过滤条件
- `clearFilter(streamId)`: 清除指定 Stream 的过滤条件

**事件**:
- `onDidChangeLog`: 日志变更事件
- `onDidChangeStreams`: Stream 变更事件
- `onDidChangeActiveStream`: 活跃 Stream 变更事件

**特性**:
- 自动创建 Stream（首次收到日志时）
- 新创建的 Stream 自动成为活跃 Stream
- 每个 Stream 最多保留 20000 条日志（自动清理旧日志）
- 禁用的 Stream 不接收新日志
- 每个 Stream 维护独立的过滤条件

### 2. LogTreeDataProvider (logTreeDataProvider.ts)

**职责**:
- 实现 VSCode TreeDataProvider 接口
- 显示当前活跃 Stream 的日志
- 应用过滤和搜索功能
- 响应日志和 Stream 变更并刷新视图

**关键方法**:
- `getTreeItem(element)`: 获取树节点
- `getChildren(element?)`: 获取子节点（只显示活跃 Stream 的日志）
- `setFilter(filter)`: 设置当前活跃 Stream 的过滤条件
- `clearFilter()`: 清除当前活跃 Stream 的过滤条件
- `getFilter()`: 获取当前活跃 Stream 的过滤条件

**过滤功能**:
- 按日志级别过滤（ERROR/WARN/INFO/DEBUG）
- 按关键词搜索（在消息和原始文本中匹配）

**监听事件**:
- `onDidChangeLog`: 日志变更时刷新视图
- `onDidChangeActiveStream`: 活跃 Stream 变更时刷新视图

### 3. StreamListDataProvider (streamListDataProvider.ts)

**职责**:
- 实现 VSCode TreeDataProvider 接口
- 显示所有可用的 Stream 列表
- 响应 Stream 变更并刷新视图

**关键方法**:
- `getTreeItem(element)`: 获取树节点
- `getChildren(element?)`: 获取子节点（所有 Stream）

**监听事件**:
- `onDidChangeStreams`: Stream 列表变更时刷新
- `onDidChangeLog`: 日志变更时刷新（更新日志数量）
- `onDidChangeActiveStream`: 活跃 Stream 变更时刷新（更新高亮状态）

### 4. StreamListItem (streamListItem.ts)

**职责**:
- 表示单个 Stream 的列表节点
- 提供图标、描述、工具提示
- 支持点击选择和右键菜单操作

**特性**:
- 显示日志数量
- 使用 radio-tower 图标
  - 活跃 Stream: 蓝色图标
  - 非活跃 Stream: 灰色图标
- contextValue 区分启用/禁用状态
  - `streamListItem-enabled`: 启用状态
  - `streamListItem-disabled`: 禁用状态
- 点击 Stream 触发 `dsl-agent-logs.selectStream` 命令
- Tooltip 显示 Stream 名称、状态和日志数量

### 5. LogTreeItem (logTreeItem.ts)

**职责**:
- 表示单条日志的树节点
- 提供图标、描述、工具提示
- 支持点击复制操作

**特性**:
- 根据日志等级显示不同的图标和颜色
  - ERROR: 红色 error 图标
  - WARN: 黄色 warning 图标
  - INFO: 蓝色 info 图标
  - DEBUG: 灰色 bug 图标
- 显示时间戳、日志级别和消息
- 如果有 source，显示在 description 中
- 点击日志触发 `dsl-agent-logs.copyLogEntry` 命令
- Tooltip 显示完整信息（Stream、时间、级别、source、taskId、消息）
- 使用 resourceUri 配合 LogDecorationProvider 提供颜色装饰

### 6. LogDecorationProvider (logDecorationProvider.ts)

**职责**:
- 实现 VSCode FileDecorationProvider 接口
- 为日志条目提供颜色装饰

**特性**:
- 根据日志级别提供不同的颜色
  - ERROR: errorForeground
  - WARN: editorWarning.foreground
  - INFO: charts.blue
  - DEBUG: descriptionForeground
- 使用 `dsl-log://` URI scheme 识别日志条目

### 7. Extension (extension.ts)

**职责**:
- 扩展激活入口
- 注册所有命令
- 创建两个 TreeView（Logs 和 Streams）
- 绑定服务和视图
- 管理过滤器上下文状态

**注册的命令**:
- `dsl-agent-logs.selectStream`: 选择 Stream（切换活跃 Stream）
- `dsl-agent-logs.removeStream`: 移除 Stream
- `dsl-agent-logs.enableStream`: 启用 Stream
- `dsl-agent-logs.disableStream`: 禁用 Stream
- `dsl-agent-logs.clear`: 清空当前活跃 Stream 的日志
- `dsl-agent-logs.export`: 导出当前活跃 Stream 的日志
- `dsl-agent-logs.filterByLevel`: 按级别过滤（QuickPick）
- `dsl-agent-logs.clearLevelFilter`: 清除级别过滤
- `dsl-agent-logs.search`: 搜索日志
- `dsl-agent-logs.clearSearchFilter`: 清除搜索过滤
- `dsl-agent-logs.copyLogEntry`: 复制日志条目
- `dsl-agent-logs.addLogEntry`: 添加日志条目（内部命令）

**上下文管理**:
- `dsl-agent-logs.hasLevelFilter`: 是否有级别过滤
- `dsl-agent-logs.hasSearchFilter`: 是否有搜索过滤
- 根据过滤状态动态显示/隐藏工具栏按钮

**导出 API**:
```typescript
{
  addLogEntry: (streamId, streamName, logEntry) => void;
  removeStream: (streamId) => Promise<void>;
  toggleStream: (streamId, enabled) => Promise<void>;
  getStreams: () => StreamInfo[];
  clearAllLogs: (streamId) => void;
}
```

## 类型定义 (types.ts)

### LogLevel

```typescript
enum LogLevel {
  ERROR = 'ERROR',
  WARN = 'WARN',
  INFO = 'INFO',
  DEBUG = 'DEBUG'
}
```

### StreamInfo

```typescript
interface StreamInfo {
  id: string;           // Stream ID（基于 session_id）
  name: string;         // 显示名称
  enabled: boolean;     // 是否启用
}
```

### LogEntry

```typescript
interface LogEntry {
  id: string;              // 唯一标识
  streamId: string;        // Stream ID
  streamName: string;      // Stream 显示名称
  timestamp: Date;         // 时间戳
  level: LogLevel;         // 日志级别
  message: string;         // 日志消息
  source?: string;         // 日志来源
  taskId?: string;         // Task ID
  rawText: string;         // 原始文本
}
```

### LogFilter

```typescript
interface LogFilter {
  level?: LogLevel;      // 按级别过滤
  searchText?: string;   // 按关键词搜索
  streamId?: string;     // 按 Stream 过滤
}
```

## 视图配置

### ViewContainer

在 `package.json` 中配置:

```json
{
  "viewsContainers": {
    "panel": [{
      "id": "dsl-agent-logs",
      "title": "DSLAgentLogs",
      "icon": "assets/icon.png"
    }]
  }
}
```

这会在控制面板中创建一个新的栏目。

### Views

```json
{
  "views": {
    "dsl-agent-logs": [
      {
        "id": "dsl-agent-logs-view",
        "name": "Logs"
      },
      {
        "id": "dsl-agent-logs-streams",
        "name": "Streams"
      }
    ]
  }
}
```

两个独立的视图：
- **Logs**: 显示当前活跃 Stream 的日志
- **Streams**: 显示所有 Stream 列表

### 工具栏按钮

**Logs 视图工具栏** (`view/title`):
- 搜索 (🔍) / 清除搜索 (🔍) - `navigation@1`
  - 根据 `dsl-agent-logs.hasSearchFilter` 上下文切换
- 按级别过滤 (📋) / 清除级别过滤 (📋) - `navigation@2`
  - 根据 `dsl-agent-logs.hasLevelFilter` 上下文切换
- 清空 (🗑️) - `navigation@3`
- 导出 (📤) - `navigation@4`

### 右键菜单

**Stream 列表节点** (`view/item/context`):
- Disable Stream (⏸️) - 当 `viewItem == streamListItem-enabled` 时显示 (inline@1)
- Enable Stream (▶️) - 当 `viewItem == streamListItem-disabled` 时显示 (inline@1)
- Remove Stream (🗑️) - 当 `viewItem =~ /streamListItem/` 时显示 (inline@2)

**Log 节点** (`view/item/context`):
- Copy Log Entry - 当 `viewItem == logEntry` 时显示

## 架构设计

### 推送模式 vs 拉取模式

**当前实现：推送模式**

优势：
- 简单高效，无需维护连接
- 无需处理重连、错误恢复
- 减少网络开销
- 更好的性能

流程：
```
Rust 端 → TypeScript 端 → VSCode 命令 → 扩展接收
```

**已移除：拉取模式**

之前的实现包含：
- SSE/WebSocket 连接
- 自动重连逻辑
- 连接状态管理
- LogStreamConnection 类

### 多路日志流

**设计原则**：
- 每个 `session_id` 对应一个独立的日志流
- Stream 自动创建（首次收到日志时）
- 每个 Stream 可以独立管理
- 同时只有一个活跃 Stream（当前查看的 Stream）
- 每个 Stream 维护独立的过滤条件

**Stream 生命周期**：
1. **创建**: 首次收到该 session_id 的日志时自动创建，并设为活跃 Stream
2. **选择**: 用户可以在 Streams 视图中点击切换活跃 Stream
3. **使用**: 接收和展示日志（只有活跃 Stream 的日志在 Logs 视图中显示）
4. **禁用**: 用户可以禁用 Stream（不再接收新日志，但保留已有日志）
5. **清空**: 清空该 Stream 的所有日志（保留 Stream 和过滤条件）
6. **移除**: 删除 Stream 及其所有日志和过滤条件

### 数据流

```
┌─────────────────┐
│   Rust 端       │
│  dsl_agent.rs   │
└────────┬────────┘
         │ 发送 DSLAgentServerSendEvent
         │ {
         │   session_id: string,
         │   task_id: string,
         │   event: DslAgentLogEvent
         │ }
         ↓
┌─────────────────┐
│ TypeScript 端   │
│dslAgent.impl.ts │
└────────┬────────┘
         │ handleLogPayload 解析
         │ 提取: session_id, task_id
         │       event.Log.{timestamp, level, message, source}
         │ 生成: streamId = `dsl-agent-${session_id}`
         │       streamName = `DSL Agent ${session_id.slice(0,8)}`
         ↓
┌─────────────────┐
│  VSCode 命令    │
│  executeCommand │
└────────┬────────┘
         │ 'dsl-agent-logs.addLogEntry'
         │ (streamId, streamName, logEntry)
         ↓
┌─────────────────┐
│  VSCode 扩展    │
│  extension.ts   │
└────────┬────────┘
         │ multiStreamService.addLogEntry()
         ↓
┌─────────────────────┐
│ MultiStreamService  │
└────────┬────────────┘
         │ 1. 检查 Stream 是否存在
         │ 2. 不存在则创建 Stream
         │ 3. 设置为活跃 Stream
         │ 4. 检查 Stream 是否启用
         │ 5. 添加日志到该 Stream 的缓冲区
         │ 6. 触发事件:
         │    - onDidChangeStreams (如果是新 Stream)
         │    - onDidChangeActiveStream (如果是新 Stream)
         │    - onDidChangeLog
         ↓
┌──────────────────────────────────────┐
│  LogTreeDataProvider                 │
│  StreamListDataProvider              │
└────────┬─────────────────────────────┘
         │ 监听事件并刷新视图:
         │ - LogTreeDataProvider:
         │   监听 onDidChangeLog 和 onDidChangeActiveStream
         │   只显示活跃 Stream 的日志
         │   应用该 Stream 的过滤条件
         │
         │ - StreamListDataProvider:
         │   监听 onDidChangeStreams, onDidChangeLog, onDidChangeActiveStream
         │   显示所有 Stream 列表
         │   高亮活跃 Stream
         ↓
┌─────────────────────────────────────┐
│   TreeView UI (双视图)              │
│   - Logs: 当前活跃 Stream 的日志   │
│   - Streams: 所有 Stream 列表       │
└─────────────────────────────────────┘
```

## 使用 TreeView 的优势

1. **原生体验**: 完全符合 VSCode 原生 UI 风格
2. **性能优异**: 不需要加载 HTML/CSS/JS，内存占用小
3. **实现简单**: 不需要 React/Webpack 等前端工具链
4. **无需打包**: 只需编译 TypeScript
5. **主题适配**: 自动适配 VSCode 主题
6. **快捷键支持**: 自动支持 VSCode 的快捷键
7. **虚拟滚动**: 自动处理大量数据的性能问题

## 性能考虑

1. **日志缓冲**: 每个 Stream 最多保留 20000 条日志，自动清理旧日志
2. **虚拟滚动**: TreeView 自动处理虚拟滚动
3. **过滤优化**: 在内存中过滤，不影响原始日志
4. **事件节流**: 使用 EventEmitter 避免频繁刷新
5. **禁用 Stream**: 禁用的 Stream 不接收新日志，减少内存占用
6. **独立过滤**: 每个 Stream 维护独立的过滤条件，避免相互影响
7. **按需渲染**: Logs 视图只显示活跃 Stream 的日志，减少渲染开销

## 扩展性

### 已实现的扩展功能

- ✅ 多路日志流（按 session_id 分组）
- ✅ 双视图架构（Logs + Streams）
- ✅ 活跃 Stream 管理和切换
- ✅ 按级别过滤（支持所有级别）
- ✅ 关键词搜索
- ✅ 每个 Stream 独立的过滤条件
- ✅ Stream 管理（启用/禁用/移除/选择）
- ✅ 日志导出（导出当前活跃 Stream）
- ✅ 清空日志（清空当前活跃 Stream）
- ✅ 复制日志
- ✅ 日志颜色装饰
- ✅ 动态工具栏按钮（根据过滤状态切换）

### 未来可以添加的功能

- 日志统计图表
- 日志高亮规则
- 日志级别配置
- 日志回放功能
- 按时间范围过滤
- 正则表达式搜索
- 日志标签功能
- 自动滚动到最新日志
- 批量导出多个 Stream
- Stream 重命名功能
- 日志持久化（保存到磁盘）

## 技术栈

- **语言**: TypeScript
- **运行时**: VSCode Extension API
- **视图**: TreeView (原生)
- **构建**: TypeScript Compiler
- **通信**: VSCode 命令系统

## 测试

### 单元测试

目前未实现单元测试，建议添加：
- MultiStreamService 测试
- LogTreeDataProvider 测试
- 过滤逻辑测试

### 集成测试

1. 启动 Rust 服务
2. 触发 DSL Agent 执行
3. 观察日志是否正确展示
4. 测试各项功能：
   - 多路展示（多个 Stream）
   - Stream 切换（点击 Streams 列表）
   - 活跃 Stream 高亮
   - 过滤功能（级别过滤）
   - 搜索功能（关键词搜索）
   - 导出功能（导出当前 Stream）
   - 清空功能（清空当前 Stream）
   - Stream 管理（启用/禁用/移除）
   - 独立过滤（不同 Stream 的过滤条件互不影响）

## 部署

### 开发模式

```bash
cd extensions/dsl-agent-logs
npm install
npm run watch
# 按 F5 启动调试
```

### 生产模式

```bash
npm run compile
# 扩展会被自动加载
```

### 打包发布

```bash
npm run package
# 生成 .vsix 文件
```

## 故障排查

### 日志不显示

1. 检查 Rust 端是否正确发送日志
2. 检查 TypeScript 端是否正确解析
3. 检查命令是否正确调用
4. 检查 Stream 是否被禁用
5. 检查是否选中了正确的活跃 Stream
6. 检查过滤条件是否过于严格

### 性能问题

1. 检查日志数量是否超过限制（每个 Stream 20000 条）
2. 检查是否有大量 Stream
3. 考虑清空旧日志
4. 考虑禁用不需要的 Stream
5. 考虑移除不再使用的 Stream

### UI 不更新

1. 检查事件是否正确触发
2. 检查过滤条件是否正确
3. 检查活跃 Stream 是否正确
4. 尝试切换到其他 Stream 再切换回来
5. 重启扩展

### Stream 切换问题

1. 检查 Streams 视图是否正确显示所有 Stream
2. 检查点击 Stream 是否触发 selectStream 命令
3. 检查活跃 Stream 是否正确高亮（蓝色图标）
4. 检查 Logs 视图是否显示正确 Stream 的日志

## 贡献指南

1. Fork 项目
2. 创建特性分支
3. 提交变更
4. 推送到分支
5. 创建 Pull Request

## 许可证

MIT
