@lmzhen/dsh-evolution-state-json 0.3.16 → 0.3.18

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.
Files changed (2) hide show
  1. package/lib/index.js +58 -17
  2. package/package.json +5 -5
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@ import z from "@deepseek-ai/schemastery";
2
2
  import { join } from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
  import { homedir } from "node:os";
5
+ import { CLAIM_EXPIRY_MS, canClaimPending, canResolvePending, releasedStatus } from "@lmzhen/dsh-evolution-state-storage";
5
6
  //#region ../evolution-core/src/io.ts
6
7
  /**
7
8
  * Run `task` inside `io.transact` when the backend provides it; otherwise fall
@@ -301,6 +302,24 @@ createPromptBundle({
301
302
  const FILLER = String.raw`(?:\w+\s+){0,8}`;
302
303
  new RegExp(String.raw`ignore\s+${FILLER}(?:previous|above|prior|all)\s+${FILLER}instructions`, "i"), new RegExp(String.raw`new\s+${FILLER}system\s+${FILLER}prompt`, "i"), new RegExp(String.raw`forget\s+${FILLER}(?:everything|all)\s+${FILLER}(?:discussed|you\s+know)`, "i"), new RegExp(String.raw`you\s+have\s+been\s+${FILLER}(?:updated|upgraded|patched)\s+to`, "i"), new RegExp(String.raw`do\s+not\s+${FILLER}tell\s+${FILLER}the\s+user`, "i"), new RegExp(String.raw`output\s+${FILLER}(?:system|initial)\s+prompt`, "i");
303
304
  //#endregion
305
+ //#region ../evolution-core/src/serial.ts
306
+ /**
307
+ * A process-local serial task queue: each task starts only after the previous
308
+ * one settles (success or failure), so read-modify-write sequences that share
309
+ * one file never interleave inside this process. The durable cross-process
310
+ * serialization layer is the IO backend's transact lock; this chain is the
311
+ * second layer (0.3.17 S2.8, T-1: the shape was duplicated in state-json and
312
+ * memory-files — one factory now).
313
+ */
314
+ function makeSerialQueue() {
315
+ let chain = Promise.resolve();
316
+ return (task) => {
317
+ const run = chain.then(task, task);
318
+ chain = run.then(() => void 0, () => void 0);
319
+ return run;
320
+ };
321
+ }
322
+ //#endregion
304
323
  //#region ../../../node_modules/.pnpm/js-yaml@4.2.0/node_modules/js-yaml/dist/js-yaml.mjs
305
324
  /*! js-yaml 4.2.0 https://github.com/nodeca/js-yaml @license MIT */
306
325
  var __create = Object.create;
