The fixture mirrors the host's plan unit: a double-event fold over
command/run (name plan) and plan/mode serves the {active, pending} value in
the projections baseline and advances it with session/projection frames;
/plan joins the command catalog with the host handler's wording; the prompt
path commits an outstanding selection as plan/mode inside the opened turn
(the agent/step boundary parallel). Baseline expectations in the fixture
specs gain the always-present plan key.
Conflict rulings follow the projection-reattach plan:
- host/runtime package (deleted on master): take master; the PR's boot
composition moves to the cordis.yml roster and its goals handlers will be
re-landed in dsh-host-apiproxy; the session.prompt slash interception and
its spec are dropped entirely (superseded by command.execute + command/run
logging).
- client core (rewritten on master): take master; the PR's Session goal
fields/methods, ConversationSnapshot.goal, goalActions injection, and the
hard-mounted GoalBar are all superseded by the 'goal' session projection
(useProjection) and will return as the ui-goal plugin.
- wire contract: union of master's workspace/command/skill domains and the
PR's goal domain, minus goal.get (the read side is the projection block +
session/projection frames; six mutation RPCs stay).
- GoalBar component and spec leave ui-conversation (they re-land in the new
ui-goal package); IconSparkle16 stays in ui-conversation chat.
- The web-slash-command-dispatch note documents the dropped interception and
is removed; the goal-bar note will be rewritten for the projection model.
- pnpm-lock.yaml taken from master (reinstall recomputes).
Sweep the silently auto-merged leftovers of the superseded #587 wire layer:
the PlanModeState interface and planMode/setPlanMode entries in the sessions
API and rpc map, the fixture plan RPC spec, the composer.controls README
paragraph and spec assertions, the old-format plan-mode snapshot, and the
two Agent Notes written against the RPC design (to be rewritten for the
projection reattachment). ChatView/PendingCard keep the approval-only
pending rendering; QuestionComposer improvements ride along unchanged.
Resolution follows the reattachment plan: the #587 wire layer (planMode/
setPlanMode RPC, prompt.planMode, client Session projection fences) is
dropped in favor of the session-projection base now on master; master
structure wins in all shared files. Kept from the PR side: the approval-only
pending filter in ChatView (questions render solely through the composer
takeover) and the auto-merged QuestionComposer improvements. The deleted
host/runtime package and retired test carriers are removed with master.
ds-review-bot round 1: the seam documented a merge-extensible union but
shipped a closed alias, and the gateway schema rejected any kind beyond
dialog/browse — a third backend could neither implement the seam nor be
advertised. The union now derives from an augmentable DirectoryPickerCapabilities
map, host.describe.directoryPicker preserves unknown wire kinds, and the
browse fixture applies listDirectory's root special case so creating under
'/' no longer mints a '//name' identity.
The consumer-side restated declare-merges retire (user ruling: one home per
projection key): TodoPanel imports the todos merge and TodoItem through
@deepseek-ai/dsh-tool-todo/client, and the manager takes the title merge
through @deepseek-ai/dsh-session-title/client — both pure-type outlets
re-exporting the domain's single-source types.ts, so no host value import or
Context merge enters the client program (type-only edges, exempt from the
plugin value-import ban). Workspace deps and tsconfig references added.
Also aligns the fixture's empty-log tail block with the host convention
(asOfSeq -1 with empty values, block always present on tail requests).
The manager's titleSnapshots Map and its session/title frame consumption
dissolve into resident per-session ProjectionValueStores (create-on-demand,
outliving instantiation — the same role the snapshot map played): a
session/projection frame lands whether or not the Session exists, list rows
read the store's 'title' key, subscribed baselines truncate phantom rows, and
session-removed drops the store. The fixture converts to the host parallel:
a projections block on the tail page (title + todos units), push frames on
unit-advancing events, and a post-subscribe projection baseline replacing the
bespoke title control frame.
Lifting the single-in_progress cap makes a list shape reachable that the
web surfaces never received. Two sites derived their one-line summary with
todos.find(t => t.status === 'in_progress') — the collapsed TodoPanel header
and the todo_write row — which was total under the old cap and silently
dropped every active item but the first once several could match: a plan
with three running tasks collapsed to the name of one. The expanded list
was always correct, so neither PR's tests covered it.
Both sites now take planSummary in contract/todo-plan-model.ts, the
domain-shared face both the skeleton and toolviews domains may import; the
duplicated derivation was why one find could be fixed while the other
stayed wrong. The hint names the first active item and suffixes +<n> for
the rest, so the collapsed line reports how many tasks are running.
The web fixture's todo sample now runs two items in_progress, so the
assembled web transcript replays a parallel plan: the row reads
'1/4 已完成 · 实现 fixture 样本 +1' over the built bundles.
- host trio kept on the merged api-proxy: session.models (provider-grouped
advisory directory), session.selectModel (validated provider, advisory
model), installAgentLlmTarget threaded through create/resume/ensureSession;
the gateway declares the llm inject it reads
- history no longer piggybacks modelTarget: the current target travels on
session.models alone (the /model popup is the sole consumer)
- new @deepseek-ai/dsh-client-ui-model plugin: /model popupSelect over the
wire — options load the directory (group label in the detail column,
provider-local failures listed inline), onSelect routes selectModel;
failures ride the popup shell's error/retry surface
- ModelSelector package, conversation.composer.control slot, and the
Session-side modelSelection state machine are removed: model selection
belongs to the /model popup; the named conversation.input.model seat
stays empty until a control-seat entry is designed for it
New and touched sources reach the CI per-file 100% thresholds: HoverCard
(timers, placement clamp, disabled guard), Menu label/danger/pointer-leave
branches, WorkspaceBrowser (mode switch, search, rail icons, rename dialog,
drag), rows and tree derivations, the workspace fixture stubs, the rename/
insertSessionBefore wire rows, and the entity move semantics. HoverCard's
position state narrows to {left, top} (equivalent refactor, no behavior
change).
The tool appends the snapshot mid-execution, between tool/call and
tool/result; the fixture spliced it after step/end with a post-turn
timestamp, so acceptance never exercised the production ordering. A spec
pins call → snapshot → result with monotonic times.
The web host now composes the sandboxed product path (sandbox-local +
sandbox-policy behind bash-sandbox/fs-sandbox, with user-approval and
permission on top); BootHostOptions.sandbox carries the deployment
defaults (workspace-write + ask).
createApiProxy owns the approval pending registry: a ctx.approval ask
becomes an answerable approval/requested mux frame with a stable rpcId,
replayed verbatim on every mux open until settled; respond routes by the
echoed rpcId, validates the ApprovalResponsePayload audit correlation,
and broadcasts approval/resolved; the ask's abort signal withdraws the
question as cancelled.
session.permissions / session.setPermission project ctx.permission into
a protocol-owned PermissionOption select; idle switches are held
last-write-wins and
flushed into the next prompted turn (the ACP bridge's anchoring
pattern). The shared hasOpenTurn fold moved to dsh-session,
deduplicating the private copies in user-approval, the ACP bridge, and
the proxy.
Client, per the designer draft: a pending approval takes over the
composer (ApprovalPanel replaces the InputBar — amber strip,
justification headline, paired command, one-shot refuse/allow, keyed by
rpcId so a queued second approval remounts live; the resolved frame
restores the composer); the sidebar session row shows an amber
waiting-approval dot that outranks the running ring (manager-tracked
approvalId set, idempotent under mux-open replays, cleared per
connection generation, lit for uninstantiated sessions too); the
permission selector is a composer bottom-row chip over an invisible
native select, with a presentation-only title-case transform
(workspace-write renders as Workspace Write; wire names untouched). Question placeholders stay in the message flow. The
connection fixture mirrors the host behavior for keyless browser
acceptance.