@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.
Files changed (95) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +97 -2
  5. package/dist/bin/cli.js +14350 -12518
  6. package/dist/bin/exe-agent.js +97 -88
  7. package/dist/bin/exe-assign.js +1003 -854
  8. package/dist/bin/exe-boot.js +1257 -320
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +29 -6
  11. package/dist/bin/exe-dispatch.js +210 -34
  12. package/dist/bin/exe-doctor.js +403 -6
  13. package/dist/bin/exe-export-behaviors.js +175 -72
  14. package/dist/bin/exe-forget.js +97 -2
  15. package/dist/bin/exe-gateway.js +550 -171
  16. package/dist/bin/exe-healthcheck.js +1 -0
  17. package/dist/bin/exe-heartbeat.js +100 -5
  18. package/dist/bin/exe-kill.js +175 -72
  19. package/dist/bin/exe-launch-agent.js +189 -76
  20. package/dist/bin/exe-link.js +902 -80
  21. package/dist/bin/exe-new-employee.js +38 -8
  22. package/dist/bin/exe-pending-messages.js +96 -2
  23. package/dist/bin/exe-pending-notifications.js +97 -2
  24. package/dist/bin/exe-pending-reviews.js +98 -3
  25. package/dist/bin/exe-rename.js +564 -23
  26. package/dist/bin/exe-review.js +231 -73
  27. package/dist/bin/exe-search.js +989 -226
  28. package/dist/bin/exe-session-cleanup.js +4806 -1665
  29. package/dist/bin/exe-settings.js +20 -5
  30. package/dist/bin/exe-status.js +97 -2
  31. package/dist/bin/exe-team.js +97 -2
  32. package/dist/bin/git-sweep.js +899 -207
  33. package/dist/bin/graph-backfill.js +175 -72
  34. package/dist/bin/graph-export.js +175 -72
  35. package/dist/bin/install.js +38 -7
  36. package/dist/bin/list-providers.js +1 -0
  37. package/dist/bin/scan-tasks.js +904 -211
  38. package/dist/bin/setup.js +867 -268
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +1 -0
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +548 -166
  43. package/dist/hooks/bug-report-worker.js +208 -23
  44. package/dist/hooks/commit-complete.js +897 -205
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1638 -1194
  47. package/dist/hooks/ingest.js +3 -0
  48. package/dist/hooks/instructions-loaded.js +707 -97
  49. package/dist/hooks/notification.js +699 -89
  50. package/dist/hooks/post-compact.js +714 -104
  51. package/dist/hooks/pre-compact.js +897 -205
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +242 -101
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +242 -101
  56. package/dist/hooks/session-end.js +3941 -400
  57. package/dist/hooks/session-start.js +1001 -226
  58. package/dist/hooks/stop.js +725 -115
  59. package/dist/hooks/subagent-stop.js +714 -104
  60. package/dist/hooks/summary-worker.js +1964 -1330
  61. package/dist/index.js +1651 -1053
  62. package/dist/lib/cloud-sync.js +907 -86
  63. package/dist/lib/consolidation.js +2 -1
  64. package/dist/lib/database.js +642 -87
  65. package/dist/lib/db-daemon-client.js +503 -0
  66. package/dist/lib/device-registry.js +547 -7
  67. package/dist/lib/embedder.js +14 -28
  68. package/dist/lib/employee-templates.js +84 -74
  69. package/dist/lib/employees.js +9 -0
  70. package/dist/lib/exe-daemon-client.js +16 -29
  71. package/dist/lib/exe-daemon.js +1955 -922
  72. package/dist/lib/hybrid-search.js +988 -226
  73. package/dist/lib/identity.js +87 -67
  74. package/dist/lib/keychain.js +9 -1
  75. package/dist/lib/messaging.js +8 -1
  76. package/dist/lib/reminders.js +91 -74
  77. package/dist/lib/schedules.js +96 -2
  78. package/dist/lib/skill-learning.js +103 -85
  79. package/dist/lib/store.js +234 -73
  80. package/dist/lib/tasks.js +111 -22
  81. package/dist/lib/tmux-routing.js +120 -31
  82. package/dist/lib/token-spend.js +273 -0
  83. package/dist/lib/ws-client.js +11 -0
  84. package/dist/mcp/server.js +5222 -475
  85. package/dist/mcp/tools/complete-reminder.js +94 -77
  86. package/dist/mcp/tools/create-reminder.js +94 -77
  87. package/dist/mcp/tools/create-task.js +120 -22
  88. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  89. package/dist/mcp/tools/list-reminders.js +94 -77
  90. package/dist/mcp/tools/list-tasks.js +31 -1
  91. package/dist/mcp/tools/send-message.js +8 -1
  92. package/dist/mcp/tools/update-task.js +39 -10
  93. package/dist/runtime/index.js +911 -219
  94. package/dist/tui/App.js +997 -295
  95. package/package.json +6 -1