@@ -2648,13 +2667,24 @@ function apply(ctx, rawConfig) {
2648
2667
  const root = rawConfig.root || defaultRoot();
2649
2668
  const io = () => ctx.evolutionIo.provider();
2650
2669
  const pathOf = (file) => join(root, file);
2670
+ /** 0.3.17 (E-9): a malformed state file used to parse to `null` and was then
2671
+ * OVERWRITTEN by the next save — every other session's review state / the
2672
+ * whole pending table vanished silently. Fail loud instead: preserve the
2673
+ * original bytes beside it and throw, so the operator can rescue and the
2674
+ * corruption is never accepted as "empty". */
2675
+ async function quarantine(file, raw, reason) {
2676
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2677
+ const dest = `${pathOf(file)}.corrupt-${stamp}-${Math.random().toString(36).slice(2, 6)}`;
2678
+ await io().writeText(dest, raw).catch(() => {});
2679
+ throw new Error(`evolution state file "${file}" is not valid JSON (${reason}); original preserved at ${dest} — inspect and fix it, then retry.`);
2680
+ }
2651
2681
  async function readJson(file) {
2652
2682
  const raw = await io().readText(pathOf(file));
2653
2683
  if (raw === null) return null;
2654
2684
  try {
2655
2685
  return JSON.parse(raw);
2656
- } catch {
2657
- return null;
2686
+ } catch (error) {
2687
+ return await quarantine(file, raw, error instanceof Error ? error.message : String(error));
2658
2688
  }
2659
2689
  }
2660
2690
  /**
@@ -2669,19 +2699,14 @@ function apply(ctx, rawConfig) {
2669
2699
  let parsed = null;
2670
2700
  if (current !== null) try {
2671
2701
  parsed = JSON.parse(current);
2672
- } catch {
2673
- parsed = null;
2702
+ } catch (error) {
2703
+ return await quarantine(file, current, error instanceof Error ? error.message : String(error));
2674
2704
  }
2675
2705
  const next = await task(parsed);
2676
2706
  return next === null ? null : JSON.stringify(next, null, 2);
2677
2707
  });
2678
2708
  }
2679
- let chain = Promise.resolve();
2680
- function mutate(task) {
2681
- const run = chain.then(task, task);
2682
- chain = run.then(() => void 0, () => void 0);
2683
- return run;
2684
- }
2709
+ const mutate = makeSerialQueue();
2685
2710
  async function loadPendingMap() {
2686
2711
  const [current, legacy] = await Promise.all([readJson("pending-state.json"), readJson("pending.json")]);
2687
2712
  return {
@@ -2717,6 +2742,21 @@ function apply(ctx, rawConfig) {
2717
2742
  }));
2718
2743
  });
2719
2744
  },
2745
+ async transactCuratorState(task) {
2746
+ await mutate(async () => {
2747
+ await jsonTransact("curator-state.json", (current) => {
2748
+ const next = task(current?.primary ?? null);
2749
+ if (next === null) {
2750
+ if (current !== null) delete current.primary;
2751
+ return current ?? {};
2752
+ }
2753
+ return {
2754
+ ...current ?? {},
2755
+ primary: next
2756
+ };
2757
+ });
2758
+ });
2759
+ },
2720
2760
  async listPending(status = "pending") {
2721
2761
  return await mutate(async () => {
2722
2762
  const map = await loadPendingMap();
@@ -2743,12 +2783,13 @@ function apply(ctx, rawConfig) {
2743
2783
  ...current ?? {}
2744
2784
  };
2745
2785
  const record = map[id] ?? null;
2746
- if (record === null || record.status !== "pending") return map;
2786
+ if (record === null || !canClaimPending(record.status)) return map;
2747
2787
  const now = Date.now();
2748
2788
  const claimedAt = typeof record.claimedAt === "string" ? Date.parse(record.claimedAt) : 0;
2749
- if (record.claimedBy !== void 0 && Number.isFinite(claimedAt) && now - claimedAt < 10 * 6e4) return map;
2789
+ if (record.claimedBy !== void 0 && Number.isFinite(claimedAt) && now - claimedAt < CLAIM_EXPIRY_MS) return map;
2750
2790
  slot.claimed = {
2751
2791
  ...record,
2792
+ status: "executing",
2752
2793
  claimedBy: claimId,
2753
2794
  claimedAt: new Date(now).toISOString()
2754
2795
  };
@@ -2766,10 +2807,10 @@ function apply(ctx, rawConfig) {
2766
2807
  ...current ?? {}
2767
2808
  };
2768
2809
  const record = map[id];
2769
- if (record && record.status === "pending" && record.claimedBy === claimId) {
2770
- delete record.claimedBy;
2771
- delete record.claimedAt;
2772
- }
2810
+ if (!record || record.claimedBy !== claimId) return map;
2811
+ record.status = releasedStatus(record.status);
2812
+ delete record.claimedBy;
2813
+ delete record.claimedAt;
2773
2814
  return map;
2774
2815
  });
2775
2816
  });
@@ -2786,7 +2827,7 @@ function apply(ctx, rawConfig) {
2786
2827
  ...current ?? {}
2787
2828
  };
2788
2829
  const record = map[id] ?? null;
2789
- if (record === null || record.status !== "pending") {
2830
+ if (record === null || !canResolvePending(record.status)) {
2790
2831
  result = {
2791
2832
  record,
2792
2833
  applied: false
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-state-json",
3
3
  "description": "JSON-file evolution state provider over the IO seam (community build)",
4
- "version": "0.3.16",
4
+ "version": "0.3.18",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -37,12 +37,12 @@
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
39
39
  "@deepseek-ai/cordis": "^4.0.1",
40
- "@lmzhen/dsh-evolution-io": "^0.3.16",
41
- "@lmzhen/dsh-evolution-state-storage": "^0.3.16"
40
+ "@lmzhen/dsh-evolution-io": "^0.3.18",
41
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.18"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
45
- "@lmzhen/dsh-evolution-io": "^0.3.16",
46
- "@lmzhen/dsh-evolution-state-storage": "^0.3.16"
45
+ "@lmzhen/dsh-evolution-io": "^0.3.18",
46
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.18"
47
47
  }
48
48
  }