@lmzhen/dsh-evolution-review 0.1.0-rc.42 → 0.1.0-rc.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/lib/index.js CHANGED
@@ -64,6 +64,12 @@ function apply(ctx, rawConfig) {
64
64
  ctx.on("session/event", (session, event) => {
65
65
  if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
66
66
  if (event.type !== "turn/end") return;
67
+ if (turnStarts.size >= 128 || cumulativeToolCalls.size >= 128 || completionInjected.size >= 128) {
68
+ const isAlive = (id) => ctx.agents.get(id) !== void 0;
69
+ sweepDeadSessionEntries(turnStarts, isAlive);
70
+ sweepDeadSessionEntries(cumulativeToolCalls, isAlive);
71
+ sweepDeadSessionEntries(completionInjected, isAlive);
72
+ }
67
73
  onTurnEnd(session, event);
68
74
  });
69
75
  async function onTurnEnd(session, event) {
@@ -175,49 +181,55 @@ function apply(ctx, rawConfig) {
175
181
  }
176
182
  }
177
183
  });
178
- const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
179
- const result = await run.result;
180
- await run.dispose();
181
- if (!result.structured) return true;
182
- const snapshot = policy();
183
- const plan = result.structured;
184
- const policyFingerprint = fingerprintPolicy(snapshot);
185
- const validation = validateEvolutionPlan(plan, {
186
- sessionSeq: session.seq - 1,
187
- maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
188
- protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
189
- maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
190
- maxUserChars: snapshot?.userChars ?? DEFAULT_USER_CHAR_LIMIT,
191
- maxSkillContentChars: snapshot?.skillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS
192
- });
193
- const acceptedSkillOps = validation.accepted.skillOps ?? [];
194
- const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
195
- validation.accepted.skillOps = acceptedSkillOps;
196
- const actions = await executePlan(validation.accepted);
197
- const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
198
- ctx.emit("evolution/plan-applied", {
199
- sessionId: session.id,
200
- planId: randomUUID(),
201
- policyFingerprint,
202
- memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
203
- skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
204
- rejectedOps: validation.rejected.length + skippedUnread,
205
- evidenceQuotes,
206
- estimatedInputChars: reviewText.length
207
- });
208
- if (actions.length > 0) agent.inject(createUserMessage({
209
- content: [{
210
- type: "text",
211
- text: `💾 Self-improvement review: ${actions.join(" · ")}`
212
- }],
213
- source: {
214
- kind: "plugin",
215
- plugin: "dsh-evolution-review",
216
- form: "notice",
217
- summary: "self-improvement review"
184
+ try {
185
+ const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
186
+ const result = await run.result;
187
+ if (!result.structured) return true;
188
+ const snapshot = policy();
189
+ const plan = result.structured;
190
+ const policyFingerprint = fingerprintPolicy(snapshot);
191
+ const validation = validateEvolutionPlan(plan, {
192
+ sessionSeq: session.seq - 1,
193
+ maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
194
+ protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
195
+ maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
196
+ maxUserChars: snapshot?.userChars ?? DEFAULT_USER_CHAR_LIMIT,
197
+ maxSkillContentChars: snapshot?.skillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS
198
+ });
199
+ const acceptedSkillOps = validation.accepted.skillOps ?? [];
200
+ const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
201
+ const actions = await executePlan(validation.accepted);
202
+ const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
203
+ ctx.emit("evolution/plan-applied", {
204
+ sessionId: session.id,
205
+ planId: randomUUID(),
206
+ policyFingerprint,
207
+ memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
208
+ skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
209
+ rejectedOps: validation.rejected.length + skippedUnread,
210
+ evidenceQuotes,
211
+ estimatedInputChars: reviewText.length
212
+ });
213
+ if (actions.length > 0) agent.inject(createUserMessage({
214
+ content: [{
215
+ type: "text",
216
+ text: `💾 Self-improvement review: ${actions.join(" · ")}`
217
+ }],
218
+ source: {
219
+ kind: "plugin",
220
+ plugin: "dsh-evolution-review",
221
+ form: "notice",
222
+ summary: "self-improvement review"
223
+ }
224
+ }));
225
+ return true;
226
+ } finally {
227
+ try {
228
+ await run.dispose();
229
+ } catch (disposeError) {
230
+ ctx.logger.warn(`dsh-evolution-review: subagent dispose failed: ${disposeError instanceof Error ? disposeError.message : String(disposeError)}`);
218
231
  }
219
- }));
220
- return true;
232
+ }
221
233
  } catch (error) {
222
234
  ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
223
235
  return false;
@@ -352,6 +364,23 @@ function collectReadSkillNames(session) {
352
364
  }
353
365
  return names;
354
366
  }
