dsh-plugin-shop 0.8.7 → 0.9.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -3983,6 +3983,30 @@ async function appBootFacts(dshDir) {
3983
3983
  peerCheck: peerCheckOf(exported, appBootDir)
3984
3984
  };
3985
3985
  }
3986
+ /** `value` when it is a non-null object, else null. */
3987
+ const objectOrNull = (value) => typeof value === "object" && value !== null ? value : null;
3988
+ /**
3989
+ * The list a refusal names, read off one answer of dsh's compatibility check
3990
+ * or of its plugin manager, under whichever name the dsh that gave the answer
3991
+ * uses: `requirements` from 0.2.1, `peers` from 0.1.7 to 0.2.0. app-boot's
3992
+ * `evaluatePluginCompatibility` and dsh-plugin-manager's `IncompatiblePlugin`
3993
+ * were renamed together (design 2026-10-19-dsh-021-readiness, section 4, read
3994
+ * off 0.2.1-rc.2's own types and code on 2026-10-19). A key is a dsh peer's
3995
+ * package name or the literal `engines.dsh`; a value is the range the package
3996
+ * declared.
3997
+ *
3998
+ * `requirements` is read first, and only when it holds an object: an answer
3999
+ * whose `requirements` is anything else is not in the newer shape, so its
4000
+ * `peers` is tried. Only the entries whose range is a string are kept, as
4001
+ * `incompatibleList` always kept them, so one malformed range costs its own
4002
+ * entry and not the rest of the list. An answer naming neither reads as an
4003
+ * empty list, never as a throw.
4004
+ */
4005
+ function refusedRequirements(answer) {
4006
+ const list = objectOrNull(answer.requirements) ?? objectOrNull(answer.peers);
4007
+ if (list === null) return {};
4008
+ return Object.fromEntries(Object.entries(list).filter((pair) => typeof pair[1] === "string"));
4009
+ }
3986
4010
  /**
3987
4011
  * The peer check an app-boot exports, bound to its own version (see
3988
4012
  * `PeerCheck.evaluate`), or null when it exports only part of one — a rule
@@ -3990,6 +4014,10 @@ async function appBootFacts(dshDir) {
3990
4014
  * or when its manifest carries no version, which is the one `evaluate`
3991
4015
  * judges by. Never throws, so a check that cannot be formed costs the
3992
4016
  * templates and the patch-list answer nothing.
4017
+ *
4018
+ * What `evaluate` hands back is app-boot's answer with the shop's own `peers`
4019
+ * filled from whichever name the list came under (`refusedRequirements`), so
4020
+ * a dsh that renames it again costs this function and nothing past it.
3993
4021
  */
3994
4022
  function peerCheckOf(exported, appBootDir) {
3995
4023
  const { evaluatePluginCompatibility: evaluate, readProfileVersionExemptions: exemptions } = exported;
@@ -4004,7 +4032,14 @@ function peerCheckOf(exported, appBootDir) {
4004
4032
  const runtime = version;
4005
4033
  const judge = evaluate;
4006
4034
  return {
4007
- evaluate: (manifest, profileExemptions) => judge(manifest, profileExemptions, runtime),
4035
+ evaluate: (manifest, profileExemptions) => {
4036
+ const answer = judge(manifest, profileExemptions, runtime);
4037
+ if (answer === void 0 || answer === null) return void 0;
4038
+ return {
4039
+ ...answer,
4040
+ peers: refusedRequirements(answer)
4041
+ };
4042
+ },
4008
4043
  exemptions
4009
4044
  };
4010
4045
  }
@@ -4092,7 +4127,7 @@ function incompatibleList(value) {
4092
4127
  name,
4093
4128
  version,
4094
4129
  runtimeVersion,
4095
- peers: Object.fromEntries(Object.entries(record(entry.peers)).filter((pair) => typeof pair[1] === "string"))
4130
+ peers: refusedRequirements(entry)
4096
4131
  }];
4097
4132
  });
4098
4133
  }
@@ -4228,7 +4263,8 @@ const CODE_SENTENCE = {
4228
4263
  "stale-approval": "a build approval names a package pnpm no longer holds",
4229
4264
  "management-required": "the plugin belongs to dsh itself",
4230
4265
  unaddressable: "the plugin is not a row of the profile's own patch, so dsh cannot address it",
4231
- "operation-error": "dsh hit an unexpected error"
4266
+ "operation-error": "dsh hit an unexpected error",
4267
+ "already-installed": "this dsh updates no installed package in place: uninstall it, then install it again"
4232
4268
  };
