@lmzhen/dsh-evolution-replay 0.3.66 → 0.3.68

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
@@ -1,5 +1,6 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import { clampedNumber } from "@lmzhen/dsh-evolution-core";
2
+ import { clampedNumber, evolutionHome, evolutionIoAdapter } from "@lmzhen/dsh-evolution-core";
3
+ import { loadActivity } from "@lmzhen/dsh-evolution-activity";
3
4
  //#region lib/types/index.js
4
5
  /**
5
6
  * Replay/A-B evaluation for evolution plans.
@@ -8,9 +9,16 @@ import { clampedNumber } from "@lmzhen/dsh-evolution-core";
8
9
  * driver records every `evolution/plan-applied` process event (payload v2,
9
10
  * with sessionId) into an in-memory leaderboard and exposes `/evolution
10
11
  * replay` (via the `/evolution` command family) for comparison, so a human can
11
- * A/B review policy/prompt changes against real plan outcomes. Durability
12
- * across restarts is the evolution-activity store's job; this leaderboard is
13
- * deliberately in-memory.
12
+ * A/B review policy/prompt changes against real plan outcomes.
13
+ *
14
+ * V24-13 (v24): the in-memory leaderboard is BACKFILLED at mount from the
15
+ * `evolution-activity` sidecar (`$DSH_HOME/evolution/activity.json`) — the
16
+ * header previously claimed "durability across restarts is the
17
+ * evolution-activity store's job" while nothing ever read the sidecar, so a
18
+ * restart emptied `/evolution replay` and the persisted history sat unread.
19
+ * Records are fed in sidecar order (oldest first); the driver's `maxPlans`
20
+ * window keeps the newest. Best-effort: a missing/unreadable sidecar starts
21
+ * the leaderboard empty, exactly as before.
14
22
  * @module @lmzhen/dsh-evolution-replay
15
23
  */
16
24
  const DEFAULT_WEIGHTS = {
@@ -80,9 +88,26 @@ function comparePlans(plans, weights = DEFAULT_WEIGHTS) {
80
88
  report: scored.map(({ plan, score }) => `${plan.policyId}: ${score.toFixed(1)} (${plan.acceptedOps} accepted, ${plan.rejectedOps} rejected${(plan.executionFailures ?? 0) > 0 ? `, ${plan.executionFailures} failed${plan.executionError !== void 0 ? `: ${plan.executionError}` : ""}` : ""})`).join("\n")
81
89
  };
82
90
  }
