@modesto_ma/dsh-channels 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +302 -2
  2. package/cordis.patch.yml +3 -0
  3. package/docs/access-control-design.md +550 -0
  4. package/docs/assets/web-settings-panel.png +0 -0
  5. package/docs/assets/web-settings-restart-pending.png +0 -0
  6. package/docs/feasibility-and-design.md +213 -0
  7. package/docs/feishu-onboarding.md +268 -0
  8. package/docs/persistence.md +199 -0
  9. package/docs/refactor-plan.md +56 -0
  10. package/docs/review-dsh-0.2.0.md +389 -0
  11. package/docs/slash-commands-implementation-plan.md +410 -0
  12. package/docs/web-display-only-surface-roadmap.md +363 -0
  13. package/lib/client.js +2226 -0
  14. package/lib/client.js.map +1 -0
  15. package/lib/index.js +10217 -0
  16. package/lib/index.js.map +1 -0
  17. package/package.json +98 -4
  18. package/src/access/cards.ts +188 -0
  19. package/src/access/controller.ts +201 -0
  20. package/src/access/index.ts +13 -0
  21. package/src/access/policy.ts +103 -0
  22. package/src/access/requests.ts +512 -0
  23. package/src/access/store.ts +220 -0
  24. package/src/approvals.ts +8 -0
  25. package/src/cards/approval.ts +114 -0
  26. package/src/cards/index.ts +11 -0
  27. package/src/cards/primitives.ts +82 -0
  28. package/src/cards/question.ts +141 -0
  29. package/src/cards/streaming.ts +563 -0
  30. package/src/cards.ts +8 -0
  31. package/src/cleanup.ts +398 -0
  32. package/src/client/api.ts +262 -0
  33. package/src/client/catalog.ts +92 -0
  34. package/src/client/index.ts +61 -0
  35. package/src/client/panel.tsx +1475 -0
  36. package/src/client/styles.ts +98 -0
  37. package/src/commands/registry.ts +155 -0
  38. package/src/commands.ts +335 -0
  39. package/src/config.ts +206 -0
  40. package/src/domain.ts +428 -0
  41. package/src/dsh-types.ts +11 -0
  42. package/src/feishu/agent-context.ts +62 -0
  43. package/src/feishu/delivery.ts +188 -0
  44. package/src/feishu/inbound-attachments.ts +436 -0
  45. package/src/feishu-channel.ts +339 -0
  46. package/src/health.ts +233 -0
  47. package/src/host/agent.ts +181 -0
  48. package/src/host/commands.ts +63 -0
  49. package/src/host/core.ts +345 -0
  50. package/src/host/index.ts +12 -0
  51. package/src/host/interaction.ts +101 -0
  52. package/src/host/services.ts +320 -0
  53. package/src/index.ts +122 -0
  54. package/src/interactions/approvals.ts +154 -0
  55. package/src/interactions/pending.ts +162 -0
  56. package/src/interactions/questions.ts +274 -0
  57. package/src/questions.ts +8 -0
  58. package/src/routing/inbound.ts +524 -0
  59. package/src/service.ts +2138 -0
  60. package/src/session/manager.ts +352 -0
  61. package/src/session/repair.ts +157 -0
  62. package/src/session/request-policy.ts +192 -0
  63. package/src/session/routes.ts +165 -0
  64. package/src/session-commands.ts +310 -0
  65. package/src/session-export.ts +112 -0
  66. package/src/settings/catalog.ts +309 -0
  67. package/src/settings/contract.ts +508 -0
  68. package/src/settings/overlay.ts +164 -0
  69. package/src/settings/routes.ts +409 -0
  70. package/src/settings/surface.ts +773 -0
  71. package/src/stats.ts +485 -0
  72. package/src/streamer.ts +1108 -0
  73. package/src/workspaces.ts +199 -0
