- commit
- 7b523f6
- parent
- e8fba02
- author
- im_wower
- date
- 2026-04-02 08:50:26 +0800 CST
docs: add conversation/execution layer protocol proposal
1 files changed,
+443,
-0
Raw patch view.
1diff --git a/plans/CONVERSATION_EXECUTION_LAYER_PROTOCOL.md b/plans/CONVERSATION_EXECUTION_LAYER_PROTOCOL.md
2new file mode 100644
3index 0000000000000000000000000000000000000000..0cc3a3ff41614c65e320f87927d6c7f3ace10745
4--- /dev/null
5+++ b/plans/CONVERSATION_EXECUTION_LAYER_PROTOCOL.md
6@@ -0,0 +1,443 @@
7+# 对话层 / 执行层分层协议提案
8+
9+日期:`2026-04-02`
10+状态:`proposed`
11+
12+## 目标
13+
14+把当前自动化主链从“页面 / tab 驱动”收口成“对话 / 转发任务驱动”:
15+
16+- `conductor` 的协议层只管理对话与任务
17+- Firefox 插件的执行层只管理凭证、模板、页面上下文和真实代发
18+- `tabId`、`pageUrl`、`shellPage` 不再成为上层业务主键
19+- `browser.final_message`、`proxy_delivery`、`renewal` 使用同一套对话主语义
20+
21+## 为什么要分层
22+
23+当前实现能工作,但抽象泄漏较重:
24+
25+- `conductor` 会把 `active_link` 收口到 `tab:<id>`
26+- renewal / dispatcher 直接依赖 `tabId` 定位目标页面
27+- 页面漂移、shell tab 恢复、新 business tab 建立后,协议层会感知到执行层细节
28+- 同一平台存在多个页面时,`tab` 很容易从执行细节升级成错误的业务主键
29+
30+更稳定的路由真相源应当是:
31+
32+- `platform`
33+- `remote_conversation_id`
34+- `local_conversation_id`
35+- `assistant_message_id`
36+
37+执行层仍然可以使用页面和 tab,但只能作为插件内部 runtime 解析手段。
38+
39+## 分层总览
40+
41+### 对话层
42+
43+只回答三个问题:
44+
45+- 这是哪条对话
46+- 这次要发什么任务
47+- 这条任务当前推进到了什么状态
48+
49+### 执行层
50+
51+只回答三个问题:
52+
53+- 这条对话当前有哪些可用运行时上下文
54+- 该用哪份凭证、模板、接口形状来发
55+- 这次发送和后续 final-message 是否已经被真实页面接收和观察到
56+
57+## 一、对话层协议与设计
58+
59+### 1.1 协议对象
60+
61+对话层只保留三个核心对象:
62+
63+- `Conversation`
64+- `MessageAnchor`
65+- `DeliveryJob`
66+
67+### 1.2 `Conversation`
68+
69+`Conversation` 是上层业务主键,不暴露页面细节。
70+
71+建议字段:
72+
73+```text
74+conversation_key TEXT // 建议为 platform + remote_conversation_id 的稳定组合键
75+platform TEXT
76+remote_conversation_id TEXT
77+local_conversation_id TEXT
78+
79+mode TEXT // auto | manual | paused
80+pause_reason TEXT
81+
82+latest_assistant_message_id TEXT
83+latest_observed_at INTEGER
84+
85+created_at INTEGER
86+updated_at INTEGER
87+```
88+
89+约束:
90+
91+- `platform + remote_conversation_id` 应视为平台侧稳定业务键
92+- `local_conversation_id` 是 conductor 本地稳定键
93+- `tabId`、`pageUrl`、`routePath` 不进入这张主对象
94+
95+### 1.3 `MessageAnchor`
96+
97+`MessageAnchor` 表示“从哪一条 assistant 消息继续往下写”。
98+
99+建议字段:
100+
101+```text
102+anchor_id TEXT
103+conversation_key TEXT
104+assistant_message_id TEXT
105+observed_at INTEGER
106+source TEXT // browser.final_message | snapshot | replay
107+
108+diagnostic_tab_id INTEGER NULL
109+diagnostic_page_url TEXT NULL
110+```
111+
112+约束:
113+
114+- `assistant_message_id` 是续写锚点
115+- `diagnostic_*` 只做排障,不参与路由主判断
116+
117+### 1.4 `DeliveryJob`
118+
119+`DeliveryJob` 是 conductor 发给执行层的唯一正式任务对象。
120+
121+建议字段:
122+
123+```text
124+job_id TEXT
125+conversation_key TEXT
126+assistant_message_id TEXT
127+message_text TEXT
128+delivery_kind TEXT // proxy_delivery
129+dedupe_key TEXT
130+
131+status TEXT // pending | dispatched | acked | finalized | retry_wait | failed
132+attempt_count INTEGER
133+max_attempts INTEGER
134+last_error TEXT
135+
136+created_at INTEGER
137+updated_at INTEGER
138+started_at INTEGER NULL
139+finished_at INTEGER NULL
140+```
141+
142+约束:
143+
144+- 对话层任务只引用 `conversation_key`
145+- 不要求上层直接持有 `tabId`
146+- 任务的 retry / pause / manual / drain 都按 `conversation` 语义推进
147+
148+### 1.5 对话层状态机
149+
150+`Conversation` 状态:
151+
152+- `auto`
153+- `manual`
154+- `paused`
155+
156+`DeliveryJob` 状态:
157+
158+- `pending`
159+- `dispatched`
160+- `acked`
161+- `finalized`
162+- `retry_wait`
163+- `failed`
164+
165+语义:
166+
167+- `pending`:任务已生成,等待插件执行
168+- `dispatched`:任务已发送到插件
169+- `acked`:插件确认已把请求派发到真实平台请求链
170+- `finalized`:已观察到对应对话的后续 final-message 或等价完成信号
171+- `retry_wait`:本次失败,但允许后续重试
172+- `failed`:不可重试或超过最大重试次数
173+
174+### 1.6 对话层职责边界
175+
176+`conductor` 在这层只负责:
177+
178+- 从 `browser.final_message` 提取 BAA
179+- 解析 target / tool / params
180+- 产出 `DeliveryJob`
181+- 做去重、排队、暂停/恢复、审计
182+- 记录执行结果和失败原因
183+
184+`conductor` 在这层明确不负责:
185+
186+- 选择具体 tab
187+- 维护具体页面 URL
188+- 解析发送模板 body 结构
189+- 直接持有原始平台凭证
190+
191+## 二、执行层协议与设计
192+
193+### 2.1 协议对象
194+
195+执行层建议收口到四个对象:
196+
197+- `ConversationRuntime`
198+- `CredentialBinding`
199+- `SendTemplate`
200+- `DeliveryAttempt`
201+
202+### 2.2 `ConversationRuntime`
203+
204+`ConversationRuntime` 表示“某条平台对话当前在浏览器里有哪些可用执行上下文”。
205+
206+建议字段:
207+
208+```text
209+runtime_key TEXT
210+platform TEXT
211+remote_conversation_id TEXT
212+
213+credential_fingerprint TEXT NULL
214+endpoint_profile TEXT NULL
215+organization_id TEXT NULL
216+
217+last_seen_tab_ids JSON NULL
218+last_seen_page_urls JSON NULL
219+last_seen_at INTEGER
220+
221+runtime_status TEXT // ready | stale | missing_template | missing_credential | missing_route
222+```
223+
224+约束:
225+
226+- 一个 `Conversation` 可以映射到多个候选页面上下文
227+- `tabId` 只属于 `ConversationRuntime`
228+- 插件应优先按 `remote_conversation_id` 找 runtime,再在内部选择实际页面
229+
230+### 2.3 `CredentialBinding`
231+
232+`CredentialBinding` 表示该平台当前可用的浏览器本地凭证上下文。
233+
234+建议字段:
235+
236+```text
237+platform TEXT
238+account TEXT NULL
239+credential_fingerprint TEXT
240+freshness TEXT // fresh | stale | lost
241+captured_at INTEGER
242+last_seen_at INTEGER
243+```
244+
245+约束:
246+
247+- 原始 `cookie` / `token` 不上送到 conductor
248+- conductor 只看到指纹和 freshness
249+- 插件执行时按最新可用指纹选择运行时
250+
251+### 2.4 `SendTemplate`
252+
253+`SendTemplate` 表示最近一次从真实页面拦截到的、可复用的发送请求形状。
254+
255+建议字段:
256+
257+```text
258+template_key TEXT
259+platform TEXT
260+remote_conversation_id TEXT
261+
262+request_path TEXT
263+headers_shape_json TEXT
264+body_shape_json TEXT
265+
266+captured_at INTEGER
267+credential_fingerprint TEXT NULL
268+template_status TEXT // ready | stale | invalid
269+```
270+
271+约束:
272+
273+- 模板按 conversation 维度缓存,而不是按 tab 维度缓存
274+- 插件可以内部记录“此模板最后来自哪个 tab”,但这只做诊断
275+- 若模板失效,由插件重新从真实页面流量中更新
276+
277+### 2.5 `DeliveryAttempt`
278+
279+`DeliveryAttempt` 表示执行层对某个 `DeliveryJob` 的一次真实发送尝试。
280+
281+建议字段:
282+
283+```text
284+attempt_id TEXT
285+job_id TEXT
286+runtime_key TEXT
287+template_key TEXT NULL
288+
289+status TEXT // resolving | sending | acked | streamed | finalized | failed
290+error_code TEXT NULL
291+error_message TEXT NULL
292+
293+diagnostic_tab_id INTEGER NULL
294+diagnostic_page_url TEXT NULL
295+
296+created_at INTEGER
297+updated_at INTEGER
298+```
299+
300+### 2.6 执行层职责边界
301+
302+插件在这层负责:
303+
304+- 根据 `platform + remote_conversation_id` 找到 `ConversationRuntime`
305+- 根据当前 runtime 找到可用的 `CredentialBinding`
306+- 根据 conversation 找到或刷新 `SendTemplate`
307+- 决定这次请求落到哪个真实页面上下文执行
308+- 代理发送真实请求
309+- 继续观察 `final_message`、SSE、后续 page route 变化
310+
311+插件在这层可以使用:
312+
313+- `tabId`
314+- `pageUrl`
315+- `shellPage`
316+- DOM fallback
317+- content-script / page-context bridge
318+
319+但这些都不应升级为上层协议主键。
320+
321+## 三、跨层接口
322+
323+### 3.1 对话层 -> 执行层
324+
325+建议正式消息体只保留会话语义,不传 tab:
326+
327+```json
328+{
329+ "type": "delivery.enqueue",
330+ "job_id": "job_123",
331+ "platform": "chatgpt",
332+ "remote_conversation_id": "conv_abc",
333+ "local_conversation_id": "lc_123",
334+ "assistant_message_id": "msg_prev",
335+ "message_text": "请总结上一轮执行结果"
336+}
337+```
338+
339+执行层自己解析:
340+
341+- 用哪个 runtime
342+- 用哪个 template
343+- 用哪个页面上下文发
344+
345+### 3.2 执行层 -> 对话层
346+
347+执行层回传的结果也应首先表达任务和对话,不先表达 tab:
348+
349+```json
350+{
351+ "type": "delivery.result",
352+ "job_id": "job_123",
353+ "platform": "chatgpt",
354+ "remote_conversation_id": "conv_abc",
355+ "status": "acked",
356+ "error_code": null,
357+ "diagnostic": {
358+ "tab_id": 40,
359+ "page_url": "https://chatgpt.com/c/conv_abc"
360+ }
361+}
362+```
363+
364+`diagnostic` 可保留,但不参与上层主路由。
365+
366+## 四、`browser.final_message` 的分层语义
367+
368+`browser.final_message` 应视为“执行层观察结果”,不是“页面主键同步消息”。
369+
370+它至少需要提供:
371+
372+- `platform`
373+- `remote_conversation_id`
374+- `assistant_message_id`
375+- `raw_text`
376+- `observed_at`
377+
378+它可以额外带:
379+
380+- `tab_id`
381+- `page_url`
382+- `page_title`
383+
384+但这些字段只做:
385+
386+- 诊断
387+- runtime 收敛
388+- stale replay 排查
389+
390+而不应决定 conductor 侧的业务主键。
391+
392+## 五、推荐链路
393+
394+建议后的最小主链:
395+
396+1. AI 页面产出最终 assistant message
397+2. 插件观察到 `browser.final_message`
398+3. conductor 解析 BAA,产出 `DeliveryJob`
399+4. conductor 把任务发给插件执行层
400+5. 插件按 `platform + remote_conversation_id` 解析 runtime
401+6. 插件内部选择具体页面上下文并代理发送
402+7. 插件继续观察同一对话的后续 final-message
403+8. conductor 按 `DeliveryJob` 和 `Conversation` 更新状态
404+
405+## 六、迁移原则
406+
407+### 6.1 保留 tab,但下沉为执行细节
408+
409+不需要删除 `tabId`,但需要调整其地位:
410+
411+- 从 `active_link` 的主键字段,降级为 runtime 诊断字段
412+- 从 conductor 的业务路由真相源,降级为插件内部执行句柄
413+
414+### 6.2 先改路由,再改实现
415+
416+迁移顺序建议:
417+
418+1. 让 renewal / dispatcher 先按 `conversation` 路由
419+2. 保留插件内部 `conversation -> page runtime` 映射
420+3. 再逐步移除对 `tab:<id>` 的上层依赖
421+
422+### 6.3 失败语义按层拆开
423+
424+需要区分两类失败:
425+
426+- 对话层失败:任务状态推进失败、重复任务、暂停态阻断
427+- 执行层失败:缺模板、缺凭证、缺 route、代理发送失败、final-message 未回流
428+
429+不要把执行层的 `tab` 漂移直接暴露成对话层的根失败原因。
430+
431+## 七、非目标
432+
433+这份提案当前不展开:
434+
435+- 多节点、多浏览器客户端编排
436+- 上传 / 下载 / 二进制任务协议
437+- 非浏览器平台的统一 runtime 抽象
438+- GUI 控制面细节
439+
440+## 八、结论
441+
442+推荐把当前系统明确拆成:
443+
444+- 协议层:`Conversation + MessageAnchor + DeliveryJob`
445+- 执行层:`ConversationRuntime + CredentialBinding + SendTemplate + DeliveryAttempt`
446+
447+核心边界只有一句话:
448+
449+> 上层只决定“发到哪条对话、发什么任务”;下层自己决定“具体用哪个页面上下文把它发出去”。