@@ -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 existsSync4 } from "fs";
1170
- import path4 from "path";
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 ?? path4.join(os3.homedir(), ".exe-os");
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 path4.join(getKeyDir(), "master.key");
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 (!existsSync4(keyPath)) {
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 (existsSync4(keyPath)) {
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 readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync as unlinkSync2, openSync, closeSync } from "fs";
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 path5 from "path";
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 readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync } from "fs";
1345
- import { randomUUID } from "crypto";
1346
- import path3 from "path";
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 = path3.join(EXE_AI_DIR, "license.key");
1349
- var CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
1350
- var DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
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 = path3.join(EXE_AI_DIR, "device.json");
2126
+ const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
1353
2127
  try {
1354
- if (existsSync3(deviceJsonPath)) {
1355
- const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
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 (existsSync3(DEVICE_ID_PATH)) {
1362
- const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
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 = randomUUID();
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 = path5.join(homedir(), ".exe-os", "workers.log");
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 = path5.join(EXE_AI_DIR, "roster-merge.lock");
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 = openSync(ROSTER_LOCK_PATH, "wx");
1395
- closeSync(fd);
1396
- writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
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(readFileSync4(ROSTER_LOCK_PATH, "utf-8"), 10);
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
- unlinkSync2(ROSTER_LOCK_PATH);
1405
- const fd = openSync(ROSTER_LOCK_PATH, "wx");
1406
- closeSync(fd);
1407
- writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
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
- unlinkSync2(ROSTER_LOCK_PATH);
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
- const stmts = pullResult.records.map((rec) => ({
1566
- sql: `INSERT OR REPLACE INTO memories
1567
- (id, agent_id, agent_role, session_id, timestamp,
1568
- tool_name, project_name, has_error, raw_text, version,
1569
- author_device_id, scope)
1570
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1571
- args: [
1572
- sqlSafe(rec.id),
1573
- sqlSafe(rec.agent_id),
1574
- sqlSafe(rec.agent_role),
1575
- sqlSafe(rec.session_id),
1576
- sqlSafe(rec.timestamp),
1577
- sqlSafe(rec.tool_name),
1578
- sqlSafe(rec.project_name),
1579
- sqlSafe(rec.has_error ?? 0),
1580
- sqlSafe(rec.raw_text ?? ""),
1581
- sqlSafe(rec.version ?? 0),
1582
- sqlSafe(rec.author_device_id),
1583
- sqlSafe(rec.scope ?? "business")
1584
- ]
1585
- }));
1586
- await client.batch(stmts, "write");
1587
- pulled = pullResult.records.length;
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 = path5.join(EXE_AI_DIR, "identity");
1736
- if (existsSync5(idDir)) {
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 = path5.join(EXE_AI_DIR, "roster-deletions.json");
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 (existsSync5(ROSTER_DELETIONS_PATH)) {
1759
- deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
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
- writeFileSync3(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
2585
+ writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
1765
2586
  }
1766
2587
  function consumeRosterDeletions() {
1767
2588
  try {
1768
- if (!existsSync5(ROSTER_DELETIONS_PATH)) return [];
1769
- const deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
1770
- writeFileSync3(ROSTER_DELETIONS_PATH, "[]");
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 ?? path5.join(EXE_AI_DIR, "exe-employees.json");
1778
- const identityDir = paths?.identityDir ?? path5.join(EXE_AI_DIR, "identity");
1779
- const configPath = paths?.configPath ?? path5.join(EXE_AI_DIR, "config.json");
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 (existsSync5(rosterPath)) {
2602
+ if (existsSync7(rosterPath)) {
1782
2603
  try {
1783
- roster = JSON.parse(readFileSync4(rosterPath, "utf-8"));
2604
+ roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
1784
2605
  } catch {
1785
2606
  }
1786
2607
  }
1787
2608
  const identities = {};
1788
- if (existsSync5(identityDir)) {
2609
+ if (existsSync7(identityDir)) {
1789
2610
  for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
1790
2611
  try {
1791
- identities[file] = readFileSync4(path5.join(identityDir, file), "utf-8");
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 (existsSync5(configPath)) {
2618
+ if (existsSync7(configPath)) {
1798
2619
  try {
1799
- config = JSON.parse(readFileSync4(configPath, "utf-8"));
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 ?? path5.join(EXE_AI_DIR, "config.json");
2696
+ const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
1876
2697
  let local = {};
1877
- if (existsSync5(cfgPath)) {
2698
+ if (existsSync7(cfgPath)) {
1878
2699
  try {
1879
- local = JSON.parse(readFileSync4(cfgPath, "utf-8"));
2700
+ local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
1880
2701
  } catch {
1881
2702
  }
1882
2703
  }
1883
2704
  const merged = { ...remoteConfig, ...local };
1884
- const dir = path5.dirname(cfgPath);
1885
- if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1886
- writeFileSync3(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
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 ?? path5.join(EXE_AI_DIR, "identity");
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 (!existsSync5(identityDir)) mkdirSync2(identityDir, { recursive: true });
1913
- const idPath = path5.join(identityDir, `${remoteEmp.name}.md`);
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 = existsSync5(idPath) ? readFileSync4(idPath, "utf-8") : null;
2737
+ localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
1917
2738
  } catch {
1918
2739
  }
1919
2740
  if (localIdentity !== remoteIdentity) {
1920
- writeFileSync3(idPath, remoteIdentity, "utf-8");
2741
+ writeFileSync4(idPath, remoteIdentity, "utf-8");
1921
2742
  identitiesUpdated++;
1922
2743
  }
1923
2744
  }