4233
4269
  /** What each classified pnpm failure was, as dsh classifies it. */
4234
4270
  const KIND_SENTENCE = {
@@ -4568,6 +4604,90 @@ function startManagerOperation(options) {
4568
4604
  finished
4569
4605
  };
4570
4606
  }
4607
+ /**
4608
+ * `first`, continued by the record `next` starts when `first` ends refused,
4609
+ * as one record under `first`'s id: design 2026-10-19-dsh-021-readiness,
4610
+ * section 2. dsh-plugin-manager 0.2.1-rc.2 refuses an update in place as
4611
+ * `already-installed`, and the gateway continues such an update through
4612
+ * dsh's CLI. One record, so the client keeps polling the id it was given,
4613
+ * and the restart gate and eviction, which read every tracked record, see
4614
+ * the command that runs.
4615
+ *
4616
+ * While `first` runs, this is `first`. Whether it continues is decided once,
4617
+ * when `first` ends: the first `status()` read or `first.finished` settling
4618
+ * that sees `first` terminal asks `refused`, and that answer is kept. So a
4619
+ * caller may set its flag while reading the answer that ends `first`, and a
4620
+ * flag flipped after that changes neither `status()` nor `finished`, which
4621
+ * answer from that one decision. Not refused, this stays `first`. Refused,
4622
+ * `next` runs, exactly once, at that same read or finish, so no read between
4623
+ * the refusal and the next command reports the refusal. From then on the
4624
+ * record is `next`'s, its state, detail and activation, with `first`'s log,
4625
+ * then `bridge`, then `next`'s log. That log joins two bounded logs and the
4626
+ * bridge, so a continued record can carry about twice `MAX_LOG_LINES` and
4627
+ * `MAX_LOG_BYTES`, where one record of `startManagerOperation` carries at most
4628
+ * one line over them.
4629
+ */
4630
+ function continueAfter(first, refused, next, bridge) {
4631
+ let begun = false;
4632
+ /** Whether `first` ended refused: `refused`'s answer, asked once, by the
4633
+ * first read or finish that sees `first` terminal. */
4634
+ let refusal;
4635
+ /** The log `first` ended on, and the record carrying on from it: what
4636
+ * `next` returned, or the failure it threw. */
4637
+ let continued;
4638
+ const behind = (head, tail) => ({
4639
+ ...tail,
4640
+ log: [
4641
+ ...head,
4642
+ bridge,
4643
+ ...tail.log
4644
+ ]
4645
+ });
4646
+ const status = () => {
4647
+ if (continued !== void 0) return behind(continued.head, continued.running.status());
4648
+ if (begun) return {
4649
+ state: "running",
4650
+ log: [...first.status().log, bridge]
4651
+ };
4652
+ const own = first.status();
4653
+ if (!isTerminalInstallState(own.state)) return own;
4654
+ refusal ??= refused();
4655
+ if (!refusal) return own;
4656
+ begun = true;
4657
+ let running;
4658
+ try {
4659
+ running = next();
4660
+ } catch (error) {
4661
+ const failed = {
4662
+ state: "failed",
4663
+ log: [],
4664
+ detail: `dsh-plugin-shop: ${messageOf(error)}`
4665
+ };
4666
+ running = {
4667
+ installId: first.installId,
4668
+ status: () => failed,
4669
+ finished: Promise.resolve(failed)
4670
+ };
4671
+ }
4672
+ continued = {
4673
+ head: own.log,
4674
+ running
4675
+ };
4676
+ return behind(own.log, running.status());
4677
+ };
4678
+ const finished = first.finished.then(async (own) => {
4679
+ status();
4680
+ const now = continued;
4681
+ if (now === void 0) return own;
4682
+ return behind(now.head, await now.running.finished);
4683
+ });
4684
+ finished.catch(() => {});
4685
+ return {
4686
+ installId: first.installId,
4687
+ status,
4688
+ finished
4689
+ };
4690
+ }
4571
4691
  //#endregion
4572
4692
  //#region src/host/index.ts
4573
4693
  /** ShopGateway: the Host half of dsh-plugin-shop (§5.1). */
@@ -4618,10 +4738,11 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
4618
4738
  * WHY a restart is impossible. They did: `version()` reported a boolean that
