Files
deepseek-harness/missions/ui-product.md
imccyu 0681ac47de chore(gui): mission work logs
chore(gui): mission work logs — cordis design finalization, tool-card wire archive, incident records

chore: missions

chore: missions

chore(gui): mission ledger — batch-2 answers, parallel dispatch state, jsdom coverage re-scope

chore(gui): ledger — night-mode standing orders (self-commit small, no push, 5-min refresh)

chore(gui): ledger — jsdom batches 2-4 landed (233 green), coverage probe next

chore(gui): ledger — web-ui coverage probe 65%, four-tier fill plan approved

chore(gui): ledger — cordis-impl B1 state after third API drop, decisions on file

chore(gui): ledger — 01:32 patrol snapshot (jsdom tier-1 landed, coverage-fixer probed)

chore(gui): ledger — 01:37 patrol (peer src trio landed, coverage-fixer still silent)

chore(gui): ledger — 01:42 patrol (jsdom tier-2 landed, peer committed x2, coverage-fixer 2nd probe)

chore(gui): ledger — coverage diagnosis complete (6-file gap list), web-ui at 91.4%

chore(gui): ledger — 01:46 patrol (B1 done, jsdom tier-3 landed, coverage fix batch running)

chore(gui): ledger — 01:51 patrol (jsdom tails x2 landed, B2 underway)

chore(gui): ledger — 01:56 patrol (gateway.ts 337 lines, checkpoint T-7min)

chore(gui): ledger — hold/pending split ruling, coverage-fixer externalize-or-restart ultimatum

chore(gui): ledger — 02:01 patrol (B2 done, jsdom final arms, coverage ultimatum pending)

chore(gui): ledger — 02:03 checkpoint executed (fixer2 respawn, four lanes released, three owners cold-started)

chore(gui): ledger — all six lanes acked, type isolation first live proof (client closure clean)

chore(gui): ledger — 02:08 patrol (all seven lanes active, wire carrier assembled)

docs(gui): respond-design task checkpoint — apiproxy wire-layer recon done

chore(gui): ledger — P0-2 contributor AGENTS.md landed (dd28a5019)

docs(gui): respond-design checkpoint 2 — host-side recon (stub respond, frame types, approval seam, ACP answerer precedent)

docs(gui): OOP debt inventory — seven territories, 2 real debts (createApiProxy, createFixtureApi), rest ruled keep-as-is

docs(gui): disambiguation note on the archived i18n design task

chore(gui): ledger — 02:12 patrol (exclude removed, mixed-knife incident under reconciliation)

chore(gui): ledger — 02:15 wave (jsdom mission closed, OOP audit done, B3 isolation proof, mixed-knife resolved)

chore(gui): ledger — 02:17 patrol (attribution reversal filed, arch-session probed)

chore(gui): ledger — 02:22 patrol (B4 done, B5+B6 merged batch, arch-session deadline set)

docs(gui): respond-design checkpoint 3 — client-side recon (pending map, PendingCard onRespond stub, AbstractApiClient.respond ready, bootHost missing approval mounts)

docs(gui): peer carrier territory review — 2 fixes (SSE cancel leak, route-reservation guard), 1 ruling ask (RPC-log visibility), compliance ledger

chore(gui): ledger — 02:27 (arch-shell respawn, territory review verdicts routed, ask-deny finding flagged)

docs(gui): P1-5 respond design page complete — pending registry, wire answerer, client state machine, first-wins arbitration

chore(gui): ledger — 02:31 patrol (respond design complete, B5 wire smoke green, shell knife 1 underway)

docs(gui): respond design — add §0 status warning (web host ask defaults to deny), mount-behavior delta, no-timeout ruling with Config discipline

chore(gui): ledger — 02:42 patrol (respond line closed pending review, B7 last piece underway)

docs(gui): respond design contract review — direction pass, 2 doc fixes (settle-order contradiction, answering-state race), A/B/C compliance ledger

docs(gui): respond design — contract review fixes (R1 verify-before-delete arbitration, R2 answering+resolved-frame transition, ask dual-source wording, rejected-is-ok-value note)

chore(gui): ledger — 02:46 patrol (respond line final, two user decisions distilled, arch-shell deadline)

docs(gui): respond review addendum — contract-gap ruling: approve plan A (ApprovalRequest.id), wire unchanged, drop plan B backscan

chore(gui): ledger — cordis B7 summit: real-browser 10/10 green, user acceptance criterion proven