367
+ /** Map/set size that triggers a dead-session counter sweep (bounded, not a hard cap). */
368
+ const COUNTER_SWEEP_THRESHOLD = 128;
369
+ /**
370
+ * Remove every entry whose session is no longer live (rc.42 audit P1-10):
371
+ * `turnStarts` / `cumulativeToolCalls` / `completionInjected` are keyed by
372
+ * SessionId with no platform session-end hook to prune against, so they grew
373
+ * unbounded over a long-lived host. Works for maps and sets; returns the
374
+ * number of removed entries.
375
+ */
376
+ function sweepDeadSessionEntries(entries, isAlive) {
377
+ let removed = 0;
378
+ for (const id of [...entries.keys()]) if (!isAlive(id)) {
379
+ entries.delete(id);
380
+ removed += 1;
381
+ }
382
+ return removed;
383
+ }
355
384
  /**
356
385
  * Drop mutating ops whose target was not read this session, in place.
357
386
  * Create is exempt (no read required to author a new skill). Covers the same
@@ -421,4 +450,4 @@ function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars)
421
450
  ].join("\n");
422
451
  }
423
452
  //#endregion
424
- export { Config, apply, collectReadSkillNames, filterUnreadSkillOps, inject, name, shouldCompletionReview };
453
+ export { COUNTER_SWEEP_THRESHOLD, Config, apply, collectReadSkillNames, filterUnreadSkillOps, inject, name, shouldCompletionReview, sweepDeadSessionEntries };
@@ -38,6 +38,16 @@ export declare function shouldCompletionReview(reason: {
38
38
  } | undefined, sessionToolCalls: number, minToolCalls: number): boolean;
39
39
  /** Skill names this session loaded (read-before-write source for the background review). */
40
40
  export declare function collectReadSkillNames(session: Session): Set<string>;
41
+ /** Map/set size that triggers a dead-session counter sweep (bounded, not a hard cap). */
42
+ export declare const COUNTER_SWEEP_THRESHOLD = 128;
43
+ /**
44
+ * Remove every entry whose session is no longer live (rc.42 audit P1-10):
45
+ * `turnStarts` / `cumulativeToolCalls` / `completionInjected` are keyed by
46
+ * SessionId with no platform session-end hook to prune against, so they grew
47
+ * unbounded over a long-lived host. Works for maps and sets; returns the
48
+ * number of removed entries.
49
+ */
50
+ export declare function sweepDeadSessionEntries<K>(entries: Map<K, unknown> | Set<K>, isAlive: (id: K) => boolean): number;
41
51
  /**
42
52
  * Drop mutating ops whose target was not read this session, in place.
43
53
  * Create is exempt (no read required to author a new skill). Covers the same
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.1.0-rc.42",
4
+ "version": "0.1.0-rc.43",
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.1.0-rc.42",
37
- "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.42"
36
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.43",
37
+ "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.43"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/cordis": "^4.0.1",
@@ -43,7 +43,7 @@
43
43
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
44
44
  "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
45
45
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
46
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.42"
46
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.43"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
@@ -54,8 +54,8 @@
54
54
  "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6",
55
55
  "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.0-rc.6",
56
56
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
57
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.42",
58
- "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.42",
59
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.42"
57
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.43",
58
+ "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.43",
59
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.43"
60
60
  }
61
61
  }