Merge branch 'codex/web-plan-wire' into codex/web-plan-mode
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
2026-07-24-web-plan-mode-projection.md: 9c894a30a0c456d2d6216baa23220db8d68fdda3
|
||||
2026-07-24-web-plan-mode-projection.zh.md: 9f8876c4f222059066ff1bff42aebc37acb5683c
|
||||
2026-07-24-web-plan-mode-projection.md: 57bf397fd6b8b3fcefc04ba3faebbc6d8648bf5a
|
||||
2026-07-24-web-plan-mode-projection.zh.md: 9d868c8494abe988955d328e5482fefff9c6ceec
|
||||
|
||||
@@ -12,12 +12,14 @@ The host does not mount plan mode for every product composition. The wire must d
|
||||
|
||||
## Decision
|
||||
|
||||
The session RPC domain exposes `session.planMode({ sessionId })` and `session.setPlanMode({ sessionId, active })`. Their shared value is `null | { active: boolean, pending?: boolean }`. `null` means the optional `ctx.planMode` service is absent; `{ active: false }` means the service is available and inactive. Both methods resume a cold session through the same host-owned path as history and prompt before reading or changing state.
|
||||
The session RPC domain exposes `session.planMode({ sessionId })` and `session.setPlanMode({ sessionId, active })`. Their shared value is `null | { active: boolean, pending?: boolean }`. `null` means the optional `ctx.planMode` service is absent; `{ active: false }` means the service is available and inactive. Both methods resume a cold session through the same host-owned path as history and prompt before reading or changing state. `session.prompt` additionally accepts an optional `planMode` target, letting a client bind the selected target to the prompt it submits.
|
||||
|
||||
The host adapter delegates selection and folding to `ctx.planMode`; it does not append events or duplicate boundary logic. `active` is the last committed logged value. When present, `pending` is the selected target value awaiting a model-request boundary and differs from `active`; its presence, rather than its boolean value, identifies a user-visible pending transition. Re-selecting the committed value can leave an internal cleanup intent in the service, but the adapter canonicalizes that net-zero state to `{ active }`. The boundary then removes the intent without logging a redundant state event. The wire schema rejects equal `active` and `pending` values. The RPC does not cancel a running request, so a selection made during generation leaves that request unchanged and shapes the next one.
|
||||
|
||||
The browser session object queries the complete state after history opens and on reconnect. A failed plan query is fail-soft: history remains usable and the last known capability state is retained. A reconnect generation fence prevents a superseded open from overwriting the newer result. One monotonic plan-request fence covers both queries and selections, so an older unary response cannot replace the result of a newer request. A separate local event-version fence prevents a current query or selection response from overwriting a `plan/mode` commit that overtook it on the mux stream; an early commit remains private until a successful query confirms capability presence. Successful selections otherwise update the snapshot only from the host-confirmed response, while business and transport failures leave the prior state intact.
|
||||
|
||||
Prompt admission waits for the latest selector request and follows any newer overlapping selection that supersedes the one it was awaiting. The latest outcome is retained after settlement so an already-completed failure cannot be missed. That failure rejects the prompt locally, letting the composer restore its draft instead of sending under an uncertain mode. After selection succeeds, the browser attaches the confirmed `pending ?? active` target to `session.prompt`. The host applies that target immediately before the synchronous `send` or `steer` admission, with no await where another request could interleave; unavailable plan capability fails closed, and a synchronous admission rejection restores the preceding target. Model generation begins only after admission and remains independently cancellable.
|
||||
|
||||
Committed `plan/mode` session events remain the live notification. When the host advertised the capability, a valid event replaces `active` and clears `pending`. Both the append path and a history replacement window observe the newest valid plan event by sequence, so gap repair applies a recovered commit even when the buffered triggering frame becomes replay overlap. The object layer ignores malformed events and does not infer capability from a raw event alone. This keeps full-state reads authoritative while preserving the existing logged event stream as the commit signal.
|
||||
|
||||
## State and timing
|
||||
@@ -44,9 +46,9 @@ Stopping generation remains a separate session operation. A pending selection su
|
||||
## Verification
|
||||
|
||||
- API schemas reject invalid request and state shapes, including equal committed and pending values, and both fetch directions dispatch the two methods.
|
||||
- Host runtime tests cover capability absence, real-service pending state, canonical net-zero cancellation, cold-session errors, and shared RPC semantics.
|
||||
- Client object tests cover open, selection success, overlapping selection response order, business and transport failure, committed live events, gap-repaired commits, malformed and unavailable events, fail-soft queries, reconnect refresh, superseded-query fencing, and mux commits overtaking unary responses.
|
||||
- Host runtime tests cover capability absence, real-service pending state, canonical net-zero cancellation, cold-session errors, atomic prompt admission, admission rollback, and shared RPC semantics.
|
||||
- Client object tests cover open, selection success, prompt waiting and failure containment, overlapping selection response order, business and transport failure, committed live events, gap-repaired commits, malformed and unavailable events, fail-soft queries, reconnect refresh, superseded-query fencing, and mux commits overtaking unary responses.
|
||||
|
||||
## Consequences
|
||||
|
||||
Web UI packages can discover plan mode without importing its host implementation and can display boundary-pending state without duplicating the plan service. Other clients may use the same optional projection. The contract deliberately does not combine switching with stop, invent generic mode identifiers, or make plan capability mandatory for every host composition.
|
||||
Web UI packages can discover plan mode without importing its host implementation, display boundary-pending state without duplicating the plan service, and bind one submitted prompt to the selected target. Other clients may use the same optional projection. The contract deliberately does not combine switching with stop, invent generic mode identifiers, or make plan capability mandatory for every host composition.
|
||||
|
||||
@@ -12,12 +12,14 @@ plan 服务拥有持久状态和边界时序,但 Web 宿主契约无法发现
|
||||
|
||||
## 决策
|
||||
|
||||
会话 RPC 域公开 `session.planMode({ sessionId })` 和 `session.setPlanMode({ sessionId, active })`。两者返回相同的值类型:`null | { active: boolean, pending?: boolean }`。`null` 表示可选的 `ctx.planMode` 服务不存在;`{ active: false }` 表示该服务可用,但当前未激活。读取或修改状态前,这两个方法都会通过宿主用于历史记录与提示词请求的同一路径恢复冷会话。
|
||||
会话 RPC 域公开 `session.planMode({ sessionId })` 和 `session.setPlanMode({ sessionId, active })`。两者返回相同的值类型:`null | { active: boolean, pending?: boolean }`。`null` 表示可选的 `ctx.planMode` 服务不存在;`{ active: false }` 表示该服务可用,但当前未激活。读取或修改状态前,这两个方法都会通过宿主用于历史记录与提示词请求的同一路径恢复冷会话。`session.prompt` 还接受可选的 `planMode` 目标,使客户端可以把所选目标绑定到其提交的提示词。
|
||||
|
||||
宿主适配器把选择与折叠工作交给 `ctx.planMode`,不会自行追加事件或重复实现边界逻辑。`active` 是最近一次已提交并记录到日志的值。`pending` 存在时,其值是等待模型请求边界生效的所选目标,且必定不同于 `active`;表示用户可见的待生效转换的是该字段是否存在,而不是其布尔值。重新选择已提交值时,服务内部可能仍留有一项清理意图,但适配器会把这一无净变化状态规范化为 `{ active }`。随后,边界会移除该意图,且不会记录多余的状态事件。协议 schema 会拒绝 `active` 与 `pending` 相等的值。该 RPC 不会取消正在执行的请求,因此生成期间作出的选择不会改变本次请求,只会影响下一次请求。
|
||||
|
||||
浏览器会话对象在历史记录加载完成后以及重连时查询完整状态。plan 查询失败不会阻断其他功能:历史记录仍可使用,并保留最近一次已知的功能状态。重连使用代际围栏,避免已被取代的打开流程覆盖较新的结果。一个单调递增的 plan 请求围栏同时覆盖查询和选择,因此较早的 unary 响应无法替换较新请求的结果。另一道独立的本地事件版本围栏会阻止当前查询或选择响应覆盖 mux 流中已抢先到达的 `plan/mode` 提交;提前到达的提交会保持为内部状态,直到查询成功并确认该功能存在。除上述情况外,选择成功后,只有宿主确认的响应才会更新快照;业务错误和传输失败都会保留先前状态。
|
||||
|
||||
提示词准入会等待最近一次选择器请求;若有更新的重叠选择取代原先等待的请求,准入也会转而跟随该选择。请求完成后仍会保留最新结果,因此不会漏掉已经完成的失败结果。该失败会在本地拒绝提示词,使输入区可以恢复草稿,而不会在模式不确定时发送。选择成功后,浏览器会把已确认的 `pending ?? active` 目标附加到 `session.prompt`。宿主会在同步 `send` 或 `steer` 准入之前立即应用该目标;期间不会执行 await,因此其他请求无法插入。plan 功能不可用时默认拒绝,同步准入遭拒时则会恢复先前目标。模型生成仅在准入完成后开始,并仍可独立取消。
|
||||
|
||||
已提交的 `plan/mode` 会话事件仍作为实时通知。当宿主已公布该功能时,有效事件会替换 `active` 并清除 `pending`。追加路径和历史替换窗口都会按序号采用最新的有效 plan 事件,因此即使缓冲的触发帧在回放时已与窗口重叠,缺口回补仍会应用恢复出的提交。对象层会忽略格式错误的事件,也不会仅凭一条原始事件推断功能是否可用。这样既以完整状态读取为真源,又保留现有的日志事件流作为提交信号。
|
||||
|
||||
## 状态与时序
|
||||
@@ -44,9 +46,9 @@ plan 服务拥有持久状态和边界时序,但 Web 宿主契约无法发现
|
||||
## 验证
|
||||
|
||||
- API schema 拒绝无效的请求与状态结构,包括 `active` 与 `pending` 相等的情况;两个 fetch 方向均可分派这两个方法。
|
||||
- 宿主运行时测试覆盖功能不存在、真实服务的待生效状态、取消时无净变化状态的规范化、冷会话错误,以及共享 RPC 语义。
|
||||
- 客户端对象测试覆盖打开、选择成功、重叠选择请求的响应顺序、业务错误与传输失败、已提交的实时事件、经缺口回补的提交、格式错误及功能不可用时的事件、查询失败时的容错、重连刷新、针对已被取代查询的围栏保护,以及 mux 提交抢先于 unary 响应到达的情况。
|
||||
- 宿主运行时测试覆盖功能不存在、真实服务的待生效状态、取消时无净变化状态的规范化、冷会话错误、原子化提示词准入、准入回滚,以及共享 RPC 语义。
|
||||
- 客户端对象测试覆盖打开、选择成功、提示词等待与失败隔离、重叠选择请求的响应顺序、业务错误与传输失败、已提交的实时事件、经缺口回补的提交、格式错误及功能不可用时的事件、查询失败时的容错、重连刷新、针对已被取代查询的围栏保护,以及 mux 提交抢先于 unary 响应到达的情况。
|
||||
|
||||
## 后果
|
||||
|
||||
Web UI 包(package)无需导入 plan mode 的宿主实现即可发现该功能,也能显示边界处的待生效状态,而不必重复实现 plan 服务。其他客户端也可以使用同一个可选投影。该契约有意不把模式切换与停止操作合并,不发明通用模式标识符,也不要求每一种宿主组合都必须提供 plan 功能。
|
||||
Web UI 包(package)无需导入 plan mode 的宿主实现即可发现该功能,也能在不重复实现 plan 服务的情况下显示边界处的待生效状态,并把一次提示词提交绑定到所选目标。其他客户端也可以使用同一个可选投影。该契约有意不把模式切换与停止操作合并,不发明通用模式标识符,也不要求每一种宿主组合都必须提供 plan 功能。
|
||||
|
||||
Reference in New Issue
Block a user