@askexenow/exe-os 0.8.83 → 0.8.85
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/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/lib/cloud-sync.js
CHANGED
|
@@ -246,6 +246,443 @@ var init_employees = __esm({
|
|
|
246
246
|
}
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
+
// src/lib/exe-daemon-client.ts
|
|
250
|
+
import net from "net";
|
|
251
|
+
import { spawn } from "child_process";
|
|
252
|
+
import { randomUUID } from "crypto";
|
|
253
|
+
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
254
|
+
import path3 from "path";
|
|
255
|
+
import { fileURLToPath } from "url";
|
|
256
|
+
function handleData(chunk) {
|
|
257
|
+
_buffer += chunk.toString();
|
|
258
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
259
|
+
_buffer = "";
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
let newlineIdx;
|
|
263
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
264
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
265
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
266
|
+
if (!line) continue;
|
|
267
|
+
try {
|
|
268
|
+
const response = JSON.parse(line);
|
|
269
|
+
const id = response.id;
|
|
270
|
+
if (!id) continue;
|
|
271
|
+
const entry = _pending.get(id);
|
|
272
|
+
if (entry) {
|
|
273
|
+
clearTimeout(entry.timer);
|
|
274
|
+
_pending.delete(id);
|
|
275
|
+
entry.resolve(response);
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function cleanupStaleFiles() {
|
|
282
|
+
if (existsSync3(PID_PATH)) {
|
|
283
|
+
try {
|
|
284
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
285
|
+
if (pid > 0) {
|
|
286
|
+
try {
|
|
287
|
+
process.kill(pid, 0);
|
|
288
|
+
return;
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
unlinkSync2(PID_PATH);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
unlinkSync2(SOCKET_PATH);
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function findPackageRoot() {
|
|
305
|
+
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
306
|
+
const { root } = path3.parse(dir);
|
|
307
|
+
while (dir !== root) {
|
|
308
|
+
if (existsSync3(path3.join(dir, "package.json"))) return dir;
|
|
309
|
+
dir = path3.dirname(dir);
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
function spawnDaemon() {
|
|
314
|
+
const pkgRoot = findPackageRoot();
|
|
315
|
+
if (!pkgRoot) {
|
|
316
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
320
|
+
if (!existsSync3(daemonPath)) {
|
|
321
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
322
|
+
`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const resolvedPath = daemonPath;
|
|
326
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
327
|
+
`);
|
|
328
|
+
const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
|
|
329
|
+
let stderrFd = "ignore";
|
|
330
|
+
try {
|
|
331
|
+
stderrFd = openSync(logPath, "a");
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
335
|
+
detached: true,
|
|
336
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
337
|
+
env: {
|
|
338
|
+
...process.env,
|
|
339
|
+
TMUX: void 0,
|
|
340
|
+
// Daemon is global — must not inherit session scope
|
|
341
|
+
TMUX_PANE: void 0,
|
|
342
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
343
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
344
|
+
EXE_DAEMON_PID: PID_PATH
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
child.unref();
|
|
348
|
+
if (typeof stderrFd === "number") {
|
|
349
|
+
try {
|
|
350
|
+
closeSync(stderrFd);
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function acquireSpawnLock() {
|
|
356
|
+
try {
|
|
357
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
358
|
+
closeSync(fd);
|
|
359
|
+
return true;
|
|
360
|
+
} catch {
|
|
361
|
+
try {
|
|
362
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
363
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
364
|
+
try {
|
|
365
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
370
|
+
closeSync(fd);
|
|
371
|
+
return true;
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function releaseSpawnLock() {
|
|
381
|
+
try {
|
|
382
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function connectToSocket() {
|
|
387
|
+
return new Promise((resolve) => {
|
|
388
|
+
if (_socket && _connected) {
|
|
389
|
+
resolve(true);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
393
|
+
const connectTimeout = setTimeout(() => {
|
|
394
|
+
socket.destroy();
|
|
395
|
+
resolve(false);
|
|
396
|
+
}, 2e3);
|
|
397
|
+
socket.on("connect", () => {
|
|
398
|
+
clearTimeout(connectTimeout);
|
|
399
|
+
_socket = socket;
|
|
400
|
+
_connected = true;
|
|
401
|
+
_buffer = "";
|
|
402
|
+
socket.on("data", handleData);
|
|
403
|
+
socket.on("close", () => {
|
|
404
|
+
_connected = false;
|
|
405
|
+
_socket = null;
|
|
406
|
+
for (const [id, entry] of _pending) {
|
|
407
|
+
clearTimeout(entry.timer);
|
|
408
|
+
_pending.delete(id);
|
|
409
|
+
entry.resolve({ error: "Connection closed" });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
socket.on("error", () => {
|
|
413
|
+
_connected = false;
|
|
414
|
+
_socket = null;
|
|
415
|
+
});
|
|
416
|
+
resolve(true);
|
|
417
|
+
});
|
|
418
|
+
socket.on("error", () => {
|
|
419
|
+
clearTimeout(connectTimeout);
|
|
420
|
+
resolve(false);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function connectEmbedDaemon() {
|
|
425
|
+
if (_socket && _connected) return true;
|
|
426
|
+
if (await connectToSocket()) return true;
|
|
427
|
+
if (acquireSpawnLock()) {
|
|
428
|
+
try {
|
|
429
|
+
cleanupStaleFiles();
|
|
430
|
+
spawnDaemon();
|
|
431
|
+
} finally {
|
|
432
|
+
releaseSpawnLock();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const start = Date.now();
|
|
436
|
+
let delay2 = 100;
|
|
437
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
438
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
439
|
+
if (await connectToSocket()) return true;
|
|
440
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
445
|
+
return new Promise((resolve) => {
|
|
446
|
+
if (!_socket || !_connected) {
|
|
447
|
+
resolve({ error: "Not connected" });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const id = randomUUID();
|
|
451
|
+
const timer = setTimeout(() => {
|
|
452
|
+
_pending.delete(id);
|
|
453
|
+
resolve({ error: "Request timeout" });
|
|
454
|
+
}, timeoutMs);
|
|
455
|
+
_pending.set(id, { resolve, timer });
|
|
456
|
+
try {
|
|
457
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
458
|
+
} catch {
|
|
459
|
+
clearTimeout(timer);
|
|
460
|
+
_pending.delete(id);
|
|
461
|
+
resolve({ error: "Write failed" });
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
function isClientConnected() {
|
|
466
|
+
return _connected;
|
|
467
|
+
}
|
|
468
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
469
|
+
var init_exe_daemon_client = __esm({
|
|
470
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
471
|
+
"use strict";
|
|
472
|
+
init_config();
|
|
473
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
|
|
474
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
|
|
475
|
+
SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
476
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
477
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
478
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
479
|
+
_socket = null;
|
|
480
|
+
_connected = false;
|
|
481
|
+
_buffer = "";
|
|
482
|
+
_pending = /* @__PURE__ */ new Map();
|
|
483
|
+
MAX_BUFFER = 1e7;
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// src/lib/daemon-protocol.ts
|
|
488
|
+
function serializeValue(v) {
|
|
489
|
+
if (v === null || v === void 0) return null;
|
|
490
|
+
if (typeof v === "bigint") return Number(v);
|
|
491
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
492
|
+
if (v instanceof Uint8Array) {
|
|
493
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
494
|
+
}
|
|
495
|
+
if (ArrayBuffer.isView(v)) {
|
|
496
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
497
|
+
}
|
|
498
|
+
if (v instanceof ArrayBuffer) {
|
|
499
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
500
|
+
}
|
|
501
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
502
|
+
return String(v);
|
|
503
|
+
}
|
|
504
|
+
function deserializeValue(v) {
|
|
505
|
+
if (v === null) return null;
|
|
506
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
507
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
508
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
509
|
+
}
|
|
510
|
+
return v;
|
|
511
|
+
}
|
|
512
|
+
function deserializeResultSet(srs) {
|
|
513
|
+
const rows = srs.rows.map((obj) => {
|
|
514
|
+
const values = srs.columns.map(
|
|
515
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
516
|
+
);
|
|
517
|
+
const row = values;
|
|
518
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
519
|
+
const col = srs.columns[i];
|
|
520
|
+
if (col !== void 0) {
|
|
521
|
+
row[col] = values[i] ?? null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
Object.defineProperty(row, "length", {
|
|
525
|
+
value: values.length,
|
|
526
|
+
enumerable: false
|
|
527
|
+
});
|
|
528
|
+
return row;
|
|
529
|
+
});
|
|
530
|
+
return {
|
|
531
|
+
columns: srs.columns,
|
|
532
|
+
columnTypes: srs.columnTypes ?? [],
|
|
533
|
+
rows,
|
|
534
|
+
rowsAffected: srs.rowsAffected,
|
|
535
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
536
|
+
toJSON: () => ({
|
|
537
|
+
columns: srs.columns,
|
|
538
|
+
columnTypes: srs.columnTypes ?? [],
|
|
539
|
+
rows: srs.rows,
|
|
540
|
+
rowsAffected: srs.rowsAffected,
|
|
541
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
542
|
+
})
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
var init_daemon_protocol = __esm({
|
|
546
|
+
"src/lib/daemon-protocol.ts"() {
|
|
547
|
+
"use strict";
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// src/lib/db-daemon-client.ts
|
|
552
|
+
var db_daemon_client_exports = {};
|
|
553
|
+
__export(db_daemon_client_exports, {
|
|
554
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
555
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
556
|
+
});
|
|
557
|
+
function normalizeStatement(stmt) {
|
|
558
|
+
if (typeof stmt === "string") {
|
|
559
|
+
return { sql: stmt, args: [] };
|
|
560
|
+
}
|
|
561
|
+
const sql = stmt.sql;
|
|
562
|
+
let args = [];
|
|
563
|
+
if (Array.isArray(stmt.args)) {
|
|
564
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
565
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
566
|
+
const named = {};
|
|
567
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
568
|
+
named[key] = serializeValue(val);
|
|
569
|
+
}
|
|
570
|
+
return { sql, args: named };
|
|
571
|
+
}
|
|
572
|
+
return { sql, args };
|
|
573
|
+
}
|
|
574
|
+
function createDaemonDbClient(fallbackClient) {
|
|
575
|
+
let _useDaemon = false;
|
|
576
|
+
const client = {
|
|
577
|
+
async execute(stmt) {
|
|
578
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
579
|
+
return fallbackClient.execute(stmt);
|
|
580
|
+
}
|
|
581
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
582
|
+
const response = await sendDaemonRequest({
|
|
583
|
+
type: "db-execute",
|
|
584
|
+
sql,
|
|
585
|
+
args
|
|
586
|
+
});
|
|
587
|
+
if (response.error) {
|
|
588
|
+
const errMsg = String(response.error);
|
|
589
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
590
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
591
|
+
`);
|
|
592
|
+
return fallbackClient.execute(stmt);
|
|
593
|
+
}
|
|
594
|
+
throw new Error(errMsg);
|
|
595
|
+
}
|
|
596
|
+
if (response.db) {
|
|
597
|
+
return deserializeResultSet(response.db);
|
|
598
|
+
}
|
|
599
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
600
|
+
return fallbackClient.execute(stmt);
|
|
601
|
+
},
|
|
602
|
+
async batch(stmts, mode) {
|
|
603
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
604
|
+
return fallbackClient.batch(stmts, mode);
|
|
605
|
+
}
|
|
606
|
+
const statements = stmts.map(normalizeStatement);
|
|
607
|
+
const response = await sendDaemonRequest({
|
|
608
|
+
type: "db-batch",
|
|
609
|
+
statements,
|
|
610
|
+
mode: mode ?? "deferred"
|
|
611
|
+
});
|
|
612
|
+
if (response.error) {
|
|
613
|
+
const errMsg = String(response.error);
|
|
614
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
615
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
616
|
+
`);
|
|
617
|
+
return fallbackClient.batch(stmts, mode);
|
|
618
|
+
}
|
|
619
|
+
throw new Error(errMsg);
|
|
620
|
+
}
|
|
621
|
+
const batchResults = response["db-batch"];
|
|
622
|
+
if (batchResults) {
|
|
623
|
+
return batchResults.map(deserializeResultSet);
|
|
624
|
+
}
|
|
625
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
626
|
+
return fallbackClient.batch(stmts, mode);
|
|
627
|
+
},
|
|
628
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
629
|
+
async transaction(mode) {
|
|
630
|
+
return fallbackClient.transaction(mode);
|
|
631
|
+
},
|
|
632
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
633
|
+
async executeMultiple(sql) {
|
|
634
|
+
return fallbackClient.executeMultiple(sql);
|
|
635
|
+
},
|
|
636
|
+
// migrate — delegate to fallback
|
|
637
|
+
async migrate(stmts) {
|
|
638
|
+
return fallbackClient.migrate(stmts);
|
|
639
|
+
},
|
|
640
|
+
// Sync mode — delegate to fallback
|
|
641
|
+
sync() {
|
|
642
|
+
return fallbackClient.sync();
|
|
643
|
+
},
|
|
644
|
+
close() {
|
|
645
|
+
_useDaemon = false;
|
|
646
|
+
},
|
|
647
|
+
get closed() {
|
|
648
|
+
return fallbackClient.closed;
|
|
649
|
+
},
|
|
650
|
+
get protocol() {
|
|
651
|
+
return fallbackClient.protocol;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
return {
|
|
655
|
+
...client,
|
|
656
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
657
|
+
_enableDaemon() {
|
|
658
|
+
_useDaemon = true;
|
|
659
|
+
},
|
|
660
|
+
/** Check if daemon routing is active */
|
|
661
|
+
_isDaemonActive() {
|
|
662
|
+
return _useDaemon && isClientConnected();
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
667
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
668
|
+
const connected = await connectEmbedDaemon();
|
|
669
|
+
if (!connected) {
|
|
670
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
674
|
+
client._enableDaemon();
|
|
675
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
676
|
+
return client;
|
|
677
|
+
}
|
|
678
|
+
var init_db_daemon_client = __esm({
|
|
679
|
+
"src/lib/db-daemon-client.ts"() {
|
|
680
|
+
"use strict";
|
|
681
|
+
init_exe_daemon_client();
|
|
682
|
+
init_daemon_protocol();
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
249
686
|
// src/lib/database.ts
|
|
250
687
|
var database_exports = {};
|
|
251
688
|
__export(database_exports, {
|
|
@@ -254,6 +691,7 @@ __export(database_exports, {
|
|
|
254
691
|
ensureSchema: () => ensureSchema,
|
|
255
692
|
getClient: () => getClient,
|
|
256
693
|
getRawClient: () => getRawClient,
|
|
694
|
+
initDaemonClient: () => initDaemonClient,
|
|
257
695
|
initDatabase: () => initDatabase,
|
|
258
696
|
initTurso: () => initTurso,
|
|
259
697
|
isInitialized: () => isInitialized
|
|
@@ -281,8 +719,27 @@ function getClient() {
|
|
|
281
719
|
if (!_resilientClient) {
|
|
282
720
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
283
721
|
}
|
|
722
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
723
|
+
return _resilientClient;
|
|
724
|
+
}
|
|
725
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
726
|
+
return _daemonClient;
|
|
727
|
+
}
|
|
284
728
|
return _resilientClient;
|
|
285
729
|
}
|
|
730
|
+
async function initDaemonClient() {
|
|
731
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
732
|
+
if (!_resilientClient) return;
|
|
733
|
+
try {
|
|
734
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
735
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
736
|
+
} catch (err) {
|
|
737
|
+
process.stderr.write(
|
|
738
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
739
|
+
`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
286
743
|
function getRawClient() {
|
|
287
744
|
if (!_client) {
|
|
288
745
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -769,6 +1226,12 @@ async function ensureSchema() {
|
|
|
769
1226
|
} catch {
|
|
770
1227
|
}
|
|
771
1228
|
}
|
|
1229
|
+
try {
|
|
1230
|
+
await client.execute(
|
|
1231
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1232
|
+
);
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
772
1235
|
await client.executeMultiple(`
|
|
773
1236
|
CREATE TABLE IF NOT EXISTS entities (
|
|
774
1237
|
id TEXT PRIMARY KEY,
|
|
@@ -821,7 +1284,30 @@ async function ensureSchema() {
|
|
|
821
1284
|
entity_id TEXT NOT NULL,
|
|
822
1285
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
823
1286
|
);
|
|
1287
|
+
|
|
1288
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1289
|
+
name,
|
|
1290
|
+
content=entities,
|
|
1291
|
+
content_rowid=rowid
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1295
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1296
|
+
END;
|
|
1297
|
+
|
|
1298
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1299
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1300
|
+
END;
|
|
1301
|
+
|
|
1302
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1303
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1304
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1305
|
+
END;
|
|
824
1306
|
`);
|
|
1307
|
+
try {
|
|
1308
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1309
|
+
} catch {
|
|
1310
|
+
}
|
|
825
1311
|
await client.executeMultiple(`
|
|
826
1312
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
827
1313
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1002,6 +1488,33 @@ async function ensureSchema() {
|
|
|
1002
1488
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1003
1489
|
ON conversations(channel_id);
|
|
1004
1490
|
`);
|
|
1491
|
+
await client.executeMultiple(`
|
|
1492
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1493
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1494
|
+
agent_id TEXT NOT NULL,
|
|
1495
|
+
session_name TEXT,
|
|
1496
|
+
task_id TEXT,
|
|
1497
|
+
project_name TEXT,
|
|
1498
|
+
started_at TEXT NOT NULL
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1502
|
+
ON session_agent_map(agent_id);
|
|
1503
|
+
`);
|
|
1504
|
+
try {
|
|
1505
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1506
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1507
|
+
await client.execute({
|
|
1508
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1509
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1510
|
+
FROM memories
|
|
1511
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1512
|
+
GROUP BY session_id, agent_id`,
|
|
1513
|
+
args: []
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1005
1518
|
try {
|
|
1006
1519
|
await client.execute({
|
|
1007
1520
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1135,15 +1648,41 @@ async function ensureSchema() {
|
|
|
1135
1648
|
});
|
|
1136
1649
|
} catch {
|
|
1137
1650
|
}
|
|
1651
|
+
for (const col of [
|
|
1652
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1653
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1654
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1655
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1656
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1657
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1658
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1659
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1660
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1661
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1662
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1663
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1664
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1665
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1666
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1667
|
+
]) {
|
|
1668
|
+
try {
|
|
1669
|
+
await client.execute(col);
|
|
1670
|
+
} catch {
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1138
1673
|
}
|
|
1139
1674
|
async function disposeDatabase() {
|
|
1675
|
+
if (_daemonClient) {
|
|
1676
|
+
_daemonClient.close();
|
|
1677
|
+
_daemonClient = null;
|
|
1678
|
+
}
|
|
1140
1679
|
if (_client) {
|
|
1141
1680
|
_client.close();
|
|
1142
1681
|
_client = null;
|
|
1143
1682
|
_resilientClient = null;
|
|
1144
1683
|
}
|
|
1145
1684
|
}
|
|
1146
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1685
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1147
1686
|
var init_database = __esm({
|
|
1148
1687
|
"src/lib/database.ts"() {
|
|
1149
1688
|
"use strict";
|
|
@@ -1151,11 +1690,238 @@ var init_database = __esm({
|
|
|
1151
1690
|
init_employees();
|
|
1152
1691
|
_client = null;
|
|
1153
1692
|
_resilientClient = null;
|
|
1693
|
+
_daemonClient = null;
|
|
1154
1694
|
initTurso = initDatabase;
|
|
1155
1695
|
disposeTurso = disposeDatabase;
|
|
1156
1696
|
}
|
|
1157
1697
|
});
|
|
1158
1698
|
|
|
1699
|
+
// src/lib/crdt-sync.ts
|
|
1700
|
+
var crdt_sync_exports = {};
|
|
1701
|
+
__export(crdt_sync_exports, {
|
|
1702
|
+
_setStatePath: () => _setStatePath,
|
|
1703
|
+
applyRemoteUpdate: () => applyRemoteUpdate,
|
|
1704
|
+
destroyCrdtDoc: () => destroyCrdtDoc,
|
|
1705
|
+
getDiffUpdate: () => getDiffUpdate,
|
|
1706
|
+
getFullState: () => getFullState,
|
|
1707
|
+
getStateVector: () => getStateVector,
|
|
1708
|
+
importExistingBehaviors: () => importExistingBehaviors,
|
|
1709
|
+
importExistingMemories: () => importExistingMemories,
|
|
1710
|
+
initCrdtDoc: () => initCrdtDoc,
|
|
1711
|
+
isCrdtSyncEnabled: () => isCrdtSyncEnabled,
|
|
1712
|
+
onUpdate: () => onUpdate,
|
|
1713
|
+
readAllBehaviors: () => readAllBehaviors,
|
|
1714
|
+
readAllMemories: () => readAllMemories,
|
|
1715
|
+
rebuildFromDb: () => rebuildFromDb
|
|
1716
|
+
});
|
|
1717
|
+
import * as Y from "yjs";
|
|
1718
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1719
|
+
import path5 from "path";
|
|
1720
|
+
import { homedir } from "os";
|
|
1721
|
+
function getStatePath() {
|
|
1722
|
+
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
1723
|
+
}
|
|
1724
|
+
function _setStatePath(p) {
|
|
1725
|
+
_statePathOverride = p;
|
|
1726
|
+
}
|
|
1727
|
+
function initCrdtDoc() {
|
|
1728
|
+
if (doc) return doc;
|
|
1729
|
+
doc = new Y.Doc();
|
|
1730
|
+
const sp = getStatePath();
|
|
1731
|
+
if (existsSync5(sp)) {
|
|
1732
|
+
try {
|
|
1733
|
+
const state = readFileSync5(sp);
|
|
1734
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
1735
|
+
} catch {
|
|
1736
|
+
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
1737
|
+
try {
|
|
1738
|
+
unlinkSync3(sp);
|
|
1739
|
+
} catch {
|
|
1740
|
+
}
|
|
1741
|
+
rebuildFromDb().catch((err) => {
|
|
1742
|
+
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
doc.on("update", () => {
|
|
1747
|
+
persistState();
|
|
1748
|
+
});
|
|
1749
|
+
return doc;
|
|
1750
|
+
}
|
|
1751
|
+
function getMemoriesMap() {
|
|
1752
|
+
const d = initCrdtDoc();
|
|
1753
|
+
return d.getMap("memories");
|
|
1754
|
+
}
|
|
1755
|
+
function getBehaviorsMap() {
|
|
1756
|
+
const d = initCrdtDoc();
|
|
1757
|
+
return d.getMap("behaviors");
|
|
1758
|
+
}
|
|
1759
|
+
function applyRemoteUpdate(update) {
|
|
1760
|
+
const d = initCrdtDoc();
|
|
1761
|
+
Y.applyUpdate(d, update);
|
|
1762
|
+
}
|
|
1763
|
+
function getFullState() {
|
|
1764
|
+
const d = initCrdtDoc();
|
|
1765
|
+
return Y.encodeStateAsUpdate(d);
|
|
1766
|
+
}
|
|
1767
|
+
function getDiffUpdate(remoteStateVector) {
|
|
1768
|
+
const d = initCrdtDoc();
|
|
1769
|
+
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
1770
|
+
}
|
|
1771
|
+
function getStateVector() {
|
|
1772
|
+
const d = initCrdtDoc();
|
|
1773
|
+
return Y.encodeStateVector(d);
|
|
1774
|
+
}
|
|
1775
|
+
function importExistingMemories(memories) {
|
|
1776
|
+
const map = getMemoriesMap();
|
|
1777
|
+
const d = initCrdtDoc();
|
|
1778
|
+
let imported = 0;
|
|
1779
|
+
d.transact(() => {
|
|
1780
|
+
for (const mem of memories) {
|
|
1781
|
+
if (!mem.id) continue;
|
|
1782
|
+
if (map.has(mem.id)) continue;
|
|
1783
|
+
const entry = new Y.Map();
|
|
1784
|
+
entry.set("id", mem.id);
|
|
1785
|
+
entry.set("agent_id", mem.agent_id ?? null);
|
|
1786
|
+
entry.set("agent_role", mem.agent_role ?? null);
|
|
1787
|
+
entry.set("session_id", mem.session_id ?? null);
|
|
1788
|
+
entry.set("timestamp", mem.timestamp ?? null);
|
|
1789
|
+
entry.set("tool_name", mem.tool_name ?? null);
|
|
1790
|
+
entry.set("project_name", mem.project_name ?? null);
|
|
1791
|
+
entry.set("has_error", mem.has_error ?? 0);
|
|
1792
|
+
entry.set("raw_text", mem.raw_text ?? "");
|
|
1793
|
+
entry.set("version", mem.version ?? 0);
|
|
1794
|
+
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
1795
|
+
entry.set("scope", mem.scope ?? "business");
|
|
1796
|
+
map.set(mem.id, entry);
|
|
1797
|
+
imported++;
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
return imported;
|
|
1801
|
+
}
|
|
1802
|
+
function importExistingBehaviors(behaviors) {
|
|
1803
|
+
const map = getBehaviorsMap();
|
|
1804
|
+
const d = initCrdtDoc();
|
|
1805
|
+
let imported = 0;
|
|
1806
|
+
d.transact(() => {
|
|
1807
|
+
for (const beh of behaviors) {
|
|
1808
|
+
if (!beh.id) continue;
|
|
1809
|
+
if (map.has(beh.id)) continue;
|
|
1810
|
+
const entry = new Y.Map();
|
|
1811
|
+
entry.set("id", beh.id);
|
|
1812
|
+
entry.set("agent_id", beh.agent_id ?? null);
|
|
1813
|
+
entry.set("project_name", beh.project_name ?? null);
|
|
1814
|
+
entry.set("domain", beh.domain ?? null);
|
|
1815
|
+
entry.set("content", beh.content ?? null);
|
|
1816
|
+
entry.set("active", beh.active ?? 1);
|
|
1817
|
+
entry.set("priority", beh.priority ?? "p1");
|
|
1818
|
+
entry.set("created_at", beh.created_at ?? null);
|
|
1819
|
+
entry.set("updated_at", beh.updated_at ?? null);
|
|
1820
|
+
map.set(beh.id, entry);
|
|
1821
|
+
imported++;
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
return imported;
|
|
1825
|
+
}
|
|
1826
|
+
function readAllMemories() {
|
|
1827
|
+
const map = getMemoriesMap();
|
|
1828
|
+
const records = [];
|
|
1829
|
+
map.forEach((entry, id) => {
|
|
1830
|
+
records.push({
|
|
1831
|
+
id,
|
|
1832
|
+
agent_id: entry.get("agent_id"),
|
|
1833
|
+
agent_role: entry.get("agent_role"),
|
|
1834
|
+
session_id: entry.get("session_id"),
|
|
1835
|
+
timestamp: entry.get("timestamp"),
|
|
1836
|
+
tool_name: entry.get("tool_name"),
|
|
1837
|
+
project_name: entry.get("project_name"),
|
|
1838
|
+
has_error: entry.get("has_error"),
|
|
1839
|
+
raw_text: entry.get("raw_text"),
|
|
1840
|
+
version: entry.get("version"),
|
|
1841
|
+
author_device_id: entry.get("author_device_id"),
|
|
1842
|
+
scope: entry.get("scope")
|
|
1843
|
+
});
|
|
1844
|
+
});
|
|
1845
|
+
return records;
|
|
1846
|
+
}
|
|
1847
|
+
function readAllBehaviors() {
|
|
1848
|
+
const map = getBehaviorsMap();
|
|
1849
|
+
const records = [];
|
|
1850
|
+
map.forEach((entry, id) => {
|
|
1851
|
+
records.push({
|
|
1852
|
+
id,
|
|
1853
|
+
agent_id: entry.get("agent_id"),
|
|
1854
|
+
project_name: entry.get("project_name"),
|
|
1855
|
+
domain: entry.get("domain"),
|
|
1856
|
+
content: entry.get("content"),
|
|
1857
|
+
active: entry.get("active"),
|
|
1858
|
+
priority: entry.get("priority"),
|
|
1859
|
+
created_at: entry.get("created_at"),
|
|
1860
|
+
updated_at: entry.get("updated_at")
|
|
1861
|
+
});
|
|
1862
|
+
});
|
|
1863
|
+
return records;
|
|
1864
|
+
}
|
|
1865
|
+
function onUpdate(callback) {
|
|
1866
|
+
const d = initCrdtDoc();
|
|
1867
|
+
const handler = (update) => callback(update);
|
|
1868
|
+
d.on("update", handler);
|
|
1869
|
+
return () => d.off("update", handler);
|
|
1870
|
+
}
|
|
1871
|
+
function persistState() {
|
|
1872
|
+
if (!doc) return;
|
|
1873
|
+
try {
|
|
1874
|
+
const sp = getStatePath();
|
|
1875
|
+
const dir = path5.dirname(sp);
|
|
1876
|
+
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
1877
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
1878
|
+
writeFileSync3(sp, Buffer.from(state));
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function isCrdtSyncEnabled() {
|
|
1883
|
+
return process.env.EXE_CRDT_SYNC !== "0";
|
|
1884
|
+
}
|
|
1885
|
+
async function rebuildFromDb() {
|
|
1886
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
1887
|
+
const client = getClient2();
|
|
1888
|
+
const result = await client.execute(
|
|
1889
|
+
"SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
|
|
1890
|
+
);
|
|
1891
|
+
const memories = result.rows.map((row) => ({
|
|
1892
|
+
id: String(row.id),
|
|
1893
|
+
agent_id: row.agent_id,
|
|
1894
|
+
agent_role: row.agent_role,
|
|
1895
|
+
session_id: row.session_id,
|
|
1896
|
+
timestamp: row.timestamp,
|
|
1897
|
+
tool_name: row.tool_name,
|
|
1898
|
+
project_name: row.project_name,
|
|
1899
|
+
has_error: row.has_error,
|
|
1900
|
+
raw_text: row.raw_text,
|
|
1901
|
+
version: row.version,
|
|
1902
|
+
author_device_id: row.author_device_id,
|
|
1903
|
+
scope: row.scope
|
|
1904
|
+
}));
|
|
1905
|
+
const count = importExistingMemories(memories);
|
|
1906
|
+
persistState();
|
|
1907
|
+
return count;
|
|
1908
|
+
}
|
|
1909
|
+
function destroyCrdtDoc() {
|
|
1910
|
+
if (doc) {
|
|
1911
|
+
doc.destroy();
|
|
1912
|
+
doc = null;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
1916
|
+
var init_crdt_sync = __esm({
|
|
1917
|
+
"src/lib/crdt-sync.ts"() {
|
|
1918
|
+
"use strict";
|
|
1919
|
+
DEFAULT_STATE_PATH = path5.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
1920
|
+
_statePathOverride = null;
|
|
1921
|
+
doc = null;
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1159
1925
|
// src/lib/keychain.ts
|
|
1160
1926
|
var keychain_exports = {};
|
|
1161
1927
|
__export(keychain_exports, {
|
|
@@ -1166,14 +1932,14 @@ __export(keychain_exports, {
|
|
|
1166
1932
|
setMasterKey: () => setMasterKey
|
|
1167
1933
|
});
|
|
1168
1934
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1169
|
-
import { existsSync as
|
|
1170
|
-
import
|
|
1935
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1936
|
+
import path6 from "path";
|
|
1171
1937
|
import os3 from "os";
|
|
1172
1938
|
function getKeyDir() {
|
|
1173
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
1939
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os3.homedir(), ".exe-os");
|
|
1174
1940
|
}
|
|
1175
1941
|
function getKeyPath() {
|
|
1176
|
-
return
|
|
1942
|
+
return path6.join(getKeyDir(), "master.key");
|
|
1177
1943
|
}
|
|
1178
1944
|
async function tryKeytar() {
|
|
1179
1945
|
try {
|
|
@@ -1194,13 +1960,21 @@ async function getMasterKey() {
|
|
|
1194
1960
|
}
|
|
1195
1961
|
}
|
|
1196
1962
|
const keyPath = getKeyPath();
|
|
1197
|
-
if (!
|
|
1963
|
+
if (!existsSync6(keyPath)) {
|
|
1964
|
+
process.stderr.write(
|
|
1965
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1966
|
+
`
|
|
1967
|
+
);
|
|
1198
1968
|
return null;
|
|
1199
1969
|
}
|
|
1200
1970
|
try {
|
|
1201
1971
|
const content = await readFile3(keyPath, "utf-8");
|
|
1202
1972
|
return Buffer.from(content.trim(), "base64");
|
|
1203
|
-
} catch {
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
process.stderr.write(
|
|
1975
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
1976
|
+
`
|
|
1977
|
+
);
|
|
1204
1978
|
return null;
|
|
1205
1979
|
}
|
|
1206
1980
|
}
|
|
@@ -1229,7 +2003,7 @@ async function deleteMasterKey() {
|
|
|
1229
2003
|
}
|
|
1230
2004
|
}
|
|
1231
2005
|
const keyPath = getKeyPath();
|
|
1232
|
-
if (
|
|
2006
|
+
if (existsSync6(keyPath)) {
|
|
1233
2007
|
await unlink(keyPath);
|
|
1234
2008
|
}
|
|
1235
2009
|
}
|
|
@@ -1273,10 +2047,10 @@ var init_keychain = __esm({
|
|
|
1273
2047
|
|
|
1274
2048
|
// src/lib/cloud-sync.ts
|
|
1275
2049
|
init_database();
|
|
1276
|
-
import { readFileSync as
|
|
2050
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
1277
2051
|
import crypto2 from "crypto";
|
|
1278
|
-
import
|
|
1279
|
-
import { homedir } from "os";
|
|
2052
|
+
import path7 from "path";
|
|
2053
|
+
import { homedir as homedir2 } from "os";
|
|
1280
2054
|
|
|
1281
2055
|
// src/lib/crypto.ts
|
|
1282
2056
|
import crypto from "crypto";
|
|
@@ -1341,30 +2115,30 @@ function decompress(input) {
|
|
|
1341
2115
|
|
|
1342
2116
|
// src/lib/license.ts
|
|
1343
2117
|
init_config();
|
|
1344
|
-
import { readFileSync as
|
|
1345
|
-
import { randomUUID } from "crypto";
|
|
1346
|
-
import
|
|
2118
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync } from "fs";
|
|
2119
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2120
|
+
import path4 from "path";
|
|
1347
2121
|
import { jwtVerify, importSPKI } from "jose";
|
|
1348
|
-
var LICENSE_PATH =
|
|
1349
|
-
var CACHE_PATH =
|
|
1350
|
-
var DEVICE_ID_PATH =
|
|
2122
|
+
var LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
|
|
2123
|
+
var CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
|
|
2124
|
+
var DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
|
|
1351
2125
|
function loadDeviceId() {
|
|
1352
|
-
const deviceJsonPath =
|
|
2126
|
+
const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
|
|
1353
2127
|
try {
|
|
1354
|
-
if (
|
|
1355
|
-
const data = JSON.parse(
|
|
2128
|
+
if (existsSync4(deviceJsonPath)) {
|
|
2129
|
+
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
1356
2130
|
if (data.deviceId) return data.deviceId;
|
|
1357
2131
|
}
|
|
1358
2132
|
} catch {
|
|
1359
2133
|
}
|
|
1360
2134
|
try {
|
|
1361
|
-
if (
|
|
1362
|
-
const id2 =
|
|
2135
|
+
if (existsSync4(DEVICE_ID_PATH)) {
|
|
2136
|
+
const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
|
|
1363
2137
|
if (id2) return id2;
|
|
1364
2138
|
}
|
|
1365
2139
|
} catch {
|
|
1366
2140
|
}
|
|
1367
|
-
const id =
|
|
2141
|
+
const id = randomUUID2();
|
|
1368
2142
|
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1369
2143
|
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
1370
2144
|
return id;
|
|
@@ -1372,13 +2146,14 @@ function loadDeviceId() {
|
|
|
1372
2146
|
|
|
1373
2147
|
// src/lib/cloud-sync.ts
|
|
1374
2148
|
init_config();
|
|
2149
|
+
init_crdt_sync();
|
|
1375
2150
|
init_employees();
|
|
1376
2151
|
function sqlSafe(v) {
|
|
1377
2152
|
return v === void 0 ? null : v;
|
|
1378
2153
|
}
|
|
1379
2154
|
function logError(msg) {
|
|
1380
2155
|
try {
|
|
1381
|
-
const logPath =
|
|
2156
|
+
const logPath = path7.join(homedir2(), ".exe-os", "workers.log");
|
|
1382
2157
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
1383
2158
|
`);
|
|
1384
2159
|
} catch {
|
|
@@ -1387,24 +2162,24 @@ function logError(msg) {
|
|
|
1387
2162
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
1388
2163
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
1389
2164
|
var PUSH_BATCH_SIZE = 5e3;
|
|
1390
|
-
var ROSTER_LOCK_PATH =
|
|
2165
|
+
var ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
|
|
1391
2166
|
var LOCK_STALE_MS = 3e4;
|
|
1392
2167
|
async function withRosterLock(fn) {
|
|
1393
2168
|
try {
|
|
1394
|
-
const fd =
|
|
1395
|
-
|
|
1396
|
-
|
|
2169
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2170
|
+
closeSync2(fd);
|
|
2171
|
+
writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
|
|
1397
2172
|
} catch (err) {
|
|
1398
2173
|
if (err.code === "EEXIST") {
|
|
1399
2174
|
try {
|
|
1400
|
-
const ts = parseInt(
|
|
2175
|
+
const ts = parseInt(readFileSync6(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
1401
2176
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
1402
2177
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
1403
2178
|
}
|
|
1404
|
-
|
|
1405
|
-
const fd =
|
|
1406
|
-
|
|
1407
|
-
|
|
2179
|
+
unlinkSync4(ROSTER_LOCK_PATH);
|
|
2180
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2181
|
+
closeSync2(fd);
|
|
2182
|
+
writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
|
|
1408
2183
|
} catch (retryErr) {
|
|
1409
2184
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
1410
2185
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -1417,7 +2192,7 @@ async function withRosterLock(fn) {
|
|
|
1417
2192
|
return await fn();
|
|
1418
2193
|
} finally {
|
|
1419
2194
|
try {
|
|
1420
|
-
|
|
2195
|
+
unlinkSync4(ROSTER_LOCK_PATH);
|
|
1421
2196
|
} catch {
|
|
1422
2197
|
}
|
|
1423
2198
|
}
|
|
@@ -1562,29 +2337,75 @@ async function cloudSync(config) {
|
|
|
1562
2337
|
const pullResult = await cloudPull(lastPullVersion, config);
|
|
1563
2338
|
let pulled = 0;
|
|
1564
2339
|
if (pullResult.records.length > 0) {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
2340
|
+
if (isCrdtSyncEnabled()) {
|
|
2341
|
+
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
2342
|
+
initCrdtDoc2();
|
|
2343
|
+
importExistingMemories2(
|
|
2344
|
+
pullResult.records.map((rec) => ({
|
|
2345
|
+
id: String(rec.id ?? ""),
|
|
2346
|
+
agent_id: rec.agent_id,
|
|
2347
|
+
agent_role: rec.agent_role,
|
|
2348
|
+
session_id: rec.session_id,
|
|
2349
|
+
timestamp: rec.timestamp,
|
|
2350
|
+
tool_name: rec.tool_name,
|
|
2351
|
+
project_name: rec.project_name,
|
|
2352
|
+
has_error: rec.has_error ?? 0,
|
|
2353
|
+
raw_text: rec.raw_text ?? "",
|
|
2354
|
+
version: rec.version ?? 0,
|
|
2355
|
+
author_device_id: rec.author_device_id,
|
|
2356
|
+
scope: rec.scope ?? "business"
|
|
2357
|
+
}))
|
|
2358
|
+
);
|
|
2359
|
+
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
2360
|
+
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
2361
|
+
const stmts = merged.map((rec) => ({
|
|
2362
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
2363
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
2364
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
2365
|
+
author_device_id, scope)
|
|
2366
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2367
|
+
args: [
|
|
2368
|
+
sqlSafe(rec.id),
|
|
2369
|
+
sqlSafe(rec.agent_id),
|
|
2370
|
+
sqlSafe(rec.agent_role),
|
|
2371
|
+
sqlSafe(rec.session_id),
|
|
2372
|
+
sqlSafe(rec.timestamp),
|
|
2373
|
+
sqlSafe(rec.tool_name),
|
|
2374
|
+
sqlSafe(rec.project_name),
|
|
2375
|
+
sqlSafe(rec.has_error ?? 0),
|
|
2376
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
2377
|
+
sqlSafe(rec.version ?? 0),
|
|
2378
|
+
sqlSafe(rec.author_device_id),
|
|
2379
|
+
sqlSafe(rec.scope ?? "business")
|
|
2380
|
+
]
|
|
2381
|
+
}));
|
|
2382
|
+
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
2383
|
+
pulled = pullResult.records.length;
|
|
2384
|
+
} else {
|
|
2385
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
2386
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
2387
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
2388
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
2389
|
+
author_device_id, scope)
|
|
2390
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2391
|
+
args: [
|
|
2392
|
+
sqlSafe(rec.id),
|
|
2393
|
+
sqlSafe(rec.agent_id),
|
|
2394
|
+
sqlSafe(rec.agent_role),
|
|
2395
|
+
sqlSafe(rec.session_id),
|
|
2396
|
+
sqlSafe(rec.timestamp),
|
|
2397
|
+
sqlSafe(rec.tool_name),
|
|
2398
|
+
sqlSafe(rec.project_name),
|
|
2399
|
+
sqlSafe(rec.has_error ?? 0),
|
|
2400
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
2401
|
+
sqlSafe(rec.version ?? 0),
|
|
2402
|
+
sqlSafe(rec.author_device_id),
|
|
2403
|
+
sqlSafe(rec.scope ?? "business")
|
|
2404
|
+
]
|
|
2405
|
+
}));
|
|
2406
|
+
await client.batch(stmts, "write");
|
|
2407
|
+
pulled = pullResult.records.length;
|
|
2408
|
+
}
|
|
1588
2409
|
}
|
|
1589
2410
|
if (pullResult.maxVersion > lastPullVersion) {
|
|
1590
2411
|
await client.execute({
|
|
@@ -1732,8 +2553,8 @@ async function cloudSync(config) {
|
|
|
1732
2553
|
try {
|
|
1733
2554
|
const employees = await loadEmployees();
|
|
1734
2555
|
rosterResult.employees = employees.length;
|
|
1735
|
-
const idDir =
|
|
1736
|
-
if (
|
|
2556
|
+
const idDir = path7.join(EXE_AI_DIR, "identity");
|
|
2557
|
+
if (existsSync7(idDir)) {
|
|
1737
2558
|
rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
|
|
1738
2559
|
}
|
|
1739
2560
|
} catch {
|
|
@@ -1751,52 +2572,52 @@ async function cloudSync(config) {
|
|
|
1751
2572
|
roster: rosterResult
|
|
1752
2573
|
};
|
|
1753
2574
|
}
|
|
1754
|
-
var ROSTER_DELETIONS_PATH =
|
|
2575
|
+
var ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
|
|
1755
2576
|
function recordRosterDeletion(name) {
|
|
1756
2577
|
let deletions = [];
|
|
1757
2578
|
try {
|
|
1758
|
-
if (
|
|
1759
|
-
deletions = JSON.parse(
|
|
2579
|
+
if (existsSync7(ROSTER_DELETIONS_PATH)) {
|
|
2580
|
+
deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1760
2581
|
}
|
|
1761
2582
|
} catch {
|
|
1762
2583
|
}
|
|
1763
2584
|
if (!deletions.includes(name)) deletions.push(name);
|
|
1764
|
-
|
|
2585
|
+
writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
1765
2586
|
}
|
|
1766
2587
|
function consumeRosterDeletions() {
|
|
1767
2588
|
try {
|
|
1768
|
-
if (!
|
|
1769
|
-
const deletions = JSON.parse(
|
|
1770
|
-
|
|
2589
|
+
if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
|
|
2590
|
+
const deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
2591
|
+
writeFileSync4(ROSTER_DELETIONS_PATH, "[]");
|
|
1771
2592
|
return deletions;
|
|
1772
2593
|
} catch {
|
|
1773
2594
|
return [];
|
|
1774
2595
|
}
|
|
1775
2596
|
}
|
|
1776
2597
|
function buildRosterBlob(paths) {
|
|
1777
|
-
const rosterPath = paths?.rosterPath ??
|
|
1778
|
-
const identityDir = paths?.identityDir ??
|
|
1779
|
-
const configPath = paths?.configPath ??
|
|
2598
|
+
const rosterPath = paths?.rosterPath ?? path7.join(EXE_AI_DIR, "exe-employees.json");
|
|
2599
|
+
const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
|
|
2600
|
+
const configPath = paths?.configPath ?? path7.join(EXE_AI_DIR, "config.json");
|
|
1780
2601
|
let roster = [];
|
|
1781
|
-
if (
|
|
2602
|
+
if (existsSync7(rosterPath)) {
|
|
1782
2603
|
try {
|
|
1783
|
-
roster = JSON.parse(
|
|
2604
|
+
roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
|
|
1784
2605
|
} catch {
|
|
1785
2606
|
}
|
|
1786
2607
|
}
|
|
1787
2608
|
const identities = {};
|
|
1788
|
-
if (
|
|
2609
|
+
if (existsSync7(identityDir)) {
|
|
1789
2610
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
1790
2611
|
try {
|
|
1791
|
-
identities[file] =
|
|
2612
|
+
identities[file] = readFileSync6(path7.join(identityDir, file), "utf-8");
|
|
1792
2613
|
} catch {
|
|
1793
2614
|
}
|
|
1794
2615
|
}
|
|
1795
2616
|
}
|
|
1796
2617
|
let config;
|
|
1797
|
-
if (
|
|
2618
|
+
if (existsSync7(configPath)) {
|
|
1798
2619
|
try {
|
|
1799
|
-
config = JSON.parse(
|
|
2620
|
+
config = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
1800
2621
|
} catch {
|
|
1801
2622
|
}
|
|
1802
2623
|
}
|
|
@@ -1872,23 +2693,23 @@ async function cloudPullRoster(config) {
|
|
|
1872
2693
|
}
|
|
1873
2694
|
}
|
|
1874
2695
|
function mergeConfig(remoteConfig, configPath) {
|
|
1875
|
-
const cfgPath = configPath ??
|
|
2696
|
+
const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
|
|
1876
2697
|
let local = {};
|
|
1877
|
-
if (
|
|
2698
|
+
if (existsSync7(cfgPath)) {
|
|
1878
2699
|
try {
|
|
1879
|
-
local = JSON.parse(
|
|
2700
|
+
local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
|
|
1880
2701
|
} catch {
|
|
1881
2702
|
}
|
|
1882
2703
|
}
|
|
1883
2704
|
const merged = { ...remoteConfig, ...local };
|
|
1884
|
-
const dir =
|
|
1885
|
-
if (!
|
|
1886
|
-
|
|
2705
|
+
const dir = path7.dirname(cfgPath);
|
|
2706
|
+
if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
|
|
2707
|
+
writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
1887
2708
|
}
|
|
1888
2709
|
async function mergeRosterFromRemote(remote, paths) {
|
|
1889
2710
|
return withRosterLock(async () => {
|
|
1890
2711
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
1891
|
-
const identityDir = paths?.identityDir ??
|
|
2712
|
+
const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
|
|
1892
2713
|
const localEmployees = await loadEmployees(rosterPath);
|
|
1893
2714
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
1894
2715
|
let added = 0;
|
|
@@ -1909,15 +2730,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
1909
2730
|
) ?? lookupKey;
|
|
1910
2731
|
const remoteIdentity = remote.identities[matchedKey];
|
|
1911
2732
|
if (remoteIdentity) {
|
|
1912
|
-
if (!
|
|
1913
|
-
const idPath =
|
|
2733
|
+
if (!existsSync7(identityDir)) mkdirSync3(identityDir, { recursive: true });
|
|
2734
|
+
const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
|
|
1914
2735
|
let localIdentity = null;
|
|
1915
2736
|
try {
|
|
1916
|
-
localIdentity =
|
|
2737
|
+
localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
|
|
1917
2738
|
} catch {
|
|
1918
2739
|
}
|
|
1919
2740
|
if (localIdentity !== remoteIdentity) {
|
|
1920
|
-
|
|
2741
|
+
writeFileSync4(idPath, remoteIdentity, "utf-8");
|
|
1921
2742
|
identitiesUpdated++;
|
|
1922
2743
|
}
|
|
1923
2744
|
}
|