@sparkelf/dsh-plugin-mobile-gateway 0.8.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/LICENSE +21 -0
- package/PROTOCOL.md +1180 -0
- package/README.md +281 -0
- package/bin/setup-ip.mjs +474 -0
- package/cordis.patch.yml +44 -0
- package/docs/assets/mobile-device-management.png +0 -0
- package/docs/assets/public-access-ui.png +0 -0
- package/docs/assets/whale-girl-ios-app-promo-16x9.png +0 -0
- package/docs/blog-mobile-gateway.md +542 -0
- package/docs/dsh-0.1.5-rc.2-compatibility-audit.md +255 -0
- package/docs/dsh-0.1.6-alpha.1-compatibility-audit.md +122 -0
- package/docs/dsh-rc2-mobile-integration.md +155 -0
- package/docs/multi-gateway-app-integration.md +124 -0
- package/docs/multi-gateway-phase1-acceptance.md +179 -0
- package/docs/multi-gateway-todo.md +137 -0
- package/docs/remote-gateway-refactor-plan.md +52 -0
- package/docs/runtime-architecture.architecture.json +264 -0
- package/docs/runtime-architecture.html +15001 -0
- package/docs/runtime-architecture.visual-check.html +32 -0
- package/docs/runtime-architecture.visual-check.json +548 -0
- package/docs/session-agent-preset-app-integration.md +93 -0
- package/docs/typert-remote-gateway-feature-checklist.md +294 -0
- package/helper/dsh_mobile_gateway_helper.py +227 -0
- package/lib/client.js +520 -0
- package/lib/devices.js +209 -0
- package/lib/dsh-host-adapter.mjs +466 -0
- package/lib/gateway-state.mjs +57 -0
- package/lib/index.mjs +3197 -0
- package/lib/session-follower.mjs +136 -0
- package/lib/wire-json.mjs +7 -0
- package/package.json +50 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# DSH 0.1.5-rc.2 协议兼容性审计
|
|
2
|
+
|
|
3
|
+
核对日期:2026-09-13。本文保留审计与修复过程;当前实现状态以本节为准,后面的合并复核及首次审计为历史记录。
|
|
4
|
+
|
|
5
|
+
## 当前源码修复状态
|
|
6
|
+
|
|
7
|
+
按用户要求,Host 唯一目标为 DSH 0.1.5-rc.2,不再实现旧版分支。基于 `b37b55c` 的本地修改已完成:
|
|
8
|
+
|
|
9
|
+
- 命令保持新版 `submittedAttachments`,目录只读取 `input.attachments`;删除旧 chunkrow 展开。
|
|
10
|
+
- 删除有重复 seq、缺失 turn/step 的全局临时 chunk 转发,新增 `session-follower.mjs`:使用持续 follow 的原子历史/活动生成快照与有序增量。
|
|
11
|
+
- 独立 `assistant-stream` 帧保留 attemptId/revision/index,补齐 turn/step;持久 event 仍使用真实 seq。缺口自动重建,切换/断连/卸载取消订阅,迟到 opening 不污染新订阅。
|
|
12
|
+
- history 输出格式版本/cursor;带 beforeSeq 或 atSeq 的请求必须带格式版本 3,拒绝旧/未标注游标,避免误用迁移前坐标。
|
|
13
|
+
- conversation 历史移除内嵌 stream 和系统/request 元信息;原始视图保留 stream;实时 attempt、中断标识、usage、surface 元信息及工具失败标识已补齐。
|
|
14
|
+
- control baseline 安装 todos/goal,重连和新连接可收到整体投影快照,清除过期值。
|
|
15
|
+
- 新增 decoder 生命周期/缺口/取消测试,以及真实 WebSocket 的独立流、持久消息去重、中途重连、控制基线、历史裁剪和游标版本测试。全量测试通过,gateway dispatch 为 119 项;发布包的 29 端点/34 调用样例契约检查通过。
|
|
16
|
+
|
|
17
|
+
**移动端还需要接入新协议。** 配对无需修改,但独立流展示、缓存重建和游标版本字段不能靠旧客户端自动完成。接入契约见 [rc.2 移动端接入说明](dsh-rc2-mobile-integration.md)。本次修改范围为 gateway 仓库,尚未发布,也未执行新版真实 Host 与 App 全流程联调。
|
|
18
|
+
|
|
19
|
+
## 合并 PR #9 / #10 后的复核(修复前记录)
|
|
20
|
+
|
|
21
|
+
复核提交:`b37b55ce5191d4b1c227899308ada7a67b15bdc5`,版本号仍为 `0.7.3`。相对首次审计,已合入命令适配 PR #9(`bc47e17`)与实时流 PR #10(`82c4da6`)。本节取代下文首次审计中关于当前状态的判断;后面的分析保留作为原始基线。
|
|
22
|
+
|
|
23
|
+
**结论:命令调用层已适配 rc.2;实时流接入了正确来源,但映射存在确定缺陷,尚不能认定流式对话完整兼容。**
|
|
24
|
+
|
|
25
|
+
| 项目 | 合并后状态 | 依据 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `commands.execute` 参数 | 已适配 rc.2 | 改为 `submittedAttachments`;已有 `parseWireImages()` 会增加 `type: image` |
|
|
28
|
+
| 命令目录附件能力 | 已适配 | `commandAcceptsAttachments()` 同时识别 `input.attachments` 和 `input.images` |
|
|
29
|
+
| 原有 Remote 样例输入 | 全部通过 | 重跑精确 rc.2 发布包检查,29 个端点、34 次调用,无失败 |
|
|
30
|
+
| 实时 token 来源 | 已接入,映射不正确 | 监听了 `agent/assistant-stream`,但序号冲突且缺少 turn/step |
|
|
31
|
+
| 活动生成的重连恢复 | 未适配 | 丢弃 start/end,不保留 attempt 状态,没有读取活动 stream 基线 |
|
|
32
|
+
| 历史内嵌 stream / attempt / 格式标识 | 未改动 | 旧探针复跑仍复现原问题 |
|
|
33
|
+
| 旧 DSH 0.1.2-rc.1 命令兼容 | 出现回退兼容问题 | 无条件发送新版参数,旧描述符只接受 `images` |
|
|
34
|
+
|
|
35
|
+
### 阻断项 A:多个 chunk 与最终消息使用同一 seq
|
|
36
|
+
|
|
37
|
+
位置:`lib/index.mjs:2755`。
|
|
38
|
+
|
|
39
|
+
新版 `session.seq` 是“下一个持久事件的序号”,不是临时 chunk 的序号。chunk 不写入 Session 日志,所以连续 chunk 之间它通常不变;接下来提交的 `assistant/message` 会使用这个相同序号。
|
|
40
|
+
|
|
41
|
+
用当前生产 listener、按 rc.2 帧形状输入 start → 两个 chunk → 最终 message → end,实际得到:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
assistant/chunk seq=42 text="Hello"
|
|
45
|
+
assistant/chunk seq=42 text=" world"
|
|
46
|
+
assistant/message seq=42 text="Hello world"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
移动端共享层 `SharedConversationStore.receiveEvent()` 要求 `record.seq > lastSequence`,基线替换还会按 seq 去重。因此该输出不满足现有消费契约,会导致增量被拒绝、同序数据被覆盖或消息无法正确收敛。不能简单对 `session.seq` 加 index 修复,否则仍会占用后续持久序号。
|
|
50
|
+
|
|
51
|
+
应使用独立临时流身份,并在 committed 时与持久 seq 对齐;如果坚持旧移动协议,必须提供完整的虚拟序号及历史/fork 双向映射方案。
|
|
52
|
+
|
|
53
|
+
### 阻断项 B:从 chunk 读取不存在的 turn/step
|
|
54
|
+
|
|
55
|
+
位置:`lib/index.mjs:2748`、`lib/index.mjs:2757`。
|
|
56
|
+
|
|
57
|
+
rc.2 的 start 帧有 `turn/step`,chunk 帧只有 `attemptId/revision/index/time/chunk`。当前代码先过滤掉 start,再读取 `frame.turn/frame.step`,序列化后的两个 chunk 都没有这两个字段。
|
|
58
|
+
|
|
59
|
+
移动端 `ConversationProjection.kt:96` 使用 turn/step 构建消息键,缺失时落到 `-1--1`,无法与带真实 turn/step 的最终消息正确配对。即使修好 seq,这个问题仍单独存在。
|
|
60
|
+
|
|
61
|
+
需要按 session/attempt 保存 start 元信息,在 chunk 时补齐,处理 end/取消/Agent 替换时清理;中途订阅和重连必须从上游快照恢复,不能只依赖此前是否收到过 start。
|
|
62
|
+
|
|
63
|
+
### 旧版本回退兼容
|
|
64
|
+
|
|
65
|
+
PR #9 对目录做了新旧字段兼容,但 execute 仅支持新版参数。用本机精确 `0.1.2-rc.1` 发布描述符复核,当前调用缺少 `images`、多出 `submittedAttachments`。如果还要维持 README 声明的旧 DSH 支持,应增加 Host 能力/版本分支;否则应明确提高最低支持版本。不能把目录双字段识别理解成执行接口也同时兼容。
|
|
66
|
+
|
|
67
|
+
### 复核验证及下一步
|
|
68
|
+
|
|
69
|
+
- 现有 `npm test` 全部通过:115 项 gateway dispatch、认证、LAN、多网关 11 项等。
|
|
70
|
+
- 新增流 listener 在仓库现有测试中没有对应 `agent/assistant-stream` 用例,因此全量测试通过不能排除上述缺陷。
|
|
71
|
+
- 临时探针 `check-post-merge-stream.mjs` 直接执行生产映射函数与 listener,确认重复 seq、turn/step 缺失;输出存于 `post-merge-stream-results.json`。
|
|
72
|
+
- `check-post-merge-old-host.mjs` 验证旧 Host 参数不匹配;`check-event-mapping.mjs` 确认 attempt、工具错误判定、内嵌历史裁剪和格式标识问题仍在。
|
|
73
|
+
- 本次脚本和日志均在 `/private/tmp/dsh-rc2-packs/`;合并后的全量日志为 `gateway-tests-post-merge.log`。未执行新版真实 Host 与真机完整联调。
|
|
74
|
+
|
|
75
|
+
建议先修复实时流两个阻断项并覆盖连续 chunk、最终结算、失败重试、中途重连;随后处理历史代际/缓存和内嵌流精简。普通文件上传、jobs、PTC 详情仍属于后续能力扩展。
|
|
76
|
+
|
|
77
|
+
## 首次审计结论(合并前,历史基线)
|
|
78
|
+
|
|
79
|
+
当前 gateway 不能直接认定为完整兼容 DSH 0.1.5-rc.2。主要问题是命令参数变更、实时 Assistant 流与持久事件分离,以及 Session 历史格式升级后的序号与缓存语义。
|
|
80
|
+
|
|
81
|
+
普通文字/图片 prompt、会话与工作区管理、队列、模型、设置、Goal 和审批的核心入口仍在。无需重写整套网关,也无需因 DSH 升级而更换配对协议。
|
|
82
|
+
|
|
83
|
+
只修命令参数,可以恢复命令调用,但不能恢复逐 token 显示。若要完整支持流式输出、生成中重连和新版轨迹,建议 gateway 与移动端协同增加独立 Assistant 流能力。仅改 gateway、保持旧 App 完整体验,需要另外设计稳定的虚拟序号及双向游标映射,复杂度明显更高。
|
|
84
|
+
|
|
85
|
+
## 核对基线与证据
|
|
86
|
+
|
|
87
|
+
- gateway:`package.json` 为 `0.7.3`,提交 `ee4cf3f75d501107cd75df2023ea15f8bea5e71a`;检查开始时工作区干净。
|
|
88
|
+
- 当前文档声明:DSH `0.1.2-rc.1` / `0.1.3-alpha.1`。本机实际安装的 CLI 及所检查的相关依赖为 `0.1.2-rc.1`。
|
|
89
|
+
- 目标版本的准确 npm 名称:`@deepseek-ai/dsh@0.1.5-rc.2`。
|
|
90
|
+
- 下载了目标版本的 Session/Workspace/Settings Controller、Commands、Goal、Agent Presets、LLM、API Gateway、Session、Agent、User Approval 发布包,检查真实 `typert.host.js` 与类型定义。
|
|
91
|
+
- 官方源码审计固定于 `c291e7961a515f6d7af9304e7fd1d257929aef26`,该源码的 CLI 版本为 `0.1.5-rc.2`。调用契约结论以精确 npm 发布包为准,源码用于追踪行为。
|
|
92
|
+
- 未更新本机 DSH、未启动新版实例、未迁移用户会话。
|
|
93
|
+
|
|
94
|
+
官方入口:[npm 精确版本](https://www.npmjs.com/package/@deepseek-ai/dsh/v/0.1.5-rc.2)、[固定源码提交](https://github.com/deepseek-ai/deepseek-harness/tree/c291e7961a515f6d7af9304e7fd1d257929aef26)。
|
|
95
|
+
|
|
96
|
+
注意三个独立版本:`dsh-mobile-v1` 是移动 WebSocket 子协议,`hello.protocol = 3` 是当前移动握手值,`Session header.version = 3` 是新版 DSH 持久会话格式。后两者数值相同没有兼容含义。
|
|
97
|
+
|
|
98
|
+
## 必须处理的差异
|
|
99
|
+
|
|
100
|
+
### 1. 命令执行参数和命令附件能力改变
|
|
101
|
+
|
|
102
|
+
当前 `lib/dsh-host-adapter.mjs:173` 发送:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
{ agentId: sessionId, line, images }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
新版 `commands/execute` 精确参数是:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
{
|
|
112
|
+
agentId: sessionId,
|
|
113
|
+
line,
|
|
114
|
+
submittedAttachments: images.map(image => ({ type: 'image', ...image })),
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
无附件也必须传 `submittedAttachments: []`。图片不仅需要改字段名,每项还要增加 `type: 'image'`。
|
|
119
|
+
|
|
120
|
+
新版严格边界拒绝旧参数:缺少 `submittedAttachments`,同时存在多余的 `images`。实际网关会在调用业务方法之前抛出 `gateway/arguments-invalid`。影响范围包括 `command-execute`、通过命令实现的菜单操作,以及 `/permission` 权限切换。
|
|
121
|
+
|
|
122
|
+
此外,命令目录的 `input.images` 改为 `input.attachments`。当前 `lib/index.mjs:1099` 的 `commandUiDescriptor()` 仍读取旧字段,会错误地把支持附件的命令标为不支持图片。适配层应把新版目录映射回现有移动 `ui.images`;普通文件支持再单独增加 capability。
|
|
123
|
+
|
|
124
|
+
兼容旧 DSH 时,应在适配初始化阶段选择版本/能力分支,或者读取可用的描述符。不要遇到任意执行异常就换参数重试有副作用的命令。
|
|
125
|
+
|
|
126
|
+
来源:[Commands 执行实现](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/interaction/commands/src/index.ts)、[附件与目录定义](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/interaction/commands/src/types.ts)。
|
|
127
|
+
|
|
128
|
+
### 2. 实时 token 不再是持久 `session/event`
|
|
129
|
+
|
|
130
|
+
| 项目 | 当前 gateway 假设 | DSH 0.1.5-rc.2 |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| 实时增量来源 | `session/event` 的 `assistant/chunk` | `agent/assistant-stream` |
|
|
133
|
+
| 增量身份 | 持久事件 `seq` | `attemptId`、`revision`、`index` |
|
|
134
|
+
| 生命周期 | chunk 后接最终消息 | `start` / `chunk` / `end` |
|
|
135
|
+
| 持久结算 | chunk 与 message 各自入日志 | 一个 `assistant/message` 或 `assistant/attempt`,内嵌 `data.stream` |
|
|
136
|
+
| 中途重连 | 当前 history 仅返回持久事件 | `follow({ assistantStream: true })` 可返回活动 attempt 的快照 |
|
|
137
|
+
|
|
138
|
+
`lib/index.mjs:2703` 只监听 `session/event`,所以正常完成后的 `assistant/message` 仍能收到,但生成中的文字、思考和工具参数增量不再到达手机。当前 `sessionSnapshot()` 还会取第一帧即关闭 stream,且没有请求 `assistantStream: true`,不能恢复活动中的生成前缀。
|
|
139
|
+
|
|
140
|
+
需要在 Host Adapter 增加持续 follow 或独立流适配。优先使用上游 `session/follow` 的快照与增量协定处理选中会话;若保留全局 Cordis 监听,则必须自行处理基线、订阅切换和重连竞态,并避免同一持久事件重复转发。
|
|
141
|
+
|
|
142
|
+
建议移动端新增显式选择的独立 Assistant 流帧,保留 `attemptId/revision/index`,处理:
|
|
143
|
+
|
|
144
|
+
- `start`:建立临时展示状态。
|
|
145
|
+
- `chunk`:按 attempt 和连续 index 追加;发现缺口后重新取基线。
|
|
146
|
+
- `end.committed`:用其 `seq` 与持久消息合并,避免重复显示。
|
|
147
|
+
- `end.abandoned`:清理临时 attempt,不伪造已经提交的会话消息。
|
|
148
|
+
- 生成中重连:安装 `snapshot.assistantStream.activeAttempt` 后接续后续帧。
|
|
149
|
+
|
|
150
|
+
不能直接把 `index`、`revision` 或 `startedAfterSeq` 当成旧 `event.seq`。临时流没有独占的持久序号。本项目的移动端共享层 `SharedConversationStore.receiveEvent()` 明确要求新事件 `seq > lastSequence`;随意合成会与去重、分页、fork 坐标冲突。
|
|
151
|
+
|
|
152
|
+
来源:[Agent 流类型](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/core/agent/src/runtime-types.ts)、[Remote 流类型](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/api/session-controller/src/types.ts)、[follow 快照与增量实现](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/api/session-controller/src/history.ts)。
|
|
153
|
+
|
|
154
|
+
### 3. 历史内嵌 stream、失败 attempt 和精简逻辑
|
|
155
|
+
|
|
156
|
+
新版 `SessionHistoryRecord` 只保留 `{ type: 'event', event }`;原来的顶层 `chunks + chunkrow/*` 由事件中的 `data.stream` 替代。内嵌紧凑记录包括 `text-chunks`、`reasoning-chunks`、`tool-call-chunks` 和单个 `chunk`。
|
|
157
|
+
|
|
158
|
+
当前 `expandHistoryRecords()` 对普通 event 直接透传,因此不能说新版历史会全部解码失败:已完成消息的 `data.message` 仍在,基础历史对话可以继续工作。但存在这些缺口:
|
|
159
|
+
|
|
160
|
+
- `lib/index.mjs:266` 对 `assistant/attempt` 落入默认分支,仅发事件类型,丢掉 turn/step、失败或重试 attempt 的流内容。
|
|
161
|
+
- `trimConversationEvent()` 只丢旧顶层 `assistant/chunk`。新版 `assistant/message.data.stream` 和 `assistant/attempt.data.stream` 仍全部进入 `view: conversation`,造成历史包体和序列化成本膨胀,可能更早触发 `maxBytes` 截断。
|
|
162
|
+
- 当前实时 `assistant/message` 映射不带 `interrupted` 和 usage;适配时应明确保留哪些完成/中断元信息,不能靠旧 usage chunk 推导全部状态。
|
|
163
|
+
|
|
164
|
+
改动建议:在适配层区分旧压缩行与新版内嵌 stream;普通对话历史保留最终 message,移除不需要的完整流;轨迹按需提供 attempt 和紧凑记录。不要为了兼容旧 chunk 直接按 `seq + index` 展开新版流,这会占用其他持久事件的真实坐标。
|
|
165
|
+
|
|
166
|
+
来源:[Session 事件定义](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/core/session/src/types.ts)、[Assistant 紧凑流定义](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/llm/llm/src/assistant-stream.ts)。
|
|
167
|
+
|
|
168
|
+
### 4. Session 格式迁移会改变历史 seq
|
|
169
|
+
|
|
170
|
+
目标发布包明确声明 `SESSION_FORMAT_VERSION = 3`。
|
|
171
|
+
|
|
172
|
+
- V1 → V2:把独立 chunk 收进 Assistant 事件,重新分配存续事件序号。
|
|
173
|
+
- V2 → V3:插入 `system/message`,重排序号与同会话引用;替换区间改为 `surfaceOp: { op: 'replace', startSeq, endSeq }`。
|
|
174
|
+
- 同一个 `sessionId` 升级后,历史 `seq` 不保证仍指向原事件。
|
|
175
|
+
|
|
176
|
+
当前 adapter 从快照取出 records/projections,却丢弃 `header.version`。移动端无法通过 history 响应识别格式切换。旧缓存若继续增量合并、沿用 `beforeSeq` 或把旧 `atSeq` 发给 fork,会有漏消息、错误去重或错误定位的风险;若客户端已经全量替换基线,则需用升级用例证明这一点。
|
|
177
|
+
|
|
178
|
+
需要在移动历史/同步契约中传递会话格式或历史代际标识,格式变化时清除该会话缓存和分页游标,重新建立完整基线;旧缓存没有标识时首次连接新版应执行一次重建。fork 必须使用当前代际中取得的真实持久序号。
|
|
179
|
+
|
|
180
|
+
`system/message` 不应被当作普通用户聊天展示;涉及上下文/轨迹时要定义其呈现规则。需要使用 surface 替换的消费者应适配 `startSeq/endSeq`,并保留必要 provenance。上游负责迁移磁盘日志,gateway 无需自行改写 JSONL。
|
|
181
|
+
|
|
182
|
+
来源:[V1 → V2 迁移](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/session/session-format-v1-to-v2/README.md)、[V2 → V3 迁移](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/session/session-format-v2-to-v3/README.md)。
|
|
183
|
+
|
|
184
|
+
## 兼容时应一并修正的现有缺口
|
|
185
|
+
|
|
186
|
+
这些是当前代码与新版有效数据之间的缺口,不全部代表 rc.2 才新增的破坏性变化。
|
|
187
|
+
|
|
188
|
+
| 缺口 | 当前行为 | 建议改动 |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| 工具失败判定 | `buildWireEvent()` 用 `!!data.error` 判断失败 | 新版允许 `tool-result` block 的 `isError: true` 而无结构化 `data.error`;以 block 状态为准,兼容已有错误元数据 |
|
|
191
|
+
| 控制流重连基线 | baseline 只处理 queues,忽略 projections | 安装 baseline 中的 todos/goal 等快照,避免断连期间变化没有后续增量就无法收敛 |
|
|
192
|
+
| 上游数据归一化边界 | 流帧解析与部分投影/目录兼容逻辑散落在 `lib/index.mjs` | 将 DSH 格式判断与数据映射集中到 Host Adapter;index 保留移动连接、鉴权和业务分发 |
|
|
193
|
+
| 能力声明 | 当前能力列表没有表明 Session 格式/Assistant 流模型 | 区分网关可提供能力与客户端主动选择的能力,避免向旧客户端发送其不能处理的新帧 |
|
|
194
|
+
|
|
195
|
+
## 已有核心接口中未发现直接参数阻断的部分
|
|
196
|
+
|
|
197
|
+
用实际 Host Adapter 的样例调用检查了 29 个不同 Remote 端点、34 次调用。除 `commands/execute` 外,其余样例通过发布包参数名和输入 schema 检查。
|
|
198
|
+
|
|
199
|
+
| 领域 | 核对结果 |
|
|
200
|
+
|---|---|
|
|
201
|
+
| Session list/search/create/prompt/attachment/fork/cancel/updateQueue/rename/selectModel/modelCatalog/canOpenWorkspacePath | 当前入口和样例参数仍有效;`session.list` **仍然使用 `_request`** |
|
|
202
|
+
| session.follow/page/control | 调用形状仍有效;历史/流输出和附加能力需按前文适配 |
|
|
203
|
+
| Workspace follow/create/archiveSession | baseline、归档及现有请求形状仍可用 |
|
|
204
|
+
| Settings describe/update | `ns/patch/expectedRevision` 仍可用;可省略 expectedRevision |
|
|
205
|
+
| Commands list | agentId 入参仍可用;目录的附件能力字段必须映射 |
|
|
206
|
+
| Skills、Agent Presets、LLM providers | 当前 namespace 和样例参数仍有效 |
|
|
207
|
+
| Goals edit/pause/resume/clear | `agentId/ref/request` 和 `maxGoalRounds` 仍有效 |
|
|
208
|
+
| 问答与审批 waterfall | `user-questions/request`、`approval/request` 及原回答形状仍在;不能把它们误判为需要迁移到另一个 RPC |
|
|
209
|
+
| Host 集成 | `typertGateway.invoke/stream`、`agentDefaultModel.currentSelection/saveSelection`、WebServer 注册入口仍在 |
|
|
210
|
+
| Web 管理面板与搜索配置 | sidebar/footer 和 shell/overlay slots 仍在;`session-query-sqlite` 与 `first-search` 配置仍有效 |
|
|
211
|
+
|
|
212
|
+
这不是新版真实 Host 全流程通过的证明;样例输入校验不覆盖业务状态、所有数据组合、返回值语义和真实浏览器集成。
|
|
213
|
+
|
|
214
|
+
## 可选的新能力,不阻塞基础兼容
|
|
215
|
+
|
|
216
|
+
1. **普通文件上传**:新版 prompt 支持 `{ type: 'file', receiptId }`,需先通过 `fileUploads/upload` 获取绑定 Agent 的 receipt。现有 gateway 只有图片提交与自身的文件列表/下载,不能等同于 DSH 文件附件上传。完整接入还需移动上传请求、receipt 绑定及文件 block 展示;不能直接把任意主机路径当 receipt。
|
|
217
|
+
2. **后台 jobs**:新版 `session.control` baseline 包含 jobs,增量有 job 状态。当前 gateway 忽略这部分;若要对齐新版后台任务面板,需要新增映射。它与已有 todos 清单不同。
|
|
218
|
+
3. **PTC 轨迹**:上游迁移把 `tool/code-dispatch*` 改为 `tool/ptc-dispatch*`,内置 preset `code` 改为 `ptc`。gateway 动态获取 preset,通常无需硬编码别名;若要呈现 PTC 详情,则需补充具体事件载荷与移动轨迹解释。不能全局替换字符串 `code`。
|
|
219
|
+
|
|
220
|
+
文件来源:[上传服务](https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/packages/client/file-upload/src/index.ts)。其他扩展见上面的 Session 类型及迁移来源。
|
|
221
|
+
|
|
222
|
+
## 推荐实施顺序与文件范围
|
|
223
|
+
|
|
224
|
+
| 顺序 | 范围 | 交付标准 |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| 1 | `lib/dsh-host-adapter.mjs`:命令参数及目录归一化;`test/host-adapter.test.mjs` | 空附件、图片附件、权限命令均通过目标发布包契约,兼容旧版本分支 |
|
|
227
|
+
| 2 | adapter 的历史输出;`lib/index.mjs` 的历史精简、错误/attempt 映射 | 已完成消息、失败 attempt、中断标识、大历史页行为有明确且验证过的移动输出 |
|
|
228
|
+
| 3 | adapter 的持续 Assistant 流;index 订阅/取消/通道分发;`PROTOCOL.md` | 新客户端可恢复实时输出和生成中重连,旧客户端按声明的降级策略运行 |
|
|
229
|
+
| 4 | 历史格式/代际标识;移动端共享历史与会话投影 | 旧格式缓存不会污染新序号,分页/fork/去重使用正确坐标 |
|
|
230
|
+
| 5 | 控制流 baseline 收敛、真实 Host 集成、文档 | todo/goal/queue 重连状态正确,Web 面板与配对在新版可用 |
|
|
231
|
+
| 后续 | 文件上传、jobs、PTC 完整轨迹 | 按新增 capability 单独交付 |
|
|
232
|
+
|
|
233
|
+
基础命令与普通历史适配可以保留 `dsh-mobile-v1` 和现有握手值。独立 Assistant 流可以采用向后兼容的显式订阅扩展;若选择改变既有 `event.seq` 的含义,就必须作为不兼容移动协议处理,不能静默修改。
|
|
234
|
+
|
|
235
|
+
## 验证结果与后续验收清单
|
|
236
|
+
|
|
237
|
+
本次已完成:
|
|
238
|
+
|
|
239
|
+
- `npm test` 全部通过,包括 gateway dispatch 115 项、认证、LAN 与多网关 11 项。首次沙箱运行因无法绑定测试端口退出,允许本地监听后重跑成功。
|
|
240
|
+
- 发布包契约检查:29 个端点、34 次调用;唯一失败项为旧 `commands/execute` 参数。新版空附件和带 `type: image` 的图片样例均通过。
|
|
241
|
+
- 事件映射探针复现:`assistant/attempt` 输出仅剩类型;合法 `isError: true` 且无 `data.error` 的工具失败被输出为 false;conversation 精简仍携带内嵌 stream;history 未输出格式版本。
|
|
242
|
+
|
|
243
|
+
审计脚本与详细输出暂存在 `/private/tmp/dsh-rc2-packs/`:`check-contracts.mjs`、`contract-results.json`、`check-event-mapping.mjs`、`gateway-tests.log`。临时目录可能被系统清理;正式实施时应把相关用例固化到仓库测试,并使用固定版本 fixture 或隔离的真实 Host。
|
|
244
|
+
|
|
245
|
+
正式兼容发布前至少补测:
|
|
246
|
+
|
|
247
|
+
- 旧 DSH 与 0.1.5-rc.2 的命令、附件目录、权限切换回归。
|
|
248
|
+
- 新版 stream start/chunk/end、失败重试、取消、无可见输出、工具参数流。
|
|
249
|
+
- 生成中断网重连、订阅切换、双通道连接;快照与增量无遗漏/重复。
|
|
250
|
+
- V1/V2 会话经真实上游迁移后打开、分页、fork,以及旧移动缓存失效。
|
|
251
|
+
- 内嵌流特别大的 history,conversation 视图确实去除流明细。
|
|
252
|
+
- 工具失败仅存在 block.isError、控制流重连安装完整投影基线。
|
|
253
|
+
- 真实 DSH Host 中插件加载、Web 管理入口、配对、普通消息、审批与重连闭环。
|
|
254
|
+
|
|
255
|
+
本次结论覆盖发布包契约和源码分析;尚未执行最后一组新版真实 Host 联调。
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# DSH 0.1.6-alpha.1 协议兼容性审计
|
|
2
|
+
|
|
3
|
+
核对日期:2026-09-16。
|
|
4
|
+
|
|
5
|
+
## 结论
|
|
6
|
+
|
|
7
|
+
当前 gateway `0.7.5` **不能直接声明完整兼容 DSH `0.1.6-alpha.1`**。确定的主要阻断是权限选择协议拆分:新版会话投影不再包含候选列表,而 gateway 的权限菜单仍依赖该列表,导致读取菜单和通过菜单切换权限均失败。
|
|
8
|
+
|
|
9
|
+
核心会话、消息提交、历史分页、Assistant 独立流、普通问答和人工审批没有发现本次升级引入的接口阻断。另有默认模式的条件性语义差异、SSH 文件访问缺口、版本误报及新增事件元数据丢失。
|
|
10
|
+
|
|
11
|
+
本次仅增加审计文档;没有修改生产代码、升级本机 DSH 或迁移用户会话。
|
|
12
|
+
|
|
13
|
+
## 基线与验证范围
|
|
14
|
+
|
|
15
|
+
- gateway:提交 `a92a039332bcfa4a1fadc55888e6b7a7e80dfd3d`,版本 `0.7.5`;开始审计时工作区干净。
|
|
16
|
+
- 上游:对比官方 `dsh-v0.1.5-rc.2` 与 `dsh-v0.1.6-alpha.1` 两个 tag 的完整源码。
|
|
17
|
+
- 发布包:分别下载两版 Session Controller、Workspace Controller、Settings Controller、Commands、Goal、Agent Presets、LLM 共七个包,比较生成的 `typert.host.js`;另外检查新版 Permission Presets 发布包。
|
|
18
|
+
- 七组包的旧版 55 个 Remote 端点没有删除;新版增加 `workspace/unarchiveSession`。现有入参 schema 唯一差异为 `session/updateQueue` 的图片块增加可选 `offloaded: true`,没有要求旧调用增加必填参数。此数量不代表 DSH 全部端点;新 Permission Presets 包另提供 `permissionPresets/catalog`。
|
|
19
|
+
- 用现有 Host Adapter 测试的真实调用,附加新版发布描述符的参数名与 schema 校验:18 次调用、14 个不同端点通过。
|
|
20
|
+
- `npm test` 全部通过,包括 125 项 gateway dispatch 检查、认证配对、LAN、多网关 11 项检查,以及 Host Adapter、Session Follower、Unicode wire、setup 测试。首次运行被沙箱禁止监听端口;允许本地监听后完整运行成功。
|
|
21
|
+
- 使用当前生产 `handleQuery()` 配合新版权限投影样例,复现下述权限菜单问题。
|
|
22
|
+
|
|
23
|
+
验证边界:没有执行新版真实 Host 与手机全流程联调。发布描述符输入验证及旧测试通过,不代表业务行为完整兼容;权限问题恰好说明旧测试夹具可能掩盖新版差异。
|
|
24
|
+
|
|
25
|
+
官方依据:[发布说明](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.6-alpha.1)、[版本比较](https://github.com/deepseek-ai/deepseek-harness/compare/dsh-v0.1.5-rc.2...dsh-v0.1.6-alpha.1)。
|
|
26
|
+
|
|
27
|
+
## 1. 必须适配:权限目录从会话投影拆出
|
|
28
|
+
|
|
29
|
+
旧版 `projections.values.permissions`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{"options":[{"value":"workspace-write","name":"Workspace Write"}],"currentValue":"workspace-write"}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
新版会话投影只保留:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{"currentValue":"workspace-write"}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
候选项改从新的进程级目录读取:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
typertGateway.invoke({
|
|
45
|
+
namespace: 'permissionPresets',
|
|
46
|
+
method: 'catalog',
|
|
47
|
+
args: {},
|
|
48
|
+
})
|
|
49
|
+
// 返回 { options: [{ value, name, description? }] }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
源码在 `packages/interaction/permission-presets/src/index.ts` 注册的投影视图明确只有 `currentValue`;这不是单纯的类型重命名。新版 `auto` 由运行中的 Auto review 插件动态贡献,不属于默认权限设置中的静态候选表。`custom` 是派生的当前状态,不是可切换候选项。
|
|
53
|
+
|
|
54
|
+
gateway 受影响位置:
|
|
55
|
+
|
|
56
|
+
- `lib/index.mjs:1211` 的 `loadCommandOptions()` 强制要求 `permissions.options`。
|
|
57
|
+
- `lib/index.mjs:1245` 的 `selectCommandOption()` 先查询上述菜单,因此写入前就失败。
|
|
58
|
+
- `lib/index.mjs:1629` 的旧 `permission-options` 接口直接透传投影,新版返回值不再包含 `sessionPermissions.options`;它读取的 settings namespace 也无法补全动态 Auto 候选。
|
|
59
|
+
|
|
60
|
+
生产函数复现结果:
|
|
61
|
+
|
|
62
|
+
| 请求 | 当前结果 |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `command-options`,`command=permission` | `command-options-unavailable` |
|
|
65
|
+
| `command-select`,`command=permission` | 同样失败,尚未执行写入;还沿用 `requestType=command-options` |
|
|
66
|
+
| `permission-options` | 成功,但 `sessionPermissions` 只剩 `currentValue` |
|
|
67
|
+
| 直接 `permission`,明确传入有效名称 | 可到达原有 `/permission` 执行入口;没有被菜单前置检查阻断 |
|
|
68
|
+
|
|
69
|
+
建议:Host Adapter 增加权限目录查询,将 catalog 的候选项与会话投影的 `currentValue` 合成现有移动菜单和兼容响应。保留 `/permission` 写入路径;动态目录需在使用前刷新,若缓存则处理 `permission-presets/catalog-changed`。会话 Auto 选项不能写成全局默认权限。
|
|
70
|
+
|
|
71
|
+
这可以在 gateway 内适配,现有 `command-options` / `command-select` 移动消息形状可继续使用。若 App 自行解析历史中的权限投影,则也需要核对其是否依赖旧 `options` 字段。
|
|
72
|
+
|
|
73
|
+
依据:[权限类型变更](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/interaction/permission-presets/src/types.ts)、[权限目录与投影实现](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/interaction/permission-presets/src/index.ts)。
|
|
74
|
+
|
|
75
|
+
## 2. 条件性不一致与元数据缺口
|
|
76
|
+
|
|
77
|
+
| 项目 | 新版变化及当前影响 | 建议 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| 默认 Agent 模式 | 增加 `modeSelectionEnabled`。为 false 时,Host 对未指定 preset 的新会话使用配置默认值,忽略保存的用户默认值。gateway 的 `defaults` 仍返回 settings 中保存的 `default`;`set-default` 返回 `applied: true` 也不代表后续新会话采用该值 | 查询有效默认值时结合 `agentPresets/list` 的 `isDefault` 和 `modeSelectionEnabled`;区分已保存与实际生效。`agent-presets` 原始响应已经透传新字段 |
|
|
80
|
+
| SSH 工作区 | 新版文件/进程可通过 SSH provider 操作远端。gateway `resolveSessionRoot()`、文件列表、下载及目录操作仍使用本机 `node:fs` | 启用 SSH provider 的部署不应宣称文件功能兼容;接入 Host 文件能力或明确禁用。远端路径本机不存在会失败,同名本机路径存在时可能访问错误目录。普通本地工作区不受此项影响 |
|
|
81
|
+
| 宿主版本误报 | `lib/dsh-host-adapter.mjs:16` 将 `DSH_VERSION` 写死为 `0.1.5-rc.2`;`host.describe` 和 `hello` 均使用该值 | 改为真实宿主版本或明确的兼容目标字段。这不是运行时版本拒绝逻辑,但会误导诊断/版本判断 |
|
|
82
|
+
| `image/offload` | 新增持久事件,`data.targets` 包含消息 `seq` 和 `imageIndexes`。gateway 接受其真实序号,历史保留原始数据;实时 `buildWireEvent()` 的默认分支只发事件类型,丢弃 targets | 基础聊天不因未知类型直接中断,但实时 offload 语义缺失;需要展示模型实际保留的图片上下文时补齐映射 |
|
|
83
|
+
| Auto review 拒绝原因 | `tool/result.data.error` 增加可选 `reason`,位于模型消息之外。实时映射只保留 `isError` 和模型内容 preview,丢失结构化错误名、代码和原始原因 | 保留必要的 error 元信息。历史仍保留这些数据,实时展示存在缺口 |
|
|
84
|
+
|
|
85
|
+
Auto review 使用 `tools/pre-execute` 的自动决策入口。普通 `approval/request` / `user-questions/request` 的签名及回答形状没有改变,不能据此把所有审批流判定为失效。
|
|
86
|
+
|
|
87
|
+
依据:[默认模式选择策略](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/preset/agent-presets/src/index.ts)、[SSH 文件系统提供方](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/ssh/fs-ssh/README.zh.md)、[image/offload 定义](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/compaction/compaction-image-offload/src/projection.ts)、[Session 工具错误定义](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/core/session/src/types.ts)、[Auto review 实现](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/experimental/auto-review/src/index.ts)。
|
|
88
|
+
|
|
89
|
+
## 3. 保持兼容或仅增加能力的部分
|
|
90
|
+
|
|
91
|
+
| 范围 | 核对结果 |
|
|
92
|
+
|---|---|
|
|
93
|
+
| 会话格式 | `SESSION_FORMAT_VERSION` 仍为 3。本次没有 rc.2 那次跨格式的历史游标迁移;不要把 DSH 软件版本、Session 格式和移动握手值混为一谈 |
|
|
94
|
+
| 会话读取和控制 | `session/list` 仍使用 `{ _request: {} }`;`follow/page/control` 入参保持不变。Assistant 的快照及 `start/chunk/end` 协定保持不变 |
|
|
95
|
+
| 消息、命令、队列 | prompt 原有文字和图片结构仍有效;命令仍使用 `submittedAttachments`;队列图片块新增可选 `offloaded`,现有文字编辑请求不需修改 |
|
|
96
|
+
| Session fork | 参数不变,切点改为所选边界 `seq + 1`,不再继续带入下次 `turn/start` 前的后续输入/设置。这是上游行为修复,gateway 原有 atSeq 转发无需改协议 |
|
|
97
|
+
| 工作区归档 | 原有 archive 和归档集合推送保持;增加 `workspace/unarchiveSession({ request: { sessionId } })`。gateway 尚未提供恢复归档请求,但从 Web 恢复后,可通过已有 workspace stream 同步归档集合 |
|
|
98
|
+
| Skill / 命令目录 | Skill 增加可选 `path`,CommandDescriptor 增加可选 `definitionId`;当前按名字执行不受影响,移动组合菜单没有保留这两个新元信息 |
|
|
99
|
+
| 插件生命周期 | `agent/session-start` 移除,`agent/created` 改为等待完成的异步串行初始化;gateway 不监听这两个事件,不需要修改该 hook |
|
|
100
|
+
| 同步 Session 历史 API | `snapshotEvents/eventAt/ownEvents` 是弃用,非本版本全部删除。gateway 通过 Remote follow/page 读取,不直接依赖这些同步 API |
|
|
101
|
+
| Host 服务及 Web UI | `typertGateway.invoke/stream`、`agentDefaultModel`、`webServer.register/registerUpgrade` 及当前 sidebar/footer、shell/overlay 集成入口未发现阻断变化 |
|
|
102
|
+
|
|
103
|
+
依据:[Session Remote 类型](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/api/session-controller/src/types.ts)、[fork 实现](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/api/session-controller/src/commands.ts)、[Workspace Controller](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/api/workspace-controller/src/index.ts)、[Agent 生命周期](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/core/agent/src/runtime-types.ts)。
|
|
104
|
+
|
|
105
|
+
## 4. 模型协议和部署配置变化
|
|
106
|
+
|
|
107
|
+
DeepSeek 默认由 Chat Completions 改为 Messages,并增加 Files API 图片复用。这发生在 Host 到模型服务之间;gateway 使用 Host 的统一 Session/LLM 数据模型,不需要把移动 WebSocket 改成 Anthropic Messages。
|
|
108
|
+
|
|
109
|
+
显式配置旧官方根地址的部署,需要移除覆盖或使用 `https://api.deepseek.com/anthropic`;自定义 API 地址会保留,因此要核对其与所选协议是否匹配。否则可能出现手机正常连接、模型请求失败的情况。本次没有读取用户凭据或验证实际模型服务配置。
|
|
110
|
+
|
|
111
|
+
PTC 包/服务统一改为 `ptc-runtime`、工作流执行器改为 `workflow-ptc`、E2B 移除、Ralph 默认关闭,以及 Sandbox/Shell 异步接口变化,均需使用相关自定义插件或配置的部署另行适配。当前 gateway 没有这些直接依赖,bundle patch 也没有引用这些旧名称。终端、MCP resources、Browser/Computer Use 属于新增能力,不能据此认定当前移动端已经支持。
|
|
112
|
+
|
|
113
|
+
依据:[DeepSeek 协议默认配置](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.6-alpha.1/packages/llm/llm-deepseek/src/config.ts)、[官方发布说明](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.6-alpha.1)。
|
|
114
|
+
|
|
115
|
+
## 建议顺序
|
|
116
|
+
|
|
117
|
+
1. 优先修复权限目录适配,覆盖新版只有 `currentValue` 的投影、动态 Auto、派生 custom、菜单查询与选择及兼容接口。
|
|
118
|
+
2. 修正宿主版本信息和默认 Agent 模式的有效值语义。
|
|
119
|
+
3. 本地工作区完成真实 Host 与 App 的消息、生成中重连、历史分页、fork、权限切换、问答/审批、文件下载及归档同步联调。
|
|
120
|
+
4. 单独决定是否接入恢复归档、SSH 文件访问、image/offload 展示和 Auto review 错误详情。
|
|
121
|
+
|
|
122
|
+
临时证据目录:`/private/tmp/dsh-alpha1-audit/`,含双版本源码、npm 包、`check-contract.mjs`、`schema-deltas.json`、`reproduce-permissions.mjs`、`permission-reproduction.json` 和完整测试日志。临时探针没有改动仓库生产实现或原有测试。
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# DSH 0.1.5-rc.2 移动端接入
|
|
2
|
+
|
|
3
|
+
本次源码以 DSH 0.1.5-rc.2 为唯一 Host 基线,不提供旧 Host 分支。配对、设备 token、双连接和 `dsh-mobile-v1` 保持原样;以下是新的实时订阅和历史坐标约定。App 需要同步实现本说明,不能把独立流帧交给旧的 `SessionEvent.seq` reducer。
|
|
4
|
+
|
|
5
|
+
## 1. 能力与历史缓存
|
|
6
|
+
|
|
7
|
+
`hello` 增加:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"kind": "hello",
|
|
12
|
+
"protocol": 3,
|
|
13
|
+
"dshVersion": "0.1.5-rc.2",
|
|
14
|
+
"historyFormatVersion": 3,
|
|
15
|
+
"capabilities": ["assistant-stream-v1", "history-format-version", "projection-baseline"]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
示例只列出新增字段和能力;原有能力仍在。`dshVersion` 是本适配器的目标版本声明,非运行时 CLI 版本探测。握手协议值 3 与 Session 格式值 3 是独立概念。
|
|
20
|
+
|
|
21
|
+
缓存键至少包含 `(gatewayId, sessionId, historyFormatVersion)`。缓存没有格式版本或与服务端不同,必须丢弃该 Session 的旧历史、分页游标、fork 锚点及临时输出,重新安装基线。DSH 自己迁移磁盘日志,客户端不得迁移旧 seq。
|
|
22
|
+
|
|
23
|
+
## 2. 订阅一个会话
|
|
24
|
+
|
|
25
|
+
在 conversation 连接(或未拆分的连接)发送:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{ "type": "subscribe", "sessionId": "s1", "assistantStream": true }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
先收到确认:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "kind": "subscribed", "sessionId": "s1", "assistantStream": true, "subscriptionId": "<UUID>" }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
再收到原子基线:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"kind": "session-snapshot",
|
|
42
|
+
"sessionId": "s1",
|
|
43
|
+
"subscriptionId": "<UUID>",
|
|
44
|
+
"streamId": "<本次上游 follow 的 UUID>",
|
|
45
|
+
"historyFormatVersion": 3,
|
|
46
|
+
"cursor": 41,
|
|
47
|
+
"replace": true,
|
|
48
|
+
"view": "conversation",
|
|
49
|
+
"events": [],
|
|
50
|
+
"bytes": 0,
|
|
51
|
+
"hasMore": false,
|
|
52
|
+
"projections": { "asOfSeq": 41, "values": {} },
|
|
53
|
+
"assistantStream": {
|
|
54
|
+
"revision": 2,
|
|
55
|
+
"activeAttempt": {
|
|
56
|
+
"attemptId": "s1:1",
|
|
57
|
+
"turn": 2,
|
|
58
|
+
"step": 3,
|
|
59
|
+
"startedAfterSeq": 41,
|
|
60
|
+
"nextIndex": 1,
|
|
61
|
+
"stream": [{ "type": "text-chunks", "time0": 100, "index": 0, "texts": ["Hello"], "dt": [] }]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
这是最新历史窗口和活动生成状态在同一个上游切点的快照。没有正在生成的 attempt 时省略 `activeAttempt`,客户端应清空临时输出。`hasMore` 为 true 时同时返回 `nextBeforeSeq`,按普通历史请求补更早内容;`replace` 指当前订阅基线需要替换,不能把该窗口误认为完整历史。
|
|
68
|
+
|
|
69
|
+
首屏窗口:网关向 Host `session.follow` 显式传入 `maxMessages: 12`,再将 conversation 事件限制在约 256 KiB 的最新连续后缀。单条最新消息不可拆分,独自超限时仍完整保留;活动 attempt 和 projections 不计入此正文预算,也不裁剪。被预算排除的较早记录通过 `hasMore/nextBeforeSeq` 按原协议分页获取。历史正文窗口可以缩小,但 `cursor` 始终保留 Host 的原子切点。
|
|
70
|
+
|
|
71
|
+
读取更早页时,为获取当前切点而打开的短暂 follow 仅请求 1 条消息;实际 `session.page` 继续使用客户端请求的 `maxMessages`。此调整同时作用于 iOS 和 Android,不依赖本地历史磁盘缓存。
|
|
72
|
+
|
|
73
|
+
客户端只处理当前 `subscriptionId`。收到新 snapshot 后更新 `streamId`,清理上一个 stream 的临时状态,并把持久流水位设为 `cursor`,不能使用精简 events 的最大 seq 代替它:精简视图可能隐藏了末尾的系统事件。
|
|
74
|
+
|
|
75
|
+
未发送 `assistantStream: true` 的连接仍接收持久 `event`,不会收到伪装成持久事件的 token。control 连接不能订阅对话流。
|
|
76
|
+
|
|
77
|
+
## 3. 临时增量与最终结算
|
|
78
|
+
|
|
79
|
+
每个独立流帧携带 `sessionId/subscriptionId/streamId`,其根节点没有 `seq`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"kind": "assistant-stream",
|
|
84
|
+
"sessionId": "s1",
|
|
85
|
+
"subscriptionId": "<UUID>",
|
|
86
|
+
"streamId": "<UUID>",
|
|
87
|
+
"frame": {
|
|
88
|
+
"type": "chunk",
|
|
89
|
+
"attemptId": "s1:1",
|
|
90
|
+
"revision": 3,
|
|
91
|
+
"index": 1,
|
|
92
|
+
"time": 101,
|
|
93
|
+
"turn": 2,
|
|
94
|
+
"step": 3,
|
|
95
|
+
"chunk": { "type": "text-delta", "index": 0, "text": " world" }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `start`:含 `attemptId/revision/startedAfterSeq/turn/step`,新建临时消息。
|
|
101
|
+
- `chunk`:原样保留上游 chunk(文字、思考、工具参数、usage、finish 等),网关从 start 或 snapshot 补齐 `turn/step`。
|
|
102
|
+
- `end`:含 `attemptId/revision/index/turn/step/outcome`;`index` 是该 attempt 的 chunk 总数。
|
|
103
|
+
- `end.outcome = { kind: "committed", eventType, seq }`:之前的持久 event 已入流,使用 seq 关联 `assistant/message` 或 `assistant/attempt`,合并/清理临时消息,避免重复显示。
|
|
104
|
+
- `end.outcome = { kind: "abandoned" }`:清理临时 attempt,不生成虚构的持久消息。没有任何 chunk 的 attempt 也可能直接结束。
|
|
105
|
+
|
|
106
|
+
`revision` 属于一次 Agent 生命周期,`index` 属于一次 attempt。不要把其中任何一个写入持久事件 seq。网关检查连续性;App 仍应按当前 streamId/attemptId 去重和隔离展示。
|
|
107
|
+
|
|
108
|
+
持久帧仍使用 `kind: "event"`,由同一个上游 follow 有序转发,带真实 seq 以及 subscriptionId/streamId。不会再与全局 `session/event` 重复转发。消息附带中断标识 `interrupted` 和可选 usage;失败 attempt 保留 turn/step 与紧凑 stream。`surfaceOp` 和 `sourceEventSeqs` 在存在时位于持久帧根节点。
|
|
109
|
+
|
|
110
|
+
## 4. 重连、切换与取消
|
|
111
|
+
|
|
112
|
+
- 上游结束、连续性断档或临时失败:发送 `session-stream-reset`,含 `sessionId/subscriptionId/streamId/code/message/retrying`。立即冻结旧 stream 并清理临时输出,等待新 snapshot;网关按退避间隔重新打开 follow。
|
|
113
|
+
- 新 snapshot 使用新的 streamId,可能有重叠历史,必须替换基线后再继续,不能按旧流水位直接追加。
|
|
114
|
+
- 不支持的 Session 格式等永久失败:reset 的 `retrying: false`,App 提示错误;网关不切换到不安全的全局流回退。
|
|
115
|
+
- 手机 WebSocket 断开:重连、重新订阅。网关从上游快照取得已经生成但尚未提交的前缀,随后只发送基线之后的增量。
|
|
116
|
+
- 切换会话或 `unsubscribe`:网关取消旧 follow,迟到的旧 opening 不再发送。App 清除旧 subscriptionId 的临时显示。
|
|
117
|
+
- `session-cancel` 仍走控制连接,最终以持久中断消息/attempt 和 end 为准。
|
|
118
|
+
|
|
119
|
+
## 5. 历史、分页与 fork
|
|
120
|
+
|
|
121
|
+
普通 history 首次请求无需游标:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "type": "history", "sessionId": "s1", "view": "conversation" }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
返回值新增 `historyFormatVersion: 3`、`cursor`。conversation 视图隐藏 `system/message`、`request/header`、`request/context`,移除 Assistant 事件中的 `data.stream`,保留真实 seq、最终 message、usage 和 interrupted。默认原始视图保留完整内嵌 stream,用于按需读取轨迹。全部为隐藏事件的页仍返回可前进的 nextBeforeSeq。
|
|
128
|
+
|
|
129
|
+
携带游标必须同时携带读取该游标时的格式版本:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{ "type": "history", "sessionId": "s1", "beforeSeq": 20, "historyFormatVersion": 3 }
|
|
133
|
+
{ "type": "fork", "sessionId": "s1", "atSeq": 42, "historyFormatVersion": 3 }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
缺失版本、旧版本或版本不匹配,返回 `code: "history-format-mismatch"`、`resetRequired: true` 和当前 historyFormatVersion,且不会调用上游分页/fork。客户端必须重建缓存后再取新坐标,不能给旧坐标补一个 3 后重试。游标必须是非负安全整数。省略 atSeq 的 fork 仍表示从最近完成轮次分叉。
|
|
137
|
+
|
|
138
|
+
收到 session-snapshot 后,不要用晚到的普通 history 响应覆盖正在进行的订阅基线;更早历史页只合并到历史部分。这样可以避免独立查询与实时流交错造成回滚。
|
|
139
|
+
|
|
140
|
+
## 6. 控制基线
|
|
141
|
+
|
|
142
|
+
连接建立和上游 control baseline 更新时,控制连接收到:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"kind": "projection-baseline",
|
|
147
|
+
"projections": { "s1": { "asOfSeq": 42, "values": { "todos": [], "goal": null } } }
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
这是网关当前持有的 todos/goal 快照,按整体替换处理;缺失 Session/键代表清除旧值。空对象也具有清除语义。后续仍用 `tasks-updated` / `goal-updated` 增量更新。网关也向原有消费者发对应基线值,避免断连期间修改的 Goal 只能等下一次增量才出现。
|
|
152
|
+
|
|
153
|
+
## 验证范围
|
|
154
|
+
|
|
155
|
+
仓库测试覆盖 rc.2 的参数封装、两种独立序号、缺口恢复、取消清理、真实 WebSocket 的原子基线/最终结算、重连前缀、历史裁剪、游标版本校验、工具失败与控制投影。真实 rc.2 Host 与 App 联调仍需在客户端接入后执行。普通文件上传、jobs 和 PTC 专用 UI 属于后续能力扩展。
|