Commit Graph
49 Commits
Author SHA1 Message Date
imccyu abf8b760ec fix(plugin-manager): preserve live runtime resolution mappings 2026-10-02 17:09:01 +08:00
imccyu 869afc493d fix(plugin-manager): refresh runtime package resolution 2026-10-02 17:09:01 +08:00
Turtle 9c5fb6b0c5 Merge pull request #5550 from deepseek-harness/turtle/plugin-version-hint-66b6a2501f82
feat(plugin-manager): name the exact spec when pnpm installs an older version
2026-10-01 12:36:36 +08:00
Turtle bab69fbec5 feat(plugin-manager): name the exact spec when pnpm installs an older version 2026-09-30 18:13:26 +08:00
turtle1999 00b363074a fix(plugin-manager): let the page remove a selection no package holds
Desktop loads its profile with `loadProfileDirectory`, so the retired
bundle removal moves there from `loadProfile` and covers every loader.

A name in `dsh.profile.bundles` that neither the profile's dependencies
nor the installation holds had no uninstall on the Plugins page: the
button required `installed`. The Host now reports such a selection as
`removable`, and `removeBundle` deselects it without running pnpm. The
page shows uninstall for `installed || removable`.
2026-09-30 17:36:56 +08:00
turtle1999 9b79f97010 fix(plugins): keep aliases and whole user information in bundle sources
Address the second ds-review-bot round on the bundle Source section:

- remove an http(s) URL's user information up to its last `@`, so a
  username containing `@` no longer leaves the password behind;
- keep the dependency name before a local or git spec when the package's
  own manifest name differs, so reinstalling restores the alias;
- accept scp-like git addresses with any user name;
- show Built in for a profile dependency whose loaded copy the
  installation supplies, instead of hiding the section;
- align the PackageView, subsystem page, and README wording with the
  shadowing rule and the kept query string, and assert the source of
  not-bundle and unreadable bundles.
2026-09-29 13:27:43 +08:00
turtle1999 6d1e12b237 fix(plugins): keep credentials and shadowed copies out of bundle sources
Address the first ds-review-bot round on the bundle Source section:

- strip the user information of http(s) and git+http(s) URLs so a private
  Git URL's token reaches neither the page nor the plugin_manager tool;
- expand `~` in file:/link: paths to the home directory as pnpm does;
- write jsr: aliases after the dependency name, like npm: aliases;
- give no source for a profile dependency the installation also supplies,
  whose installation copy is the one that loads;
- label the English row "Code source" to match the Chinese copy and hold
  for built-in bundles, and sync the subsystem page, READMEs, CSS comment,
  and tool test.
2026-09-29 12:07:56 +08:00
turtle1999 b96959ecf4 feat(plugins): show where a bundle comes from on its page
The bundle page ends with a Source section: the spec that installs the
bundle elsewhere, as `pnpm add` accepts it, or Built in for an official
bundle the installation ships, followed by its current version.

The Host's listBundles carries the new `source` field for profile
dependencies: git addresses and URLs as recorded, file:/link: paths made
absolute against the profile, and registry ranges, tags, or aliases after
the package name.
2026-09-29 10:51:33 +08:00
Turtle 1bd3df926d fix(plugin-manager): wait for a pnpm run its exited operation left behind
Review found three ways the takeover could let two writers overlap.

- A claimant that read an exited PID could remove the lock of a new
  holder that reused that PID before the claim. The claimant now probes
  the PID again under the claim, after re-reading the record.
- The browser Worker's process shim reports its own PID as absent, so a
  second writer in the Worker took over the first one's live lock. A
  record naming the contender's own process is never taken over.
- Takeover proves only that the holder exited, while a pnpm run it
  started can still be writing the profile. Each profile operation now
  records its pnpm run in .plugin-manager/run.json and removes the record
  when the run ends; an operation that finds a record waits for the run
  within the existing tree bound and otherwise refuses, naming the
  process and the record, without running pnpm.

