@lmzhen/dsh-evolution-review 0.3.39 → 0.3.41

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 CHANGED
@@ -23,6 +23,7 @@ const Config = z.object({
23
23
  reviewMaxDepth: z.number().min(1).default(1),
24
24
  reviewProvider: z.string(),
25
25
  skillReviewTrigger: z.string().default(DEFAULT_SKILL_REVIEW_TRIGGER),
26
+ reviewWakeInject: z.boolean().default(true),
26
27
  skillReviewCompletionMinToolCalls: z.number().min(1).default(DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS)
27
28
  });
28
29
  let statelessReviewStateWarned = false;
@@ -64,18 +65,20 @@ function apply(ctx, rawConfig) {
64
65
  const completionInjected = /* @__PURE__ */ new Set();
65
66
  const pendingCadenceReviews = /* @__PURE__ */ new Map();
66
67
  const pendingCadenceWarned = /* @__PURE__ */ new Set();
68
+ const skipNextCadenceFire = /* @__PURE__ */ new Map();
67
69
  let reviewInFlight = false;
68
70
  const policy = () => ctx.get("evolutionPolicy")?.get();
69
71
  ctx.on("session/event", (session, event) => {
70
72
  if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
71
73
  if (event.type !== "turn/end") return;
72
- if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceReviews.size >= COUNTER_SWEEP_THRESHOLD) {
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) {
73
75
  const isAlive = (id) => ctx.agents.get(id) !== void 0;
74
76
  sweepDeadSessionEntries(turnStarts, isAlive);
75
77
  sweepDeadSessionEntries(cumulativeToolCalls, isAlive);
76
78
  sweepDeadSessionEntries(completionInjected, isAlive);
77
79
  sweepDeadSessionEntries(pendingCadenceReviews, isAlive);
78
80
  sweepDeadSessionEntries(pendingCadenceWarned, isAlive);
81
+ sweepDeadSessionEntries(skipNextCadenceFire, isAlive);
79
82
  }
80
83
  onTurnEnd(session, event);
81
84
  });
@@ -109,33 +112,45 @@ function apply(ctx, rawConfig) {
109
112
  lastTurn: -1
110
113
  };
111
114
  const snapshot = policy();
112
- const kind = advanceReview(state, event.data.turn, signal, {
115
+ const skipFire = skipNextCadenceFire.get(session.id) ?? false;
116
+ if (skipFire) skipNextCadenceFire.delete(session.id);
117
+ const rawKind = advanceReview(state, event.data.turn, signal, {
113
118
  memoryInterval: snapshot?.reviewMemoryInterval ?? config.memoryInterval,
114
119
  skillInterval: snapshot?.reviewSkillInterval ?? config.skillInterval,
115
120
  substantiveMinToolCalls: snapshot?.substantiveMinToolCalls ?? 3,
116
121
  substantiveMinUserChars: snapshot?.substantiveMinUserChars ?? 200,
117
- substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? 500
122
+ substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? 500,
123
+ resetOnFire: false
118
124
  });
125
+ const kind = skipFire ? null : rawKind;
119
126
  await stateService?.saveReviewState(session.id, state);
120
127
  const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
121
128
  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
+ };
122
148
  if (event.data.reason.kind === "completed") {
123
- const pendingKind = pendingCadenceReviews.get(session.id);
149
+ const pendingKind = pendingCadenceReviews.get(session.id) ?? kind ?? void 0;
124
150
  if (pendingKind !== void 0) {
125
151
  pendingCadenceReviews.delete(session.id);
126
152
  if ((policy()?.reviewMode ?? config.reviewMode) === "inject") try {
127
- agent.inject(createUserMessage({
128
- content: [{
129
- type: "text",
130
- text: reviewPrompt(pendingKind)
131
- }],
132
- source: {
133
- kind: "plugin",
134
- plugin: "dsh-evolution-review",
135
- form: "notice",
136
- summary: "auto-review"
137
- }
138
- }));
153
+ deliverReview(reviewPrompt(pendingKind));
139
154
  } catch (injectError) {
140
155
  ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
141
156
  }
@@ -147,24 +162,16 @@ function apply(ctx, rawConfig) {
147
162
  assistantChars: signal.assistantChars
148
163
  });
149
164
  else try {
150
- agent.inject(createUserMessage({
151
- content: [{
152
- type: "text",
153
- text: reviewPrompt(pendingKind)
154
- }],
155
- source: {
156
- kind: "plugin",
157
- plugin: "dsh-evolution-review",
158
- form: "notice",
159
- summary: "auto-review"
160
- }
161
- }));
165
+ deliverReview(reviewPrompt(pendingKind));
162
166
  } catch (injectError) {
163
167
  ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
164
168
  }
169
+ state.turnsSinceMemory = 0;
170
+ state.turnsSinceSkill = 0;
171
+ await stateService?.saveReviewState(session.id, state);
165
172
  }
166
173
  }
