@lmzhen/dsh-evolution-state-json 0.3.15 → 0.3.17
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 +44 -42
- 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
|
|
@@ -17,31 +18,7 @@ async function transactIo(io, path, task) {
|
|
|
17
18
|
if (next === null) await io.remove(path);
|
|
18
19
|
else await io.writeText(path, next);
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
//#region ../evolution-core/src/prompts.ts
|
|
22
|
-
/**
|
|
23
|
-
* Review and curation prompts adapted from Hermes Agent
|
|
24
|
-
* `agent/background_review.py`, `agent/curator.py`, and
|
|
25
|
-
* `agent/learn_prompt.py`, with tool names translated to the DSH-native
|
|
26
|
-
* catalog (`memory`, `skill_manage`, `skill`, `bash`, `str_replace_editor`).
|
|
27
|
-
*
|
|
28
|
-
* Alignment policy (2026-08-29): the OPERATIONAL steps and instructions the
|
|
29
|
-
* model follows mirror the Hermes originals structurally (signal list,
|
|
30
|
-
* preference order, support-file taxonomy, curator package integrity,
|
|
31
|
-
* consolidated/pruned reporting block). Tool and platform differences are
|
|
32
|
-
* DSH-adapted (native tool names, pinned-within-review semantics, this
|
|
33
|
-
* platform's index cap), and DSH-only additions are marked as such.
|
|
34
|
-
*
|
|
35
|
-
* Every prompt is pinned in a versioned bundle. Review workers verify the
|
|
36
|
-
* bundle digest before spending a model call, so a partially-patched
|
|
37
|
-
* deployment fails closed instead of silently running a truncated prompt.
|
|
38
|
-
*/
|
|
39
|
-
/**
|
|
40
|
-
* Prompt bundle identity. Bump both id and version whenever a prompt's text
|
|
41
|
-
* changes semantically: the bundle digest is the fail-closed signal for
|
|
42
|
-
* review workers, so a stale id across deployments must be distinguishable.
|
|
43
|
-
*/
|
|
44
|
-
const PROMPT_BUNDLE_ID = "dsh-evolution@13";
|
|
21
|
+
const PROMPT_BUNDLE_ID = `dsh-evolution@13`;
|
|
45
22
|
const MEMORY_REVIEW_PROMPT = `[Auto-review — Memory]
|
|
46
23
|
Review the conversation above and consider saving to memory if appropriate.
|
|
47
24
|
|
|
@@ -325,6 +302,24 @@ createPromptBundle({
|
|
|
325
302
|
const FILLER = String.raw`(?:\w+\s+){0,8}`;
|
|
326
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");
|
|
327
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
|
|
328
323
|
//#region ../../../node_modules/.pnpm/js-yaml@4.2.0/node_modules/js-yaml/dist/js-yaml.mjs
|
|
329
324
|
/*! js-yaml 4.2.0 https://github.com/nodeca/js-yaml @license MIT */
|
|
330
325
|
var __create = Object.create;
|
|
@@ -2672,13 +2667,24 @@ function apply(ctx, rawConfig) {
|
|
|
2672
2667
|
const root = rawConfig.root || defaultRoot();
|
|
2673
2668
|
const io = () => ctx.evolutionIo.provider();
|
|
2674
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
|
+
}
|
|
2675
2681
|
async function readJson(file) {
|
|
2676
2682
|
const raw = await io().readText(pathOf(file));
|
|
2677
2683
|
if (raw === null) return null;
|
|
2678
2684
|
try {
|
|
2679
2685
|
return JSON.parse(raw);
|
|
2680
|
-
} catch {
|
|
2681
|
-
return
|
|
2686
|
+
} catch (error) {
|
|
2687
|
+
return await quarantine(file, raw, error instanceof Error ? error.message : String(error));
|
|
2682
2688
|
}
|
|
2683
2689
|
}
|
|
2684
2690
|
/**
|
|
@@ -2693,19 +2699,14 @@ function apply(ctx, rawConfig) {
|
|
|
2693
2699
|
let parsed = null;
|
|
2694
2700
|
if (current !== null) try {
|
|
2695
2701
|
parsed = JSON.parse(current);
|
|
2696
|
-
} catch {
|
|
2697
|
-
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
return await quarantine(file, current, error instanceof Error ? error.message : String(error));
|
|
2698
2704
|
}
|
|
2699
2705
|
const next = await task(parsed);
|
|
2700
2706
|
return next === null ? null : JSON.stringify(next, null, 2);
|
|
2701
2707
|
});
|
|
2702
2708
|
}
|
|
2703
|
-
|
|
2704
|
-
function mutate(task) {
|
|
2705
|
-
const run = chain.then(task, task);
|
|
2706
|
-
chain = run.then(() => void 0, () => void 0);
|
|
2707
|
-
return run;
|
|
2708
|
-
}
|
|
2709
|
+
const mutate = makeSerialQueue();
|
|
2709
2710
|
async function loadPendingMap() {
|
|
2710
2711
|
const [current, legacy] = await Promise.all([readJson("pending-state.json"), readJson("pending.json")]);
|
|
2711
2712
|
return {
|
|
@@ -2767,12 +2768,13 @@ function apply(ctx, rawConfig) {
|
|
|
2767
2768
|
...current ?? {}
|
|
2768
2769
|
};
|
|
2769
2770
|
const record = map[id] ?? null;
|
|
2770
|
-
if (record === null || record.status
|
|
2771
|
+
if (record === null || !canClaimPending(record.status)) return map;
|
|
2771
2772
|
const now = Date.now();
|
|
2772
2773
|
const claimedAt = typeof record.claimedAt === "string" ? Date.parse(record.claimedAt) : 0;
|
|
2773
|
-
if (record.claimedBy !== void 0 && Number.isFinite(claimedAt) && now - claimedAt <
|
|
2774
|
+
if (record.claimedBy !== void 0 && Number.isFinite(claimedAt) && now - claimedAt < CLAIM_EXPIRY_MS) return map;
|
|
2774
2775
|
slot.claimed = {
|
|
2775
2776
|
...record,
|
|
2777
|
+
status: "executing",
|
|
2776
2778
|
claimedBy: claimId,
|
|
2777
2779
|
claimedAt: new Date(now).toISOString()
|
|
2778
2780
|
};
|
|
@@ -2790,10 +2792,10 @@ function apply(ctx, rawConfig) {
|
|
|
2790
2792
|
...current ?? {}
|
|
2791
2793
|
};
|
|
2792
2794
|
const record = map[id];
|
|
2793
|
-
if (record
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2795
|
+
if (!record || record.claimedBy !== claimId) return map;
|
|
2796
|
+
record.status = releasedStatus(record.status);
|
|
2797
|
+
delete record.claimedBy;
|
|
2798
|
+
delete record.claimedAt;
|
|
2797
2799
|
return map;
|
|
2798
2800
|
});
|
|
2799
2801
|
});
|
|
@@ -2810,7 +2812,7 @@ function apply(ctx, rawConfig) {
|
|
|
2810
2812
|
...current ?? {}
|
|
2811
2813
|
};
|
|
2812
2814
|
const record = map[id] ?? null;
|
|
2813
|
-
if (record === null || record.status
|
|
2815
|
+
if (record === null || !canResolvePending(record.status)) {
|
|
2814
2816
|
result = {
|
|
2815
2817
|
record,
|
|
2816
2818
|
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.
|
|
4
|
+
"version": "0.3.17",
|
|
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.
|
|
41
|
-
"@lmzhen/dsh-evolution-state-storage": "^0.3.
|
|
40
|
+
"@lmzhen/dsh-evolution-io": "^0.3.17",
|
|
41
|
+
"@lmzhen/dsh-evolution-state-storage": "^0.3.17"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
45
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
46
|
-
"@lmzhen/dsh-evolution-state-storage": "^0.3.
|
|
45
|
+
"@lmzhen/dsh-evolution-io": "^0.3.17",
|
|
46
|
+
"@lmzhen/dsh-evolution-state-storage": "^0.3.17"
|
|
47
47
|
}
|
|
48
48
|
}
|