dsh-plugin-shop 0.8.3 → 0.8.4-beta.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/README.md +11 -8
- package/lib/client.js +1133 -421
- package/lib/index.js +1400 -178
- package/lib/typert.host.js +69 -22
- package/lib/typert.remote-client.js +52 -13
- package/lib/types/client/ShopTab.d.ts +19 -2
- package/lib/types/client/locales.d.ts +42 -2
- package/lib/types/client/module-table.d.ts +131 -0
- package/lib/types/client/present.d.ts +126 -2
- package/lib/types/client/restart-monitor.d.ts +58 -0
- package/lib/types/host/activation.d.ts +12 -9
- package/lib/types/host/bundle-patch.d.ts +35 -0
- package/lib/types/host/catalog.d.ts +9 -3
- package/lib/types/host/compatibility.d.ts +223 -0
- package/lib/types/host/dsh-cli.d.ts +8 -0
- package/lib/types/host/harness.d.ts +109 -0
- package/lib/types/host/hot.d.ts +8 -8
- package/lib/types/host/index.d.ts +197 -28
- package/lib/types/host/install.d.ts +1 -1
- package/lib/types/host/peers.d.ts +181 -10
- package/lib/types/host/profile.d.ts +61 -4
- package/lib/types/host/restart.d.ts +5 -1
- package/lib/types/host/types.d.ts +27 -1
- package/lib/types/own-version.d.ts +18 -2
- package/package.json +2 -1
|
@@ -6,6 +6,10 @@ export declare const zh: {
|
|
|
6
6
|
error: string;
|
|
7
7
|
installedError: string;
|
|
8
8
|
retry: string;
|
|
9
|
+
/** The shop's own error boundary caught a render error; the error's message
|
|
10
|
+
* follows on its own line, and Retry remounts the tab (design
|
|
11
|
+
* 2026-09-26-market-borrowings §4). */
|
|
12
|
+
tabCrashed: string;
|
|
9
13
|
search: string;
|
|
10
14
|
all: string;
|
|
11
15
|
catalog: string;
|
|
@@ -35,12 +39,29 @@ export declare const zh: {
|
|
|
35
39
|
installedRestartNotice: string;
|
|
36
40
|
incompatibleBadge: string;
|
|
37
41
|
incompatibleDetail: string;
|
|
42
|
+
/** The author's own `dsh.compatibility`, unmet on this machine (design
|
|
43
|
+
* 2026-09-01 §8.2). Each names BOTH sides — what the author declared and
|
|
44
|
+
* what this dsh runs — because the host sends both, and a bare
|
|
45
|
+
* "unsupported" leaves the reader guessing which side is off. */
|
|
46
|
+
harnessRangeDetail: string;
|
|
47
|
+
harnessProfileDetail: string;
|
|
48
|
+
/** dsh's OWN refusal (0.1.7 on), not the author's claim: the install button
|
|
49
|
+
* is disabled beside it (design 2026-09-26-dsh-017-readiness, B1). `{peers}`
|
|
50
|
+
* is each refused peer with the range the package declared. The remedy
|
|
51
|
+
* leads into the exemption command, rendered on its own line after it, and
|
|
52
|
+
* names the Refresh button by its label. */
|
|
53
|
+
harnessPeersDetail: string;
|
|
54
|
+
harnessPeersRemedy: string;
|
|
38
55
|
hotNoPatchNotice: string;
|
|
39
56
|
hotNotSimpleNotice: string;
|
|
40
57
|
hotHostUnsupportedNotice: string;
|
|
41
58
|
hotTimeoutNotice: string;
|
|
42
59
|
hotMountFailedNotice: string;
|
|
43
|
-
|
|
60
|
+
/** No mount was tried: this dsh already loaded the package once, and Node
|
|
61
|
+
* will not load a second copy from the same place (design
|
|
62
|
+
* 2026-09-26-market-borrowings §1). Covers an update and a reinstall alike,
|
|
63
|
+
* so it names the new files rather than a "new version". */
|
|
64
|
+
hotAlreadyLoadedNotice: string;
|
|
44
65
|
installedNoRestartNotice: string;
|
|
45
66
|
installedReloadNotice: string;
|
|
46
67
|
reload: string;
|
|
@@ -65,6 +86,9 @@ export declare const zh: {
|
|
|
65
86
|
/** The incompatible filter's label. FIXED — the switch beside it carries the
|
|
66
87
|
* state, so the words name what the switch does and never flip with it. */
|
|
67
88
|
hideIncompatible: string;
|
|
89
|
+
/** What the filter takes away is every card whose badge reads 不兼容 —
|
|
90
|
+
* missing components OR an author's declaration this dsh does not meet —
|
|
91
|
+
* so these three name the badge, not one of its reasons. */
|
|
68
92
|
incompatibleFilterTitle: string;
|
|
69
93
|
showIncompatibleTitle: string;
|
|
70
94
|
emptyIncompatibleFiltered: string;
|
|
@@ -79,6 +103,7 @@ export declare const zh: {
|
|
|
79
103
|
tarballIntegrityCode: string;
|
|
80
104
|
ambiguousIdentityCode: string;
|
|
81
105
|
nameTakenCode: string;
|
|
106
|
+
desktopProfileCode: string;
|
|
82
107
|
nameTakenBadge: string;
|
|
83
108
|
nameTakenDetail: string;
|
|
84
109
|
npmSource: string;
|
|
@@ -101,7 +126,14 @@ export declare const zh: {
|
|
|
101
126
|
restartConfirm: string;
|
|
102
127
|
restarting: string;
|
|
103
128
|
restartFailedNotice: string;
|
|
129
|
+
/** The same failure, when the host said where the new process writes —
|
|
130
|
+
* which is where a boot that died explains itself (design
|
|
131
|
+
* 2026-09-26-market-borrowings §3). "Or did not stay up": the page now
|
|
132
|
+
* waits for the new server to keep answering, so this also covers a boot
|
|
133
|
+
* that answered and then died. */
|
|
134
|
+
restartFailedLogNotice: string;
|
|
104
135
|
restartTransportFailed: string;
|
|
136
|
+
restartBlockedDesktopNotice: string;
|
|
105
137
|
restartBlockedWindowsNotice: string;
|
|
106
138
|
restartBlockedSystemdNotice: string;
|
|
107
139
|
restartBlockedPortZeroNotice: string;
|
|
@@ -124,6 +156,7 @@ export declare const en: {
|
|
|
124
156
|
error: string;
|
|
125
157
|
installedError: string;
|
|
126
158
|
retry: string;
|
|
159
|
+
tabCrashed: string;
|
|
127
160
|
search: string;
|
|
128
161
|
all: string;
|
|
129
162
|
catalog: string;
|
|
@@ -153,12 +186,16 @@ export declare const en: {
|
|
|
153
186
|
installedRestartNotice: string;
|
|
154
187
|
incompatibleBadge: string;
|
|
155
188
|
incompatibleDetail: string;
|
|
189
|
+
harnessRangeDetail: string;
|
|
190
|
+
harnessProfileDetail: string;
|
|
191
|
+
harnessPeersDetail: string;
|
|
192
|
+
harnessPeersRemedy: string;
|
|
156
193
|
hotNoPatchNotice: string;
|
|
157
194
|
hotNotSimpleNotice: string;
|
|
158
195
|
hotHostUnsupportedNotice: string;
|
|
159
196
|
hotTimeoutNotice: string;
|
|
160
197
|
hotMountFailedNotice: string;
|
|
161
|
-
|
|
198
|
+
hotAlreadyLoadedNotice: string;
|
|
162
199
|
installedNoRestartNotice: string;
|
|
163
200
|
installedReloadNotice: string;
|
|
164
201
|
reload: string;
|
|
@@ -189,6 +226,7 @@ export declare const en: {
|
|
|
189
226
|
tarballIntegrityCode: string;
|
|
190
227
|
ambiguousIdentityCode: string;
|
|
191
228
|
nameTakenCode: string;
|
|
229
|
+
desktopProfileCode: string;
|
|
192
230
|
nameTakenBadge: string;
|
|
193
231
|
nameTakenDetail: string;
|
|
194
232
|
npmSource: string;
|
|
@@ -211,7 +249,9 @@ export declare const en: {
|
|
|
211
249
|
restartConfirm: string;
|
|
212
250
|
restarting: string;
|
|
213
251
|
restartFailedNotice: string;
|
|
252
|
+
restartFailedLogNotice: string;
|
|
214
253
|
restartTransportFailed: string;
|
|
254
|
+
restartBlockedDesktopNotice: string;
|
|
215
255
|
restartBlockedWindowsNotice: string;
|
|
216
256
|
restartBlockedSystemdNotice: string;
|
|
217
257
|
restartBlockedPortZeroNotice: string;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser half of the peer verdict (design 2026-09-01-harness-compatibility
|
|
3
|
+
* §9): a declared peer is missing only if NEITHER the host NOR this page's
|
|
4
|
+
* module table provides it.
|
|
5
|
+
*
|
|
6
|
+
* The host judges each entry's peers by node resolution from the profile
|
|
7
|
+
* (`host/peers.ts`), and that answers only half of how a client plugin's
|
|
8
|
+
* `require(x)` is served. The module table in `@deepseek-ai/dsh-client-modules`
|
|
9
|
+
* answers in this order: a platform SEED word; an already materialized module
|
|
10
|
+
* (`loadCache`); a graph row (`manifest.modules` — a `dsh.client` package in
|
|
11
|
+
* this profile's boot graph); a registered factory; otherwise it throws. Seed
|
|
12
|
+
* words never touch node resolution, and of the nine the 0.1.5-rc.3 web shell
|
|
13
|
+
* seeds only `@deepseek-ai/cordis` exists as a package on disk — so the host
|
|
14
|
+
* reports the other eight missing for every plugin that declares them, which
|
|
15
|
+
* on the live catalog made most of the host's badges false — `react` among
|
|
16
|
+
* them, the very module this shop's own client runs with. Design
|
|
17
|
+
* 2026-09-01-harness-compatibility §9.1 owns the measurement.
|
|
18
|
+
*
|
|
19
|
+
* So the host's list is never rendered as it arrives: every name the live
|
|
20
|
+
* table provides is removed first (`index.ts`, at the moment a result is
|
|
21
|
+
* handed to the tab). A pure core, `refineIncompatible`, driven by any oracle;
|
|
22
|
+
* and a thin shell, `moduleTableOracle`, over the harness's live object.
|
|
23
|
+
*/
|
|
24
|
+
/** What the module table says about one specifier: `true` provided, `false`
|
|
25
|
+
* absent, `null` unknown. Injected, so fixtures drive every verdict and only
|
|
26
|
+
* one oracle ever touches the harness. */
|
|
27
|
+
export type ModuleOracle = (spec: string) => Promise<boolean | null>;
|
|
28
|
+
/**
|
|
29
|
+
* How long one probe may take before its answer counts as unknown.
|
|
30
|
+
*
|
|
31
|
+
* The work a probe can actually reach is a microtask: a seed-word lookup, or a
|
|
32
|
+
* factory this page already registered. So this bounds a harness that never
|
|
33
|
+
* answers, not work — and it exists because the cost of the alternative is the
|
|
34
|
+
* whole shelf. `refineAgainstModuleTable` promises that a table in any state
|
|
35
|
+
* cannot cost the reader the catalog, and an `import()` that never settles
|
|
36
|
+
* would have made that promise false by leaving the tab with nothing to render
|
|
37
|
+
* (§9's degradation rule: an unavailable fact is silence, never an accusation).
|
|
38
|
+
* A probe per name, not one deadline for the whole refinement: every other
|
|
39
|
+
* name is still judged, and the tab waits at most this long however many names
|
|
40
|
+
* there are.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MODULE_PROBE_TIMEOUT_MS = 2000;
|
|
43
|
+
/**
|
|
44
|
+
* The host's verdict less every name the oracle provides.
|
|
45
|
+
*
|
|
46
|
+
* An entry whose list empties is dropped, and an entry ANY of whose names
|
|
47
|
+
* comes back unknown gets no verdict at all — the rule `incompatibilityMap`
|
|
48
|
+
* follows on the host, for the host's reason: an unavailable fact must never
|
|
49
|
+
* read as an accusation, and keeping the rest of a list whose one unknown name
|
|
50
|
+
* was dropped would publish a partial answer as the whole one. Each distinct
|
|
51
|
+
* name is asked once, however many entries declare it. Never rejects: any
|
|
52
|
+
* failure of the oracle is an unknown.
|
|
53
|
+
*/
|
|
54
|
+
export declare function refineIncompatible(incompatible: Readonly<Record<string, readonly string[]>>, provides: ModuleOracle, timeoutMs?: number): Promise<Record<string, string[]>>;
|
|
55
|
+
/**
|
|
56
|
+
* An oracle over the live `modules` service, or null when the page offers no
|
|
57
|
+
* usable table.
|
|
58
|
+
*
|
|
59
|
+
* The service is the harness's `ClientModuleLoader`, published to client
|
|
60
|
+
* plugins as `ctx.reflect.provide('modules', …)`. It arrives as unknown-typed
|
|
61
|
+
* foreign state, so only its PUBLIC contract is read, and only after
|
|
62
|
+
* narrowing: `manifest.modules` must be an array, `loadCache` a Map and
|
|
63
|
+
* `import` a function. `seed`, `factories` and `graphRows` exist on the
|
|
64
|
+
* instance too, but they are TypeScript-`private` in the harness's own
|
|
65
|
+
* declarations, and nothing here names them.
|
|
66
|
+
*
|
|
67
|
+
* Per spec, in the table's own order but with every side effect kept out:
|
|
68
|
+
* 1. A graph row provides it, and `import` is NOT called — for a row it
|
|
69
|
+
* fetches the bundle and runs the module body, and a probe must never be
|
|
70
|
+
* what loads a plugin. `manifest.modules` is the row index, but it is not
|
|
71
|
+
* fixed for the page's whole life on every harness build. On 0.1.5-rc.3
|
|
72
|
+
* `this.manifest = options.manifest` is the constructor's only write, so a
|
|
73
|
+
* row placed at boot stands unchanged for as long as the page lives. On
|
|
74
|
+
* 0.1.7-rc.2 (`next`) `updateManifest` REPLACES `this.manifest` outright
|
|
75
|
+
* with a new manifest that no longer lists an uninstalled package, and
|
|
76
|
+
* `reconcile` removes the plugin from the loader — so UNTIL this page's own
|
|
77
|
+
* uninstall reconciles, the OLD row still stands in `manifest.modules` and
|
|
78
|
+
* this step clears the entry falsely; AFTER it reconciles, this step can no
|
|
79
|
+
* longer be the false clear, because `manifest.modules` has already dropped
|
|
80
|
+
* the package. The false clear then comes from `prune`, which keeps the
|
|
81
|
+
* removed id only in the PRIVATE `graphRows` and in `loadCache`, and only
|
|
82
|
+
* while some retained module still references it — that is step 2 below,
|
|
83
|
+
* or the side effect of `import` in step 3 (a private graph row this
|
|
84
|
+
* function cannot see).
|
|
85
|
+
* 2. A `loadCache` record provides it, again without `import`.
|
|
86
|
+
* 3. Otherwise `import(spec, '', {})`, with the spec as written — the seed
|
|
87
|
+
* holds `react-dom/client` itself, not its stripped form. Resolving means a
|
|
88
|
+
* seed word, whose lookup has no side effect; rejecting with the
|
|
89
|
+
* "cannot resolve" message means absent; any other rejection is unknown.
|
|
90
|
+
*
|
|
91
|
+
* A kept-but-unmaterialized row is this function's own remaining blind spot:
|
|
92
|
+
* asked about here, it still reaches step 3's `import` probe, which fetches
|
|
93
|
+
* and runs it — the one side effect this module promises never to cause on
|
|
94
|
+
* its own account. `refineAgainstModuleTable`'s page-removed set is the fix
|
|
95
|
+
* for the one case a page can know about on its own — a name uninstalled
|
|
96
|
+
* through THIS shop, on THIS page, since it loaded — by bypassing this oracle
|
|
97
|
+
* for that name entirely rather than trusting a row that may only be kept. An
|
|
98
|
+
* uninstall made from another tab, another window, or the CLI is not in that
|
|
99
|
+
* set and stays unknown to this page until it reloads.
|
|
100
|
+
*
|
|
101
|
+
* One side-effect path remains: a registered page-local factory that is
|
|
102
|
+
* neither a graph row nor yet materialized, which `import` materializes. That
|
|
103
|
+
* is reachable only for a name the host could not resolve AND the page
|
|
104
|
+
* registered without a row — the shell's own page-local modules are
|
|
105
|
+
* node-resolvable, so the host never lists them. And what it does is what the
|
|
106
|
+
* first real `require` of that name does anyway: run a factory this page
|
|
107
|
+
* registered precisely so it would be materialized, once, memoized in
|
|
108
|
+
* `loadCache`, fetching nothing. It moves that moment earlier; it runs nothing
|
|
109
|
+
* the page did not already hold.
|
|
110
|
+
*/
|
|
111
|
+
export declare function moduleTableOracle(service: unknown): ModuleOracle | null;
|
|
112
|
+
/**
|
|
113
|
+
* The host's verdict as this page can stand behind it: refined against the
|
|
114
|
+
* live table, or `{}` — no verdicts at all — when there is no usable table.
|
|
115
|
+
*
|
|
116
|
+
* Never the host's list unrefined. Measured, that list is majority-false on
|
|
117
|
+
* this harness line (see the header), so showing it whenever the table cannot
|
|
118
|
+
* be read would hand the reader a verdict already known to be unreliable;
|
|
119
|
+
* silence is this project's documented degradation — an unavailable fact must
|
|
120
|
+
* never read as an accusation. Never rejects, so a table in any state cannot
|
|
121
|
+
* cost the reader the catalog.
|
|
122
|
+
*
|
|
123
|
+
* `removed` is the page-removed set (design 2026-09-01-harness-compatibility
|
|
124
|
+
* section 9.1): package names this page has uninstalled since it loaded.
|
|
125
|
+
* Every name in it is reported missing exactly
|
|
126
|
+
* as the host said, WITHOUT ever reaching the table — not the rows check, not
|
|
127
|
+
* `loadCache`, not `import` — because a row or a cache record can outlive a
|
|
128
|
+
* restart-free uninstall (`moduleTableOracle`'s header), and the table's own
|
|
129
|
+
* "provided" would be precisely the false clear this set exists to prevent.
|
|
130
|
+
*/
|
|
131
|
+
export declare function refineAgainstModuleTable(incompatible: Readonly<Record<string, readonly string[]>>, service: unknown, removed?: ReadonlySet<string>, timeoutMs?: number): Promise<Record<string, string[]>>;
|
|
@@ -5,7 +5,7 @@ export { isShopLike } from '../shared/shop-like.ts';
|
|
|
5
5
|
export { identityKey, type EntryIdentity } from '../shared/identity.ts';
|
|
6
6
|
import { type EntryIdentity } from '../shared/identity.ts';
|
|
7
7
|
import type { ShopLocaleKey } from './locales.ts';
|
|
8
|
-
import type { CatalogEntry, HotRestartReason, InstallRejectionCode, RestartBlockedReason } from '../host/index.ts';
|
|
8
|
+
import type { CatalogEntry, HarnessVerdict, HotRestartReason, InstallRejectionCode, RestartBlockedReason } from '../host/index.ts';
|
|
9
9
|
import type { Activation } from '../host/activation.ts';
|
|
10
10
|
import { type InstallState } from '../shared/install-state.ts';
|
|
11
11
|
export type { Activation };
|
|
@@ -50,6 +50,105 @@ export declare function tierKey(tier: CatalogEntry['tier']): ShopLocaleKey;
|
|
|
50
50
|
* both rendered as no warning at all. The map is keyed by install identity,
|
|
51
51
|
* never by name, because two entries can share a name. */
|
|
52
52
|
export declare function missingPeersOf(incompatible: Record<string, string[]>, key: string): string[];
|
|
53
|
+
/** The author's own `dsh.compatibility` that this installation does not meet
|
|
54
|
+
* (design 2026-09-01 §8.2), or undefined when the host gave no verdict. Keyed
|
|
55
|
+
* by install identity, never by name, for the reason `missingPeersOf` is.
|
|
56
|
+
*
|
|
57
|
+
* The map itself may be absent, whatever the result type says: a tab can
|
|
58
|
+
* outlive a shop self-update by one reload and then be talking to the OLD
|
|
59
|
+
* in-process host until dsh restarts, which answers the shape from before the
|
|
60
|
+
* key existed. That is "no verdict", never a crash. */
|
|
61
|
+
export declare function harnessVerdictOf(verdicts: Readonly<Record<string, HarnessVerdict>> | undefined, key: string): HarnessVerdict | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* One thing standing between an entry and a working install, carrying the
|
|
64
|
+
* facts its copy names — never the copy, which the tab localizes (§4: no copy
|
|
65
|
+
* crosses the RPC).
|
|
66
|
+
*
|
|
67
|
+
* Refusals and warnings, each with its own remedy. Two are refusals, and
|
|
68
|
+
* disable the button (`refusesInstall`): a taken name, which the host WILL
|
|
69
|
+
* refuse and the reader fixes by uninstalling a plugin; and harness peers the
|
|
70
|
+
* running dsh WILL refuse (0.1.7 on), fixed by dsh's exact-version exemption,
|
|
71
|
+
* whose command the host sends when dsh would accept one. Everything else is
|
|
72
|
+
* about this harness and is advisory — the host installs anyway: components
|
|
73
|
+
* node resolution and the page's module table both lack, and an author's
|
|
74
|
+
* declared range or profile list this dsh falls outside of.
|
|
75
|
+
*/
|
|
76
|
+
export type Blocker = {
|
|
77
|
+
kind: 'name-taken';
|
|
78
|
+
holder: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'harness-peers';
|
|
81
|
+
refused: Readonly<Record<string, string>>;
|
|
82
|
+
running: string;
|
|
83
|
+
allowCommand: string | null;
|
|
84
|
+
} | {
|
|
85
|
+
kind: 'missing-peers';
|
|
86
|
+
modules: readonly string[];
|
|
87
|
+
} | {
|
|
88
|
+
kind: 'harness-range';
|
|
89
|
+
range: string;
|
|
90
|
+
running: string;
|
|
91
|
+
} | {
|
|
92
|
+
kind: 'harness-profile';
|
|
93
|
+
declared: readonly string[];
|
|
94
|
+
running: string;
|
|
95
|
+
};
|
|
96
|
+
export type BlockerKind = Blocker['kind'];
|
|
97
|
+
/**
|
|
98
|
+
* Everything standing in one entry's way, in the order every surface renders
|
|
99
|
+
* it — the card's detail lines, the badge's accessible name, the outdated
|
|
100
|
+
* row's refusal — and the one list the incompatible filter reads, through
|
|
101
|
+
* `readsIncompatible`, and the install button, through `refusesInstall`.
|
|
102
|
+
*
|
|
103
|
+
* The refusals read first. The name conflict leads: it is about a plugin the
|
|
104
|
+
* reader chose and would lose. Then dsh's own refusal, then what this machine
|
|
105
|
+
* is measured to lack, then what the author declared, range before profiles —
|
|
106
|
+
* the order the host's verdict carries its halves.
|
|
107
|
+
*/
|
|
108
|
+
export declare function blockersOf(missing: readonly string[], harness: HarnessVerdict | undefined, nameTakenBy: string | undefined): Blocker[];
|
|
109
|
+
/**
|
|
110
|
+
* The badge's visible word for these blockers, or null when there are none.
|
|
111
|
+
*
|
|
112
|
+
* "Incompatible" for anything about this harness, whichever reason put it
|
|
113
|
+
* there. A taken name is the more serious condition and names a different
|
|
114
|
+
* remedy, so it decides the word whenever it holds — the card is perfectly
|
|
115
|
+
* compatible, and a different plugin is in the way.
|
|
116
|
+
*/
|
|
117
|
+
export declare function blockerBadgeKey(blockers: readonly {
|
|
118
|
+
kind: BlockerKind;
|
|
119
|
+
}[]): ShopLocaleKey | null;
|
|
120
|
+
/** Whether the badge reads "Incompatible" — exactly the set the incompatible
|
|
121
|
+
* filter counts and takes away. Derived from the badge's own rule, never
|
|
122
|
+
* restated beside it: the filter used to test the peer list by hand, which
|
|
123
|
+
* held only while missing peers were the one reason for the word — a card
|
|
124
|
+
* badged for its declared range would have read "Incompatible" and survived
|
|
125
|
+
* the filter that names it. */
|
|
126
|
+
export declare function readsIncompatible(blockers: readonly {
|
|
127
|
+
kind: BlockerKind;
|
|
128
|
+
}[]): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Whether this install is refused before it starts, so its button is
|
|
131
|
+
* disabled rather than opening the acknowledgement gate for an install that
|
|
132
|
+
* cannot proceed: by the host, for a taken name, or by the running dsh, for
|
|
133
|
+
* its harness peers.
|
|
134
|
+
*
|
|
135
|
+
* The second is the one harness reason that blocks (design
|
|
136
|
+
* 2026-09-26-dsh-017-readiness, B1, amending "warn, never block"): dsh 0.1.7
|
|
137
|
+
* refuses the install itself, before pnpm runs, and the shop cannot install
|
|
138
|
+
* past it. The verdict is dsh's own rule on the profile's own exemptions, so
|
|
139
|
+
* a disabled button is dsh's answer given early, never the shop's opinion —
|
|
140
|
+
* and recording the exemption the card shows is what enables it again.
|
|
141
|
+
*/
|
|
142
|
+
export declare function refusesInstall(blockers: readonly {
|
|
143
|
+
kind: BlockerKind;
|
|
144
|
+
}[]): boolean;
|
|
145
|
+
/**
|
|
146
|
+
* The refused peers as the refusal's copy names them: each peer and the range
|
|
147
|
+
* the package declared, in the order dsh reported them. A blank range — which
|
|
148
|
+
* dsh refuses — is quoted, or the sentence would name the peer and nothing
|
|
149
|
+
* it asked for.
|
|
150
|
+
*/
|
|
151
|
+
export declare function refusedPeersText(refused: Readonly<Record<string, string>>): string;
|
|
53
152
|
/**
|
|
54
153
|
* The plugin already holding this entry's bundle name, if a DIFFERENT one is,
|
|
55
154
|
* named the way the host's refusal names it.
|
|
@@ -160,8 +259,33 @@ export declare const INSTALL_POLL_MS = 1000;
|
|
|
160
259
|
* the NEW server, never the dying old one. */
|
|
161
260
|
export declare const RESTART_GRACE_MS = 3000;
|
|
162
261
|
/** §8 restart handoff: how long the client waits for the new server to
|
|
163
|
-
*
|
|
262
|
+
* START answering before reporting the manual restart command. A run of good
|
|
263
|
+
* answers that began inside it may finish after it (see
|
|
264
|
+
* `restartMonitorVerdict`). */
|
|
164
265
|
export declare const RESTART_WAIT_MS = 30000;
|
|
266
|
+
/** §8 restart handoff: how long the new server must keep answering before the
|
|
267
|
+
* page reloads into it (design 2026-09-26-market-borrowings §3). The Loader
|
|
268
|
+
* mounts entries concurrently, so a boot that is about to fail can bind the
|
|
269
|
+
* port and answer before the settled tree is audited and the process exits;
|
|
270
|
+
* dsh-market measured that window inside 8 s. */
|
|
271
|
+
export declare const RESTART_STABLE_MS = 8000;
|
|
272
|
+
/**
|
|
273
|
+
* The restart monitor's decision after a probe of the origin.
|
|
274
|
+
*
|
|
275
|
+
* `stableSinceMs` is when the current unbroken run of good answers began —
|
|
276
|
+
* null when the last probe failed, or none has succeeded yet — and both times
|
|
277
|
+
* count from the moment the restart was committed. One answer is not enough:
|
|
278
|
+
* reloading on it is what landed a reader on a boot that died a moment later,
|
|
279
|
+
* a blank page in place of the notice naming the log. So the page reloads
|
|
280
|
+
* only once the run has lasted `RESTART_STABLE_MS`, and the monitor gives up
|
|
281
|
+
* only when no run is under way past `RESTART_WAIT_MS` — which bounds the
|
|
282
|
+
* whole wait at the sum of the two, since a run that began in time either
|
|
283
|
+
* completes or breaks after the deadline.
|
|
284
|
+
*/
|
|
285
|
+
export declare function restartMonitorVerdict(input: {
|
|
286
|
+
elapsedMs: number;
|
|
287
|
+
stableSinceMs: number | null;
|
|
288
|
+
}): 'wait' | 'reload' | 'failed';
|
|
165
289
|
/** How long the check-update button reports "up to date" after a re-check
|
|
166
290
|
* finds no newer release, before reverting to its idle label. */
|
|
167
291
|
export declare const CHECK_UP_TO_DATE_MS = 3000;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The restart handoff's browser half (design §8), held by the page rather
|
|
3
|
+
* than by the tab that confirmed the restart.
|
|
4
|
+
*
|
|
5
|
+
* It lived in a RestartPanel effect until 2026-09-27, and a self-update
|
|
6
|
+
* killed it. dsh's client HMR (`@deepseek-ai/dsh-client-hmr`, mounted by the
|
|
7
|
+
* web bundle unconditionally) stat-polls every client bundle and hot-swaps
|
|
8
|
+
* one whose file changed; a self-update rewrites this package's own
|
|
9
|
+
* `lib/client.js`, so the swap disposes the tab's fiber and mounts a fresh
|
|
10
|
+
* instance of a freshly imported module. The effect's cleanup then stopped
|
|
11
|
+
* the probe, and once the restart finished nothing reloaded the page: it sat
|
|
12
|
+
* on the old process's answers while dsh's socket quietly reconnected to the
|
|
13
|
+
* new one. Measured on dsh 0.1.5-rc.3 updating 0.8.2 to 0.8.3; with the
|
|
14
|
+
* `/plugins/events` channel blocked the same flow reloaded.
|
|
15
|
+
*
|
|
16
|
+
* So the state sits on `globalThis` under a registry symbol — the one thing a
|
|
17
|
+
* swapped-in module instance shares with the one it replaced — and the probe
|
|
18
|
+
* loop is plain timers owned by no fiber. Any tab reads the state, so the
|
|
19
|
+
* replacement renders the restart under way instead of offering another.
|
|
20
|
+
*/
|
|
21
|
+
/** A restart this page committed: still waiting for the new server, or
|
|
22
|
+
* given up on it. `logFile` is where the host said the new process writes;
|
|
23
|
+
* a host older than that field sends none. */
|
|
24
|
+
export type RestartMonitorState = {
|
|
25
|
+
kind: 'restarting';
|
|
26
|
+
logFile?: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'failed';
|
|
29
|
+
logFile?: string;
|
|
30
|
+
};
|
|
31
|
+
/** The page's restart, or null when it has committed none. The same object
|
|
32
|
+
* until the state changes, as `useSyncExternalStore` requires. */
|
|
33
|
+
export declare function readRestartMonitor(): RestartMonitorState | null;
|
|
34
|
+
/** Be told when the page's restart changes state. */
|
|
35
|
+
export declare function subscribeRestartMonitor(listener: () => void): () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Watch the origin for the restarted server and reload into it: probe once
|
|
38
|
+
* the grace period is over (the host exits within it, so an answer is the NEW
|
|
39
|
+
* server), and reload only after it has KEPT answering for the stable window.
|
|
40
|
+
* A boot that is about to fail can bind the port and answer before its plugin
|
|
41
|
+
* tree is audited and it exits; reloading on that one answer left the reader
|
|
42
|
+
* on a blank page instead of the notice naming the log (design
|
|
43
|
+
* 2026-09-26-market-borrowings §3). `restartMonitorVerdict` decides; this
|
|
44
|
+
* loop only probes. Probes never overlap: each is scheduled one poll interval
|
|
45
|
+
* after the previous one settled.
|
|
46
|
+
*
|
|
47
|
+
* A restart already being watched is left alone — every offer on the page
|
|
48
|
+
* renders this state, so a second press can only come from a panel that has
|
|
49
|
+
* not re-rendered yet, and a second loop would probe the same origin twice.
|
|
50
|
+
*/
|
|
51
|
+
export declare function startRestartMonitor(options: {
|
|
52
|
+
reload: () => void;
|
|
53
|
+
logFile?: string;
|
|
54
|
+
}): void;
|
|
55
|
+
/** Stop the probe loop and forget the page's restart. Nothing in the page
|
|
56
|
+
* calls this — a committed restart ends in a reload or a failure — so its
|
|
57
|
+
* caller is a test isolating one case from the next. */
|
|
58
|
+
export declare function resetRestartMonitor(): void;
|
|
@@ -35,18 +35,21 @@ export type Activation = 'live' | 'reload' | 'restart';
|
|
|
35
35
|
* - A change to the BOOT COMPOSITION — a toggle, an uninstall — moves an
|
|
36
36
|
* entry the registry already enumerates, and the served graph follows it
|
|
37
37
|
* within seconds (§2, measured 2026-09-11). `true`.
|
|
38
|
-
* - A HOT MOUNT — an install
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* whole time. `false` — and a `restart` is then the only honest answer,
|
|
44
|
-
* because there is nothing a reload could fetch.
|
|
38
|
+
* - A HOT MOUNT — an install — adds entries under the shop's own loader
|
|
39
|
+
* entry, which the registry enumerates like any other: across a reload
|
|
40
|
+
* following the mount the served graph gains the package and the page runs
|
|
41
|
+
* its browser half (measured 2026-09-26 on 0.1.5-rc.3 and 0.1.7-rc.2, in
|
|
42
|
+
* `web-full-flow.e2e.ts`). `true`.
|
|
45
43
|
*
|
|
46
44
|
* The asymmetry that decides every unknown still runs the same way: offering
|
|
47
45
|
* a step that was not needed costs the reader one action, withholding one
|
|
48
|
-
* that was needed is the defect this module exists to fix.
|
|
49
|
-
*
|
|
46
|
+
* that was needed is the defect this module exists to fix. Which step a hot
|
|
47
|
+
* mount needs has been measured twice. On 2026-09-14 the graph came back
|
|
48
|
+
* byte-identical across the reload, and installs moved to `restart` — but the
|
|
49
|
+
* shop then registered the tree from whichever context made the RPC call, the
|
|
50
|
+
* typert gateway's, and the registry never composed a tree hung there. Once
|
|
51
|
+
* the tree hung off the shop's own entry (2026-09-26), the same measurement
|
|
52
|
+
* found the package in the graph, and installs moved back to `reload`.
|
|
50
53
|
*/
|
|
51
54
|
export declare function activationOf(input: {
|
|
52
55
|
hostLive: boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The patch files a bundle declares, read the way dsh reads them (design
|
|
3
|
+
* 2026-09-26-dsh-017-readiness, B4).
|
|
4
|
+
*
|
|
5
|
+
* dsh 0.1.7's app-boot (`bundlePatchFiles`) accepts `dsh.bundle.patch` as one
|
|
6
|
+
* package-relative file or as a list of them, applied in order, and throws on
|
|
7
|
+
* anything else. dsh 0.1.5 reads only the string form: it joins the
|
|
8
|
+
* declaration onto the package directory, so a list makes the profile fail to
|
|
9
|
+
* start. What the RUNNING dsh accepts is `RunningHarness.patchLists`; this
|
|
10
|
+
* reads the declaration itself, for the shop's own readers of a package's
|
|
11
|
+
* patch — the entries it owns, the rows a hot mount replicates.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The declared patch files in application order: one for a string, the
|
|
15
|
+
* listed ones for a list of strings. Null for a declaration dsh refuses —
|
|
16
|
+
* neither a path nor a list of paths — which is distinct from an absent one:
|
|
17
|
+
* only absence means the conventional `cordis.patch.yml`, and a caller that
|
|
18
|
+
* fell back to it for a refused declaration would act on a patch dsh never
|
|
19
|
+
* applies. Pure, and the list is a copy.
|
|
20
|
+
*/
|
|
21
|
+
export declare function bundlePatchFiles(declared: unknown): string[] | null;
|
|
22
|
+
/** Why a bundle patch declaration would stop the profile from starting:
|
|
23
|
+
* `list-unsupported` is a list on a dsh that reads one file, `malformed` a
|
|
24
|
+
* declaration no dsh loads. */
|
|
25
|
+
export type PatchHazard = 'list-unsupported' | 'malformed';
|
|
26
|
+
/**
|
|
27
|
+
* Whether an installed package's `dsh.bundle.patch` would stop the profile
|
|
28
|
+
* from starting at the next boot, given whether the running dsh reads a list
|
|
29
|
+
* (`RunningHarness.patchLists`). An absent declaration is no hazard: dsh adds
|
|
30
|
+
* a package to the profile's bundles only when it declares a patch, so
|
|
31
|
+
* nothing reads the field. An unknown harness (null) forms no verdict on a
|
|
32
|
+
* list — the rule for every harness read — but a malformed declaration is
|
|
33
|
+
* refused by every dsh, so it is named whatever is known. Pure.
|
|
34
|
+
*/
|
|
35
|
+
export declare function patchDeclarationHazard(declared: unknown, patchLists: boolean | null): PatchHazard | null;
|
|
@@ -5,9 +5,15 @@ import type { CatalogEntry, DeniedEntry } from './types.ts';
|
|
|
5
5
|
* 3 adds `source` and repo entries (github install channel); 4 adds `subdir`
|
|
6
6
|
* for monorepo-subpackage entries (2026-08-31 hub-borrowings A); 5 adds
|
|
7
7
|
* `added`, `tarball` (release rescue), the `theme` category, and
|
|
8
|
-
* `denied[].replacement` (2026-08-31 market borrowings)
|
|
9
|
-
*
|
|
10
|
-
* (2026-09-01 harness
|
|
8
|
+
* `denied[].replacement` (2026-08-31 market borrowings).
|
|
9
|
+
*
|
|
10
|
+
* 6 adds nothing. It was reserved for `peers` (2026-09-01 harness
|
|
11
|
+
* compatibility), and that gate came off on 2026-09-03 without ever being
|
|
12
|
+
* opened: `peers` rides 5, as every additive optional key does, because this
|
|
13
|
+
* schema strips a key it does not know while a higher version NUMBER is what
|
|
14
|
+
* a capped client refuses outright. Every shop built since this constant
|
|
15
|
+
* became 6 accepts a 6 without knowing what one would mean, so a change those
|
|
16
|
+
* shops must refuse has to be 7. */
|
|
11
17
|
export declare const SUPPORTED_SCHEMA_VERSION = 6;
|
|
12
18
|
export interface CatalogSnapshot {
|
|
13
19
|
schemaVersion: number;
|