167
- if (kind) {
174
+ if (kind && event.data.reason.kind !== "completed") {
168
175
  pendingCadenceReviews.set(session.id, kind);
169
176
  if (!pendingCadenceWarned.has(session.id)) {
170
177
  pendingCadenceWarned.add(session.id);
@@ -267,7 +274,7 @@ function apply(ctx, rawConfig) {
267
274
  });
268
275
  const acceptedSkillOps = validation.accepted.skillOps ?? [];
269
276
  const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
270
- const executed = await executePlan(validation.accepted, session.id);
277
+ const executed = await executePlan(validation.accepted, session);
271
278
  const actions = executed.actions;
272
279
  const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
273
280
  if (actions.length > 0) {
@@ -345,7 +352,8 @@ function apply(ctx, rawConfig) {
345
352
  reviewInFlight = false;
346
353
  }
347
354
  }
348
- async function executePlan(plan, sessionId) {
355
+ async function executePlan(plan, session) {
356
+ const sessionId = session?.id;
349
357
  const memory = ctx.get("memory");
350
358
  const approval = ctx.get("evolutionApproval");
351
359
  const origins = resolveOrigins(void 0, true);
@@ -361,7 +369,7 @@ function apply(ctx, rawConfig) {
361
369
  facts: op.facts ?? op.content,
362
370
  old_text: op.old_text
363
371
  };
364
- const result = approval ? await runApproved("memory", `memory ${normalized.target} ${normalized.action}`, normalized, normalized) : await memory?.applyBatch(normalized.target, [normalized]);
372
+ const result = approval ? await runApproved("memory", `memory ${normalized.target} ${normalized.action}`, normalized, normalized, session) : await memory?.applyBatch(normalized.target, [normalized]);
365
373
  if (result?.ok) actions.push("Memory updated");
366
374
  else {
367
375
  ok = false;
@@ -378,7 +386,7 @@ function apply(ctx, rawConfig) {
378
386
  operation: args,
379
387
  origin: origins.library
380
388
  };
381
- const result = approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs) : await executeSkillDirect(args);
389
+ const result = approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs, session) : await executeSkillDirect(args);
382
390
  if (result?.ok) actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
383
391
  else {
384
392
  ok = false;
@@ -401,7 +409,7 @@ function apply(ctx, rawConfig) {
401
409
  aborted: reason
402
410
  };
403
411
  }
404
- async function runApproved(kind, summary, stored, runnerArgs) {
412
+ async function runApproved(kind, summary, stored, runnerArgs, approvalSession) {
405
413
  if (!approval) return void 0;
406
414
  if (approval.isEnabled === true && !approval.hasRunner(kind)) {
407
415
  ctx.logger.warn(`dsh-evolution-review: approval enabled but no replay runner registered for kind "${kind}" - skipping write (${summary})`);
@@ -415,7 +423,8 @@ function apply(ctx, rawConfig) {
415
423
  summary,
416
424
  args: stored,
417
425
  origin: origins.approval,
418
- ...sessionId ? { sessionId } : {}
426
+ ...sessionId ? { sessionId } : {},
427
+ ...approvalSession ? { session: approvalSession } : {}
419
428
  });
420
429
  if (decision.action === "staged") return {
421
430
  ok: false,
@@ -32,6 +32,11 @@ export interface Config {
32
32
  skillReviewTrigger?: string;
33
33
  /** Cumulative session tool calls before a session counts as proven-long for the completion channel. */
34
34
  skillReviewCompletionMinToolCalls?: number;
35
+ /** 0.3.40: deliver the deferred review with a WAKING inject for a summary the
36
+ * model starts immediately (agent.followup — same send() queue, wakeup bit
37
+ * differs). Default true; false degrades to the non-waking inject (the
38
+ * summary then waits for the next driver wake). */
39
+ reviewWakeInject?: boolean;
35
40
  }
36
41
  export declare const Config: z<Config>;
37
42
  /**
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.39",
4
+ "version": "0.3.41",
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.39",
35
- "@lmzhen/dsh-evolution-core": "^0.3.39",
36
- "@lmzhen/dsh-evolution-plan-validator": "^0.3.39"
34
+ "@lmzhen/dsh-evolution-approval": "^0.3.41",
35
+ "@lmzhen/dsh-evolution-core": "^0.3.41",
36
+ "@lmzhen/dsh-evolution-plan-validator": "^0.3.41"
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.39",
46
- "@lmzhen/dsh-evolution-policy": "^0.3.39"
45
+ "@lmzhen/dsh-evolution-state": "^0.3.41",
46
+ "@lmzhen/dsh-evolution-policy": "^0.3.41"
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.39",
58
- "@lmzhen/dsh-evolution-core": "^0.3.39",
59
- "@lmzhen/dsh-evolution-plan-validator": "^0.3.39",
60
- "@lmzhen/dsh-evolution-state": "^0.3.39"
57
+ "@lmzhen/dsh-evolution-approval": "^0.3.41",
58
+ "@lmzhen/dsh-evolution-core": "^0.3.41",
59
+ "@lmzhen/dsh-evolution-plan-validator": "^0.3.41",
60
+ "@lmzhen/dsh-evolution-state": "^0.3.41"
61
61
  }
62
62
  }