- subagents.history computes its projections best-effort on both arms
(a hostile unit's fold rejection serves the page without the block,
matching the session-list precedent) with dual-arm coverage
- the lifecycle-witness spec probes every field of the seven-key
witness, protecting the key list itself
- list-children's own module docs catch up with the seq-gate contract,
and the design note records the rung-two later-event window as an
accepted, self-healing deviation of the corruption class
- the cached-identity rung gains a finality gate: the identity value
carries its descriptor seq and a cached row is served only when that
seq lands in the child's own suffix, so a fork seed's replayed
ancestor identity can never outrank the authoritative refold
(stateVersion bumped for the state-shape change)
- the cold preparation validates the inspected header against the
enumerated candidate's lifecycle witness; a republished id degrades to
that child's corrupt diagnostic instead of leaking the new owner's log
- the new projection registration proves HMR disposal; companion notes
qualify the superseded decision text and record the deliberate
error-face asymmetry
subagents.history drops its session-query read: a live child serves its
in-memory events with the registry watermark snapshot, a cold child goes
through inspectServable with a detached fold, and the retired
SESSION_QUERY_* arms disappear while every wire face stays put. The
design note records the shared read source.
- the subagent projection's empty state becomes a serializable null
sentinel (undefined fields vanish in JSON push frames, leaving stale
identities in clients); consumers treat null and a missing key alike,
and cached sentinels defer to the authoritative refold
- the tool catalog generator mounts the projection registry for
list_agents; the shared unavailable-error copy goes catalog-neutral
- the design note's verification section records the new
subagent-diagnostic snapshot honestly, the 2026-07-22 note's test
inventory is rewritten to the current mechanism, and the session-store
error code is documented
Cold children consult the optional session-projection-cache checkpoint
before paying a preparation recompute: the identity is immutable once
appended, so a cached value is definitive regardless of its watermark.
The cache stays a read-only accelerator (absent service or any rung-two
fault falls through silently; verdicts stay with the authoritative
refold), and the note plus core-data-structures pages describe the
three-rung ladder.
- listChildren contains per-child projection faults on both ladder rungs
(any registered unit's fold/schema rejection maps to that child's corrupt
diagnostic) and pins the whole-enumeration listing-failure rethrow
- the base bundle mounts session-projection (web-app's own insert retired
to avoid the double mount); stale composition comment updated
- the shared projections-unavailable wire face is pinned across
list/history/prompt; retired session-query arms removed from the catalog
paths
- the design note records the unknown-parent semantics shift and the
fold-fault isolation rule
The subagent API surface change staled the committed catalog artifacts;
the snapshot fixture agent is referenced only from its cordis.snapshot.yml,
so knip learns it as an entry like its siblings.
- listChildren reads the session store via strict ctx.get (property proxy
is caller-scoped), orders candidates branchlessly, narrows the cold-read
return type, and pins the cost model and store/registry composition gaps
with tests; per-file coverage restored
- acp-agent and headless-agent compositions mount session-projection; a
keyless snapshot pins the descriptor-less diagnostic row
- api-proxy cold spec pins header-origin ownership and the legacy
descriptor-only opt-out
- design note ships as implemented with its English pairing; companion
notes and core-data-structures pages synced
knip: ui-settings no longer imports react-dom (the portal moved into
OnboardingSurface), so the react-dom peer/dev dependencies and
@types/react-dom go away with the usage.
Review follow-ups: the Agent Note's Testing section now records the
held-join reload regression scenario this PR adds (both languages,
pairing re-recorded); the e2e hold gates EVERY settings.describe issued
before release instead of only the first, so a future boot-time join
consumer cannot silently collapse the widened window; the sampler's
persistence across later navigations is documented and the vacuity
wording softened to what the hold actually buys (timing independence).
The settings shell painted the onboarding overlay (opaque stage, mask,
#root inert) the moment a step was registered and not locally completed,
while every step still had to load its private join before deciding
whether to show — rendering null could not suppress the shell-owned
chrome. Every reload on the hero therefore flashed a full-screen opaque
layer (white in the light palette) for one settings/credential RPC
round-trip after the session list turned ready.
The chrome now belongs to the step: a new zero-cordis OnboardingSurface
primitive (ui-primitives) renders the body-portaled overlay/mask/stage
verbatim from the former SettingsRoot stylesheet and holds #root inert
for exactly its own lifetime. WelcomeNotice and DeepSeekOnboardingDialog
wrap only their visible branch in it, so their existing null branches
paint and block nothing by construction. SettingsRoot keeps the
coordinator unchanged but renders the elected step bare, and the
settings.onboarding contract now names the surface wrap as the
registrant's obligation.
The onboarding e2e gains a held-join reload scenario pinning that a
configured world never mounts the takeover chrome or inerts the app.
The branch control on user and consumed-steering bubbles could enable
only when a completed turn ended with no node after the message at all,
so readers saw a permanently unavailable control whose tooltip promised
a state it could not reach. An enabled one would mislead anyway: a fork
at a message seq cuts at the containing turn/end and takes the answer
along, the opposite of the branch-to-re-ask reading a control on one's
own bubble suggests.
MessageItem loses its fork props, PendingSteeringBubble loses the
showBranch special case, and messageBranchSeqs narrows to
assistantBranchSeqs: only a completed turn's transcript tail that is
the turn's own content-text assistant may fork. A steered turn keeps
its fork point under the settled answer, because fork is a log-prefix
cut and the steer is model-visible history the child inherits.
Web aria goldens drop the user-bubble disabled-branch row and its
hidden explanation text; the nested-subagent golden also loses the one
enabled user-tail fork handle, a loss the decision note accepts.
The profile rework left references to the old entry modes behind. Renames
the user patch-layer API and its spec file (watchPersonalPatches ->
watchUserPatches, personal-config.spec.ts -> user-patches.spec.ts) and
retargets the prose that still named `config.yaml`, `--config`, raw-config
mode, and surface overlays: repository-plugin and mcp-memory READMEs, the
credentials-local anchor into app-boot, vendor manifest items 12-13, the
vendored include/hmr comments, and install.sh.
Restores the boot-failure guard the rework dropped with raw mode: the
built-bin case now boots `--profile web --patch <invalid>` and asserts the
settled diagnostic and exit 1, so the HMR initial-scan deadlock stays
covered; its orphaned raw fixture is renamed and the unused one deleted.
The superseded personal-config Agent Note and its superseding profile note
are now cross-linked.
A profile manifest and a bundle manifest are different kinds and shared one
flat `dsh` section: `dsh.plugins` listed bundles (not plugins) and `dsh.patch`
declared a bundle's layer. Each kind now names its role — a bundle declares
`dsh.bundle.patch`, a profile declares `dsh.profile.bundles` — so a
package.json states which role it plays and the list name matches its contents.
`DEFAULT_PROFILE_PLUGINS` becomes `DEFAULT_PROFILE_BUNDLES`, and
`DshManifestSection` splits into `DshBundleManifest`/`DshProfileManifest`.
Pre-release: no compatibility shim; turtle-ui moved with it (bd5ff10).
Correct the Agent Note's consequence: a running turn withholds the footer
below its own trigger bubble, while every earlier completed turn keeps its
seat — which the package test already asserts.
Give the running-phase barrier an explicit budget: it is armed before the
park and awaited after the stop click, so the 30s replay default left no
headroom for the marker poll, the UI polls, and two aria captures.
Number the running-turn test's boundary seqs like the log does, with each
turn/end strictly after its own nodes.
`assistantActionsSeqs` picked the last content-text assistant of each turn
from the finalized transcript alone. That quantity is stable only once the
turn closes: while a turn is still producing steps, the narration written
before a tool call is the last content assistant so far, so copy, branch,
and the clock appeared under an intermediate sentence for as long as the
tool ran and then moved down to the next step's text.
Pass `ConversationSnapshot.turnEnds` into the derivation and grant the row
only inside a turn that has a durable `turn/end`. This is the same
completion fact the branch control and the `Ran for` label already read, so
the three parts of one row now agree; mid-turn narration owns nothing, and
the seat appears once under the settled answer.
`hasContentText` moves to chat-flow.ts so the ownership gate and
AssistantMarkdown's mount gate cannot drift apart.
apps/web/tests/turn-tail-actions.e2e.ts pins both states through the
assembled application: a hang sidecar on the second model call parks a turn
whose first step narrated before calling bash, and the two goldens hold the
parked flow and the flow after stopping.
Every capacity assertion typed into the field first, so the path that reads a
stored count — the one an already-configured route takes every time it is
opened — was never exercised, and the coverage gate said so. A row now opens
on stored counts and is expected to spell them `1M` and `256K`, the same
vocabulary the field accepts.
`capacityText` takes the row it is rendering rather than looking it up again
by index, which retires the impossible empty-row branch that lookup needed.
The list editor's pre-flight model check is unreachable from the card that
disables submit on the same failure, and says so where it stands.
The two ways to gain a provider had picked up the shared button base's pill
shape and shrunk to their labels, so they read as two stray buttons of
different lengths under the list instead of its last slot. They split the row
evenly again, on the row cards' own corner and the dashed outline this page
already uses for "nothing here yet"; the rule that overrides the base now
says so in one place rather than layering a second `.addButton` block.
An empty capacity shows the adapter's route-level fallback as its placeholder,
so a blank field reads as "sized by the route" rather than as a model with no
capacity. It is a hint, not a mirror: the field counts K as 1000 while the
fallback is 262144, and a deployment may override it.
The picker's description says what the list is without promising an edit the
rows themselves already offer.
The `@media (prefers-reduced-motion: reduce)` block never closed, so every
rule after it — the whole fetch dialog, its candidate list, and the rows
inside — applied only to viewers whose system asks for reduced motion. The
sheet still parsed and the classes still attached, so the list painted with
the browser's own bullets, indentation, and inline label, and the reported
model id ran straight into the capacity beside it.
Report the id alone: it is the string adoption writes, and the capacities the
endpoint disclosed are adopted with it and editable in the row that appears.
The candidate row's remaining font name is the one the theme declares.
The styles gate now checks that the sheet's braces balance, and reads every
theme sheet rather than the platform tokens alone, so a name declared in a
sibling is not called undeclared and a `--dsh-` typo cannot pass as a token.
The Models page could name a provider's key and little else. Adding an
OpenAI-compatible gateway meant opening $DSH_HOME/settings.yaml and
knowing the profile shape; correcting a stale context window meant the
same. This layer puts both on the page: a card that declares a route
pi-ai does not ship — id, endpoint, protocol, key, models — and a model
list on the pi-ai editor that can ask the provider what it serves and
adopt the answer.
It follows the DeepSeek catalog editor that landed in #1050 rather than
inventing a second look for the same job. Both editors now share the
section shell and heading, the danger-tinted delete, the add-model
button, the empty state, the per-row validator that names a bad row by
its position, and one K/M capacity vocabulary — 256K and 1M are read and
spelled back, while settings.yaml still stores plain token counts. The
row type is structurally open like that editor's, so a profile field
this card does not edit survives an edit here.
Three of that editor's decisions replaced weaker ones this branch had
made. Inheritance now reads the composition base rather than the
effective value, which would echo an override back the moment a reset
dropped it. Validation names the offending row instead of stating a
blanket problem. And emptying the list is no longer conflated with
handing the catalog back to the adapter — those are separate acts, with
separate affordances.
The create write carries the revision the card opened at, so a route
another tab declared meanwhile is a conflict rather than a silent
overwrite of its profile.