# dsh-plugin-log-forwarder 接口文档 > 版本:V1.0 · 更新:2026-09-06 本插件为 DeepSeek Harness 的 cordis 插件,宿主侧接口包括:**配置 schema、标准事件格式、输出通道 API、HTTP 端点、模型工具**。客户端侧接口见 [README「接入 Harness 客户端」](../README.md)。 ## 1. 插件声明 | 导出 | 说明 | |---|---| | `name` | `'dsh-plugin-log-forwarder'` | | `inject` | `['sessions', 'tools']`(会话存储 + 工具注册表) | | `Config` | schemastery schema(cordis.yml 校验与默认值) | | `apply(ctx, config)` | 插件主函数(加载即装配通道与订阅) | 宿主侧另在 `@deepseek-ai/cordis` Events 上声明事件: | 事件 | 负载 | 说明 | |---|---|---| | `log-forwarder/status` | `PluginState` | 状态变更通知(每 1s + 暂停/恢复/清空时 emit),供客户端面板经 remotes 桥接监听 | ## 2. 配置项(cordis.yml) 完整默认配置: ```yaml enable: true # 插件总开关 autoStart: true # 加载后是否立即开始转发 channels: websocket: enable: true # WebSocket 通道 port: 18765 # 绑定 127.0.0.1,占用自动 +1(最多 5 次) file: enable: false # 本地文件通道 path: '' # 路径模板;空 → ~/.deepseek-harness/logs/session-.jsonl maxFileSizeBytes: 52428800 # 单文件上限,超出停止写入 loki: enable: false # Loki 通道 url: '' # http(s)://host:port tenantId: '' # X-Scope-OrgID(多租户必填) token: '' # Authorization: Bearer bufferSize: 1000 # 缓冲上限,超出丢弃最旧 maxConsecutiveFailures: 10 # 连续失败阈值 → disconnected filter: includeEventTypes: [] # 白名单(优先),空 = 全部 excludeEventTypes: [] # 黑名单 redaction: enable: true # 敏感字段脱敏 sensitiveFields: [api_key, apikey, password, token, secret, authorization] ``` ### 配置校验规则 - `port` 为自然数且 ≤ 65535;`bufferSize`/`maxConsecutiveFailures`/`maxFileSizeBytes` 为非负自然数。 - `channels.*.enable` 均默认关闭/开启如上表;缺失的嵌套键由 schemastery 默认值补齐。 - `filter` 语义:`include` 命中即转发(优先于 `exclude`);`include` 为空时 `exclude` 生效;两者皆空 = 全部转发。 ## 3. 标准事件格式 ```typescript interface StandardEvent { id: string // 'evt_' + 32 位十六进制 timestamp: string // ISO 8601 UTC sessionId: string // 无会话上下文(agent/error)时为 '' turnIndex: number // 0 起;无法判定为 -1 type: string // 标准类型名(见下表) payload: Record } ``` 事件类型映射(Harness → 标准): | Harness 事件 | 标准类型 | payload 要点 | |---|---|---| | `session/created` | `session_start` | sessionId、cwd、parentSession、origin、delegationDepth、agentPreset | | `user/message` | `user_input` | message(完整)、text | | `turn/start` | `turn_start` | turn、reason | | `assistant/attempt` | `reasoning` | turn、step、text(思考流)、streamLength | | `assistant/message` | `model_output` | turn、step、message、text、usage、interrupted | | `tool/call` | `tool_call` | turn、step、callId、name、arguments | | `tool/result`(无错误) | `tool_result` | turn、step、callId、name、text | | `tool/result`(isError/error) | `tool_error` | 同上 + error | | `turn/end` | `turn_end` | turn、reason | | `session/disposed` | `session_end` | sessionId、finalSeq | | `agent/error` | `error` | turn、step、agentId、message、stack | | 其他 | 原始类型名 | rawType、seq、原 data 字段透传 | 容错约定:只读取稳定字段;`data` 缺失、结构变化、序列化失败均不抛错(失败事件计入通道 `failed`,跳过该事件继续后续)。 ## 4. 输出通道 API 所有通道继承 `src/channels/base.ts` 的 `Channel` 基类: ```typescript abstract class Channel { readonly name: 'websocket' | 'file' | 'loki' readonly enabled: boolean get status(): 'running' | 'paused' | 'disconnected' | 'error' get stats(): ChannelStats // { forwarded, failed, dropped, bufferSize } get error(): string | undefined applyGlobalPause(paused: boolean): void snapshot(): ChannelState abstract start(): Promise // 启动(失败置 error,不影响其他通道) abstract deliver(event: StandardEvent): void // 同步投递(契约:不抛错) abstract flush(): Promise // 持久化 / 落盘检查点 abstract stop(): Promise // 关闭(幂等) } ``` ### 4.1 WebSocket 通道 - 绑定 `127.0.0.1`,首选端口可配;占用时自动 +1,最多 5 次,全失败 → `error`(其余通道不受影响)。 - 多客户端广播;客户端断开/异常自动清理;30s 心跳探测死连接。 - 额外状态:`clientCount`、`port`;`target` 为 `ws://127.0.0.1:`。 - HTTP 端点:`/`(状态页 HTML)、`/status`、`/channels`、`POST /pause`、`POST /resume`、`POST /clear-stats`(全部开启 CORS)。 ### 4.2 文件通道 - 路径模板:含 `{sessionId}` → 按会话分文件;否则单文件聚合。 - 批量落盘:每 `flushIntervalMs`(100ms) 或满 `maxBatchLines`(50) 行;`session/flush` 与卸载强制冲刷。 - 大小上限:单文件超 `maxFileSizeBytes` → 置 `error` 并停止写入(后续事件计 dropped)。 - 路径守卫:命中系统关键目录(Windows 系统目录、/etc、/usr、/bin 等)→ 置 `error` 拒绝写入。 - `target`:输出路径模板(未配置时给出默认 `~/.deepseek-harness/logs/session-{sessionId}.jsonl`)。 ### 4.3 Loki 通道 - 推送地址:`/loki/api/v1/push`;请求体为 Loki JSON 流格式(纳秒时间戳)。 - 标签:`source=dsh-log-forwarder`、`session_id`、`event_type`、`turn_index`。 - 请求头:`X-Scope-OrgID`(tenantId 非空时)、`Authorization: Bearer `(token 非空时)。 - 批量:满 `batchSize`(100) 条或间隔 `batchIntervalMs`(200ms) 触发;单飞防并发。 - 重试:失败指数退避(基址 500ms、上限 30s、单次推送最多 5 次退避重试),事件回放缓冲不丢失。 - 缓冲:上限 `bufferSize`,溢出丢最旧并记 dropped + 警告。 - 断连:连续失败 ≥ `maxConsecutiveFailures`(10) → `disconnected` 停止重试;`recover()`(恢复工具/面板按钮)重置计数并重推缓冲。 - 卸载:尽力冲刷缓冲,`Promise.race` 超时上限 5 秒。 - `target`:Loki 推送端点 `/loki/api/v1/push`。 ## 5. 模型工具 | 工具 | 入参 schema | 返回 | 异常 | |---|---|---|---| | `log_forwarder_status` | `{}` | `{running, startTime, totalEvents, totalForwarded, totalDropped, uptimeMs, channels: ChannelState[]}` | — | | `log_forwarder_pause` | `{}` | `{success, running}` | — | | `log_forwarder_resume` | `{}` | `{success, running}` | — | | `log_forwarder_channel_status` | `channel: enum[websocket,file,loki]` | `ChannelState`(websocket 含 clientCount/port) | 通道未启用时抛错 | `ChannelState`: ```typescript interface ChannelState { name: 'websocket' | 'file' | 'loki' enabled: boolean status: 'running' | 'paused' | 'disconnected' | 'error' stats: { forwarded: number; failed: number; dropped: number; bufferSize: number } target?: string // 输出目标:ws://地址 / 文件路径模板 / Loki 推送端点 error?: string } // websocket 通道扩展(WebSocketChannelState) clientCount: number port: number ``` ## 6. 生命周期 1. **加载 `apply(ctx, config)`**:校验配置 → 装配启用通道(异步启动,失败置 error 不影响其他)→ 订阅 5 类事件 → 注册 4 个工具 → 补发已存在会话的 `session_start` → 启动状态通知定时器(1s)。 2. **运行**:事件流水线 = 采集(`running` 才采集)→ 标准化 → `totalEvents++` → 脱敏 → 过滤(未命中计丢弃)→ 逐通道 `deliver`。 3. **暂停**:停止采集(不缓冲、不计丢弃),通道状态联动 `paused`,WebSocket Server 保持运行。 4. **恢复**:继续采集;同时恢复 `disconnected` 的 Loki 与 `error` 的 WebSocket。 5. **卸载**:effect disposer 按序执行——停止采集、清定时器、逐通道 `stop()`(WS 断开全部客户端并关服、文件冲刷、Loki 限时冲刷)、清空状态。无残留。