@remnic/plugin-openclaw 1.0.9 → 1.0.11

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 (69) hide show
  1. package/dist/{calibration-674TDQNV.js → calibration-WCHOK6DX.js} +12 -4
  2. package/dist/capsule-cli-TFKLAG3S.js +329 -0
  3. package/dist/capsule-crypto-K3IRTKRH.js +17 -0
  4. package/dist/capsule-export-CVA3CKUQ.js +265 -0
  5. package/dist/capsule-import-CFX7BY5W.js +16 -0
  6. package/dist/capsule-merge-7RVOHJK3.js +189 -0
  7. package/dist/{causal-chain-OKDZSDEB.js → causal-chain-WYN5QOPS.js} +3 -2
  8. package/dist/{causal-consolidation-5BEXLQV5.js → causal-consolidation-JD6KJJH6.js} +16 -12
  9. package/dist/{causal-retrieval-3BKBXVXD.js → causal-retrieval-NZHQOZOE.js} +6 -5
  10. package/dist/{causal-trajectory-graph-RQIT37DN.js → causal-trajectory-graph-VBPE2WPM.js} +1 -1
  11. package/dist/chunk-37NKFWSO.js +233 -0
  12. package/dist/chunk-3G7FAF6S.js +60 -0
  13. package/dist/{chunk-Z7GRLVK3.js → chunk-3GUF7RQI.js} +235 -19
  14. package/dist/chunk-3I7RHWYT.js +214 -0
  15. package/dist/chunk-4G2XCSD2.js +186 -0
  16. package/dist/chunk-6IWEAUN6.js +148 -0
  17. package/dist/{chunk-LN5UZQVG.js → chunk-6UFI73TJ.js} +5 -3
  18. package/dist/chunk-7OQEPGQF.js +529 -0
  19. package/dist/chunk-B52XADV3.js +244 -0
  20. package/dist/chunk-BU5KJVWF.js +78 -0
  21. package/dist/chunk-CXM7EBAO.js +289 -0
  22. package/dist/chunk-ETJZRIAM.js +227 -0
  23. package/dist/chunk-FQRSVYY4.js +110 -0
  24. package/dist/chunk-HRGFO6AW.js +349 -0
  25. package/dist/chunk-I6B2W2IY.js +47 -0
  26. package/dist/chunk-JZBOXOUC.js +259 -0
  27. package/dist/chunk-K7EUBNDD.js +185 -0
  28. package/dist/chunk-L4PRBB2A.js +1860 -0
  29. package/dist/chunk-MBIFE6SA.js +250 -0
  30. package/dist/chunk-N7EOZY6F.js +400 -0
  31. package/dist/chunk-NKVIN6RD.js +118 -0
  32. package/dist/chunk-OEI7GLV2.js +17 -0
  33. package/dist/{chunk-S2ISS4AH.js → chunk-P3DIW2SD.js} +10 -10
  34. package/dist/{chunk-7TENHBV2.js → chunk-RQCTMECT.js} +10 -48
  35. package/dist/chunk-SSFTU6LP.js +182 -0
  36. package/dist/{chunk-BXTMZDRT.js → chunk-SVSQAG6M.js} +7 -5
  37. package/dist/chunk-TLVIQLB4.js +874 -0
  38. package/dist/{chunk-JJSNPSCD.js → chunk-TNH24SF6.js} +352 -50
  39. package/dist/chunk-TVKKIS53.js +720 -0
  40. package/dist/{chunk-YHH3SXKD.js → chunk-WPINX4MF.js} +1 -59
  41. package/dist/{chunk-HCFFXBLV.js → chunk-XMSDA5WA.js} +5 -1861
  42. package/dist/chunk-YGGGUTG3.js +125 -0
  43. package/dist/chunk-YGXXBRV7.js +10 -0
  44. package/dist/cipher-VHAFCG7Z.js +27 -0
  45. package/dist/dreams-ledger-3I52ISYR.js +285 -0
  46. package/dist/{engine-65C2J63X.js → engine-VMTFKFGO.js} +5 -2
  47. package/dist/{fallback-llm-LVK5PDIM.js → fallback-llm-WCWNGIQ3.js} +2 -1
  48. package/dist/first-start-migration-I24M2JEE.js +258 -0
  49. package/dist/forget-NI4RBDPB.js +68 -0
  50. package/dist/fs-utils-PZRI2HDZ.js +29 -0
  51. package/dist/graph-edge-decay-5CVKWBYH.js +203 -0
  52. package/dist/index.js +9791 -2902
  53. package/dist/kdf-H5B23ZM2.js +25 -0
  54. package/dist/memory-governance-DWGFV4FX.js +25 -0
  55. package/dist/metadata-JAGIWHEA.js +20 -0
  56. package/dist/migrate-from-identity-anchor-N3354WMP.js +7 -0
  57. package/dist/path-5LCUBAAZ.js +8 -0
  58. package/dist/peers-JF2I6RCR.js +43 -0
  59. package/dist/purge-XN2VSPZ2.js +204 -0
  60. package/dist/secure-store-FWJ7LBPH.js +149 -0
  61. package/dist/state-PVISYXRH.js +7 -0
  62. package/dist/state-store-LP5BO6SF.js +15 -0
  63. package/dist/{storage-DM4ZGOCN.js → storage-T2OGFUF4.js} +3 -1
  64. package/dist/tier-stats-IZNW66NC.js +147 -0
  65. package/dist/trace-NJESSGH7.js +289 -0
  66. package/dist/tui-MGK2LYJY.js +12 -0
  67. package/dist/types-H5R5D3WF.js +30 -0
  68. package/openclaw.plugin.json +519 -4
  69. package/package.json +1 -1
