Conflict resolution: session.ts keeps this branch's paired-start callTime
(the start event exists here; a settle-only window stays null — the PR2
fix's semantics compose). The README's codeDispatches section is restored
to the running→settled lifecycle this branch implements, keeping PR2's
null-callTime nuance for starts outside the window.
Responding to ds-review-bot round 2 on #648: the env seam's accepted
values, native default, process-wide scope, loud-failure behavior, and
temporary status now live in apps/cli/README.md next to the Web/headless
surface it configures, not only in the cordis.yml comment.
Responding to ds-review-bot on #664:
- SubSpanLane gains a 'timing' discriminant (measured | running | unknown).
A settle-only replay entry (callTime null, start outside the window) was
previously indistinguishable from a measured 0 ms span; it now renders
hollow with a 'duration unknown' hover title, and durationMs stays null
for anything unmeasured. Pairs with the client-runtime fix that stopped
fabricating callTime = settle time (826c3696a on the live-parallel PR).
- The built-client Code Mode fixture snapshot now switches to the
Trajectory and Waterfall tabs and pins the assembled rendering: three
Sub cells with real +0.8s durations and three measured lanes with their
hover titles — product-visible coverage through the real bundle graph,
not just package-level jsdom fixtures.
Agent Note (both languages) updated for the timing contract; pairing
re-recorded.
ui-sidebar shell landed on master (#643: geometry-only shell, the
browsing region moved to the sidebar.workspaces slot); this branch's
sidebar.settings foot seat re-applies on top — the hardcoded foot row
becomes the seat, the seat rides the railIn crossfade, and the shell
spec's renderSlot stub key-splits region vs settings.
The shell is now a pure composition face: no dictionaries, no locale
dependency, and three new chrome content seats (settings.trigger /
settings.header / settings.close) whose slot content also carries the
accessible names (trigger text, dialog aria-labelledby, visually hidden
close label). ui-settings-general returns as the owner of copy that
belongs to no single feature: chrome content, the General section with
its item slot, and the settings dictionaries. Slot types split homes —
trigger/header/close/section live in the shell contract; the
settings.general.item entry moves to the locale package (the common
dependency of every item registrant), with ui-theme consuming it
through a re-export seam; the verbatim duplicate merges are gone and
the dependency graph is a clean DAG.
ds-review-bot findings: the browser e2e's error-state check now requires
at least one error sub-row (was >= 0); the runtime README documents the
codeDispatches snapshot contract and the ui-conversation README the code
variant + nested sub-row semantics.
One highlighter for the client: a synchronous fine-grained shiki core
(JS regex engine, no WASM) in ui-primitives with an explicit grammar
allowlist (typescript, shellscript, json — aliases resolve, unknown
languages take a geometry-identical plain arm). The shared CodeBlock
component owns both arms; markdown fences, the run_code expanded
program body (typescript), and the details panel Input (json) all
route through it. Token colors live in a new ui-theme shiki.css sheet
as --shiki-* custom properties (light/dark blocks), wired through the
shell's base.css chain — tokens-only styling holds; shiki's generated
span tree is the sanctioned innerHTML path (static output, no user
HTML). jsdom specs pin token spans, aliases, both fallbacks, and the
fence route; the built-bundle snapshot asserts the highlighted program
under the code row.
The bridge replaces its serialization queue with a pool that reuses the
native concurrency contract: submissions classify through
registry.executionMode (fail-closed isConcurrencySafe), start strictly in
submission order, overlap up to the validated maxParallelSubCalls config
(default 10; 1 restores serial), and exclusive calls drain the pool, run
alone, and bar later calls. Each started sub-call logs a
tool/code-dispatch-start event at pool entry; the existing
tool/code-dispatch settles the pair (started ⇔ settles exactly once;
abandoned queued calls log neither). SDK prompt guidance now states the
true Promise.all contract — re-recorded across every code/both-mode
snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the
required description arg).
Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts
land the running shape (rows wear the native running ring), settles
replace in place preserving start order, callTime pairs to the start
time. Fixture emits start/settle pairs; jsdom pins the running sub-row;
runtime specs pin in-place settlement and out-of-order completion.
The client indexes tool/code-dispatch events into
ConversationSnapshot.codeDispatches (parent callId -> ToolResultNode-shaped
sub-calls; live mux and history replay build the identical index). ChatView
renders each run_code parent as the new code variant (description summary,
program as the expanded monospace body) with its sub-dispatches as
always-visible indented rows — every sub-row dispatches through the SAME
keyed conversation.chat.toolview hole with the same GenericToolCard
fallback, so custom registrations (bash sample) take over sub-rows exactly
as top-level rows. The details panel resolves sub-callIds to full logged
args and complete output through the native path.
Evidence: fixture turn 64 + built-bundle jsdom snapshot, real-machinery
jsdom suites (nesting, error state, details, running parent, reference
stability), and a recorded code-mode browser e2e round (keyless replay +
aria golden). Scaffold gains a toolsMode patch knob.
Settings collaboration direction (recorded in the note): the shell only
provides composition faces — feature plugins register themselves. The
General section moves into the ui-settings shell (order 0, skeleton
rows) and declares the settings.general.item list slot; locale registers
the Language row and ui-theme the Appearance row (each with its own
store mirror, dictionaries, and ledger-judged deferral); the
ui-settings-general package is gone. ui-settings-models becomes
ui-models — a feature package that contributes its Settings section
rather than a settings-owned satellite. The item-slot SlotMap entry is
authored in the ui-settings contract and repeated verbatim in
locale/ui-theme (reference-cycle avoidance; declaration merging keeps
the copies identical).
run_code gains a required bash-style description parameter: presentCall
titles the card with it and moves the program to rawInput, so every
surface gets a readable label. tool/code-dispatch now logs each
sub-call's complete content/isError (the tool/result vocabulary),
replacing the bounded resultSummary and deleting the summarize/cwd
machinery — a UI renders sub-calls through the identical path as native
results. The dsh config tree mounts the worker code runtime and reads
DSH_TOOLS_MODE (temporary seam until per-session mode selection lands).
Session format stays v0 (pre-release churn). Code-mode ACP/TUI fixtures
re-recorded; TUI presenter pin refreshed; catalogs regenerated. Keyless
web smoke pins the code-mode wire contract (tools=[run_code] + SDK
prompt section).
The hero composer renders the frontend Session Intent's retained prompt
from the sessions list snapshot, but updateIntent only reached that
snapshot through the intent watch's microtask-deferred markDirty. React
therefore rolled the controlled textarea back during the change tick,
which corrupted IME composition (Pinyin "nihao" committed fragments
like "nnini hni hani hao你好") and jumped the caret on plain typing.
SessionManager.updateIntent now calls notifyNow after
updatePendingPrompt, per the Notifier channel rule for direct echoes of
user gestures. The workspace-flow snapshot helper asserts the same-tick
echo instead of waiting for it, and a runtime unit test pins the
contract at the manager seam.
Add the browser Settings surface as slot-composed plugins over new
preference services:
- Rename dsh-client-i18n to dsh-client-locale (locale is the domain
name); LocaleService adds getLocale()/setLocale(id), immutable
snapshots, a locale/change event, and dsh.locale persistence.
- ThemeService owns the light/dark/system preference (default system),
resolves system via prefers-color-scheme, publishes theme/change
snapshots, persists dsh.theme, and no longer touches the DOM;
ui-layout's ThemePresenter applies resolved snapshots
(body[data-ds-dark-theme] + alias tokens) and cleans up on dispose.
- ui-sidebar drops the phase-1 settings dropdown/modal; the foot renders
the new sidebar.settings slot with the column state.
- New ui-settings shell occupies sidebar.settings: foot trigger row and
the centered 1080x700 panel (figma 501:29947) with 24% mask, close
button / mask click / Escape all closing, and a 188px nav projected
from the settings.section list slot it declares. Nav labels are
registrant-localized; sections re-register on locale change, so the
ledger version is the shell's only subscription.
- ui-settings-general registers the General section: Permission and
Tool Call skeletons, live Language (locale menu) and Appearance
(Light/Dark/System cubes following the persisted preference); its
slot store mirrors both service snapshots via apply-side listeners.
- ui-settings-models registers the Models nav entry with an empty
content column.
- Portaled menus pin z-index above modal overlays (a menu anchored
inside the settings dialog rendered underneath it and was
unclickable).
- theme/data/list-pen icons in ui-primitives; settings copy ships as
zh/en dictionaries; fixture manifests gain the settings rows.
Integrate the Commander adapter with master's `dsh web --workspace-root`
(workspace-aware session flow).
- args.ts: add `--workspace-root <path>` to the web subcommand; WebInvocation
carries workspaceRoot.
- web.ts: keep the adapter-parsed signature, take (host, port, dev,
workspaceRoot) and pass workspaceRoot through to AppCLIEntry (drop master's
re-added parseArgs and CLI host/port validation — the schema owns those).
- bin.ts forwards invocation.workspaceRoot; args.spec + the Agent Note pair note
the flag.
Integrate the Commander adapter with master's headless rework (dsh -p now boots
through AppCLIEntry — the same cordis.yml composition as dsh web, web-observable
while it runs — via toFetchHandler(ctx.apiProxy)).
- headless.ts: keep master's AppCLIEntry-based body but take the adapter's
pre-parsed `task` (drop the re-added parseArgs). Old startHost path and the
now-deleted packages/host/runtime dependency are gone.
- remove the stale packages/host/runtime dir left on disk by master's deletion
(mirrors the earlier packages/ui/acp cleanup).
ds-review-bot: `dsh web -p task`, `dsh web --resume s`, and `dsh --config c.yml
web` reached the web action with those values in program.opts() but the action
ignored them and served — silently dropping mode-specific inputs. The web
action now reads the parent opts and fails loud (exit 1) on a leaked
--config/-p/--resume, matching the root mode's mixing guard. Covered in
args.spec.ts.
Also (ds-review-bot): tui-demo/README documented the removed `dsh
[path-to-cordis.yml]` positional form; corrected to bare `dsh` / `dsh --config
<path>`. Agent Note + Chinese pair note the web-leak guard.
The adapter no longer validates --host/--port or declares the allowed set:
LOOPBACK_HOST/ALL_INTERFACES_HOST leave args.ts. --host/--port are now
unvalidated pass-through overrides — the adapter only Number-coerces the port
string (the dsh-host-webserver schema wants a number). That schema
(host a 127.0.0.1/0.0.0.0 literal union, port a natural <= 65535) is the single
source of both the default (the shipped cordis.yml webserver row) and validity;
AppCLIEntry patches an explicit flag into that row, so a bad host/port fails
loud at the schema on boot (verified: `dsh web --host 9.9.9.9` and `--port abc`
both exit 1 with the schema's ValidationError).
web.ts keeps two display-only literals (the printed loopback URL, the
all-interfaces LAN-detection check), commented as mirrors of the schema, not a
source of truth. Agent Note + Chinese pair and README updated; the args spec
drops the host/port exit-code cases (now the schema's job, covered by the web
smoke on boot).
Review (turtle1999): the opening narrated control flow. Drop the argv-parse/
switch narration; keep only the two non-obvious facts (per-mode dynamic imports,
and that the adapter exits so only a valid mode reaches the switch).
Drop the bare `dsh <config>` positional in favor of a `--config <path>` flag.
Without a root positional, `web` can be a real Commander subcommand in one
program instead of the reserved-first-token dispatch to a second parser, so
`dsh --help` lists every mode natively (no hand-pasted command text) and the
second parser + reserved-token machinery are gone.
Grammar:
dsh TUI (shipped tree + ~/.dsh overlay)
dsh --config <path> TUI, alternate tree (demos/tests only)
dsh --resume <id> TUI, resume a session
dsh -p "task" headless one-shot
dsh web [--host --port --dev]
`dsh` is the product front door with no positional; `--config` exists only so
demo:cordis, demo:code-mode, and the keyless PTY smokes can point the shipped
bin at an example tree. Those three sites and the /resume re-exec argv move to
`--config <path>`. The `-p` + `--config`/`--resume` mode-mixing guard and the
cordis.yml-owns-host/port-default fix are preserved.
Agent Note + Chinese pair, README, tui.ts docs updated. All 13 PTY smokes
(including code-mode via --config and the exec-replace resume handoff) green.
The merge's "always pass adapter-resolved host/port to AppCLIEntry" made the
adapter's 127.0.0.1/3080 shadow apps/cli/cordis.yml's webserver row — editing
the yml port would have had no effect, a duplicated default.
The adapter now assigns no host/port default: an absent --host/--port leaves the
field undefined (WebInvocation.host?/port?), runWeb forwards each to AppCLIEntry
only when present, and AppCLIEntry patches the webserver row only for an
explicit flag. cordis.yml is the single source of the host/port default; the
adapter still validates a flag when given. Removes the now-unused
DEFAULT_WEB_PORT; LOOPBACK_HOST/ALL_INTERFACES_HOST stay as the allowed-value
vocabulary (validation + the printed URL/LAN line).
Coverage: api-proxy.ts joins the web-transport exclusion block (its
behavior specs moved here with it; the assembled-entry coverage lands with
the GUI test lane). Static: config-catalog regenerated after the log-round
revert shifted a source anchor. Prose brought current per review: the cli
README now describes the one shared composition (and its build
prerequisite), the apiproxy README points at the api-gateway row instead of
the deleted runtime package, and the config-tree agent note's headless
deferral paragraph records what actually landed (bilingual pair
re-recorded).
dsh -p now runs AppCLIEntry over the same cordis.yml as dsh web — one
composition, no disabled rows, no layer marks. The only surface difference
is port 0 (parallel runs never collide), and the printed URL opens the live
headless session in a browser while it runs. The model face gains what web
already had (ask_user_question, workspace context, model titles) per the
unification ruling. InProcessApiClient now wraps toFetchHandler(ctx.apiProxy)
directly, so bootHost/startHost lose their last consumer and the
dsh-host-runtime package retires; its api-proxy behavior specs move to
dsh-host-apiproxy where the implementation lives.
Boots a test-only cordis.yml through the real Loader and asserts the route
service's behavior surface: exact/longest-prefix matching, tapIndex
transform order and unsubscription, traversal 403, non-GET 405, SPA-200
fallback, malformed-request 400 without process exit, duplicate-pattern
throw, dispose closing held connections with register/disposer symmetry,
and a listen-failure fail-loud case (EADDRINUSE -> FAILED fiber + late
rejection). Replaces the retired factory-era specs.
Integrate the Commander argument adapter and dsh-front-door work with master's
config-tree `dsh web` (#601: AppCLIEntry + apps/cli/cordis.yml) and the
packages/ui/acp → packages/acp/acp relocation.
- web.ts: keep master's AppCLIEntry-based boot, but take the adapter's parsed
(host, port, dev) instead of an internal parseArgs. The adapter's host/port
defaults (127.0.0.1/3080) match cordis.yml, so always passing them is
behavior-equivalent to master's "undefined keeps the yml default".
- apps/cli/package.json: master's expanded config-tree dep set + commander.
- retire-readline Agent Note: point the TUI refusal proof at
apps/cli/tests/built-bin.e2e.ts (both languages), re-record the pair.
- READMEs reconciled (demo-bin removal + master's ACP/channel rewording).