dsh-plugin-shop 0.8.5 → 0.8.6
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/client.js +1788 -358
- package/lib/index.js +87 -33
- package/lib/typert.host.js +20 -15
- package/lib/typert.remote-client.js +16 -11
- package/lib/types/client/ShopTab.d.ts +6 -0
- package/lib/types/client/index.d.ts +11 -0
- package/lib/types/client/locales.d.ts +32 -0
- package/lib/types/client/present.d.ts +21 -1
- package/lib/types/client/restart-monitor.d.ts +38 -7
- package/lib/types/client/useFlows.d.ts +6 -0
- package/lib/types/client/useUpdateSelf.d.ts +5 -0
- package/lib/types/host/index.d.ts +21 -2
- package/lib/types/host/restart.d.ts +9 -1
- package/lib/types/host/types.d.ts +4 -0
- package/lib/types/own-version.d.ts +13 -1
- package/lib/types/shared/zh-intents.d.ts +89 -0
- package/package.json +1 -1
|
@@ -30,12 +30,24 @@
|
|
|
30
30
|
* records the request before anything is awaited, and a request whose path is
|
|
31
31
|
* withdrawn mid-call is asked again through a live one. The host answers a
|
|
32
32
|
* repeat with the restart it already committed.
|
|
33
|
+
*
|
|
34
|
+
* The VERDICT outlives the page, since 2026-10-06. The monitor reloads on
|
|
35
|
+
* any origin that keeps answering, and a process that never exited answers
|
|
36
|
+
* as steadily as one that replaced it: on macOS a confirmed restart reloaded
|
|
37
|
+
* into the very process it had asked to go, and the row went on showing the
|
|
38
|
+
* version before the update with nothing to say why. A commit therefore
|
|
39
|
+
* records, in this tab's sessionStorage, which process committed it (its
|
|
40
|
+
* boot identity and pid), and every version answer the reloaded page
|
|
41
|
+
* receives is judged against that record (`noteBoot`).
|
|
33
42
|
*/
|
|
34
43
|
import type { ShopRestartResult } from '../host/index.ts';
|
|
35
44
|
/** What this page is doing about a restart: waiting for the host to answer
|
|
36
|
-
* the confirm, waiting for the new server once it committed,
|
|
37
|
-
* that server
|
|
38
|
-
*
|
|
45
|
+
* the confirm, waiting for the new server once it committed, given up on
|
|
46
|
+
* that server, or reloaded into the process that was asked to restart and
|
|
47
|
+
* found it still answering — `pid` is that process. `logFile` is where the
|
|
48
|
+
* host said the new process writes; a host older than that field sends
|
|
49
|
+
* none, and one older than the boot identity sends no `pid` and is never
|
|
50
|
+
* judged `not-restarted` at all. */
|
|
39
51
|
export type RestartMonitorState = {
|
|
40
52
|
kind: 'requesting';
|
|
41
53
|
} | {
|
|
@@ -44,6 +56,10 @@ export type RestartMonitorState = {
|
|
|
44
56
|
} | {
|
|
45
57
|
kind: 'failed';
|
|
46
58
|
logFile?: string;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'not-restarted';
|
|
61
|
+
pid?: number;
|
|
62
|
+
logFile?: string;
|
|
47
63
|
};
|
|
48
64
|
/** How one press ended, for the panel that made it: the page follows the
|
|
49
65
|
* restart from here, the host refused it, or it never reached a host that
|
|
@@ -79,6 +95,8 @@ export declare function subscribeRestartMonitor(listener: () => void): () => voi
|
|
|
79
95
|
export declare function startRestartMonitor(options: {
|
|
80
96
|
reload: () => void;
|
|
81
97
|
logFile?: string;
|
|
98
|
+
bootId?: string;
|
|
99
|
+
pid?: number;
|
|
82
100
|
}): void;
|
|
83
101
|
/**
|
|
84
102
|
* Register one module instance's path to the host; the client half's apply
|
|
@@ -107,8 +125,21 @@ export declare function requestRestart(options: {
|
|
|
107
125
|
reload: () => void;
|
|
108
126
|
restart: () => Promise<ShopRestartResult>;
|
|
109
127
|
}): Promise<RestartRequestOutcome>;
|
|
110
|
-
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* the
|
|
128
|
+
/**
|
|
129
|
+
* Judge this tab's last committed restart by the process answering now. The
|
|
130
|
+
* client half hands every `version()` answer here (index.ts), so the verdict
|
|
131
|
+
* is made on the first answer a reloaded page receives, before any tab opens.
|
|
132
|
+
*
|
|
133
|
+
* The same boot as the one that committed is a restart that never happened:
|
|
134
|
+
* the page says so, naming that process, for as long as it keeps answering
|
|
135
|
+
* — across reloads too, since the record stays. Any other answer, an older
|
|
136
|
+
* host's included, is a process that replaced it, and the record is spent.
|
|
137
|
+
* Nothing is judged while this page's own restart is under way: the process
|
|
138
|
+
* that committed answers until it exits, two seconds later.
|
|
139
|
+
*/
|
|
140
|
+
export declare function noteBoot(bootId: string | undefined): void;
|
|
141
|
+
/** Stop the probe loop and forget the page's restart, its request, every
|
|
142
|
+
* registered path and the tab's record of its last commit. Nothing in the
|
|
143
|
+
* page calls this — a committed restart ends in a reload or a failure — so
|
|
144
|
+
* its caller is a test isolating one case from the next. */
|
|
114
145
|
export declare function resetRestartMonitor(): void;
|
|
@@ -40,6 +40,12 @@ export interface UseKeyedFlows<TArgs> {
|
|
|
40
40
|
* registry's settle callback can supersede the other's receipt for the same
|
|
41
41
|
* key without taking a dependency on that registry's output. */
|
|
42
42
|
resetFlow: (key: string) => void;
|
|
43
|
+
/** Return every identity whose view `predicate` selects to `idle`, and
|
|
44
|
+
* nothing else. Stable across renders like `resetFlow`; a call selecting
|
|
45
|
+
* nothing leaves the registry — and so every flow and `pending` — exactly
|
|
46
|
+
* as it was. The tab clears the receipts dsh restarting under it settled
|
|
47
|
+
* with this (design §8, 2026-10-06 amendment). */
|
|
48
|
+
resetWhere: (predicate: (view: InstallView) => boolean) => void;
|
|
43
49
|
/**
|
|
44
50
|
* The identities whose flow is not `idle` — asked by the shelf, which has
|
|
45
51
|
* to keep a settled uninstall's row in the Installed view after the
|
|
@@ -10,6 +10,11 @@ export interface UseUpdateSelfResult {
|
|
|
10
10
|
start: (args: {
|
|
11
11
|
version: string;
|
|
12
12
|
}) => Promise<void>;
|
|
13
|
+
/** Return the view to `idle` when `predicate` selects it; stable across
|
|
14
|
+
* renders. The keyed registries' `resetWhere`, for the one self-update:
|
|
15
|
+
* dsh restarting under the page settles an update that was waiting for
|
|
16
|
+
* exactly that (design §8, 2026-10-06 amendment). */
|
|
17
|
+
resetWhere: (predicate: (view: InstallView) => boolean) => void;
|
|
13
18
|
}
|
|
14
19
|
/** Drive one self-update: the host's business union, the shared polling
|
|
15
20
|
* loop, and terminal states. The `rejected` install state never occurs. */
|
|
@@ -237,6 +237,17 @@ export interface ShopVersionResult {
|
|
|
237
237
|
* the process running (design §7.3, 2026-09-27 amendment).
|
|
238
238
|
*/
|
|
239
239
|
pendingVersion?: string | null;
|
|
240
|
+
/**
|
|
241
|
+
* Which process answered (own-version.ts `bootId`): fixed for its life and
|
|
242
|
+
* different in the next. The client compares it across a reconnect — a new
|
|
243
|
+
* one is dsh restarted under the page — and across a restart's reload —
|
|
244
|
+
* the same one is a restart that never happened (design §8, 2026-10-06
|
|
245
|
+
* amendment).
|
|
246
|
+
*
|
|
247
|
+
* This host always sends it. Absent, it comes from a host older than the
|
|
248
|
+
* field, and the client judges nothing by it.
|
|
249
|
+
*/
|
|
250
|
+
bootId?: string;
|
|
240
251
|
}
|
|
241
252
|
/** `shop/updateStart` result (§7.3): the self-update spawn, or a typed
|
|
242
253
|
* refusal (a version that is not plain semver). */
|
|
@@ -251,16 +262,24 @@ export type ShopUpdateResult = {
|
|
|
251
262
|
/** `shop/installed` entry (§7.3): one installed catalog plugin. The identity
|
|
252
263
|
* fields distinguish same-named npm and GitHub rows; `installed` remains the
|
|
253
264
|
* manifest spec or the recorded GitHub pin, and the client never does version
|
|
254
|
-
* math.
|
|
265
|
+
* math.
|
|
266
|
+
*
|
|
267
|
+
* `shadowed` marks a row synthesized from the profile manifest for an
|
|
268
|
+
* installed GitHub dependency whose name an npm catalog entry now holds: the
|
|
269
|
+
* catalog row itself is refused `shadowed-by-npm`, so this row is the only
|
|
270
|
+
* one the user can act on. It carries no catalog `latest` (hence the union)
|
|
271
|
+
* and no `outdated` verdict, because there is no catalog truth to compare
|
|
272
|
+
* against. */
|
|
255
273
|
export interface ShopInstalledEntry {
|
|
256
274
|
name: string;
|
|
257
275
|
source: 'npm' | 'github';
|
|
258
276
|
repo?: string;
|
|
259
277
|
subdir?: string;
|
|
260
278
|
installed: string;
|
|
261
|
-
latest: string;
|
|
279
|
+
latest: string | null;
|
|
262
280
|
outdated: boolean;
|
|
263
281
|
enabled: boolean;
|
|
282
|
+
shadowed?: true;
|
|
264
283
|
}
|
|
265
284
|
/** How many bytes a release tarball may be at the integrity check. The
|
|
266
285
|
* registry already refuses to publish a tarball over 32 MiB, so 64 MiB is
|
|
@@ -15,10 +15,18 @@
|
|
|
15
15
|
* manual command and `logFile`, the file the new process's output goes to:
|
|
16
16
|
* where a boot that died explains itself, at a path that depends on DSH_HOME
|
|
17
17
|
* and the shop row's `cacheDir`, which only the host knows. A host older than
|
|
18
|
-
* the field answers without it, so the client reads it as optional.
|
|
18
|
+
* the field answers without it, so the client reads it as optional.
|
|
19
|
+
*
|
|
20
|
+
* `bootId` and `pid` name the process that committed: its boot identity
|
|
21
|
+
* (own-version.ts) and the pid the takeover waits on. A page reloaded into a
|
|
22
|
+
* process with that same boot is a restart that never happened, and the pid
|
|
23
|
+
* is what the reader has to stop by hand (design §8, 2026-10-06 amendment).
|
|
24
|
+
* Optional for the same older-host reason as `logFile`. */
|
|
19
25
|
export type RestartOutcome = {
|
|
20
26
|
ok: true;
|
|
21
27
|
logFile?: string;
|
|
28
|
+
bootId?: string;
|
|
29
|
+
pid?: number;
|
|
22
30
|
} | {
|
|
23
31
|
ok: false;
|
|
24
32
|
detail: string;
|
|
@@ -109,6 +109,10 @@ export interface CatalogEntry {
|
|
|
109
109
|
* 6,492 live github entries — which is the plugin's own cost and not the
|
|
110
110
|
* repository's; registry `Entry.installSize` carries the measured caveat. */
|
|
111
111
|
installSize?: number;
|
|
112
|
+
/** Whether GitHub reports the repository as archived (design 2026-10-08
|
|
113
|
+
* §1.3). github entries only, additive and optional: the shelf badges it
|
|
114
|
+
* and nothing else — install, update, and enable are unchanged. */
|
|
115
|
+
archived?: boolean;
|
|
112
116
|
}
|
|
113
117
|
export interface DeniedEntry {
|
|
114
118
|
name: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** The shop's own published version, read from the package.json that ships
|
|
2
2
|
* next to this package — the RUNNING version, not the manifest's range
|
|
3
|
-
* spec
|
|
3
|
+
* spec — and the identity of the process running it (`bootId`). This lives
|
|
4
|
+
* at the package root (not under src/host) on purpose:
|
|
4
5
|
* both the source tree (tests) and the bundled `lib/index.js` sit exactly
|
|
5
6
|
* one level below the package root, so the same relative URL resolves in
|
|
6
7
|
* both. */
|
|
@@ -21,6 +22,17 @@ export declare function versionAtLoad(read: () => string): () => string;
|
|
|
21
22
|
/** The version of the code in this process, read when the module was first
|
|
22
23
|
* evaluated — at boot, before any self-update could rewrite the file. */
|
|
23
24
|
export declare const runningVersion: () => string;
|
|
25
|
+
/**
|
|
26
|
+
* Which run of the code this is: drawn when the module is first evaluated,
|
|
27
|
+
* fixed for the life of the process, and different in the next one.
|
|
28
|
+
*
|
|
29
|
+
* The version cannot answer that. A restart that installed no new shop keeps
|
|
30
|
+
* it, and so does one that never happened — which is what a page reloaded
|
|
31
|
+
* into the process it asked to restart could not tell apart from success,
|
|
32
|
+
* and showed as a self-update that silently did not land (design §8,
|
|
33
|
+
* 2026-10-06 amendment).
|
|
34
|
+
*/
|
|
35
|
+
export declare const bootId: string;
|
|
24
36
|
/**
|
|
25
37
|
* The peer ranges this build declares — the input to the load-time harness
|
|
26
38
|
* self-check. Read from the same shipped manifest as `runningVersion`, so the
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Chinese → English query dictionary whose expansion the shelf search
|
|
3
|
+
* applies (design `2026-10-08-search-query-expansion.md` §2–§4, borrowing C2
|
|
4
|
+
* from `2026-09-26-market-borrowings.md`; reference: 2BingLing/dsh-market's
|
|
5
|
+
* `plugin/core/src/zh-intent.ts`).
|
|
6
|
+
*
|
|
7
|
+
* The dictionary is a curated DATA table, not code. It lives here, in
|
|
8
|
+
* `src/shared/` (mirroring `src/shared/identity.ts`), because the seam is
|
|
9
|
+
* one shared filter module and a later consumer in `host/` should not have
|
|
10
|
+
* to change paths to use it; today the only caller is `client/present.ts`.
|
|
11
|
+
*
|
|
12
|
+
* One intent is `{ key, words, terms }`:
|
|
13
|
+
* - `words` are the Chinese trigger phrases. A query containing any of them,
|
|
14
|
+
* as a lowercase substring, fires the intent. They are always ≥ 2 Han
|
|
15
|
+
* characters so a stray Han in an English query never fires one.
|
|
16
|
+
* - `terms` are the recall vocabulary scanned against each entry's name,
|
|
17
|
+
* `summary.en`, `summary.zh` and category key. A term prefixed with `#`
|
|
18
|
+
* is matched on a WORD BOUNDARY, not as a substring: `#ai` refuses to
|
|
19
|
+
* fire on `email`, `main`, `pipeline` and fires on `ai-chat`, `AI聊天`.
|
|
20
|
+
* The marker exists because, without it, 2BingLing measured 713 false
|
|
21
|
+
* `ai` hits in 9,145 entries; on our own 2026-10-08 catalog (14,167
|
|
22
|
+
* entries), `#ai` holds `AI大模型` to 2,760 hits instead of the many
|
|
23
|
+
* thousand the bare token would match.
|
|
24
|
+
*
|
|
25
|
+
* Credit: the trigger-to-recall structure, the `#` boundary marker, and
|
|
26
|
+
* the "curated, defensive, every-term-measured" discipline are all from
|
|
27
|
+
* 2BingLing/dsh-market's `zh-intent.ts`, read live 2026-10-08. The
|
|
28
|
+
* dictionary's rows are our own, trimmed from theirs against the live
|
|
29
|
+
* catalog (`2026-10-08-search-query-expansion.md` §3): the four intents
|
|
30
|
+
* their data also shows to be dead in our ecosystem (`农历节日`, `Twitter`,
|
|
31
|
+
* `微博`, `Notion`) were dropped because their marginal recall was 3 to 7.
|
|
32
|
+
*/
|
|
33
|
+
/** One curated intent. UI surfaces name `key` when they cite the expansion. */
|
|
34
|
+
export interface ZhIntent {
|
|
35
|
+
/** The intent's label, always a short Chinese phrase. */
|
|
36
|
+
key: string;
|
|
37
|
+
/** Trigger words: any substring match on the lowercased query fires it. */
|
|
38
|
+
words: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Recall terms: any substring match (or word-boundary match, for a term
|
|
41
|
+
* prefixed with `#`) on the lowercased entry fields recalls that entry.
|
|
42
|
+
*/
|
|
43
|
+
terms: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The recall side of an intent that fired for a query.
|
|
47
|
+
*
|
|
48
|
+
* The result is always deduplicated and ordered by the dictionary's rows;
|
|
49
|
+
* the query itself, lowercased, is always the FIRST element of `terms`, so
|
|
50
|
+
* the 18 author-written `summary.zh` entries today are always reachable
|
|
51
|
+
* through the raw query and never depend on a dictionary expansion.
|
|
52
|
+
*/
|
|
53
|
+
export interface ZhExpansion {
|
|
54
|
+
/** The user's query, trimmed and lowercased. */
|
|
55
|
+
query: string;
|
|
56
|
+
/** The intent keys that fired, in dictionary order. */
|
|
57
|
+
intents: readonly string[];
|
|
58
|
+
/** The fired intents with their surviving recall terms, deduplicated. */
|
|
59
|
+
expansions: readonly {
|
|
60
|
+
intent: string;
|
|
61
|
+
terms: readonly string[];
|
|
62
|
+
}[];
|
|
63
|
+
/** Every recall term, deduplicated, query first. */
|
|
64
|
+
terms: readonly string[];
|
|
65
|
+
}
|
|
66
|
+
export declare function tokenInText(term: string, text: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The curated dictionary. Ordered by design §3's measured marginal recall
|
|
69
|
+
* (largest first). Rows trimmed from 2BingLing's eighty-four to eighty by
|
|
70
|
+
* dropping the four whose marginal recall on the 2026-10-08 catalog was
|
|
71
|
+
* single-digit: `农历节日`, `Twitter`, `微博`, `Notion`.
|
|
72
|
+
*
|
|
73
|
+
* To add a row: write the intent's key in Chinese, its triggers (≥ 2 Han
|
|
74
|
+
* characters each, as a defensive rule, so an isolated Han inside someone
|
|
75
|
+
* else's name never fires the intent), its recall terms with `#` where a
|
|
76
|
+
* bare short Latin term would otherwise act like a substring, and one
|
|
77
|
+
* comment line pointing at this document's measured table.
|
|
78
|
+
*/
|
|
79
|
+
export declare const ZH_INTENTS: readonly ZhIntent[];
|
|
80
|
+
/**
|
|
81
|
+
* Fire every intent whose trigger appears in the query (lowercase, substring),
|
|
82
|
+
* deduplicate the recall vocabulary, and return the query itself first.
|
|
83
|
+
*
|
|
84
|
+
* The call site never has to know whether expansion happened: `terms` is
|
|
85
|
+
* always at least `[query]`. The caller joins these with OR against each
|
|
86
|
+
* entry's searchable fields, in the same `includes`/`tokenInText` grammar
|
|
87
|
+
* as the raw query.
|
|
88
|
+
*/
|
|
89
|
+
export declare function expandZhQuery(rawQuery: string): ZhExpansion;
|