@lmzhen/dsh-evolution-review 0.3.71 → 0.3.73
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 +1 -1
- package/lib/index.js +45 -38
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -40,5 +40,5 @@ Independent of request-prefix construction. This package does not alter the asse
|
|
|
40
40
|
|
|
41
41
|
- **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.
|
|
42
42
|
- **`skillReviewTrigger`** (default `'cadence'`): the cadence channel is **always on** (one end-of-conversation review from the cadence latch per task segment); the flag gates **only the completion channel** — `'cadence'` disables it, `'completion'` enables it (cadence still fires), `'both'` enables it on top of the always-on cadence. At one boundary a turn is served by exactly one review: the cadence flush runs first and returns, so `'both'` never double-sends a second task-complete prompt at the same boundary (V10-13).
|
|
43
|
-
- **`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
|
+
- **`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 wake primitive is always called ON the agent instance** — the platform's `Agent.followup`/`inject` are prototype methods that call `this.send(...)`, so extracting one into a local and calling the detached reference throws (0.3.73: that throw was caught and logged while the cadence reset still ran, silently consuming every segment's review from 2026-09-07). A refused delivery now returns `false` and the caller keeps its latch and counters, so the review retries at the next completed boundary instead of vanishing; `evolution-host/tests/wake-delivery-guard.spec.ts` pins the call form mechanically. 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.
|
|
44
44
|
- **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
|
@@ -173,8 +173,11 @@ function apply(ctx, rawConfig = {}) {
|
|
|
173
173
|
const pendingKind = kind ?? pendingCadenceReviews.get(session.id) ?? void 0;
|
|
174
174
|
if (pendingKind !== void 0) {
|
|
175
175
|
pendingCadenceReviews.delete(session.id);
|
|
176
|
-
if ((policy()?.reviewMode ?? config.reviewMode) === "inject")
|
|
177
|
-
deliverMessage(agent, reviewPrompt(pendingKind), "auto-review")
|
|
176
|
+
if ((policy()?.reviewMode ?? config.reviewMode) === "inject") {
|
|
177
|
+
if (!deliverMessage(agent, reviewPrompt(pendingKind), "auto-review")) {
|
|
178
|
+
pendingCadenceReviews.set(session.id, pendingKind);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
178
181
|
try {
|
|
179
182
|
ctx.emit("evolution/review-scheduled", {
|
|
180
183
|
sessionId: session.id,
|
|
@@ -187,10 +190,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
187
190
|
} catch (emitError) {
|
|
188
191
|
ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
189
192
|
}
|
|
190
|
-
}
|
|
191
|
-
ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
193
|
+
} else {
|
|
194
194
|
const reviewOutcome = await trySubagentReview(session, agent, pendingKind, signal);
|
|
195
195
|
if (reviewOutcome === true) try {
|
|
196
196
|
ctx.emit("evolution/review-scheduled", {
|
|
@@ -207,8 +207,11 @@ function apply(ctx, rawConfig = {}) {
|
|
|
207
207
|
else if (reviewOutcome === "dropped") {
|
|
208
208
|
pendingCadenceReviews.set(session.id, pendingKind);
|
|
209
209
|
return;
|
|
210
|
-
} else if (reviewOutcome !== "deferred")
|
|
211
|
-
deliverMessage(agent, reviewPrompt(pendingKind), "auto-review")
|
|
210
|
+
} else if (reviewOutcome !== "deferred") {
|
|
211
|
+
if (!deliverMessage(agent, reviewPrompt(pendingKind), "auto-review")) {
|
|
212
|
+
pendingCadenceReviews.set(session.id, pendingKind);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
212
215
|
try {
|
|
213
216
|
ctx.emit("evolution/review-scheduled", {
|
|
214
217
|
sessionId: session.id,
|
|
@@ -221,8 +224,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
221
224
|
} catch (emitError) {
|
|
222
225
|
ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
223
226
|
}
|
|
224
|
-
} catch (injectError) {
|
|
225
|
-
ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
try {
|
|
@@ -271,11 +272,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
271
272
|
}
|
|
272
273
|
return;
|
|
273
274
|
}
|
|
274
|
-
|
|
275
|
-
deliverMessage(agent, COMPLETION_SKILL_REVIEW_PROMPT, "completion review");
|
|
276
|
-
} catch (injectError) {
|
|
275
|
+
if (!deliverMessage(agent, COMPLETION_SKILL_REVIEW_PROMPT, "completion review")) {
|
|
277
276
|
completionInjected.delete(session.id);
|
|
278
|
-
ctx.logger.warn(`dsh-evolution-review: completion review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
279
277
|
return;
|
|
280
278
|
}
|
|
281
279
|
try {
|
|
@@ -294,7 +292,16 @@ function apply(ctx, rawConfig = {}) {
|
|
|
294
292
|
/** V7-03 (0.3.42): shared waking delivery — review prompts AND result
|
|
295
293
|
* notices go through the same followup-first channel (skip the woken turn's
|
|
296
294
|
* cadence fire once, degrade to inject when reviewWakeInject is off or the
|
|
297
|
-
* host lacks followup).
|
|
295
|
+
* host lacks followup).
|
|
296
|
+
*
|
|
297
|
+
* 0.3.73: returns false when the host refused the delivery, and never throws.
|
|
298
|
+
* Callers that own a one-shot latch or the cadence reset MUST honour the
|
|
299
|
+
* result: the pre-0.3.73 form read `agent.followup` into a local and called
|
|
300
|
+
* the detached reference, so EVERY real delivery threw
|
|
301
|
+
* `TypeError: … reading 'send'` inside the platform's ReactLoopAgent (a
|
|
302
|
+
* prototype method), the caller's catch demoted that to a console warning,
|
|
303
|
+
* and the cadence reset ran anyway — the segment's review was consumed with
|
|
304
|
+
* nothing queued (silent no-delivery window: 2026-09-07 → 0.3.73). */
|
|
298
305
|
const deliverMessage = (agent, text, summary) => {
|
|
299
306
|
const message = createUserMessage({
|
|
300
307
|
content: [{
|
|
@@ -308,11 +315,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
308
315
|
summary
|
|
309
316
|
}
|
|
310
317
|
});
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
followup
|
|
314
|
-
|
|
315
|
-
|
|
318
|
+
const wake = agent;
|
|
319
|
+
try {
|
|
320
|
+
if (config.reviewWakeInject && typeof wake.followup === "function") {
|
|
321
|
+
wake.followup(message);
|
|
322
|
+
skipNextCadenceFire.set(agent.session.id, true);
|
|
323
|
+
} else agent.inject(message);
|
|
324
|
+
return true;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
ctx.logger.warn(`dsh-evolution-review: review delivery failed (${error instanceof Error ? error.message : String(error)}) — nothing was queued; the review is NOT consumed and retries at the next completed boundary`);
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
316
329
|
};
|
|
317
330
|
const withTimeout = (promise, ms, label) => new Promise((resolve, reject) => {
|
|
318
331
|
const timer = setTimeout(() => {
|
|
@@ -376,6 +389,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
376
389
|
const agentOptions = { model };
|
|
377
390
|
if (config.reviewProvider) agentOptions.provider = config.reviewProvider;
|
|
378
391
|
const preRunHashes = await treeSkillHashes();
|
|
392
|
+
const sessionSeqAtPlanTime = session.seq - 1;
|
|
379
393
|
const run = await subagents.start("spawn", {
|
|
380
394
|
label: "dsh-evolution-review",
|
|
381
395
|
prompt: [{
|
|
@@ -398,14 +412,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
398
412
|
} catch (emitError) {
|
|
399
413
|
ctx.logger.warn(`dsh-evolution-review: review-error emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
400
414
|
}
|
|
401
|
-
|
|
415
|
+
const failureShape = result;
|
|
416
|
+
const stopDetail = typeof failureShape.stopReason === "string" ? failureShape.stopReason : void 0;
|
|
417
|
+
const diagDetail = typeof failureShape.diagnostic === "string" ? failureShape.diagnostic : void 0;
|
|
418
|
+
ctx.logger.warn(`dsh-evolution-review: review subagent returned no structured plan${stopDetail !== void 0 ? ` (stopReason=${stopDetail}${diagDetail !== void 0 ? `; diagnostic=${diagDetail}` : ""})` : ""}`);
|
|
402
419
|
return false;
|
|
403
420
|
}
|
|
404
421
|
const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
|
|
405
422
|
const plan = result.structured;
|
|
406
423
|
const policyFingerprint = fingerprintPolicy(snapshot);
|
|
407
424
|
const validation = validateEvolutionPlan(plan, {
|
|
408
|
-
sessionSeq:
|
|
425
|
+
sessionSeq: sessionSeqAtPlanTime,
|
|
409
426
|
maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
|
|
410
427
|
protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
|
|
411
428
|
maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
|
|
@@ -449,13 +466,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
449
466
|
if (actions.length > 0) {
|
|
450
467
|
const applied = actions.join(" · ");
|
|
451
468
|
const failedNote = executed.failedOps.length > 0 ? ` 失败 ${executed.failedOps.length} 个:${executed.failedOps.join(";")}。` : "";
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
deliverMessage(agent, `💾 Self-improvement review: ${applied}${note}`, "self-improvement review");
|
|
455
|
-
} catch (injectError) {
|
|
456
|
-
ctx.logger.warn(`dsh-evolution-review: result notice inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
457
|
-
}
|
|
458
|
-
} else try {
|
|
469
|
+
deliverMessage(agent, `💾 Self-improvement review: ${applied}${executed.ok ? "" : `\n部分操作失败。${failedNote}以上操作已应用,请勿重复执行。`}`, "self-improvement review");
|
|
470
|
+
} else {
|
|
459
471
|
const reasons = [];
|
|
460
472
|
if (validation.rejected.length > 0) reasons.push(`${validation.rejected.length} op(s) rejected by validation`);
|
|
461
473
|
if (skippedUnread > 0) reasons.push(`${skippedUnread} op(s) skipped (skill not read this session)`);
|
|
@@ -465,8 +477,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
465
477
|
let text = `💾 Self-improvement review: 0 ops landed. ${reasons.join(" ")}`;
|
|
466
478
|
if (text.length > 500) text = `${text.slice(0, 497)}…`;
|
|
467
479
|
deliverMessage(agent, text, "self-improvement review");
|
|
468
|
-
} catch (injectError) {
|
|
469
|
-
ctx.logger.warn(`dsh-evolution-review: zero-landing notice inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
470
480
|
}
|
|
471
481
|
emitApplied({
|
|
472
482
|
actions,
|
|
@@ -489,11 +499,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
489
499
|
reviewInFlight = false;
|
|
490
500
|
const deferred = deferredFallbackReviews.splice(0);
|
|
491
501
|
for (const { agent: waitingAgent, sessionId: entrySession, kind: waitingKind, prompt, label, channel, counts: entryCounts } of deferred) {
|
|
492
|
-
|
|
493
|
-
deliverMessage(waitingAgent, prompt, label);
|
|
494
|
-
} catch (injectError) {
|
|
502
|
+
if (!deliverMessage(waitingAgent, prompt, label)) {
|
|
495
503
|
if (channel === "completion") completionInjected.delete(entrySession);
|
|
496
|
-
ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
497
504
|
continue;
|
|
498
505
|
}
|
|
499
506
|
try {
|
|
@@ -566,13 +573,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
566
573
|
if (stageFile !== void 0) args.staged_from_sha256 = stageFile === null ? "absent" : contentHash(stageFile);
|
|
567
574
|
}
|
|
568
575
|
const opName = typeof args.name === "string" ? args.name : "";
|
|
569
|
-
const hashChecked = (args.action === "update" || args.action === "edit") && opName !== "" && preRunHashes?.has(opName) === true;
|
|
576
|
+
const hashChecked = (args.action === "update" || args.action === "edit" || args.action === "patch") && opName !== "" && preRunHashes?.has(opName) === true;
|
|
570
577
|
if (hashChecked) {
|
|
571
|
-
const live = stageCurrent;
|
|
578
|
+
const live = args.action === "patch" ? hashLibrary ? await hashLibrary.read(opName).catch(() => null) : null : stageCurrent;
|
|
572
579
|
const preRun = preRunHashes.get(opName);
|
|
573
580
|
if (live === null || preRun !== void 0 && contentHash(live) !== preRun) {
|
|
574
581
|
ok = false;
|
|
575
|
-
failedOps.push(`skill ${args.action} ${args.name}: the skill changed while this review ran — update refused as stale; produce a fresh plan`);
|
|
582
|
+
failedOps.push(`skill ${args.action} ${args.name}: the skill changed while this review ran — ${args.action === "patch" ? "patch" : "update"} refused as stale; produce a fresh plan`);
|
|
576
583
|
continue;
|
|
577
584
|
}
|
|
578
585
|
}
|
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.73",
|
|
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.73",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.73",
|
|
36
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.73"
|
|
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.5-rc.2",
|
|
43
43
|
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
44
44
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
45
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
46
|
-
"@lmzhen/dsh-evolution-policy": "^0.3.
|
|
45
|
+
"@lmzhen/dsh-evolution-state": "^0.3.73",
|
|
46
|
+
"@lmzhen/dsh-evolution-policy": "^0.3.73"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"@deepseek-ai/dsh-session-persistence": "^0.1.5-rc.2",
|
|
55
55
|
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.5-rc.2",
|
|
56
56
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
57
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
58
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
59
|
-
"@lmzhen/dsh-evolution-curator": "^0.3.
|
|
60
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
61
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
57
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.73",
|
|
58
|
+
"@lmzhen/dsh-evolution-core": "^0.3.73",
|
|
59
|
+
"@lmzhen/dsh-evolution-curator": "^0.3.73",
|
|
60
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.73",
|
|
61
|
+
"@lmzhen/dsh-evolution-state": "^0.3.73"
|
|
62
62
|
}
|
|
63
63
|
}
|