docs(gui): respond design — contract gap #4 approved as plan A (ApprovalRequest.id), backscan fallback retired, blade 0 prepended

docs(gui): respond design — final polish (owner-approval vs user-go-ahead wording, implementation handoff notes)

chore(gui): ledger — 02:51 (shell-exec third respawn with operational script, respond line 4-knife final)

chore(gui): ledger — 02:53 wave (respond five-knife true final, B7 closed 12/12, client.ts green)

chore(gui): ledger — 02:56 patrol (cordis closeout bounced pending R1/R2/N1, shell-exec first sign of life)

chore(gui): ledger — 03:01 patrol (R1/R2/N1 remediation in flight across four files)

chore(gui): ledger — cordis line officially closed and archived, verified on disk (24 knives, 12/12, reviews closed)

chore(gui): ledger — 03:06 patrol (shell-exec final window, lowered first-knife bar)

chore(gui): ledger — 03:11 patrol (shell line iced-broken: three registries on disk)

chore(gui): ledger — 03:15 patrol (quiet window, both active lanes within threshold)

chore(gui): ledger — 03:20 patrol (shell five files up, api-proxy plan reported)

chore(gui): ledger — 03:25 patrol (shell migration in flight with history-preserving moves, webserver green)

chore(gui): ledger — 03:30 patrol (shell knife-1 in verification, api-proxy patching)

chore(gui): ledger — shell knife 1 accepted (694cecc53), knife 2 released

chore(gui): ledger — 03:40 patrol (knife 2 pre-move stage, cold-list spec appears)

chore(gui): ledger — 03:45 patrol (rpclog moves staged, api-proxy two specs in flight)

chore(gui): ledger — 03:54 patrol (knife-2 code done, coverage full-run final check)

docs(gui): coverage-fixer task ledger — fixer2 takeover, per-file fix log, isolated reportsDirectory pitfall

chore(gui): ledger — coverage lane closed and accepted (a19f069a5), the PR #443 CI fix knife

chore(gui): ledger — 04:04 patrol (knife-2 calibration, sole active lane)

chore(gui): ledger — shell knife 2 accepted (f8fb77b95), knife 3 released as final night task

chore(gui): ledger — 04:19 patrol (knife-3 past half: callback chain through, ToolCallDetail up)

chore(gui): ledger — night closeout summary: seven lanes closed, wake-up decision sheet

chore: missions

chore: missions

chore: missions

chore(gui): mission-local browser/probe verify scripts under missions/scripts/

The six acceptance/probe scripts move here as mission-side working
material (headers and relative imports adjusted for the new location):
carrier-errors, rpclog-panel, session, session-real,
webserver-backpressure, webserver-hardening.

chore(gui): verify-relocate mission log

chore(gui): verify-relocate mission log — R1 guard addendum

chore(gui): gates-continue mission log — CI-equivalent sequence all green

chore(gui): VS Code 扩展体系双边设计调研报告

chore(gui): 调研追加 4.5 节——git 扩展数据面与 scope 绑定

docs(gui): web plugin system RFC — walkthrough + design notes

docs(gui): RFC — restore existing SSE/POST as the v1 transport; envelope rides on it (D16)

docs(gui): RFC — envelope demoted to chan-dispatch, scope out of envelope, rpc-log cut, peer deferred, scope tree is native cordis (D17-D21)

docs(gui): RFC — hooks re-derived from component needs: useWatch/useAction only, useService removed; sessionHub cut, projections user-space, router rename, loader-only root (D22-D25)

docs(gui): RFC — drop stale fork vocabulary (vendored cordis has Fiber only; scope = mintScope pattern), hook idempotence contract (D26-D27)

docs(gui): RFC — session precision seam: plugins read scope key (host paradigm), React gets it from tree position via SlotOutlet (D28)

docs(gui): RFC — domain hooks owned by plugins over framework primitives; useConversation paradigm carried over (D29)

docs(gui): RFC — ctx services are the inter-plugin API (cordis proper); declarations are wire-only; get(id) returns scoped ctx (D30)

docs(gui): RFC — full ctx.conversation walkthrough: root-singleton scope-sensitive service, caller-ctx scope key, get(key) as scoped ctx (D31)

docs(gui): RFC — no client-side agents collection: session state machine already expresses the duality; agent resolution stays host authority (D32)

docs(gui): RFC — v1 stays session-precision, no agent-level isolation; incarnation/agent-axis designs archived in ledger (D33)

docs(gui): RFC walkthrough — full rewrite to final state (D16-D33 consolidated), end-to-end chain restored

