@modesto_ma/dsh-channels 0.4.0 → 0.5.0-beta.1
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 +73 -55
- package/docs/access-control-design.md +7 -5
- package/docs/attachment-pipeline-v2.md +33 -41
- package/docs/feishu-onboarding.md +33 -33
- package/docs/persistence.md +6 -4
- package/docs/refactor-plan.md +1 -1
- package/docs/workspace-isolation-design.md +7 -8
- package/lib/client.js +173 -287
- package/lib/client.js.map +1 -1
- package/lib/index.js +3001 -2627
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/cards.ts +9 -9
- package/src/access/controller.ts +10 -10
- package/src/access/index.ts +1 -0
- package/src/access/policy.ts +10 -10
- package/src/access/requests.ts +32 -31
- package/src/access/store.ts +12 -12
- package/src/access/targets.ts +56 -0
- package/src/cards/approval.ts +96 -26
- package/src/cards/index.ts +1 -0
- package/src/cards/onboarding.ts +1 -0
- package/src/cards/picker.ts +115 -0
- package/src/cards/streaming.ts +56 -7
- package/src/client/api.ts +5 -25
- package/src/client/panel/access-card.tsx +24 -15
- package/src/client/panel/access-tab.tsx +3 -3
- package/src/client/panel/connection-tab.tsx +70 -1
- package/src/client/panel/index.tsx +11 -62
- package/src/client/panel/shared.tsx +0 -23
- package/src/client/panel/workspace-tab.tsx +2 -18
- package/src/client/styles.ts +2 -0
- package/src/commands/registry.ts +2 -2
- package/src/config.ts +25 -56
- package/src/domain.ts +28 -125
- package/src/feishu/inbound-attachments.ts +1 -1
- package/src/feishu/manifest.ts +8 -31
- package/src/feishu/message-read.ts +19 -22
- package/src/feishu/scopes.ts +171 -0
- package/src/feishu/send-file.ts +543 -0
- package/src/feishu/slash-commands.ts +134 -0
- package/src/feishu/tools.ts +72 -112
- package/src/feishu-channel.ts +270 -9
- package/src/index.ts +5 -4
- package/src/interactions/approvals.ts +187 -7
- package/src/interactions/pending.ts +3 -3
- package/src/routing/inbound.ts +73 -106
- package/src/service.ts +463 -438
- package/src/session/manager.ts +1 -1
- package/src/session/routes.ts +2 -3
- package/src/settings/contract.ts +68 -85
- package/src/settings/routes.ts +3 -17
- package/src/settings/surface.ts +37 -88
- package/src/streamer.ts +94 -5
- package/src/workspaces.ts +21 -29
- package/src/cleanup.ts +0 -546
- package/src/client/panel/cleanup-card.tsx +0 -106
- package/src/feishu/pending-attachments.ts +0 -562
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- 飞书私聊 / 对话群 / 话题群收到消息时,**会话、话题、流式卡片零等待**:不再等附件下载完才开始模型回合。
|
|
12
12
|
- 触发消息中的图片预取为原生 `ImageBlock`,模型首轮即可看见;预取有上限与短超时,失败降级为清单。
|
|
13
13
|
- 文件默认**不下载**;prompt 用结构化清单声明“附件未加载”,模型需要时调用 `feishu_attachment_fetch` 按 `message_id` 懒取。
|
|
14
|
-
- 纯附件消息(私聊仅附件、群未
|
|
14
|
+
- 纯附件消息(私聊仅附件、群未 @、已绑定话题内仅附件)**保持静默**:不回复、不建会话、不下载、不加表情、本地不留状态;用户引用该消息(多个文件可合并转发后引用)再提问时进入清单并可懒取。
|
|
15
15
|
- 引用消息与话题历史可读:`<feishu_quote>` 摘录、首轮 `<feishu_history>`、`feishu_topic_messages` 工具翻页;发现的资源同时获得 fetch 授权。
|
|
16
16
|
- 所有入站附件(图片与文件)统一存入 DSH 原生附件库:不可变、内容寻址去重、读取校验、随 Session 回放;插件不再承担入站附件清理。
|
|
17
17
|
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
| 决定 | 内容 |
|
|
30
30
|
| --- | --- |
|
|
31
31
|
| D1 | 采用“立即建会话 + 清单/摘录进 prompt + 图片预取 + 文件懒取”总体方案 |
|
|
32
|
-
| D2 |
|
|
32
|
+
| D2 | 仅附件消息保持静默:不下载、不回复、不建会话、不加表情;不登记、不扫描历史,靠引用进入下一轮(2026-10-21 起取代本地 `pending_attachments` 登记表) |
|
|
33
33
|
| D3 | 引用机器人消息但未 @ 不触发模型(对话群主聊天区);已绑定话题 / 话题群内保持“无需 @ 续聊” |
|
|
34
34
|
| D4 | 模型漏调工具用工具与 prompt 优化收敛,不做预下载兜底 |
|
|
35
35
|
| D5 | 大文件懒取 + 单次资源数 / 字节上限 + 超时 / 取消;工具内流式写入 |
|
|
36
|
-
| D6 | 图片与文件统一走 DSH
|
|
36
|
+
| D6 | 图片与文件统一走 DSH 原生附件库;插件不再提供入站附件清理 |
|
|
37
37
|
| D11 | 引用 / 历史附件通过清单行的 `message_id` “可发现 + 可取”;被动读使用独立 3s 预算;degraded chat route 拒绝 topic 消息读取 |
|
|
38
|
-
| D12 | 旧 `_staging` 一次性迁入保留一个版本周期:本版本兼容迁入(成功时 warn),v2 发布后的下一个 minor
|
|
38
|
+
| D12 | 旧 `_staging` 一次性迁入保留一个版本周期:本版本兼容迁入(成功时 warn),v2 发布后的下一个 minor 版本移除 |
|
|
39
39
|
|
|
40
40
|
## 3. 端到端数据流
|
|
41
41
|
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
3. 发出卡片第一帧并以 `reply_in_thread` 创建话题(飞书拒绝时按 `threadFallback: degrade` 退化为会话级路由)。
|
|
49
49
|
4. 确保用户工作区(`<baseDir>/p2p/<open_id>/` 或 `<baseDir>/groups/<chat_id>/<open_id>/`),规划话题目录(子目录或 Git worktree)。
|
|
50
50
|
5. 写入重启修复快照 → 创建 Session(preset `setup` 内挂载工具与 guidance)→ 进入话题目录 → 迁入旧版 `_staging` 遗留文件(仅兼容升级)。
|
|
51
|
-
6. **持久化路由记录后**、`followup` 前组装首轮消息:登记触发消息的附件白名单 → 并发读取引用摘录与首轮话题历史(各自独立 3s 预算)→ 预取触发消息图片(有界、短超时)→
|
|
51
|
+
6. **持久化路由记录后**、`followup` 前组装首轮消息:登记触发消息的附件白名单 → 并发读取引用摘录与首轮话题历史(各自独立 3s 预算)→ 预取触发消息图片(有界、短超时)→ 按固定顺序拼块。
|
|
52
52
|
7. `followup` 投递后关闭首回合投递闸门;预取窗口内到达的同话题消息等待该闸门,避免乱序。
|
|
53
53
|
|
|
54
54
|
首轮消息的块顺序固定为:
|
|
@@ -63,24 +63,24 @@
|
|
|
63
63
|
|
|
64
64
|
空块直接省略,因此无附件 / 无引用 / 无历史的回合不产生额外 token。archive 后端只使用文本 + 引用摘录 + 首轮历史(无图片块、无清单)。
|
|
65
65
|
|
|
66
|
-
### 3.2 纯附件消息 /
|
|
66
|
+
### 3.2 纯附件消息 / 未触发消息(静默 + 引用)
|
|
67
67
|
|
|
68
68
|
native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的分类**(是否携带资源、剥离占位符后的剩余文本、`attachmentOnly`):
|
|
69
69
|
|
|
70
70
|
| 场景 | 行为 |
|
|
71
71
|
| --- | --- |
|
|
72
|
-
| 私聊仅附件 |
|
|
73
|
-
| 对话群未 @ 的纯附件 |
|
|
74
|
-
| 已绑定话题内的纯附件 |
|
|
72
|
+
| 私聊仅附件 | 静默;不回复、不建会话、不加表情、不下载 |
|
|
73
|
+
| 对话群未 @ 的纯附件 | 同上;引用它 @ 机器人时列入清单 |
|
|
74
|
+
| 已绑定话题内的纯附件 | 同上;引用它续聊时列入清单,模型也可用 `feishu_topic_messages` 读到 |
|
|
75
75
|
| 文字 + 附件(触发) | 立即建会话;图片预取,文件进清单懒取 |
|
|
76
|
-
| 文字 + 附件(群未 @、无话题) |
|
|
77
|
-
| 问答阻塞期间收到的文字 + 附件 | 文字作为 `ask_user_question`
|
|
76
|
+
| 文字 + 附件(群未 @、无话题) | 不触发;引用它 @ 机器人时列入清单 |
|
|
77
|
+
| 问答阻塞期间收到的文字 + 附件 | 文字作为 `ask_user_question` 的回答被消费;附件可由引用或 `feishu_topic_messages` 读到 |
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
本地不写任何记录,也不在下一轮扫描聊天主线或话题历史去找回这类消息:引用意图明确,不需要猜“上一轮之后”的边界,也不会跨话题读取。被引用消息的附件进入清单与 fetch 白名单(§7.1);话题内首轮的 `<feishu_history>` 与 `feishu_topic_messages` 读到的附件同样获得授权。首次私聊引导卡提示用户“引用文件消息再提问”。
|
|
80
80
|
|
|
81
81
|
### 3.3 archive 后端(旧行为)
|
|
82
82
|
|
|
83
|
-
显式 `attachmentStore: 'archive'`、宿主未挂载 `ctx.attachments` 或 `feishuTools.attachmentFetch=false` 时,入站走旧同步路径:会话建立**之前**把图片/文件流式写入话题目录(未建话题时先写 `_staging`),合并转发记录写 `*-forwarded-record.txt`,成功加 `DONE`、失败加 `No` 并回一条纯文字失败提示;纯附件消息不触发模型。`acceptAttachments=false`
|
|
83
|
+
显式 `attachmentStore: 'archive'`、宿主未挂载 `ctx.attachments` 或 `feishuTools.attachmentFetch=false` 时,入站走旧同步路径:会话建立**之前**把图片/文件流式写入话题目录(未建话题时先写 `_staging`),合并转发记录写 `*-forwarded-record.txt`,成功加 `DONE`、失败加 `No` 并回一条纯文字失败提示;纯附件消息不触发模型。`acceptAttachments=false` 时附件消息**惰性**:不保存、不触发、不列入清单,混合消息只保留剥离占位符后的文字(走 archive 的 disabled 分支,不下载任何字节)。该路径的行为与 v1 一致,仅作为降级。
|
|
84
84
|
|
|
85
85
|
## 4. 存储后端
|
|
86
86
|
|
|
@@ -93,7 +93,7 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
93
93
|
| `native` | 已挂载 | `true` | `true` | **native** |
|
|
94
94
|
| `native` | 未挂载 | 任意 | 任意 | archive + 一次性告警 |
|
|
95
95
|
| `native` | 已挂载 | `false` | 任意 | archive(否则清单会宣称一个不可用的工具) |
|
|
96
|
-
| `native` | 已挂载 | 任意 | `false` | archive /
|
|
96
|
+
| `native` | 已挂载 | 任意 | `false` | archive / 惰性(附件消息不保存、不触发、不列入清单) |
|
|
97
97
|
| `archive` | 任意 | 任意 | 任意 | archive(显式配置优先) |
|
|
98
98
|
|
|
99
99
|
同一判定同时用于:准入分流(`InboundRouter.attachmentBackend()`)、guidance 选择(native / archive 两套文案)、fetch 工具是否挂载、`feishu_topic_messages` 描述是否追加“本部署无法取附件”的提示。
|
|
@@ -132,9 +132,9 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
132
132
|
- 固定英文尾部:不要获取用户未提及的附件,绝不访问其他会话的附件。
|
|
133
133
|
- **无条目时整块不产生**(零 token)。
|
|
134
134
|
|
|
135
|
-
**条目来源与顺序**:触发消息未加载资源 →
|
|
135
|
+
**条目来源与顺序**:触发消息未加载资源 → 引用消息资源。合并转发触发消息 / 引用消息都只出**容器行**(子资源按容器 id 下载,P0 规则)。
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
**去重**:按 `(message_id, file_key)` 去重(保留首次出现);已成功预取的图片按 `file_key` 从触发条目中排除。
|
|
138
138
|
|
|
139
139
|
**边界与防注入**:最多渲染 `MANIFEST_MAX_ROWS = 50` 行,超出追加 `- ... (N more attachments omitted)`;id 只保留 `[A-Za-z0-9_:.-]` 并截断到 128;文件名截断到 80 个码点后对 `"`、`\`、`<>`、C0/C1 控制字符、Unicode 格式控制符(bidi / 零宽等)、行/段分隔符做 `\uXXXX` 转义,单行无法被注入换行。
|
|
140
140
|
|
|
@@ -146,18 +146,17 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
146
146
|
|
|
147
147
|
- 参数:`message_id`(必填);`file_key`(可选,只取该消息的某个资源,缺省取全部)。
|
|
148
148
|
- 输出:`{ message_id, ok, summary, results[] }`;每个结果含 `ok / kind(image|file) / name / file_key / bytes / media_type / width / height / read_path / handle_text / code / reason / attachment`。渲染为文本 + 每个成功资源的 `ImageBlock` / `FileBlock`;文本始终存在(文本模型或宿主不支持 `FileBlock` 时的兜底)。
|
|
149
|
-
- **授权链**:先查本 route 的进程内白名单(LRU,每 route 128
|
|
150
|
-
- **授权来源**:触发消息(准入层登记,`source='trigger'`)、引用消息(`quote`)、首轮历史(`history`)、工具自身成功解析的消息(`tool-result`)进入本 route
|
|
149
|
+
- **授权链**:先查本 route 的进程内白名单(LRU,每 route 128 条);未命中时,`message_id` 符合 `^om_[A-Za-z0-9_-]{1,128}$` 才用 `message.get` 回读该消息(格式不符直接拒绝,不发请求),只有 route owner 本人发出、未撤回、属于本 route 容器(同 chat;话题路由只认本话题,chat 路由为主线)的消息才授权;别的话题与未登记的主线消息一律拒绝,话题之间不互通,回读失败一律拒绝。`message_id` 单独出现在参数里不足以授权;跨 chat / 跨话题、他人未登记的消息一律 `AUTHORIZATION_DENIED`,不下载任何字节。`file_key` 必须属于该消息已解析的资源集,否则 `UNKNOWN_FILE_KEY`。
|
|
150
|
+
- **授权来源**:触发消息(准入层登记,`source='trigger'`)、引用消息(`quote`)、首轮历史(`history`)、工具自身成功解析的消息(`tool-result`)进入本 route 的进程内白名单;回读授权的消息以 `tool-result` 记入白名单。`containerMessageId` 只能来自可信登记,绝不从模型参数取下载 id。
|
|
151
151
|
- **合并转发**:用顶层容器 `message_id` 展开子消息并下载子资源;子消息 id 不能作为下载 id(P0:子 id 返回 400)。
|
|
152
152
|
- **限制与取消**:单次资源数、总字节预算(文件在流式写入中逐 chunk 计量)、整段 `timeoutMs`;遵守 `exec.signal`,超时/取消把当前与剩余资源标记 `TIMEOUT` / `ABORTED`,不提交半文件、逐资源失败不中断整体调用。图片受宿主 `maxImageBytes` 额外约束。
|
|
153
|
-
- **消费语义**:只有至少一个资源成功后,才把对应 pending 记录标记 `consumed`(失败保留可重试);该写操作与取消信号竞速,不拖住工具返回。
|
|
154
153
|
- **挂载**:仅在 native 生效后端、`feishuTools.attachmentFetch=true` 且 `acceptAttachments=true` 时注册;注册在 preset `setup` 之后,`inject(['tools','attachments'])` 任一服务缺失则 no-op + 一次性告警,不阻塞会话;缺服务时最多等待 2 s(后续会话 250 ms 快速失败),晚到的服务仍可补注册。卡片用 `presentCall` 展示“获取飞书附件 …”。
|
|
155
154
|
|
|
156
155
|
### 6.2 `feishu_topic_messages`
|
|
157
156
|
|
|
158
157
|
- 参数:`message_id?`(单条读取,形状 `^om_[A-Za-z0-9_-]{1,128}$`)、`limit?`(列表页大小,默认 10、上限 50)、`cursor?`(上一页返回的 opaque page token,≤2048 字符)。`message_id` 与 `cursor` 互斥。
|
|
159
158
|
- 输出:`{ ok, summary, container{type,id}, order(newest_first|single), message_count, has_more, cursor?, messages[], code?, reason? }`;每条消息含 `message_id / type / sender / time / deleted / text / parent_message_id / resources[]`(图片/文件/文件夹元数据)。渲染文本有界(≤50 行、12k 字符)并显式输出 `- 下一页 cursor: <token>`(P2 真机缺陷修复,提交 `7964c64`)。
|
|
160
|
-
- **读取范围**:只允许当前 route 的容器——thread
|
|
159
|
+
- **读取范围**:只允许当前 route 的容器——thread 路由读该话题(单条读取另接受已登记到本 route 的主线消息);degraded chat 路由读会话主时间线并过滤其他话题消息;`message_id` 模式校验容器归属,跨 chat / 跨话题一律 `MESSAGE_OUT_OF_ROUTE`。
|
|
161
160
|
- **授权联动**:单条读取会展开合并转发并按容器 id 登记;列表只登记“带资源 / 合并转发”的行,避免纯文本长历史把 LRU 白名单挤爆;读到的资源即获得 fetch 授权。
|
|
162
161
|
- **错误**:`ROUTE_NOT_FOUND`、`TOOL_DISABLED`、`INVALID_ARGUMENT`、`MESSAGE_OUT_OF_ROUTE`、`MESSAGE_READ_FAILED`、`PERMISSION_DENIED`、`ABORTED`、`TIMEOUT`、`INTERNAL_ERROR`,均带中文可读原因;缺读取 scope 时不会报死错。
|
|
163
162
|
- **挂载**:`feishuTools.topicMessages=true`(默认)即挂载,**与存储后端无关**;archive 部署仍可读历史文本,但工具描述会追加“本部署 `feishu_attachment_fetch` 不可用”的提示。
|
|
@@ -187,22 +186,19 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
187
186
|
| 宿主未挂载 `ctx.attachments`(headless / 旧宿主) | 生效后端回退 archive;旧同步落盘 + DONE/No;日志一次性 warn;不挂 fetch 工具 |
|
|
188
187
|
| 显式 `attachmentStore: 'archive'` | 旧 archive 行为;不预取、不产生清单、不挂 fetch 工具;`feishu_topic_messages` 仍可读文本 |
|
|
189
188
|
| `feishuTools.attachmentFetch=false` | 整体回退 archive(文件无法懒取);新会话不挂 fetch 工具 |
|
|
190
|
-
| `acceptAttachments=false` |
|
|
189
|
+
| `acceptAttachments=false` | 附件消息惰性:纯附件不建会话/不回复/不列入清单,混合消息只保留剥离占位符后的文字;不挂附件 guidance 与 fetch 工具 |
|
|
191
190
|
| 宿主缺 `tools` 服务 | fetch / topic 工具均 no-op + 一次告警;会话照常建立,模型没有这些工具 |
|
|
192
191
|
| 图片预取失败 / 单图超限 / 超总预算 / 超时 | 已成功图片保留,其余进入清单由工具懒取,不阻塞回合 |
|
|
193
192
|
| 文件下载失败 / 文件已过期 / 文件夹消息 | 工具按资源返回可读失败(`DOWNLOAD_FAILED` / `FOLDER_UNSUPPORTED` 等),其余资源继续 |
|
|
194
193
|
| 工具超时 / 回合取消 | 剩余资源标记 `TIMEOUT` / `ABORTED`,不留下半文件;已知限制:飞书 HTTP 层不可取消 |
|
|
195
|
-
| pending 表不可用 | 进程内 TTL Map 降级,只告警一次,重启丢失 |
|
|
196
|
-
| pending 写失败 / 超时 | 只 warn,不失败入站流程;下次同 id 仍可登记(幂等) |
|
|
197
194
|
| 缺消息读取 scope(`im:message:readonly` 等) | 引用 / 历史块静默省略(首次一条 warn);显式调用 `feishu_topic_messages` 返回可读 `PERMISSION_DENIED`;普通对话不受影响 |
|
|
198
195
|
| 话题回复被拒(非主题群等) | `threadFallback: degrade` 退化为会话级路由;degraded 路由拒绝 topic 消息的引用/读取 |
|
|
199
|
-
| 重启 |
|
|
196
|
+
| 重启 | 路由懒恢复;进程内白名单不重放;本人在本话题的消息由 fetch 回读授权;首轮从主线列出的附件与他人的引用/历史消息需重新出现才可取 |
|
|
200
197
|
|
|
201
198
|
## 9. 隐私与安全边界
|
|
202
199
|
|
|
203
|
-
- **按 route 授权**:所有附件获取都必须同时满足“route 精确匹配(key/kind/chat/chatType/sender)+
|
|
204
|
-
-
|
|
205
|
-
- **白名单容量与失效**:每 route LRU 128;route 退休 / GC / 关闭 / 清理时回收;进程内不重放 Session 历史(重启后由准入 / 引用 / 历史层按当前回合重新登记)。
|
|
200
|
+
- **按 route 授权**:所有附件获取都必须同时满足“route 精确匹配(key/kind/chat/chatType/sender)+ 该消息已被可信来源登记或回读确认为 route owner 本人在本 route 容器里的消息”。只有 message id 无法解锁任何资源;跨会话、跨话题、跨用户一律拒绝。
|
|
201
|
+
- **白名单容量与失效**:每 route LRU 128;route 退休 / GC / 关闭时回收;进程内不重放 Session 历史(重启后由准入 / 引用 / 历史层按当前回合重新登记)。
|
|
206
202
|
- **不读凭据、不直连 OpenAPI**:guidance 明确禁止读取 / 打印飞书凭据(如 `$DSH_HOME/.credentials.yaml`)和直接调用飞书 OpenAPI;插件只通过注入的飞书 channel 与宿主服务访问数据。P2 真机曾暴露“模型绕路读取凭据”的宿主侧风险,已加 guidance,建议轮换 secret 并在宿主层限制 channel 会话读取 `$DSH_HOME`(非插件可改范围)。
|
|
207
203
|
- **附件字节不进提示**:prompt 只含元数据清单 / 引用摘录 / 历史摘要与原生 `ImageBlock`;文件字节只经工具写入原生库,模型通过只读路径按需读取;清单与工具输出对 id / 文件名做字符白名单与转义,防止换行注入与提示注入。
|
|
208
204
|
- **错误可读化**:工具错误统一映射为稳定 code + 中文原因,非 Error 抛值做字段白名单与截断(≤500 字符),不把原始对象 / 堆栈写进会话。
|
|
@@ -210,10 +206,9 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
210
206
|
|
|
211
207
|
## 10. 清理与保留
|
|
212
208
|
|
|
213
|
-
- **原生附件库**:由 DSH
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
- **`_staging`(旧版兼容,D12)**:v2 主路径不再写入;本版本仅在首个 Session 创建 / 续聊时对旧版遗留文件做一次性迁入(best-effort,成功时输出一条 warn,含迁入数量与退役提示)。移除时点:attachment_pipeline_v2 交付发布后的**下一个 minor 版本**发布任务中删除 `stagingPathFor` / `stagingRoot` / 迁入调用及相关文档。`<baseDir>/_staging` 被显式排除在保留期目录清理之外(planning 与 apply 双层拒绝),避免清理误删尚未迁入的遗留附件——DSH 原生附件库本就不在工作区、不参与清理。
|
|
209
|
+
- **原生附件库**:由 DSH 拥有,插件**不提供清理入口**,也不在路由 GC 中触碰。磁盘随使用增长,用户已接受(decisions D6 / roadmap 非目标)。
|
|
210
|
+
- **工作区与话题目录**:插件不删除任何工作区或话题目录,也不删除 DSH Session 日志与事件;需要回收磁盘时由运维手动删除话题目录(下次对话时重建空目录)。若显式使用 archive 后端,`attachments/` 副本位于话题目录,会随目录一起删除。
|
|
211
|
+
- **`_staging`(旧版兼容,D12)**:v2 主路径不再写入;本版本仅在首个 Session 创建 / 续聊时对旧版遗留文件做一次性迁入(best-effort,成功时输出一条 warn,含迁入数量与退役提示)。移除时点:attachment_pipeline_v2 交付发布后的**下一个 minor 版本**发布任务中删除 `stagingPathFor` / `stagingRoot` / 迁入调用及相关文档。
|
|
217
212
|
- **路由墓碑**:过期话题路由退休为持久化墓碑(thread/chat/owner/工作区/会话目录/最后活动时间),后续消息明确提示“话题已过期”,不恢复旧 Session;Session 与日志不动。
|
|
218
213
|
- **升级连续性**:旧 archive 部署切到 native 后,历史话题里的旧附件目录不会被新会话枚举;Session 历史仍可用原路径读取。新入站附件只进原生库。
|
|
219
214
|
|
|
@@ -230,8 +225,6 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
230
225
|
| `prefetchTotalMaxBytes` | `52428800`(50 MiB) | 1 B – 1 GiB | 内容与附件 |
|
|
231
226
|
| `prefetchTimeoutMs` | `10000` | 100 – 120000 ms(注意:Issue 早期草稿写作 `prefetchImageTimeoutMs`,实际配置键以代码为准) | 内容与附件 |
|
|
232
227
|
| `historyBootstrapLimit` | `10` | 0 – 20;`0` 关闭首轮历史 | 内容与附件 |
|
|
233
|
-
| `pendingAttachmentTtlMs` | `604800000`(7 天) | ≥0;`0` 关闭按时间清理 | 内容与附件 |
|
|
234
|
-
| `pendingAttachmentLimit` | `10` | 1 – 100(每 scope) | 内容与附件 |
|
|
235
228
|
| `promptContextTimeoutMs` | `3000` | 100 – 10000 ms;被动引用/历史预算 | 模型工具 |
|
|
236
229
|
| `feishuTools.attachmentFetch` | `true` | 关闭即整体回退 archive | 模型工具 |
|
|
237
230
|
| `feishuTools.attachmentFetchMaxResources` | `10` | 1 – 50 | 模型工具 |
|
|
@@ -249,14 +242,13 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
249
242
|
| 测试文件 | 覆盖内容 |
|
|
250
243
|
| --- | --- |
|
|
251
244
|
| `tests/native-attachments.test.ts` | 存储层:服务发现/未挂载、图片类型嗅探与上限、文件流式与取消、只读路径兜底、archive 回退与三态 plan、句柄文本 |
|
|
252
|
-
| `tests/pending-attachments.test.ts` | 登记幂等、scope 隔离、thread→chat 回退、TTL/容量、prune 幂等、内存降级、配置边界 |
|
|
253
245
|
| `tests/feishu-tools.test.ts` | 两个工具:白名单授权、合并转发容器、资源/字节/超时/取消、逐资源失败、JSON 无损、渲染、分页/cursor、参数注入 |
|
|
254
|
-
| `tests/manifest.test.ts` |
|
|
246
|
+
| `tests/manifest.test.ts` | 清单行格式、顺序、去重、零 token、转义、50 行上限、quote 行 |
|
|
255
247
|
| `tests/inbound-image-prefetch.test.ts` | 预取顺序、单图/总字节上限、整段超时、失败降级、不抛异常、无悬挂 timer |
|
|
256
248
|
| `tests/inbound-attachments.test.ts` | 不下载分类、占位符剥离、post `files[]` 恢复、旧 archive 行为回归 |
|
|
257
249
|
| `tests/message-read.test.ts` | get/mget/list 与渲染、引用/历史块边界、route 归属矩阵、失败与权限降级 |
|
|
258
|
-
| `tests/service.test.ts` |
|
|
259
|
-
| `tests/config.test.ts` / `tests/
|
|
250
|
+
| `tests/service.test.ts` | 零等待、首回合闸门、静默附件不进后续清单、懒取、archive 降级、问答拦截 |
|
|
251
|
+
| `tests/config.test.ts` / `tests/settings.test.ts` | 新配置默认值/边界、面板读写与嵌套合并 |
|
|
260
252
|
|
|
261
253
|
各 Issue 交付时记录的测试规模(作为历史证据,不代替当前 `pnpm check`):P1 原生存储 47 项;pending 33 项;fetch 工具 66 项;admission 后全量 30 files / 770 tests;P2 后 31 files / 860 tests。P3 的回归以实际 `pnpm check` 结果为准。
|
|
262
254
|
|
|
@@ -302,17 +294,17 @@ native 后端下,准入通过后由 `inboundAttachmentState` 做**不下载的
|
|
|
302
294
|
|
|
303
295
|
- 图片预取期间卡片已可见,但 `followup` 会等待预取窗口(默认 ≤10 s,可配);这是“首轮图片可见”与“首字延迟”的取舍。
|
|
304
296
|
- 文件懒取依赖模型主动调用工具;prompt 已用“未加载”措辞、工具描述与清单提示收敛漏调风险,但不做预下载兜底(D4)。
|
|
305
|
-
- 白名单为进程内 LRU(128/route
|
|
297
|
+
- 白名单为进程内 LRU(128/route),重启后清空;本人在本话题的消息由 fetch 回读授权,首轮从主线列出的附件与他人的引用/历史消息需重新出现才可取。
|
|
306
298
|
- 单 route 容量、manifest 50 行、历史 N≤20、工具输出 12k/50 行等均为有界设计,超限时给出显式截断/省略标注。
|
|
307
|
-
-
|
|
299
|
+
- 只发附件、不带文字的消息不会在下一轮自动列出;“先发文件、再单独提问”而不引用时模型看不到该文件,需要引用(多个文件可合并转发后引用)。
|
|
308
300
|
- 合并转发单条读取子消息 >50 条时截断(summary 标注),无翻页。
|
|
309
301
|
- archive 后端会阻塞会话建立(旧语义),且入口/清理语义与 native 不同(见 §8、§10)。
|
|
310
|
-
- `_staging` 仅旧版一次性迁入兼容(成功时 warn),v2 发布后的下一个 minor 版本移除(D12
|
|
302
|
+
- `_staging` 仅旧版一次性迁入兼容(成功时 warn),v2 发布后的下一个 minor 版本移除(D12)。
|
|
311
303
|
|
|
312
304
|
## 14. 相关文档
|
|
313
305
|
|
|
314
306
|
- [README.md](../README.md):安装、命令、面板、配置示例与行为总览。
|
|
315
307
|
- [docs/feishu-onboarding.md](feishu-onboarding.md):飞书应用权限、配置与真机联调 / P3 验证清单。
|
|
316
|
-
- [docs/workspace-isolation-design.md](workspace-isolation-design.md)
|
|
308
|
+
- [docs/workspace-isolation-design.md](workspace-isolation-design.md):工作区粒度与话题目录。
|
|
317
309
|
- `.agents/roadmaps/attachment_pipeline_v2/`:roadmap / decisions / acceptance。
|
|
318
310
|
- 各任务证据:`.agents/issues/{platform_spike,native_attachment_store,pending_attachment_registry,attachment_fetch_tool,admission_manifest_flow,quote_history_tools,panel_docs_release}.md`。
|
|
@@ -20,10 +20,14 @@
|
|
|
20
20
|
| `im:message`(获取与发送单聊、群组消息) | 收发消息、回复话题、下载消息内图片/文件资源(`messageResource.get`,具体 scope 以控制台该接口要求为准) |
|
|
21
21
|
| `im:message:readonly`(读取单聊、群组消息内容) | 引用摘录、话题历史、`feishu_topic_messages` 的消息读取(message.get / mget / list),以及附件资源定位与合并转发子资源读取 |
|
|
22
22
|
| `im:message.p2p_msg:readonly`(读取用户发给机器人的单聊消息) | 私聊通道 |
|
|
23
|
+
| `contact:user.employee_id:readonly`(获取用户 user ID,**必需**) | 插件用 `user_id` 识别用户(准入、话题归属、工作区目录);未开通时所有用户消息都会被丢弃(控制台打印 warn)。外部联系人/跨租户用户没有本租户 `user_id`,无法使用 |
|
|
23
24
|
| `im:message.group_msg`(读取群组中所有消息,**敏感权限**) | 读取群/话题中机器人入群前或未 @ 机器人的历史消息;控制台要求时申请并由企业管理员审批,未开通时按“机器人可见范围”降级 |
|
|
24
25
|
| `im:message.reactions:write_only`(发送、删除消息表情回复) | Typing / DONE / No 表态 |
|
|
26
|
+
| `im:resource`(获取与上传图片或文件资源,**可选**;`im:resource:upload` 亦可) | 模型用 `feishu_send_file` 把生成的图片/文件发给用户;不开通时该工具返回可读错误 |
|
|
25
27
|
| `im:message:update`(更新消息卡片) | 流式卡片刷新(patch) |
|
|
26
28
|
| `im:chat:readonly` 一类(获取群信息) | 入群通知卡展示群名/成员数(具体 scope 以控制台要求为准) |
|
|
29
|
+
| 「获取应用信息」一类(如 `admin:app.info:readonly` / `application:application:self_manage`,**可选**,以控制台要求为准) | 未配置 `adminUserId` 时连接后自动把应用所有者设为管理员;不便申请时跳过即可,按 3.3 手动设置管理员 |
|
|
30
|
+
| `application:app_slash_command:read` 与 `application:app_slash_command:write`(读取/管理应用斜杠指令,可选,仅 `slashCommandPanel: true` 时需要) | 连接后把插件命令同步到飞书原生斜杠指令面板(输入 `/` 时列出,客户端约 5 分钟后刷新);缺失时只是面板没有列表,直接输入命令不受影响 |
|
|
27
31
|
|
|
28
32
|
> 卡片更新接口本身支持 `im:message` 一项即可,但建议把 `im:message:update` 也加上,避免组合差异。
|
|
29
33
|
>
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
4. **事件与回调** → 事件订阅方式选择 **使用长连接接收事件**(WebSocket,免公网 IP)。
|
|
34
38
|
- 添加事件:**接收消息 im.message.receive_v1**(勾选“接收群聊中 @ 机器人消息”)。
|
|
35
39
|
- 添加事件:**机器人进群 im.chat.member.bot.added_v1**(机器人被拉进群后私聊管理员发群信息卡;未订阅时退化为“群里第一次未授权 @ 时补发”)。
|
|
36
|
-
- 添加回调:**卡片回传交互 card.action.trigger
|
|
40
|
+
- 添加回调:**卡片回传交互 card.action.trigger**(流式卡停止按钮、选择卡、HITL 审批、授权卡的按钮回传,同样走长连接)。
|
|
37
41
|
5. **凭证与基础信息** 页复制 `App ID` 与 `App Secret`。
|
|
38
42
|
6. **版本管理与发布** → 创建版本(权限与事件配置随版本生效)→ 发布。
|
|
39
43
|
7. **可用范围**:把自己(和测试同事)加入可用范围。
|
|
@@ -113,7 +117,7 @@ chmod 600 $DSH_HOME/.credentials.yaml
|
|
|
113
117
|
dmMode: 'open' # 私聊通道开关
|
|
114
118
|
threadFallback: 'degrade' # 话题被拒时降级为会话级路由
|
|
115
119
|
topicIsolation: 'auto' # 话题目录隔离:auto(Git 仓库用 worktree,否则子目录)/ subdir / worktree
|
|
116
|
-
acceptAttachments: true # 接收飞书附件:native
|
|
120
|
+
acceptAttachments: true # 接收飞书附件:native 下图片预取、文件懒取、纯附件静默不回复
|
|
117
121
|
attachmentStore: 'native' # 入站附件后端:native(DSH 原生附件库)/ archive(旧版工作区 attachments/)
|
|
118
122
|
prefetchImages: true # 触发消息图片预取为首轮 ImageBlock
|
|
119
123
|
prefetchImageMaxBytes: 20971520 # 单张预取图片上限(20 MiB)
|
|
@@ -121,8 +125,6 @@ chmod 600 $DSH_HOME/.credentials.yaml
|
|
|
121
125
|
prefetchTimeoutMs: 10000 # 图片预取整段超时(10 s)
|
|
122
126
|
historyBootstrapLimit: 10 # 新会话首轮话题历史条数(0 关闭,上限 20)
|
|
123
127
|
promptContextTimeoutMs: 3000 # 被动引用/历史读取预算(3 s)
|
|
124
|
-
pendingAttachmentTtlMs: 604800000 # 静默登记保留期(7 天;0 = 不按时间清理)
|
|
125
|
-
pendingAttachmentLimit: 10 # 每个 (会话,话题,发送者) scope 的静默登记容量
|
|
126
128
|
feishuTools: # 模型工具(Web 面板按子键合并,不写空未改子键)
|
|
127
129
|
attachmentFetch: true # feishu_attachment_fetch:按需懒取已知消息的图片/文件
|
|
128
130
|
attachmentFetchMaxResources: 10 # 单次最多取回资源数(1–50)
|
|
@@ -132,7 +134,7 @@ chmod 600 $DSH_HOME/.credentials.yaml
|
|
|
132
134
|
topicMessagesTimeoutMs: 20000 # 单次话题读取整段超时(20 s)
|
|
133
135
|
approvalTimeoutMs: 300000 # HITL 审批决策卡等待时长(默认 5 分钟)
|
|
134
136
|
accessMode: 'guarded' # 准入模式:guarded(默认)| open(旧行为,不安全)
|
|
135
|
-
|
|
137
|
+
adminUserId: 'a1b2c3d4' # 管理员 user_id(可留空,留空时自动使用应用所有者;都没有时 guarded 下拒绝一切对话)
|
|
136
138
|
authRequestTtlDays: 7 # 入群/用户授权申请卡有效期(天)
|
|
137
139
|
loggerLevel: 'info'
|
|
138
140
|
```
|
|
@@ -142,7 +144,7 @@ chmod 600 $DSH_HOME/.credentials.yaml
|
|
|
142
144
|
| 配置项 | 默认 | 说明 |
|
|
143
145
|
|--------|------|------|
|
|
144
146
|
| `appId` / `appSecret` | `FEISHU_APP_ID` / `FEISHU_APP_SECRET` | 凭据引用名(shell 标识符),经 `ctx.credentials` 解析;仅当凭据库使用其它变量名时才需要覆盖。明文密钥会被拒绝 |
|
|
145
|
-
| `baseDir` | `./channels-data` | 工作区目录树根:每个用户一个工作区(私聊 `p2p/<
|
|
147
|
+
| `baseDir` | `./channels-data` | 工作区目录树根:每个用户一个工作区(私聊 `p2p/<user_id>/`,群聊 `groups/<chat_id>/<user_id>/`),每个话题的工作目录为 `<工作区>/topics/<话题>/`(模型/工具产生文件的落点);Web 面板可覆盖,只接受绝对路径,`~` 展开,保存时创建并校验目录,只影响之后新建的会话 |
|
|
146
148
|
| `provider` / `model` | 部署默认 | 新建 session 的路由;省略时继承部署默认 |
|
|
147
149
|
| `reasoningEffort` | 适配器/provider 默认 | Session 初始/重置推理档位;可在话题内用 `/effort` 覆盖 |
|
|
148
150
|
| `maxTokens` | 适配器默认 | 每次模型请求输出上限 |
|
|
@@ -153,16 +155,14 @@ chmod 600 $DSH_HOME/.credentials.yaml
|
|
|
153
155
|
| `topicIsolation` | `auto` | 话题工作目录形式:`auto`(工作区含 `.git` 时用 Git worktree,否则普通子目录)/ `subdir` / `worktree`(失败回退子目录) |
|
|
154
156
|
| `approvalTimeoutMs` | `300000` | HITL 审批决策卡等待时长(10s–1h);超时自动拒绝 |
|
|
155
157
|
| `accessMode` | `guarded` | 准入模式:`guarded` 仅管理员+白名单;`open` 保留旧行为(任何可用范围用户可对话,不安全) |
|
|
156
|
-
| `
|
|
158
|
+
| `adminUserId` | 未设置 = 应用所有者 | 唯一管理员 user_id(不是 `ou_` 开头的 open_id);留空时连接后自动使用应用所有者(需可选的「获取应用信息」权限),两者都没有 + guarded = 拒绝一切对话(`/userid` 引导仍可用)。面板可改 |
|
|
157
159
|
| `authRequestTtlDays` | `7` | 入群/用户授权申请卡有效期(1–30 天) |
|
|
158
|
-
| `acceptAttachments` | `true` | 接收飞书图片/文件/合并转发记录。native
|
|
160
|
+
| `acceptAttachments` | `true` | 接收飞书图片/文件/合并转发记录。native 下触发消息图片预取、文件懒取、纯附件静默不回复(引用该消息再提问时列入清单);`false` 时附件消息完全惰性(不保存、不触发、不列入清单) |
|
|
159
161
|
| `attachmentStore` | `native` | 入站附件后端:`native`(DSH 原生附件库,默认;不可变、内容寻址去重、随 Session 回放、不自动清理)/ `archive`(旧版下载到话题工作区 `attachments/`,会阻塞会话建立) |
|
|
160
162
|
| `prefetchImages` | `true` | native 下把触发消息图片预取为首轮 `ImageBlock`;失败/超限/超时降级为清单懒取项。关闭后图片也需 `feishu_attachment_fetch` |
|
|
161
163
|
| `prefetchImageMaxBytes` | `20971520` | 单张预取图片字节上限(20 MiB,1 B–200 MiB) |
|
|
162
164
|
| `prefetchTotalMaxBytes` | `52428800` | 一次触发消息预取的总字节预算(50 MiB,1 B–1 GiB);用尽后剩余图片降级清单 |
|
|
163
165
|
| `prefetchTimeoutMs` | `10000` | 图片预取整段超时(100–120000 ms);到期保留已取回图片、其余降级清单。注意配置键是 `prefetchTimeoutMs`(早期 Issue 草稿写作 `prefetchImageTimeoutMs`) |
|
|
164
|
-
| `pendingAttachmentTtlMs` | `604800000` | 静默登记元数据保留期(7 天,≥0;`0` = 不按时间清理,每 scope 容量上限仍生效) |
|
|
165
|
-
| `pendingAttachmentLimit` | `10` | 每个 `(会话, 话题, 发送者)` scope 的静默登记容量(1–100);超出淘汰该 scope 最旧记录 |
|
|
166
166
|
| `historyBootstrapLimit` | `10` | 新会话首轮自动附带本话题最近 N 条消息摘要(0 关闭,上限 20;失败静默降级);更早的消息用 `feishu_topic_messages` 翻页 |
|
|
167
167
|
| `promptContextTimeoutMs` | `3000` | 被动引用/首轮历史读取的整段预算(100–10000 ms);超时即省略对应块、回合照跑。模型主动调用工具的读取超时另由 `feishuTools.topicMessagesTimeoutMs` 控制 |
|
|
168
168
|
| `feishuTools.attachmentFetch` | `true` | 是否挂载 `feishu_attachment_fetch`;关闭后文件无法懒取,插件**整体回退 archive 后端** |
|
|
@@ -173,24 +173,24 @@ chmod 600 $DSH_HOME/.credentials.yaml
|
|
|
173
173
|
| `feishuTools.topicMessagesTimeoutMs` | `20000` | 单次 `feishu_topic_messages` 整段超时(1000–120000 ms) |
|
|
174
174
|
| `loggerLevel` | `info` | 官方 SDK 日志级别 |
|
|
175
175
|
|
|
176
|
-
> **面板与配置位置**:v2 附件配置已加入 Web 面板「设置 → 飞书渠道 → 会话与行为」:`acceptAttachments` / `attachmentStore` / `prefetch*` / `
|
|
176
|
+
> **面板与配置位置**:v2 附件配置已加入 Web 面板「设置 → 飞书渠道 → 会话与行为」:`acceptAttachments` / `attachmentStore` / `prefetch*` / `historyBootstrapLimit` 在「内容与附件」组,`feishuTools.*` 与 `promptContextTimeoutMs` 在「模型工具」组;保存写入同一 Web 覆盖文件,`feishuTools.*` 按子键合并(不会把未修改子键写空)。直接写 profile 补丁层同样生效,面板覆盖优先。其中 `feishuTools.topicMessages=false` 只关闭模型工具,被动的引用/历史 prompt 摘录仍生效;要完全关闭首轮历史请设 `historyBootstrapLimit: 0`。archive 后端下 `feishu_attachment_fetch` 不挂载,`feishu_topic_messages` 仍可读取历史文本,但工具描述会提示附件不可取。
|
|
177
177
|
|
|
178
178
|
### 3.3 初始化管理员与授权(默认准入)
|
|
179
179
|
|
|
180
|
-
插件默认 `accessMode: 'guarded'
|
|
180
|
+
插件默认 `accessMode: 'guarded'`:**没有管理员时,所有对话都会被拒绝**(这是 fail-closed 的安全默认值)。如果应用开通了可选的「获取应用信息」权限,留空 `adminUserId` 时连接后会自动把应用所有者设为管理员(Web「访问授权」页显示“已自动使用应用所有者”),可以跳过下面第 1–2 步;没开通或查询失败时按以下顺序手动设置:
|
|
181
181
|
|
|
182
|
-
1. 启动 dsh 后在飞书**私聊机器人**发送 `/userid`(未配置管理员时任何私聊用户都可以用,只回固定文本,不建会话)→ 复制返回的
|
|
183
|
-
2. 打开 Web **设置 → 飞书渠道 →
|
|
182
|
+
1. 启动 dsh 后在飞书**私聊机器人**发送 `/userid`(未配置管理员时任何私聊用户都可以用,只回固定文本,不建会话)→ 复制返回的 user_id;
|
|
183
|
+
2. 打开 Web **设置 → 飞书渠道 → 访问授权**,把它填进「管理员 user_id」并保存(立即生效);也可以直接写 profile 的 `adminUserId`;
|
|
184
184
|
3. 现在管理员私聊机器人即可正常对话。**其他用户私聊会收到一条固定提示**(“非管理员禁用私聊功能哦…”),不会建会话;
|
|
185
185
|
4. **给群授权**(三种入口任选;目的是放行**非管理员**,管理员本人在任何群都能直接对话):
|
|
186
186
|
- 机器人被拉进群 → 管理员私聊收到**群信息卡**(群名/`chat_id`/成员数/邀请人)→ 点「授权本群」;
|
|
187
187
|
- 管理员在群里发 `/auth allow` → 回执「已授权本群」;`/auth revoke` 撤销;
|
|
188
188
|
- Web 面板「访问与授权 → 已授权群」手动添加 `chat_id`(可用 `/groupid` 查看);
|
|
189
189
|
5. **给用户授权**(三种入口任选):
|
|
190
|
-
- 已授权群里未授权用户 @ 机器人 → 管理员收到**用户授权申请卡**(含昵称/
|
|
191
|
-
-
|
|
192
|
-
- Web 面板「访问与授权 → 已授权用户」手动添加
|
|
193
|
-
6. 授权/撤销**立即生效**;白名单与待处理申请持久化在插件 `
|
|
190
|
+
- 已授权群里未授权用户 @ 机器人 → 管理员收到**用户授权申请卡**(含昵称/user_id)→ 点「授权用户」;
|
|
191
|
+
- 管理员在群里发 `/auth user add @用户`(或 `/auth user add <user_id>`);`/auth user remove @用户` 撤销;外部用户没有 user_id,无法授权;
|
|
192
|
+
- Web 面板「访问与授权 → 已授权用户」手动添加 user_id(用户可用 `/userid` 查看);
|
|
193
|
+
6. 授权/撤销**立即生效**;白名单与待处理申请持久化在插件 `channels_routes_v2` 域,重启不丢。`/auth list` 可随时查看白名单与待处理申请。
|
|
194
194
|
|
|
195
195
|
> 说明:**管理员不受白名单限制**——机器人被拉进新群后,管理员可以直接 @ 机器人开会话,不需要先授权该群;授权群/用户只影响**非管理员**。管理员在任意群里都可以使用 `/auth`、`/groupid` 等命令。
|
|
196
196
|
|
|
@@ -216,7 +216,9 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
216
216
|
[dsh-channels] 准入=guarded,管理员已配置
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
>
|
|
219
|
+
> 斜杠指令面板:连接后日志出现 `slash command panel synced (N commands, x created, y updated, z removed)` 即同步成功,此时在飞书聊天框输入 `/` 应能看到插件命令;`飞书原生斜杠指令面板同步失败(直接输入命令不受影响):…` 表示同步失败,原因同时显示在面板「连接」页。
|
|
220
|
+
|
|
221
|
+
> 准入相关控制台输出:`准入=open`(不安全,仅调试)、`准入=guarded,管理员已配置`、`准入=guarded 但未配置管理员 user_id:所有对话都会被拒绝…`(此时按 §3.3 配置管理员即可)。
|
|
220
222
|
|
|
221
223
|
> 第二行只在宿主挂载了 `approval` 服务时出现;若显示 `主机未挂载 approval 服务,HITL 审批应答关闭`,说明该部署未启用 HITL(`approval/request` 事件永远不会派发),并非插件故障。
|
|
222
224
|
|
|
@@ -227,7 +229,6 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
227
229
|
| `attachmentStore=native 但宿主未挂载 ctx.attachments,入站附件回退话题 attachments/ 工作区落盘` | 宿主没有 DSH 原生附件库:入站附件按旧 archive 行为处理;如需 native 请确认 web profile 启用 `dsh-attachment-local` |
|
|
228
230
|
| `feishu_attachment_fetch 未挂载:宿主 tools/attachments 服务不可用(会话继续)` | 缺 `tools` 或 `attachments` 服务:该工具不注册,会话照常建立 |
|
|
229
231
|
| `feishu_topic_messages 未挂载:宿主 tools 服务不可用(会话继续)` | 缺 `tools` 服务:话题读取工具不注册 |
|
|
230
|
-
| `pending attachment storage domain unavailable; using a process-memory TTL fallback…` | 宿主拒绝 `pending_attachments` 表:静默登记退化为进程内 TTL,重启丢失(仅防御场景) |
|
|
231
232
|
| `adopted N legacy staged attachment(s) into …; the one-time _staging migration is deprecated and will be removed after one release cycle` | 旧版 `_staging` 遗留文件被一次性迁入话题目录;按 D12,该兼容路径在 v2 发布后的下一个 minor 版本移除,可据此清理旧数据 |
|
|
232
233
|
|
|
233
234
|
**未配置凭据(不是启动失败)**:日志会提示 `飞书应用凭据未配置完整(缺少 FEISHU_APP_…)`,插件照常加载但**不连接**;打开 Web **设置 → 飞书渠道 → 连接**,按三步引导填写 App ID / App Secret 并保存后自动连接。
|
|
@@ -255,18 +256,18 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
255
256
|
| 2 | 私聊续聊 | 在刚建的话题里直接再发消息(**不 @**) | 继续同一 session:完成后主卡结算页脚第一行的 session id 不变,input/output token 累计增长 |
|
|
256
257
|
| 3 | 群聊 @ | 在普通群 @ 机器人发消息 | 同 #1;该消息成为新话题根 |
|
|
257
258
|
| 4 | 群聊话题内 | 在上一步的话题里直接发消息(不 @) | 延续该 session,不再要求 @ |
|
|
258
|
-
| 5 | 同群第二个用户 | 换另一个人 @ 机器人 | 新话题 + 新 session;dsh 工作区列表中出现**该用户的独立工作区**(`groups/<chat_id>/<用户
|
|
259
|
+
| 5 | 同群第二个用户 | 换另一个人 @ 机器人 | 新话题 + 新 session;dsh 工作区列表中出现**该用户的独立工作区**(`groups/<chat_id>/<用户 user_id>/`),会话实际工作在它自己的 `topics/<话题>/` 下,与原用户的话题目录互不可见 |
|
|
259
260
|
| 6 | 话题群 | 在话题群某话题中 @ 机器人 | 机器人直接在该话题内回复卡片(不新建话题) |
|
|
260
261
|
| 7 | 折叠面板 | 点面板标题 | 展开推理内容,右侧箭头翻转向上;再点收起 |
|
|
261
262
|
| 8 | 重启恢复 | 重启 dsh 后在旧话题里发消息 | 机器人懒恢复原 session,token 统计继续累计 |
|
|
262
263
|
| 9 | 降级路径 | 在关闭话题功能的群里 @ 机器人 | 正常回复(普通回复气泡),后续 @ 延续同一 session |
|
|
263
264
|
| 10 | HITL 审批 | 让模型执行需要审批的动作(如提示沙箱升级:`bash` 被沙箱拒绝后带 `sandbox_permissions` 重试) | 同一话题内出现独立的「⚠️ 需要你的审批」卡片(✅ 允许 / ❌ 拒绝);点允许后卡片变「✅ 已允许执行」且动作继续,点拒绝变「❌ 已拒绝」;不点则到时(默认 5 分钟)变「⏱ 已超时未处理,自动拒绝」 |
|
|
264
|
-
| 11 | 管理员初始化(准入) | 未配置 `
|
|
265
|
+
| 11 | 管理员初始化(准入) | 未配置 `adminUserId` 时私聊 `/userid`,把 user_id 填入面板保存 | 未配置时 `/userid` 也只回固定文本、不建会话;保存后面板显示管理员已配置,管理员私聊正常对话 |
|
|
265
266
|
| 12 | 非管理员私聊 | 用另一个飞书账号私聊机器人 | 只收到一条固定提示(“非管理员禁用私聊功能哦…”);不建话题/会话、不产生模型调用;该账号发 `/userid` 仍可拿到自己的 ID |
|
|
266
267
|
| 13 | 入群通知 | 把机器人拉进一个新群 | 管理员私聊收到群信息卡(群名/`chat_id`/成员数/邀请人),未授权群带「授权本群 / 忽略」按钮;群里没有任何机器人消息 |
|
|
267
268
|
| 14 | 管理员直通(免群授权) | 刚被拉进未授权群时,管理员直接 @ 机器人发消息 | 正常开会话(管理员不受白名单限制);不授权该群也不影响管理员使用 |
|
|
268
|
-
| 14a | 群授权与群内准入 | 管理员点「授权本群」(或在群内发 `/auth allow`);未授权用户 @;管理员 `/auth user add <
|
|
269
|
-
| 15 | 撤销与持久化 | `/auth revoke`(群内)/ `/auth user remove <
|
|
269
|
+
| 14a | 群授权与群内准入 | 管理员点「授权本群」(或在群内发 `/auth allow`);未授权用户 @;管理员 `/auth user add <user_id>` 后该用户再 @ | 授权前非管理员 @ 无响应(管理员除外);授权后白名单用户可分别开会话;未授权用户 @ 不建会话且管理员收到用户授权卡;授权该用户后可正常开会话 |
|
|
270
|
+
| 15 | 撤销与持久化 | `/auth revoke`(群内)/ `/auth user remove <user_id>`;重启 dsh 后再试 | 撤销后对应主体立即无法开启对话;重启后已授权群/用户仍有效(持久化) |
|
|
270
271
|
| 16 | 话题越权防护 | 授权群里用户 A 创建话题后,用户 B 在 A 的话题里发普通消息/附件/回答提问 | B 的消息被静默丢弃,不会进入 A 的 Session、不会写入 A 的话题目录;A 自己发言正常 |
|
|
271
272
|
|
|
272
273
|
### 5.0 HITL 审批验证点(重点)
|
|
@@ -286,13 +287,13 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
286
287
|
打开 dsh Web GUI → 工作区列表,应看到**每人一个**项目:
|
|
287
288
|
|
|
288
289
|
```
|
|
289
|
-
飞书私聊 ·
|
|
290
|
-
飞书群聊 · oc_yyy ·
|
|
290
|
+
飞书私聊 · <user_id> (对应 <baseDir>/p2p/<user_id>/)
|
|
291
|
+
飞书群聊 · oc_yyy · <user_id> (对应 <baseDir>/groups/oc_yyy/<user_id>/)
|
|
291
292
|
```
|
|
292
293
|
|
|
293
294
|
每个新话题在该工作区内创建独立的话题工作目录 `<工作区>/topics/<话题 id>/`,会话列表仍归属该项目;同一话题内 `/renew` 复用同一话题目录。也可在飞书里用 `/workspace` 查看当前会话的实际工作目录。
|
|
294
295
|
|
|
295
|
-
**附件落点(v2)**:默认 `attachmentStore: 'native'` 下入站图片/文件存入 **DSH 原生附件库**(`$DSH_HOME/attachments/v1` 一类宿主目录,由宿主管理),**话题目录下不会出现 `attachments/`**;只有显式 archive 后端或宿主缺原生附件库时才会看到 `<工作区>/topics/<话题>/attachments/`。v2 主路径也不再写 `<baseDir>/_staging/`:该目录只保留旧版遗留文件的一次性迁入(成功时一条 warn),按 D12 在 v2 发布后的下一个 minor
|
|
296
|
+
**附件落点(v2)**:默认 `attachmentStore: 'native'` 下入站图片/文件存入 **DSH 原生附件库**(`$DSH_HOME/attachments/v1` 一类宿主目录,由宿主管理),**话题目录下不会出现 `attachments/`**;只有显式 archive 后端或宿主缺原生附件库时才会看到 `<工作区>/topics/<话题>/attachments/`。v2 主路径也不再写 `<baseDir>/_staging/`:该目录只保留旧版遗留文件的一次性迁入(成功时一条 warn),按 D12 在 v2 发布后的下一个 minor 版本移除。插件不会自动删除工作区或话题目录。
|
|
296
297
|
|
|
297
298
|
### 5.3 引用与话题历史(P2)验证点
|
|
298
299
|
|
|
@@ -316,8 +317,8 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
316
317
|
| A1 | 私聊·文本 | 私聊发送一条普通文字 | Typing → 卡片**立即**出现并流式回复;会话/话题正常建立 | ✅ 已真机(P1/P2) |
|
|
317
318
|
| A2 | 私聊·图片 | 发一张截图 + “这是什么” | 卡片先出现;首轮含 `ImageBlock`,模型不调用 fetch 即可描述(可能另用 `read_image`);首轮清单不列该已预取图片;原生库对象数 +1、话题目录无 `attachments/` | ✅ 已真机(P1 步骤 2) |
|
|
318
319
|
| A3 | 私聊·文件懒取 | 发 “看下这个文件” + 一个文件(如 48 MB zip) | 卡片先出现;首轮只有清单行(`message_id/file_key/name`),**下载发生在回合开始之后**;模型调 `feishu_attachment_fetch` 后结果为 `[text, file]` + 只读路径;文件字节进原生库、话题目录无 `attachments/`、无 `_staging` | ✅ 已真机(P1 步骤 3) |
|
|
319
|
-
| A4 | 私聊·纯附件静默 | 只发一张图片(不带文字),然后再发一条文件(不带文字) |
|
|
320
|
-
| A5 | 私聊·静默引用 | 在 A4
|
|
320
|
+
| A4 | 私聊·纯附件静默 | 只发一张图片(不带文字),然后再发一条文件(不带文字) | 无回复、无表情、无新话题/会话、无下载;本地不写记录,原生库对象数与工作区文件不变 | ✅ 已真机(P1 步骤 1 图片;P2 步骤 3a 文件) |
|
|
321
|
+
| A5 | 私聊·静默引用 | 在 A4 之后引用刚才的文件消息发文字:“这个文件叫什么/内容是什么” | 清单列出被引用的文件;模型 fetch 取回后正确回答。不引用直接问时清单里没有该文件(预期) | ⏳ 待手测(2026-10-21 改为只靠引用,需重测) |
|
|
321
322
|
| A6 | 私聊·引用文件 | 引用一条带文件的消息,问文件内容 | prompt 出现 `<feishu_quote>`(含 message_id),清单出现 quote 行;模型 fetch 成功 | ✅ 已真机(P2 步骤 3c) |
|
|
322
323
|
| A7 | 私聊·引用合并转发 | 合并转发两条消息(含一个文件),引用它问“总结/取其中文件” | quote 块展开子消息并标“合并转发,共 N 条”;清单为容器行;fetch 用**顶层容器 message_id** 成功(子消息 id 会 400,属预期) | ✅ 已真机(P2 步骤 4) |
|
|
323
324
|
| A8 | 私聊·大文件 | 发 ≥100 MB(如有 1 GB 级更好)文件 + “看下这个文件” | 卡片立即出现、不等待;清单先列文件;模型调 fetch 后流式入库,超过 `attachmentFetchTimeoutMs` 时返回可读超时,不阻塞后续对话 | ⏳ 待手测(P0 已验 980 MB 下载/取消,admission 链路未验;自动测试覆盖超时/取消) |
|
|
@@ -336,7 +337,6 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
336
337
|
| E1 | 权限缺失·读取 | 临时移除 `im:message:readonly`(或让 `im:message.group_msg` 未审批)后发布版本,发引用消息/在群内 @ | 普通对话正常;引用/历史块静默省略;首次降级一条 warn;显式 `feishu_topic_messages` 返回可读 `PERMISSION_DENIED`;恢复权限并重发版本后恢复 | ⏳ 待手测(用户已决定跳过;自动测试覆盖 throw/resolved 两种降级) |
|
|
337
338
|
| E2 | 权限缺失·下载 | 临时移除消息资源读取相关 scope,让模型 fetch 一个文件 | 工具逐资源返回可读失败(403/权限说明),回合不崩;archive 下则加 `No` 并回纯文字失败提示 | ⏳ 待手测(自动测试覆盖) |
|
|
338
339
|
| F1 | 面板配置 | 在 Web 「会话与行为」依次修改存储后端、预取开关/上限、登记 TTL/容量、两个工具开关与上限并保存,新建会话验证生效 | 面板读写与帮助文案正确;嵌套 `feishuTools.*` 只改被改子键;旧值不丢;保存后入站行为即时按新配置判定、工具挂载对新会话生效;非法值被拒 | ⏳ 待手测 + 自动测试(settings 覆盖) |
|
|
339
|
-
| F2 | 清理语义 | 在 native 后端执行「目录清理」,再回到旧话题发消息 | 只删过期话题/旧版目录;用户工作区与会话日志保留;native 附件不受影响(Session 历史仍可读);archive 后端下话题目录内 `attachments/` 随目录删除 | ⏳ 待手测(cleanup/settings 自动测试覆盖) |
|
|
340
340
|
|
|
341
341
|
**记录要求**:每个「待手测」项执行后,在 `acceptance.md` 对应 AC 下写「操作 / 实际结果 / 结论 / 环境(构建 SHA、profile、日期)」;失败的项保留原文与日志摘录,修复后复测再更新。`historyBootstrapLimit: 0`、非本 route 负向、`acceptAttachments=false` 等用户已决定跳过真机的项,标注「自动测试覆盖,用户决定跳过真机」,不要写成已验证。
|
|
342
342
|
|
|
@@ -345,17 +345,17 @@ DSH_HOME=$DSH_HOME pnpm dsh web # 默认 http://127.0.0.1:3080
|
|
|
345
345
|
| 现象 | 排查 |
|
|
346
346
|
|------|------|
|
|
347
347
|
| 私聊无响应 | 用户是否在应用**可用范围**内;权限 `im:message.p2p_msg:readonly` 是否随版本发布;若返回的是“非管理员禁用私聊功能哦”,说明准入生效(把该用户加入白名单或让管理员对话) |
|
|
348
|
-
| 私聊提示“机器人尚未配置管理员” | `accessMode: 'guarded'` 且未配置 `
|
|
348
|
+
| 私聊提示“机器人尚未配置管理员” | `accessMode: 'guarded'` 且未配置 `adminUserId`:按 §3.3 在面板填写管理员 user_id |
|
|
349
349
|
| 机器人被拉进群但管理员没收到群信息卡 | 是否订阅 **im.chat.member.bot.added_v1** 事件并重新发布版本;群信息读取权限是否已开通(`getChatInfo` 失败只会缺群名,不影响卡片本体) |
|
|
350
350
|
| 群内 @ 机器人无响应(登录台无 access 报错) | 管理员不受白名单限制,可直接对话;**非管理员**需要该群已授权且自己在用户白名单里。可用 `/auth status`(管理员)或 Web 面板「访问授权」标签页确认 |
|
|
351
351
|
| `/auth` 无反应 | `/auth` 仅管理员可用;非管理员在群里会被静默忽略、私聊会收到固定提示 |
|
|
352
352
|
| 群聊 @ 无响应 | 事件订阅是否勾选“接收群聊中 @ 机器人消息”;机器人是否在群内且未被群主禁言 |
|
|
353
353
|
| 模型说看不到刚发的文件 / 文件没有自动出现 | 属 v2 设计:文件默认不加载,只在 `<feishu_context>` 清单里声明;模型需调用 `feishu_attachment_fetch`。可明确要求“用 feishu_attachment_fetch 取清单里 message_id=… 的附件”;若工具没注册,检查启动日志 `feishu_attachment_fetch 未挂载` 与宿主 `tools`/`attachments` 服务 |
|
|
354
|
-
| 只发附件(不带文字)机器人毫无反应 | native
|
|
354
|
+
| 只发附件(不带文字)机器人毫无反应 | native 后端下的**预期行为**:纯附件消息静默,不回复、不建会话、不下载;随后引用这条附件消息发文字提问,附件即列入清单;不引用时模型看不到它。若引用后仍没有,检查消息读取权限(群聊需 `im:message.group_msg`)。archive 后端下会看到 `DONE` 表情且附件落到话题目录 |
|
|
355
355
|
| 模型没有调用 feishu_attachment_fetch | 检查 `<feishu_context>` 清单是否出现在 prompt(有未加载附件时才有);确认工具开关 `feishuTools.attachmentFetch` 与 native 后端;必要时在消息里点明“用附件工具取一下”。漏调风险由清单措辞 / 工具描述 / guidance 收敛,没有预下载兜底(决定 D4) |
|
|
356
356
|
| 引用或话题历史内容缺失 | 缺消息读取 scope(`im:message:readonly` / 敏感的 `im:message.group_msg`):引用/历史块静默省略、普通对话不受影响;启动后首次降级有一条 warn,之后 debug 级。按 §1 申请权限并重新发布版本;显式调用 `feishu_topic_messages` 会返回可读 `PERMISSION_DENIED` |
|
|
357
357
|
| `feishu_attachment_fetch` 超时 / 报字节超限 | 大文件受 `feishuTools.attachmentFetchTimeoutMs`(默认 120 s)与 `attachmentFetchMaxBytes`(默认 100 MiB)限制:可调大上限/超时,或用 `file_key` 分批取;资源过期/被撤回时逐资源返回可读失败,可直接重试或让用户重发 |
|
|
358
|
-
| 升级到 v2 后在旧话题里“找不到以前的附件” | 旧 archive 部署的附件仍在旧话题 `attachments/`,但新会话不再枚举该目录;Session
|
|
358
|
+
| 升级到 v2 后在旧话题里“找不到以前的附件” | 旧 archive 部署的附件仍在旧话题 `attachments/`,但新会话不再枚举该目录;Session 历史里的只读路径仍可读。需要长期保留请勿手动删除该话题目录,或从历史消息重新发送/引用 |
|
|
359
359
|
| 话题目录里出现 `attachments/` | 当前生效的是 archive 后端:检查 `attachmentStore`(是否显式设为 `archive`)、宿主是否挂载 `ctx.attachments`、`feishuTools.attachmentFetch` 是否为 `false`。native 后端不会写话题 `attachments/` |
|
|
360
360
|
| 有 Typing 但无卡片 | 权限 `im:message` / `im:message:update`;卡片 2.0 需要较新的飞书客户端(升级客户端后重试) |
|
|
361
361
|
| 卡片正文只到某长度 | 旧版行为;新版单卡目标 28 KB(`CARD_TARGET_BYTES`,飞书上限 30 KB 留 2 KB 冗余),超长正文自动分段为多张续卡(「📄 回复分段 n」,每卡 ≤ 5 个表格,断点避开代码围栏与表格),续卡无面板/页脚、可用满 28 KB,全文保留;推理面板独立 5 KB 上限,超出的推理只保留开头并追加「已截断」提示 |
|
package/docs/persistence.md
CHANGED
|
@@ -5,16 +5,18 @@
|
|
|
5
5
|
| 层 | 负责方 | 落点 |
|
|
6
6
|
|---|---|---|
|
|
7
7
|
| 会话本体(事件日志 + header) | 宿主 `dsh-session-persistence` | `$DSH_HOME/sessions/<workspace>/<sessionId>/session.jsonl.zstd` |
|
|
8
|
-
| 插件自有状态 | 插件 → `ctx.storageDomain` | 域 `
|
|
8
|
+
| 插件自有状态 | 插件 → `ctx.storageDomain` | 域 `channels_routes_v2`,JSON 后端为 `$DSH_HOME/storages/channels_routes_v2.json` |
|
|
9
9
|
| 工作区 / 话题目录 | 磁盘目录 + 宿主 registry + Session 日志 | 工作区 `<baseDir>/p2p|groups/...`(`baseDir` 可在 Web 面板配置,注册表 `$DSH_HOME/storages/workspace.json`);每个话题的工作目录(`<工作区>/topics/<话题>` 或 Git worktree)由 Session 日志的 `working-directory/change` 事件恢复 |
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
用户一律以飞书 `user_id` 标识。open_id 时代的旧域 `channels_routes`(`channels_routes.json`)与按 open_id 命名的旧工作区目录不再读取、也不迁移,原样留在磁盘上,确认不需要后可手动删除。
|
|
11
13
|
## 一、已持久化的内容
|
|
12
14
|
|
|
13
|
-
1. **路由与过期墓碑**(`src/domain.ts` 域 `
|
|
15
|
+
1. **路由与过期墓碑**(`src/domain.ts` 域 `channels_routes_v2`):`routes` 表以 `thread_id` 或降级键 `chat:p2p|group:<chatId>:<userId>` 保存活动 `RouteRecord`(sessionId / chatId / chatType / senderUserId / workspacePath / sessionDir? / worktreeBranch? / cardMessageId / createdAt / lastActivityAt);`workspacePath` 是用户工作区(Session header cwd),`sessionDir` 是该话题的实际工作目录(旧记录缺省 = `workspacePath`);`expired_routes` 以 thread id 保存 TTL 退休后的最小墓碑,跨重启拒绝旧话题误建 Session。`cardMessageId` 每 turn 更新,`lastActivityAt` 供路由退休使用。
|
|
14
16
|
2. **会话本体与事件日志**:宿主代持。插件消费的 `assistant/chunk`、`tool/call`、`tool-workflow/run-*`、`subagent/descriptor` 等全部在持久化日志里,因此 `src/stats.ts` 的 token/缓存统计不单独存,`foldAll()` 重放日志,重启后继续累计。`meta.cwd`、`agentPreset` 随 header 落盘。
|
|
15
17
|
3. **工作区与话题目录**:工作区目录确定性生成、幂等 mkdir,宿主 `workspace.json` 持久化 path/title/sessionIds;话题目录路径记录在路由的 `sessionDir`,实际工作目录同时由 Session 的 `working-directory/change` 事件持久化。
|
|
16
18
|
4. **凭据**:密钥存宿主 `$DSH_HOME/.credentials.yaml`,插件只持引用名。
|
|
17
|
-
5. **准入授权**(`src/domain.ts` 域 `
|
|
19
|
+
5. **准入授权**(`src/domain.ts` 域 `channels_routes_v2`,同样保持 `version: 1` 加法新增):`access_groups` 表以 `chat_id` 保存已授权群(群名/来源/授权人/时间);`access_users` 表以 `user_id` 保存全局用户白名单;`access_requests` 表以不透明 token 保存入群/用户授权申请(含 `messageId`,重启后旧授权卡仍可点击结算;过期状态在同一表落审计)。管理员 user_id 与准入模式属于配置层(profile + Web 覆盖),不在这三张表里。
|
|
18
20
|
|
|
19
21
|
## 二、内存态(有意不持久化)
|
|
20
22
|
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
|
|
46
48
|
## 方案
|
|
47
49
|
|
|
48
|
-
在 `
|
|
50
|
+
在 `channels_routes_v2` 域**新增 `inflight` 表**(加法操作,域 `version` 保持 `1` —— 宿主语义:介质版本与 spec 版本不一致会 `version-mismatch` 拒绝打开,因此**不能** bump;spec 里新增的表在介质上不存在时按空表加载)。
|
|
49
51
|
|
|
50
52
|
**键**:主卡(每 turn 第一张卡)的 message id —— 每 turn 唯一且 attach 时已知,避免并发 turn 互相覆盖。
|
|
51
53
|
|
package/docs/refactor-plan.md
CHANGED
|
@@ -46,7 +46,7 @@ src/
|
|
|
46
46
|
| 问答卡自由文本回答 | ✅ | 无选项问题直接开放答题;阻塞期间普通文本回复即答案(`custom`) |
|
|
47
47
|
| `/model` 查看与切换 | ✅ | `session/request-policy.ts` 统一 provider/model/effort;`ctx.llm.listModels` 校验;经 `command/run` 持久化 |
|
|
48
48
|
| 附件输入 | ✅ | v2:入站图片/文件默认进 DSH 原生附件库;触发消息图片预取、文件由 `feishu_attachment_fetch` 懒取、纯附件静默登记;`archive` 降级保留旧行为(`feishu/inbound-attachments.ts` 落工作区 `attachments/`,合并转发记录转存 `.txt`);详见 [attachment-pipeline-v2.md](attachment-pipeline-v2.md) |
|
|
49
|
-
| 目录清理 |
|
|
49
|
+
| 目录清理 | 已移除 | 插件不删除工作区与话题目录;运维按需手动删除话题目录,下次对话时重建空目录 |
|
|
50
50
|
|
|
51
51
|
## 验证基线
|
|
52
52
|
|
|
@@ -23,14 +23,14 @@ DSH 本身允许这两个维度解耦,见下。
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
<baseDir>/
|
|
26
|
-
p2p/<
|
|
26
|
+
p2p/<user_id>/ # 私聊用户工作区(注册 workspace,header.cwd)
|
|
27
27
|
topics/<topic-token>/ # 话题目录(会话实际工作目录)
|
|
28
28
|
attachments/ # 仅 archive 后端:该话题的入站附件(v2 native 默认不写)
|
|
29
|
-
groups/<chat_id>/<
|
|
29
|
+
groups/<chat_id>/<user_id>/ # 群聊中某成员的工作区(注册 workspace)
|
|
30
30
|
topics/<topic-token>/
|
|
31
31
|
attachments/ # 同上
|
|
32
32
|
topics/... # 旧版「话题=工作区」目录,原样保留,不自动迁移
|
|
33
|
-
_staging/<chatType>/<chatId>/<
|
|
33
|
+
_staging/<chatType>/<chatId>/<userId>/ # 仅旧版:话题建立前的附件暂存(v2 不再写入)
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
- 私聊一个用户一个工作区;群聊**一个群 × 一个用户**一个工作区(不能做成「用户全局一个」:否则同一个人在不同群之间会串文件,A 群会话可能把 B 群的文件内容贴给 A 群成员)。
|
|
@@ -56,7 +56,7 @@ DSH 本身允许这两个维度解耦,见下。
|
|
|
56
56
|
`channels_routes` 的 v1 记录保持可读,新增可选字段:
|
|
57
57
|
|
|
58
58
|
- `sessionDir`:会话实际工作目录(话题目录 / worktree);缺省表示旧记录——直接用 `workspacePath` 作为会话目录。
|
|
59
|
-
- `worktreeBranch`:worktree
|
|
59
|
+
- `worktreeBranch`:worktree 模式下的话题分支,供目录丢失后的恢复使用。
|
|
60
60
|
|
|
61
61
|
`workspacePath` 语义变化为「用户工作区(会话 header cwd / 沙箱根)」。
|
|
62
62
|
|
|
@@ -64,10 +64,10 @@ DSH 本身允许这两个维度解耦,见下。
|
|
|
64
64
|
|
|
65
65
|
- **隔离强度**:话题子目录/worktree 只保证「工具默认在话题目录工作」。同一用户工作区内的绝对路径访问仍然可行;沙箱写边界仍是用户工作区。
|
|
66
66
|
- **硬隔离**(不采用):把 header.cwd 指向话题目录,会话在 GUI 中不再归属工作区。只有当话题来源不可信、必须防互相读取时才值得。
|
|
67
|
-
- **附件**:v2 默认(`attachmentStore: 'native'`)入站图片/文件存 DSH
|
|
67
|
+
- **附件**:v2 默认(`attachmentStore: 'native'`)入站图片/文件存 DSH 原生附件库,**不写话题目录**;显式 archive 后端(或宿主缺原生附件库)才落当前话题目录的 `attachments/`,话题建立前收到的附件先暂存 `_staging/`,首个 Session 创建时一次性迁入。v2 主路径不再写 staging;`_staging` 仅作旧版兼容(成功迁入时 warn),v2 发布后的下一个 minor 版本移除(D12)。
|
|
68
68
|
- **`/renew`**:同一话题内新建 Session,复用同一话题目录(worktree 也复用)。
|
|
69
|
-
-
|
|
70
|
-
- **迁移**:不做自动迁移。旧版 `topics/...`
|
|
69
|
+
- **清理**:插件不删除工作区、话题目录、`_staging/` 或 Session 日志。运维可手动删除不再需要的话题目录:续聊时 `restoreTopicDir` 会重建空目录(worktree 模式下新建 worktree 分支),Session 不会回落到共享工作区根目录。手动删除 worktree checkout 后,Git 记录可用 `git worktree prune` / `branch -D` 回收。
|
|
70
|
+
- **迁移**:不做自动迁移。旧版 `topics/...` 工作区继续服务旧路由;旧版「用户 × 聊天」共享工作区即新的用户工作区,直接复用。
|
|
71
71
|
|
|
72
72
|
## 7. 实现位置
|
|
73
73
|
|
|
@@ -77,5 +77,4 @@ DSH 本身允许这两个维度解耦,见下。
|
|
|
77
77
|
| `src/session/isolation.ts` | 隔离档位判定、工作目录进入、worktree 创建与降级恢复 |
|
|
78
78
|
| `src/routing/inbound.ts` | 建会话与续聊时的目录编排、旧版 `_staging` 一次性迁入、路由写入 |
|
|
79
79
|
| `src/session/manager.ts` | 会话创建/恢复/`/renew` 的目录挂载与附件 guidance |
|
|
80
|
-
| `src/cleanup.ts` + `src/service.ts` | 按话题目录/旧版工作区回收的保留期策略与面板接线 |
|
|
81
80
|
| `src/domain.ts` | 路由记录 v1 兼容扩展(`sessionDir` / `worktreeBranch`) |
|