@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.
- package/dist/{calibration-674TDQNV.js → calibration-WCHOK6DX.js} +12 -4
- package/dist/capsule-cli-TFKLAG3S.js +329 -0
- package/dist/capsule-crypto-K3IRTKRH.js +17 -0
- package/dist/capsule-export-CVA3CKUQ.js +265 -0
- package/dist/capsule-import-CFX7BY5W.js +16 -0
- package/dist/capsule-merge-7RVOHJK3.js +189 -0
- package/dist/{causal-chain-OKDZSDEB.js → causal-chain-WYN5QOPS.js} +3 -2
- package/dist/{causal-consolidation-5BEXLQV5.js → causal-consolidation-JD6KJJH6.js} +16 -12
- package/dist/{causal-retrieval-3BKBXVXD.js → causal-retrieval-NZHQOZOE.js} +6 -5
- package/dist/{causal-trajectory-graph-RQIT37DN.js → causal-trajectory-graph-VBPE2WPM.js} +1 -1
- package/dist/chunk-37NKFWSO.js +233 -0
- package/dist/chunk-3G7FAF6S.js +60 -0
- package/dist/{chunk-Z7GRLVK3.js → chunk-3GUF7RQI.js} +235 -19
- package/dist/chunk-3I7RHWYT.js +214 -0
- package/dist/chunk-4G2XCSD2.js +186 -0
- package/dist/chunk-6IWEAUN6.js +148 -0
- package/dist/{chunk-LN5UZQVG.js → chunk-6UFI73TJ.js} +5 -3
- package/dist/chunk-7OQEPGQF.js +529 -0
- package/dist/chunk-B52XADV3.js +244 -0
- package/dist/chunk-BU5KJVWF.js +78 -0
- package/dist/chunk-CXM7EBAO.js +289 -0
- package/dist/chunk-ETJZRIAM.js +227 -0
- package/dist/chunk-FQRSVYY4.js +110 -0
- package/dist/chunk-HRGFO6AW.js +349 -0
- package/dist/chunk-I6B2W2IY.js +47 -0
- package/dist/chunk-JZBOXOUC.js +259 -0
- package/dist/chunk-K7EUBNDD.js +185 -0
- package/dist/chunk-L4PRBB2A.js +1860 -0
- package/dist/chunk-MBIFE6SA.js +250 -0
- package/dist/chunk-N7EOZY6F.js +400 -0
- package/dist/chunk-NKVIN6RD.js +118 -0
- package/dist/chunk-OEI7GLV2.js +17 -0
- package/dist/{chunk-S2ISS4AH.js → chunk-P3DIW2SD.js} +10 -10
- package/dist/{chunk-7TENHBV2.js → chunk-RQCTMECT.js} +10 -48
- package/dist/chunk-SSFTU6LP.js +182 -0
- package/dist/{chunk-BXTMZDRT.js → chunk-SVSQAG6M.js} +7 -5
- package/dist/chunk-TLVIQLB4.js +874 -0
- package/dist/{chunk-JJSNPSCD.js → chunk-TNH24SF6.js} +352 -50
- package/dist/chunk-TVKKIS53.js +720 -0
- package/dist/{chunk-YHH3SXKD.js → chunk-WPINX4MF.js} +1 -59
- package/dist/{chunk-HCFFXBLV.js → chunk-XMSDA5WA.js} +5 -1861
- package/dist/chunk-YGGGUTG3.js +125 -0
- package/dist/chunk-YGXXBRV7.js +10 -0
- package/dist/cipher-VHAFCG7Z.js +27 -0
- package/dist/dreams-ledger-3I52ISYR.js +285 -0
- package/dist/{engine-65C2J63X.js → engine-VMTFKFGO.js} +5 -2
- package/dist/{fallback-llm-LVK5PDIM.js → fallback-llm-WCWNGIQ3.js} +2 -1
- package/dist/first-start-migration-I24M2JEE.js +258 -0
- package/dist/forget-NI4RBDPB.js +68 -0
- package/dist/fs-utils-PZRI2HDZ.js +29 -0
- package/dist/graph-edge-decay-5CVKWBYH.js +203 -0
- package/dist/index.js +9791 -2902
- package/dist/kdf-H5B23ZM2.js +25 -0
- package/dist/memory-governance-DWGFV4FX.js +25 -0
- package/dist/metadata-JAGIWHEA.js +20 -0
- package/dist/migrate-from-identity-anchor-N3354WMP.js +7 -0
- package/dist/path-5LCUBAAZ.js +8 -0
- package/dist/peers-JF2I6RCR.js +43 -0
- package/dist/purge-XN2VSPZ2.js +204 -0
- package/dist/secure-store-FWJ7LBPH.js +149 -0
- package/dist/state-PVISYXRH.js +7 -0
- package/dist/state-store-LP5BO6SF.js +15 -0
- package/dist/{storage-DM4ZGOCN.js → storage-T2OGFUF4.js} +3 -1
- package/dist/tier-stats-IZNW66NC.js +147 -0
- package/dist/trace-NJESSGH7.js +289 -0
- package/dist/tui-MGK2LYJY.js +12 -0
- package/dist/types-H5R5D3WF.js +30 -0
- package/openclaw.plugin.json +519 -4
- package/package.json +1 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
gatherConsoleState
|
|
3
|
+
} from "./chunk-MBIFE6SA.js";
|
|
4
|
+
|
|
5
|
+
// ../remnic-core/src/console/tui.ts
|
|
6
|
+
var ANSI_CLEAR_HOME = "\x1B[2J\x1B[H";
|
|
7
|
+
var ANSI_HIDE_CURSOR = "\x1B[?25l";
|
|
8
|
+
var ANSI_SHOW_CURSOR = "\x1B[?25h";
|
|
9
|
+
var FRAME_INNER_WIDTH = 70;
|
|
10
|
+
var DEFAULT_REFRESH_INTERVAL_MS = 2e3;
|
|
11
|
+
function runConsoleTui(orchestrator, options = {}) {
|
|
12
|
+
const refreshIntervalMs = Math.max(
|
|
13
|
+
50,
|
|
14
|
+
options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS
|
|
15
|
+
);
|
|
16
|
+
const output = options.output ?? process.stdout;
|
|
17
|
+
const now = options.now ?? (() => Date.now());
|
|
18
|
+
const installSigintHandler = options.installSigintHandler ?? true;
|
|
19
|
+
let stopped = false;
|
|
20
|
+
let inFlight = false;
|
|
21
|
+
let inFlightTickPromise = null;
|
|
22
|
+
let traceWritePending = false;
|
|
23
|
+
let traceFramesDropped = 0;
|
|
24
|
+
let resolveDone;
|
|
25
|
+
const done = new Promise((resolve) => {
|
|
26
|
+
resolveDone = resolve;
|
|
27
|
+
});
|
|
28
|
+
safeWrite(output, ANSI_HIDE_CURSOR);
|
|
29
|
+
const tick = async () => {
|
|
30
|
+
if (stopped || inFlight) return;
|
|
31
|
+
inFlight = true;
|
|
32
|
+
try {
|
|
33
|
+
let snapshot = null;
|
|
34
|
+
let renderError = null;
|
|
35
|
+
try {
|
|
36
|
+
snapshot = await gatherConsoleState(orchestrator);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
renderError = describeError(err);
|
|
39
|
+
}
|
|
40
|
+
if (stopped) return;
|
|
41
|
+
let frame;
|
|
42
|
+
try {
|
|
43
|
+
frame = renderFrame({ snapshot, renderError, now });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
frame = `remnic console: render failed: ${describeError(err)}
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
safeWrite(output, ANSI_CLEAR_HOME);
|
|
49
|
+
safeWrite(output, frame);
|
|
50
|
+
if (snapshot && options.traceRecorder && !traceWritePending) {
|
|
51
|
+
traceWritePending = true;
|
|
52
|
+
void options.traceRecorder.append(snapshot).catch(() => {
|
|
53
|
+
}).finally(() => {
|
|
54
|
+
traceWritePending = false;
|
|
55
|
+
});
|
|
56
|
+
} else if (snapshot && options.traceRecorder) {
|
|
57
|
+
traceFramesDropped += 1;
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
inFlight = false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const launchTick = () => {
|
|
64
|
+
const p = runTickSafely(tick);
|
|
65
|
+
inFlightTickPromise = p;
|
|
66
|
+
void p.then(() => {
|
|
67
|
+
if (inFlightTickPromise === p) {
|
|
68
|
+
inFlightTickPromise = null;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
launchTick();
|
|
73
|
+
const handle = setInterval(launchTick, refreshIntervalMs);
|
|
74
|
+
const sigintHandler = () => {
|
|
75
|
+
stop();
|
|
76
|
+
};
|
|
77
|
+
if (installSigintHandler) {
|
|
78
|
+
process.on("SIGINT", sigintHandler);
|
|
79
|
+
}
|
|
80
|
+
const stop = () => {
|
|
81
|
+
if (stopped) return;
|
|
82
|
+
stopped = true;
|
|
83
|
+
clearInterval(handle);
|
|
84
|
+
if (installSigintHandler) {
|
|
85
|
+
try {
|
|
86
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
safeWrite(output, ANSI_SHOW_CURSOR);
|
|
91
|
+
void (async () => {
|
|
92
|
+
try {
|
|
93
|
+
const pendingTick = inFlightTickPromise;
|
|
94
|
+
if (pendingTick) {
|
|
95
|
+
await pendingTick;
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
resolveDone();
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
stop,
|
|
104
|
+
done,
|
|
105
|
+
getDroppedTraceFrames: () => traceFramesDropped
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function renderFrame(input) {
|
|
109
|
+
const ts = new Date(input.now()).toISOString();
|
|
110
|
+
const lines = [];
|
|
111
|
+
lines.push(renderHeader(ts));
|
|
112
|
+
for (const panel of renderPanels(input)) {
|
|
113
|
+
lines.push(panel);
|
|
114
|
+
}
|
|
115
|
+
lines.push(renderFooter());
|
|
116
|
+
return lines.join("\n") + "\n";
|
|
117
|
+
}
|
|
118
|
+
function renderHeader(ts) {
|
|
119
|
+
const title = " remnic console ";
|
|
120
|
+
const trailing = ` ${ts} `;
|
|
121
|
+
const fillerLen = Math.max(
|
|
122
|
+
1,
|
|
123
|
+
FRAME_INNER_WIDTH - title.length - trailing.length
|
|
124
|
+
);
|
|
125
|
+
const filler = "\u2550".repeat(fillerLen);
|
|
126
|
+
return `\u2554${title}${filler}${trailing}\u2557`;
|
|
127
|
+
}
|
|
128
|
+
function renderFooter() {
|
|
129
|
+
return `\u255A${"\u2550".repeat(FRAME_INNER_WIDTH)}\u255D`;
|
|
130
|
+
}
|
|
131
|
+
function renderPanels(input) {
|
|
132
|
+
const lines = [];
|
|
133
|
+
const snap = input.snapshot;
|
|
134
|
+
if (input.renderError !== null) {
|
|
135
|
+
lines.push(panelLine("Error", `refresh failed: ${input.renderError}`));
|
|
136
|
+
lines.push(panelLine("Buffer", "(unavailable)"));
|
|
137
|
+
lines.push(panelLine("Extraction", "(unavailable)"));
|
|
138
|
+
lines.push(panelLine("Dedup", "(unavailable)"));
|
|
139
|
+
lines.push(panelLine("Maintenance", "(unavailable)"));
|
|
140
|
+
lines.push(panelLine("QMD", "(unavailable)"));
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
143
|
+
if (snap) {
|
|
144
|
+
lines.push(
|
|
145
|
+
panelLine(
|
|
146
|
+
"Buffer",
|
|
147
|
+
`turns=${snap.bufferState.turnsCount} bytes=${snap.bufferState.byteCount}`
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
const verdicts = snap.extractionQueue.recentVerdicts;
|
|
151
|
+
const accepts = verdicts.filter((v) => v.kind === "accept").length;
|
|
152
|
+
const rejects = verdicts.filter((v) => v.kind === "reject").length;
|
|
153
|
+
lines.push(
|
|
154
|
+
panelLine(
|
|
155
|
+
"Extraction",
|
|
156
|
+
`queue=${snap.extractionQueue.depth} recent verdicts: accept(${accepts})/reject(${rejects})`
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
const dedupSummary = formatDedupSummary(snap.dedupRecent, input.now);
|
|
160
|
+
lines.push(panelLine("Dedup", dedupSummary));
|
|
161
|
+
const maintSummary = formatMaintenanceTail(snap.maintenanceLedgerTail);
|
|
162
|
+
lines.push(panelLine("Maintenance", maintSummary));
|
|
163
|
+
lines.push(panelLine("QMD", formatQmdSummary(snap)));
|
|
164
|
+
}
|
|
165
|
+
if (snap && snap.errors.length > 0) {
|
|
166
|
+
const head = snap.errors[0];
|
|
167
|
+
lines.push(panelLine("Errors", head ?? ""));
|
|
168
|
+
}
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
171
|
+
function formatDedupSummary(decisions, now) {
|
|
172
|
+
if (decisions.length === 0) return "no recent decisions";
|
|
173
|
+
const last = decisions[decisions.length - 1];
|
|
174
|
+
if (!last) return "no recent decisions";
|
|
175
|
+
const ageMs = ageMsFromIso(last.ts, now);
|
|
176
|
+
const ageStr = ageMs === null ? "T-?" : `T-${Math.round(ageMs / 1e3)}s`;
|
|
177
|
+
const fp = last.fingerprint ? `hash=${last.fingerprint}` : "hash=?";
|
|
178
|
+
return `recent: ${fp} decision=${last.decision} (${ageStr})`;
|
|
179
|
+
}
|
|
180
|
+
function formatMaintenanceTail(events) {
|
|
181
|
+
if (events.length === 0) return "no events";
|
|
182
|
+
const last = events[events.length - 1];
|
|
183
|
+
if (!last) return "no events";
|
|
184
|
+
return `n=${events.length} last: ${last.category} ${truncate(last.summary, 40)}`;
|
|
185
|
+
}
|
|
186
|
+
function formatQmdSummary(snap) {
|
|
187
|
+
const probe = snap.qmdProbe.available ? "ok" : "down";
|
|
188
|
+
const uptimeH = snap.daemon.uptimeMs / 36e5;
|
|
189
|
+
const uptimeStr = uptimeH < 1 ? `${Math.round(snap.daemon.uptimeMs / 1e3)}s` : `${uptimeH.toFixed(1)}h`;
|
|
190
|
+
const mode = snap.qmdProbe.daemonMode ? "daemon" : "cli";
|
|
191
|
+
return `probe=${probe} mode=${mode} uptime=${uptimeStr}`;
|
|
192
|
+
}
|
|
193
|
+
function panelLine(label, value) {
|
|
194
|
+
const LABEL_WIDTH = 13;
|
|
195
|
+
const labelCol = padRight(label, LABEL_WIDTH);
|
|
196
|
+
const remaining = FRAME_INNER_WIDTH - LABEL_WIDTH - 2;
|
|
197
|
+
const valueCol = padRight(truncate(value, remaining), remaining);
|
|
198
|
+
return `\u2551 ${labelCol}${valueCol} \u2551`;
|
|
199
|
+
}
|
|
200
|
+
function padRight(s, width) {
|
|
201
|
+
if (s.length >= width) return s;
|
|
202
|
+
return s + " ".repeat(width - s.length);
|
|
203
|
+
}
|
|
204
|
+
function truncate(s, max) {
|
|
205
|
+
if (max <= 0) return "";
|
|
206
|
+
if (s.length <= max) return s;
|
|
207
|
+
if (max <= 1) return s.slice(0, max);
|
|
208
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
209
|
+
}
|
|
210
|
+
function ageMsFromIso(iso, now) {
|
|
211
|
+
const ms = Date.parse(iso);
|
|
212
|
+
if (!Number.isFinite(ms)) return null;
|
|
213
|
+
const delta = now() - ms;
|
|
214
|
+
return delta < 0 ? 0 : delta;
|
|
215
|
+
}
|
|
216
|
+
async function runTickSafely(tick) {
|
|
217
|
+
try {
|
|
218
|
+
await tick();
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function safeWrite(output, chunk) {
|
|
223
|
+
try {
|
|
224
|
+
output.write(chunk);
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function describeError(err) {
|
|
229
|
+
if (err instanceof Error) return err.message;
|
|
230
|
+
try {
|
|
231
|
+
return String(err);
|
|
232
|
+
} catch {
|
|
233
|
+
return "unknown error";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function stripAnsi(s) {
|
|
237
|
+
return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export {
|
|
241
|
+
runConsoleTui,
|
|
242
|
+
renderFrame,
|
|
243
|
+
stripAnsi
|
|
244
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ../remnic-core/src/tier-migration.ts
|
|
2
|
+
import { appendFile, mkdir } from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var TierMigrationExecutor = class {
|
|
5
|
+
storage;
|
|
6
|
+
qmd;
|
|
7
|
+
hotCollection;
|
|
8
|
+
coldCollection;
|
|
9
|
+
autoEmbed;
|
|
10
|
+
journalPath;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.storage = options.storage;
|
|
13
|
+
this.qmd = options.qmd;
|
|
14
|
+
this.hotCollection = options.hotCollection;
|
|
15
|
+
this.coldCollection = options.coldCollection;
|
|
16
|
+
this.autoEmbed = options.autoEmbed === true;
|
|
17
|
+
this.journalPath = options.journalPath ?? path.join(this.storage.dir, "state", "tier-migration-journal.jsonl");
|
|
18
|
+
}
|
|
19
|
+
async migrateMemory(request) {
|
|
20
|
+
const { memory, fromTier, toTier, reason } = request;
|
|
21
|
+
const targetPath = this.storage.buildTierMemoryPath(memory, toTier);
|
|
22
|
+
if (fromTier === toTier) {
|
|
23
|
+
const noChange = {
|
|
24
|
+
memoryId: memory.frontmatter.id,
|
|
25
|
+
fromTier,
|
|
26
|
+
toTier,
|
|
27
|
+
changed: false,
|
|
28
|
+
reason,
|
|
29
|
+
targetPath
|
|
30
|
+
};
|
|
31
|
+
await this.appendJournal(noChange);
|
|
32
|
+
return noChange;
|
|
33
|
+
}
|
|
34
|
+
const moved = await this.storage.migrateMemoryToTier(memory, toTier);
|
|
35
|
+
const result = {
|
|
36
|
+
memoryId: memory.frontmatter.id,
|
|
37
|
+
fromTier,
|
|
38
|
+
toTier,
|
|
39
|
+
changed: moved.changed,
|
|
40
|
+
reason,
|
|
41
|
+
targetPath: moved.targetPath
|
|
42
|
+
};
|
|
43
|
+
await this.appendJournal(result);
|
|
44
|
+
if (result.changed) {
|
|
45
|
+
const destinationCollection = this.collectionForTier(toTier);
|
|
46
|
+
const sourceCollection = this.collectionForTier(fromTier);
|
|
47
|
+
await this.qmd.updateCollection(destinationCollection);
|
|
48
|
+
if (this.autoEmbed) {
|
|
49
|
+
await this.qmd.embedCollection(destinationCollection);
|
|
50
|
+
if (sourceCollection !== destinationCollection) {
|
|
51
|
+
await this.qmd.embedCollection(sourceCollection);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
collectionForTier(tier) {
|
|
58
|
+
return tier === "cold" ? this.coldCollection : this.hotCollection;
|
|
59
|
+
}
|
|
60
|
+
async appendJournal(result) {
|
|
61
|
+
const entry = {
|
|
62
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
memoryId: result.memoryId,
|
|
64
|
+
fromTier: result.fromTier,
|
|
65
|
+
toTier: result.toTier,
|
|
66
|
+
changed: result.changed,
|
|
67
|
+
reason: result.reason,
|
|
68
|
+
targetPath: result.targetPath
|
|
69
|
+
};
|
|
70
|
+
await mkdir(path.dirname(this.journalPath), { recursive: true });
|
|
71
|
+
await appendFile(this.journalPath, `${JSON.stringify(entry)}
|
|
72
|
+
`, "utf-8");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
TierMigrationExecutor
|
|
78
|
+
};
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildMetadata,
|
|
3
|
+
decodeMetadataSalt,
|
|
4
|
+
parseMetadata,
|
|
5
|
+
serializeMetadata
|
|
6
|
+
} from "./chunk-JZBOXOUC.js";
|
|
7
|
+
import {
|
|
8
|
+
KDF_KEY_LENGTH,
|
|
9
|
+
KDF_SALT_LENGTH,
|
|
10
|
+
deriveKey
|
|
11
|
+
} from "./chunk-6IWEAUN6.js";
|
|
12
|
+
import {
|
|
13
|
+
open,
|
|
14
|
+
seal
|
|
15
|
+
} from "./chunk-YGGGUTG3.js";
|
|
16
|
+
import {
|
|
17
|
+
__export
|
|
18
|
+
} from "./chunk-MLKGABMK.js";
|
|
19
|
+
|
|
20
|
+
// ../remnic-core/src/secure-store/header.ts
|
|
21
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
22
|
+
import path from "path";
|
|
23
|
+
var SECURE_STORE_DIR_NAME = ".secure-store";
|
|
24
|
+
var HEADER_FILENAME = "header.json";
|
|
25
|
+
var HEADER_FORMAT = "remnic.secure-store.header";
|
|
26
|
+
var HEADER_FORMAT_VERSION = 1;
|
|
27
|
+
var VERIFIER_PLAINTEXT = Buffer.from("remnic-secure-store-v1", "utf8");
|
|
28
|
+
var VERIFIER_AAD = Buffer.from("remnic-secure-store/verifier", "utf8");
|
|
29
|
+
function secureStoreDir(memoryDir) {
|
|
30
|
+
return path.join(memoryDir, SECURE_STORE_DIR_NAME);
|
|
31
|
+
}
|
|
32
|
+
function headerPath(memoryDir) {
|
|
33
|
+
return path.join(secureStoreDir(memoryDir), HEADER_FILENAME);
|
|
34
|
+
}
|
|
35
|
+
function buildHeader(options) {
|
|
36
|
+
const { metadata, derivedKey } = options;
|
|
37
|
+
if (!Buffer.isBuffer(derivedKey) || derivedKey.length !== KDF_KEY_LENGTH) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`derivedKey must be a ${KDF_KEY_LENGTH}-byte Buffer, got length=${derivedKey?.length ?? "non-buffer"}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const salt = decodeMetadataSalt(metadata);
|
|
43
|
+
if (salt.length !== KDF_SALT_LENGTH) {
|
|
44
|
+
throw new Error(`metadata salt is ${salt.length} bytes, expected ${KDF_SALT_LENGTH}`);
|
|
45
|
+
}
|
|
46
|
+
const envelope = seal(derivedKey, salt, VERIFIER_PLAINTEXT, { aad: VERIFIER_AAD });
|
|
47
|
+
return {
|
|
48
|
+
format: HEADER_FORMAT,
|
|
49
|
+
formatVersion: HEADER_FORMAT_VERSION,
|
|
50
|
+
metadata,
|
|
51
|
+
verifier: envelope.toString("hex"),
|
|
52
|
+
createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function serializeHeader(header) {
|
|
56
|
+
validateHeader(header);
|
|
57
|
+
const metadataString = serializeMetadata(header.metadata);
|
|
58
|
+
const metadataObject = JSON.parse(metadataString);
|
|
59
|
+
const ordered = {
|
|
60
|
+
format: header.format,
|
|
61
|
+
formatVersion: header.formatVersion,
|
|
62
|
+
metadata: metadataObject,
|
|
63
|
+
verifier: header.verifier,
|
|
64
|
+
createdAt: header.createdAt
|
|
65
|
+
};
|
|
66
|
+
return JSON.stringify(ordered, null, 2);
|
|
67
|
+
}
|
|
68
|
+
function parseHeader(json) {
|
|
69
|
+
if (typeof json !== "string") {
|
|
70
|
+
throw new Error("header input must be a string");
|
|
71
|
+
}
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(json);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
77
|
+
throw new Error(`header is not valid JSON: ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
80
|
+
throw new Error("header must be a JSON object");
|
|
81
|
+
}
|
|
82
|
+
const obj = parsed;
|
|
83
|
+
if (obj.format !== HEADER_FORMAT) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`unexpected header format: ${String(obj.format)} (expected ${HEADER_FORMAT})`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (obj.formatVersion !== HEADER_FORMAT_VERSION) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`unsupported header formatVersion: ${String(obj.formatVersion)} (this build supports ${HEADER_FORMAT_VERSION})`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (typeof obj.verifier !== "string" || obj.verifier.length === 0) {
|
|
94
|
+
throw new Error("header.verifier must be a non-empty hex string");
|
|
95
|
+
}
|
|
96
|
+
if (!/^[0-9a-fA-F]+$/.test(obj.verifier)) {
|
|
97
|
+
throw new Error("header.verifier must be a hex-encoded string");
|
|
98
|
+
}
|
|
99
|
+
if (obj.verifier.length % 2 !== 0) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"header.verifier hex string must have even length (each byte encodes as two hex digits)"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (typeof obj.createdAt !== "string" || obj.createdAt.length === 0) {
|
|
105
|
+
throw new Error("header.createdAt must be a non-empty string");
|
|
106
|
+
}
|
|
107
|
+
if (typeof obj.metadata !== "object" || obj.metadata === null) {
|
|
108
|
+
throw new Error("header.metadata must be an object");
|
|
109
|
+
}
|
|
110
|
+
const metadata = parseMetadata(JSON.stringify(obj.metadata));
|
|
111
|
+
const header = {
|
|
112
|
+
format: HEADER_FORMAT,
|
|
113
|
+
formatVersion: HEADER_FORMAT_VERSION,
|
|
114
|
+
metadata,
|
|
115
|
+
verifier: obj.verifier,
|
|
116
|
+
createdAt: obj.createdAt
|
|
117
|
+
};
|
|
118
|
+
validateHeader(header);
|
|
119
|
+
return header;
|
|
120
|
+
}
|
|
121
|
+
function validateHeader(header) {
|
|
122
|
+
if (header.format !== HEADER_FORMAT) {
|
|
123
|
+
throw new Error(`header.format must be ${HEADER_FORMAT}`);
|
|
124
|
+
}
|
|
125
|
+
if (header.formatVersion !== HEADER_FORMAT_VERSION) {
|
|
126
|
+
throw new Error(`header.formatVersion must be ${HEADER_FORMAT_VERSION}`);
|
|
127
|
+
}
|
|
128
|
+
if (typeof header.createdAt !== "string" || header.createdAt.length === 0) {
|
|
129
|
+
throw new Error("header.createdAt must be a non-empty ISO-8601 string");
|
|
130
|
+
}
|
|
131
|
+
if (typeof header.verifier !== "string" || header.verifier.length === 0) {
|
|
132
|
+
throw new Error("header.verifier must be a non-empty hex string");
|
|
133
|
+
}
|
|
134
|
+
if (!/^[0-9a-fA-F]+$/.test(header.verifier)) {
|
|
135
|
+
throw new Error("header.verifier must be a hex-encoded string");
|
|
136
|
+
}
|
|
137
|
+
if (header.verifier.length % 2 !== 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"header.verifier hex string must have even length (each byte encodes as two hex digits)"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (header.metadata.format !== "remnic.secure-store.metadata") {
|
|
143
|
+
throw new Error("header.metadata.format must be remnic.secure-store.metadata");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function verifyKey(header, candidateKey) {
|
|
147
|
+
if (!Buffer.isBuffer(candidateKey) || candidateKey.length !== KDF_KEY_LENGTH) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const envelope = Buffer.from(header.verifier, "hex");
|
|
151
|
+
try {
|
|
152
|
+
const plaintext = open(candidateKey, envelope, { aad: VERIFIER_AAD });
|
|
153
|
+
return plaintext.equals(VERIFIER_PLAINTEXT);
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function deriveKeyFromHeader(header, passphrase) {
|
|
159
|
+
const salt = decodeMetadataSalt(header.metadata);
|
|
160
|
+
const params = header.metadata.kdf.params;
|
|
161
|
+
return deriveKey(header.metadata.kdf.algorithm, passphrase, salt, params);
|
|
162
|
+
}
|
|
163
|
+
async function readHeader(memoryDir) {
|
|
164
|
+
const target = headerPath(memoryDir);
|
|
165
|
+
let raw;
|
|
166
|
+
try {
|
|
167
|
+
raw = await readFile(target, "utf8");
|
|
168
|
+
} catch (e) {
|
|
169
|
+
if (e.code === "ENOENT") {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
throw e;
|
|
173
|
+
}
|
|
174
|
+
return parseHeader(raw);
|
|
175
|
+
}
|
|
176
|
+
async function writeHeader(memoryDir, header) {
|
|
177
|
+
validateHeader(header);
|
|
178
|
+
const dir = secureStoreDir(memoryDir);
|
|
179
|
+
await mkdir(dir, { recursive: true });
|
|
180
|
+
const target = headerPath(memoryDir);
|
|
181
|
+
try {
|
|
182
|
+
await writeFile(target, serializeHeader(header), {
|
|
183
|
+
encoding: "utf8",
|
|
184
|
+
mode: 384,
|
|
185
|
+
flag: "wx"
|
|
186
|
+
});
|
|
187
|
+
} catch (e) {
|
|
188
|
+
if (e.code === "EEXIST") {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`secure-store header already exists at ${target}. Refusing to overwrite \u2014 initialize a fresh store or remove the existing header explicitly.`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
195
|
+
return target;
|
|
196
|
+
}
|
|
197
|
+
function buildHeaderFromPassphrase(options) {
|
|
198
|
+
const { passphrase, salt } = options;
|
|
199
|
+
const algorithm = options.algorithm ?? "argon2id";
|
|
200
|
+
const metadataOpts = { algorithm, salt };
|
|
201
|
+
if (options.params !== void 0) metadataOpts.params = options.params;
|
|
202
|
+
if (options.createdAt !== void 0) metadataOpts.createdAt = options.createdAt;
|
|
203
|
+
if (options.note !== void 0) metadataOpts.note = options.note;
|
|
204
|
+
const metadata = buildMetadata(metadataOpts);
|
|
205
|
+
const params = metadata.kdf.params;
|
|
206
|
+
const derivedKey = deriveKey(algorithm, passphrase, salt, params);
|
|
207
|
+
const headerOpts = {
|
|
208
|
+
metadata,
|
|
209
|
+
derivedKey
|
|
210
|
+
};
|
|
211
|
+
if (options.createdAt !== void 0) headerOpts.createdAt = options.createdAt;
|
|
212
|
+
const header = buildHeader(headerOpts);
|
|
213
|
+
return { header, derivedKey };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ../remnic-core/src/secure-store/keyring.ts
|
|
217
|
+
var keyring_exports = {};
|
|
218
|
+
__export(keyring_exports, {
|
|
219
|
+
getKey: () => getKey,
|
|
220
|
+
lock: () => lock,
|
|
221
|
+
lockAll: () => lockAll,
|
|
222
|
+
size: () => size,
|
|
223
|
+
status: () => status,
|
|
224
|
+
unlock: () => unlock
|
|
225
|
+
});
|
|
226
|
+
var ENTRIES = /* @__PURE__ */ new Map();
|
|
227
|
+
function unlock(id, key, now = () => /* @__PURE__ */ new Date()) {
|
|
228
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
229
|
+
throw new Error("keyring id must be a non-empty string");
|
|
230
|
+
}
|
|
231
|
+
if (!Buffer.isBuffer(key) || key.length !== 32) {
|
|
232
|
+
throw new Error(`keyring key must be a 32-byte Buffer, got length=${key?.length ?? "non-buffer"}`);
|
|
233
|
+
}
|
|
234
|
+
const existing = ENTRIES.get(id);
|
|
235
|
+
if (existing) {
|
|
236
|
+
existing.key.fill(0);
|
|
237
|
+
}
|
|
238
|
+
ENTRIES.set(id, { key, unlockedAt: now().toISOString() });
|
|
239
|
+
}
|
|
240
|
+
function getKey(id) {
|
|
241
|
+
const entry = ENTRIES.get(id);
|
|
242
|
+
return entry ? entry.key : null;
|
|
243
|
+
}
|
|
244
|
+
function lock(id) {
|
|
245
|
+
const entry = ENTRIES.get(id);
|
|
246
|
+
if (!entry) return false;
|
|
247
|
+
entry.key.fill(0);
|
|
248
|
+
ENTRIES.delete(id);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
function lockAll() {
|
|
252
|
+
for (const entry of ENTRIES.values()) {
|
|
253
|
+
entry.key.fill(0);
|
|
254
|
+
}
|
|
255
|
+
ENTRIES.clear();
|
|
256
|
+
}
|
|
257
|
+
function status(id) {
|
|
258
|
+
const entry = ENTRIES.get(id);
|
|
259
|
+
if (!entry) {
|
|
260
|
+
return { unlocked: false, unlockedAt: null };
|
|
261
|
+
}
|
|
262
|
+
return { unlocked: true, unlockedAt: entry.unlockedAt };
|
|
263
|
+
}
|
|
264
|
+
function size() {
|
|
265
|
+
return ENTRIES.size;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export {
|
|
269
|
+
SECURE_STORE_DIR_NAME,
|
|
270
|
+
HEADER_FILENAME,
|
|
271
|
+
HEADER_FORMAT,
|
|
272
|
+
HEADER_FORMAT_VERSION,
|
|
273
|
+
secureStoreDir,
|
|
274
|
+
headerPath,
|
|
275
|
+
buildHeader,
|
|
276
|
+
serializeHeader,
|
|
277
|
+
parseHeader,
|
|
278
|
+
validateHeader,
|
|
279
|
+
verifyKey,
|
|
280
|
+
deriveKeyFromHeader,
|
|
281
|
+
readHeader,
|
|
282
|
+
writeHeader,
|
|
283
|
+
buildHeaderFromPassphrase,
|
|
284
|
+
unlock,
|
|
285
|
+
getKey,
|
|
286
|
+
lock,
|
|
287
|
+
status,
|
|
288
|
+
keyring_exports
|
|
289
|
+
};
|