dsh-continual-evolve 0.7.0 → 0.7.2
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 +22 -8
- package/README.zh.md +17 -8
- package/lib/apply.js +17 -9
- package/lib/auto.d.ts +24 -5
- package/lib/auto.js +159 -71
- package/lib/command.d.ts +5 -0
- package/lib/command.js +141 -3
- package/lib/evolve-event.d.ts +4 -2
- package/lib/evolve-event.js +12 -2
- package/lib/fate.d.ts +1 -1
- package/lib/fate.js +11 -5
- package/lib/index.d.ts +60 -4
- package/lib/index.js +52 -22
- package/lib/llm-text.d.ts +23 -0
- package/lib/llm-text.js +50 -33
- package/lib/planner.d.ts +17 -0
- package/lib/planner.js +23 -5
- package/lib/prefix-cache.d.ts +78 -0
- package/lib/prefix-cache.js +168 -0
- package/lib/review-scheduler.d.ts +37 -0
- package/lib/review-scheduler.js +110 -0
- package/lib/review.d.ts +14 -1
- package/lib/review.js +23 -2
- package/lib/runtime.d.ts +24 -0
- package/lib/runtime.js +87 -0
- package/lib/service.d.ts +11 -1
- package/lib/service.js +19 -3
- package/lib/store.d.ts +38 -0
- package/lib/store.js +88 -1
- package/lib/token-usage.d.ts +68 -0
- package/lib/token-usage.js +211 -0
- package/lib/tool.d.ts +9 -0
- package/lib/tool.js +37 -6
- package/lib/turn-snapshot.d.ts +58 -0
- package/lib/turn-snapshot.js +188 -0
- package/lib/wrapup-command.js +10 -1
- package/lib/wrapup.d.ts +5 -0
- package/lib/wrapup.js +6 -0
- package/package.json +1 -1
package/lib/command.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface CommandRuntimeOptions {
|
|
|
17
17
|
autoCase: boolean;
|
|
18
18
|
/** Mechanical promotion guards for wrapup/fate (2026-08-22 policy). */
|
|
19
19
|
promotionPolicy: PromotionPolicy;
|
|
20
|
+
/**
|
|
21
|
+
* Initial auto-review default from plugin config. The listener is always
|
|
22
|
+
* registered; this is not a registration gate.
|
|
23
|
+
*/
|
|
24
|
+
autoReview?: boolean;
|
|
20
25
|
}
|
|
21
26
|
export declare function registerEvolveCommand(ctx: Context, engine: EvolutionEngine, opts: CommandGateOptions, runtime: CommandRuntimeOptions): void;
|
|
22
27
|
/**
|
package/lib/command.js
CHANGED
|
@@ -5,7 +5,8 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { requireGlobalApproval } from "./approval.js";
|
|
7
7
|
import { saveHarnessState } from "./state.js";
|
|
8
|
-
import { appendResult, storePaths } from "./store.js";
|
|
8
|
+
import { appendResult, pruneJsonlFile, storePaths } from "./store.js";
|
|
9
|
+
import { DEFAULT_REFINEMENTS_RETAIN } from "./store.js";
|
|
9
10
|
import { entrySourceOf } from "./source.js";
|
|
10
11
|
import { filterLogBySession, formatLogLine, pluginLogFilePath } from "./logfile.js";
|
|
11
12
|
import { collectFailureSummary, formatFailureSummary } from "./failures.js";
|
|
@@ -14,8 +15,10 @@ import { executeMountCommand, executeUnmountCommand } from "./mount-command.js";
|
|
|
14
15
|
import { executeBenchmarkCommand } from "./benchmark-command.js";
|
|
15
16
|
import { executeWrapupCommand } from "./wrapup-command.js";
|
|
16
17
|
import { projectKeyOf } from "./project.js";
|
|
17
|
-
import { loadUsage } from "./usage.js";
|
|
18
|
+
import { loadUsage, getUsageCount } from "./usage.js";
|
|
19
|
+
import { loadGateRuntime, saveGateRuntime } from "./runtime.js";
|
|
18
20
|
import { planConsolidation } from "./consolidate.js";
|
|
21
|
+
import { loadTokenUsage, renderTokenUsageReport } from "./token-usage.js";
|
|
19
22
|
const USAGE = `Usage:
|
|
20
23
|
/evolve show this help and the current local store
|
|
21
24
|
/evolve list [project|global] list entries (default local staging; "project" = this project's cross-session store, "global" = cross-project store)
|
|
@@ -40,7 +43,10 @@ const USAGE = `Usage:
|
|
|
40
43
|
/evolve unmount <id> remove a hot-mounted plugin
|
|
41
44
|
/evolve goal show the evolution goal (round-driven auto-review)
|
|
42
45
|
/evolve goal <objective> create/update the evolution goal
|
|
43
|
-
/evolve goal done complete the evolution goal
|
|
46
|
+
/evolve goal done complete the evolution goal
|
|
47
|
+
/evolve pause | resume pause/resume the auto-review gate (manual tools and commands keep working)
|
|
48
|
+
/evolve status gate state (patch flag + runtime switch) plus store entry counts
|
|
49
|
+
/evolve usage injection counts + exact direct-call token usage (benchmark subagents excluded)`;
|
|
44
50
|
export function registerEvolveCommand(ctx, engine, opts, runtime) {
|
|
45
51
|
ctx.commands.register({
|
|
46
52
|
name: "evolve",
|
|
@@ -323,6 +329,14 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
323
329
|
appendResult(paths, result);
|
|
324
330
|
}
|
|
325
331
|
}
|
|
332
|
+
// Storage hygiene (#20): an imported history obeys the
|
|
333
|
+
// same tail budget as a live one.
|
|
334
|
+
try {
|
|
335
|
+
pruneJsonlFile(paths.resultsPath, engine.retention?.refinements ?? DEFAULT_REFINEMENTS_RETAIN);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
// ignored — the next apply retries
|
|
339
|
+
}
|
|
326
340
|
}
|
|
327
341
|
return success(`imported ${scope} store from ${path}`);
|
|
328
342
|
}
|
|
@@ -338,6 +352,12 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
338
352
|
...(instructions ? { instructions } : {}),
|
|
339
353
|
global: scope === "global",
|
|
340
354
|
signal: invocation.signal,
|
|
355
|
+
tokenUsage: {
|
|
356
|
+
baseDir: engine.baseDir,
|
|
357
|
+
sessionId,
|
|
358
|
+
retain: engine.retention.tokenUsage,
|
|
359
|
+
onError: (cause) => ctx.logger("continual-evolve").warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
360
|
+
},
|
|
341
361
|
// skill-creator template facts (fallback: builtin guide).
|
|
342
362
|
skillsRoot: join(engine.baseDir, "skills"),
|
|
343
363
|
});
|
|
@@ -354,6 +374,31 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
354
374
|
case "wrapup": {
|
|
355
375
|
return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy);
|
|
356
376
|
}
|
|
377
|
+
case "pause":
|
|
378
|
+
case "resume": {
|
|
379
|
+
// The switch is independent of the static registration flag. A
|
|
380
|
+
// resume explicitly enables automatic snapshots; a pause leaves
|
|
381
|
+
// the enable bit intact so status explains the paused state.
|
|
382
|
+
const pausing = sub === "pause";
|
|
383
|
+
const defaultEnabled = runtime.autoReview ?? false;
|
|
384
|
+
const current = loadGateRuntime(engine.baseDir, defaultEnabled);
|
|
385
|
+
if (pausing && current.paused) {
|
|
386
|
+
return success("auto-review gate is already paused (no change).");
|
|
387
|
+
}
|
|
388
|
+
if (!pausing && current.enabled && !current.paused) {
|
|
389
|
+
return success("auto-review gate is already running (no change).");
|
|
390
|
+
}
|
|
391
|
+
saveGateRuntime(engine.baseDir, pausing, pausing ? current.enabled : true);
|
|
392
|
+
return success(pausing
|
|
393
|
+
? "auto-review gate paused: no automatic snapshots, fate assessments, or gate LLM calls until /evolve resume. Manual evolve_* tools and /evolve commands keep working."
|
|
394
|
+
: "auto-review gate resumed: successful turns will produce incremental snapshots immediately.");
|
|
395
|
+
}
|
|
396
|
+
case "status": {
|
|
397
|
+
return success(renderGateStatus(engine, sessionId, projectKeyOf(invocation.agent), runtime));
|
|
398
|
+
}
|
|
399
|
+
case "usage": {
|
|
400
|
+
return success(renderUsageReport(engine, sessionId, projectKeyOf(invocation.agent)));
|
|
401
|
+
}
|
|
357
402
|
case "goal": {
|
|
358
403
|
return executeGoalCommand(ctx, invocation, rest);
|
|
359
404
|
}
|
|
@@ -412,6 +457,99 @@ function demoteEntry(engine, id, sessionId, projectKey) {
|
|
|
412
457
|
}
|
|
413
458
|
return error(`entry ${id} not found in the global, project, or local store`);
|
|
414
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Status answers whether the always-registered listener is enabled at runtime
|
|
462
|
+
* and what stores/retention it currently sees.
|
|
463
|
+
*/
|
|
464
|
+
function renderGateStatus(engine, sessionId, projectKey, runtime) {
|
|
465
|
+
const configuredDefault = runtime.autoReview === undefined ? "unknown" : runtime.autoReview ? "on" : "off";
|
|
466
|
+
const current = loadGateRuntime(engine.baseDir, runtime.autoReview ?? false);
|
|
467
|
+
const effective = current.paused ? "PAUSED (resume with /evolve resume)" : current.enabled ? "running" : "off (enable with /evolve resume)";
|
|
468
|
+
const gateLine = `gate: listener registered · config default ${configuredDefault} · runtime ${effective}`;
|
|
469
|
+
const countEntries = (state) => Object.values(state.entries).reduce((n, byKind) => n + Object.keys(byKind).length, 0);
|
|
470
|
+
const lines = [gateLine];
|
|
471
|
+
lines.push(`stores: global ${countEntries(engine.load("global", undefined))} entries · local(${sessionId}) ${countEntries(engine.load("local", sessionId))} entries`);
|
|
472
|
+
if (projectKey) {
|
|
473
|
+
try {
|
|
474
|
+
lines[lines.length - 1] += ` · project ${countEntries(engine.load("project", projectKey))} entries`;
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
lines[lines.length - 1] += " · project (unavailable)";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const retention = engine.retention;
|
|
481
|
+
if (retention) {
|
|
482
|
+
lines.push(`retention: snapshots ${retention.snapshots} · refinements ${retention.refinements} · reviews ${retention.reviews} · token usage ${retention.tokenUsage} (historyRetain)`);
|
|
483
|
+
}
|
|
484
|
+
return lines.join("\n");
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* #21 P0 usage ledger: injection counts per entry across the stores the
|
|
488
|
+
* human can see (global + this session + this project). Counts are
|
|
489
|
+
* per-session since the v2 usage shape — "in how many sessions did this
|
|
490
|
+
* entry surface" — the exposure half of the #18a归零实验 verdict.
|
|
491
|
+
*/
|
|
492
|
+
function renderUsageReport(engine, sessionId, projectKey) {
|
|
493
|
+
const store = loadUsage(engine.baseDir);
|
|
494
|
+
const rows = [];
|
|
495
|
+
const seen = new Set();
|
|
496
|
+
const collect = (state) => {
|
|
497
|
+
for (const kind of Object.keys(state.entries)) {
|
|
498
|
+
for (const entry of Object.values(state.entries[kind])) {
|
|
499
|
+
const key = `${kind}:${entry.id}`;
|
|
500
|
+
if (seen.has(key))
|
|
501
|
+
continue;
|
|
502
|
+
seen.add(key);
|
|
503
|
+
const count = getUsageCount(store, kind, entry.id);
|
|
504
|
+
rows.push({
|
|
505
|
+
key,
|
|
506
|
+
title: entry.title,
|
|
507
|
+
count,
|
|
508
|
+
...(store.lastSession?.[`${kind}:${entry.id}`] ? { lastSession: store.lastSession[`${kind}:${entry.id}`] } : {}),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
collect(engine.load("global", undefined));
|
|
514
|
+
collect(engine.load("local", sessionId));
|
|
515
|
+
if (projectKey) {
|
|
516
|
+
try {
|
|
517
|
+
collect(engine.load("project", projectKey));
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
// project store unavailable here — global+local still report
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const liveKeys = new Set(rows.map((r) => r.key));
|
|
524
|
+
const orphaned = Object.keys(store.counts).filter((k) => !liveKeys.has(k)).length;
|
|
525
|
+
const total = rows.reduce((n, r) => n + r.count, 0);
|
|
526
|
+
const injected = rows.filter((r) => r.count > 0).sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : 1));
|
|
527
|
+
const stale = rows.filter((r) => r.count === 0).sort((a, b) => (a.key < b.key ? -1 : 1));
|
|
528
|
+
const lines = [`injected entries: ${total} injections across ${injected.length} of ${rows.length} stored entries${orphaned > 0 ? ` (+${orphaned} historical key(s) for deleted entries)` : ""}`];
|
|
529
|
+
if (injected.length > 0) {
|
|
530
|
+
lines.push("injected (top 15):");
|
|
531
|
+
for (const row of injected.slice(0, 15)) {
|
|
532
|
+
lines.push(` ${row.key} — ${row.count}× · ${row.title}${row.lastSession ? ` (last in ${row.lastSession})` : ""}`);
|
|
533
|
+
}
|
|
534
|
+
if (injected.length > 15) {
|
|
535
|
+
lines.push(` … and ${injected.length - 15} more`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
lines.push("injected: (none yet — nothing has surfaced into a session prompt)");
|
|
540
|
+
}
|
|
541
|
+
if (stale.length > 0) {
|
|
542
|
+
lines.push(`never injected (${stale.length}):`);
|
|
543
|
+
for (const row of stale.slice(0, 20)) {
|
|
544
|
+
lines.push(` ${row.key} · ${row.title}`);
|
|
545
|
+
}
|
|
546
|
+
if (stale.length > 20) {
|
|
547
|
+
lines.push(` … and ${stale.length - 20} more`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
lines.push("", ...renderTokenUsageReport(loadTokenUsage(engine.baseDir), engine.retention.tokenUsage));
|
|
551
|
+
return lines.join("\n");
|
|
552
|
+
}
|
|
415
553
|
function renderResult(result) {
|
|
416
554
|
const applied = result.appliedEdits.filter((e) => e.applied);
|
|
417
555
|
const failed = result.appliedEdits.filter((e) => !e.applied);
|
package/lib/evolve-event.d.ts
CHANGED
|
@@ -32,7 +32,9 @@ export declare function buildEvolveCompleteEvent(result: RefinementResult, trigg
|
|
|
32
32
|
/**
|
|
33
33
|
* Emit an evolve_complete event to the reviews.jsonl audit trail. The event
|
|
34
34
|
* is JSONL-formatted (one line) so consumers can tail and parse it. This is
|
|
35
|
-
* a best-effort write — failure never blocks the refinement path.
|
|
35
|
+
* a best-effort write — failure never blocks the refinement path. Storage
|
|
36
|
+
* hygiene (#20): the trail is truncated to its tail after the append so the
|
|
37
|
+
* audit file cannot grow without bound; readers only need a recent window.
|
|
36
38
|
*/
|
|
37
|
-
export declare function emitEvolveComplete(baseDir: string, event: EvolveCompleteEvent): void;
|
|
39
|
+
export declare function emitEvolveComplete(baseDir: string, event: EvolveCompleteEvent, reviewsRetain?: number): void;
|
|
38
40
|
//# sourceMappingURL=evolve-event.d.ts.map
|
package/lib/evolve-event.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
+
import { DEFAULT_REVIEWS_RETAIN, pruneJsonlFile, reviewsPath } from "./store.js";
|
|
14
15
|
/** Build a structured evolve-complete event from a refinement result. */
|
|
15
16
|
export function buildEvolveCompleteEvent(result, trigger, sessionId) {
|
|
16
17
|
return {
|
|
@@ -34,9 +35,11 @@ export function buildEvolveCompleteEvent(result, trigger, sessionId) {
|
|
|
34
35
|
/**
|
|
35
36
|
* Emit an evolve_complete event to the reviews.jsonl audit trail. The event
|
|
36
37
|
* is JSONL-formatted (one line) so consumers can tail and parse it. This is
|
|
37
|
-
* a best-effort write — failure never blocks the refinement path.
|
|
38
|
+
* a best-effort write — failure never blocks the refinement path. Storage
|
|
39
|
+
* hygiene (#20): the trail is truncated to its tail after the append so the
|
|
40
|
+
* audit file cannot grow without bound; readers only need a recent window.
|
|
38
41
|
*/
|
|
39
|
-
export function emitEvolveComplete(baseDir, event) {
|
|
42
|
+
export function emitEvolveComplete(baseDir, event, reviewsRetain = DEFAULT_REVIEWS_RETAIN) {
|
|
40
43
|
try {
|
|
41
44
|
const dir = join(baseDir, "evolve");
|
|
42
45
|
mkdirSync(dir, { recursive: true });
|
|
@@ -44,6 +47,13 @@ export function emitEvolveComplete(baseDir, event) {
|
|
|
44
47
|
}
|
|
45
48
|
catch {
|
|
46
49
|
// Event emission is diagnostic; never interrupt the refinement path.
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
pruneJsonlFile(reviewsPath(baseDir), reviewsRetain);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignored — the next emission retries
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
//# sourceMappingURL=evolve-event.js.map
|
package/lib/fate.d.ts
CHANGED
|
@@ -119,7 +119,7 @@ export interface FateApplyResult {
|
|
|
119
119
|
export declare function applyLocalFates(engine: EvolutionEngine, sessionId: string, plan: FatePlan, localState: HarnessState, mode: FateApplyMode): FateApplyResult;
|
|
120
120
|
/**
|
|
121
121
|
* The gate's local-fate phase. Runs after the review phase on every gate
|
|
122
|
-
* trigger (
|
|
122
|
+
* trigger (turn_snapshot / compact), subject to cadence and cooldown. All
|
|
123
123
|
* failures are contained and recorded — a broken fate dimension never
|
|
124
124
|
* disturbs the agent loop.
|
|
125
125
|
*/
|
package/lib/fate.js
CHANGED
|
@@ -192,7 +192,7 @@ export function applyLocalFates(engine, sessionId, plan, localState, mode) {
|
|
|
192
192
|
}
|
|
193
193
|
/**
|
|
194
194
|
* The gate's local-fate phase. Runs after the review phase on every gate
|
|
195
|
-
* trigger (
|
|
195
|
+
* trigger (turn_snapshot / compact), subject to cadence and cooldown. All
|
|
196
196
|
* failures are contained and recorded — a broken fate dimension never
|
|
197
197
|
* disturbs the agent loop.
|
|
198
198
|
*/
|
|
@@ -201,6 +201,12 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
201
201
|
return;
|
|
202
202
|
const logger = ctx.logger("continual-evolve");
|
|
203
203
|
const sessionId = agent.id;
|
|
204
|
+
const tokenUsage = {
|
|
205
|
+
baseDir: engine.baseDir,
|
|
206
|
+
sessionId,
|
|
207
|
+
retain: engine.retention.tokenUsage,
|
|
208
|
+
onError: (cause) => logger.warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
209
|
+
};
|
|
204
210
|
const localState = engine.load("local", sessionId);
|
|
205
211
|
const globalState = engine.load("global", undefined);
|
|
206
212
|
const candidates = listLocalCandidates(localState, globalState, engine.baseDir);
|
|
@@ -218,7 +224,7 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
218
224
|
state.lastFateAt = state.turns;
|
|
219
225
|
let assessment;
|
|
220
226
|
try {
|
|
221
|
-
assessment = await assessLocalEntries(ctx, agent, candidates);
|
|
227
|
+
assessment = await assessLocalEntries(ctx, agent, candidates, { tokenUsage, tokenUsagePhase: "fate" });
|
|
222
228
|
}
|
|
223
229
|
catch (cause) {
|
|
224
230
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -249,7 +255,7 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
249
255
|
rationale: `fate: ${assessment.rationale} (${applied.join("; ")})`,
|
|
250
256
|
refinementId: results.map((result) => result.id).join(","),
|
|
251
257
|
});
|
|
252
|
-
if (config.notifyOnAutoReview && reason === "turn_interval" && applied.length > 0) {
|
|
258
|
+
if (config.notifyOnAutoReview && (reason === "turn_snapshot" || reason === "turn_interval") && applied.length > 0) {
|
|
253
259
|
notifyFateApplied(ctx, agent, applied);
|
|
254
260
|
}
|
|
255
261
|
return;
|
|
@@ -303,7 +309,7 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
303
309
|
rationale: `fate: ${assessment.rationale} (${applied.join("; ")})`,
|
|
304
310
|
refinementId: results.map((result) => result.id).join(","),
|
|
305
311
|
});
|
|
306
|
-
if (config.notifyOnAutoReview && reason === "turn_interval") {
|
|
312
|
+
if (config.notifyOnAutoReview && (reason === "turn_snapshot" || reason === "turn_interval")) {
|
|
307
313
|
notifyFateApplied(ctx, agent, applied);
|
|
308
314
|
}
|
|
309
315
|
return;
|
|
@@ -327,7 +333,7 @@ export function buildFateNotice(applied) {
|
|
|
327
333
|
"请用一句话简短确认即可,不要调用任何工具。",
|
|
328
334
|
].join("\n");
|
|
329
335
|
}
|
|
330
|
-
/** Queue the follow-up notice turn (
|
|
336
|
+
/** Queue the follow-up notice turn (successful-turn path only). */
|
|
331
337
|
function notifyFateApplied(ctx, agent, applied) {
|
|
332
338
|
try {
|
|
333
339
|
agent.followup(createUserMessage({
|
package/lib/index.d.ts
CHANGED
|
@@ -10,14 +10,23 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
10
10
|
baseDir: z<string, string, "plain">;
|
|
11
11
|
/** System-prompt section order for the evolution guidance. */
|
|
12
12
|
sectionOrder: z<number, number, "defined">;
|
|
13
|
-
/**
|
|
13
|
+
/** Initial automatic-review default when no runtime switch exists (off by default: it costs model calls). */
|
|
14
14
|
autoReview: z<boolean, boolean, "defined">;
|
|
15
|
-
/**
|
|
15
|
+
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
16
16
|
reviewIntervalTurns: z<number, number, "defined">;
|
|
17
17
|
/** Trajectory slice handed to the gate, in characters. */
|
|
18
18
|
maxReviewInputChars: z<number, number, "defined">;
|
|
19
19
|
/** Output budget for the cheap gate call. */
|
|
20
20
|
reviewBudgetTokens: z<number, number, "defined">;
|
|
21
|
+
/**
|
|
22
|
+
* Planner prefix-cache routing for the gate and planner inputs: `auto`
|
|
23
|
+
* prepends a session-derived message prefix when the session shows
|
|
24
|
+
* cache-read evidence (dropping the redundant flat trajectory text),
|
|
25
|
+
* `session` always prefixes, `off` keeps the legacy flat-text input.
|
|
26
|
+
*/
|
|
27
|
+
plannerPrefixCache: z<"session" | "off" | "auto", "session" | "off" | "auto", "defined">;
|
|
28
|
+
/** Session-prefix budget for Route A planning inputs, in characters. */
|
|
29
|
+
plannerPrefixMaxChars: z<number, number, "defined">;
|
|
21
30
|
/** After an approved gate run with applied edits, queue a visible follow-up notice. */
|
|
22
31
|
notifyOnAutoReview: z<boolean, boolean, "defined">;
|
|
23
32
|
/** Cross-session (global) edits require an explicit human approval. */
|
|
@@ -74,19 +83,47 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
74
83
|
promotionMinChars: z<number, number, "defined">;
|
|
75
84
|
/** Entry-directory lines injected per build before folding into a counter. */
|
|
76
85
|
injectionDirectoryLines: z<number, number, "defined">;
|
|
86
|
+
/**
|
|
87
|
+
* Storage hygiene (#20): how much append-only past each write keeps.
|
|
88
|
+
* `snapshots` = full-state copies per store, `refinements` = tail lines
|
|
89
|
+
* per store history, `reviews` = tail lines of the shared audit trail,
|
|
90
|
+
* `tokenUsage` = tail lines of the direct-call token ledger.
|
|
91
|
+
* Absent fields fall back to the store defaults (20 / 500 / 500 / 500) —
|
|
92
|
+
* rollback needs recent snapshots, readers need a recent window.
|
|
93
|
+
*/
|
|
94
|
+
historyRetain: z<Schemastery.ObjectS<NoInfer<{
|
|
95
|
+
snapshots: z<number, number, "plain">;
|
|
96
|
+
refinements: z<number, number, "plain">;
|
|
97
|
+
reviews: z<number, number, "plain">;
|
|
98
|
+
tokenUsage: z<number, number, "plain">;
|
|
99
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
100
|
+
snapshots: z<number, number, "plain">;
|
|
101
|
+
refinements: z<number, number, "plain">;
|
|
102
|
+
reviews: z<number, number, "plain">;
|
|
103
|
+
tokenUsage: z<number, number, "plain">;
|
|
104
|
+
}>>, "plain">;
|
|
77
105
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
78
106
|
/** Root for evolution stores; defaults to the resolved DSH home. */
|
|
79
107
|
baseDir: z<string, string, "plain">;
|
|
80
108
|
/** System-prompt section order for the evolution guidance. */
|
|
81
109
|
sectionOrder: z<number, number, "defined">;
|
|
82
|
-
/**
|
|
110
|
+
/** Initial automatic-review default when no runtime switch exists (off by default: it costs model calls). */
|
|
83
111
|
autoReview: z<boolean, boolean, "defined">;
|
|
84
|
-
/**
|
|
112
|
+
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
85
113
|
reviewIntervalTurns: z<number, number, "defined">;
|
|
86
114
|
/** Trajectory slice handed to the gate, in characters. */
|
|
87
115
|
maxReviewInputChars: z<number, number, "defined">;
|
|
88
116
|
/** Output budget for the cheap gate call. */
|
|
89
117
|
reviewBudgetTokens: z<number, number, "defined">;
|
|
118
|
+
/**
|
|
119
|
+
* Planner prefix-cache routing for the gate and planner inputs: `auto`
|
|
120
|
+
* prepends a session-derived message prefix when the session shows
|
|
121
|
+
* cache-read evidence (dropping the redundant flat trajectory text),
|
|
122
|
+
* `session` always prefixes, `off` keeps the legacy flat-text input.
|
|
123
|
+
*/
|
|
124
|
+
plannerPrefixCache: z<"session" | "off" | "auto", "session" | "off" | "auto", "defined">;
|
|
125
|
+
/** Session-prefix budget for Route A planning inputs, in characters. */
|
|
126
|
+
plannerPrefixMaxChars: z<number, number, "defined">;
|
|
90
127
|
/** After an approved gate run with applied edits, queue a visible follow-up notice. */
|
|
91
128
|
notifyOnAutoReview: z<boolean, boolean, "defined">;
|
|
92
129
|
/** Cross-session (global) edits require an explicit human approval. */
|
|
@@ -143,6 +180,25 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
143
180
|
promotionMinChars: z<number, number, "defined">;
|
|
144
181
|
/** Entry-directory lines injected per build before folding into a counter. */
|
|
145
182
|
injectionDirectoryLines: z<number, number, "defined">;
|
|
183
|
+
/**
|
|
184
|
+
* Storage hygiene (#20): how much append-only past each write keeps.
|
|
185
|
+
* `snapshots` = full-state copies per store, `refinements` = tail lines
|
|
186
|
+
* per store history, `reviews` = tail lines of the shared audit trail,
|
|
187
|
+
* `tokenUsage` = tail lines of the direct-call token ledger.
|
|
188
|
+
* Absent fields fall back to the store defaults (20 / 500 / 500 / 500) —
|
|
189
|
+
* rollback needs recent snapshots, readers need a recent window.
|
|
190
|
+
*/
|
|
191
|
+
historyRetain: z<Schemastery.ObjectS<NoInfer<{
|
|
192
|
+
snapshots: z<number, number, "plain">;
|
|
193
|
+
refinements: z<number, number, "plain">;
|
|
194
|
+
reviews: z<number, number, "plain">;
|
|
195
|
+
tokenUsage: z<number, number, "plain">;
|
|
196
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
197
|
+
snapshots: z<number, number, "plain">;
|
|
198
|
+
refinements: z<number, number, "plain">;
|
|
199
|
+
reviews: z<number, number, "plain">;
|
|
200
|
+
tokenUsage: z<number, number, "plain">;
|
|
201
|
+
}>>, "plain">;
|
|
146
202
|
}>>, "plain">;
|
|
147
203
|
/**
|
|
148
204
|
* Structurally typed resolved config (loader passes the validated object).
|
package/lib/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mounts the evolution engine, registers the model-facing evolve_* tools,
|
|
5
5
|
* the human-facing /evolve command, the system-prompt guidance section, and
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* the automatic review gate. The listener is always registered, while its
|
|
7
|
+
* effective state is controlled by the runtime switch. Successful turns feed
|
|
8
|
+
* incremental snapshots through a per-session serial scheduler.
|
|
9
9
|
*/
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -29,14 +29,23 @@ export const Config = z.object({
|
|
|
29
29
|
baseDir: z.string(),
|
|
30
30
|
/** System-prompt section order for the evolution guidance. */
|
|
31
31
|
sectionOrder: z.natural().default(118),
|
|
32
|
-
/**
|
|
32
|
+
/** Initial automatic-review default when no runtime switch exists (off by default: it costs model calls). */
|
|
33
33
|
autoReview: z.boolean().default(false),
|
|
34
|
-
/**
|
|
34
|
+
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
35
35
|
reviewIntervalTurns: z.natural().default(6),
|
|
36
36
|
/** Trajectory slice handed to the gate, in characters. */
|
|
37
37
|
maxReviewInputChars: z.natural().default(40000),
|
|
38
38
|
/** Output budget for the cheap gate call. */
|
|
39
39
|
reviewBudgetTokens: z.natural().default(4096),
|
|
40
|
+
/**
|
|
41
|
+
* Planner prefix-cache routing for the gate and planner inputs: `auto`
|
|
42
|
+
* prepends a session-derived message prefix when the session shows
|
|
43
|
+
* cache-read evidence (dropping the redundant flat trajectory text),
|
|
44
|
+
* `session` always prefixes, `off` keeps the legacy flat-text input.
|
|
45
|
+
*/
|
|
46
|
+
plannerPrefixCache: z.union([z.const("auto"), z.const("session"), z.const("off")]).default("auto"),
|
|
47
|
+
/** Session-prefix budget for Route A planning inputs, in characters. */
|
|
48
|
+
plannerPrefixMaxChars: z.natural().default(12000),
|
|
40
49
|
/** After an approved gate run with applied edits, queue a visible follow-up notice. */
|
|
41
50
|
notifyOnAutoReview: z.boolean().default(true),
|
|
42
51
|
/** Cross-session (global) edits require an explicit human approval. */
|
|
@@ -93,6 +102,20 @@ export const Config = z.object({
|
|
|
93
102
|
promotionMinChars: z.natural().default(100),
|
|
94
103
|
/** Entry-directory lines injected per build before folding into a counter. */
|
|
95
104
|
injectionDirectoryLines: z.natural().default(15),
|
|
105
|
+
/**
|
|
106
|
+
* Storage hygiene (#20): how much append-only past each write keeps.
|
|
107
|
+
* `snapshots` = full-state copies per store, `refinements` = tail lines
|
|
108
|
+
* per store history, `reviews` = tail lines of the shared audit trail,
|
|
109
|
+
* `tokenUsage` = tail lines of the direct-call token ledger.
|
|
110
|
+
* Absent fields fall back to the store defaults (20 / 500 / 500 / 500) —
|
|
111
|
+
* rollback needs recent snapshots, readers need a recent window.
|
|
112
|
+
*/
|
|
113
|
+
historyRetain: z.object({
|
|
114
|
+
snapshots: z.natural(),
|
|
115
|
+
refinements: z.natural(),
|
|
116
|
+
reviews: z.natural(),
|
|
117
|
+
tokenUsage: z.natural(),
|
|
118
|
+
}),
|
|
96
119
|
});
|
|
97
120
|
export function apply(ctx, config) {
|
|
98
121
|
const baseDir = resolveDshHome(config.baseDir);
|
|
@@ -111,7 +134,7 @@ export function apply(ctx, config) {
|
|
|
111
134
|
.warn(`skill materialization failed for ${result.id}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
112
135
|
}
|
|
113
136
|
},
|
|
114
|
-
});
|
|
137
|
+
}, config.historyRetain ? { historyRetain: { ...config.historyRetain } } : {});
|
|
115
138
|
ctx.provide(EVOLUTION_SERVICE, { engine, baseDir });
|
|
116
139
|
ctx.systemPrompt.section({
|
|
117
140
|
name: "tool:continual-evolve",
|
|
@@ -139,6 +162,7 @@ export function apply(ctx, config) {
|
|
|
139
162
|
autoRollbackOnReject: config.autoRollbackOnReject ?? true,
|
|
140
163
|
autoCase: config.autoCase ?? true,
|
|
141
164
|
promotionPolicy,
|
|
165
|
+
...(config.autoReview !== undefined ? { autoReview: config.autoReview } : {}),
|
|
142
166
|
});
|
|
143
167
|
// Plugin-owned file logging: every cordis log message lands in
|
|
144
168
|
// <baseDir>/evolve/plugin.log regardless of how dsh web was launched —
|
|
@@ -153,22 +177,28 @@ export function apply(ctx, config) {
|
|
|
153
177
|
void restoreMounted(ctx, baseDir).catch((cause) => {
|
|
154
178
|
ctx.logger("continual-evolve").warn(`mount restore failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
155
179
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
// The listener is always registered. `autoReview` is only the initial
|
|
181
|
+
// runtime default; `/evolve resume` can enable it without a profile edit
|
|
182
|
+
// or process restart, while manual tools and commands stay independent.
|
|
183
|
+
registerAutoReview(ctx, engine, {
|
|
184
|
+
intervalTurns: config.reviewIntervalTurns ?? 6,
|
|
185
|
+
enabledByDefault: config.autoReview ?? false,
|
|
186
|
+
maxInputChars: config.maxReviewInputChars ?? 40000,
|
|
187
|
+
budgetTokens: config.reviewBudgetTokens ?? 4096,
|
|
188
|
+
notifyOnAutoReview: config.notifyOnAutoReview ?? true,
|
|
189
|
+
localFate: config.localFate ?? true,
|
|
190
|
+
fateIntervalTurns: config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6,
|
|
191
|
+
goalBlockedWrapupTurns: config.goalBlockedWrapupTurns ?? 3,
|
|
192
|
+
promotionPolicy,
|
|
193
|
+
autoCase: config.autoCase ?? true,
|
|
194
|
+
rubricKey,
|
|
195
|
+
...(config.historyRetain?.reviews !== undefined ? { reviewsRetain: config.historyRetain.reviews } : {}),
|
|
196
|
+
...(config.reviewModel ? { reviewModel: config.reviewModel } : {}),
|
|
197
|
+
...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
|
|
198
|
+
...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
|
|
199
|
+
});
|
|
200
|
+
const runtimeDefault = config.autoReview ?? false;
|
|
201
|
+
ctx.logger("continual-evolve").info(`continual-evolve auto-review registered (default ${runtimeDefault ? "on" : "off"}; successful-turn snapshots; local-fate ${config.localFate ?? true ? "on" : "off"} every ${config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6} turns)`);
|
|
172
202
|
ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
|
|
173
203
|
}
|
|
174
204
|
//# sourceMappingURL=index.js.map
|
package/lib/llm-text.d.ts
CHANGED
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* and JSON parsing.
|
|
10
10
|
*/
|
|
11
11
|
import type { Context } from "@deepseek-ai/cordis";
|
|
12
|
+
import { type Message, type TokenUsage } from "@deepseek-ai/dsh-llm";
|
|
13
|
+
/** Terminal outcome of one shared direct LLM call. */
|
|
14
|
+
export type StreamTextOutcome = "success" | "max-tokens" | "error" | "aborted" | "empty";
|
|
15
|
+
/** Provider usage and terminal result observed after the stream settles. */
|
|
16
|
+
export interface StreamTextObservation {
|
|
17
|
+
usage?: TokenUsage;
|
|
18
|
+
outcome: StreamTextOutcome;
|
|
19
|
+
}
|
|
20
|
+
/** Diagnostic observer invoked before streamText returns or throws. */
|
|
21
|
+
export type StreamTextUsageObserver = (observation: StreamTextObservation) => void;
|
|
12
22
|
export interface StreamTextOptions {
|
|
13
23
|
provider: string;
|
|
14
24
|
model: string;
|
|
@@ -16,6 +26,19 @@ export interface StreamTextOptions {
|
|
|
16
26
|
prompt: string;
|
|
17
27
|
maxTokens?: number;
|
|
18
28
|
signal?: AbortSignal | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Route A session prefix: session-derived messages sent before the
|
|
31
|
+
* trailing caller message so providers with prompt caching can serve
|
|
32
|
+
* the shared context at cache-read price. Empty/omitted keeps the
|
|
33
|
+
* legacy single-message request.
|
|
34
|
+
*/
|
|
35
|
+
prefixMessages?: readonly Message[];
|
|
36
|
+
/**
|
|
37
|
+
* Observe the provider-reported usage and terminal outcome after the
|
|
38
|
+
* stream settles. The callback runs before success/error return and is
|
|
39
|
+
* diagnostic-only: throwing from it never changes the model call result.
|
|
40
|
+
*/
|
|
41
|
+
onUsage?: StreamTextUsageObserver;
|
|
19
42
|
}
|
|
20
43
|
/**
|
|
21
44
|
* Stream a single-turn text completion through `ctx.llm`. Forces
|