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.
@@ -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, or given up on
37
- * that server. `logFile` is where the host said the new process writes; a
38
- * host older than that field sends none. */
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
- /** Stop the probe loop and forget the page's restart, its request and every
111
- * registered path. Nothing in the page calls this — a committed restart ends
112
- * in a reload or a failure — so its caller is a test isolating one case from
113
- * the next. */
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. This lives at the package root (not under src/host) on purpose:
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "keywords": [
6
6
  "dsh-plugin",