dsh-plugin-shop 0.4.13 → 0.5.0

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.
@@ -5,6 +5,7 @@ import { loadCatalog, type LoadCatalogOptions } from './catalog.ts';
5
5
  import type { CatalogEntry, DeniedEntry } from './types.ts';
6
6
  import { type InstallArgs, type InstallRejectionCode } from './install.ts';
7
7
  import { type InstallStatus } from './executor.ts';
8
+ import { hotMount, hotUnmount } from './hot.ts';
8
9
  import { type RestartOutcome } from './restart.ts';
9
10
  import { type RepoPinFs } from './repo-pins.ts';
10
11
  export type { InstallArgs, InstallRejectionCode } from './install.ts';
@@ -16,6 +17,18 @@ export interface InventoryEntry {
16
17
  moduleName: string;
17
18
  enabled: boolean;
18
19
  }
20
+ /** One boot-layer Loader entry, structurally — the surface `liveDisable`
21
+ * consumes. `fiber` is the entry's live activation (present while the plugin
22
+ * is up); `update` flips its options. */
23
+ export interface LoaderEntryLike {
24
+ options: {
25
+ name?: string;
26
+ };
27
+ fiber?: unknown;
28
+ update(options: {
29
+ disabled: boolean | null;
30
+ }, create?: boolean, force?: boolean): Promise<void>;
31
+ }
19
32
  /** Test-only injection points; production callers pass nothing. */