@@ -0,0 +1,410 @@
1
+ # 飞书斜杠命令实施任务与验收计划
2
+
3
+ > 状态:实施中(SC-01 至 SC-10 及 Session `/effort` 增量已完成)
4
+ > 范围:仅修改 `dsh_channels` 插件;不修改 DeepSeek Harness
5
+ > 目标:在飞书消息进入模型之前识别并执行人类命令,命令输入和直接结果不进入模型上下文
6
+
7
+ ## 1. 交付范围
8
+
9
+ 本轮交付以下命令:
10
+
11
+ | 命令 | 行为 |
12
+ |---|---|
13
+ | `/help` | 列出当前支持的斜杠命令;允许在尚未绑定 Session 或已过期话题中使用 |
14
+ | `/userid` | 仅在私聊中直接回复当前消息发送者的 user ID;无需绑定 Session,不创建话题 |
15
+ | `/groupid` | 仅在群聊中直接回复当前群组的 group ID;无需绑定 Session,不创建话题;普通群聊遵循 `requireMention`,话题内无需重复 @ |
16
+ | `/health` | 显示 Harness 组合状态和飞书 WebSocket 通道状态;允许在尚未绑定 Session 的私聊或群聊中使用 |
17
+ | `/status` | 显示当前 Session 的模型、推理强度、token、上下文、缓存、LLM/工具耗时和吞吐指标 |
18
+ | `/workspace` | 显示当前 Session 绑定的工作区绝对路径 |
19
+ | `/effort [档位-id\|reset]` | 查看或切换当前 Session 推理强度;成功选择随 Session 日志恢复 |
20
+ | `/compact` | 调用 Harness 已注册的原生 `/compact`,显示压缩前后上下文占用和文本条形图 |
21
+ | `/renew` | 在当前飞书话题中创建并绑定新 Session,复用原工作区,保留旧 Session |
22
+ | `/history` | 显示当前话题绑定过的历史 Session;默认展示最近 20 条并标记当前项 |
23
+ | `/export` | 将当前 Session 的逻辑日志导出为 gzip 压缩的 JSONL 文件并发送到当前话题 |
24
+
25
+ 明确排除:
26
+
27
+ - 不提供 `/model list`、`/model <name>` 或 `/effort list`;`/status` 仍只读。
28
+ - `/effort <档位-id>` 将不透明 id 写入当前 Session 的后续请求;`/effort reset` 恢复频道配置默认。插件不预判 provider 是否支持该 id。
29
+ - 不修改 Harness,不新增 Host 服务或修改 Web UI。
30
+ - `/export` 不追求 Web 导出 ZIP 的完整语义:不递归包含子代理 Session,不收集附件,不复制持久化后端的原始物理文件。
31
+ - 不将斜杠命令作为普通用户消息传给模型,也不让未知命令退化为模型输入。
32
+
33
+ ## 2. 统一行为约定
34
+
35
+ ### 2.1 命令识别与执行
36
+
37
+ 1. 消息仍先经过现有 message-id 去重门,确保命令副作用不会因飞书重投而重复执行。
38
+ 2. 仅当去除消息首尾空白后的内容以 `/` 开头时进入命令平面;命令名沿用 Harness 的小写字母、数字、`_`、`-` 语法。
39
+ 3. `/help`、`/userid`、`/groupid`、`/health` 可以在无路由或已过期话题中执行;`/userid` 只接受私聊,`/groupid` 只接受群聊;普通群聊中的 ID 查询遵循 `requireMention`,未 @ 时静默忽略,话题内无需重复 @;其余命令必须先解析到当前话题的 `RouteRecord`。
40
+ 4. 除全局快速返回的 `/help`、`/userid`、`/groupid` 外,有 Session 的命令通过 `ctx.commands.execute(agent, line, signal)` 执行,以保留 Harness 的 `command/run`、`command/done` 生命周期记录。
41
+ 5. Harness 未注册的命令返回明确错误和当前支持的命令列表,不调用 `agent.followup()`。
42
+ 6. 命令结果使用独立、非流式的飞书结果卡;`/userid`、`/groupid` 使用普通回复,不设置 `reply_in_thread`,其余命令保持话题回复;命令执行期间沿用 Typing → DONE 表态,但不创建模型 turn。
43
+ 7. 同一路由上的普通消息、`/effort`、`/compact`、`/renew` 和 `/export` 必须串行准入,避免配置切换、路由切换、压缩和普通 turn 互相穿插。
44
+
45
+ ### 2.2 身份与信息边界
46
+
47
+ - `/help` 只显示公开的命令名和功能说明,不读取 Session 或路由信息。
48
+ - `/userid` 只在私聊中回显当前消息的 `senderId`;`/groupid` 只在群聊中回显当前消息的 `chatId`,错误会话类型不回显 ID。
49
+ - `/health` 只显示脱敏的整体状态,不显示凭据引用值、错误堆栈、插件配置或文件路径。
50
+ - 其余 Session 命令仅允许 `msg.senderId === route.senderOpenId` 的话题所有者执行。
51
+ - 非所有者执行 Session 命令时返回“无权操作此会话”,不恢复 Agent、不读取 Session 日志、不产生命令生命周期事件。
52
+ - `/status` 的工具事件只显示工具名称、状态和耗时,不显示调用参数或工具结果正文。
53
+ - `/export` 含完整逻辑日志,可能包含工作区路径、用户输入和工具输出;只能发送给当前路由所有者所在的原话题。
54
+ - 错误卡使用稳定、可操作的说明,不直接回显未知异常对象、原始 SDK 响应或敏感配置。
55
+
56
+ ### 2.3 修改型命令的准入
57
+
58
+ - `/compact` 沿用 Harness 原生命令的 idle/busy 判定。
59
+ - `/effort` 查询可随时执行;切换/reset 仅在 Agent idle 且 inbox 为空时执行。只有成功配对的 `command/run` / `command/done` 才进入恢复折叠,失败或未完成命令不改变选择。
60
+ - `/renew` 仅在旧 Agent 为 `idle` 且 next-turn/next-step inbox 均为空时执行;否则保持原绑定不变并返回明确错误。
61
+ - `/renew` 创建新 Session 和提交新路由期间,当前路由保持互斥;提交失败时销毁未发布或未绑定的新 Agent,旧路由继续有效。
62
+ - `/renew` 的 `command/done` 仍写入旧 Session。只有 `ctx.commands.execute()` 完成后,插件才可释放自己持有的旧 Agent handle;释放 live Agent 不得删除其持久化 Session。
63
+ - `/export` 的命令 handler 只确认“导出已请求”。插件在 `ctx.commands.execute()` 返回、`command/done` 已写入后生成并发送文件,使导出内容包含本次命令生命周期。
64
+
65
+ ## 3. 指标口径
66
+
67
+ `/status` 使用 Session 完整事件日志计算,压缩只改变模型可见表面,不删除历史统计:
68
+
69
+ | 字段 | 口径 |
70
+ |---|---|
71
+ | 模型、推理强度 | 最新 `request/header.config`;没有请求记录时显示“尚未产生模型请求” |
72
+ | 输入/输出 token | 所有 `assistant/message.usage` 的 Session 累计值 |
73
+ | 上下文 | `ctx.tokenMeter.measure(session).totalTokens` / 当前模型 `contextWindow` |
74
+ | 上下文百分比 | `contextTokens / contextWindow × 100%`;未知窗口时不显示百分比 |
75
+ | 缓存命中率 | 最近一次 provider usage 的 `cacheRead / (cacheRead + uncachedInput)`,与现有流式卡片口径一致 |
76
+ | LLM 时间 | 所有完成消息的 `step/start → assistant/message` 时间之和 |
77
+ | 工具时间 | 按 call id 匹配的 `tool/call → tool/result` 时间之和 |
78
+ | 工具事件 | 累计调用数、当前未完成工具、最近 5 次工具名称/结果/耗时 |
79
+ | 平均首 token | `ttftMs / ttftSteps`;没有产生 token 的 step 不进入分母 |
80
+ | 平均生成速度 | `decodeTokens / decodeMs × 1000`;只统计同时具备首 token、完成时间和 output usage 的 step |
81
+
82
+ `/compact` 在执行原生命令前后各调用一次 token meter,并使用相同的上下文窗口展示:
83
+
84
+ ```text
85
+ 压缩前 ████████░░ 78% 99,840 / 128,000
86
+ 压缩后 ███░░░░░░░ 31% 39,680 / 128,000
87
+ 释放 60,160 tokens
88
+ ```
89
+
90
+ 条形图固定为 10 格,并始终伴随精确 token 数和百分比;上下文窗口未知时只显示 token 数。没有可压缩历史时,前后值保持一致并显示 Harness 原生命令结果。
91
+
92
+ ## 4. 持久化设计
93
+
94
+ 现有 `RouteRecord.sessionId` 继续表示当前绑定,不另建第二套路由表。为支持 `/renew` 和 `/history`,在同一记录中增加可选历史字段:
95
+
96
+ ```ts
97
+ sessionHistory?: Array<{
98
+ sessionId: string
99
+ createdAt: number
100
+ }>
101
+ ```
102
+
103
+ 兼容和写入规则:
104
+
105
+ 1. 旧记录没有 `sessionHistory` 时,在内存中规范化为 `[{ sessionId: record.sessionId, createdAt: record.createdAt }]`。
106
+ 2. 新建路由从第一个 Session 起写入历史数组。
107
+ 3. `/renew` 成功创建新 Session 后,用一次 `routes.put()` 同时写入新的 `sessionId` 和追加后的 `sessionHistory`,避免两个表之间出现提交裂缝。
108
+ 4. 同一个 `sessionId` 不重复追加;数组按创建时间升序,当前项始终等于 `RouteRecord.sessionId`。
109
+ 5. 路由 TTL GC 退休插件活动路由;真实话题留下最小墓碑以拒绝后续消息误建 Session,其历史 Session 和工作区由 Harness 持久化层拥有,不随路由 GC 删除。
110
+ 6. 存储域 version 保持现值;新增字段为可选字段,旧介质可直接读取。
111
+
112
+ ## 5. 任务依赖
113
+
114
+ ```text
115
+ SC-01 命令 Host 类型桥与组合依赖
116
+ └─ SC-02 命令准入、授权、串行和结果卡
117
+ ├─ SC-03 /health
118
+ ├─ SC-04 /status
119
+ ├─ SC-05 /workspace
120
+ ├─ SC-06 /compact
121
+ ├─ SC-07 /renew 与历史持久化
122
+ │ └─ SC-08 /history
123
+ └─ SC-09 /export
124
+ └─ SC-10 故障恢复与完整自动化验证
125
+ └─ SC-11 文档、真机验收与交付检查
126
+ ```
127
+
128
+ 任务按上述依赖串行交付;每个任务完成时都必须保持已有普通消息、流式卡片、HITL 审批和重启修复测试通过。
129
+
130
+ ## 6. 实施任务
131
+
132
+ ### SC-01:补齐命令与诊断 Host 类型桥
133
+
134
+ **依赖**:无。
135
+
136
+ **状态**:已完成(2026-08-17)。
137
+
138
+ **工作内容**:
139
+
140
+ - 在插件 `inject` 中声明 `commands`,确保消息接收前 Harness 命令服务已经可用。
141
+ - 在 `src/dsh-types.ts` 增加插件实际消费的最小类型:命令 list/find/execute、Agent options/context、Session event `time`、step end、工具结果 call id、Cordis registry/fiber 状态。
142
+ - 扩展 `FeishuChannel` 最小接口,暴露 WebSocket connection status 和文件发送能力;适配官方 SDK 的 `getConnectionStatus()` 与 `{ file: { source, fileName } }`。
143
+ - 不引入 Harness 运行时包依赖,继续使用当前结构化 Host 类型桥。
144
+
145
+ **交付物**:Host 类型、SDK 适配接口及 stub 更新。
146
+
147
+ **验收条件**:
148
+
149
+ - `pnpm typecheck` 通过。
150
+ - SDK stub 能分别返回 connected、reconnecting、failed 状态并接收文件 Buffer。
151
+ - 插件在缺少 `commands` 的组合中保持 PENDING,而不是接收消息后才失败。
152
+
153
+ ### SC-02:建立统一命令准入与结果呈现
154
+
155
+ **依赖**:SC-01。
156
+
157
+ **状态**:已完成(2026-08-17)。
158
+
159
+ **工作内容**:
160
+
161
+ - 在普通消息路由到 `continueSession()` / `startSession()` 之前增加 slash admission。
162
+ - 保留现有 message-id 去重顺序,保证命令至多处理一次。
163
+ - 实现无 Session `/help`、`/health` 快速路径,以及有 Session 的 `ctx.commands.execute()` 路径。
164
+ - 增加路由所有者校验和每路由异步操作串行器。
165
+ - 在 Agent setup 中注册 channel-scoped 的 `help`、`health`、`status`、`workspace`、`renew`、`history`、`export`;复用全局 `compact`,不重复注册同名命令。
166
+ - 增加统一成功、输入错误、未知命令、未绑定 Session、无权限、busy 和内部错误结果卡。
167
+ - 命令输入、错误和结果不进入 `agent.followup()`,结果卡不接入 `TurnStreamer`。
168
+
169
+ **交付物**:命令解析/调度模块、结果卡纯函数、Service 接入和单元测试。
170
+
171
+ **验收条件**:
172
+
173
+ - Session 命令产生且只产生一对 `command/run` / `command/done`;全局 `/help` 和无 Session `/health` 不伪造 Session 日志。
174
+ - 未知 `/foo` 明确报错,Session 中没有 user/message 或 model turn。
175
+ - 同一 message id 重投不重复压缩、renew、导出或发送结果卡。
176
+ - 群话题中的非所有者不能执行任何 Session 命令。
177
+ - 普通非 slash 文本仍沿用原消息路径。
178
+
179
+ ### SC-03:实现 `/health`
180
+
181
+ **依赖**:SC-02。
182
+
183
+ **状态**:已完成(2026-08-17)。
184
+
185
+ **工作内容**:
186
+
187
+ - 汇总 Harness fiber 状态数量:active、pending、loading、failed、unloading;不展示配置和失败堆栈。
188
+ - 检查本插件依赖的 agents、commands、workspace、storage、credentials、llm、token-meter 服务是否可用。
189
+ - 展示飞书 connection state、最近连接时间、下次重连时间、重连次数和机器人 open_id 的脱敏尾部。
190
+ - 展示插件 started 状态、持久化路由数量、live Agent 数和 in-flight turn 数。
191
+ - 将整体结论归一为 healthy、degraded、unhealthy:连接重试或存在 pending 为 degraded,连接 failed 或关键依赖不可用为 unhealthy。
192
+
193
+ **交付物**:健康快照折叠器、健康卡和状态映射测试。
194
+
195
+ **验收条件**:
196
+
197
+ - 私聊首条消息直接发送 `/health` 时不创建工作区或 Session。
198
+ - connected/reconnecting/failed 三种状态显示正确且不泄漏凭据、完整 open_id 或内部错误堆栈。
199
+ - Harness 有 pending/failed fiber 时不会仍显示全绿 healthy。
200
+
201
+ ### SC-04:实现 `/status`
202
+
203
+ **依赖**:SC-02。
204
+
205
+ **状态**:已完成(2026-08-17)。
206
+
207
+ **工作内容**:
208
+
209
+ - 扩展现有 `src/stats.ts`,在一个纯 fold 中累计 token、LLM/tool 时间、TTFT、decode、工具调用和最近事件。
210
+ - 读取最新 request header 展示模型和推理强度,只读不修改。
211
+ - 复用 token meter 和 model info 获取当前上下文占用及窗口。
212
+ - 渲染指标卡;缺失 usage、token meter、model info 或时间样本时显示“暂无数据”,不显示 `NaN`、`Infinity` 或伪造的 0%。
213
+ - 最近工具事件最多 5 条,不渲染 arguments、message content 或 error cause。
214
+
215
+ **交付物**:完整 Session 状态 fold、状态卡及事件序列测试。
216
+
217
+ **验收条件**:
218
+
219
+ - live 和重启后 resume 的同一日志得到相同统计。
220
+ - 多 step、多工具、失败/取消 step、未完成工具和无 usage 情况均有测试。
221
+ - 平均 TTFT 和 token/s 使用本文件第 3 节口径。
222
+ - `/status` 自身不增加模型 token;新增的命令生命周期事件不改变统计。
223
+
224
+ ### SC-05:实现 `/workspace`
225
+
226
+ **依赖**:SC-02。
227
+
228
+ **状态**:已完成(2026-08-17)。
229
+
230
+ **工作内容**:
231
+
232
+ - 优先读取 `agent.session.header.cwd`,并与 `RouteRecord.workspacePath` 对照。
233
+ - 两者一致时显示绝对路径;不一致时返回明确诊断并记录脱敏警告,不猜测其中一个是正确值。
234
+ - 无 cwd 或路径不可用时返回可操作错误。
235
+
236
+ **交付物**:工作区命令 handler、结果卡和一致性测试。
237
+
238
+ **验收条件**:
239
+
240
+ - 私聊、群聊用户隔离路径显示正确。
241
+ - 路由与 header 不一致时不静默返回错误目录。
242
+ - 非路由所有者无法读取路径。
243
+
244
+ ### SC-06:实现 `/compact` 前后对比
245
+
246
+ **依赖**:SC-02、SC-04 的上下文测量函数。
247
+
248
+ **状态**:已完成(2026-08-17)。
249
+
250
+ **工作内容**:
251
+
252
+ - 执行前捕获 context tokens/window。
253
+ - 确认当前 Agent 能解析到 Harness 全局 `compact` 命令;缺失时明确提示部署未挂载手动压缩能力。
254
+ - 调用 `ctx.commands.execute(agent, '/compact', signal)`,完整保留 Harness busy、无历史、摘要失败和持久化失败语义。
255
+ - 命令完成后重新测量上下文,计算释放 token 和前后百分比,渲染两条 10 格文本条形图。
256
+ - 失败时显示原始上下文仍保持不变;不在插件内实现第二套 compaction。
257
+
258
+ **交付物**:compact adapter、对比卡和命令结果映射测试。
259
+
260
+ **验收条件**:
261
+
262
+ - 成功、无可压缩内容、busy、取消和 backend error 均有稳定结果。
263
+ - 成功结果同时显示精确 token 数和条形图。
264
+ - `/compact` 不产生普通用户消息或模型对话 turn;仅 compaction 自身允许发起辅助摘要请求。
265
+
266
+ ### SC-07:实现 `/renew` 与话题 Session 历史
267
+
268
+ **依赖**:SC-02。
269
+
270
+ **状态**:已完成(2026-08-17)。
271
+
272
+ **工作内容**:
273
+
274
+ - 给 `RouteRecord` 增加可选 `sessionHistory`,实现旧记录规范化、去重和 schema 测试。
275
+ - `/renew` 校验 owner、Agent idle 和 inbox empty,复用原 workspace path、agent options 与 preset composition 创建新 Agent。
276
+ - 在一个 route `put` 中原子切换 `sessionId` 并追加历史;workspace registry 附加新 Session。
277
+ - 新 Agent 创建或路由提交失败时回滚新 handle,保持旧记录和旧 Agent 可用。
278
+ - 命令执行完成后再释放插件拥有的旧 Agent handle;借用的外部 Agent handle 保持 no-op dispose。
279
+ - 结果卡显示旧/新 Session ID 和复用的工作区,不删除或覆盖旧 Session 日志。
280
+
281
+ **交付物**:历史 schema、renew transaction、回滚/恢复测试。
282
+
283
+ **验收条件**:
284
+
285
+ - renew 后同一话题的下一条普通消息只进入新 Session。
286
+ - 旧 Session 仍可由 Harness persistence inspect/resume,日志未删除。
287
+ - 重启后路由仍指向新 Session,历史数组完整。
288
+ - 新 Agent 创建失败、route put 失败、结果卡发送失败三种路径均不会把话题置于无可用 Session 状态。
289
+ - 与正在运行的 turn 或已排队 inbox 冲突时明确拒绝,不启动新 Session。
290
+
291
+ ### SC-08:实现 `/history`
292
+
293
+ **依赖**:SC-07。
294
+
295
+ **状态**:已完成(2026-08-17)。
296
+
297
+ **工作内容**:
298
+
299
+ - 从当前 RouteRecord 规范化历史,不扫描整个 Harness Session 仓库猜测话题归属。
300
+ - 按创建时间倒序显示总数、当前 Session 标记、创建时间和完整 Session ID。
301
+ - 默认展示最近 20 条;超过部分显示“另有 N 条未展示”,暂不增加分页语法。
302
+ - 对旧记录自动显示其当前 Session 作为唯一历史项。
303
+
304
+ **交付物**:历史查询/渲染函数和兼容测试。
305
+
306
+ **验收条件**:
307
+
308
+ - 连续 renew 后历史顺序、总数和当前标记正确。
309
+ - 重启前后输出一致。
310
+ - `/history` 不恢复历史 Agent、不加载历史 Session 完整日志。
311
+ - 非所有者不能枚举 Session ID。
312
+
313
+ ### SC-09:实现 `/export`
314
+
315
+ **依赖**:SC-02。
316
+
317
+ **状态**:已完成(2026-08-17)。
318
+
319
+ **工作内容**:
320
+
321
+ - 注册 channel-scoped `export` 命令,只接受无参数形式。
322
+ - 在 `ctx.commands.execute()` 返回后,对当前不可变 Session 快照生成逻辑 JSONL:第一行是带格式版本的 export manifest 和 Session header,后续每行是一个原始逻辑 SessionEvent。
323
+ - 使用 Node gzip 在内存中压缩,文件名为 `dsh-session-<sessionId>.logical.jsonl.gz`,通过飞书 SDK 文件消息回复到原话题。
324
+ - manifest 明确标记 `logical: true`、导出时间、插件版本,以及“不含 descendants/attachments/raw artifact”。
325
+ - 序列化或文件发送失败时发送独立错误卡;不把失败包装成已成功交付。
326
+
327
+ **交付物**:逻辑日志序列化器、gzip/file sender、格式测试和安全测试。
328
+
329
+ **验收条件**:
330
+
331
+ - 解压文件后逐行 JSON 可解析,header、seq、time、type 和 data 保持不变。
332
+ - 文件包含本次 `/export` 的 `command/run` 和 `command/done`。
333
+ - 文件仅发送一次且只发送到当前话题。
334
+ - 非所有者、未知参数、gzip 失败和飞书 upload/send 失败均有明确结果。
335
+ - README/文件 manifest 不把该格式描述为 Harness 原始物理日志或 Web ZIP。
336
+
337
+ ### SC-10:并发、重启与回归自动化验证
338
+
339
+ **依赖**:SC-03 至 SC-09。
340
+
341
+ **状态**:已完成(2026-08-17)。
342
+
343
+ **工作内容**:
344
+
345
+ - 增加同路由命令与普通消息并发、重复 message id、shutdown/command race 测试。
346
+ - 增加 renew 路由提交与重启恢复、compact busy、export post-command 快照测试。
347
+ - 验证 command result card 不进入 `TurnStreamer`,不污染现有 in-flight 修复表。
348
+ - 回归私聊、普通群、话题群路由、流式分段卡、HITL、去重、GC 和 shutdown。
349
+
350
+ **交付物**:Service/commands/stats/domain/cards 的完整自动化用例。
351
+
352
+ **验收条件**:
353
+
354
+ - `pnpm typecheck` 通过。
355
+ - `pnpm test` 通过。
356
+ - `git diff --check` 通过。
357
+ - 测试不存在真实飞书凭据、真实网络或真实 LLM 依赖。
358
+
359
+ ### SC-11:用户文档与真机验收
360
+
361
+ **依赖**:SC-10。
362
+
363
+ **工作内容**:
364
+
365
+ - 更新 README 的功能、目录结构、行为/降级和安全说明。
366
+ - 更新 `docs/feishu-onboarding.md`,加入命令联调步骤、预期卡面和故障排查。
367
+ - 更新 `docs/persistence.md`,记录 `sessionHistory` 的所有权、兼容读取、renew 提交和 GC 语义。
368
+ - 按下一节执行真机验收,并在本文档的记录栏填写日期、环境和结论。
369
+
370
+ **交付物**:用户文档、持久化文档、真机验收记录。
371
+
372
+ **验收条件**:
373
+
374
+ - 文档中的命令列表与实际注册表一致。
375
+ - 所有相对链接可解析,Markdown 无死链。
376
+ - 真机验收没有把命令输入发送给模型,没有出现跨用户 Session 信息泄漏。
377
+
378
+ ## 7. 真机验收记录
379
+
380
+ > 实施完成前保持“未执行”;不得用 stub/单元测试结果代替真机结论。
381
+
382
+ | 编号 | 场景 | 操作与预期 | 状态 | 日期/证据 |
383
+ |---|---|---|---|---|
384
+ | MV-00 | 命令帮助 | 新私聊首条发送 `/help`;返回完整命令与说明,不创建 Session | 未执行 | — |
385
+ | MV-01 | 无 Session 健康检查 | 私聊首条发送 `/health`;返回健康卡,不创建 Session | 未执行 | — |
386
+ | MV-02 | 已绑定健康检查 | 话题内 `/health`;显示 Harness 与飞书连接状态 | 未执行 | — |
387
+ | MV-03 | Session 状态 | 完成含工具调用的 turn 后 `/status`;模型、effort、token、上下文、耗时和工具事件正确 | 未执行 | — |
388
+ | MV-04 | 工作区 | 私聊和群聊分别 `/workspace`;路径符合用户隔离规则 | 未执行 | — |
389
+ | MV-05 | 压缩成功 | 构造可压缩历史后 `/compact`;显示前后数值和条形图 | 未执行 | — |
390
+ | MV-06 | 压缩 busy | turn 运行中发送 `/compact`;明确拒绝且不破坏当前回复 | 未执行 | — |
391
+ | MV-07 | Renew | idle 话题执行 `/renew`;下一条消息进入新 Session,工作区不变 | 未执行 | — |
392
+ | MV-08 | History | 至少 renew 两次后 `/history`;顺序、总数和当前标记正确 | 未执行 | — |
393
+ | MV-09 | 重启恢复 | renew 后重启插件;`/history` 和当前绑定保持不变 | 未执行 | — |
394
+ | MV-10 | Export | `/export`;收到 gzip 文件,解压后包含本次命令生命周期 | 未执行 | — |
395
+ | MV-11 | 未知命令 | 发送 `/foo`;返回支持列表,模型没有收到该文本 | 未执行 | — |
396
+ | MV-12 | 跨用户拒绝 | 群话题内由另一用户执行 `/status`、`/workspace`、`/history`、`/export`;全部拒绝 | 未执行 | — |
397
+ | MV-13 | 飞书重投 | 重放同一 message id 的 `/renew` 或 `/export`;副作用只发生一次 | 未执行 | — |
398
+ | MV-14 | 通道重连 | WebSocket reconnecting/reconnected 期间检查 `/health`;状态转换准确 | 未执行 | — |
399
+
400
+ ## 8. 完成交付标准
401
+
402
+ 只有同时满足以下条件,斜杠命令功能才算完成:
403
+
404
+ - 七个命令全部符合第 1 节行为,排除项没有被重新引入。
405
+ - 所有 Session 命令都执行 owner 校验;日志、路径和 Session ID 不泄漏给其他群成员。
406
+ - 命令输入与直接结果不进入模型上下文,未知命令也不退化为普通 prompt。
407
+ - `/renew` 在失败和重启场景中不丢旧 Session,不产生无路由话题。
408
+ - `/export` 格式和能力边界描述准确,文件包含本次命令生命周期。
409
+ - 自动化检查全部通过,真机验收 MV-01 至 MV-14 全部记录为通过。
410
+ - 本轮实现仅修改 `dsh_channels`;若发现必须修改 Harness 的阻塞项,应停止扩大范围并重新评审,而不是隐式跨仓库修改。