4619
4739
  * covered two of the refusals, the client had one string for that boolean, and
4620
4740
  * that string named systemd — so a Windows user was told to restart a systemd
4621
- * service and to set an override that the platform check, being the first gate
4622
- * of the three, could never reach. */
4741
+ * service and to set an override that the platform check, being checked ahead
4742
+ * of the systemd one, could never reach. */
4623
4743
  const RESTART_BLOCKED_DETAIL = {
4624
4744
  desktop: "dsh-plugin-shop: the desktop profile is managed by the DeepSeek Harness desktop app, and dsh refuses to restart it from here; restart the app to apply the change",
4745
+ "from-default-profile": "dsh-plugin-shop: restart is not supported when dsh was launched with --from-default-profile; from dsh 0.2.1 a restart would recreate the profile from its template and delete every plugin installed in it, this shop included. Restart dsh manually without that flag to apply the change",
4625
4746
  windows: "dsh-plugin-shop: restart is not supported on Windows yet; restart dsh manually to apply the change",
4626
4747
  systemd: "dsh-plugin-shop: restart is disabled because this process is a systemd service — a restart would kill the takeover helper along with the unit, and the service would not come back. Set allowRestart: true in the shop row config to override.",
4627
4748
  "port-zero": "dsh-plugin-shop: restart is not supported when dsh was launched with --port 0; restart dsh manually"
@@ -4640,6 +4761,14 @@ const DESKTOP_PROFILE_DETAIL = "dsh-plugin-shop: the desktop profile is managed
4640
4761
  * CLI path's switch, and switching a deselected package off) is not
4641
4762
  * refused. */
4642
4763
  const switchWhileBusyDetail = (name) => `dsh-plugin-shop: an install, update or uninstall is still running in this profile, and dsh holds the profile until it ends; switch ${name} after it finishes.`;
4764
+ /** The line a record gains when dsh's plugin manager refuses an update in
4765
+ * place and the shop continues it through dsh's CLI (design
4766
+ * 2026-10-19-dsh-021-readiness §2): what dsh refused, and the command that
4767
+ * runs instead. It names a `dsh plugin` command, so it never reaches a
4768
+ * desktop reader: the continuation does not run in that profile. */
4769
+ function cliFallbackLine(profile, spec) {
4770
+ return `dsh-plugin-shop: dsh's plugin manager updates no installed package in place (already-installed); running \`dsh plugin --profile ${profile} add ${spec}\` instead`;
4771
+ }
4643
4772
  /** How many bytes a release tarball may be at the integrity check. The
4644
4773
  * registry already refuses to publish a tarball over 32 MiB, so 64 MiB is
4645
4774
  * headroom, not a gate of its own. */
@@ -5531,14 +5660,32 @@ let ShopGateway = (() => {
5531
5660
  *
5532
5661
  * The order is load-bearing for the copy a reader sees. The desktop
5533
5662
  * profile first: dsh itself refuses to launch it, so no platform,
5534
- * supervisor or port could make a restart possible there. Then Windows,
5535
- * because the platform check has no override and reporting the systemd one
5536
- * there sends a Windows user to set `allowRestart: true`, which this gate
5537
- * would still refuse.
5663
+ * supervisor or port could make a restart possible there, and the remedy
5664
+ * is the app. Then `from-default-profile` and Windows, both ahead of
5665
+ * systemd, because neither has an override and reporting the systemd one
5666
+ * there sends the reader to set `allowRestart: true`, which this gate would
5667
+ * still refuse. And `from-default-profile` ahead of Windows, because every
5668
+ * remedy after `desktop` is "restart dsh manually", which a reader launched
5669
+ * with the flag would do with the flag, and on 0.2.1 that recreates the
5670
+ * profile: its copy is the one that says "without that flag", and a
5671
+ * Windows, systemd or `--port 0` reader needs it as much as any other.
5538
5672
  *
5539
5673
  * - `desktop`: dsh's CLI refuses `--profile desktop` ("managed exclusively
5540
5674
  * by the Electron application"), so the relaunch would exit at once and
5541
5675
  * leave nothing on the port; the app restarts it.
5676
+ * - `from-default-profile`: from dsh 0.2.1, `--from-default-profile <name>`
5677
+ * replaces an existing custom profile with a fresh copy of the shipped
5678
+ * template on every launch, and the relaunch re-runs this argv verbatim,
5679
+ * so it would delete every plugin in the profile, this shop included.
5680
+ * Measured 2026-10-19 in throwaway homes: 0.2.1-rc.2 booted a relaunched
5681
+ * profile that had held the shop with no dependencies, while 0.2.0-rc.2
5682
+ * refused to boot an existing profile at all, so there the restarted dsh
5683
+ * failed to come back. Both spellings are the flag:
5684
+ * `--from-default-profile <name>` and `--from-default-profile=<name>`. It
5685
+ * is left in the argv rather than stripped: the restarted process would no
5686
+ * longer run the command the reader chose, and their next launch with the
5687
+ * flag recreates the profile anyway. No override (design
5688
+ * 2026-10-19-dsh-021-readiness §3).
5542
5689
  * - `windows`: the handoff helper is a POSIX shell one-liner (restart.ts)
5543
5690
  * and there is no `sh` on Windows. That spawn fails ASYNCHRONOUSLY, so
5544
5691
  * committing would answer `ok: true`, exit this process, and leave nothing
@@ -5546,11 +5693,12 @@ let ShopGateway = (() => {
5546
5693
  * - `systemd`: under a unit the two-phase handoff kills itself, because the
5547
5694
  * main process exiting also kills the unit's cgroup and takes the detached
5548
5695
  * helper with it; the service never comes back. Overridable, and the only
5549
- * one of the three that is.
5696
+ * one that is.
5550
5697
  * - `port-zero`: the OS hands the NEW process a fresh port the browser
5551
5698
  * cannot know, so a restart would strand the client on a dead origin. */
5552
5699
  staticRestartBlock() {
5553
5700
  if (isDesktopProfile(this.profile)) return "desktop";
5701
+ if (this.restartArgv.some((arg) => arg === "--from-default-profile" || arg.startsWith("--from-default-profile="))) return "from-default-profile";
5554
5702
  if (!this.restartPlatformSupported()) return "windows";
5555
5703
  if (detectSupervisor(this.env, { ppid: this.ppid }) === "systemd" && !this.allowRestartConfigured()) return "systemd";
5556
5704
  const portIndex = this.restartArgv.indexOf("--port");
@@ -5678,10 +5826,14 @@ let ShopGateway = (() => {
5678
5826
  const alreadyImported = isUpdate || this.imported.has(args.name);
5679
5827
  const harness = await this.runningHarness();
5680
5828
  if (manager !== null) {
5681
- const running = this.startManagerInstall(manager, spec, `install ${spec}`, (raw) => {
5682
- if (!isUpdate && readChange(raw).stage === "enable") this.imported.add(args.name);
5829
+ const continues = isUpdate && !isDesktopProfile(this.profile);
5830
+ let refusedInPlace = false;
5831
+ const running = continueAfter(this.startManagerInstall(manager, spec, `install ${spec}`, (raw) => {
5832
+ const change = readChange(raw);
5833
+ if (!isUpdate && change.stage === "enable") this.imported.add(args.name);
5834
+ if (continues && change.errorCode === "already-installed") refusedInPlace = true;
5683
5835
  return managerOutcome(raw, this.outcomeContext(args.name, isUpdate ? "update" : "install", alreadyImported, this.packageHasClientHalf(args.name)));
5684
- }, () => this.postInstallHazard(args.name, harness, " It is on disk: uninstall it from the shop to undo this install."));
5836
+ }, () => this.postInstallHazard(args.name, harness, " It is on disk: uninstall it from the shop to undo this install.")), () => refusedInPlace, () => this.startCliInstall(args.name, spec, harness, true), cliFallbackLine(this.profile, spec));
5685
5837
  this.track(running);
5686
5838
  this.recordGithubPin(entry);
5687
5839
  return {
@@ -5690,13 +5842,31 @@ let ShopGateway = (() => {
5690
5842
  state: running.status().state
5691
5843
  };
5692
5844
  }
5693
- const running = startInstall({
5845
+ const running = this.startCliInstall(args.name, spec, harness, alreadyImported);
5846
+ this.recordGithubPin(entry);
5847
+ this.track(running);
5848
+ return {
5849
+ ok: true,
5850
+ installId: running.installId,
5851
+ state: running.status().state
5852
+ };
5853
+ }
5854
+ /**
5855
+ * One install of `name` through dsh's CLI, `dsh plugin --profile <p> add
5856
+ * <spec>` (§7.2): the path every install takes when dsh offers no plugin
5857
+ * manager, and the one that continues an update the manager refuses in
5858
+ * place (design 2026-10-19-dsh-021-readiness §2). `alreadyImported` is
5859
+ * whether this process may already hold the package's module, which rules
5860
+ * out a hot mount. The caller tracks the record and records any pin, once.
5861
+ */
5862
+ startCliInstall(name, spec, harness, alreadyImported) {
5863
+ return startInstall({
5694
5864
  profile: this.profile,
5695
5865
  spec,
5696
5866
  dshBin: this.dshBin,
5697
- expectedName: args.name,
5867
+ expectedName: name,
5698
5868
  prefetcher: this.prefetcher,
5699
- alsoConfirm: () => this.postInstallHazard(args.name, harness, ` It is on disk: run \`dsh plugin --profile ${this.profile} remove ${args.name}\` to undo this install.`),
5869
+ alsoConfirm: () => this.postInstallHazard(name, harness, ` It is on disk: run \`dsh plugin --profile ${this.profile} remove ${name}\` to undo this install.`),
5700
5870
  afterDone: async () => {
5701
5871
  if (alreadyImported) return {
5702
5872
  activation: "restart",
@@ -5706,8 +5876,8 @@ let ShopGateway = (() => {
5706
5876
  mount: hotMount,
5707
5877
  unmount: hotUnmount
5708
5878
  };
5709
- this.imported.add(args.name);
5710
- const result = await hot.mount({ plugin: (plugin, config) => this.home.plugin(plugin, config) }, this.profileDirResolved(), args.name);
5879
+ this.imported.add(name);
5880
+ const result = await hot.mount({ plugin: (plugin, config) => this.home.plugin(plugin, config) }, this.profileDirResolved(), name);
5711
5881
  if (!result.ok) return {
5712
5882
  activation: "restart",
5713
5883
  ...result.reason !== null ? { restartReason: result.reason } : {}
@@ -5715,17 +5885,10 @@ let ShopGateway = (() => {
5715
5885
  return { activation: activationOf({
5716
5886
  hostLive: true,
5717
5887
  clientLive: true,
5718
- hasClientHalf: this.packageHasClientHalf(args.name)
5888
+ hasClientHalf: this.packageHasClientHalf(name)
5719
5889
  }) };
5720
5890
  }
5721
5891
  });
5722
- this.recordGithubPin(entry);
5723
- this.track(running);
5724
- return {
5725
- ok: true,
5726
- installId: running.installId,
5727
- state: running.status().state
5728
- };
5729
5892
  }
5730
5893
  /**
5731
5894
  * What would stop the profile from starting now that `name`'s files are on
@@ -6157,7 +6320,12 @@ let ShopGateway = (() => {
6157
6320
  };
6158
6321
  const spec = `dsh-plugin-shop@${args.version}`;
6159
6322
  if (manager !== null) {
6160
- const running = this.startManagerInstall(manager, spec, `update ${spec}`, (raw) => managerOutcome(raw, this.outcomeContext("dsh-plugin-shop", "update", true, true)));
6323
+ const continues = !isDesktopProfile(this.profile);
6324
+ let refusedInPlace = false;
6325
+ const running = continueAfter(this.startManagerInstall(manager, spec, `update ${spec}`, (raw) => {
6326
+ if (continues && readChange(raw).errorCode === "already-installed") refusedInPlace = true;
6327
+ return managerOutcome(raw, this.outcomeContext("dsh-plugin-shop", "update", true, true));
6328
+ }), () => refusedInPlace, () => this.startCliSelfUpdate(spec), cliFallbackLine(this.profile, spec));
6161
6329
  this.track(running);
6162
6330
  return {
6163
6331
  ok: true,
@@ -6165,13 +6333,7 @@ let ShopGateway = (() => {
6165
6333
  state: running.status().state
6166
6334
  };
6167
6335
  }
6168
- const running = startInstall({
6169
- profile: this.profile,
6170
- spec,
6171
- dshBin: this.dshBin,
6172
- expectedName: "dsh-plugin-shop",
6173
- prefetcher: this.prefetcher
6174
- });
6336
+ const running = this.startCliSelfUpdate(spec);
6175
6337
  this.track(running);
6176
6338
  return {
6177
6339
  ok: true,
@@ -6179,6 +6341,20 @@ let ShopGateway = (() => {
6179
6341
  state: running.status().state
6180
6342
  };
6181
6343
  }
6344
+ /** The shop's own update through dsh's CLI: the path when dsh offers no
6345
+ * plugin manager, and the one that continues a self-update the manager
6346
+ * refuses in place (design 2026-10-19-dsh-021-readiness §2). It passes no
6347
+ * `afterDone`, so it reports a restart: a host half cannot swap itself live
6348
+ * (§8). The caller tracks the record. */
6349
+ startCliSelfUpdate(spec) {
6350
+ return startInstall({
6351
+ profile: this.profile,
6352
+ spec,
6353
+ dshBin: this.dshBin,
6354
+ expectedName: "dsh-plugin-shop",
6355
+ prefetcher: this.prefetcher
6356
+ });
6357
+ }
6182
6358
  };
6183
6359
  })();
6184
6360
  //#endregion
@@ -165,7 +165,7 @@ const dsh_plugin_shop_shop_version_result$schema = z.object({
165
165
  'installed': z.string(),
166
166
  'latest': z.union([z.literal(null), z.string()]),
167
167
  'outdated': z.boolean(),
168
- 'restartBlocked': z.union([z.literal(null), z.literal("desktop"), z.literal("windows"), z.literal("systemd"), z.literal("port-zero")]),
168
+ 'restartBlocked': z.union([z.literal(null), z.literal("desktop"), z.literal("from-default-profile"), z.literal("windows"), z.literal("systemd"), z.literal("port-zero")]),
169
169
  'pendingVersion': z.union([z.undefined(), z.literal(null), z.string()]).optional(),
170
170
  'bootId': z.union([z.undefined(), z.string()]).optional(),
171
171
  })
@@ -202,7 +202,7 @@ export const TYPERT = {
202
202
  schema: dsh_plugin_shop_shop_catalog_result$schema,
203
203
  create: () => dsh_plugin_shop_shop_catalog_result$schema,
204
204
  },
205
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1272,"column":9},
205
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1301,"column":9},
206
206
  },
207
207
  {
208
208
  id: 'dsh-plugin-shop#shop/installed',
@@ -218,7 +218,7 @@ export const TYPERT = {
218
218
  schema: dsh_plugin_shop_shop_installed_result$schema,
219
219
  create: () => dsh_plugin_shop_shop_installed_result$schema,
220
220
  },
221
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1739,"column":9},
221
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1804,"column":9},
222
222
  },
223
223
  {
224
224
  id: 'dsh-plugin-shop#shop/installedSpecs',
@@ -234,7 +234,7 @@ export const TYPERT = {
234
234
  schema: dsh_plugin_shop_shop_installedSpecs_result$schema,
235
235
  create: () => dsh_plugin_shop_shop_installedSpecs_result$schema,
236
236
  },
237
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1730,"column":9},
237
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1795,"column":9},
238
238
  },
239
239
  {
240
240
  id: 'dsh-plugin-shop#shop/installStart',
@@ -262,7 +262,7 @@ export const TYPERT = {
262
262
  schema: dsh_plugin_shop_shop_installStart_result$schema,
263
263
  create: () => dsh_plugin_shop_shop_installStart_result$schema,
264
264
  },
265
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1398,"column":9},
265
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1427,"column":9},
266
266
  },
267
267
  {
268
268
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -289,7 +289,7 @@ export const TYPERT = {
289
289
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
290
290
  create: () => dsh_plugin_shop_shop_installStatus_result$schema,
291
291
  },
292
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1706,"column":3},
292
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1771,"column":3},
293
293
  },
294
294
  {
295
295
  id: 'dsh-plugin-shop#shop/restart',
@@ -305,7 +305,7 @@ export const TYPERT = {
305
305
  schema: dsh_plugin_shop_shop_restart_result$schema,
306
306
  create: () => dsh_plugin_shop_shop_restart_result$schema,
307
307
  },
308
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1990,"column":9},
308
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2055,"column":9},
309
309
  },