docs(gui): RFC — apiproxy demoted to generic channel routing; domain RPCs dissolve into owner plugins (D34)

docs(gui): RFC — TS-interface-first wire contract (zod internal), conversation owns the dialogue frame with pluggable views (D35)

docs(gui): RFC — page skeleton (sidebar+conversation), projects as plugin not service, nested slots via owner registries (D36)

docs(gui): RFC — SlotMap declaration-merging slot model: single register API, inject-as-ownership, FC-typed registration, typed outlets (D37)

docs(gui): RFC — slot props whitelist: identity, display params, materialized snapshot slices, stable UI callbacks (D38)

docs(gui): RFC — end-to-end data flow: three transforms, equality protocol table, immer placement; i18n/theme kept standard (D39-D40)

docs(gui): RFC — full external-injection model: props carry values + stable injected hooks; shared/client/react example rewritten (D41-D43)

docs(gui): RFC final trio — modules.md (agent implementation spec), architecture.md (human walkthrough), plugins.md (business plugin inventory)

docs(gui): RFC — props three-source merge (scope-standard useSession auto-injected); keyed key vs list id disambiguated (D44)

docs(gui): RFC — inject comment says what it is (the React-facing props bundle); SessionHandle rename; snapshot-production story unified on buildSnapshot

docs(gui): RFC architecture — full React component tree walkthrough: props three sources, slot vs plain children, hook taxonomy per node

docs(gui): RFC — module map finalized (ui-slots/web-react/connection/runtime/ui-*/web); slots onChange replaced by cordis events; toolcall dimension; detail sidebar default-collapsed with toolName-keyed routing

docs(gui): RFC plugins — openDetail relay chain: toolcard calls chat-view injected action, chat-view relays to conversation sidebar

chore(gui): progress ledger — full archive rewrite: RFC outcome digest, open gaps, dispatch plan, cold-start entry

docs(gui): RFC grill pass 1 — SlotScope axis (root/session) on declares, Gate dependency inversion, inject handle by scope, W5 acceptance list, gantt relay chain fixed

docs(gui): figma analysis — sidebar/projects/sessions 区域交互视觉理解报告

docs(gui): figma 解析报告 — details 面板/多视图 tabs/未来功能区盘点 + slot 需求清单

docs(gui): figma 对话主区解析报告 — 消息流/tool calls 变体/审批接管输入框/Header tabs/视觉 token

docs(gui): plugins.md rewritten from figma analysis — three-column layout, full slot reservation table, selection channel, composer-takeover approvals, phased scope

docs(gui): layout dynamics ruled (drag+collapse both rails, details yields first, composer swap-panel, same-component transition); toolviews promoted to named scope-aware registry

docs(gui): P-I scope locked (details minimal, dual theme, chat-view, custom toolview sample); teammate dispatch plan — 6 owners by package, dependency-driven waves, contract arbitration

docs(gui): P-I api-contracts (full inter-package API spec) + dispatch plan (T0 skeleton knife, 7-dev roster, task briefs, milestones)

docs(gui): api-contracts v2 — scope tree in P-I, bundle loader + per-plugin CSS isolation in P-I, agent-scoped toolviews live, zustand engine, renames (SessionProvider/ObservableSnapshot/SessionBinding), router owns all shell view-state

docs(gui): services roster + progressive loading (no blocking loadAll), SlotsService as real cordis Service, renderSlot/renderSuspenseSlot duo, ui-traj teammate

docs(gui): loading-chain gaps ruled — dev=rebundle no HMR, ui-primitives package, externals on globals (no import map), host injects __DSH_BOOT__ into HTML (zero round-trip)

docs(gui): api-contracts v3 + dispatch v2 final — 12 packages, services merged in, progressive loader, global externals, __DSH_BOOT__ injection, 8-dev roster with convo split and ui-traj

docs(gui): v3 amendments — router renamed ctx.layout, ui-trajectory has no service (pure consumer sample), wait-for-settled loading (no Suspense in P-I, ledger 6b)

chore(gui): progress — pre-compact final state: v3 revision chain, 8-dev roster, T0 procedure, doc authority order

docs(gui): authority banners — modules/architecture get v3 term-mapping headers, walkthrough marked as archived process doc

docs(gui): cssdesign token set is THE theme source (--dsw-* variables, data-ds-dark-theme switch); recorded in contracts + progress

