@jesonliu/lark-claudecode-bridge 1.4.0 → 1.4.1

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.
@@ -0,0 +1,461 @@
1
+ // 「用户与密钥」Tab 后端(记忆系统最终方案 v1.1 §六 Tab2 / §三 决策 2·4·5):Admin API 用户
2
+ // 聚合 + 密钥保险库 + 审计留痕。
3
+ //
4
+ // 身份模型(OpenViking 0.5.0 源码实证,见方案附录):key = base64url(account).base64url(user).
5
+ // base64url(secret),一把 key 天然绑定一个固定身份;api_key 模式下声明头被服务端静默删除,
6
+ // 身份只认 key。Admin API(X-API-Key 携 root key):
7
+ // - GET /api/v1/admin/accounts/{account}/users → 列表(api_key 模式下明文回带 api_key,
8
+ // server/auth/__init__.py should_expose_user_key:trusted 模式才隐藏——LCB 的保险库可自愈回读)
9
+ // - POST /api/v1/admin/accounts/{account}/users → 创建即返回 user_key(仅此响应出现)
10
+ // - POST /api/v1/admin/accounts/{account}/users/{uid}/key → Regenerate(旧 key 立即失效)
11
+ // - DELETE /api/v1/admin/accounts/{account}/users/{uid} → 202 异步清理(LCB 先备份再删)
12
+ //
13
+ // 安全边界(方案 §六 / §七):
14
+ // - 密钥明文只在本进程与 Admin API 间流转;落盘一律 DPAPI 加密(Windows CurrentUser 域绑定,
15
+ // PowerShell stdin 传 base64,明文不上命令行——进程列表不可见);
16
+ // - 浏览器只见脱敏提示;reveal(打开记忆空间 / 复制 key)单次放行并留痕;
17
+ // - 每次查看/重置/删除/初始化行为写审计日志(JSONL 追加);
18
+ // - 删除动作备份前置:打包 user 目录(明文数据可直接 tar/zip)到 backups/ 再调 Admin 删除。
19
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
20
+ import { spawn } from 'node:child_process';
21
+ import { dirname, join } from 'node:path';
22
+ import { MANAGED_OV_CONF, MEMORY_MANAGED_DIR } from '../memory-installer.js';
23
+ import { memoryEndpoint, probeMemoryHealth } from '../memory.js';
24
+ import { maskSecret } from './config-api.js';
25
+ /** 网页管理端用户约定 uid(方案 §四 阶段 3:{"user_id":"web"}) */
26
+ export const WEB_USER_ID = 'web';
27
+ /** 密钥保险库(uid → base64(DPAPI(plain));DPAPI CurrentUser 域绑定,换机/换账户不可解密) */
28
+ const VAULT_FILE = join(MEMORY_MANAGED_DIR, 'user-keys.vault.json');
29
+ /** 审计日志(JSONL 追加;查看/重置/删除/初始化留痕,方案 §六「管理员每次查看行为留痕」) */
30
+ export const MEMORY_AUDIT_LOG = join(MEMORY_MANAGED_DIR, 'audit.log');
31
+ /** 删除前备份目录(先备份后删除,方案 §六/§七「清理动作必须二次确认 + 先备份」) */
32
+ export const MEMORY_BACKUP_DIR = join(MEMORY_MANAGED_DIR, 'backups');
33
+ /** user_id 合法性(路径安全 + OpenViking uid 口径):字母数字下划线连字符,≤128 */
34
+ export function isValidUserId(uid) {
35
+ return /^[A-Za-z0-9_-]{1,128}$/.test(uid);
36
+ }
37
+ // ============ 审计留痕 ============
38
+ /** 审计写一条(JSONL;失败静默——留痕不可用不应阻断管理动作本身) */
39
+ export function auditMemory(action, detail = {}) {
40
+ try {
41
+ mkdirSync(MEMORY_MANAGED_DIR, { recursive: true });
42
+ appendFileSync(MEMORY_AUDIT_LOG, JSON.stringify({ ts: new Date().toISOString(), action, ...detail }) + '\n', 'utf8');
43
+ }
44
+ catch { /* 留痕失败不阻断 */ }
45
+ }
46
+ // ============ root key 解析 ============
47
+ /** 读受管 ov.conf 的 server.root_api_key(memory.api_key 未配置时的兜底——一键安装写 conf 后
48
+ * config.yaml 回写中断等场景,root key 仍在 conf 里可自愈) */
49
+ function managedRootKey() {
50
+ try {
51
+ const doc = JSON.parse(readFileSync(MANAGED_OV_CONF, 'utf8'));
52
+ return doc.server?.root_api_key || undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ /** Admin API 用的 root key:config.memory.api_key 优先,受管 conf 兜底 */
59
+ function resolveRootKey(memory) {
60
+ return memory?.apiKey?.trim() || managedRootKey();
61
+ }
62
+ /** Admin API 调用(X-API-Key 携 root key;10s 超时——管理面动作不拖页面)。root key 只进本进程 */
63
+ async function adminCall(memory, rootKey, method, path, body) {
64
+ const base = memoryEndpoint(memory).replace(/\/+$/, '');
65
+ try {
66
+ const res = await fetch(`${base}${path}`, {
67
+ method,
68
+ headers: { 'X-API-Key': rootKey, ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}) },
69
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
70
+ signal: AbortSignal.timeout(10_000),
71
+ });
72
+ const json = (await res.json().catch(() => ({})));
73
+ const msg = typeof json.message === 'string' ? json.message : undefined;
74
+ return { ok: res.ok, status: res.status, json, ...(msg ? { message: msg } : {}) };
75
+ }
76
+ catch (e) {
77
+ return { ok: false, status: 0, json: {}, message: e instanceof Error ? e.message : String(e) };
78
+ }
79
+ }
80
+ const adminPath = (memory, suffix) => `/api/v1/admin/accounts/${encodeURIComponent(memory.account ?? 'lcb')}${suffix}`;
81
+ function loadVault() {
82
+ try {
83
+ const doc = JSON.parse(readFileSync(VAULT_FILE, 'utf8'));
84
+ return doc && typeof doc === 'object' && !Array.isArray(doc) ? doc : {};
85
+ }
86
+ catch {
87
+ return {};
88
+ }
89
+ }
90
+ /** 原子写(tmp + rename;与 memory-installer writeAtomic 同款,独立内联避免反向依赖) */
91
+ function writeAtomic(path, text) {
92
+ mkdirSync(dirname(path), { recursive: true });
93
+ const tmp = `${path}.tmp-${Date.now()}`;
94
+ writeFileSync(tmp, text, 'utf8');
95
+ renameSync(tmp, path);
96
+ }
97
+ function saveVault(v) {
98
+ writeAtomic(VAULT_FILE, JSON.stringify(v, null, 2));
99
+ }
100
+ function removeVaultEntry(uid) {
101
+ const v = loadVault();
102
+ if (!(uid in v))
103
+ return;
104
+ delete v[uid];
105
+ saveVault(v);
106
+ }
107
+ /** DPAPI protect/unprotect(PowerShell stdin 收发 base64——明文不上命令行,进程列表不可见;
108
+ * CurrentUser 域绑定 = 同机同账户可解,符合「机器绑定加密」约束)。非 Windows 返回 null(调用方降级) */
109
+ function dpapi(mode, b64) {
110
+ if (process.platform !== 'win32')
111
+ return Promise.resolve(null);
112
+ const script = mode === 'protect'
113
+ ? `Add-Type -AssemblyName System.Security;$b=[Convert]::FromBase64String([Console]::In.ReadToEnd().Trim());$p=[System.Security.Cryptography.ProtectedData]::Protect($b,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);[Console]::Out.Write([Convert]::ToBase64String($p))`
114
+ : `Add-Type -AssemblyName System.Security;$b=[Convert]::FromBase64String([Console]::In.ReadToEnd().Trim());$u=[System.Security.Cryptography.ProtectedData]::Unprotect($b,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);[Console]::Out.Write([Convert]::ToBase64String($u))`;
115
+ return new Promise((resolve) => {
116
+ try {
117
+ const p = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] });
118
+ let out = '', err = '';
119
+ const timer = setTimeout(() => { try {
120
+ p.kill();
121
+ }
122
+ catch { /* 已退出 */ } resolve(null); }, 15_000);
123
+ p.stdout.on('data', (d) => { out += String(d); });
124
+ p.stderr.on('data', (d) => { err += String(d); });
125
+ p.on('error', () => { clearTimeout(timer); resolve(null); });
126
+ p.on('close', (code) => {
127
+ clearTimeout(timer);
128
+ resolve(code === 0 && out.trim() ? out.trim() : null);
129
+ if (code !== 0)
130
+ console.warn(`[memory-users] DPAPI ${mode} 失败(退出码 ${code}):${err.trim().slice(0, 200)}`);
131
+ });
132
+ p.stdin.write(b64);
133
+ p.stdin.end();
134
+ }
135
+ catch {
136
+ resolve(null);
137
+ }
138
+ });
139
+ }
140
+ /** 密钥入保险库:Windows DPAPI 加密;其他平台降级 base64 混淆 + 控制台明示(方案适用面为 Windows)。
141
+ * 明文进程内缓存:列表每次聚合都会回带 api_key——值未变时跳过 DPAPI 重加密(省一次 PowerShell 进程) */
142
+ const vaultSeenPlain = new Map();
143
+ async function vaultStore(uid, plain) {
144
+ if (vaultSeenPlain.get(uid) === plain && loadVault()[uid])
145
+ return;
146
+ const v = loadVault();
147
+ if (process.platform === 'win32') {
148
+ const b64 = Buffer.from(plain, 'utf8').toString('base64');
149
+ const sealed = await dpapi('protect', b64);
150
+ if (sealed) {
151
+ v[uid] = sealed;
152
+ saveVault(v);
153
+ vaultSeenPlain.set(uid, plain);
154
+ return;
155
+ }
156
+ console.warn('[memory-users] DPAPI 保护失败,降级 base64 混淆存储(建议检查 PowerShell 可用性)');
157
+ v[uid] = `plain:${b64}`;
158
+ }
159
+ else {
160
+ v[uid] = `plain:${Buffer.from(plain, 'utf8').toString('base64')}`;
161
+ }
162
+ saveVault(v);
163
+ vaultSeenPlain.set(uid, plain);
164
+ }
165
+ /** 取密钥明文(reveal / 打开记忆空间用;解不开返回 undefined) */
166
+ async function vaultRead(uid) {
167
+ const sealed = loadVault()[uid];
168
+ if (!sealed)
169
+ return undefined;
170
+ if (sealed.startsWith('plain:')) {
171
+ try {
172
+ return Buffer.from(sealed.slice(6), 'base64').toString('utf8');
173
+ }
174
+ catch {
175
+ return undefined;
176
+ }
177
+ }
178
+ const b64 = await dpapi('unprotect', sealed);
179
+ if (!b64)
180
+ return undefined;
181
+ try {
182
+ return Buffer.from(b64, 'base64').toString('utf8');
183
+ }
184
+ catch {
185
+ return undefined;
186
+ }
187
+ }
188
+ // ============ 磁盘元信息(体量 / 最近活跃) ============
189
+ /** user 目录(方案 §五-P3:data/viking/<account>/user/<uid>/ 为明文,可直接统计/打包) */
190
+ export function memoryUserDir(account, uid) {
191
+ return join(MEMORY_MANAGED_DIR, 'data', 'viking', account, 'user', uid);
192
+ }
193
+ /** 递归统计目录体量与最近修改(容错:目录不存在/单文件 stat 失败均按 0 继续) */
194
+ function dirStats(dir) {
195
+ let size = 0;
196
+ let last = 0;
197
+ const walk = (d) => {
198
+ let entries;
199
+ try {
200
+ entries = readdirSync(d, { withFileTypes: true });
201
+ }
202
+ catch {
203
+ return;
204
+ }
205
+ for (const e of entries) {
206
+ const p = join(d, e.name);
207
+ if (e.isDirectory())
208
+ walk(p);
209
+ else {
210
+ try {
211
+ const st = statSync(p);
212
+ size += st.size;
213
+ if (st.mtimeMs > last)
214
+ last = st.mtimeMs;
215
+ }
216
+ catch { /* 竞态删除/权限:跳过该文件 */ }
217
+ }
218
+ }
219
+ };
220
+ if (existsSync(dir))
221
+ walk(dir);
222
+ return { sizeBytes: size, ...(last ? { lastActive: new Date(last).toISOString() } : {}) };
223
+ }
224
+ /** 用户列表聚合:Admin API + 本地磁盘统计。全程容错(服务不可达返回可达性标记,不抛出) */
225
+ export async function listMemoryUsers(config) {
226
+ const memory = config.memory;
227
+ const account = memory?.account ?? 'lcb';
228
+ const empty = { reachable: false, users: [], totalBytes: 0, authInitNeeded: false };
229
+ if (!memory)
230
+ return { ...empty, error: 'memory 段未配置' };
231
+ const health = await probeMemoryHealth(memoryEndpoint(memory));
232
+ if (!health.healthy)
233
+ return { ...empty, error: health.detail ?? '服务不可达', ...(health.authMode ? { authMode: health.authMode } : {}) };
234
+ if (health.authMode !== 'api_key') {
235
+ // dev 模式:Admin API 被服务端禁用(403)——这是「无需用户密钥」的正常形态而非错误
236
+ return { reachable: true, authMode: health.authMode, users: [], totalBytes: 0, authInitNeeded: false };
237
+ }
238
+ const rootKey = resolveRootKey(memory);
239
+ if (!rootKey)
240
+ return { ...empty, authMode: health.authMode, error: 'root key 未配置(memory.api_key 与受管 ov.conf 均未见)——请先「检查修复」对齐鉴权' };
241
+ const r = await adminCall(memory, rootKey, 'GET', adminPath(memory, '/users'));
242
+ if (!r.ok) {
243
+ return { ...empty, authMode: health.authMode, error: `用户列表获取失败:HTTP ${r.status}${r.message ? `(${r.message})` : ''}` };
244
+ }
245
+ const rawUsers = (Array.isArray(r.json.result) ? r.json.result : []);
246
+ const botNames = new Map(config.apps.map((a) => [a.appId, a.name]));
247
+ const rows = [];
248
+ for (const u of rawUsers) {
249
+ const uid = String(u.user_id ?? '');
250
+ if (!uid)
251
+ continue;
252
+ // 保险库自愈:列表明文回带的 api_key 刷入保险库(DPAPI 重加密)——重装/换 Key 后无须重初始化
253
+ if (typeof u.api_key === 'string' && u.api_key)
254
+ await vaultStore(uid, u.api_key);
255
+ const kind = uid === WEB_USER_ID ? 'web' : botNames.has(uid) ? 'bot' : 'other';
256
+ // 聊天者维度(历史复合 ID:{appId}-{ou_xxx},插件旧口径创建):前缀归属到对应飞书应用,
257
+ // 页面显示「<应用名> · 聊天者」——OpenViking 无显示名字段(user_id 仅 ASCII),1933 右上角
258
+ // 只能显示技术 ID,人可读对照以本列表为准
259
+ const ownerBot = kind === 'other'
260
+ ? [...botNames.entries()].find(([appId]) => uid.startsWith(`${appId}-`))
261
+ : undefined;
262
+ const stats = dirStats(memoryUserDir(account, uid));
263
+ rows.push({
264
+ userId: uid,
265
+ kind,
266
+ ...(kind === 'bot' ? { appName: botNames.get(uid) } : ownerBot ? { appName: `${ownerBot[1]} · 聊天者` } : {}),
267
+ ...(u.role ? { role: u.role } : {}),
268
+ keyAvailable: typeof u.api_key === 'string' && !!u.api_key,
269
+ ...(typeof u.api_key === 'string' && u.api_key ? { keyHint: maskSecret(u.api_key).secretHint } : {}),
270
+ sizeBytes: stats.sizeBytes,
271
+ ...(stats.lastActive ? { lastActive: stats.lastActive } : {}),
272
+ });
273
+ }
274
+ return {
275
+ reachable: true,
276
+ authMode: health.authMode,
277
+ users: rows,
278
+ totalBytes: rows.reduce((s, r2) => s + (r2.sizeBytes ?? 0), 0),
279
+ // 待初始化:api_key 模式跑着但网页端用户还没建(浏览器打开 1933 会 401 重定向的根因)
280
+ authInitNeeded: !rows.some((x) => x.userId === WEB_USER_ID),
281
+ };
282
+ }
283
+ /** 一键初始化(方案 §六:建 web 用户 + 各 bot 用户 → 签 key → 加密落盘)。幂等可重跑:
284
+ * 已存在用户由列表自愈回读 key(创建响应的 user_key 只出现一次,但 api_key 模式列表可回带)。
285
+ * dev 模式 / 服务不可达:直接说明,不视为失败动作。 */
286
+ export async function initMemoryUsers(config) {
287
+ const memory = config.memory;
288
+ const out = { ok: false, created: [], existing: [], errors: [], detail: '' };
289
+ if (!memory)
290
+ return { ...out, errors: ['memory 段未配置'], detail: '请先开启记忆增强并保存模型配置' };
291
+ const health = await probeMemoryHealth(memoryEndpoint(memory));
292
+ if (!health.healthy)
293
+ return { ...out, errors: [`服务不可达(${health.detail ?? '未知原因'})`], detail: '请先在「服务与配置」拉起服务,再一键初始化' };
294
+ if (health.authMode !== 'api_key') {
295
+ return {
296
+ ...out, ok: true,
297
+ detail: `服务为 ${health.authMode ?? '未知'} 模式:本机回环免鉴权、身份经命名空间隔离,无须创建用户密钥(部署走向 api_key 模式后再初始化)`,
298
+ };
299
+ }
300
+ const rootKey = resolveRootKey(memory);
301
+ if (!rootKey)
302
+ return { ...out, errors: ['root key 未配置'], detail: '「检查修复」可对齐 memory.api_key 与受管 ov.conf' };
303
+ // 期望用户:web + 每个 bot(appId 即 OpenViking 侧 user_id,与 ensureMemoryUserKeys 签发口径一致)
304
+ const desired = [WEB_USER_ID, ...config.apps.map((a) => a.appId)];
305
+ // 先列表自愈:已存在用户的 key 直接刷保险库(避免对已存在用户重复创建撞 409)
306
+ const listR = await adminCall(memory, rootKey, 'GET', adminPath(memory, '/users'));
307
+ const known = new Set();
308
+ if (listR.ok && Array.isArray(listR.json.result)) {
309
+ for (const u of listR.json.result) {
310
+ const uid = String(u.user_id ?? '');
311
+ if (uid)
312
+ known.add(uid);
313
+ if (uid && typeof u.api_key === 'string' && u.api_key)
314
+ await vaultStore(uid, u.api_key);
315
+ }
316
+ }
317
+ for (const uid of desired) {
318
+ if (!isValidUserId(uid)) {
319
+ out.errors.push(`用户名 ${uid} 含非法字符,跳过`);
320
+ continue;
321
+ }
322
+ if (known.has(uid)) {
323
+ out.existing.push(uid);
324
+ continue;
325
+ }
326
+ const r = await adminCall(memory, rootKey, 'POST', adminPath(memory, '/users'), { user_id: uid, role: 'user' });
327
+ const result = (r.json.result ?? {});
328
+ if (r.ok && typeof result.user_key === 'string' && result.user_key) {
329
+ await vaultStore(uid, result.user_key);
330
+ out.created.push(uid);
331
+ }
332
+ else if (r.ok) {
333
+ // 创建成功但响应未带 key(should_expose_user_key=false 等服务端形态)→ 列表自愈兜底
334
+ out.created.push(uid);
335
+ out.errors.push(`${uid} 已创建但响应未含密钥——已尝试从用户列表回读`);
336
+ }
337
+ else {
338
+ out.errors.push(`创建 ${uid} 失败:HTTP ${r.status}${r.message ? `(${r.message})` : ''}`);
339
+ }
340
+ }
341
+ out.ok = out.errors.length === 0;
342
+ out.detail = `初始化完成:新建 ${out.created.length ? out.created.join('、') : '无'};已存在 ${out.existing.length ? out.existing.join('、') : '无'}${out.errors.length ? `;${out.errors.length} 项异常` : ''}`;
343
+ auditMemory('init', { created: out.created, existing: out.existing, errors: out.errors });
344
+ return out;
345
+ }
346
+ /** 取某用户密钥明文(单次放行 + 审计留痕)。保险库未命中时尝试列表自愈回读(服务端 api_key
347
+ * 模式列表可回带明文),再未命中提示重置。root key / 他人密钥的边界:本接口只服务本机配置页 */
348
+ export async function revealUserKey(config, uid) {
349
+ if (!isValidUserId(uid))
350
+ return { ok: false, error: '用户名非法' };
351
+ const memory = config.memory;
352
+ if (!memory)
353
+ return { ok: false, error: 'memory 段未配置' };
354
+ const cached = await vaultRead(uid);
355
+ if (cached) {
356
+ auditMemory('reveal', { user_id: uid, source: 'vault' });
357
+ return { ok: true, key: cached };
358
+ }
359
+ // 自愈:服务端列表回读 → 刷保险库
360
+ const rootKey = resolveRootKey(memory);
361
+ if (rootKey) {
362
+ const r = await adminCall(memory, rootKey, 'GET', adminPath(memory, '/users'));
363
+ if (r.ok && Array.isArray(r.json.result)) {
364
+ const hit = r.json.result.find((u) => String(u.user_id ?? '') === uid);
365
+ if (hit && typeof hit.api_key === 'string' && hit.api_key) {
366
+ await vaultStore(uid, hit.api_key);
367
+ auditMemory('reveal', { user_id: uid, source: 'admin-list' });
368
+ return { ok: true, key: hit.api_key };
369
+ }
370
+ }
371
+ }
372
+ auditMemory('reveal-miss', { user_id: uid });
373
+ return { ok: false, error: '本地保险库无此密钥且服务端未回带——请用「重置」重新签发(旧使用方需同步更新)' };
374
+ }
375
+ // ============ 重置(Regenerate) ============
376
+ /** 重置密钥(Admin Regenerate:旧 key 立即失效,新 key 入保险库)。方案 §六:危险操作,
377
+ * 前端二次确认 + 提示「所有使用方需同步更新 + 重启会话」;此处负责执行与留痕 */
378
+ export async function resetUserKey(config, uid) {
379
+ if (!isValidUserId(uid))
380
+ return { ok: false, error: '用户名非法' };
381
+ const memory = config.memory;
382
+ if (!memory)
383
+ return { ok: false, error: 'memory 段未配置' };
384
+ const rootKey = resolveRootKey(memory);
385
+ if (!rootKey)
386
+ return { ok: false, error: 'root key 未配置' };
387
+ const r = await adminCall(memory, rootKey, 'POST', adminPath(memory, `/users/${encodeURIComponent(uid)}/key`), {});
388
+ const result = (r.json.result ?? {});
389
+ if (r.ok && typeof result.user_key === 'string' && result.user_key) {
390
+ await vaultStore(uid, result.user_key);
391
+ auditMemory('reset', { user_id: uid });
392
+ return { ok: true, key: result.user_key };
393
+ }
394
+ auditMemory('reset-fail', { user_id: uid, status: r.status });
395
+ return { ok: false, error: `重置失败:HTTP ${r.status}${r.message ? `(${r.message})` : ''}` };
396
+ }
397
+ // ============ 删除(备份前置) ============
398
+ /** 打包目录为压缩档(Windows bsdtar -a 依扩展名产 zip;其他平台 GNU tar 产 tar.gz)。
399
+ * 返回备份文件路径。tar 失败抛错(调用方放弃删除——备份前置是硬约束) */
400
+ async function backupUserDir(account, uid) {
401
+ const srcDir = memoryUserDir(account, uid);
402
+ if (!existsSync(srcDir))
403
+ return ''; // 无数据目录(纯密钥用户):无须备份
404
+ mkdirSync(MEMORY_BACKUP_DIR, { recursive: true });
405
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
406
+ const isWin = process.platform === 'win32';
407
+ const archive = join(MEMORY_BACKUP_DIR, `${uid}-${stamp}${isWin ? '.zip' : '.tar.gz'}`);
408
+ const tarCmd = isWin ? join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'tar.exe') : 'tar';
409
+ const args = isWin ? ['-a', '-c', '-f', archive, '-C', dirname(srcDir), uid] : ['-czf', archive, '-C', dirname(srcDir), uid];
410
+ await new Promise((resolve, reject) => {
411
+ const p = spawn(tarCmd, args, { windowsHide: true, stdio: ['ignore', 'ignore', 'pipe'] });
412
+ let err = '';
413
+ p.stderr?.on('data', (d) => { err += String(d); });
414
+ p.on('error', reject);
415
+ p.on('close', (code) => code === 0 ? resolve() : reject(new Error(`备份打包失败(退出码 ${code}):${err.trim().slice(0, 200)}`)));
416
+ });
417
+ if (!existsSync(archive))
418
+ throw new Error('备份打包后未找到产物');
419
+ return archive;
420
+ }
421
+ /** 删除用户:先备份 user 目录到 backups/,再调 Admin 删除(202 异步清理),最后清保险库条目。
422
+ * 方案 §六/§七:二次确认在前端;备份前置在这里是硬约束(备份失败即中止删除) */
423
+ export async function deleteMemoryUser(config, uid) {
424
+ if (!isValidUserId(uid))
425
+ return { ok: false, detail: '用户名非法' };
426
+ const memory = config.memory;
427
+ if (!memory)
428
+ return { ok: false, detail: 'memory 段未配置' };
429
+ const rootKey = resolveRootKey(memory);
430
+ if (!rootKey)
431
+ return { ok: false, detail: 'root key 未配置' };
432
+ const account = memory.account ?? 'lcb';
433
+ // 备份前置:打包失败绝不删除(磁盘明文数据是唯一正本)
434
+ let backupPath;
435
+ try {
436
+ const p = await backupUserDir(account, uid);
437
+ if (p)
438
+ backupPath = p;
439
+ }
440
+ catch (e) {
441
+ auditMemory('delete-abort', { user_id: uid, reason: e instanceof Error ? e.message : String(e) });
442
+ return { ok: false, detail: `已中止删除——备份失败:${e instanceof Error ? e.message : String(e)}` };
443
+ }
444
+ const r = await adminCall(memory, rootKey, 'DELETE', adminPath(memory, `/users/${encodeURIComponent(uid)}`));
445
+ if (!r.ok && r.status !== 202) {
446
+ auditMemory('delete-fail', { user_id: uid, status: r.status });
447
+ return { ok: false, detail: `删除失败:HTTP ${r.status}${r.message ? `(${r.message})` : ''}${backupPath ? `;数据备份在 ${backupPath}` : ''}` };
448
+ }
449
+ removeVaultEntry(uid);
450
+ auditMemory('delete', { user_id: uid, ...(backupPath ? { backupPath } : {}) });
451
+ return {
452
+ ok: true,
453
+ detail: `已删除 ${uid}(服务端异步清理数据)${backupPath ? `;数据备份:${backupPath}` : '(该用户无本地记忆数据)'}`,
454
+ ...(backupPath ? { backupPath } : {}),
455
+ };
456
+ }
457
+ /** 保险库脱敏概览(审计/排障用:只回 uid 与是否在库,绝不回明文) */
458
+ export function vaultOverview() {
459
+ return Object.entries(loadVault()).map(([userId, sealed]) => ({ userId, sealed: !sealed.startsWith('plain:') }));
460
+ }
461
+ //# sourceMappingURL=memory-users-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-users-api.js","sourceRoot":"","sources":["../../src/web/memory-users-api.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,7 @@ import { bridgeStatus, resolveLcbEntry, restartBridgeWithHelper, spawnBridgeDeta
29
29
  import { getAutostartStatus, setAutostartEnabled } from './autostart.js';
