@remnic/plugin-openclaw 1.0.10 → 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 +9775 -2900
- 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,250 @@
|
|
|
1
|
+
// ../remnic-core/src/console/state.ts
|
|
2
|
+
import { promises as fs, createReadStream } from "fs";
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var MAX_LEDGER_TAIL = 50;
|
|
6
|
+
var MAX_VERDICT_TAIL = 25;
|
|
7
|
+
var MAX_DEDUP_TAIL = 10;
|
|
8
|
+
async function gatherConsoleState(orchestrator) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11
|
+
const bufferState = readBufferState(orchestrator, errors);
|
|
12
|
+
const extractionQueue = readExtractionQueue(orchestrator, errors);
|
|
13
|
+
const dedupRecent = readDedupRecent(orchestrator, errors);
|
|
14
|
+
const maintenanceLedgerTail = await readMaintenanceLedgerTail(
|
|
15
|
+
orchestrator,
|
|
16
|
+
errors
|
|
17
|
+
);
|
|
18
|
+
const qmdProbe = readQmdProbe(orchestrator, errors);
|
|
19
|
+
const daemon = readDaemonInfo(orchestrator, errors);
|
|
20
|
+
return {
|
|
21
|
+
capturedAt,
|
|
22
|
+
bufferState,
|
|
23
|
+
extractionQueue,
|
|
24
|
+
dedupRecent,
|
|
25
|
+
maintenanceLedgerTail,
|
|
26
|
+
qmdProbe,
|
|
27
|
+
daemon,
|
|
28
|
+
errors
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function readBufferState(orchestrator, errors) {
|
|
32
|
+
try {
|
|
33
|
+
const getTurns = orchestrator.buffer?.getTurns;
|
|
34
|
+
if (typeof getTurns !== "function") {
|
|
35
|
+
return { turnsCount: 0, byteCount: 0 };
|
|
36
|
+
}
|
|
37
|
+
const turns = getTurns.call(orchestrator.buffer) ?? [];
|
|
38
|
+
let byteCount = 0;
|
|
39
|
+
for (const turn of turns) {
|
|
40
|
+
const content = typeof turn?.content === "string" ? turn.content : "";
|
|
41
|
+
byteCount += Buffer.byteLength(content, "utf8");
|
|
42
|
+
}
|
|
43
|
+
return { turnsCount: turns.length, byteCount };
|
|
44
|
+
} catch (err) {
|
|
45
|
+
errors.push(`bufferState: ${describeError(err)}`);
|
|
46
|
+
return { turnsCount: 0, byteCount: 0 };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function readExtractionQueue(orchestrator, errors) {
|
|
50
|
+
try {
|
|
51
|
+
const depth = typeof orchestrator.getConsoleExtractionQueueDepth === "function" ? orchestrator.getConsoleExtractionQueueDepth() : 0;
|
|
52
|
+
const verdicts = typeof orchestrator.getConsoleExtractionRecentVerdicts === "function" ? orchestrator.getConsoleExtractionRecentVerdicts() : [];
|
|
53
|
+
const recentVerdicts = (verdicts ?? []).slice(-MAX_VERDICT_TAIL).map((v) => ({
|
|
54
|
+
ts: typeof v?.ts === "string" ? v.ts : "",
|
|
55
|
+
kind: typeof v?.kind === "string" ? v.kind : "unknown",
|
|
56
|
+
...typeof v?.reason === "string" ? { reason: v.reason } : {}
|
|
57
|
+
}));
|
|
58
|
+
return { depth: Number.isFinite(depth) ? depth : 0, recentVerdicts };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
errors.push(`extractionQueue: ${describeError(err)}`);
|
|
61
|
+
return { depth: 0, recentVerdicts: [] };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function readDedupRecent(orchestrator, errors) {
|
|
65
|
+
try {
|
|
66
|
+
if (typeof orchestrator.getConsoleDedupRecentDecisions !== "function") return [];
|
|
67
|
+
const raw = orchestrator.getConsoleDedupRecentDecisions() ?? [];
|
|
68
|
+
return raw.slice(-MAX_DEDUP_TAIL).map((d) => ({
|
|
69
|
+
ts: typeof d?.ts === "string" ? d.ts : "",
|
|
70
|
+
decision: typeof d?.decision === "string" ? d.decision : "unknown",
|
|
71
|
+
...typeof d?.fingerprint === "string" ? { fingerprint: d.fingerprint } : {},
|
|
72
|
+
...typeof d?.similarity === "number" && Number.isFinite(d.similarity) ? { similarity: d.similarity } : {}
|
|
73
|
+
}));
|
|
74
|
+
} catch (err) {
|
|
75
|
+
errors.push(`dedupRecent: ${describeError(err)}`);
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function streamLedgerTopN(ledgerPath, topN, project) {
|
|
80
|
+
let stream;
|
|
81
|
+
try {
|
|
82
|
+
await fs.stat(ledgerPath);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const code = err.code;
|
|
85
|
+
if (code === "ENOENT") return [];
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
stream = createReadStream(ledgerPath, { encoding: "utf-8" });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
const code = err.code;
|
|
92
|
+
if (code === "ENOENT") return [];
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
96
|
+
const cap = Math.max(1, topN) * 2;
|
|
97
|
+
const buf = [];
|
|
98
|
+
const truncate = () => {
|
|
99
|
+
buf.sort((a, b) => {
|
|
100
|
+
const aMs = Date.parse(a.ts);
|
|
101
|
+
const bMs = Date.parse(b.ts);
|
|
102
|
+
if (Number.isFinite(aMs) && Number.isFinite(bMs)) return bMs - aMs;
|
|
103
|
+
if (Number.isFinite(aMs)) return -1;
|
|
104
|
+
if (Number.isFinite(bMs)) return 1;
|
|
105
|
+
return 0;
|
|
106
|
+
});
|
|
107
|
+
if (buf.length > topN) buf.length = topN;
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
for await (const line of rl) {
|
|
111
|
+
const trimmed = line.trim();
|
|
112
|
+
if (!trimmed) continue;
|
|
113
|
+
let parsed;
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(trimmed);
|
|
116
|
+
} catch {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const event = project(parsed);
|
|
123
|
+
if (event === null) continue;
|
|
124
|
+
if (!event.ts || !Number.isFinite(Date.parse(event.ts))) continue;
|
|
125
|
+
buf.push(event);
|
|
126
|
+
if (buf.length >= cap) truncate();
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
rl.close();
|
|
130
|
+
stream.close();
|
|
131
|
+
}
|
|
132
|
+
truncate();
|
|
133
|
+
return buf.reverse();
|
|
134
|
+
}
|
|
135
|
+
function projectLedgerRow(p) {
|
|
136
|
+
const ts = typeof p.ts === "string" && p.ts.length > 0 ? p.ts : typeof p.hour === "string" && p.hour.length > 0 ? p.hour : typeof p.rebuiltAt === "string" && p.rebuiltAt.length > 0 ? p.rebuiltAt : "";
|
|
137
|
+
const category = typeof p.category === "string" && p.category.length > 0 ? p.category : typeof p.verdictKind === "string" ? "judge-verdict" : typeof p.turnCount === "number" ? "observation" : "unknown";
|
|
138
|
+
const summary = summarizeLedgerEvent(p);
|
|
139
|
+
return { ts, category, summary };
|
|
140
|
+
}
|
|
141
|
+
async function readMaintenanceLedgerTail(orchestrator, errors) {
|
|
142
|
+
try {
|
|
143
|
+
const memoryDir = orchestrator.config?.memoryDir;
|
|
144
|
+
if (!memoryDir || typeof memoryDir !== "string") return [];
|
|
145
|
+
const ledgerDir = path.join(memoryDir, "state", "observation-ledger");
|
|
146
|
+
const verdictsTopN = await streamLedgerTopN(
|
|
147
|
+
path.join(ledgerDir, "extraction-judge-verdicts.jsonl"),
|
|
148
|
+
MAX_LEDGER_TAIL,
|
|
149
|
+
projectLedgerRow
|
|
150
|
+
);
|
|
151
|
+
const rebuiltTopN = await streamLedgerTopN(
|
|
152
|
+
path.join(ledgerDir, "rebuilt-observations.jsonl"),
|
|
153
|
+
MAX_LEDGER_TAIL,
|
|
154
|
+
projectLedgerRow
|
|
155
|
+
);
|
|
156
|
+
const events = [...verdictsTopN, ...rebuiltTopN];
|
|
157
|
+
if (events.length === 0) return [];
|
|
158
|
+
events.sort((a, b) => {
|
|
159
|
+
const aMs = Date.parse(a.ts);
|
|
160
|
+
const bMs = Date.parse(b.ts);
|
|
161
|
+
if (Number.isFinite(aMs) && Number.isFinite(bMs)) return bMs - aMs;
|
|
162
|
+
if (Number.isFinite(aMs)) return -1;
|
|
163
|
+
if (Number.isFinite(bMs)) return 1;
|
|
164
|
+
return 0;
|
|
165
|
+
});
|
|
166
|
+
const tail = events.slice(0, MAX_LEDGER_TAIL);
|
|
167
|
+
return tail.reverse();
|
|
168
|
+
} catch (err) {
|
|
169
|
+
errors.push(`maintenanceLedgerTail: ${describeError(err)}`);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function summarizeLedgerEvent(p) {
|
|
174
|
+
const parts = [];
|
|
175
|
+
if (typeof p.verdictKind === "string") parts.push(`verdict=${p.verdictKind}`);
|
|
176
|
+
if (typeof p.reason === "string" && p.reason.length > 0) {
|
|
177
|
+
const trimmed = p.reason.length > 80 ? `${p.reason.slice(0, 80)}\u2026` : p.reason;
|
|
178
|
+
parts.push(`reason=${trimmed}`);
|
|
179
|
+
}
|
|
180
|
+
if (typeof p.candidateCategory === "string") {
|
|
181
|
+
parts.push(`cat=${p.candidateCategory}`);
|
|
182
|
+
}
|
|
183
|
+
if (typeof p.sessionKey === "string" && p.sessionKey.length > 0) {
|
|
184
|
+
parts.push(`session=${p.sessionKey}`);
|
|
185
|
+
}
|
|
186
|
+
if (typeof p.turnCount === "number" && Number.isFinite(p.turnCount)) {
|
|
187
|
+
parts.push(`turns=${p.turnCount}`);
|
|
188
|
+
}
|
|
189
|
+
if (typeof p.userTurns === "number" && Number.isFinite(p.userTurns)) {
|
|
190
|
+
parts.push(`u=${p.userTurns}`);
|
|
191
|
+
}
|
|
192
|
+
if (typeof p.assistantTurns === "number" && Number.isFinite(p.assistantTurns)) {
|
|
193
|
+
parts.push(`a=${p.assistantTurns}`);
|
|
194
|
+
}
|
|
195
|
+
if (parts.length === 0) {
|
|
196
|
+
return typeof p.category === "string" ? p.category : "event";
|
|
197
|
+
}
|
|
198
|
+
return parts.join(" ");
|
|
199
|
+
}
|
|
200
|
+
function readQmdProbe(orchestrator, errors) {
|
|
201
|
+
try {
|
|
202
|
+
const qmd = orchestrator.qmd;
|
|
203
|
+
if (!qmd) {
|
|
204
|
+
return { available: false, daemonMode: false, debug: "qmd unavailable" };
|
|
205
|
+
}
|
|
206
|
+
const available = typeof qmd.isAvailable === "function" ? qmd.isAvailable() : false;
|
|
207
|
+
const daemonMode = typeof qmd.isDaemonMode === "function" ? qmd.isDaemonMode() : false;
|
|
208
|
+
const debug = typeof qmd.debugStatus === "function" ? qmd.debugStatus() : "";
|
|
209
|
+
return { available, daemonMode, debug };
|
|
210
|
+
} catch (err) {
|
|
211
|
+
errors.push(`qmdProbe: ${describeError(err)}`);
|
|
212
|
+
return { available: false, daemonMode: false, debug: "" };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function readDaemonInfo(orchestrator, errors) {
|
|
216
|
+
try {
|
|
217
|
+
if (typeof orchestrator.getConsoleDaemonInfo === "function") {
|
|
218
|
+
const info = orchestrator.getConsoleDaemonInfo();
|
|
219
|
+
return {
|
|
220
|
+
uptimeMs: Number.isFinite(info?.uptimeMs) ? info.uptimeMs : 0,
|
|
221
|
+
version: typeof info?.version === "string" ? info.version : "unknown"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const uptimeMs = Math.round((process.uptime?.() ?? 0) * 1e3);
|
|
225
|
+
return { uptimeMs, version: resolvePackageVersion() };
|
|
226
|
+
} catch (err) {
|
|
227
|
+
errors.push(`daemon: ${describeError(err)}`);
|
|
228
|
+
return { uptimeMs: 0, version: "unknown" };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function resolvePackageVersion() {
|
|
232
|
+
try {
|
|
233
|
+
const env = process.env?.REMNIC_VERSION;
|
|
234
|
+
if (typeof env === "string" && env.length > 0) return env;
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
return "unknown";
|
|
238
|
+
}
|
|
239
|
+
function describeError(err) {
|
|
240
|
+
if (err instanceof Error) return err.message;
|
|
241
|
+
try {
|
|
242
|
+
return String(err);
|
|
243
|
+
} catch {
|
|
244
|
+
return "unknown error";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export {
|
|
249
|
+
gatherConsoleState
|
|
250
|
+
};
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildHeaderFromPassphrase,
|
|
3
|
+
deriveKeyFromHeader,
|
|
4
|
+
getKey,
|
|
5
|
+
headerPath,
|
|
6
|
+
lock,
|
|
7
|
+
readHeader,
|
|
8
|
+
secureStoreDir,
|
|
9
|
+
status,
|
|
10
|
+
unlock,
|
|
11
|
+
verifyKey,
|
|
12
|
+
writeHeader
|
|
13
|
+
} from "./chunk-CXM7EBAO.js";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_ARGON2ID_PARAMS,
|
|
16
|
+
DEFAULT_SCRYPT_PARAMS,
|
|
17
|
+
KDF_SALT_LENGTH
|
|
18
|
+
} from "./chunk-6IWEAUN6.js";
|
|
19
|
+
import {
|
|
20
|
+
migrateMemoryDirToEncrypted
|
|
21
|
+
} from "./chunk-3I7RHWYT.js";
|
|
22
|
+
import {
|
|
23
|
+
generateSalt
|
|
24
|
+
} from "./chunk-YGGGUTG3.js";
|
|
25
|
+
|
|
26
|
+
// ../remnic-core/src/secure-store/cli-handlers.ts
|
|
27
|
+
async function runSecureStoreInit(options) {
|
|
28
|
+
const { memoryDir, readPassphrase } = options;
|
|
29
|
+
if (typeof memoryDir !== "string" || memoryDir.length === 0) {
|
|
30
|
+
throw new Error("secure-store init: memoryDir is required");
|
|
31
|
+
}
|
|
32
|
+
const existing = await readHeader(memoryDir);
|
|
33
|
+
if (existing !== null) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`secure-store header already exists at ${headerPath(memoryDir)}. Run 'remnic secure-store status' to inspect, or remove the .secure-store directory explicitly to reinitialize.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const passphrase = await readPassphrase("Enter new passphrase: ", { confirm: true });
|
|
39
|
+
validatePassphrase(passphrase);
|
|
40
|
+
const algorithm = options.algorithm ?? "argon2id";
|
|
41
|
+
const params = resolveParams(algorithm, options.params);
|
|
42
|
+
const salt = options.salt ?? generateSalt();
|
|
43
|
+
if (salt.length !== KDF_SALT_LENGTH) {
|
|
44
|
+
throw new Error(`salt must be ${KDF_SALT_LENGTH} bytes, got ${salt.length}`);
|
|
45
|
+
}
|
|
46
|
+
const built = buildHeaderFromPassphrase({
|
|
47
|
+
passphrase,
|
|
48
|
+
salt,
|
|
49
|
+
algorithm,
|
|
50
|
+
params,
|
|
51
|
+
...options.note !== void 0 ? { note: options.note } : {},
|
|
52
|
+
...options.now ? { createdAt: options.now().toISOString() } : {}
|
|
53
|
+
});
|
|
54
|
+
built.derivedKey.fill(0);
|
|
55
|
+
const writtenPath = await writeHeader(memoryDir, built.header);
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
headerPath: writtenPath,
|
|
59
|
+
kdf: built.header.metadata.kdf,
|
|
60
|
+
createdAt: built.header.createdAt
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async function runSecureStoreUnlock(options) {
|
|
64
|
+
const { memoryDir, readPassphrase } = options;
|
|
65
|
+
const header = await readHeader(memoryDir);
|
|
66
|
+
if (!header) {
|
|
67
|
+
return { ok: false, reason: "not-initialized" };
|
|
68
|
+
}
|
|
69
|
+
const passphrase = await readPassphrase("Enter passphrase: ");
|
|
70
|
+
validatePassphrase(passphrase);
|
|
71
|
+
const candidateKey = deriveKeyFromHeader(header, passphrase);
|
|
72
|
+
if (!verifyKey(header, candidateKey)) {
|
|
73
|
+
candidateKey.fill(0);
|
|
74
|
+
return { ok: false, reason: "wrong-passphrase" };
|
|
75
|
+
}
|
|
76
|
+
const id = options.keyringId ?? secureStoreDir(memoryDir);
|
|
77
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
78
|
+
unlock(id, candidateKey, now);
|
|
79
|
+
const status2 = status(id);
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
unlockedAt: status2.unlockedAt ?? now().toISOString(),
|
|
83
|
+
algorithm: header.metadata.kdf.algorithm
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function runSecureStoreLock(options) {
|
|
87
|
+
const id = options.keyringId ?? secureStoreDir(options.memoryDir);
|
|
88
|
+
const cleared = lock(id);
|
|
89
|
+
return { ok: true, cleared };
|
|
90
|
+
}
|
|
91
|
+
async function runSecureStoreMigrate(options) {
|
|
92
|
+
const { memoryDir } = options;
|
|
93
|
+
const header = await readHeader(memoryDir);
|
|
94
|
+
if (!header) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
reason: "not-initialized",
|
|
98
|
+
encrypted: 0,
|
|
99
|
+
skipped: 0,
|
|
100
|
+
errors: []
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const id = options.keyringId ?? secureStoreDir(memoryDir);
|
|
104
|
+
const key = getKey(id);
|
|
105
|
+
if (key === null) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
reason: "locked",
|
|
109
|
+
encrypted: 0,
|
|
110
|
+
skipped: 0,
|
|
111
|
+
errors: []
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const result = await migrateMemoryDirToEncrypted(memoryDir, key);
|
|
115
|
+
if (result.errors.length > 0) {
|
|
116
|
+
return { ok: false, reason: "file-errors", ...result };
|
|
117
|
+
}
|
|
118
|
+
return { ok: true, ...result };
|
|
119
|
+
}
|
|
120
|
+
async function runSecureStoreStatus(options) {
|
|
121
|
+
const { memoryDir } = options;
|
|
122
|
+
const id = options.keyringId ?? secureStoreDir(memoryDir);
|
|
123
|
+
const header = await readHeader(memoryDir);
|
|
124
|
+
const ks = status(id);
|
|
125
|
+
const target = headerPath(memoryDir);
|
|
126
|
+
if (!header) {
|
|
127
|
+
return {
|
|
128
|
+
initialized: false,
|
|
129
|
+
headerPath: target,
|
|
130
|
+
locked: !ks.unlocked,
|
|
131
|
+
unlockedAt: ks.unlockedAt,
|
|
132
|
+
kdf: null,
|
|
133
|
+
createdAt: null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
initialized: true,
|
|
138
|
+
headerPath: target,
|
|
139
|
+
locked: !ks.unlocked,
|
|
140
|
+
unlockedAt: ks.unlockedAt,
|
|
141
|
+
kdf: header.metadata.kdf,
|
|
142
|
+
createdAt: header.createdAt
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
var MIN_PASSPHRASE_LENGTH = 8;
|
|
146
|
+
function validatePassphrase(passphrase) {
|
|
147
|
+
if (typeof passphrase !== "string") {
|
|
148
|
+
throw new Error("passphrase must be a string");
|
|
149
|
+
}
|
|
150
|
+
if (passphrase.length === 0) {
|
|
151
|
+
throw new Error("passphrase must not be empty");
|
|
152
|
+
}
|
|
153
|
+
if (passphrase.length < MIN_PASSPHRASE_LENGTH) {
|
|
154
|
+
throw new Error(`passphrase must be at least ${MIN_PASSPHRASE_LENGTH} characters`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function resolveParams(algorithm, override) {
|
|
158
|
+
if (override !== void 0) return override;
|
|
159
|
+
if (algorithm === "scrypt") return { ...DEFAULT_SCRYPT_PARAMS };
|
|
160
|
+
return { ...DEFAULT_ARGON2ID_PARAMS };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ../remnic-core/src/secure-store/cli-renderer.ts
|
|
164
|
+
function renderInitReport(report) {
|
|
165
|
+
const lines = [];
|
|
166
|
+
lines.push("=== Remnic secure-store initialized ===");
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push(`header: ${report.headerPath}`);
|
|
169
|
+
lines.push(`createdAt: ${report.createdAt}`);
|
|
170
|
+
lines.push(...renderKdfLines(report.kdf));
|
|
171
|
+
lines.push("");
|
|
172
|
+
lines.push("Note: init does NOT auto-unlock the store. Run");
|
|
173
|
+
lines.push(" remnic engram secure-store unlock");
|
|
174
|
+
lines.push("to register the master key with the running daemon.");
|
|
175
|
+
return lines.join("\n");
|
|
176
|
+
}
|
|
177
|
+
function renderUnlockReport(report) {
|
|
178
|
+
if (report.ok) {
|
|
179
|
+
return `OK \u2014 secure-store unlocked at ${report.unlockedAt} (algorithm=${report.algorithm}).`;
|
|
180
|
+
}
|
|
181
|
+
if (report.reason === "not-initialized") {
|
|
182
|
+
return "ERR \u2014 secure-store is not initialized. Run 'remnic engram secure-store init' first.";
|
|
183
|
+
}
|
|
184
|
+
return "ERR \u2014 wrong passphrase.";
|
|
185
|
+
}
|
|
186
|
+
function renderLockReport(report) {
|
|
187
|
+
if (report.cleared) {
|
|
188
|
+
return "OK \u2014 secure-store key cleared from in-memory keyring.";
|
|
189
|
+
}
|
|
190
|
+
return "OK \u2014 secure-store was already locked (no in-memory key to clear).";
|
|
191
|
+
}
|
|
192
|
+
function renderMigrateReport(report) {
|
|
193
|
+
if (!report.ok && report.reason === "not-initialized") {
|
|
194
|
+
return "ERR \u2014 secure-store is not initialized. Run 'remnic engram secure-store init' first.";
|
|
195
|
+
}
|
|
196
|
+
if (!report.ok && report.reason === "locked") {
|
|
197
|
+
return "ERR \u2014 secure-store is locked. Run 'remnic engram secure-store unlock' before migrate.";
|
|
198
|
+
}
|
|
199
|
+
const lines = [];
|
|
200
|
+
lines.push(report.ok ? "OK \u2014 secure-store migration complete." : "ERR \u2014 secure-store migration completed with file errors.");
|
|
201
|
+
lines.push(`encrypted: ${report.encrypted}`);
|
|
202
|
+
lines.push(`skipped: ${report.skipped}`);
|
|
203
|
+
lines.push(`errors: ${report.errors.length}`);
|
|
204
|
+
for (const entry of report.errors.slice(0, 10)) {
|
|
205
|
+
lines.push(`- ${entry.filePath}: ${entry.error}`);
|
|
206
|
+
}
|
|
207
|
+
if (report.errors.length > 10) {
|
|
208
|
+
lines.push(`- ... ${report.errors.length - 10} more error(s)`);
|
|
209
|
+
}
|
|
210
|
+
return lines.join("\n");
|
|
211
|
+
}
|
|
212
|
+
function renderStatusReport(report) {
|
|
213
|
+
const lines = [];
|
|
214
|
+
lines.push("=== Remnic secure-store status ===");
|
|
215
|
+
lines.push("");
|
|
216
|
+
lines.push(`header: ${report.headerPath}`);
|
|
217
|
+
lines.push(`initialized: ${report.initialized ? "yes" : "no"}`);
|
|
218
|
+
if (!report.initialized) {
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("Run 'remnic engram secure-store init' to initialize a new store.");
|
|
221
|
+
return lines.join("\n");
|
|
222
|
+
}
|
|
223
|
+
lines.push(`createdAt: ${report.createdAt ?? "n/a"}`);
|
|
224
|
+
lines.push(`locked: ${report.locked ? "yes" : "no"}`);
|
|
225
|
+
if (!report.locked) {
|
|
226
|
+
lines.push(`lastUnlockAt: ${report.unlockedAt ?? "n/a"}`);
|
|
227
|
+
}
|
|
228
|
+
if (report.kdf) {
|
|
229
|
+
lines.push(...renderKdfLines(report.kdf));
|
|
230
|
+
}
|
|
231
|
+
return lines.join("\n");
|
|
232
|
+
}
|
|
233
|
+
function renderKdfLines(kdf) {
|
|
234
|
+
const lines = [];
|
|
235
|
+
lines.push(`kdf.algorithm: ${kdf.algorithm}`);
|
|
236
|
+
if (kdf.algorithm === "scrypt") {
|
|
237
|
+
const { N, r, p, keyLength, maxmem } = kdf.params;
|
|
238
|
+
lines.push(`kdf.params: N=${N} r=${r} p=${p} keyLength=${keyLength} maxmem=${maxmem}`);
|
|
239
|
+
} else {
|
|
240
|
+
const { memoryKiB, iterations, parallelism, keyLength } = kdf.params;
|
|
241
|
+
lines.push(
|
|
242
|
+
`kdf.params: memoryKiB=${memoryKiB} iterations=${iterations} parallelism=${parallelism} keyLength=${keyLength}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return lines;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ../remnic-core/src/secure-store/passphrase-reader.ts
|
|
249
|
+
import { createInterface } from "readline";
|
|
250
|
+
import { StringDecoder } from "string_decoder";
|
|
251
|
+
function createPassphraseReader(options = {}) {
|
|
252
|
+
const input = options.input ?? process.stdin;
|
|
253
|
+
const output = options.output ?? process.stdout;
|
|
254
|
+
const errorStream = options.errorStream ?? process.stderr;
|
|
255
|
+
let nonTtyReader = null;
|
|
256
|
+
let nonTtyWarned = false;
|
|
257
|
+
return async function readPassphrase(prompt, readerOptions) {
|
|
258
|
+
const first = await readSinglePassphrase(prompt);
|
|
259
|
+
if (readerOptions?.confirm) {
|
|
260
|
+
const second = await readSinglePassphrase("Confirm passphrase: ");
|
|
261
|
+
if (first !== second) {
|
|
262
|
+
throw new Error("passphrases did not match");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return first;
|
|
266
|
+
};
|
|
267
|
+
async function readSinglePassphrase(prompt) {
|
|
268
|
+
const inputAsAny = input;
|
|
269
|
+
if (inputAsAny.isTTY && typeof inputAsAny.setRawMode === "function") {
|
|
270
|
+
return readNoEcho(prompt, inputAsAny, output);
|
|
271
|
+
}
|
|
272
|
+
if (!nonTtyWarned) {
|
|
273
|
+
errorStream.write(
|
|
274
|
+
"[remnic secure-store] warning: stdin is not a TTY; reading passphrase as a plain line. Take care that the passphrase is not exposed in shell history.\n"
|
|
275
|
+
);
|
|
276
|
+
nonTtyWarned = true;
|
|
277
|
+
}
|
|
278
|
+
errorStream.write(prompt);
|
|
279
|
+
if (!nonTtyReader) nonTtyReader = createNonTtyLineReader(input);
|
|
280
|
+
return nonTtyReader.next();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function readNoEcho(prompt, input, output) {
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
output.write(prompt);
|
|
286
|
+
let buffer = "";
|
|
287
|
+
let settled = false;
|
|
288
|
+
const wasRaw = input.isRaw === true;
|
|
289
|
+
const decoder = new StringDecoder("utf8");
|
|
290
|
+
if (input.setRawMode) input.setRawMode(true);
|
|
291
|
+
input.resume();
|
|
292
|
+
const cleanup = () => {
|
|
293
|
+
input.pause();
|
|
294
|
+
input.removeListener("data", onData);
|
|
295
|
+
decoder.end();
|
|
296
|
+
if (input.setRawMode) input.setRawMode(wasRaw);
|
|
297
|
+
output.write("\n");
|
|
298
|
+
};
|
|
299
|
+
const onData = (chunk) => {
|
|
300
|
+
const str = decoder.write(chunk);
|
|
301
|
+
for (const ch of str) {
|
|
302
|
+
if (settled) return;
|
|
303
|
+
const code = ch.charCodeAt(0);
|
|
304
|
+
if (ch === "\n" || ch === "\r") {
|
|
305
|
+
settled = true;
|
|
306
|
+
cleanup();
|
|
307
|
+
resolve(buffer);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (code === 3) {
|
|
311
|
+
settled = true;
|
|
312
|
+
cleanup();
|
|
313
|
+
reject(new Error("passphrase entry aborted (Ctrl+C)"));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (code === 4) {
|
|
317
|
+
settled = true;
|
|
318
|
+
cleanup();
|
|
319
|
+
if (buffer.length === 0) {
|
|
320
|
+
reject(new Error("passphrase entry aborted (EOF)"));
|
|
321
|
+
} else {
|
|
322
|
+
resolve(buffer);
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (code === 8 || code === 127) {
|
|
327
|
+
if (buffer.length > 0) {
|
|
328
|
+
const codePoints = Array.from(buffer);
|
|
329
|
+
buffer = codePoints.slice(0, -1).join("");
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (code < 32) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
buffer += ch;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
input.on("data", onData);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function createNonTtyLineReader(input) {
|
|
343
|
+
const rl = createInterface({ input, terminal: false });
|
|
344
|
+
const lineQueue = [];
|
|
345
|
+
const waiterQueue = [];
|
|
346
|
+
const errorQueue = [];
|
|
347
|
+
let closed = false;
|
|
348
|
+
let error = null;
|
|
349
|
+
rl.on("line", (line) => {
|
|
350
|
+
const waiter = waiterQueue.shift();
|
|
351
|
+
if (waiter) {
|
|
352
|
+
waiter(line);
|
|
353
|
+
} else {
|
|
354
|
+
lineQueue.push(line);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
rl.on("close", () => {
|
|
358
|
+
closed = true;
|
|
359
|
+
while (waiterQueue.length > 0) {
|
|
360
|
+
const w = waiterQueue.shift();
|
|
361
|
+
errorQueue.shift();
|
|
362
|
+
w("");
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
rl.on("error", (err) => {
|
|
366
|
+
error = err;
|
|
367
|
+
while (errorQueue.length > 0) {
|
|
368
|
+
const r = errorQueue.shift();
|
|
369
|
+
waiterQueue.shift();
|
|
370
|
+
r(err);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
next() {
|
|
375
|
+
if (error) return Promise.reject(error);
|
|
376
|
+
const queued = lineQueue.shift();
|
|
377
|
+
if (queued !== void 0) return Promise.resolve(queued);
|
|
378
|
+
if (closed) return Promise.resolve("");
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
waiterQueue.push(resolve);
|
|
381
|
+
errorQueue.push(reject);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export {
|
|
388
|
+
runSecureStoreInit,
|
|
389
|
+
runSecureStoreUnlock,
|
|
390
|
+
runSecureStoreLock,
|
|
391
|
+
runSecureStoreMigrate,
|
|
392
|
+
runSecureStoreStatus,
|
|
393
|
+
MIN_PASSPHRASE_LENGTH,
|
|
394
|
+
renderInitReport,
|
|
395
|
+
renderUnlockReport,
|
|
396
|
+
renderLockReport,
|
|
397
|
+
renderMigrateReport,
|
|
398
|
+
renderStatusReport,
|
|
399
|
+
createPassphraseReader
|
|
400
|
+
};
|