20
33
  export interface ShopGatewayOptions {
21
34
  catalogUrl?: string;
@@ -30,6 +43,15 @@ export interface ShopGatewayOptions {
30
43
  inventory?: {
31
44
  list(): InventoryEntry[];
32
45
  };
46
+ /** Test-only injection: the hot-mount functions; production uses the real
47
+ * hotMount/hotUnmount. */
48
+ hot?: {
49
+ mount: typeof hotMount;
50
+ unmount: typeof hotUnmount;
51
+ };
52
+ /** Test-only injection: the Loader's boot-layer entries; production reads
53
+ * them from `ctx.loader`. */
54
+ loaderEntries?: () => Array<LoaderEntryLike>;
33
55
  dshBin?: string;
34
56
  /** The dsh argv this process was launched with, for `shop/restart`;
35
57
  * defaults to the real `process.argv` minus node and the script path. */
@@ -52,6 +74,17 @@ export interface ShopGatewayOptions {
52
74
  hasGit?: () => boolean;
53
75
  /** Test-only injection: the pins file's filesystem; production uses node:fs. */
54
76
  pinFs?: RepoPinFs;
77
+ /** Test-only injection: the explicit `allowRestart` override; production
78
+ * reads the loader row's `config.allowRestart`. */
79
+ allowRestart?: boolean;
80
+ /** The environment `detectSupervisor` reads; production uses process.env. */
81
+ env?: NodeJS.ProcessEnv;
82
+ /** The pid `detectSupervisor` inspects; production uses process.ppid —
83
+ * the PARENT pid (a systemd unit's main process has ppid 1). */
84
+ ppid?: number;
85
+ /** Test-only injection: how the release-tarball integrity check fetches
86
+ * the release asset; production uses global fetch. */
87
+ fetchTarball?: (url: string) => Promise<Response>;
55
88
  }
56
89
  /** `shop/installStart` result (§7.3): rejections are typed wire values with an
57
90
  * author-readable `detail`, not thrown RPC errors. */
@@ -93,6 +126,10 @@ export interface ShopVersionResult {
93
126
  installed: string;
94
127
  latest: string | null;
95
128
  outdated: boolean;
129
+ /** Whether `shop/restart` is usable: false when a supervisor owns this
130
+ * process and no `allowRestart` override is set. The client hides the
131
+ * restart offer on false but keeps the pending-change notice. */
132
+ restartSupported: boolean;
96
133
  }
97
134
  /** `shop/updateStart` result (§7.3): the self-update spawn, or a typed
98
135
  * refusal (a version that is not plain semver). */
@@ -114,7 +151,22 @@ export interface ShopInstalledEntry {
114
151
  installed: string;
115
152
  latest: string;
116
153
  outdated: boolean;
154
+ enabled: boolean;
117
155
  }
156
+ /** How many bytes a release tarball may be at the integrity check. The
157
+ * registry already refuses to publish a tarball over 32 MiB, so 64 MiB is
158
+ * headroom, not a gate of its own. */
159
+ export declare const MAX_TARBALL_BYTES: number;
160
+ /**
161
+ * Fetch a release tarball and verify its sha256 against the catalog record
162
+ * (market borrowings §3.1). Returns a rejection detail, or null when the
163
+ * bytes match. The read streams through the byte cap, so an oversized or
164
+ * hostile body is refused without ever being buffered. Every failure — fetch
165
+ * throw, non-2xx, unreadable body, over-cap, hash mismatch — carries the same
166
+ * `tarball-integrity` code with a detail naming what happened, so the plugin
167
+ * author can read the cause.
168
+ */
169
+ export declare function verifyTarballSha256(fetchTarball: (url: string) => Promise<Response>, url: string, recordedSha256: string, maxBytes?: number): Promise<string | null>;
118
170
  /** `shop/catalog` result (§7.3), plus the denied list for the install gate's UI. */
119
171
  export interface ShopCatalogResult {
120
172
  schemaVersion: number;
@@ -136,6 +188,8 @@ export declare class ShopGateway extends TypertRemoteService {
136
188
  private readonly profile;
137
189
  private readonly profileDir?;
138
190
  private readonly inventory?;
191
+ private readonly hot?;
192
+ private readonly loaderEntriesInjected?;
139
193
  private readonly dshBin;
140
194
  /** The argv `shop/restart` re-spawns: the real process argv minus node and
141
195
  * the CLI script path, or a test-provided substitute. */
@@ -148,6 +202,14 @@ export declare class ShopGateway extends TypertRemoteService {
148
202
  private readonly latestVersion;
149
203
  private readonly hasGit;
150
204
  private readonly pinFs;
205
+ private readonly allowRestart?;
206
+ private readonly env;
207
+ /** The parent pid `detectSupervisor` inspects; production defaults to
208
+ * process.ppid (a systemd unit's main process has ppid 1). */
209
+ private readonly ppid;
210
+ /** The release-tarball fetch for the install-time integrity check; global
211
+ * fetch in production, a fixture response in tests. */
212
+ private readonly fetchTarball;
151
213
  /** The install gate runs against the last loaded snapshot, never a fresh
152
214
  * fetch per request (§7.2: the Host's cached snapshot is the truth). */
153
215
  /** Finished install records retained, so a poll sees the true terminal
@@ -177,20 +239,36 @@ export declare class ShopGateway extends TypertRemoteService {
177
239
  * without requiring a real profile above this module. */
178
240
  private profileDirResolved;
179
241
  private listInventory;
242
+ /** The Loader's boot-layer entries; a harness without the loader answers
243
+ * with an empty list (there is then nothing to live-disable). */
244
+ private loaderEntries;
245
+ /** Live-disable one boot-layer entry, retrying until its fiber is actually
246
+ * down. A disable can land while the entry's init is still in flight: the
247
+ * options flip but the finishing init brings the fiber up anyway, and a
248
+ * plain re-update no-ops on the empty diff (dsh-market themes.ts:74-93).
249
+ * For an update swap this sequencing is mandatory, not defensive: two live
250
+ * instances of a service-providing plugin would collide at provision. */
251
+ private liveDisable;
180
252
  /** Enable or disable one installed plugin, hot (§8): a disable writes the
181
253
  * row to the user layer, an enable drops it again so the bundle default
182
- * rules — the CLI's watchUserPatches applies either through HMR. */
254
+ * rules — the CLI's watchUserPatches applies either through HMR. The shop's
255
+ * own row and the framework's bundles are never toggleable: disabling the
256
+ * host chain would break HMR itself. */
183
257
  setEnabled(args: {
184
258
  name: string;
185
259
  enabled: boolean;
186
260
  }): ShopSetEnabledResult;
187
261
  private rowConfig;
262
+ /** The explicit restart override. Only the row's `config:` sub-object is
263
+ * passed to a plugin — a top-level `allowRestart:` beside `name:` would be
264
+ * silently ignored by the loader (dsh-market README, #227). */
265
+ private allowRestartConfigured;
188
266
  /** Browse the catalog (§7.3): cached snapshot, refreshed on demand. */
189
267
  catalog(args?: {
190
268
  refresh?: boolean;
191
269
  }): Promise<ShopCatalogResult>;
192
270
  /**
193
- * Install one cataloged version into the profile (§7.2). The four rejection
271
+ * Install one cataloged version into the profile (§7.2). The rejection
194
272
  * paths run against this Host's snapshot before anything is spawned; only a
195
273
  * passing request reaches the executor.
196
274
  */
@@ -1,6 +1,6 @@
1
- /** Install gate: the four rejection paths of §7.2, as a pure function. */
1
+ /** Install gate: the gate rejection paths of §7.2, as a pure function. */
2
2
  import type { CatalogSnapshot } from './catalog.ts';
3
- export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'git-missing';
3
+ export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'git-missing' | 'tarball-integrity';
4
4
  export interface InstallArgs {
5
5
  name: string;
6
6
  version: string;
@@ -0,0 +1,15 @@
1
+ /** systemd-supervisor detection for `shop/restart` (design 2026-08-31
2
+ * market-borrowings C-1).
3
+ *
4
+ * Pure: the environment and the process snapshot are parameters, so the
5
+ * policy is fixture-driven. Two signals are required on purpose:
6
+ * `INVOCATION_ID` (and `JOURNAL_STREAM`) are inherited by every descendant
7
+ * of a unit — an ordinary terminal opened inside a service would carry them
8
+ * too. Only the unit's own main process has ppid 1; hiding the restart button
9
+ * for anything else would be the worse bug (dsh-market's restart.ts:31-44
10
+ * documents the same measured failure). */
11
+ export type Supervisor = 'systemd' | null;
12
+ export interface ProcessSnapshot {
13
+ ppid: number;
14
+ }
15
+ export declare function detectSupervisor(env: Record<string, string | undefined>, proc: ProcessSnapshot): Supervisor;
@@ -4,7 +4,7 @@ export interface CatalogSummary {
4
4
  zh?: string;
5
5
  }
6
6
  export interface CatalogSection {
7
- category: 'tool' | 'provider' | 'ui' | 'workflow' | 'integration' | 'other';
7
+ category: 'tool' | 'provider' | 'ui' | 'workflow' | 'integration' | 'theme' | 'other';
8
8
  summary: CatalogSummary;
9
9
  capabilities: string[];
10
10
  }
@@ -20,6 +20,10 @@ export interface CatalogEntry {
20
20
  review?: {
21
21
  reviewedVersion?: string;
22
22
  reviewedCommit?: string;
23
+ /** For release-rescued entries, the review pin is the tarball sha256 —
24
+ * the content-addressed identity; the tag is display only, a mutable ref
25
+ * that must never carry the trust. */
26
+ reviewedSha256?: string;
23
27
  reviewer: string;
24
28
  reviewCommit: string;
25
29
  notes: string;
@@ -29,8 +33,21 @@ export interface CatalogEntry {
29
33
  source: 'npm' | 'github';
30
34
  /** `owner/slug`; present exactly when `source` is github. */
31
35
  repo?: string;
36
+ /** Subpackage directory inside the repo; present exactly when the entry is
37
+ * a monorepo subpackage rather than the repo root. */
38
+ subdir?: string;
39
+ /** The prebuilt GitHub Release tarball, present exactly when the entry was
40
+ * rescued from `requires-build`. The Host installs this URL instead of the
41
+ * git form (market borrowings §3.1). */
42
+ tarball?: {
43
+ url: string;
44
+ sha256: string;
45
+ };
46
+ /** The date this entry first appeared in the catalog (YYYY-MM-DD). */
47
+ added: string;
32
48
  }
33
49
  export interface DeniedEntry {
34
50
  name: string;
35
51
  detail: string;
52
+ replacement?: string;
36
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.4.13",
3
+ "version": "0.5.0",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,6 +38,7 @@
38
38
  "./package.json": "./package.json"
39
39
  },
40
40
  "files": [
41
+ "LICENSE",
41
42
  "lib/index.js",
42
43
  "lib/client.js",
43
44
  "lib/types/**/*.js",
@@ -48,7 +49,7 @@
48
49
  "lib/typert.remote-client.d.ts",
49
50
  "cordis.patch.yml"
50
51
  ],
51
- "license": "MIT",
52
+ "license": "Apache-2.0",
52
53
  "engines": {
53
54
  "node": "^22.19.0 || >=24.0.0"
54
55
  },
@@ -74,6 +75,7 @@
74
75
  },
75
76
  "peerDependencies": {
76
77
  "@deepseek-ai/cordis": "^4.0.1",
78
+ "@deepseek-ai/cordis-plugin-include": "^1.0.6",
77
79
  "@deepseek-ai/dsh-app-boot": "^0.1.1-rc.2",
78
80
  "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
79
81
  "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2"
@@ -82,6 +84,9 @@
82
84
  "@deepseek-ai/cordis": {
83
85
  "optional": true
84
86
  },
87
+ "@deepseek-ai/cordis-plugin-include": {
88
+ "optional": true
89
+ },
85
90
  "@deepseek-ai/dsh-app-boot": {
86
91
  "optional": true
87
92
  },