30
30
  import { checkUpdate, hasNewerVersion, installMode, runUpdate } from './update.js';
31
31
  import { memoryStatus, testMemoryConnection } from './memory-api.js';
32
+ import { deleteMemoryUser, initMemoryUsers, listMemoryUsers, resetUserKey, revealUserKey, } from './memory-users-api.js';
32
33
  import { MANAGED_OV_CONF, getInstallState, runInstall, runStartPipeline, stopManagedServer } from '../memory-installer.js';
33
34
  import { checkLarkCliAuth, checkLarkCliLatest, detectLarkCli, detectLarkCliSkill, getLarkCliConfigStatus, getLarkCliDeviceStatus, runLarkCliActionFlow, startLarkCliConfigFlow, startLarkCliDeviceAuth, } from '../lark-cli-manager.js';
34
35
  /** PUT /api/config 参与整段替换的顶级键;body 未携带的键保持磁盘原文(含注释)。
@@ -381,18 +382,19 @@ async function handle(req, res, ctx) {
381
382
  }
382
383
  }
383
384
  if (path === '/api/memory/test-connection' && req.method === 'POST') {
384
- // 表单现值直测(不落盘):把 Key/端点/模型名错误挡在开启之前(§6.8 双绿门禁)
385
+ // 表单现值直测(不落盘):把 Key/端点/模型名错误挡在开启之前(§6.8 双绿门禁)。
386
+ // P2 双端点拆分:主 Key 与向量 Key 分别回退各自已保存值(表单留空 = 测已保存值)
385
387
  const body = await readJsonBody(req);
386
- // Key 输入框脱敏不回显:表单 Key 留空时回退配置已保存值(firstRun/配置损坏按无保存值直测)
387
- let savedKey;
388
+ let saved;
388
389
  if (!firstRun) {
389
390
  try {
390
- savedKey = loadConfig(ctx.configPath).memory?.model?.apiKey;
391
+ const model = loadConfig(ctx.configPath).memory?.model;
392
+ saved = { apiKey: model?.apiKey, embeddingApiKey: model?.embeddingApiKey };
391
393
  }
392
394
  catch { /* 按无保存值直测 */ }
