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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>