Conversation view tabs added a tooltip to every label in 0.2.1-rc.3, including short labels that fit the 88px cell. Estimate the glyph width and only enable the tooltip when the label is expected to overflow; keep the More trigger tooltip.
Refs #7181
Conversation view tabs added a tooltip to every label in 0.2.1-rc.3, including short labels that fit the 88px cell. Estimate the glyph width and only enable the tooltip when the label is expected to overflow; keep the More trigger tooltip.
Refs #7181
The MCP SDK answers resources/list and resources/templates/list for a
server without the resources capability with an empty page, but first
writes a console.debug line to stdout. Under the ACP profile that line
lands in the JSON-RPC stream whenever the model lists resources of a
client-supplied, tools-only MCP server. Return the same empty pages
before calling the SDK, and cover it with a unit assertion and an ACP
stdout regression test.
* refactor(llm-deepseek): share the /models listing machinery
Move the account route's `/models` request and decoding, its
credential-scoped listing cache, and listed-catalog resolution with the
version gate into the shared DeepSeek Messages library, so a second
route can follow the same listing. Names drop the account prefix, the
cache takes each access's endpoint settings with the call, and it gains
`invalidate()` for a forced reload. `isBuiltInCatalog()` recognizes the
catalog an uncustomized `models` setting resolves to.
The account route keeps its behavior; only its imports and the
override diagnostic wording ("model listing") change.
* feat(llm-deepseek-api-key): follow the official /models listing
While `models` equals the built-in catalog and the Messages origin is
https://api.deepseek.com, `deepseek-official` serves the official
`GET /models` listing, authenticated with the API key in `x-api-key`.
Loading, refresh, fallback, merging, and the client-version gate match
the account route: the first access waits up to `modelsTimeoutMs`, a
listing older than `modelsMaxAgeMs` refreshes in the background on the
next access, a failure keeps the last good listing or the built-in
catalog, and listed models this build cannot serve are refused before
network I/O.
A customized `models` list replaces the catalog and requests nothing.
Switching between a customized catalog and the listing discards the
cached listing, so Restore defaults reloads it on the next access.
Suites that boot the route on the official endpoint now answer the
listing themselves instead of reaching the network.
* docs: describe the official route model listing
Document when `deepseek-official` follows the official `/models`
listing, its timing fields, customization precedence, and the forced
reload after Restore defaults in the API-key README, the shared
transport README, and the provider guide. Record the decision in a new
Agent Note, keep the account note's statement of where the shared
listing code lives current, and regenerate the configuration catalog.
* test(llm-deepseek-api-key): pin the once-per-listing hidden-entry diagnostic
* docs: regenerate module and event graphs for the shared listing
* fix(llm-deepseek-api-key): report credential failures on the model request
On the official endpoint, `connect` waited for the listing view, whose
credential lookup rethrew a malformed key or a credential-store failure
before the agent loop recorded the prompt. A failed lookup now resolves
the connection from the current listing view, so `resolveAuth` reports
the failure on the model request and the turn keeps the prompt and the
failed attempt. A caller abort still rejects.
* docs(llm-deepseek-api-key): name which hidden listing entries are logged
* test: keep unit suites off the official model listing
Suites that boot the DeepSeek routes with a dummy or inherited key now
request `GET https://api.deepseek.com/models` on the official endpoint.
A unit-lane setup file rejects that request at once, so such a suite
serves the built-in catalog instead of reaching production or waiting
out the listing deadline on a network that never answers; suites that
exercise the listing answer `fetch` themselves. Other requests pass
through unchanged.
* feat(llm-deepseek): read tool_update from the model listing
Both DeepSeek routes now take a listed model's Messages tool update mode
from `api_capabilities.anthropic_messages.tool_update`, alongside the
system prompt mode. `in-history` and `addition-only` replace the
bundled mode; any other value reads as `leading-only` and removes it.
An absent or `null` mode keeps the bundled one, so a listing that does
not declare the field leaves `deepseek-flash` on `addition-only`.
`system_prompt_update: null` likewise keeps the bundled mode now; it
previously read as `leading-only`, which a serializer writing empty
values as null would have turned into a cache-unfriendly downgrade.
* fix(llm-deepseek): sync the built-in context window with the official listing
The official `/models` listing declares a 1,048,576-token context window
for `deepseek-flash` and `deepseek-v4-pro`; the built-in catalog said
1,000,000, a decimal-versus-binary mismatch. Both built-in entries now
carry the listed value, so offline starts and listing failures size the
context the same way the listing does. The Models page shows the exact
count for these rows, since 1,048,576 has no decimal K/M spelling.
`defaultContextWindow`, the fallback for model ids the catalog does not
describe, stays 1,000,000.
* test(llm-deepseek-api-key): reject the stubbed listing request with an Error
* test(cli): keep the built SDK smoke off the official model listing
The SDK profile's initialize now resolves the deepseek-official catalog,
which requests GET https://api.deepseek.com/models on the official
endpoint. With the smoke's fixture key that request answers 401 and the
route warns on stderr, so the test's empty-stderr assertion failed in the
e2e and snapshots lanes. Point the run at a loopback mock and assert it
received no model call.
* test(llm-deepseek): compose the malformed-key turn without the native executor
release/dsh-0.2.1 predates @deepseek-ai/dsh-tool-executor-native; its tool
runtime executes tools itself. Compose the official-route turn the way
account-routing.spec.ts does on this branch.
* fix(llm-deepseek-api-key): gate nothing when the credential lookup fails
The listing cache switches scope only after it reads a credential, so a
failed lookup left `connect` serving the previous credential's listing.
After a key whose listing refuses a model was replaced by a malformed one,
`admit` refused that model with CLIENT_UPGRADE_REQUIRED before the turn
recorded the prompt, instead of `resolveAuth` reporting the credential
error on the request. Serve the built-in catalog without listing gates
when the lookup fails.
* test(llm-deepseek-api-key): pin the once-per-listing diagnostic across request preparation
The hidden-entry diagnostic test repeated the same discovery assertion to
prove a second pass reports nothing new, which read as a duplicate. Assert
the single warning after the request preparations instead; they resolve
the same listing, so dropping the per-listing deduplication still fails
the test.
* docs(llm-deepseek-api-key): note that a list matching an earlier built-in catalog counts as customized
Customization is judged against the running version's built-in catalog,
so a saved `models` list equal to an earlier version's catalog stops
following the listing once the built-in catalog changes, until Restore
defaults. Record the consequence in the Agent Note and the README's known
limitations.
* fix(ui): separate navigation pins from selection and show menu icons
* fix(ui): simplify More menus and fold conversation views on resize
* test(ui): fix navigation CI regressions and refresh catalogs
* refactor(ui): render sidebar panel icons directly
* fix(ui): keep Chat visible and stop persisting navigation pins
* refactor(ui): drop unreachable pin validation from in-memory stores
* feat(analytics): track navigation entry clicks, preset switches, and new-session sends
Rebuilds the click events of #6600 on the region-owned More menus in
release/dsh-0.2.1. Exposure events, creator_mode_enter, and the
ui-workspace onOpened observer are not included.
- sidebar_menu_click gains position plus the new_session and more entries:
New Session (sidebar button, brand, titlebar), Plugins and Automation
rows, the left sidebar More button, and entries opened from its menu.
- more_menu_item_click records Browse other plugins and Create your plugin
per region.
- agent_preset_switch reports applied or unbound staged picks; the seat
controller now returns an explicit apply outcome.
- code_work_view_toggle reports a successful change of the setting.
- send_button_click adds is_new_session and the Session's preset category.
Co-authored-by: imccyu <[email protected]>
* test(analytics): cover navigation, preset switch, toggle, and send analytics
Co-authored-by: imccyu <[email protected]>
* docs(analytics): document click events and regenerate catalogs
Co-authored-by: imccyu <[email protected]>
* test(ui): reuse the developer tools row accessor without a new unknown cast
Co-authored-by: imccyu <[email protected]>
* fix(analytics): classify preset switches by preset id only
agent_preset_switch now maps the preset id the same way send_button_click does: cordis is creator, ptc, minimal and standard keep their ids, and any other id is other; a published preset name no longer changes the category.
Co-authored-by: imccyu <[email protected]>
---------
Co-authored-by: dingyifan <[email protected]>
Co-authored-by: imccyu <[email protected]>
* fix(ui): avoid tooltip trigger overlap when a side fits
* feat(ui): add navigation entry customization
* fix(test): keep tooltip geometry independent of JSX types
* fix(ui): align customization catalogs and integration fixtures
* test(ui): complete customization lifecycle and release integration coverage
* fix(ui): return entry discovery to the installed plugin list
* docs: refresh module graph for entry customization
* build: refresh workspace links for the moved entry customization package
* docs: regenerate catalogs and pairing hashes for the moved entry customization package
* test(web): cover default-loaded entry customization in the Web e2e
* test(ui): describe entry customization as a browser plugin in the host entry spec
* build: order entry customization publication files as workspace constraints require
* test(release): drop the moved entry customization package from the experimental family list
* style(ui): use the shared focus ring color on entry customization triggers
* test(web): re-record goldens for the default-loaded More navigation menus
* test(web): add the More conversation views trigger to the remaining CI-reported goldens
* docs: record region-owned pins for plugin entry customization
* refactor(ui): rebuild entry customization on region-owned pins and More menus
- Each navigation region (left sidebar, Conversation header, right sidebar
guide) persists its own unpinned list under
dsh.slot.manual-order.<list slot key>.v1, filters its own launchers, and
draws its own More button and Menu around its actions slot.
- ui-entry-customization only registers the shared menu rows (entries with
pin buttons, Browse other plugins, Create your plugin) into the three
actions slots and keeps no state.
- Remove ctx.layout.entryVisibility, the plugin store, left-sidebar drag
reordering, and agentPresetNavigation; ui-layout and ui-agent-preset match
release/dsh-0.2.1 again.
- Keep the release <nav> and Conversation tab-bar visibility conditions.
- Move the More glyph into ui-primitives as IconAppsAddOutline{Regular,Medium}.
* docs: compile the entry customization owner props example
* docs(ui): describe the wrapping Conversation tab row in the header comment
* fix(ui): return New Session to the Workspace blank and its unsent draft
- New Session (startSession without options) takes the blank-reuse path
again instead of creating a fresh Session on every call, which hid the
previous blank and its unsent draft since a5c9c2aaeef (#5775).
- A request that names an Agent preset skips blanks listing a different
preset, so Creator entries no longer take over an ordinary draft.
- Record the change in a v0.2.1-rc.2 upgrade guide; the Host refresh of a
stale loaded blank stays deferred (see the Agent Note).
* docs: regenerate the Cordis inspect catalog for New Session blank reuse
* fix(ui): keep the selected entry visible while unpinned
- Left-sidebar rows and View tabs keep the selected entry, with aria-current or aria-selected, until another entry is selected.
- Entry menu Creator action takes no region argument; the registration's region selects the prompt.
- Document the 2px panel-row and 32px View-tab gaps; drop commit hashes from the New Session Agent Note.
* fix(ui): leave a blank of another preset unstaged when starting Creator
startCreatorDraft stages cordis only when the main blank lists no preset or already lists cordis; navigation opens a separate Creator blank otherwise, so a Standard draft blank keeps its preset.
* feat(ui): choose whether a draft prompt replaces, appends to, or keeps an existing draft
- DraftInitializationOptions gains whenDraftExists ('replace' | 'append' | 'keep', default 'keep'); clearPreviousDraft stays and maps to 'replace' only when whenDraftExists is omitted.
- Append adds the prompt as new paragraphs after the existing text and references; attachments stay in every mode.
- The three Create your plugin entries append their regional prompt.
* test(web): align e2e with New Session blank reuse and the visible selected entry
* refactor(ui): share the draft paragraph writer between setDraft and appendDraft
---------
Co-authored-by: Yif <[email protected]>
Co-authored-by: imccyu <[email protected]>
* fix(web-search-deepseek): disable thinking for auxiliary search requests
The provider discards the generated text of the auxiliary Messages turn,
so reasoning tokens have no consumer. DeepSeek enables thinking by default,
which adds latency and output tokens to every search.
Fixes#6099.
* feat(web): add opt-in traditional Search and Fetch APIs
* test(web): verify API settings persistence and refresh Session evidence
* fix(web): require an explicit Search API base with its route prefix
* docs(web): document direct Search and Fetch API decisions
* fix(web): keep Messages search default behind the new Web Search API switch
- Rename useTraditionalApi/traditionalBaseURL to useSearchApi/searchApiBaseURL and
label the switch "Use the new Web Search API"; Messages search stays the default.
- Default the Search / Fetch API base to https://api.deepseek.com/v1/tools.
- Authenticate the Search API with an API key only: DeepSeek Account tokens
returned an empty HTTP 404 on /v1/tools/* when probed on 2026-10-16.
- Remove the count setting, which tool-web's maxResults always overrides, and
the undocumented DeepSeek-API-Version header.
- Name the active API's base URL field and the current settings path in
endpoint guidance; re-record the endpoint guidance Session.
- Keep WebFetchResult.statusCode required: DeepSeek Fetch reports HTTP 200 and
drops the page title, so the fetch-metadata widening and its upgrade guide
are removed.
* fix(web): accept account sign-in on the live Search API
The public Search and Fetch routes went live on 2026-10-16 and accept the
DeepSeek Account token, so account-route Sessions send it to the Search API
as they do for Messages search, and the settings copy no longer says account
sign-in does not apply.
Record the live behavior in the README and Agent Note: summaries of
1,000-2,300 characters, an HTTP 500 for an unresolvable Fetch target, and
migration notes for gateways without /v1/tools routes. Test doubles now use
the live response fields and RFC 3339 offsets.
* feat(web): configure Search API result count and send the triggering question
- Add web-search-deepseek.count (1-50, default 10, the service default) and
show it as "Results per search" in Search API mode, where "Max searches per
request" applies only to Messages; a smaller request maxResults still wins.
- Send the initiating Session's latest human prompt as ori_question, with
whitespace collapsed and cut to the API's 128-character limit; blank or
absent prompts omit it, and sendOriginalQuestion: false disables it. The
settings hint states that the question is sent.
- Match the web search double on the request's query field, since
ori_question now carries every query in the prompt.
* fix(client): unify web search endpoint override controls
* feat(web): configure registered search and fetch providers through shared settings
* feat(web): include Exa and Perplexity in provider settings
* test(web): cover provider settings branches and refresh plugin snapshots
* revert(web): drop the provider dropdown, schema-driven form, and Exa/Perplexity settings
Revert aed8c5cb609, d88c47dcd80, and 036a5a337d3. The Web search settings
page returns to the DeepSeek toggle; the DeepSeek Search and Fetch API split
is reintroduced as one plugin in the following commit.
* feat(web): move the DeepSeek Search and Fetch APIs into web-deepseek-api
The new web-deepseek-api plugin registers deepseek-search-api and
deepseek-fetch-api over one credential and API base, and exports the
authentication and redirect-rejecting transport that web-search-deepseek now
reuses for Messages search only. The web service reads searchProvider and
fetchProvider for each operation, so the Web search settings switch selects
both APIs together on save and restores Messages search with local HTTP fetch
when turned off. The switch hint links to the DeepSeek search and fetch guide.
* refactor(client): keep the web search endpoint fields on the shared override controls
Drop the always-visible reset and the inherited-address placeholder from the
Web search page, restoring ui-primitives to its base. Each endpoint field shows
the selected section's effective value and offers Reset to default only for an
override, like every other settings field.
* refactor(web): keep Messages search independent of web-deepseek-api
Restore dsh-web-search-deepseek to its own credential and transport code,
keeping only the disabled-thinking request and the current settings guidance,
so the Messages plugin no longer depends on the Search and Fetch API package.
dsh-web-deepseek-api keeps its transport internal; the regions that repeat the
Messages helpers carry jscpd:ignore markers, with the rationale recorded in the
Search and Fetch API Agent Note.
* refactor(web): keep the web-deepseek-api request type module-private
---------
Co-authored-by: LegGasai <[email protected]>
Pass execution-specific phrases directly to the run_code flavor factory and render SDK instructions from explicit program-format branches. Keep all emitted text byte-identical while removing coupling to exact shared prose. Validate the owning tests with full scoped coverage, focused session/SDK replays, and a complete build.
Set the required normal retry mode so the Loader accepts the full provider patch, and type-check the policy before JSON serialization. Assert the effective request settings and successful warm-up turns, report failed attempt causes immediately, and inherit suite retries for transient external API failures.
While the right sidebar is collapsed, its docked hosts are translated past
the frame's right edge and widen the frame's scrollable overflow. Outside
macOS, PageUp in a text field moves the caret and reveals it against the
start edge of every scrollable ancestor, so the `overflow: hidden` frame
scrolled sideways and the whole UI shifted left.
Clip the panel's overflow. `overflow: clip` does not make the panel a scroll
container and does not clip the fixed-position floating panels, whose
containing block is the viewport; the DockLayout ancestor contract now says so.
Fixes#6341
The page clock failure depended on the real-to-fixture distance modulo
2^32 ms, so a regression run on today's date alone passes against the old
fixture for half of every period. The test now shifts the Host clock across
eight phases of that period; the page.clock fixture fails every phase,
either with stopped page timers or with a Date that does not advance.
pinHostClock replaces Date.now in the Vitest worker, where Playwright's
server also runs. page.clock.setFixedTime therefore recorded the fixture
day, and after a navigation the page clock replayed the gap to the real
day. The resulting negative timer delay was truncated to a 32-bit integer
and, from 2026-10-15T13:45:20Z, became a delay of weeks that stopped every
page timer, failing the steering and seeded-history suites.
pinBrowserClock now replaces only the page's Date so it reads the fixture
day while native timers and animation frames keep their real schedule; a
regression test navigates twice with both clocks pinned.
State why every plugin manager operation drops every cached catalog, and
list two remaining gaps among the known limitations: on-disk SKILL.md
changes forward no event to an open session, and draft skill highlighting
waits for the next catalog fetch after an invalidation.
Address the ds-review-bot round on #6272: the README summary, completion
notice, token effect, and Known Limitations lines no longer call the
payload the final answer; the empty-result error reads "without an
answer"; the ordering spec also interleaves commentary; the partial_answer
branch cites its upstream source.
The browser skill source cached each Session's catalog until a preset
switch or connection reset, so an open Session kept listing the skills of
a plugin switched off on the Plugins page and missed those of a plugin
switched on. Clear every cached catalog on the forwarded
plugin-manager/changed event, which the Host emits after the operation's
reload.
Fixes#6268
The reasoning-level editor hardcoded pi-ai's seven levels, so a level a
pi-ai upgrade adds would be missing from the page and a stored key outside
the list would be dropped by the next edit. The levels now come from the
llm-pi-ai namespace schema, the key union of reasoningEfforts, read the
way protocolChoices reads the protocols, and travel explicitly from the
Models section and the provider editor through the model list to each row.
A stored key the schema does not list keeps its row and stays selectable.
The reasoning control now appears wherever that list is non-empty instead
of being inferred from a missing catalog provider.
The goal domain e2e runs over the shipped headless profile, whose base
bundle mounts goal-round-driver. GoalService.create() arms the goal the
fixture seeds, so the driver queued round 1 when the task turn went idle
and the test's headless driver disposed the context mid-round. The
assertions held only while the aborted round's turn/end missed the log
before disposal; an extra await in the agent loop's pre-step path (#6024)
let it land and the test failed deterministically.
Disarm the goal right after creating it. Activation is process-local, so
the persisted goal/change snapshot is unchanged and the driver starts no
round. Also assert a single turn/start, which records a started round
even when disposal aborts it before its turn/end is persisted.
The reasoning-level editor kept the levels it switched off, its row order,
and typed text in component state, while the model list keys rows by
position, so deleting a row handed that state to the next model: turning
the survivor's switch on wrote the deleted model's levels into it. The
editor now renders only from the model; rows follow the stored order,
switching on always starts from the defaults, and only the focused field
keeps its typed text until the field is left.
The reasoning check moves out of the shared model validator into the two
cards whose rows edit it, so DeepSeek and catalog routes are untouched,
and it now mirrors every pi-ai refusal: a valueless field, a missing value
on a level besides off, and an empty value. A refused value marks its
field, and leaving that field repairs it with the level name.
The Providers guide now points custom model API users to the page before
the YAML, and both package READMEs describe the new behavior.
Codex app-server 0.162 adds `partial_answer` to the agentMessage phases
(stable answer text that later assistant output or tool calls may follow).
The wire treated every phase other than final_answer, commentary, and null
as fatal, so one such message failed the whole delegation. Record the
latest partial answer and select final, then partial, then unphased text;
unknown phases still fail the run. The pinned @openai/codex 0.153.4 payload
never emits the phase, so behavior with the shipped binary is unchanged.
The help tooltip now says that Off needs no value but keeps and sends a
value already written in the configuration, matching the disabled field
that shows it. Both READMEs add the rule that a custom value carries over
only between two thinking levels, so changing a row to or from Off resets
it. The Models settings e2e header comment is rewrapped.
The persisted draft now records the leading command chip's claim token
(DraftSnapshot.command). Restoring the draft asks the trigger pipeline's
new resolveClaim, which polls the optional InputTriggerSource.matchClaim
hook: the command source awaits its catalog and answers only a
leadingInput claim, never opening a popup, running an action, or
executing the command. The input rebuilds the chip once the claim
resolves, only at the revision the restore produced and merged into the
seed's history entry; otherwise the literal token stays and Enter still
adjudicates it.
A model row on a hand-declared pi-ai route now edits its reasoningEfforts
from the Models settings page instead of only through cordis.patch.yml.
A switch turns the field on or off; when on, each row pairs a level
select with the API value sent for it, which defaults to the level name.
Off only turns reasoning off, so its value field is disabled and a value
already stored for it is kept. Saving refuses a list without a level
besides off, and a help tooltip beside the label explains the values.
Catalog routes and the DeepSeek card are unchanged.
Refs #6260
The packer images the web template bundles, every optional bundle, and an installation manifest instead of a dumped composition, and the Worker boots that profile through app-boot like dsh web. Package-local lazy client chunks move into content-addressed archives that the VFS fetches on first asynchronous read; client-modules reads chunks asynchronously.
The Worker replaces node-addon-require-builtin with Node v24.18.0 internal-module interfaces and reports Node 24, so plugin metadata, the Cordis Loader, and HMR reach one cascaded loader backed by the Worker loader. Resolution failures carry Node error codes, the empty load cache lets HMR apply profile changes, and every optional bundle switches on and off live. Platform stubs throw ERR_FEATURE_UNAVAILABLE_ON_PLATFORM.
Copy each plugin's manifest repository as its web page and package
directory, converting shorthands and Git URLs with hosted-git-info. Parse
options and generate help with commander.
Generate a validated format-1 plugin market index from a list of npm package
names and https Git repository URLs, without running package code.
- List every non-deprecated npm bundle version, and Git versions at release
tags and the default branch, each with its engines.dsh range.
- Read display fields, keywords, homepage, and icons the way DSH reads
installed plugins; inline small icons up to the format's 32 KiB limit.
- Cache results per immutable input and rewrite output only when it changes.
A Discover install passes the entry's localized title and version with its
spec. Every install dialog screen names the plugin and version, and shows the
spec, such as a pinned git+https URL, below in smaller monospace text.
The Installed tab panel now wraps its loading, unavailable, and empty states.
Custom plugin markets joins the bundles the More page holds back until the
profile enables them, and the repository field and its directory rule are
documented.
Indexes may give each plugin an optional repository: the web URL of its
source repository and the package directory inside it. The Discover entry
page links it as Source code, and the plugin_market tool returns it.
Read configured plugin market indexes on the Host, search them with fuzzy
multilingual matching, and install a chosen version from the Plugins page.
- dsh-plugin-market-index validates format-1 indexes and builds install
arguments; inline icons are capped at 32 KiB.
- The pluginMarket service fetches configured http(s) URLs, parses bodies in
a Worker Thread, caches documents, and pages filtered, ordered results with
installation status and engines.dsh compatibility.
- Discover is an opt-in experimental bundle; the Plugins page gains a
plugins.tab slot and confirmed installs.
- The plugin_market tool lists indexes, plugins, and versions for Creator mode.
- Plugin compatibility reads engines.dsh before DSH peer dependencies.
A Client plugin loaded after the page starts fetches its bootstrap once. The Host now registers that route before starting the Worker and answers once the Worker is ready, so enabling Developer Tools on a running page opens the bottom DevTools without a reload. A failed start answers 503.
When the platform cannot run the Worker (ERR_FEATURE_UNAVAILABLE_ON_PLATFORM), the Host warns once and stays active with only the 503 bootstrap route; serve-static and open load after the Worker starts. Every other startup failure still fails the fiber.
verify-package-dependencies now covers every packages/*/* manifest, experimental included, and reads each package's tsdown build entries so Worker and child-process entries count as Host runtime sources. Experimental Client packages no longer declare browser and type relationships as peers.
Package modules that other code imports must not suspend evaluation at module scope. A syntax-aware gate scans packages/*/*/src and excludes only executables (a shebang or a package bin target). The SenseVoice worker, the SSH helper entry, and the Inspector worker entry now run an explicit main().
The provider's Linux decisions now live in LinuxScopeSelector, a state machine
over injected observations: the deep probe, the user-manager listener, the
manager confirmation, and the systemd-run binary identity. Each decision
carries its reason and probe trigger, the runtime logs it at debug level, and
table-driven scenarios cover every transition.
- A positive verdict probes again when the systemd-run found through PATH
changes device, inode, size, or modification time, so an in-place upgrade
past v254 cannot reuse options that no longer keep argv literal.
- The listener is read before the probe, so a manager that starts during a
failed probe counts as a new listener.
- The kernel socket table is decoded as UTF-8, matching runtime directories
outside ASCII.
- LinuxScopeNotStartedError drops its unused code field.
A cached positive verdict let a hung user manager that still listens fail every
later command, and a cached negative verdict ignored a manager that started
later until the provider restarted.
- A scope launch whose bootstrap never consumed its launch request now fails
with LinuxScopeNotStartedError, and the runtime clears the verdict it
launched under, so the next eligible spawn probes again and records the
fallback when that probe fails. A generation counter keeps launches from a
superseded probe from forcing another probe.
- After a negative verdict, the runtime records the /proc/net/unix listener on
$XDG_RUNTIME_DIR/systemd/private and probes again only when a different
listener, meaning a new manager instance, appears.
The real-socket test rejects on a listen error instead of hanging, the table
fixture comment names the accepted-connection row it models, and the remaining
linux-scope module mocks provide confirmLinuxManager. The README notes that
the socket-table read matches only listeners in the same network namespace and
costs time proportional to the table size.
systemd closes the private listening socket on shutdown without unlinking its
path, so the socket file outlives a manager stopped in place. The fast path now
reads /proc/net/unix, where the kernel lists a bound socket only while a
process holds it open, and accepts the manager only when a process listens on
$XDG_RUNTIME_DIR/systemd/private; otherwise the systemctl query decides. The
scope startup error names an unreachable manager as the cause, and the spawn
test again asserts that Windows launches never reach the fallback.
Without a per-spawn check, a user manager that stops after a positive probe,
such as after a logout without lingering, made every later scope launch fail.
Each eligible spawn now confirms the manager: its socket file at
$XDG_RUNTIME_DIR/systemd/private disappears when the manager stops, so its
presence answers with one stat; otherwise the restored systemctl --user show
query decides and the spawn falls back while it fails. A crashed or hung
manager that leaves its socket file still fails the scope launch with the
existing restart hint.
The probe always passes --collect, which systemd-run rejects before v236, so those hosts use the fallback regardless of the --expand-environment=no retry.
With the probe cached for the provider lifetime, a user manager that stops
after a positive probe makes every later scope launch fail. The startup error
now says to restart the provider to probe native containment again. The README
no longer lists older user-systemd among fallback hosts and notes the second
probe attempt for a systemd-run that rejects --expand-environment=no.
The README says the provider also keeps a failed or timed-out first probe and
names the user manager as reachable rather than live, since it is checked once.
The probe cache documents why no later spawn repeats the probe, and the spawn
test now starts two Windows commands to match its per-spawn Job recheck title.
systemd-run before v254 rejects --expand-environment=no, so the scope probe
always failed and those hosts used the weaker fallback. Its scope mode never
expands argv, so the probe retries without the option only when systemd-run
reports it as unrecognized, and scope launches reuse the probed options.
systemctl before v252 rejects --kill-whom. Use --kill-who, the older
spelling, which newer systemctl accepts as an unambiguous prefix.
Every Linux spawn() and spawnTerminal() ran a synchronous spawnSync probe
before launching the command: the full systemd-run scope probe until it first
succeeded (a failed verdict was never cached), then a systemctl manager recheck.
spawnSync blocks the host event loop until the probe exits, so hosts without a
usable user manager paid the full probe on every grep, glob, and shell command.
Keep the first verdict, positive or negative, for the provider lifetime and
drop the per-spawn manager recheck.
Plugin Manager hardcodes feature previews by package name, like its
experimental bundle lists, instead of reading a package manifest field.
The DSH badge skill bundle shows an English or Chinese illustration
below its description. Client bundles embed imported SVG, PNG, and
WebP files as data URLs through the shared tsdown preset, which
replaces the account package's own asset loader.
Co-authored-by: ZiyaZhang <[email protected]>
conversation.hero.options seats compact Session options at the right end of
the New Session row, after the workspace and agent-preset controls. It is a
session-scoped list slot rendered only while a Session exists, so plugins can
add choices such as a worktree toggle without displacing the shipped controls.
Reject case variants of shipped profile names, detect a concurrent winner by
the target's state instead of errno, warn instead of failing when staging
cleanup fails after a successful replacement, and cover the restore paths with
deterministic tests. Update the profile Agent Note, reference recovery path,
and upgrade-guide migration steps.
Repeated rescue runs such as `dsh rescue --from-default-profile web` failed
because the target profile already existed. The option now assembles a fresh
template copy in a staging directory, moves it over the target, and deletes the
previous profile; a failed move restores it.
A revealing row that ran on its own CSS transition and was then taken
over by a batch entered the landing twice: the measured starting layout
held the height it had already reached, and the growth amount added its
full height again while the batch restarted it from zero. The extra
height raised the tail target above the real floor and stayed as bottom
room. Rows revealed while a batch ran kept their own transitions and were
taken over this way by the next batch.
- A row that starts revealing while a batch runs waits at zero size like
a held closing row; the next flush drives it in a batch or starts its
own transition when nothing closes.
- A batch continues every member from its current size: closing rows
shrink from the height they have now, and revealing rows grow from the
height they reached, so only the remaining growth enters the landing.
A row that started closing while a fold batch ran kept its own CSS
transition and outlived the batch. During that window following moved to
the floor while reclaimBelow held still, so the bottom room was followed
in full instead of being consumed by the content that arrived, and it
stayed under the reader afterwards.
- A row that closes while a batch runs keeps its size without a
transition until the batch settles; the next batch starts at once and
drives it. Without a driver at that flush the held row finishes at once.
- A batch takes over a closing row at its current size and finishes a
row that has no size left.
- The tail landing reads the committed layout when it finishes: it lands
at the larger of the target and the real floor and keeps only the room
that target needs.
- A tail request counts only for closing rows that can start a batch,
using the same membership rule as the batch, and detaching clears it.
- A batch keeps the reader landing while reading movement awaits
sampling, so an unsampled scroll away from the tail is not undone.
- Reader scrolling during a reader landing moves the reference row's
recorded position, so the final correction keeps only layout changes.
- The blank-area matrix releases a partly opened world, fails a case on
cleanup errors, mounts only the provider catalog for the Turn that fails
before its model call, names its gap as a layout gap, and labels the
steering case as a no-fold control.
- The note records the pending-sampling rule and the opacity of a row
that joins the travelling set after fading.
contextBreakdown kept one entry per retained surface node, so its
checkpoint grew with the message count (7.13 MB across 836 local Sessions,
up to 103 KB for one) and every cached Session listing validated it. The
state now holds the surviving system prompts, one surface total, and at
most one shadow-price claim. Appends add their price; a replacement
subtracts the adjacent compaction/summary or compaction/prune price and
drops the system prompts its shadowedSeqs name; a system-prompt rewrite
subtracts the retained prompt price. Other unmetered replacements fold at
zero delta, as in contextPressure. State version 6 discards positional
rows. Folding all 836 local logs with both versions gives identical
figures after every event; retained state drops to 110 KB.
Every `@` candidate query labeled each listed Session through three
service lookups, and each lookup and method read through a Cordis service
proxy allocates wrappers, so the labeling step grew with the Session count.
The resolver now resolves sessions, projections, and the projection cache
once per call, indexes attached Sessions in one map, and binds
cachedSnapshot once for cold Sessions.
list(), the root encoding check, and historical corpus fingerprints read
project directories, Session directories, headers, and stats one at a time.
On a network filesystem every listing paid the summed round trips. These
reads now run through one p-limit limiter per backend (listConcurrency,
default 3), shared by concurrent listings; results and the first reported
failure keep the sequential order.
The queued and steer cases held the model call at a global call index and
waited without bounds, so any shifted or missing call stalled the case
until the 240 s test timeout. The hold now targets the first call in the
case's Session that streams the case's marker text, every wait has an
explicit timeout whose error lists the observed calls and the Session's
latest events, scroll settling is bounded, and the cases use the web
lane's default timeout.
- A tail request counts only while a fold batch runs or is scheduled,
so rows still closing on CSS transitions cannot strand own input.
- A reader-content-still landing adds the reader's own scrolling between
frames to the position it keeps still.
- A newer tail request that takes over a running batch recomputes which
closing rows travel with the content.
- Symmetric cancel and resume guards; Set-based travelling bookkeeping.
- The blank-area matrix holds the subagent continuation route for child
sends, writes per-frame evidence only on failure, and labels the parent
message case as a no-fold control.
- The group-tree note now links the single fold motion for input without
an echo on this page.
Folding on the next message reserved the whole closing height as bottom
room and kept scrollTop whenever the input had no transcript echo on this
page: a message from another tab, a queued message delivered after the
previous Turn, or a send from a subagent child in the right Sidebar. A
reader below the fold was left on an empty viewport.
Every batch of closing rows now runs on the viewport's frame-clock driver.
It lands at the tail for this client's own input or a following reader and
otherwise keeps the reader's content still, installing only the room the
landing lacks. Subagent child sends register a local echo and pass its
request id to subagents.prompt, so the child's recorded message retires it.
With HMR, installing, enabling, disabling and removing bundles take effect live. Each profile recomposition compares
the loaded modules below the profile directory and its linked targets with disk, stops the plugins whose files
changed, and recomposes the profile through app-boot's recomposeProfile(): the rows the next generation drops are
removed first, the latest runtime resolution is published once, and the generation is applied and judged. Afterwards
HMR evicts the modules below the mappings the recomposition dropped.
- app-boot: recomposeProfile(); PluginPackages.resolution; ResolutionRestartRequired; the resolver accepts in-place
version changes and relinks, and relocates Node's cached link real paths.
- plugin-manager: with HMR, installBundle refuses an installed package (already-installed), so an upgrade is a
removal and an installation; every restart-required result carries a restartReason; removal uses the registry the
package was installed from.
- ui-plugin-manager: the Official Update removes and then installs; restart prompts name their reason.
* fix(schedule): keep timing and execution notes out of task titles
* fix(schedule): avoid repeating task title guidance
* fix(schedule): keep title guidance examples in English
---------
Co-authored-by: ZiyaZhang <[email protected]>
The macOS chooser ran AppleScript's `choose folder`, whose panel belongs to an
osascript helper that has no application menu. A native panel resolves
⌘C/⌘V/⌘A/⌘X as main-menu key equivalents rather than as behavior of the
focused field editor, so Go to Folder, Search, and Tags could not be pasted
into even though the panel's own ⌘⇧G worked.
Run one JXA script that installs the standard Edit menu and then opens
NSOpenPanel with the previous configuration; untargeted items let AppKit
enable them against the responder chain that owns the panel's field editor.
The prompt stays argv data, and a cancel now prints an empty line, so the
stderr "User canceled" sniffing and its helper are gone.
The README pair re-records its consistency record for the two edited sections.
Re-serializing a document with a foreign integer beyond 2^53 no longer rounds it;
the reload warning is tested; the GITLAB_TOKEN test no longer depends on the host
environment; README and JSDoc describe the tolerated top-level keys.
Switching on an experimental bundle from the More page left it only on
that page, so a plugin the profile had enabled was missing from the main
Experimental plugins group. Both lists now read one order over the
offered bundles: the main list holds the always-held ones plus the
held-back ones the profile enabled, and a pinned bundle keeps its
More-list position. A bundle's details opened outside the lists return to
the list that shows it.
The root-session descendant-count dropdown registered at order -30, which put
it ahead of the preset label at the start of the header actions band. Order 10
places the catalog between the preset label (-10) and background tasks (20),
after Team navigation (-20), and off the list slot's default 0 that unordered
registrations share.
The web e2e assertion and header golden that pin these contributors' relative
order move with it.
Enable image reading and automatic context compaction in Minimal GUI and SDK profiles, with /compact in the GUI and per-tool filesystem configuration. Include image-budget recovery, cancellation handling, documentation, and updated runtime snapshots.
- office-render: report inspection results in text and never link or embed
removed previews. Desktop and Web replies otherwise embed deleted preview
images, because their file-reference guidance encourages inline images.
- office-export-pdf: validate with the command result, nonempty output, and
%PDF- signature; do not search for PDF readers unless the user requests
deeper checks. The bundled runtime has none, so the old conditional made
the model probe system Python and command-line tools.
- Assert that the PDF export e2e does not probe PDF reader tools.
- Replace stale "structural checks" wording in the Desktop Office plugin
JSDoc and the skill package description.
The eight snippets in this note are before/after sketches: two import the
Remote source modules this change deletes, and the rest are fragments without
their surrounding context. They were never type-checked, yet each one counted
against the repository-wide opt-out ratio, which now sits above the 50% ceiling
that `doc-typecheck:contracts-ready` enforces.
Label them as text so the ceiling reflects blocks that could be checked.
The Typert host program analyzes packages/api/session-controller/src/commands.ts
on a clean checkout, where only tsconfig paths resolve
@deepseek-ai/dsh-client-file-upload/types; without the mapping the dependency
policy gate fails with TS2307.
The caller-connection work — forwarded-event opt-in, physical socket
ownership, and the Node/MCP artifact-only consumer fixture — moves to a
stacked PR on top of this one. This branch now changes only how Remote
definitions are generated (Typert artifacts, lazy method info,
consumer-selected input strictness, tool-schema projection) and how the
existing Registry consumes them.
Add a placement table to the Definition section: which `groupTree`
position a Chat Node declares for an opening human input, another Turn
input, a Turn notice outside folding, an independent folded row, and
ordinary process content, with links to the contract and the built-in
positions, and a pointer to `exclusive` claims.
`observedInputs` now reads every `input`-position Node, and a Node's
payload is not guaranteed to be an object. Check the payload and its
`source` before reading the composer `rpcId`, so an input Node without a
message payload no longer throws while pending echoes are matched.
The position-order case includes `input`. A Turn without opening input
whose control anchors after a position-less row still renders the control
before the group that holds that row.
State in the `exclusive` JSDoc and the Conversation reference that the
claimed target's fallback does not run and that a fallback's own claim
has no effect. Reword the `input` position as the Turn's opening input
and separate the finalized-answer exemption from position-less
`assistant-step` rows. Point both package READMEs at the claim and the
group-tree positions.
Assembler cases cover a claim in either registration order and on live
append, other targets and target-less Definitions, the skipped fallback,
and both dispatch failures. Chat grouping cases split the step run by
position regardless of kind and regroup on a position change; the late
reply produces one Chat Node through a real assembler.
Add the exclusive Conversation matches Agent Note; bring the group-tree
note up to the `input` position and the migrated kind lists, and the
timed-question note up to claim-based reply deduplication. Pair the
Chinese Conversation reference and Chat grouping README with the
English updates.
Chat grouping, whole-Turn folding, and Turn-input detection read each
Node's declared `groupTree` position instead of the `INDEPENDENT` and
`TURN_PROCESS_INDEPENDENT_KINDS` kind lists and the user/steering/
turn-trigger checks. `ChatGroupTree` gains an `input` position between
`exchange` and `turn`; the built-in Definitions declare the positions
their kinds had, so built-in rows keep their placement.
The late question reply claims its message on the Chat target, so the
generic message Definition no longer projects a duplicate trigger and
grouping drops the message-id aggregation. Its placement is unchanged.
A match result may set `exclusive: true`. The assembler evaluates every
match of an event before accepting any, so a claim is independent of
registration order: other Definitions of the claimed target build no
Context for the event and the target's fallback does not run, while
Definitions of other targets and target-less Definitions still receive
it. Two claims on one target, or a claim without a target, throw.
The responsive change had moved `.pageIntro` off flex, which put the information
icon 1.06px off the subtitle's vertical centre and failed the existing geometry
assertion in `plugin-manager.e2e.ts`. Restore the flex row and the button's
`flex: none`, which keeps the 4px gap and the centring. The header still wraps
its action group onto another line, which is what this change is for.
Pin the wrap threshold in both header scenarios. Each records whether the action
group sits beside or below the heading, the plugin scenario asserts the wide and
narrow sides explicitly, and the plugin scenario launches its own scaffold so an
install task left by an earlier test cannot move the threshold between runs.
- Compute each group's baseline watermark once per render.
- Skip the row-motion reset when folding drops no raised watermark.
- Document that the watermark is a row count that a reorder can outrun, and
that any unmount of the grouped tree resets it; pin both in tests.
- Cover raise-to-current, keep-after-leaving, and fold reset in the keyless
Web folding scenario.
The page header keeps the title at its natural width and moves the New action
onto another line once the two no longer fit, so a narrow content column never
splits the title across lines.
Register the new browser scenario in both web programs: the host aggregate owns
files that boot the host spine, and the client-registered app project excludes
them.
The scenario asserts the title stays on one line, the action separates when the
column narrows, and focus survives the change.
The page header wraps its action group onto another line when the title and the
actions no longer fit together, so the title keeps its width and the actions keep
their labels on one line. The information button follows the subtitle text
instead of a flex row, which keeps it beside the copy at every width.
Cover the header in the plugin-manager browser scenario: the action group stays
inline when it fits, moves below when it does not, keeps its labels on one line,
stays inside the header, and retains focus across the change.
Add `.agents/skills/dsh-responsive-ui`, the repository standard for adapting
client surfaces to narrow available widths: the 390 CSS px acceptance minimum,
the order in which space and information yield, text truncation, titles,
icon-only controls, mobile hover behaviour, mobile tooltips, and focus
retention across width changes.
Link it from `dsh-client-ui-ux` so the existing client UI/UX guidance routes to
the new standard.
The subagent guidance tells the model to end its turn while it waits for
delegated results. With an armed goal, the round driver would then queue a
new round at every idle point, spending the round budget while the agent
only waits. The driver now joins the agent's progressing descendants
through the optional subagent service before it reserves a round, and stops
waiting when the plugin unloads.
Each Workspace shows a contiguous prefix of its Sessions up to a watermark.
The watermark rises to cover running, waiting, unread-completion, and
current Sessions within the first 50 rows, and to a search reveal target at
any row. It never falls on its own; Show more raises it past 50, and Show
less or folding (including an ancestor) resets it.
The subagent guidance now tells the model to end its turn when only waiting
for delegated results remains, since each result starts a new turn. The read
guidance names sed, head, and tail alongside cat and covers line ranges.
The compaction-output-reserve and compaction-summary-headroom context windows
grow by 40 tokens to keep their triggers between the history and its
condensed replacement.
PowerPoint and Excel tasks in PTC mode wrote inspect.py, which shadows the
standard library and forces a delete-and-retry step. Apply the Word naming
rule to both skills, listing the libraries each one imports.
- Restore the office-docx new-document example from master. #6040 concerns
edits, and no Word creation case covers its removal.
- Tell direct package writers to copy each member once into a new ZIP;
python-docx, python-pptx, and openpyxl all reopen packages with duplicate
members without error.
- Anchor the script-name assertion so hyphenated names such as edit-docx.py,
which shadow no module, do not fail the Word e2e.
#5938 turned @deepseek-ai/dsh-session-persistence-jsonl into a function
plugin without a default export, while the SDK resume spec from #5988
still default-imported it. Typecheck failed with TS1192 and the spec's
six cases mounted an undefined plugin, which kept the coverage, snapshot
and Windows build checks red for every pull request.
Users of v0.2.1-alpha.2 who saved an account catalog through the Models
page editor must move single-model changes into modelOverrides, because
a models field now stops llm-deepseek-account at load. pi-ai routes that
name a missing credential now list no models.
The account README now says that a prerelease orders below its release,
that listed capabilities do not bound an override, that an override for
a refused unknown model is checked once the model becomes available, and
that an account whose listed models are all refused shows the generic
empty state. resolveAdapterOptions documents that resolved models keep
the input order, which the account catalog relies on.
The listing deadline now comes from dsh-timeout and carries
MODEL_LISTING_TIMEOUT, so a listing that misses modelsTimeoutMs fails
with TIMEOUT and "timed out after <n> ms" instead of ABORTED. Rescope
and disposal aborts keep reporting ABORTED.
The account route no longer accepts a models catalog, so paid Flash
translation compares Account modelOverrides (Official keeps models) when
it checks that the owner's model configuration is unchanged before
dispatch. The paid fixture and the Web e2e serve the account GET /models
listing instead of configuring models on the account entry.
Reuse read_image and automatic compaction in the standalone profile without
adding SDK slash-command handling. Update profile guidance, upgrade steps,
and startup and recorded-session coverage.
Product profiles mount no console exporter for ctx.logger, so the
listing failure warning never surfaced. The account route now writes
each loaded listing, the first failure of a failing streak with the
catalog it falls back to, and hidden listing entries to the console
under the [llm-deepseek-account] tag, as the account Platform plugin
does. Hidden entries are logged once per listing on every path, not
only during model discovery.
The account route always reads /models at the Messages origin; the
modelsURL option is removed so a configuration cannot point the listing
elsewhere. Model refusals now report "current version is <version>"
instead of "this build is", since users usually run a downloaded release
rather than a local build, and override diagnostics name the built-in
catalog instead of the build.
The session.create change is not perceptible to users: its only caller is the
Web client that ships in the same release. Remove the guide and the
cross-reference added to the New Session upgrade guide.
The analyzer normalizes its root with fs.realpathSync, which keeps a
Windows 8.3 short name such as RUNNER~1 in the runner's TEMP, while
TypeScript reports module-resolution results through
fs.realpathSync.native, which expands it. The guard compared the two
spellings, found the resolved lib/ declaration outside the workspace,
and let a missing alias through on Windows. It now canonicalizes the
root and every file with realpathSync.native and reports paths spelled
under the caller's root; the guard specs also run with the workspace
root spelled through a directory link.
Refs #5852
doc-sync, test:docs, hygiene, and check:all run at most 8 gates in CI as
well; the constant, the development guide, and the Agent Note now say so.
The Note also states that the source-only Typert guard rejects unresolved
workspace imports, so a missing alias fails before or after host-src.
Refs #5852
uniquePrefixMatch is exported and covered for unique, shared, last,
exact, absent, and empty cases, and a fixture repository with two commits
whose names share a seven-digit prefix shows that the shared prefix is
accepted while a longer unique prefix is rejected.
Refs #5852
rejectBuildOutputs caught a missing paths alias only when the package's
lib/types already existed. On a tree whose packages are not yet compiled,
as when host-typert runs beside host-src on a clean checkout, the import
resolved to nothing and checkDiagnostics: false hid it. The guard now also
fails when a face program cannot resolve an import of a workspace package,
found through its node_modules link, and names the paths entry derived
from the package's exports types condition.
Refs #5852
gen-doc-graphs states that its probe memo patches the system and needs a synchronous, non-nested caller; the typecheck graph comments why host-check does not wait for host-typert; the API gateway guide names the CI build lanes as the bundling Typert path; and the build-split Note marks its host-libraries measurement as superseded by host-contracts.
Refs #5852
The new Agent Note records why type checks generate Typert declarations
without bundling, why pre-commit leaves third-party notices to the test
lane, how the local worker budget is chosen, and how the documentation
gates and type-model.spec avoid repeated work, with the measurements and
the evidence that outputs stay the same. Notes whose facts these changes
superseded now describe the source-only Typert step and the test-lane
ownership of notices freshness.
Refs #5852
gen-cli-help launched its twelve source-mode dsh --help processes one
after another, each spending most of its time waiting on profile boot
I/O; they now start concurrently within the default worker budget, each
with its own temporary DSH_HOME, and the existing comparison consumes
them in the original order. gen-doc-graphs memoizes ts.sys file, directory,
realpath, and directory-listing probes for one run. Generated pages are
byte-identical; CLI help drops from about 52 s to 7 s and doc graphs from
about 47 s to 33 s.
Refs #5852
uniqueRepoFiles read every directory once per pattern and ** level and
resolved every file with realpath. One walk now lists each directory
once across patterns, resolves only paths reached through symbolic
links, and lets doc refs skip non-leading lib directories, whose files
it always excluded. markdown wrap, doc refs, and package paths return
the same file lists with about half the directory reads.
Refs #5852
markdown links parsed anchor targets twice more and probed existence
without a cache; translation pairing reparsed every document after link
normalization and globbed four times; the README model-experience and
limitations checks parsed each README three times; type equivalence
parsed all 1,835 documents; Session format references reparsed each pair
four times. Each gate now parses a document once: links and anchors come
from one tree, pairing reuses the original tree when every rewritten
destination keeps the block structure and falls back to a full reparse
otherwise, the README checks share one tree, a text prefilter skips
documents that cannot hold a source-equivalence fence, and the
persistence renderer passes its trees on. Outputs are byte-identical on
the repository, and seeded faults are reported identically.
Refs #5852
Each of the 61 cases copied the fixture onto disk and ran a full analysis
whose four TypeScript programs reloaded the DOM library, @types/node, and
zod v4; about 22,000 file-system probes per analysis dominated on a
network filesystem. The spec now mocks the node:fs calls the analyzer
makes and the ts.sys host for an in-memory workspace, stubs zod and
@types/node except in the Emitter case that runs real zod, drops the DOM
library from the fixture, and shares the pristine analysis read-only.
The file drops from about 316 s to 15 s with the same 61 cases and
assertions; the snapshot's external zod path no longer names a pnpm
store version.
Refs #5852
Browser inputs make every package source file a generator input, so the
pre-commit job ran the 16-second generator on nearly every commit while
few commits change its output. gen-third-party-notices.spec.ts already
asserts the committed bytes in the test lane, including the required
node 24 / coverage pull-request lane, and names the regeneration command
when they are stale. The generated preamble, the notices decision, and
the development guide now describe that ownership.
Refs #5852
verify-repository-references asked git cat-file for each of about 26,000
abbreviated hexadecimal candidates, which took 135 s on a network
filesystem. One --batch-all-objects listing (about 1.2 million objects,
0.6 s) now feeds an in-memory prefix search; a candidate still counts
only when its prefix names exactly one available object and that object
is a commit. The gate drops from 166.8 s to 15.7 s, and both
implementations classify 27,050 real and synthetic candidates
identically.
Refs #5852
os.availableParallelism() reports host CPUs rather than a container's
CPU quota (256 against a 64-CPU quota here), so local defaults spawned
255 Vitest forks (55 GiB peak RSS) and oversubscribed every scheduler.
scripts/parallelism.ts keeps the CPU count in CI and caps other runs at
12, or 48 for Vitest forks and the local coverage budget; documentation
and hygiene aggregates run up to 8 gates instead of 4. Explicit
DSH_GATE_CONCURRENCY, DSH_OXLINT_THREADS, DSH_COVERAGE_MAX_WORKERS, and
--maxWorkers still win.
Refs #5852
Type checks only need Host declarations and Typert-generated
declarations, but typecheck, lint, lint:fix, doc-typecheck, and the CI
typert-contracts gate rebuilt the whole Host tsdown pass, Desktop bundle
included, to obtain them. A source-only scripts/generate-typert.ts now
runs the same generator and artifact writer as the tsdown plugin, beside
Host package compilation: Typert reads only sources once the missing
@deepseek-ai/dsh-client-file-upload/types path alias exists, and
rejectBuildOutputs fails the pass when a workspace lib/ declaration
enters its program. typecheck runs as a build.ts scope that checks both
aggregates in build mode after their own face compiles.
Refs #5852
Share locationCoordinates between the Chat Builder and GroupTreeSegments,
find the last opening Turn with a loop instead of spreading every key,
and pass the resolved Location through chatNode so message Nodes resolve
it once. The notice e2e now waits for observable barriers (each Step's
row, the notice Turn's start, the notice row) instead of fixed sleeps,
asserts the settled tail through polling plus a later-frame recheck
instead of one absolute blank threshold, and fails on console warnings.
Record exchange in the terminology table and keep Goal Round in English.
Under "On next message" folding, a Turn woken by a notice (settled
subagent, goal round, schedule, background job) advanced the bottom Turn
and folded the finished Turn without an own-input driver, so the generic
fold room left a following reader looking at blank space.
Chat Nodes now declare a group-tree position (`groupTree`, ordered
`exchange` > `turn` > `process`). Human input claimed in Step 1 marks its
Turn as opening an exchange; notices and later steering carry no
position. `GroupTreeSegments` splits the loaded Turns at opening Turns,
and `bottomSource` covers the whole latest exchange, so notice-woken
Turns no longer fold earlier ones while the next human message folds the
exchange through the own-input path. Only `exchange` is read today; the
`turn` and `process` positions are reserved for the existing kind lists.
- WebServer.port reads number | undefined instead of masking the unbound
listener with an assertion; tests that dial it narrow the bound port.
- LegacyPrompt stays module-private; the web-app type import is a
devDependency only, since the emitted declarations never reference it.
- Copy: --public-url help names runtime context again, the public
deployments guide, web-app JSDoc, Config.publicUrl consumers, the Agent
Note matching rule, and three Known Limitations in the package README.
The web-runtime row provides webApp.getAdvertisedUrl(): the row's
configured publicUrl, otherwise the listener origin. harness-dev-context
reads it instead of webStartup.publicUrl, so a profile override of
publicUrl reaches the model as it already reaches DSH_WEB_URL and the
startup URL. The new package no longer injects webServer or webStartup,
and the startup flag provider returns to its master behavior.
- WebServer exposes getOrigin() instead of an origin getter, and it fails
loud before listen binds a port.
- The earlier Web-surface paragraph is a frozen literal, so later guidance
edits cannot stop earlier Sessions from matching; the projection reads a
prompt version with the loop's single-text-block rule.
- web-app drops its unused dsh-system-prompt dependency and stale
prompt-section wording in its description, README, patch comment,
--public-url help, and the round-trip test title.
Each recorded runtime-context snapshot with optional contexts gains the
tokenized checkout and Web GUI URL lines, matching the refresh output of
the code-language scenario, where only that message changed. The
minimal-preset snapshot suppresses optional context and stays unchanged.
The harness checkout path and Web GUI URL now reach the model as runtime
context from the new dsh-harness-dev-context plugin, so a process started
on another port, checkout, or installation no longer rewrites a Session's
logged system prompt. A Session whose prompt already embeds the earlier
value-bearing paragraphs keeps them verbatim through a host-only session
projection.
dsh-host-webserver gains WebServer.origin and bindUrlHost(); web-app keeps
only DSH_WEB_URL, the startup URL, and the browser handoff, and its root
declares the webStartup service type. app-boot drops
addHarnessSourceSection and HARNESS_SOURCE_SECTION.
build:artifacts produces every artifact, including the tsdown bundles,
the Desktop bundle, and Web, so naming it after sources undersold it;
build:check checks scripts, benchmarks, and website code besides tests.
The build.ts scopes, aggregate step ids, the Python builder flag
(--build-artifacts), workflows, callers, docs, and the build-split Agent
Note follow the new names. The note also records the naming
alternatives.
Refs #5852
- docs/development.md gains a situation-to-command table for build:src,
build:test, build, typecheck, lint and doc-typecheck (Chinese pair
updated).
- Note A states the typertPlugin() compatibility facts without discussing
an upgrade guide.
Refs #5852
- Note B: final medians of two interleaved samples (build 317.1 -> 169.1 s,
build:src developer view -51.6%, lint prerequisite 159.8 -> 90.9 s), the
CI lanes that run the parallel build, and their concurrency settings.
- Both notes: medians with sample counts instead of raw sample lists.
- Note A: why the typertPlugin() hook changes need no upgrade guide; final
serial stage times.
- Typert Remote note: the top-level build now runs the scripts/build.ts
step graph; build:lib and build:lib:host keep their remaining callers.
- docs/development.md agreement fix; packages/client/AGENTS.md rule 8 names
the manifest globs the gate checks.
Refs #5852
verify-package-paths reads root-relative packages/* strings in tests and
rejected the fixture's packages/generator, which names the real
packages/typert/generator leaf in the wrong group. The fixture now mirrors
that layout.
Refs #5852
- 2026-10-11-lossless-build-speedups: the React type packages declared only
at the root, the five Typert discovery/worker changes with the transform
filter, and the client tsdown preset manifest read; each item states its
purpose, change, measured effect, why outputs are unchanged, and its
trade-offs.
- 2026-10-11-build-src-test-split: build:src / build:test / build, the
dependency graph and scheduling, callers and CI lanes, which command
detects which error, measurements, and the intentional trade-offs.
- 2026-10-05-pr-artifact-typechecks: one-line adjustments where this change
replaces --artifacts-only with build:src and renames the Python flag.
Refs #5852
pnpm run build ran every step in one serial chain, including the two
noEmit aggregate programs (tests, scripts, benchmarks, website) that no
artifact step consumes, so the Host aggregate check alone delayed tsdown by
about 66 s.
- build:src (scripts/build.ts --scope src) builds every artifact in the
existing order and writes the client build record; it replaces the
removed --artifacts-only flag and the libraries CLI mode.
- build:test (--scope test) type-checks only the Host and Client aggregate
programs with tsc -p, concurrently, without compiling references or
checking prerequisites.
- build runs both as one dependency graph through run-gates' new
runAggregate: each aggregate check starts once its face's packages are
compiled, and Client compilation stays after the Host bundle that writes
the Typert /remote declarations. DSH_GATE_CONCURRENCY=1 serializes it;
DSH_GATE_FAIL_FAST=1 stops the remaining steps at the first failure.
Passing steps print one PASS line (DSH_GATE_VERBOSE=1 shows their output).
- PR artifact builds (e2e, release, preview, Node 22 compatibility) and the
Python builder (--build-src) use build:src; dev:web and dev:desktop use
build:src. test:*, bench, typecheck and the required consumer/Windows
lanes keep the complete build.
- lint, lint:fix, doc-typecheck and the Typert contracts gate prepare with
host-libraries (Host package compilation + Host bundle) instead of
build:lib:host; their own checks never read the aggregate root or the
Desktop bundle.
Parallel and serial build produce byte-identical lib/, Web dist and client
build record; a test-only type error fails build and build:test while
build:src passes.
Refs #5852
The client tsdown presets found a package's own manifest by name: a glob of
every packages/*/*/package.json, parsed until the name matched. tsdown's
native config loader imports each package config without module cache, so
the module-level cache never hit and all 82 configs repeated the glob
(40-125 ms each) synchronously on tsdown's main thread, where it delayed
every other package build. It was 34.5% of the client pass's main thread.
- clientBundle, staticLinked and clientLibrary take the package directory
(import.meta.dirname at every call site) and read <dir>/package.json
once; the package name, loader id and externals come from that manifest.
- A missing, unreadable or nameless manifest throws an actionable error.
- The glob, the name search and the module-level caches are removed.
tsdown client pass 12.78 s -> 8.73 s (median of 3 interleaved runs each,
-31.7%); all 11,566 lib outputs byte-identical; all 82 configs resolve the
same externals and banners.
Refs #5852
TypeScript builds a synthetic signature for every JSX intrinsic element and
computes a module specifier for @types/react (index and jsx-runtime) per
importing file. computeModuleSpecifiers visits every symlinked location of
that file and scans all 491 tsconfig paths for each; pnpm gave 65 packages
their own node_modules/@types/react link. Client test-aggregate Check
followed 26.9 + 0.494 * N seconds for N locations.
- Remove @types/react and @types/react-dom from all 67 workspace manifests
and declare them once in the root devDependencies; packages keep declaring
the react and react-dom they import (ui-slots gains react).
- verify-package-dependencies rejects either package in any workspace
manifest and requires the root declaration, so a new package cannot add
the link back.
- packages/client/AGENTS.md states the rule.
Client aggregate Check 59.1 s -> 26.5 s; the 76 client-only package
projects 54-57 s -> ~44 s wall. File set (2,613) and diagnostics unchanged.
Refs #5852
The Host tsdown pass ran Typert synchronously inside the first package's
writeBundle, blocking every other workspace build's JavaScript hooks for
about 12 s. Discovery dominated that time: it expanded the Host aggregate's
2,064 include entries, parsed all 362 referenced tsconfigs (Client ones
included), and walked 322 packages although only 28 declare a Typert export.
- Select packages by their manifest exports before any source walk.
- Parse face aggregates without expanding their file lists.
- Parse package tsconfigs lazily, only for analyzed packages; tsc -b keeps
validating every referenced tsconfig.
- Resolve the workspace root as the nearest ancestor holding both
tsconfig.host.json and packages/.
- Run workspace generation in a Worker Thread started at the first
buildStart; writeBundle awaits it once and fails the build with
TypertWorkerError on any worker failure.
- Filter the decorator transform hook by id so only TypeScript modules
cross into JavaScript.
tsdown host median 22.6 s -> ~10 s; all 743 hashed lib outputs are
byte-identical except the generator's own bundle.
Refs #5852
Hold cleanup of an older retained process while a newer call is waiting, then unload the provider. Require the pending call to reject after disposal without starting another process.
Validation: 305 focused tests passed, one skipped; persistent.ts, index.ts, and policy-processes.ts have 100% statements, branches, functions, and lines. Removing the shutdown guard makes the new regression fail.
Preserve the persistent runtime package description with master's alpha.2 version and locale exports. Refresh the two PTC prompt snapshots for the current plan-tool result type.
Validation: full build, 434 focused tests (one skipped), two built-runtime tests, two selected snapshots, package metadata, and all 43 doc-sync checks passed.
--no-lazy-fetch exists only in Git 2.45+, so older Git exited 129 before
creating a worktree. The environment variable is also honored by the
2.39.4, 2.40.2, 2.41.1, 2.42.2, 2.43.4, and 2.44.1 maintenance releases.
The root file passes the 1,960-word ceiling on the branch alone but exceeds it
once master's newest standing order is merged in, which is what `node 24 /
static` reports. Raise the ceiling to 2,080 and update the documented target in
the same change, per the "a too-low ceiling is a budget bug" rule.
- package-surface: split the two plugin forms (a function plugin named-exports
the Loader four, a service plugin default-exports its class), keep Host
Service Definition exports and the Client `/client` export discipline, replace
the wrong example that quoted live production code, state that the static
client libraries and `packages/util/*` are closed to new members, and drop
census numbers that rot on the next package.
- client-components: use the real Slot API — the inject face with the reserved
`hooks` compartment, the parent's derived `renderSlot`, `registerFactory`
scope declarations with local components at `renderFactorySlot` — drop the
string seat, pass the injected `useResource` explicitly with a protocol key,
and describe persistence as the spec's framework-run `persist` key.
- conversation-and-session: rewrite the reasoning example on the real
`conversation.chat.reasoning.body` seat and the published `reasoning.content`
Factory, and drop status annotations and future-tense spec-speak.
- remote-and-api: scope the JSON requirement to the wire payload so
`@Remote({ mode: 'stream' })`, lookup arguments, and a trailing `AbortSignal`
stay valid; drop a leaked Chinese parenthetical from the English page.
- general: measure through `benchmarks/` (`test:bench`, `check:ci:bench`,
`test:bench:memory`) instead of an invented command, state the baseline
without presuming a hook, and drop a leaked exception line.
- api-gateway: link the Remote and API rules instead of restating them.
- development.zh and AGENTS: drop an orphan anchor and widen the standing
order's trigger to the directory's scope.
The earlier "retired routing skill" claim was wrong and is dropped: that skill
was never committed, so the diff deletes nothing under `.agents/skills/`.
Move `docs/defensive-patterns.md` into `docs/defensive-patterns/`, one page per
artifact plus a README router, and state each rule as its requirement, the
condition a reviewer judges it by, its boundary and approver, and a correct and
a wrong example.
- `general.md` (the moved page): the existing bug-class rules plus timing,
value representation, shared-state ownership, decentralization, performance
evidence, and migrations.
- `package-surface.md`, `remote-and-api.md`, `client-components.md`,
`conversation-and-session.md`: the review requirements for those artifacts,
linking type definitions and semantics to `docs/subsystems/` instead of
restating them.
- `README.md`: the habits behind the rules and the route from artifact to page.
- `dsh-code-review` reads the owning pages before judging a diff; the
`dsh-architecture-discipline` routing skill is retired in favour of the
directory, and every inbound reference is repaired.
- Chinese counterparts, pairing records, and the documentation tier table are
updated together.
A write that completes at once only reached the local send buffer, so
counting it delayed dead-peer detection until that buffer filled. Only a
write that waited in the full buffer now resets the count, and the
connection owns all three activity signals.
A congested remote.mux link can hold the Pong behind data frames, so the
Host terminated sockets that were still moving data and the Client then
reconnected and resent. Any received frame or completed downlink write now
resets the missed-heartbeat count.
Name the original size when the store downscaled the image, keep repeated
whitespace in the path, localize the Chinese size line, and gate the card
on the envelope separately from collecting extra text.
The expanded read_image row no longer repeats the file name above the
image or prints the raw <path>/<type>/<content> envelope below it. It now
shows the full recorded path and each image's WxHpx and byte size in the
small code font; text appended by post-execute hooks stays visible.
Register calls before awaiting prior process cleanup. Carry policy cancellation into both Node execution modes and retain each admission until its call settles. Track persistent calls during provider disposal as well.
Cover unchanged-mode temporary escalation and policy changes that return to the original mode while cleanup is pending. Document the admission guarantee.
Validation: 308 focused tests passed, one skipped; both changed source files have 100% coverage. Two built-runtime smokes, two selected Session snapshots, all 43 doc-sync gates, and typed lint passed.
Add a parameterized admission regression for per-call and persistent-in-memory execution. Change the Session policy synchronously after submitting the runtime call and require cancellation before spawn.
The two new cases intentionally fail on the reviewed PR #5035 head: execution succeeds with danger-full-access after the Session becomes read-only. The 47 existing host-protocol and policy-process tests pass. No production changes.
A dismissed review now returns a successful exit_plan_mode result, so
the plan policy must not treat any success as leaving plan mode. Also
update the user-facing description of a dismissed review.
Decide the command chip's selecting step on deletion input events as well
as the character-deletion command, so iOS ranged deleteContentBackward no
longer removes the chip in one press. A selected chip now clears only
after a keydown that produced no character deletion, which keeps platform
shortcuts such as macOS Ctrl+H working.
A failed command submission settles to the leading chip like every other
transition, so submit-settled no longer carries the draft. The claim event
reuses the same handler. The seat rule runs as chip and paragraph
transforms instead of walking the whole document on every update.
Document the reload limitation under Known Limitations and assert that a
chip ending its block does not grow the composer.
Group built-in settings before optional and installed plugins, and open the full experimental catalog on a separate page.
Preserve detail navigation and unsaved configuration when installation finishes, and use the visible More label as the accessible name.
Choosing "Request changes" on a plan review rejected the question, and
exit_plan_mode turned that into a failed tool result asking the model to
stop. The loop then spent one more model request only to have the model
wait. A dismissed review now returns { approved: false } and concludes the
turn, so the user's next message starts the following request.
A claimed command is now a CommandChipNode on the draft's leading seat
instead of styled literal text. The submit machine follows the chip: it
holds the claim while the chip leads the document, releases it when the
chip is deleted or displaced, and re-enters claimed when history
restores the chip. Deleting at the chip's edge takes two steps: the
first Backspace or Delete selects the chip, the next removes it.
The claim-token decoration transform and the text-reference precedence
rule are removed with the literal token.
Silero cannot back-date the first segment of a cold detector, so it
reports a start index several frames after speech actually began. Every
recording therefore lost its opening audio: measured across 50
utterances the median loss was 102 ms and the worst 230 ms — a full
syllable, which surfaced as dropped leading words ("麻烦把发票…" →
"把发票…", "用 Python…" → "写一个 Python…").
Only the first segment is affected; later segments aligned within 3 ms
because the detector retains history by then. Tuning cannot fix it:
lowering minSpeechSeconds from 0.25 s to 0.05 s left the median loss at
110 ms, and lowering the threshold to 0.2 still lost up to 174 ms while
risking false triggers.
Restore the dropped onset from the recording itself, bounded by the new
vadOnsetPaddingSeconds (default 0.3 s, zero disables). Measured on the
same corpus with identical DSH settings, this recovers the full no-VAD
accuracy and in the technical domain exceeds it, since segmentation also
trims silence:
语料 条件 无VAD VAD丢起音 VAD+回填 改善
audio clean 12.50% 16.33% 12.50% 23.5%
audio noisy10 17.74% 19.15% 17.94% 6.3%
audio_tech clean 10.34% 15.65% 9.81% 37.3%
audio_tech noisy10 25.73% 30.50% 26.26% 13.9%
Inference cost is unchanged (2.6 s vs 2.6 s on both clean corpora).
Update the blank reuse Agent Note and the writer acquisition note for the
review fixes: arrival-order claims with directory checks before preset
resolution, restoration from the logged preset, latest-connection coalescing,
and the registry select that leaves an already composed blank unchanged.
The Creator entry names cordis in its session.create request, and its chip may
still select cordis when the Session list updates before the new projection
arrives. select recomposed and logged unconditionally, so that race appended a
second agent-preset/selected record and announced tools/change again. select
now leaves a blank Session that records the preset and runs its current
revision unchanged; a replaced definition still recomposes and logs. The
Creator Web scenario pins a single selection record.
Keep one Workspace navigation options type: StartSessionOptions now carries
agentPreset, and openWorkspace and connectWorkspace take its preset field.
A connection joins the latest in-flight connection to its Workspace only when
both name the same preset, so a superseding request always reaches the Host,
and the map is keyed by the branded WorkspaceId instead of a composite
string. The fresh New Session path no longer accepts a preset it would drop.
Restoring a saved blank reloads that Session's projections first, because
listed values are cached hints that can lag the log after an unclean exit.
The New Session upgrade guide points to the session.create reuse guide.
session.create resolved the requested preset before queueing on the named
identity, so a later request whose preset resolved first could run first, and
an undeclared preset masked the directory conflict the README promises.
Queue first, check the named Session's directory and ownership, then resolve
the preset inside the queue, and report the preset each request left the
Session running instead of reading it after the queue moved on. The no-roster
conflict message now names its actual cause.
Use the package's own `SubprocessTerminalActivity['state']` instead of a derived
alias, label the samples as `state:revision` so a reader cannot mistake the
suffix for a count, name the summariser's bound as a group count, and state only
the two observables the prompt count supports.
Refs #5755
An activity poll that times out reports only "unknown" or "Matcher did not
succeed in time". The Linux standby has failed that way twice, at different
polls, and the log shows nothing about what `inspect()` read, which returns
`unknown` for several distinct conditions.
Every activity poll now records the state and revision it samples and counts the
prompts printed while it waits, so a timeout names both. Repeated samples
collapse and the printed list is bounded. Timeouts, intervals and expectations
are unchanged, and where the poll budget equals the test budget the test timer
can still preempt this message.
Refs #5755
An unexpected fork exit can close the worker's channel under a send, which
Vitest reports as a runner-level worker error instead of the clean exit. It
then tears the worker down before the ChildProcess exit event, and
DiagnosticForkWorker treated that teardown as an orderly shutdown: it cleared
the observer and marked the exit expected, so the coverage log lost the pid,
file, code and signal it exists to report.
A worker error observed before a stop now keeps the observer, and a stop
requested after that error no longer marks the exit as expected.
Vitest reports one such exit either as the clean exit path or as a
runner-level worker error, so the spec accepts both forms and compares the
record against whichever appeared.
Refs #5813
openWorkspace and connectWorkspace accept NewSessionOptions, so the Client
Cordis inspect catalog spec pinned outdated signatures and failed both
coverage lanes. Declare openWorkspace on one line so the generated catalog
signature carries no collapsed line breaks, regenerate the catalog, and pin
the new signatures.
session.create now reuses a named Session only while it is a reusable blank,
runs the requested or default preset, and creates a new Session otherwise.
Record the migration for JSON-RPC clients and Client plugins that name a
sessionId, including the removed session/writer-held result and the
existingPreset detail.
Master now creates a fresh Session for a plain New Session, and the Creator
entry names its preset in the session.create request instead of a separate
agentPresets.select call. Wait for that request in the Creator scenario and
count the startup blank in the outdated-cache scenario.
pi-ai sends Anthropic tool changes natively only when the initial tool list
has an active declaration. When every projected declaration is deferred,
prepend a non-deferred DeferredToolPlaceholder instead of failing the request.
Document the placeholder, the catalog-owned update capabilities, and the
image role limit as separate README entries.
Master removed `backgroundMode` and the `run_in_background` argument, so
every delegation starts a managed activation. Mount the working-directory
owner SubagentRuntime injects and the JSONL persistence root the spawn
provider requires, and synchronize on the child's cancellation, which the
parent teardown or the manager drain raises instead of a parent-only cause.
The Typert workspace pass scans the analyzer's symbol table, and every
symbol asked `registrationForFile`/`packageNameForFile` for its owning
package. Both looked up the real path and then scanned every registration
with `isWithin`, and `isWithin` re-ran `path.resolve` through `realPath`
for both arguments on each call. A CPU profile of the host pass put
17.0 s of the 30.9 s sampled in that scan, almost all of it in
`realPath`/`resolve`/`existsSync`.
The registration set and the analyzer's face are fixed for the analyzer's
lifetime, so each source path's answer cannot change: memoize both
lookups per analyzer. `resolve` depends only on the requested path and the
process working directory, so memoize the normalization step too; the
existing canonical-path cache keeps its "existing paths only" rule.
tsdown host pass, interleaved A/B: 39.89/37.18 s -> 25.70/25.06 s.
Isolated generator phase: discover 8.69/8.59 s -> 7.33/7.44 s,
generate 17.19/16.96 s -> 4.21/4.21 s. Every emitted `typert.*` file
hashes identically before and after each run.
waitForTitleAfterTurnEnd and waitForEventAfterTurnEnd now name the session,
expected record, and deadline when the deadline passes before the first log
harvest settles, and keep the vi.waitFor failure as cause. Previously they
surfaced Vitest's generic 'Timed out in waitFor!', which failed the title
diagnostic spec on the macOS Sandbox lane at master 090348e4.
The title diagnostic spec uses isolateDiagnosticTimeout like its siblings,
replacing the Windows-only 5s timeout.
Refs #5631
The upload warns instead of silently ignoring missing files, and the spec
pins the upload after the benchmark step. benchmarks/AGENTS.md and the
report Agent Note now name the artifact CI publishes.
Refs #5840
The node-24-bench job uploads benchmarks/.dsh-report after the benchmark
step, including when a budget assertion fails, so the report the Vitest
globalSetup renders survives the runner. #5838 deferred this step because
the authoring token had no workflow scope.
Refs #5838
Extension failures, unreadable package identities, a blocked session-log
upload, and degraded Messages replay recur on every request. Log the first
occurrence per field, route, module, or blocking event instead.
Co-authored-by: turtle1999 <[email protected]>
Request-extension fields are auxiliary. A contributor whose preparation
throws is now logged and omitted while the other fields are still sent,
and a post-2xx acceptance failure is logged without failing the request.
Package inventory is best effort: a named manifest without a version, or
a bare package whose manifest is missing or unreadable, reports version
"unknown" instead of failing preparation.
A failing request-extension contributor surfaced only
'DeepSeek request extension preparation failed'; the cause stayed on
LlmError.cause, which the turn failure record and retry notice drop.
Append the cause chain to preparation and acceptance messages.
Every recorded case states what it measures and which user scenario it
affects, shown on hover in the report. Endpoints whose workers report
process CPU time default to the measured non-CPU share and busy
parallelism; the Session corpus worker now reports CPU time per
endpoint. Machine profiles carry logical CPU counts, and the page
shows each default share with its source and can reset overrides.
The General font-size row gains a More font settings disclosure that expands
a font field for each of text, code, and the sidebar terminal, plus code
(10-16 px) and terminal (10-20 px) size steppers. Font lists are normalized,
stored in the ui-theme namespace, embedded by the boot script, and placed
ahead of each role's built-in stack; the code size moves the code typography
tokens; the terminal applies font and size live and refits without replacing
the shell. ThemeRuntime adopts Host sections only after its own writes settle.
Refs #5217
Two fixture helpers in surface.spec.ts returned ': SessionEvent', a
declaration-merge-extensible mapped union. Spreading such a value (the
rejection cases build '{ ...event, data: ... }') materialises an anonymous
union per fixture, and the following 'as SessionEvent' casts then compare
union against union, which dominates the root type-check program.
Return the single variant each helper actually builds, spelled as the
canonical indexed access SessionEvent<'system/message'> /
SessionEvent<'user/message'>, drop the casts that only existed because data
was a union, and trim the comment of the member count that drifts with every
declaration merge.
Measured on the host build: check 110.9 s -> 84.6 s (median, -23.7 %),
wall -17 % to -24 %; fixture types alone (clean buildinfo, root program):
check 53.4/45.8 s (Extract alias) vs 45.3/49.1 s (indexed access).
Two declaration forms relied on a checker binding a name the permissive way:
- cordis-host-runner declared 'export function CordisDynamicPluginId'
alongside 'import type { CordisDynamicPluginId }' of the branded type, so
the call sites resolved through the type-only import. Export the four
constructors under separate local names; the public names, JSDoc and the
type names in signatures are unchanged, and the typert signature metadata
keeps the original type names.
- views-type-chain.client.spec.tsx spread one slots.register call over three
lines, which lets a checker anchor the rejected overload on the argument
line instead of the call line, outside the @ts-expect-error directive.
Keeping the call on one line keeps the negative assertion effective.
Let a title provider replace one that is still draining, so switching the
titles bundle during an in-flight request no longer leaves no provider.
Omit group container rows from bundle inventories, since they have no
plugin entry to switch. Drop the obsolete preset-target product-use case,
fix the Claude Code row id example, and refresh stale Agent Note text.
Session restore and projection run after the file closes, so they use
no storage share; the page keeps one share per endpoint. Record the
repeat projection-list budget, drop the post-GC page heap from the
memory view, remove the unused profile group, and add the owning
Agent Note.
Remove the `{ preset, insert }` / `{ preset, id, ...override }` profile patch
operation added in #5773, together with the app-boot compiler and reload drain,
config-editor filtering, plugin-manager preset row inventory and retained-module
removal checks, the read-only preset rows on the Plugins page, and the
agent-preset-registry row identities that served them.
Session search, Ralph, persistent terminals, Codex, and Claude Code bundles now
insert ordinary Host rows whose tools register in the global tool layer, like the
earlier optional bundles. Every preset, including minimal, sees them, and toggling
a bundle updates live Agents without new composition generations.
Record budgeted benchmark endpoints and peak memory, and render a
self-contained interactive report that rescales measured times to
reviewed laptop and cloud profiles by single-thread CPU rating and
default-disk latency, and compares peaks with 4 GB and 8 GB of
available memory.
The packed rehearsal now names each stage on the child's stdout and bounds the
manager's removal, so an operation that never settles reports the profile lock
and the still-live pnpm process instead of only a test timeout. That evidence
located pnpm 11.7.0 leaving its worker pool alive after a short-circuit return
(pnpm issue 12297, fixed in 11.23.0 and carried by this stack's base).
Three attempts to keep pnpm off that path — a private pnpm store, installing
both bundles before removing either, and pre-declaring the profile's age
exemptions — did not change the outcome, so none of them remain.
- The summary lists only the catalog roots as installable specs; the other 29
archives stay in the run manifest, where the installer resolves them itself.
- Only a pnpm entrypoint receives pnpm's arguments, a catalog root counts as a
required edge for platform admission, and a signal that stops packing reports
the interrupted exit instead of an exception.
- The packed rehearsal names the Agent SDK's musl artifact on musl hosts again,
and its comments match what the fixture does.
- The cache test registers its pnpm children for termination, and a new case
keeps one profile and its real lockfile: pnpm refuses a new run's URL with
ERR_PNPM_TARBALL_URL_MISMATCH until the stale resolution is cleared with
pnpm's own `pnpm clean --lockfile` remedy, which the desktop README now
documents for same-version iteration.
The teardown asserted the Web process's SIGTERM exit inside `finally`, so a
body failure was reported as a shutdown timeout instead of itself. The
assertions now run only when the body passed, the earlier failure is rethrown
after teardown, and the listener readiness became a named step that no longer
clones the release pack coordinator's listen idiom.
Both are failure paths the registry owns: a signal that stops packing must
report as a cancellation rather than as a pack failure, and a client that
disconnects mid-tarball must not stop later requests. execa reports the first
either as a resolved canceled result or as a thrown error, so the packing step
now names both.
A packaged application installs an Official on-demand bundle as
`name@<running DSH version>`, and the plugin manager asks a custom registry
alone, so the unsigned test DMG cannot install a locally built provider before
its release reaches npm: each packed manifest still names the rest of the
repository by version, and ordinary bundle tarballs therefore resolve against
a registry that does not have them yet.
`pnpm run dev:bundle-registry` validates the Official catalog and the workspace
inventory, builds the official artifacts, packs the catalog's complete local
dependency closure with the release families' own payload and manifest checks,
records the run in `dist/test-bundles/<run-id>/bundle-registry.json` as its
completion marker, and only then serves it from 127.0.0.1 on an allocated port
under a fresh namespace. Repository-owned names are answered from this run or
refused locally; third-party packages keep npm's real metadata and bytes
through a fixed redirect. The run publishes nothing, changes no global
npm/pnpm configuration, and leaves DMG packaging untouched.
The packed installer rehearsal now installs that closure into an isolated
profile with no `file:` override, checkout link, ignore list, or scoped
registry standing in for a package the registry should answer, and it asserts a
transitive repository package served only by this run.
The fixture workspaces declare packageManager, so corepack resolves that
version when pack-release runs pnpm pack inside them; leaving 11.7.0 there
would exercise the version this change replaces.
pnpm 11.28.5 runs verifyDepsBeforeRun before a script, so `pnpm run install`
inside the installed node-pty directory first installed node-pty's own
dependencies into the package and ran its `prepare` build. Both reached the
packaged payload: the SEA blob then carried a node-gyp tree and tripled pkg's
file records, and postject aborted while injecting it. The step now runs the
package's install script alone, as it did with pnpm 11.7.0.
pnpm writes allowBuilds placeholders for blocked dependency scripts only when
it can prompt, so a managed run that always captures output left
pendingBuilds empty and the approval flow could not offer anything. The failed
run's own report is the surviving record: the profile policy now records those
names, which keeps approval, persistence, and the wildcard and decision guards
unchanged.
pnpm's legacy deploy links the deployed project into the target's node_modules
when the target sits inside that project. Materializing that link copies the
payload into itself and fails with ERR_FS_CP_EINVAL, which broke the
release-shaped Python runtime matrix on Linux and Windows. The staged payload
never carries the project source, so the staging tree's owner drops the entry
and leaves the deploy root's files alone.
pnpm 11.7.0 can finish its work, print `Done in Xs`, and then never exit
(pnpm/pnpm#12297, pnpm/pnpm#13617): finishWorkers() clears the worker pool
reference, and a later worker API call recreates a pool that is never
finished, so idle worker threads keep the event loop alive. The upstream fix
(pnpm/pnpm#13226) shipped in 11.23.0, and 11.25.0 fixed the hoisted-linker
rename race as well.
The hang surfaces in this repository as a packed-profile rehearsal that runs
`pnpm remove` through the plugin manager and then waits for the test deadline,
holding the profile write lock for the whole time.
Bump packageManager and devDependencies.pnpm in the root, the pinned pnpm in
apps/desktop, and the native/system packageManager; relock; exempt the fresh
package manager from the release-age window; follow the development guide
pair.
Review found the same Host profile write plus inventory reload chain still
uncovered in worktree-opt-in.e2e.ts, and still written as 10_000 literals in
plugin-manager.e2e.ts. Both now use PLUGIN_TOGGLE_SETTLE_MS, the README keeps
only the convention, and the constant's JSDoc names the store's load() as the
reader that republishes the switch.
A plugin-card switch republishes `aria-checked` only after the Host writes
the profile and the panel re-reads its inventory. That chain measured
0.5-0.8 s on an idle host and 2.0-4.8 s across 32 samples in a 16-worker
lane, so Vitest's 1-second `expect.poll` default truncated it and failed
the switch assertions of the CoT translation scenarios in CI.
Every poll that follows a plugin-card switch click now passes the shared
PLUGIN_TOGGLE_SETTLE_MS budget; assertions, retries, and product code are
unchanged.
A reasoning route always declares at least one level, so the adapter's
"no floor" path is defensive; the resolver rejects an empty list before any
consumer sees it. Mark that branch with the repo's v8-ignore convention and
state why.
`purpose` is a label for replay and transport metadata, so no adapter should
branch on one caller's value. Exact-model metadata now reports
`reasoning.floorEffort`, the least effort a route accepts, and the title
service selects it for its request. `llm-pi-ai` and `llm-deepseek` dispatch
the effort they receive and no longer recognize `session-title`; the
`minimizeReasoning` field is gone. A route with no registered adapter, such
as the recorded replay lanes serve, reports no floor, so the title request
keeps the adapter default instead of failing before dispatch.
Replaces the `minimizeReasoning` field from the previous commit.
`purpose` is a provider-neutral label for replay and transport metadata, so
an adapter must not branch on one caller's value. `GenerateOptions` gains
`minimizeReasoning`, which any auxiliary caller sets when reasoning would
consume the output budget it reserved for a short visible result; both
shipped adapters dispatch such a request at the least reasoning the model
supports, and `session-title-llm` sets it instead of relying on the
adapter recognizing `'session-title'`. The pasted type in the subsystem
page and the generated Cordis API catalog follow.
Prompted by review: llm-pi-ai must not know about session-title.
A request body also carries the session log, whose recorded title event
embeds the framed prompt, so a body-wide marker search matches the main
request too; both mocks now read the request's own messages. The headless
scenarios also assert the dispatched cap, which is the base-bundle ceiling
on the 256000-token route and the route override's 1024 in the pi-ai
compatibility scenario. The extraction cases cover asterisks that are not
emphasis.
The lowest-level dispatch keeps reasoning off the title's output ceiling,
not a small cap's visible text, and it deliberately skips the
requested-effort check. The README pair records that the dispatched cap
is the smaller of the configured ceiling and the latest folded
request/header, including the tighter-main-cap and override consequences,
and the accepted-title comment states the deliberate whole-line emphasis
rule.
Headless stream-json and session expected outputs record the title
request's maxTokens, and every one of them still recorded the previous
base-bundle cap.
The README pair states that an accepted result is the first non-empty
line of the model text, with one wrapping emphasis pair removed, and that
the dispatched cap is the smaller of maxOutputTokens and the cap the
routed adapter declares. The pair is re-recorded with both sides.
The headless and Web mock providers recognize a title request by its
max_tokens, which still assumed the previous 256-token base-bundle cap.
The recorded expected outputs carry that cap too and follow with it.
The prompt asks for a short title, so the cap only stops runaway output:
reasoning a model cannot switch off also counts as output, and 256 tokens
truncated titles on the HFAI gateway. The measured worst title response
was 153 output tokens, so 4096 is about 25 times the observed maximum.
Claude wraps some titles in asterisk emphasis and writes the title, a
blank line, then an explanation when the messages say little. Joining the
text blocks made that explanation part of the saved title. Take the first
non-empty line, drop one emphasis pair around it, and ask the model for a
short best-effort title instead of an explanation.
The dispatched cap is now the smaller of the configured cap and the cap
the session's current main request recorded, so a route the deployment
capped below the shipped ceiling is never asked for more than that cap.
A session-title request dispatches off exactly as a selected off does,
which on protocols that omit the reasoning field leaves the gateway
default in effect. The README now says so instead of implying reasoning
is always switched off. requestReasoningLevel follows the
resolveReasoningLevel it calls and names when it returns no level, and
the Loader composition helper takes provider config and further entries
as separate parameters.
Headless stream-json and session expected outputs record the title
request's maxTokens, and the headless and Web mock providers recognize a
title request by its max_tokens. Both still assumed the previous 64-token
base-bundle cap.
The base bundle capped title generation at 64 output tokens, sized for a
route that disables thinking. Models whose reasoning cannot be switched
off spend output on it even at their lowest level: on the HFAI gateway,
GPT-6 Sol and GPT-6 Astra used up to 87 and 121 output tokens for
five-word titles at reasoning low, so Sol still finished with max-tokens
on most first prompts. Raise the shipped cap to 256; routes that disable
thinking stop after the title and are unaffected.
Session-title calls carry a 64-token output cap and rely on the adapter to
keep reasoning out of it. llm-deepseek maps purpose 'session-title' to
thinking disabled, but llm-pi-ai ignored the purpose, so pi-ai routes sent
title calls at the route's default effort. On HFAI's adaptive-thinking
Claude models (profile reasoning: max) the thinking consumed the cap, the
call finished with max-tokens, and the session kept its fallback title.
The pi-ai adapter now dispatches a session-title request at the model's
lowest supported level: off when the model offers it, otherwise the
lowest declared level. GenerateOptions.purpose documents that obligation
for every adapter.
A successful verifier message was treated as terminal even though the
Worker Thread was still alive. The parent immediately terminated the
worker and detached cancellation, so an error, nonzero exit, or caller
cancellation after that message could be ignored and verification could
resolve successfully.
Keep the first successful response pending until an observed natural exit
with code 0. A missing response or nonzero exit rejects after the worker
has already exited. Error and abort callbacks retain the existing
terminate-and-join path, including its first-error and AggregateError
behavior. The scheduler stays unchanged: its permit remains held until
the verification promise settles, now after successful worker quiescence.
The worker already closes its parent port in finally, so success needs
neither a forced stop nor another protocol message.
Replace success-termination expectations with deterministic message/exit
barriers and cover post-response errors, nonzero exit, cancellation, and
permit handoff. Remove the obsolete hand-written termination thenable and
its retired unknown-assertion baseline entry. Add real source workers for
plain and Zstandard input, and require the built plain-Node migration smoke
to complete with zero termination calls. Document the settlement condition.
Native Windows CI recorded process access violation 0xC0000005 while a
migration fixture was active. Removing unnecessary successful-worker
termination is consistent with that evidence, but this commit does not
claim that the native faulting module or mechanism has been established.
The deterministic regressions prove the lifecycle defect independently;
the next native CI run must assess whether the observed crash recurs.
Validation:
- Original source failed all new message-to-exit regression scenarios;
the updated focused lifecycle file passes 19 tests with 100% coverage.
- Node 24 owning package: 19 files, 741 passed, 1 skipped; every included
package source file reaches 100% statements/branches/functions/lines.
- Real source workers passed for none/zstd; built plain-Node smoke passed
with zero terminate calls and no timeout or process signal.
- Exact Node 22.19 source-mode none/zstd workers naturally exited 0 with
zero terminate calls, exercising the async Module.register fallback.
- Host TypeScript build, focused lint, no-new-unknown-assertion gate,
README translation pairing, and all 42 doc-sync gates passed.
Related to #4387 and #4394.
The master cwd integration updated the shipped headless profile, but three
current snapshot scenarios merged without textual conflicts and retained
expectations from before the directory service was composed. Full CI
replay found the stale product-subagent-both persona and missing current
working-directory context in the no-message-tool and structured-parent
parent logs; those two scenarios also owned stale child/header sidecars.
Refresh only those three scenarios through the real built headless profile.
Record the required runtime cwd section in their four parent/child V4
logs, remove static cwd text from three prompt sidecars, and include the
new cwd start parameters and working_directory tool in three schema
sidecars. Preserve every model response and event count. No production
code, scenario input, workspace oracle, or historical V0-V3 fixture changes.
Validation: selective refresh passed 3/3. Full DSH_EXAMPLE_MODE=lib pnpm
run test:snapshot passed all 8 files: 197 passed and 2 PowerShell scenarios
skipped. The first full local run selected macOS CPython 3.9.6 and failed
the two Python PTC scenarios; the final full run prepended the existing
CPython 3.12.13 bin directory to PATH, with no runtime or fixture workaround.
SHA256 guards confirmed all 363 retained historical inputs unchanged;
JSON comparison confirmed the four changed logs differ only by cwd context.
TextMate scanning cost grows superlinearly with line length, so a
generated line of tens of thousands of characters blocked the page for
seconds when the sidebar previewed the file. Lines of 1,000 or more
UTF-16 units now render as one plain run in every highlighted surface.
Integrate the observed master checkpoint 7ab215e23e5d977aae7eb5d4fb91782d39969dbb without rewriting the earlier published checkpoints. Master adds current-directory events, directory-aware consumers, and retained Git worktrees; its old subagent startup paths overlapped the unified activation implementation.
Resolve directory selection within managed startup after acquiring parent ownership. Preserve the original Session origin while setting each new local child directory, retain persisted directory state on cold resume, and pass a required absolute cwd to external providers. Keep unified local/external ownership, full completion notices, parked-input host completion, SDK wait/error/shutdown behavior, and ACP cancellation semantics. Retain removal of the old in-process driver and continuation managers.
Combine SDK wait with the new directory requests, preserve the new master directory/worktree fixtures, regenerate derived catalogs from the combined sources, and refresh only the four stale prompt/schema sidecars. Preserve every protected historical schema, archived note, catalog-v2 acknowledgement, and recorded predecessor input.
Validation: full build; 59 CI static gates; 855 subagent tests with 100% coverage on changed core/external sources; affected consumer suites including 119 repaired-fixture tests; SDK 11 and headless 16 recorded scenarios; two local headless expected cases; six built nested SDK/ACP cases; lint, strict cast inventory, workspace constraints, and whitespace checks. Native Windows worker diagnostics remain additive and require the next CI run to classify the original crash.
Repeated native Windows coverage runs stopped a Vitest fork while a JSONL
migration suite was unfinished. Vitest's generic unexpected-exit error
discarded the process exit code and signal, preventing classification of
the failure despite identifying the missing test file from the inventory.
Reuse the public ForksPoolWorker diagnostic implementation and protocol
fixtures from independently reviewed PR #4388, pinned at
77e215ff76711ec75ed8aa08aa5b414780e539ae. The runtime helper is unchanged.
It reports PID, project, worker identity, outstanding file request, code,
and signal before stock Vitest reports the original failure. It clears
ownership only on the typed testfileFinished response, suppresses expected
stop, and leaves cancellation and failure handling with the original pool.
Wire that existing pool into generated coverage partition configs. Keep
the original worker counts, partition scheduling, test timeouts, coverage
thresholds, and failure verdicts. No environment, raw argv, or test object
is serialized. This commit adds diagnostic evidence; it does not claim to
repair the still-unidentified Windows crash. PR #4388 remains the separate
reviewable owner; no duplicate diagnostic framework or PR is introduced.
Adapt the copied fixtures to the current no-new-unknown-assertions rule:
declare the private preload exit function, validate consumed logger fields
with the public logger type, and assign matcher/rejection values to typed
unknown locals without assertions. Add a concise bilingual testing note.
Validation:
- Diagnostic suite: 10 passed, including a real configured Vitest fork
exiting 23, stock-pool negative control, successful silent shutdown,
unexpected exit 0, early exit, independent forks, and signal metadata.
- Coverage coordinator suite: 51 passed.
- Targeted lint and focused strict TypeScript check passed.
- pnpm exec tsc -b tsconfig.host.json passed.
- verify-no-unknown-casts passed; 1430 existing assertions unchanged.
- doc-sync passed 40 gates; the two initially failing gates were repaired
and rerun successfully: doc-typecheck and verify-doc-budgets.
- Translation pairing and git diff --check passed.
Refs #4387, #4388, #4394.
Co-authored-by: Chinesezjc <[email protected]>
The cwd regression used a fake Context cast through unknown. Updating its service lookup for descendant shutdown changed the exact legacy assertion fingerprint, which the no-new-unknown-casts CI gate correctly rejected.
Exercise initialize, prompt, and wait with the real Context, Agent registry, Session store, and a scripted LLM adapter. Spy on the real service methods to keep the cwd, reasoning effort, and token-limit assertions. Remove both obsolete unknown assertions and prune only their recorded allowances; do not add or broaden an exception.
Validation: the cwd regression passes; all 54 SDK server tests pass with 100% server coverage. The full check:ci:static aggregate passes 59/59, including the strict unknown-cast inventory. No production behavior changes.
The gated final-flush fixture awaited the real flush but dropped its boolean return value. The regression ran correctly under Vitest, but the full Host build rejected the mock against Sessions.flush Promise<boolean>.
Return the real flush result after the gate. This retains the observation order and matches the service signature without a cast or altered production behavior.
Validation: targeted terminal-capture-failure regression passes; the complete build is rerun after this typing correction.
SDK shutdown disposed its root Agent handles without stopping managed
Activations below those exact roots. The manager owns descendant handles
in a separate context, so an interrupted child with parked input remained
registered after shutdown resolved. Its settlement watcher could then
outlive the SDK root and race later projection teardown.
Close scoped descendant admission and await child-first drain before
releasing root handles or the server adapter. Keep draining limited to the
server's exact root Agents so unrelated parent trees remain live. Collect
drain failures without skipping the existing root/adapter teardown, and
preserve the final single-error or aggregate-error result.
Add real AgentLoop/SubagentRuntime regressions for parked children at
one and two levels, child-first disposal, preservation of an unrelated
parked tree, and descendant/root teardown failure aggregation. Correct an
existing Context.get test stub to return its LLM only for the LLM service.
Document the lifecycle order in the server JSDoc and bilingual README.
Validation: 52 SDK server tests with 100% server.ts statements, branches,
functions, and lines; four final shutdown regressions; host TypeScript
build; full lint; 21 quick documentation checks; staged pairing/diff checks.
Doc-sync passed 41/42 checks; the pre-existing config catalog source link
for subagent-acp/index.ts is stale at base and left to integration refresh.
Interrupting a local child can preserve unclaimed steering while leaving the Agent idle. Activation residency intentionally continues in that state, but completion barriers joined its result forever. The real SDK server reproduced the hang for both direct children and a nested parked leaf.
Join progressing work and cleanup while retaining parked inboxes and their activation results. Observe local activity and maintenance, external execution, unpublished ownership holds, and materialization; rescan after changes. Recheck resumed Agents after concurrent idle observations. Inbox insertion and ownership publication wake pending observations, so late non-waking injection cannot strand a result wait. Existing descendants remain responsible for their own work; no second state store or polling timer is introduced.
Document that persistent SDK/ACP hosts keep these children available for later wake-up, while headless shutdown discards unclaimed input. Simplify the final settlement recheck to retry changed readiness uniformly. Update generated API and event references from their sources.
Validation: real SDK negative controls fail at both depths on the prior manager with clean teardown; 545 tests pass across 27 files. Manager, local activation, and headless runner each reach 100% statements/branches/functions/lines. Six deterministic races cover revival, preparation publication/rollback, flush-time injection, and late children. doc-sync 42/42, targeted build, lint, and whitespace checks pass.
Removing the duplicate ACP directory helpers moved its Config declaration by one line. The generated English and Chinese catalogs still referenced the old line, so the exact-source freshness gate rejected the otherwise unchanged configuration.
Regenerate only the affected source links. No configuration fields, defaults, examples, or translated prose change.
Validation: pnpm run gen-config-catalog produced exactly these two link updates; the combined doc-sync run verifies freshness.
A child completion can wake the parent after its first completed turn. If
that summary cannot append turn/start or turn/end, AgentLoop reports an
agent/error and contains the rejected driver. Whole-agent idleness then
resolves, but the headless runner previously reused the older completed
terminal and exited successfully, sometimes printing the older answer.
Retain failures from the exact owned Agent with the current Session log
offset. Only a later terminal committed by the same Session clears that
failure. After work and persistence drain, fail the run before emitting a
final answer when no such terminal exists. Dispose both observers with
the run. Ordinary durable error outcomes and later successful recovery
continue to use the log's final terminal.
Add real AgentLoop regressions for missing summary start/end commits in
plain and JSON output, ordinary hook failures with and without recovery,
recovery after persistence failures, and unrelated Agent errors. Update
the bilingual exit contract and generated event consumer matrix.
Validation: 106 headless package tests; 100% runner statements, branches,
functions, and lines; 9 focused terminal-failure tests after the typed
append wrapper correction; package TypeScript build; full lint including
host build; 21 quick documentation checks; 42 doc-sync checks; diff check.
Local activation results were derived only from committed Session events.
When the first turn/end append failed, no ending existed and the result
defaulted to completed. When the first turn/start append failed, the initial
message remained unclaimed and kept the idle activation resident forever.
Both paths emitted agent/error but had no durable terminal to project.
Retain the log offset of the latest exact-Agent live failure on the local
activation. Clear it only when the same Session commits a turn/end at or
after that offset, so a later recovery can supply its own outcome and a
reentrant older terminal notification cannot erase a newer failure. The
child scope owns both observers and removes them with its Agent handle.
Capture error while that live failure remains. Let the failed local
activation settle despite its own unclaimed inbox, while preserving the
existing requirement to wait for owned descendants. Wake its settlement
observer when a live failure changes readiness. No public API, raw Agent
idle semantics, or descendant teardown policy changes.
Add regressions through real local activation creation for first start/end
append failures, recovery by later durable endings, exact-Agent isolation,
and an error emitted reentrantly during an older terminal notification.
Update the bilingual package contract and generated event consumer graph.
Validation:
- Both first-append regressions reproduced before the fix: turn/start
remained pending; turn/end returned completed instead of error.
- Six focused behavior cases passed after the fix.
- 423 subagent package tests passed; activation.ts and manager.ts reached
100% statements, branches, functions, and lines under scoped coverage.
- pnpm exec tsc -b packages/subagent/subagent/tsconfig.json passed.
- Focused source/test oxlint and git diff --check passed.
- doc-sync passed 41 gates; its sole stale event-graph failure was corrected
with gen-doc-graphs, then verify-doc-graphs passed for all seven graphs.
- README translation pairing regenerated and passed the doc-sync check.
Caller delivery changes result ownership, but does not make local workflow children private or remove their continuable catalog entries. The workflow README stated durable discovery without making the message and cold-resume consequences explicit.
Document that resident workflow children accept continuation and that cold continuation requires persistence and Session query. An ephemeral child therefore returns NOT_RESUMABLE after disposal. This describes current supported behavior and does not add fallback storage or a new child mode.
Validation: checked against startActivation, sendMessage, and cold-resume admission; English and Chinese pairing updated. Full doc-sync follows the combined review fixes.
ACP retained a private copy of the external-provider directory helpers after the other providers adopted the shared implementation. The copies differed on absolute-path validation and imposed separate filesystem branches and maintenance.
Use resolveChildCwd and validateConfiguredCwd from the existing subagent package. Preserve configuration-time relative-path resolution, parent workspace inheritance, missing-directory failures, and ACP diagnostic normalization. This deletes the duplicate directory implementation without introducing an abstraction or dependency.
Validation: 85 passing tests across the ACP provider and shared out-of-process helper suites, including real child process cwd, configured overrides, and invalid-directory rejection.
Parent completion notices now always carry the child closing answer, even when the child can send progress messages. Two assembled headless expectations still asserted the removed omission and made the snapshots-and-artifacts CI lane fail.
Require CHILD_RESULT in the settlement assertion and refresh only the owning parent and stream output expectations. Preserve released replay inputs, event ordering, sandbox denial evidence, and the recorded model responses.
Validation: DSH_EXAMPLE_MODE=lib pnpm run test:expected apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts (18 passed).
The subsystem result paragraph still said that a parent log records the
final cleanup-failure status and that the result had already been returned.
The manager instead captures the execution result, performs cleanup, sends
that preserved result in the parent notice, and then resolves the public
result. Cleanup failure separately rejects dispose and marks the live
subagent/end event as error.
Correct the bilingual paragraph to name those actual recipients and timing.
Do not imply that the successful execution notice becomes a failed notice,
or change the runtime's independent execution/cleanup outcomes.
Validation: the updated bilingual pair passes. The statement is checked
against finishDisposal/notifySettlement and the existing cleanup-failure
regressions; final documentation gates run before publication.
The initial-cancellation regression supplied a synchronous content array to
defineContentToolFixture, whose executor contract requires a Promise of
content blocks. Vitest executes the fixture, but the strict repository
Host build correctly rejects its signature with TS2739.
Return the same blocks through Promise.resolve. Keep the deterministic
stream/output gates and assertions unchanged; do not widen the shared
fixture API merely to accept this one caller.
Validation: the focused initial-cancellation regression passes. The full
Host build is rerun before publication to validate the fixture signature.
Cancelling during the prompt's initial whenIdle() wait could retain its
slot indefinitely. If a child delivered a result while the aborted root
was unwinding, Agent.whenIdle() followed the replacement driver and waited
for the newly autonomous summary. The earlier child-wait recheck could
not help because settlement had not yet reached that child wait.
This is an inherited latent issue: the pre-PR ACP bridge also awaited the
same replacement-following whenIdle() after cancellation. Existing
continuable spawn children could expose it; the unified asynchronous fork
default makes the ordering possible for fork delegations as well.
Give the existing InflightPrompt one cancellation-completion promise,
replacing the local cancellation wake-up and its listener. The private
admission AbortSignal is the cancellation flag instead of duplicated
boolean state. The exact root's emitted status identifies the interrupted
activity's retirement and synchronously adopts that output queue. Later
root work and later output cannot replace the first captured completion.
Record whether cancellation began at public idle. Maintenance has that
public status too, so its next running transition establishes release of
work behind maintenance; the ordinary whenIdle promise provides a
fallback when no transition occurs or plugin disposal detaches observers.
Both observations settle the same promise. Settlement races that promise
against initial idle and child waits, then waits only for the interrupted
activity/output on the cancellation path. No future work is re-cancelled,
child admission remains open, and session close keeps its separate full
quiescence and descendant teardown.
Add two deterministic real-Agent regressions. One gates aborted model
cleanup, previously queued image delivery, and the later autonomous
summary independently. The other gates cancelled maintenance cleanup and
the later summary. Both failed before this fix; both now wait for old work
and output, return cancelled while later work remains gated, and permit a
subsequent ACP prompt to execute another real local delegation. Existing
plugin-disposal tests also verify the idle fallback after observer cutoff.
Update the bilingual ACP semantics and generated event-consumer matrix.
Validation:
- Both focused regressions reproduced pending cancellation before the fix.
- pnpm exec vitest run packages/acp/acp/tests --coverage --coverage.include=packages/acp/acp/src/session.ts --coverage.include=packages/acp/acp/src/index.ts --coverage.reportsDirectory=/tmp/pr4394-acp-owned-cancel-89ec33e5788-consumer-v2
(150 passed; both changed source files have 100% statements, branches,
functions, and lines)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm exec tsdown --config-loader native --env.DSH_BUILD_FACE host --filter '@deepseek-ai/dsh-acp'
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts -t '^external tasks.*acp'
(3 passed, 3 unselected)
- Focused lint, named translation pairing, generated graph verification,
and git diff --check passed.
Independent ACP rereview found no remaining issue, passed seven focused
turn/disposal cases, and verified a prepended idle listener can start a
new hanging root before ACP handles the old idle event without retaining
the cancelled prompt.
Preserve the published 78ae4fc2 integration checkpoint and merge the later master tip separately. This master delta changes only the browser-GIF skill to use the managed DSH credential store through normal application configuration; it changes no product source, schema, or test behavior. The existing credential-provider recording overlay already follows that guidance.
Seed a fixed POSIX effective directory before the first request and disable ambient instruction discovery in the two pressure fixtures. Preserve the original project and existing output, headroom, retention, and summary limits while keeping shadowed token counts independent of temporary workspace path length.
Supply the required directory collaborator to current runtime fixtures, verify absolute result navigation and deployment fallback, and retain the valid V4 working-directory recording instead of the unreleased V3 input that contains a V4-only event.
The completion coverage audit left two uncovered paths: replaying an ACP
abort that occurred before listener registration, and ignoring an Agent
error from outside the SDK server's owned root sessions.
Remove the redundant ACP abort replay. The private cancelPrompt method
is the sole owner of admissionController.abort(), sets cancelRequested
first, and never resets it. Settlement checks that sticky cancellation
state after its initial idle observation and before awaiting the local
cancelled promise. A pre-listener abort therefore never needs to resolve
that promise; the listener still handles cancellation during child waits.
Add a real local subagent failure regression for SDK ownership. The child
emits agent/error through the normal AgentLoop path and returns an error
result, while the SDK root's wait and a subsequent prompt both complete.
The test also rejects observer warnings, so dropping the ownership guard
fails on the swallowed TypeError rather than merely executing the branch.
No fake Agent identities or private-state mutation are used.
Validation:
- Focused descendant test passed; removing the ownership guard as a
temporary negative control failed on the unexpected observer error.
- pnpm exec vitest run packages/acp/acp/tests packages/sdk/server/tests packages/subagent/subagent-dsh-sdk/tests --coverage --coverage.include=packages/acp/acp/src/session.ts --coverage.include=packages/sdk/server/src/server.ts --coverage.reportsDirectory=/tmp/pr4394-completion-coverage-00243b2f89e-consumer
(251 tests passed; measured statements, branches, functions and lines 100%)
- Focused lint and git diff --check passed.
The early-cancellation audit also reproduced a separate initial-idle wait
liveness issue. It is intentionally left for its own authorized follow-up
commit rather than being combined with this coverage change.
The SDK subagent provider now uses session/wait to keep a delegated child
runtime alive through nested work. A deployment that explicitly selects an
older dshBin can still initialize and accept a prompt but cannot satisfy
that completion request; it must report an execution failure rather than
silently treating the first idle as success.
State the required operation in the provider's owning failure/recovery
contract and update the current activation upgrade guide to tell users to
upgrade a separately configured runtime with the provider. This adds no
capability negotiation, compatibility fallback, or new runtime behavior.
Validation: upgrade-guide structure/word budget and both bilingual pairs
pass. The requirement matches the provider's direct session/wait request
and the SDK server's explicit unknown-method rejection.
A summary can fail while appending turn/start or turn/end and still leave
the root idle with a stable Session sequence. The SDK wait operation then
returned {}, letting the external provider treat the preceding completed
terminal as the result of the entire task. A listener installed only for
the wait would also miss failures between prompt admission and the wait
request.
Retain one latest live failure on each existing SDK Session record, scoped
to the exact owned Agent. Record the Session's next log offset when the
failure occurs. A turn/end from the exact owned Session clears it only
when that terminal was committed at or after that offset. This preserves
failures raised during terminal publication, keeps ordinary represented
turn errors in the durable outcome, and permits later successful activity
to recover. At stable root/descendant completion, session/wait rejects any
remaining failure. Raw session.status and HarnessSession.run behavior are
unchanged; no generic failure history or new notification is introduced.
Add seven owner-local cases for later start/end failures before and during
wait, subsequent successful prompts, represented errors, and publication
ordering including an undefined thrown value. Six failure cases returned
success before the fix. Strengthen listener teardown coverage to assert
all actual subscriptions are disposed after a disposer fails.
Extend the existing recorded subagent-continuable SDK scenario to exercise
session/wait. It still asserts that run() returns DONE at parent turn 1,
then requires child completion and parent turn 2/idle plus its final
summary to be delivered before the wait response. Bypassing the wait made
that ordering assertion fail. No recorded Session or golden output was
invented or refreshed. Update the protocol/server bilingual READMEs and
regenerate the event-consumer matrix for the new error listener.
Validation:
- pnpm exec vitest run packages/sdk/server/tests/wait.spec.ts before the fix:
6 failed, 1 passed (the represented-error case)
- pnpm exec vitest run packages/sdk/server/tests/server.spec.ts packages/sdk/server/tests/wait.spec.ts (40 passed)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts (8 passed)
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts (8 passed)
- pnpm run test:snapshot snapshots/sdk/sdk.snapshot.ts -t 'replays subagent-continuable through dsh --profile sdk' (1 passed, 24 unselected)
- pnpm run doc-sync: Host compile/bundle, doc-typecheck and 40 other gates
passed; the generated event graph was stale, then regenerated and passed
pnpm run verify-doc-graphs (all 7 graph docs current)
- pnpm run lint:contracts-ready and focused lint of all changed SDK code
- named translation pairing checks and git diff --check
Independent SDK rereview and 7/7 focused tests found no remaining issue.
The parent task owns unchanged Python advanced/recovery validation on the
combined branch; this commit changes no Python or high-level SDK results.
Master adds the claude-code-mods recorded scenario, whose full assembled
prompt and tool-schema pins still describe foreground fork calls and the
removed run_in_background switch. Git merged this new scenario cleanly,
but its actual tool composition uses this PR's managed activation tools,
so CI rejects the stale request header and shared delegation guidance.
Refresh only the scenario's system-prompt and tool-schema pins. Preserve
its Session recording, model replies, Mods behavior, and runtime code.
The expectation remains a strict comparison with the actual shipped tool
assembly; no comparison rule is relaxed.
Validation: the owning lib-mode scenario passes refresh and then strict
replay. The original mismatch is recorded in the current-head snapshots
and artifacts CI job.
The two-stage real-stack workflow test still expected an empty parent
catalog after the caller-delivery discovery fix. That obsolete assertion
fails deterministically in both Linux and Windows coverage jobs: the two
local children now correctly leave continuable membership records.
Compare catalog identities and modes with independently observed workflow
agent-start identities. Keep the assertions that neither parent followup
nor steer was called, and that both child Agents reached disposal. This
checks the intended separation between durable discovery and result
notification rather than restoring the old missing-navigation behavior.
Validation: all three workflow integration cases pass. The original empty
catalog assertion is the exact failure in both current-head coverage logs.
A later summary can fail while appending turn/start, before AgentLoop
advances phase.turn. Its agent/error event therefore carries the previous
completed turn number. ACP ignored that matching number and reported the
old completed outcome. A failed turn/end append was also ignored and
returned cancelled despite no cancellation.
Retain every live Agent failure observed while the prompt owns queued
work. A subsequently committed turn/end supersedes that live failure and
provides the authoritative terminal reason. If the start/end append fails,
no terminal event clears the error, so ACP rejects the prompt. Remove the
unused turn-number argument from the bridge callback; numeric equality is
not evidence that a failure has a durable outcome.
Add deterministic regressions for later turn/start and turn/end append
failures after an earlier completed turn. Both failed before this change:
start failure returned end_turn, while end failure returned cancelled.
Normal represented model/turn errors retain their existing error mapping,
and the existing cancellation/recovery cases remain covered. Update the
bilingual ACP behavior documentation.
Validation:
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts -t 'rejects a later'
rejected both pre-fix outcomes (2 regressions reproduced)
- pnpm exec vitest run packages/acp/acp/tests (148 passed)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm run verify-translation-pairing --write packages/acp/acp/README.md
- git diff --check
The ACP completion loop previously observed root idle and no children,
then awaited outputTail outside that loop. An autonomous root turn could
start while an earlier image update was blocked in conversion. Turn
correlation adopted the new turn and cleared endReason, but settlement
did not wait for that turn. Releasing the old image therefore returned
cancelled without any cancellation while the root was still running.
Keep committed output delivery inside the normal completion observation.
After output drains, re-read root status and Session sequence before
settling; any new root activity returns to the idle/descendant checks.
Read mutable cancellation and idle state through local observation
functions so each post-await decision uses the current values. Preserve
the separate cancellation path and its prompt output drain.
Add a deterministic regression that blocks the initial image update,
allows the first idle/no-child check, starts a gated autonomous root turn,
and releases the original image. The prompt remains pending until the
later turn completes, then reports end_turn. A subsequent ACP prompt
runs another real local subagent through the shared delegation fixture.
The bilingual README defines work accepted during output draining as
part of the still-open ACP activity interval.
Validation:
- The new focused regression failed before the fix: it observed cancelled
while the later root turn remained gated.
- pnpm exec vitest run packages/acp/acp/tests (146 passed)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts (39 passed)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 passed)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm run lint:contracts-ready
- pnpm run doc-sync (41 gates passed; doc-typecheck identified an optional
Agent in the extracted test fixture, corrected with an explicit guard)
- pnpm run doc-typecheck (Host build and all 79 documentation blocks pass)
- pnpm run verify-translation-pairing --write packages/acp/acp/README.md
- git diff --check
Independent rereview confirmed both completion races are fixed and that
an original image-delivery failure remains attributed to the prompt even
when a later root turn has already started.
An ACP prompt can be waiting for descendants while its root is idle.
Cancellation aborts that wait and calls agent.cancel(), but a child's
settlement message can wake the root in the next microtask. The wait race
then returns with the root running, causing settlement to enter another
whenIdle() before checking cancelRequested. An unrelated long-running
summary consequently retains the cancelled prompt slot indefinitely.
Recheck cancellation immediately after the descendant wait, before any
new root idle wait. Cancellation stops work present at the cancellation
call and releases that prompt; child messages accepted afterward remain
autonomous work. Do not re-cancel those later turns or drain descendants,
which would either race further wakeups or close future child admission.
Document this cancellation boundary in the bilingual ACP README.
Add a deterministic regression that delivers a child message in the
microtask after agent.cancel(), gates the new root turn, and verifies the
original prompt returns cancelled while that turn is still gated. After
releasing the autonomous turn, a second ACP prompt invokes a real local
subagent activation through a tool and completes without another cancel.
Declare the test's subagent dependency and retain only its workspace
importer change in the lockfile.
Validation:
- The new focused regression failed before the fix: the prompt result was
undefined while the late root turn remained gated.
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts (38 passed)
- pnpm run verify-translation-pairing --write packages/acp/acp/README.md
- git diff --check
A registered provider can be used in a composition without the agents
service. startActivation checked the absent registry against the supplied
parent first, so it reported UNAUTHORIZED instead of identifying the
missing runtime dependency with CONTINUATION_UNAVAILABLE. This misdirected
callers toward parent identity even though no activation manager existed.
Require the manager before checking the live parent. Keep provider lookup,
capability validation, and parent identity enforcement in place; provider
startup still cannot run in an incomplete composition. Document the two
failure conditions in the owning bilingual README.
Validation: the new missing-runtime regression failed with UNAUTHORIZED
before the change and passes afterward. All 51 service tests pass, including
unsupported providers/capabilities and invalid parent identity. The changed
README pair verifies successfully.
The unified delegation tool returns as soon as an activation accepts its
prompt. SDK and ACP child runtimes could therefore reach root idle while
an admitted fork was still running. Both external providers interpreted
that idle edge as successful task completion and disposed the runtime,
returning a waiting message while cancelling the nested work.
Add the opt-in SDK session/wait request for existing SDK-owned sessions.
It waits for root idle, managed descendants, and any resulting root turns,
then requires stable idle state and Session sequence across the final
check. Preserve raw session.status notifications and HarnessSession.run's
receipt-to-next-idle API; only the task-owning SDK provider uses the new
completion operation. Subscribe before prompt admission, validate and
fold queued root events through the FIFO wait response, and retain
accepted partial output on cancellation or transport failure.
Keep ACP prompt ownership through descendant completion and later root
summary turns, including their stop reasons and output-delivery failures.
Race child waiting against the existing prompt cancellation controller;
cancellation releases the prompt without permanently closing descendant
admission or repinning the original model selection onto later turns.
Session close retains child-first descendant teardown.
Add real Loader/profile regressions with a gated nested fork for SDK and
ACP completion, cancellation, and final-summary failure. Add focused
coverage for unknown/malformed wait requests, SDK root summary ordering,
ACP cancellation followed by another prompt, and later-summary image
output draining/failure. Update the five owning bilingual README pairs.
Validation:
- pnpm run build
- pnpm run typecheck
- pnpm exec vitest run packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts packages/sdk/server/tests/server.spec.ts packages/acp/acp/tests/turns.spec.ts (123 tests at initial fix)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts packages/sdk/client/tests/sdk-client.spec.ts (80 tests)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts packages/sdk/server/tests/server.spec.ts (70 tests)
- pnpm exec vitest run packages/acp/acp/tests (144 tests)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 tests)
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 tests)
- pnpm run test:docs (21 gates)
- pnpm run doc-sync (42 gates)
- pnpm run lint:contracts-ready
- git diff --check
The unchanged SDK next-idle API remains covered by its existing client
tests; no raw notification or persisted Session event format changes.
A structured local activation now accepts continuation messages, but its
terminal predicate checked only delegated children. A correction accepted
during the model request could therefore remain in either Inbox queue while
the earlier response committed structured_output. The loop later consumed
the correction, but the terminal tool guard prevented revising the captured
value, so callers could receive the obsolete answer as completed work.
Require both Inbox queues to be empty as well as all owned children to be
released before staging a structured result. Keep input admission closed
from that synchronous staging point through authoritative result policy,
including the enclosing PTC result. Derive this admission state from the
existing staged, pending, and captured records instead of introducing a
second lifecycle controller. Failed results delete their stage and reopen
input; successful capture keeps the existing terminal behavior. Caller-owned
delivery, complete final text, descendant ownership, and cold resume remain
unchanged.
Add deterministic barriers for accepted steering and queued input, and for
Native/PTC post-execute acceptance and rejection. All six new cases failed
against the unchanged runtime for stale results or incorrectly accepted
late input, then passed with the fix. Extend the authored headless structured
scenario to reject answer 41, consume a parent correction, and commit 42.
Its two own schema sidecars also need the current master bash/edit/write
definitions: retaining their previous definitions fails verifyHeaders on
this pinned base. Update the owning bilingual result-admission documentation.
Validation:
- pnpm run build:native-system
- pnpm exec vitest run packages/subagent/subagent/tests/structured.spec.ts
packages/subagent/subagent/tests/continuation.spec.ts
packages/subagent/subagent/tests/external-activation.spec.ts (214 passed)
- pnpm run test:snapshot:refresh snapshots/session/headless.snapshot.ts
-t subagent-structured-parent
- pnpm run test:snapshot snapshots/session/headless.snapshot.ts
-t subagent-structured-parent (1 selected scenario passed)
- pnpm run test:docs (21 gates passed)
- pnpm run doc-sync (42 gates passed)
- pnpm run lint completed its host build; after correcting test deferred
generic types, pnpm run lint:contracts-ready passed
- git diff --check
SubagentRun.id previously promised uniqueness only within one parent,
but the unified manager indexes every external activation by that id in
one runtime-wide resident map. Publication also checks the live Agent and
Session registries. Two compliant-looking provider results could therefore
collide even when they belonged to different parents.
Document the existing shared Session id namespace across all parents,
providers, and local Sessions. Update the package reference, bilingual
type-equivalent declaration, and the current upgrade guide so custom
providers know to replace parent-local counters with fresh execution ids.
The shipped ACP, DSH SDK, Codex, and Claude Code providers already mint
UUIDs; no runtime mapping or additional retained identity state is needed.
Add a cross-parent collision test through startActivation. It proves that
publication rejects the duplicate, disposes only the new external handle,
leaves the second parent catalog empty, and preserves the first catalog
entry and execution result through normal cleanup.
Validation: all seven external-lifecycle tests pass, as do scoped bilingual
pairing, verify-upgrade-guides, doc-sync, and full lint. Cordis catalog and
inspection-catalog regeneration produces no tracked artifact changes.
The activation refactor removes tool-subagent configuration keys, changes
the model-visible result from foreground or Job-backed output to an
activation receipt, and removes a published in-process driver package.
Updated package references describe the new behavior but do not tell an
existing deployment or package consumer how to migrate those surfaces.
Add the bilingual v0.2.1-alpha.1 upgrade guide and pairing record. Explain
which cordis.yml keys and tool arguments to remove, how to consume parent
completion notices, when host code should await caller-delivery results,
and how publication changes cancellation ownership. Name the existing
Spawn/Fork providers and prepareContinuable API as the driver replacement
without adding a compatibility adapter or promising a foreground mode.
Validation: verify-upgrade-guides, scoped verify-translation-pairing,
verify-md-links, and doc-sync pass. A temporary runtime probe executed the
guide's exact YAML and TypeScript fences and confirmed the caller result,
catalog membership, and absence of a parent notice. The four existing
parent-result tests pass. The probe was removed after execution.
The retained continuation decision still claims a public followup method,
a bare MessageId start result, no activation result/cancellation ownership,
and no human Queue/Steer operations. Those statements contradict the current
startActivation receipt and adjacent-Agent/host message operations already
documented in the same record. The subsystem introduction also says the
activation result can settle before cleanup, despite the manager resolving
that result after teardown and notification.
Correct the current realization in the English/Chinese active documents.
Keep the original single-inbox and residency rationale, distinguish accepted
message identity from the activation receipt, and state the actual result
and disposal timing. Do not change implementation, archived decisions,
provider-level result timing, or runtime APIs.
Validation: doc-sync passed 41 gates on the first run. Its remaining pairing
gate found one Chinese README target using the English locale; correct that
target, re-record the pair, and verify both changed pairs successfully.
The two subagent scenarios introduced by this PR retain tool-schema pins
from before master's Bash deletion/path guidance and filesystem argument
ordering changes. Their actual runtime behavior and recorded model replies
still replay, but strict assembled request-header comparison now rejects
those stale schema descriptions and required-argument order.
Refresh only the three owning schema-pin files against the merged shipped
profile. Preserve every Session recording, model stream, event reference,
timestamp, prompt, and runtime implementation. This keeps the real header
assertion instead of weakening the normalizer or rolling back master tools.
Validation: both affected headless scenarios pass in refresh mode and then
in strict lib-mode replay; the other 33 selected replays passed unchanged.
The recording workflow currently points only to a repository .env file.
A valid key can instead live in ~/.dsh/.credentials.yaml, so missing
DEEPSEEK_API_KEY and .env do not establish that real-model recording is
blocked.
Name the managed credential source and route it through the existing
credentials-local path override when the recording uses a scratch
DSH_HOME. Preserve application-state isolation and keep secret resolution
inside the normal provider rather than printing or manually extracting
key values. This changes only recording instructions.
The collapsed Windows caption toggle and New Session kept a 50% circle after
the unified radius standard moved compact icon buttons to --dsw-radius-sm, so
the toggle's hover fill changed shape when the sidebar collapsed.
Integrate master 78ae4fc2d4d8ecaebb4b674028f781a358b92cc9 after the four
independent review fixes. Keep this base update separate from those fixes
and from subsequent review changes so the integration is independently
reviewable and the observed-master checkpoint remains preserved.
Retain activation-owned execution, caller-independent local discovery,
and unconditional parent final-answer delivery. Preserve master's preset
scoped scheduling tools and all delegated-child schedule deny rules. Move
the new scheduling regression from the removed driver package into the
surviving subagent package and run it through a real Spawn activation.
Preserve master's removal of runtime invariant plugins, dependencies,
exports, and test mounts. Keep context disposal before fixture directory
cleanup. Combine Python workflow ordering with activation/catalog events
without inventing unsupported source-plane import paths.
Keep all frozen Agent Note archives byte-for-byte identical to master;
do not accept Git's automatic edits to renamed archived notes. Retain
current lifecycle and delivery facts in active documentation, repair its
links, and regenerate catalogs/persistence schemas from merged source.
All 62 master persistence roots remain; only subagent/catalog gains the
PR's external membership extension. Keep master's third-party lock graph
and update only workspace dependency changes.
Validation before this checkpoint: 683 focused tests passed, seven
Windows-only cases skipped; 2,772 archived artifacts verified. Generated
catalog and active-pair checks passed. Fresh artifact build and remaining
build-dependent documentation checks continue before publication.
A local child can have send_message available and still finish with a plain
text answer or use the tool only for progress. Selecting completion content
from tool availability drops that captured answer and leaves the parent
with a status-only notice. The recorded spawn case already demonstrates
this: its child returns CHILD_OK without calling the available tool.
Remove the includeClosingText policy and pass every captured final text
through the existing text-only notice renderer. Preserve structured
results, safe diagnostics, external continuation wording, and caller-only
result delivery. No delivery ledger or additional lifecycle state is
needed. A child that sends its final answer explicitly may repeat it in the
completion notice; that cost is preferable to making result delivery
depend on model compliance.
Extend the real-loop parent-result regression with an available-but-unused
messaging tool, restore the unconditional lifecycle expectation, and pin
text alongside structured results and diagnostics. The new regression
fails on the prior implementation because CHILD_RESULT_42 is missing.
Update the owning bilingual descriptions and the exact parent notice
content in headless and TypeScript SDK recordings. Keep replay streams,
released generations, and unrelated recorded clocks unchanged.
Validation: 317 focused lifecycle/workflow tests; full build; 26 focused
headless/SDK replays; Python SDK advanced/recovery smoke (unchanged
fixtures); test:docs (20 gates); doc-sync (42 gates); full lint;
git diff --check.
Caller delivery previously suppressed both settlement messages and local
catalog membership. Workflows therefore stopped publishing the durable
parent-child edge that the parent header and recursive discovery require.
The workflow panel only opens running members, and the ordinary sidebar
hides subagent Sessions, so completed workflow transcripts had no normal
navigation path even though their logs still existed.
Record every successfully admitted local child in its parent's catalog,
independently of the result recipient. Membership answers which local
Sessions belong to the parent; delivery answers who consumes this activation's
result. Keeping those decisions separate restores completed-child and
descendant discovery without duplicating workflow completion messages or
adding return guidance to caller-owned work. Publication failures retain
the existing rollback transaction for both delivery modes.
These local children intentionally retain the unified continuable mode:
list_agents includes them, the parent catalog opens their transcripts after
reload, and the existing direct-parent controls can cold-resume them. No
one-shot execution path or additional lifecycle state is introduced.
Caller-owned external executions still use their consumer's membership
records because they have no local Session to open.
Update the existing bilingual service/workflow documentation and decision
records, refresh the owning workflow/Ralph/advanced Session recordings and
Python SDK event expectations, and preserve unrelated recorded stream times.
Validation:
- Full repository build passed.
- 443 subagent, workflow-host, and Ralph tests; 14 list_agents tests passed.
- Four focused headless workflow/advanced snapshot refreshes and replays passed.
- Three shipped-Web workflow tests passed, including completed-child catalog
navigation after reload and the continuable composer.
- Python SDK advanced and scheduler-recovery snapshots refreshed and replayed
through the built dsh CLI with Python 3.12.
- Three discovery regressions fail with the original catalog exclusion and
pass after restoring this fix.
- test:docs (20 gates), doc-sync (42 gates), lint:contracts-ready, Host and
Client TypeScript builds, and git diff --check passed.
Portal the session-header job popover to the document body and place it
with useAnchoredPosition, so the clipping center column and the right
sidebar no longer cut off its right edge.
Restore six expected-output files to the PR merge-base versions so the
subagent activation refactor does not carry unrelated recording churn.
The persistent-tools SDK notifications contain the same parsed records;
only JSON object key order changed. The multi-turn, empty-response-retry,
error-finish, packed-chunks, and text-turn writer oracles were refreshed
from V3 to V4 representations together with volatile stream clocks and
hook timing. These cases do not exercise the changed subagent behavior.
The snapshot harness explicitly accepts older supported writer oracles
and restores them through the strict Session catalog before comparison.
Keeping the existing outputs retains the same assertions and historical
coverage without rewriting canonical released Session generations.
Validation:
- pnpm install --frozen-lockfile completed.
- pnpm run build passed, including the host addon and runtime artifacts.
- Six focused lib-mode keyless replays passed: SDK multi-turn and
persistent-tools; headless empty-response-retry, error-finish,
packed-chunks, and text-turn (2 files; 6 passed, 155 filtered out).
- Parsed persistent-tools protocol records are equal before and after.
- All six files exactly match merge base 8af775c480d73c4692813387622fe16bf8eadf37;
no canonical Session recordings or production files changed.
- git diff --check passed.
Remove the second child_process mock added during subagent integration.
The existing mock already isolates Git from concurrent workspace writes
and reads the fixture's sourceCommit and dirtyFiles controls. The later
factory replaced those controls with a constant commit and clean status,
so the Windows tests could no longer detect changes made while awaiting
packaging confirmation.
Keep the original mock and its fail-loud command handling. This restores
the existing git-head and git-status rejection cases without changing
packaging behavior or adding a second fixture abstraction.
Validation:
- Owning Vitest file: 3 passed, 7 Windows-x64-only cases skipped on macOS.
- Prior isolated duplicate-mock probe: 2 failures with the duplicate,
2 passes with only the original state-controlled factory.
- Original exact-head Windows CI fails the same two Git-change cases.
JavaScript object enumeration moves canonical array-index property keys ahead
of other string keys. Describing every numeric-looking Loader ID as reordered
is too broad: leading-zero and other noncanonical numeric strings remain
ordinary string properties.
Name the exact key category in the inventory fixture's local comment. The
inventory-* IDs exclude every canonical array-index key, and the real Loader
activation, fixture order, output assertions, and production code are unchanged.
This addresses the automatic review's nonblocking terminology clarification.
Validation:
- Type-aware Oxlint passed for the owning inventory.spec.ts.
- git diff --check passed.
- No behavior or test inputs changed, so the already observed 46-test and
74/74-branch evidence remains applicable.
`collectCliHelp` stops propagating an alias when it reaches a page
already on the current path, so a `help [command]` row that prints its
parent's help no longer recurses until the stack overflows. `serve|s`
command rows are split into separately collected names.
The CLI reference and upgrade guide describe the forwarded-argument
boundary that Commander applies, including a leading pnpm option, and
args.spec.ts pins that `--profile` placed after a leading pnpm option is
forwarded. Generated pages use the repository's two-line generated
header, and docs/AGENTS.md lists the CLI help among the generated
references.
Refs #4114
`scripts/gen-cli-help.ts` runs the source `dsh` launcher with temporary
Harness and Agents homes and captures the launcher, `dsh plugin`, and every
shipped profile's text help (profile names come from `PROFILE_TEMPLATES`).
It recursively discovers subcommands from each page's `Commands:` section,
shows identical alias help once with every invocation, and writes
`docs/cli-help.md`, its Chinese page, and their pairing record. The record
resolves links between the two pages as written, so a first run already
matches `verify-translation-pairing`. `verify-cli-help` checks all three
files and runs as the `cli-help` doc-sync leaf, which the static CI lane
also runs on an unbuilt tree.
`dsh plugin` now owns `-h`/`--help` and reads its own options only before
the forwarded pnpm arguments: `dsh plugin --help` prints its help instead of
failing on the missing `--profile`, while everything after the pnpm command
(including `-h`, `--help`, and `--profile`) reaches pnpm verbatim. A
`--profile` placed after the pnpm command is no longer accepted; the
v0.2.1-alpha.1 upgrade guide covers the migration.
Refs #4114
The parameterized versioned/name-only inventory test reversed plugin arrival
order but left Loader entry IDs random. EntryTree generates hexadecimal IDs
and enumerates its object-backed store with Object.values(). Numeric-only
IDs become JavaScript integer property keys and enumerate before other keys,
so the fixture's declared arrival order did not determine the comparator's
operand order. Both parameter cases could exercise the same orientation
while retaining the correct sorted output, intermittently leaving the right
version's nullish fallback uncovered by the per-file 100% CI gate.
Use the typed root-group update API with fixed non-integer inventory-* IDs
for this fixture. Both requested arrival orders now reach the inventory
collector without integer-key reordering. Keep real Loader imports and
activation, duplicate entries, and the existing output assertions. Explain
the non-obvious identifier requirement locally in the test. Production
sorting, package identity rules, and request fields are unchanged.
A controlled negative uses numeric IDs for versioned entries: all 46 tests
pass, but package source branch coverage is 73/74 (98.64%), with index.ts:186
uncovered exactly as in the observed node 24 coverage failure. The final
non-integer IDs pass the same owning file with 74/74 branches (100%). No
global Math.random interception, retry, timeout, or coverage exception is
introduced.
Validation:
- pnpm install --frozen-lockfile --ignore-scripts installed the existing lockfile.
- pnpm exec vitest run packages/llm/plugin-package-inventory-deepseek/tests/inventory.spec.ts --coverage --coverage.include='packages/llm/plugin-package-inventory-deepseek/src/**/*.ts' --coverage.reporter=text --coverage.reportsDirectory=/tmp/dsh-merge-5719/inventory-negative
reproduced the controlled coverage failure with 46 passing tests.
- The same command with reportsDirectory=/tmp/dsh-merge-5719/inventory-fixed
passed 46 tests and 100% statements (80/80), branches (74/74), functions
(15/15), and lines (68/68) for the package's executable source.
- pnpm --silent run change-scope --base 70996a53b84212db9efafe21a4db3dddd46b47ec
confirms the sole changed path is the owning inventory.spec.ts.
- git diff --check passed.
The capture clone shares one source with the caller's body stream, so the
caller key alone keeps the Request reachable on every path, including when
capture has already stopped at its byte limit. Cover that path in the
regression test and document the process-wide --expose-gc flag.
Refs #5614
When a visible shared file became a content duplicate, reconciliation emitted
a removal notice and discarded its metadata. Its durable state then remained
remove, while the metadata fast path accepted only candidates without any
previous record. Every subsequent unchanged pass therefore reread the file,
even after a later read had populated the cache. This also affected the path
where an edited earlier winner made an unchanged later file a duplicate.
Retain the still-present duplicate's metadata with the removal's deferred
version update. Pass the already-cached state when removal follows an unchanged
metadata probe, and the newly read state when the duplicate's content changed.
Apply this metadata only when the associated removal survives rendering, using
the existing exact-transition filter; confirmed absence still clears metadata.
Allow both never-rendered candidates and previous removals to skip content reads
when unchanged provider metadata and an earlier kept digest identify the file as
a duplicate. Continue probing metadata. Once the winner changes or disappears,
read and promote the shared file normally, then suppress unchanged follow-ups.
Document the metadata guarantee in the local cache types and bilingual README.
Model-visible messages, source records, ordering, and byte budgets stay the same.
Add four production-plugin regressions covering duplication after either file
changes, then promotion after the winner is edited or deleted. They inspect the
provider's actual content reads across two unchanged passes and require exactly
one shared read for promotion, rather than trusting the context's self-report.
Validation:
- All four new cases failed before the fix because each unchanged pass reread
the shared file despite unchanged metadata.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'caches a removed duplicate': 4 passed after the fix.
- README pairing was rerecorded; git diff --check passed.
- The removal regression narrows its source kind and compares the change as a
plain typed value; no matcher carrying any is assigned into an expected record.
- Type-aware Oxlint passed for the three affected packages and the new
metadata-failure fixture; focused cache regressions passed again after the
assertion correction.
A content-deduplicated shared file has no visible change record. If its
metadata became unavailable while the harness-home winner changed or vanished,
the reconciler skipped the failed candidate and still published the winner's
replacement or removal. Guidance that remained in the shared file disappeared
from the model's effective instructions. Content-read failures had the same
partial-group behavior even when metadata probing succeeded.
Rollback the probed candidate group whenever any metadata probe is unavailable
or its bounded content read cannot complete. Include candidates without a
visible predecessor and candidates previously removed as duplicates. Reuse
one rollback operation for proposed messages, deferred version updates,
metadata mutations, and retained digest bookkeeping, so a later successful pass
can reconstruct the complete group independent of cache warmth.
A filesystem path can also appear as a project instruction when the Harness
home is the project root. Releasing observed paths on rollback let that later
alias publish the very content withheld by the failed global group. A transient
failure of the first member also left failed and unvisited paths unreserved.
Factor the existing scope-to-file path calculation and reserve every eligible
member's absolute path for the rest of that pass when its group rolls back.
Project aliases therefore cannot bypass group preservation, without additional
filesystem operations. The shared resolver keeps probing and rollback path
calculations identical. Add metadata, content-read, and one-shot first-member
failure regressions for this supported same-file layout and its recovery.
Keep cancellation propagating to the caller. Keep explicit baseline-budget
exclusions outside observation rollback, because their removals are deliberate
policy decisions. No new session fields, baseline identities, polling, retries,
or process-global state are needed. Clarify the metadata/content and hidden
candidate guarantee in the bilingual README.
Add four real-plugin outage/recovery tests for metadata or content failure while
the retained home file is edited or deleted, plus a cold-cache reconciliation
regression. Add an authored headless session scenario with instance-local
provider failure injection: request two keeps baseline A while shared metadata
is unavailable; request three receives the home B replacement and shared A
addition together after recovery. The fixture restores the provider method on
disposal and asserts the real home/shared files before restoring observation.
Declare its test-only filesystem dependency without changing runtime packages.
Regenerate the persistence catalog's source anchors after the shared path
helper import shifts AgentInstructionSource by one line. Its persisted fields
and SHA-256 type fingerprint remain unchanged; no type acknowledgement or
Session migration is required for a source-location-only update.
Validation:
- All five new regressions failed with the intended partial-group emissions
before the source fix.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'hidden duplicate has|cold global group|unavailable active candidate|unavailable scope': 8 passed.
- pnpm exec vitest run packages/context/agent-instructions: 174 passed.
- The focused authored headless snapshot refresh and replay passed.
- Restoring the early skip for an unavailable hidden candidate makes the
headless snapshot fail with the premature harness-home replacement.
- README translations were updated together and their pairing rerecorded.
- Three project-alias regressions failed on the initial group rollback fix,
then passed after reserving failed and unvisited candidate paths.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'frozen across a project alias|hidden duplicate has|cold global group|caches a removed duplicate': 12 passed.
A baseline can deduplicate identical harness/shared instructions before the
byte renderer omits their retained winner. Removing every deduplicated scope
from excludedScopes then made the shared copy eligible on the next unchanged
step. It injected the broader instructions the baseline had deliberately
omitted, contradicting the retained-baseline budget policy. Project sibling
copies had the same problem.
Allow a duplicate to leave the exclusion set only when the included baseline
contains its candidate group and trimmed digest. Match the digest as well as
the group: a different sibling may fit the budget while the duplicate's winner
does not. Use the included files' original content so truncated and empty
represented winners retain their ordinary duplicate-promotion behavior.
Copies of omitted content inherit that omission for the visible baseline.
Duplicates of represented content remain probed and can be promoted when their
winner changes or disappears. Keep both README languages, the existing shared
root decision, and the local result-field documentation consistent with this
rule. No durable event fields or baseline identities change.
Add plugin regressions for identical global copies across repeated steps and
resumes, and an omitted project digest with a different retained sibling. Add
an authored recorded-session scenario that runs the shipped headless profile
with identical 1900-byte global files and a 700-byte budget: its two model
requests retain one project-only baseline and never inject the shared copy.
Validation:
- Both new plugin regressions failed on the reviewed implementation with two
contexts instead of one, then passed with the fix.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'duplicate budget-omitted|different digest fits|content-deduplicated shared|changed shared file': 4 passed.
- pnpm run test:snapshot:refresh snapshots/session/headless.snapshot.ts -t agent-instructions-budget: passed.
- pnpm run test:snapshot snapshots/session/headless.snapshot.ts -t agent-instructions-budget: passed.
- Reverting only src/index.ts to the reviewed head makes that snapshot fail
with an extra agents-global set and 304 bytes of omitted global content.
- pnpm run test:snapshot scripts/session-snapshot-corpus.corpus.ts: 3 passed.
- Scoped bilingual pairing was rerecorded and verified; git diff --check passed.
The shared-root Agent Note still describes agentsHome as an accepted row field
and gives explicit row configuration precedence. The decision retained those
statements from the configurable-root proposal, although the instruction loader
now exposes neither dshHome nor agentsHome. Maintainers could therefore add an
unsupported row override or mistake the skill provider's root options for the
instruction loader's API.
State that instruction loading resolves the harness home from DSH_HOME or
~/.dsh and the shared agents root from DSH_AGENTS_HOME or ~/.agents. Keep the
shared resolver and its tilde/blank-environment behavior, while distinguishing
dsh-skill-filesystem's retained explicit home fields for its own skill roots.
Update the English and Chinese Decision/Consequences passages together and
record their confirmed pairing hashes. Root order, durable scope keys, and the
other decision rationale are unchanged.
Validation:
- pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md
- pnpm run verify-translation-pairing .agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md: one named pair consistent
- pnpm run verify-agent-note-format: all 276 Agent Notes conform
- git diff --check, scoped to the shared-agents-root-instructions EN/ZH/sidecar triplet: passed
The user-global instruction scope now reads two roots: $DSH_HOME/AGENTS.md
first, then the shared agents root that other agent tools use
($DSH_AGENTS_HOME, else ~/.agents). Both resolve from the process environment
through @deepseek-ai/dsh-home-paths; no config field is added, because the
stack base (PR #5718) removed the per-row home override.
The two files are candidates of one candidate group: identical trimmed content
renders once from the harness home, an unobservable candidate preserves the
group's last-good state, and reconciliation probes both roots so external
edits, removals, and new files become visible change notices. A candidate
dropped as a duplicate keeps being reconciled, so deleting or changing the
retained candidate promotes it. The harness-home file keeps its recorded
`user-global` scope key; the shared file carries `agents-global`.
A user-global delta states `These user-global instructions apply to all work.`
instead of naming its reconciliation scope, and a project path whose first
component would collide with a user-global scope directory renders with a
leading `.` so it keeps its own scope.
The removed dshHome row key still redirected instruction loading away from
DSH_HOME. Schemastery's non-strict object validator preserves unknown keys,
and resolveConfig forwarded the validated row wholesale to the library
discovery normalizer, which intentionally accepts an already-resolved home.
Removing the public field and schema entry therefore left the old runtime
override active and contradicted the upgrade guide.
Copy only the declared discovery controls when normalizing a plugin row.
Home resolution now follows process policy even when the row retains a stale
dshHome or another undeclared home field such as agentsHome. This allowlist
also applies when the shared-agents-root stack layer adds its internal home
parameter, without widening the deployment-facing configuration.
Keep the exported discovery and baseline-loading helpers' explicit home
parameter: it carries a library owner's already-resolved path, rather than
granting a plugin row its own home. Extend that regression through baseline
loading to preserve this distinction. Document the ignored stale row key in
both package READMEs and refresh their pairing record. No Session format,
candidate ordering, budget, or instruction rendering changes are required.
Add a production Cordis/schema regression that mounts a row with both stale
home keys and checks model-visible baseline and refresh content. Before the
fix, its baseline contained the stale row instruction instead of the process
home instruction. Both stages now use the process home, and the same test
will reject an agentsHome leak after propagation into the upper stack layer.
Validation:
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'ignores a stale row home'
failed before the source fix with the intended content mismatch.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'ignores a stale row home|uses an already-resolved home'
passed both focused tests after the fix.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts
passed all 157 tests with both stale row keys present.
- pnpm exec tsc -b packages/context/agent-instructions/tsconfig.json passed.
- pnpm run doc-sync passed all 42 gates.
- pnpm run test:snapshot snapshots/session/headless.snapshot.ts -t 'agent-instructions'
passed the selected recorded-session replay.
- pnpm run verify-translation-pairing --write packages/context/agent-instructions/README.md
refreshed the bilingual record; doc-sync verified it.
- git diff --check passed.
`resolveDiscoveryConfig` accepted `DiscoverOptions.dshHome` but resolved the
home from the environment alone, so a caller passing an already-resolved home
silently received the process home while the option comment claimed otherwise.
It now normalizes the passed value, which keeps the plugin's mount-time
resolution authoritative for discovery and reconciliation. A test pins that an
owner-resolved home wins over `$DSH_HOME`.
Review follow-ups:
- The guide claimed the removed field was the last per-row home override;
other providers (skill-filesystem, shell-env, credentials-local,
subagent-dsh-sdk, attachment-local) keep their own fields, so it now states
that the removal covers the instruction loader, and step 1 also names the
home-level `$DSH_HOME/cordis.patch.yml` layer.
- `DiscoverOptions` regains an already-resolved `dshHome` that `apply()` passes
from its mount-time `resolveConfig()`, so baseline discovery and
reconciliation share one resolution point instead of reading the environment
separately.
- The package README says the loader reads the process home rather than "the
same home as sessions, skills, and storage", and the resolver Agent Note
states what the skill provider's fields actually select.
- `discoverBaselineInstructionFiles` JSDoc no longer lists a removed option.
The agent-instructions row accepted `dshHome`, letting one consumer read a
different harness home than sessions, skills, and storage. Home is process
policy: the plugin now resolves `$DSH_HOME`, else `~/.dsh`, through
`resolveDshHome()` from `@deepseek-ai/dsh-home-paths`, and the config field and
the library's explicit-home option are gone.
Tests and both snapshot fixtures pin the home with `vi.stubEnv('DSH_HOME', ...)`
and the harness's existing `DSH_HOME` export instead of row configuration.
`dsh-skill-filesystem` keeps its own `dshHome`/`agentsHome` fields.
The removed cordis.yml key gets an upgrade guide.
Node's fetch forwards a caller's AbortSignal only while the Request it was
given is reachable. The fetch observer creates that Request locally, so after
garbage collection an abort after response headers no longer cancelled the
body: capture never finished, Network.loadingFinished was never sent, and the
connection stayed open.
Fixes#5614
Review follow-up: `const closed` beside the boolean `this.closed` reads as the
flag, so name the promise `closing` at every site; the test-side comment
repeated the production mechanism verbatim, so each test keeps only the
ordering assumption its fake encodes.
The EPERM path in the Windows coverage failure also matches the HMR
exact-config watcher: it watches the temp harness home with chokidar's
default 2 s write-settle window, and that spec constructs it on the very
directory its afterEach deletes. Apply the same containment to that
watcher and to the skill-filesystem root watcher, the remaining owners
that enable awaitWriteFinish.
A sloppy-mode .cjs or .js file can spell the name as '\141ppendPluginRecord',
which TypeScript decodes to appendPluginRecord, but the prefilter only
removed the backslash and skipped the file. The prefilter now decodes a
legacy octal escape as TypeScript does, taking three digits only after
0 to 3, so the value stays within 0o377. The spec rejects two spellings
that use three-digit octal escapes, and tests the decoder itself for
the digit limit, which no spelling of the name, whose letters are all
0o1xx, can exercise.
chokidar's close() drops every listener while a scheduled write-settle poll
can still run. That straggler stats a document the home teardown is
deleting, which Windows reports as EPERM, and an 'error' emission with no
listener is rethrown as an uncaught exception: the whole coverage
partition fails while every test file passes.
Re-attach the provider's error listener across the close so the straggler
stays inside the provider, and make it a no-op after disposal.
The caller gate parsed only files whose text held appendPluginRecord or a
\u escape, so a release package could reach the function through
S['\x61ppendPluginRecord'], a \u{61} code point escape, a needless
character escape, or a line continuation, which TypeScript reads as the
same string. The gate now also parses a file when its text with every
escape unescaped holds the name, and its spec rejects each spelling.
- Make the record commit an ES private method, so appendPluginRecord()
is its only runtime entry.
- Document PluginRecordType and PluginRecord on the session subsystem
page, and state in the README and JSDoc that the V3-to-V4 edge renames
unknown ignorable V3 events into the plugin: namespace and that plugins
outside this repository must not call the writer.
- The caller gate parses only files that contain the name or a \u
escape, documents that wildcard re-exports are not reported, and its
spec reads a temporary repository instead of this checkout.
- Cover Session.fromRestore in the record test and name SessionEventMap
in the catalog generator's diagnostic.
classShape dropped only #private properties; methods and accessors with a private name reached the declaration text the cordis_inspect catalog serves. It now drops every member whose name is a private identifier, as the public-api projection in verify-type-equiv does.
Add appendPluginRecord(), which appends one ignorable event whose type is
`plugin:` followed by lowercase slash-separated segments, and
pluginRecordOf(), which reads such an event back. Records are not
SessionEventMap members, so the persistence catalog is unchanged; a build
that does not recognize one retains and skips it, and records never enter
the model-visible surface. Resume and fork carry them with the log, and a
format migration keeps them on a best-effort basis.
Only production source under packages/experimental/ may call the writer.
The verify-plugin-record-callers hygiene gate checks every reference in
repository sources, and the persistence catalog generator rejects a
SessionEventMap member in the plugin: namespace.
Session.append() and the record writer share one commit path, so a record
follows the same reentrancy, detach, and observer-containment rules. The
ignorable-events decision, the session README, the format-version
cookbook, and the plugin-author practices describe the records.
A class's static initialization block has no type-level meaning, and the
public-api projection that verify-type-equiv checks already drops it.
The generated declaration text, which the model-visible cordis_inspect
catalog serves, printed it with the class's public members.
pi-ai 1.0.1 and 1.0.2 bring model catalog updates and adapter fixes:
Anthropic native tool changes define later tools inline, Bedrock binds
replayed thinking blocks, OpenAI-compatible requests can pick sampling
parameters by thinking level, and "model is at capacity" is retried.
The runtime dependencies change only in @anthropic-ai/sdk (0.124.0 to
0.129.0) and @earendil-works/pi-telemetry (^1.0.2).
1.0.2 still re-parses the whole accumulated tool-argument JSON on every
stream delta (earendil-works/pi#9265), so the patch is regenerated
against the 1.0.2 sources: it removes the same six per-delta parses as
the 1.0.0 patch, and only the bedrock-converse-stream.js hunk moves, by a
line.
State the Pi-to-DSH direction of the built-in tool translation, how setActiveTools treats Agent-scoped extension tools, that module-scope extension state is shared by concurrent Sessions, which format edges retain plugin records, and that SessionStartSource already declares clear and compact. Date the npm package count and narrow the plugin note's opening sentence.
Record two proposals from the Pi 1.0.0 compatibility investigation.
The Pi extensions bridge note states the design of an experimental bridge
that loads unmodified Pi extension sources on existing DSH extension
points, and ranks the DSH core mechanisms that are narrower than Pi's
extension API.
The Pi plugins note defers a compatibility layer for Pi's Chord-facet
plugins, whose API upstream declares unstable, and defines six levels of
support with the upstream trigger and the DSH work for each, plus the
runtime differences the plugin model exposes.
pi-ai 1.0.0 adds Sign in with ChatGPT to the `openai` provider, and that
login refuses to start unless the app supplies a stable installation UUID
through LoginOptions.getDeviceId. The harness keeps no installation ID
meant for a third-party account sign-in, so the route offers its API-key
login only; a Loader-composed test pins the offered methods.
catalogProvider(), catalogModels(), and catalogProviderIds() now read one
index that leaves out providers without a chat model, so a hand-declared
route under such a key inherits nothing from pi-ai. The browser-worker
stub gains the builtinModels refusal llm-pi-ai imports and drops the
getBuiltinProviders reader nothing imports. The tool-argument streaming
spec also covers the patched Anthropic Messages path.
The chat-model filter left a provider out whenever pi-ai listed no chat
model for it. The browser-worker preview replaces pi-ai with a stub that
lists no models at all, so every provider was dropped and the empty
directory registration failed the llm-pi-ai mount. Only a provider whose
catalog lists models but no chat model is left out now; the stub gains
the `getAllBuiltinModels` reader the filter calls.
pi-ai 1.0.0 adds classifier and image models to the built-in catalog and
ships `typesafe` with classifier models only. The adapter dispatches chat
requests only, so a catalog provider without a chat model gets no entry in
the configurable-provider directory and no sign-in flow.
Move the pi-ai provider adapter from 0.87.1 to 1.0.0 and re-create the
local tool-argument patch for the new dist files. pi-ai 1.0.0 still
re-parses the whole accumulated argument JSON on every stream delta in
the Anthropic, Bedrock, Mistral, OpenAI Completions, OpenAI Responses,
and pi-messages adapters (upstream earendil-works/pi#9265), so the patch
removes the same six per-delta parses; arguments are still parsed once at
toolcall_end.
The release-age exemption moves to the exact 1.0.0 pi-ai and
pi-telemetry versions, and the generated third-party notices and README
links name the new patch file.
Replace the persisted background marker with a client-only rule. A
foreground run writes run-end before its Tool Step ends and a background
run outlives that Step, so only a Turn closed after a crash or at a fork
boundary shows a run without run-end as interrupted.
The examples subpath alias of the mods bridge points at TypeScript entries
that ship no declarations; a documentation fence never imports them, so the
mapper keeps the authored path instead of failing the gate.
The aliases leave tsconfig.base.json, which doc-typecheck maps to built
declarations, and live in the Vitest projects' resolve.alias; the examples
subpath maps to itself there because example plugin entries ship no
declarations. The README's wrapper fence declares its register import so
the fence compiles on its own.
The coverage partitions list *.spec.ts files only, so the published
*.test.ts files reached no partition and the test kit's mount paths went
uncovered there. examples.spec.ts imports them by computed path; the kit
resolves the mod under test from the outermost describe when the file runs
through a spec.
The band's bordered boxes and buttons draw the theme's 0.5px hairline, as
the elevation gate requires; KNOWN_EVENTS is built from ENGINE_EVENTS
instead of repeating the list.
- Disposal cancels detached runs before the engine waits for timer callbacks,
so a callback parked in a $ call ends instead of blocking unload.
- A forgotten band wakes and ends its watch streams; a refresh asked for
during a pass joins one more pass that settles after it; the Web band drops
an ended stream so the next subscriber opens a fresh one.
- $ has Claude Code's namespaces: an unserved member rejects with
'no implementation for <namespace>.<member>' instead of a TypeError.
- A .catch handler's next makes the rewrite check; a tool rename is refused
like an argument rewrite; a null op answer is reported as no answer;
defineMod returns its disposer from apply; the live turn record is copied
before the chain freezes it; prompt.submit is raised only for batches with
a human message and names the submitting mod as origin.
- $.http.fetch bounds the body while streaming; bandColumns/bandRows are
Config; the client package drops its stale gateway edges; the source
overlay lives in the package and anchors its entries to the file, and the
dsh installation no longer depends on the experimental packages.
A scenario-local guard mod adds a context block after the prompt and
refuses a force push, beside Replay Theater and Token Weather from the
examples; the recording shows the context inside the user's message and
the refusal as the tool result. Blast Radius stays out of the headless
composition because it holds a command until a button press.
Hook results spread the typed result from beneath, element props accept an
explicit undefined as a JavaScript mod passes it, the band's registry
payload is narrowed by assertion, and the example wrappers join the Host
typecheck project.
docs/subsystems/claude-code-mods.md lists every way a mod behaves
differently through the bridge than under Claude Code 2.1.287 — packaging,
events, the chain, $ members, drawing, testing, and the example mods,
including the four official mods assessed by reading. The bridge README
describes defineMod, the band, and the test kit; the band package gets its
README; the group index, release families, config and client catalogs,
module and seam graphs, and the Cordis API catalog are regenerated.
A Client package mounts the bridge's Remote contribution, watches each
session's band through watchBand, draws the serialized tree in the input
dock with Claude Code's Box/Text/Button vocabulary on this theme's tokens,
and sends button clicks back through pressBand with the generation they
were drawn in. An opt-in overlay composes the bridge, the band, and the
three example mods for a source launch.
The bridge keeps one band per session: ui.render is raised through the
mods in load order, the tree's buttons keep their onPress host-side behind
per-drawing action ids, a $.state read during a render subscribes the band
to that slot, and $.ui.open/close/invalidate, the end of a tool.call chain,
and turn.complete redraw it. ClaudeCodeMods is a TypertRemoteService:
watchBand streams snapshots, pressBand runs a button and redraws. null is
an accepted hook answer (a surface drawn empty).
A background workflow run outlives the Tool Step that started it, so the
Web workflow node inferred interruption as soon as that Step closed and
showed live members as interrupted. Mark background runs on
tool-workflow/run-start and infer interruption only for foreground runs.
Refs #5638
A mod is now a plugin built with defineMod() around its register(on,
options); the bridge is the ClaudeCodeMods service (ctx.claudeCodeMods)
that keeps the loaded mods and raises their events. The plugin.json /
hooks.json loader, pluginDirs, and dynamic imports are gone.
prompt.submit context becomes text blocks appended to the human's own
prompt message and $.prompt.submit queues a user-sourced message, so the
bridge adds no message source kind: the persistence record and the
regenerated schema are removed and the artifacts match master again.
Engine correctness from the review round: next() is memoized and every
started run beneath is awaited before a hook's answer settles; a rewritten
tool argument skips the hook with a report pointing at the pre-tool input
rewrite proposal; timers belong to the session whose event started them;
$.tool.call injects a tool's deferred contexts; $.clock.sleep honors the
event signal; a built-in tool answered with { result } is validated against
its output schema before it becomes an error-shaped result; hooks on events
this host never raises are reported at load.
Examples: the three mods from the Claude Code blog post under examples/,
Token Weather and its test unchanged, Blast Radius and Replay Theater
completed from the published fragments, run by their own tests through a
claude-code/testing alias over the new test kit ($.ui.mount/find/press).
Review follow-up on the watchdog rewrite:
- Register the deadline reaction inside `next()` and detach it when the
demand settles. Racing against one watchdog-lifetime promise kept every
settled race reachable through that promise's reactions, retaining each
resolved `StreamChunk` for the length of the stream: a 10,000-value probe
kept 100/100 sampled values before this change and 0/100 after.
- Take the neighboring stall case's 1,000ms idle budget in the new
transport-recovery case: the retry performs a real local HTTP/SSE round
trip, and a 50ms budget would let coverage-lane scheduling decide it.
- Document the contract where it is described: `packages/util/timeout/README`
(both languages) and the bounded-recovery and timeout-library notes no
longer claim that only the transport observes the watchdog signal.
- prompt.submit sees and rewrites only the human's own messages, gains the
rewritten text on an image-only prompt, and keeps only typed result fields
- a mod's $.tool.call reaches only the mods loaded before it, attributed to
the caller, instead of re-entering every mod through the pipeline
- a .catch handler shares the failed hook's beneath state so nothing beneath
runs twice; a late next from an abandoned handler is ignored
- hook results are validated before use; detached runs report rejections
- session.end keeps $.state readable until its hooks settle; agent creation
cancellation abandons a waiting session.start hook; agents is read lazily
- a success a hook marks isError becomes a failure; replacement entries clear
on tools/result; per-agent registrations drop with the agent
- $.store writes are serialized per plugin and bounded in UTF-8 bytes;
$.http.fetch bodies are bounded; timers refuse rescheduling after unload
and unload awaits running callbacks; regex matchers reset lastIndex
An aborted read can stay pending forever: undici drops the error when the
abort lands on an in-flight response-body read (observed on the coverage
lane's Node v24.13.0), and the stalled body of
`llm-retry/tests/transport-recovery.spec.ts` then never settles. The idle
watchdog delegated termination to the iterator observing its signal, so the
request outlived its own deadline and the agent stayed `running` until
Vitest's 90s budget; awaiting the abandoned request generator's `return()`
queued behind that demand and would have re-hung the settled outcome.
`idleWatchdog.next` now races its demand against its own deadline and
rejects with the watchdog's TimeoutReason, so the deadline — not the
transport — decides when an outstanding demand ends. Both adapters that
consume the watchdog stop awaiting the abandoned generator's drain; the
abort they already issue owns socket and SDK teardown.
Evidence: instrumented stress harness at ~1s idle on Node v24.13.0 hung 4/160
iterations before this change (each with the abort delivered at ~1002ms and
the underlying read never settling) and 0/160 after; Node v24.19.0 never
hung. The new case in transport-recovery.spec.ts pins the behavior
deterministically: it fails (times out) with the watchdog change alone and
with no product change at all.
Root build scripts, the desktop bundle and the dev:web watcher pass the
native loader so tsdown never selects the undeclared unrun fallback. A spec
covers the build.ts rejection path.
Without process.features.typescript, tsdown falls back to the undeclared
unrun loader, which fails to import and, once installed, resolves
import.meta.url in packages/client/tsdown.client.ts to the entry config.
Refs #5622
Resolve expected module paths through the platform path module and keep
the signal-termination assertion to POSIX, where a self-killed child has no
exit code.
Type the mod tool's output as the answering text, read JSON into a typed
unknown, and replace the test doubles that went through unknown with a
real $ over a slow invoke, an Agent-comparable facts object, loosely typed
on() calls, and the Loader's own import path.
Add @deepseek-ai/dsh-experimental-claude-code-mods, an alpha bridge that
loads Claude Code mods (plugin directories whose hooks/hooks.json names a
hooks module), runs register(on, options) unchanged, and raises the mod
events from harness extension points: session.start on agent/created,
prompt.submit and turn.start on agent/pre-step, tool.call around
tools/execute, turn.complete on turn/end, session.end on agent/disposed,
and command.run from registered commands. The $ mods API is served over
composed services (commands, tools, user-questions, fs, subprocess,
storage domain, projections); unserved members fail by name.
Includes a middleware chain with Claude Code's failure rules and budget
clock, a claude-code/testing-style test kit, fixtures from the mods
tutorial, full-loop and Loader composition tests, the attribution-only
claude-code-mods message source with its persistence record, and the
regenerated catalogs.
Review follow-up: the developer message history is the net tool-list change
between two consecutive requests, not one entry per successful call, so a
remove/add pair batched into one step logs no message. Derive the expected
history from the recorded request headers, search for the later call after the
earlier one, and accept an already-removed tool in the removal prompt so the
REMOVED reply stays required.
The dynamic tool cache e2e asserted the exact recorded call sequence, so a
model that also removed the temporary tool inside the add turn failed an
otherwise valid run. Assert the calls the cache contract depends on and their
relative order instead, and derive the expected transcript registry history
from those recorded calls so one developer message per successful registry
change is still required.
Moving a loader's element (a keyed list reorder) or showing it again
after a display:none ancestor restarts its CSS animations, and the mount
ref does not run again, so the loader left the shared phase. The loader
now pins its animations to document time zero on every animationstart.
Split the single 'stats' conversation.composer.dock entry into
'activity' (counts, speed, time dialog) and 'usage' (tokens, cache hit)
so another plugin can replace one pill by id instead of reimplementing
the whole row. The rendered pills are unchanged.
The fixed 19387 listener fails when Windows (Hyper-V/WinNAT) reserves a dynamic
port range covering it, even though no process uses the port. Electron already
reads the bound port from the Host's ready message.
Ordinary plugins now show exported icons, and subpath plugins use
<subpath>/icon instead of a package.json. Update the display checklist
and nested-package gate cases to match.
Manifest icon diagnostics name the package.json the author edits.
verify-package-meta rejects a package.json export that resolves to build
output instead of reading it, and PackageManifest.exports uses a narrow
export-target type.
Desktop loads its profile with `loadProfileDirectory`, so the retired
bundle removal moves there from `loadProfile` and covers every loader.
A name in `dsh.profile.bundles` that neither the profile's dependencies
nor the installation holds had no uninstall on the Plugins page: the
button required `installed`. The Host now reports such a selection as
`removable`, and `removeBundle` deselects it without running pnpm. The
page shows uninstall for `installed || removable`.
`@deepseek-ai/dsh-experimental-schedule-bundle` shipped in dsh-v0.2.0-rc.1
and rc.2 and was removed when the Web composition started mounting
Schedule itself. A profile that switched it on kept the name in
`dsh.profile.bundles`, so every start reported an unresolvable bundle and
the Plugins page showed a failed entry with no removal action.
`loadProfile` now removes names listed in `RETIRED_BUNDLES` from any
profile's bundle list, alongside the existing installation-owned tuple
normalization, and rewrites the manifest only when the list changes.
Materialization tagged every untagged <style> in the document for the
materializing plugin, so styles inserted later by other owners (Cordis
dynamic plugins, runtime-injecting libraries) were removed when an
unrelated plugin was toggled.
Resolutions:
- `docs/subsystems/schedule.*` and `packages/client/ui-schedule/README.*` keep
this branch's shipped-composition text with master's newer wording (Rules and
Records tabs, the icon-button linked-session layout).
- `docs/event-producer-consumer.*` regenerated by `gen-doc-graphs`.
Delete the now-unused llm-retry history helper, drop the unreachable
credentials-local refresh rejection handler, and add direct tests for the
gateway SRC skip of unprovided Services and llm-retry projection dedupe.
Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion
and its tests, the invariant gates and Vitest setup, the scoped-event
resolver generator, and the INVARIANT-coded listener rethrow paths.
Update docs, READMEs, Agent Notes, and add an upgrade guide.
Only packages/typert/loader/tests/loader.spec.ts conflicted. Master's side
was kept (master's non-awaited ctx.loader.remove calls, restored by the revert
of the transactional Cordis reload changes) and this branch's two vi.waitFor
conversions were re-applied on top, so the merged file differs from master
only by those two polling assertions.
Subpath plugins are not packages, so they never read a package.json and
take their icon from the exported <specifier>/icon resource, like locale.
Package roots keep manifest text fallback and icon, with ./icon as the
fallback image. Drop the bundle-only reader option.
`@deepseek-ai/dsh-schedule` and `@deepseek-ai/dsh-tool-schedule` now depend on
`@deepseek-ai/dsh-subagent`, and the schedule package no longer imports the
Session controller's ownership predicate, so the committed module graph moves.
Typing at the end of the highlighted command token lands in the styled
node. The decoration split the overflow back out, but splitText copied
the token style onto it, so Lexical's normalization merged the halves
again and the transform looped until Lexical aborted the update. The
DOM kept the native insertion while the editor state rolled back, which
left the composer desynchronized for every later keystroke.
Clear the overflow style in the same transform pass.
Refs #4490
Review follow-ups on the delegated-child rule:
- Every one of the four tools refuses a delegated caller before dispatch,
reading `delegationDepthOf(exec.agent)` and returning the stable
`subagent_session` refusal, so `schedule_list`, `schedule_delete`, and a
content-only `schedule_update` are covered too. The preset `toolFilter.deny`
stays as the layer that keeps the tools out of a child's prompt.
- The service keys the arming refusal on that same delegation accounting
alone; `hasApiSessionSubagentOwner` returns to package-private and
`@deepseek-ai/dsh-schedule` no longer imports it.
- The preset child row is `time-context` again: the `preset-` prefix separated
it from a Host row that no longer exists, and `verify-cordis-config` passes
with the sibling-style id.
- Docs, Agent Notes, and the generated catalogs follow.
Move the auto-growing textarea into ui-primitives as InlineEditor and use it
for both the queued-message and goal editors. It also refits when its width
changes and treats keyCode 229 as IME composition.
A delegated child could arm reminders through the tools its preset mount
passes down, even though delivery can never reach a child's Session. The
refusal now reads the delegation-depth accounting the delegation cap itself
enforces, and the shipped presets deny the four tools to children outright:
- `reminderTargetRefusal` refuses when `delegationDepthOf(agent)` is above
zero, or when the Session is subagent-owned without a depth record.
- `standard`, `cordis`, and `ptc` set `toolFilter.deny` on both delegation
rows, so the four tools leave a child's prompt and reject execution.
`create` still rejects and `update` still returns the non-mutating refusal.
Tighten goal-round-driver docs and regression assertions for the withdrawn
queued round: continuation disarms when the cancelled turn belonged to other
work, and the removal is a durable canceled next-turn splice.
Render one Button for both layouts so crossing the fit threshold keeps focus,
anchor the tooltip on the wrapper so a disabled compact entry still names
itself, observe each tab, and rename the tab in the user and subsystem docs.
Rebind the scrollbar thumb to the l2 pair on the elevated goal strip, treat
keyCode 229 as IME composition, and re-measure the editor when the inline
error changes its width.
The classification was recorded before the subtree scan, so a run that found and
removed package ACEs under the requested directory still reported NOT_THIS_CLASS
or PRECONDITION with an empty packageObjects, contradicting README and SKILL. The
scan now runs first, so the verdict and packageObjects describe what the run
actually repairs.
A report-file write failure now prints REPORT_IO_FAILED with the path and the
error on stderr instead of silently leaving an incomplete report behind.
Shrink the linked Session entry to a chat icon button with a Linked session
tooltip when the detail strip cannot fit it, hide the tab list scrollbar,
and rename the English Delivery records tab to Records.
A goal round queued behind human work stayed in the inbox after Stop. The
next human prompt claimed that stale round first, pre-step rejected it, and
the driver exited with the human prompt still parked until another send.
The goal edit field was a single-line input: Enter always saved, a newline
could not be entered, and a long objective scrolled sideways on one line.
Use an auto-growing textarea capped at six lines, matching the queued-message
editor: Enter saves, Shift+Enter breaks the line, IME confirmation does not
save, Escape cancels.
Clarify acknowledgement and first-failure semantics, update configuration and timeout terminology, and explain request-side fixture checks without changing execution logic or recorded expectations.
The desktop archive probe called the script with only -Path, which the new
interface refuses, so it now passes -AllowRoot and -Out. The skill no longer
suggests running the repair confined: the confined token cannot write a DACL, so
that run would report the sandbox's own restriction as a missing right.
The subtree walk bounds its first enumeration too, and a directory whose stat
fails counts as unreadable instead of vanishing. The removal decision says
inherited copies follow their repaired source, and the pre-flight comment names
grant targets as well. Add the third behavior test the rewrite dropped: an
inherit-only FullControl ACE survives a grant.
The approval rule and the stop template are the text a user reads, so they now
name Windows file permissions and permission entries; ACL and ACE stay in the
model-facing technical sections and the report.
The stop paragraph now says only the user can lift confinement, names the term
the Chinese UI shows (完全权限), and offers both options that switch opens: keep
working under full access, or have the agent keep investigating the ACL problem
from the report's mechanism and evidence. A bilingual template carries the same
message, and the surrounding skill text is tightened to stay inside its budget.
The stop path told the agent to switch the session to full access, which only the
user can do. Ask them to switch it instead, keep the agent out of that decision,
and record the same wording in the windows-acl-skill snapshot.
Tool output keeps only its tail, so a long repair run lost its early observations
and nothing durable held the full record set. Every run now writes each REPORT
record to \`-Out\acl-report-*.jsonl\` and ends with a \`RECAP\` line carrying the
verdicts, changes, verifications, refusals and scans, followed by the report path
and the counts. Verification re-reads stop repeating an ACE list the run already
printed, and the localised icacls rendering leaves the record.
The skill now tells the agent what to do when a repair stops: name the blocking
object and right, unblock the user by switching the session to full access
temporarily (stating that confinement is off and that this is a workaround), and
ask for feedback with this session so the unhandled scenario reaches the team,
quoting the report records because only what reaches the conversation travels.
Also from review: the subtree walk reports directories it could not read instead
of claiming a complete walk, a grant target without effective WRITE_DAC is refused
before any change rather than failing mid-repair, grant targets require complete
observations, and repair sources are ordered ancestor first for scanned paths too.
The recorded session embeds the skill description and the loaded instructions,
so the review-driven edits to SKILL.md need the same two-line fixture update.
Review found the unified run had dropped the grant pre-check: a target without
effective WRITE_DAC was attempted anyway, failed, marked a recovery attempted, and
the rollback could not restore it either, ending in restore_pending_then_stop with
a command that cannot run. Refuse that target before any change, and gate grants on
complete observations the way package removals already are.
Repair sources are now ordered ancestor first for chain and scan-discovered paths
alike; the old reverse made a deeper scanned source come first. The subtree walk
reports directories it could not read instead of claiming a complete walk, and the
docs record the authorized-root trigger plus one recovery pair per change.
Restore the two behavior tests the rewrite dropped: a deny sharing the removed
allow's SID, and an inherited source outside -AllowRoot with an in-root explicit
child.
The recorded session embeds the skill's catalog description and the loaded
instructions, so the keyless replay diffed the previous two-step text against
the shipped one. Replace both with the shipped text; nothing else changes.
The note still described a diagnosis-then-repair workflow with a mode choice.
Record what shipped: one invocation for both repairs, the bounded subtree walk
under a directory the caller cannot provision, the authorized root repairing and
restoring itself, and the per-object recovery record plus script.
The script made the caller choose a mode, so one sandbox failure cost two
approvals: one to diagnose and one to repair. It now has no modes. A single run
adds the signed-in user's full-control allow ACE to the directories on the chain
that lack effective WRITE_DAC or WRITE_OWNER, removes explicit package allow ACEs
at their sources, ancestor first, and verifies every change by re-reading it.
A directory the caller cannot provision also has its subtree checked for explicit
package allow ACEs in that same run: the sandbox reports its provisioning failure
on the workspace root while the conflicting entry often sits deeper, and the walk
is bounded and reports truncation. The authorized root itself may be granted and
restored, so a workspace root can repair and roll itself back.
Recovery artifacts shrink to one record and one script per modified object; the
unused icacls save file and the duplicated command file are gone. The skill, its
README paragraphs and the script tests follow the single command: the former
-Fix, -GrantFullControl and -Compact switches are rejected.
Comparing whole DACL strings and whole icacls listings reported a successful
package-ACE removal as a collateral change: listing order, inherited entries
and the object's own entries were treated as one value. The package allow
filter also matched the rendered ACE row instead of the ACE's SID, so -Fix
could never verify a removal.
Compare the object's own ACEs and its inheritance protection instead, save and
restore own ACEs only so a parent that changes during the repair cannot make a
correct restore fail, and let -GrantFullControl accept the authorized root
itself so a caller can repair its own workspace root.
Use Gateway event stream availability before dispatch without changing Peer or manifest synchronization APIs. Keep the response deadline after disconnection and cover connected, offline, and disconnect-after-dispatch behavior.
The Bash tool accepts sandbox_permissions equal to the current mode with a
blank justification, and a blank justification alone. The terminal card
rejected both, leaving successful commands without an expandable body.
Escalation validity depends on the Session sandbox mode, which only the Host
knows; a rejected call already settles as an error result.
session.create treats a named sessionId as a blank Session to reuse while
the Host can verify it is still reusable: no turn has started, it belongs to
the requested directory, it is an unarchived member of the requested
Workspace, and no other owner writes it. The reused Session runs the
requested agentPreset or the configured default; its recorded preset is not
resolved, so a blank recorded under a preset that is no longer declared no
longer breaks New Session or startup restoration. A change is logged as
agent-preset/selected before the Agent is published. A candidate that is no
longer reusable stays unchanged and the Host creates a new Session, which
replaces the client's writer-held fallback.
The Web client restores a saved draft with its recorded preset and falls back
to the default when that preset is missing or fails to activate, passes the
creator entry's preset through creation instead of issuing a concurrent
select, and reloads the projections of a declined candidate so a stale blank
hint no longer hides a started conversation.
Related to #2566
Address the second ds-review-bot round on the bundle Source section:
- remove an http(s) URL's user information up to its last `@`, so a
username containing `@` no longer leaves the password behind;
- keep the dependency name before a local or git spec when the package's
own manifest name differs, so reinstalling restores the alias;
- accept scp-like git addresses with any user name;
- show Built in for a profile dependency whose loaded copy the
installation supplies, instead of hiding the section;
- align the PackageView, subsystem page, and README wording with the
shadowing rule and the kept query string, and assert the source of
not-bundle and unreadable bundles.
The collapsed dock renders the count header and no rows at all; Playwright text
matching does match elements inside a hidden list. State what the assertion
actually relies on.
The scenario expanded the two-row queue dock by clicking whatever button
`[data-queue-dock]` exposed. Every visible row renders its own Edit, Remove,
and Steer actions, so that query also matches them, and Playwright's strict
mode rejects the click. Name the count header and pin the collapsed state it
is expanded from.
The dock spec states the same invariant directly: the collapsed dock exposes
only the count header, and the expanded dock adds one action group per row.
The install dialog is content-sized, so trimming the guide to its one
package-name entry leaves it shorter than the 520x600 viewport and centered
inside the same clearances; the caption-overlays case pinned y to the margin
and height to the full available box, which only held while the three-entry
guide overflowed. Assert the intent instead: the dialog stays inside the
Windows caption margin and clear of the mask.
Address the first ds-review-bot round on the bundle Source section:
- strip the user information of http(s) and git+http(s) URLs so a private
Git URL's token reaches neither the page nor the plugin_manager tool;
- expand `~` in file:/link: paths to the home directory as pnpm does;
- write jsr: aliases after the dependency name, like npm: aliases;
- give no source for a profile dependency the installation also supplies,
whose installation copy is the one that loads;
- label the English row "Code source" to match the Chinese copy and hold
for built-in bundles, and sync the subsystem page, READMEs, CSS comment,
and tool test.
Review follow-up: the guide shows one package-name entry, so the comments
and copy that still described three forms or a displayed Git/local-path
example no longer match. The Git and local-path template strings keep their
replacement reminder for a field that still holds them; the constants' JSDoc
and the package READMEs now say that instead of claiming a displayed example.
Also moves .installSafetyText below the rule its comment heads, and pins the
upgrade assertion to the trust box rather than the whole dialog text.
The install guide listed the package name, a GitHub address, and a local
directory as numbered steps; only the package name needs an example. Keep
that entry and drop the index badge, give the item's hint and example the
title's text color, and balance the container's vertical padding.
The trust box above Install also states that installed plugins do not update
automatically, that upgrading means uninstalling and installing the new
version, and that later releases will keep improving that experience.
`update` threw `ScheduleInputError` for a Session subagent routing owns, so the
Remote gateway turned it into a failed result and the Web editor rendered
`rule.error.unknown`; the `timing.subagentSession` message could never appear.
The refusal now travels the non-mutating `ScheduleUpdateResult` path, which the
editor already maps through the same `code` lookup as `schedule_not_found` and
`schedule_conflict`. `create` keeps rejecting because it has no result path.
The bundle page ends with a Source section: the spec that installs the
bundle elsewhere, as `pnpm add` accepts it, or Built in for an official
bundle the installation ships, followed by its current version.
The Host's listBundles carries the new `source` field for profile
dependencies: git addresses and URLs as recorded, file:/link: paths made
absolute against the profile, and registry ranges, tags, or aliases after
the package name.
The measured-p99 source (7,229 events) forks in 1,066.2 ms on hosted CI,
below the 1,485-1,523 ms of the former 99.3% source; its budget drops to
1,375 ms. Record the new higher maxima of the other endpoints.
Shell discovery keyed entries by resolved path. On merged-/usr hosts the
login shell /bin/bash from $SHELL and the PATH hit /usr/bin/bash for the
`bash` candidate are one file with two spellings, so the menu showed
"bash" twice. Key discovered shells by executable name, compared without
letter case or `.exe`, and keep the earliest entry so the default shell
stays first.
Moves the interface to the position SidebarRightBinding held, after the controller factory, so the change reads as one interface replacing the other. Its content is unchanged.
Upgrade guides now pair guide.md with guide.zh.md and guide.i18n.yaml under
the ordinary translation-pairing gate. verify-upgrade-guides checks the
Chinese sibling's metadata and its 变更/迁移 sections; the 500-word ceiling
applies to the English source. Remove the session-log-otel-collector guide.
Record the three externally perceptible breaks between dsh-v0.1.7-rc.2 and
dsh-v0.2.0-rc.1: Schedule rows moving into the Automation tasks optional
bundle, the legacy work-details value reading as Detailed, and Session-log
uploads requiring dsh-otel with a new collector host. The template no longer
asks for pull request links, which verify-repository-references rejects.
- Fork the measured p99 at rank 988, the highest top-level rank of that
anchor; rank 990 fell in the 99.3% anchor. Derive fork ranks from the
corpus size and pin their shapes in a unit test.
- Seed separate search and fork roots so fork children never reach the
search index.
- Bound child deadlines by the file limit and give every test a deadline
that outlasts its sequential children, so failures reap children before
cleanup.
- Budget peak RSS of the list, search, and fork processes, and check
every recorded median against its budget.
- Name a missing folded record, correct the list-order comment, state that
search models an opt-in deployment, and keep new dependencies sorted.
An install refused as incompatible keeps asking for a compatible version; an
installed plugin found incompatible now says to uninstall it and install a
version compatible with this DSH.
The case budget fires before eventually() can name the pending step, so the
native-delivery cases print the observed refresh values on failure. The
barrier JSDoc cites the libuv close-time wait and states that any directory
handle rebuilds the per-process stream; the transaction case takes the same
budget as its siblings; the hmr README records the darwin registration
window as a known limitation.
Chokidar reports ready once fs.watch() returns, but libuv on darwin builds
the per-process FSEvents stream later on its CoreFoundation thread, so a
write that lands first is never reported. The watch-config cases now close
a second fs.watch directory handle after registration, which blocks until
that stream is rebuilt with the remaining handles, and eventually() takes
the case's effective budget instead of a local 10 s constant.
Fixes#5414
On the EPYC 7763 runner the file took 294.5 s, 85 s of it single-process
seeding. Two preparation processes now author, validate, and fold
byte-balanced anchor groups in parallel, and a seed process writes the
corpora from their saved bodies and records. Budgets cover the higher
median of the 7763 and 9V74 runner models.
Resolves the semantic conflict with #5331: its ci-unit gate spread
coverageTestTimeoutArgs, which this branch retires; the gate is now a
plain vitest run and the darwin lane budget reaches vitest.config.ts
through the inherited DSH_COVERAGE_TEST_TIMEOUT_MS. The ci-unit spec
cases pin that shape under both budget values.
A bundle the installation ships is refused with a note that it upgrades with
DSH, since it cannot be uninstalled. The reinstall-to-upgrade sentence follows
an incompatibility once, however many packages it names, joined by a
locale-owned sentence separator.
Keep each case at its hardest setting that fits five minutes of hosted CI:
search and fork stay at 1,000 Sessions with the longest fork sampled once,
the list runs 3 samples over 3,000 Sessions, and seeding validates authored
bodies in memory instead of a separate verification pass. The file's last
case asserts the limit; the benchmark job timeout becomes 20 minutes.
Standard two-CPU hosted runs are 2.4-3.4 times the reference machine, above
the shared reference scale, so the budgets use rounded hosted medians with
the shared 1.25 headroom. The benchmark job took 14.4 minutes with these
cases; its timeout becomes 25 minutes.
Starting every frame compression at once kept one native Zstandard context
per pending frame alive and peaked the seed worker at 7.7 GB RSS, which
exceeds the standard hosted runner's memory. Bound compression like file
writes; the seed now peaks at 2.2 GB and writes identical bytes.
Add a required benchmark over synthetic corpora whose per-Session length
distribution is at least a measured local DSH corpus: list at 1,000 and
5,000 Sessions, cold and built content search and stratified forks at
1,000 Sessions, all through the real SessionController on built plain-Node
workers. Raise the benchmark job timeout to 30 minutes for the added cases.
* fix(webworker): load plugin manager and HMR in the preview Worker
The Worker disabled both rows, which left Creator mode's
tool-plugin-manager waiting for its service and the Plugins page without
a manageable profile. Only package commands are unavailable; the
services load. The Worker provides appReady from its own readiness
promise, resolved once the tunnel serves, and its process shim reports
no entry script, so HMR keeps only its profile configuration watches.
Fixes#5372
* fix(webworker): show xdg-open text files in a preview viewer
The Worker implements xdg-open as a platform executable that sends one
VFS text file to the page, which shows it in a read-only modal dialog.
Settings' "Open configuration file" now opens the profile patch in the
preview instead of failing on a missing desktop opener.
Fixes#5371
Introduce docs/upgrade-guide/v<version>/<item>/guide.md for externally
perceptible breaking changes. The dsh-create-upgrade-guide skill owns scope,
placement, maintenance, and format; verify-upgrade-guides enforces layout,
frontmatter, sections, and the 500-word ceiling. Guides are English-only and
registered as the upgrade-guide document kind.
The reminder stack no longer hides behind an optional experimental bundle.
`packages/bundle/web-app/cordis.patch.yml` inserts `schedule` and
`ui-schedule`, so a shipped profile mounts the task store, the delivery
runtime, and the Automation tasks page; the
`@deepseek-ai/dsh-experimental-schedule-bundle` package and its wiring are
removed.
Preset ownership is unchanged: `standard`, `cordis`, and `ptc` declare the
clock and the four reminder tools in their own scope, and `minimal` declares
neither, so a minimal session carries no reading and no reminder tool even
though the service is mounted.
`apps/cli` depends on `@deepseek-ai/dsh-tool-schedule`, and
`packages/bundle/web-app` declares `@deepseek-ai/dsh-schedule` and
`@deepseek-ai/dsh-client-ui-schedule`: preset and profile rows resolve plugin
names from the installation anchor, so a missing declaration leaves the row
without a fiber and every session creation fails with
`agent-preset/invalid`.
Documentation, Agent Notes, and the recorded Web fixtures follow the shipped
composition.
* fix(dockkit): keep floating headers below the Windows caption
* fix(windows): preserve caption gaps across dialogs and fullscreen
* fix(client): unify overlay clearance and preserve the Windows caption
* docs(desktop): explain fullscreen IPC platform guard
Review follow-ups: the Electron payload smoke allocates the way the product
does on Windows, so the packaged tree resolves the console host assets it now
depends on; the README states that dependency and its loud failure as a known
limitation; the terminal handle takes the platform this method already
resolved; and the note links the earlier tail-grace note, records why that
grace cannot cover a malformed tail, and drops the claim that the smoke
exercises the operating system host.
The per-platform case reads the third spawn argument to assert the console-host
option is absent on POSIX; an untyped vi.fn() declares no parameters, so the
indexed call argument fails typecheck.
The README, the Ctrl-C comment, and the bare-kill comment still described
Windows' own console host as the one backing the PTY. Name the host the code
now selects, record the exit-event behaviour of both teardown paths measured on
the pool machine, state that a missing conpty asset fails loud, and move the
note to the bug-fix class next to the other Windows console fix. The terminal
case now asserts the option key is absent on POSIX rather than not true.
Fixes the review findings on the work-budget change:
- unify the measured npm time range to 137-205 s in the note's problem
statement, which had carried the older 153-190 s range
- correct the peer-edge share to about three quarters (1874 of 2524),
which the sentence had called two thirds
- state the ratchet threshold as the packages-times-edges product, about
12% in packages and edges together
- derive NPM_HANG_GUARD_MS from MAX_RESOLUTION_WORK_UNITS so a raised
budget cannot shrink the guard's headroom, and state the guard's
guarantee as covering a runner four times slower than the measured host
- name the cost side of the hang guard in the note's consequences
The note records which categories carry this change: the per-platform unit
case, the master push's Windows standby run as the end-to-end signal, and the
desktop payload smoke for the packaged runtime tree.
The console host Windows provides, on the images the master standby pool runs
(Server 2022, Windows 10 22H2), replays a prompt line that scrolled out of the
viewport as `\ndsh>\x1b[1C`: the trailing space becomes a cursor move, so the
persistent-pwsh readiness path never sees its `dsh> ` tail and every send waits
out the silence tier. The same case passes on the Blacksmith Windows image,
whose console host emits the tail verbatim.
Allocate Windows PTYs with `useConptyDll: true`, so the PTY is backed by the
OpenConsole the node-pty package ships. The option is set only for `win32`;
the terminal spec pins it per platform.
Probing the pool machine with the bundled console host rendered the `dsh> `
tail for all seven prompts of the failing case's command sequence, and
`pty.pid` still resolves to `pwsh.exe` and equals the shell's own `$PID`.
The Dependency layout lane failed whenever the npm child process exceeded
a hardcoded 300 s deadline. That deadline measured the runner rather than
the graph: the same layout resolves in 137-205 s on an idle developer host
and exceeded 300 s on CI while the placement assertion reported no error,
so the failure carried no information about what had grown.
The gate now bounds the verified graph by resolution work units,
dshPackagesPerVersion * checkedDshEdges against MAX_RESOLUTION_WORK_UNITS,
and keeps the wall clock only as a hang guard derived from that budget.
Growth fails with the graph's own measured counts on any runner.
The resolution cost itself is unchanged. Measurement attributed 99.3% of
it to npm's peer-conflict placement, and cache reuse, graph splitting and
the placement flags were measured and rejected.
The off-state thumb reused the palette's primary foreground, which resolves
to near-black in dark mode; against the translucent track it read as
disabled, while a disabled control differs only by half opacity. Give the
off state its own thumb color through the new `--dsw-alias-switch-thumb`
alias: white in light mode and neutral-bluish 400 in dark mode.
The plugin management info button also becomes an 11px caption-colored icon
in a 20px square, keeping the 4px gap beside the subtitle.
Refs #5347
The rejection of a longer fixed wait or an unbound retry already lives in the
two linked notes; this note now keeps only what is specific to the case, that
`findByText` resolves on a commit that does not drop the panel.
The note links the two 2026-09-27 testing notes that record the same decision
family, and both link back. Line-number anchors give way to file and symbol
references, the deferred-refresh alternative the neighboring cases use is
recorded with why it was not chosen here, and the negative control states that
the close call is removed.
The diagnosis script suite asserted that repaired paths are owned by the invoking user. On an elevated Windows runner the fixture objects are owned by BUILTIN\\Administrators, so the assertion failed without any repair defect. Compare the owner before and after the repair instead, which keeps the stated owner-preservation guarantee without assuming the host token's default owner. Verified locally: 34 tests pass.
The fixture's poll case counts attempts instead of reading a clock, so a host
stall cannot turn a budget-ended poll into a pass; the control case reads the
poll's reported duration, which the 1000 ms default bounds from below. The
spawning spec acquires its temporary config in beforeAll, bounds the child
with a timeout and asserts no signal ended it, and counts the polled values it
inspects. CoverageTestTimeoutOptions is Vitest's own TestUserConfig members;
the rule's reach has one home in its JSDoc, which the config, gate, spec, and
note comments reference.
DSH_COVERAGE_TEST_TIMEOUT_MS expanded to --testTimeout, --hookTimeout, and
--expect.poll.timeout on the coverage and serial lanes, but Vitest forwards
only a fixed list of CLI overrides into inline projects and neither
hookTimeout nor expect is on it, so under the root config's two projects the
hook and expect.poll budgets stayed at Vitest's defaults on every lane.
vitest.config.ts now reads the variable through coverageTestTimeoutOptions and
spreads the three budgets into both projects; run-gates and the partition
coordinator pass no timeout flags, leaving one owner for the rule. A new spec
spawns vitest on the root config with a 200 ms budget and proves all three
waits end inside both projects, that unset keeps the defaults, and that a
malformed value fails at config load.
Fixes#5335
Related to #5325
run-gates.spec.ts clears DSH_COVERAGE_PARTITIONS and DSH_COVERAGE_MAX_WORKERS
before each case, so the coverage gates it builds take their shape from the
case, not from a partitioned lane that exports the variables to every child.
The ci-unit case derives its two gates from the `test` package script's
segments. The serial-reference note keeps only the lane fact; the mechanism
fact about inline-project inheritance is owned by the fix that changes it.
The case awaited the empty list's text and then read the panel once. The
detail closes through the effect that observes the removed row, one commit
after the render that replaces the list with its empty state, so that single
read could still see the panel. The assertion now waits for the state it
asserts, and a panel that never closes still fails.
Refs #5332
master now ships the reminder stack as the optional
`@deepseek-ai/dsh-experimental-schedule-bundle`, which inserts the rows the
Web composition leaves out. This branch keeps the preset-level ownership
beside it.
Resolutions:
- The bundle inserts `schedule` and `ui-schedule` only. It no longer inserts
`time-context`: the clock belongs to the `standard`, `cordis`, and `ptc`
presets, and a Host row for the same package would mount it in two scopes
and append two readings per step.
- `packages/bundle/web-app/package.json` declares `@deepseek-ai/dsh-time-context`
again: the presets reference it by bare name, so `verify-cordis-config`
requires the dependency.
- The bundle's patch spec, locale, package, README pair, the optional-bundle
composition spec, the plugin-manager component list, and the
`schedule-after` roster assertions describe the two-row bundle and the
preset-owned clock and tools.
- Documentation and Agent Notes follow the same split; the note master
archived stays frozen with its manifest hashes.
The unit-darwin job ran the whole unit inventory at Vitest's 5000 ms default
on a shared 3-vCPU macos-latest runner; the first two master runs after #5292
each timed out a different set of cases that pass in under a second on a quiet
host. Every other whole-inventory lane raises the budget through
DSH_COVERAGE_TEST_TIMEOUT_MS=90000.
The job now sets that variable and runs `pnpm run check:ci:unit`, a run-gates
aggregate that is `pnpm run test` (native build, then `vitest run`) through the
path that consumes it. ci-unit receives no CI worker settings, so the inventory
inherits the environment `pnpm run test` finds: on a 3-CPU host the defaults
would inject DSH_COVERAGE_PARTITIONS=2, which run-gates.spec.ts reads as a
partitioned coverage lane.
Fixes#5325
Read fields from the YAML syntax tree so non-string scalars and aliases keep
their authored text, show the verbatim block while the field chunk loads or
when it fails, close an empty block at its first delimiter, and refresh the
skill preview golden.
The bundled diagnosis skill told the model to run its script unconfined from the first call, which forbids trying the confined path and leaves no path but handing the command to the user. Run it in the current sandbox first, and request one escalated retry of the same call when a denial or an unwritable report directory prevents it; a refused or unavailable approval reports the path as undiagnosed. Refresh the recorded skill replay and re-record the README pairing hashes.
Packed consumers resolved ^3.1.0 to 3.3.2, whose source build fails under the Linux release check. Pin every consumer to the existing lockfile version 3.1.1 so published installs use the tested dependency.
Review round 1: the settle assertions build their message only when one
is about to fail, so a passing run never pays the host-facts shell probe;
send windows are bounded by event sequence numbers instead of
milliseconds, so two sends that hand over within one millisecond keep
their own events; the replay starts at the send's own input write and
ignores the emulator's terminal-protocol replies; a throwing foreground
inspection is recorded before the send rejects; a sanitizer is attributed
to the most recently spawned terminal, which the class documents as the
sequential-session assumption. A unit spec pins the replay rule against
the session's evidence states, the window boundaries, the reply/input
distinction, and the bounded, escaped rendering. Notes and comments drop
change narration and the unverified claim about the pool's build.
Seven cases, not nine, carried 90_000 before the change; the other seven
ran on the default and five of those spawn. Record the lane-flag
alternative, cite the archived describe-level budget notes and the
accepted risk, and bound the idle-host timing claim to the measured
cases.
Addresses ds-review-bot: the keyboard step now asserts that focus landed on
the sheet overlay before pressing arrows, the comment keeps only the local
why, and the Agent Note carries the measured counts (10 of 11 master runs,
7 of 38 probe runs, 26 of 26 keyboard runs, 10 of 10 local repetitions).
Five cases in scripts/oxlint-contract.spec.ts spawned oxlint or the
repository lint entrypoint on vitest's 5000 ms default while their nine
siblings carried the 90_000 ceiling from the Windows spawn-budget
alignment. The first darwin parity run on master (Sandbox run
36332053320) ran the file 2.2x slower than the passing run and timed out
`prints only the final diagnostics when a fix retry still fails` at
6309 ms. Give the five cases the same per-case ceiling and record the
decision in an Agent Note.
Fixes#2581
Related to #2677
The 2026-09-27 14:30Z master run carried the tail grace and took the same
20.8 s as the run before it, so the lane's mechanism is not a stalled
prompt tail; the composition keeps the default and the notes record the
open investigation, the native pwsh 7.6.6 evidence, the ConPTY repaint
re-emission of the 133;D mark as the leading hypothesis, and issue #5297
for the Windows pid 0 side finding.
The self-hosted Windows lane settles one or two of seven sends on the
controlled prompt and the rest at the plain silence bound, and the
settle reasons alone cannot say whether the marker was missing, its tail
was invalidated by later output, or the foreground comparison failed.
The case now records every decoded pty chunk with the sanitizer's marker
verdict and prompt tail, every foreground poll, every input write, each
send's settle reason and elapsed time, the session's prompt evidence
replayed from those chunks, and the host, pwsh, PSReadLine, and console
host versions; the settle assertions carry that timeline in their
failure message and onTestFailed prints it when the runner's budget ends
the case first.
promptTailGraceMs returns to the product default: master runs with and
without it took the same 20.8 s, so the lane's degraded sends never held
the state it covers, and a nonzero value there would only lengthen a
never-arriving-tail fallback.
The meeting-sheet step of document-preview.e2e.ts clicked the sheet overlay
at the cell FortuneSheet already selects on mount, so its polls passed before
the click committed, and the press resolved its cell inside a React state
updater that later renders re-execute against the reflowed grid. On loaded
Linux runners that re-execution landed after the first sidebar resize and
committed B1 (the two-column sheet clamps the stale pointer x to its last
column), failing the reflow loop with `expected 'B1' to be 'A1'`.
Select A1 through ArrowRight/ArrowLeft instead: keyboard moves compute the
target from committed state, so a re-executed updater yields the same cell,
and the B1 -> A1 transition verifies the interaction before the reflows.
Refs #5242
Review follow-up: name the parity job in the workflow header, point the unit
comment at ci.yml's Linux jobs instead of a nonexistent `checks` job, state the
budget as a completion ceiling and raise it to 60 minutes so the slowest observed
runner keeps headroom, and let the owning Agent Note describe the budget and the
workflow's statuses without restating numbers the workflow owns.
The seatbelt leg of the sandbox-e2e matrix also ran `pnpm run test`, and the
complete unit suite takes 16 to well over 20 minutes on macos-latest, so the
shared 20-minute job budget cancelled the leg on most master pushes — before
the Seatbelt confinement e2e ever ran. Move the parity run into a `unit-darwin`
job with a 45-minute budget; the four e2e legs keep 20 minutes.
Review round 1: the tolerance now requires the accumulated tail to remain a
prefix of the controlled prompt, so a prompt that arrived and was invalidated by
later output keeps the plain silence bound instead of waiting a tolerance with
nothing left to wait for. The field is validated as zero or at least one
`pollIntervalMs`, matching the handoff grace rule, and tests pin the extended
bound's expiry, the invalidated tail, and that rule. The owning readiness design
note records the new field and Tier 2 bound and cross-links this decision.
Windows sessions on the self-hosted lane settled one or two of seven sends on
the controlled prompt and charged the silence tier for the rest (issue 2487).
The marker is written by the shell's own prompt function and the printable tail
by the same render, so a tail that arrives past `idleSilenceMs +
handoffGraceMs` means a stalled delivery, not an absent prompt.
`dsh-terminal-bash` gains a validated `promptTailGraceMs` bound (default 0,
which reproduces the previous bound exactly). While a send has seen the marker
and its printable tail has not arrived, the silence bound grows by it. The
pwsh Loader-composition case sets 5000 ms, the host class the bound is for, and
still fails when no marker appears at all.
Review found two factual errors in the prose this change adds.
plugin-install-cancel: the scenario pauses the page clock before the
enable click, so the highlight's `setTimeout(clearHighlight, 2400)`
cannot fire while the status poll runs. The comment claimed a reload
could outlive that expiry, and the note explained the two CI timeouts
at the untouched highlight locator the same way. The cause of those
timeouts is not diagnosed, so both now say so instead of offering a
mechanism the paused clock rules out. The pre-change file reproduces the
same hang under the same lane command, which the note records.
workspace-new-session-folding: `nth(7)` is the first row the batch
reveals, not the last established one, and the reflowing `showMore.click()`
is above the comment, not below. Both are corrected, and the workspace-row
comment names what `:hover` reveals instead of restating a cell swap the
project row does not have.
turn-tail-actions: clear the pointer modality flag with `Shift`, which
moves no focus, matching focus-rings.e2e.ts.
The zh dictionary rendered subagent, list_agents, interrupt_agent, and
ralph tool cards with 代理 (proxy) for Agent. Use 智能体 like the goal hint
and wait_agent copy already do.
The self-hosted Windows serial lane failed 12 cases from one cause: the host
exported Git's indexed command-line configuration (`GIT_CONFIG_COUNT` with
`GIT_CONFIG_KEY_0` / `GIT_CONFIG_VALUE_0`), and the shared subprocess scrub
removes credential-shaped names, so a child inherited the counter without its
key and died with `error: missing config key GIT_CONFIG_KEY_0` before it read
any configuration file.
Both spec files now delete that group from their own environment before they
spawn Git, because they judge profile behavior rather than host state. The
product-side handling stays out of this change; it is preserved on the branch
`deferred/git-command-line-config-scrub-20260927` for a later product PR.
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.bare GIT_CONFIG_VALUE_0=true pnpm exec
vitest run packages/boot/plugin-manager/tests/github-connection.spec.ts
packages/boot/plugin-manager/tests/manager.spec.ts -> 12 failed at the parent
revision, 95/95 passed with the isolation.
The three-file lane run passes all three scenarios in three consecutive
local runs, and the install-cancel scenario keeps a residual exposure:
it finds the card through the highlight, so a reload that outlives the
highlight while the card is absent fails that locator. One lane run
failed there and it did not reproduce.
Master's 07dcdb46e5 moved the question step's settled title into the
`Ask question waiting` row of mid-steer.expected.md, so the button this
scenario waited for no longer renders. The wait therefore never resolved and
parked the scenario on the question page, producing the lane's cascade: `no
turn/end within 30000ms`, no live root session matching session.v4.jsonl, and
`llm-replay: fixture not fully consumed`. `captureStableAria` already polls
until two consecutive normalized captures agree, so the settled state is
captured without the extra wait.
DSH_SNAPSHOT=replay pnpm exec vitest run --config vitest.web.config.ts
apps/web/tests/steering.e2e.ts -> 7 passed (reproduced twice); with the wait
present the same command fails at its 10 s bound and again widened to 30 s.
Three scenarios captured an aria region while the state their golden
records was implied by the pointer, the keyboard, or an in-flight Host
read, so the capture could land on the other state.
workspace-new-session-folding captures two goldens of one sidebar, each
recording a hovered row that Rows.module.css renders with the row
actions in place of the relative time. Both hovered rows are now
established before their capture instead of inherited from wherever the
Show-more click left a stationary pointer. The serial self-hosted master
lane failed this scenario in every failing run observed between
2026-09-24 and 2026-09-27.
turn-tail-actions records a tooltip beside the focused Copy footer in
running.expected.md. Tooltip ignores focus while the last input came
from a pointer and only a keydown clears that flag, so the scenario now
establishes keyboard-owned focus and waits for the bubble.
plugin-install-cancel polls the enabled card's status instead of
sampling it once. The store marks the card and starts the directory
reload the Host answers, and the status flipped disabled -> running 61 ms
after the highlight in three local runs, which the single read sampled
inside.
Goldens, product code, and assertion strength are unchanged.
The fullscreen command needs to know whether the frame is narrow enough
that an expanded panel is fullscreen automatically. Reading it from a new
ctx.layout.viewportWidth widened the layout plugin's public face for this
one consumer. The seats already receive the frame width as owner props and
compute the rule they render with, so each seat now reports that verdict
through its inject face, as it reports its docking kit's room. The command
reads the latest report only when the user runs it, after the commit that
rendered the width has settled.
- Revert ctx.layout.viewportWidth and the test fakes that provided it.
- SidebarRightInjected gains reportAutoFullscreen.
ctx.sidebarRight acted on a binding the right Sidebar seat published from
passive effects. React runs a commit's passive effects in tree order and
the frame renders the Conversation column before the right column, so an
opener mounting in the commit that switched Sessions or returned from a
global panel ran while no seat was bound, and its open threw.
The plugin now names the on-screen Session itself: the selected Session
while the Conversation fills the main column, derived from the Sidebar's
Session views and ctx.layout.panelInfo. Both notify before React renders,
so `mounted` already names the arriving Session during that commit, and
its seat mints the Session's store in the same render. Public commands act
through the Session's adopted store; the seat reports only the room its
docking kit measured, and focus continuity and the auto-fullscreen rule
come from a host the plugin supplies.
- ctx.layout gains viewportWidth from the root layout store.
- SidebarRightBinding, controller.bind(), and the seat's bindService are
removed; the seat's inject face gains measureRoom.
- ui-plan's review opener returns to a plain open gated on `mounted`.
- The plan-review e2e expands the Workspace group by its aria state.
`operations.spec.ts` read the profile's run record from a 100 ms timer armed
before the refused installation whose repair it observes; a loaded runner
reached the repair run after that timer and the case failed with `null`
against the recorded run. The observing child now stays in flight until the
record appears, and the bounded wait starts at the launcher hand-over rather
than at test start.
The loader-composition case counts `stdin_read` settlements but reported only
the count, so its Windows failures could not say which tier settled which
send. The recorded reasons now ride in the assertion message; the threshold
and the assertions are unchanged.
Historical team/message/queued and team/message/delivered records remain
decodable and projected, but Team no longer drains or acknowledges them
after restart. Remove the compatibility dispatcher, its disposal wait,
and its tests, and document that historical pending mail stays undelivered.
Extract settleRefresh() so the spinner-minimum hold has one settle point,
advance the e2e fake clock past the 400ms hold before asserting the button
restores, and drive the disposal-during-hold store test on fake timers for
both success and failure reads.
The install registry picker used MenuSurface and overrode the theme-owned
menu material tokens, which the shared-menu-material gate rejects. Return
the picker to a fieldset (its native group role) and paint an opaque
bg-layer-2 surface so the form controls stay readable without touching
menu tokens. This also restores the group's radios to the accessible tree.
Align plugin-install-registry.e2e and its snapshots with the PR's visible
text: registry option names drop the parentheses around the host, the
custom address field is no longer disabled, and the custom hint spells out
http(s) and ~/.npmrc.
Hold the manual-refresh spinner to a 400ms minimum so a fast read does not
flash it, clearing to idle (or the failure toast) only after the minimum,
and dropping a refresh that settles after disposal.
Prune the retired no-unknown-casts baseline entry for browser-plugin.client.spec.
Drop the plugin-manager refresh e2e to a single failure round: the store's
success and retry outcomes are covered deterministically by
manager-store.client.spec.ts, while the e2e keeps its unique value — real
500ms tooltip timing, a running spinner animation, aria-busy, and the
portaled failure toast over kept cards. Regenerate refresh.expected.md.
Remove the components spec tooltip-delay case; ui-primitives'
tooltip.client.spec.tsx already covers 499/500ms hover/focus delay, early
cancellation, and portaling. PluginManagerPage.tsx stays at 100% coverage.
The clock row is preset-level, so a `standard`, `cordis`, or `ptc` session
appends one reading per eligible step; the recorded sessions carry that
message and the identifier shifts it causes. The plugin manager loses the
clock wording from the bundle description.
Resolutions:
- `python/sdk-runtime/package.json` and `pnpm-lock.yaml` keep both sides'
additions (`@deepseek-ai/dsh-otel` beside the schedule packages).
- The generated graph pages are re-emitted from the merged sources.
Closing the provider editor confirms its write, but the browser directory
refresh lands after it, and the reopened editor copies the previous input
selection into a new draft — checkbox polling cannot correct a draft that
was already built. Run 36159502081 (job 108152262196) read a stale `true`
from that draft at models-settings.e2e.ts:395.
Wait for the saved-provider notice, which the refresh publishes, before the
third reopen. The adjacent reopen in the same test file waits on the same
notice.
The browser lane pins the browser to Asia/Shanghai, and formatMessageClock
prefixes clock.md / clock.ymd to the clock once a message's local calendar
day differs from the renderer's. Four scenarios left both clocks on the
wall clock while their message times are stamped during the run — a live
composer send by the Host, a `createdAt: 0` seed anchored from Date.now()
— so the job that began six minutes before local midnight rendered
`9/25 23:58` where their goldens record `{{clock}}` (run 36157253018, job
108144715986: steering, markdown-images, message-actions, and
reference-composer failed together, then passed in the next run).
Read WEB_FIXTURE_TIME on both clocks instead. Live-stamped steering suites
install pinHostClock(), a Date.now spy that advances from the anchor with
real elapsed time; pinBrowserClock() re-applies one frozen instant from the
anchor on an interval, because holding a single instant removes elapsed
time from the 150 ms process-title hold and the composer's 10 ms
composition guard, which then never elapse. Seeded scenarios anchor their
seed beside the pinned browser clock, which keeps the relative-age buckets
their goldens assert.
The steering mid capture waits for the settled question title its golden
records, and the whole-queue scenario waits for the running turn before its
queue gestures, whose decision resolves against an agent that reports busy.
Goldens, the Asia/Shanghai browser timezone, and product rendering are
unchanged.
Reading Git's path text verbatim to keep trailing whitespace duplicated the
line-terminator helper that scripts/change-scope.ts already owns, which the
duplication gate rejects. Canonicalizing the fixture-side common directory
with realpathSync.native removes the text parsing entirely: short names,
junctions, and directory symlinks all collapse onto the physical directory
the installer resolves, and the injected path stays exact.
Drops the trailing-space case: with no text trimming left in the
derivation, that path already matched the installer before this change, so
the case could not fail and proved nothing.
master now ships the reminder stack as the optional
`@deepseek-ai/dsh-experimental-schedule-bundle`; this branch keeps the
preset-level ownership beside it.
Resolutions:
- The Host rows keep master's `disabled: true`. The optional bundle switches
`schedule` and `ui-schedule` on and no longer `time-context`: the clock row
is preset-level, so flipping the Host row as well would mount the plugin in
two scopes and write two readings per step.
- `standard`, `cordis`, and `ptc` declare `preset-time-context` and
`tool-schedule`; `minimal` declares neither.
- `tool-schedule` injects `tools` and resolves `schedule` per scope through
`ctx.inject`, so the row stays valid but produces no tools while the service
is off, and produces them once the bundle mounts it.
- The assembled-Web Schedule spec keeps master's bundle overlay and restates
the shipped `preset-standard` row instead of the deleted fixture, because a
top-level patch cannot reach a plugin a preset declares.
- Documentation and Agent Notes describe both layers; the note master archived
stays frozen with its manifest hashes.
git() trims its output, which also drops a worktree directory's own
trailing whitespace; the installer removes only the line terminator. Derive
the expected lock path through gitPath()/stripGitLineTerminator(), which
applies the installer's exact rule, so the same-string claim holds for
every path.
Adds the trailing-space injection case (skipped on Windows, which cannot
create such a directory); it fails with the same probe ENOENT under the
trimming derivation.
Merge origin/master and move plugin manager, artwork, and automation trigger changes to their independent PR. Cover keyboard-focused inline cards without warming the next pointer visit.
The five lock-access cases only diverged on a host whose temp directory
already carried a short name or directory alias. Route one case through an
aliased worktree root (a junction on Windows, a directory symlink
elsewhere) so the installer's resolved common directory necessarily differs
from the path this spec composes; the case fails with the same probe ENOENT
under the previous derivation on every platform.
The alias case shares the injection body through
expectInjectedLockAccessFailure, so the five existing case names and their
assertions are unchanged.
The lock-access cases injected a filesystem failure on a path this spec
predicted from the mkdtemp fixture directory. The installer resolves its
common directory against Git's own top-level path instead, and the host
spells that path differently: Git canonicalizes Windows 8.3 short names
and the macOS /var symlink. When the spellings diverged the injection
never fired, so the probe file was never written and all five cases
failed with ENOENT at the probe read - the signature seen in the Windows
coverage lane.
Resolving the relative common directory against Git's top-level, as the
installer does, makes the expected path identical to the one the
installer touches on every platform. The five cases fail deterministically
on macOS before this change and pass after it.
`ToolCallRecovery.observe` compared `entry?.turn` against the event's turn
before reading `entry.step`. For a `tool/result` whose callId never registered
as pending and whose event carries no turn/step, both sides were `undefined`,
the guard passed, and the `entry.step` read threw. A pending entry is now
required before the turn and step comparison.
The bundle no longer declares `dsh.bundle.rowSwitches`, so its page offers a
switch per row while it is on, as every other bundle does. The README records
that the three rows work only together, and the Agent Note records the
rejected lock.
With `dsh.bundle.rowSwitches: false`, `listPlugins` reports each row the
bundle inserts with the `bundle-switch` read-only reason, so
`setPluginEnabled`, the `plugin_manager` tool, and the Plugins page agree,
and the page draws no switch for such a row. `bundleRowSwitches` validates
the manifest field; `listBundles` reports a non-boolean value on the bundle.
`BundleInfo.rowSwitches` is removed.
A stale-file response naming many file ids made RequestFiles.retry invalidate
each mapping through its own writer-lock cycle on files-v3.json. The lock waits
two seconds with a fixed backoff cadence, so concurrent contenders advance one
per round and most of them time out once a response names more than about a
dozen ids; the timeout surfaced as TRANSPORT and the replacement request never
left. DeepSeekUploadIndex.remove and DeepSeekFileStore.invalidate now take every
rejected generation at once and rewrite the index under one lock acquisition.
The language-switch case now reads the Automation tasks row's English title
and description, and the English guide writes the UI label as plain text like
the rest of the guide.
The visible id chip and the accessible name now share one check for whether
the entry id adds to the title, so an include: id that matches the title is
left out of both. The loading skeleton's status region carries visually
hidden text so screen readers announce it. The settings-chrome browser
scenario expects the preset group open and enabled rows without a tag, and
its two ARIA snapshots are refreshed. The row-partner CSS comment states its
two-column assumption.
The bundle README said the right sidebar shows the Automation tasks page. The
ui-schedule client registers that page as a `main` panel with a sidebar entry and
keeps the right Sidebar for the single-task detail tab.
The Web composition no longer carries `time-context`, `schedule`, and
`ui-schedule`. The optional bundle inserts the three rows and depends on their
packages, as the other optional bundles insert theirs, so the Plugins page lists
them under the bundle; `dsh.bundle.rowSwitches: false` keeps the bundle's own
switch as their only control. The three packages gain localized titles and
descriptions for that list.
A bundle whose rows work only together sets `dsh.bundle.rowSwitches: false`.
`listBundles` reports it as `rowSwitches`, and the Plugins page then lists the
bundle's rows with their state and offers only the bundle's own switch. A
bundle that omits the field keeps a switch per row.
The 0.1.7-rc.2 release bumped the root and every dsh-family manifest; the new
experimental bundle carries the same version so check-workspace-constraints passes.
The 2026-09-16 host-schedule-storage note now names the optional experimental
bundle as what mounts Schedule, since the Web composition carries the row disabled.
The opt-in note's Decision drops the sentence the Problem already owns, and the
assembled-remote mock states why its Schedule answers stay declared.
The overlay alternative now says only its insert form fails to override a shipped
row, since an id-targeted --patch overlay does set one. The composed assertion
compares the overridden row's package name with the shipped layer's name instead of
asserting a string.
Cards in one grid row now share a height, with title rows and descriptions
starting at the same offsets. The entry id shows only when it differs from
the title, as a code chip in the card's bottom-left corner; tags mark only
states other than plainly enabled; descriptions clamp to two lines until the
card expands; the preset group starts open; loading shows skeleton cards.
Group headers share one height, the subtitle separator is evenly spaced, the
title weight drops to 500, 12.5px text moves to 12px, and a container query
on the tab switches the grid to one column.
Cross-link the archived decision the opt-in note supersedes, state the Loader's one
entry per id and last-declaration-wins behavior in the overlay and extraction
alternatives, scope the missing-row warning to profiles that mount no Plugins page,
and move the switch-on-but-ineffective case to the list-order paragraph. Assert one
top-level composed row per id and correct the stale assembled-remote comment.
Archive the reversed 2026-09-24-web-default-schedule-composition triplet and name
the four bundles in the optional-bundles note. Rewrite the opt-in note's Problem to
the state at the branch base and state why the bundle switches time-context on with
schedule. Record the missing-row warnings, the bundle list order, and the bundle
detail page's empty state in the bundle README. Pin the composed Schedule rows in
scripts/optional-bundles.spec.ts and move the cli dependency entry into order.
Add a path-check reminder to the bash and pwsh tool descriptions. pwsh
also warns against assigning automatic variables such as $HOME; bash
also asks agents to guard path variables with ${VAR:?}.
The row now carries the name of the sidebar entry it adds and a
plain-language description, and its icon is an alarm clock drawn in the
Official group's 36px style. The README and the user guide quote the new
title; the README names the sidebar entry instead of a right-sidebar page.
A denial this backend cannot explain — the confined child cannot open an object it
was granted, or provisioning itself fails with Win32 5 — reached the user as a bare
"Access is denied". The README documented both causes but shipped no way to tell
them apart.
Add the `diagnose-windows-sandbox-acl` skill. Its script reads unless a repair switch
is passed, and classifies the failing path and its ancestors before anything
changes; its two repair modes are the only supported ACL edits. `-Fix` removes a
foreign package-SID allow ACE, and `-GrantFullControl` supplies the
WRITE_DAC/WRITE_OWNER pair the mandatory-label write needs without ever changing an
owner. Both require `-AllowRoot`, refuse a target outside it, and refuse to run
together.
`dsh-sandbox-local` registers the skill on Windows through `ctx.inject(['skills'],
...)`, so the skill registry stays optional and the sandbox never depends on it. The
assets ship in this package's `files` list, which the workspace-constraints gate now
pins alongside the runner and emitted-type entries.
Tests cover the script's verdicts (foreign ACE, healthy object, inert capability
SID, inert DENY ACE), its surgical removal, the grant's owner invariant, and both
refusals; the catalog spec covers registration and disposal.
The scenario boots the shipped composition, which disables the Schedule
service and the clock, and the two reminder rows live inside
`preset-standard`. A top-level patch cannot address a plugin a preset
declares, so the spec builds its overlay from the shipped preset and restates
that row with `preset-time-context` and `tool-schedule` enabled, beside the
Host row enables. Directly created Agents mount the default preset so their
tool set comes from the product composition, and the shipped-roster assertion
covers both planes.
The static fixture is deleted: it restated only the Host rows.
A preset row that waits for a disabled service fails the boot audit:
`tool-schedule` cannot resolve `schedule` while the shipped bundle keeps it
off, and the Web preset suite boots exactly that composition. The three
presets now declare `preset-time-context` and `tool-schedule` with the same
`disabled: true`, so the arrangement is recorded while nothing
reminder-related activates; the preset rows are enabled together with the
Host rows when the stopgap is lifted.
`#5175` disabled the three Host rows (`time-context`, `schedule`,
`ui-schedule`) as a temporary stopgap and refreshed the Web fixtures for that
composition. Those rows and fixtures stay as master has them; this branch
keeps its preset ownership of the reminder stack, which takes effect when the
stopgap is lifted.
Resolutions:
- `packages/bundle/web-app/cordis.patch.yml` keeps master's `disabled: true`
on all three rows.
- The three presets declare the clock under `preset-time-context`: a row id
belongs to exactly one plane, and the Host row retains `time-context`.
- `apps/web/tests/schedule-after.e2e.ts`, `apps/web/tests/fixtures/
time-context-every-step.patch.yml`, `web-default-isolation.expected.e2e.ts`,
and the `minimal-preset` session fixture keep master's stopgap-era state.
The shipped Web composition carries `time-context`, `schedule`, and
`ui-schedule` disabled. This adds
`@deepseek-ai/dsh-experimental-schedule-bundle`, whose patch sets
`disabled: false` on those three rows and inserts none, and names it in
`OPTIONAL_BUNDLES` with a runtime dependency of `apps/cli`, so every
installation ships it switched off and the Web Plugins page offers it in
the Official group.
The two Web suites and the built-Web Schedule Remote scenario enable the
rows through that bundle instead of a scenario-local fixture.
`packages/bundle/web-app/cordis.patch.yml` inserted `time-context` as a Host
row, so every session in the shipped Web profile — `minimal` included —
received a per-step clock reading its composition never declared. The row
moves into the `standard`, `cordis`, and `ptc` presets; `minimal` mounts
neither the reading nor the reminder tools. The Schedule service row and the
`ui-schedule` client row stay Host-wide. 139a7eb379 carried the bundle,
preset, and runtime-closure half of this move; this commit carries the
fixtures, documentation, and Agent Notes that follow it.
The `minimal` session fixture is refreshed keylessly, the assembled-Web spec
builds its every-step overlay from the shipped preset because a preset plugin
cannot be addressed by id from a top-level patch, and the composition Agent
Note points at the new note that owns the preset-scoped reading.
`SubagentSessionError` shifts line numbers in the Schedule types these
generated pages cite; both generators re-emit them, and the
persistence-catalog pair is re-recorded.
Resolutions:
- `snapshots/web/minimal-preset/tool-schemas.expected.json` keeps this
branch's side: the four reminder tools are absent from a `minimal` session
under the new composition, so master's description refresh of those entries
does not apply.
- `packages/schedule/tool-schedule/src/index.ts` keeps this branch's plugin
shape and ports master's trimmed tool descriptions (20bea14129) onto the
moved definitions; `docs/tool-catalog.md` is regenerated from them.
- `docs/tool-catalog.zh.md` has its schema code block realigned with the
regenerated English side, and the pairing record is re-recorded.
`subagent_session` joins the update-result error union, so the client's
exhaustive mapping needs an entry: the timing switch returns a new
`timing.subagentSession` key and both locale dictionaries carry the copy.
The editor shows it when someone edits a task whose Session subagent routing
owns.
Review found three ways the takeover could let two writers overlap.
- A claimant that read an exited PID could remove the lock of a new
holder that reused that PID before the claim. The claimant now probes
the PID again under the claim, after re-reading the record.
- The browser Worker's process shim reports its own PID as absent, so a
second writer in the Worker took over the first one's live lock. A
record naming the contender's own process is never taken over.
- Takeover proves only that the holder exited, while a pnpm run it
started can still be writing the profile. Each profile operation now
records its pnpm run in .plugin-manager/run.json and removes the record
when the run ends; an operation that finds a record waits for the run
within the existing tree bound and otherwise refuses, naming the
process and the record, without running pnpm.
A failed removal of the exited holder's lock now waits instead of
failing the acquisition, the probe range is stated as int32, and the
tests inject exited PIDs instead of reusing real ones, which Windows
reissues at once.
A preset mount reaches the preset's whole subtree, so a `standard`, `cordis`,
or `ptc` subagent now sees the reminder tools the removed
`ctx.agents.roots()` filter kept away from it. A task bound to such a Session
can never be delivered — `ctx.sessionController.resolveAgent` refuses it — so
`ScheduleService.create` and `ScheduleService.update` refuse it with
`subagent_session`, in the operation that creates the obligation rather than
in each consumer.
`list` and `delete` keep serving those Sessions so a task stored before the
rule stays removable, and the tool maps the new code into its error output.
The Agent Note, both package READMEs, and the generated catalogs record the
subtree exposure and the enforcement point.
The tools pipeline's ask decision now carries an optional displayReason
that ToolRuntime forwards to the approval service, so the Auto review
prompt is localized while the audited reason stays English.
Adds the auto-review-approval Web snapshot for a user rejection after a
reviewer denial and for a reviewer failure, and updates the permission
preset pages, READMEs, Agent Notes, and child-agent JSDoc that still
described the shared never bundle.
The takeover note said no Windows binding exists for a kernel-released lock; the Session write lease already holds a named semaphore there through koffi.
skippedProfileBundles recomputed the selected-minus-loaded bundle set from
the manifest, a second home for what loadProfileDirectory already records.
Runtime resolution and generateConfigSchema now read
profile.skippedBundles, so generateConfigSchema no longer reads the profile
manifest and drops its binName parameter.
Optional request extension fields had no size bound. A very large field
could exceed the runtime's string limit while the request was serialized,
which failed every attempt, including retries.
- Bound the extension payload carried by one request (8 MiB by default,
configurable) and send the remainder with later requests.
- If the combined request still cannot be serialized, send the base
request without extension fields and log which fields were omitted.
- Add regression tests and update the related references and decision note.
The seat kept the pending row in local state, so a stale settlement after
connection/reset could clear a newer selection's spinner, a remount lost
it, and /model popup selections never marked a row. ModelDirectory now
publishes the submitted selection as pending under its generation guard.
The trigger spins whenever a selection is pending, so leaving the row's
pane keeps the feedback visible.
The hostname guarded only writers on several hosts sharing one file,
which no supported deployment does, and macOS changes its hostname when
networks change, so locks from before the change could not be taken
over. The nonce closed a race that needs the PID space to wrap between
two reads. Keeping the `<pid>\n` record lets processes of both releases
interoperate.
A preset now mounts `@deepseek-ai/dsh-tool-schedule`, so the executable
deploy manifest must supply it and every required workspace peer reachable
from it: the Python runtime manifest gains the tool package, the Schedule
service and the peers that service requires. The stale product/consumer and
module graphs are regenerated for the new package.
A process that ends without running withFileLock's finally left
<file>.lock behind, and every later writer timed out until an operator
deleted it. `dsh plugin` interrupted with Ctrl-C, `dsh web` exiting
right after disposal while a cancelled install still restores files,
and any crash all left the profile package lock this way.
The lock now records its holder's PID, hostname, and a nonce. A
contender that finds a record from this host whose PID a signal probe
reports absent claims that record through a wx-created claim file,
re-reads the lock, removes it only while it still holds the record, and
retries at once. A PID-only record from an earlier release counts as
written on this host, so locks left before this change are taken over
too. Empty, incomplete, foreign-host, and live-holder records are still
waited for.
The Host plugin registered `schedule_create`, `schedule_list`,
`schedule_update`, and `schedule_delete` on every live root Agent, so a
`minimal` session received four reminder tools its preset never declared. The
tools move to the preset-scoped Consumer `@deepseek-ai/dsh-tool-schedule`,
which injects `tools` and `schedule` and registers them in the scope that
mounts it: `standard`, `cordis`, and `ptc` carry the row, `minimal` does not.
`@deepseek-ai/dsh-schedule` keeps the Host service, storage, delivery, and the
Automation surface.
The five `minimal` assertions in the Web preset suite and the `minimal-preset`
tool-schema snapshot return to the bash-only table, the tool catalog is
regenerated under the new owner, and the Agent Note that recorded the previous
composition points at the new one.
loadProfileDirectory printed each skipped bundle on every call, and startup,
runtime resolution, the config editor, the plugin manager, and HMR all load
the profile, so one incompatible bundle printed the same warning six times at
startup and again on each Web page load. Loading now returns the skips in
Profile.skippedBundles, and the CLI and Desktop launchers print them once per
start with reportSkippedBundles.
session.selectModel can take seconds while the Host persists the default
model. The composer seat disabled its rows with no other feedback; the
chosen row now spins in its check slot, and the trigger spins in place of
its chevron while the menu is closed.
- State the contract precisely: the cut never introduces a lone high
surrogate, and an unpaired surrogate the input already carries is not
repaired.
- Document the new export in the package README: a character-budget preview
section naming the persistent bash and pwsh shells and str_replace_editor,
plus a Known Limitations bullet that no longer contradicts it.
- Drop the assertion restatement from the two shell test comments.
- Let the str-replace test helper reuse a root so the clipped context reads
its file inside its own working directory.
The Decision no longer says the renderers open with the fixed line: the bracketed
marker is the first line, and the shared line is the second. The snapshot
paragraph names both missing harness capabilities, clock control and seeding the
schedule storage domain, and the pinning sentence distinguishes the full-string
unit assertions from the Web assertion over the assembled request.
The snapshot paragraph now states what the tree does not have (no scenario patch
controls the wall clock delivery reads, and no Web scenario scripts a delivered
reminder) instead of concluding that the harness cannot support such a fixture.
The README attributes the presentation wording to schedule_create and the update
wording to schedule_update, and the note names the batch payload field alongside
the one-shot one.
The shipped Web bundle gives every live root Agent the Host Schedule
tools, and every other scenario in this file that boots the shipped
base/web bundle with the minimal preset expects ['bash',
...SCHEDULE_TOOLS]. This case uses the same bootWeb composition and adds
only a settings patch, which does not change tool composition, so its
['bash'] expectation failed on every PR.
Review of the previous commit found two inaccuracies it introduced. The
repeat-tool-reminder producer still writes `form: 'notice'` and its
`<tool> × <count>` summary — `src/index.ts` spreads them onto
`REMINDER_SOURCE` — so only the wrapper should have been dropped. The cron row
also claimed an `inject()` path that `dsh-schedule` does not have:
`runtime.ts` always delivers through `followup()` and flushes the session.
Link `MessageSourceMap` to the page that owns it, and write `V4` as the corpus
does for the format name. Both pairs are re-recorded.
The Agent Note now states what a model infers from the origin line and records
the residual risk of dropping the untrusted-content instruction as accepted, with
its reintroduction condition and the assertions that keep a later change
deliberate. It also records why no recorded-session snapshot covers a delivered
reminder, and the host note links back to it.
The package README keeps the creation-side contract in view: prompt stays the
content to present to the user when the target becomes due.
A reviewer denial now returns the tools pipeline's ask decision when the
Session's approval policy is ask, so the user can approve, reject, or
cancel the call. Selecting Auto writes the ask policy; a recorded Auto
selection still matches never, which delegated children pin, so their
denials stay final.
A reviewer failure now fails the call with its specific error instead of
presenting it as an Auto review denial.
Keep the timing rules that apply to every selector in the create
description once, move the cron day-field and fixed-rate alignment rules
onto their parameters, and drop parameter formats, result fields, and
Host compare-and-set details from the tool descriptions.
Drop the extension-less path hint from read_image and the
returns-before-stopped note from job_kill, and describe send_message
delivery without "resumes a turn". image-compaction lowers its context
window by the 30-token tool-schema estimate reduction.
Tool guidance sections keep only what the tool definitions do not say.
Apply review: reword interrupt_agent and list_agents, carry "a rejected
escalation stays denied" in the rejection error, pin
sandboxPermissionsDescription, and fix stale README and Agent Note
statements. Record the tool-definition rules in agent-experience.
Compaction threshold snapshots shift their context windows by the
370-token system-prompt estimate reduction; escalation-rejected moves to
a v4 fixture for the new rejection text.
Delete constraints the model learns from the call result, shorten
implementation detail, and move per-parameter rules into the owning
parameter descriptions. Bash, pwsh, and fs share one
sandbox_permissions description in dsh-sandbox.
The standard preset's first-turn prompt drops from 7985 to 6551 tokens.
Compaction threshold snapshots lower their context windows by the
1548-token tool-schema estimate reduction so each trigger keeps its
position; their fixtures move to v4 generations.
Broaden the skill description to cover enabling a shipped plugin that a
bundle leaves disabled, add the workspace-bundle recipe for it, forbid
hand-editing profile files that install_bundle owns, and verify installs
through the install result and cordis_inspect_query instead of logs,
process lists, or paged list_plugins calls.
* feat(approval): localize prompts and allow consent for one turn
* test(approval): update cast baseline and Web PTC prompt pin
* fix(approval): cover PTC language and clarify turn consent
* fix(approval): group turn consent under allow menu
* fix(approval): keep turn consent in memory only
* test(snapshot): refresh owners for merged justification guidance
* fix(approval): drop duplicate allow-once from the allow menu
* refactor(approval): derive turn consent expiry from one check
Replace the turn approval bookkeeping (revocation state, incremental log
cursor, and turn-signal listeners) with one side-effect-free expiry check
over the log and lifetime flags. Events now only withdraw open
presentations early; the expiry check decides whether an answer or turn
consent still applies, including for Sessions without event publication.
* refactor(approval): drop one-turn consent and keep localization only
Remove the allowed-turn answer, the live turn-consent state, the allow
menu, and their tests, notes, and recorded Web scenario. The approval
service returns to the master request path; the request keeps the
localized displayReason for presentation, and the card resolves it in
the active UI language.
* test(snapshots): record the localized justification schema text in new scenarios
---------
Co-authored-by: Turtle <[email protected]>
Co-authored-by: Kaige-Gao <[email protected]>
#5108 added this case against a master where the shipped Web bundle had no
Schedule rows; #5123 then made Schedule part of the default Web composition.
Every other minimal-preset session in this file already expects bash plus
the four Schedule tools, so this one does too.
The shipped Web composition gained the four Host Schedule tools, and the
other four minimal-agent assertions in this file already expect them. The
legacy profile-patch scenario still expected bash alone, so master's E2E
lane failed on it. Align it with its siblings.
Session format v4 refuses the retired `{ kind: 'plugin', plugin: '<name>' }`
wrapper at message admission, and the v3-to-v4 migration maps released plugin
names to producer-owned kinds. Five paired documents still show the wrapper as
the current shape, so a plugin author copying them writes a source the session
refuses.
The examples now name the kind the producer declares through
`MessageSourceMap`, and the tool cookbook states that declaration requirement;
the wrapper appears only as the form the admission check rejects. The two
package READMEs also disagreed with their own sources: time-context writes
`{ kind: 'time-context', form: 'snapshot', sections }`, and
repeat-tool-reminder writes `{ kind: 'repeat-tool-reminder' }` with no `form`
or `summary`.
Conflict resolution:
- docs/config-catalog and docs/module-graph: regenerate with the current
generators, which write the bilingual pages together.
- The conflict came from master's new short-fingerprint pairing records: our
branch still carried the previous full-hash records. Take master's record
format, then re-record the output-retention README pair after regenerating
its two sides.
Broaden the skill description to cover enabling a shipped plugin that a
bundle leaves disabled, add the workspace-bundle recipe for it, forbid
hand-editing profile files that install_bundle owns, and verify installs
through the install result and cordis_inspect_query instead of logs,
process lists, or paged list_plugins calls.
The Consequences and the alternative treated the task's Session binding as the
constraint on who writes the prompt. The package README records that the same
binding is not caller authorization, and the Web task detail writes the prompt
through the same service, so both sentences now name the prompt's writing paths
and the binding's actual scope instead.
The note said the Session "owns" the schedule; the storage binds a task to a
sessionId and the owning note keeps "Host-owned" for the Host storage domain, so
the Decision and the alternative now use that binding. The Decision also stops
calling every dynamic field JSON-encoded, since only the id and the prompt are,
and the alternative states plainly that the prompt was called untrusted input.
When the loaded catalog offers no model and no provider failed to load,
the composer trigger shows a warning dot and "No model configured"
instead of the saved route id, and its menu opens on guidance to sign in
to a DeepSeek account or add an API key. A saved selection that becomes
unavailable while other models remain keeps its route id. The README no
longer claims an unavailable model disables the composer.
The Consequences now state that the prompt text carries no authorship guarantee:
a model-written reminder can quote text read from the web, files, or command
output and deliver it under the user-origin line, while the Session binding only
limits which Session's model writes the task. They also name the Web notice that
renders the delivered message, and the Problem and Decision drop the repeated
"standing" wording and the change narration.
The Decision now separates the message's origin (a schedule owned by that
Session) from the authorship of the prompt text, which the schedule tools let the
model write on the user's behalf. It also replaces the claim that every dynamic
field is JSON-encoded: the occurrence instant is a validated canonical RFC 3339
value, and the id and prompt are JSON strings.
Each record rendered its whole saved prompt, so a long instruction on a
recurring task filled the records tab with the same text once per run.
A prompt longer than two lines now shows two lines and an Expand toggle;
the toggle appears only while the clamp hides text, and a collapsed prompt
re-measures when the panel width changes.
Also drop the stale comment about the removed message-id anchor.
Due reminders entered their Session as user-role messages whose fixed text told
the model to treat the reminder prompt as untrusted content rather than new user
instructions. Both delivery paths now open with one shared origin line,
"This is a scheduled message from the user", so the message states where it came
from instead of what standing to give its own payload.
One-shot and recurring batch framing share SCHEDULED_MESSAGE_FRAMING, so the two
paths cannot drift. The bracketed markers and the JSON-encoded dynamic fields are
unchanged, so a prompt still cannot forge framing lines.
The rationale and alternatives are recorded in the Agent Note
.agents/notes/implemented/architecture/2026-09-24-scheduled-message-framing.md.
The official DeepSeek route and pi-ai routes that name an API-key
reference again return an empty catalog while that reference resolves
to nothing, so model pickers stop offering models that can only fail
with MISSING_CREDENTIAL. A malformed key still reports
INVALID_CREDENTIAL, and routes with provider-native authentication keep
their catalogs. This restores the discovery rule that 6db08fd80f
replaced with credential-independent catalogs.
lint:contracts-ready rejected the override filter's undefined check
against its declared type and four asymmetric matchers typed any. The
override filter now takes the parsed configuration as it arrives, and
the tests narrow their matchers the way other packages do.
The shipped Web composition now puts four Schedule tools on every live root
Agent, which pushes a full-preset `cordis_inspect_query` answer past the
12,500-token inline budget, so the spill policy retains its head and tail with
a spill path instead of the complete JSON.
Refreshed owners publish a v4 generation, so every scenario that names
another owner's Session fixture must name that owner's highest generation.
The shipped Web composition also puts the Host Schedule tools on every live
root Agent, so the minimal-preset tool-table assertions include them.
* feat(experimental): ship every experimental capability as an optional bundle
dsh-base mounts the registration-only browser-use and computer-use
services; the Playwright MCP, Chrome DevTools MCP, native and MCP Cua
Driver providers, Auto review, and the Inspector become one-row bundles
listed in OPTIONAL_BUNDLES with an icon and locale display metadata.
verify-default-product-isolation requires both for every optional
bundle. Python PTC and Stagehand stay explicit compositions.
* feat(experimental): keep optional bundles inside the install closure
Drop the Playwright MCP, Chrome DevTools MCP, and native Cua Driver
bundles: each is a runtime dependency of every install and would add
about 85 MB of provider runtimes whether or not enabled. dsh-browser-use
leaves dsh-base with them. The three packages keep their locale display
metadata for component rows.
* feat(experimental): drop the Cua Driver MCP bundle and leave dsh-base untouched
* test(web): refresh Official-group goldens for the four optional bundles
* fix(plugins): use an uninstalled bundle as the install example and pin optional-bundle metadata
The install guide's example is now @deepseek-ai/dsh-subagent-codex,
a published bundle no shipped profile carries, since Auto review is an
installed optional bundle. The optional-bundle spec guards its corpus and
pins each bundle's title, description, and icon in both languages; the
Inspector's Loader test mounts its real bundle patch, its Client names
the reload a hot-enabled page needs, and its description states the
loopback DevTools endpoint's reach.
* test(web): refresh GitHub install recovery placeholder snapshot
The account row lost its only action when the catalog editor was removed
and rendered as a bare name. It now carries a green "Signed in" status;
the store keeps the row only while the account lists models, so the
status holds whenever the row renders. The package README no longer
describes the removed editor.
The Web bundle now mounts `time-context` and `schedule` and leaves the
`ui-schedule` client row enabled, so the Automation tasks page, the Session
reminder catalog, and the `schedule_*` tools work without an overlay. Both
packages join the bundle's declared dependencies, and the example overlay is
deleted: re-inserting its rows would mount both Host plugins twice.
The fixed-rate floor drops from 300 to 60 seconds across the Host decoder,
the create and update validation, the model-visible tool schema, and the
client's per-unit floors.
Web recorded sessions and header sidecars are refreshed for the readings
`time-context` now appends; `normalizeWebSessionVolatiles` tokenizes their
sampled instant, browser zone, and elapsed duration so replay stays stable.
The sidebar toggle is flush with `.logoRow`, which clips its own overflow, so
the standard outward ring lost every pixel that left the row: the reviewer saw
the ring cut on the toggle in both the expanded header and the settled rail.
The 36px rail toggle is also 1px wider than its 35px row, so the control's own
right edge was clipped there. Draw this control's ring inside its own box and
stop clipping the row once the rail has settled; the ring keeps the standard
`--dsw-focus-ring-width` colour and width, and pointer modality still resolves
`--dsw-focus-ring-color` to transparent.
`focus-rings.e2e.ts` asserts the whole ring against every clipping ancestor in
both layouts, in both themes.
Only the ui-theme README pairing record conflicted; re-recorded. The stylesheet
corpus spec passes over master's new client sheets, so no ring needed realigning.
`.selected` still named `--dsw-alias-brand-primary-new-colorprimary-new-color`, the
corrupted token left by a botched rename; its dark value is `deepseek-450` while
every focus ring now uses `deepseek-400`. The selection ring reads the clean
brand-blue alias instead, so one blue covers selection and focus.
- Only the definitive Windows session-end message bypasses the quit
confirmation; the macOS power-off flag clears on the next focus or show
because another application can cancel that shutdown.
- A bypassing quit disposes the pending confirmation so a late inspection
result opens no box and an answered box cannot approve.
- Cancelling a quit that closing the welcome window started shows the
welcome window again.
- The first-hide notice records its marker only after delivery and
withdraws it on failure; the Windows quit dialog uses the multi-size ICO.
- windowShown settles when the awaited window is destroyed.
- Cross-link the partially superseded window-menus note; tidy docs.
Closing the main window hides it on macOS and Windows; the page and the
Host keep running and the next show resumes the same document. Windows
gets a permanent tray icon (open / quit) and a one-time notification the
first time the window is hidden after installation.
Every ordinary quit entry asks the Host over a new quit-inspection IPC
request for active tasks (same rule as the update restart check) and
armed scheduled reminders (schedule family of workspace/session-activity
for loaded sessions). Nothing to interrupt quits silently; otherwise a
native message box explains what stops, with Quit as the default and
Cancel on Esc. Inspection failure or a missed two-second deadline counts
as running tasks. The installer restart, fatal recovery, the development
restart command, and OS session end skip the confirmation.
Also: defer the downloaded-update confirmation while the window is hidden,
render the tray ICO from the vector icon, and point the installer's
running-application message at the tray.
Closes#4013
Resolve local image paths through the authenticated dsh-app file route and allow that route in rewritten Markdown images. Cover desktop rendering, previews, encoded filenames, and unsupported origins.
Fixes#5105.
Resolve the account provider import to carry both the account quota
codes from #4858 and APP_IDENTITY for the model gate. The upstream
account-quota spec now answers the account listing request with 404,
because the account route requests /models before its first Messages
call and a shared mocked Response body can only be read once.
A plan review that mounted in the same commit as a Sidebar seat change
still crashed its opener, which retired the View full plan link and every
automatic open until the page reloaded. Two paths reached the crash:
- Returning from another Session: PlanReviewOpen read the departing seat
as mounted during render, and that seat released before the review's
effect ran while the arriving seat bound after it.
- A Session without a saved Sidebar layout: the seat released and rebound
around every store commit, and the commit that created the layout was
the one in which the review opened.
The seat now republishes its binding after each commit without releasing
it in between, and releases the latest binding only when it leaves the
screen. The review opener reads the mounted snapshot when it acts,
reports whether an open reached a seat, retries whenever the mounted
Session changes, and records the review as opened only after an open
does. Embedded subagent reviews still open in the visible parent Sidebar.
* feat(desktop): distinguish account quota alerts from API key notices
* docs: condense architecture introduction within existing budget
(cherry picked from commit 01aacaee4d48fd7f4d00acb5e23758084d3f224e)
* test(desktop): cover quota notice lifecycle and remove unknown casts
(cherry picked from commit 7fd09407c482d1f0e30f24ba0ebf23cd844ac9a1)
* chore(account-quota): align tests, catalogs, and bilingual pairs with the account plugin
* fix(ui-settings-account): pass the Platform retry label to the account top-up overlay
* fix(chat): preserve quota failures and global notices
* fix(account): keep active top-up pages across quota notices
* fix(account): share platform page ownership and release quota holds
* fix(account): defer notices behind the shared Platform page
The single native Platform view covers the document, so a quota failure that arrives while a Settings-opened page shows is deferred rather than retained: the newest failure still replaces an older notice and its Modal appears after the viewer returns if still current. Top up remains the retained case, releasing its hold and page request with the entry.
* test(account): align quota fixture with bonus actions
* fix(account): preserve quota notice and Platform return focus
* fix(account): preserve onboarding return reads and failed notices
* docs(account): clarify page opener return ownership
* test(account): drive bonus acknowledgement retry deterministically
---------
Co-authored-by: liyao <[email protected]>
Co-authored-by: lsdsjy <[email protected]>
* fix(plugins): name the default install source by the registry it names
The add-plugin dialog showed "默认安装源" for pnpm's own registry, which
told the reader nothing about where the install would download from. Name
npm's own registry as the official npm registry, and any other registry
pnpm's own configuration names by that registry's name or host, so the
folded control reads by the registry the install actually asks.
* test(plugins): cover comparing an unparseable registry address
---------
Co-authored-by: Turtle <[email protected]>
The archived filter reaches the row toast through the registration's declared
store instead of a registrant-owned view hook, per the cross-entry view-state
rule. The README names the toast action by its actual label, and the
unarchive e2e switches back to Hide archived so it again proves the restored
row is an ordinary row in the default view.
The view-options filter is now hide/all/only radio items, the
archived-only view drops Workspaces without an archived Session, and
tree children of a dropped Workspace nest under the nearest shown
ancestor (#4854).
The installed-wheel smoke checks real model tool use and externally verifies
the created file, a fresh host-only challenge, the copied receipt, and the
unchanged source. DeepSeek can return the requested acknowledgement before
a file-details paragraph; requiring it to be the last line makes correct
SDK execution fail according to the placement of explanatory prose.
Accept the exact acknowledgement on any standalone line, retaining completed
turns, model-requested tool calls, exact file bytes, and session-log checks.
Replay the observed response through both turns and keep negative coverage
for absent or embedded acknowledgements and incorrect files.
Only the bilingual pairing records conflicted. Master moved Agent Note records to
per-heading hashes, so this branch's note record was re-recorded in that format;
the ui-primitives and ui-theme README records were re-recorded unchanged. The
stylesheet corpus spec still passes over master's new client sheets, so no ring
needed realigning this time.
The probe needs a value that cannot be inferred from the prompt, not protected information. Describing that generated UUID as a private secret made the API test depend on an unrelated disclosure decision. Use an explicitly synthetic sample label and a benign formatting update while preserving execution audits, first-continuation cache assertions, and disabled retries.
Open the running Think disclosure explicitly when a process group also contains tool updates, and keep the fork process summary expandable in its ARIA expectation. Record the Goal request-series marker in a V4 fixture successor while preserving the historical V3 recording. Production behavior and the scroll assertions stay unchanged.
Record explicit request-series starts and the tool additions admitted when resuming a seeded parent with no tool declarations. Keep tool names and the shifted source-event reference alongside that activation history. Refresh only the affected owner-local outputs to the current writer format; retain historical replay fixtures and the unchanged child expectation.
Tool changes previously forced prompt consolidation even when the prepared
route could activate new tools through history. Rewriting the first system
message, or clearing an earlier appended prompt, reset the declaration
series and turned additions into immediate declarations.
Only routes without toolUpdate need that tool-triggered consolidation.
Capable routes append the full changed prompt and keep existing messages.
Explicit series starts, surface replacements, and unsupported prompt
routes still consolidate; retained-name definition changes still rebuild
tool declarations in Session.
Cover both update modes and reset paths, and pin the combined prompt/tool
transition through the shipped headless profile with shared schema goldens.
Run a real DeepSeek conversation through the built SDK profile and change the scoped tool registry through a model-invoked controller. Compare the first post-addition cache usage with the preceding request after unique user history has been cached; verify deferred declarations, chronological activation, and actual tool execution. Removal remains covered without requiring its cache prefix to survive. Disable test and provider retries so a warmed transition cannot hide a cache regression.
Log tool changes as developer messages independently of model capability,
fold committed headers and updates into Session.toolHistory(), and let the
LLM runtime construct deferred and retained declarations for routes that
declare a toolUpdate mode. DeepSeek serializes updates as system-role
tool_addition/tool_removal blocks with defer_loading and the tool-changes
beta header. An unchanged restored tool continues the series and is
re-offered through its recorded blocks instead of rewriting declarations.
Replace the obsolete rejection tests with wire assertions and add coverage
for the session fold, loop emission, route projection, config validation,
and the beta header. Chat and Trajectory changes land separately.
* feat(i18n): key translation pairing records by heading section
Replace the whole-file blob hashes in *.i18n.yaml with one entry per
heading section that still holds language-specific content, keyed by the
English heading-slug path. Blocks identical across the pair (generated
table rows, code blocks, shared regions) stay out of the hash, so edits to
different sections and identical regenerations merge with Git's default
text merge, including on GitHub.
Remove the dsh-translation-pairing merge driver, its resolver command and
installer wiring; gen-translation-brief recovers last-confirmed text from
commit history. Migrate all active records, archive the superseded Agent
Note, and record the replayed conflict reduction in the new one.
* test(i18n): refresh translation prompt snapshot for the terminology note
* feat(i18n): exclude only code blocks and generated regions from pairing records
Replace byte-equality masking with an explicit rule: a record hash skips
fenced code blocks and BEGIN/END GENERATED regions, the two kinds of
content the pairing gate already requires to be identical on both sides.
Move generated data into regions written into both pages:
gen-config-catalog renders both languages and the record with per-package
regions labelled by code identifiers; gen-module-graph keeps its table in a
region; gen-doc-graphs splices the event matrix regions into the Chinese
page. Add a generic region splice beside the region grammar.
master released 0.1.7-rc.1 while the package added by this branch still declared 0.1.7-alpha.2, which failed Dependency layout (verify-npm-install-layout), Pack npm tarballs (release members must share one version) and node 24 / static (constraints) from one cause. Merge origin/master and bump the new package to the root version.
The page carried a 28px top inset above its head row, so the window's topmost 28px on that page belonged to no drag region — the frame's former band covered it, and the per-row mark did not. The inset moves into the head rows themselves (.pageHead and .detailTop), which now start at the window's top edge while the content keeps its position. The browser lane pins each row's top edge at zero and the negative control confirms both assertions fail on the pre-fix sheet.
* fix(plugins): deny incompatible bundles and clarify compatibility refusals
A bundle package is not a plugin row, so startup admission never read its
own DSH peers: a bundle installed outside DSH or before a DSH upgrade still
applied its patch layer. loadProfileDirectory now skips such a bundle like an
unreadable one unless the profile exempts that exact pair.
A post-install refusal in a profile without a lockfile reinstalls from the
restored manifest instead of failing a frozen install and leaving the
rejected packages in node_modules.
Compatibility warnings now name the `dsh plugin allow-version` command for
CLI-managed profiles, startup denials name the row id instead of a resolved
module URL, and the CLI no longer reports "pnpm failed" when pnpm never ran.
* feat(plugins): report incompatible versions as typed refusals
Compatibility checks now yield the refused packages as data instead of a
profile-specific sentence. runProfilePnpm returns them as
PackageResult.incompatible, and the plugin manager refuses with the
incompatible-version code carrying the same records. Each surface renders
them: the CLI prints the exact allow-version command for its profile, the
Web plugin manager words them through its locale dictionary, and app-boot
keeps one neutral English diagnostic for logs and startup stderr.
* fix(plugin-manager): drop the agent exemption hint from the incompatibility reason
Review caught two real gaps in the previous commit.
The rename dialog's no-op check compared the localized display title, so
on an automatically titled Workspace the seeded prefill equalled it and
Rename stayed disabled — the user had no direct way to pin the visible
name. The dialog now seeds the draft with the label on screen while the
stored title decides whether confirming saves, and the duplicate check
excludes the target by Workspace identity instead of by title, since the
seeded label equals the row's own displayed title.
The registry took the automatic title from the canonical path, so a
symlink at the first-use path titled the Workspace after its target and
the default lost its localized label. The title now comes from the
requested directory's final segment; the path stays canonical.
Also: drop a rotting file count and the stale localized-path wording from
the Agent Notes, record the two cwd-derived Session label fallbacks as
known consequences, trim the WorkspaceBrowser memo comment to the
non-obvious part, and remove the deleted request type from the
hand-maintained catalog LINK_MAP.
* feat(web): restore the process-row polish on the grouped work details
The sweep highlight, shared row colors, expanded-row spacing, and the
12–24px font-size range were developed against the pre-revert work-details
implementation. Re-apply them to the grouped implementation that replaced it.
- TextShimmer keeps its active-driven span and now paints a masked sweep
overlay in the theme's `--dsw-alias-label-shimmer` color, so the base text
inherits the caller's color and hover changes.
- DisclosureRow owns the row color: tertiary at rest, secondary on hover,
with glyphs, titles, and summaries inheriting it; tool, skill, command,
reasoning, and workflow rows drop their own hover darkening.
- ChatView spaces process rows 8px apart and keeps 16px beside Assistant
responses; the grouped header sits 8px above its first member.
- The Settings font-size stepper accepts 12–24px, and the grouped header's
leading box and glyph scale with the content font-size delta.
* fix(web): restore blue running status and refine process disclosures
* fix(web): finalize running snapshots and whale animation
* fix(web): preserve disclosure layout and repair CI fixtures
Resolving packaged-app icons grew the embedded C# until the generated
-EncodedCommand line reached 32,883 characters for a 222-character path,
past the 32767-character CreateProcess limit, so association queries stopped
starting for long multibyte paths. Write the adapter to a private temporary
script and pass it with -File instead; the command line no longer grows with
the path. The temporary directory is removed once the call settles.
The icon fallback also reads the handler name the caller already fetched
instead of calling GetName again, and the README records that icon
visibility depends on resolving the Shell's indirect reference.
With no OS-marked default the split button labelled the first registered
application and marked it "(default)" in the menu, but the primary action
still asked the OS to resolve the association again; on Windows that can
prompt or open a different application than the button promises. Send that
application's id explicitly and keep the association path for the case the
Host actually marked a default.
The snapshot scenario now clears the platform's own default marker and
re-enters the file, so the association is read again instead of reusing the
answer already held, and it asserts the launched target rather than only the
labels.
Review threads: shrink READ_LANG_BY_EXTENSION to the suffixes whose own name is the better label (tsx/jsx/tf/tfvars/gradle) since the other 41 values already equal the language short name; label .hcl as hcl and .tfvars as tfvars instead of tf; accept .groovy alongside .gradle with its own short name; document that LANG_ALIASES also carries the persisted short ids; route the three inline alias lookups through grammarForHint; state that a short name may equal the grammar id; fix the util README wording.
Tests: cover .groovy, the four self-naming suffixes, and every id each suffix reaches through the shipped surfaces (the hand-synced LAZY_ALIASES list could not catch an alias whose target is neither boot nor lazy).
Compatibility checks now yield the refused packages as data instead of a
profile-specific sentence. runProfilePnpm returns them as
PackageResult.incompatible, and the plugin manager refuses with the
incompatible-version code carrying the same records. Each surface renders
them: the CLI prints the exact allow-version command for its profile, the
Web plugin manager words them through its locale dictionary, and app-boot
keeps one neutral English diagnostic for logs and startup stderr.
The retained-tab-layout note still stated that existing window-drag region pulses remain; this layer's pulse is gone and the shell watcher replaces it. Rewrite both sides and re-record the pair.
The recall watcher now tolerates a short quiet window so a report that arrives before a CSS transition moves anything cannot end the loop on its first unchanged sample, re-arms on a transition or animation that touches a marked row, and re-arms on a child list change in a container holding one. A dock picture withdraws its strip's drag claim while a pane floats, because a float sorted before that row would otherwise sit earlier in the document and lose its own subtraction. The settings panel closes when an onboarding step appears, since the step's overlay marks only #root inert. Prose and the two Agent Notes record the Windows narrowing of the Platform overlay return bar, the collection rule, and the rows' one home in the ui-theme manifest.
Node's module-hooks thread serializes resolver errors and returns the stack
accessor as a read-only configurable data property. Rewriting the routed
importer then assigned error.stack, threw a TypeError, and replaced the
original ERR_PACKAGE_PATH_NOT_EXPORTED or ERR_MODULE_NOT_FOUND error, so
plugin metadata reported a failure instead of falling back to package fields.
The importer rewrite now redefines a rejected stack value in place and
rethrows the original error with its code.
Master's newest commits add `.close` in ImageLightbox and `.imageButton` in
MarkdownText, each declaring the brand token directly. Both now read
`--dsw-focus-ring-color` through `--dsw-focus-ring-width`, as the stylesheet
corpus spec requires of every client focus rule.
A bundle package is not a plugin row, so startup admission never read its
own DSH peers: a bundle installed outside DSH or before a DSH upgrade still
applied its patch layer. loadProfileDirectory now skips such a bundle like an
unreadable one unless the profile exempts that exact pair.
A post-install refusal in a profile without a lockfile reinstalls from the
restored manifest instead of failing a frozen install and leaving the
rejected packages in node_modules.
Compatibility warnings now name the `dsh plugin allow-version` command for
CLI-managed profiles, startup denials name the row id instead of a resolved
module URL, and the CLI no longer reports "pnpm failed" when pnpm never ran.
The new no-default scenario cleared the macOS application catalog, which
does nothing on Linux: `gio mime` owns the OS default there, and the
Linux stub never wrote the launch log, so CI timed out waiting for the
application gesture. Clear the marker each platform actually reads, log
launches from both stubs, and compare the launched id against the
platform's own registration id.
Conflict resolution:
- docs/config-catalog: regenerate the English page; the Chinese page follows the
three shifted Config sources and the bilingual pair record is re-recorded.
- docs/module-graph: the generator rewrites all three artifacts together.
- packages/shell/tool-pwsh-persistent/src/index.ts: keep master's rewrite of the
prompt handling and re-apply the surrogate cap and its import.
- The three package.json dependency entries follow master's workspace:* range
convention, and the lockfile importer specifiers match them.
Master's newest agent-preset sheet carries both the new `.helpButton` and the
retained `.iconButton`, whose focus ring still declared the brand token
directly. The stylesheet corpus spec rejects that form, so it now reads
`--dsw-focus-ring-color` with the brand-token fallback.
ui-theme/README.md, README.zh.md and README.i18n.yaml conflicted again: master
extended the `design-platform.css` sentence with the `--dsw-alias-file-diff-*`
palette. Kept that sentence together with this branch's `focus.css` paragraphs
and the seven-sheet entry list, then re-recorded the pairing hashes.
* feat(preset): add creator-mode plugin practices skill
Creator mode mounts packages/preset/agent-preset/skills. The new
cordis-plugin-practices skill states Harness-specific principles and
concrete rules for plugins that survive upgrades and avoid polling or
rescanning session logs: waterfall delegation, restrict/guard over
assemble, agent.ctx ownership, no new session event types from profile
plugins, session projections, and slot-based UI.
* refactor(preset): fold plugin practices into cordis-plugin-development
A separate skill competed with cordis-plugin-development for the same
trigger. The practices now live in references/practices.md, read from
procedure step 2 before choosing an extension point.
* feat(preset): require Harness components and tokens for plugin pages
A creator-built management page served as an iframe with its own
stylesheet could not match the Harness design. The practices reference
now requires slot-rendered React pages built from ui-primitives, theme
tokens from Theme inspection, and light/dark checks in the real slot.
* feat(preset): make host UI consistency a principle and a verification step
Adds principle 6: the rendering surface decides whether a plugin can
match the Harness UI, so it is chosen before any view. Step 4 of
cordis-plugin-development now verifies primitives, token-only styles,
and light/dark rendering beside a comparable host page.
* feat(preset): prefer theme tokens over pre-stable ui-primitives
ui-primitives is a pre-stable platform module; creator plugins are plain
JS without type checks, and a throwing component blanks the slot entry.
The practices reference now treats tokens as the default way to match
the host, limits primitives to behavior that is costly to reproduce, and
tells the agent how to detect a slot entry crash after an upgrade.
* feat(preset): forbid runtime imports of Harness Client packages in plugins
Creator plugins must assume ui-primitives and other Client packages break
without notice. The practices reference now has plugins copy markup, CSS,
and behavior from primitives, or inspect the rendered host control, and
keep --dsw-alias-* tokens as the only shared styling dependency.
* fix(preset): correct plugin practices against runtime behavior
- agent.ctx registrations need a plugin-owned disposer too; plugin
unload does not remove them.
- Timers start work with followup(); inject() does not wake the agent.
- inject() logs agent/inbox/spliced at call time; agent/request cannot
change messages.
- Guards are synchronous; awaited decisions return ask from
tools/pre-execute.
- Chat node renderers are keyed by kind; slot inject reinstalls on
declaration return.
skills.spec now keeps whole-file cordis-plugin-development references
below the pruner threshold as the read tool renders them.
The reported defect is the branch where the Shell lists associated
applications but marks none as the default: the split button promoted
"Show file location" to the primary action and hid the applications
behind the menu.
Record that state in the keyless document-preview scenario. The new
golden pins the primary action to the opening control, and the gesture
log asserts the main button opens the file while the menu's marked
default launches the first registered application.
Two focus treatments still named a colour of their own: the TurnNavigator
mark's tick filled with `state-business-primary` under a comment that already
claimed the brand colour, and the plugin inventory search input drew its focus
border in `state-business-primary` while the shadow beside it read
`--dsw-focus-ring-color`. Both now read the same ring colour as the rest.
`Tooltip` states why it takes `pointerModality()`'s wide answer while focus
rings read the narrower `data-input-modality` attribute the module publishes.
The ui-web source map gains the two window-drag modules and its boot row names the watcher install; ui-settings-general states that the settings panel portals beside #root. Both pairs are re-recorded. The ui-settings-general manifest keeps master's dependency order so only the two new dev entries appear in its diff.
The first-use directory name and its stored title came from the Client's
startup language, so one product decision carried two incompatible
requirements: a path addressed by shell commands, tool arguments, @path
references, and session logs, and a label a reader reads.
The controller now owns the fixed `default-workspace` directory name and
the registry titles the Workspace after that same segment, so
`workspace.initializeDefault` takes no request at all —
WorkspaceInitializeDefaultRequest and its name validation are gone, and
the registry resolver returns a path instead of a path and title. No
language reaches the Host.
Browser consumers label a Workspace through the new
`workspaceDisplayTitle`: a title still equal to the automatic one reads
as the localized default name from the `common` namespace, every other
title reads verbatim. The Workspace browser resolves it once at the store
read, so rows, hover card, search meta, and the rename and delete dialogs
inherit it; the hero picker and the Conversation chip resolve it too.
Confirming a rename stores the text on screen, which is what stops a row
following the language.
`welcome-startup.spec.ts` imports the unpackaged main entry, which now
requires `DSH_DESKTOP_PRIMARY_RUNTIME_DIR`. Stub it beside the neighbouring
`DSH_DESKTOP_*` values so the suite reaches the welcome startup it covers.
* fix(plugin-manager): bound pnpm runs so a silent child cannot hold the profile lock
A pnpm child that prints its completion line and then never exits holds the
profile write lock until the host is restarted, so every other session and host
on that profile fails after its own lock timeout (issue #4981). execa resolves
its promise only once the piped stdio has ended, so a run now completes on the
process's own exit and drains the pipes under a bound instead of awaiting their
end.
A service run whose captured output stays silent for idleTimeoutMs is
terminated, reports timedOut alongside its exit status, and is not retried on
the next registry; a run with inherited descriptors captures nothing and is
left unbounded for its operator.
* test(plugin-manager): type the mocked child and await the drain descendants
The inherited-descriptor fixture no longer needs its legacy `as unknown` cast,
because the mocked child is typed for the fields the operations read; the
retired baseline entry is pruned (verify-no-unknown-casts).
The drain specs wait for the killed descendant to exit before removing its
directory: Windows refuses to delete a directory a live process still holds.
* test(plugin-manager): stop the drain descendant deterministically
The descendant that holds the pipes now ends on a stop file the teardown
writes, and on its own after a minute, so the fixture leaves no process behind
when a kill cannot be delivered. Windows stops it through taskkill, and the
spec waits for the exit before removing the directory it ran in.
* test(plugin-manager): run the drain descendant from its own script file
Windows cannot carry a nested `-e` script through another spawn argument: the
descendant failed to start there, so the fixture asserted on a process that was
already gone. The descendant now runs from a script file the fixture writes.
* test(plugin-manager): cover the bounded drain everywhere and stage the real descendant on POSIX
Windows does not stage a descendant that keeps the inherited pipes open past
the direct child's exit, so the real-process cases run on POSIX only. The
mocked child now models execa's own completion rule — its promise settles only
once both pipes end — which covers the bounded drain on every platform and
still fails without the raw-exit race.
* test(plugin-manager): cover the drain failure path without a child process
An output consumer failure that the bounded drain cuts short is staged with the
mocked child as well, so the per-file coverage gate also holds where the
real-process cases are skipped.
* fix(plugin-manager): terminate the whole tree of a stalled run
A lifecycle script is a child of pnpm, so signalling pnpm alone left an
approved postinstall writing into the profile after the operation reported a
timeout and released the lock. A stalled run now stops its process group (or
Windows process tree) and waits for it to disappear before the caller restores
the files. The cut tail's close is told apart from a reading failure by where it
happened, so a failure from before the cut still surfaces.
* docs(plugin-manager): regenerate the config catalog after the rebase
The English catalog gains `idleTimeoutMs` from the regenerator, and the Chinese
counterpart carries the same interface line so the pair stays consistent.
* fix(plugin-manager): terminate a run's tree through execa, keeping exit cleanup
Spawning a service run with `detached` made execa skip the handler that kills
the child when the manager exits, so a crashed manager left the pnpm tree
running in the profile. `killDescendants` spawns the run as its own process
group while keeping that cleanup, and it addresses the tree on every kill path,
including cancellation. The module now only decides the probe target and waits
for quiescence.
`prepareDevelopmentProject()` resolved the build target from the host
platform and architecture, so a Linux host threw `unsupported target
linux-x64` while writing the runtime descriptor. Pass the target in
instead: the launcher supplies the target it prepared, and isolated tests
supply a fixed supported target.
Extract `desktopTargetPlatform()` into desktop-build-paths.mjs so the
target-to-payload mapping has one home, and use it for the qualification
runner, which previously derived both its target directory and its
descriptor from the host architecture.
Correct the README limitation to name the module that now performs the
architecture check.
Explain configuration, window and input semantics, source ownership and current limitations. Record why persistence belongs to the execution host and refresh repository catalogs.
Exercise the real Loader, AgentLoop, helper transport and attachment pipeline with an explicit desktop protocol fixture. Add a headless V4 Session replay for observations and actions.
Use disposable applications and independent state records to verify clicks, Unicode input, modifiers, scrolling, dragging, capture and stale-target cleanup. Share the native test transport.
Declare package exports, workspace dependencies and build entries. Add an opt-in macOS source composition and enforce the experimental release payload.
Register native operations and model guidance, and bridge objects through the current authorized tool binding. Join Agent and provider cleanup without introducing a persistent evaluator.
Implement serial JSONL transport with request/response limits, deadlines, cancellation and process-range cleanup. Cover protocol and stream failures across host platforms.
Provide discovery, observations, screenshots and input through retained objects. Preserve per-window snapshot identity and prevent action options from replacing the selected target.
Define the evaluator-independent app/window API and validate method-specific requests and native responses. Test invalid targets and parameter admission.
Expose app and window handles, scoped AX snapshots, permissions, input and capture through a JSONL helper. Bind handles to process lifetimes and report stable native errors.
pnpm install --lockfile-only, run to align the new workspace edge's specifiers, also re-resolved unrelated entries: it pinned registry.npmjs.org tarball URLs on the two libreoffice-kit optional platform packages and downgraded glob 7.2.3 to 7.2.0. The tarball URLs fail verify-lockfile-policy against the mirror's published metadata.
Rebuild the file as the base lockfile plus only the three intended importer additions (the new package's block and its two consumers), so the resolution entries stay byte-identical to master.
The Team panel opens on hover with the subagent-catalog grace timings, pins
on click, polls the one-shot view RPC every 20s while open (replacing the
toolbar refresh/close controls), and eagerly loads on mount so the trigger
keeps a stable two-person icon. The lead card navigates from teammate
conversations, the current conversation carries a Current chat tag (selected
ring only among multiple members), task descriptions clamp to two lines
behind an expand toggle, an empty board shows a one-line description, and a
lone member narrows the panel to one column.
The header title row becomes an inline-size container: the preset label
hides first, then the Team trigger collapses to its icon and switches to
click-only opening. The subagent catalog moves to the front of the actions
band so lineage stays beside the title crumbs, and its pinned-menu behavior
matches the Team panel. Tooltip gains a gap prop for tight anchors.
Review follow-up on the tooltip layering fix. The helper probes
`bubble.contains(hit)` instead of identity, so a bubble that grows child
elements still passes, and each probe returns the hit element's tag and
class, so a regression reports what covered the bubble instead of a bare
`expected false to be true` — the pre-fix run now names `DIV.u82K-q_input`
for both probes.
The `portal` JSDoc says the ancestor stacking context confines the
bubble's z-index to that context rather than resolving it against
siblings.
The Summary narrated the change ("the suffixes the old read table recognized keep their historical value") rather than the contract, and at 109 words it exceeded the 100-word package Summary limit.
State the rule a reader at HEAD can act on: a suffix a recorded session already holds keeps its persisted value, every other suffix takes its language's short name. The dropped path-separator and leading-dot detail stays documented under Language selection. Both languages updated and the pairing record re-recorded.
The package joined the workspace as version 0.1.6-alpha.2 while the root release moved on, which failed three CI jobs from one cause: constraints (version must match root), Dependency layout (verify-npm-install-layout finds no workspace version), and Pack npm tarballs (dsh release members must share one version).
The open paragraph accepted Explorer's exit code 1 as a delegated handoff but
did not repeat the reveal paragraph's warning that the acknowledgement proves no
application opened the file, which the Agent Note records for the Remote answer
`{ opened: true }`.
The goal strip and the queue dock ride directly above the input card, and
both create a stacking context (`isolation: isolate`) so their frosted
panel stays behind their own content. A tooltip bubble left inside that
context resolves its z-index there, where the card — painted later in the
same context — wins: the goal strip's bubble showed a two-pixel sliver
and the queue dock's stayed entirely hidden.
Portal those dock tooltips to the body, where the bubble's z-index
resolves above the whole card. The ui-primitives `portal` contract now
names the capping stacking context beside clipped containers, because
neither container clips its bubble.
The queue-actions aria goldens lose the tooltip line: the captured center
column no longer contains the portaled bubble. The scenario still covers
it by role and adds a hit test at the bubble's center and bottom edge, as
does goal-bar.
The new JSDoc and test prose narrated the change instead of the shipped
behavior: "pre-unification", "the old read table", "used to persist ... it
now persists". A reader at HEAD cannot resolve those stages, and the
persisted-value rule they describe is a durability contract that stands on
its own.
Restate each as the present contract: a suffix whose value a recorded
session already holds keeps it byte-identical; every other suffix takes its
language's short name. Rename the test's transcribed expectation away from
the branch vantage and re-wrap the module comment the earlier edit left
over-long.
`package-target.ts` imported `macos-notarization-proxy.ts`, whose top-level
import of `@deepseek-ai/node-addon-system/flock` resolves through the package
`exports` to the unbuilt `lib/flock.js` on a clean checkout. The step that
emits that file runs inside the same packaging script, so `check:package`,
`package:*`, and the `restore:mac-proxy` recovery command all failed with
`ERR_MODULE_NOT_FOUND` before reaching it; running `pnpm run typecheck` first
left the host addon binary missing instead, which failed at lock time.
The proxy lock now loads the entry at first use through `flock-entry.ts`. When
the import is unresolvable or the host platform package lacks a binary listed
in its `prebuilds.json`, the loader runs `pnpm run build:native-system` and
`pnpm --dir native/system run build:ts` before importing again; other errors
and build failures propagate, and a failed load is retried by the next call.
`check:package` no longer touches the entry at all.
Tests cover the loader steps and load the proxy module from a directory where
the addon package cannot be resolved. The Desktop README records the
behavior in both languages.
(cherry picked from commit 2d74cf50310d4efbbccbbe79a105fcc3a468b19d)
On Windows 11 ARM64 build 26200 Explorer splits its own command line at
commas and equals signs and rejects percent-encoded non-ASCII in a file
URI, falling back to the Documents folder in both cases without reporting
it. `explorerTarget()` now escapes `=` next to `,` and decodes Node's
non-ASCII escapes back to the literal characters the shell resolves, so
the two rules stay in one place. The Agent Note and the target-encoding
table record the measured behavior.
The packaged-imports plugin moves from generateBundle to moduleParsed so
external dynamic import() and require() targets, which chunk metadata
omits, fail the bundle too. Each bundle now carries an import policy:
the main process allows Node builtins, sandboxed preloads only the
modules Electron's require polyfill resolves, and the failure names the
importing module and the allowed set. The wine Windows gate bundles
apps/desktop after the Host tsdown pass like build:lib:host, the
ts-build-config note lists that step, and the tsdown docs name the
workspace match exactly.
Fixes#4993
Review follow-up: the caption menu is inert while the Platform view is open,
which the previous geometry hid; state it under Known Limitations, and drop
the duplicated 40px figure the Desktop owns. The style spec's rule lookup
anchors on the rule boundary and asserts with toContain.
The embedded Platform view renders its 48px return bar at the top of the
window. On Windows the Desktop-owned caption menu occupies that strip (fixed
at top 0, 40px high, z-index 1100), so the "Back to DeepSeek Harness" label
and the Application/Edit menu text were painted over each other.
Offset the overlay by --dsh-windows-titlebar-height where the Desktop preload
publishes its caption marker, the same clearance AppFrame and the sidebar
caption controls use. The return bar and the native view bounds, which come
from the viewport element's rectangle, both move below the strip.
Point selectable hover content at HoverCard (Tooltip has no interactive
prop), require a persistently mounted toast host, scope the font-weight
cap to new feature CSS, prefer theme typography tokens, link the styling
owners, date the design-reviewer roster, add the scrollbar containment
rule, and make the skill discoverable from packages/client/AGENTS.md.
Four focus rings added on master after this branch's base still declared their
own colour: PresetGuideDialog's panel, CodeCard's action (state-business-primary),
SegmentedControl's tab, and AccountSection's link button (button-primary-fill).
The stylesheet corpus spec now rejects each of them, so they read
`--dsw-focus-ring-color` with the brand-token fallback like every other ring.
CodeCard keeps its 1px dense width; the other three take
`--dsw-focus-ring-width`.
Conflicts and how they were resolved:
- ui-agent-preset/AgentPresetSection.module.css: master replaced the icon-only
tooltip actions (`.iconButton`, `.iconDanger`, `.revealedPath`) with
`.cardHelp` + `.helpButton`, which deleted the one declaration this branch
edited. Took master's structure and re-applied the branch's change to the two
`.cardMain:focus-visible` rings, which master kept unchanged.
- ui-theme/README.md and README.zh.md: master documented the separately exported
`brand-font.css`; this branch documented `focus.css`. Kept both, with the
dynamic entry's sheet list at seven sheets including `focus.css`.
`README.i18n.yaml` re-recorded with `verify-translation-pairing --write`.
The recorded exit code 0 after about 30 seconds was not reproduced: an SSH
session 0 on Windows 11 ARM64 build 26200 measured exit code 1 after about a
second with no association invoked, the same exit code a delegated open
returns. The README, both Agent Note languages, and the `explorerTarget` JSDoc
now record that measurement, and the `Invoke-Item` contrast keeps its
qualitative part without the timing.
Pointer suppression previously cleared `outline-color` on the focused element
alone, so a ring painted by a descendant, a pseudo-element, or a `box-shadow`
stayed visible, and editable text controls lost the focus feedback they need on
click.
Ring colour now flows through `--dsw-focus-ring-color`, which component outlines
and focus-ring shadows read alongside the brand token. Every ring that inherits
the variable is suppressed together, and `body :focus-visible:not(:read-write)`
limits the suppression to rings while editable controls keep their own feedback.
Keyboard modality returns on Tab, Home, End, PageUp, or PageDown, and on focus
reaching a different control after a non-composing key. IME composition keys,
refocusing the same control, and window blur leave rings silent. The desktop
preload menubar suppresses its own ring inside its shadow root, where the
document-level attribute does not reach.
A verbatim `\\?\` prefix and a `\\.\` device namespace take Node's UNC
resolution, and the comma encoding has to survive them. The drive-root case is
pinned separately because Node restores a trailing separator against the host's
`path.sep`, which differs on the Windows host this opener runs on.
The root tsdown workspace pass builds every member concurrently, so the
Desktop main bundle could resolve its inlined workspace devDependencies
(dsh-home-paths, dsh-app-boot, dsh-deepseek-account) before their lib/
output existed. rolldown then kept them as external imports, which the
asar does not ship, and the packaged application failed at launch with
ERR_MODULE_NOT_FOUND on a clean checkout; stale lib/ output on long-lived
checkouts hid the defect.
apps/desktop leaves the root workspace list and build:lib:host bundles it
after the Host pass. Every Desktop bundle now runs a rolldown plugin that
fails the build when a chunk imports a bare specifier the packaged
application cannot resolve: electron and the manifest dependencies for
the main bundle, electron alone for sandboxed preloads.
Collects the standing judgment rules for product-user-visible GUI work:
token and shared-treatment reuse, light/dark readability, font weight and
size scale, feedback-surface choice (toast vs in-place notice vs empty
state), loading treatments, overlay dismiss/fit/clip checks, macOS/Windows
window adaptation, and spacing review. dsh-code-review links it from its
sources of truth so UI PR reviews load it.
- Agent Notes: cross-link the new decision with the native fatal recovery
and fail-loud release notes both ways, rewrite the older note's
console-retention and dialog-detail facts in place, and state the report's
actual bounds.
- crash-report: cut the rendered error section at 256 KiB and prune only
files matching the generated crash-<time>-<source>.log syntax.
- main: backendReady follows the backend state except during shutdown, so a
failure while tearing down a ready backend still reads as running.
- subprocess-local: the temporary-file-cleaner hint is appended only for
ENOENT; other codes rely on the appended error.
- client-modules: describeError is module-private; BootModuleRow.url's
JSDoc names its fallback role.
The ui-chat README described the visibility rule as "while the built-in browser
is available", a phrase that now reads like an option label. Both languages name
the Sidebar Browser, matching the sentence that follows.
A Host startup failure reached the crash report as a fresh Error built from
a message string three hops away from the original: the Host sent only
error.message over the fatal IPC event, DesktopHostProcess wrapped it in a
new Error, desktopErrorState flattened it to text, and main.ts wrapped that
text again. Stack, cause, code, and path were gone, and the Host's stderr
tail arriving with the later exit was blocked by the first-failure latch.
The Host now sends its complete inspected error alongside the message
(bounded to 64 KiB; carried over IPC rather than stderr because the two
race and the shell reports the first failure it sees). DesktopHostProcess
keeps it on DesktopHostFatalError.diagnostic, the backend error state
carries the original failure object, main.ts reports that object, and the
crash report prints the Host diagnostic verbatim in its own section.
apps/desktop imports @deepseek-ai/dsh-api-gateway/stream-protocol, whose
import chain reaches @deepseek-ai/dsh-typert-protocol and then
@deepseek-ai/cordis. Both intermediate packages declare cordis as a
peerDependency and nothing satisfied it for the application closure, so
electron-builder omitted cordis from app.asar and the packaged main process
died before opening a window with ERR_MODULE_NOT_FOUND. A workspace checkout
resolves the peer through pnpm's own links, so development runs and the unit
lanes stayed green while every packaged build since efbc8e26b6 (2026-09-20)
failed to launch.
The lockfile additionally drops a stale duplicate [email protected] in favour of the
7.2.3 it already carries; any real pnpm install removes it.
Verified with a signed win-x64 installer built from this tree: app.asar now
ships cordis and @standard-schema/spec, and the installed application opens
its main window.
The Settings → General row for Chat link destinations now reads
网页链接默认打开方式 with the description 对话中网页链接的打开位置 and the option
labels 应用内侧边栏 and 默认浏览器. English keeps the title "Open chat links in" and
now reads "Choose where to open web links", "In-App Sidebar", and "Default
Browser".
The `ui-chat.linkOpening` setting, its values, persistence, visibility rule, and
open behavior are unchanged. The row spec, the clickable-link gallery scenario,
and the two package READMEs name the new copy; the bilingual pairing records were
re-confirmed for the README pairs.
Carry the user-scoped agent-experience guidance into the repository unchanged so
project sessions discover it beside the other repository skills. The file,
including its frontmatter, is byte-identical to the personal copy.
Skip the per-owner wake ledger entirely when no cap is configured.
Rewrite the 2026-08-11 Agent Note in place for the opt-in cap and add
a note recording why the default was removed. Keep `subagent`
untranslated in Chinese prose and drop change narration from the test.
Refs #4944
The Contact us action already stopped prefilling the signed-in UID, but a
configured contact form URL could still carry its own prefill_uid or hide_uid.
contactUrl now drops both query parameters unconditionally, with regression
coverage that starts from a form URL containing them.
The contact URL test also pins the shipped default form path, and the injected
contactUs documentation and the AccountProfile summary no longer describe a
support UID that is no longer sent.
* fix(web): resolve XLSX part relationships and namespace aliases
* fix(web): keep Excel browser scenario in the host test program
* fix(excel): preserve XML encodings and relocated workbook parts
* fix(excel): exclude installed launchers from dependency patch
* fix(excel): restore Strict and case-equivalent workbook previews
The account menu entry that opens the questionnaire is now labeled Feedback in
English and 意见反馈 in Chinese. The menu action, contact form URL and its
configuration options keep their existing names.
Expected menu snapshots and the bilingual README record the new copy.
`maxConsecutiveWakes` no longer defaults to 3. Unset, every background
job completion wakes its idle owner; the cap is opt-in.
With the default cap, a session chaining background commands and
one-shot subagents exhausted the budget after three wakes. The fourth
notice was injected into the next-step inbox without waking the owner,
the client showed nothing, and the session stalled until the user
typed something (dsh-external/issues#641).
Closesdsh-external/issues#641
The recorded-session scenario edited with a single-line value, so the ARIA
golden could not catch a return to a newline-stripping editor. Edit with a
two-line value and assert the model-bound user message keeps the line break;
with the previous input element the scenario fails on that assertion. Also
refresh the stale input cast and pin that Shift+Enter is not consumed.
The inline queue editor was a single-line input, and HTML strips newlines
from such a value: opening and saving a multi-line queued message silently
collapsed it into one line before delivery. Edit in a textarea that grows
with its content, capped at six lines; Enter saves, Shift+Enter breaks the
line, and Escape still cancels.
client-modules: a batch that already executed is never requested again,
even when its first importer was a row it did register — every executed
batch URL is remembered and a still-missing row goes straight to its
one-resource URL. The in-process module system test in test-support is
updated to the aggregated arrival message. describeError replaces three
inline non-Error ternaries and is covered.
subprocess-local: the spill path is published only after the exclusive
open succeeds, so a failed open (EEXIST on a planted entry included) can no
longer unlink a path this process did not create; a reporter that throws is
contained and written to stderr; logSpillFailure builds the owner reporter
for both the runtime and the SSH helper so the logging path is exercised
once instead of duplicated. Tests cover the runtime logger, the bare-caller
stderr default, EEXIST, and a throwing reporter.
desktop: a fatal failure during shutdown still writes its report; the
report phase follows the backend state so a failure during a restart is
'startup' again; writeReport is a required recovery operation and the
dialog tests await the write; the shell pid is labelled; the withheld
response-header list states why set-cookie is dropped.
app-boot, READMEs, Agent Note: "never resumes" is restated as control never
returning to the failed operation while the loop runs until release
settles; inspect output is bounded; incident inferences (who removed the
spill directory, which batch failure occurred) are written as inferences.
- Headline skips the archive path 7-Zip echoes before an open failure
(also when the path contains spaces) and prefers the operation line.
- Raw process statuses such as 0xC0000005 are kept and printed in hex
instead of being clamped to INT_MAX.
- The expanded panel excerpts the result and 7-Zip's own lines rather
than the report header.
- The saved report keeps up to 16 MiB of output, records any cut, and
trims a partial UTF-8 sequence before decoding.
- The details view no longer reads the UTF-8 log as ANSI; the test seam
define and the zero-padded GetTime order are documented; the smoke
asserts a year-first report name and normalizes -FrameLibrary.
- README names the real updater cache directory.
master grew a fourth drag owner after this branch: the Platform overlay's return
bar (ui-settings-account) declared `app-region: drag` on its own header. The
branch absorbs it into the contract — the header marks itself `data-window-drag`,
its own app-region drag/no-drag rules are deleted, the ui-theme manifest pins the
row and its 48px height, and the Agent Note's row list names it — so the tree
declares the drag surface in exactly two places again (the shell's mark rule and
the Windows caption row).
Earlier resolutions from the first merge stand: the dockkit strip keeps its mark,
the sidebar-right pulse stays deleted, ui-settings-general keeps upstream's
dsh-settings and this branch's react-dom, and tsconfig.host.json takes upstream's
`files: []`.
Checks: build, build:web, typecheck, test:gui, the ui-theme/base-styles gates, the
window-drag-coverage lane, doc-sync and translation pairing.
Conflict resolutions:
- ui-dockkit TabPanel: keep upstream's reworked tab strip and re-add the row's
`data-window-drag` mark on `css.tabStrip`.
- ui-sidebar-right SidebarRight.tsx/.module.css: keep upstream's retention
rewrite and re-delete its hand-written one-frame app-region pulse; the shell's
window-drag watcher owns recollection.
- ui-settings-general/package.json: keep upstream's `@deepseek-ai/dsh-settings`
dev dependency and this branch's `react-dom`.
- tsconfig.host.json: take upstream's `files: []`; client sources belong to the
client aggregate.
- pnpm-lock.yaml: master's lock plus only the react-dom importer entries.
The fatal recovery dialog showed the last eight lines of the error and
promised that the full diagnostic was in the Electron console, which a
packaged installation never surfaces. Two field failures were diagnosed
from screenshots of those eight lines: a Host killed by an uncaught
exception whose path happened to be in the tail, and a renderer boot
failure whose real import error lived only in the renderer console.
Every reportFatal caller now names its source (host, web-boot, renderer,
main). Before the first dialog opens, the shell writes
crash-<time>-<source>.log under app.getPath('logs') with the source, the
startup or running phase, application and runtime versions, the complete
inspected error with its properties and cause chain, and the primary
window's recent error-level console output (a 64 KiB tail captured from
webContents 'console-message'). The dialog names the file on its own line,
independent of whether the error was shortened and in the listener-conflict
variant too; the wait is bounded to one second and a failed write shows the
dialog without a path. Startup keeps the ten newest reports. Buttons and
recovery actions are unchanged.
forwardWebRequest relayed transfer-encoding, connection, keep-alive, and
the other hop-by-hop headers from the Host's response into the dsh-app://
response, describing a Node fetch connection that Chromium never saw. It
also relayed the Host's `cache-control: public, max-age=31536000,
immutable` on plugin bundles whose revision changes on every launch, so
each start wrote another unreusable copy into the renderer's disk cache.
The forwarder now strips the connection-level header set alongside the
encoding headers it already removed and marks /plugins/ responses
no-store. Whether either header contributed to the observed batch load
failure is unverified; both are corrections in their own right.
One application batch is a single classic script that registers every
package in sequence, requested exactly once during boot. A transient
failure of that one <script> request failed every row in the batch and,
through arriveGraphRow's dependency wait, every row that injects or
requests them; the boot audit then listed each as "import failed (see
console for the import error)" with the real error only in the renderer
console.
Arrival now distinguishes the two failure kinds. A transport failure
(script error event, nothing executed) is retried once on the same URL and
the retry is shared by every row waiting on that batch. A script that
loaded without registering a row is never re-executed, because register()
rejects duplicates and a replay would stop at the first package the
original run did register. Either way each still-missing row then loads its
own one-resource combo URL, which the Host serves for every package. A batch
URL that failed is remembered so later rows skip straight to their
fallback; a one-resource URL is not, so it stays retryable on the next
import as before. Dependency failures are wrapped with the consumer and
dependency names so a cascade reads as a chain.
The module system records the last import() or prefetch() failure per
graph row at the outer operation, so factory execution errors are captured
as well as arrival errors, and clears it on success or invalidation.
assertEntriesActive takes the module system and reports that text per
fiberless entry.
The Desktop Host and every other profile launcher installed a fail-loud
handler for unhandled rejections only. A synchronous throw from a stream
'data' listener fell through to Node's default uncaught-exception path: it
printed and exited without the surface's release hook, and the label and
format differed from every other fatal diagnostic.
installFailLoud now registers the same report for 'uncaughtException' with
the label `fatal uncaught exception`, shares the exit latch and release
timeout with rejections, and never resumes the process. The diagnostic is
util.inspect(err, { depth: 4 }) instead of err.stack so enumerable
properties (a node:fs error's code, syscall, and path) and the cause chain
reach stderr; the `fatal load failure` label for rejections is unchanged
because the Web profile expected-output e2e tests match it.
OutputCollector.spillAll ran openSync/writeSync inside the stream 'data'
listener with no containment. When the private per-process spill directory
had been removed by a temporary-file cleaner, the first overflowing chunk
threw ENOENT from Pipe.onStreamRead, became an uncaught exception in the
Desktop Host, and the Electron shell reported it as a fatal failure.
Spilling is best-effort by contract (spillPath is optional; a failed final
close already withholds it). Every filesystem failure while opening or
appending the spill file now discards the spill, reports once through the
owner's logger, and leaves in-memory collection untouched. Spill inputs move
into one SpillOptions object with the reporter, so tail-only collectors no
longer pass an empty directory; prepareManagedProcessBinding is the explicit
resolve step for the directory and reporter defaults, and the runtime and
SSH helper supply their plugin loggers.
Tests cover ENOENT on open (removed directory), ENOTDIR on open, and ENOSPC
on append after the file exists, asserting the tail stays byte-exact, the
spill file is withdrawn, and the reporter fires once.
The installer showed "Failed to decompress files" and discarded 7-Zip's
error output, so a failure such as security software quarantining an
extracted file could not be told apart from a full disk or a broken
download.
- InstallerExtract returns negated Win32 errors for launch failures so
they no longer collide with 7-Zip exit codes; 7-Zip writes UTF-8 errors.
- window-frame.dll composes a report (result, archive, destination,
Windows version, full 7-Zip output), saves it under the updater cache
directory, and presents a task dialog with the first error line, a
collapsible bounded excerpt, and a Copy error details button. Silent
installs keep only the report; a missing task dialog falls back to a
message box.
- Unsigned Windows builds name their installer *-unsigned.exe so a
shared file cannot pass for a release artifact; version discovery
still counts them.
- The directory smoke asserts the saved report on a broken archive and
smoke-windows.ps1 accepts the prepared DLL.
Refs #4893
Opened and copied authorization links carried no palette, so the Platform
login page rendered its own stored preference. Append the effective Desktop
palette as theme=light or theme=dark, resolving system through the theme
source the preload publishes.
The theme source now reaches nativeTheme on Windows as well: the preload
drops its macOS-only guard, since shouldUseDarkColors is read for every
platform, and gates the DOMContentLoaded listener with once.
master replaced the startup-bound messages constant with a locale that
follows the settings preference; the About dialog now reads the same
locale as the update prompts so the two never mix languages.
The Contact us entry now opens the current Feishu questionnaire, and the URL
no longer carries the signed-in account UID: contactUs stops reading the
account profile, contactUrl stops writing prefill_uid and hide_uid, and the
remaining build and browser context fields keep their existing behavior.
The url, package tests and the bilingual README and account-login note state
the same current facts.
The declarative-preset refactor (#4569) removed the registry's read Remote
together with the YAML editor, and with it the 查看配置 card action that
opened a preset's composition read-only. #4850 brought the Creator entry
back and had to leave 查看配置 out because `agentPresets/read` was gone.
Give the registry a read-only `read` Remote that renders a declaration's
child plugin list from the in-memory definition in the Loader's own
entry-list dialect, so `!!js` conditions read as declared, and put the card
action back at the right end of each card footer: an icon that opens the
YAML in a read-only dialog titled after the preset. Nothing accepts YAML
back, and a broken preset stays readable because its diagnostic points into
that YAML.
Fixes#4895
The settings sheet's Open configuration file action opened nothing on Windows when the user's default lived only in the Explorer per-user record (FileExts\\<ext>\\UserChoiceLatest), which Explorer honors and the association resolver inside the host process does not. Invoke-Item also exits 0 when it finds no application, so the Remote answered { opened: true } and the sheet rendered no error.
Windows opens now hand the path to explorer.exe as one argv element - the resolution a double-click performs - and revealNativePath shares that invocation rule. The shared helper accepts Explorer's delegate exit 1 for an open and a reveal alike; every other failure and every cancellation still rejects. The PowerShell command string and powershellLiteral are gone: the path crosses the process boundary as argv, with no escaping step.
Fixes#4428
The consolidated commit refreshed unrelated transitive resolutions while it
resolved the settings portal's new react-dom dependency. Restore those entries to
their previous versions and keep only the two importer entries the portal needs;
the lockfile already carries both snapshots.
Review pointed out that About read its copy from the renderer-reported
Windows language while the shared update dialog stamps its lang and
close label from the startup locale, so an in-app language switch mixed
languages inside one dialog. About now uses the same startup messages
as Check for Updates. The menu click also logs a dialog that fails to
open instead of leaving an unhandled rejection, and the return type no
longer hides the message-box result behind unknown.
The shared code-language table must not claim a suffix a dedicated viewer
declares, and the Host read card must keep the `lang` values it already
persisted.
- Drop `csv` from the shared table: Code registers before Spreadsheet, so
the earlier body would take `.csv` and the browser snapshot gate fails.
- Add `readLangHintForPath`, the read card's legacy projection over the
shared table: the 43 pre-unification suffixes keep their old short ids
byte for byte, new suffixes use the canonical id.
- Add an ownership regression test over the full preview registry that
turns red when `csv` returns, plus byte-level and alias-normalized tests
for the read projection.
The client UI i18n gate rejects literal message text in main.ts; the
Windows About dialog now reads the product name from the dictionaries
like every other shell string.
Three tools capped the preview text they return with String.prototype.slice
at a UTF-16 code-unit budget. A cut that lands inside a surrogate pair
leaves a lone high surrogate, which is not well-formed text: a strict JSON
reader rejects the durable Session log carrying it, and the preview shows a
broken glyph. A lone surrogate retained in history has already blocked every
later Messages request for one session.
Centralize the cap in output-retention as
truncateWithoutSplittingSurrogatePair, and use it from the persistent bash
and pwsh shells and the string-replace editor. The cut drops the unpaired
half instead of keeping it, so the retained text stays a prefix and is at
most one code unit shorter than before.
The new dependency edges and the shifted Config sources regenerate
docs/module-graph and docs/config-catalog, including the committed
translation pair.
Refs #4817
Electron's About role on Windows falls back to a bare system message
box, which looks nothing like the shell's dimmed update prompts. Design
asked for the version check to match Check for Updates, so the Windows
menu item now opens the shared update dialog with the application name,
localized version line and a single OK button; macOS keeps the native
panel. The About command follows the renderer's chosen language like
the rest of the Windows caption menu.
The document Code preview and the Host read card each owned an
extension-to-language table that had drifted: the preview table had 13
extensions the read card lacked, and the two returned different language
ids for the same suffix. Both now resolve through the new zero-dependency
@deepseek-ai/dsh-util-code-language, which also adds common Windows,
config, markup, and language extensions. Every added grammar stays in the
lazy set; the boot grammars are unchanged.
Fixes#4822
The Windows badge used a 44px radius on its 1024px plate, which reads as
nearly square on the desktop next to other Windows 11 icons. Raise the
radius to 192px (3/16 of the plate, matching Tencent Meeting's corner)
and re-export the transparent 1024×1024 PNG from the SVG; the ICO for
the app, installer and uninstaller and the About panel image all derive
from that PNG.
The manual check dialog explained that the release bundles its matching
dsh runtime before mentioning the restart. Users only need to know the
app restarts to finish updating; the bundling detail belongs in the
README, not the confirmation. Both locales carry the shorter sentence.
The composition suite's idleSilenceMs: 30000 was below the suite's own send
bound (dsh-terminal-bash timeoutMs: 300000), so the comment claiming silence
could settle nothing was wrong: a regressed send still settled as inferred_idle
after ~30 s and only the accumulated case timeout caught it.
The case now records every send's waitReason and requires at least six
stdin_read settlements with no inferred_idle. Reinstating the removed prompt
override fails it in 30 s with 'expected 0 to be greater than or equal to 6'
while the output assertions still pass. The silence tier returns to its product
default.
Also renames the two stale test titles ('private shell prompt', and the
cancellation phase that no longer covers an initialization send), pins the
stdin_read fallback's session retention, and records the interactive-child
consequence of that retention in the package README pair and the Agent Note.
The issue reported .ini files with no preview. On master the ini grammar is
already in CODE_HIGHLIGHT_EXTENSIONS, so the code renderer claims the
suffix and .ini previews. No production change is required; this test
records the admission behavior so a future allowlist change cannot silently
drop the reported suffix.
The static CI gate rejected four new assertions to unknown in the
rewritten Team panel specs. Build the Session list, Session snapshot,
and injected-callback fixtures as typed values, prune the retired
baseline fingerprints, and regenerate the module graph, config, and
persistence catalogs for the removed Remote dependency edges and the
shifted source lines.
The Host's default marker is best effort: on Windows it comes from the
Shell's ASSOCSTR_EXECUTABLE/APPID answer, which does not always name an
enumerated handler. When no application carried that marker the split
button promoted 显示文件位置 to the primary action even though the menu
listed applications that can open the file.
Select the OS-marked default when present, otherwise the first registered
handler in Shell preference order, and mark it as the menu default.
Reveal is promoted only when no application is registered at all.
Windows 11 ships some applications (Notepad, other Store apps) as MSIX
packages whose Shell handler reports an indirect icon reference such as
@{Microsoft.WindowsNotepad_...?ms-resource://.../NotepadAppList.png}
instead of a file path. SHDefExtractIcon cannot read that reference, so
the menu showed the generic fallback glyph.
Resolve an indirect reference with SHLoadIndirectString before extracting,
and fall back to the handler's executable when the resolved source still
yields no image.
The Team panel read a Remote snapshot on open and behind an explicit
refresh button, while the subagent catalog panel updates from Host
projection frames. The Team projection was Host-only and its `apply`
mutated state in place, so the projection registry could never publish
a client view for it.
Publish a durable-only `agentTeam` wire view: roster rows with their
durable phase, non-deleted task views through the same readiness,
owner-name, and write-overlap derivation the task board returns to
agents, and `failure` beside the last valid state when a persisted Team
record is rejected. `apply` now returns a new state per applied Team
event and replaces only the touched collection, so mailbox-only events
reuse the previous view reference and publish nothing.
The Web panel derives every row from the shared Session store: the Lead
from the current Session's subagent address, running/inactive from
Session status, and the model from the member Session's durable
`modelSelection` projection. Opening requests the Lead's projection
baseline when the store has none, a failed read shows a retry control,
and the refresh button and Remote mount are gone. The `agentTeams/view`
Remote method remains for callers that need live availability and the
configured model.
The Team panel web e2e now asserts that Host-side task creation and
claiming reach the open panel without any user action.
tool-pwsh-persistent installs its own `prompt` function after spawn, so
terminal-bash prompt readiness never matched and every send degraded to the
silence tier (idleSilenceMs + handoffGraceMs) under production defaults. This
is the defect the persistent bash tool fixed for the bash stack.
The tool now initializes without a prompt send and detects the no-end-marker
fallback through the seam's stdin_read wait reason instead of matching its own
prompt text. The private prompt constant and its stripping are deleted, so
partial output can carry the backend's own trailing prompt.
Tool calls drop from 8493/3722/3832/3759 ms to 1340/255/251/241 ms (spawn and
initialization plus the first command, then three warm commands; Windows,
production defaults, real Loader composition). The loader composition suite
pushes idleSilenceMs beyond the send bound, so every case fails if prompt
readiness regresses.
Re-render the four installer brand banners from the vector sources:
the badge follows the Figma spec (three-stop gradients, layered drop
shadows) and the whale no longer inherits shape-rendering=crispEdges,
which disabled anti-aliasing and left jagged edges. The wordmark now
reaches its design color instead of a soft #353638.
Draw the progress-page brand with high-quality bicubic interpolation
and half-pixel offset so the always-2x bitmap stays crisp when scaled
down at 96 DPI.
- main-startup: assert primaryRuntime for the unpackaged Host and cover a
missing DSH_DESKTOP_PRIMARY_RUNTIME_DIR as a fatal startup dialog with no
Host started.
- test-workspace-updates: pass the prepared primary-runtime directory instead
of an application/runtime path the runner never creates.
- development-project: derive the descriptor platform/arch from
resolveDesktopBuildTarget so it matches the prepared payload.
- main: resolve the unpackaged primary runtime once at startup instead of
inside the Host factory.
- dev: keep the environment override symmetric with its neighbours.
- README: document the variable as required for unpackaged launches and record
the remaining win32-arm64 payload architecture limitation.
Numeric download progress renders without an ellipsis, and a failure keeps the update chip's palette instead of a separate error color. Pin the progress label and the shared badge background, and let the retry label carry the failure distinction.
The macOS window's draggable area was a hand-written band: a 52px strip, deepened
to 76px by a marker the Conversation header had to keep in step with its own
height. It drifted from the layout — blank runs inside a chrome row did not drag,
and a control a few pixels away dragged the window instead.
Chrome rows now mark their own element (`data-window-drag`) and the shell declares
the surface once (`html[data-platform='darwin'] [data-window-drag]`), so a row's own
box is its draggable geometry and no fixed band has to match any row's height. The
frame band, its 52/76 figures, the marker, and the per-sheet drag rules are gone;
interactive controls and overlays portal beside `#root` where one rule subtracts
them.
Recollection becomes one shell watcher (`window-drag/recall.ts`): Electron rebuilds
the window's drag rects only when a computed app-region value changes
(electron#32341), so the watcher measures every marked row each frame while the
surface moves, sets `data-window-drag-recall` for the frames whose geometry changed,
and clears it on the frame that finds the geometry settled. The right panel's
hand-written pulse is deleted with it.
Coverage is a contract with executable parts. `window-drag/regions.ts` publishes the
composition rule and the interactive selector that both the sheet and its spec
compare against; the ui-theme gate asserts the package tree declares drag in exactly
two places and pairs every row's mark with its sheet, selector, and authored height;
and the browser lane boots the shipped composition in Chromium with the darwin
marker to assert per-row geometry, a content body under a marked head row, a
covering overlay, and the watcher's pulse. The plugin manager's detail views mark
the head row they share rather than the pane they render in, so their text and form
labels stay content.
Both real machines agree: the macOS matrix ran on a real `hiddenInset` window with
HID-level pointer input and window-frame readback, and the Windows matrix on a
Windows 11 ARM64 VM — chrome rows drag, content and the covering overlay do not,
and the controls keep their clicks.
Refs #4619
Every ring that used 2px now reads --dsw-focus-ring-width. The dense tables and
toolbars keep their 1px ring on purpose: at 2px they visibly outweigh the rows
they sit between, so the standard value and the dense exception stay distinct
rather than converging on a number nobody measured.
The action cluster animates its width with overflow hidden, and the search pill
clips to its rounded shape, so an outer ring on the add, search and clear
controls was cut to a sliver. This package declared no focus style at all — every
control fell back to the browser default inside those boxes.
Ring the three controls inside the element, the direction issue #4655 allows.
A tooltip asks what the last input was: after any key the user is working from
the keyboard, so a programmatic focus may raise a bubble. A focus ring asks
something narrower — Chromium reveals an already-focused control as soon as any
key arrives, including a modifier or Escape that never navigated anywhere, which
is the ring users report as unexplained.
Record both answers and publish the ring's narrower one, so tooltips keep their
existing behaviour and rings stay silent for a pointer's focus.
The breadcrumb trail and the Miller column scroll, and the column's left edge
carries no padding, so an outer ring on their buttons was clipped to a sliver.
The TurnNavigator marker sits at the scroller's right edge, which clipped three
of its four sides. Ring those inside the element instead — the direction issue
#4655 allows — and change no layout value.
Clicking a control leaves it focused with :focus-visible false, so nothing is
drawn. Chromium then reveals the ring the moment any key arrives — a modifier,
Escape, a typed character — which is the unexplained focus ring users report:
they never navigated there, yet a ring appears under the pointer's last target.
Record the input modality on the document element, publish the keyboard modality
only for focus navigation (Tab, arrow keys), and let the global focus sheet keep
a pointer's focus transparent. Tooltip's pointer modality moves into the same
module so tooltips and rings follow one rule.
Focus rings named eight different colours across 54 client stylesheets, and a
control that declared no ring fell back to the browser default, whose colour
follows the operating-system accent — so the same build showed orange on one
machine and blue on another.
Every keyboard focus ring now uses --dsw-alias-brand-primary. A global ui-theme
focus.css sheet reaches the controls that declare no ring of their own, and 49
declarations name the same token. Only the colour changed: each declaration
keeps its width, style and offset.
The unpackaged shell derived the primary-runtime directory from process.arch, while preparePrimaryRuntime() resolves the target through resolveDesktopBuildTarget(), which fixes Windows to x64. On a Windows ARM64 host the shell looked under targets/win-arm64 while the payload was under targets/win-x64, and the missing office-skills/scripts/check_office.py became a fatal startup dialog.
The launcher now resolves that directory through the same resolver and passes it to the shell as DSH_DESKTOP_PRIMARY_RUNTIME_DIR.
Windows directory junctions are reparse points that lstat reports as symlinks, so list refused My Music, My Pictures and My Videos with workspace-file/not-directory while its own listing reported them as directories. list now follows a final link, requires the resolved target to be a directory inside the workspace root, and keeps read's no-follow gate.
'contains steady-state registration failures' flaked on the CI coverage
lane (mock called 0 times): the spec slept a fixed 20ms and asserted an
async continuation had run, which contention can outlast. Convert the
three positive-assertion sleeps to the file's existing vi.waitFor
pattern; the absence-assertion sleeps stay, because polling cannot prove
nothing happened.
2026-08-28 16:15:51 +08:00
10217 changed files with 525650 additions and 182694 deletions
@@ -16,7 +16,7 @@ Every Agent Note has two axes, both encoded in its **path** — `{lifecycle}/{cl
The date in the filename is when the topic was **first proposed** (per git history). Cross-references between Agent Notes use relative markdown links (`[topic](../../implemented/architecture/2026-…-….md)`) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders.
The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`: it duplicates path metadata and makes unrelated note changes contend on one generated file. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
## Classification
@@ -39,7 +39,7 @@ Delete an implemented Agent Note that only describes small UI adjustments or pur
The archive is path-encoded as `archived/{class}/yyyy-mm-dd-topic-title.md`; `implemented` is deliberately absent because only implemented notes can enter it. An archival change moves the complete English/Chinese/sidecar triplet, retains `Status: implemented`, inserts the same `Archived: YYYY-MM-DD` line immediately below that status in both language files, re-records the sidecar, and repairs or deletes inbound links. These are the only permitted content changes during archival. Existing title punctuation, blank-line layout, and language-switcher wording do not block archival; preserve them with the body.
Once sealed, every archived triplet is permanently frozen. Do not edit, translate, reformat, update, move, or delete it, and do not treat it as authority for current behavior. Documentation gates skip archived sources, including their outbound links; active prose may still link into an archived note when it intentionally cites history. [`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) enforces the closed class tree, complete triplets, archive metadata, sidecar hashes, and the append-only frozen-content manifest. The [archive-policy Agent Note](implemented/process/2026-07-26-frozen-agent-note-archive.md) owns the rationale.
Once sealed, every archived triplet is permanently frozen. Do not edit, translate, reformat, update, move, or delete it, and do not treat it as authority for current behavior. Documentation gates skip archived sources, including their outbound links; active prose may still link into an archived note when it intentionally cites history. [`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) enforces the closed class tree, complete triplets, archive metadata, sidecar hashes, and the append-only frozen-content manifest, except for the exact seal transitions documented in [archive instructions](archived/AGENTS.md). The [archive-policy Agent Note](implemented/process/2026-07-26-frozen-agent-note-archive.md) owns the rationale.
## When to write one
@@ -53,7 +53,7 @@ A feature-addition note may be consolidated into the later removal note only whe
## The file format
Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [the uniform-format Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above.
Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [archived the uniform-format Agent Note](archived/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above.
@@ -5,3 +5,5 @@ Archived Agent Note triplets under the kind directories are frozen historical sn
The archival change may only relocate a complete English/Chinese/sidecar triplet, insert the identical `Archived: YYYY-MM-DD` line below both `Status: implemented` lines, re-record the sidecar, and repair or delete inbound links. Do not inspect, verify, or repair links out of archived notes.
Run the [`dsh-archive-agent-notes`](../../skills/dsh-archive-agent-notes/SKILL.md) workflow and append new artifact hashes with `pnpm run verify-archived-agent-notes --write`. The normal verifier rejects changed or missing sealed artifacts, incomplete triplets, unknown kind folders, and invalid archive metadata.
The authorized Figma-link removal from `feature/2026-08-10-durable-workflow-runs-in-chat` has three exact old-to-new seal exceptions in `scripts/archived-agent-notes.ts` for its English, Chinese, and pairing files. These exceptions permit no other content change, reversal, or deletion.
# Agent Note: The job registry absorbs the observation record; the standalone activity seam is removed
Status: implemented
Archived: 2026-09-30
Superseded: the record declaration (`JobStart.record`, `RecordingJob`, `readRecord`, `pumpJobOutput`) and the split wire (`SessionJob.record` on the session control stream, `ctx.jobOutput`) described below were consolidated into one output ring and direct operations on `ctx.jobs` — see [the jobs seam consolidation](2026-09-03-jobs-seam-consolidation.md). The argument for absorbing the standalone activity seam into `ctx.jobs` still holds.
# Agent Note: Right Sidebar tab types and navigation
Status: implemented
Archived: 2026-09-30
English | [中文](2026-09-05-sidebar-tab-types-and-navigation.zh.md)
@@ -72,7 +73,7 @@ interface SidebarRightTabParamsMap {} // key: kind — a page type declar
`openResource` accepts the union of every declared resource shape and `openTab<K>` the shape declared for `K`; a body narrows `navigation.params` by the protocol or kind it knows it serves. Parameters belong to the resource type rather than to the viewer because a line number is a fact about a file location, not about the text preview, and any type that claims `file` addresses receives the same shape. Values must be JSON-serializable, and a record must be rebuildable from address and parameters alone, because undo, redo, reload, and HMR rebuild tabs after the opener is gone.
Beside the two opens, the face carries `close(tabId)`, `active()`, `isExpanded()`, `toggleExpanded()`, and four operational methods — `focus(tabId)`, `split(paneId?)` (returning the new pane, or `undefined` when the pane budget or the width rule forbids the split, recording nothing), `float(tabId, rect?)`, and `dock(paneId)` — each recording one history entry and a no-op on a missing target or one already in the requested state. There is no layout snapshot, no subscription, and no lookup by address: the face grants control over the layout, not a view of it. The seat publishes its binding — its session, its store's actions, and its surface — while mounted; a command on the public face acts on the mounted session and throws with no mounted session surface. A tab's own actions reach their session's own store instead: the slot runtime mints one store per session, the plugin adopts each as it is minted, and the controller routes by session id, so an action fired after the user switched sessions still lands, and does nothing for a session whose store was never minted.
Beside the two opens, the face carries `close(tabId)`, `active()`, `isExpanded()`, `toggleExpanded()`, and four operational methods — `focus(tabId)`, `split(paneId?)` (returning the new pane, or `undefined` when the pane budget or the width rule forbids the split, recording nothing), `float(tabId, rect?)`, and `dock(paneId)` — each recording one history entry and a no-op on a missing target or one already in the requested state. There is no layout snapshot, no subscription, and no lookup by address: the face grants control over the layout, not a view of it. The plugin names the Session on screen — the selected Session while the Conversation fills the main column — from the Session selection and the main panel, before React renders either change, and publishes it as `mounted`; a command on the public face acts on that Session through its adopted store and throws when no Session is on screen or its store has not been minted. The seat reports only what it renders with: the room its docking kit measured and whether its frame width presents the panel fullscreen automatically. A tab's own actions reach their session's own store instead: the slot runtime mints one store per session, the plugin adopts each as it is minted, and the controller routes by session id, so an action fired after the user switched sessions still lands, and does nothing for a session whose store was never minted.
### Addresses
@@ -100,6 +101,8 @@ The conversation's `openFile(path, { line? })` — tool-row path links, produced
**Slot priorities to express an extension taking over a builtin, then registry-minted slot keys.** The first attempt had the overriding type register its body at a lower slot priority through an exported constant — a value import across dynamic plugins, and a second rule system (slot priority) standing in for the registry's. The second attempt had the registry mint a key per registration and return it from `register()`, which made registration a two-step dance whose ordering mattered. Letting the implementation declare its own `id` — required, unique, the same string it registers its seats under — needs no constant, no minting, and no ordering, and gives the registry the identity it needs to reject duplicates.
**A binding the seat publishes from its own effects.** The first form had the mounted seat publish its session, actions, and surface after each commit and release them when it left. React runs a commit's passive effects in tree order, and the frame renders the Conversation column before the right column, so in a commit that switched Sessions or returned from a global panel a Conversation component opening content from its effect ran while no seat was bound — after the departing seat's release and before the arriving seat's bind — and the open threw. The Session selection and the main panel change before React renders, so deriving the on-screen Session from them removes that ordering dependency. The seat still measures pane room, because only its rendered kit can.
## Consequences
- A type is one static object plus one or two keyed seat registrations; its occurrence information is read through injected `useTabInfo()`. The framework grows no per-type API surface, and a type shipped from outside this repository imports only types from the Sidebar package.
@@ -116,5 +119,5 @@ The conversation's `openFile(path, { line? })` — tool-row path links, produced
## Deferred
- A navigation protocol beyond `sidebar://<kind>`: sub-routes within a page, naming an implementation, and the ecosystem-facing rules for other navigation schemes.
- Opening into a session other than the one on screen from the public face, which acts on the mounted session only; a tab's own actions already act on their tab's session.
- Opening into a session other than the one on screen from the public face, which acts on the on-screen Session only; a tab's own actions already act on their tab's session.
- A localized message when an open fails from the conversation; the failure is currently the thrown error's text.
Loaded 100 of 10217 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.