@lmzhen/dsh-evolution-review 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/README.md +3 -1
- package/lib/index.js +55 -24
- package/lib/types/index.d.ts +1 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -21,9 +21,11 @@ Independent of request-prefix construction. This package does not alter the asse
|
|
|
21
21
|
## Known Limitations and Deferred Work
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
- Review subagents
|
|
24
|
+
- Review subagents are spawned with the plain `skill` tool only (`reviewToolAllow` default and the host/preset config both = `[skill]` — the DSH tool catalog has no `skill_search`/`skill_load` discovery pair, so the Hermes-lineage Anchored Standard `skill_search`/`skill_load` allow-list does not exist here).
|
|
25
25
|
- Review subagents run as `spawn` children on the deployment default preset rather than inheriting the parent agent's composition (`fork`): a fork child is always promoted by the Anchored Standard bootstrap and its narrowed resident catalog would drop the plain `skill` tool from the review allow-list.
|
|
26
26
|
- The review request text is redacted for credential-shaped patterns before it reaches the subagent, but redaction is pattern-based and best-effort, not a security boundary.
|
|
27
|
+
- Read-before-write tracks only reads through the `skill` tool. `skill_manage` has no per-skill read action (`list`/`review` are whole-library, not targeted at one name), so a skill that was only listed via `skill_manage` is not marked as read — a background review may still reject a patch to it until it is actually loaded.
|
|
28
|
+
- The completion-channel counters (`cumulativeToolCalls` / `completionInjected`) are in-memory only. A process restart resets them, which is accepted behavior: the completion review is a one-per-session post-task adaptation and a restart is treated as a fresh conversation boundary. The cadence state (`turnsSinceMemory` / `turnsSinceSkill`) is persisted via `ReviewState` and survives restart.
|
|
27
29
|
|
|
28
30
|
## Configuration
|
|
29
31
|
|
package/lib/index.js
CHANGED
|
@@ -9,10 +9,10 @@ import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
|
|
|
9
9
|
* @module @lmzhen/dsh-evolution-review
|
|
10
10
|
*/
|
|
11
11
|
const name = "evolution-review";
|
|
12
|
-
const inject = ["agents"
|
|
12
|
+
const inject = ["agents"];
|
|
13
13
|
const Config = z.object({
|
|
14
14
|
reviewEnabled: z.boolean().default(true),
|
|
15
|
-
reviewMode: z.
|
|
15
|
+
reviewMode: z.union([z.const("subagent"), z.const("inject")]).default("subagent"),
|
|
16
16
|
memoryInterval: z.number().default(DEFAULT_REVIEW_MEMORY_INTERVAL),
|
|
17
17
|
skillInterval: z.number().default(DEFAULT_REVIEW_SKILL_INTERVAL),
|
|
18
18
|
reviewToolAllow: z.array(z.string()).default(["skill"]),
|
|
@@ -31,6 +31,7 @@ function apply(ctx, rawConfig) {
|
|
|
31
31
|
const turnStarts = /* @__PURE__ */ new Map();
|
|
32
32
|
const cumulativeToolCalls = /* @__PURE__ */ new Map();
|
|
33
33
|
const completionInjected = /* @__PURE__ */ new Set();
|
|
34
|
+
let reviewInFlight = false;
|
|
34
35
|
const policy = () => ctx.get("evolutionPolicy")?.get();
|
|
35
36
|
ctx.on("session/event", (session, event) => {
|
|
36
37
|
if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
|
|
@@ -44,6 +45,14 @@ function apply(ctx, rawConfig) {
|
|
|
44
45
|
onTurnEnd(session, event);
|
|
45
46
|
});
|
|
46
47
|
async function onTurnEnd(session, event) {
|
|
48
|
+
try {
|
|
49
|
+
await runOnTurnEnd(session, event);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
ctx.logger.warn(`dsh-evolution-review: turn-end review pipeline failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runOnTurnEnd(session, event) {
|
|
47
56
|
if (!config.reviewEnabled) return;
|
|
48
57
|
if (session.header.origin === "subagent") return;
|
|
49
58
|
const agent = ctx.agents.get(session.id);
|
|
@@ -68,14 +77,14 @@ function apply(ctx, rawConfig) {
|
|
|
68
77
|
const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
|
|
69
78
|
cumulativeToolCalls.set(session.id, cumulative);
|
|
70
79
|
if (kind) {
|
|
71
|
-
ctx.emit("evolution/review-scheduled", {
|
|
80
|
+
if (await trySubagentReview(session, agent, kind, signal)) ctx.emit("evolution/review-scheduled", {
|
|
72
81
|
sessionId: session.id,
|
|
73
82
|
kind,
|
|
74
83
|
toolCalls: signal.toolCalls,
|
|
75
84
|
userChars: signal.userChars,
|
|
76
85
|
assistantChars: signal.assistantChars
|
|
77
86
|
});
|
|
78
|
-
|
|
87
|
+
else agent.inject(createUserMessage({
|
|
79
88
|
content: [{
|
|
80
89
|
type: "text",
|
|
81
90
|
text: reviewPrompt(kind)
|
|
@@ -118,9 +127,11 @@ function apply(ctx, rawConfig) {
|
|
|
118
127
|
if ((policy()?.reviewMode ?? config.reviewMode) === "inject") return false;
|
|
119
128
|
const subagents = ctx.get("subagents");
|
|
120
129
|
if (!subagents) return false;
|
|
130
|
+
if (reviewInFlight) return false;
|
|
131
|
+
reviewInFlight = true;
|
|
121
132
|
try {
|
|
122
|
-
const
|
|
123
|
-
const model = kind === "memory" ?
|
|
133
|
+
const snapshot = policy();
|
|
134
|
+
const model = kind === "memory" ? snapshot?.memoryReviewModel ?? "deepseek-v4-flash" : snapshot?.skillReviewModel ?? "deepseek-v4-pro";
|
|
124
135
|
const reviewText = redactSecrets(buildReviewRequest(session, kind, signal, config.reviewContextMessages, config.reviewMessageChars));
|
|
125
136
|
const agentOptions = { model };
|
|
126
137
|
if (config.reviewProvider) agentOptions.provider = config.reviewProvider;
|
|
@@ -154,9 +165,12 @@ function apply(ctx, rawConfig) {
|
|
|
154
165
|
});
|
|
155
166
|
try {
|
|
156
167
|
const result = await run.result;
|
|
157
|
-
if (!result.structured)
|
|
168
|
+
if (!result.structured) {
|
|
169
|
+
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
170
|
+
ctx.logger.warn("dsh-evolution-review: review subagent returned no structured plan");
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
158
173
|
const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
|
|
159
|
-
const snapshot = policy();
|
|
160
174
|
const plan = result.structured;
|
|
161
175
|
const policyFingerprint = fingerprintPolicy(snapshot);
|
|
162
176
|
const validation = validateEvolutionPlan(plan, {
|
|
@@ -169,7 +183,8 @@ function apply(ctx, rawConfig) {
|
|
|
169
183
|
});
|
|
170
184
|
const acceptedSkillOps = validation.accepted.skillOps ?? [];
|
|
171
185
|
const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
|
|
172
|
-
const
|
|
186
|
+
const executed = await executePlan(validation.accepted, session.id);
|
|
187
|
+
const actions = executed.actions;
|
|
173
188
|
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
|
|
174
189
|
ctx.emit("evolution/plan-applied", {
|
|
175
190
|
sessionId: session.id,
|
|
@@ -181,18 +196,22 @@ function apply(ctx, rawConfig) {
|
|
|
181
196
|
evidenceQuotes,
|
|
182
197
|
estimatedInputChars: reviewText.length
|
|
183
198
|
});
|
|
184
|
-
if (actions.length > 0)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
if (actions.length > 0) {
|
|
200
|
+
const applied = actions.join(" · ");
|
|
201
|
+
const note = executed.ok ? "" : "\n部分操作失败。以下操作已应用,请勿重复执行。";
|
|
202
|
+
agent.inject(createUserMessage({
|
|
203
|
+
content: [{
|
|
204
|
+
type: "text",
|
|
205
|
+
text: `💾 Self-improvement review: ${applied}${note}`
|
|
206
|
+
}],
|
|
207
|
+
source: {
|
|
208
|
+
kind: "plugin",
|
|
209
|
+
plugin: "dsh-evolution-review",
|
|
210
|
+
form: "notice",
|
|
211
|
+
summary: "self-improvement review"
|
|
212
|
+
}
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
196
215
|
return true;
|
|
197
216
|
} finally {
|
|
198
217
|
try {
|
|
@@ -204,6 +223,8 @@ function apply(ctx, rawConfig) {
|
|
|
204
223
|
} catch (error) {
|
|
205
224
|
ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
225
|
return false;
|
|
226
|
+
} finally {
|
|
227
|
+
reviewInFlight = false;
|
|
207
228
|
}
|
|
208
229
|
}
|
|
209
230
|
async function executePlan(plan, sessionId) {
|
|
@@ -211,6 +232,7 @@ function apply(ctx, rawConfig) {
|
|
|
211
232
|
const approval = ctx.get("evolutionApproval");
|
|
212
233
|
const origins = resolveOrigins(void 0, true);
|
|
213
234
|
const actions = [];
|
|
235
|
+
let ok = true;
|
|
214
236
|
for (const op of plan.memoryOps ?? []) {
|
|
215
237
|
if (!Array.isArray(op.evidence) || op.evidence.length === 0) continue;
|
|
216
238
|
const normalized = {
|
|
@@ -220,6 +242,7 @@ function apply(ctx, rawConfig) {
|
|
|
220
242
|
old_text: op.old_text
|
|
221
243
|
};
|
|
222
244
|
if ((approval ? await runApproved("memory", `memory ${normalized.target} ${normalized.action}`, normalized, normalized) : await memory?.applyBatch(normalized.target, [normalized]))?.ok) actions.push("Memory updated");
|
|
245
|
+
else ok = false;
|
|
223
246
|
}
|
|
224
247
|
for (const op of plan.skillOps ?? []) {
|
|
225
248
|
if (!Array.isArray(op.evidence) || op.evidence.length === 0 || !op.name) continue;
|
|
@@ -232,8 +255,12 @@ function apply(ctx, rawConfig) {
|
|
|
232
255
|
origin: origins.library
|
|
233
256
|
};
|
|
234
257
|
if ((approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs) : await executeSkillDirect(args))?.ok) actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
|
|
258
|
+
else ok = false;
|
|
235
259
|
}
|
|
236
|
-
return
|
|
260
|
+
return {
|
|
261
|
+
actions,
|
|
262
|
+
ok
|
|
263
|
+
};
|
|
237
264
|
async function runApproved(kind, summary, stored, runnerArgs) {
|
|
238
265
|
if (!approval) return void 0;
|
|
239
266
|
if (approval.isEnabled === true && !approval.hasRunner(kind)) {
|
|
@@ -334,12 +361,16 @@ function apply(ctx, rawConfig) {
|
|
|
334
361
|
function shouldCompletionReview(reason, sessionToolCalls, minToolCalls) {
|
|
335
362
|
return reason?.kind === "completed" && sessionToolCalls >= minToolCalls;
|
|
336
363
|
}
|
|
337
|
-
/** Skill names this session loaded (read-before-write source for the background review).
|
|
364
|
+
/** Skill names this session loaded (read-before-write source for the background review).
|
|
365
|
+
* Only the real `skill` tool is a read (E-59e): the platform has no `skill_load`/
|
|
366
|
+
* `skill_search` discovery pair, so that branch was dead. `skill_manage` has no
|
|
367
|
+
* per-skill read action (its `list`/`review` are whole-library), so a specific
|
|
368
|
+
* skill read through it cannot be tracked — see README Known Limitations. */
|
|
338
369
|
function collectReadSkillNames(session) {
|
|
339
370
|
const names = /* @__PURE__ */ new Set();
|
|
340
371
|
for (const event of session.events) {
|
|
341
372
|
if (event.type !== "tool/call") continue;
|
|
342
|
-
if (event.data.name !== "skill"
|
|
373
|
+
if (event.data.name !== "skill") continue;
|
|
343
374
|
const raw = event.data.arguments;
|
|
344
375
|
let parsed = {};
|
|
345
376
|
if (typeof raw === "string") try {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare const name = "evolution-review";
|
|
|
8
8
|
export declare const inject: string[];
|
|
9
9
|
export interface Config {
|
|
10
10
|
reviewEnabled?: boolean;
|
|
11
|
-
reviewMode?:
|
|
11
|
+
reviewMode?: 'subagent' | 'inject';
|
|
12
12
|
memoryInterval?: number;
|
|
13
13
|
skillInterval?: number;
|
|
14
14
|
/**
|
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.3.
|
|
4
|
+
"version": "0.3.19",
|
|
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.3.
|
|
37
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.3.19",
|
|
37
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.19"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
44
44
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
45
45
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
46
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
46
|
+
"@lmzhen/dsh-evolution-state": "^0.3.19",
|
|
47
|
+
"@lmzhen/dsh-evolution-policy": "^0.3.19"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
|
|
@@ -54,9 +55,9 @@
|
|
|
54
55
|
"@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
|
|
55
56
|
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.1-rc.2",
|
|
56
57
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
57
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
58
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
59
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
60
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
58
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.19",
|
|
59
|
+
"@lmzhen/dsh-evolution-core": "^0.3.19",
|
|
60
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.19",
|
|
61
|
+
"@lmzhen/dsh-evolution-state": "^0.3.19"
|
|
61
62
|
}
|
|
62
63
|
}
|