Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion
and its tests, the invariant gates and Vitest setup, the scoped-event
resolver generator, and the INVARIANT-coded listener rethrow paths.
Update docs, READMEs, Agent Notes, and add an upgrade guide.
Review round 1: the settle assertions build their message only when one
is about to fail, so a passing run never pays the host-facts shell probe;
send windows are bounded by event sequence numbers instead of
milliseconds, so two sends that hand over within one millisecond keep
their own events; the replay starts at the send's own input write and
ignores the emulator's terminal-protocol replies; a throwing foreground
inspection is recorded before the send rejects; a sanitizer is attributed
to the most recently spawned terminal, which the class documents as the
sequential-session assumption. A unit spec pins the replay rule against
the session's evidence states, the window boundaries, the reply/input
distinction, and the bounded, escaped rendering. Notes and comments drop
change narration and the unverified claim about the pool's build.
The self-hosted Windows lane settles one or two of seven sends on the
controlled prompt and the rest at the plain silence bound, and the
settle reasons alone cannot say whether the marker was missing, its tail
was invalidated by later output, or the foreground comparison failed.
The case now records every decoded pty chunk with the sanitizer's marker
verdict and prompt tail, every foreground poll, every input write, each
send's settle reason and elapsed time, the session's prompt evidence
replayed from those chunks, and the host, pwsh, PSReadLine, and console
host versions; the settle assertions carry that timeline in their
failure message and onTestFailed prints it when the runner's budget ends
the case first.
promptTailGraceMs returns to the product default: master runs with and
without it took the same 20.8 s, so the lane's degraded sends never held
the state it covers, and a nonzero value there would only lengthen a
never-arriving-tail fallback.
Review round 1: the tolerance now requires the accumulated tail to remain a
prefix of the controlled prompt, so a prompt that arrived and was invalidated by
later output keeps the plain silence bound instead of waiting a tolerance with
nothing left to wait for. The field is validated as zero or at least one
`pollIntervalMs`, matching the handoff grace rule, and tests pin the extended
bound's expiry, the invalidated tail, and that rule. The owning readiness design
note records the new field and Tier 2 bound and cross-links this decision.
Windows sessions on the self-hosted lane settled one or two of seven sends on
the controlled prompt and charged the silence tier for the rest (issue 2487).
The marker is written by the shell's own prompt function and the printable tail
by the same render, so a tail that arrives past `idleSilenceMs +
handoffGraceMs` means a stalled delivery, not an absent prompt.
`dsh-terminal-bash` gains a validated `promptTailGraceMs` bound (default 0,
which reproduces the previous bound exactly). While a send has seen the marker
and its printable tail has not arrived, the silence bound grows by it. The
pwsh Loader-composition case sets 5000 ms, the host class the bound is for, and
still fails when no marker appears at all.
`operations.spec.ts` read the profile's run record from a 100 ms timer armed
before the refused installation whose repair it observes; a loaded runner
reached the repair run after that timer and the case failed with `null`
against the recorded run. The observing child now stays in flight until the
record appears, and the bounded wait starts at the launcher hand-over rather
than at test start.
The loader-composition case counts `stdin_read` settlements but reported only
the count, so its Windows failures could not say which tier settled which
send. The recorded reasons now ride in the assertion message; the threshold
and the assertions are unchanged.
- State the contract precisely: the cut never introduces a lone high
surrogate, and an unpaired surrogate the input already carries is not
repaired.
- Document the new export in the package README: a character-budget preview
section naming the persistent bash and pwsh shells and str_replace_editor,
plus a Known Limitations bullet that no longer contradicts it.
- Drop the assertion restatement from the two shell test comments.
- Let the str-replace test helper reuse a root so the clipped context reads
its file inside its own working directory.
Conflict resolution:
- docs/config-catalog and docs/module-graph: regenerate with the current
generators, which write the bilingual pages together.
- The conflict came from master's new short-fingerprint pairing records: our
branch still carried the previous full-hash records. Take master's record
format, then re-record the output-retention README pair after regenerating
its two sides.
* feat(i18n): key translation pairing records by heading section
Replace the whole-file blob hashes in *.i18n.yaml with one entry per
heading section that still holds language-specific content, keyed by the
English heading-slug path. Blocks identical across the pair (generated
table rows, code blocks, shared regions) stay out of the hash, so edits to
different sections and identical regenerations merge with Git's default
text merge, including on GitHub.
Remove the dsh-translation-pairing merge driver, its resolver command and
installer wiring; gen-translation-brief recovers last-confirmed text from
commit history. Migrate all active records, archive the superseded Agent
Note, and record the replayed conflict reduction in the new one.
* test(i18n): refresh translation prompt snapshot for the terminology note
* feat(i18n): exclude only code blocks and generated regions from pairing records
Replace byte-equality masking with an explicit rule: a record hash skips
fenced code blocks and BEGIN/END GENERATED regions, the two kinds of
content the pairing gate already requires to be identical on both sides.
Move generated data into regions written into both pages:
gen-config-catalog renders both languages and the record with per-package
regions labelled by code identifiers; gen-module-graph keeps its table in a
region; gen-doc-graphs splices the event matrix regions into the Chinese
page. Add a generic region splice beside the region grammar.
Conflict resolution:
- docs/config-catalog: regenerate the English page; the Chinese page follows the
three shifted Config sources and the bilingual pair record is re-recorded.
- docs/module-graph: the generator rewrites all three artifacts together.
- packages/shell/tool-pwsh-persistent/src/index.ts: keep master's rewrite of the
prompt handling and re-apply the surrogate cap and its import.
- The three package.json dependency entries follow master's workspace:* range
convention, and the lockfile importer specifiers match them.
Three tools capped the preview text they return with String.prototype.slice
at a UTF-16 code-unit budget. A cut that lands inside a surrogate pair
leaves a lone high surrogate, which is not well-formed text: a strict JSON
reader rejects the durable Session log carrying it, and the preview shows a
broken glyph. A lone surrogate retained in history has already blocked every
later Messages request for one session.
Centralize the cap in output-retention as
truncateWithoutSplittingSurrogatePair, and use it from the persistent bash
and pwsh shells and the string-replace editor. The cut drops the unpaired
half instead of keeping it, so the retained text stays a prefix and is at
most one code unit shorter than before.
The new dependency edges and the shifted Config sources regenerate
docs/module-graph and docs/config-catalog, including the committed
translation pair.
Refs #4817
The composition suite's idleSilenceMs: 30000 was below the suite's own send
bound (dsh-terminal-bash timeoutMs: 300000), so the comment claiming silence
could settle nothing was wrong: a regressed send still settled as inferred_idle
after ~30 s and only the accumulated case timeout caught it.
The case now records every send's waitReason and requires at least six
stdin_read settlements with no inferred_idle. Reinstating the removed prompt
override fails it in 30 s with 'expected 0 to be greater than or equal to 6'
while the output assertions still pass. The silence tier returns to its product
default.
Also renames the two stale test titles ('private shell prompt', and the
cancellation phase that no longer covers an initialization send), pins the
stdin_read fallback's session retention, and records the interactive-child
consequence of that retention in the package README pair and the Agent Note.
tool-pwsh-persistent installs its own `prompt` function after spawn, so
terminal-bash prompt readiness never matched and every send degraded to the
silence tier (idleSilenceMs + handoffGraceMs) under production defaults. This
is the defect the persistent bash tool fixed for the bash stack.
The tool now initializes without a prompt send and detects the no-end-marker
fallback through the seam's stdin_read wait reason instead of matching its own
prompt text. The private prompt constant and its stripping are deleted, so
partial output can carry the backend's own trailing prompt.
Tool calls drop from 8493/3722/3832/3759 ms to 1340/255/251/241 ms (spawn and
initialization plus the first command, then three warm commands; Windows,
production defaults, real Loader composition). The loader composition suite
pushes idleSilenceMs beyond the send bound, so every case fails if prompt
readiness regresses.