docs(gui): architecture.md full v3 rewrite — loading chain, 12-package map, service roster, slot/inject/toolviews, data flow, component tree, perf model, all current

docs(gui): contracts — UI plugins are dual-entry host plugins (node half serves client asset via ctx.webPlugins; __DSH_BOOT__ derives from it; client-closure gate back in scope)

docs(gui): contracts — closure-factory bundles with DI require (no globals), package.json dshWeb declarative discovery (no serve ritual), create-then-send empty state with project picker, ancestry() for breadcrumb, unload stubbed until HMR, props.renderSlot confirmed

docs(gui): dshClient declaration (inject/platform/immediately, exports./client), closure-DI require loading — synced across contracts/dispatch/modules/architecture/walkthrough

chore(gui): progress — record final loading-chain rulings (dshClient declaration, closure-DI require, startSession) before compact

docs(gui): architecture.md — developer-facing whole-web architecture on master baseline 6b16a67cb: what exists, what is new, no process narrative

docs(gui): architecture.md — self-contained whole-web architecture: absorbs still-valid substance from the branch RFCs (host layering, four-quadrant RPC, object layer, testing tiers) under the new plugin system as the override

chore(gui): progress — final pre-compact snapshot: contracts digest, apiproxy purity ruling, T0 procedure with first-action list

docs(gui): api-contracts v3 §3.1 — apiproxy purity principle with three-way existing-code verdicts

docs(gui): api-contracts v3 — immediately reinterpreted as static-infra group (8-package dshClient scope, boot manifest reconciliation)

docs(gui): api-contracts v3 — immediately corrected to early-load dynamic group (prod shell must not rebundle); loader shell-held; bundles register their export surface into module table

docs(gui): architecture — align with immediately=early-load dynamic group ruling; loader shell-held; module-table registration of loaded bundles

docs(gui): T0 checklist — 12-package skeleton table, 4-cut sequence, mv/attic/rewire rules (pre-drafted, awaiting go)

docs(gui): t0-checklist — pin figma-flows findings (missing font-family base vars, three alias vars behind upstream)

docs(gui): dispatch v2.1 — drop cordis-web salvage wording, two-wave staffing, loader/immediately boundary updates

docs(gui): progress + t0-checklist ledger — T0 landed, staffing status, execution accounting

docs(gui): api-contracts v3 — arbitration round 1: renderBody deps, RootBindingProvider, flush default sync, prune current, loader subpath, config-source P-I bar

docs(gui): v3 §3.2 connection 导出清单附录(rt-core 对账)+ rt-core 实现计划档案

docs(gui): progress — T1 milestone, arbitration round 1 ledger, fw-react timeout escalation

docs(gui): progress rolling update — per-line battlefield state at 00:2x, mailbox-vs-contract lesson, small-batch discipline reinforced

docs(gui): fw-react notes — v3 §2 complete, seven knives, T1/T2 follow-ups

docs(fw-slots): archive — four packages landed, open tails logged

docs(gui): progress — framework layer complete (web-react five, fw-slots four packages), T2 gated on rt-core runtime knife only

docs: api-contracts

docs: style-spec

docs

docs(gui): tsconfig convergence ruling — no host.json, root resumes host-aggregate duty, typecheck = root + client aggregates

docs(gui): missions 根三份 07-18 世代档案加「已被取代」头注——指向 web-plugin-rfc 现行权威并注明新旧对应

missions

docs(gui): progress rewritten for post-closeout state — wave ledger, architecture finale, teammate roster with handover notes, pending-user-command queue
2026-07-22 21:30:30 +08:00