A failed removal of the exited holder's lock now waits instead of
failing the acquisition, the probe range is stated as int32, and the
tests inject exited PIDs instead of reusing real ones, which Windows
reissues at once.
2026-09-24 19:55:41 +08:00
turtle1999 51d70c5f5c 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.
2026-09-23 19:51:29 +08:00
turtle1999 747c98b0db 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.
2026-09-23 19:14:19 +08:00
turtle1999 2c67633990 feat(plugins): enforce DSH peer compatibility with exact exemptions 2026-09-23 18:46:08 +08:00
Turtle ccaa0dc11c fix(plugin-manager): bound pnpm runs so a silent child cannot hold the profile lock (#4982)
* fix(plugin-manager): bound pnpm runs so a silent child cannot hold the profile lock

A pnpm child that prints its completion line and then never exits holds the
profile write lock until the host is restarted, so every other session and host
on that profile fails after its own lock timeout (issue #4981). execa resolves
its promise only once the piped stdio has ended, so a run now completes on the
process's own exit and drains the pipes under a bound instead of awaiting their
end.

A service run whose captured output stays silent for idleTimeoutMs is
terminated, reports timedOut alongside its exit status, and is not retried on
the next registry; a run with inherited descriptors captures nothing and is
left unbounded for its operator.

* test(plugin-manager): type the mocked child and await the drain descendants

The inherited-descriptor fixture no longer needs its legacy `as unknown` cast,
because the mocked child is typed for the fields the operations read; the
retired baseline entry is pruned (verify-no-unknown-casts).

The drain specs wait for the killed descendant to exit before removing its
directory: Windows refuses to delete a directory a live process still holds.

* test(plugin-manager): stop the drain descendant deterministically

The descendant that holds the pipes now ends on a stop file the teardown
writes, and on its own after a minute, so the fixture leaves no process behind
when a kill cannot be delivered. Windows stops it through taskkill, and the
spec waits for the exit before removing the directory it ran in.

* test(plugin-manager): run the drain descendant from its own script file

Windows cannot carry a nested `-e` script through another spawn argument: the
descendant failed to start there, so the fixture asserted on a process that was
already gone. The descendant now runs from a script file the fixture writes.

* test(plugin-manager): cover the bounded drain everywhere and stage the real descendant on POSIX

Windows does not stage a descendant that keeps the inherited pipes open past
the direct child's exit, so the real-process cases run on POSIX only. The
mocked child now models execa's own completion rule — its promise settles only
once both pipes end — which covers the bounded drain on every platform and
still fails without the raw-exit race.

* test(plugin-manager): cover the drain failure path without a child process

An output consumer failure that the bounded drain cuts short is staged with the
mocked child as well, so the per-file coverage gate also holds where the
real-process cases are skipped.

* fix(plugin-manager): terminate the whole tree of a stalled run

A lifecycle script is a child of pnpm, so signalling pnpm alone left an
approved postinstall writing into the profile after the operation reported a
timeout and released the lock. A stalled run now stops its process group (or
Windows process tree) and waits for it to disappear before the caller restores
the files. The cut tail's close is told apart from a reading failure by where it
happened, so a failure from before the cut still surfaces.

* docs(plugin-manager): regenerate the config catalog after the rebase

The English catalog gains `idleTimeoutMs` from the regenerator, and the Chinese
counterpart carries the same interface line so the pair stays consistent.

* fix(plugin-manager): terminate a run's tree through execa, keeping exit cleanup

Spawning a service run with `detached` made execa skip the handler that kills
the child when the manager exits, so a crashed manager left the pnpm tree
running in the profile. `killDescendants` spawns the run as its own process
group while keeping that cleanup, and it addresses the tree on every kill path,
including cancellation. The module now only decides the probe target and waits
for quiescence.
2026-09-23 16:11:30 +08:00
ZiyaZhang 75d74a055e fix(plugin-manager): preserve authentication fallback during GitHub checks 2026-09-22 22:15:09 -07:00
ZiyaZhang 0edfe83573 fix(plugin-manager): bound GitHub connection checks before installation 2026-09-22 08:33:49 -07:00
Yichen Jiang 3c50bf6b6c feat(plugin-manager): choose the first responsive public registry 2026-09-22 18:38:43 +08:00
Yichen Jiang 8cf07ea76c fix(plugin-manager): preserve remembered registries during initial lookup 2026-09-22 15:32:16 +08:00
Turtle 654caa4bbd feat(bundle): accept an ordered patch file list in dsh.bundle.patch (#4722)
* feat(bundle): accept an ordered patch file list in dsh.bundle.patch

`dsh.bundle.patch` now accepts `string | string[]`. A list names patch
files applied in order as one bundle layer; each file's relative plugin
paths still resolve beside that file. `bundlePatchPaths` in dsh-app-boot
is the one resolver from a declaration to absolute paths, and every
reader uses it: profile loading, plugin-manager reconcile and bundle
rows, the client-runtime roster, and the workspace, cordis-config, and
product-isolation gates. `ProfileLayer.patchPath` becomes `patchPaths`.

* fix(bundle): keep declared patch files package-relative in the workspace gate

path.join produced backslash entries on Windows, so the expected files
list did not match package.json files. bundlePatchFiles returns the
validated declaration as written; bundlePatchPaths joins it.
2026-09-20 18:20:24 +08:00
imccyu a9f205c584 refactor(plugins): rename PackageMeta to PluginLocalizedMeta 2026-09-19 20:19:08 +08:00
imccyu 0b7cb4102b feat(plugins): resolve installed metadata through runtime resource exports 2026-09-19 20:19:04 +08:00
Yichen Jiang 5fe06ee64b Merge remote-tracking branch 'origin/master' into worktree/npm-registry-mirror-config-029a47
# Conflicts:
#	packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx
#	packages/client/ui-plugin-manager/src/client/manager-store.ts
2026-09-18 20:37:39 +08:00
Yichen Jiang 1dfd026b84 Merge remote-tracking branch 'origin/master' into worktree/npm-registry-mirror-config-029a47
# Conflicts:
#	apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/boot/plugin-manager/README.i18n.yaml
#	packages/boot/plugin-manager/README.md
#	packages/boot/plugin-manager/README.zh.md
#	packages/client/ui-plugin-manager/README.i18n.yaml
#	packages/client/ui-plugin-manager/README.md
#	packages/client/ui-plugin-manager/README.zh.md
#	packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx
2026-09-18 20:21:59 +08:00
Yichen Jiang e7094c81e2 Merge origin/master into worktree/plugin-install-close 2026-09-18 20:03:08 +08:00
Yichen Jiang f938ebad64 fix(web): recover lost plugin installation responses 2026-09-18 19:58:04 +08:00
Yichen Jiang cb2750019a fix(plugin-manager): read pnpm's own registry, its stdout refusals, and lay failures where they belong
Review of #4539. A refusal pnpm view prints as JSON on stdout was read
as unknown, so no registry was ever asked after a dead one; stderr and
stdout are now read alike, and a lookup killed at its bound is a
network refusal. pnpm's own registry joins the fallback plan only once
pnpm config get registry names npm's own or a configured fallback, so
a company registry in .npmrc is never followed by a public one, and a
registry pnpm already names is asked once. The Host lays each failure
at the registry or the spec's own host, reading the host off error
lines only, and reports it as ChangeResult.failedAt; a stop that lands
between attempts is cancelled, not failed. The package publishes its
registry rule through ./registry so the dialog plans with the Host's
own function, starts from the registry the Host asks first until one
is remembered, tags that one as the default, labels pnpm's own by the
host it names, and words and offers a change of registry from the
Host's attribution. ERR_PNPM_NO_OFFLINE_META is no longer a network
failure.
2026-09-18 17:03:04 +08:00
Turtle de662ee010 fix(boot): skip failed profile bundles and retain recovery controls (#4516) 2026-09-18 16:35:50 +08:00
Yichen Jiang 398a4247ae feat(client): pick and remember the registry a plugin install asks first
The install dialog folds a registry control beside the guide: the
official registry, the mirrors the Host configured, and a typed
address, remembered in this browser and passed to the check and the
run. The installing screen names the registry the Host moved on to,
each pnpm run carries its registry, and a failed run names every
registry asked, or the host a git or tarball spec could not reach,
with a shortcut back to the registry options. The Host answers the
spec's own host and the registries it asked so the dialog can say so.

Also documents the registry plan in the package READMEs, the boot
subsystem page, the catalogs, and an Agent Note; pins the flow through
a real Host/browser e2e and refreshes the tool-schema snapshots.
2026-09-18 15:21:44 +08:00
Yichen Jiang 716e2e2683 feat(plugin-manager): ask configured registries in turn while one is unreachable
Add registry and fallbackRegistries to the manager Config, ask them in
order from inspect and installBundle with --registry, and move on only
for failures another registry can change: unreachable, timed out, or a
copy the registry lacks. A git or tarball spec's own host is never
retried through a registry, and a registry outside the configured set
is asked alone. The lookup runs without pnpm's retries so a dead
registry is reported within inspectTimeoutMs; the install keeps pnpm's
defaults. Results carry the registries asked, the install-state event
names each attempt, and the plugin_manager tool takes a registry.
2026-09-18 15:21:44 +08:00
turtle1999 8c5ae870c2 fix(creator): address plugin workflow review findings 2026-09-16 17:22:38 +08:00
turtle1999 9a83772560 fix(plugin-manager): request approval for single-call full access 2026-09-16 16:53:34 +08:00
turtle1999 470d4aae11 fix(plugin-manager): require full access for agent tool calls 2026-09-16 16:27:48 +08:00
Yichen Jiang a9fe93fc0b refactor(plugins): name bundles by their package name only
`dsh.title` was a manifest field this branch introduced: the manager read
it for `listBundles` and `inspect`, and the Web page showed it over the
package name. A bundle is named by its package name now, so `DshManifest`
keeps only its shipped fields, the page derives every heading from the
name, and the install dialog's subject line reads the name the Host
answered. The two e2e fixtures drop their titles and the manager lane's
goldens follow.
2026-09-15 23:31:54 +08:00
Yichen Jiang 79729c3422 refactor(plugins): read a bundle's one-liner from the package description only
The dsh manifest gains no description field: a bundle's card shows the
package.json description, and the shipped optional bundles carry no
display title or description of their own until localized metadata is
designed. The plugin author tutorial shows the bundle declaration alone.
2026-09-15 22:26:58 +08:00
Yichen Jiang fa320209ab refactor(plugins): name the optional bundles in the launcher, not the installation manifest
OPTIONAL_BUNDLES in app-boot lists the bundles the installation ships
switched off; the plugin manager, the default-product isolation gate,
the workspace constraints, and the packed-install check read that one
list instead of a dsh.optionalBundles field in apps/cli/package.json.
2026-09-15 22:22:22 +08:00
Yichen Jiang f04c265725 refactor(plugins): stop announcing patch generations applied outside the manager
app-boot no longer emits profile/reconciled after a reload, and the plugin
manager no longer turns such a generation into a plugin-manager/changed
notice: the Web page learns of a CLI or hand edit on its next read. The
change event keeps only the reasons a manager operation produces.
2026-09-15 22:21:06 +08:00
Yichen Jiang 075aab54a7 Merge remote-tracking branch 'origin/master' into feat/plugin-mgmt-4-web
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	docs/subsystems/boot.i18n.yaml
#	docs/subsystems/boot.md
#	docs/subsystems/boot.zh.md
#	packages/api/remotes/src/client/index.ts
#	packages/boot/plugin-manager/README.i18n.yaml
#	packages/boot/plugin-manager/README.md
#	packages/boot/plugin-manager/README.zh.md
#	packages/boot/plugin-manager/src/index.ts
#	packages/boot/plugin-manager/src/types.ts
#	packages/boot/plugin-manager/tests/manager.spec.ts
#	packages/client/ui-settings-plugin-inventory/src/client/locales.ts
#	packages/client/ui-settings-plugin-inventory/src/client/management.tsx
#	packages/client/ui-settings-plugin-inventory/tests/components.client.spec.tsx
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	pnpm-lock.yaml
#	scripts/verify-default-product-isolation.spec.ts
2026-09-15 21:15:15 +08:00
Yichen Jiang bc0ecae21f feat(plugins): ship optional bundles switched off and guide the install dialog
The dsh installation lists under `dsh.optionalBundles` the bundles it
ships for a person to switch on. The plugin manager reports them as
`optional`, and the Web plugin page shows them first, in a built-in
group with an official tag, off until selected and never removable.
Agent Teams ships this way, so a blank profile offers both of its
layers. Default-product isolation, the workspace constraints, and the
packed-install check accept that one dependency edge and nothing else
experimental.

A bundle's title and one-liner come from `dsh.title` and the new
`dsh.description`, else from the package name and `description`. A
dependency without a bundle patch is no longer listed unless the
profile selects it. The install dialog asks for a package name or
address; a guide under the field shows the three accepted forms with
an example each and ends in a security warning, and the copy no longer
speaks of a plugin ID.
2026-09-15 20:51:12 +08:00
Turtle a5dea502a6 fix(plugin-manager): address approval review and remove agent notices 2026-09-15 20:37:36 +08:00
Turtle ffffac3cba fix(plugin-manager): approve blocked dependency builds before retrying 2026-09-15 18:09:28 +08:00
Yichen Jiang dc9256536b Merge remote-tracking branch 'origin/master' into feat/plugin-mgmt-4-web
Master now carries the final #4182 plugin manager and the live client
module graph of #4189, so the web management layer is re-ported onto
them:

- The manager keeps master's `ManagementError` codes, `ChangeResult`
  stages, `configure()` under `hmr.runExclusive`, `reload()` warnings
  and `writePluginEnabled` name matching, and adds back `inspect`,
  request-id installs with `plugin-manager/install-log` and
  `plugin-manager/install-state`, `cancelInstall`, `listBundles` titles,
  rows and overrides, `packageResult.kind`, `pnpmCommand` and
  `inspectTimeoutMs`, and `plugin-manager/changed` after each change and
  each `profile/reconciled`. A failed or cancelled installation restores
  the snapshotted manifest and lockfile instead of master's one-shot
  `pnpm remove` cleanup, so `cleanup` and `remainingDependencies` leave
  the result.
- App boot keeps master's reconciliation diagnostics and emits
  `profile/reconciled`; `watchUserPatches` goes with the launcher's
  watcher now that HMR owns profile reloads.
- The settings Plugin list tab stays read-only with #4189's page-local
  sync failures and retry; master's bundle form and row checkboxes are
  dropped again. The sidebar page words the Host's codes through its
  dictionary, and a client bundle change follows the live module graph
  without a page reload.
- The web e2e scaffold no longer chooses a reload policy: the base
  bundle's `hmr` row turns on with the profile context and application
  readiness, and `profile.hmr: false` disables it through an overlay for
  the startup-only scenario.
2026-09-15 17:28:08 +08:00
Yichen Jiang 4d1adb8541 feat(web): manage plugins over the #4182 manager from the sidebar page
The sidebar Plugins page now drives `ctx.pluginManager` from #4182 and the
settings Plugin list tab goes back to read-only: its bundle form and row
checkboxes are removed, and the page joins `listBundles` with
`listPlugins` into one view per bundle, decides availability from the
inventory's `managementAvailable`, switches bundles and rows through
`setBundleEnabled` and `setPluginEnabled`, and confirms uninstalls.

The manager gains what the guided install dialog needs: `inspect(spec)`
reads a spec through the registry or a directory before anything installs
and refuses specs without a bundle; `installBundle` takes a request id,
streams each pnpm run as `plugin-manager/install-log`, reports its phases
as `plugin-manager/install-state`, classifies a failed run into
`packageResult.kind`, names the bundle it added, and restores
`package.json` and `pnpm-lock.yaml` when the run fails, is cancelled, or
adds a package without a bundle patch; `cancelInstall(requestId)` answers
only after pnpm exited and the files are back; `listBundles` carries
titles, one-liners, declared rows with their live entries, and the
built-in rows a bundle overrides; every completed operation and every
patch generation applied outside the manager emits
`plugin-manager/changed`. The config gains `pnpmCommand` and
`inspectTimeoutMs`. App boot emits `profile/reconciled` after each applied
patch generation and finds a root Include mounted under its pinned id by
another copy of the module.

Row diagnostics, dependency checks, and the non-bundle dependency group
are not carried over; the web e2e scaffold mounts a launcher-style
managed profile instead of the removed profile runtime.
2026-09-15 16:36:31 +08:00
Turtle 311156b1c2 Merge master and preserve profile-managed HMR 2026-09-15 16:20:47 +08:00
Turtle 08021a16d8 fix(plugin-manager): keep package operations outside HMR 2026-09-15 16:08:22 +08:00
Turtle 61665c3454 fix(plugin-manager): clean failed installs and address critical review findings 2026-09-15 15:15:49 +08:00
Turtle abd765a600 refactor(hmr): own profile reload lifecycle through YAML 2026-09-15 13:01:06 +08:00
Turtle 8e327513ba fix: remove inactive bundles after incomplete installation 2026-09-14 23:17:24 +08:00
Turtle 438c862fea refactor: keep profile launch context data-only and HMR backend-scoped 2026-09-14 23:09:06 +08:00
Turtle d06e6b5519 feat: coordinate profile management through dsh-hmr 2026-09-14 23:09:06 +08:00
Turtle 98b92b683c feat: add current-profile plugin manager service and Web controls 2026-09-14 23:09:05 +08:00