@@ -0,0 +1,189 @@
1
+ import {
2
+ parseExportBundle
3
+ } from "./chunk-K7EUBNDD.js";
4
+ import {
5
+ assertIsDirectoryNotSymlink,
6
+ assertRealpathInsideRoot,
7
+ fromPosixRelPath,
8
+ isPathInsideRoot,
9
+ sha256String
10
+ } from "./chunk-NKVIN6RD.js";
11
+ import "./chunk-XMSDA5WA.js";
12
+ import {
13
+ createVersion
14
+ } from "./chunk-6OJAU466.js";
15
+ import "./chunk-MLKGABMK.js";
16
+
17
+ // ../remnic-core/src/transfer/capsule-merge.ts
18
+ import { lstat, mkdir, readFile, realpath, stat, writeFile } from "fs/promises";
19
+ import path from "path";
20
+ import { gunzipSync } from "zlib";
21
+ async function mergeCapsule(opts) {
22
+ const archiveAbs = path.resolve(opts.sourceArchive);
23
+ const rootAbs = path.resolve(opts.targetRoot);
24
+ await assertIsDirectoryNotSymlink(rootAbs, "mergeCapsule", "targetRoot");
25
+ const conflictMode = opts.conflictMode ?? "skip-conflicts";
26
+ if (conflictMode !== "skip-conflicts" && conflictMode !== "prefer-source" && conflictMode !== "prefer-local") {
27
+ throw new Error(
28
+ `mergeCapsule: unknown conflictMode ${JSON.stringify(conflictMode)}; expected "skip-conflicts", "prefer-source", or "prefer-local"`
29
+ );
30
+ }
31
+ const raw = await readFile(archiveAbs);
32
+ let json;
33
+ try {
34
+ json = gunzipSync(raw).toString("utf-8");
35
+ } catch (cause) {
36
+ throw new Error(
37
+ `mergeCapsule: archive is not a valid gzip file: ${archiveAbs}`,
38
+ { cause }
39
+ );
40
+ }
41
+ let parsedJson;
42
+ try {
43
+ parsedJson = JSON.parse(json);
44
+ } catch (cause) {
45
+ throw new Error(
46
+ `mergeCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,
47
+ { cause }
48
+ );
49
+ }
50
+ const parsed = parseExportBundle(parsedJson);
51
+ if (parsed.capsuleVersion !== 2) {
52
+ throw new Error(
53
+ "mergeCapsule: archive is V1; only V2 capsule archives are supported"
54
+ );
55
+ }
56
+ const bundle = parsed.bundle;
57
+ const manifest = bundle.manifest;
58
+ const capsule = manifest.capsule;
59
+ const manifestIndex = /* @__PURE__ */ new Map();
60
+ for (const f of manifest.files) {
61
+ manifestIndex.set(f.path, f);
62
+ }
63
+ if (manifestIndex.size !== manifest.files.length) {
64
+ throw new Error("mergeCapsule: manifest contains duplicate file paths");
65
+ }
66
+ const recordPaths = /* @__PURE__ */ new Set();
67
+ for (const rec of bundle.records) {
68
+ if (recordPaths.has(rec.path)) {
69
+ throw new Error(
70
+ `mergeCapsule: bundle contains duplicate record path: ${rec.path}`
71
+ );
72
+ }
73
+ recordPaths.add(rec.path);
74
+ }
75
+ const rootReal = await realpath(rootAbs).catch(() => rootAbs);
76
+ const seenTargetPaths = /* @__PURE__ */ new Map();
77
+ for (const rec of bundle.records) {
78
+ const entry = manifestIndex.get(rec.path);
79
+ if (!entry) {
80
+ throw new Error(
81
+ `mergeCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`
82
+ );
83
+ }
84
+ const { sha256, bytes } = sha256String(rec.content);
85
+ if (sha256 !== entry.sha256 || bytes !== entry.bytes) {
86
+ throw new Error(
87
+ `mergeCapsule: archive checksum mismatch for ${rec.path}: expected sha256=${entry.sha256} bytes=${entry.bytes}, got sha256=${sha256} bytes=${bytes}`
88
+ );
89
+ }
90
+ if (rec.path.includes("\\")) {
91
+ throw new Error(
92
+ `mergeCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`
93
+ );
94
+ }
95
+ const posixNormalized = path.posix.normalize(rec.path);
96
+ if (rec.path.startsWith("/") || rec.path.split("/").some((seg) => seg === "..") || posixNormalized.startsWith("..") || posixNormalized.startsWith("/")) {
97
+ throw new Error(
98
+ `mergeCapsule: record path escapes target root: ${rec.path}`
99
+ );
100
+ }
101
+ const targetAbs = path.join(rootReal, fromPosixRelPath(rec.path));
102
+ if (!isPathInsideRoot(rootReal, targetAbs)) {
103
+ throw new Error(
104
+ `mergeCapsule: record path escapes target root: ${rec.path}`
105
+ );
106
+ }
107
+ await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, "mergeCapsule");
108
+ const targetLstat = await lstat(targetAbs).catch(() => null);
109
+ if (targetLstat !== null && targetLstat.isSymbolicLink()) {
110
+ throw new Error(
111
+ `mergeCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`
112
+ );
113
+ }
114
+ const dedupKey = targetAbs.toLowerCase();
115
+ const firstSourcePath = seenTargetPaths.get(dedupKey);
116
+ if (firstSourcePath !== void 0) {
117
+ throw new Error(
118
+ `mergeCapsule: manifest contains two entries that resolve to the same target path: "${firstSourcePath}" and "${rec.path}" both map to "${rec.path}"`
119
+ );
120
+ }
121
+ seenTargetPaths.set(dedupKey, rec.path);
122
+ }
123
+ for (const f of manifest.files) {
124
+ if (!recordPaths.has(f.path)) {
125
+ throw new Error(
126
+ `mergeCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`
127
+ );
128
+ }
129
+ }
130
+ const merged = [];
131
+ const skipped = [];
132
+ const conflicts = [];
133
+ const sortedRecords = [...bundle.records].sort(
134
+ (a, b) => a.path.localeCompare(b.path)
135
+ );
136
+ for (const rec of sortedRecords) {
137
+ const targetAbs = path.join(rootReal, fromPosixRelPath(rec.path));
138
+ const entry = manifestIndex.get(rec.path);
139
+ const localContent = await readLocalFile(targetAbs);
140
+ if (localContent === null) {
141
+ await mkdir(path.dirname(targetAbs), { recursive: true });
142
+ await writeFile(targetAbs, rec.content, "utf-8");
143
+ merged.push({ sourcePath: rec.path, targetPath: rec.path, snapshotted: false });
144
+ continue;
145
+ }
146
+ const { sha256: localSha256 } = sha256String(localContent);
147
+ if (localSha256 === entry.sha256) {
148
+ skipped.push({ path: rec.path, reason: "identical" });
149
+ continue;
150
+ }
151
+ const { sha256: archiveSha256 } = sha256String(rec.content);
152
+ conflicts.push({
153
+ path: rec.path,
154
+ archiveSha256,
155
+ localSha256
156
+ });
157
+ if (conflictMode === "skip-conflicts" || conflictMode === "prefer-local") {
158
+ skipped.push({ path: rec.path, reason: "conflict" });
159
+ continue;
160
+ }
161
+ let snapshotted = false;
162
+ if (opts.versioning && opts.versioning.enabled) {
163
+ await createVersion(
164
+ targetAbs,
165
+ localContent,
166
+ "manual",
167
+ opts.versioning,
168
+ opts.log,
169
+ `capsule-merge: ${capsule.id}`,
170
+ rootReal
171
+ );
172
+ snapshotted = true;
173
+ }
174
+ await writeFile(targetAbs, rec.content, "utf-8");
175
+ merged.push({ sourcePath: rec.path, targetPath: rec.path, snapshotted });
176
+ }
177
+ merged.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
178
+ skipped.sort((a, b) => a.path.localeCompare(b.path));
179
+ conflicts.sort((a, b) => a.path.localeCompare(b.path));
180
+ return { merged, skipped, conflicts, manifest };
181
+ }
182
+ async function readLocalFile(absPath) {
183
+ const st = await stat(absPath).catch(() => null);
184
+ if (!st || !st.isFile()) return null;
185
+ return readFile(absPath, "utf-8");
186
+ }
187
+ export {
188
+ mergeCapsule
189
+ };
@@ -6,8 +6,9 @@ import {
6
6
  stitchCausalChain,
7
7
  validateCausalEdge,
8
8
  writeChainIndex
9
- } from "./chunk-BXTMZDRT.js";
10
- import "./chunk-YHH3SXKD.js";
9
+ } from "./chunk-SVSQAG6M.js";
10
+ import "./chunk-WPINX4MF.js";
11
+ import "./chunk-3G7FAF6S.js";
11
12
  import "./chunk-5LE4HTVL.js";
12
13
  import "./chunk-UFU5GGGA.js";
13
14
  import "./chunk-MLKGABMK.js";
@@ -1,27 +1,31 @@
1
- import {
2
- buildExtensionsBlockForConsolidation,
3
- runPostConsolidationMaterialize
4
- } from "./chunk-S2ISS4AH.js";
5
- import {
6
- FallbackLlmClient
7
- } from "./chunk-7TENHBV2.js";
8
- import "./chunk-3A5ELHTT.js";
9
- import "./chunk-JJSNPSCD.js";
10
- import "./chunk-6OJAU466.js";
11
1
  import {
12
2
  readChainIndex,
13
3
  resolveChainsDir
14
- } from "./chunk-BXTMZDRT.js";
4
+ } from "./chunk-SVSQAG6M.js";
5
+ import {
6
+ buildExtensionsBlockForConsolidation,
7
+ runPostConsolidationMaterialize
8
+ } from "./chunk-P3DIW2SD.js";
9
+ import "./chunk-WPINX4MF.js";
15
10
  import {
16
11
  isRecord
17
- } from "./chunk-YHH3SXKD.js";
12
+ } from "./chunk-3G7FAF6S.js";
18
13
  import {
19
14
  listJsonFiles,
20
15
  readJsonFile
21
16
  } from "./chunk-5LE4HTVL.js";
17
+ import {
18
+ FallbackLlmClient
19
+ } from "./chunk-RQCTMECT.js";
20
+ import "./chunk-3A5ELHTT.js";
21
+ import "./chunk-TNH24SF6.js";
22
+ import "./chunk-6OJAU466.js";
23
+ import "./chunk-3I7RHWYT.js";
22
24
  import {
23
25
  log
24
26
  } from "./chunk-UFU5GGGA.js";
27
+ import "./chunk-YGGGUTG3.js";
28
+ import "./chunk-I6B2W2IY.js";
25
29
  import "./chunk-MLKGABMK.js";
26
30
 
27
31
  // ../remnic-core/src/causal-consolidation.ts
@@ -1,11 +1,12 @@
1
- import {
2
- searchCausalTrajectories
3
- } from "./chunk-LN5UZQVG.js";
4
1
  import {
5
2
  readChainIndex,
6
3
  resolveChainsDir
7
- } from "./chunk-BXTMZDRT.js";
8
- import "./chunk-YHH3SXKD.js";
4
+ } from "./chunk-SVSQAG6M.js";
5
+ import {
6
+ searchCausalTrajectories
7
+ } from "./chunk-6UFI73TJ.js";
8
+ import "./chunk-WPINX4MF.js";
9
+ import "./chunk-3G7FAF6S.js";
9
10
  import "./chunk-5LE4HTVL.js";
10
11
  import {
11
12
  log
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  appendEdge
3
- } from "./chunk-Z7GRLVK3.js";
3
+ } from "./chunk-3GUF7RQI.js";
4
4
  import "./chunk-MLKGABMK.js";
5
5
 
6
6
  // ../remnic-core/src/causal-trajectory-graph.ts
@@ -0,0 +1,233 @@
1
+ import {
2
+ expandTildePath
3
+ } from "./chunk-OEI7GLV2.js";
4
+
5
+ // ../remnic-core/src/connectors/live/state-store.ts
6
+ import { promises as fs } from "fs";
7
+ import path from "path";
8
+
9
+ // ../remnic-core/src/connectors/live/framework.ts
10
+ var CONNECTOR_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
11
+ function isValidConnectorId(id) {
12
+ return typeof id === "string" && CONNECTOR_ID_PATTERN.test(id);
13
+ }
14
+
15
+ // ../remnic-core/src/connectors/live/state-store.ts
16
+ var STATE_DIR_NAME = "state";
17
+ var CONNECTORS_DIR_NAME = "connectors";
18
+ var MAX_ERROR_LENGTH = 1024;
19
+ var VALID_SYNC_STATUSES = /* @__PURE__ */ new Set([
20
+ "success",
21
+ "error",
22
+ "never"
23
+ ]);
24
+ var ConnectorStateCorruptionError = class extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "ConnectorStateCorruptionError";
28
+ }
29
+ };
30
+ function resolveConnectorsDir(memoryDir) {
31
+ if (typeof memoryDir !== "string" || memoryDir.length === 0) {
32
+ throw new TypeError("memoryDir must be a non-empty string");
33
+ }
34
+ return path.join(expandTildePath(memoryDir), STATE_DIR_NAME, CONNECTORS_DIR_NAME);
35
+ }
36
+ function resolveConnectorStatePath(memoryDir, id) {
37
+ if (!isValidConnectorId(id)) {
38
+ throw new TypeError(
39
+ `invalid connector id ${JSON.stringify(id)} \u2014 must match /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/`
40
+ );
41
+ }
42
+ return path.join(resolveConnectorsDir(memoryDir), `${id}.json`);
43
+ }
44
+ function isConnectorStateShape(value) {
45
+ if (typeof value !== "object" || value === null) return false;
46
+ const v = value;
47
+ if (typeof v.id !== "string") return false;
48
+ if (typeof v.lastSyncStatus !== "string") return false;
49
+ if (!["success", "error", "never"].includes(v.lastSyncStatus)) return false;
50
+ if (typeof v.totalDocsImported !== "number" || !Number.isInteger(v.totalDocsImported)) return false;
51
+ if (v.totalDocsImported < 0) return false;
52
+ if (typeof v.updatedAt !== "string") return false;
53
+ if (v.lastSyncAt !== null && typeof v.lastSyncAt !== "string") return false;
54
+ if (v.cursor !== null) {
55
+ if (typeof v.cursor !== "object" || v.cursor === null) return false;
56
+ const c = v.cursor;
57
+ if (typeof c.kind !== "string" || typeof c.value !== "string" || typeof c.updatedAt !== "string") {
58
+ return false;
59
+ }
60
+ }
61
+ if (v.lastSyncError !== void 0 && typeof v.lastSyncError !== "string") return false;
62
+ return true;
63
+ }
64
+ async function assertNoSymlinkOnPath(memoryDir, filePath) {
65
+ const expandedRoot = expandTildePath(memoryDir);
66
+ const root = path.resolve(expandedRoot);
67
+ const target = path.resolve(filePath);
68
+ const rel = path.relative(root, target);
69
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
70
+ throw new Error(
71
+ `connector state path ${target} escapes memory root ${root}`
72
+ );
73
+ }
74
+ const segments = rel.length === 0 ? [] : rel.split(path.sep);
75
+ let current = root;
76
+ const componentsToCheck = [current];
77
+ for (const seg of segments) {
78
+ current = path.join(current, seg);
79
+ componentsToCheck.push(current);
80
+ }
81
+ for (const component of componentsToCheck) {
82
+ let stat;
83
+ try {
84
+ stat = await fs.lstat(component);
85
+ } catch (err) {
86
+ if (err.code === "ENOENT") {
87
+ continue;
88
+ }
89
+ throw err;
90
+ }
91
+ if (stat.isSymbolicLink()) {
92
+ throw new Error(
93
+ `connector state path component ${component} is a symlink; refusing to follow`
94
+ );
95
+ }
96
+ }
97
+ }
98
+ async function readConnectorState(memoryDir, id) {
99
+ const filePath = resolveConnectorStatePath(memoryDir, id);
100
+ await assertNoSymlinkOnPath(memoryDir, filePath);
101
+ let raw;
102
+ try {
103
+ raw = await fs.readFile(filePath, "utf-8");
104
+ } catch (err) {
105
+ if (err.code === "ENOENT") return null;
106
+ throw err;
107
+ }
108
+ let parsed;
109
+ try {
110
+ parsed = JSON.parse(raw);
111
+ } catch (err) {
112
+ throw new ConnectorStateCorruptionError(
113
+ `connector state at ${filePath} is not valid JSON: ${err.message}`
114
+ );
115
+ }
116
+ if (!isConnectorStateShape(parsed)) {
117
+ throw new ConnectorStateCorruptionError(
118
+ `connector state at ${filePath} does not match ConnectorState shape`
119
+ );
120
+ }
121
+ if (parsed.id !== id) {
122
+ throw new ConnectorStateCorruptionError(
123
+ `connector state at ${filePath} has mismatched id ${JSON.stringify(parsed.id)}; expected ${JSON.stringify(id)}`
124
+ );
125
+ }
126
+ return parsed;
127
+ }
128
+ async function writeConnectorState(memoryDir, id, state) {
129
+ if (!isValidConnectorId(id)) {
130
+ throw new TypeError(
131
+ `invalid connector id ${JSON.stringify(id)} \u2014 must match /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/`
132
+ );
133
+ }
134
+ if (state.id !== id) {
135
+ throw new Error(
136
+ `writeConnectorState(): state.id ${JSON.stringify(state.id)} does not match id argument ${JSON.stringify(id)}`
137
+ );
138
+ }
139
+ if (!VALID_SYNC_STATUSES.has(state.lastSyncStatus)) {
140
+ throw new Error(
141
+ `writeConnectorState(): lastSyncStatus must be one of ${[...VALID_SYNC_STATUSES].join(", ")}, got ${JSON.stringify(state.lastSyncStatus)}`
142
+ );
143
+ }
144
+ if (state.lastSyncAt !== null && typeof state.lastSyncAt !== "string") {
145
+ throw new Error(
146
+ `writeConnectorState(): lastSyncAt must be a string or null, got ${typeof state.lastSyncAt}`
147
+ );
148
+ }
149
+ if (state.cursor !== null) {
150
+ if (typeof state.cursor !== "object") {
151
+ throw new Error(`writeConnectorState(): cursor must be an object or null`);
152
+ }
153
+ if (typeof state.cursor.kind !== "string" || typeof state.cursor.value !== "string" || typeof state.cursor.updatedAt !== "string") {
154
+ throw new Error(
155
+ `writeConnectorState(): cursor must have string kind, value, and updatedAt`
156
+ );
157
+ }
158
+ }
159
+ if (typeof state.totalDocsImported !== "number" || !Number.isInteger(state.totalDocsImported) || state.totalDocsImported < 0) {
160
+ throw new Error(
161
+ `writeConnectorState(): totalDocsImported must be a non-negative integer`
162
+ );
163
+ }
164
+ if (state.lastSyncError !== void 0 && typeof state.lastSyncError !== "string") {
165
+ throw new Error(`writeConnectorState(): lastSyncError must be a string when provided`);
166
+ }
167
+ const truncatedError = state.lastSyncError !== void 0 && state.lastSyncError.length > MAX_ERROR_LENGTH ? state.lastSyncError.slice(0, MAX_ERROR_LENGTH) : state.lastSyncError;
168
+ const finalState = {
169
+ id: state.id,
170
+ cursor: state.cursor,
171
+ lastSyncAt: state.lastSyncAt,
172
+ lastSyncStatus: state.lastSyncStatus,
173
+ ...truncatedError !== void 0 ? { lastSyncError: truncatedError } : {},
174
+ totalDocsImported: state.totalDocsImported,
175
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
176
+ };
177
+ const dir = resolveConnectorsDir(memoryDir);
178
+ const targetPath = path.join(dir, `${id}.json`);
179
+ await assertNoSymlinkOnPath(memoryDir, targetPath);
180
+ await fs.mkdir(dir, { recursive: true });
181
+ const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
182
+ const body = `${JSON.stringify(finalState, null, 2)}
183
+ `;
184
+ try {
185
+ await fs.writeFile(tmpPath, body, { encoding: "utf-8", mode: 384 });
186
+ await fs.rename(tmpPath, targetPath);
187
+ } catch (err) {
188
+ try {
189
+ await fs.unlink(tmpPath);
190
+ } catch {
191
+ }
192
+ throw err;
193
+ }
194
+ return finalState;
195
+ }
196
+ async function listConnectorStates(memoryDir) {
197
+ const dir = resolveConnectorsDir(memoryDir);
198
+ await assertNoSymlinkOnPath(memoryDir, dir);
199
+ let entries;
200
+ try {
201
+ entries = await fs.readdir(dir);
202
+ } catch (err) {
203
+ if (err.code === "ENOENT") return [];
204
+ throw err;
205
+ }
206
+ const out = [];
207
+ for (const entry of entries) {
208
+ if (!entry.endsWith(".json")) continue;
209
+ const id = entry.slice(0, -".json".length);
210
+ if (!isValidConnectorId(id)) continue;
211
+ try {
212
+ const state = await readConnectorState(memoryDir, id);
213
+ if (state !== null) out.push(state);
214
+ } catch (err) {
215
+ if (err instanceof ConnectorStateCorruptionError) {
216
+ continue;
217
+ }
218
+ throw err;
219
+ }
220
+ }
221
+ out.sort((a, b) => a.id.localeCompare(b.id));
222
+ return out;
223
+ }
224
+ function _connectorStatePathForTest(memoryDir, id) {
225
+ return resolveConnectorStatePath(memoryDir, id);
226
+ }
227
+
228
+ export {
229
+ readConnectorState,
230
+ writeConnectorState,
231
+ listConnectorStates,
232
+ _connectorStatePathForTest
233
+ };
@@ -0,0 +1,60 @@
1
+ // ../remnic-core/src/store-contract.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function assertString(value, field) {
6
+ if (typeof value !== "string" || value.trim().length === 0) {
7
+ throw new Error(`${field} must be a non-empty string`);
8
+ }
9
+ return value.trim();
10
+ }
11
+ function optionalString(value) {
12
+ if (typeof value !== "string" || value.trim().length === 0) return void 0;
13
+ return value.trim();
14
+ }
15
+ function assertSafePathSegment(value, field) {
16
+ if (value === "." || value === ".." || value.includes("/") || value.includes("\\")) {
17
+ throw new Error(`${field} must be a safe path segment`);
18
+ }
19
+ return value;
20
+ }
21
+ function assertIsoRecordedAt(value, field = "recordedAt") {
22
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(value)) {
23
+ throw new Error(`${field} must be an ISO timestamp`);
24
+ }
25
+ return value;
26
+ }
27
+ function recordStoreDay(recordedAt) {
28
+ const day = recordedAt.slice(0, 10);
29
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) {
30
+ throw new Error("recordedAt must start with a valid YYYY-MM-DD date");
31
+ }
32
+ return day;
33
+ }
34
+ function optionalStringArray(value, field) {
35
+ if (value === void 0) return void 0;
36
+ if (!Array.isArray(value)) throw new Error(`${field} must be an array of strings`);
37
+ const items = value.map((item, index) => assertString(item, `${field}[${index}]`));
38
+ return items.length > 0 ? items : void 0;
39
+ }
40
+ function validateStringRecord(raw, field = "metadata") {
41
+ if (raw === void 0) return void 0;
42
+ if (!isRecord(raw)) throw new Error(`${field} must be an object of strings`);
43
+ const out = {};
44
+ for (const [key, value] of Object.entries(raw)) {
45
+ if (typeof value !== "string") throw new Error(`${field} must be an object of strings`);
46
+ out[key] = value;
47
+ }
48
+ return Object.keys(out).length > 0 ? out : void 0;
49
+ }
50
+
51
+ export {
52
+ isRecord,
53
+ assertString,
54
+ optionalString,
55
+ assertSafePathSegment,
56
+ assertIsoRecordedAt,
57
+ recordStoreDay,
58
+ optionalStringArray,
59
+ validateStringRecord
60
+ };