310
310
  {
311
311
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -332,7 +332,7 @@ export const TYPERT = {
332
332
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
333
333
  create: () => dsh_plugin_shop_shop_setEnabled_result$schema,
334
334
  },
335
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":943,"column":9},
335
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":953,"column":9},
336
336
  },
337
337
  {
338
338
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -360,7 +360,7 @@ export const TYPERT = {
360
360
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
361
361
  create: () => dsh_plugin_shop_shop_uninstallStart_result$schema,
362
362
  },
363
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1884,"column":9},
363
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1949,"column":9},
364
364
  },
365
365
  {
366
366
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -387,7 +387,7 @@ export const TYPERT = {
387
387
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
388
388
  create: () => dsh_plugin_shop_shop_updateStart_result$schema,
389
389
  },
390
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2097,"column":9},
390
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2162,"column":9},
391
391
  },
392
392
  {
393
393
  id: 'dsh-plugin-shop#shop/version',
@@ -403,7 +403,7 @@ export const TYPERT = {
403
403
  schema: dsh_plugin_shop_shop_version_result$schema,
404
404
  create: () => dsh_plugin_shop_shop_version_result$schema,
405
405
  },
406
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2054,"column":9},
406
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2119,"column":9},
407
407
  },
408
408
  ],
409
409
  model: {
@@ -544,7 +544,7 @@ export const TYPERT = {
544
544
  },
545
545
  {
546
546
  "name": "RestartBlockedReason",
547
- "declaration": "export type RestartBlockedReason = 'desktop' | 'windows' | 'systemd' | 'port-zero';"
547
+ "declaration": "export type RestartBlockedReason = 'desktop' | 'from-default-profile' | 'windows' | 'systemd' | 'port-zero';"
548
548
  },
549
549
  {
550
550
  "name": "RestartOutcome",
@@ -165,7 +165,7 @@ const dsh_plugin_shop_shop_version_result$schema = z.object({
165
165
  'installed': z.string(),
166
166
  'latest': z.union([z.literal(null), z.string()]),
167
167
  'outdated': z.boolean(),
168
- 'restartBlocked': z.union([z.literal(null), z.literal("desktop"), z.literal("windows"), z.literal("systemd"), z.literal("port-zero")]),
168
+ 'restartBlocked': z.union([z.literal(null), z.literal("desktop"), z.literal("from-default-profile"), z.literal("windows"), z.literal("systemd"), z.literal("port-zero")]),
169
169
  'pendingVersion': z.union([z.undefined(), z.literal(null), z.string()]).optional(),
170
170
  'bootId': z.union([z.undefined(), z.string()]).optional(),
171
171
  })
@@ -199,7 +199,7 @@ export const TYPERT_REMOTE = {
199
199
  schema: dsh_plugin_shop_shop_catalog_result$schema,
200
200
  create: () => dsh_plugin_shop_shop_catalog_result$schema,
201
201
  },
202
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1272,"column":9},
202
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1301,"column":9},
203
203
  },
