dsh-plugin-shop 0.5.2 → 0.5.3
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 +3 -3
- package/lib/client.js +41 -11
- package/lib/index.js +215 -74
- package/lib/typert.host.js +15 -11
- package/lib/typert.remote-client.js +10 -10
- package/lib/types/client/locales.d.ts +10 -0
- package/lib/types/client/present.d.ts +7 -3
- package/lib/types/host/executor.d.ts +4 -3
- package/lib/types/host/hot.d.ts +24 -11
- package/lib/types/host/index.d.ts +9 -2
- package/lib/types/host/profile.d.ts +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,9 +52,9 @@ install them once with `npm install -g @deepseek-ai/dsh pnpm` and verify with
|
|
|
52
52
|
# holds back very recent releases, so a bare `add dsh-plugin-shop` can hand
|
|
53
53
|
# you an older version for a while. Pin the current release — refresh it
|
|
54
54
|
# with `npm view dsh-plugin-shop version`.
|
|
55
|
-
dsh plugin --profile web add dsh-plugin-shop@0.5.
|
|
55
|
+
dsh plugin --profile web add dsh-plugin-shop@0.5.3
|
|
56
56
|
# or straight through npx, nothing installed:
|
|
57
|
-
npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.5.
|
|
57
|
+
npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.5.3
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Replace `web` with your profile if you use another one. Then restart `dsh` once — a
|
|
@@ -84,7 +84,7 @@ ls -1 "${DSH_HOME:-$HOME/.dsh}/profiles" | grep -v '^node_modules$'
|
|
|
84
84
|
cooldown, and deterministic installs are the point of the agent path.
|
|
85
85
|
|
|
86
86
|
```sh
|
|
87
|
-
dsh plugin --profile <profile> add dsh-plugin-shop@0.5.
|
|
87
|
+
dsh plugin --profile <profile> add dsh-plugin-shop@0.5.3
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
**3. Verify — do not skip this.** A zero exit from step 2 means pnpm resolved the
|
package/lib/client.js
CHANGED
|
@@ -4363,7 +4363,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4363
4363
|
literal(false),
|
|
4364
4364
|
literal(true)
|
|
4365
4365
|
]).optional(),
|
|
4366
|
-
"restartReason": union([
|
|
4366
|
+
"restartReason": union([
|
|
4367
|
+
_undefined(),
|
|
4368
|
+
literal("no-patch"),
|
|
4369
|
+
literal("not-simple"),
|
|
4370
|
+
literal("host-unsupported"),
|
|
4371
|
+
literal("timeout"),
|
|
4372
|
+
literal("mount-failed")
|
|
4373
|
+
]).optional(),
|
|
4367
4374
|
"detail": union([_undefined(), string()]).optional()
|
|
4368
4375
|
});
|
|
4369
4376
|
const dsh_plugin_shop_shop_restart_result$schema = union([object({ "ok": literal(true) }), object({
|
|
@@ -4427,7 +4434,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4427
4434
|
},
|
|
4428
4435
|
sourceLocation: {
|
|
4429
4436
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4430
|
-
"line":
|
|
4437
|
+
"line": 521,
|
|
4431
4438
|
"column": 9
|
|
4432
4439
|
}
|
|
4433
4440
|
},
|
|
@@ -4445,7 +4452,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4445
4452
|
},
|
|
4446
4453
|
sourceLocation: {
|
|
4447
4454
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4448
|
-
"line":
|
|
4455
|
+
"line": 677,
|
|
4449
4456
|
"column": 9
|
|
4450
4457
|
}
|
|
4451
4458
|
},
|
|
@@ -4473,7 +4480,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4473
4480
|
},
|
|
4474
4481
|
sourceLocation: {
|
|
4475
4482
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4476
|
-
"line":
|
|
4483
|
+
"line": 547,
|
|
4477
4484
|
"column": 9
|
|
4478
4485
|
}
|
|
4479
4486
|
},
|
|
@@ -4500,7 +4507,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4500
4507
|
},
|
|
4501
4508
|
sourceLocation: {
|
|
4502
4509
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4503
|
-
"line":
|
|
4510
|
+
"line": 666,
|
|
4504
4511
|
"column": 3
|
|
4505
4512
|
}
|
|
4506
4513
|
},
|
|
@@ -4518,7 +4525,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4518
4525
|
},
|
|
4519
4526
|
sourceLocation: {
|
|
4520
4527
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4521
|
-
"line":
|
|
4528
|
+
"line": 827,
|
|
4522
4529
|
"column": 9
|
|
4523
4530
|
}
|
|
4524
4531
|
},
|
|
@@ -4545,7 +4552,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4545
4552
|
},
|
|
4546
4553
|
sourceLocation: {
|
|
4547
4554
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4548
|
-
"line":
|
|
4555
|
+
"line": 442,
|
|
4549
4556
|
"column": 9
|
|
4550
4557
|
}
|
|
4551
4558
|
},
|
|
@@ -4573,7 +4580,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4573
4580
|
},
|
|
4574
4581
|
sourceLocation: {
|
|
4575
4582
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4576
|
-
"line":
|
|
4583
|
+
"line": 769,
|
|
4577
4584
|
"column": 9
|
|
4578
4585
|
}
|
|
4579
4586
|
},
|
|
@@ -4600,7 +4607,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4600
4607
|
},
|
|
4601
4608
|
sourceLocation: {
|
|
4602
4609
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4603
|
-
"line":
|
|
4610
|
+
"line": 887,
|
|
4604
4611
|
"column": 9
|
|
4605
4612
|
}
|
|
4606
4613
|
},
|
|
@@ -4618,7 +4625,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4618
4625
|
},
|
|
4619
4626
|
sourceLocation: {
|
|
4620
4627
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4621
|
-
"line":
|
|
4628
|
+
"line": 871,
|
|
4622
4629
|
"column": 9
|
|
4623
4630
|
}
|
|
4624
4631
|
}
|
|
@@ -4626,6 +4633,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4626
4633
|
};
|
|
4627
4634
|
//#endregion
|
|
4628
4635
|
//#region src/client/present.ts
|
|
4636
|
+
/** Hot-mount reason code → locale key, so the notice reads in the language
|
|
4637
|
+
* the person set in dsh. An absent (or unrecognized) reason keeps the generic
|
|
4638
|
+
* restart line rather than showing a bare code. */
|
|
4639
|
+
function restartReasonKey(reason) {
|
|
4640
|
+
switch (reason) {
|
|
4641
|
+
case "no-patch": return "hotNoPatchNotice";
|
|
4642
|
+
case "not-simple": return "hotNotSimpleNotice";
|
|
4643
|
+
case "host-unsupported": return "hotHostUnsupportedNotice";
|
|
4644
|
+
case "timeout": return "hotTimeoutNotice";
|
|
4645
|
+
case "mount-failed": return "hotMountFailedNotice";
|
|
4646
|
+
default: return "installedRestartNotice";
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4629
4649
|
/** Tier → locale key, for the entry-card tier badge (§6.2). */
|
|
4630
4650
|
function tierKey(tier) {
|
|
4631
4651
|
switch (tier) {
|
|
@@ -4845,6 +4865,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4845
4865
|
acknowledgementBody: ACKNOWLEDGEMENT_ZH,
|
|
4846
4866
|
installing: "正在安装…",
|
|
4847
4867
|
installedRestartNotice: "已安装;重启 dsh 后生效",
|
|
4868
|
+
hotNoPatchNotice: "该插件没有可热挂载的补丁文件;重启 dsh 后生效",
|
|
4869
|
+
hotNotSimpleNotice: "该插件的补丁包含无法热挂载的配置;重启 dsh 后生效",
|
|
4870
|
+
hotHostUnsupportedNotice: "当前环境不支持热挂载;重启 dsh 后生效",
|
|
4871
|
+
hotTimeoutNotice: "热挂载超时;重启 dsh 后生效",
|
|
4872
|
+
hotMountFailedNotice: "热挂载失败;重启 dsh 后生效",
|
|
4848
4873
|
installedNoRestartNotice: "已安装,但 profile 未变化——目录可能已过期,请刷新后重试",
|
|
4849
4874
|
installFailed: "安装失败",
|
|
4850
4875
|
installTransportFailed: "安装请求未能送达。请稍后重试。",
|
|
@@ -4922,6 +4947,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4922
4947
|
acknowledgementBody: ACKNOWLEDGEMENT_EN,
|
|
4923
4948
|
installing: "Installing…",
|
|
4924
4949
|
installedRestartNotice: "installed; restart dsh to activate",
|
|
4950
|
+
hotNoPatchNotice: "no patch file to hot-mount; restart dsh to activate",
|
|
4951
|
+
hotNotSimpleNotice: "the patch has config rows that cannot be hot-mounted; restart dsh to activate",
|
|
4952
|
+
hotHostUnsupportedNotice: "this harness cannot hot-mount; restart dsh to activate",
|
|
4953
|
+
hotTimeoutNotice: "hot-mount timed out; restart dsh to activate",
|
|
4954
|
+
hotMountFailedNotice: "hot-mount failed; restart dsh to activate",
|
|
4925
4955
|
installedNoRestartNotice: "installed, but the profile did not change — the catalog may be stale; refresh and try again",
|
|
4926
4956
|
installFailed: "Install failed",
|
|
4927
4957
|
installTransportFailed: "The install request could not be delivered. Please retry.",
|
|
@@ -5430,7 +5460,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5430
5460
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5431
5461
|
className: _dsh_plugin_shop_css_e3675a89_default.notice,
|
|
5432
5462
|
"data-shop-restart-notice": true,
|
|
5433
|
-
children: view.needsRestart ? view.restartReason
|
|
5463
|
+
children: view.needsRestart ? t(restartReasonKey(view.restartReason)) : t("installedNoRestartNotice")
|
|
5434
5464
|
}),
|
|
5435
5465
|
view.needsRestart && restartSupported && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
|
|
5436
5466
|
t,
|
package/lib/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
5
5
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import { basename, dirname, join } from "node:path";
|
|
8
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import { dump } from "js-yaml";
|
|
10
|
+
import { JSON_SCHEMA, Type, dump, load } from "js-yaml";
|
|
11
11
|
//#region src/own-version.ts
|
|
12
12
|
/** The shop's own published version, read from the package.json that ships
|
|
13
13
|
* next to this package — the RUNNING version, not the manifest's range
|
|
@@ -432,7 +432,7 @@ function spawnPluginCli(options) {
|
|
|
432
432
|
restartReason = outcome?.restartReason;
|
|
433
433
|
} catch {
|
|
434
434
|
needsRestartOnDone = true;
|
|
435
|
-
restartReason = "
|
|
435
|
+
restartReason = "mount-failed";
|
|
436
436
|
}
|
|
437
437
|
state = "done";
|
|
438
438
|
} else state = "done";
|
|
@@ -504,13 +504,26 @@ function startUninstall(options) {
|
|
|
504
504
|
* tree changes back to the file it read (dsh-market hot.ts; the in-tree
|
|
505
505
|
* precedent is dsh's agent-presets PresetTree).
|
|
506
506
|
*
|
|
507
|
+
* Reasons are CODES, never copy: the host has no idea which language the
|
|
508
|
+
* person reading it set in dsh, so it names what happened and the client
|
|
509
|
+
* renders it through dsh's own locale service.
|
|
510
|
+
*
|
|
507
511
|
* Deliberate non-port: dsh-market's client-only shim (`mountClientOnlyDeps`
|
|
508
512
|
* and `shimNames`, which hot-mounted a package with no server-side entry by
|
|
509
513
|
* inserting a shim loader entry) is omitted. Our catalog never lists a
|
|
510
514
|
* package without `dsh.bundle`, so the shim branch is unreachable here
|
|
511
515
|
* (YAGNI). `hotMount` still distinguishes "no patch file / not
|
|
512
|
-
* hot-mountable" from "restart will fix it" through the
|
|
516
|
+
* hot-mountable" from "restart will fix it" through the `reason` code.
|
|
513
517
|
*/
|
|
518
|
+
/** The loader's YAML dialect: `!!js` scalars round-trip as expression nodes
|
|
519
|
+
* rather than throwing, so a patch carrying one is refused for its SHAPE
|
|
520
|
+
* (below) instead of being indistinguishable from malformed YAML. Nothing is
|
|
521
|
+
* evaluated here — the loader evaluates at entry activation. */
|
|
522
|
+
const PATCH_SCHEMA = JSON_SCHEMA.extend(new Type("tag:yaml.org,2002:js", {
|
|
523
|
+
kind: "scalar",
|
|
524
|
+
resolve: () => true,
|
|
525
|
+
construct: (data) => ({ __jsExpr: data })
|
|
526
|
+
}));
|
|
514
527
|
const nodeFs = {
|
|
515
528
|
read: (path) => readFileSync(path, "utf8"),
|
|
516
529
|
write: (path, data) => {
|
|
@@ -528,52 +541,43 @@ const HOT_DIR = ".dsh-shop";
|
|
|
528
541
|
/** `hot-<n>.yml` files: the only things `cleanHotDir` wipes and the only
|
|
529
542
|
* files `hotMount` writes. */
|
|
530
543
|
const HOT_FILE_RE = /^hot-(\d+)\.yml$/;
|
|
531
|
-
/** Reasons, bilingual, distinguishing the P0-2 categories: a transient mount
|
|
532
|
-
* failure ("restart will fix it") versus a package that structurally cannot
|
|
533
|
-
* hot-mount. All tell the user to restart; the head names which category. */
|
|
534
|
-
const NO_PATCH_REASON = "该插件没有可热挂载的补丁文件,重启后生效 / the plugin has no patch file to hot-mount — restart required";
|
|
535
|
-
const NOT_SIMPLE_REASON = "该插件的补丁包含无法热挂载的配置,重启后生效 / the plugin's patch has config rows that cannot be hot-mounted — restart required";
|
|
536
|
-
const HOST_CANNOT_HOT_MOUNT_REASON = "当前环境不支持热挂载,重启后生效 / this harness cannot hot-mount — restart required";
|
|
537
|
-
const TIMEOUT_REASON = "热挂载超时,重启后生效 / hot-mount timed out — restart required";
|
|
538
|
-
const MOUNT_FAILED_REASON = "热挂载失败,重启后生效 / hot-mount failed — restart required";
|
|
539
|
-
const ID_LINE_RE = /^- id: (.+)$/;
|
|
540
|
-
const NAME_LINE_RE = /^ name: (.+)$/;
|
|
541
544
|
/**
|
|
542
545
|
* Parse a bundle patch into the plain insert rows a hot tree can replicate.
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
546
|
+
*
|
|
547
|
+
* Only an `insert` list of plain `id`/`name` entries parses. A bare id-keyed
|
|
548
|
+
* row is a TARGETING patch — the loader looks the id up in the already
|
|
549
|
+
* composed tree and skips it when absent (applyEntryPatches), so it creates
|
|
550
|
+
* nothing and there is nothing to replicate. A row carrying config, a group,
|
|
551
|
+
* or an `!!js` expression name returns null too, and every null sends the
|
|
552
|
+
* caller to restart activation. Parsed as YAML in the loader's own dialect,
|
|
553
|
+
* so line endings and comments are the parser's problem, not ours, and
|
|
554
|
+
* nothing in the patch is evaluated. Pure: string in, rows out.
|
|
548
555
|
*/
|
|
549
556
|
function parseSimplePatch(patchText) {
|
|
550
|
-
|
|
557
|
+
let parsed;
|
|
558
|
+
try {
|
|
559
|
+
parsed = load(patchText, { schema: PATCH_SCHEMA });
|
|
560
|
+
} catch {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
if (!Array.isArray(parsed)) return null;
|
|
551
564
|
const rows = [];
|
|
552
|
-
for (
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (value === void 0) return null;
|
|
568
|
-
if (value.includes("!!js/expression")) return null;
|
|
569
|
-
name = value;
|
|
570
|
-
break;
|
|
565
|
+
for (const patch of parsed) {
|
|
566
|
+
if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return null;
|
|
567
|
+
const keys = Object.keys(patch);
|
|
568
|
+
if (keys.length !== 1 || keys[0] !== "insert") return null;
|
|
569
|
+
const inserted = patch.insert;
|
|
570
|
+
if (!Array.isArray(inserted)) return null;
|
|
571
|
+
for (const item of inserted) {
|
|
572
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) return null;
|
|
573
|
+
const { id, name } = item;
|
|
574
|
+
if (typeof id !== "string" || typeof name !== "string") return null;
|
|
575
|
+
if (Object.keys(item).some((key) => key !== "id" && key !== "name")) return null;
|
|
576
|
+
rows.push({
|
|
577
|
+
id,
|
|
578
|
+
name
|
|
579
|
+
});
|
|
571
580
|
}
|
|
572
|
-
if (name === void 0) return null;
|
|
573
|
-
rows.push({
|
|
574
|
-
id,
|
|
575
|
-
name
|
|
576
|
-
});
|
|
577
581
|
}
|
|
578
582
|
return rows.length > 0 ? rows : null;
|
|
579
583
|
}
|
|
@@ -637,7 +641,7 @@ function suppressWrite(treeClass) {
|
|
|
637
641
|
* Include subtree: read its bundle patch, replicate the simple rows under
|
|
638
642
|
* `mkt-` ids into `<profile>/.dsh-shop/hot-<n>.yml`, register the tree, and
|
|
639
643
|
* race its activation against `timeoutMs`. Success returns `ok: true` with
|
|
640
|
-
* no reason; any fallback returns `ok: false` with a
|
|
644
|
+
* no reason; any fallback returns `ok: false` with a reason code
|
|
641
645
|
* distinguishing "restart will fix it" (timeout, activation failure,
|
|
642
646
|
* unavailable include) from "this package can never hot-mount" (no patch
|
|
643
647
|
* file, or rows the hot tree cannot replicate). The plugin is installed in
|
|
@@ -654,7 +658,7 @@ async function hotMount(ctx, profileDir, packageName, deps = {}) {
|
|
|
654
658
|
ctx.logger?.warn(`hot-mount ${packageName}: no patch file to mount — restart will activate it`);
|
|
655
659
|
return {
|
|
656
660
|
ok: false,
|
|
657
|
-
reason:
|
|
661
|
+
reason: "no-patch"
|
|
658
662
|
};
|
|
659
663
|
}
|
|
660
664
|
const rows = parseSimplePatch(patchText);
|
|
@@ -662,7 +666,7 @@ async function hotMount(ctx, profileDir, packageName, deps = {}) {
|
|
|
662
666
|
ctx.logger?.warn(`hot-mount ${packageName}: patch has rows that cannot be hot-mounted — restart will activate it`);
|
|
663
667
|
return {
|
|
664
668
|
ok: false,
|
|
665
|
-
reason:
|
|
669
|
+
reason: "not-simple"
|
|
666
670
|
};
|
|
667
671
|
}
|
|
668
672
|
let treeClass = deps.hotTreeClass;
|
|
@@ -671,7 +675,7 @@ async function hotMount(ctx, profileDir, packageName, deps = {}) {
|
|
|
671
675
|
ctx.logger?.warn(`hot-mount ${packageName}: the include plugin is unavailable in this harness — restart will activate it`);
|
|
672
676
|
return {
|
|
673
677
|
ok: false,
|
|
674
|
-
reason:
|
|
678
|
+
reason: "host-unsupported"
|
|
675
679
|
};
|
|
676
680
|
}
|
|
677
681
|
const file = join(dir, `hot-${nextHotNumber(fs, dir)}.yml`);
|
|
@@ -684,7 +688,7 @@ async function hotMount(ctx, profileDir, packageName, deps = {}) {
|
|
|
684
688
|
ctx.logger?.warn(`hot-mount ${packageName}: mounting the include tree failed — restart will activate it (${String(error)})`);
|
|
685
689
|
return {
|
|
686
690
|
ok: false,
|
|
687
|
-
reason:
|
|
691
|
+
reason: "mount-failed"
|
|
688
692
|
};
|
|
689
693
|
}
|
|
690
694
|
const previous = hotHandles.get(packageName);
|
|
@@ -712,7 +716,7 @@ async function hotMount(ctx, profileDir, packageName, deps = {}) {
|
|
|
712
716
|
}
|
|
713
717
|
return {
|
|
714
718
|
ok: false,
|
|
715
|
-
reason: outcome === "timeout" ?
|
|
719
|
+
reason: outcome === "timeout" ? "timeout" : "mount-failed"
|
|
716
720
|
};
|
|
717
721
|
}
|
|
718
722
|
/** Race the fiber's activation against the deadline. The remaining time is
|
|
@@ -902,22 +906,99 @@ function realpathNearestExisting(startPath) {
|
|
|
902
906
|
}
|
|
903
907
|
}
|
|
904
908
|
/**
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
+
* Write every user-layer row of one package in a single pass.
|
|
910
|
+
*
|
|
911
|
+
* A package owns as many loader entries as its bundle patch inserts (see
|
|
912
|
+
* {@link ownedEntryIds}), and they toggle together: writing them one at a
|
|
913
|
+
* time would read-modify-write the file once per entry, so a crash between
|
|
914
|
+
* two writes would leave the package half disabled.
|
|
909
915
|
*/
|
|
910
|
-
function
|
|
916
|
+
function setUserLayerRows(options) {
|
|
911
917
|
const file = join(options.profileDir, "cordis.patch.yml");
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
|
|
918
|
+
const existing = loadOptionalPatches("dsh-plugin-shop", file) ?? [];
|
|
919
|
+
const touched = new Set(options.rows.map((row) => row.id));
|
|
920
|
+
const next = [...existing.filter((row) => !touched.has(row.id)), ...options.rows.filter((row) => row.disabled).map((row) => ({
|
|
921
|
+
id: row.id,
|
|
915
922
|
disabled: true
|
|
916
|
-
}]
|
|
923
|
+
}))];
|
|
917
924
|
const tmp = `${file}.tmp`;
|
|
918
925
|
writeFileSync(tmp, dump(next, { noRefs: true }));
|
|
919
926
|
renameSync(tmp, file);
|
|
920
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* The loader entry ids one installed package contributes to the tree.
|
|
930
|
+
*
|
|
931
|
+
* This — not the entry's module name — is what identifies a package's rows.
|
|
932
|
+
* A package declares a bundle patch whose `insert` list may add zero, one, or
|
|
933
|
+
* many entries, and an inserted entry's `name` is the MODULE it mounts, which
|
|
934
|
+
* need not be the package at all: `@tt-a1i/archify-dsh` inserts a configured
|
|
935
|
+
* instance of `@deepseek-ai/dsh-skill-filesystem` and registers no module of
|
|
936
|
+
* its own. Matching a package to its rows by module name therefore finds
|
|
937
|
+
* nothing for such a package, which is why the toggle reported it as not
|
|
938
|
+
* installed. The ids the package's own patch inserts are the only honest
|
|
939
|
+
* answer, and the inventory's `entryId` is exactly that id verbatim.
|
|
940
|
+
*
|
|
941
|
+
* Ids the patch merely TARGETS (a bare id-keyed row overriding config) are
|
|
942
|
+
* not owned: those rows belong to whoever inserted them, and claiming them
|
|
943
|
+
* would let one package disable another's entries.
|
|
944
|
+
*
|
|
945
|
+
* A package with no bundle patch, or one absent from the profile, owns
|
|
946
|
+
* nothing and yields `[]` — a distinct fact from "the entry is not live",
|
|
947
|
+
* which only the inventory can answer.
|
|
948
|
+
*/
|
|
949
|
+
function ownedEntryIds(options) {
|
|
950
|
+
const packageDir = join(options.profileDir, "node_modules", ...options.packageName.split("/"));
|
|
951
|
+
const manifestPath = join(packageDir, "package.json");
|
|
952
|
+
if (!existsSync(manifestPath)) return [];
|
|
953
|
+
let patchRelative;
|
|
954
|
+
try {
|
|
955
|
+
patchRelative = JSON.parse(readFileSync(manifestPath, "utf8")).dsh?.bundle?.patch;
|
|
956
|
+
} catch (error) {
|
|
957
|
+
throw new Error(`dsh-plugin-shop: failed to read ${manifestPath}: ${String(error)}`);
|
|
958
|
+
}
|
|
959
|
+
if (typeof patchRelative !== "string") return [];
|
|
960
|
+
const patchFile = resolve(packageDir, patchRelative);
|
|
961
|
+
const inside = relative(packageDir, patchFile);
|
|
962
|
+
if (inside === "" || inside.startsWith("..") || isAbsolute(inside)) throw new Error(`dsh-plugin-shop: ${options.packageName} declares a bundle patch outside its own directory: ${patchRelative}`);
|
|
963
|
+
const ids = [];
|
|
964
|
+
collectInsertedIds(loadOptionalPatches("dsh-plugin-shop", patchFile) ?? [], ids);
|
|
965
|
+
return [...new Set(ids)];
|
|
966
|
+
}
|
|
967
|
+
/** Walk a patch list, appending the id of every INSERTED entry. A patch row
|
|
968
|
+
* without `insert` targets an entry someone else composed — the loader's
|
|
969
|
+
* applyEntryPatches looks it up and skips it when absent, so it creates
|
|
970
|
+
* nothing and owns nothing. An inserted GROUP owns its children, which the
|
|
971
|
+
* loader reads from the group's own `config` array. */
|
|
972
|
+
function collectInsertedIds(rows, into) {
|
|
973
|
+
for (const row of rows) {
|
|
974
|
+
if (row === null || typeof row !== "object") continue;
|
|
975
|
+
const inserted = row.insert;
|
|
976
|
+
if (Array.isArray(inserted)) collectEntryIds(inserted, into);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function collectEntryIds(entries, into) {
|
|
980
|
+
for (const entry of entries) {
|
|
981
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
982
|
+
const { id, group, config } = entry;
|
|
983
|
+
if (typeof id === "string") into.push(id);
|
|
984
|
+
if (group === true && Array.isArray(config)) collectEntryIds(config, into);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Does a live loader entry id belong to the package that owns `owned`?
|
|
989
|
+
*
|
|
990
|
+
* A boot-layer entry carries the bare id its bundle patch inserted. The
|
|
991
|
+
* shop's own hot subtree mounts that same row as `mkt-<id>` inside an Include
|
|
992
|
+
* tree, which the loader spells `include:<tree>:mkt-<id>` — the same plugin,
|
|
993
|
+
* one restart earlier. Both spellings are the package's own row.
|
|
994
|
+
*/
|
|
995
|
+
function ownsEntryId(owned, entryId) {
|
|
996
|
+
if (owned.has(entryId)) return true;
|
|
997
|
+
const colon = entryId.lastIndexOf(":");
|
|
998
|
+
if (colon === -1) return false;
|
|
999
|
+
const tail = entryId.slice(colon + 1);
|
|
1000
|
+
return tail.startsWith("mkt-") && owned.has(tail.slice(4));
|
|
1001
|
+
}
|
|
921
1002
|
//#endregion
|
|
922
1003
|
//#region src/host/index.ts
|
|
923
1004
|
/** ShopGateway: the Host half of dsh-plugin-shop (§5.1). */
|
|
@@ -1261,10 +1342,26 @@ let ShopGateway = (() => {
|
|
|
1261
1342
|
* plain re-update no-ops on the empty diff (dsh-market themes.ts:74-93).
|
|
1262
1343
|
* For an update swap this sequencing is mandatory, not defensive: two live
|
|
1263
1344
|
* instances of a service-providing plugin would collide at provision. */
|
|
1264
|
-
|
|
1345
|
+
/** The package's owned entry ids, or none when its bundle patch cannot be
|
|
1346
|
+
* read. For the paths where a live disable is an optimization and the
|
|
1347
|
+
* operation must succeed regardless; `setEnabled` reports the failure
|
|
1348
|
+
* instead, because there the patch IS the answer being asked for. */
|
|
1349
|
+
ownedEntryIdsOrNone(packageName) {
|
|
1350
|
+
try {
|
|
1351
|
+
return ownedEntryIds({
|
|
1352
|
+
profileDir: this.profileDirResolved(),
|
|
1353
|
+
packageName
|
|
1354
|
+
});
|
|
1355
|
+
} catch {
|
|
1356
|
+
return [];
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
async liveDisableIds(ids) {
|
|
1360
|
+
if (ids.length === 0) return false;
|
|
1361
|
+
const owned = new Set(ids);
|
|
1265
1362
|
let found = false;
|
|
1266
1363
|
for (const entry of this.loaderEntries()) {
|
|
1267
|
-
if (entry.
|
|
1364
|
+
if (entry.id === void 0 || !ownsEntryId(owned, entry.id)) continue;
|
|
1268
1365
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1269
1366
|
try {
|
|
1270
1367
|
await entry.update({ disabled: true }, false, true);
|
|
@@ -1288,17 +1385,39 @@ let ShopGateway = (() => {
|
|
|
1288
1385
|
ok: false,
|
|
1289
1386
|
detail: `dsh-plugin-shop: ${args.name} is part of the harness chain and cannot be toggled from the shop`
|
|
1290
1387
|
};
|
|
1291
|
-
const
|
|
1292
|
-
if (
|
|
1388
|
+
const profileDir = this.profileDirResolved();
|
|
1389
|
+
if ((readProfileManifest("dsh-plugin-shop", profileDir).dependencies ?? {})[args.name] === void 0) return {
|
|
1293
1390
|
ok: false,
|
|
1294
1391
|
detail: `dsh-plugin-shop: ${args.name} is not installed`
|
|
1295
1392
|
};
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1393
|
+
let owned;
|
|
1394
|
+
try {
|
|
1395
|
+
owned = ownedEntryIds({
|
|
1396
|
+
profileDir,
|
|
1397
|
+
packageName: args.name
|
|
1398
|
+
});
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
return {
|
|
1401
|
+
ok: false,
|
|
1402
|
+
detail: `dsh-plugin-shop: ${args.name} has a bundle patch that could not be read: ${String(error)}`
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
if (owned.length === 0) return {
|
|
1406
|
+
ok: false,
|
|
1407
|
+
detail: `dsh-plugin-shop: ${args.name} contributes no plugin entries, so there is nothing to enable or disable`
|
|
1408
|
+
};
|
|
1409
|
+
const ownedSet = new Set(owned);
|
|
1410
|
+
const rows = (await this.listInventory()).filter((entry) => ownsEntryId(ownedSet, entry.entryId)).map((entry) => entry.entryId);
|
|
1411
|
+
if (rows.length === 0) return {
|
|
1412
|
+
ok: false,
|
|
1413
|
+
detail: `dsh-plugin-shop: ${args.name} is installed but its entries are not in the running plugin tree; restart dsh to compose them`
|
|
1414
|
+
};
|
|
1415
|
+
setUserLayerRows({
|
|
1416
|
+
profileDir,
|
|
1417
|
+
rows: rows.map((id) => ({
|
|
1418
|
+
id,
|
|
1300
1419
|
disabled: !args.enabled
|
|
1301
|
-
}
|
|
1420
|
+
}))
|
|
1302
1421
|
});
|
|
1303
1422
|
return { ok: true };
|
|
1304
1423
|
}
|
|
@@ -1390,6 +1509,7 @@ let ShopGateway = (() => {
|
|
|
1390
1509
|
spec = `github:${entry.repo}#${args.version}${entry.subdir !== void 0 ? `&path:${entry.subdir}` : ""}`;
|
|
1391
1510
|
} else spec = `${args.name}@${args.version}`;
|
|
1392
1511
|
const isUpdate = (readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {})[args.name] !== void 0;
|
|
1512
|
+
const priorEntryIds = isUpdate ? this.ownedEntryIdsOrNone(args.name) : [];
|
|
1393
1513
|
const running = startInstall({
|
|
1394
1514
|
profile: this.profile,
|
|
1395
1515
|
spec,
|
|
@@ -1400,7 +1520,7 @@ let ShopGateway = (() => {
|
|
|
1400
1520
|
mount: hotMount,
|
|
1401
1521
|
unmount: hotUnmount
|
|
1402
1522
|
};
|
|
1403
|
-
if (isUpdate) await this.
|
|
1523
|
+
if (isUpdate) await this.liveDisableIds(priorEntryIds);
|
|
1404
1524
|
const result = await hot.mount({ plugin: (plugin, config) => this.ctx.plugin(plugin, config) }, this.profileDirResolved(), args.name);
|
|
1405
1525
|
return result.ok ? { needsRestart: false } : {
|
|
1406
1526
|
needsRestart: true,
|
|
@@ -1464,10 +1584,30 @@ let ShopGateway = (() => {
|
|
|
1464
1584
|
}
|
|
1465
1585
|
const dependencies = readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {};
|
|
1466
1586
|
const pins = readRepoPins(this.pinFs, this.pinsPath());
|
|
1467
|
-
|
|
1587
|
+
const live = /* @__PURE__ */ new Map();
|
|
1588
|
+
let haveInventory = false;
|
|
1468
1589
|
try {
|
|
1469
|
-
|
|
1590
|
+
for (const entry of await this.listInventory()) live.set(entry.entryId, entry.enabled);
|
|
1591
|
+
haveInventory = true;
|
|
1470
1592
|
} catch {}
|
|
1593
|
+
/** A package is enabled when every entry it owns and that is live is
|
|
1594
|
+
* enabled. Keyed by entry id, never by module name — the entry a package
|
|
1595
|
+
* inserts may mount a different package's module (see ownedEntryIds). */
|
|
1596
|
+
const enabledOf = (name) => {
|
|
1597
|
+
if (!haveInventory) return true;
|
|
1598
|
+
let owned;
|
|
1599
|
+
try {
|
|
1600
|
+
owned = ownedEntryIds({
|
|
1601
|
+
profileDir: this.profileDirResolved(),
|
|
1602
|
+
packageName: name
|
|
1603
|
+
});
|
|
1604
|
+
} catch {
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1607
|
+
const ownedSet = new Set(owned);
|
|
1608
|
+
const present = [...live].filter(([entryId]) => ownsEntryId(ownedSet, entryId));
|
|
1609
|
+
return present.length === 0 || present.every(([, enabled]) => enabled);
|
|
1610
|
+
};
|
|
1471
1611
|
const installed = [];
|
|
1472
1612
|
for (const entry of this.lastSnapshot.entries) {
|
|
1473
1613
|
const spec = dependencies[entry.name];
|
|
@@ -1479,14 +1619,14 @@ let ShopGateway = (() => {
|
|
|
1479
1619
|
installed: pin ?? spec,
|
|
1480
1620
|
latest: entry.version,
|
|
1481
1621
|
outdated: pin !== void 0 && pin !== entry.version,
|
|
1482
|
-
enabled:
|
|
1622
|
+
enabled: enabledOf(entry.name)
|
|
1483
1623
|
});
|
|
1484
1624
|
} else installed.push({
|
|
1485
1625
|
name: entry.name,
|
|
1486
1626
|
installed: spec,
|
|
1487
1627
|
latest: entry.version,
|
|
1488
1628
|
outdated: this.isBehind(spec, entry.version),
|
|
1489
|
-
enabled:
|
|
1629
|
+
enabled: enabledOf(entry.name)
|
|
1490
1630
|
});
|
|
1491
1631
|
}
|
|
1492
1632
|
return installed;
|
|
@@ -1528,6 +1668,7 @@ let ShopGateway = (() => {
|
|
|
1528
1668
|
ok: false,
|
|
1529
1669
|
detail: `dsh-plugin-shop: ${args.name} is not installed`
|
|
1530
1670
|
};
|
|
1671
|
+
const priorEntryIds = this.ownedEntryIdsOrNone(args.name);
|
|
1531
1672
|
const running = startUninstall({
|
|
1532
1673
|
profile: this.profile,
|
|
1533
1674
|
name: args.name,
|
|
@@ -1537,7 +1678,7 @@ let ShopGateway = (() => {
|
|
|
1537
1678
|
await (this.hot ?? {
|
|
1538
1679
|
mount: hotMount,
|
|
1539
1680
|
unmount: hotUnmount
|
|
1540
|
-
}).unmount(args.name) || await this.
|
|
1681
|
+
}).unmount(args.name) || await this.liveDisableIds(priorEntryIds);
|
|
1541
1682
|
return { needsRestart: false };
|
|
1542
1683
|
}
|
|
1543
1684
|
});
|
package/lib/typert.host.js
CHANGED
|
@@ -77,7 +77,7 @@ const dsh_plugin_shop_shop_installStatus_result$schema = z.object({
|
|
|
77
77
|
'state': z.union([z.literal("running"), z.literal("done"), z.literal("failed")]),
|
|
78
78
|
'log': z.array(z.string()),
|
|
79
79
|
'needsRestart': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
|
|
80
|
-
'restartReason': z.union([z.undefined(), z.
|
|
80
|
+
'restartReason': z.union([z.undefined(), z.literal("no-patch"), z.literal("not-simple"), z.literal("host-unsupported"), z.literal("timeout"), z.literal("mount-failed")]).optional(),
|
|
81
81
|
'detail': z.union([z.undefined(), z.string()]).optional(),
|
|
82
82
|
})
|
|
83
83
|
const dsh_plugin_shop_shop_restart_result$schema = z.union([z.object({
|
|
@@ -151,7 +151,7 @@ export const TYPERT = {
|
|
|
151
151
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
152
152
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
153
153
|
},
|
|
154
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
154
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":521,"column":9},
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -166,7 +166,7 @@ export const TYPERT = {
|
|
|
166
166
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
167
167
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
168
168
|
},
|
|
169
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
169
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":677,"column":9},
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -192,7 +192,7 @@ export const TYPERT = {
|
|
|
192
192
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
193
193
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
194
194
|
},
|
|
195
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
195
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":547,"column":9},
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -217,7 +217,7 @@ export const TYPERT = {
|
|
|
217
217
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
218
218
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
219
219
|
},
|
|
220
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
220
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":666,"column":3},
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
223
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -232,7 +232,7 @@ export const TYPERT = {
|
|
|
232
232
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
233
233
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
234
234
|
},
|
|
235
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
235
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":827,"column":9},
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -257,7 +257,7 @@ export const TYPERT = {
|
|
|
257
257
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
258
258
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
259
259
|
},
|
|
260
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
260
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":442,"column":9},
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -283,7 +283,7 @@ export const TYPERT = {
|
|
|
283
283
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
284
284
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
285
285
|
},
|
|
286
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
286
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":769,"column":9},
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -308,7 +308,7 @@ export const TYPERT = {
|
|
|
308
308
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
309
309
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
310
310
|
},
|
|
311
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
311
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":887,"column":9},
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -323,7 +323,7 @@ export const TYPERT = {
|
|
|
323
323
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
324
324
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
325
325
|
},
|
|
326
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
326
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":871,"column":9},
|
|
327
327
|
},
|
|
328
328
|
],
|
|
329
329
|
model: {
|
|
@@ -423,6 +423,10 @@ export const TYPERT = {
|
|
|
423
423
|
"name": "DeniedEntry",
|
|
424
424
|
"declaration": "export interface DeniedEntry {\n name: string;\n detail: string;\n replacement?: string;\n}"
|
|
425
425
|
},
|
|
426
|
+
{
|
|
427
|
+
"name": "HotRestartReason",
|
|
428
|
+
"declaration": "export type HotRestartReason = 'no-patch' | 'not-simple' | 'host-unsupported' | 'timeout' | 'mount-failed';"
|
|
429
|
+
},
|
|
426
430
|
{
|
|
427
431
|
"name": "InstallArgs",
|
|
428
432
|
"declaration": "export interface InstallArgs {\n name: string;\n version: string;\n acknowledged?: boolean;\n}"
|
|
@@ -437,7 +441,7 @@ export const TYPERT = {
|
|
|
437
441
|
},
|
|
438
442
|
{
|
|
439
443
|
"name": "InstallStatus",
|
|
440
|
-
"declaration": "export interface InstallStatus {\n state: InstallState;\n log: string[];\n needsRestart?: boolean;\n restartReason?:
|
|
444
|
+
"declaration": "export interface InstallStatus {\n state: InstallState;\n log: string[];\n needsRestart?: boolean;\n restartReason?: HotRestartReason;\n detail?: string;\n}"
|
|
441
445
|
},
|
|
442
446
|
{
|
|
443
447
|
"name": "RestartOutcome",
|
|
@@ -77,7 +77,7 @@ const dsh_plugin_shop_shop_installStatus_result$schema = z.object({
|
|
|
77
77
|
'state': z.union([z.literal("running"), z.literal("done"), z.literal("failed")]),
|
|
78
78
|
'log': z.array(z.string()),
|
|
79
79
|
'needsRestart': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
|
|
80
|
-
'restartReason': z.union([z.undefined(), z.
|
|
80
|
+
'restartReason': z.union([z.undefined(), z.literal("no-patch"), z.literal("not-simple"), z.literal("host-unsupported"), z.literal("timeout"), z.literal("mount-failed")]).optional(),
|
|
81
81
|
'detail': z.union([z.undefined(), z.string()]).optional(),
|
|
82
82
|
})
|
|
83
83
|
const dsh_plugin_shop_shop_restart_result$schema = z.union([z.object({
|
|
@@ -148,7 +148,7 @@ export const TYPERT_REMOTE = {
|
|
|
148
148
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
149
149
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
150
150
|
},
|
|
151
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
151
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":521,"column":9},
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -163,7 +163,7 @@ export const TYPERT_REMOTE = {
|
|
|
163
163
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
164
164
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
165
165
|
},
|
|
166
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
166
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":677,"column":9},
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -189,7 +189,7 @@ export const TYPERT_REMOTE = {
|
|
|
189
189
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
190
190
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
191
191
|
},
|
|
192
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
192
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":547,"column":9},
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
195
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -214,7 +214,7 @@ export const TYPERT_REMOTE = {
|
|
|
214
214
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
215
215
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
216
216
|
},
|
|
217
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
217
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":666,"column":3},
|
|
218
218
|
},
|
|
219
219
|
{
|
|
220
220
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -229,7 +229,7 @@ export const TYPERT_REMOTE = {
|
|
|
229
229
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
230
230
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
231
231
|
},
|
|
232
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
232
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":827,"column":9},
|
|
233
233
|
},
|
|
234
234
|
{
|
|
235
235
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -254,7 +254,7 @@ export const TYPERT_REMOTE = {
|
|
|
254
254
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
255
255
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
256
256
|
},
|
|
257
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
257
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":442,"column":9},
|
|
258
258
|
},
|
|
259
259
|
{
|
|
260
260
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -280,7 +280,7 @@ export const TYPERT_REMOTE = {
|
|
|
280
280
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
281
281
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
282
282
|
},
|
|
283
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
283
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":769,"column":9},
|
|
284
284
|
},
|
|
285
285
|
{
|
|
286
286
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -305,7 +305,7 @@ export const TYPERT_REMOTE = {
|
|
|
305
305
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
306
306
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
307
307
|
},
|
|
308
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
308
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":887,"column":9},
|
|
309
309
|
},
|
|
310
310
|
{
|
|
311
311
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -320,7 +320,7 @@ export const TYPERT_REMOTE = {
|
|
|
320
320
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
321
321
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
322
322
|
},
|
|
323
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
323
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":871,"column":9},
|
|
324
324
|
},
|
|
325
325
|
],
|
|
326
326
|
}
|
|
@@ -30,6 +30,11 @@ export declare const zh: {
|
|
|
30
30
|
acknowledgementBody: string;
|
|
31
31
|
installing: string;
|
|
32
32
|
installedRestartNotice: string;
|
|
33
|
+
hotNoPatchNotice: string;
|
|
34
|
+
hotNotSimpleNotice: string;
|
|
35
|
+
hotHostUnsupportedNotice: string;
|
|
36
|
+
hotTimeoutNotice: string;
|
|
37
|
+
hotMountFailedNotice: string;
|
|
33
38
|
installedNoRestartNotice: string;
|
|
34
39
|
installFailed: string;
|
|
35
40
|
installTransportFailed: string;
|
|
@@ -109,6 +114,11 @@ export declare const en: {
|
|
|
109
114
|
acknowledgementBody: string;
|
|
110
115
|
installing: string;
|
|
111
116
|
installedRestartNotice: string;
|
|
117
|
+
hotNoPatchNotice: string;
|
|
118
|
+
hotNotSimpleNotice: string;
|
|
119
|
+
hotHostUnsupportedNotice: string;
|
|
120
|
+
hotTimeoutNotice: string;
|
|
121
|
+
hotMountFailedNotice: string;
|
|
112
122
|
installedNoRestartNotice: string;
|
|
113
123
|
installFailed: string;
|
|
114
124
|
installTransportFailed: string;
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* timers, no I/O: every function here maps a value to a value, so fixtures
|
|
3
3
|
* drive all of it. */
|
|
4
4
|
import type { ShopLocaleKey } from './locales.ts';
|
|
5
|
-
import type { CatalogEntry, InstallRejectionCode } from '../host/index.ts';
|
|
5
|
+
import type { CatalogEntry, HotRestartReason, InstallRejectionCode } from '../host/index.ts';
|
|
6
|
+
/** Hot-mount reason code → locale key, so the notice reads in the language
|
|
7
|
+
* the person set in dsh. An absent (or unrecognized) reason keeps the generic
|
|
8
|
+
* restart line rather than showing a bare code. */
|
|
9
|
+
export declare function restartReasonKey(reason: HotRestartReason | undefined): ShopLocaleKey;
|
|
6
10
|
/** Tier → locale key, for the entry-card tier badge (§6.2). */
|
|
7
11
|
export declare function tierKey(tier: CatalogEntry['tier']): ShopLocaleKey;
|
|
8
12
|
/** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
|
|
@@ -37,7 +41,7 @@ export interface InstallStatusShape {
|
|
|
37
41
|
state: 'running' | 'done' | 'failed';
|
|
38
42
|
log: string[];
|
|
39
43
|
needsRestart?: boolean;
|
|
40
|
-
restartReason?:
|
|
44
|
+
restartReason?: HotRestartReason;
|
|
41
45
|
detail?: string;
|
|
42
46
|
}
|
|
43
47
|
/** The install view state machine (§7.2). */
|
|
@@ -54,7 +58,7 @@ export type InstallView = {
|
|
|
54
58
|
} | {
|
|
55
59
|
kind: 'done';
|
|
56
60
|
needsRestart: boolean;
|
|
57
|
-
restartReason?:
|
|
61
|
+
restartReason?: HotRestartReason;
|
|
58
62
|
} | {
|
|
59
63
|
kind: 'failed';
|
|
60
64
|
detail: string;
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* per profile. One implementation drives both `dsh plugin add` (install) and
|
|
3
3
|
* `dsh plugin remove` (uninstall); the only differences are the verb and the
|
|
4
4
|
* post-exit manifest confirmation. */
|
|
5
|
+
import type { HotRestartReason } from './hot.ts';
|
|
5
6
|
export type InstallState = 'running' | 'done' | 'failed';
|
|
6
7
|
export interface InstallStatus {
|
|
7
8
|
state: InstallState;
|
|
8
9
|
log: string[];
|
|
9
10
|
needsRestart?: boolean;
|
|
10
|
-
restartReason?:
|
|
11
|
+
restartReason?: HotRestartReason;
|
|
11
12
|
detail?: string;
|
|
12
13
|
}
|
|
13
14
|
interface RunningInstall {
|
|
@@ -29,7 +30,7 @@ export declare function startInstall(options: {
|
|
|
29
30
|
expectedName?: string;
|
|
30
31
|
afterDone?: (home: string | undefined) => Promise<{
|
|
31
32
|
needsRestart: boolean;
|
|
32
|
-
restartReason?:
|
|
33
|
+
restartReason?: HotRestartReason;
|
|
33
34
|
} | void>;
|
|
34
35
|
onStatus?: (status: InstallStatus) => void;
|
|
35
36
|
}): RunningInstall;
|
|
@@ -48,7 +49,7 @@ export declare function startUninstall(options: {
|
|
|
48
49
|
expectedName?: string;
|
|
49
50
|
afterDone?: (home: string | undefined) => Promise<{
|
|
50
51
|
needsRestart: boolean;
|
|
51
|
-
restartReason?:
|
|
52
|
+
restartReason?: HotRestartReason;
|
|
52
53
|
} | void>;
|
|
53
54
|
onStatus?: (status: InstallStatus) => void;
|
|
54
55
|
}): RunningInstall;
|
package/lib/types/host/hot.d.ts
CHANGED
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
* tree changes back to the file it read (dsh-market hot.ts; the in-tree
|
|
17
17
|
* precedent is dsh's agent-presets PresetTree).
|
|
18
18
|
*
|
|
19
|
+
* Reasons are CODES, never copy: the host has no idea which language the
|
|
20
|
+
* person reading it set in dsh, so it names what happened and the client
|
|
21
|
+
* renders it through dsh's own locale service.
|
|
22
|
+
*
|
|
19
23
|
* Deliberate non-port: dsh-market's client-only shim (`mountClientOnlyDeps`
|
|
20
24
|
* and `shimNames`, which hot-mounted a package with no server-side entry by
|
|
21
25
|
* inserting a shim loader entry) is omitted. Our catalog never lists a
|
|
22
26
|
* package without `dsh.bundle`, so the shim branch is unreachable here
|
|
23
27
|
* (YAGNI). `hotMount` still distinguishes "no patch file / not
|
|
24
|
-
* hot-mountable" from "restart will fix it" through the
|
|
28
|
+
* hot-mountable" from "restart will fix it" through the `reason` code.
|
|
25
29
|
*/
|
|
26
30
|
/** One hot-mountable insert row: a plain `- id:` / `name:` pair. */
|
|
27
31
|
export interface HotRow {
|
|
@@ -43,12 +47,17 @@ export interface PluginHandle {
|
|
|
43
47
|
await(): Promise<unknown>;
|
|
44
48
|
dispose(): Promise<unknown> | void;
|
|
45
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Why a hot mount could not activate — a stable code the client turns into
|
|
52
|
+
* copy in the reader's own dsh language. It distinguishes "restart will fix
|
|
53
|
+
* it" (`timeout`, `mount-failed`) from "this package can never hot-mount"
|
|
54
|
+
* (`no-patch`, `not-simple`) and "this harness cannot" (`host-unsupported`).
|
|
55
|
+
*/
|
|
56
|
+
export type HotRestartReason = 'no-patch' | 'not-simple' | 'host-unsupported' | 'timeout' | 'mount-failed';
|
|
46
57
|
export interface HotMountResult {
|
|
47
58
|
ok: boolean;
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
* patch the hot tree cannot replicate). null exactly when ok. */
|
|
51
|
-
reason: string | null;
|
|
59
|
+
/** null exactly when ok. */
|
|
60
|
+
reason: HotRestartReason | null;
|
|
52
61
|
}
|
|
53
62
|
/** Test injection for the host shell (filesystem + ctx + clock). Production
|
|
54
63
|
* defaults: the real include import, node:fs, `join(profileDir, '.dsh-shop')`
|
|
@@ -71,11 +80,15 @@ export interface HotFs {
|
|
|
71
80
|
}
|
|
72
81
|
/**
|
|
73
82
|
* Parse a bundle patch into the plain insert rows a hot tree can replicate.
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
83
|
+
*
|
|
84
|
+
* Only an `insert` list of plain `id`/`name` entries parses. A bare id-keyed
|
|
85
|
+
* row is a TARGETING patch — the loader looks the id up in the already
|
|
86
|
+
* composed tree and skips it when absent (applyEntryPatches), so it creates
|
|
87
|
+
* nothing and there is nothing to replicate. A row carrying config, a group,
|
|
88
|
+
* or an `!!js` expression name returns null too, and every null sends the
|
|
89
|
+
* caller to restart activation. Parsed as YAML in the loader's own dialect,
|
|
90
|
+
* so line endings and comments are the parser's problem, not ours, and
|
|
91
|
+
* nothing in the patch is evaluated. Pure: string in, rows out.
|
|
79
92
|
*/
|
|
80
93
|
export declare function parseSimplePatch(patchText: string): HotRow[] | null;
|
|
81
94
|
/**
|
|
@@ -83,7 +96,7 @@ export declare function parseSimplePatch(patchText: string): HotRow[] | null;
|
|
|
83
96
|
* Include subtree: read its bundle patch, replicate the simple rows under
|
|
84
97
|
* `mkt-` ids into `<profile>/.dsh-shop/hot-<n>.yml`, register the tree, and
|
|
85
98
|
* race its activation against `timeoutMs`. Success returns `ok: true` with
|
|
86
|
-
* no reason; any fallback returns `ok: false` with a
|
|
99
|
+
* no reason; any fallback returns `ok: false` with a reason code
|
|
87
100
|
* distinguishing "restart will fix it" (timeout, activation failure,
|
|
88
101
|
* unavailable include) from "this package can never hot-mount" (no patch
|
|
89
102
|
* file, or rows the hot tree cannot replicate). The plugin is installed in
|
|
@@ -9,6 +9,7 @@ import { hotMount, hotUnmount } from './hot.ts';
|
|
|
9
9
|
import { type RestartOutcome } from './restart.ts';
|
|
10
10
|
import { type RepoPinFs } from './repo-pins.ts';
|
|
11
11
|
export type { InstallArgs, InstallRejectionCode } from './install.ts';
|
|
12
|
+
export type { HotRestartReason } from './hot.ts';
|
|
12
13
|
export type { CatalogEntry } from './types.ts';
|
|
13
14
|
/** One Loader inventory entry, structurally — the shop never depends on
|
|
14
15
|
* cordis-plugin-loader, whose types do not reach this package's typecheck. */
|
|
@@ -17,10 +18,11 @@ export interface InventoryEntry {
|
|
|
17
18
|
moduleName: string;
|
|
18
19
|
enabled: boolean;
|
|
19
20
|
}
|
|
20
|
-
/** One boot-layer Loader entry, structurally — the surface `
|
|
21
|
+
/** One boot-layer Loader entry, structurally — the surface `liveDisableIds`
|
|
21
22
|
* consumes. `fiber` is the entry's live activation (present while the plugin
|
|
22
23
|
* is up); `update` flips its options. */
|
|
23
24
|
export interface LoaderEntryLike {
|
|
25
|
+
id?: string;
|
|
24
26
|
options: {
|
|
25
27
|
name?: string;
|
|
26
28
|
};
|
|
@@ -258,7 +260,12 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
258
260
|
* plain re-update no-ops on the empty diff (dsh-market themes.ts:74-93).
|
|
259
261
|
* For an update swap this sequencing is mandatory, not defensive: two live
|
|
260
262
|
* instances of a service-providing plugin would collide at provision. */
|
|
261
|
-
|
|
263
|
+
/** The package's owned entry ids, or none when its bundle patch cannot be
|
|
264
|
+
* read. For the paths where a live disable is an optimization and the
|
|
265
|
+
* operation must succeed regardless; `setEnabled` reports the failure
|
|
266
|
+
* instead, because there the patch IS the answer being asked for. */
|
|
267
|
+
private ownedEntryIdsOrNone;
|
|
268
|
+
private liveDisableIds;
|
|
262
269
|
/** Enable or disable one installed plugin, hot (§8): a disable writes the
|
|
263
270
|
* row to the user layer, an enable drops it again so the bundle default
|
|
264
271
|
* rules — the CLI's watchUserPatches applies either through HMR. The shop's
|
|
@@ -28,3 +28,49 @@ export declare function setUserLayerRow(options: {
|
|
|
28
28
|
profileDir: string;
|
|
29
29
|
row: UserLayerRow;
|
|
30
30
|
}): void;
|
|
31
|
+
/**
|
|
32
|
+
* Write every user-layer row of one package in a single pass.
|
|
33
|
+
*
|
|
34
|
+
* A package owns as many loader entries as its bundle patch inserts (see
|
|
35
|
+
* {@link ownedEntryIds}), and they toggle together: writing them one at a
|
|
36
|
+
* time would read-modify-write the file once per entry, so a crash between
|
|
37
|
+
* two writes would leave the package half disabled.
|
|
38
|
+
*/
|
|
39
|
+
export declare function setUserLayerRows(options: {
|
|
40
|
+
profileDir: string;
|
|
41
|
+
rows: UserLayerRow[];
|
|
42
|
+
}): void;
|
|
43
|
+
/**
|
|
44
|
+
* The loader entry ids one installed package contributes to the tree.
|
|
45
|
+
*
|
|
46
|
+
* This — not the entry's module name — is what identifies a package's rows.
|
|
47
|
+
* A package declares a bundle patch whose `insert` list may add zero, one, or
|
|
48
|
+
* many entries, and an inserted entry's `name` is the MODULE it mounts, which
|
|
49
|
+
* need not be the package at all: `@tt-a1i/archify-dsh` inserts a configured
|
|
50
|
+
* instance of `@deepseek-ai/dsh-skill-filesystem` and registers no module of
|
|
51
|
+
* its own. Matching a package to its rows by module name therefore finds
|
|
52
|
+
* nothing for such a package, which is why the toggle reported it as not
|
|
53
|
+
* installed. The ids the package's own patch inserts are the only honest
|
|
54
|
+
* answer, and the inventory's `entryId` is exactly that id verbatim.
|
|
55
|
+
*
|
|
56
|
+
* Ids the patch merely TARGETS (a bare id-keyed row overriding config) are
|
|
57
|
+
* not owned: those rows belong to whoever inserted them, and claiming them
|
|
58
|
+
* would let one package disable another's entries.
|
|
59
|
+
*
|
|
60
|
+
* A package with no bundle patch, or one absent from the profile, owns
|
|
61
|
+
* nothing and yields `[]` — a distinct fact from "the entry is not live",
|
|
62
|
+
* which only the inventory can answer.
|
|
63
|
+
*/
|
|
64
|
+
export declare function ownedEntryIds(options: {
|
|
65
|
+
profileDir: string;
|
|
66
|
+
packageName: string;
|
|
67
|
+
}): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Does a live loader entry id belong to the package that owns `owned`?
|
|
70
|
+
*
|
|
71
|
+
* A boot-layer entry carries the bare id its bundle patch inserted. The
|
|
72
|
+
* shop's own hot subtree mounts that same row as `mkt-<id>` inside an Include
|
|
73
|
+
* tree, which the loader spells `include:<tree>:mkt-<id>` — the same plugin,
|
|
74
|
+
* one restart earlier. Both spellings are the package's own row.
|
|
75
|
+
*/
|
|
76
|
+
export declare function ownsEntryId(owned: ReadonlySet<string>, entryId: string): boolean;
|
package/package.json
CHANGED