dsh-continual-evolve 0.7.1 → 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 +13 -6
- package/README.zh.md +8 -6
- package/lib/auto.d.ts +11 -5
- package/lib/auto.js +119 -81
- package/lib/command.d.ts +2 -3
- package/lib/command.js +19 -17
- package/lib/fate.d.ts +1 -1
- package/lib/fate.js +4 -4
- package/lib/index.d.ts +4 -4
- package/lib/index.js +27 -24
- package/lib/planner.d.ts +5 -0
- package/lib/planner.js +1 -1
- package/lib/review-scheduler.d.ts +37 -0
- package/lib/review-scheduler.js +110 -0
- package/lib/review.d.ts +3 -1
- package/lib/review.js +1 -1
- package/lib/runtime.d.ts +13 -13
- package/lib/runtime.js +41 -37
- package/lib/turn-snapshot.d.ts +58 -0
- package/lib/turn-snapshot.js +188 -0
- package/package.json +1 -1
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,9 +29,9 @@ 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),
|
|
@@ -177,25 +177,28 @@ export function apply(ctx, config) {
|
|
|
177
177
|
void restoreMounted(ctx, baseDir).catch((cause) => {
|
|
178
178
|
ctx.logger("continual-evolve").warn(`mount restore failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
179
179
|
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
|
|
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)`);
|
|
199
202
|
ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
|
|
200
203
|
}
|
|
201
204
|
//# sourceMappingURL=index.js.map
|
package/lib/planner.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface PlanOptions {
|
|
|
24
24
|
* every planning call is grounded in what the user actually said.
|
|
25
25
|
*/
|
|
26
26
|
trajectory?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Explicit session-trajectory rows for prefix-cache Route A. When supplied,
|
|
29
|
+
* the planner sees only the scheduler snapshot rather than the full session.
|
|
30
|
+
*/
|
|
31
|
+
trajectoryEvents?: readonly unknown[];
|
|
27
32
|
/**
|
|
28
33
|
* Skills root to read the skill-creator template facts from
|
|
29
34
|
* (`<root>/skill-creator/references/template.md`). When omitted or the
|
package/lib/planner.js
CHANGED
|
@@ -90,7 +90,7 @@ export async function planWithLlm(ctx, options) {
|
|
|
90
90
|
// as a session-derived message prefix (cache-eligible on providers with
|
|
91
91
|
// prompt caching) and drops the auto-extracted trajectory block; Route B
|
|
92
92
|
// keeps the legacy flat-text block. An empty prefix falls back to B.
|
|
93
|
-
const events = sessionEventsOf(agent);
|
|
93
|
+
const events = options.trajectoryEvents ?? sessionEventsOf(agent);
|
|
94
94
|
const routing = resolvePrefixCache(options.prefixCache);
|
|
95
95
|
const route = detectPlannerRoute(events, routing.mode);
|
|
96
96
|
let prefixMessages = [];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serial latest-pending scheduler for automatic review snapshots.
|
|
3
|
+
*
|
|
4
|
+
* This is the DSH equivalent of ZCode's memory extraction scheduler: one
|
|
5
|
+
* runner per session, at most one running snapshot, and one replaceable
|
|
6
|
+
* pending snapshot while the runner is busy. A cursor advances only after a
|
|
7
|
+
* successful or no-op execution; errors and aborts leave it untouched so the
|
|
8
|
+
* next snapshot retries the same durable boundary.
|
|
9
|
+
*/
|
|
10
|
+
/** Terminal state of one scheduled snapshot. */
|
|
11
|
+
export type ReviewSchedulerStatus = "success" | "no-op" | "error" | "aborted";
|
|
12
|
+
/** A scheduler execution receives the exact snapshot and its owner signal. */
|
|
13
|
+
export interface ReviewSchedulerExecution<TSnapshot> {
|
|
14
|
+
readonly snapshot: TSnapshot;
|
|
15
|
+
readonly signal: AbortSignal;
|
|
16
|
+
}
|
|
17
|
+
/** Public scheduler surface used by the auto-review driver and tests. */
|
|
18
|
+
export interface ReviewScheduler<TSnapshot> {
|
|
19
|
+
/** Wait until the current run and all coalesced pending work settle. */
|
|
20
|
+
drain(): Promise<void>;
|
|
21
|
+
/** Last successfully processed or skipped snapshot boundary. */
|
|
22
|
+
getCursor(): string | undefined;
|
|
23
|
+
/** Whether a run or a replaceable pending snapshot exists. */
|
|
24
|
+
hasPendingWork(): boolean;
|
|
25
|
+
/** Submit a snapshot (or an async acquisition) without blocking the caller. */
|
|
26
|
+
schedule(snapshot: TSnapshot | Promise<TSnapshot>): void;
|
|
27
|
+
/** Abort the owner signal, drop pending work, and make later schedules no-ops. */
|
|
28
|
+
shutdown(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a scheduler with ZCode-compatible coalescing and cursor semantics.
|
|
32
|
+
*
|
|
33
|
+
* @param execute - one serial extraction/review operation
|
|
34
|
+
* @param cursorOf - extracts the durable boundary from a snapshot
|
|
35
|
+
*/
|
|
36
|
+
export declare function createReviewScheduler<TSnapshot>(execute: (input: ReviewSchedulerExecution<TSnapshot>) => Promise<ReviewSchedulerStatus>, cursorOf: (snapshot: TSnapshot) => string | undefined): ReviewScheduler<TSnapshot>;
|
|
37
|
+
//# sourceMappingURL=review-scheduler.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serial latest-pending scheduler for automatic review snapshots.
|
|
3
|
+
*
|
|
4
|
+
* This is the DSH equivalent of ZCode's memory extraction scheduler: one
|
|
5
|
+
* runner per session, at most one running snapshot, and one replaceable
|
|
6
|
+
* pending snapshot while the runner is busy. A cursor advances only after a
|
|
7
|
+
* successful or no-op execution; errors and aborts leave it untouched so the
|
|
8
|
+
* next snapshot retries the same durable boundary.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Create a scheduler with ZCode-compatible coalescing and cursor semantics.
|
|
12
|
+
*
|
|
13
|
+
* @param execute - one serial extraction/review operation
|
|
14
|
+
* @param cursorOf - extracts the durable boundary from a snapshot
|
|
15
|
+
*/
|
|
16
|
+
export function createReviewScheduler(execute, cursorOf) {
|
|
17
|
+
let cursor;
|
|
18
|
+
let latestPending;
|
|
19
|
+
let running;
|
|
20
|
+
let shuttingDown = false;
|
|
21
|
+
const shutdownController = new AbortController();
|
|
22
|
+
const processSnapshot = async (snapshot) => {
|
|
23
|
+
if (shuttingDown || shutdownController.signal.aborted)
|
|
24
|
+
return;
|
|
25
|
+
let status;
|
|
26
|
+
try {
|
|
27
|
+
status = await execute({ snapshot, signal: shutdownController.signal });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// A failed acquisition/extraction is deliberately contained. The
|
|
31
|
+
// cursor remains unchanged and the next snapshot retries it.
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!shuttingDown && (status === "success" || status === "no-op")) {
|
|
35
|
+
cursor = cursorOf(snapshot);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const run = async (first) => {
|
|
39
|
+
try {
|
|
40
|
+
let current = first;
|
|
41
|
+
while (current && !shuttingDown) {
|
|
42
|
+
const acquisition = await waitForSnapshotAcquisitionOrShutdown(current, shutdownController.signal);
|
|
43
|
+
if (acquisition.status === "shutdown" || shuttingDown)
|
|
44
|
+
break;
|
|
45
|
+
if (acquisition.status === "acquired") {
|
|
46
|
+
await processSnapshot(acquisition.snapshot);
|
|
47
|
+
}
|
|
48
|
+
current = shuttingDown ? undefined : latestPending;
|
|
49
|
+
latestPending = undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (shuttingDown)
|
|
54
|
+
latestPending = undefined;
|
|
55
|
+
running = undefined;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
async drain() {
|
|
60
|
+
while (running) {
|
|
61
|
+
await running;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
getCursor() {
|
|
65
|
+
return cursor;
|
|
66
|
+
},
|
|
67
|
+
hasPendingWork() {
|
|
68
|
+
return running !== undefined || latestPending !== undefined;
|
|
69
|
+
},
|
|
70
|
+
schedule(snapshot) {
|
|
71
|
+
if (shuttingDown)
|
|
72
|
+
return;
|
|
73
|
+
const acquisition = acquireSnapshot(snapshot);
|
|
74
|
+
if (running) {
|
|
75
|
+
// Replace, never queue: a burst of turns contributes one latest
|
|
76
|
+
// boundary and the next extraction consumes all intervening rows.
|
|
77
|
+
latestPending = acquisition;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
running = run(acquisition);
|
|
81
|
+
},
|
|
82
|
+
shutdown() {
|
|
83
|
+
if (shuttingDown)
|
|
84
|
+
return;
|
|
85
|
+
shuttingDown = true;
|
|
86
|
+
latestPending = undefined;
|
|
87
|
+
shutdownController.abort();
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function acquireSnapshot(snapshot) {
|
|
92
|
+
return Promise.resolve(snapshot).then((value) => ({ status: "acquired", snapshot: value }), () => ({ status: "error" }));
|
|
93
|
+
}
|
|
94
|
+
function waitForSnapshotAcquisitionOrShutdown(acquisition, signal) {
|
|
95
|
+
if (signal.aborted)
|
|
96
|
+
return Promise.resolve({ status: "shutdown" });
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
99
|
+
const onAbort = () => {
|
|
100
|
+
cleanup();
|
|
101
|
+
resolve({ status: "shutdown" });
|
|
102
|
+
};
|
|
103
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
104
|
+
void acquisition.then((result) => {
|
|
105
|
+
cleanup();
|
|
106
|
+
resolve(result);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=review-scheduler.js.map
|
package/lib/review.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface AutoRefineReview {
|
|
|
15
15
|
rationale: string;
|
|
16
16
|
instructions?: string;
|
|
17
17
|
}
|
|
18
|
-
export type AutoRefineReason = "turn_interval" | "compact" | "goal_blocked";
|
|
18
|
+
export type AutoRefineReason = "turn_snapshot" | "turn_interval" | "compact" | "goal_blocked";
|
|
19
19
|
export interface AutoRefineReviewContext {
|
|
20
20
|
reason: AutoRefineReason;
|
|
21
21
|
turnsSinceLastReview: number;
|
|
@@ -27,6 +27,8 @@ export interface ReviewOptions {
|
|
|
27
27
|
context: AutoRefineReviewContext;
|
|
28
28
|
/** Serialized trajectory text; when absent the gate is skipped by the caller. */
|
|
29
29
|
trajectory?: string;
|
|
30
|
+
/** Incremental surface rows captured for this turn snapshot. */
|
|
31
|
+
trajectoryEvents?: readonly unknown[];
|
|
30
32
|
signal?: AbortSignal;
|
|
31
33
|
budgetTokens?: number;
|
|
32
34
|
/** Gap C1: optional provider/model override for the review gate (cheaper model). */
|
package/lib/review.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function reviewAutoRefine(ctx, options) {
|
|
|
101
101
|
}
|
|
102
102
|
// Route A carries the judged conversation as a session-derived message
|
|
103
103
|
// prefix and drops the flat block; an empty prefix falls back to B.
|
|
104
|
-
const events = sessionEventsOf(agent);
|
|
104
|
+
const events = options.trajectoryEvents ?? sessionEventsOf(agent);
|
|
105
105
|
const routing = resolvePrefixCache(options.prefixCache);
|
|
106
106
|
const route = detectPlannerRoute(events, routing.mode);
|
|
107
107
|
let prefixMessages = [];
|
package/lib/runtime.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
/** On-disk runtime gate state. Version 2 adds an explicit enable bit. */
|
|
1
2
|
export interface GateRuntime {
|
|
2
|
-
version:
|
|
3
|
-
/**
|
|
3
|
+
version: 2;
|
|
4
|
+
/** Whether the human has enabled automatic review. */
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
/** Whether the human has explicitly paused automatic review. */
|
|
4
7
|
paused: boolean;
|
|
5
8
|
/** ISO timestamp of the last pause/resume write. */
|
|
6
9
|
updatedAt: string;
|
|
@@ -8,17 +11,14 @@ export interface GateRuntime {
|
|
|
8
11
|
/** Full path of the runtime switch file. */
|
|
9
12
|
export declare function runtimePath(baseDir: string): string;
|
|
10
13
|
/**
|
|
11
|
-
* Load the runtime switch
|
|
12
|
-
*
|
|
13
|
-
* for a bad file).
|
|
14
|
+
* Load the runtime switch. Missing, malformed, or legacy content uses the
|
|
15
|
+
* supplied configured default and never throws; this is a trigger-path read.
|
|
14
16
|
*/
|
|
15
|
-
export declare function loadGateRuntime(baseDir: string): GateRuntime;
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* through the command's own error path instead.
|
|
20
|
-
*/
|
|
21
|
-
export declare function saveGateRuntime(baseDir: string, paused: boolean): string;
|
|
22
|
-
/** True while the human paused the automatic review gate (fail-open). */
|
|
17
|
+
export declare function loadGateRuntime(baseDir: string, defaultEnabled?: boolean): GateRuntime;
|
|
18
|
+
/** Persist the runtime switch atomically (temp file + rename). */
|
|
19
|
+
export declare function saveGateRuntime(baseDir: string, paused: boolean, enabled?: boolean): string;
|
|
20
|
+
/** True while the human paused the automatic review gate. */
|
|
23
21
|
export declare function isGatePaused(baseDir: string): boolean;
|
|
22
|
+
/** Effective automatic-review state, including the explicit enable bit. */
|
|
23
|
+
export declare function isGateEnabled(baseDir: string, defaultEnabled?: boolean): boolean;
|
|
24
24
|
//# sourceMappingURL=runtime.d.ts.map
|
package/lib/runtime.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime gate switch
|
|
3
|
-
* automatic review
|
|
2
|
+
* Runtime gate switch: a small human-owned file that enables or pauses the
|
|
3
|
+
* automatic review listener without editing the profile or restarting DSH.
|
|
4
4
|
*
|
|
5
|
-
* - file: `<baseDir>/evolve/runtime.json` (`{version, paused, updatedAt}`)
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* exceptional state; a broken file must never silently wedge the gate
|
|
12
|
-
* shut (the next save rewrites it cleanly).
|
|
5
|
+
* - file: `<baseDir>/evolve/runtime.json` (`{version: 2, enabled, paused, updatedAt}`)
|
|
6
|
+
* - the file is consulted at every trigger and before scheduler execution;
|
|
7
|
+
* - `/evolve pause` sets `paused`, `/evolve resume` sets `enabled=true` and
|
|
8
|
+
* clears the pause, while manual tools and commands remain independent;
|
|
9
|
+
* - a missing or malformed file uses the caller's configured default. A
|
|
10
|
+
* legacy v1 `{paused}` file is readable and preserves its pause state.
|
|
13
11
|
*/
|
|
14
|
-
import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
13
|
import { randomUUID } from "node:crypto";
|
|
17
14
|
import { join } from "node:path";
|
|
18
15
|
import { EVOLVE_DIR } from "./store.js";
|
|
@@ -21,57 +18,54 @@ const RUNTIME_FILE = "runtime.json";
|
|
|
21
18
|
export function runtimePath(baseDir) {
|
|
22
19
|
return join(baseDir, EVOLVE_DIR, RUNTIME_FILE);
|
|
23
20
|
}
|
|
24
|
-
function
|
|
25
|
-
return { version:
|
|
21
|
+
function state(enabled, paused) {
|
|
22
|
+
return { version: 2, enabled, paused, updatedAt: new Date(0).toISOString() };
|
|
26
23
|
}
|
|
27
24
|
/**
|
|
28
|
-
* Load the runtime switch
|
|
29
|
-
*
|
|
30
|
-
* for a bad file).
|
|
25
|
+
* Load the runtime switch. Missing, malformed, or legacy content uses the
|
|
26
|
+
* supplied configured default and never throws; this is a trigger-path read.
|
|
31
27
|
*/
|
|
32
|
-
export function loadGateRuntime(baseDir) {
|
|
28
|
+
export function loadGateRuntime(baseDir, defaultEnabled = false) {
|
|
33
29
|
const path = runtimePath(baseDir);
|
|
34
|
-
if (!existsSync(path))
|
|
35
|
-
return
|
|
36
|
-
}
|
|
30
|
+
if (!existsSync(path))
|
|
31
|
+
return state(defaultEnabled, false);
|
|
37
32
|
try {
|
|
38
33
|
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
39
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
40
|
-
return
|
|
41
|
-
}
|
|
34
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
35
|
+
return state(defaultEnabled, false);
|
|
42
36
|
const record = raw;
|
|
37
|
+
const paused = record["paused"] === true;
|
|
38
|
+
// v1 had no enable bit. Its only meaningful state was pause, so retain
|
|
39
|
+
// the configured default when it is not paused.
|
|
40
|
+
const enabled = typeof record["enabled"] === "boolean" ? record["enabled"] : defaultEnabled;
|
|
43
41
|
return {
|
|
44
|
-
version:
|
|
45
|
-
|
|
42
|
+
version: 2,
|
|
43
|
+
enabled,
|
|
44
|
+
paused,
|
|
46
45
|
updatedAt: typeof record["updatedAt"] === "string" ? record["updatedAt"] : new Date(0).toISOString(),
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
catch {
|
|
50
|
-
return
|
|
49
|
+
return state(defaultEnabled, false);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
* for a bad file — callers (pause/resume commands) surface real IO errors
|
|
56
|
-
* through the command's own error path instead.
|
|
57
|
-
*/
|
|
58
|
-
export function saveGateRuntime(baseDir, paused) {
|
|
52
|
+
/** Persist the runtime switch atomically (temp file + rename). */
|
|
53
|
+
export function saveGateRuntime(baseDir, paused, enabled = true) {
|
|
59
54
|
const path = runtimePath(baseDir);
|
|
60
55
|
mkdirSync(join(baseDir, EVOLVE_DIR), { recursive: true });
|
|
61
56
|
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
62
|
-
const payload = { version:
|
|
57
|
+
const payload = { version: 2, enabled, paused, updatedAt: new Date().toISOString() };
|
|
63
58
|
writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
64
59
|
try {
|
|
65
60
|
renameSync(tempPath, path);
|
|
66
61
|
}
|
|
67
62
|
finally {
|
|
68
|
-
if (existsSync(tempPath))
|
|
63
|
+
if (existsSync(tempPath))
|
|
69
64
|
unlinkSync(tempPath);
|
|
70
|
-
}
|
|
71
65
|
}
|
|
72
66
|
return path;
|
|
73
67
|
}
|
|
74
|
-
/** True while the human paused the automatic review gate
|
|
68
|
+
/** True while the human paused the automatic review gate. */
|
|
75
69
|
export function isGatePaused(baseDir) {
|
|
76
70
|
try {
|
|
77
71
|
return loadGateRuntime(baseDir).paused;
|
|
@@ -80,4 +74,14 @@ export function isGatePaused(baseDir) {
|
|
|
80
74
|
return false;
|
|
81
75
|
}
|
|
82
76
|
}
|
|
77
|
+
/** Effective automatic-review state, including the explicit enable bit. */
|
|
78
|
+
export function isGateEnabled(baseDir, defaultEnabled = false) {
|
|
79
|
+
try {
|
|
80
|
+
const current = loadGateRuntime(baseDir, defaultEnabled);
|
|
81
|
+
return current.enabled && !current.paused;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return defaultEnabled;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
83
87
|
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn-boundary snapshots for the automatic review gate.
|
|
3
|
+
*
|
|
4
|
+
* The scheduler must see a bounded, mechanically filtered slice of the
|
|
5
|
+
* current session, not a fresh full-history LLM request on every turn. This
|
|
6
|
+
* module owns the snapshot boundary, cursor-compatible incremental rows, and
|
|
7
|
+
* the cheap eligibility checks that run before any model call.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
10
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
11
|
+
/** Why a scheduler snapshot was captured. */
|
|
12
|
+
export type TurnSnapshotReason = "turn_snapshot" | "compact";
|
|
13
|
+
/** Mechanical reasons a snapshot can be skipped without an LLM call. */
|
|
14
|
+
export type SnapshotSkipReason = "no-new-events" | "internal-agent" | "direct-memory-write" | "no-user-prose";
|
|
15
|
+
/** One immutable scheduler input. The cursor is a durable event boundary. */
|
|
16
|
+
export interface TurnSnapshot {
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
readonly projectKey?: string;
|
|
19
|
+
readonly turn: number;
|
|
20
|
+
readonly reason: TurnSnapshotReason;
|
|
21
|
+
readonly cursor: string;
|
|
22
|
+
readonly events: readonly unknown[];
|
|
23
|
+
readonly trajectory: string;
|
|
24
|
+
readonly userText: string;
|
|
25
|
+
readonly sourceSeqs: readonly number[];
|
|
26
|
+
readonly eligible: boolean;
|
|
27
|
+
readonly skipReason?: SnapshotSkipReason;
|
|
28
|
+
}
|
|
29
|
+
/** Eligibility result kept separate so tests and future extractors share it. */
|
|
30
|
+
export interface SnapshotEligibility {
|
|
31
|
+
readonly eligible: boolean;
|
|
32
|
+
readonly reason?: SnapshotSkipReason;
|
|
33
|
+
readonly userText: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Capture the current durable surface and select only rows after `cursor`.
|
|
37
|
+
* A seq-bearing session uses `seq:<n>`; older/test surfaces fall back to an
|
|
38
|
+
* event-count cursor so the scheduler still has a deterministic boundary.
|
|
39
|
+
*/
|
|
40
|
+
export declare function captureTurnSnapshot(ctx: Context, agent: Agent, opts: {
|
|
41
|
+
turn: number;
|
|
42
|
+
reason: TurnSnapshotReason;
|
|
43
|
+
cursor?: string;
|
|
44
|
+
maxChars: number;
|
|
45
|
+
}): Promise<TurnSnapshot>;
|
|
46
|
+
/**
|
|
47
|
+
* Decide whether the incremental rows deserve a model call. This intentionally
|
|
48
|
+
* does not decide whether a fact is valuable; that remains the review model's
|
|
49
|
+
* job. It only removes mechanical noise before the scheduler spends tokens.
|
|
50
|
+
*/
|
|
51
|
+
export declare function evaluateSnapshotEligibility(events: readonly unknown[], internalAgent?: boolean): SnapshotEligibility;
|
|
52
|
+
/** True when an assistant row records an explicit evolve memory mutation. */
|
|
53
|
+
export declare function containsDirectMemoryWrite(events: readonly unknown[]): boolean;
|
|
54
|
+
/** Serialize only user/assistant text, retaining the bounded tail. */
|
|
55
|
+
export declare function serializeSnapshotEvents(events: readonly unknown[], maxChars: number): string;
|
|
56
|
+
/** A DSH subagent header is an internal source when its origin says so. */
|
|
57
|
+
export declare function isInternalAgent(agent: Agent | undefined): boolean;
|
|
58
|
+
//# sourceMappingURL=turn-snapshot.d.ts.map
|