apps/cli/cordis.yml holds the whole composition flat — the host runtime
rows, the api-gateway row, the webserver row, and the ten dshClient rows.
AppCLIEntry is the pre-cordis glue: layered env (ambient > cwd .env >
$DSH_HOME/.env, fixing DSH_HOME=... dsh web not finding its key), patch
composition from the three non-yml sources (profile json through the static
PROFILE_MAPPINGS table, CLI flags, the resolved frontend distIndex), the
Loader include boot (--dev appends the hmr row before the settle), and the
fail-loud triple (assertEntriesLoaded + installFailLoud + an all-ACTIVE
sweep for PENDING fibers). web.ts shrinks to argv parsing + the URL line.
The four free functions in boot.tsx become one kernel class holding what
must exist before cordis: the parsed BootManifest, the ClientModuleSystem
instance, and the loading-page handles. Context/Loader setup runs in
parallel with the immediately-tier prefetch, but entry creation awaits the
prefetch: materialization is tree.import's synchronous require, so
cross-package require edges (i18n -> runtime/client) need every
immediately-tier factory registered first — unbarriered creation raced
10-25% of boots. The kernel adopts the modules entry (writes the
__DSH_MODULES__ slot pre-cordis, creates the entry first, skips its graph
row), and provide('modules') now lives in the adoption apply. apps/web
drops its host-package edges (composition is apps/cli's job).
connection binds the web transport: it injects httpServer + apiProxy and
registers toFetchHandler(ctx.apiProxy) under the /api prefix (the node:http
to fetch bridge moves in from the webserver, keeping the res-close disconnect
detection and drain/close backpressure waits). hmr owns dev reload: a
stat-poll watch per graph row driven by clientModuleHost.onGraphChanged,
rebuilt(id) on content change, and the /plugins/events SSE route (GET/HEAD
guarded); frame types are single-sourced in events.ts shared by both halves.
The node half is ClientModuleHostService (ctx.clientModuleHost): it composes
the __DSH_BOOT__ graph by scanning loader entries for dshClient packages,
serves /plugins/<id>/client.js, taps the index render, and exposes
rebuilt/onRebuilt/onGraphChanged. Scanning is incremental per package — no
full-rescan path exists: internal/plugin marks the fiber's entry name dirty,
a flush reconciles each name against live entries, package metadata
(including negative verdicts) caches forever, and re-hashing is reachable
only through rebuilt(id). The browser half moves wholesale to the standard
./client export (ClientModuleSystem, parseBootManifest with the dual-view
BootManifest, and the adoption plugin face that reads the
window.__DSH_MODULES__ slot and provides ctx.modules).
HttpServerService provides ctx.httpServer: register(route) -> disposer
(duplicate patterns throw), tapIndex transforms in registration order, and
the bound port; matching is exact > longest prefix > static dist fallback
(403/405/SPA semantics preserved). The server listens on activation, answers
per-request failures with 400 + a log line instead of exiting the process,
and knows no harness concepts — the boot graph, bundle routes, SSE channel,
and /api prefix all moved to their owning plugins.
createApiProxy moves from dsh-host-runtime into dsh-host-apiproxy (the
dependency direction already pointed this way); the package now
default-exports ApiProxyService (config {provider, model}, provides
ctx.apiProxy) while staying transport-agnostic — it registers no routes.
Runtime keeps bootHost/startHost for the headless path with its import
re-anchored, and drops the mountWebPlugins roster mounting helper.
Master's i18n batches added Chinese counterparts to ~50 docs this PR
edits in English. Bring each zh side along with the minimal edits
covering the en diff (recorded-hash diffs, not re-translations),
reunite the stream-workflow-progress pair under rejected/ with its
manifest entry, re-record all pairing hashes, and regenerate the
event/persistence/tool catalogs and doc graphs over the merged tree.
The dev bundle watch missed rebuilds that landed while the registry was
constructing: fs.watchFile captures its comparison baseline with an
ASYNCHRONOUS first stat, so a write racing that window is absorbed into
the baseline and never reported. Standalone repro missed 24/400 same-tick
rewrites; the CI flake in web-plugins.spec.ts ('watch mode: a bundle
content change re-hashes the row...') was exactly this — the spec writes
immediately after createHostWebPluginRegistry returns.
The watch now polls from one registry-owned setInterval against a stat
baseline the scan itself captures synchronously, stat-before-read: a
write landing between stat and read leaves the hash newer than the
baseline (next tick re-hashes to the same rev, no spurious notify); a
write landing after the read leaves the baseline older (next tick
detects and notifies). No blind window. The poll iterates the live
table, so rescans retarget the watch for free and dispose clears one
timer. Stress: real-registry same-tick rewrite 0/600 missed (was 1/300);
spec watch tests 0/50.
New regression test pins the same-tick-as-construction write. Its
rewrite deliberately differs in size from the seed: a same-millisecond
same-size rewrite is invisible to any mtime+size poll (coarse fs
timestamps) — a stat-polling limit, not this regression. Loading-model
Agent Note updated in both languages (pair re-recorded).
Keep inherited child prompt markers bounded by the normal silence fallback. Stage web-plugin rescans atomically and retain missing watch state until a successful rebuild.
- gen-doc-graphs still summarized the ACP demo as 'text-only'; say
baseline-prompt and regenerate composition.md.
- The llm-catalog note claimed unit coverage for per-agent target
isolation that no test pins directly; state the actual basis
(agent-scoped listener installation) in both languages and re-record.
The automation-only rewrite edited many implemented Agent Notes; several
edits replaced still-live or historical rationale instead of reframing:
- llm-model-catalog: restore the prompt/request consistency section and
selection-ownership alternatives — installAgentLlmTarget and the TUI
/model selector still ship that design; only the ACP wire is gone.
- plan-specific-collaboration-state, acp-multi-session, todo-write,
ask-user-question: link the superseding automation-only note instead
of silently rewriting the original decision or motivation; drop a
paragraph duplicating the Web-provider facts stated two paragraphs up.
- sandbox: stop claiming unit coverage for turn-enclosed config writes
(that mechanism left with the bridge) and retitle the commit-boundary
paragraph accordingly.
- Fix the missing blank line before '## Consequences' in the
plugin-command-registration pair, the JSON-RPC/Web render-intent
consumer misattribution (the second consumer is the host/client
runtime), stale bash_output/bash_kill names, and 'optional goals' in
architecture.md.
- examples/acp-agent/README.md: point at the package contract instead
of restating it; packages/ui/permission and plan-mode READMEs record
the consumer-less preset service and the exit_plan_mode coverage gap
under Known Limitations.
- 2026-06-19-acp-snapshot-tests: the new note defers the corpus
migration rather than committing to it; say so.
Re-record the touched bilingual pairs.
ACP v1 requires every agent to accept text AND resource_link prompt
content; the automation rewrite dropped the resource_link half of that
baseline. Restore the old bracketed-reference flattening in the codec,
reject only beyond-baseline blocks, and update the package contract and
Agent Note.
Also release the per-session prompt slot when agent.send() throws
synchronously (an agent disposed outside the bridge would otherwise
wedge the session into permanent 'already in flight' rejections),
drop the tautological version-negotiation branch, prove the scenario
env layer reaches the snapshot subprocess, pin bridge-side fail-closed
permission errors, and correct two overpromising test names.
Address ds-review-bot: the bounded-request-recovery Agent Note stated the
shipped default carried four transient codes, and the llm-streaming contract
omitted the new cross-adapter empty-response classification. Update both
current-state contract docs to the five-code default and cross-link the
empty-response bug-fix note.
The fake terminal retains earlier autocomplete frames, so the directory follow-up assertion could pass before the scoped lookup completed. Wait for the quoted file mention to be applied before submitting.
A well-formed provider stream that ends with finish_reason stop and zero
content blocks previously became a successful empty assistant message: the
turn completed silently, and drivers like goal-session counted the no-op
round. Both adapters now map that degenerate completion to a finish
{kind:'error'} with the new canonical EMPTY_RESPONSE code from dsh-llm, and
dsh-llm-retry adds the code to its default retryable set, so the existing
closed-step recovery path retries it and fails loud once the budget is
exhausted.
Covered by adapter unit tests, an llm-retry default-policy test, and a new
authored keyless ACP snapshot (empty-response-retry) with a deterministic
1 ms zero-jitter retry overlay.