The label-dimmed alias rides the bluish neutral ramp, whose cast shows
at the 56px compass; the hero now takes neutral-200 (light) and
neutral-700 (dark) from the pure-gray static ramp.
The guide tab gains a compass hero, per-entry descriptions (shown while
at most four entries are listed), and a quieter cube placeholder for
entries that registered no glyph; the files entry registers its
description and folder glyph. Bundled stat fixes: the session usage
dialog drops the zero cache-write row, and the turn-time pill renders as
plain text when its dialog would only repeat the pill's own duration.
Seed sidebar default pages lazily on expansion, scope page-tab dedupe
to the target pane, let a sole tab split its own pane with a factory
backfill, close menus when focus enters a sandboxed iframe, and pin the
code preview copy banner while unifying its background with the pane.
A file whose stat and first page both fail — outside the workspace, gone —
showed the same line twice: the metadata bar above an identical centred body
failure. The bar now stands only over pages already loaded, and the centred
retry runs the full reload so one press clears both halves.
The expand/collapse and wrap toggles' accessible names diverged from their
tooltips, and the replay harness now normalizes clientTimeZone in recorded
sessions; every golden holding the old names or the literal zone refreshes.
- Split toggle aria-labels from their tooltips: the sidebar expand/collapse
buttons announce "Open/Collapse right sidebar" so the two sidebars stay
distinguishable, and the wrap toggle keeps an invariant "Line wrap" name
beside aria-pressed while the tooltip keeps the action wording.
- A hidden tab close also takes no pointer events, so a touch press on the
chip's trailing edge activates the tab instead of closing it; e2e closes
hover the chip first, the way a reader would.
- Closing the last docked tab in fullscreen hands the mode back to push, so
the next expand does not give the whole window to the start page.
- The drop scrim mixes from bg-base, not bg-layer-2, which dark mode binds
to a lighter bluish step than the column beneath it.
- Document the menuitem probe in TabMenuExtras and the strip fade's known
gap; match the open-in-app split-button comment to the 28px control row.
The lone guide tab withholds its close and menu, any other lone tab closes
together with the column, and the expand button renders only while collapsed
so the corner seat costs the header nothing.
Chip capsules with scroll fades and live titles, drop-zone hint cards, the
canCloseTab control policy with the quiet lone chip and self-dismissing empty
menu, and a room reading that stays independent of hidden split controls.
Dislike and a bare /feedback open the Session's feedback dialog, which
collects one of seven fixed categories and an optional description and
records through the messageFeedback put or the new sessionFeedback
Remote; Like records at once. A recorded rating shows the filled glyph
and every submission is acknowledged by a toast. feedback/record and
MessageFeedbackItem carry the optional category; the note popover, its
layout scenario, and the clearNote/clear verbs are removed. ui-commands
gains the action command kind the /feedback decoration runs as.
Closes#3515
Merge 3ea52fc3e475a3e7c430812bde4209258416e57b into a0a61a8237f61344a01448a97954df603d8fff73. Retain the V2-to-V3 migration dependency alongside native system flock support, removing the replaced fs-ext dependency. Union the lockfile workspace importer entries; frozen installation validates the result without regeneration.
Preserve release publint async lane-signal cancellation, close ownership, and spawn-error regressions, satisfying the incoming CI budget intent. Keep canonical, prompt, preset, and latest migration regression behavior. Rename an arbitrary preset fixture value to custom-agent to avoid the inherited vendor-rescope false positive without changing test semantics.
Validation: frozen install; full build including Darwin addon; focused V3/persistence/prompt/publint and runtime tests; built migration, two-process lease, and loader smokes; native flock and package-matrix tests; touched dependency and CI owner tests; all hygiene leaves (vendor residue repaired and rechecked); doc-sync 34/34.
The minimal compositions serve the same models that trained against a
one-shot `bash` tool; three surface differences remained visible to them.
- Every settled command now appends `[Command finished with exit code N]`,
including exit code 0, instead of `[exit code: N]` only on failure. A
timeout appends `[Command timed out or OOM]` after the partial output.
- Trailing newlines are all dropped before the status trailer rather than
only the last one.
- The `bash` description in the `minimal` preset and the `sdk-minimal`
bundle replaces the two fixed network lines with a single environment-
dependent line; the pwsh description is unchanged.
Tests, READMEs, the web minimal-preset schema snapshot, and the packaged
Python minimal model-visible snapshot follow the new wording. The
standalone `persistent-tools` SDK replay fixture keeps its own copy of the
old description since it is a per-launch customization, not the profile.
The icon-set spec still pinned 74 exports after IconGaugeOutline16
landed (a hand-drawn product glyph — the set is now eight of those).
Moving the preview badge inside the hero title group had merged the
headline into one text node with the badge, which broke exact text
lookup (hmr-live e2e); the headline gets its own span again.
StatsPills buttons carry explicit aria-labels separating segments with
' · '; a log with no timed figure renders the counts pill as a static
reading instead of a button opening an empty dialog; the pills row owns
one exclusive open slot so sibling dialogs never stack. ContextMeter
restores the '~' approximation prefix (usedTokens prefers heuristic
projectedTokens). ModelSelect's jscpd:ignore now names the real gap
(useAnchoredPosition places from the left edge only). The pills Agent
Note's stale facts (locale keys, context occupancy, golden ownership)
are rewritten in place, en+zh. plan-review.e2e.ts parks the pointer
after Approve so the ContextMeter tooltip cannot leak into the aria
captures; affected goldens refreshed.
The plain-to-capable transition emits the model-selection notice inherited from master. Preserve the exact in-history system ordering and assert both the admitted user input and the single durable notice instead of expecting the obsolete five-message request. Reproduces the identical Linux and Windows CI failure; all 57 adjacent admission, projection, reconstruction and selection tests pass.
Master archived three Agent Notes this branch had fact-updated (archived
copies restored to their frozen master content; the pills note now links
the archived path), extended the steer-all placeholder to continuable
children (spec and subagent e2e take master's behavior with this branch's
reworded copy), and dropped the session-sharing sentence from feedback
acks (goldens re-refreshed with the new placeholder).
The 2026-08-06 continuable-subagent interrupt note said the child's Send
always queued; it now names the busy-Enter delivery and cross-links the
busy Send button note, which links back. The new note's and bench's
plain-Send enumerations now exclude the ordinary running empty/blocked
draft, whose seat is Stop.
The A/B winner over the single-line StatsLine: a gauge pill (counts + TPS,
opening the session-stats dialog) and a database pill (billed total +
cache-hit share, opening the exact token-usage dialog), sharing a new
stat-dialog module. StatsLine and its dead locale keys are deleted, the
gauge icon is optically centered, and the composer keeps its 4px clearance
through the data-composer-stats contract. Agent Note added; stale
StatsLine references across implemented notes updated in place.
Review follow-up: the mode label predicate now includes !uploadsPending,
so a running draft whose file is still uploading (or failed) keeps the
plain Send label on its disabled button, matching the documented
'enabled button' condition. Test pins pending vs ready upload states;
the Agent Note and README pair name the upload gate.
Include the root merge from #3678 without altering the previously validated implementation tree. Preserve structural V3 system messages and the current native-runtime recordings.
Review follow-up. The mode label predicate lacked `!empty`, so a running
continuable child with an empty draft showed a disabled "Queue message";
and a claimed slash command or a `/` line headed for adjudication executes
a command rather than delivering a message, so naming Queue/Steer there
misdescribed the click. The label now names the mode only when the click
would deliver a plain message: running, steer-capable, enabled, non-empty,
unclaimed, and not a `/` line. Tests pin the empty-draft child, the slash
line, and the claimed command; the Agent Note, README pair, and
ComposerKeyboard.submit JSDoc state the exact predicate.
The ui-conversation.busyEnter setting selected Queue or Steer for plain
Enter only; the running draft's Send button always submitted through the
public InputActions.submit() face, which is fixed to Queue, while labeled
"Send message". A user who chose Steer got Steer from Enter and Queue
from the button beside the same draft with nothing explaining why.
InputBar now resolves the primary click through the same `enter` gesture
and steer-availability predicate as the keyboard path (ordinary Sessions
and continuable children) and submits via ComposerKeyboard.submit(mode).
While that delivery is available the button's tooltip and accessible name
state it ("Queue message" / "Steer message"; 排队发送 / 插话发送), so the
mode is never hidden; idle sessions, one-shot children, and locked
composers keep the plain Send label. The composer bar inject face
publishes the live preference as hooks.busyEnter instead of a resolver
closure, and resolveSubmitMode is a pure function taking the preference
explicitly, so label and delivery derive from one value in one render and
the label follows live Settings changes. The Settings row is retitled
"Send behavior while busy" with a description that names both Enter and
the button; the busyEnter field, its default, and the Host schema are
unchanged.
Tests pin both preferences on the running button for ordinary sessions
and continuable children, the live relabel, the idle Queue path, and the
pure resolver; the settings-chrome ARIA goldens and the live-interactions
running-draft and queue-actions failed goldens carry the new copy. A new
Agent Note owns the decision and reverses the pointer-ignores-preference
clause of the archived 2026-08-20 running-draft note.
Preserve both sdk-minimal-in-history and native runner smoke scenarios. Record the current Cordis inspection through the actual upper headless owner while retaining upstream V2 bytes and strict prompt admission semantics.
Preserve strict live-smoke challenges and structural system messages while adopting the native subprocess runtime. Regenerate only the current Cordis inspection recording from the upstream V2 source to retain its no-ordinary-PID coverage; predecessor bytes match master.
Preserve the root V3 current-writer, PTC dispatch identity, and reliability fixes while incorporating provider-owned native ranges and the private packaged runner bootstrap.
Refresh only the current cordis-inspect-jsdoc V3 owner through the built headless profile so upstream subprocess API coverage is exercised. Preserve incoming master V2 bytes and all other fixtures and benchmark thresholds.
Preserve prepared-route admission and V3 prompt semantics while adopting latest master. Regenerate the new model-switch recording at its actual upper-layer title ordering, retain the V2 predecessor, and keep queued-image and publint lifetime assertions strict.
Adopt current model-switch notices, lazy observation, and open-in-app integration while preserving structural V3 admission. Regenerate only the new model-switch owner’s native V3 recording, prompt-count manifest and expected delimiter; keep the master V2 predecessor byte-exact.
Bring model-switch notices, lazy observations, and open-in-app into the root V3 integration without importing the child structural header changes. Preserve V3 migration, PTC, and all predecessor fixtures.
Refresh only the model-switch-notice owner through dsh --profile headless to harvest native current-writer session.v3.jsonl. Keep the master session.v2.jsonl byte-exact; existing header pin and versioned writer helpers require no changes.
- A media-named FIFO or device node is refused before the open would
block on it (pre-open isFile check); POSIX FIFO regression test added.
- The full-body stream is bounded to the stat'ed size so concurrent
appends cannot exceed the declared Content-Length.
- Error responses carry no body for HEAD requests.
- Docs no longer overclaim the replacement race: the stat-identity
comparison narrows (does not fully close) the replacement window, and
the earlier realpath-to-stat window is acknowledged. Pairs re-recorded.
range-parser never validates the range unit, so /api/file now parses only
headers that start with the bytes unit: unsatisfiable ranges answer 416,
while malformed, unknown-unit, and multi-range headers are ignored for a
full 200 body per RFC 9110. The filesystem-root workspace case stays
POSIX-only in tests (a Windows drive-root spelling cannot be constructed
portably); containment already treats any separator-terminated root the
same. Docs (README pair, Agent Note pair) re-recorded.
Review follow-up. A forwarding module's explicit re-exports are tried
before its star re-exports and an entered module is skipped on later
edges, so a star edge that loops back no longer hides the explicit
re-export beside it. Import resolution outside the checker goes through
the face's shared compiler host and module-resolution cache, and an
import naming an unregistered package fails the public-export check
instead of throwing a TypeError. Tests pin the looping star, the
relative-only cycle exit, namespace re-exports, and re-exported
namespace imports.
Add the first-party open-in-app host and client packages with a localized Open In menu in the Web Session header.
Resolve installed applications on macOS, Windows, and Linux, launch workspace directories through platform-specific adapters, and remember the selected application.
Closes#1500.
Co-authored-by: ihsiang <[email protected]>
- HEAD never opens a file stream; GET streams are destroyed on client
abort and are never opened for already-aborted requests.
- Validation and reading bind to the same opened file: pre-open and
opened stat identities are compared, so replacement/re-linking races
are refused (dev/ino check, read through the FileHandle).
- Workspace containment compares path components, so a filesystem-root
workspace serves files instead of doubling the separator.
- Multi-range headers are ignored for a full 200 body instead of a
mislabeled single-segment 206; single-range behavior unchanged.
- Tests now cover the reviewed edges (empty path, multi-range, root
workspace, HEAD+range, mid/early abort, unreadable file, explicit
unregister-on-dispose, private outside-target directories); module
coverage is back at 100/100/100/100.
- Agent Note bilingual facts and the ui-primitives/session-controller
README pairs are synced to the shipped behavior (pairs re-recorded).
Include the root merge ancestry from #3669. Preserve the already validated system-message child recording rather than restoring the root’s header-system representation. The resulting tree is unchanged from the preceding representation head.
A single-consumer pure helper does not earn its own file: the mapping
now lives beside the prose vocabulary it serves (same shape as the
deliverables file-mention logic), and its unit cases merged into the
component spec.
- /api/file module now exports only the SessionMediaReferences plugin
contribution; MIME and range policy helpers are module-private and
exercised entirely through the registered route.
- Replace the hand-rolled media extension table with mime-types (served
categories image/video/audio, excluding image/svg+xml) and the
hand-rolled Range parser with range-parser; keep the fail-closed
workspace containment policy.
- Spec rewritten as route-level behavior tests (12 cases) covering the
same branches; Agent Note facts updated in the same change.
Observe actual shared-preparation waiter admission and settle both callers before cleanup, preserving current V3 migration expectations. Adopt master’s cross-platform teardown regressions without changing budgets or Session data.
Observe actual shared-preparation waiter admission and settle both callers before cleanup, preserving current V3 migration expectations. Adopt master’s cross-platform teardown regressions without changing budgets or Session data.
A type imported by relative path from a module of the same package that
re-exports it from another package failed as a cross-package reference
without an explicit package import. The analyzer now resolves the relative
specifier, follows re-exports while the file stays inside the referencing
package, and applies the public-export check to the package specifier the
chain reaches. Relative paths into other packages and forwarded private
exports still fail.
Fixes#3525
Drop the byte-signature sniff duplicated from fs/tool-fs's read_image tool:
the cross-file duplication gate forbids the clone, and no shared owner
exists without widening the attachment package's public API. The
extension allowlist keeps non-media content out; corrupt image payloads
fail in the browser, not on the route. Agent Note facts updated in the
same change.
Preserve in-history request ordering and native V3 system heads while incorporating master’s human inbox controls. Regenerate only the current SDK child recording through its actual owner; retained V2 predecessor bytes match master.
Record current V3 output from master’s human inbox control scenario while retaining the V2 predecessor unchanged. No additional product behavior or validation change.
Refresh the current V3 child recording through the actual SDK scenario after master’s inbox-control change. Preserve the V2 predecessor inherited from master and retain system-message admission and current references.
Preserve the V3 PTC SDK assertion and enable the new human-steering runtime fixture while carrying master’s inbox lifecycle changes. Retain prior deterministic CI repairs and immutable predecessor ownership.
Preserve the V3 PTC scenario while enabling the current subagent human-steering fixture environment. All product changes are inherited from master; no V3 migration semantics are altered.
* feat(subagent): align continuable human inbox controls
* fix: repair rebase documentation and close test persistence
* docs: refresh rebased module dependency graph
Assistant prose that references a workspace-contained local media path
(e.g. ``) now renders through a same-origin
`GET|HEAD /api/file?path=` route instead of inert alt text.
- ui-primitives: MarkdownText gains a settled-only MarkdownPathImages
vocabulary gate (same posture as file mentions); no vocabulary means
byte-identical output.
- ui-chat: AssistantMarkdown supplies a page-stable rewrite vocabulary
for absolute POSIX paths (local-path-media.ts).
- session-controller: SessionMediaReferences plugin contribution mounts
the route on the authenticated connection.fetch channel; per-request
policy = workspace-root containment after realpath, regular file,
allowlisted media extension (images additionally signature-checked),
range/HEAD streaming, private no-store + nosniff, fail-closed statuses.
- Agent Note added (feature/2026-09-07-session-prose-local-media-display).
Closes#3662.
Preserve the V3 system-prompt projection while adopting master’s per-agent frozen-message provenance and frontend performance gates. Keep benchmark workload admission and diagnostics fixes intact, and retain both migration and first-open/reopen lifecycle guarantees.
Generated V0 logs put user surfaces before the first step and cannot migrate to a protected V3 system head without changing chronology. Author native current headers, open the first step before the empty system head and user, keep title sequence references aligned, and supply embedded assistant streams.
Give the preset child a current identified user message. Preserve all paging, geometry, preset assertions and committed historical fixtures. Validated both generators red/green through the source seed reader, one full build, all preset/stats/trajectory browser tests (11), and scoped type-aware lint.
The long-chat builder emitted current V3 with user/message before system/message, so seed restoration rejected its protected head before browser assertions ran. Open the first step and append the system head before the user surface; keep Session-assigned title and tool-result references.
Add an independent seed-reader regression for current version, protected head order, title/tool references and closed turns. Observed protected-head failure before the fix; focused regression and full configured type-aware lint pass. Existing browser scroll assertions are unchanged; full built owner runs follow.
Adopt master’s explicit personaPrefix/personaSuffix configuration in the in-history test harness. The assembly override and all admission assertions remain unchanged; this fixes the typed build integration rather than weakening the tests.
Keep prepared-route in-history append, clear-all, and normalization semantics while adopting named persona prefix/suffix and late environment facts from master. Resolve documentation against the derived system-message history and retain the existing strict migration behavior.
Preserve system/message persistence while adopting named persona prefix/suffix and environment facts after reusable instructions. Resolve the Web assertion against the logged system message, not retired header.system. Keep the existing protected V3 integration PR as the root publication path; no rules are bypassed.
Preserve valid native V3 system heads in resumed fixture seeds and inherit the scanner coverage repair. Regenerate only upper-layer owner-local expected output through the actual loop so fallback title follows header/context. All five affected expected cases pass refresh; no historical Session generation or assertion is weakened.
Keep the synthetic compressed historical fixture step-first without changing frozen generation data or shared fixtures. Pin its migrated empty system head and unchanged source bytes. Exercise malformed native system rows before and after recoverable damage through scanner and read/write handles. Remove the unreachable duplicate unsupported-format catch after current-row admission; preserve its owned rejection and generic error path.
Validation: original zstd regression red; admission-bypass negative control 3/3 red; five focused specs 314/314 pass with format.ts 100% statements/branches/functions/lines. Final admission cleanup 15/15 pass. Full build-backed lint found one matcher typing error; exact identifier oracle replaces it and focused type-aware lint passes.
Propagate the V3 parent and current master CI reliability fixes without changing in-history semantics, performance budgets, or recorded predecessor ownership. PR-owned CLI seed and source-coverage repairs follow in their lower owning layer.
Retain the protected system-message fixture representation while incorporating the latest master PowerShell, browser timezone, asynchronous subscription and Python-runtime CI fixes through the V3 parent. Existing parent V3 successor refreshes are preserved without rewriting predecessor bytes beyond upstream master.
Local build and pwsh one-shot refresh passed. Persistent PowerShell completes the model turn but times out during process exit on both this layer and the updated V3 parent; an exact-master control is running before attributing that failure. PR-owned seed and coverage repairs are isolated follow-ups.
Capture the inherited SDK PTC scenario under the upper loop lifecycle using the real current writer. The shared V3 dispatch vocabulary and protected system head remain unchanged; title follows admitted header/context as in the other upper recordings.
Regenerate current Python Session expectations through the real macOS arm64 SEA executable built from fdd44667b4b496093e765af57d3751dfb917d834. The native writer emits session/title after request/header and request/context. Update only five current V3 logs; result.json, minimal/model-visible.json, and minimal-in-history/prompt-history.json regenerate identically. All five V2 predecessors remain byte-identical to b2b300f9.
Build evidence: LEFTHOOK=0 pnpm install --frozen-lockfile --offline; pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets node24-macos-arm64 (full build, no skip-build): passed. file dist-exe/deepseek-harness-sdk-runtime-macos-arm64: Mach-O 64-bit executable arm64.
Smoke evidence: export PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=python/sdk/src:python/sdk-runtime/src; PY=/Users/cty/dsh-deploy/.agents/worktrees/sp-kv-sdk-evidence/tmp/py-sdk-venv/bin/python. For each scenario in sdk-snapshot sdk-restart sdk-minimal sdk-minimal-in-history, ran "$PY" scripts/smoke-python-runtime.py --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64 --scenario "$scenario" --update-snapshots, then the same command without --update-snapshots: all eight passed.
Helpers: PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=python/sdk/src:python/sdk-runtime/src uv run --offline --no-project --python /Users/cty/.local/share/uv/python/cpython-3.14.5-macos-aarch64-none/bin/python3 --with pytest --with "pydantic>=2.12,<3" python -m pytest python/sdk/tests/test_smoke_model.py -q: 28 passed. git diff --quiet b2b300f9 -- "scripts/snapshots/python-sdk-single-exe/**/*.v2.jsonl" and git diff --cached --check passed. No source, migration, test, or launcher changes.
Refresh both PowerShell native V3 cases with the installed PowerShell runtime so fallback title follows admitted request metadata consistently with the other upper-layer recordings. Refresh and independent replay both pass; historical inputs and output content remain unchanged.
Align the in-history ownership note with compact projection checkpoint version4. Both PRs use Session format3; the projection cache version is independent and invalidates scalar caches without introducing another persistence format.
Regenerate only native V3 Session/output oracles through the shipped headless and SDK profiles. These outputs retain system-message admission before users, bind request metadata before title fallback, and record real V3 delivery acknowledgments. Historical V0/V1/V2 replay inputs remain unchanged; pinned scenarios keep separate writer outputs.
The upper headless/SDK refresh covered109 cases; the only initial failure used macOS system Python3.9 for the PTC Python scenario, which passed with the declared CPython3.14 PATH. No assertion or runtime behavior was changed to accommodate it.
Forward the new headerless-window regression flag to replaceWindow instead of silently ignoring an extra test-helper argument. This makes the regression exercise a real incomplete history window and fixes the aggregate test typecheck without changing production behavior.
Give appended Chat system messages their own cards and suppress the following initial or same-step update header card. Preserve replacement/header ownership and conservative unknown surface ordering. Trajectory uses prompt-only presentation when no request header is loaded, never fabricated provider, model, or tools; prepend deduplicates against real request changes. Test-first regressions showed missing Chat cards and missing trajectory prompt data; 148 focused tests and affected client project typechecks pass.
The parent representation fix now correctly invalidates header-based caches with scalar state version3. The dependent in-history projection has a different compact live-surface schema and must not reuse that version. Advance it to4 and prove that a lower-layer version3 cache is discarded and refolded, alongside the existing version2 regression. No historical Session migration or compatibility mechanism is added.
Addresses ds-review-bot cache-version thread3921300994 across the stack. Validation:62 focused projection, adapter and admission tests passed; context.ts and breakdown-projection.ts each have100% exact-source coverage; paired README record updated.
Seed the request surface watermark from the attached session so a replacement before the first resumed request consolidates retained prompt versions. An unchanged resume still continues the series. Actual-loop replacement regression fails before the fix; 53 focused tests and the agent-loop project typecheck pass.
The normalized embedded stream in the skill seed contains no positive decode interval. Its rendered metrics correctly omit throughput rather than invent a rate. Refresh only the owning ARIA expectation after the full Web lane isolated this final mismatch; user/model/skill content and metric calculation remain unchanged.
Validation: owning refresh and subsequent read-only browser replay pass. The preceding full Web run had only this one expectation mismatch.
The in-operator narrows SessionEvent to events whose surfaceOp is required. Remove redundant undefined checks from both target Definitions; retain append-versus-replacement discrimination and shared prompt interpretation. This deletes dead conditions reported by the full type-aware CI lint gate without adding fallback behavior.
Validation: both Chat and Trajectory definition regression suites pass with the simplified predicates.
State the implemented empty-head reservation and messages-only summarization input in the feature note. Align the Chinese Chat contract with the shared surviving-surface interpretation rather than chronological predecessor selection. These localized corrections remove stale claims left by independently merged fixes without changing code or tests.
Validation: both bilingual pairs re-recorded and normal pairing hooks pass; the referenced runtime behavior is covered by the already executed empty-head, compaction and partial-window regressions.
Synchronous admission finalization moves the first request/header to seq10 before the deferred title. Read that actual header rather than seq11 request/context so the spill scenario exercises a large canonical request record. Keep the inherited dynamic-locator verification and independent file-content oracle; the test now requires a real successful read and SPILL_CANONICAL_OK rather than recording shell failure.
Validation: targeted built refresh and subsequent read-only replay both pass with complete spilled JSON equal to the live request/header and the session_event_search schema present.
The breakdown refactor correctly stopped consuming scalar shadow claims, but its old negative test was the only coverage of the pressure fold rejection path. Exercise the remaining real pressure consumer with mismatched start and end claims, preserving the runtime invariant rather than deleting or ignoring its guard.
Validation: Linux and Windows CI reported the two uncovered locations in surface-projection.ts. All118 token-meter tests pass and the exact pressure fold file now has100% statement, branch, function and line coverage.
Refresh advanced and restart expectations through the final native packaged runtime. Synchronous request finalization places deferred fallback-title events after header/context in parent and child logs; expected SDK notifications must reflect that actual lifecycle instead of forcing the old incidental scheduling order.
Validation: the advanced mismatch was reproduced against the built macOS ARM64 executable, both owning scenarios refreshed, then the complete keyless --scenario all passed, including the new in-history prompt evidence and independent shell/file effects. No SDK runtime or normalizer behavior changes in this commit.
Regenerate the source-owned service and persistence catalogs after prepared-route admission and clear semantics changed. Synchronize exact bilingual declaration blocks and replace the obsolete SDK evidence gap with links to the recorded TypeScript notifications and Python prompt history. Keep generated descriptions tied to the source instead of preserving the old previous-context decision rule.
Validation: both changed catalog/note pairs passed scoped pairing and exact type-equivalence/graph checks passed in the preceding isolated documentation repair. Complete documentation gates are rerun on the final integrated tree.
Request preparation now precedes durable user admission and finalization is synchronous. The title service intentionally defers fallback publication, so titles naturally follow request/header and request/context instead of landing in an incidental await gap. Refresh the actual headless, SDK, ACP and CLI recordings rather than adding a synthetic yield or changing title ownership to preserve obsolete ordering.
Keep every model response, user input, tool effect and identity relationship; review reordered title/header facts and remapped references. Current master also removes the base editor contribution, so shared schema pins follow the actual shipped profile. Historical generations remain unchanged: their test-only comparison orders title facts independently while preserving title payloads and references. Normalize only generated stream clocks in refreshed fixtures.
Validation: owning refresh runs passed current scenarios, CLI expected tier28/28, Web minimal/replay11/11 and targeted SDK in-history1/1. Read-only full built corpus passed127 cases with2 unavailable-PowerShell skips; its sole failure is the separately repaired historical invalid-citation negative control on the lower PR. Six retained historical comparison cases pass. No production behavior is changed in this commit.
Copy the current SessionEventMap prompt and route JSDoc into both subsystem references so type-equiv validates the prepared-route and clear-all semantics. Remove the Mermaid statement delimiter from the generated retry note while retaining the same lifecycle meaning; regenerate graph artifacts and confirm both bilingual pairs.
Validation: verify-type-equiv, verify-mermaid, verify-doc-graphs, scoped verify-translation-pairing, and git diff --check.
A durable system message may contain multiple text blocks or other content. The runtime-context helper recognizes only one text block; treating an unrecognized system message as empty allowed clearing and incapable-route normalization to leave its instructions active. Keep undefined text distinct from genuinely empty content so every noncanonical node is replaced or cleared instead of silently skipped. Runtime-context snapshot equality remains unchanged.
Regression: a multiblock head and nontext tail previously survived a clear request; the new source-level case fails before this fix. Both system-prompt projection and actual-loop admission suites pass (21 tests).
The system message content is readonly. Build a replacement fixture value for the multi-block extraction test instead of assigning into a readonly message property. Preserve the exact behavioral assertion without weakening production types or adding a cast that hides mutation.
Validation: aggregate client build exposed TS2540 at the mutation; all seven system prompt interpretation tests pass with immutable fixture construction.
The session projection API can return undefined when a unit is not registered. Assert the registered test fixture produced state before inspecting compact entries, rather than assuming a value that the current master type does not guarantee. This preserves the retained-state regression and adds no production fallback.
Validation: the aggregate build identified three TS2532 errors in this test; all context-breakdown projection cases pass after narrowing. Full build is rerun separately.
The meter regression deliberately rewrites the system node during request-error recovery. After the retry reconciliation fix, the next request must restore the captured admitted prompt rather than trust that temporary replacement. Keep the replacement stimulus and zero-delta metering assertions, but compare the retried prompt with the initial request so the two independently corrected components are tested together.
Validation: the combined regression run exposed this obsolete retry-text expectation while 945 other cases passed; all eight compaction-loop-repro cases pass with the admitted-prompt assertion.
Namespace prompt prefixes per invocation and compare appended updates against an identical warm request. Check exact pre-dispatch message prefixes and stopped responses; use the same latest prompt for rewritten-head control, without an artificial Updated prefix or assumed cache block size. This measures provider behavior, not loop capability selection.
Validation: serialize.spec.ts 54 passed; adapter.spec.ts 154 passed after offline dependency install resolved collection failures; focused e2e selector collected and skipped without standard key/model configuration; focused staged-config lint and git diff --check passed. No live provider result claimed.
Chronological lookup keeps B after compaction shadows it even though the model sees surviving head A and no system event is emitted. Share pure extraction and compact immutable surface interpretation through uiConversation; target-owned Definitions preserve historical cards. Track inherited surface positions in a map containing only surviving replacement endpoints. No full-log scans or prompt work on ordinary messages/chunks.
A partial window cannot infer the order of older unindexed endpoints: pre-window [head seq5, tail seq3], followed by replacements 3->6 and 5->7, must not report seq7 as effective. Withhold subsequent prompt text until prepend reconstructs the prefix; exact pure and both target regressions cover unavailable -> C. Drop shadowed replacement-index entries; repeated rewriting one node keeps one entry, while historical prefix maps remain immutable. Current replacement work is O(S+P), where S is surviving system nodes and P surviving replacement endpoints; history retains per-prefix copies, not an unbounded linked chain.
Coverage includes A->B->C, A->B->compaction->A without a system event in replay/live/partial windows, chained nonmonotonic replacements, empty tails, head rewrites, unknown ordering, inherited prompt withholding, and index pruning. Focused suites total 89 passing cases; rebuilt trajectory smoke 3 passed. Affected client types/lint/bundles, diff checks, and 33/33 doc-sync gates passed. README EN/ZH and owning note updated. Parent owns final GUI/recorded-session evidence; no server started.
Cover the missing SDK projections through shipped dsh profiles. TypeScript reuses the headless read-completion composition and records SDK notifications. Python all includes a focused minimal-profile variant with an ordinary prompt section updated after shell completion, unchanged tools, persisted/subscribed append events, and independently checked editor output.
Observed: focused TS refresh and built replay passed (1 test); Python sdk-minimal-in-history and sdk-minimal passed against apps/cli/lib/bin.js using uv; unchanged-prompt negative control failed as expected then restored scenario passed; corpus 3 tests, doc-sync 33 gates, and lint:contracts-ready passed. Host-only build initially left SDK built startup incomplete; build:lib:client completed the prerequisites. Native packaged/wheel and Windows validation remain pending parent integration.
The proposed and implemented 2026-09-02 feature triplets record the same decision. Keeping the proposal active presents obsolete node-0/config-change rules as live work even though PR3483 already shipped the capability. Delete the complete proposed EN/ZH/sidecar triplet rather than archive a proposal or retain a redundant rejection.
Read both complete bilingual notes before consolidation. The implemented owner already retains the motivation, all five proposal alternatives, cache cost and proxy/model risks, capability validation, and current snapshot/e2e evidence. Preserve the still-valid unchanged-prompt/resume lifecycle verification and both SDK typed appended-event expected-output requirement in its Testing paragraph. Explicitly identify missing SDK appended-event coverage instead of treating the headless snapshot as SDK evidence. Do not revive the obsolete plan-mode fixture or absolute cache-hit inequality; the implemented note records the shipped fixture and comparative e2e assertion.
Only the implemented Testing paragraph and pairing record change. Runtime, accounting, UI, and frozen archived notes are untouched. Active inbound references already target the implemented owner, so no link retarget is necessary. This commit addresses only the reviewed lifecycle/consolidation finding.
Focused evidence (all exit 0): pnpm install --frozen-lockfile; pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md (1 record written); pnpm run verify-translation-pairing .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md (1 named pair consistent); pnpm run verify-md-links (1381 files, all links/fragments resolve); pnpm run verify-agent-note-format (246 notes conform); git diff --check; git diff --cached --check. Full combined doc-sync remains with the parent integration branch after runtime fixes; no full gates, push, rebase, or hook bypass.
An in-history route can append A, B, and C without another request/header. The system-message Definition read only the prior real header, so C incorrectly reported A as its previous prompt. Select the newer real or synthetic header through the existing indexed predecessor reader; preserve target-owned Definitions and avoid any log scan.
Add replay and per-event live A -> B -> C regressions. Both fail before the fix with previous=A and pass with previous=B. All 13 trajectory Definition tests pass; focused staged lint, bilingual pairing, and diff checks pass. Update the trajectory README pair and owning design note.
Scalar shadow prices cannot restore an earlier effective prompt when compaction removes the newest system node: subtracting that price from messages can permanently make the published bucket negative. Reuse the positional measurement planner over compact current surface entries and choose the last nonempty system by surface order, not event sequence or provenance citations.
Explicitly trade the scalar checkpoint for O(current retained surface) state, dropping shadowed entries and message bodies. Version 3 invalidates old scalar checkpoints; pure transitions preserve wire identity for same-price rewrites. Cover newest/middle removal, reversed sequence ranges, extra sources, empty fallback nodes, replay and late registration, cache version rejection, and route-independent heuristic totals. Document the bounded-state exception and rejected prompt-only ancestry approach in both languages.
Use the same logged per-node normalization for an empty rendering as for incapable routes and broken request series: empty each active later system node, then empty the first node. Clearing only the latest node exposes stale earlier instructions, including converter-demoted user text. Dormant empty tails neither override effective text nor produce repeated replacements.
Keep the cleared structural head and ordinary restoration semantics: a capable continuing series may append new nonempty instructions, while incapable or broken-series requests refill the head. No initial empty-head creation change, deletion event, or rollback machinery.
Tests: capable/plain clearing after three active prompt versions fails before the fix and passes afterward; repeated clear and seeded resume remain empty without duplicate system events, restoration includes only new instructions, pi-ai conversion has no stale prompt, and source-event replay reconstructs every request. Final 395 loop tests pass with exact agent.ts/runtime-context.ts coverage at 100% all metrics; source tsc and focused lint pass. Fix new test discriminant narrowing discovered by focused compiler audit. Update EN/ZH consumers and owning rationale, seven pairs verified; parent owns broad gates and generated catalog/SDK/GUI updates.
Every same-step attempt resolves its bound route and reconciles the accepted rendered prompt against the current surface before deriving history. Preserve assembly, pre-step admission and entered users exactly once; compaction retries must not resurrect an older surviving prompt.
Normalize a broken request series to the current head plus dormant empty later system nodes, even if later versions survive or the effective text is unchanged. This removes the later-survivor exception without a delete operation and avoids appending instructions after already-admitted users on retry. Capture surface generation after reconciliation so subsequent ordinary retries do not emit phantom series headers.
Tests: both compaction cases fail before the fix (stale head, or stale later survivor), then pass with real loop/MockAdapter, pi-ai conversion and source-event reconstruction. Assert two retries keep one assembly/pre-step/user admission, preserve rendered text across section changes, and log one series boundary. Add unchanged-text explicit/tool series regressions. 391 loop tests with exact agent.ts/runtime-context.ts coverage pass at 100%; final admission spec has 11 passing cases. Source tsc and focused lint pass. Update EN/ZH implementation and owning rationale plus lifecycle retry documentation.
Resolve agent/request and prepareCall inside the accepted open step before committing system and user input. Preserve prompt-assembly model selection, bind capability and dispatch to the same prepared adapter, then log the envelope and derive the request synchronously. Preparation cancellation leaves a balanced empty step with no admitted input.
Normalize incapable-route retained prompt versions with logged per-node empty replacements and a current head, including unchanged rendering and resumed history. Dormant empty tails do not override the effective prompt. No new event or delete operation.
Tests: 389 agent-loop cases pass with exact agent.ts/runtime-context.ts coverage at 100% for all metrics; tsc -b packages/core/agent-loop/tsconfig.json passes. Seven actual-loop MockAdapter cases exercise pi-ai conversion, source-event reconstruction, both route transitions, resume, cancellation barriers, and bound selection. Stale-capability negative control fails all three transition regressions. Update EN/ZH request visibility, lifecycle generator, and owning rationale. Broad catalogs, recorded SDK/GUI and aggregate gates remain parent-owned.
Consolidate the existing in-history capability, loop, UI and artifacts into one baseline. Preserve the reviewed tree so each independently verified correction has a subsequent rationale-rich commit.
Begin each synthetic step before its user surface message so the structural V2-to-V3 migration can reserve its protected system head without reordering historical events. Keep the workload size, frame partition, original chunk provenance, and every timing/heap budget unchanged.
Add a small real persistence migration/publication/reopen prerequisite that preserves all user and assistant content and original V0 bytes. Both this regression and bounded stderr-headline regression fail before the fix and pass afterward. Worker errors retain a bounded head and tail instead of hiding migration refusal behind a generic stack tail.
Validation: full Session-opening benchmark16/16 passed, including all six128MB completion cases; focused full type-aware lint passed. First Agent resume median178.7ms and reopen27.4ms are below unchanged450/100ms budgets.
Refresh both current-writer successors with the real PowerShell runtime and existing headless composition. Align their owned prompt/schema pins with installed tools; retain every historical Session generation and the recorded model/tool behavior.
Refresh the inherited SDK PTC scenario through the native V3 writer so its protocol oracle includes the protected system head. The generated Session remains unchanged and independent replay passes.
Compose the shared release branch PTC rename with the structural system-head migration in the existing V2-to-V3 stage. Audit source payloads and remap local coordinates before renaming dispatch vocabulary and owned plugin attribution; retain original message identities and generation-qualified references. Native V3 validation combines the same system and PTC lifecycle semantics through a private released-validation view.
Reuse the V3 row-admission owner in current JSONL scanning before recoverable-tail suppression, so malformed rows cannot hide retired required PTC tags or header.system. The new corruption-prefix regressions and all173 existing JSONL scanner tests pass. Combined migration/catalog/persistence suite:300 tests; executable V3 migration source has100% coverage. No released generation, prior migration implementation, shared release branch, benchmark or CI workflow is modified by this integration.
Keep the generated configuration source pointers aligned with the smaller replay module. No configuration semantics or public runtime behavior changes.
The implemented owner at .agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md retains the decision, alternatives, consequences, and verification. Remove only the reintroduced pre-promotion triplet under the lifecycle rules in .agents/notes/README.md; the dependent feature proposal already links to the implemented owner. No proposal archival or body rewrite is needed.
Treat Vitest asymmetric matchers as unknown expected values and spell void assertion callbacks explicitly. This preserves the migration identity and refusal checks under the full type-aware lint configuration without relaxing validation or suppressing a rule.
Regenerate advanced and restart expectations through the shipped macOS arm64 SEA runtime built from 0521815d45. Real delivery-accepted events now record sessionFormatVersion 3 in the five V3 logs and advanced SDK notifications. Restart result.json and requests.json regenerate identically. Preserve all five V2 predecessors byte-for-byte from b2b300f9; no production, migration, launcher, or test changes.
Evidence (macOS arm64): pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets node24-macos-arm64 passed, including the full pnpm build. file dist-exe/deepseek-harness-sdk-runtime-macos-arm64 reports Mach-O 64-bit executable arm64.
For each SCENARIO=sdk-snapshot,sdk-restart: PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=python/sdk/src:python/sdk-runtime/src /Users/cty/dsh-deploy/.agents/worktrees/sp-kv-sdk-evidence/tmp/py-sdk-venv/bin/python scripts/smoke-python-runtime.py --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64 --scenario SCENARIO --update-snapshots passed; the same two commands without --update-snapshots passed.
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=python/sdk/src:python/sdk-runtime/src uv run --no-project --python /Users/cty/.local/share/uv/python/cpython-3.14.5-macos-aarch64-none/bin/python3 --with pytest --with pydantic>=2.12,<3 python -m pytest python/sdk/tests/test_smoke_model.py -q: 28 passed. Earlier helper invocations lacked pytest or SDK imports; corrected environment passed without source changes. git diff --quiet b2b300f9 -- scripts/snapshots/python-sdk-single-exe/**/*.v2.jsonl and git diff --cached --check passed.
Require a string call identity before constructing the private repair-validation prefix; do not stringify arbitrary durable JSON. Remove a redundant narrowed version comparison and use explicit void test callbacks.
Verified full type-aware package Oxlint (90 rules), all 71 focused cases with 100% exact V3 statements/branches/functions/lines, and host TypeScript build. No coverage ignores or broad lint exceptions.
Exercise malformed source envelopes, feedback metadata, file attachments, native system ownership, protected head replacement, started versus repaired tools, dangling coordinate mappings and successful compaction ranges. The focused package suite now has 71 cases with exact V3 source coverage at 100% statements, branches, functions and lines.
Remove unreachable rechecks after owned payload validation instead of adding artificial tests or coverage ignores. Unseeded inherited markers already fail at arrival; message arrays and IDs are validated before observation. Bind the unchanged frozen decoder run callback without an unreachable wrapper.
Construct supported historical inputs with the first surface inside step/start instead of weakening publication, singleflight, packed-row, and read-handoff assertions into refusal cases. Account for the inserted empty system head and shifted event sequence when V2 rows become V3; preserve exact native V3 expectations and original source bytes.
Keep structural system-message V3 recordings when taking the release branch spacing fix, rather than reintroducing retired header.system snapshots. Refresh native SDK delivery evidence and the retained multi-turn writer oracle; historical V2 inputs remain byte-for-byte inherited from the release base. Document generation-aware fixture storage checks so current child invariants stay enforced without rewriting retired historical roles.
Retained V0/V1 fixtures remain replay inputs while writer.expected JSONL records the actual current V3 output. Record these through the shipped headless scenarios rather than reverse-normalizing system messages back into historical headers. Preserve generation-qualified reference provenance, and correct the unreleased PowerShell seed to start its step and protected system head before user surface input.
Validation: the structural migration, real persistence, corpus and snapshot-support gate passed616 tests with one intentional skip; all91 headless refresh cases pass after the audited assistant-attempt source handling. Every predecessor path remains restored to the shared V3 base, and generated changes are confined to V3/output oracles and current tool schema pins.
Current V3 promotion must preserve chronology, so historical user surfaces before the first step are unsupported. Assert refusal for read and write opens, preserve source bytes and identity, and forbid intermediate or current publication. Frozen codec validation remains in its existing owner tests; V2 no-downgrade coverage lives in v2-system-migration.spec.ts.
V2 assistant attempts have no V0 semantic dispatch case: validate their exact V2 members and positive step coordinates locally while preserving the embedded stream. Explicitly classify agent-message relay attribution and file attachment metadata; both preserve foreign identities and non-Session byte counts, and reject unknown members.
Native V3 request headers retain current extension fields while rejecting retired header.system, independently of the closed V2 migration inventory. Add direct and multihop attempt regressions, exact source/file negative cases, and native header roundtrip coverage. All 41 focused tests and host tsc pass; scoped lint and bilingual pairing pass.
The structural V2-to-V3 edge reuses the frozen V0 payload and relationship helpers through their public package exports. Record the explicit workspace dependency so clean installs resolve the same validation code without editing a released migration package.
Stream an empty protected head after the first step and replace it before changed or cleared request headers, without moving source events. Remap only audited local sequence references, derive inherited cuts after upstream cardinality changes, and preserve generation-qualified captures and message identities.
Refuse pre-step surfaces and out-of-step prompt changes rather than invent lifecycle events. Reject unclassified migration payloads and detect deterministic generated-ID collisions in either source order. Native V3 accepts empty and in-history system messages, protects only the head, and reuses frozen relationships through a private nonescaping projection; historical repair IDs remain opaque.
Keep released codecs untouched and distinguish migration admission from native extension admission. V3 structural payload preflight cannot disappear behind recoverable row corruption. Validation: 35 focused tests, host TypeScript build, focused Oxlint, documentation quick gates and paired README recording. Parent owns installed-core/catalog/persistence integration and lockfile propagation.
Record order-preserving structural conversion and strict refusal instead of an identity edge; distinguish native writer layout and local references from historical delivery facts. Keep released generations frozen and one evolving unreleased V3 target, with canonical envelopes composing afterward.
Restore all 147 historical canonical fixtures changed by the lower branch to exact shared base 220ff708e3628a9be46d9747d09af6d8cd742f0d bytes (145 V2 and two V0). All 180 committed V0/V1/V2 canonical files now have zero diff against that base, including unchanged Python, test-support and preview sources.
Preserve corrected lower writer bodies in 146 V3 successors across 119 owner directories: replace 135 unreleased V3 counterparts and add 11 siblings. Change only each copied Session header version; preserve all body bytes, raw model streams, inline images, IDs, references and historical delivery generation markers. Remove two unshipped V2 additions after creating their V3 sibling: record-suite/rec-pin and empty-response-retry-current.
Keep six explicitly historical snapshot owners pinned with no V3 sibling. Restore the two pre-step V0 record child sources without synthesizing current output. Fix seven embedded Session header versions in exactly five test-support behavior owners whose output filenames already select V3. Leave protocol expectations, shared references, sidecars, consumer scripts and production migration/normalization code untouched.
Validation: purpose-built temporary Python inventory and verifier checked 337 canonical headers, 3714 successor JSON rows, message roles, contiguous child roles, exact successor bodies, all 180 historical base bytes and zero git diff, noncanonical JSONL immutability, six retained owners and six unchanged preview fixtures. git diff --cached --check passed. Temporary generators removed. Strict migration, native refresh and replay remain pending parent integration; unsupported pre-step sources are reported separately.
Use a typed expected failure object and the current toThrow matcher in the new leading-system-image regression. This preserves the rejection test while avoiding deprecated matcher and unsafe-any diagnostics from the full type-aware lint gate.
Address ds-review-bot thread 3921300994 (PRRT_kwDOS3Pfcs6eyLsu). contextBreakdown now folds system/message instead of request/header.system, so version-2 checkpoints have different semantics even though their numeric fields still pass the current schema. Bump the lower projection to stateVersion 3 so cache-only reads omit old values and restore replays the full log; derived caches are not covered by the Session-log persistence compatibility waiver. No migration or fallback is added.
Regression seeds a schema-valid v2 row at the current watermark with stale system/message prices. Before the bump, cache views returned it, restoreFloor selected seq 2 rather than 0, and restore retained system=0/message=17 instead of system=8/message=9. After the bump, the 13-test owning suite passes and exact src/breakdown-projection.ts coverage is 100% statements, branches, functions, and lines; refreshed rows equal a fresh fold at version 3. Existing token-meter README prose documents the current system/message fold without a version literal, so it needs no edit.
Propagation requirement: the upper layer already uses version 3 for a different compact cache representation. Advance that layer to version 4 when propagating this fix to avoid assigning one version to two schemas.
Address ds-review-bot thread 3921300999 (PRRT_kwDOS3Pfcs6eyLsz). The synchronous conversion removed a leading system message before its image check, silently discarding image-only, mixed, and nested image content. Reuse assertSupportedImageRoles on the unsplit history, matching the image-aware path without changing text prompt precedence or user-image storage requirements.
Regression: all three leading-system image cases failed against 8082f4a950 (expected rejection, received a context). The focused context suite now passes 20 tests, including both conversion paths, no attachment reads on rejection, empty/text leading prompts, later systems, and explicit options.system precedence. Exact src/context.ts coverage is 100% statements, branches, functions, and lines. Updated the paired README limitation and synchronous JSDoc.
These two current fixtures already contain system/message, so searching
only for stale request/header.system misses their persisted replay drift.
Both omit the shipped standard agentPreset. Plan exit must replace the
first system message (sequence 10), not append a second system prompt.
The Goal fixture's first get_goal result must reference its own tool call
at sequence 18 rather than the preceding bash call at sequence 16.
Refresh both owning browser scenarios. Retain the writer's canonical
embedded Assistant stream packing and refreshed timing where repacking
requires it. Expanded stream chunks, all 44 user/model/tool messages and
calls, and the deliberate shuf command failure remain unchanged. The
plan title remains before request/header on this lower branch. No UI,
runtime, normalizer, test-driver, or frozen v0/v1 files change.
Evidence at lower d29574fed9, reusing the dedicated tree's own prior build
(the intervening subagent spacing edits do not affect these owners):
- DSH_SNAPSHOT=replay pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/{plan-review,goal-multi-turn-actions}.e2e.ts: before refresh, both owners failed persisted-session comparisons.
- DSH_SNAPSHOT=refresh pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/{plan-review,goal-multi-turn-actions}.e2e.ts: 2 files passed, 4 tests passed, 1 record-only test skipped (10.24s).
- DSH_SNAPSHOT=replay pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/{plan-review,goal-multi-turn-actions}.e2e.ts: 2 files passed, 4 tests passed, 1 record-only test skipped (10.24s).
- JSON payload comparison: all 44 messages/calls and expanded chunk content
across all 14 Assistant streams preserved; stream timing is excluded.
- git diff --check passed; only the two current session.v2.jsonl files changed.
Require exactly two System prompt controls for the unchanged-header resume fixture, and pin the repeated prompt in both collapsed and expanded subagent history. Keep all branch eligibility, disabled action, cancellation, disclosure, and inventory assertions intact.
Refresh stale July 25 clocks only where canonical fixture createdAt=0 already selects the seedSession near-now anchor. Preserve nonzero historical timestamps and the existing normalizer: do not conceal calendar differences globally. Update navigation read paths to the recorded workspace subdirectory and omit decode throughput for the zero-duration Bash cancellation stream.
Evidence: own frozen install and full build; baseline eight-file replay 10 failed, 37 passed, 3 record skips plus stale question teardown; six-file ARIA refresh 36 passed, 2 record skips; final read-only eight-file replay 47 passed, 3 record skips after separately authored Web fixture prerequisite eaa5277d82. Baseline exact count rejected 2 versus 1 and old date/prompt goldens rejected actual output. No Session generation, helper, runtime, or normalization edits in this commit. Live/question source mismatch is repaired solely by the prerequisite; their passing ARIA and skill goldens stay unchanged.
Fourteen current Web v2 fixtures still store the system prompt in
request/header.system and omit system/message. The built Cordis owner
passes its five behavior assertions but fails persisted-session replay.
Refresh thirteen owning recorded scenarios and update the authored pwsh
seed input to carry the same system text as a message event.
Keep the lower branch's title-before-header order. Preserve all 90
user/model/tool messages and all 35 recorded Assistant streams, including
timing. Only the system event, header field removal, message identifiers,
and their sequence references differ. Frozen v0/v1, UI goldens, runtime,
and normalizers remain unchanged.
Evidence on macOS, base 8d66f7c917:
- pnpm install --frozen-lockfile && pnpm run build: passed.
- DSH_SNAPSHOT=replay pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/cordis-tool-round.e2e.ts: before repair, persisted replay failed after 5 behavior tests passed.
- DSH_SNAPSHOT=refresh pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/{approval-composer,cordis-tool-round,feedback-command,file-upload-round,replay-round-trip,lifecycle-chrome,live-interactions,permission-policy-context,ptc-round,question-composer,steering,turn-tail-actions,web-search-round}.e2e.ts: 13 files passed; 71 tests passed, 2 record-only tests skipped.
- PATH="/tmp/pwsh:$PATH" DSH_SNAPSHOT=refresh pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/pwsh-terminal.e2e.ts: 2 tests passed with existing PowerShell 7.4.6.
- PATH="/tmp/pwsh:$PATH" DSH_SNAPSHOT=replay pnpm exec vitest run --config vitest.web.config.ts apps/web/tests/{approval-composer,cordis-tool-round,feedback-command,file-upload-round,replay-round-trip,lifecycle-chrome,live-interactions,permission-policy-context,ptc-round,pwsh-terminal,question-composer,steering,turn-tail-actions,web-search-round}.e2e.ts: 14 files passed; 73 tests passed, 2 record-only tests skipped (102.62s).
- Exact base/current JSON comparison: all 14 files equal base plus the system event, header.system removal and deterministic ID/sequence remapping.
- No current Web v2 request/header.system remains; git diff --check passed.
Give JSON.parse results in the unexpected-header regression their actual record type before searching by event tag. Preserve the forbidden-field oracle while satisfying the full type-aware lint rules; do not weaken lint or production parsing.
Validation: exact-head Linux artifact CI reported unsafe-return and unsafe-member-access in this regression. The owning replay suite passes after the local typed result correction.
Replace the replay's fixed temporary-root find command with the existing fromRequest capture of the preceding session_event_read result. Quote the exact returned file and use portable grep -Fq/printf checks; emit SPILL_CANONICAL_OK only after request/header and session_event_search are present. Preserve the owning layer's seq11 request/header target.
Before cleanup or fixture refresh, require both successful tool results and the exact verification marker, read the returned spill file independently, and compare its complete JSON to the live request/header event. This prevents accepting a failed verification transcript or an unrelated/incomplete spill after event ordering changes. No runtime or error-text normalization changes.
Evidence: nonexistent recorded-root negative control fails at the marker assertion (bash exit 2 still carries isError:false). Focused keyless refresh, read-only source replay, built-profile replay, corpus ownership guard, and focused driver lint pass. Host/client library builds pass. Portable POSIX command reviewed; Linux CI remains the platform confirmation.
A resume header starts a visible request series even when the system node text is unchanged. Include resume in the request-prompt visibility predicate so full history and an older-page prepend retain the restart card. Keep startsSeries handling and existing regression assertions unchanged; align the local JSDoc and README pair.
Carry the newly merged current-writer spill recording through the representation change on its owning PR. Add the system surface message and remove header.system, while retaining the representation layer title-before-request order and every captured spill/source fact. The dependent admission PR separately records its changed title scheduling.
Validation: JSON records parse; the same recorded scenario was refreshed through the built integrated loop. Only layer-specific title order differs, and both variants are replayed before publication.
The source fixture now contains the system surface node, so a literal sequence13 no longer names its captured tail. Record the source watermark before saveText mutates the session and assert that exact independent observation. This continues proving the spill uses the pre-mutation capture without coupling the test to unrelated fixture event counts.
Validation: the newly merged upstream regression failed with expected13 versus actual14; the session-reference suite passes with the captured watermark assertion.
The historical-comparison negative control replaces title.messageSeqs with [0], which cites a non-human event. The current Session validator correctly rejects this before normalization; comparing the malformed result for inequality made the otherwise passing built snapshot lane fail.
Assert the specific earlier-human-message validation error for that malformed control. Keep the valid prompt and model difference comparisons, every citation field, and all production restoration and normalization logic unchanged.
Validation: full DSH_EXAMPLE_MODE=lib DSH_SNAPSHOT=replay pnpm exec vitest run --config vitest.snapshot.config.ts reproduced the lone negative-control failure, then passed all 6 files and 126 tests after repair (2 existing PowerShell availability skips). PATH used the existing uv Python 3.14.5 bin; no software installed.
The committed current rec-pin fixture held only request/header, so the corpus restore gate rejected it outside an open turn. Its owning scripted behavior already emits turn/start, step/start and the system surface message.
Regenerate the selected current fixture with ACP_SNAPSHOT_SPEC_BOOTSTRAP=1 and the focused rec-pin record test. Keep all retired parent and child generations unchanged. No parser, normalizer or production behavior changes.
Validation: the corpus restore test reproduced the missing-turn failure; documented owner bootstrap passed; pnpm exec vitest run packages/test-support/llm-replay/tests/session-format-corpus.spec.ts packages/test-support/session-snapshot/tests/suite.spec.ts passed (141 tests, one intentional skip).
The current Session relationship validator correctly rejects a second step/start while the previous step remains open. Both replay and refresh of the synthetic pin-turn scenario failed after the stricter master validation landed.
Add the missing step/end to the owning scripted behavior and resequence its later events. Regenerate only the current pin-turn fixture through defineAcpSnapshotSuite refresh mode. Preserve both system messages, replacement provenance, and all three request headers; do not relax migration validation or normalization.
Validation: reproduced both pin-turn failures with the focused suite filter; owning refresh passed; pnpm exec vitest run packages/test-support/session-snapshot/tests/suite.spec.ts passed (140 tests, one intentional record skip). Full build and doc-sync also passed before this fixture-only repair.
Retain installLlmReplay, Config validation and apply when resolving the streaming migration API change. The representation layer requires no replay plugin source changes, so restore its complete current-master implementation rather than a truncated conflict fragment. Capability additions remain exclusively in the dependent feature layer.
Validation: the restored source is byte-identical to origin/master and the replay unit suite passes. The initial normal commit hook failed because this detached worktree had no dependency links; pnpm install --frozen-lockfile installs them before retrying the unchanged hook, without bypass.
The default and retry header pins were retained pre-system-node logs, so preserving their historical header.system correctly exposed stale expectations across headless and shared SDK compositions. Move the default pin to tool-call-turn and add a current retry companion; retain every committed predecessor and keep readable sidecar ownership on text-turn.
Replace the historical blanket body-comparison skip with a headless-only semantic projection: current system nodes contribute historical header.system and event citations follow retained positions. Full normalized logs still compare, current-writer logs stay unadapted, and independent prompt/header checks remain strict. Negative controls preserve prompt, model and citation differences. Refresh only pi-ai metadata and Web minimal-preset current artifacts; no SDK outputs, Python fixtures or package oracles changed.
Validation: 18 focused headless/SDK/corpus tests; three pin, sidecar and historical mutation controls; Web minimal-preset persisted replay; focused oxlint; typecheck and client/Web builds. doc-sync:31 passed,2 failed on pre-existing token-meter/Cordis and config catalog freshness; no broad catalog rewrite. Full suite, browser interaction and platform matrix not rerun.
Regenerate source-owned catalogs after the oracle simplification and usage-anchor correction. The configuration catalog retains the same declared options while its source location follows removed compatibility stripping; the token-meter service docs now state that the usage anchor includes all admitted request inputs. Keep bilingual generated declarations aligned instead of retaining stale copied contracts.
Validation: pnpm run doc-sync identified only cordis catalog and config catalog freshness failures (31 other gates passed); pnpm run gen-cordis-catalog and pnpm run gen-config-catalog regenerated their owners, and the config pair was updated and recorded. Full layer documentation validation follows integration.
Cause: SystemPromptProjection skipped the first empty rendered prompt. The initial admitted user then occupied surface node zero, so a later nonempty prompt appended behind user history. Routes without in-history system support lost the leading system role; pi-ai demotes a non-leading system message to user content.
Fix: append the initial system node even when its content is empty. The existing loop commit order reserves node zero before admitted user messages; later prompt text replaces that node. Empty content still derives to no wire message. Keep retained-node replacement, clearing, multi-system handling, and pi-ai conversion unchanged; this addresses only the reviewed PR3476 initial-empty finding, not PR3483.
Tests: added initial-empty projection and two-turn loop regressions for empty wire output, reserved surface head, later leading system role, replacement intent, and series header. Negative control failed before the source fix. Focused projection/runtime-context/loop/request-reconstruction/session-surface/pi-ai-context suites passed 176 tests; exact runtime-context.ts coverage is 100% statements, branches, functions, and lines. test:docs passed all 15 gates. Updated README EN/ZH, architecture map and owning architecture note; recorded all three translation pairs. Broad doc-sync/lint stopped at parent request for combined-layer validation. No normalize.ts conflict-comment edit.
The representation PR left child and restart recordings in the old header-system representation while the packaged writer emits system/message before entered user messages. Restart result expectations also retained standalone assistant/chunk notifications after the writer moved stream records into assistant/message. These are stale expected artifacts, not fields to erase in normalization.
Regenerate the owning advanced and restart scenarios through the native macOS ARM64 packaged dsh runtime. Retain messages, tool effects, typed feedback and packed streams; update only the missing system nodes and their sequence references, and remove obsolete standalone chunk notifications. The advanced parent result and parent session already match the writer after the rebase, so this commit changes only two child logs and the restart result/logs.
Validation: pnpm run build; pnpm exec tsx scripts/build-exe-for-python-sdk.ts --skip-build --targets=node24-macos-arm64; uv run --project python/sdk python scripts/smoke-python-runtime.py --scenario sdk-snapshot --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64 and the equivalent sdk-restart command each reproduced the mismatch, then passed with --update-snapshots and again without it. Both read-only reruns pass. Other native targets remain covered by exact-head CI.
The rec-child fake-agent behavior used an 11-digit UUID tail for both copies of its shared system message. Supply the missing digit in both places without changing their identity relationship. This semantic correction is separate from the preceding five-file formatting-only change.
Add a focused owner-fixture assertion requiring two matching complete v4 UUIDs. It fails on the original short tail and passes after repair. Evidence: pnpm exec vitest run packages/test-support/session-snapshot/tests/suite.spec.ts: 140 passing, one intentional record-mode skip, including replay/record/refresh and UUID validation. No expected-output refresh or normalization change is needed.
Compact each logged event onto one line in exactly five owner-local fake-agent behavior fixtures: record-suite rec-child/rec-pin and suite pin-turn/plain-turn/shared-pin. Keep wrapper structure readable so event sequencing and payload changes remain reviewable without hundreds of formatting-only lines.
Evidence: node deepStrictEqual compares parsed working-tree JSON against HEAD for all five changed files, with exactly five paths asserted; all semantic values and ordering are identical. git diff --check passes. The malformed rec-child UUID is deliberately retained here for a separate semantic repair commit. The unchanged fixture behavior passed the preceding suite.spec.ts run (139 passing, one intentional skip). No snapshot refresh was run.
Delete the leftover diff3 parent marker and both copies of the obsolete request-header prompt JSDoc. Keep only the current system/message tokenization contract. This is a local comment-only finding; neither oracle behavior nor fixture content changes.
Evidence: git diff --check is clean; focused normalize.spec.ts scrubSystemPrompts test passes (1 selected, 64 skipped). The earlier header-preservation commit intentionally retained this marker so the findings stay independent.
sessionFixtures already selects the highest generation independently for each parent or child role. Filtering its output to files[0] silently exempted every child from prompt and schema fixed points and prompt-before-request ordering. Merge those assertions into the per-role loop and expose the actual checker for focused negative controls without mocking Vitest registration.
The three selected-child controls reject missing system/message, raw prompt text, and raw tool schemas; each resolved incorrectly with the parent-only filter. A positive mixed-generation case proves retained predecessors remain unselected. Correct the two versionless record-suite child fixtures that the restored enforcement exposes, including the retired-child copy used by recording tests. No released historical generation or broad recording is rewritten.
Evidence: pnpm exec vitest run packages/test-support/session-snapshot/tests/storage-policy.spec.ts packages/test-support/session-snapshot/tests/suite.spec.ts --coverage --coverage.include=packages/test-support/session-snapshot/src/suite.ts: 143 pass, one intentional record-mode skip, suite.ts 100% statements/branches/functions/lines. Update and re-record the session-snapshot README EN/ZH pair.
The loop appends step/start before system/message and the entered user
messages. Capturing nodes at step/start therefore omits inputs already
included in the provider's successful usage, then adds those inputs back
as a positive surface delta. Prompt replacement can also incorrectly add
or subtract the difference from the prior prompt on a completed call.
Snapshot the current priced surface immediately before assistant/message
commits. Keep provider output separate from the durable assistant node so
listener rewrites retain their signed delta. The invariant is zero delta
immediately after an unchanged successful output: provider usage already
includes every admitted prompt input. Later appends/replacements still
produce signed deltas, and low or absent usage keeps heuristic fallback.
Delete stepStart.nodes rather than adding prompt-specific corrections or
another request snapshot: the existing transactional surface fold already
contains the successful request inputs, including replacements made during
same-step retry recovery. Keep turn/step state and all overlap, mismatch,
and late-assistant lifecycle validation. Retry attempts are log-only and
request middleware changes configuration; injected messages remain queued
until admission. No loop, event format, projection, or retry policy changes.
Exercise the real loop with reported usage and initial, growing, shrinking,
and empty prompts; same-step failed attempt plus retry prompt replacement;
request middleware; eager observation and fresh seeded replay. The two
regressions fail before the fix with spurious deltas of +48 and +18 tokens.
Retain existing durable-output rewrite, route repricing, missing/low usage,
transactional failure, and lifecycle tests. Update README EN/ZH and the
existing system-prompt surface-node Agent Note, including pairing records.
Validation (dedicated worktree, no full unit suite):
- pnpm exec vitest run packages/llm/token-meter/tests packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts --coverage --coverage.include='packages/llm/token-meter/src/index.ts'
118 passed; exact changed runtime file 100% statements/branches/functions/lines.
- pnpm exec vitest run packages/core/agent-loop/tests/request-reconstruction.spec.ts packages/compaction/compaction-basic/tests/compaction-basic.spec.ts packages/compaction/compaction-basic/tests/loader-composition.spec.ts
118 passed, including retry reconstruction and real Loader composition.
- pnpm run doc-sync: 33 gates passed.
- pnpm run test:docs: 15 gates passed.
- pnpm run lint: passed, 0 warnings/errors.
- git diff --cached --check: passed.
Baseline normalize.ts comment conflict marker is intentionally untouched.
The session already derives the protected system head as a Message, and both adapters accept leading system history. Passing it through a separate SummarizationInput.system string unnecessarily flattens that value and rebuilds the same wire message in the adapter. Prepend the derived head to messages and remove textContent, the separate field, and GenerateOptions.system plumbing from the summarizer.
Keep range selection, shadowed seq accounting, session head protection, routed tools, image references, target policy, and the model-visible compaction instruction unchanged. Empty-content heads still derive to null and contribute no request message, but their surface node remains protected. Update subclass consumers/tests, EN/ZH package and subsystem prose, and the existing system-prompt surface owning note with refreshed pairing records.
Evidence: pnpm exec vitest run packages/compaction/compaction-basic/tests packages/llm/llm-deepseek/tests/serialize.spec.ts packages/llm/llm-pi-ai/tests/context.spec.ts --coverage --coverage.include='packages/compaction/compaction-basic/src/region.ts' --coverage.include='packages/compaction/compaction-basic/src/summarizer.ts' passed 203 tests in 6 files; both changed sources have 100% statements, branches, functions, and lines. Region-to-default-summarizer cases pin exact prefix and tools for nonempty Unicode/multiline, empty, and absent heads. DeepSeek JSON byte equality and pi-ai context equality pin leading-message vs separate-system equivalence on text and image-capable conversion paths.
pnpm run doc-sync passed all 33 gates including doc-typecheck, documentation build, translation pairing and model-experience checks. git diff --check passed. Own dependencies installed with pnpm install --frozen-lockfile. An initial test iteration used a nonexistent ctx.dispose teardown on the in-memory fixture; corrected to its existing fixture lifecycle and reran successfully. No runtime/model behavior, normalizer marker, main worktree, push, or rebase changes.
Remove both unconditional header.system erasures from log normalization and replay comparison encoding, including the unrelated Session-header deletion. The catalog owns released-format migration; comparison must not turn an unexpected field into equality or supply a compatibility shim.
Add direct log/snapshot and catalog-restoration controls that retain unexpected request/header.system and differ from the field-absent fixture. Preserve malformed provenance data as well, closing the owned normalizer coverage gap. Update the two package README pairs with this oracle obligation.
Evidence: pnpm exec vitest run packages/test-support/session-snapshot/tests/normalize.spec.ts packages/test-support/llm-replay/tests/llm-replay.spec.ts --coverage --coverage.include=packages/test-support/session-snapshot/src/normalize.ts --coverage.include=packages/test-support/llm-replay/src/index.ts: 205 tests pass, both files 100% statements/branches/functions/lines. Direct normalizer negative control fails before the fix. Baseline diff3 comment remains untouched for the separate cleanup finding.
Consolidate the representation-change PR and its rebase reconciliations into one baseline. Preserve the exact tree and keep the in-history feature in the dependent PR. Follow-up fixes remain separate.
Master's windows-python-console-spawn-wait fix removed the plugin-add flake
this branch previously retried, so that retry is dropped entirely; the
branch keeps the changes that remain useful.
- Retry the immutable install step up to three attempts total with a
ten-second pause (bash on every platform): node-gyp's Node-header
download from nodejs.org intermittently times out, as observed on the
hosted node24-macos-x64 cell.
- Keep checkout credentials out of the build tree (persist-credentials:
false) so dependency-install scripts cannot read the embedded token.
- Failover runbook: drop the stray leading '#' before the Dependabot
paragraph on both sides.
- Hosted-runtime note: name the PR/commit ownership and UTC timestamps of
the cited runs and record the constraints a future self-hosted attempt
must satisfy; new note records the install retry decision.
The failover runbook's Windows switch scope returns to the native Windows jobs:
remove the Python runtime eligibility sentences, the pool Python prerequisite,
and the proposal links. The #3629 self-hosted Python runtime proposal is
retired (deleted as obsolete after its unmeasured-throughput run), and a new
implemented note records that the Windows x64 runtime lane stays on
GitHub-hosted Windows with the evidence and alternatives. Bilingual sidecars
re-recorded.
The failover runbook's Windows switch scope returns to the native Windows jobs:
remove the Python runtime eligibility sentences, the pool Python prerequisite,
and the links to the proposal. The self-hosted Python runtime proposal is
moved to rejected with its unmeasured-throughput verdict, and a new
implemented note records that the Windows x64 runtime lane stays on
GitHub-hosted Windows with the reasons. Bilingual sidecars re-recorded.
Windows x64 runtime builds resolve their hosted matrix.runner unconditionally
again (windows-2025 for pull-request CI). Remove the DSH_CI_FAILOVER_WINDOWS
selector, job-private Python toolchain, self-hosted dependency install and
post-step cleanup, the private setup script, and the routing spec introduced
in #3629. The Windows failover switch again covers only the native Windows
jobs in ci.yml.
Consolidate the existing notes-only PR into one review baseline. Preserve the exact reviewed tree; subsequent corrections remain separate commits with their own rationale and regression evidence.
Reflect the review feedback on the reader list: assistantStreamFirstTokenTime and the has-visible readers stop at the first qualifying member, while lastAssistantStreamChunk, assistantStreamChunks, and joinAssistantStreamText scan the whole stream; assembleAssistantStream feeds a BlockAssembler one joined delta per run. Record-level readers trust the static record type; expandAssistantStream is the validating path.
Session format v2 embeds each attempt's compact stream in
assistant/message and assistant/attempt, but Host and client consumers
still expanded it into per-member TimedStreamChunk arrays and did
per-member work; expandAssistantStream materializes the full array before
find/toReversed/break can answer. Session Stats (the projection phase of
every Session open), the token meter's usage and provider-assembly folds,
the subagent output fold, and the Session Controller image lookup still
paid O(members) allocation and time per settlement.
The Chat and Trajectory definitions were already settled from
message.content on master; the remaining per-member folds stay.
dsh-llm now exports record-level readers (first token, visible content,
visible text, last raw chunk of a type, raw chunks of a type, joined
text, run-aware assembly, per-run first-token/first-visible times) that
scan the compact records once with early exit. Session Stats reads
assistantStreamFirstTokenTime, the token meter reads
lastAssistantStreamChunk(stream, 'usage') and assembles through
assembleAssistantStream, the subagent output fold appends
joinAssistantStreamText, and the Session Controller scans
assistantStreamChunks(stream, 'block-end').
expandAssistantStream is deliberately not memoized: retaining expansions
costs roughly ten times the compact stream for the Session's lifetime.
It remains the validating path at durable boundaries.
Synthetic 200-turn v0 migration benchmark, median of five: first-open
projection 28.0 ms -> 5.4 ms, first-open total 76.9 -> 50.0 ms, peak RSS
137.2 -> 94.9 MB; reopen projection 17.8 -> 5.6 ms; all phase budgets and
the 128 MB heap constraint keep passing.
The migrating first open measured 1,963 ms on the CI runner under the
128 MB heap limit; 4,000 ms keeps a 2x margin while the repeated-snapshot
implementation still fails by heap exhaustion and would need ~10 s.
A records-proportional fold still joins 20x more text in the large window,
which measured about 2.5x the small fold; the per-delta replay measured
about 11x. A 5x bound separates the two on a noisy runner.
The migrating first open of the synthetic log costs about 1 s on the
reference machine under the 128 MB heap limit and about twice that on the
CI runner, so 3,000 ms keeps headroom while staying far below the ~5 s
(~10 s on CI) of the repeated-snapshot implementation.
scripts/test-proxy-environment.spec.ts pins the Vitest configs that declare
setupFiles so the proxy isolation setup is never dropped; the benchmark
lane declares it too.
Add a benchmark lane (`vitest.bench.config.ts`, `pnpm run test:bench`,
gate mode `ci-bench`) and a required `node 24 / benchmarks` CI job that
runs it alone. Benchmarks synthesize their input in-process from fixed
parameters and fail on documented budgets:
- `open-generation.bench.ts`: a 200-turn released-v0 log with 500 text
and 125 reasoning deltas per reply (127,400 events, ~2.8 MB) encoded
through the frozen v0 codec; the migrating first `open()` must finish
within 2,000 ms in a child process capped at 128 MB of old space, and a
fresh process must open the published current generation within 500 ms.
- `conversation-fold.bench.client.ts`: 200 replies whose compact streams
hold 2,000 text + 500 reasoning deltas each, folded through every Chat
Definition by the real assembler; the fold must finish within 150 ms
and stay within 3x the fold of the same window with 100 deltas per
reply.
On this commit both gates fail: the migration exhausts the 128 MB heap
(4.8 s and 696 MB peak RSS without the cap; the pre-stack decode of the
same bytes took 34 ms and 168 MB) and the fold scales 11x with the delta
count. The stacked fixes bring both paths to O(records).
Replace the external ArrayBuffer view and manual UTF-16 scan with native Koffi decoding through a pointer-width buffer. Preserve the original address for CoTaskMemFree.
Exercise production resultPath with real Koffi decoding of live UTF-16 buffers. Cover zero-low-byte BMP characters, surrogate pairs, NUL termination, and strings exceeding 32 KiB; test four- and eight-byte BigInt pointer preservation and the original release address. A negative-control mutation confirms truncating a 64-bit address fails.
Keep the bilingual Agent Note focused on implementation rationale and required memory ownership. Preserve master archive seals during rebase.
Co-authored-by: zhanglunet <[email protected]>
Archiving low-future-value Agent Notes (#3548) stripped the inbound
citations to three now-archived notes from the ui-primitives README, and
this branch rewrote the same "Controls and icons" opener so the new
component catalog owns the export enumeration. The resolution keeps this
branch's opener and drops the archived-note citation master removed, in
both languages; the pairing record is re-recorded from the merged pair.
The track's radius is half its height, so its corners consume the whole
side and the global `superellipse(1.5)` squares the ends off — beside a
thumb that already opts out at `border-radius: 50%`, leaving a round knob
in a squared-off slot. Pair `corner-shape: round` with the radius, the way
every full-round radius in the repository does.
The corner-shape stylesheet spec recognizes 50%, 100%, and radii at or
above 99px, so a radius that is full-round only relative to its own box
passes it; the comment records that gap at the rule it affects.
The `idle` addition left two maintainer-facing statements behind: the
README's limitations entry still listed four supported StateDot states,
and TerminalBlock's run-state JSDoc still counted the union as four while
correctly saying three of them are reachable there.
Pill's header comment still sent every capsule without `onClick` to Tag,
which contradicts both the catalog this pull request corrected and
TerminalBlock's own static read-only Pill two files away. Size separates
the two controls as much as interactivity does, and the comment now says
so. The function JSDoc and the static-mode tests stay as they are: they
describe supported behavior.
`unloading` marks a teardown the Host is running, which an async disposer
can hold for a while, so the inventory's dot animates it like `loading`
instead of showing the static grey `idle` mark that means no activity.
Correct four documentation facts against the shipped code: StateDot's
render JSDoc still promised four states, the catalog claimed four Tooltip
sides where TooltipSide has three, the Agent Note's geometry audit counted
three matching rules where two match, and the migrated-site and shipped-
appearance counts had drifted.
Record two things the first pass left out: TerminalBlock renders its exit
status as a static Pill, which the Tag/Pill catalog entry now accounts for
by size rather than by interactivity alone, and the disabled inventory tag
changed fill token, which joins the visual-change list.
Pin Tag's eight tones with a stylesheet spec, for the same reason StateDot
has one: CSS Modules resolve to class-name maps in component suites, so a
missing or wrong tone rule passes every render assertion.
- Stop treating three archived records as current authority: bounded-llm
citation now points at the llm-retry README, the semantic-composer
proposal cites the runtime-owned child guard as history it builds on,
and the code-runtime comment states the deferred rationale locally.
- Fix the stale "dsh family stays restricted" claims left without their
rationale citation: the check-workspace-constraints comment, the
release publish comment, and the kept npm-release-sequences note now
record that the family published publicly on 2026-08-13 (87f8e6a728).
- The rejected client-settings-locale-theme body keeps its pristine
proposal prose with only the Status line and the two link paths whose
targets this PR archived; the link remaps are required for
verify-md-links and pairing, and every prose byte is untouched.
Run the dsh-archive-agent-notes audit over every active Agent Note on
current master, judging each record by whether its rationale still guides
work rather than by size or age.
- Archive 453 implemented bilingual triplets (417,882 English words):
completed UI chrome, narrow adapters, closed bug fixes, implementation
walkthroughs whose package READMEs, docs pages, generators, or successor
notes now carry the useful behavior, and 51 records fully superseded by
a later active note. Keep 201 implemented notes whose ownership rules,
negative guarantees, durable or wire semantics, security rules,
reintroduction conditions, or still-tempting rejected alternatives
remain useful.
- Reject 7 proposals whose premise is gone or whose work shipped in
amended form under other records; delete 2 rejected notes that no
longer prevent a plausible mistake.
- Retarget every remaining inbound link to the archived path, and repair
active prose that named an archived record as the owner of a live fact:
parenthetical citations drop, ownership sentences redirect to the
README, docs page, or active note that states the fact, and history
citations say so. Chinese files link the English archived path because
the pairing gate treats the frozen tree as outside the bilingual corpus.
- Seal 1,359 new frozen artifacts; existing seals are unchanged and
outbound links from archived notes are neither inspected nor repaired.
- Regenerate docs/config-catalog.md after the hook-bridge comment edits
shifted two source line numbers.
Client feature plugins never import one another's components, so
ui-primitives is their only channel for sharing one. Nothing told an
author to look there first, and the package offered nothing to look
at: its README named six source files against forty-plus exports.
Add Tag (read-only capsule badge, eight tones) and Switch (36x20
toggle with a required accessible name), extend StateDotState with
idle for a tracked subject with no activity, and move ui-agent-preset,
ui-settings-plugins, and ui-settings-plugin-inventory onto them.
The plugin inventory's conditional tag referenced
--dsw-alias-state-warning-primary, which does not exist, so both
themes had been painting its hardcoded #b45309 fallback.
State the reuse rule as the first step of the new-component checklist,
and give the README a component catalog so the rule has something to
check. The rule is guidance, not a gate: a package may still write its
own control, and the Agent Note records the five that stay local.
The Agent Note rules keep the `# Agent Note: ` title prefix and the
`Status:` line in English verbatim on both language sides. 51 Chinese
counterparts carried a full-width colon (`# Agent Note:`), an annotated
prefix (`# Agent Note(agent 决策记录):`), or a translated status token
(`状态:已实现`, `状态:implemented`, `状态:已实施`, `状态:proposed`), which the
archive verifier rejects on entry to the frozen tree. Normalize the tokens
and re-record the bilingual sidecars; no body text changes.
The heuristic tools segment counts tool schemas only, so the row reads 工具定义
/ Tool definitions; the header figures lose the ~ because they are anchored to
provider-reported usage, unlike the heuristic rows that keep it.
The default and hero placeholders drop the ellipsis and name the / commands
and @ references as comma-separated actions; goldens and e2e assertions
refresh with the copy.
Tighten the composer card's top pad and draft-text pads, pull the attachment
rail closer to the draft, settle the toolbar gaps on 12, give the hero
workspace row its right clearance, and let the wrapped hero badge center under
the title with more air between the wrapped fish and the title line.
ModelSelect renders its dropdown through a body portal with viewport-clamped
fixed placement, so the panel keeps its full width instead of being cut by the
sidebar edge. The preset seat passes Menu an anchor class with an icon-only
min-width floor and moves its label ellipsis onto a dedicated span, keeping the
chip's overflow degradation breakpoint-free. Toasts move to the viewport top
and shrink to fit their text.
Clickable artifact links across the transcript (markdown anchors, prose
file mentions, web source/fetch links, produced-file chips, workflow
member links) now share one language: a dedicated --dsw-alias-link color
(light deepseek-500, dark deepseek-400) decoupled from
state-business-primary, font-weight 500, no underline at rest, and a
dotted 3px-offset underline on hover/focus. A new ui-primitives LinkIcon
leads each link with a currentColor category glyph (url / folder / code /
image / document / other) derived from the path extension by
classifyLinkPath. Produced-file chips drop the grey pill and 96px cap and
render at natural width, shrinking with ellipsis only on row overflow.
ToolRow's grey dotted file links and the grey show-in-folder action stay
as they were. The inline-code chip tint moves to neutral-50 (dark
neutral-800) with its 0.5px border kept.
Coverage: LinkIcon unit spec, refreshed markdown-dom fixtures, and the
clickable-links-gallery web e2e registered in the host compiler face.
Closes#3546
Stop re-exporting the verbatim file-store helpers from attachment-local, move
the raw-byte upload route handler into a non-entry module of file-upload, and
drop the redundant hook type re-exports from the Client entry. The route test
moves next to the module it exercises.
Write handles now hold a durable lease (session.lock.json beside the
log): a random owner token, diagnostic pid, and an expiry. Acquisition
wins by exclusive create; a second process's create or write open rejects
while the record is unrenewed for less than leaseTtlMs (default 5 min),
and takes over after that — a crashed holder is waited out, never
reclaimed by pid. The holder renews every leaseRenewIntervalMs (default
4 min); a renewal that finds a foreign, vanished, or expired record — or
fails outright — marks the lease lost permanently, so every later
append/flush rejects with SessionOwnershipLostError while reads continue.
Close releases the record; read handles never touch it. Takeover of an
expired record is eventually exclusive: a replaced holder stops within
one renewal interval.
Refs #3245
The dsh-family version rule lived only in the packages/ block of
checkWorkspaceManifest, so apps/ members and the root-named CLI carried the
shared version with no static check of their own. The name-based rule now
covers every scanned manifest; regression coverage names the stale-version
merge that shipped packages/util/http-proxy at 0.1.2-alpha.5 behind a
0.1.2-rc.1 family.
`SESSION_FORMAT_VERSION` is the only value that names the Session format
generation. Two other version fields had moved to 2 alongside it:
- The `dsh_session_log` request extension's outer schema `version` returns
to 1 and its Session header projection keeps `seedLength`, derived from
the logical inherited cut, so the external wire is unchanged in this PR;
`sessionFormatVersion` alone identifies the embedded generation.
- The `sessionStats` projection unit's `stateVersion` returns to 1: the
projection cache binds every checkpoint to the format generation, so a
generation change discards old rows without a unit version bump.
Test doubles and the standalone Client fixture that build a current
header now spell `SESSION_FORMAT_VERSION` instead of the literal.
The README file table listed four of the six source modules and omitted
the two largest, so the package read as an oversized identity conversion.
The summary now states that most of the source is the frozen released
v0/v1 event vocabulary, why the edge refuses a malformed historical log
before the installed current restorer runs, and that later edges reuse
those shapes; the table gains `payload-validation.ts` and
`relationships.ts` rows.
The `start` frame and the reconnect baseline attempt carried a wall-clock
`startedTime` that no Host or Client consumer read: the Host accumulator
and the Client reconciler copied it into their attempt maps and nothing
looked at it again. Presentation frames now carry only the coordinates
consumers use (attempt, revision, turn, step, chunk index, and v1 seq
provenance); the type-equivalence block, event graph, READMEs, and the
live-stream Agent Note follow the type.
`session[.vN].jsonl` was assembled independently by the JSONL provider
(twice), the session-log export archive, and the recorded-session fixture
helpers. `dsh-session-format` now owns `sessionFormatLogFilename` and
`parseSessionFormatLogFilename`; the three consumers append only their
compression suffix, and the migration note names the owner.
`snapshotSessionFormatJson` re-implemented the lossless JSON walk that
`dsh-util-values` already publishes as `snapshotJsonValue` + `deepFreeze`,
and the frozen v0 relationship validator carried a third structural JSON
comparison next to `deepEqualJson`. Both now delegate; the format package
reports one `is not lossless JSON` diagnostic per labelled subject instead
of ten member-specific ones, and enumerable accessors whose values survive
a JSON round trip are accepted like `JSON.stringify` accepts them.
`assertFixtureInventory` folds every retained generation of one parent or
child fixture into a single role before comparing, so an older generation kept
beside the current one is not an inventory drift. The generation spec writes
headers that match each filename, since fixture selection validates the chosen
generation, and asserts that an absent parent resolves only for an
override-only replay. The `dsh_session_log` composition expectation follows the
extension's version 2 payload.
The continuable-child headless expectation and the Python `advanced` and
`restart` scenarios pin the current runtime, whose subagent children carry no
`session/end-seed` marker and whose assistant messages embed their streams:
the child fixture drops the marker and renumbers its references, `advanced/`
re-records `result.json`, and both scenarios gain their v2 Session fixtures.
Unit tests cover the `tool-call-delta` id and name validation, embedded
stream members without timing arrays, and the versionless-header refusal.
The JSONL backend rethrows storage errno failures and abort signals from the
source read of a pending migration unwrapped; a test now drives both through
the stat seam so the classification stays covered. The navigation-panes Web
e2e derives the exported archive entry from `SESSION_FORMAT_VERSION`, which
`session-log-export` uses for its `session.v<N>.jsonl` name. The module graph
records the `session-log-export` → `session` peer edge.
`session-format-v1-to-v2` takes the `0.1.2-rc.1` root version that master
requires, and `defineReleasedPayloadDisposition` drops the `@param` tag for a
parameter it no longer has.
`session-log-export` names its archive entry after `SESSION_FORMAT_VERSION`,
a runtime value of the shared `dsh-session` instance, so `dsh-session` becomes
a peer dependency and the export is classified as peer-required in the package
dependency policy. The three Session format packages take the `0.1.2-rc.1`
root version that master now requires.
The repository-only benchmark, its unit-suite spec, the root script, and the
README section are removed; the accepted acceptance run's figures stay in the
embedded-assistant-streams note as recorded facts. The note also records why
the migration edge reuses the `dsh-llm` stream helpers instead of frozen
copies and why its target validation re-checks message/stream agreement
itself. A validation test that recomputed `RELEASED_V2_EVENT_TYPES` from the
same expression as its definition is dropped.
`ensureJsonlGenerationCurrent` returned a live Zstandard body iterator that
every production caller disposed unread before decoding the same bytes again;
the header read now decodes only the first frame and owns no decoder past its
return. The JSONL backend reuses the generation module's stable-revision read
instead of keeping a second copy, `encodeCurrent` encodes the validated
current artifact it is documented to receive instead of re-running the
migration chain on it, and the test-only `inspectVersion` catalog method and
the `migrateSessionSnapshotFixture` duplicate of
`prepareSessionSnapshotFixtureForComparison` are removed.
Second review round (ds-review-bot v4p/v5/v6): the exit cleanup claim that
collectors unlink their spill files on dispose was wrong — completed spill
files are retained (seal() only closes, discardSpill() is the only unlink),
so the exit removal only ever applies to directories that never spilled.
Reword the JSDoc, README, and Agent Note accordingly, drop the redundant
readdirSync precheck (rmdirSync ENOTEMPTY is caught anyway), and recount the
v8-ignore window. The spawn.spec default-spill test now records the default
per-process directory it created and removes it in afterAll, so a completed
spill file is never left behind by the unit suite.
The Agent Note now links the 2026-07-17 local-spill-startup-cleanup note it
builds on, and the privateSpillDir JSDoc documents the empty-dir exit
removal alongside the directory's creation contract.
Review (ds-review-bot) found the exit-time deletion of per-process spill
roots conflicts with the documented retention decision: spill artifacts are
model-visible locators that resumed or forked sessions may still reference
(2026-07-17-local-spill-startup-cleanup), and an exception thrown from an
'exit' listener can change the process exit code.
- dsh-spill-local: revert the exit handler entirely; its default root is
already owned by the package's 30-day startup sweep.
- dsh-subprocess-local/spawn: remove the per-process spill dir at a
JavaScript-observable exit only while it is EMPTY (collectors unlink
their spill files on dispose), best-effort with a named swallow so a
Windows-held handle never changes the exit code; dirs holding spill
files keep them for external cleanup.
- Document the empty-dir exit removal in the package README and the Agent
Note, and re-record both bilingual pairs.
The exit listener runs after the coverage dump, so its body can never be
measured by the unit coverage lane; mark it v8-ignore with the reason, as
with other process-exit-only code paths.
Full-template residue histogram on the CI host surfaced four more normal-exit
leaks below the earlier cutoff: tool-bash/tool-pwsh tools.spec (module spill
dir / per-test homes), bash-sandbox sandbox.spec (module spill dir and the
read-only denial root), and fs-sandbox fs-sandbox.spec (inline tmp dir). All
four now remove what they create at the same teardown points as their
neighbors.
Spec files that create /tmp/dsh-* directories via mkdtemp now track and
delete them in afterEach/afterAll; module-scope fixture dirs (executor
spill dirs) are removed in afterAll. The file list came from the
observed-residue inventory on the self-hosted CI host: only specs whose
dirs actually accumulated were leak sources (issue #3134), superseding
the kept-but-unmerged CI sweep branch per the #3233 review decision.
Product per-process spill roots (dsh-subprocess-local spawn,
dsh-spill-local store) register a process-exit handler that removes the
memoized dir, so processes that used the spawn/spill path clean up on
normal exit. A SIGKILLed process cannot run in-process teardown; the
machine-side timer remains the backstop for that path.
Agent Note: .agents/notes/implemented/process/2026-08-28-test-temp-dir-self-cleanup.md
Expose a live goals/get read plus goal/activation-changed for the Web strip, reject model resume of durable paused goals, refresh snapshot and generated documentation surfaces, and record the follow-up decision.
Review feedback: the dsh-terminal-bash product default is 30s, not 300s;
the raised value bounds one send plus the complete startup sequence, so it
must cover the same cold start the tool deadline does. The vitest case
budget stays at its pre-existing 120s: the case-level timeout overrides
the lane --testTimeout, so syncing it to the plugin deadline would make a
stalled partition wait 300s instead of 120s.
Replies captured on 2026-09-02 from OpenRouter, models.dev, and DeepSeek
replay through the discovery parser, and OpenRouter's nested
top_provider.max_completion_tokens now feeds the output-token cap.
Model requests hand the configured baseURL to pi-ai unchanged. Only the
discovery listing URL accepts the Anthropic API root with or without a
trailing /v1, because gateway documentation publishes both spellings.
The new keyless sdk-spawn-node scenario drives the platform shell tool
through a command starting with node and requires the machine's own Node
version in the tool result with no PKG_EXECPATH in the child environment,
so a pkg upgrade that re-records the child-process patch cannot silently
restore the hijack. Runs on every target through --scenario all.
@yao-pkg/pkg's SEA bootstrap rewrites child_process commands named node --
including the string after a -c flag, exactly the Bash tool's bash -c form --
to the executable itself and stamps PKG_EXECPATH into every child
environment, so a model-issued 'node --version' silently booted the dsh CLI
instead of the machine's Node. Pin the packager as an exact root
devDependency invoked through pnpm exec and patch out the single
patchChildProcess call from the SEA bootstrap bundle; packaged children now
resolve node through PATH like any other process. The third-party notices
drop the build-time tools section: the packager is now a declared, patched
devDependency, so the manifest and patch tiers disclose it.
master released 0.1.2-alpha.5 after this package was created, so the
release bump never reached it; the workspace constraints gate requires
every package version to match the root.
Prefer gh v2.99.0's repeatable --attach flag for publishing demonstration
GIFs: it uploads the artifact and rewrites the body's local-path
reference in place, keeping media out of git history without an orphan
assets branch. The assets-branch workflow remains the fallback when the
GIF exceeds 10 MB, gh is older than v2.99.0, or the repository is not on
github.com.
The first call pays the full pwsh cold-start latency (spawn + .NET +
PSReadLine + Defender) inside the tool deadline. A 60s bound on the fully
loaded self-hosted Windows pool is exceeded often enough to reset the
session mid-test: two master CI runs (2026-09-01, runs 33524764567 and
33534262413) each failed at ~62s with the second call observing a fresh
session (cwd back at root, env empty). 300s matches the product default
so cold start no longer races the budget.
Windows folds `https_proxy` and `HTTPS_PROXY` into one variable, so the
exported spelling shadowed the file's lowercase one and the case failed
there. Each spelling now gets its own name: the file supplies a name the
shell did not export, and the shell outranks the file for the one it did.
The 0.1.2-alpha.4 release on master touched every manifest that existed
there; this package did not, so the merge left it at alpha.3 and
`check-workspace-constraints` refused the tree.
Refusing a response whose tool call never receives an identity needed a new
failure code, a change to the default retryable set, and a `[DONE]` gate that
overrode the finish reason a provider had already sent — turning a safe
`max-tokens` truncation into up to five retries. The lenient wire it guarded
against is hypothetical: no report describes a stream that omits identity
entirely, and the pre-existing test for it is labelled as such.
Only `acceptIdentity` and the widened wire types remain. They close the
reported erasure and cannot reach a worse outcome than the previous
assignment, because the set of inputs that assign only narrows.
`proxyEnvironmentForChild()` hands a child the proxy values the user
exported, including one this package refused — a SOCKS URL kept because
`curl` reads it — and sets `NODE_USE_ENV_PROXY=1` so a child Node honors
them. Node parses `HTTP_PROXY` and `HTTPS_PROXY` under that flag before
running the program and exits on any scheme other than `http:` or
`https:`. So a user with a usable `HTTP_PROXY` and `HTTPS_PROXY=socks4://…`
lost every Node child — stdio MCP servers, subagent CLIs, `npm` in the
bash tool — before its first line, while this process had reported only
that the scheme stayed direct. Measured on Node 24.17: `socks4://`,
`ftp://`, and a malformed value all exit 1; `socks5://` is accepted there
and only there.
The flag is now withheld whenever a value under the names Node parses is
one `isSupportedProxyUrl` refuses. Such a child connects directly, which
is what this process already said about that scheme, and `curl` still
reads the value it was kept for. Node does not read `ALL_PROXY`, so a
refused value there alone changes nothing.
The socks5 case in `install.spec.ts` now asserts the flag absent; the
ALL_PROXY fill case asserts it present; a new case spawns a real child
Node under the overlay for each refused shape and asserts it starts.
A direct policy installed over a proxied one swapped the global
dispatcher but left `process.env` holding the outer install's published
normalization. `proxyEnvironmentForChild()` returns nothing under a
direct policy, and `scrubbedParentEnv()` copies `process.env` as it is,
so a child spawned in that window inherited values no active policy
stood behind: an `HTTPS_PROXY` derived from `HTTP_PROXY` the user never
set, or the loss of a SOCKS value they set for `curl` and this package
had refused.
The direct branch now writes the user's own values — the record the
outermost install keeps — back into `process.env` for the window, and
re-applies the outer install's published values when it ends. An
install underneath that proxied nothing published nothing, so there is
nothing to put back. Publishing and restoring share one `writeProxyEnv`;
the empty-string special case it replaced was unreachable, since a
resolved bypass list always carries the loopback entries and an
accepted proxy URL is never empty.
The nesting is reachable only from tests since the plugin was removed;
the fix keeps the disposer symmetric for whoever layers installs next.
`LlmRuntime.adapterStream` normalizes a thrown `LlmError` into the same error
`finish` the loop routes to `agent/request-error`, so throwing would reach
retry too. The Note claimed otherwise. Yielding is chosen because it reports
the attempt's billed usage first and matches the neighbouring `EMPTY_RESPONSE`
refusal.
The rejection comment repeats the corrected durability wording, and the
assembler's delta-only fallback carries a TODO for the empty name it still
invents for adapters that never close a tool-call block.
The proxy guide told users a proxy could live in a project or
`$DSH_HOME` `.env`. It could not: `loadLayeredEnv` refuses the four
proxy names from any discovered file, as it refuses `PATH` and
`NODE_OPTIONS`, and the launch fails with a pointer to `export`.
That refusal is right for the invoking directory's file — it arrives
with a clone, and a repository must not choose where the harness sends
its traffic — and wrong for the user's own `$DSH_HOME/.env`, which
already holds their API key. `readEnvLayer` now accepts `HTTP_PROXY`,
`HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` from the directory that is
the Harness home, and nowhere else. `DSH_HOME` is itself bootstrap-only,
so no `.env` can relocate the exemption; the CA and TLS names in the
same group stay refused everywhere, since they change what is trusted
rather than where traffic goes. A project `.env` that sets a proxy name
still fails the launch, and its message now names the home file as the
second way out. Launching from inside the home directory reads that one
file as the project layer; the exemption follows the directory.
The seven existing refusal cases all write to the project layer and
pass unchanged. Four new cases cover the home layer accepting both
casings below an exported value, the home layer still refusing
`SSL_CERT_FILE`, the project layer's new message, and the same-directory
launch. The guide, both package READMEs, and the two Agent Notes that
stated the old rule now state this one.
The `MALFORMED_TOOL_CALL` JSDoc claimed nothing durable is written, but the
loop appends an `assistant/chunk` for every yielded chunk; only the assistant
message and tool result are withheld. The bounded-recovery Note still listed
a five-code transient set, and neither Note linked the other.
A keyless `malformed-tool-call-retry` scenario now records the refusal, the
retry, and the absence of a `tool/call` for the failed attempt. A translator
case pins that an already closable block also withholds its `block-end`.
Review found the package's prose still describing a plugin that an
earlier revision removed, and three factual slips about behavior.
- policy.ts, install.ts, install.spec.ts, and the Agent Note named a
`Config` surface, a `cordis.yml` source, a mountable plugin, and a
`plugin.spec.ts` that no longer exist; each now describes the
environment-only resolution the launcher actually runs.
- `NO_PROXY=example.com` bypasses `api.example.com` as well — the matcher
accepts the host and every subdomain under it, and a leading `.` or
`*.` means the same thing. The guide, README, and JSDoc claimed a bare
entry matched only the exact host, which would let a reader believe a
subdomain was proxied when it went direct.
- A rejection diagnostic names the variable and never its value, so no
username is shown; the guide said the username was shown with the rest
masked. The README's source map claimed a "redaction" step that does
not exist.
- The `node:https` worker placeholder was added for a `node:http` agent
factory this PR later removed; nothing imports `node:https` now, so the
stub, its VFS mapping, and its test return to their state on master.
Adding `MALFORMED_TOOL_CALL` to the default retryable set changes two
expected outputs the session snapshot lane does not own: the shipped Web
composition's inline snapshot, and the canonical packed layout of the
refreshed `empty-response-retry` fixture.
The persistence seam is now create/open/stat/list returning per-session
SessionHandles (read/append/flush/close); every log read and write flows
through the owning handle. The seam package exports only the service and
handle contracts, consumer-visible errors, and pure durable-data
validation helpers; each backend owns its complete storage runtime, and
the shared contract suites pin equivalent observable behavior. The
backend routes published sessions' live events by id into the active
write handle; agent-loop only acquires, seeds, and closes the handle.
Resume appends interruptedTurnClosers through its write handle;
session-query owns the revision-keyed cold cache. Legacy-only surfaces
are removed in the same swap: locate/readRaw/supportsRawArtifacts, the
legacy event-shape read migration, zstd torn-frame salvage,
DSH_SESSION_JSONL, and hook transcript_path population; a torn final
zstd frame is discarded whole; the session-list cold blank probe returns
on stat metadata (eventCount derived from the last physical row,
sizeBytes). The WebUI ZIP export serializes the logical log from a read
handle, so both backends export identically.
Refs #3245
A continuation SSE delta that repeats a tool call's `id` or `name` as an
empty string — or as `null`, which some OpenAI-compatible gateways send —
erased the identity established by the call's first delta. The assembled
block reached the loop with an empty name and failed as `unknown tool ""`,
and the empty `callId` persisted into `tool/result`, which the session
reader refuses on reopen.
`acceptIdentity` accepts only a non-empty string, so a repeated empty or
null field means "no update". A tool call still missing `id` or `name` at
`[DONE]` ends the response with the new retryable `MALFORMED_TOOL_CALL`
code instead of closing an unusable block.
Reverting telemetry to its own transport dropped the package's dependency
on the proxy library, and the generated graph still recorded that edge.
The gate that catches this is `verify-module-graph`, which runs in
`check:ci:static` and not in `doc-sync`; the revert was checked with the
latter alone.
Telemetry was the only call site this PR could not cover without changing
the SDK transport underneath it, and both ways of doing that cost more
than the channel is worth.
Routing an `http.Agent` needs Node's `proxyEnv`, added in 22.21 and 24.5
— inside the engines range, so three supported runtimes stayed direct
anyway, and the proxy package had to keep a `createNodeHttpAgent` export
for a path that only sometimes worked. Replacing the transport with the
SDK's `fetch` delegate covered every runtime but has no compression,
while the shipped `base` bundle enables gzip and a realistic OTLP batch
is 6.4x smaller with it; keeping both meant gzipping at the serializer,
which put transport code inside a telemetry plugin.
Telemetry is the one outbound channel whose loss costs the user nothing:
no tool, model request, or session depends on it, and an export that
cannot connect is already dropped silently. A user behind a mandatory
proxy is left where they were rather than regressed.
`src/index.ts`, `otel.spec.ts`, and `tsconfig.json` return to their state
on master; the package keeps only a dev dependency on the proxy library.
`egress.spec.ts` inverts: it installs a policy and asserts the fake proxy
saw nothing, so an SDK upgrade that moved the exporter onto `fetch` would
surface as a failing test rather than silently routing telemetry.
The previous commit moved the OTLP exporter to the SDK's `fetch` delegate
and refused `exporter.compression` at load, on the belief that nothing
shipped enabled it. `packages/bundle/base/cordis.patch.yml` does, so the
refusal broke every test that boots the shipped bundle — the snapshot,
e2e, and Windows observational jobs all failed on that one load error.
Dropping gzip was the wrong trade anyway: a realistic OTLP batch measures
6.4x smaller with it, so trading it for proxy support would have charged
every deployment to fix one. The `fetch` transport has no compression
hook, but serialization is the seam before the body reaches it — the
plugin now gzips there and declares `Content-Encoding` itself.
`keepAlive` and `httpAgentOptions` have no such seam, since they
configure a connection pool `fetch` does not expose, so those two stay
refused at load rather than accepted and ignored. `compression` is typed
as the two values this package can actually apply rather than the SDK's
wider enum, and a third value fails loud at load.
The package exported six functions, four of them shaped by one SDK's
transport each: a dispatcher factory, a `node:http` agent factory, a
proxy-URL lookup, and a policy accessor. Review asked whether the call
sites could converge instead of the package growing an export per SDK.
They could, and each removal took a whole shape with it:
- The OTLP exporter moves to the SDK's `fetch` delegate, retiring
`createNodeHttpAgent`. Its Node-version floor goes too: `proxyEnv` on
an `http.Agent` needs 22.21 or 24.5, inside the engines range, so
telemetry was direct on 22.19, 22.20, and 24.0-24.4. The cost is
`compression`, a Node-transport option; the plugin now refuses it,
`keepAlive`, and `httpAgentOptions` at load instead of ignoring them.
- `web-fetch-http` builds its own address-pinning agent under an
annotated `proxy-exempt:` exemption, retiring `createDispatcher`.
Pinning is per-request state a process-wide dispatcher cannot hold.
- E2B reads `route.proxy`, retiring `proxyUrlFor`.
What remains is `installProxyFromEnvironment`, `proxyRouteFor`,
`proxyEnvironmentForChild`, and `clearedProxyEnv` — one per way a caller
can need the policy. Installation absorbs resolution and diagnostic
reporting, which no caller needed apart.
`proxyRouteFor` also closes a defect the old accessor made expressible:
`web-fetch-http` read the policy to decide whether to pin, then read it
again to build a transport, so an unmount between the two returned a
direct, unpinned agent for a URL the first read had cleared as proxied.
A route carries the answer and the transport that answer assumed.
Every egress spec now installs through `installProxyFromEnvironment`, so
no test asserts a policy object a real launch could not produce.
Master moved 212 commits. Conflicts were eight `package.json` and nine
`tsconfig.json` files, all dependency-and-reference unions: master dropped the
`runtime-diagnostics/invariants` reference across packages while this branch
added `util/http-proxy`. The lockfile, `tsconfig.base.json`, and the module
graph were regenerated rather than merged by hand.
Master also brought `verify-package-invariants`, which rejects an empty
invariant companion. This package's companion was empty by design, so it is
gone with its publication wiring — the export, the `files` entry, the project
reference, the `dsh-invariants` peer, and the test. Master did the same across
`util/`. The README carries the reason sentence that gate requires.
Carried in the same commit, because the review arrived while the merge was open:
`e2bApiUrl` moves out of the `dsh-e2b` entry into `src/api-url.ts`. Code Entropy
flagged it as a public name with no consumer outside its own package; the seam
keeps its URL-precedence test, which is worth having directly — getting that
order wrong hands a proxy the control-plane traffic and its API key.
Review asked why this is a plugin and why it exports so much. The design note
this branch shipped answered the second question itself — "a pure resolution
function plus an installation function" — and the code drifted to seventeen
exports and a Cordis plugin nobody approved or mounted.
The plugin is gone. Transport policy has one answer per process: nothing to
swap, and no scope narrower than the process to give one. Its `Config` was also
the only supplier of a configuration branch, so resolution now reads the
environment and nothing else — `mode`, the config-sourced fields, and the
`config` policy source were unreachable the moment the plugin left.
Four exports nothing outside the package used are internal again, and
`currentProxyPolicy` answers with the direct policy instead of `undefined`, so
`DIRECT_POLICY` no longer needs a public face. Nine functions remain, one per
way a caller can need the policy; two is not reachable with six consumer seams.
The package moves to `util/`. The note claimed a dependency on `undici`
disqualified it from that group; the charter governs harness dependencies, not
external ones, and the process note that says so predates this branch. The real
blocker was the harness dependency: resolution needed one method of
`LaunchEnvironmentSnapshot`, so it names a structural `EnvLookup` and the
launcher passes its snapshot unchanged. `net/` is dissolved.
Dropping the group's line from the repository layout also returns `AGENTS.md`
to its original ceiling, so the raise the merge needed is reverted.
`AGENTS.md` sat at exactly its 1950-word ceiling on master, so the one line this
branch adds to the repository layout — a new top-level package group — cannot
fit at any length: even a one-word description overflows. The description is
condensed to five words and the ceiling raised by ten, the smallest change that
keeps every package group listed. Omitting only `net/` from a list that names
every other group was the alternative.
One new test drove an IPv4-mapped literal through a full fetch. An IPv6 literal
sends `resolvePublicAddresses` looking for a NAT64 prefix before it refuses
anything, and that is a real DNS query — 5s under load, 6ms here, which is why
it passed alone and timed out in the full suite. The three IPv4 cases already
prove the branch end to end without touching the network, so the mapped form is
asserted on the predicate instead.
Review found `127.0.0.2` routed through the proxy. The bypass list carries four
literal loopback entries because that is all a consumer reading an environment
can match, and `proxyForUrl` matched only those — leaving the rest of
`127.0.0.0/8`, `0.0.0.0`, and the IPv4-mapped spellings routed through a proxy
that could then reach them. Loopback is now recognised structurally, which a
list entry cannot express; the published entries stay for the environment
readers.
The same review case exposed a wider one. `web_fetch` skips its address checks
on a proxied hop, because the proxy resolves the origin — but a literal needs no
resolution, so the skip bought nothing and let a proxy on this machine reach
every private range those checks refuse, `169.254.169.254` included. A literal
the checks would refuse now takes the validated path, where the existing
refusal already covers it.
Tests that proved a tunnelled hop used a loopback origin, which no policy can
route through a proxy any more. They name a host only the proxy can answer for
instead — closer to what a proxied request actually looks like.
The user guide promised the proxy carried every outbound request including
telemetry. It carries neither on an older Node, nor anything a model-authored
script sends, so the promise is narrowed and the exceptions listed. A password
in a proxy URL reaching every tool DSH runs is documented there too: it is how
the variable already behaves, and worth knowing before putting one in.
A settled top-level `read_image` call printed its raw attachment object as
literal text in the tool card — `{"type":"image","attachment":{…}}` —
instead of the image, because no presentation metadata told a client card
how to present the reference and the tool-card layer had no image concept.
Host: `read_image` declares an `output.presentationMeta` persisting
`{ path }` only. The attachment reference deliberately lives in the
settled result content — the single record a `tools/post-execute`
replacement rewrites — not in `meta`; the id is opaque and
provider-owned, checked for existence only.
Client: `imageCardModel` derives the card from the call head, the meta
path, the result's own image block, and a shape-matched envelope. ToolRow
gains an `image` card slot; the `read_image` toolview declares the
Tool-owned `tool.call.images` slot as its child and dispatches the
gallery through it. ui-chat down-threads the session-authorized loader
(`ChatNodeOwnerProps.loadImage`), so the tool layer supplies only derived
references plus the loader and never imports an attachment
implementation; ui-attachment fills the slot with its message gallery
renderer. The card keeps the envelope text below the gallery for the
no-attachment-plugin deployment. An image-bearing tool registers a keyed
toolview; the generic fallback keeps its flattened text. `read_image`
joins the read variant with its own locale title key; both rows share
`read-family-row.tsx`.
Verification: `read-image.spec.ts` (metadata projection, envelope by
shape, reference narrowing, real-execution round trip, rejection
branches incl. non-digest ids), `image-card.client.spec.tsx` (derivation,
row render site dispatching the slot, keyed registration with the
child-slot declaration, empty-slot fallbacks, media-type enum), keyless
snapshots (`read-image-gif` added; read-image/-dimension/-reencode
updated to the `{path}` meta), five injected-defect negative controls,
and a demo GIF recorded from this PR's head through the official
image-capable model.
The Windows coverage lane failed on four cases that assume `http_proxy` and
`HTTP_PROXY` are separate variables. They are one variable there: `process.env`
is case-insensitive, and the launch snapshot folds names for the same reason. A
scenario built on "the user set only the lowercase name" cannot exist.
Two of them assert a contract rather than a spelling, so they now hold either
way: what reaches a child for a scheme the user named is the user's own value
and never the derived one, and a nested install carries the active policy's
value rather than the outer install's published one. Both read over the pair of
names instead of one.
The other two are about the case distinction itself. Neither can hold on a
folded environment, so each asserts what that platform does instead of skipping:
the later entry wins where a preference cannot be expressed, and a diagnostic
names the spelling resolution asked for. Both were verified against the folding
arm rather than reasoned about.
A developer's Clash and a CI runner's squid both export HTTP_PROXY and its
siblings. Now that the harness honors them, an ambient value decides test
outcomes: this PR has already recorded a proxy's 502 page as a snapshot's
expected output, and let a runner's own export stand in for "what the user
exported" in an assertion about inherited names.
A Vitest setup file clears the proxy names before any suite runs, wired into
every configuration that declares a setup. Real-API e2e is cleared too: before
proxy support existed every request connected directly and that suite passed, so
direct is the environment it is known to work in.
`NODE_USE_ENV_PROXY` cannot be cleared this way — Node samples the proxy
environment at process start — and the module says so. A proxy application never
exports it, and the eight names one does export are fully handled: with all of
them set, the affected suites pass.
The wiring is what regresses, so that is what the test pins. Configurations are
discovered rather than listed, because the web suites carry no setup today and a
hand-written list would let one of them gain a setup without gaining this one.
`plugin.spec.ts` kept its own copy of the eight names to guard against the
machine; it never sets a proxy variable itself, so the setup replaces that
entirely. `install.spec.ts` had one assertion waiting on a DNS miss with no
deadline, which timed out once under load.
`installGlobalProxy` claimed each worker thread calls it with a policy its host
passed through `workerData`. Nothing does: the two workers this repository ships
evaluate model-authored scripts and are deliberately left without a proxy URL
that may carry credentials.
`agentBindings` now walks the whole tree to reach a dynamic `import('undici')`,
which pushed the repository-wide scan past the 5s default under the coverage
lane's instrumentation.
Both violations name one of two words in source: an agent construction needs a
binding from the undici module, and the option is a property called
`dispatcher`. Skipping a file that mentions neither leaves 21 of 1597 files to
parse, so the scan runs in ~60ms instead of ~500ms — well clear of the timeout
even instrumented.
Second review pass on the outbound proxy work.
The installed dispatcher was undici's EnvHttpProxyAgent, which reuses the HTTP
proxy for `https:` whenever no HTTPS proxy is present. That is exactly the state
this package resolves after refusing a SOCKS or malformed URL the user named for
`https:`, so the scheme the diagnostic reported as direct was tunnelled anyway.
The dispatcher is now an Agent whose per-origin factory calls `proxyForUrl`, so
routing and `proxyForUrl` cannot disagree by parsing the same list twice.
`childProxyEnv` returned only the names the user exported, which left a child
Node direct whenever the proxy came from `ALL_PROXY` or from cordis.yml — Node's
`NODE_USE_ENV_PROXY` reads neither — and stripped the merged loopback bypass so
the child sent its own localhost traffic to the proxy. A scheme the user named
in either casing still reaches the child exactly as written; one they named in
neither now carries the resolved value, and the bypass list is always the merged
one.
A nested install (the plugin mounted over the launcher's policy) recorded the
outer policy's published values as the user's, then cleared the record on
disposal, so every later child inherited the normalization instead. The record
now belongs to the outermost install and is restored, not dropped.
`web_fetch` read the active policy twice — once to skip address pinning, again
inside the transport — so a disposal landing between the two reads produced an
unpinned direct connection to a host nothing validated. One snapshot now decides
both.
Also: the node:http proxy test asserted a route the engines range does not always
have, and the gate could not see undici bound through `await import('undici')`,
the form this repository actually uses.
The case builds a user environment and asserts a child receives it verbatim,
so a runner that exports its own proxy supplied half the "user" values and
decided the assertion.
The workflow worker no longer receives proxy configuration: it executes the
model-authored script body, and a proxy URL may carry credentials. A child
process now inherits the values the user exported rather than this process's
normalization, so a SOCKS proxy set for curl survives and no HTTPS_PROXY is
invented. `mode: 'off'` installs a direct dispatcher instead of recording a
policy the global dispatcher ignores, and the environment snapshot is taken
before any write so Windows restores the user's values.
E2B picks its proxy from the control-plane URL the SDK will really call, the
OTLP agent honors `exporter.keepAlive`, and a scheme whose own value was
refused stays direct instead of borrowing another scheme's proxy.
verify-no-bare-dispatcher parses the TypeScript AST as scripts/AGENTS.md
requires; it immediately found the `{ dispatcher }` shorthand the regex missed.
A replay must not depend on the runner's network policy, the same reason it
pins its home and sessions root. The harness now honors the proxy environment,
so a runner exporting one sent the web-fetch scenario's fixture request to a
proxy that could not resolve the fixture host and recorded that proxy's error
page. Clear the proxy names in both test-support spawners, from the one list
dsh-http-proxy owns.
Both adapters' inference requests were argued from a code read and, for
DeepSeek, from one product smoke. Drive each shipping adapter at an
unresolvable endpoint through a fake proxy instead, and cover pi-ai's provider
stream rather than only its model discovery.
The VFS packer sweeps module requests statically, so dsh-http-proxy's agent
factory made the preview image unpackable: it names node:https for the SDKs
that post through Node's core HTTP modules, a path the worker never takes.
Mock it the way node:net is mocked rather than hiding the request.
NODE_USE_ENV_PROXY reaches Node 24.0+ and 22.21+, while engines admits 22.19.
Assert the direct connection on an older runtime instead of only the proxied
one, so the seam is executable rather than prose.
Node's built-in fetch ignores HTTP_PROXY, so every harness request connected
directly regardless of what the user exported. Resolve one policy from the
launch environment and install it as undici's global dispatcher, then wire the
four surfaces a global dispatcher cannot reach: web_fetch's pinned transport,
the OTLP exporter's node:http agent, the E2B SDK's own proxy option, and the
environment a child process or worker thread is given.
Each outbound call site carries an egress test that drives its real code path
through a fake proxy; that measurement is what found the OTLP and E2B gaps.
The native Windows containment tests expect the stdout stream to end
shortly after the direct target exits, but 1s was too tight under the
Windows CI runner's cold-start and process-teardown jitter. Use a 5s
bounded race so the assertion still proves EOF eventually arrives
without flaking on slow runner startup.
@@ -20,7 +20,7 @@ The active lifecycle tree is the working inventory: browse its lifecycle/class f
## Classification
Each Agent Note belongs to one path-encoded class from the closed set in `scripts/agent-note-tree.ts`; the classification gate rejects other folders. Adding a class requires updating the canonical set and this section. See the [classification Agent Note](implemented/process/2026-06-20-agent-note-classification.md).
Each Agent Note belongs to one path-encoded class from the closed set in `scripts/agent-note-tree.ts`; the classification gate rejects other folders. Adding a class requires updating the canonical set and this section.
- PickOutcome gains a `{text}` arm; the new scoped bail event `slash/input-insert-text``{text, span}` (the same contract as the other three: draftRev CAS, returning true ⟺ an actual rewrite); facade.insertText goes through setDraft concatenation — zero machine changes.
- Sources get an optional `lexicon?(session)` hook: a synchronous hot-snapshot name roster, with `undefined` = data not warm — zero decoration, never triggering a fetch (the render path stays synchronous and side-effect-free); the paired optional `subscribeLexicon?(session, listener)` hook is the invalidation channel for rolls that change after warm (catalog settles, children spawn/exit). The controller aggregates the rolls into its `lexicon` snapshot store (re-polling on each source notification); sources registered after scope birth are warmed and folded in via the service's live-controller broadcast.
- `decorations.scanTextRefs`: a word-boundary scan of the draft (`/name`, `@name` at line start / after whitespace; `x/name` never hits) against the roster; a hit becomes a `TextRefNode` entity in the Lexical tree (the claim decoration has precedence on the leading-token seat — [the Lexical composer note](2026-08-20-web-composer-lexical-editor.md)); an edit breaking the match shape reverts the entity to plain text.
- Sending is the literal text (no more `<skill>` serialization); on the bubble side MessageItem decorates both shapes (the legacy `<skill>` tag + plain-text tokens).
- `decorations.scanTextRefs`: a word-boundary scan of the draft (`/name`, `@name` at line start / after whitespace; `x/name` never hits; a `/name` token also ends at whitespace or the draft end — the whitespace-bounded shape of the host skill gesture, so `/nfs-hg/xxx` is a path and `/plan。` is prose; the sent-text projection `projectUserText` in ui-primitives applies the same shape) against the roster; a hit becomes a `TextRefNode` entity in the Lexical tree (the claim decoration has precedence on the leading-token seat — [the Lexical composer note](2026-08-20-web-composer-lexical-editor.md)); an edit breaking the match shape reverts the entity to plain text.
- Sending is the literal text (no more `<skill>` serialization); on the bubble side `projectUserText` decorates a plain-text `/name` token only when the same step logged a `skill-invocation` injection for that name — ui-chat's `SkillNameProjector` attaches the step's injected names to the direct message Node, the way the recall projector attaches session labels — so `/123` or a stray `/word` stays plain; a command-input bubble (ui-goal) names its executed command the same way and renders the token as a `command` chip; `@name` tokens still decorate by shape.
- Decoration reactivity: the shell subscribes to the controller's lexicon store and re-scans the document on each roll change, so a roll that settles after the scope-birth prewarm lights existing draft tokens up without any menu interaction or unrelated re-render.
### Per-session provide contributions and the private keyboard surface
Loaded 100 of 6766 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.