@lmzhen/dsh-evolution-review 0.3.23 → 0.3.25
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 +2 -0
- package/lib/index.js +72 -33
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ Independent of request-prefix construction. This package does not alter the asse
|
|
|
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
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
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.
|
|
29
|
+
- `evolution/review-scheduled` and `evolution/review-error` are emitted for platform/user wiring only — this family has no in-repo production `ctx.on` consumer for them. They are declared externally owned (the platform side wires consumption), which matches the `EXEMPT_ORPHANS` set in `scripts/verify-event-pairing.mjs`.
|
|
30
|
+
- When the `evolution-state` service is not mounted, the memory/skill cadence state is not persisted and every turn restarts from a clean `{ turnsSinceMemory: 0, turnsSinceSkill: 0 }` baseline — the review schedule is stateless and re-decided each turn rather than accumulating across the conversation. The loss is surfaced once per process as a logger warning at the first turn/end.
|
|
29
31
|
|
|
30
32
|
## Configuration
|
|
31
33
|
|
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 { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_USER_CHAR_LIMIT, PROMPT_BUNDLE, SkillLibrary, advanceReview, evolutionIoAdapter, foldTurn, redactSecrets, resolveOrigins, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
4
|
+
import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_USER_CHAR_LIMIT, PROMPT_BUNDLE, SkillLibrary, advanceReview, clampedNumber, evolutionIoAdapter, foldTurn, redactSecrets, resolveOrigins, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
5
5
|
import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
|
|
6
6
|
//#region lib/types/index.js
|
|
7
7
|
/**
|
|
@@ -13,21 +13,52 @@ const inject = ["agents"];
|
|
|
13
13
|
const Config = z.object({
|
|
14
14
|
reviewEnabled: z.boolean().default(true),
|
|
15
15
|
reviewMode: z.union([z.const("subagent"), z.const("inject")]).default("subagent"),
|
|
16
|
-
memoryInterval: z.number().default(DEFAULT_REVIEW_MEMORY_INTERVAL),
|
|
17
|
-
skillInterval: z.number().default(DEFAULT_REVIEW_SKILL_INTERVAL),
|
|
16
|
+
memoryInterval: z.number().min(1).default(DEFAULT_REVIEW_MEMORY_INTERVAL),
|
|
17
|
+
skillInterval: z.number().min(1).default(DEFAULT_REVIEW_SKILL_INTERVAL),
|
|
18
18
|
reviewToolAllow: z.array(z.string()).default(["skill"]),
|
|
19
|
-
reviewTimeoutMs: z.number().default(12e4),
|
|
20
|
-
executionTimeoutMs: z.number().default(3e4),
|
|
21
|
-
reviewContextMessages: z.number().default(60),
|
|
22
|
-
reviewMessageChars: z.number().default(2e3),
|
|
23
|
-
reviewMaxDepth: z.number().default(1),
|
|
19
|
+
reviewTimeoutMs: z.number().min(1).default(12e4),
|
|
20
|
+
executionTimeoutMs: z.number().min(1).default(3e4),
|
|
21
|
+
reviewContextMessages: z.number().min(1).default(60),
|
|
22
|
+
reviewMessageChars: z.number().min(1).default(2e3),
|
|
23
|
+
reviewMaxDepth: z.number().min(1).default(1),
|
|
24
24
|
reviewProvider: z.string(),
|
|
25
25
|
skillReviewTrigger: z.string().default(DEFAULT_SKILL_REVIEW_TRIGGER),
|
|
26
|
-
skillReviewCompletionMinToolCalls: z.number().default(DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS)
|
|
26
|
+
skillReviewCompletionMinToolCalls: z.number().min(1).default(DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS)
|
|
27
27
|
});
|
|
28
|
+
let statelessReviewStateWarned = false;
|
|
29
|
+
/**
|
|
30
|
+
* G3.1 (0.3.23): clamp the numeric review config at assembly so a 0/negative/
|
|
31
|
+
* NaN/±Infinity value falls back to the package default instead of folding as a
|
|
32
|
+
* "disabled" special value (a 0 interval would fire a review every turn; NaN
|
|
33
|
+
* folds as NaN into the cadence/timeout). The schema `.min(1)` guards the
|
|
34
|
+
* loader path; this clamp also covers NaN/±Infinity (which schemastery lets a
|
|
35
|
+
* bare number schema through) and direct construction. `reviewMaxDepth` clamps
|
|
36
|
+
* to at least 1 because 0 is the historical 0.3.1 maximum-depth defect (a 0
|
|
37
|
+
* rejects the spawn outright). Warn once when a user-supplied value had to be
|
|
38
|
+
* corrected.
|
|
39
|
+
*/
|
|
40
|
+
function clampReviewConfig(rawConfig, ctx) {
|
|
41
|
+
const clamped = [];
|
|
42
|
+
const field = (name, value, fallback, min) => {
|
|
43
|
+
const result = clampedNumber(value, fallback, { min });
|
|
44
|
+
if (value !== void 0 && result !== value) clamped.push(name);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
const config = Object.assign({}, rawConfig, {
|
|
48
|
+
memoryInterval: field("memoryInterval", rawConfig.memoryInterval, DEFAULT_REVIEW_MEMORY_INTERVAL, 1),
|
|
49
|
+
skillInterval: field("skillInterval", rawConfig.skillInterval, DEFAULT_REVIEW_SKILL_INTERVAL, 1),
|
|
50
|
+
reviewTimeoutMs: field("reviewTimeoutMs", rawConfig.reviewTimeoutMs, 12e4, 1),
|
|
51
|
+
reviewContextMessages: field("reviewContextMessages", rawConfig.reviewContextMessages, 60, 1),
|
|
52
|
+
reviewMessageChars: field("reviewMessageChars", rawConfig.reviewMessageChars, 2e3, 1),
|
|
53
|
+
reviewMaxDepth: field("reviewMaxDepth", rawConfig.reviewMaxDepth, 1, 1),
|
|
54
|
+
skillReviewCompletionMinToolCalls: field("skillReviewCompletionMinToolCalls", rawConfig.skillReviewCompletionMinToolCalls, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, 1)
|
|
55
|
+
});
|
|
56
|
+
if (clamped.length > 0) ctx.logger.warn(`dsh-evolution-review: ${clamped.join(", ")} provided an invalid value; falling back to the default`);
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
28
59
|
function apply(ctx, rawConfig) {
|
|
29
60
|
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to schedule review work");
|
|
30
|
-
const config = rawConfig;
|
|
61
|
+
const config = clampReviewConfig(rawConfig, ctx);
|
|
31
62
|
const turnStarts = /* @__PURE__ */ new Map();
|
|
32
63
|
const cumulativeToolCalls = /* @__PURE__ */ new Map();
|
|
33
64
|
const completionInjected = /* @__PURE__ */ new Set();
|
|
@@ -60,6 +91,10 @@ function apply(ctx, rawConfig) {
|
|
|
60
91
|
const signal = foldTurn(session, turnStarts.get(session.id) ?? Math.max(0, session.seq - 1));
|
|
61
92
|
turnStarts.delete(session.id);
|
|
62
93
|
const stateService = ctx.get("evolutionState");
|
|
94
|
+
if (!stateService && !statelessReviewStateWarned) {
|
|
95
|
+
statelessReviewStateWarned = true;
|
|
96
|
+
ctx.logger.warn("dsh-evolution-review: evolution-state service not mounted — memory/skill review cadence is not persisted and resets every turn (see README Known Limitations).");
|
|
97
|
+
}
|
|
63
98
|
const state = await stateService?.loadReviewState(session.id) ?? {
|
|
64
99
|
turnsSinceMemory: 0,
|
|
65
100
|
turnsSinceSkill: 0,
|
|
@@ -103,13 +138,6 @@ function apply(ctx, rawConfig) {
|
|
|
103
138
|
if (completionInjected.has(session.id)) return;
|
|
104
139
|
if (!shouldCompletionReview(event.data.reason, cumulative, config.skillReviewCompletionMinToolCalls)) return;
|
|
105
140
|
completionInjected.add(session.id);
|
|
106
|
-
ctx.emit("evolution/review-scheduled", {
|
|
107
|
-
sessionId: session.id,
|
|
108
|
-
kind: "skill",
|
|
109
|
-
toolCalls: signal.toolCalls,
|
|
110
|
-
userChars: signal.userChars,
|
|
111
|
-
assistantChars: signal.assistantChars
|
|
112
|
-
});
|
|
113
141
|
agent.inject(createUserMessage({
|
|
114
142
|
content: [{
|
|
115
143
|
type: "text",
|
|
@@ -122,6 +150,13 @@ function apply(ctx, rawConfig) {
|
|
|
122
150
|
summary: "completion review"
|
|
123
151
|
}
|
|
124
152
|
}));
|
|
153
|
+
ctx.emit("evolution/review-scheduled", {
|
|
154
|
+
sessionId: session.id,
|
|
155
|
+
kind: "skill",
|
|
156
|
+
toolCalls: signal.toolCalls,
|
|
157
|
+
userChars: signal.userChars,
|
|
158
|
+
assistantChars: signal.assistantChars
|
|
159
|
+
});
|
|
125
160
|
}
|
|
126
161
|
async function trySubagentReview(session, agent, kind, signal) {
|
|
127
162
|
if ((policy()?.reviewMode ?? config.reviewMode) === "inject") return false;
|
|
@@ -186,6 +221,26 @@ function apply(ctx, rawConfig) {
|
|
|
186
221
|
const executed = await executePlan(validation.accepted, session.id);
|
|
187
222
|
const actions = executed.actions;
|
|
188
223
|
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
|
|
224
|
+
if (actions.length > 0) {
|
|
225
|
+
const applied = actions.join(" · ");
|
|
226
|
+
const note = executed.ok ? "" : "\n部分操作失败。以下操作已应用,请勿重复执行。";
|
|
227
|
+
try {
|
|
228
|
+
agent.inject(createUserMessage({
|
|
229
|
+
content: [{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: `💾 Self-improvement review: ${applied}${note}`
|
|
232
|
+
}],
|
|
233
|
+
source: {
|
|
234
|
+
kind: "plugin",
|
|
235
|
+
plugin: "dsh-evolution-review",
|
|
236
|
+
form: "notice",
|
|
237
|
+
summary: "self-improvement review"
|
|
238
|
+
}
|
|
239
|
+
}));
|
|
240
|
+
} catch (injectError) {
|
|
241
|
+
ctx.logger.warn(`dsh-evolution-review: result notice inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
189
244
|
ctx.emit("evolution/plan-applied", {
|
|
190
245
|
sessionId: session.id,
|
|
191
246
|
planId: randomUUID(),
|
|
@@ -196,22 +251,6 @@ function apply(ctx, rawConfig) {
|
|
|
196
251
|
evidenceQuotes,
|
|
197
252
|
estimatedInputChars: reviewText.length
|
|
198
253
|
});
|
|
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
|
-
}
|
|
215
254
|
return true;
|
|
216
255
|
} finally {
|
|
217
256
|
try {
|
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.25",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
36
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.25",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.25",
|
|
36
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.25"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
43
43
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
44
44
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
45
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
46
|
-
"@lmzhen/dsh-evolution-policy": "^0.3.
|
|
45
|
+
"@lmzhen/dsh-evolution-state": "^0.3.25",
|
|
46
|
+
"@lmzhen/dsh-evolution-policy": "^0.3.25"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
|
|
55
55
|
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.1-rc.2",
|
|
56
56
|
"@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.
|
|
57
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.25",
|
|
58
|
+
"@lmzhen/dsh-evolution-core": "^0.3.25",
|
|
59
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.25",
|
|
60
|
+
"@lmzhen/dsh-evolution-state": "^0.3.25"
|
|
61
61
|
}
|
|
62
62
|
}
|