@lmzhen/dsh-evolution-review 0.3.41 → 0.3.43
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 +7 -0
- package/lib/index.js +38 -47
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -34,3 +34,10 @@ Independent of request-prefix construction. This package does not alter the asse
|
|
|
34
34
|
`reviewProvider` selects the LLM provider for review subagents. When omitted, the subagent inherits the deployment default route instead of a hardcoded provider name. Model selection stays on the policy (`memoryReviewModel` / `skillReviewModel`).
|
|
35
35
|
|
|
36
36
|
`reviewTimeoutMs` bounds each review subagent run (an `AbortSignal.timeout`; `0` aborts immediately). `executionTimeoutMs` is a leftover declaration and is **not consumed** — it is kept only as a code comment and has no effect; configure `reviewTimeoutMs` instead.
|
|
37
|
+
|
|
38
|
+
### Review delivery contract (0.3.38-0.3.42)
|
|
39
|
+
|
|
40
|
+
- **Both channels execute at conversation end only** (a `turn/end` with `reason.kind === 'completed'`): a cadence threshold fire mid-task merely latches the kind — no subagent spawn, no inject. The flush runs BEFORE the latch block (the completing turn may itself be a threshold-firing turn). `reviewMode` (`'subagent'` default / `'inject'`) selects how the flush delivers; the explicit `'inject'` mode's historical "immediate on threshold" contract was superseded in 0.3.39 — both modes are end-of-conversation.
|
|
41
|
+
- **`skillReviewTrigger`** (default `'cadence'`): `'cadence'` runs one end-of-conversation review from the cadence latch; `'completion'` uses the separate long-session completion gate (session-cumulative tool-call threshold); `'both'` enables both (note: with the deferred cadence execution the `'both'` combination would inject a second task-complete prompt at the same boundary — prefer `'cadence'`).
|
|
42
|
+
- **`reviewWakeInject`** (default `true`): deliveries use `agent.followup` (next-turn + wake — the model starts processing immediately) instead of the non-waking `agent.inject` (which waits for the next driver wake). The host falls back to `inject` when it has no followup or the option is `false`. The woken turn's own cadence fire is suppressed once (an injected review prompt alone must not re-trigger a review under `interval=1`); a restart clears the queue, so the loop cannot survive it.
|
|
43
|
+
- **Counting window = injection-to-injection**: the `turnsSinceMemory`/`turnsSinceSkill` counters are monotonic across threshold fires (`resetOnFire: false`) and are zeroed at the flush delivery — a continued conversation starts a fresh segment from the injection. A threshold fire on the completing turn is caught by the flush (`pendingKind = latch ?? kind`). All deliveries (review prompt AND result notices) share the same waking channel; a failed counter-reset persist warns once per session (a stateful reload may re-deliver).
|
package/lib/index.js
CHANGED
|
@@ -66,12 +66,13 @@ function apply(ctx, rawConfig) {
|
|
|
66
66
|
const pendingCadenceReviews = /* @__PURE__ */ new Map();
|
|
67
67
|
const pendingCadenceWarned = /* @__PURE__ */ new Set();
|
|
68
68
|
const skipNextCadenceFire = /* @__PURE__ */ new Map();
|
|
69
|
+
const cadenceResetWarned = /* @__PURE__ */ new Set();
|
|
69
70
|
let reviewInFlight = false;
|
|
70
71
|
const policy = () => ctx.get("evolutionPolicy")?.get();
|
|
71
72
|
ctx.on("session/event", (session, event) => {
|
|
72
73
|
if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
|
|
73
74
|
if (event.type !== "turn/end") return;
|
|
74
|
-
if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceReviews.size >= COUNTER_SWEEP_THRESHOLD || skipNextCadenceFire.size >= COUNTER_SWEEP_THRESHOLD) {
|
|
75
|
+
if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceReviews.size >= COUNTER_SWEEP_THRESHOLD || skipNextCadenceFire.size >= COUNTER_SWEEP_THRESHOLD || cadenceResetWarned.size >= COUNTER_SWEEP_THRESHOLD) {
|
|
75
76
|
const isAlive = (id) => ctx.agents.get(id) !== void 0;
|
|
76
77
|
sweepDeadSessionEntries(turnStarts, isAlive);
|
|
77
78
|
sweepDeadSessionEntries(cumulativeToolCalls, isAlive);
|
|
@@ -79,6 +80,7 @@ function apply(ctx, rawConfig) {
|
|
|
79
80
|
sweepDeadSessionEntries(pendingCadenceReviews, isAlive);
|
|
80
81
|
sweepDeadSessionEntries(pendingCadenceWarned, isAlive);
|
|
81
82
|
sweepDeadSessionEntries(skipNextCadenceFire, isAlive);
|
|
83
|
+
sweepDeadSessionEntries(cadenceResetWarned, isAlive);
|
|
82
84
|
}
|
|
83
85
|
onTurnEnd(session, event);
|
|
84
86
|
});
|
|
@@ -126,31 +128,12 @@ function apply(ctx, rawConfig) {
|
|
|
126
128
|
await stateService?.saveReviewState(session.id, state);
|
|
127
129
|
const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
|
|
128
130
|
cumulativeToolCalls.set(session.id, cumulative);
|
|
129
|
-
const deliverReview = (text) => {
|
|
130
|
-
const message = createUserMessage({
|
|
131
|
-
content: [{
|
|
132
|
-
type: "text",
|
|
133
|
-
text
|
|
134
|
-
}],
|
|
135
|
-
source: {
|
|
136
|
-
kind: "plugin",
|
|
137
|
-
plugin: "dsh-evolution-review",
|
|
138
|
-
form: "notice",
|
|
139
|
-
summary: "auto-review"
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
const followup = agent.followup;
|
|
143
|
-
if (config.reviewWakeInject && typeof followup === "function") {
|
|
144
|
-
followup(message);
|
|
145
|
-
skipNextCadenceFire.set(session.id, true);
|
|
146
|
-
} else agent.inject(message);
|
|
147
|
-
};
|
|
148
131
|
if (event.data.reason.kind === "completed") {
|
|
149
132
|
const pendingKind = pendingCadenceReviews.get(session.id) ?? kind ?? void 0;
|
|
150
133
|
if (pendingKind !== void 0) {
|
|
151
134
|
pendingCadenceReviews.delete(session.id);
|
|
152
135
|
if ((policy()?.reviewMode ?? config.reviewMode) === "inject") try {
|
|
153
|
-
|
|
136
|
+
deliverMessage(agent, reviewPrompt(pendingKind), "auto-review");
|
|
154
137
|
} catch (injectError) {
|
|
155
138
|
ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
156
139
|
}
|
|
@@ -162,13 +145,20 @@ function apply(ctx, rawConfig) {
|
|
|
162
145
|
assistantChars: signal.assistantChars
|
|
163
146
|
});
|
|
164
147
|
else try {
|
|
165
|
-
|
|
148
|
+
deliverMessage(agent, reviewPrompt(pendingKind), "auto-review");
|
|
166
149
|
} catch (injectError) {
|
|
167
150
|
ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
168
151
|
}
|
|
169
152
|
state.turnsSinceMemory = 0;
|
|
170
153
|
state.turnsSinceSkill = 0;
|
|
171
|
-
|
|
154
|
+
try {
|
|
155
|
+
await stateService?.saveReviewState(session.id, state);
|
|
156
|
+
} catch (resetError) {
|
|
157
|
+
if (!cadenceResetWarned.has(session.id)) {
|
|
158
|
+
cadenceResetWarned.add(session.id);
|
|
159
|
+
ctx.logger.warn(`dsh-evolution-review: cadence counter reset could not be persisted after a delivered review (${resetError instanceof Error ? resetError.message : String(resetError)}) — a stateful reload may re-deliver this review`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
172
162
|
}
|
|
173
163
|
}
|
|
174
164
|
if (kind && event.data.reason.kind !== "completed") {
|
|
@@ -210,6 +200,29 @@ function apply(ctx, rawConfig) {
|
|
|
210
200
|
assistantChars: signal.assistantChars
|
|
211
201
|
});
|
|
212
202
|
}
|
|
203
|
+
/** V7-03 (0.3.42): shared waking delivery — review prompts AND result
|
|
204
|
+
* notices go through the same followup-first channel (skip the woken turn's
|
|
205
|
+
* cadence fire once, degrade to inject when reviewWakeInject is off or the
|
|
206
|
+
* host lacks followup). */
|
|
207
|
+
const deliverMessage = (agent, text, summary) => {
|
|
208
|
+
const message = createUserMessage({
|
|
209
|
+
content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text
|
|
212
|
+
}],
|
|
213
|
+
source: {
|
|
214
|
+
kind: "plugin",
|
|
215
|
+
plugin: "dsh-evolution-review",
|
|
216
|
+
form: "notice",
|
|
217
|
+
summary
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
const followup = agent.followup;
|
|
221
|
+
if (config.reviewWakeInject && typeof followup === "function") {
|
|
222
|
+
followup(message);
|
|
223
|
+
skipNextCadenceFire.set(agent.session.id, true);
|
|
224
|
+
} else agent.inject(message);
|
|
225
|
+
};
|
|
213
226
|
async function trySubagentReview(session, agent, kind, signal) {
|
|
214
227
|
if ((policy()?.reviewMode ?? config.reviewMode) === "inject") return false;
|
|
215
228
|
const subagents = ctx.get("subagents");
|
|
@@ -281,18 +294,7 @@ function apply(ctx, rawConfig) {
|
|
|
281
294
|
const applied = actions.join(" · ");
|
|
282
295
|
const note = executed.ok ? "" : "\n部分操作失败。以下操作已应用,请勿重复执行。";
|
|
283
296
|
try {
|
|
284
|
-
agent
|
|
285
|
-
content: [{
|
|
286
|
-
type: "text",
|
|
287
|
-
text: `💾 Self-improvement review: ${applied}${note}`
|
|
288
|
-
}],
|
|
289
|
-
source: {
|
|
290
|
-
kind: "plugin",
|
|
291
|
-
plugin: "dsh-evolution-review",
|
|
292
|
-
form: "notice",
|
|
293
|
-
summary: "self-improvement review"
|
|
294
|
-
}
|
|
295
|
-
}));
|
|
297
|
+
deliverMessage(agent, `💾 Self-improvement review: ${applied}${note}`, "self-improvement review");
|
|
296
298
|
} catch (injectError) {
|
|
297
299
|
ctx.logger.warn(`dsh-evolution-review: result notice inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
298
300
|
}
|
|
@@ -305,18 +307,7 @@ function apply(ctx, rawConfig) {
|
|
|
305
307
|
if (reasons.length === 0) reasons.push("the review plan contained nothing executable");
|
|
306
308
|
let text = `💾 Self-improvement review: 0 ops landed. ${reasons.join(" ")}`;
|
|
307
309
|
if (text.length > 500) text = `${text.slice(0, 497)}…`;
|
|
308
|
-
agent
|
|
309
|
-
content: [{
|
|
310
|
-
type: "text",
|
|
311
|
-
text
|
|
312
|
-
}],
|
|
313
|
-
source: {
|
|
314
|
-
kind: "plugin",
|
|
315
|
-
plugin: "dsh-evolution-review",
|
|
316
|
-
form: "notice",
|
|
317
|
-
summary: "self-improvement review"
|
|
318
|
-
}
|
|
319
|
-
}));
|
|
310
|
+
deliverMessage(agent, text, "self-improvement review");
|
|
320
311
|
} catch (injectError) {
|
|
321
312
|
ctx.logger.warn(`dsh-evolution-review: zero-landing notice inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
322
313
|
}
|
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.43",
|
|
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.43",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.43",
|
|
36
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.43"
|
|
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.43",
|
|
46
|
+
"@lmzhen/dsh-evolution-policy": "^0.3.43"
|
|
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.43",
|
|
58
|
+
"@lmzhen/dsh-evolution-core": "^0.3.43",
|
|
59
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.43",
|
|
60
|
+
"@lmzhen/dsh-evolution-state": "^0.3.43"
|
|
61
61
|
}
|
|
62
62
|
}
|