@lmzhen/dsh-evolution-curator 0.3.17 → 0.3.19
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 +67 -35
- package/lib/types/index.d.ts +18 -7
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var EvolutionCurator = class extends Service {
|
|
|
32
32
|
curatorProvider: z.string().default("deepseek-official"),
|
|
33
33
|
qualityWarnStaleAfterDays: z.number().default(DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS),
|
|
34
34
|
minIdleHours: z.number().default(DEFAULT_MIN_IDLE_HOURS),
|
|
35
|
+
minIdleFailOpen: z.boolean().default(true),
|
|
35
36
|
excludeSkillNames: z.array(z.string()).default([]),
|
|
36
37
|
manageUnmanaged: z.boolean().default(false),
|
|
37
38
|
pruneBuiltins: z.boolean().default(false),
|
|
@@ -53,6 +54,7 @@ var EvolutionCurator = class extends Service {
|
|
|
53
54
|
curatorProvider;
|
|
54
55
|
qualityWarnStaleAfterDays;
|
|
55
56
|
minIdleHours;
|
|
57
|
+
minIdleFailOpen;
|
|
56
58
|
excludeSkillNames;
|
|
57
59
|
manageUnmanaged;
|
|
58
60
|
pruneBuiltins;
|
|
@@ -66,6 +68,10 @@ var EvolutionCurator = class extends Service {
|
|
|
66
68
|
timer;
|
|
67
69
|
bootCheck;
|
|
68
70
|
running = false;
|
|
71
|
+
/** 0.3.18 (E-18): stateless first-run defer fires ONCE per process — the
|
|
72
|
+
* in-memory clock is seeded and later due runs must proceed, or the
|
|
73
|
+
* persisted===null defer repeats forever (no state service to persist). */
|
|
74
|
+
statelessFirstRunDeferred = false;
|
|
69
75
|
constructor(ctx, config = {}) {
|
|
70
76
|
super(ctx, "evolutionCurator");
|
|
71
77
|
this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
@@ -80,6 +86,7 @@ var EvolutionCurator = class extends Service {
|
|
|
80
86
|
this.curatorProvider = config.curatorProvider ?? "deepseek-official";
|
|
81
87
|
this.qualityWarnStaleAfterDays = config.qualityWarnStaleAfterDays ?? DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS;
|
|
82
88
|
this.minIdleHours = config.minIdleHours ?? DEFAULT_MIN_IDLE_HOURS;
|
|
89
|
+
this.minIdleFailOpen = config.minIdleFailOpen ?? true;
|
|
83
90
|
this.excludeSkillNames = new Set(config.excludeSkillNames ?? []);
|
|
84
91
|
this.manageUnmanaged = config.manageUnmanaged ?? false;
|
|
85
92
|
this.pruneBuiltins = config.pruneBuiltins ?? false;
|
|
@@ -134,15 +141,13 @@ var EvolutionCurator = class extends Service {
|
|
|
134
141
|
* kept deliberately: an unattended resume must not auto-run mid-boot).
|
|
135
142
|
*/
|
|
136
143
|
async setPaused(paused) {
|
|
137
|
-
|
|
138
|
-
const persisted = await stateService?.loadCuratorState() ?? null;
|
|
139
|
-
await stateService?.saveCuratorState({
|
|
144
|
+
await this.curatorStateService()?.transactCuratorState((current) => ({
|
|
140
145
|
schemaVersion: 1,
|
|
141
|
-
lastRunAt:
|
|
142
|
-
runCount:
|
|
143
|
-
lastSummary:
|
|
146
|
+
lastRunAt: current?.lastRunAt ?? Date.now(),
|
|
147
|
+
runCount: current?.runCount ?? 0,
|
|
148
|
+
lastSummary: current?.lastSummary ?? (paused ? "paused" : "resumed"),
|
|
144
149
|
paused
|
|
145
|
-
});
|
|
150
|
+
}));
|
|
146
151
|
}
|
|
147
152
|
/** Current persisted curator state (read-only view for /evolution curator status). */
|
|
148
153
|
async status() {
|
|
@@ -156,8 +161,24 @@ var EvolutionCurator = class extends Service {
|
|
|
156
161
|
* wake it, and never duplicates gate logic.
|
|
157
162
|
*/
|
|
158
163
|
async autoCheck() {
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
try {
|
|
165
|
+
const last = (await this.curatorStateService()?.loadCuratorState())?.lastRunAt ?? this.lastRun;
|
|
166
|
+
if (Date.now() - last >= this.lifecycle().intervalHours * 36e5) await this.run();
|
|
167
|
+
} catch (error) {
|
|
168
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
169
|
+
this.ctx.logger.warn(`evolution-curator: automatic check failed: ${reason}`);
|
|
170
|
+
try {
|
|
171
|
+
const runId = randomUUID();
|
|
172
|
+
await this.io.writeText(join(evolutionHome(), "reports", `curator-error-${runId}.json`), JSON.stringify({
|
|
173
|
+
runId,
|
|
174
|
+
failed: true,
|
|
175
|
+
error: reason,
|
|
176
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
177
|
+
}, null, 2));
|
|
178
|
+
} catch (persistenceError) {
|
|
179
|
+
this.ctx.logger.warn(`evolution-curator: failed to persist auto-check error report: ${persistenceError instanceof Error ? persistenceError.message : String(persistenceError)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
161
182
|
}
|
|
162
183
|
/**
|
|
163
184
|
* Optional Hermes-curator LLM pass. The model may only NOMINATE pruning and
|
|
@@ -173,14 +194,14 @@ var EvolutionCurator = class extends Service {
|
|
|
173
194
|
if (candidates.length === 0) return empty;
|
|
174
195
|
const llm = this.ctx.get("llm");
|
|
175
196
|
if (!llm) return empty;
|
|
176
|
-
const model = this.ctx.get("evolutionPolicy")?.get()
|
|
197
|
+
const model = this.ctx.get("evolutionPolicy")?.get()?.curatorModel ?? "deepseek-v4-pro";
|
|
177
198
|
const clusters = computePrefixClusters(candidates);
|
|
178
199
|
const clusterLines = clusters.length === 0 ? ["Prefix clusters observed in the candidate list: (none)"] : ["Prefix clusters observed in the candidate list (orientation only — verify against the names above; you may also flag additional clusters):", ...clusters.map((cluster) => `- '${cluster.key}': ${cluster.members.join(", ")}`)];
|
|
179
200
|
const prompt = [
|
|
180
201
|
options.dryRun ? CURATOR_DRY_RUN_BANNER : "",
|
|
181
202
|
CURATOR_PROMPT,
|
|
182
203
|
"",
|
|
183
|
-
|
|
204
|
+
"Stale candidates observed by the deterministic lifecycle scanner:",
|
|
184
205
|
...candidates.map((name) => `- ${name}`),
|
|
185
206
|
"",
|
|
186
207
|
...clusterLines,
|
|
@@ -211,7 +232,8 @@ var EvolutionCurator = class extends Service {
|
|
|
211
232
|
prunings: parsed.prunings.filter((name) => candidates.includes(name)),
|
|
212
233
|
consolidations: parsed.consolidations.filter((item) => candidates.includes(item.from))
|
|
213
234
|
};
|
|
214
|
-
} catch {
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.ctx.logger.warn(`evolution-curator: LLM nomination pass failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
215
237
|
return empty;
|
|
216
238
|
}
|
|
217
239
|
}
|
|
@@ -320,20 +342,24 @@ var EvolutionCurator = class extends Service {
|
|
|
320
342
|
report: this.skippedReport(runId, startedAt),
|
|
321
343
|
skipped: "active-session"
|
|
322
344
|
};
|
|
323
|
-
if (!ignoreGates && persisted === null) {
|
|
324
|
-
await stateService
|
|
345
|
+
if (!ignoreGates && persisted === null && (stateService !== void 0 || !this.statelessFirstRunDeferred)) {
|
|
346
|
+
if (stateService) await stateService.saveCuratorState({
|
|
325
347
|
schemaVersion: 1,
|
|
326
348
|
lastRunAt: Date.now(),
|
|
327
349
|
runCount: 0,
|
|
328
350
|
lastSummary: "first-run-deferred",
|
|
329
351
|
paused: false
|
|
330
352
|
});
|
|
353
|
+
else {
|
|
354
|
+
this.lastRun = Date.now();
|
|
355
|
+
this.statelessFirstRunDeferred = true;
|
|
356
|
+
}
|
|
331
357
|
return {
|
|
332
358
|
stale: [],
|
|
333
359
|
archived: [],
|
|
334
360
|
errors: [],
|
|
335
361
|
report: this.skippedReport(runId, startedAt),
|
|
336
|
-
skipped: "first-run-deferred"
|
|
362
|
+
skipped: stateService ? "first-run-deferred" : "first-run-deferred(stateless)"
|
|
337
363
|
};
|
|
338
364
|
}
|
|
339
365
|
const root = this.skills.root;
|
|
@@ -420,13 +446,15 @@ var EvolutionCurator = class extends Service {
|
|
|
420
446
|
}
|
|
421
447
|
const llmHint = !this.llmReview && result.markStale.length > 0 ? " (llmReview: off - deterministic archive only; set llmReview: true for the LLM merge channel)" : "";
|
|
422
448
|
const summary = `${dryRun ? "dry-run" : "auto"}: stale:${result.markStale.length} archived:${archivedSkills.length} consolidated:${gatedNominations.consolidations.length}${llmHint}`;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
449
|
+
await stateService?.transactCuratorState((current) => {
|
|
450
|
+
const pausedNow = current?.paused ?? false;
|
|
451
|
+
return {
|
|
452
|
+
schemaVersion: 1,
|
|
453
|
+
lastRunAt: dryRun ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
|
|
454
|
+
runCount: dryRun ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
|
|
455
|
+
lastSummary: summary,
|
|
456
|
+
paused: pausedNow
|
|
457
|
+
};
|
|
430
458
|
});
|
|
431
459
|
return {
|
|
432
460
|
stale: result.markStale,
|
|
@@ -511,6 +539,15 @@ var EvolutionCurator = class extends Service {
|
|
|
511
539
|
const suppressedAdded = /* @__PURE__ */ new Set();
|
|
512
540
|
const stateOwned = new Set(input.stateOwned ?? []);
|
|
513
541
|
for (const name of archiveCandidates) {
|
|
542
|
+
if (!treeNames.has(name)) {
|
|
543
|
+
const record = usage.get(name);
|
|
544
|
+
if (record) {
|
|
545
|
+
record.state = "archived";
|
|
546
|
+
record.archived_at = record.archived_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
547
|
+
stateOwned.add(name);
|
|
548
|
+
}
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
514
551
|
const archived = await this.skills.archive(name, {
|
|
515
552
|
reason: "Lifecycle: reached archive threshold",
|
|
516
553
|
allowBundled: this.pruneBuiltins
|
|
@@ -603,7 +640,7 @@ var EvolutionCurator = class extends Service {
|
|
|
603
640
|
}
|
|
604
641
|
recentSessionActive() {
|
|
605
642
|
const agents = this.ctx.get("agents");
|
|
606
|
-
if (!agents) return
|
|
643
|
+
if (!agents) return !this.minIdleFailOpen;
|
|
607
644
|
let latest = 0;
|
|
608
645
|
for (const agent of agents.list()) {
|
|
609
646
|
const events = agent.session.events;
|
|
@@ -650,20 +687,15 @@ var EvolutionCurator = class extends Service {
|
|
|
650
687
|
}
|
|
651
688
|
async latestReport() {
|
|
652
689
|
const reportsRoot = join(evolutionHome(), "reports");
|
|
653
|
-
const names = (await this.io.list(reportsRoot)).filter((name) => name.startsWith("curator-") && name.endsWith(".json"))
|
|
690
|
+
const names = (await this.io.list(reportsRoot)).filter((name) => name.startsWith("curator-") && name.endsWith(".json"));
|
|
654
691
|
let latest = null;
|
|
655
692
|
for (const name of names) {
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (latest === null || startedAt > latest.startedAt) latest = {
|
|
663
|
-
name,
|
|
664
|
-
startedAt
|
|
665
|
-
};
|
|
666
|
-
} catch {}
|
|
693
|
+
const mtime = await this.io.mtime?.(join(reportsRoot, name)) ?? null;
|
|
694
|
+
if (mtime === null) continue;
|
|
695
|
+
if (latest === null || mtime > latest.mtime) latest = {
|
|
696
|
+
name,
|
|
697
|
+
mtime
|
|
698
|
+
};
|
|
667
699
|
}
|
|
668
700
|
if (latest === null) return null;
|
|
669
701
|
const raw = await this.io.readText(join(reportsRoot, latest.name));
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Context, Service } from '@deepseek-ai/cordis';
|
|
|
6
6
|
import type Schema from '@deepseek-ai/schemastery';
|
|
7
7
|
import { EvolutionGateSet, SkillLibrary } from '@deepseek-ai/dsh-evolution-core';
|
|
8
8
|
import { type CuratorConsolidation, type CuratorNominations, type CuratorRunReport, type ScopeView, type SkillActionResult, type SkillHealthVerdict } from '@deepseek-ai/dsh-evolution-core';
|
|
9
|
+
import type { CuratorStateRecord } from '@deepseek-ai/dsh-evolution-state';
|
|
9
10
|
declare module '@deepseek-ai/cordis' {
|
|
10
11
|
interface Context {
|
|
11
12
|
evolutionCurator: EvolutionCurator;
|
|
@@ -23,6 +24,10 @@ export interface Config {
|
|
|
23
24
|
qualityWarnStaleAfterDays?: number;
|
|
24
25
|
/** Skip automatic runs while any session was active within this many hours (0 disables). */
|
|
25
26
|
minIdleHours?: number;
|
|
27
|
+
/** When true (default), a missing `agents` service is treated as "no active
|
|
28
|
+
* session" (fail-open — the idle gate lets the run proceed); when false the
|
|
29
|
+
* gate fails closed and defers the run until activity can be measured. */
|
|
30
|
+
minIdleFailOpen?: boolean;
|
|
26
31
|
/** Skill names excluded from the automated lifecycle. */
|
|
27
32
|
excludeSkillNames?: string[];
|
|
28
33
|
/** Include usage records whose created_by is not 'agent' in lifecycle decisions. */
|
|
@@ -54,14 +59,15 @@ export interface CuratorRunOutcome {
|
|
|
54
59
|
/** LLM nominations when the optional review pass is enabled (audit visibility). */
|
|
55
60
|
nominations?: CuratorNominations;
|
|
56
61
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Persisted curator-state record. The base fields are the authoritative
|
|
64
|
+
* `CuratorStateRecord` from evolution-state-storage; `schemaVersion` is an
|
|
65
|
+
* optional on-disk shape marker the storage seam leaves untyped (kept for
|
|
66
|
+
* legacy reads — 0.3.18, E-53).
|
|
67
|
+
*/
|
|
68
|
+
export type CuratorStateRecordShape = CuratorStateRecord & {
|
|
59
69
|
schemaVersion?: number;
|
|
60
|
-
|
|
61
|
-
runCount: number;
|
|
62
|
-
lastSummary: string;
|
|
63
|
-
paused: boolean;
|
|
64
|
-
}
|
|
70
|
+
};
|
|
65
71
|
/**
|
|
66
72
|
* Block LLM-nominated consolidations that would touch a gate-protected name:
|
|
67
73
|
* exclude / referenced / suppressed skills must never merge (neither as the
|
|
@@ -86,6 +92,7 @@ export declare class EvolutionCurator extends Service {
|
|
|
86
92
|
private readonly curatorProvider;
|
|
87
93
|
private readonly qualityWarnStaleAfterDays;
|
|
88
94
|
private readonly minIdleHours;
|
|
95
|
+
private readonly minIdleFailOpen;
|
|
89
96
|
private readonly excludeSkillNames;
|
|
90
97
|
private readonly manageUnmanaged;
|
|
91
98
|
private readonly pruneBuiltins;
|
|
@@ -99,6 +106,10 @@ export declare class EvolutionCurator extends Service {
|
|
|
99
106
|
private timer;
|
|
100
107
|
private bootCheck;
|
|
101
108
|
private running;
|
|
109
|
+
/** 0.3.18 (E-18): stateless first-run defer fires ONCE per process — the
|
|
110
|
+
* in-memory clock is seeded and later due runs must proceed, or the
|
|
111
|
+
* persisted===null defer repeats forever (no state service to persist). */
|
|
112
|
+
private statelessFirstRunDeferred;
|
|
102
113
|
constructor(ctx: Context, config?: Config);
|
|
103
114
|
private lifecycle;
|
|
104
115
|
start(): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-curator",
|
|
3
3
|
"description": "Deterministic skill lifecycle and recovery (community build)",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.19",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,20 +33,20 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.3.19"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
40
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
41
41
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
42
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
43
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
42
|
+
"@lmzhen/dsh-evolution-io": "^0.3.19",
|
|
43
|
+
"@lmzhen/dsh-evolution-state": "^0.3.19"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
47
47
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
48
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
49
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
50
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
48
|
+
"@lmzhen/dsh-evolution-core": "^0.3.19",
|
|
49
|
+
"@lmzhen/dsh-evolution-io": "^0.3.19",
|
|
50
|
+
"@lmzhen/dsh-evolution-state": "^0.3.19"
|
|
51
51
|
}
|
|
52
52
|
}
|