204
204
  {
205
205
  id: 'dsh-plugin-shop#shop/installed',
@@ -215,7 +215,7 @@ export const TYPERT_REMOTE = {
215
215
  schema: dsh_plugin_shop_shop_installed_result$schema,
216
216
  create: () => dsh_plugin_shop_shop_installed_result$schema,
217
217
  },
218
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1739,"column":9},
218
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1804,"column":9},
219
219
  },
220
220
  {
221
221
  id: 'dsh-plugin-shop#shop/installedSpecs',
@@ -231,7 +231,7 @@ export const TYPERT_REMOTE = {
231
231
  schema: dsh_plugin_shop_shop_installedSpecs_result$schema,
232
232
  create: () => dsh_plugin_shop_shop_installedSpecs_result$schema,
233
233
  },
234
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1730,"column":9},
234
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1795,"column":9},
235
235
  },
236
236
  {
237
237
  id: 'dsh-plugin-shop#shop/installStart',
@@ -259,7 +259,7 @@ export const TYPERT_REMOTE = {
259
259
  schema: dsh_plugin_shop_shop_installStart_result$schema,
260
260
  create: () => dsh_plugin_shop_shop_installStart_result$schema,
261
261
  },
262
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1398,"column":9},
262
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1427,"column":9},
263
263
  },
