The master merge committed the i18n.yaml with unresolved conflict hunks
(carried over from the adapt branch's own master merge); re-recorded via
verify-translation-pairing --write. 518 pairs consistent.
The session/queued frame no longer carries steering — AgentMessage no longer
has the field. The client derives it from the same ordered turn boundaries the
host saw (a frame arriving while a turn is open joined the steering FIFO).
- TodoPanel mounts through a 'conversation.input.dock' list entry
(todoDockEntry, QueueDock posture, order -1 above the queue rows) instead
of a ConversationRoot hardcode; the inner component is unchanged and takes
useSession from the dock entry's standard kit.
- The verify-todo-display.mjs chromium probe is replaced by an assembled
keyless snapshot (apps/web/tests/todo-display.snapshot.ts, the
code-mode-fixture idiom) pinning the TodoRow summary/state, the dock
panel content, and the collapse round-trip over built bundles.
- Fake snapshots across specs gain the todos field; bilingual note/READMEs
updated for the dock mount and the snapshot.
The runnable snapshot pins the assembled pipeline request, whose
few-shot turns are the current text of five reviewed gold pairs; this
PR edits two of them (docs/development.md and docs/i18n/README.md pairs)
so the recorded request goes stale, per the gold-pair contract in
docs/i18n/translation-prompt.md. DSH_SNAPSHOT=refresh re-record; the
diff is exactly the six affected few-shot message bodies.
The interrupted pre-commit hook ran eslint --fix while client lib/types
were stale, which stripped two deliberate 'as' casts from master's
tests; one fails typecheck under exactOptionalPropertyTypes without it.
Restore both files to master's content.
- regenerate docs/module-graph.md for the three SDK packages (the stale
graph was the node24/static CI failure)
- rewrite the four recorded SDK snapshot session fixtures into master's new
canonical packed-row layout via migrate:packed-session-fixtures; keyless
replay verified against the packed fixtures
- Extract the shared New Session action into WorkspacesService.startSession
(sidebar button and workspace browser both delegate; recent-Workspace
targeting and the no-workspace clear live in one place).
- Fold the chip-insertion transaction shared by insert-ref and paste-upgrade
into one InputMachine helper.
- Share the fixture's session-not-found guard across the sessionId-addressed
catalog routes.
- Drop the AppFrame baselines-ready loading gate (user ruling: the bare
status line reads worse than the shell's own pending rendering); both
column occupants mount from first paint.
The exhaustive lane imports the loader-facing src/index.ts of the new
ui-slash/ui-command/ui-skill/ui-subagent packages without executing them
(0% functions); same client-lane deferral as their client/ halves.
The startup-selection flow leaves a fresh world (no Workspace) in the
locked view state, so every e2e scenario that types into the composer
now connects one first via the shared connectFreshWorkspace helper
(hero picker create-by-name dialog; the default 'workspace' name keeps
the session-header cwd assertions intact).
Golden refreshes carry the current composer chrome: the plan/model
control seats are empty until their owning plugins register (the seats
shipped without occupants on this branch), the sidebar shows the
connected workspace group pre-send, and the bash details material
renders Input/code/Output as separate nodes. The cancel scenario polls
the frozen-partial swap instead of counting synchronously — the abort
frame reaches the browser over SSE after the host settles.
The startup Workspace auto-selection (wired in cd8ed43e4) changed the
boot landing: with any Workspace present the client connects its blank
session directly instead of resting in the locked view state.
- workspace-flow: New Session now reuses the blank session in place
(no locked interlude); the failed-attach scenario asserts the actual
recovery semantics — the host publishes the session before rejecting
attachment, so the next connect reuses it into the hero (connect
failures log to console, there is no view-state alert surface); the
rejected-prompt scenario anchors on the sidebar New Session row since
a send attempt leaves the hero for the engaging retry chrome.
- slash-flow: drop the stale i18n PLUGINS row (the package is locale).
The origin/master merge's directory-rename detection relocated the
trio to implemented/feature/ (master's note-archiving sweep renamed
many implemented/ files, and the rejection move predated the merge);
move it back to rejected/feature/ where the rejection commit put it.
- Mount WorkspacesService.startInitialSelection in the runtime apply (the
one-shot baseline follower shipped in 98633b5aa without a caller): a
restored current session wins, an explicit clear stays cleared, a failed
connect retries on the next baseline projection.
- Cover the policy in client-apply and the assembled workspace-flow
snapshot; startup now lands in the recent Workspace's blank session, so
the draft-carry scenario starts from the hero directly.
- Bring docs along: startup-selection paragraphs in the session-scope RFC
note (both languages), bilingual README pairs for the four new client
packages, doc-graph regeneration with client-declared events exempt from
the dispatcher requirement (client dispatch sites are structurally
invisible to the host-side ts.Program), and pairing re-records.
- gate-consolidation note: parseArgs strict mode DOES reject a
dash-leading token where a value is expected (verified with node);
only the duplicate-option behavior differs
- YAML roll-up item: scripts/verify-cordis-config.ts is a fourth
js-yaml !!js tag definition the inventory missed
Both EN+ZH, pairs re-recorded.
The aborted-mid-stream case raced the abort against the child's chunk
delivery across two pipes and lost under full-suite load. Replace it with a
same-pipe ordering probe: the fake streams one text-delta chunk and then
answers the prompt with a malformed (non-accepted) result, so frame order
guarantees the chunk precedes the failure — the accumulated partial text
must survive into the error result. Same code path (collectOutput without a
complete assistant/message), no timing window.
- Keep ConversationSession mounted for blank sessions (chrome-less) so the
draft-persistence mirror stays bound in the hero; hero typing reaches the
chat store again.
- Restore the baselines-ready gate in AppFrame: empty boot snapshots no
longer flash the New Workspace hero before either baseline lands.
- Commit ordinary sends through the machine (send-committed event +
Shell.commitSend): undo can no longer resurrect already-sent content on
the default-sink path.
- Give the production InputMachine a real wall clock so the typing-run
merge window actually expires.
- Coalesce concurrent connectWorkspace creates per workspace: the summary
has no cwd until the host frame lands, so a second New Session inside
that window minted a duplicate hidden blank session.
The duplication gate flagged three ACP/SDK clones; the shared halves move
into dsh-subagent-subprocess as provider.ts: NO_START_CAPABILITIES (frozen
all-false advertisement), assertPositiveFinite (prefix-parameterized timing
validation), settleRunResult (never-reject result settlement with contained
onError sink and listener hygiene), and subprocessRunHandle (idempotent
dispose publication). Both backends now compose these; the previously
unreachable cancelled-rejection branch is directly unit-tested at the
library level instead of v8-ignored in each backend. knip learns the
subagent-sdk workspace (e2e entry outside vitest unit includes).
Same client-lane debt as the existing GUI exclusions (TODO(gui)): the new
ui-slash/ui-command/ui-skill/ui-sidebar/ui-workspace client files and the
connection fixture keep their uncovered branches until the browser-grade
harness lands.
Delete kill(signal?) from SubprocessHandle: consumers stop a process only
through terminate()'s tree-scoped SIGTERM→graceMs→SIGKILL escalation
(idempotent, also driven by the spec's abort signal, a no-op once the tree
is gone). The single-signal verb had exactly one consumer family —
lsp-local — and what it bought there was a private re-implementation of
the same escalation. The internal kill closure stays in spawn.ts as the
dispose ladder's tier primitive; terminate() now routes through it too.
lsp-local collapses onto the seam's escalation:
- LspConnection replaces its terminate()/kill() pair with one terminate()
that delegates to handle.terminate(). Behavior change: the
framing-failure path terminates instead of instant SIGKILL, so a
misbehaving server now gets SIGTERM plus the killGraceMs window to
flush before SIGKILL.
- ConnectionSpec.pipeDrainGraceMs becomes killGraceMs: one grace, the
spawn spec's graceMs, drives both the escalation window and post-exit
pipe draining (the provider already passed killGraceMs for it).
- LspInstance.forceTerminate() drops its hand-rolled bounded first wait
(LSP_KILL_GRACE) and escalateProcessTree (deleted with its export and
unit test): the seam's escalation already commits to SIGKILL after
killGraceMs, so only the unbounded quiescence awaits stay load-bearing.
Tests: kill()-shaped spawn specs become terminate()-shaped or fold into
the terminate() suites (group-wide delivery; the settled no-op case was
already pinned by 'terminate() after the tree died'); tree-survivor
coverage is intact. A stderr-'inherit' disposition test completes the
stdout/stderr symmetry so the scoped subprocess+lsp coverage gate stands
alone instead of leaning on subagent-acp's cross-package runs.
Docs: SubprocessHandle type-equiv block, seam/impl/group READMEs, and the
consumer-migration Agent Note lose the kill(signal?) vocabulary (zh pairs
re-recorded); cordis api/services catalogs regenerated.
- examples/jsonrpc-agent gains its first snapshot suite (sdk.snapshot.ts):
the real dsh-jsonrpc-agent runtime driven through the real dsh-sdk-client,
keyless llm-replay behind a new cordis.snapshot.yml overlay; three recorded
scenarios (text turn, bash tool, spawn subagent) pin the notification
stream, the SDK turn result, and the persisted parent+child session logs.
- Bilingual READMEs for dsh-sdk-protocol / dsh-sdk-client / dsh-subagent-sdk;
sdk/ and subagent/ group tables extended; dsh-jsonrpc README points at the
extracted protocol package; Agent Note (en+zh) owns the decision.
- The proposed make-jsonrpc-directional note is updated for the transport's
new home and its second (client) consumer.
- Model Experience sentence allowlist entries for the two client-side
packages; module graph + config catalog regenerated; i18n pairings
recorded. doc-sync passes 24/24.
Test-side catch-up with the session-maybe conversation architecture: the
provide channel's descriptor shape and maybeProvideInfo in fakes, the shared
chat-store handle asserted on conversation.session (the session-maybe shell
carries no store), startSession fakes exposing the workspace list snapshot,
strict session slots declining (not throwing) without a session, AppFrame's
removed empty seat and loading gate, and the hero draft asserted on the
machine (the chat-store mirror binds with ConversationSession). Plus three
lint fixes (max-len split, boolean-compare, arrow-parens/unbound-method).
Drop SubprocessSpawnSpec.dshEnv and splitEnvChannels(); childEnv() is now
scrubbed-base + explicit entries with no namespace validation. The invariant
dropped is the reserved-namespace check on explicit entries (DSH_* rejected
from env, non-DSH_* rejected from dshEnv). Explicit-entry trust already
covers it: an explicit credential-shaped entry has always merged after the
scrub as a deliberate caller opt-in, and an explicit DSH_* entry is the same
deliberate act — the staleness invariant lives entirely in scrubbedParentEnv
dropping AMBIENT credential-shaped and DSH_* names, which stays. The
validation's only observed effect was rejecting legitimate explicit entries:
both recent CI breakages (DSH_GATE_CONCURRENCY exported into every job
crashing lsp specs, DSH_PERMISSION_MODE in acp config.env crashing the
child spawn) were this check firing on values a caller meant to pass, each
fixed by routing around the bureaucracy the seam itself imposed.
The bash seam keeps its own request/spec dshEnv field: that is bash-owned
trusted-plugin vocabulary (the ctx.bashEnv collected overlay) whose merge-last
position guarantees a caller env entry cannot displace a managed fact;
bash-local now flattens ENV_OVERRIDES -> spec.env -> spec.dshEnv into the
seam's one env map. subagent-acp and lsp-local pass their single config env
map straight through. DshEnvironment/DshEnvironmentKey/DSH_ENV_PREFIX stay on
the subprocess seam as the namespace vocabulary (bash re-exports them;
scrubbedParentEnv filters on the prefix).
Tests: the two channel-rejection specs and the splitEnvChannels partition
spec are deleted; one spawn spec now proves an explicit DSH_* env entry
reaches the child while an ambient one is scrubbed; the acp/lsp forwarding
specs keep their MOCK_ECHO_ENV / LSP_FAKE_ECHO_ENV assertions with the split
comments rewritten to merge-after-scrub. Docs (en+zh, re-recorded) and the
owning Agent Notes updated; cordis api/services catalogs regenerated.
eslint --fix autofixes plus manual repairs: max-len line splits
(fake-api handlers, notifier/slots JSDoc, spec signatures), charAt over
non-null-asserted indexing in slash detect/menu cores, Array.from for
code-point capping, typeof assertions for unbound-method in specs,
generic getByRole for the send-button cast, effect disposer void-wrap in
command register, and dropped unused type imports.