One tiny recorded text turn drives three whole-page concerns:
- workspace flow over the real wire: the empty-state hero's first send
materializes a real Workspace + Session (the jsdom workspace-flow suite
pins this state machine over the fixture client; this scenario pins it
through HTTP RPC + SSE + the gateway). Durable proof: the session
header's cwd is the create-by-name target <workspaceRoot>/workspace.
Adds the hero waiting-state aria golden.
- reload recovery: collapse the sidebar (persisted dsh.layout.panels),
page.reload, and the surface comes back whole from persistence alone —
layout collapsed, selection restored (dsh.sessions.current), the
recorded turn re-rendered from session.history with zero model calls
(the drained replay cursor makes any stray request fail loud at close).
- dark mode: no product control flips the theme yet, so the scenario
drives the ThemeService's entire DOM contract — body[data-ds-dark-theme]
— and pins the shipped cascade: the alias token flips, a painted surface
repaints, and removing the attribute restores the light sample exactly.
TODO(web-theme-gesture) upgrades to a real settings control; no theme
golden per the lane's scope ruling (aria is color-blind).
Agent Note scenario list extended in both languages; pairing re-recorded.
The bridge replaces its serialization queue with a pool that reuses the
native concurrency contract: submissions classify through
registry.executionMode (fail-closed isConcurrencySafe), start strictly in
submission order, overlap up to the validated maxParallelSubCalls config
(default 10; 1 restores serial), and exclusive calls drain the pool, run
alone, and bar later calls. Each started sub-call logs a
tool/code-dispatch-start event at pool entry; the existing
tool/code-dispatch settles the pair (started ⇔ settles exactly once;
abandoned queued calls log neither). SDK prompt guidance now states the
true Promise.all contract — re-recorded across every code/both-mode
snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the
required description arg).
Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts
land the running shape (rows wear the native running ring), settles
replace in place preserving start order, callTime pairs to the start
time. Fixture emits start/settle pairs; jsdom pins the running sub-row;
runtime specs pin in-place settlement and out-of-order completion.
Review round on the pivoted design:
- node-24 (static) kept an unconditional hosted pnpm cache restore while the
coverage and consumers lanes skip it under failover. On the self-hosted VM
that restore downloads ~180 MB into /home/runner, a path pnpm never reads
there, adding latency and contention during an outage. Gate it with the same
`vars.DSH_CI_FAILOVER != 'selfhosted'` condition so all three lanes match.
- Runbook switch step 2 said "Re-run failed jobs", but the documented
indefinite-queue outage leaves jobs queued (not failed), which cannot be
re-run in place and do not retarget on variable change. Correct both language
sides to cancel the run and re-run all jobs, or push a new commit.
- The standby-lane comment still described the switch as a one-line runs-on
change; it is now setting the admin-only DSH_CI_FAILOVER variable.
Adds the Chinese counterpart of the new seam note, records both pairs
(new note + the updated background-task runtime note), and ratchets the
translation-pairing manifest.
The failover runbook landed as .agents/notes/implemented/process/ci-failover-runbook.md,
which fails three doc-sync gates: the classification/format gates require a
yyyy-mm-dd-topic.md filename and the implemented Agent Note skeleton
(Problem/Decision/Alternatives/Consequences), and the bilingual pairing gate
requires cross-note link targets to match between the two language sides.
Rename to 2026-07-26-ci-failover-runbook.md/.zh.md, reshape both sides into the
implemented skeleton (the runbook steps live in bespoke sections under Decision),
point the sibling topology note and the ci.yml comment at the dated filename, and
make both sides link the canonical .md per the bilingual convention. Re-recorded
the i18n pairing records.
One two-turn seed (turn 1: bash + two parallel reads in a single assistant
message; turn 2: a markdown-heavy reply) rendered cold through the
seeded-history pattern — zero model calls — serving four surfaces:
- sidebar search: client-side title filter; asserted only after the durable
title lands with the attach baseline (a cold SessionSummary carries no
title — search matches the displayTitle the user sees). Negative query
empties the tree, positive narrows to the match + its force-expanded
group, clear restores.
- Trajectory tab: turn sections, the step group's tool mix ('bash read×2'),
and a view-area aria golden.
- Waterfall tab: span stats header + one lane per span. The P-I fold counts
a turn-0 prologue span (only assistant/steering nodes carry a turn
number) — pinned as-is; real spans are P-III per the view's ledger.
- details column: the bash toolview row routes click to openDetails;
open/closed is asserted on the frame's data-details-collapsed attribute
because close collapses the grid column to width 0 without unmounting
the subtree (hidden, not absent, is the contract).
Agent Note scenario list extended in both languages; pairing re-recorded.
The tasks/ family now matches the capability-seam shape: @deepseek-ai/dsh-tasks
keeps the abstract TaskService (ctx.tasks contract, vocabulary types, snapshot
invariant companion) and the new @deepseek-ai/dsh-tasks-local carries the
process-local registry (LocalTaskService: in-memory store, settlement,
owner-cleanup effects, teardown, TASK_WAIT_TIMEOUT). Compositions and test
harnesses now load dsh-tasks-local; producers, TaskKindMap merges, and
dsh-tool-tasks keep importing the seam only.
Producer misconfiguration diagnostics name dsh-tasks-local because loading the
implementation is the fix. The registry behavior suite moves to tasks-local;
the seam keeps a stub-subclass registration test and the probe-based invariant
suite.
Both languages: the three new scenarios (live-interactions overrides,
question-composer takeover, wire-level steering), the product-delta list
({ patches } override form, the carried-failure fix, the llm-retry row),
two new Deferred items (web error surface, composer steering gesture),
and de-hardcoded scenario counts; pairing re-recorded.
The client indexes tool/code-dispatch events into
ConversationSnapshot.codeDispatches (parent callId -> ToolResultNode-shaped
sub-calls; live mux and history replay build the identical index). ChatView
renders each run_code parent as the new code variant (description summary,
program as the expanded monospace body) with its sub-dispatches as
always-visible indented rows — every sub-row dispatches through the SAME
keyed conversation.chat.toolview hole with the same GenericToolCard
fallback, so custom registrations (bash sample) take over sub-rows exactly
as top-level rows. The details panel resolves sub-callIds to full logged
args and complete output through the native path.
Evidence: fixture turn 64 + built-bundle jsdom snapshot, real-machinery
jsdom suites (nesting, error state, details, running parent, reference
stability), and a recorded code-mode browser e2e round (keyless replay +
aria golden). Scaffold gains a toolsMode patch knob.
run_code gains a required bash-style description parameter: presentCall
titles the card with it and moves the program to rawInput, so every
surface gets a readable label. tool/code-dispatch now logs each
sub-call's complete content/isError (the tool/result vocabulary),
replacing the bounded resultSummary and deleting the summarize/cwd
machinery — a UI renders sub-calls through the identical path as native
results. The dsh config tree mounts the worker code runtime and reads
DSH_TOOLS_MODE (temporary seam until per-session mode selection lands).
Session format stays v0 (pre-release churn). Code-mode ACP/TUI fixtures
re-recorded; TUI presenter pin refreshed; catalogs regenerated. Keyless
web smoke pins the code-mode wire contract (tools=[run_code] + SDK
prompt section).
The hero composer renders the frontend Session Intent's retained prompt
from the sessions list snapshot, but updateIntent only reached that
snapshot through the intent watch's microtask-deferred markDirty. React
therefore rolled the controlled textarea back during the change tick,
which corrupted IME composition (Pinyin "nihao" committed fragments
like "nnini hni hani hao你好") and jumped the caret on plain typing.
SessionManager.updateIntent now calls notifyNow after
updatePendingPrompt, per the Notifier channel rule for direct echoes of
user gestures. The workspace-flow snapshot helper asserts the same-tick
echo instead of waiting for it, and a runtime unit test pins the
contract at the manager seam.
- rename same-title no-op moves inside the serialized creation chain
- insertSessionBefore maps only the typed WorkspaceMoveInvalidError to
workspace-move-invalid; storage failures stay internal
- workspace upsert rejects snapshots older than the installed projection
- flat-mode empty state shows when the query hides the intent row
- intent row no longer forces group expansion; header twist stays live
- group-by menu rides a portal; menu clicks stop propagating to the row
- intent row uses the same single-slot indent in both list modes
- regenerate cordis api/catalog + doc graphs
The three required Linux jobs now resolve their pool through the
DSH_CI_FAILOVER repository variable. Unset, everything runs exactly as
today on the hosted enterprise pools. Setting it to 'selfhosted'
(repo-admin-only, not PR-editable, no merge required — a merge would be
deadlocked behind the failing checks themselves) retargets all three
onto the vm-backup pool, halves the coverage worker bound and snapshot
concurrency for the shared VM, and skips the hosted-path cache restores.
Adds a bilingual failover runbook (switch, capacity via the four
registered spare instances, switch-back, trust boundary) and links it
from the topology note. The push-triggered standby lane remains the
continuous proof that the failover target works.
Direction change after review discussion. Moving a REQUIRED check onto
a single in-house VM traded merge-path availability for modest savings
and accumulated trust/contention caveats (six review rounds' worth).
Revert every coverage-lane change: coverage stays on the enterprise
Ubuntu 24.04 32-core pool exactly as on master.
Instead, add serial-linux-selfhosted: on every master push the in-house
pool (vm-backup) runs the complete unsharded primary aggregate as a
hot-standby drill. It blocks nothing, yet continuously proves the
environment end to end, so any hosted-pool outage can be answered with
a one-line runs-on retarget onto continuously verified capacity.
Push-triggered lanes execute the base branch's own workflow definition,
so no PR-editable path selects these runners — the entire fork-trust
discussion is structurally moot for this lane. Topology notes (en/zh +
pairing records) describe the standby lane and the switch play.