The ask-user takeover rendered the pending question set as a bottom card
capped at min(60vh, 520px) with no way to reduce it, which buried the
conversation above while the user decided. Add a minimize toggle next to
the dismiss action: collapsed, the card becomes a header strip (title plus
the two icon buttons) and the option body and footer unmount; expanding
restores the full card. Drafts and the current question index live in
QuestionFlow local state, so collapse/expand never loses them. The
free-form textarea autofocuses only on first presentation, so re-expanding
does not steal focus from the toggle.
Agent Note: .agents/notes/implemented/feature/2026-08-11-collapsible-ask-user-question-card.{md,zh.md,i18n.yaml}
A package a published browser artifact carries is a runtime disclosure whatever
section declares it, and the build answers which ones those are. Record that
clause, the dry run behind it, why a bundler's virtual module is not a shipped
package, why a types-only package is not either, and the generator run cost the
dry run adds.
Browser artifacts resolve nothing on a user's machine: tsdown inlines every
non-platform specifier, the shell dist answers the rest from its frozen module
table, and Vite inlines the shell's own imports into the published dist. Record
the resulting declaration rule, and the Agent Note behind it.
A dependency in optionalDependencies, or a peer carrying
peerDependenciesMeta.<name>.optional, may be absent from an installed
tree — that absence is the whole promise of "optional". A static import
is evaluated when the importing module loads, so one absent package
stops being "this capability is unavailable" and becomes a load failure
for everything that reaches the importing module.
Nothing checked it, and nothing here could: the failure needs an
installed tree missing that package, and a workspace install always has
every package, so the unit tests, the snapshots, and the packed-install
probe all pass while the published package is broken for the consumer
who declined the optional peer.
verify-optional-dependency-imports reads each package's own manifest for
what it allows to be absent, then scans the files that ship across both
compiler faces. Value-versus-type is decided against a bound Program
rather than the import syntax, because verbatimModuleSyntax is off: the
compiler already erases an import whose bindings resolve to types, so a
syntactic rule would report four forms that emit nothing. Only the type
phase erases an import — `import defer` still resolves and links its
module, deferring evaluation alone — which is what phaseModifier
expresses and the deprecated isTypeOnly cannot.
A violation names the package, the declaration that made it optional,
and the way out in order: import it as a type, or restructure so module
scope does not need it. A dynamic import() only moves the failure to
first use, so the gate does not offer it as the remedy.
The gate runs in ci-static and ci-primary through ciSharedStaticGates
and locally in hygiene; it needs no build. TypeScriptProject gained a
face parameter so a repository-wide gate can seed the client aggregate,
which was previously unreachable; the constraint it was built with is
unchanged, a face config and never the root solution.
The tree has no violation today, so this guards the rule rather than
fixing a defect. The spec pins all seven import forms against what tsc
emits, including the four a syntactic rule would misreport.
The verify step resolved the publish order and said only that it had:
the order a release actually follows, and the ordering it could not
honour, stayed invisible until a publication was already running.
publishOrder now returns that order together with the peer edges it
dropped, verify prints both, and pack reads the order off the plan. The
dropped edges are part of the result rather than a detail of forming it:
the dsh family drops one (dsh-api-remotes -> dsh-api-gateway) and the
vendored family drops two (cordis-plugin-include and
cordis-plugin-loader, which cordis declares as peers in return), and
only whoever reads the log can judge whether a newly dropped edge is
expected.
Because pack runs on every pull request and master push, a change to the
order is now reviewable there rather than observable only at publish
time.
The order is also checked against the edges it exists to honour. A cycle
mixing peer and dependency declarations can put a dependency on the
traversal stack, where it is skipped like a peer edge, emitting a
consumer before something it installs; no later step can detect that,
and it would surface as an unresolvable install for a consumer of the
published packages. No family has that shape today, and the new test
pins the three-package case that would.
Replace the single DSH_CI_FAILOVER variable with two independent
switches so an outage on one platform no longer retargets the other:
- DSH_CI_FAILOVER_LINUX: the three required Linux workers (node-24,
node-24-coverage, node-24-consumers) and the all-checks-passed verdict,
which resolves its pool to vm-backup and keeps its concurrency and
cache-restore branches.
- DSH_CI_FAILOVER_WINDOWS: the non-blocking windows-native job, which
resolves to the dsh-win-ci pool.
all-checks-passed rides the Linux switch because it aggregates the
required Linux workers and runs on the vm-backup pool. The Dependabot
exclusion is preserved on both switches. The failover runbook (EN/ZH)
and its translation pairing, plus the docs that referenced the old
variable, are updated in the same change.
A landlock publication failed with `E409 Failed to save packument` on the
second of three packages. The registry answers a write it could not commit that
way, and publishing several packages back to back is what provokes it.
Neither publish path could recover. The native sequence published from a shell
loop of bare `npm publish` calls: no retry, and no way to resume, because the
registry rejects a repeat of an existing version permanently — so a failure
partway through left the release stuck. publish.ts skipped versions already
present, which made a re-run safe, but had no retry either.
Both paths now attempt a tarball up to four times, space writes at least two
seconds apart, and back off 2s/4s/8s between attempts. Every retry re-reads the
registry first, because a reported failure can answer a write that landed
anyway: a version that now exists with this tarball's integrity counts as
published rather than as one to place again. That same re-read is what turns a
mid-run `E403 cannot publish over the previously published versions` into a
skip when the bytes match, and leaves it a hard failure when they do not.
The native sequence gets the registry comparison publish.ts already had, through
its own script rather than shared code — the two sequences keep separate
publication paths. Its publish job now checks out the repository, which the
shell loop did not need.
Verified against a scripted registry: a clean publish, one E409 then success, an
E409 whose write landed anyway, E409 on every attempt (fails after four), and a
version already present with matching integrity (publishes nothing).
The three release sequences shipped with publishConfig.access: restricted, so
nothing in the @deepseek-ai scope was installable from outside the organization.
A restricted dependency is what actually blocks a public consumer: every harness
package declares the vendored framework as a peerDependency, and
dsh-sandbox-local declares the Landlock entry as a dependency. Those two
sequences therefore go public first — the nine vendor/* packages and the three
native/landlock-run packages — while the dsh family stays restricted until its
own sequence is opened deliberately. No public package requires a restricted one
in this arrangement.
Access is now per sequence, so no publish path can pass --access: one flag
cannot express two levels and would override the manifest that owns the fact.
publish.ts stops passing it, matching the native workflow, and
check-workspace-constraints holds each manifest to its own sequence's level,
which is what stops the scope from drifting one package at a time.
Harness consumers reference the Landlock entry as workspace:^ instead of
workspace:*, so a published harness package accepts the entry's patch and minor
releases. The entry keeps workspace:* for its platform packages, where the
binary must match the entry version exactly.
Two rationales that named a private registry no longer describe the vendored
sequence; they now state the durable reason, which is that the verification must
not depend on the registry already carrying matching versions.
The Codex and Claude Code subagent providers were production dependencies of
@deepseek-ai/dsh-base and mounted by its Cordis composition, so every install of
the base bundle carried two providers that only some products want.
Drop both from the base bundle's dependencies and composition. The examples keep
them as explicit dependencies, base's tests lock their absence, and the product
preset e2e mounts the providers it needs explicitly.
Cherry-picked from #2387 (two commits squashed into one).
pi-ai resolves an OAuth provider from a stored OAuth credential alone, and
this adapter builds its Models collection with no credential store and runs
no login flow. `openai-codex` — the one installed provider declaring
`auth.oauth` with no `auth.apiKey` — was therefore offered on the Models page
with the keyless placeholder every pi-ai route carries, and every request on
it failed `Provider is not configured` before going out.
`catalogProviderTakesApiKey()` answers whether pi-ai's installed provider for
a route declares the one method this adapter can supply, and the directory
skips the catalog routes that fail it. Catalog membership is unchanged, so
`declared` still answers what pi-ai ships; the profile half of the union stays
unconditional, so a route a settings document already names keeps its entry
and can be edited or deleted.
Resolution is untouched: a profile naming `apiKeyEnv` on such a route still
builds a working provider.
The shipped bundles keep ctx.sessionQuery mounted but set the new
session-query-sqlite `openAt: never` phase: searchSessions/searchEvents
fail with the typed SESSION_QUERY_SEARCH_DISABLED code before any request
normalization, node:sqlite is never imported or opened, and no source
observation or reconciliation runs. Every inherited exact read, filter,
and trace — session export descendants, subagent-fork Workspace
inheritance, title reads — keeps working, and the Web sidebar search
degrades to its designed local title/workspace matching. Enabling content
search is a one-line openAt override in a later patch layer; the web e2e
scaffold keeps it enabled as the assembled opt-in coverage.