264
264
  {
265
265
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -286,7 +286,7 @@ export const TYPERT_REMOTE = {
286
286
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
287
287
  create: () => dsh_plugin_shop_shop_installStatus_result$schema,
288
288
  },
289
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1706,"column":3},
289
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1771,"column":3},
290
290
  },
291
291
  {
292
292
  id: 'dsh-plugin-shop#shop/restart',
@@ -302,7 +302,7 @@ export const TYPERT_REMOTE = {
302
302
  schema: dsh_plugin_shop_shop_restart_result$schema,
303
303
  create: () => dsh_plugin_shop_shop_restart_result$schema,
304
304
  },
305
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1990,"column":9},
305
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2055,"column":9},
306
306
  },
307
307
  {
308
308
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -329,7 +329,7 @@ export const TYPERT_REMOTE = {
329
329
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
330
330
  create: () => dsh_plugin_shop_shop_setEnabled_result$schema,
331
331
  },
332
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":943,"column":9},
332
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":953,"column":9},
333
333
  },
334
334
  {
335
335
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -357,7 +357,7 @@ export const TYPERT_REMOTE = {
357
357
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
358
358
  create: () => dsh_plugin_shop_shop_uninstallStart_result$schema,
359
359
  },
360
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1884,"column":9},
360
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":1949,"column":9},
361
361
  },
