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.
`#5175` disabled the three Host rows (`time-context`, `schedule`,
`ui-schedule`) as a temporary stopgap and refreshed the Web fixtures for that
composition. Those rows and fixtures stay as master has them; this branch
keeps its preset ownership of the reminder stack, which takes effect when the
stopgap is lifted.
Resolutions:
- `packages/bundle/web-app/cordis.patch.yml` keeps master's `disabled: true`
on all three rows.
- The three presets declare the clock under `preset-time-context`: a row id
belongs to exactly one plane, and the Host row retains `time-context`.
- `apps/web/tests/schedule-after.e2e.ts`, `apps/web/tests/fixtures/
time-context-every-step.patch.yml`, `web-default-isolation.expected.e2e.ts`,
and the `minimal-preset` session fixture keep master's stopgap-era state.
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.
A preset mount reaches the preset's whole subtree, so a `standard`, `cordis`,
or `ptc` subagent now sees the reminder tools the removed
`ctx.agents.roots()` filter kept away from it. A task bound to such a Session
can never be delivered — `ctx.sessionController.resolveAgent` refuses it — so
`ScheduleService.create` and `ScheduleService.update` refuse it with
`subagent_session`, in the operation that creates the obligation rather than
in each consumer.
`list` and `delete` keep serving those Sessions so a task stored before the
rule stays removable, and the tool maps the new code into its error output.
The Agent Note, both package READMEs, and the generated catalogs record the
subtree exposure and the enforcement point.
* feat(i18n): key translation pairing records by heading section
Replace the whole-file blob hashes in *.i18n.yaml with one entry per
heading section that still holds language-specific content, keyed by the
English heading-slug path. Blocks identical across the pair (generated
table rows, code blocks, shared regions) stay out of the hash, so edits to
different sections and identical regenerations merge with Git's default
text merge, including on GitHub.
Remove the dsh-translation-pairing merge driver, its resolver command and
installer wiring; gen-translation-brief recovers last-confirmed text from
commit history. Migrate all active records, archive the superseded Agent
Note, and record the replayed conflict reduction in the new one.
* test(i18n): refresh translation prompt snapshot for the terminology note
* feat(i18n): exclude only code blocks and generated regions from pairing records
Replace byte-equality masking with an explicit rule: a record hash skips
fenced code blocks and BEGIN/END GENERATED regions, the two kinds of
content the pairing gate already requires to be identical on both sides.
Move generated data into regions written into both pages:
gen-config-catalog renders both languages and the record with per-package
regions labelled by code identifiers; gen-module-graph keeps its table in a
region; gen-doc-graphs splices the event matrix regions into the Chinese
page. Add a generic region splice beside the region grammar.
* fix(plugins): deny incompatible bundles and clarify compatibility refusals
A bundle package is not a plugin row, so startup admission never read its
own DSH peers: a bundle installed outside DSH or before a DSH upgrade still
applied its patch layer. loadProfileDirectory now skips such a bundle like an
unreadable one unless the profile exempts that exact pair.
A post-install refusal in a profile without a lockfile reinstalls from the
restored manifest instead of failing a frozen install and leaving the
rejected packages in node_modules.
Compatibility warnings now name the `dsh plugin allow-version` command for
CLI-managed profiles, startup denials name the row id instead of a resolved
module URL, and the CLI no longer reports "pnpm failed" when pnpm never ran.
* feat(plugins): report incompatible versions as typed refusals
Compatibility checks now yield the refused packages as data instead of a
profile-specific sentence. runProfilePnpm returns them as
PackageResult.incompatible, and the plugin manager refuses with the
incompatible-version code carrying the same records. Each surface renders
them: the CLI prints the exact allow-version command for its profile, the
Web plugin manager words them through its locale dictionary, and app-boot
keeps one neutral English diagnostic for logs and startup stderr.
* fix(plugin-manager): drop the agent exemption hint from the incompatibility reason
Compatibility checks now yield the refused packages as data instead of a
profile-specific sentence. runProfilePnpm returns them as
PackageResult.incompatible, and the plugin manager refuses with the
incompatible-version code carrying the same records. Each surface renders
them: the CLI prints the exact allow-version command for its profile, the
Web plugin manager words them through its locale dictionary, and app-boot
keeps one neutral English diagnostic for logs and startup stderr.
The first-use directory name and its stored title came from the Client's
startup language, so one product decision carried two incompatible
requirements: a path addressed by shell commands, tool arguments, @path
references, and session logs, and a label a reader reads.
The controller now owns the fixed `default-workspace` directory name and
the registry titles the Workspace after that same segment, so
`workspace.initializeDefault` takes no request at all —
WorkspaceInitializeDefaultRequest and its name validation are gone, and
the registry resolver returns a path instead of a path and title. No
language reaches the Host.
Browser consumers label a Workspace through the new
`workspaceDisplayTitle`: a title still equal to the automatic one reads
as the localized default name from the `common` namespace, every other
title reads verbatim. The Workspace browser resolves it once at the store
read, so rows, hover card, search meta, and the rename and delete dialogs
inherit it; the hero picker and the Conversation chip resolve it too.
Confirming a rename stores the text on screen, which is what stops a row
following the language.