@lmzhen/dsh-evolution-commands 0.3.68 → 0.3.70
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 +51 -14
- package/lib/types/doctor.d.ts +8 -2
- package/package.json +10 -10
package/lib/index.js
CHANGED
|
@@ -26,17 +26,32 @@ const EVOLUTION_BUNDLE_TAILS = new Set([
|
|
|
26
26
|
"dsh-evolution-host",
|
|
27
27
|
"dsh-evolution-preset"
|
|
28
28
|
]);
|
|
29
|
-
/** Profile bundle rows for evolution-family packages across all profiles.
|
|
30
|
-
|
|
29
|
+
/** Profile bundle rows for evolution-family packages across all profiles.
|
|
30
|
+
*
|
|
31
|
+
* `onReadError` reports a profile whose manifest could not be READ or PARSED.
|
|
32
|
+
* Like the directory-enumeration failure, it is fail-closed at every caller:
|
|
33
|
+
* a truncated manifest is indistinguishable from "no bundles" by the returned
|
|
34
|
+
* list alone, and treating it as "no bundles" is exactly the fail-open state
|
|
35
|
+
* the double-mount exclusion exists to prevent. */
|
|
36
|
+
function collectEvolutionBundles(home, onReadError) {
|
|
31
37
|
const profilesDir = join(home, "profiles");
|
|
32
38
|
if (!existsSync(profilesDir)) return [];
|
|
33
39
|
const bundles = [];
|
|
34
|
-
|
|
40
|
+
let profileEntries;
|
|
41
|
+
try {
|
|
42
|
+
profileEntries = readdirSync(profilesDir, { withFileTypes: true });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
onReadError?.(error, "profiles-dir");
|
|
45
|
+
return bundles;
|
|
46
|
+
}
|
|
47
|
+
for (const profile of profileEntries) {
|
|
35
48
|
if (!profile.isDirectory()) continue;
|
|
36
49
|
try {
|
|
37
50
|
const manifest = JSON.parse(readFileSync(join(profilesDir, profile.name, "package.json"), "utf8"));
|
|
38
51
|
for (const name of manifest.dsh?.profile?.bundles ?? []) if (EVOLUTION_BUNDLE_TAILS.has(tailOf(name))) bundles.push(name);
|
|
39
|
-
} catch {
|
|
52
|
+
} catch (error) {
|
|
53
|
+
onReadError?.(error, "profile-manifest");
|
|
54
|
+
}
|
|
40
55
|
}
|
|
41
56
|
return bundles;
|
|
42
57
|
}
|
|
@@ -58,14 +73,17 @@ function envIssues() {
|
|
|
58
73
|
*/
|
|
59
74
|
async function diagnose(ctx, options = {}) {
|
|
60
75
|
const home = options.home ?? evolutionRoot();
|
|
61
|
-
const
|
|
76
|
+
const conflicts = [];
|
|
77
|
+
const bundles = collectEvolutionBundles(home, (error, scope) => {
|
|
78
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
79
|
+
conflicts.push(scope === "profiles-dir" ? `the home profiles directory could not be enumerated (${detail}) — bundle-based install-form and conflict checks are DEGRADED and may miss installed bundles` : `a profile manifest could not be read or parsed (${detail}) — this profile's bundle rows are UNKNOWN, so the install-form and conflict checks are DEGRADED and may miss installed bundles`);
|
|
80
|
+
});
|
|
62
81
|
const has = (name) => ctx.get(name) !== void 0;
|
|
63
82
|
const full = bundles.some((name) => tailOf(name) === "dsh-evolution-all");
|
|
64
83
|
const host = bundles.some((name) => tailOf(name) === "dsh-evolution-host");
|
|
65
84
|
const preset = bundles.some((name) => tailOf(name) === "dsh-evolution-preset");
|
|
66
85
|
const presetDirInstalled = existsSync(join(join(home, ".agent-presets", "evolution"), "agent.cordis.yml"));
|
|
67
86
|
const layered = host && presetDirInstalled;
|
|
68
|
-
const conflicts = [];
|
|
69
87
|
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.");
|
|
70
88
|
if (full && preset) conflicts.push("evolution-all and evolution-preset are installed together — the infra rows double-mount. Keep ONE.");
|
|
71
89
|
if (host && preset) conflicts.push("evolution-host and evolution-preset are installed together — the infra rows double-mount. Keep ONE.");
|
|
@@ -99,7 +117,7 @@ async function diagnose(ctx, options = {}) {
|
|
|
99
117
|
if (env.length > 0) actions.push("Fix the DSH_EVOLUTION_* variable listed above.");
|
|
100
118
|
if (services.review && !services.curator) actions.push("Curator service is not mounted — automatic curation is off; verify the host/all bundle row set is complete.");
|
|
101
119
|
if (pendingCount === null && services.approval) actions.push("Approval service is mounted but pending listing failed — check the evolution state service.");
|
|
102
|
-
if ((executingCount ?? 0) > 0) actions.push(`${executingCount} staged write(s) are
|
|
120
|
+
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.`);
|
|
103
121
|
return {
|
|
104
122
|
installForm,
|
|
105
123
|
bundles,
|
|
@@ -115,7 +133,7 @@ function renderDoctorText(report) {
|
|
|
115
133
|
const lines = [
|
|
116
134
|
`Evolution doctor — install form: ${report.installForm}`,
|
|
117
135
|
`bundles (all profiles): ${report.bundles.length > 0 ? report.bundles.join(", ") : "(none)"}`,
|
|
118
|
-
`services: review=${report.services.review} curator=${report.services.curator} approval=${report.services.approval} skillUsage=${report.services.skillUsage} io=${report.services.io}`,
|
|
136
|
+
`services: review=${report.services.review} (inferred from bundles, all profiles) curator=${report.services.curator} approval=${report.services.approval} skillUsage=${report.services.skillUsage} io=${report.services.io}`,
|
|
119
137
|
`pending: ${report.pendingCount === null ? "unknown" : report.pendingCount}`,
|
|
120
138
|
`executing: ${report.executingCount === null ? "unknown" : report.executingCount}`
|
|
121
139
|
];
|
|
@@ -224,12 +242,12 @@ function apply(ctx, rawConfig = {}) {
|
|
|
224
242
|
let maintainInFlightSince = 0;
|
|
225
243
|
ctx.inject(["commands"], (commandCtx) => {
|
|
226
244
|
const commands = commandCtx.commands;
|
|
227
|
-
|
|
245
|
+
const evolutionCommand = {
|
|
228
246
|
name: "evolution",
|
|
229
247
|
description: "Self-evolution status and approval controls",
|
|
230
248
|
recordInput: false,
|
|
231
249
|
input: { hint: renderHint() },
|
|
232
|
-
async
|
|
250
|
+
run: async (invocation) => {
|
|
233
251
|
const rawInputTrimmed = invocation.rawInput.trim();
|
|
234
252
|
const input = rawInputTrimmed.replace(/\s+/g, " ");
|
|
235
253
|
const ok = (text) => ({
|
|
@@ -243,7 +261,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
243
261
|
const approval = ctx.get("evolutionApproval");
|
|
244
262
|
const pendingMatch = /^pending(?: --detail)?$/.exec(input);
|
|
245
263
|
if (pendingMatch) {
|
|
246
|
-
const
|
|
264
|
+
const listed = approval ? [...await approval.list("pending"), ...await approval.list("executing")] : [];
|
|
265
|
+
const pending = [...new Map(listed.map((row) => [row.id, row])).values()];
|
|
247
266
|
if (pending.length === 0) return ok("No pending evolution writes.");
|
|
248
267
|
const detailed = pendingMatch[0] === "pending --detail";
|
|
249
268
|
return ok(pending.map((p) => {
|
|
@@ -449,7 +468,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
449
468
|
logger: { warn: (message) => {
|
|
450
469
|
ctx.logger.warn(message);
|
|
451
470
|
} },
|
|
452
|
-
rootExists: () => ioRegistry.provider().exists(skillsRootValue),
|
|
471
|
+
rootExists: skillsRootValue === "" ? void 0 : () => ioRegistry.provider().exists(skillsRootValue),
|
|
453
472
|
skillRoot: skillsRootValue
|
|
454
473
|
}, {
|
|
455
474
|
timeoutMs: runTimeoutMs,
|
|
@@ -482,6 +501,19 @@ function apply(ctx, rawConfig = {}) {
|
|
|
482
501
|
}
|
|
483
502
|
if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected bare `maintain`, `maintain --timeout <ms>` or `maintain --timeout=<ms>` (a positive integer). Got: " + input);
|
|
484
503
|
if (input === "preset install") {
|
|
504
|
+
{
|
|
505
|
+
let installedBundles;
|
|
506
|
+
try {
|
|
507
|
+
installedBundles = collectEvolutionBundles(evolutionRoot(), (error) => {
|
|
508
|
+
throw error;
|
|
509
|
+
});
|
|
510
|
+
} catch (enumerationError) {
|
|
511
|
+
return err(`Refusing to install the layered Evolution preset: the home profile directory or one of its profile manifests could not be read (${enumerationError instanceof Error ? enumerationError.message : String(enumerationError)}) — the all/preset double-mount check could not run. Resolve access to the profiles directory and retry, or install via the layered installer, which enforces the same exclusion up front.`);
|
|
512
|
+
}
|
|
513
|
+
const allBundle = installedBundles.find((name) => name === "dsh-evolution-all" || name.endsWith("/dsh-evolution-all"));
|
|
514
|
+
const presetBundle = installedBundles.find((name) => name === "dsh-evolution-preset" || name.endsWith("/dsh-evolution-preset"));
|
|
515
|
+
if (allBundle || presetBundle) return err(`Refusing to install the layered Evolution preset: "${allBundle ?? presetBundle}" is installed in this home — its model rows (tool-memory / tool-skill-manage / tool-session-query / skill-catalog) would double-mount with the preset's and the profile fails loud at startup. Keep ONE: remove the ${allBundle ? "evolution-all bundle" : "evolution-preset bundle"} before installing the layered preset (see /evolution doctor).`);
|
|
516
|
+
}
|
|
485
517
|
const target = join(evolutionRoot(), ".agent-presets", "evolution");
|
|
486
518
|
try {
|
|
487
519
|
const source = resolveAgentPresetDir(import.meta.url);
|
|
@@ -565,8 +597,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
565
597
|
return ok(renderDoctorText(report));
|
|
566
598
|
}
|
|
567
599
|
return ok(`Evolution: memory, skills, review, curator — self-evolution status, approval and maintenance.\n${renderHelpText()}`);
|
|
568
|
-
}
|
|
569
|
-
|
|
600
|
+
},
|
|
601
|
+
handler: (invocation) => evolutionCommand.run(invocation).catch((error) => ({
|
|
602
|
+
kind: "error",
|
|
603
|
+
text: `evolution: command failed: ${error instanceof Error ? error.message : String(error)}\nRun /evolution doctor to inspect services and state files; a corrupted state file is quarantined as <file>.corrupt for inspection.`
|
|
604
|
+
}))
|
|
605
|
+
};
|
|
606
|
+
commandCtx.effect(() => commands.register(evolutionCommand));
|
|
570
607
|
});
|
|
571
608
|
}
|
|
572
609
|
/**
|
package/lib/types/doctor.d.ts
CHANGED
|
@@ -21,8 +21,14 @@ export interface DoctorReport {
|
|
|
21
21
|
executingCount: number | null;
|
|
22
22
|
actions: string[];
|
|
23
23
|
}
|
|
24
|
-
/** Profile bundle rows for evolution-family packages across all profiles.
|
|
25
|
-
|
|
24
|
+
/** Profile bundle rows for evolution-family packages across all profiles.
|
|
25
|
+
*
|
|
26
|
+
* `onReadError` reports a profile whose manifest could not be READ or PARSED.
|
|
27
|
+
* Like the directory-enumeration failure, it is fail-closed at every caller:
|
|
28
|
+
* a truncated manifest is indistinguishable from "no bundles" by the returned
|
|
29
|
+
* list alone, and treating it as "no bundles" is exactly the fail-open state
|
|
30
|
+
* the double-mount exclusion exists to prevent. */
|
|
31
|
+
export declare function collectEvolutionBundles(home: string, onReadError?: (error: unknown, scope: 'profiles-dir' | 'profile-manifest') => void): string[];
|
|
26
32
|
/**
|
|
27
33
|
* Diagnose the deployment. `ctx` supplies service presence (a bare stub with
|
|
28
34
|
* only `get` is enough); `home` defaults to the evolution root so tests can
|
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.
|
|
4
|
+
"version": "0.3.70",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,21 +31,21 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
36
|
-
"@lmzhen/dsh-evolution-maintenance": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.70",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.70",
|
|
36
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.3.70"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@lmzhen/dsh-evolution-agent-preset": "^0.3.
|
|
39
|
+
"@lmzhen/dsh-evolution-agent-preset": "^0.3.70"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
43
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
44
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
43
|
+
"@deepseek-ai/dsh-commands": "^0.1.5-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
49
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
48
|
+
"@deepseek-ai/dsh-commands": "^0.1.5-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.2"
|
|
50
50
|
}
|
|
51
51
|
}
|