@lmzhen/dsh-evolution-commands 0.3.79 → 0.3.81

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.
Files changed (2) hide show
  1. package/lib/index.js +74 -14
  2. package/package.json +5 -5
package/lib/index.js CHANGED
@@ -119,6 +119,9 @@ function resolveAgentPresetAsset(asset) {
119
119
  }
120
120
  /** The base rows of bases.json: the comparison iterates the same table the
121
121
  * installer reads, so doctor cannot check a different set of variants.
122
+ * S1-F1: rows also carry the optional `unsupported` note — the layered-form
123
+ * detection below must not treat a base that carries NO family model rows
124
+ * (minimal) as a double-mount participant.
122
125
  * @param path - resolved bases.json.
123
126
  * @returns the rows, or null when the table is unreadable or malformed. */
124
127
  function readAgentPresetBases$1(path) {
@@ -131,7 +134,8 @@ function readAgentPresetBases$1(path) {
131
134
  if (typeof entry.name !== "string" || typeof entry.id !== "string") return null;
132
135
  rows.push({
133
136
  name: entry.name,
134
- id: entry.id
137
+ id: entry.id,
138
+ ...typeof entry.unsupported === "string" ? { unsupported: entry.unsupported } : {}
135
139
  });
136
140
  }
137
141
  return rows.length > 0 ? rows : null;
@@ -155,6 +159,38 @@ function installedPresetIds(root) {
155
159
  return ids;
156
160
  }
157
161
  /**
162
+ * S1-F1: ids of the family's DELIVERED layered presets — every
163
+ * `.agent-presets/<id>/agent.cordis.yml` artifact that can actually carry the
164
+ * family model rows. The detection used to probe ONLY the default base id
165
+ * (`evolution`), so a `--base ptc|cordis` layered install was invisible:
166
+ * installForm degraded to `none`/`host` and the action ladder recommended
167
+ * installing `evolution-all` on top — the exact double-mount the doctor exists
168
+ * to prevent (V27 G6.4 closed it for the default id only).
169
+ *
170
+ * A directory counts only when the base table names it as a SUPPORTED family
171
+ * base (the `unsupported` minimal base carries no model rows and cannot
172
+ * double-mount anything). When the table itself cannot be read, every
173
+ * delivered artifact counts — the visible-by-directory posture of
174
+ * {@link probePresetFreshness}: flag rather than stay silent.
175
+ * @param root - the `.agent-presets` home directory.
176
+ * @returns installed family base ids whose `agent.cordis.yml` is on disk.
177
+ */
178
+ function installedLayeredPresetIds(root) {
179
+ const delivered = installedPresetIds(root).filter((id) => existsSync(join(root, id, "agent.cordis.yml")));
180
+ if (delivered.length === 0) return [];
181
+ const basesPath = resolveAgentPresetAsset("bases.json");
182
+ const bases = basesPath === null ? null : readAgentPresetBases$1(basesPath);
183
+ if (bases === null) return delivered.filter((id) => {
184
+ try {
185
+ return readFileSync(join(root, id, "agent.cordis.yml"), "utf8").includes("@deepseek-ai/dsh-");
186
+ } catch {
187
+ return false;
188
+ }
189
+ });
190
+ const supported = new Set(bases.filter((row) => row.unsupported === void 0).map((row) => row.id));
191
+ return delivered.filter((id) => supported.has(id));
192
+ }
193
+ /**
158
194
  * G3-② (B2): recompute what a fresh generation would write for every
159
195
  * installed variant and compare it byte-for-byte with the file on disk.
160
196
  *
@@ -278,13 +314,15 @@ async function diagnose(ctx, options = {}) {
278
314
  const full = bundles.some((name) => tailOf(name) === "dsh-evolution-all");
279
315
  const host = bundles.some((name) => tailOf(name) === "dsh-evolution-host");
280
316
  const preset = bundles.some((name) => tailOf(name) === "dsh-evolution-preset");
281
- const presetDirInstalled = existsSync(join(join(home, ".agent-presets", "evolution"), "agent.cordis.yml"));
317
+ const layeredIds = installedLayeredPresetIds(join(home, ".agent-presets"));
318
+ const presetDirInstalled = layeredIds.length > 0;
319
+ const layeredArtifactLabel = presetDirInstalled ? layeredIds.join(", ") : "evolution";
282
320
  const layered = host && presetDirInstalled;
283
321
  if (full && host) conflicts.push("evolution-all and evolution-host are installed together — the infra rows double-mount and startup fails loud. Keep ONE: remove the other bundle.");
284
322
  if (full && preset) conflicts.push("evolution-all and evolution-preset are installed together — the infra rows double-mount. Keep ONE.");
285
323
  if (host && preset) conflicts.push("evolution-host and evolution-preset are installed together — the infra rows double-mount. Keep ONE.");
286
- if (full && presetDirInstalled) conflicts.push("evolution-all and the layered Evolution preset are both present — the model rows double-mount. Keep ONE (use layered without all, or drop the preset).");
287
- if (preset && presetDirInstalled) conflicts.push("evolution-preset and the layered Evolution preset are both present — the model rows double-mount (the preset bundle carries the same model rows as all). Keep ONE (drop the preset bundle, or remove the layered preset).");
324
+ if (full && presetDirInstalled) conflicts.push(`evolution-all and the layered Evolution preset (${layeredArtifactLabel}) are both present — the model rows double-mount. Keep ONE (use layered without all, or drop the preset).`);
325
+ if (preset && presetDirInstalled) conflicts.push(`evolution-preset and the layered Evolution preset (${layeredArtifactLabel}) are both present — the model rows double-mount (the preset bundle carries the same model rows as all). Keep ONE (drop the preset bundle, or remove the layered preset).`);
288
326
  const services = {
289
327
  review: full || host || preset,
290
328
  curator: has("evolutionCurator"),
@@ -312,7 +350,7 @@ async function diagnose(ctx, options = {}) {
312
350
  if (mountConflicts.length > 0) actions.push("Resolve the conflict first: keep exactly one of evolution-all / evolution-host / evolution-preset / layered.");
313
351
  else if (undecidable) actions.push("The install form is UNDECIDABLE (see the DEGRADED row above): an unreadable profiles directory or profile manifest hides whatever is installed, so this report must not add or remove a bundle. Fix the reported read failure, then re-run /evolution doctor.");
314
352
  else if (installForm === "none") actions.push("Install the default full bundle: dsh plugin --profile web add @lmzhen/dsh-evolution-all");
315
- else if (installForm === "preset-only") actions.push("The Evolution preset is delivered but no profile mounts an evolution bundle — re-add @lmzhen/dsh-evolution-host for the layered layout (do NOT add all on top of the preset: that double-mounts the model rows), or remove .agent-presets/evolution if the layered layout is no longer wanted.");
353
+ else if (installForm === "preset-only") actions.push(`The Evolution preset is delivered but no profile mounts an evolution bundle — re-add @lmzhen/dsh-evolution-host for the layered layout (do NOT add all on top of the preset: that double-mounts the model rows), or remove .agent-presets/${layeredArtifactLabel} if the layered layout is no longer wanted.`);
316
354
  else if (installForm === "layered") actions.push("Variant form (session opt-in): model tools follow the Evolution preset, and a session on a platform original preset carries no family rows; add @lmzhen/dsh-evolution-all instead if every session should have them.");
317
355
  const env = envIssues();
318
356
  if (env.length > 0) actions.push("Fix the DSH_EVOLUTION_* variable listed above.");
@@ -320,7 +358,7 @@ async function diagnose(ctx, options = {}) {
320
358
  if (pendingCount === null && services.approval) actions.push("Approval service is mounted but pending listing failed — check the evolution state service.");
321
359
  const memoryIssues = memoryInterpolationIssues(home);
322
360
  if (memoryIssues.length > 0) actions.push("Rewrite the memory entries listed above (or run a build with the render-time neutralization) — they broke prompt assembly on older builds.");
323
- if ((executingCount ?? 0) > 0) actions.push(`${executingCount} staged write(s) are EXECUTING (an approve crashed mid-run — or one is still in flight). Inspect with /evolution pending: if you started the approve, verify the landed write and do not reject it; only reject after verifying no write is intended.`);
361
+ if ((executingCount ?? 0) > 0) actions.push(`${executingCount} staged write(s) are EXECUTING (an approve crashed mid-run — or one is still in flight). Inspect with /evolution pending: if you started the approve, verify the landed write and do not reject it; only reject after verifying no write is intended. For a verified orphan (this build: S2-P2-22), /evolution release <id> returns it to the pending window instead.`);
324
362
  for (const row of presetFreshness) {
325
363
  if (row.status !== "differs") continue;
326
364
  actions.push(`Preset variant ${row.destination} DIFFERS from a fresh generation — it is an install-time snapshot; re-run the installer (/evolution preset install) to regenerate`);
@@ -373,6 +411,10 @@ const COMMAND_ENTRIES = [
373
411
  usage: "reject <id>",
374
412
  summary: "drop a staged write without running it"
375
413
  },
414
+ {
415
+ usage: "release <id>",
416
+ summary: "return an orphaned EXECUTING write to the pending window (verify the effect first, then approve or reject)"
417
+ },
376
418
  {
377
419
  usage: "doctor [--json]",
378
420
  summary: "read-only self-check: install form, conflicts, env, services (--json feeds scripts)"
@@ -474,10 +516,13 @@ function apply(ctx, rawConfig = {}) {
474
516
  kind: "error",
475
517
  text
476
518
  });
519
+ const invocationAgent = invocation.agent;
520
+ const agentMissing = (need) => invocationAgent === void 0 ? err(`E-305: this invocation carries no agent — \`${need}\` needs a session-backed call (run it from a session in the GUI or the CLI).`) : void 0;
477
521
  const approval = ctx.get("evolutionApproval");
478
522
  const pendingMatch = /^pending(?: --detail)?$/.exec(input);
479
523
  if (pendingMatch) {
480
- const listed = approval ? [...await approval.list("pending"), ...await approval.list("executing")] : [];
524
+ if (!approval) return err("E-301: approval service not mounted — pending writes cannot be listed or replayed. Next: the evolution-approval row ships with evolution-host/evolution-all — run /evolution doctor to see which services are mounted.");
525
+ const listed = [...await approval.list("pending"), ...await approval.list("executing")];
481
526
  const pending = [...new Map(listed.map((row) => [row.id, row])).values()];
482
527
  if (pending.length === 0) return ok("No pending evolution writes.");
483
528
  const detailed = pendingMatch[0] === "pending --detail";
@@ -504,6 +549,13 @@ function apply(ctx, rawConfig = {}) {
504
549
  };
505
550
  return result.ok ? ok(result.message) : err(result.message);
506
551
  }
552
+ if (input.startsWith("release ")) {
553
+ const id = input.slice(8).trim();
554
+ if (!approval) return err("E-301: approval service not mounted. Next: the evolution-approval row ships with evolution-host/evolution-all — run /evolution doctor to see which services are mounted.");
555
+ if (!approval.release) return err("E-304: the mounted approval service predates the release capability — reject the record instead.");
556
+ const result = await approval.release(id);
557
+ return result.ok ? ok(result.message) : err(result.message);
558
+ }
507
559
  if (input === "curator run") {
508
560
  const curator = ctx.get("evolutionCurator");
509
561
  if (!curator) return err("E-302: curator service not mounted. Next: mount the evolution-curator row (evolution-host/evolution-all) and run /evolution doctor.");
@@ -635,9 +687,15 @@ function apply(ctx, rawConfig = {}) {
635
687
  summary: "learn request"
636
688
  }
637
689
  });
638
- const woke = typeof invocation.agent.followup === "function";
639
- if (woke) invocation.agent.followup(message);
640
- else invocation.agent.inject(message);
690
+ const missingAgent = agentMissing("learn");
691
+ if (missingAgent) return missingAgent;
692
+ const agent = invocationAgent;
693
+ let woke = false;
694
+ if (typeof agent.followup === "function") {
695
+ agent.followup(message);
696
+ woke = true;
697
+ } else if (typeof agent.inject === "function") agent.inject(message);
698
+ else return err("E-305: the invocation agent exposes neither `followup` nor `inject` — this learn request has no delivery channel.");
641
699
  const eventIo = ctx.get("evolutionIo")?.provider();
642
700
  if (eventIo) appendEvolutionEvent(eventIo, eventsFile(evolutionRoot()), {
643
701
  type: "learn",
@@ -798,9 +856,9 @@ function apply(ctx, rawConfig = {}) {
798
856
  const ioRegistry = ctx.get("evolutionIo");
799
857
  if (!ioRegistry) return err("Evolution IO registry not mounted — restructure unavailable.");
800
858
  if (approval) {
801
- const session = invocation.agent.session;
859
+ const session = invocationAgent?.session;
802
860
  const sessionPolicy = effectiveSessionPolicy(ctx, session);
803
- if (approval.isEnabled !== false && sessionPolicy !== "never" && approval.stageForeground !== false && !approval.hasRunner("skill")) return err("Restructure cannot be staged: no skill replay runner is registered — mount the tool-skill-manage row (evolution-agent preset, or evolution-all) or disable evolution-approval.");
861
+ if (session !== void 0 && approval.isEnabled !== false && sessionPolicy !== "never" && approval.stageForeground !== false && !approval.hasRunner("skill")) return err("Restructure cannot be staged: no skill replay runner is registered — mount the tool-skill-manage row (evolution-agent preset, or evolution-all) or disable evolution-approval.");
804
862
  const decision = await approval.request({
805
863
  kind: "skill",
806
864
  summary: `/evolution restructure ${name}${planRunId ? ` (plan ${planRunId})` : ""}`,
@@ -817,8 +875,10 @@ function apply(ctx, rawConfig = {}) {
817
875
  libraryOrigin: "foreground"
818
876
  },
819
877
  origin: "foreground",
820
- ...session.id ? { sessionId: session.id } : {},
821
- session,
878
+ ...session !== void 0 ? {
879
+ sessionId: session.id,
880
+ session
881
+ } : {},
822
882
  ...sessionPolicy !== void 0 ? { sessionPolicy } : {}
823
883
  });
824
884
  if (decision.action === "staged") return ok(`${decision.message}${planRunId ? `\n[audit] plan=${planRunId}` : ""}`);
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.3.79",
4
+ "version": "0.3.81",
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.1",
30
- "@lmzhen/dsh-evolution-approval": "^0.3.79",
31
- "@lmzhen/dsh-evolution-core": "^0.3.79",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.3.79"
30
+ "@lmzhen/dsh-evolution-approval": "^0.3.81",
31
+ "@lmzhen/dsh-evolution-core": "^0.3.81",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.3.81"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.3.79"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.3.81"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",