362
362
  {
363
363
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -384,7 +384,7 @@ export const TYPERT_REMOTE = {
384
384
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
385
385
  create: () => dsh_plugin_shop_shop_updateStart_result$schema,
386
386
  },
387
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2097,"column":9},
387
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2162,"column":9},
388
388
  },
389
389
  {
390
390
  id: 'dsh-plugin-shop#shop/version',
@@ -400,7 +400,7 @@ export const TYPERT_REMOTE = {
400
400
  schema: dsh_plugin_shop_shop_version_result$schema,
401
401
  create: () => dsh_plugin_shop_shop_version_result$schema,
402
402
  },
403
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2054,"column":9},
403
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":2119,"column":9},
404
404
  },
405
405
  ],
406
406
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The shop's two buttons on dsh's Plugins page (design
3
+ * 2026-10-16-shop-sidebar-page §7), in the head of a detail page, before the
4
+ * page's own switch and uninstall (`plugins.detail.actions`, dsh 0.1.7-rc.2
5
+ * and later): "Open Plugin shop" on the shop's own bundle page, and "View in
6
+ * Plugin shop" on the page of any bundle the shelf lists. Any other subject
7
+ * renders nothing, as the slot's contract asks of an entry.
8
+ *
9
+ * It renders inside dsh's page, so it carries its own error boundary: a
10
+ * throw here costs the reader a button, never dsh's page.
11
+ */
12
+ import { type ReactNode } from 'react';
13
+ import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
14
+ import type { ShopCatalogResult } from '../host/index.ts';
15
+ import type { ShopPageStore } from './page-store.ts';
16
+ /** One bundle as a detail contribution sees it: the field this reads of the
17
+ * Plugins page's `PluginPackageRef` (`dsh-client-ui-plugin-manager`'s slot
18
+ * contract, identical in 0.1.7-rc.2 and 0.2.0-rc.2). */
19
+ export interface PluginPackageRef {
20
+ readonly name: string;
21
+ }
22
+ /** What a detail page is about, as the Plugins page hands it over. */
23
+ export type PluginsSubject = {
24
+ readonly kind: 'bundle';
25
+ readonly pkg: PluginPackageRef;
26
+ } | {
27
+ readonly kind: 'row';
28
+ readonly pkg: PluginPackageRef;
29
+ } | {
30
+ readonly kind: 'item';
31
+ readonly id: string;
32
+ };
33
+ /** The face index.ts hands the buttons. */
34
+ export interface ShopDetailActionInjected {
35
+ /** The catalog a listing question is asked of (index.ts `listing`). */
36
+ listing: () => Promise<ShopCatalogResult>;
37
+ /** Whether the page can be opened at all: false without a layout service
38
+ * that can select a panel. */
39
+ canOpen: () => boolean;
40
+ openShop: () => void;
41
+ page: ShopPageStore;
42
+ }
43
+ export type ShopDetailActionProps = {
44
+ subject: PluginsSubject;
45
+ } & PropsLocale<'settings.shop'> & InjectFace<ShopDetailActionInjected>;
46
+ export declare function ShopDetailAction(props: ShopDetailActionProps): ReactNode;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The shop's glyph in dsh's sidebar (design 2026-10-16-shop-sidebar-page
3
+ * §4.3): a bag with a whale tail, drawn the way dsh draws its own panel
4
+ * glyphs — a 16×16 view box, one-pixel `currentColor` strokes, no fill — so
5
+ * the row's colour and its selection treatment reach it unchanged. `active`
6
+ * is ignored, as dsh's own glyphs ignore it: the row draws the selection.
7
+ */
8
+ import type { ReactNode } from 'react';
9
+ export declare function ShopGlyph({ size }: {
10
+ size: number;
11
+ active: boolean;
12
+ }): ReactNode;