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.
- package/LICENSE +201 -21
- package/README.md +4 -4
- package/lib/client.js +211 -118
- package/lib/index.js +523 -49
- package/lib/typert.host.js +31 -20
- package/lib/typert.remote-client.js +22 -11
- package/lib/types/client/locales.d.ts +10 -0
- package/lib/types/client/present.d.ts +11 -2
- package/lib/types/host/catalog.d.ts +5 -2
- package/lib/types/host/executor.d.ts +13 -2
- package/lib/types/host/hot.d.ts +105 -0
- package/lib/types/host/index.d.ts +80 -2
- package/lib/types/host/install.d.ts +2 -2
- package/lib/types/host/supervisor.d.ts +15 -0
- package/lib/types/host/types.d.ts +18 -1
- package/package.json +7 -2
|
@@ -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
|
|
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
|
|
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.
|
|
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": "
|
|
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
|
},
|