393
395
  }
394
396
  try {
395
- return json(res, 200, await testMemoryConnection(body, savedKey));
397
+ return json(res, 200, await testMemoryConnection(body, saved));
396
398
  }
397
399
  catch (e) {
398
400
  return json(res, 400, { error: e instanceof Error ? e.message : String(e) });
@@ -437,6 +439,77 @@ async function handle(req, res, ctx) {
437
439
  void runStartPipeline({ configPath: ctx.configPath });
438
440
  return json(res, 200, { ok: true, started: true, note: '启动流水线后台执行中,轮询 /api/memory/install/status 查看进度' });
439
441
  }
442
+ // P1 保存即生效(最终方案 §五-P1):模型配置保存后前端弹「立即生效」确认 → 本端点重渲染
443
+ // ov.conf + 强制重启受管服务(runInstall forceRestart——运行中的旧进程不会热加载新模型,
444
+ // 只重渲染不重启的「检查修复」对模型变更无效)。消灭「保存后还需手动检查修复」两步坑
445
+ if (path === '/api/memory/apply' && req.method === 'POST') {
446
+ if (firstRun)
447
+ return json(res, 404, { error: '尚无配置文件(首次安装),请先提交 bootstrap 向导' });
448
+ if (getInstallState().running)
449
+ return json(res, 409, { error: '流水线执行中,请等待当前流程完成' });
450
+ try {
451
+ const config = loadConfig(ctx.configPath);
452
+ if (!config.memory?.model)
453
+ return json(res, 400, { error: '记忆模型未配置——请先在「模型配置」卡填写并保存' });
454
+ void runInstall({ configPath: ctx.configPath, model: config.memory.model, pin: config.memory.version, forceRestart: true });
455
+ return json(res, 200, { ok: true, started: true, note: '应用流水线后台执行中(重渲染配置 → 重启服务 → 校验插件),轮询 /api/memory/install/status 查看进度' });
456
+ }
457
+ catch (e) {
458
+ return json(res, 400, { error: e instanceof Error ? e.message : String(e) });
459
+ }
460
+ }
461
+ // ---- P3「用户与密钥」Tab(最终方案 §六 Tab2):用户聚合 / 一键初始化 / 密钥 reveal·重置·删除 ----
462
+ // 全部走 root key Admin API;密钥明文只在 reveal 响应单次放行(前端进剪贴板)并留痕审计
463
+ if (path === '/api/memory/users' && req.method === 'GET') {
464
+ if (firstRun)
465
+ return json(res, 404, { error: '尚无配置文件(首次安装),请先提交 bootstrap 向导' });
466
+ try {
467
+ return json(res, 200, await listMemoryUsers(loadConfig(ctx.configPath)));
468
+ }
469
+ catch (e) {
470
+ return json(res, 500, { error: `用户列表聚合失败:${e instanceof Error ? e.message : String(e)}` });
471
+ }
472
+ }
473
+ if (path === '/api/memory/users/init' && req.method === 'POST') {
474
+ if (firstRun)
475
+ return json(res, 404, { error: '尚无配置文件(首次安装),请先提交 bootstrap 向导' });
476
+ try {
477
+ return json(res, 200, await initMemoryUsers(loadConfig(ctx.configPath)));
478
+ }
479
+ catch (e) {
480
+ return json(res, 500, { error: `一键初始化失败:${e instanceof Error ? e.message : String(e)}` });
481
+ }
482
+ }
483
+ if (path === '/api/memory/users/reveal' && req.method === 'POST') {
484
+ const body = await readJsonBody(req);
485
+ const uid = typeof body.user_id === 'string' ? body.user_id : '';
486
+ try {
487
+ return json(res, 200, await revealUserKey(loadConfig(ctx.configPath), uid));
488
+ }
489
+ catch (e) {
490
+ return json(res, 500, { error: e instanceof Error ? e.message : String(e) });
491
+ }
492
+ }
493
+ if (path === '/api/memory/users/reset' && req.method === 'POST') {
494
+ const body = await readJsonBody(req);
495
+ const uid = typeof body.user_id === 'string' ? body.user_id : '';
496
+ try {
497
+ return json(res, 200, await resetUserKey(loadConfig(ctx.configPath), uid));
498
+ }
499
+ catch (e) {
500
+ return json(res, 500, { error: e instanceof Error ? e.message : String(e) });
501
+ }
502
+ }
503
+ if (path === '/api/memory/users/delete' && req.method === 'POST') {
504
+ const body = await readJsonBody(req);
505
+ const uid = typeof body.user_id === 'string' ? body.user_id : '';
506
+ try {
507
+ return json(res, 200, await deleteMemoryUser(loadConfig(ctx.configPath), uid));
508
+ }
509
+ catch (e) {
510
+ return json(res, 500, { error: e instanceof Error ? e.message : String(e) });
511
+ }
512
+ }
440
513
  // ---- 模型列表拉取(厂商无关;凭证用磁盘现值或本次表单输入,响应只含模型 id,绝不回传凭证) ----
441
514
  if (path === '/api/models/fetch' && req.method === 'POST') {
442
515
  if (firstRun)