222 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Harness UI 产品设计
> **【已被取代——历史档案】** 本文是 07-18 世代定稿,其产品结论已由 2026-07-21 figma 定稿+插件化 RFC 重设。现行权威=`missions/tasks/20260721-1520-web-plugin-rfc/` 的 plugins.md(业务规格+布局九规则,本文「两栏+侧板」已改三栏 details 第一公民)+ figma-analysis/(视觉圣经);接口=api-contracts.md v3。本文仅作决策演变考古用。
> 状态:**2026-07-18 定稿,供最终 review**。技术实现见《[UI 技术架构](ui-tech.md)》。关键决策的演变过程见文末附表;完整裁决档在 `missions/tasks/20260718-final-review/`。
## 0. 本轮范围硬约束(2026-07-18 用户定)
- 一期只实现 **Web localhost**;**Electron 为架构同构预留**(协议、注入接缝、进程模型设计完毕并保留,一期不写实现);TUI 只留协议位置。
- 通信协议按 Web / Electron / TUI **跨端一致一次定全**;实现按批次并发填(多 worktree 并行)。
- 连接模型下限承诺:**单 WebView 连接 + 刷新恢复绝对可靠**(承诺清单见 §4);多 WebView 并连、读写分离明确不做,不为其复杂化。
- 工期按 AI 天估算:不为工期砍协议/架构完整性;做不完的功能保留架构位、实现留空。
## 1. 这是什么,解决什么问题
给 DeepSeek Harness 做一套**自己的图形界面**:用户在里面选历史会话、与模型持续对话、观察工具执行、处理审批与提问、配置模型。
**现状问题**:harness 目前只能通过编辑器(ACP 协议,如 Zed)或终端接入,没有独立图形界面——用户无法在自己的界面里浏览历史 session、看流式对话、处理审批、管理 Provider/模型配置。
## 2. 产品形态
| 形态 | 启动方式 | 关页/关窗后 | 状态 |
|---|---|---|---|
| **localhost Web** | 命令 **`dsc web`**,浏览器直接访问打印的地址(免登录) | 关页/刷新不停 server 或任务;刷新原页恢复 | **一期实现** |
| Electron | Electron 启动并监管专属本地 sidecar;renderer ≡ WebUI | 关最后一个窗口即退出并停 sidecar;有活动工作先确认 | **架构预留**(同构设计完毕,见 ui-tech §9/07 层,一期不实现) |
- `dsc web` **监听 `0.0.0.0`**、控制台打印 `http://127.0.0.1:<端口>`。局域网内其他设备因此事实可达,但一期不为 LAN 做设计(免登录、单客户端、不承诺,见 §17)。
- `dsc web` 进程与(未来的)Electron sidecar 是各自独立的 Host 实例,不共享、不跨形态接续任务。
## 3. 核心原则(用户能感知的)
- **Host 是权威,界面是可重建的呈现**:离开页面、刷新、临时断网**不会停掉已接受的任务**。只要 `dsc web` 进程在跑,任务继续;重连后恢复。
- **发送(插话/排队)、停止是不同的显式操作**,不含糊;一切发送/控制能力**遵循 harness core 现有 API**(steer/queue/cancel 的 1:1 映射,见 §8),不造 core 没有的能力。
- 一期核心体验由第一方界面完整提供;插件从真实、局部的扩展点开始。
## 4. 单客户端与刷新恢复(下限承诺)
- **一个 Server 同时只允许一个活动界面 Client**。一个 Client 里可打开、切换多个 session;第二个页面/前端在连接阶段被明确拒绝(提示"已被占用",不泄露占用者信息),不影响当前 Client。
- **刷新**同一页面:用原 Client 身份重连接管,不取消任务,从 Host 权威状态重建。
**刷新/重连后必须恢复的(承诺清单)**:
1. session 列表(含运行状态、待审批/待问答计数)与当前焦点 session。
2. 已打开 session 的完整已持久对话历史。
3. **进行中消息的已生成部分(partial)**——刷新后正在流式生成的回复不是半截空白,而是先显示已生成内容、随后增量继续推。
4. Host 存活期间的待处理审批卡片与提问卡片,可继续回复。
5. Provider 设置与模型列表。
**明确不承诺的(已知边界,如实告知用户)**:
- **排队中尚未开轮的输入,刷新后暂不可见**(任务本身不丢:Host 内的队列继续持有,开轮后以正式消息出现在时间线)。原因:不在 harness 队列前复制第二套状态;未来 harness 提供队列项 ID 后升级(见 ui-tech §13)。
- 断线期间的逐 token 增量不补发,以 partial 快照收敛。
- **Host 进程重启后**:只恢复已持久化的事实;重启前 pending 的审批/提问变为只读「已中断」记录,不能再回复。
## 5. 信息架构
一期四个主要区域:
- **Session 列表**:创建、打开、重命名、排序、筛选、分页,显示运行状态和待处理计数。
- **对话区**:历史、流式响应、工具卡片、审批卡片、提问卡片、任务终态。
- **输入区**:编辑草稿、选模型、发送(插话/排队)、停止。
- **设置区**:唯一活动 Provider、API Key、模型候选、连接测试。
插件状态和错误作为诊断展示;界面不承担插件管理(安装/升级/启停)。
## 6. Session 列表
- 支持:**创建**(新会话)、打开、**重命名**、显示运行状态与待处理数量(审批 + 提问)。
- **排序/筛选/翻页由 server 驱动**(按最近活动时间稳定游标分页),session 多也不卡;列表来自轻量摘要,不需要加载完整历史。
- **标题规则**:新建为「新会话」→ 第一条进入历史的用户消息首个非空文本行**一次性**生成标题 → 用户手动重命名后**永不再自动覆盖**。
- 列表的「最近活动时间」只随消息定稿等完整边界推进,**不随逐 token 输出抖动**(避免列表频繁跳动)。
- 归档、置顶、分组、硬删除不进一期。
## 7. 对话与历史
- 打开 session 先展示最近一段时间线,向上滚动分页加载更早历史。
- 一期不做虚拟列表,但滚动行为要做对:历史插入锚定、流式增长、用户离底、重新跟随。
- 消息正文支持**流式 Markdown**(GFM + 代码高亮 + **数学公式 KaTeX**);Mermaid 一期后置(实现时按「未闭合显示源码→闭合后渲染→失败回退源码」的流式降级模式)。
- **多个打开的 session 同时在后台接收更新**:切换只改焦点,后台 session 继续收运行状态、结果、审批与提问更新。
- **system/上下文注入等非人类交互内容不进普通时间线**:折叠进「诊断」视图查看,普通时间线只保留真实的人类交互与模型回复。
- 同一轮(turn)内模型的多个 step 按轮分组呈现;思考过程与正式回复分开、可折叠。
### 工具调用卡片
**卡片类型以 harness core 现有呈现能力为准,core 没有的不自造**(已核源码:core 的 `presentCall/presentResult` 现成提供的就是下述集合)。统一呈现模型,一次工具调用一张卡片,状态**原地**从"进行中"变"完成/失败":`generic` / `terminal` / `diff` 三类卡片 + `locations` 作为正交的位置附件(不是第四种卡片);未识别类型回退通用展示。第一方 renderer 和 unknown fallback 始终存在(自定义 renderer 扩展点仅预留,见 §13)。
## 8. 输入、发送与停止
输入框内容是**本地草稿**;发送对接 harness agent 现成的两条队列,是对底层能力的忠实映射:
| 操作 | 行为 |
|---|---|
| **插话(steer)** | 把输入插入正在进行的这一轮(纠偏/追加) |
| **排队(queue)** | 排成后续独立一轮 |
| 空闲时发送 | 立即开一轮执行 |
| **停止** | 整体终止当前任务(清空排队 + 中断当前),不提交新输入 |
- **三条纪律**:UI 不假设「一次发送对应一个 turn」(开轮可能一次消化多条排队输入);不提供「取消中」中间态(停止即请求,以 Host 权威状态为准);一期无单项取消/撤回/重排(未 send 的草稿本地可丢)。
- 排队项刷新后的可见性边界见 §4。队列上限由 Host 配置,超限/状态不允许时显示 Host 返回的明确拒绝原因。
## 9. 模型选择
- 每个 session 保存所选 model ID;所有 session 共享同一个活动 Provider。
- 选择器同时给 Catalog 候选和手写输入,可保存 Catalog 没有的 model ID。
- 切换从**下一个请求边界**生效:已开始的流继续用原模型。
## 10. 审批
审批请求由 **Host 持有**:**Host 存活期间,页面刷新/重连后可恢复**待处理审批并继续回复;**Host 重启后**变为只读「已中断」记录。
按位置分层提示:
- 当前 session:时间线内**审批卡片**(按稳定锚点插位,不按到达顺序)。
- 其他 session:列表 badge + 应用内可点击 toast。
- Web:用户已授权时增加系统通知;(Electron 预留:后台时系统通知。)
通知不强制切换 session;同一审批按稳定 ID 去重。仅 Host 提供 `expiresAt` 时显示倒计时(到期判定归 server,断线不暂停)。被其他 actor/policy/超时解决后,卡片保留只读终态;迟到回复显示冲突并刷新权威结果,不覆盖已完成的决定。
## 11. 用户提问(`ask_user_question`)——独立于审批
模型可向用户提问(可多项、可多选)。独立资源、独立卡片,**不复用审批**:
- 一次提问包含多个问题项时**整批一次原子回答**,必答项必须全部覆盖。
- 终态四种:已回答 / 已取消 / 已超时 / 已中断(Host 重启)。
- 恢复口径与审批一致:Host 存活期刷新/重连可恢复继续回答;Host 重启后只读「已中断」。
- pending 提问计入 session 待处理数,并参与「退出/更新是否 busy」的判定。
## 12. Provider 设置
一期一个 Host 全局活动 Provider。设置页含:连接配置、API Key 查看修改、模型候选 + 手写 model ID、保存前连接测试、Catalog 来源/stale/错误。
- **保存 = 先用表单配置连接远端真实测试**(判据:真实拉起一次最小模型流并见到合法结束,不是 HTTP 200 就算通过):成功才落盘并切换;失败保留表单、现状不变。
- **两类失败分开**:连接/凭据探测失败**阻止保存**;仅模型发现失败**仍可保存**,模型列表保留上次成功并标 stale。
- **同时只允许一个保存事务**(第二个保存请求立即得到"忙"提示,不排队);保存一旦被接受,**不随页面断开而中止**,结果回来后在界面上如实呈现。
- 运行期间可改配置:已开始的模型流用旧配置跑完,后续请求用新配置。
- API Key 明文存本地用户目录 owner-only 文件(`~/.dsc/`);界面常态只显示"已配置",不回显明文;日志/事件/诊断统一脱敏。
## 13. 插件体验
- 一期只做两件事:**① 现有服务端(Node 侧)插件列表的展示**(加载状态与错误,不提供安装/移除/启停);**② 把 Browser Cordis Context 的能力地基搭好**(前端运行时以 Cordis root Context 组织,为未来 UI 插件留好挂载面)。
- **一期不做 UI 插件**:自定义 tool renderer、侧栏功能页两个扩展点只留架构位(slot seam 预留、不实现注册);页面/路由/设置面板/输入框/列表/通用 action 亦不开放。
- 插件由开发者通过 `cordis.yml`/CLI 引用本地路径或已装包;一期本地插件视为完全可信,不做隔离。
## 14. 退出与故障体验
- **Web**:关页/刷新/断网 ≠ 停任务;重连按 §4 承诺清单恢复。`dsc web` 进程收到 SIGINT/SIGTERM 走有序停机:停止接收新工作 → 取消进行中任务 → **落盘所有 session** → 退出;停机判定 busy 的口径与下述活动判定一致。
- **活动判定**(Web 停机与未来 Electron 退出共用):活动 = 进行中对话 + 待审批/待提问 + **后台任务(bash 等)running/stopping**;「Agent 空闲 ≠ 可退出」;任一来源状态**读不到时按"未知"处理并保守确认,不伪装空闲**。
- 单个 session 重新同步期间:保留可见内容、显示"正在重新同步"、暂禁该 session 操作;其他 session 不受影响。
- Host 崩溃:重启只恢复已持久化事实,不承诺恢复崩溃时正在执行的任务。
## 15. 一期验收行为
| 场景 | 预期 |
|---|---|
| Web 刷新 | 同一 Client 身份重连接管;任务继续;列表/历史/**进行中消息 partial**/待审批/待提问全部恢复 |
| Web 关页 | `dsc web` 进程与已接受任务继续运行 |
| 第二个页面连接 | 连接阶段明确拒绝(不泄露占用方),不影响当前 Client |
| 运行中发送 steer | 输入插入当前轮 |
| 运行中发送 queue | 排成后续轮;刷新后暂不可见但不丢失,开轮后出现在时间线 |
| 停止 | 清排队 + 中断当前任务;UI 状态以 Host 为准 |
| 新建/重命名 session | 立即生效;重命名后标题不再被自动覆盖 |
| Provider 探测失败 | 表单保留、活动配置不变;仅发现失败则可保存、列表标 stale |
| Provider 保存期间刷新 | 保存事务继续完成,重连后看到终态 |
| 单 session 事件缺口 | 只重同步该 session |
| 收到 ask_user_question | 独立提问卡片,整批原子回答;非当前 session 走 badge/toast |
| 收到审批(非当前 session) | badge + toast,不强制切换 |
| 切换 session | 只改焦点,后台 session 继续收流 |
| 服务端插件异常 | 插件列表如实显示错误状态,不影响对话主功能 |
| `dsc web` SIGINT | 有序停机:取消、落盘、退出 |
## 16. 一期范围与后续规划
| 能力 | 一期 | 预留/后续 |
|---|---|---|
| 形态 | Web localhost | **Electron(架构已同构预留)**、TUI、LAN/远程、多 Client |
| 会话列表 | server 分页、创建、重命名、状态/计数 | 分组、置顶、归档、硬删除、拖拽 |
| 对话 | 流式渲染、tool 卡片、思考折叠、审批/提问卡片、turn 分组、滚动不变量、**partial 恢复** | 消息编辑/分支、虚拟列表、逐 token 精确续传 |
| 发送 | steer/queue/停止 | 排队项列表可见、单项取消/撤回(等 harness 队列项 ID) |
| 审批/提问 | Host 存活期可恢复;重启只读中断 | 跨重启恢复(等 harness 持久化支持) |
| tool 展示 | 进行中→完成/失败 | 运行中进度(等 harness tool progress 事件) |
| Provider | 单活动 Provider、test-before-save、busy gate | 多 Provider/Profile、keychain |
| 插件 | 服务端插件列表展示 + Browser Cordis Context 地基(扩展点仅预留) | tool renderer/侧栏页实装、第三方隔离、双端 PluginPort、插件市场 |
| Markdown | GFM+高亮+KaTeX | Mermaid(含流式降级)、流式三件套移植 |
## 17. 非目标(一期不提供)
机器级 daemon / 局域网 / 远程 Host;多 Client 协作与并发观察;TUI 实现;多 Provider / Profile / 多设备同步;API Key keychain / 静态加密;插件市场 / 界面内插件管理 / 不可信插件隔离;session 归档/置顶/分组/硬删除/队列重排;消息虚拟化;Host 崩溃后运行中任务恢复;排队项的刷新后可见性(见 §4 边界)。
## 18. 附:关键决策演变(一次看全过程,正文不再重复)
> 每行 = 一个决策在三轮设计(本仓两轮 + 与平行仓 harness-1 综合)中的走向。详情:`missions/tasks/20260718-final-review/`。
| 决策 | 早期方案 | 最终结论 | 变化原因 |
|---|---|---|---|
| 一期形态 | Electron + Web 同期 | 只 Web localhost,Electron 架构预留 | 用户 2026-07-18 收窄范围;架构同构保留,后补零改业务 |
| Web 登录 | 一次性链接换 HttpOnly cookie | 裸 localhost 免鉴权 + AuthGate 接缝 | 用户拍板:单机开发体验优先;鉴权设计收档备用 |
| 排队项刷新可见 | 曾考虑 server 镜像队列 / Submission 投影 | 接受"刷新后暂不可见"边界 | 两仓独立终裁同向:不在 harness 队列前复制第二套状态 |
| 刷新恢复 partial | 一期不恢复进行中消息(要么定稿要么空白) | 恢复已生成部分(快照含 partial) | 纯 server 内存可实现,直接抬高刷新体验下限 |
| 发送语义 | 曾评估 queue+中断+撤回编辑全家 | steer/queue/停止三操作 + 三条纪律 | 忠实映射 harness 现成能力,不造第二队列 |
| 审批/提问恢复 | 曾承诺"可持久恢复" | Host 存活期可恢复;重启只读中断 | harness 审批/提问是内存态,跨重启需改 core(留迁移位) |
| 提问(ask_user_question) | 曾并入审批 | 独立资源、独立卡片、整批原子回答 | 两类交互终态语义不同,不共用 |
| session 列表 | 本地全量筛选 | server 驱动分页/筛选/排序 | session 量大时的性能与一致性 |
| system 消息 | 投成普通消息气泡 | 折叠诊断视图 | 普通时间线只留真实人类交互 |
| Markdown 范围 | (harness-1 为 Mermaid 无公式) | KaTeX 一期、Mermaid 后置 | 纯范围取舍,维持本仓选择 |
---
## 附:产品术语
| 术语 | 含义 |
|---|---|
| Session(会话) | 一次完整对话记录,可保存、回看、继续 |
| Host / Server | 跑 harness、持有权威状态的本地进程 |
| Client | 一个连到 Server 的界面实例;一期一个 Server 只允许一个活动 Client |
| 插话(steer)/ 排队(queue)/ 停止 | 运行期间的发送与控制操作(插入当前轮 / 排后续轮 / 整体终止) |
| partial 恢复 | 刷新/重连后先显示进行中消息已生成的部分,再继续增量 |
| turn(轮) | 一次任务执行;一轮内模型可有多个 step,UI 按轮分组 |
| 审批 / 提问 | 两类独立的用户交互请求;Host 存活期可恢复,重启后只读中断 |
| 请求边界 | 模型请求起点;模型/Provider 切换从下一边界生效 |
| slot(坑位) | 预留给插件注入组件的位置(tool 卡片、侧栏功能页) |
| degraded | 插件 Browser 能力失败、Node 能力仍在跑的降级状态 |