@lmzhen/dsh-evolution-review 0.1.0-rc.14 → 0.1.0-rc.16
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 +94 -11
- package/lib/types/index.d.ts +20 -0
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { CallId, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import { DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, PROMPT_BUNDLE, advanceReview, foldTurn, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
4
|
+
import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, PROMPT_BUNDLE, advanceReview, foldTurn, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
5
5
|
import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
|
|
6
6
|
//#region lib/types/redact.js
|
|
7
7
|
/**
|
|
@@ -54,12 +54,16 @@ const Config = z.object({
|
|
|
54
54
|
reviewContextMessages: z.number().default(60),
|
|
55
55
|
reviewMessageChars: z.number().default(2e3),
|
|
56
56
|
reviewMaxDepth: z.number().default(0),
|
|
57
|
-
reviewProvider: z.string()
|
|
57
|
+
reviewProvider: z.string(),
|
|
58
|
+
skillReviewTrigger: z.string().default(DEFAULT_SKILL_REVIEW_TRIGGER),
|
|
59
|
+
skillReviewCompletionMinToolCalls: z.number().default(DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS)
|
|
58
60
|
});
|
|
59
61
|
function apply(ctx, rawConfig) {
|
|
60
62
|
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to schedule review work");
|
|
61
63
|
const config = rawConfig;
|
|
62
64
|
const turnStarts = /* @__PURE__ */ new Map();
|
|
65
|
+
const cumulativeToolCalls = /* @__PURE__ */ new Map();
|
|
66
|
+
const completionInjected = /* @__PURE__ */ new Set();
|
|
63
67
|
const policy = () => ctx.get("evolutionPolicy")?.get();
|
|
64
68
|
ctx.on("session/event", (session, event) => {
|
|
65
69
|
if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
|
|
@@ -88,17 +92,50 @@ function apply(ctx, rawConfig) {
|
|
|
88
92
|
substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? 500
|
|
89
93
|
});
|
|
90
94
|
await stateService?.saveReviewState(session.id, state);
|
|
91
|
-
if (
|
|
92
|
-
|
|
95
|
+
if (kind) {
|
|
96
|
+
session.append("evolution/review-scheduled", {
|
|
97
|
+
kind,
|
|
98
|
+
toolCalls: signal.toolCalls,
|
|
99
|
+
userChars: signal.userChars,
|
|
100
|
+
assistantChars: signal.assistantChars
|
|
101
|
+
});
|
|
102
|
+
if (!await trySubagentReview(session, agent, kind, signal)) agent.inject(createUserMessage({
|
|
103
|
+
content: [{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: reviewPrompt(kind)
|
|
106
|
+
}],
|
|
107
|
+
source: {
|
|
108
|
+
kind: "plugin",
|
|
109
|
+
plugin: "dsh-evolution-review",
|
|
110
|
+
form: "notice",
|
|
111
|
+
summary: "auto-review"
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const trigger = config.skillReviewTrigger;
|
|
117
|
+
if (trigger !== "completion" && trigger !== "both") return;
|
|
118
|
+
if (completionInjected.has(session.id)) return;
|
|
119
|
+
const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
|
|
120
|
+
cumulativeToolCalls.set(session.id, cumulative);
|
|
121
|
+
if (!shouldCompletionReview(event.data.reason, cumulative, config.skillReviewCompletionMinToolCalls)) return;
|
|
122
|
+
completionInjected.add(session.id);
|
|
123
|
+
session.append("evolution/review-scheduled", {
|
|
124
|
+
kind: "skill",
|
|
125
|
+
toolCalls: signal.toolCalls,
|
|
126
|
+
userChars: signal.userChars,
|
|
127
|
+
assistantChars: signal.assistantChars
|
|
128
|
+
});
|
|
129
|
+
agent.inject(createUserMessage({
|
|
93
130
|
content: [{
|
|
94
131
|
type: "text",
|
|
95
|
-
text:
|
|
132
|
+
text: COMPLETION_SKILL_REVIEW_PROMPT
|
|
96
133
|
}],
|
|
97
134
|
source: {
|
|
98
135
|
kind: "plugin",
|
|
99
136
|
plugin: "dsh-evolution-review",
|
|
100
137
|
form: "notice",
|
|
101
|
-
summary: "
|
|
138
|
+
summary: "completion review"
|
|
102
139
|
}
|
|
103
140
|
}));
|
|
104
141
|
}
|
|
@@ -154,14 +191,17 @@ function apply(ctx, rawConfig) {
|
|
|
154
191
|
maxUserChars: snapshot?.userChars ?? 1375,
|
|
155
192
|
maxSkillContentChars: snapshot?.skillContentChars ?? 1e5
|
|
156
193
|
});
|
|
194
|
+
const acceptedSkillOps = validation.accepted.skillOps ?? [];
|
|
195
|
+
const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, collectReadSkillNames(session));
|
|
196
|
+
validation.accepted.skillOps = acceptedSkillOps;
|
|
157
197
|
const actions = await executePlan(validation.accepted, agent);
|
|
158
|
-
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...
|
|
198
|
+
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
|
|
159
199
|
session.append("evolution/plan-applied", {
|
|
160
200
|
planId: randomUUID(),
|
|
161
201
|
policyFingerprint,
|
|
162
202
|
memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
|
|
163
203
|
skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
|
|
164
|
-
rejectedOps: validation.rejected.length,
|
|
204
|
+
rejectedOps: validation.rejected.length + skippedUnread,
|
|
165
205
|
evidenceQuotes,
|
|
166
206
|
estimatedInputChars: reviewText.length
|
|
167
207
|
});
|
|
@@ -203,10 +243,11 @@ function apply(ctx, rawConfig) {
|
|
|
203
243
|
...op,
|
|
204
244
|
evidence: op.evidence
|
|
205
245
|
};
|
|
206
|
-
|
|
246
|
+
const runnerArgs = {
|
|
207
247
|
operation: args,
|
|
208
248
|
origin: "background_review"
|
|
209
|
-
}
|
|
249
|
+
};
|
|
250
|
+
if ((approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs) : await executeSkillTool(parent, args))?.ok) actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
|
|
210
251
|
}
|
|
211
252
|
return actions;
|
|
212
253
|
async function runApproved(kind, summary, stored, runnerArgs) {
|
|
@@ -241,8 +282,50 @@ function apply(ctx, rawConfig) {
|
|
|
241
282
|
}
|
|
242
283
|
ctx.effect(() => () => {
|
|
243
284
|
turnStarts.clear();
|
|
285
|
+
cumulativeToolCalls.clear();
|
|
286
|
+
completionInjected.clear();
|
|
244
287
|
}, "dsh-evolution-review.cleanup");
|
|
245
288
|
}
|
|
289
|
+
/** Completion-channel decision: task finished normally AND the session is proven long. */
|
|
290
|
+
function shouldCompletionReview(reason, sessionToolCalls, minToolCalls) {
|
|
291
|
+
return reason?.kind === "completed" && sessionToolCalls >= minToolCalls;
|
|
292
|
+
}
|
|
293
|
+
/** Skill names this session loaded (read-before-write source for the background review). */
|
|
294
|
+
function collectReadSkillNames(session) {
|
|
295
|
+
const names = /* @__PURE__ */ new Set();
|
|
296
|
+
for (const event of session.events) {
|
|
297
|
+
if (event.type !== "tool/call") continue;
|
|
298
|
+
if (event.data.name !== "skill" && event.data.name !== "skill_load") continue;
|
|
299
|
+
const raw = event.data.arguments;
|
|
300
|
+
let parsed = {};
|
|
301
|
+
if (typeof raw === "string") try {
|
|
302
|
+
parsed = JSON.parse(raw);
|
|
303
|
+
} catch {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
else parsed = raw ?? {};
|
|
307
|
+
const name = typeof parsed.name === "string" ? parsed.name : typeof parsed.skill === "string" ? parsed.skill : "";
|
|
308
|
+
if (name) names.add(name);
|
|
309
|
+
}
|
|
310
|
+
return names;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Drop patch/update ops whose target was not read this session, in place.
|
|
314
|
+
* Create is exempt (no read required to author a new skill). Returns the count
|
|
315
|
+
* of dropped ops so the plan event can report them as rejected.
|
|
316
|
+
*/
|
|
317
|
+
function filterUnreadSkillOps(ops, readNames) {
|
|
318
|
+
let dropped = 0;
|
|
319
|
+
for (let index = ops.length - 1; index >= 0; index -= 1) {
|
|
320
|
+
const op = ops[index];
|
|
321
|
+
if (!op) continue;
|
|
322
|
+
if ((op.action === "patch" || op.action === "update") && op.name && !readNames.has(op.name)) {
|
|
323
|
+
ops.splice(index, 1);
|
|
324
|
+
dropped += 1;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return dropped;
|
|
328
|
+
}
|
|
246
329
|
function fingerprintPolicy(snapshot) {
|
|
247
330
|
try {
|
|
248
331
|
return createHash("sha256").update(JSON.stringify(snapshot)).digest("hex").slice(0, 12);
|
|
@@ -266,4 +349,4 @@ function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars)
|
|
|
266
349
|
].join("\n");
|
|
267
350
|
}
|
|
268
351
|
//#endregion
|
|
269
|
-
export { Config, apply, inject, name };
|
|
352
|
+
export { Config, apply, collectReadSkillNames, filterUnreadSkillOps, inject, name, shouldCompletionReview };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Context } from '@deepseek-ai/cordis';
|
|
6
6
|
import z from '@deepseek-ai/schemastery';
|
|
7
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
7
8
|
export declare const name = "evolution-review";
|
|
8
9
|
export declare const inject: string[];
|
|
9
10
|
export interface Config {
|
|
@@ -20,7 +21,26 @@ export interface Config {
|
|
|
20
21
|
reviewMaxDepth?: number;
|
|
21
22
|
/** LLM provider for review subagents. Omit to inherit the deployment default route. */
|
|
22
23
|
reviewProvider?: string;
|
|
24
|
+
/** Skill-review trigger: cadence (interval) | completion (once after a proven-long task) | both. */
|
|
25
|
+
skillReviewTrigger?: string;
|
|
26
|
+
/** Cumulative session tool calls before a session counts as proven-long for the completion channel. */
|
|
27
|
+
skillReviewCompletionMinToolCalls?: number;
|
|
23
28
|
}
|
|
24
29
|
export declare const Config: z<Config>;
|
|
25
30
|
export declare function apply(ctx: Context, rawConfig: Config): void;
|
|
31
|
+
/** Completion-channel decision: task finished normally AND the session is proven long. */
|
|
32
|
+
export declare function shouldCompletionReview(reason: {
|
|
33
|
+
kind?: string;
|
|
34
|
+
} | undefined, sessionToolCalls: number, minToolCalls: number): boolean;
|
|
35
|
+
/** Skill names this session loaded (read-before-write source for the background review). */
|
|
36
|
+
export declare function collectReadSkillNames(session: Session): Set<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Drop patch/update ops whose target was not read this session, in place.
|
|
39
|
+
* Create is exempt (no read required to author a new skill). Returns the count
|
|
40
|
+
* of dropped ops so the plan event can report them as rejected.
|
|
41
|
+
*/
|
|
42
|
+
export declare function filterUnreadSkillOps(ops: Array<{
|
|
43
|
+
action?: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
}>, readNames: ReadonlySet<string>): number;
|
|
26
46
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-review",
|
|
3
3
|
"description": "Background review orchestration (community build)",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.16",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-core": "^0.1.0-rc.
|
|
37
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.1.0-rc.16",
|
|
37
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.16"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
44
44
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
45
45
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
46
|
-
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.
|
|
46
|
+
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.16"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
53
53
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
54
54
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
55
|
-
"@lmzhen/dsh-evolution-core": "^0.1.0-rc.
|
|
56
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.
|
|
57
|
-
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.
|
|
55
|
+
"@lmzhen/dsh-evolution-core": "^0.1.0-rc.16",
|
|
56
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.16",
|
|
57
|
+
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.16"
|
|
58
58
|
}
|
|
59
59
|
}
|