dsh-plugin-shop 0.4.14 → 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 +118 -51
- package/lib/index.js +506 -45
- package/lib/typert.host.js +27 -18
- package/lib/typert.remote-client.js +20 -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 +4 -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 +76 -1
- 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 +15 -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). */
|
|
@@ -116,6 +153,20 @@ export interface ShopInstalledEntry {
|
|
|
116
153
|
outdated: boolean;
|
|
117
154
|
enabled: boolean;
|
|
118
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>;
|
|
119
170
|
/** `shop/catalog` result (§7.3), plus the denied list for the install gate's UI. */
|
|
120
171
|
export interface ShopCatalogResult {
|
|
121
172
|
schemaVersion: number;
|
|
@@ -137,6 +188,8 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
137
188
|
private readonly profile;
|
|
138
189
|
private readonly profileDir?;
|
|
139
190
|
private readonly inventory?;
|
|
191
|
+
private readonly hot?;
|
|
192
|
+
private readonly loaderEntriesInjected?;
|
|
140
193
|
private readonly dshBin;
|
|
141
194
|
/** The argv `shop/restart` re-spawns: the real process argv minus node and
|
|
142
195
|
* the CLI script path, or a test-provided substitute. */
|
|
@@ -149,6 +202,14 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
149
202
|
private readonly latestVersion;
|
|
150
203
|
private readonly hasGit;
|
|
151
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;
|
|
152
213
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
153
214
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
154
215
|
/** Finished install records retained, so a poll sees the true terminal
|
|
@@ -178,6 +239,16 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
178
239
|
* without requiring a real profile above this module. */
|
|
179
240
|
private profileDirResolved;
|
|
180
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;
|
|
181
252
|
/** Enable or disable one installed plugin, hot (§8): a disable writes the
|
|
182
253
|
* row to the user layer, an enable drops it again so the bundle default
|
|
183
254
|
* rules — the CLI's watchUserPatches applies either through HMR. The shop's
|
|
@@ -188,12 +259,16 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
188
259
|
enabled: boolean;
|
|
189
260
|
}): ShopSetEnabledResult;
|
|
190
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;
|
|
191
266
|
/** Browse the catalog (§7.3): cached snapshot, refreshed on demand. */
|
|
192
267
|
catalog(args?: {
|
|
193
268
|
refresh?: boolean;
|
|
194
269
|
}): Promise<ShopCatalogResult>;
|
|
195
270
|
/**
|
|
196
|
-
* Install one cataloged version into the profile (§7.2). The
|
|
271
|
+
* Install one cataloged version into the profile (§7.2). The rejection
|
|
197
272
|
* paths run against this Host's snapshot before anything is spawned; only a
|
|
198
273
|
* passing request reaches the executor.
|
|
199
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;
|
|
@@ -32,8 +36,18 @@ export interface CatalogEntry {
|
|
|
32
36
|
/** Subpackage directory inside the repo; present exactly when the entry is
|
|
33
37
|
* a monorepo subpackage rather than the repo root. */
|
|
34
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;
|
|
35
48
|
}
|
|
36
49
|
export interface DeniedEntry {
|
|
37
50
|
name: string;
|
|
38
51
|
detail: string;
|
|
52
|
+
replacement?: string;
|
|
39
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
|
},
|