@lmzhen/dsh-evolution-commands 0.20.0 → 0.21.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/lib/index.js CHANGED
@@ -380,8 +380,10 @@ function presetIssues(presets) {
380
380
  * build-time guards cannot. Three classes, each with its own move:
381
381
  * - a user override (the effective value is no longer the deployment's),
382
382
  * - a deprecated alias still written in a user section (writes refuse it),
383
- * - a declared user-writable parameter whose owner publishes no user layer in
384
- * this composition (the registry promises a face this deployment never mounts).
383
+ * - a declared user-writable parameter whose owner publishes no settings namespace AT ALL
384
+ * (a registry fact: writes answer E-315), and
385
+ * - a declared user-writable parameter whose owning row this runtime does not mount
386
+ * (the registry promises a face this composition never carries).
385
387
  * G5: the two layers and the live rows come from the platform (`configEditor.configuration()`,
386
388
  * `pluginManager.listPlugins()`); the registry side (which spellings are deprecated, which rows
387
389
  * declare an E3 parameter) stays the family's own table. A surface that is not mounted is
@@ -411,14 +413,20 @@ function paramDivergences(platform) {
411
413
  return issues;
412
414
  }
413
415
  const live = liveRowTails(platform.plugins);
414
- const unreachable = PARAM_EXPOSURE.filter((entry) => entry.tier === "E3").filter((entry) => {
416
+ const noUserLayer = [];
417
+ const unmounted = [];
418
+ for (const entry of PARAM_EXPOSURE) {
419
+ if (entry.tier !== "E3") continue;
415
420
  const namespace = paramSettingsId(entry.owner);
416
- return namespace === void 0 || !live.has(namespace);
417
- }).map((entry) => entry.id);
418
- if (unreachable.length > 0) {
419
- const shown = unreachable.slice(0, 6).join(", ");
420
- const more = unreachable.length > 6 ? ` (+${unreachable.length - 6} more)` : "";
421
- issues.push(`declared user-writable but unreachable here: ${shown}${more} — the owning row is not mounted in this runtime`);
421
+ if (namespace === void 0) noUserLayer.push(entry.id);
422
+ else if (!live.has(namespace)) unmounted.push(entry.id);
423
+ }
424
+ for (const [ids, sentence] of [[noUserLayer, "declared user-writable but the owner publishes no settings namespace"], [unmounted, "declared user-writable but unreachable here"]]) {
425
+ if (ids.length === 0) continue;
426
+ const shown = ids.slice(0, 6).join(", ");
427
+ const more = ids.length > 6 ? ` (+${ids.length - 6} more)` : "";
428
+ const why = sentence.includes("no settings namespace") ? "those parameters have no user layer at all (writes answer E-315) — register the owner in PARAM_NAMESPACES or move its rows to E2" : "the owning row is not mounted in this runtime";
429
+ issues.push(`${sentence}: ${shown}${more} — ${why}`);
422
430
  }
423
431
  return issues;
424
432
  }
@@ -1318,6 +1326,14 @@ const COMMAND_ENTRIES = [
1318
1326
  usage: "skill undo <name> [--to v<N>]",
1319
1327
  summary: "把某个技能的内容回退到某一版(默认上一版;只动内容,撤销本身也记为一版)"
1320
1328
  },
1329
+ {
1330
+ usage: "skill pin <name>",
1331
+ summary: "钉住一个技能:免于删除、后台审查与生命周期整理(立即生效、随时可解钉;与 skill_manage 的 pin 动作同一语义,不走审批)"
1332
+ },
1333
+ {
1334
+ usage: "skill unpin <name>",
1335
+ summary: "解钉一个技能:撤销上面的冻结(立即生效、不走审批)"
1336
+ },
1321
1337
  {
1322
1338
  usage: "skills health",
1323
1339
  summary: "技能库结构体检"
@@ -1400,7 +1416,7 @@ const Config = z.object({
1400
1416
  skillsRoot: z.string().default(""),
1401
1417
  maintainCooldownMs: z.number().min(0).default(3e4),
1402
1418
  maintainTimeoutMs: z.number().min(1).default(6e5),
1403
- maintainHandoffOnSettle: z.boolean().default(false),
1419
+ maintainHandoffOnSettle: z.boolean().default(false).volatile(),
1404
1420
  threatExemptLabels: z.array(z.string()).default([])
1405
1421
  });
1406
1422
  /** How one run reads in `maintain status` output. */
@@ -1549,10 +1565,29 @@ async function readRunReport(io, record) {
1549
1565
  };
1550
1566
  }
1551
1567
  }
1568
+ /**
1569
+ * Read a switch that may be a live settings reference.
1570
+ *
1571
+ * The loader hands a `.volatile()` field as `{ get() }` and the value is read at USE time; a
1572
+ * directly constructed plugin passes a plain boolean (the schema is the loader's face — the same
1573
+ * split the numeric knobs document as their second clamp layer). A single-form read is wrong
1574
+ * either way: `=== true` is never true for a reference, and `?.get()` ignores the plain value.
1575
+ * @param value - the configured switch, in either form, or undefined.
1576
+ * @returns the switch; undefined reads as off.
1577
+ */
1578
+ function liveFlag(value) {
1579
+ return typeof value === "boolean" ? value : value?.get() ?? false;
1580
+ }
1552
1581
  function apply(ctx, rawConfig = {}) {
1553
1582
  const config = rawConfig;
1554
1583
  assertSkillsRootAliasRetired(rawConfig);
1555
1584
  const skillsRootValue = resolveRootConfig(rawConfig).root;
1585
+ ctx.inject(["settings"], (scope) => {
1586
+ const settings = scope.settings;
1587
+ if (typeof settings?.configure !== "function") return;
1588
+ const disposer = settings.configure({ auto: false }, ctx.fiber);
1589
+ if (typeof disposer === "function") scope.effect(() => disposer, "evolution-commands: settings presentation");
1590
+ });
1556
1591
  let runs;
1557
1592
  ctx.inject(["evolutionIo"], (ioCtx) => {
1558
1593
  const ioRegistry = ioCtx.get("evolutionIo");
@@ -1782,6 +1817,23 @@ function apply(ctx, rawConfig = {}) {
1782
1817
  };
1783
1818
  return result.ok ? ok(result.message) : err(result.message);
1784
1819
  }
1820
+ if (input === "skill pin" || input.startsWith("skill pin ") || input === "skill unpin" || input.startsWith("skill unpin ")) {
1821
+ const pinned = !input.startsWith("skill unpin");
1822
+ const name = input.slice(pinned ? 10 : 12).trim();
1823
+ if (!name) return err(`Usage: /evolution skill ${pinned ? "pin" : "unpin"} <name>`);
1824
+ const ioRegistry = ctx.get("evolutionIo");
1825
+ if (!ioRegistry) return err("Evolution IO registry not mounted — skill pinning unavailable.");
1826
+ const result = await newSkillLibrary({
1827
+ config: { root: skillsRootValue },
1828
+ io: ioRegistry.provider(),
1829
+ ctx,
1830
+ limits: {
1831
+ ...DEFAULT_SKILL_LIMITS,
1832
+ ...policyStageLimits(ctx.get("evolutionPolicy")?.get?.())
1833
+ }
1834
+ }).setPinned(name, pinned);
1835
+ return result.ok ? ok(result.message) : err(result.message);
1836
+ }
1785
1837
  if (input === "skills health") {
1786
1838
  const curator = ctx.get("evolutionCurator");
1787
1839
  if (!curator) return err(errorText("e-302-curator-service-not-mounted"));
@@ -1937,7 +1989,7 @@ function apply(ctx, rawConfig = {}) {
1937
1989
  }).catch((error) => {
1938
1990
  ctx.logger.warn(`evolution-commands: failed to record maintain event: ${String(error)}`);
1939
1991
  });
1940
- if (config.maintainHandoffOnSettle === true && resultRef !== void 0 && runAgent !== void 0) try {
1992
+ if (liveFlag(config.maintainHandoffOnSettle) && resultRef !== void 0 && runAgent !== void 0) try {
1941
1993
  if (deliverHandoff(runAgent, createUserMessage({
1942
1994
  content: [{
1943
1995
  type: "text",
@@ -2,7 +2,7 @@
2
2
  * Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate|skills refresh.
3
3
  * @module @lmzhen/dsh-evolution-commands
4
4
  */
5
- import type { Context } from '@deepseek-ai/cordis';
5
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
6
6
  import z from '@deepseek-ai/schemastery';
7
7
  import type { ContextFormed } from '@deepseek-ai/dsh-llm';
8
8
  declare module '@deepseek-ai/dsh-llm' {
@@ -38,8 +38,13 @@ export interface Config {
38
38
  maintainTimeoutMs?: number | undefined;
39
39
  /** G2 (0.20.0): when a scan settles successfully, hand its report to the model
40
40
  * automatically (the same pointer+protocol injection `maintain handoff` performs).
41
- * Default false: the injection wakes a turn, which costs a model call. */
42
- maintainHandoffOnSettle?: boolean | undefined;
41
+ * Default false: the injection wakes a turn, which costs a model call.
42
+ *
43
+ * `.volatile()` (0.21.0, G3): the field carries a USER LAYER, so the loader hands the live
44
+ * reference and the switch is read at every settle — a settings write turns it on or off without
45
+ * a reload. A directly constructed plugin still passes a plain boolean (the schema's default is
46
+ * the loader's face), so the reader accepts both. */
47
+ maintainHandoffOnSettle?: boolean | Volatile<boolean> | undefined;
43
48
  /** Threat-scan exemption labels for WRITE-path skill mutations (P2-18).
44
49
  * Threaded into the SkillLibrary this package constructs for `restructure`
45
50
  * (its only write path); the core-side constructor option carries the same
@@ -55,14 +60,14 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
55
60
  skillsRoot: z<string, string, "defined">;
56
61
  maintainCooldownMs: z<number, number, "defined">;
57
62
  maintainTimeoutMs: z<number, number, "defined">;
58
- maintainHandoffOnSettle: z<boolean, boolean, "defined">;
63
+ maintainHandoffOnSettle: z<boolean, boolean, "volatile-defined">;
59
64
  threatExemptLabels: z<string[], string[], "defined">;
60
65
  }>>, Schemastery.ObjectT<NoInfer<{
61
66
  root: z<string, string, "defined">;
62
67
  skillsRoot: z<string, string, "defined">;
63
68
  maintainCooldownMs: z<number, number, "defined">;
64
69
  maintainTimeoutMs: z<number, number, "defined">;
65
- maintainHandoffOnSettle: z<boolean, boolean, "defined">;
70
+ maintainHandoffOnSettle: z<boolean, boolean, "volatile-defined">;
66
71
  threatExemptLabels: z<string[], string[], "defined">;
67
72
  }>>, "plain">;
68
73
  export declare function apply(ctx: Context, rawConfig?: Config): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-commands",
3
3
  "description": "Human commands for the evolution family (/evolution pending|curator|maintain|doctor) (community build)",
4
- "version": "0.20.0",
4
+ "version": "0.21.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,12 +27,12 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.4",
30
- "@lmzhen/dsh-evolution-approval": "^0.20.0",
31
- "@lmzhen/dsh-evolution-core": "^0.20.0",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.20.0"
30
+ "@lmzhen/dsh-evolution-approval": "^0.21.0",
31
+ "@lmzhen/dsh-evolution-core": "^0.21.0",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.21.0"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.20.0"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.21.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",