@dsh-cc/subagent-resume-pins 0.5.0

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/store.js ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Per-child pin store: one JSON file per child under `<pinsRoot>/<childId>.json`.
3
+ *
4
+ * Durability and coherence contract (plan §4.1/§4.6):
5
+ * - `write` creates a pin and fails if one already exists (preallocated ids).
6
+ * - `update` rewrites the file atomically (temp file + rename) AND publishes
7
+ * the mutated pin to the store's single synchronous in-memory cache, so no
8
+ * stale-cache read can serve the first request after a gate mutation.
9
+ * - `read` resolves `undefined` for an absent pin (legacy/foreign child,
10
+ * pass-through) and a `{kind:'corrupt'}` sentinel for a file that exists
11
+ * but is unparseable or unsupported-version — fail-closed callers
12
+ * distinguish those; it never throws.
13
+ * - `remove` tombstone-deletes the file and the cache entry.
14
+ * - childIds are UUID-ish; anything containing a path separator or `..` is
15
+ * rejected before it can traverse out of `pinsRoot`.
16
+ *
17
+ * @module @dsh-cc/subagent-resume-pins/store
18
+ */
19
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { PinParseError, parsePin, writePin } from "./pin.js";
22
+ /** The childId shape the store accepts: no separators, no `..`. */
23
+ function assertSafeChildId(childId) {
24
+ if (childId.length === 0 || childId.includes('/') || childId.includes('\\') || childId.includes('..')) {
25
+ throw new Error(`unsafe resume-pin childId: ${JSON.stringify(childId)}`);
26
+ }
27
+ }
28
+ /**
29
+ * Owns the pin files under one root and the single shared in-memory cache the
30
+ * gate and overlay listeners consult.
31
+ */
32
+ export class PinStore {
33
+ pinsRoot;
34
+ cache = new Map();
35
+ constructor(pinsRoot) {
36
+ this.pinsRoot = pinsRoot;
37
+ mkdirSync(this.pinsRoot, { recursive: true });
38
+ }
39
+ /** The on-disk path for one child's pin (validates the childId first). */
40
+ pathFor(childId) {
41
+ assertSafeChildId(childId);
42
+ return join(this.pinsRoot, `${childId}.json`);
43
+ }
44
+ /**
45
+ * Read a pin by child id: `undefined` when absent, the corrupt sentinel when
46
+ * the file exists but cannot be parsed. Read-through: the disk is re-read on
47
+ * every call (files are tiny, one per model request), so a file deleted or
48
+ * corrupted out-of-band is never served stale from the cache; the cache only
49
+ * accelerates nothing — it exists to publish synchronous in-process updates
50
+ * and is invalidated by any failed disk read.
51
+ */
52
+ read(childId) {
53
+ const path = this.pathFor(childId);
54
+ if (!existsSync(path)) {
55
+ this.cache.delete(childId);
56
+ return undefined;
57
+ }
58
+ let text;
59
+ try {
60
+ text = readFileSync(path, 'utf8');
61
+ }
62
+ catch (cause) {
63
+ this.cache.delete(childId);
64
+ return { kind: 'corrupt', reason: `unreadable: ${String(cause)}` };
65
+ }
66
+ try {
67
+ const pin = parsePin(text);
68
+ this.cache.set(childId, pin);
69
+ return pin;
70
+ }
71
+ catch (cause) {
72
+ this.cache.delete(childId);
73
+ const reason = cause instanceof PinParseError ? cause.message : String(cause);
74
+ return { kind: 'corrupt', reason };
75
+ }
76
+ }
77
+ /** Read only the in-memory cache: `undefined` on miss (no disk access). */
78
+ getCached(childId) {
79
+ return this.cache.get(childId);
80
+ }
81
+ /** Create the pin file; fails when a pin for this child already exists. */
82
+ write(pin) {
83
+ const path = this.pathFor(pin.childId);
84
+ if (existsSync(path)) {
85
+ throw new Error(`resume pin already exists for child ${pin.childId}`);
86
+ }
87
+ this.atomicWrite(path, pin);
88
+ this.cache.set(pin.childId, pin);
89
+ }
90
+ /**
91
+ * Atomically rewrite a pin through `mutator` (a structural clone is handed
92
+ * to the mutator) and publish the result to the shared cache synchronously.
93
+ */
94
+ update(childId, mutator) {
95
+ const current = this.read(childId);
96
+ if (current === undefined)
97
+ throw new Error(`no resume pin for child ${childId}`);
98
+ if ('kind' in current && current.kind === 'corrupt') {
99
+ throw new Error(`cannot update corrupt resume pin for child ${childId}`);
100
+ }
101
+ const draft = structuredClone(current);
102
+ mutator(draft);
103
+ const updated = parsePin(writePin(draft));
104
+ this.atomicWrite(this.pathFor(childId), updated);
105
+ this.cache.set(childId, updated);
106
+ return updated;
107
+ }
108
+ /** Tombstone-delete the pin file and its cache entry (idempotent). */
109
+ remove(childId) {
110
+ rmSync(this.pathFor(childId), { force: true });
111
+ this.cache.delete(childId);
112
+ }
113
+ /**
114
+ * The child ids with a pin file on disk (best-effort; used by the
115
+ * `list_agents` annotation). Not guaranteed cached or readable.
116
+ */
117
+ ids() {
118
+ try {
119
+ return readdirSync(this.pinsRoot)
120
+ .filter(name => name.endsWith('.json'))
121
+ .map(name => name.slice(0, -'.json'.length));
122
+ }
123
+ catch {
124
+ return [];
125
+ }
126
+ }
127
+ atomicWrite(path, pin) {
128
+ const temp = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
129
+ writeFileSync(temp, writePin(pin), { flag: 'wx' });
130
+ renameSync(temp, path);
131
+ }
132
+ }
133
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC7G,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAuC,MAAM,UAAU,CAAA;AAKjG,mEAAmE;AACnE,SAAS,iBAAiB,CAAC,OAAe;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtG,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC1E,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,QAAQ;IAGU;IAFZ,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAA;IAErD,YAA6B,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;QAC3C,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,CAAC;IAED,0EAA0E;IAC1E,OAAO,CAAC,OAAe;QACrB,iBAAiB,CAAC,OAAO,CAAC,CAAA;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,OAAO,CAAC,CAAA;IAC/C,CAAC;IAED;;;;;;;OAOG;IACH,IAAI,CAAC,OAAe;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC1B,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,IAAI,IAAY,CAAA;QAChB,IAAI,CAAC;YACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC1B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAA;QACpE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;YAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;YAC5B,OAAO,GAAG,CAAA;QACZ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC1B,MAAM,MAAM,GAAG,KAAK,YAAY,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC7E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAA;QACpC,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,SAAS,CAAC,OAAe;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAED,2EAA2E;IAC3E,KAAK,CAAC,GAAc;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACtC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QACvE,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAClC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,OAAe,EAAE,OAAwC;QAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAA;QAChF,IAAI,MAAM,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAA;QAC1E,CAAC;QACD,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAmB,CAAA;QACxD,OAAO,CAAC,KAAK,CAAC,CAAA;QACd,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAA6B,CAAC,CAAC,CAAA;QACjE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAA;QAChD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAChC,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,OAAe;QACpB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC;IAED;;;OAGG;IACH,GAAG;QACD,IAAI,CAAC;YACH,OAAO,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;iBAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;iBACtC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,IAAY,EAAE,GAAc;QAC9C,MAAM,IAAI,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9F,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAClD,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACxB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@dsh-cc/subagent-resume-pins",
3
+ "description": "Pinned resume descriptors for continuable background subagents",
4
+ "version": "0.5.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/dsh-cc/dsh-cc.git",
8
+ "directory": "packages/subagent/resume-pins"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./pin": {
17
+ "types": "./lib/pin.d.ts",
18
+ "default": "./lib/pin.js"
19
+ },
20
+ "./store": {
21
+ "types": "./lib/store.d.ts",
22
+ "default": "./lib/store.js"
23
+ },
24
+ "./fingerprint": {
25
+ "types": "./lib/fingerprint.d.ts",
26
+ "default": "./lib/fingerprint.js"
27
+ },
28
+ "./gate": {
29
+ "types": "./lib/gate.d.ts",
30
+ "default": "./lib/gate.js"
31
+ },
32
+ "./overlay": {
33
+ "types": "./lib/overlay.d.ts",
34
+ "default": "./lib/overlay.js"
35
+ },
36
+ "./policy": {
37
+ "types": "./lib/policy.d.ts",
38
+ "default": "./lib/policy.js"
39
+ },
40
+ "./plugin": {
41
+ "types": "./lib/plugin.d.ts",
42
+ "default": "./lib/plugin.js"
43
+ },
44
+ "./src/*": "./src/*",
45
+ "./package.json": "./package.json"
46
+ },
47
+ "files": [
48
+ "lib"
49
+ ],
50
+ "license": "Apache-2.0",
51
+ "peerDependencies": {
52
+ "@deepseek-ai/cordis": ">=0.1.1-rc.2",
53
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
54
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2",
55
+ "@deepseek-ai/schemastery": "^3.18.1",
56
+ "@dsh-cc/claude-code-agents": "^0.5.0",
57
+ "@dsh-cc/model-aliases": "^0.5.0"
58
+ },
59
+ "dependencies": {
60
+ "@dsh-cc/claude-code-agents": "^0.5.0",
61
+ "@dsh-cc/model-aliases": "^0.5.0"
62
+ },
63
+ "devDependencies": {
64
+ "@deepseek-ai/cordis": "link:../../../../deepseek-harness/vendor/cordis",
65
+ "@deepseek-ai/dsh-agent": "link:../../../../deepseek-harness/packages/core/agent",
66
+ "@deepseek-ai/dsh-llm": "link:../../../../deepseek-harness/packages/llm/llm",
67
+ "@deepseek-ai/dsh-session": "link:../../../../deepseek-harness/packages/core/session",
68
+ "@deepseek-ai/dsh-session-persistence": "link:../../../../deepseek-harness/packages/session/session-persistence",
69
+ "@deepseek-ai/dsh-settings": "link:../../../../deepseek-harness/packages/settings/settings",
70
+ "@deepseek-ai/dsh-tools": "link:../../../../deepseek-harness/packages/core/tools",
71
+ "@deepseek-ai/schemastery": "link:../../../../deepseek-harness/vendor/schemastery",
72
+ "@dsh-cc/claude-code-agents": "^0.5.0",
73
+ "@dsh-cc/model-aliases": "^0.5.0"
74
+ },
75
+ "publishConfig": {
76
+ "access": "public"
77
+ }
78
+ }