@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.
- package/README.md +302 -2
- package/cordis.patch.yml +3 -0
- package/docs/access-control-design.md +550 -0
- package/docs/assets/web-settings-panel.png +0 -0
- package/docs/assets/web-settings-restart-pending.png +0 -0
- package/docs/feasibility-and-design.md +213 -0
- package/docs/feishu-onboarding.md +268 -0
- package/docs/persistence.md +199 -0
- package/docs/refactor-plan.md +56 -0
- package/docs/review-dsh-0.2.0.md +389 -0
- package/docs/slash-commands-implementation-plan.md +410 -0
- package/docs/web-display-only-surface-roadmap.md +363 -0
- package/lib/client.js +2226 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +10217 -0
- package/lib/index.js.map +1 -0
- package/package.json +98 -4
- package/src/access/cards.ts +188 -0
- package/src/access/controller.ts +201 -0
- package/src/access/index.ts +13 -0
- package/src/access/policy.ts +103 -0
- package/src/access/requests.ts +512 -0
- package/src/access/store.ts +220 -0
- package/src/approvals.ts +8 -0
- package/src/cards/approval.ts +114 -0
- package/src/cards/index.ts +11 -0
- package/src/cards/primitives.ts +82 -0
- package/src/cards/question.ts +141 -0
- package/src/cards/streaming.ts +563 -0
- package/src/cards.ts +8 -0
- package/src/cleanup.ts +398 -0
- package/src/client/api.ts +262 -0
- package/src/client/catalog.ts +92 -0
- package/src/client/index.ts +61 -0
- package/src/client/panel.tsx +1475 -0
- package/src/client/styles.ts +98 -0
- package/src/commands/registry.ts +155 -0
- package/src/commands.ts +335 -0
- package/src/config.ts +206 -0
- package/src/domain.ts +428 -0
- package/src/dsh-types.ts +11 -0
- package/src/feishu/agent-context.ts +62 -0
- package/src/feishu/delivery.ts +188 -0
- package/src/feishu/inbound-attachments.ts +436 -0
- package/src/feishu-channel.ts +339 -0
- package/src/health.ts +233 -0
- package/src/host/agent.ts +181 -0
- package/src/host/commands.ts +63 -0
- package/src/host/core.ts +345 -0
- package/src/host/index.ts +12 -0
- package/src/host/interaction.ts +101 -0
- package/src/host/services.ts +320 -0
- package/src/index.ts +122 -0
- package/src/interactions/approvals.ts +154 -0
- package/src/interactions/pending.ts +162 -0
- package/src/interactions/questions.ts +274 -0
- package/src/questions.ts +8 -0
- package/src/routing/inbound.ts +524 -0
- package/src/service.ts +2138 -0
- package/src/session/manager.ts +352 -0
- package/src/session/repair.ts +157 -0
- package/src/session/request-policy.ts +192 -0
- package/src/session/routes.ts +165 -0
- package/src/session-commands.ts +310 -0
- package/src/session-export.ts +112 -0
- package/src/settings/catalog.ts +309 -0
- package/src/settings/contract.ts +508 -0
- package/src/settings/overlay.ts +164 -0
- package/src/settings/routes.ts +409 -0
- package/src/settings/surface.ts +773 -0
- package/src/stats.ts +485 -0
- package/src/streamer.ts +1108 -0
- package/src/workspaces.ts +199 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# 持久化现状与缺口
|
|
2
|
+
|
|
3
|
+
本文档记录 dsh-channels 的持久化盘点(2026-08 整理)与缺口修复进度。总体分层:
|
|
4
|
+
|
|
5
|
+
| 层 | 负责方 | 落点 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| 会话本体(事件日志 + header) | 宿主 `dsh-session-persistence` | `$DSH_HOME/sessions/<workspace>/<sessionId>/session.jsonl.zstd` |
|
|
8
|
+
| 插件自有状态 | 插件 → `ctx.storageDomain` | 域 `channels_routes`,JSON 后端为 `$DSH_HOME/storages/channels_routes.json` |
|
|
9
|
+
| 工作区 | 磁盘目录 + 宿主 registry | `<baseDir>/p2p|groups/...`(`baseDir` 可在 Web 面板配置),注册表 `$DSH_HOME/storages/workspace.json` |
|
|
10
|
+
|
|
11
|
+
## 一、已持久化的内容
|
|
12
|
+
|
|
13
|
+
1. **路由与过期墓碑**(`src/domain.ts` 域 `channels_routes`):`routes` 表以 `thread_id` 或降级键 `chat:p2p|group:<chatId>:<openId>` 保存活动 `RouteRecord`(sessionId / chatId / chatType / senderOpenId / workspacePath / cardMessageId / createdAt / lastActivityAt);`expired_routes` 以 thread id 保存 TTL 退休后的最小墓碑,跨重启拒绝旧话题误建 Session。`cardMessageId` 每 turn 更新,`lastActivityAt` 供路由退休使用。
|
|
14
|
+
2. **会话本体与事件日志**:宿主代持。插件消费的 `assistant/chunk`、`tool/call`、`tool-workflow/run-*`、`subagent/descriptor` 等全部在持久化日志里,因此 `src/stats.ts` 的 token/缓存统计不单独存,`foldAll()` 重放日志,重启后继续累计。`meta.cwd`、`agentPreset` 随 header 落盘。
|
|
15
|
+
3. **工作区**:目录确定性生成、幂等 mkdir;宿主 `workspace.json` 持久化 path/title/sessionIds。
|
|
16
|
+
4. **凭据**:密钥存宿主 `$DSH_HOME/.credentials.yaml`,插件只持引用名。
|
|
17
|
+
5. **准入授权**(`src/domain.ts` 域 `channels_routes`,同样保持 `version: 1` 加法新增):`access_groups` 表以 `chat_id` 保存已授权群(群名/来源/授权人/时间);`access_users` 表以 `open_id` 保存全局用户白名单;`access_requests` 表以不透明 token 保存入群/用户授权申请(含 `messageId`,重启后旧授权卡仍可点击结算;过期状态在同一表落审计)。管理员 open_id 与准入模式属于配置层(profile + Web 覆盖),不在这三张表里。
|
|
18
|
+
|
|
19
|
+
## 二、内存态(有意不持久化)
|
|
20
|
+
|
|
21
|
+
- `TurnStreamer` 全部 in-flight 状态(正文/推理 buffer、段卡列表、phase、活动 fold 等)——**缺口 1 处理对象**
|
|
22
|
+
- live agent 注册表、`routeCache`、`pendingResume`、`contextWindows` 缓存、`botOpenId`——缓存/懒恢复性质,重启重建
|
|
23
|
+
|
|
24
|
+
## 三、缺口清单
|
|
25
|
+
|
|
26
|
+
| # | 缺口 | 影响 | 状态 |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| 1 | in-flight turn 快照:重启后卡片/表情无法修复 | 重启(含插件热更新)发生在回复中途时,飞书端卡片永远停在「推理中/回复中」,Typing 表情残留、DONE 永不补 | ✅ 已修复(见下文) |
|
|
29
|
+
| 2 | 消息幂等去重:WS 重连/重投会重复处理同一 message id | 同一条用户消息可能触发两次 followup 或重复建 session | ✅ 已修复(见下文) |
|
|
30
|
+
| 3 | 路由表 GC:活动记录永不退休 | 话题长期不用后仍保留完整 Session 路由;直接删除又会让旧话题误建新 Session | ✅ 已修复(见下文) |
|
|
31
|
+
| 4 | 段卡 message id 持久化 | 重启后无法引用/修复旧续卡(已随缺口 1 一并解决) | ✅ 已修复(随缺口 1) |
|
|
32
|
+
| 5 | 路由并发窗口:同话题并发首消息会重复建 session | 两 session 两路由,后写覆盖 | ✅ 已修复(见下文) |
|
|
33
|
+
|
|
34
|
+
缺口 5 修复前的可见症状(测试实证):话题第二条消息在首条消息的 `persistRoute` 落盘**之前**到达时,群聊无 @ 的消息会被静默丢弃;有 @ 的消息则会重复建 session、路由后写覆盖前写、先建立的 session 沦为孤儿。
|
|
35
|
+
|
|
36
|
+
明确**不需要**持久化的:统计类数据(日志重放)、`contextWindows`(惰性缓存)、工作区↔会话映射(宿主 registry 已持久化)。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# 缺口 1:in-flight turn 快照与重启修复(已实现)
|
|
41
|
+
|
|
42
|
+
## 问题
|
|
43
|
+
|
|
44
|
+
一条 turn 的全部「修复凭据」都是内存态:trigger 消息 id、Typing reaction id、所有段卡 message id、turn 号、phase。重启发生在 turn 中途时,这些信息全部丢失,飞书端的流式卡片成为僵尸卡。
|
|
45
|
+
|
|
46
|
+
## 方案
|
|
47
|
+
|
|
48
|
+
在 `channels_routes` 域**新增 `inflight` 表**(加法操作,域 `version` 保持 `1` —— 宿主语义:介质版本与 spec 版本不一致会 `version-mismatch` 拒绝打开,因此**不能** bump;spec 里新增的表在介质上不存在时按空表加载)。
|
|
49
|
+
|
|
50
|
+
**键**:主卡(每 turn 第一张卡)的 message id —— 每 turn 唯一且 attach 时已知,避免并发 turn 互相覆盖。
|
|
51
|
+
|
|
52
|
+
**值** `InflightSnapshot`:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
{
|
|
56
|
+
key: string // = 主卡 message id
|
|
57
|
+
sessionId: string
|
|
58
|
+
chatId: string // 修复操作(表情/卡片 patch)的目标会话
|
|
59
|
+
triggerMessageId: string // Typing/DONE 表情锚点(用户触发消息)
|
|
60
|
+
reactionId?: string // Typing 表情的 reaction id(add 失败时缺省)
|
|
61
|
+
cards: Array<{ messageId: string; mode: 'card' | 'text' }> // 段卡列表,[0] = 主卡
|
|
62
|
+
startedAt: number // 附加时间(未来 TTL 清理可用)
|
|
63
|
+
attempts: number // 修复重试计数(上限 2,防永久重试循环)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 生命周期
|
|
68
|
+
|
|
69
|
+
1. **写入**(`service.ts`,`startSession` / `continueSession`):回复卡发出、Typing 结果就绪后**先写快照、再写路由**(快照优先:崩溃窗口更偏安全侧——最多产生孤儿快照,修复无害)。
|
|
70
|
+
2. **段卡更新**(`streamer.ts`):续卡发送成功、message id 解析后经新钩子 `onSegmentDelivered(mainCardMessageId, index, messageId, mode)` 上报;服务端通过 `table.update()`(宿主原子 read-modify-write)合并进快照。
|
|
71
|
+
3. **清除**(`streamer.ts` `finalize`):turn 结束即经 `onTurnFinalized(sessionId, turn, mainCardMessageId)` 删除快照。会话创建失败路径(卡片已落「会话创建失败」终态)同样主动清除。
|
|
72
|
+
4. **修复**(`service.ts` `start()`,长连接建立后后台执行 `repairInflight()`):
|
|
73
|
+
- 对每条快照:所有 `mode: 'card'` 的卡 patch 为终态中断卡(主卡带折叠面板,续卡带分段头,正文为「⚠️ 服务已重启,本次回复中断,请重新发送消息。」);移除 Typing、补 DONE(表情操作 best-effort)。
|
|
74
|
+
- 成功后删除快照;失败则 `attempts + 1`,达到 2 次后删除(一次性修复 + 有限重试,防止对无法接受 patch 的介质无限重试)。
|
|
75
|
+
|
|
76
|
+
## 已知取舍(v1)
|
|
77
|
+
|
|
78
|
+
- 修复卡**全量替换**原卡:中断前已流出的部分正文会被替换掉(本地没有旧正文副本,patch 是全量语义)。
|
|
79
|
+
- 残余竞态窗口:`finalize` 同步删快照,而最终 patch 的 HTTP 请求尚未落定;若恰在此微秒级窗口崩溃,卡片可能被再次 patch 成中断卡(幂等无害)。
|
|
80
|
+
- `text` 降级段卡内容已完整送达,修复时跳过(不 patch)。
|
|
81
|
+
- 修复不依赖 session 恢复(不读 token 统计),中断卡不带虚假的 token 行。
|
|
82
|
+
|
|
83
|
+
## 涉及文件
|
|
84
|
+
|
|
85
|
+
- `src/domain.ts` — `inflight` 表 schema / 句柄
|
|
86
|
+
- `src/service.ts` — 快照写入/更新/清除、`repairInflight()` 启动修复
|
|
87
|
+
- `src/streamer.ts` — 钩子扩展:`onTurnFinalized` 携带主卡 id、新增 `onSegmentDelivered`
|
|
88
|
+
- `src/cards.ts` — `buildInterruptionCard()` 终态中断卡
|
|
89
|
+
- `tests/` — 快照生命周期与启动修复用例
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
# 缺口 5:会话建立串行化(已实现)
|
|
94
|
+
|
|
95
|
+
## 问题
|
|
96
|
+
|
|
97
|
+
`handleMessage` 全链路异步:两条首消息几乎同时到达同一 (chat, user) 时,两条都会在路由未落盘时走 `startSession` —— 重复建 session、建话题、路由后写覆盖,先建立的 session 沦为孤儿;若第二条在话题内且无 @,则被静默丢弃(无路由、无 mention)。
|
|
98
|
+
|
|
99
|
+
## 方案
|
|
100
|
+
|
|
101
|
+
**内存方案**(不需要持久化;路由一旦落盘,后续消息自然走路由表):
|
|
102
|
+
|
|
103
|
+
- `FeishuService` 新增 `establishing: Map<chatKey, Promise<RouteRecord>>` —— 会话建立的 in-flight 注册表。
|
|
104
|
+
- `startSession` 拆为两层:
|
|
105
|
+
- 包装层:**同步**计算 chat 级键(任何网络操作之前即可确定),注册建立 promise;settle 后移除条目。
|
|
106
|
+
- `establishSession`:原建立流程,返回 `RouteRecord`(路由落盘后才 resolve)。
|
|
107
|
+
- `handleMessage` 路由顺序扩展一步:thread 路由 → chat 路由 → **在途建立**。命中在途建立时 `await` 它,然后按串行语义重路由:
|
|
108
|
+
- 建立的记录是 `thread` 且本消息的 `threadId === record.key` → 话题内竞态消息 → `continueSession` 同一 session(这正是原来被丢弃/分叉的场景);
|
|
109
|
+
- 建立的记录是 `chat`(降级模式)→ 同 chat 消息 → `continueSession`;
|
|
110
|
+
- 其余(本消息不在该话题内)→ 落回 `startSession` 自己的新 session(与串行到达等价);
|
|
111
|
+
- 建立失败 → 落回自己的 `startSession`(首条消息的失败已由它自己的卡片呈现)。
|
|
112
|
+
|
|
113
|
+
## 语义保证
|
|
114
|
+
|
|
115
|
+
- 每个 (chat, user) 同时至多一个建立流程;等待者拿到 settle 后的记录再决定续聊或另起,**路由写入不再有并发窗口**。
|
|
116
|
+
- 不改变任何既有路由语义:话题内免 @、降级 chat 路由优先、群聊 @ 要求、新消息开新话题全部保持串行等价。
|
|
117
|
+
|
|
118
|
+
## 已知取舍(v1)
|
|
119
|
+
|
|
120
|
+
- 建立期间到达的、不在该新话题内的消息会**等待**到建立完成才处理(而非立即建自己的 session)——毫秒级延迟,换来路由表无竞争。
|
|
121
|
+
- 条目是内存态:跨实例无需共享(另一实例的并发首消息由路由表落盘后的正常路径接管,极端情况下双实例同时建 session 的窗口不在本方案范围内)。
|
|
122
|
+
|
|
123
|
+
## 涉及文件
|
|
124
|
+
|
|
125
|
+
- `src/service.ts` — `establishing` 注册表、`startSession`/`establishSession` 拆分、`handleMessage` 在途建立分支
|
|
126
|
+
- `tests/service.test.ts` — 4 个并发用例(话题内竞态续聊、降级并发、建立失败回落、旁路消息另起 session)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
# 缺口 3:路由表 GC(已实现)
|
|
131
|
+
|
|
132
|
+
## 问题
|
|
133
|
+
|
|
134
|
+
`routes` 表只增不减会让长期不用的话题一直保留完整 Session 路由;但直接删除又会把后来出现在同一可见话题中的消息误判为新对话,静默创建没有旧上下文的新 Session。`lastActivityAt` 用于退休活动路由,最小墓碑用于跨重启拒绝旧话题。in-flight 快照同样存在老化残留(跨多轮重启的僵尸快照)。
|
|
135
|
+
|
|
136
|
+
## 方案
|
|
137
|
+
|
|
138
|
+
- **配置** `routeTtlMs`(默认 `DEFAULT_ROUTE_TTL_MS` = 30 天,`0` = 关闭):超过该时长无活动的真实话题路由视为过期。
|
|
139
|
+
- **清扫时机**:启动时一次(长连接建立后,与中断修复同批后台任务)+ 每 6 小时一次的周期清扫(`GC_SWEEP_INTERVAL_MS`,unref 不阻塞进程退出,shutdown 时清除定时器)。
|
|
140
|
+
- **清扫内容**(`gcExpiredEntries()`):
|
|
141
|
+
- `routes` 表中过期的 `thread` 记录:先写入 `expired_routes` 最小持久化墓碑,再删除活动记录并同步清 `routeCache`;后续消息和 Session 命令明确拒绝,不恢复也不新建 Session;
|
|
142
|
+
- 过期的降级 `chat` 路由:直接删除(它没有可辨识的独立话题边界);
|
|
143
|
+
- `inflight` 表:`startedAt < cutoff` 的僵尸快照**静默删除**——不 patch 卡片(给陈年老卡打"服务已重启"补丁反而惊扰用户);
|
|
144
|
+
- 单键处理失败只 warn 跳过,不中断整体清扫。
|
|
145
|
+
- **与中断修复的分工**:`repairInflight()` 在 TTL 开启时跳过超龄快照(GC 的职责),两轮后台任务无论执行顺序如何都收敛一致;TTL 关闭时修复逻辑不变(照常修复全部快照)。
|
|
146
|
+
- **边界**:只退休插件自己的路由条目,宿主侧的 Session / 工作区不动;被 GC 的真实话题后来再有消息会命中过期墓碑并明确拒绝,绝不在旧话题中新建 Session。`/help`、`/userid`、`/groupid`、`/health` 不依赖 Session,仍可执行。
|
|
147
|
+
|
|
148
|
+
## 语义保证
|
|
149
|
+
|
|
150
|
+
- 活跃话题(每 turn 都会刷新 `lastActivityAt`)永远不会被误删。
|
|
151
|
+
- 过期拒绝是持久的:先写墓碑再删活动路由,即使两步之间重启,墓碑仍优先阻止误建 Session。
|
|
152
|
+
|
|
153
|
+
## 已知取舍(v1)
|
|
154
|
+
|
|
155
|
+
- 未监听飞书侧"群解散/话题删除"事件做主动清理(TTL 兜底,事件清理列为后续可选优化)。
|
|
156
|
+
- 墓碑为避免跨重启误建 Session 而持久保留;`routeTtlMs: 0` 只关闭后续过期处理,不自动解除已有墓碑。
|
|
157
|
+
- 跨实例共享同一介质时,各实例各自清扫同一套记录——幂等无害。
|
|
158
|
+
|
|
159
|
+
## 涉及文件
|
|
160
|
+
|
|
161
|
+
- `src/config.ts` — `routeTtlMs` 配置项 + `DEFAULT_ROUTE_TTL_MS`
|
|
162
|
+
- `src/service.ts` — `gcExpiredEntries()`、启动/周期清扫接线、`repairInflight` 超龄跳过
|
|
163
|
+
- `tests/service.test.ts` — GC 用例(启动退休、墓碑跨重启拒绝、命令拒绝、降级路由清理、僵尸快照静默删除、0 关闭)
|
|
164
|
+
- `tests/config.test.ts` — `routeTtlMs` 默认值 / 0 / 负值校验
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
# 缺口 2:消息幂等去重(已实现)
|
|
169
|
+
|
|
170
|
+
## 问题
|
|
171
|
+
|
|
172
|
+
飞书 WS 长连接在重连时会重投未 ack 的事件:同一条 `im.message.receive_v1`(同 message id)会被处理两次——命中路由 → 重复 `followup` + 重复回复卡;首消息 → 重复建 session(缺口 5 的门闸会让第二条重复等待后再续聊,同样产生重复 turn)。飞书 message id 全局唯一,天然是幂等键。
|
|
173
|
+
|
|
174
|
+
## 方案
|
|
175
|
+
|
|
176
|
+
**双层守卫**:
|
|
177
|
+
|
|
178
|
+
1. **进程内同步守卫** `accepted: Map<messageId, acceptedAt>`:`handleMessage` 入口**同步**登记,堵住"持久化查了未写"的窗口(同进程内同 id 的两次投递,第二次在第一次的 `put` 落盘前到达也会命中内存守卫);持久化记录落盘后即移除内存条目,只有 put 失败时才滞留到 GC 清扫。
|
|
179
|
+
2. **持久化记录**:域新增 `processed` 表(键 = Feishu message id,值 `{ key, processedAt }`),**在任何副作用之前** `await put` 落盘——同一时刻最多一份投递进入副作用阶段(每 message id 至多一次语义);崩溃窗口收窄到"接受之前",接受后重启重投也会被持久化记录挡下。
|
|
180
|
+
|
|
181
|
+
- **放置位置**:机器人自回、非 p2p/group、空内容、dmMode 过滤之后、路由决策之前;群聊无 @ 被丢弃的消息同样记录(重投时同样丢弃,行为一致)。
|
|
182
|
+
- **记录失败**:只 warn 不阻塞(内存守卫仍覆盖本进程;失败只略增崩溃窗口)。
|
|
183
|
+
- **清扫**:`processed` 记录与内存滞留条目由 GC 清扫**无条件**清理(`PROCESSED_RETENTION_MS` = 24 h——重投窗口是分钟级,24 h 是 100× 以上余量),与 `routeTtlMs` 是否开启无关;启动时 + 每 6 h 周期。
|
|
184
|
+
|
|
185
|
+
## 语义保证
|
|
186
|
+
|
|
187
|
+
- 每条 message id 至多被处理一次:同进程并发投递(内存守卫)、跨重启重投(持久化记录)都只会看到零副作用。
|
|
188
|
+
- 至多一次的代价:接受后、副作用完成前崩溃 → 该消息丢失——但中断修复卡本身提示"请重新发送消息",用户可自然重发(新 message id 正常处理)。
|
|
189
|
+
|
|
190
|
+
## 已知取舍(v1)
|
|
191
|
+
|
|
192
|
+
- 群聊无 @ 丢弃的消息也被记录(存储噪音可忽略,换取单点实现与全路径一致性)。
|
|
193
|
+
- 不依赖飞书 SDK 的 ack 机制(黑盒),以"先记录、后处理"自实现幂等。
|
|
194
|
+
|
|
195
|
+
## 涉及文件
|
|
196
|
+
|
|
197
|
+
- `src/domain.ts` — `processed` 表 schema / `ProcessedTable` 句柄
|
|
198
|
+
- `src/service.ts` — `accepted` 内存守卫、`handleMessage` 幂等门、`gcExpiredEntries` 无条件清扫 processed
|
|
199
|
+
- `tests/service.test.ts` — 4 个用例(首消息重投、续聊重投、跨重启重投、processed 记录 GC)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 重构计划与进度
|
|
2
|
+
|
|
3
|
+
> 目标:让 dsh-channels **更条理**(分层清晰、单一职责)、**更优雅**(去重、统一抽象、单一事实来源)、**更可用**(模型/权限/图片/历史等能力补齐)。
|
|
4
|
+
> 约束:对外行为与公开 API 保持不变;每个阶段结束跑 `tsc --noEmit` + 全量 `vitest`,保持 263 个测试全绿作为安全网。
|
|
5
|
+
|
|
6
|
+
## 目标结构
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
src/
|
|
10
|
+
index.ts / config.ts / service.ts # 入口、配置、门面编排
|
|
11
|
+
host/ # DSH 宿主类型桥(按域拆分)
|
|
12
|
+
cards/ # 卡片 2.0 渲染(原语 + 各表面)
|
|
13
|
+
interactions/ # HITL:共享待决生命周期 + 审批 + 问答
|
|
14
|
+
session/ # 路由存储、重启修复、会话管理、请求策略
|
|
15
|
+
routing/ # 入站去重 / 准入 / 路由决策树
|
|
16
|
+
feishu/ # 通道适配 + 出站投递 + 入站图片
|
|
17
|
+
commands/ # 命令注册表与分派
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 阶段进度
|
|
21
|
+
|
|
22
|
+
| 阶段 | 内容 | 状态 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| 1a | `dsh-types.ts`(824) → `host/{core,agent,commands,interaction,services}` + barrel | ✅ |
|
|
25
|
+
| 1b | `cards.ts`(792) → `cards/{primitives,streaming,approval,question}` + barrel;9 处卡片骨架收敛为原语 | ✅ |
|
|
26
|
+
| 2 | `interactions/pending.ts`:审批/问答共享待决卡生命周期(送达竞态、owner 门禁、超时、settle-once、撤回),两应答器各减约 40% 重复 | ✅ |
|
|
27
|
+
| 3a | `session/routes.ts`:RouteStore(缓存、TTL 墓碑、按会话查找) | ✅ |
|
|
28
|
+
| 3b | `session/repair.ts`:InflightRepair(快照、分段修正、启动修复) | ✅ |
|
|
29
|
+
| 3c | `feishu/delivery.ts`:CardDelivery(首帧话题卡、结果卡、分段降级、命令表情生命周期) | ✅ |
|
|
30
|
+
| 4 | `session/manager.ts`:live agent 借用/冷恢复、preset 组合、`/renew` 事务、推理强度挂载 | ✅ |
|
|
31
|
+
| 5 | `commands/registry.ts` + 注册表驱动分派:命令词表/准入/`/help` 单一事实来源;`/new` 别名 | ✅ |
|
|
32
|
+
| 6 | `routing/inbound.ts`:入站去重、准入、路由决策树、建/续会话、首触引导从门面拆出 | ✅ |
|
|
33
|
+
|
|
34
|
+
行数变化:`src/service.ts` **1902 → 1381**;宿主类型 1 文件 → 5 模块;卡片 1 文件 → 4 模块 + 原语;新增共享交互基类、命令注册表、会话管理器、路由存储、重启修复、投递层。测试 240 → **274**。
|
|
35
|
+
|
|
36
|
+
## 可用性增强(阶段 7,待做)
|
|
37
|
+
|
|
38
|
+
| 功能 | 状态 | 备注 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `/permissions` 权限预设 | ✅ | 默认只读展示;`allowPermissionSwitch: true` 才允许切换 |
|
|
41
|
+
| `/status` 增强 | ✅ | 权限预设、工作区、插件运行时长、live Agent / 路由数 |
|
|
42
|
+
| `/help` 分组 | ✅ | 由命令注册表派生,含 usage 提示 |
|
|
43
|
+
| `/new` 别名 | ✅ | 解析到 `/renew`,命令行按规范名执行 |
|
|
44
|
+
| 首触引导卡 | ✅ | 私聊首次触达一张,`welcomedChats` 持久化去重,`welcomeOnboarding` 可关 |
|
|
45
|
+
| 问答卡自由文本回答 | ✅ | 无选项问题直接开放答题;阻塞期间普通文本回复即答案(`custom`) |
|
|
46
|
+
| `/model` 查看与切换 | ✅ | `session/request-policy.ts` 统一 provider/model/effort;`ctx.llm.listModels` 校验;经 `command/run` 持久化 |
|
|
47
|
+
| 附件输入 | ✅ | `feishu/inbound-attachments.ts`:图片/文件流式落盘到工作区 `attachments/`、合并转发记录转存 `.txt`;不触发模型,Agent 用工具按需读取;`acceptAttachments` 可配 |
|
|
48
|
+
| 工作区清理 | ✅ | `cleanup.ts` 纯策略 + 设置面板卡片:`workspaceRetention`(off/7/30/90/180 天)+ 手动/每日清理,删除插件工作区并把路由退休为墓碑,Session 日志保留 |
|
|
49
|
+
|
|
50
|
+
## 验证基线
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd /Users/moka/dev/project/dsh_ext/dsh_channels
|
|
54
|
+
node_modules/.bin/tsc --noEmit
|
|
55
|
+
node_modules/.bin/vitest run # 17 files, 287 tests
|
|
56
|
+
```
|