83
- var EvolutionReplayDriver = class {
91
+ var EvolutionReplayDriver = class EvolutionReplayDriver {
84
92
  plans = [];
85
93
  maxPlans;
94
+ /** V25-01 (v25): backfill-once latch — see {@link EvolutionReplayDriver.backfill}. */
95
+ backfilled = false;
96
+ /** V26-05 (v25): plan ids recorded live BEFORE the backfill settled. A
97
+ * plan-applied landing inside the one-shot `loadActivity` read window is
98
+ * recorded live AND persisted into the sidecar the backfill is reading —
99
+ * `backfill()` drops those ids so the event is not counted twice.
100
+ *
101
+ * V27 INS-05: the set is BOUNDED. `backfill()` is the only place that cleared
102
+ * it, so a sidecar load that never succeeded (the inject callback's catch
103
+ * warns and does not retry until the io dependency is replaced) let every
104
+ * live plan id accumulate for the process lifetime. Past
105
+ * PRE_BACKFILL_ID_CAP the oldest ids are dropped: the dedupe window is then
106
+ * finite, and a dropped duplicate can at worst repeat an entry inside the
107
+ * `maxPlans` window instead of growing memory without bound. */
108
+ preBackfillIds = /* @__PURE__ */ new Set();
109
+ /** Upper bound on {@link EvolutionReplayDriver.preBackfillIds} (V27 INS-05). */
110
+ static PRE_BACKFILL_ID_CAP = 4096;
86
111
  weights;
87
112
  constructor(config = {}, warn = () => {}) {
88
113
  const maxPlans = clampedNumber(config.maxPlans ?? 50, 50, { min: 1 });
@@ -96,6 +121,14 @@ var EvolutionReplayDriver = class {
96
121
  }
97
122
  }
98
123
  record(plan) {
124
+ if (!this.backfilled) {
125
+ this.preBackfillIds.add(plan.planId);
126
+ while (this.preBackfillIds.size > EvolutionReplayDriver.PRE_BACKFILL_ID_CAP) {
127
+ const oldest = this.preBackfillIds.keys().next().value;
128
+ if (oldest === void 0) break;
129
+ this.preBackfillIds.delete(oldest);
130
+ }
131
+ }
99
132
  const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
100
133
  const countOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
101
134
  const memoryApplied = count(plan.memoryApplied);
@@ -114,6 +147,22 @@ var EvolutionReplayDriver = class {
114
147
  if (this.plans.length > this.maxPlans) this.plans.shift();
115
148
  }
116
149
  /**
150
+ * V25-01 (v25): backfill the leaderboard from the activity sidecar, ONCE
151
+ * per driver instance. The inject callback that loads the sidecar re-runs
152
+ * whenever the `evolutionIo` dependency is replaced (cordis derived-fiber
153
+ * reload — plugin restart / HMR) while THIS driver survives at the apply
154
+ * scope, so the dedupe guard must live here, not in the callback: without
155
+ * it, every io reload doubled the leaderboard entries.
156
+ * @param items - activity records in sidecar order (oldest first).
157
+ */
158
+ backfill(items) {
159
+ if (this.backfilled) return;
160
+ this.backfilled = true;
161
+ const fresh = items.filter((item) => !this.preBackfillIds.has(item.planId));
162
+ this.preBackfillIds.clear();
163
+ for (const item of fresh) this.record(item);
164
+ }
165
+ /**
117
166
  * F-11: test-support API — no production consumer reads the raw
118
167
  * plan list (the leaderboard path goes through `compare()`); tests use this
119
168
  * as a read/inspection window. Kept by declaration (same posture as the
@@ -137,6 +186,15 @@ function apply(ctx, rawConfig = {}) {
137
186
  ctx.on("evolution/plan-applied", (event) => {
138
187
  driver.record(event);
139
188
  });
189
+ ctx.inject(["evolutionIo"], (ioCtx) => {
190
+ const ioRegistry = ioCtx.evolutionIo;
191
+ const io = evolutionIoAdapter(() => ioRegistry.provider());
192
+ loadActivity(evolutionHome(), io).then((items) => {
193
+ driver.backfill(items);
194
+ }).catch((error) => {
195
+ ioCtx.logger.warn(`evolution-replay: activity sidecar backfill skipped (${error instanceof Error ? error.message : String(error)})`);
196
+ });
197
+ });
140
198
  }
141
199
  //#endregion
142
200
  export { Config, DEFAULT_WEIGHTS, EvolutionReplayDriver, apply, clampReplayWeights, comparePlans, name };
@@ -5,9 +5,16 @@
5
5
  * driver records every `evolution/plan-applied` process event (payload v2,
6
6
  * with sessionId) into an in-memory leaderboard and exposes `/evolution
7
7
  * replay` (via the `/evolution` command family) for comparison, so a human can
8
- * A/B review policy/prompt changes against real plan outcomes. Durability
9
- * across restarts is the evolution-activity store's job; this leaderboard is
10
- * deliberately in-memory.
8
+ * A/B review policy/prompt changes against real plan outcomes.
9
+ *
10
+ * V24-13 (v24): the in-memory leaderboard is BACKFILLED at mount from the
11
+ * `evolution-activity` sidecar (`$DSH_HOME/evolution/activity.json`) — the
12
+ * header previously claimed "durability across restarts is the
13
+ * evolution-activity store's job" while nothing ever read the sidecar, so a
14
+ * restart emptied `/evolution replay` and the persisted history sat unread.
15
+ * Records are fed in sidecar order (oldest first); the driver's `maxPlans`
16
+ * window keeps the newest. Best-effort: a missing/unreadable sidecar starts
17
+ * the leaderboard empty, exactly as before.
11
18
  * @module @lmzhen/dsh-evolution-replay
12
19
  */
13
20
  import type { Context } from '@deepseek-ai/cordis';
@@ -61,9 +68,36 @@ declare module '@deepseek-ai/cordis' {
61
68
  export declare class EvolutionReplayDriver {
62
69
  private readonly plans;
63
70
  private readonly maxPlans;
71
+ /** V25-01 (v25): backfill-once latch — see {@link EvolutionReplayDriver.backfill}. */
72
+ private backfilled;
73
+ /** V26-05 (v25): plan ids recorded live BEFORE the backfill settled. A
74
+ * plan-applied landing inside the one-shot `loadActivity` read window is
75
+ * recorded live AND persisted into the sidecar the backfill is reading —
76
+ * `backfill()` drops those ids so the event is not counted twice.
77
+ *
78
+ * V27 INS-05: the set is BOUNDED. `backfill()` is the only place that cleared
79
+ * it, so a sidecar load that never succeeded (the inject callback's catch
80
+ * warns and does not retry until the io dependency is replaced) let every
81
+ * live plan id accumulate for the process lifetime. Past
82
+ * PRE_BACKFILL_ID_CAP the oldest ids are dropped: the dedupe window is then
83
+ * finite, and a dropped duplicate can at worst repeat an entry inside the
84
+ * `maxPlans` window instead of growing memory without bound. */
85
+ private readonly preBackfillIds;
86
+ /** Upper bound on {@link EvolutionReplayDriver.preBackfillIds} (V27 INS-05). */
87
+ private static readonly PRE_BACKFILL_ID_CAP;
64
88
  private readonly weights;
65
89
  constructor(config?: Config, warn?: (message: string) => void);
66
90
  record(plan: EvolutionPlanAppliedEvent): void;
91
+ /**
92
+ * V25-01 (v25): backfill the leaderboard from the activity sidecar, ONCE
93
+ * per driver instance. The inject callback that loads the sidecar re-runs
94
+ * whenever the `evolutionIo` dependency is replaced (cordis derived-fiber
95
+ * reload — plugin restart / HMR) while THIS driver survives at the apply
96
+ * scope, so the dedupe guard must live here, not in the callback: without
97
+ * it, every io reload doubled the leaderboard entries.
98
+ * @param items - activity records in sidecar order (oldest first).
99
+ */
100
+ backfill(items: ReadonlyArray<import('@lmzhen/dsh-evolution-activity').EvolutionActivityRecord>): void;
67
101
  /**
68
102
  * F-11: test-support API — no production consumer reads the raw
69
103
  * plan list (the leaderboard path goes through `compare()`); tests use this
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-replay",
3
3
  "description": "Replay/A-B evaluation primitives for evolution plans (community build)",
4
- "version": "0.3.66",
4
+ "version": "0.3.68",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,7 +31,8 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.66"
34
+ "@lmzhen/dsh-evolution-activity": "^0.3.68",
35
+ "@lmzhen/dsh-evolution-core": "^0.3.68"
35
36
  },
36
37
  "peerDependencies": {
37
38
  "@deepseek-ai/cordis": "^4.0.1",
@@ -39,6 +40,7 @@
39
40
  },
40
41
  "devDependencies": {
41
42
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
42
- "@lmzhen/dsh-evolution-core": "^0.3.66"
43
+ "@lmzhen/dsh-evolution-activity": "^0.3.68",
44
+ "@lmzhen/dsh-evolution-core": "^0.3.68"
43
45
  }
44
46
  }