@lmzhen/dsh-evolution-review 0.1.0-rc.41 → 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) {
@@ -91,7 +97,8 @@ function apply(ctx, rawConfig) {
91
97
  const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
92
98
  cumulativeToolCalls.set(session.id, cumulative);
93
99
  if (kind) {
94
- session.append("evolution/review-scheduled", {
100
+ ctx.emit("evolution/review-scheduled", {
101
+ sessionId: session.id,
95
102
  kind,
96
103
  toolCalls: signal.toolCalls,
97
104
  userChars: signal.userChars,
@@ -116,7 +123,8 @@ function apply(ctx, rawConfig) {
116
123
  if (completionInjected.has(session.id)) return;
117
124
  if (!shouldCompletionReview(event.data.reason, cumulative, config.skillReviewCompletionMinToolCalls)) return;
118
125
  completionInjected.add(session.id);
119
- session.append("evolution/review-scheduled", {
126
+ ctx.emit("evolution/review-scheduled", {
127
+ sessionId: session.id,
120
128
  kind: "skill",
121
129
  toolCalls: signal.toolCalls,
122
130
  userChars: signal.userChars,
@@ -173,48 +181,55 @@ function apply(ctx, rawConfig) {
173
181
  }
174
182
  }
175
183
  });
176
- const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
177
- const result = await run.result;
178
- await run.dispose();
179
- if (!result.structured) return true;
180
- const snapshot = policy();
181
- const plan = result.structured;
182
- const policyFingerprint = fingerprintPolicy(snapshot);
183
- const validation = validateEvolutionPlan(plan, {
184
- sessionSeq: session.seq - 1,
185
- maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
186
- protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
187
- maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
188
- maxUserChars: snapshot?.userChars ?? DEFAULT_USER_CHAR_LIMIT,
189
- maxSkillContentChars: snapshot?.skillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS
190
- });
191
- const acceptedSkillOps = validation.accepted.skillOps ?? [];
192
- const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
193
- validation.accepted.skillOps = acceptedSkillOps;
194
- const actions = await executePlan(validation.accepted);
195
- const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
196
- session.append("evolution/plan-applied", {
197
- planId: randomUUID(),
198
- policyFingerprint,
199
- memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
200
- skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
201
- rejectedOps: validation.rejected.length + skippedUnread,
202
- evidenceQuotes,
203
- estimatedInputChars: reviewText.length
204
- });
205
- if (actions.length > 0) agent.inject(createUserMessage({
206
- content: [{
207
- type: "text",
208
- text: `💾 Self-improvement review: ${actions.join(" · ")}`
209
- }],
210
- source: {
211
- kind: "plugin",
212
- plugin: "dsh-evolution-review",
213
- form: "notice",
214
- 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)}`);
215
231
  }
216
- }));
217
- return true;
232
+ }
218
233
  } catch (error) {
219
234
  ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
220
235
  return false;
@@ -349,6 +364,23 @@ function collectReadSkillNames(session) {
349
364
  }
350
365
  return names;
351
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
+ }
352
384
  /**
353
385
  * Drop mutating ops whose target was not read this session, in place.
354
386
  * Create is exempt (no read required to author a new skill). Covers the same
@@ -418,4 +450,4 @@ function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars)
418
450
  ].join("\n");
419
451
  }
420
452
  //#endregion
421
- 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.41",
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.41",
37
- "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.41"
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.41"
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",
@@ -51,9 +51,11 @@
51
51
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
52
52
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
53
53
  "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
54
+ "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.0-rc.6",
54
56
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
55
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.41",
56
- "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.41",
57
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.41"
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"
58
60
  }
59
61
  }