@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
package/dist/bin/setup.js CHANGED
@@ -294,12 +294,20 @@ async function getMasterKey() {
294
294
  }
295
295
  const keyPath = getKeyPath();
296
296
  if (!existsSync2(keyPath)) {
297
+ process.stderr.write(
298
+ `[keychain] Key not found at ${keyPath} (HOME=${os2.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
299
+ `
300
+ );
297
301
  return null;
298
302
  }
299
303
  try {
300
304
  const content = await readFile2(keyPath, "utf-8");
301
305
  return Buffer.from(content.trim(), "base64");
302
- } catch {
306
+ } catch (err) {
307
+ process.stderr.write(
308
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
309
+ `
310
+ );
303
311
  return null;
304
312
  }
305
313
  }
@@ -399,10 +407,12 @@ function handleData(chunk) {
399
407
  if (!line) continue;
400
408
  try {
401
409
  const response = JSON.parse(line);
402
- const entry = _pending.get(response.id);
410
+ const id = response.id;
411
+ if (!id) continue;
412
+ const entry = _pending.get(id);
403
413
  if (entry) {
404
414
  clearTimeout(entry.timer);
405
- _pending.delete(response.id);
415
+ _pending.delete(id);
406
416
  entry.resolve(response);
407
417
  }
408
418
  } catch {
@@ -573,6 +583,9 @@ async function connectEmbedDaemon() {
573
583
  return false;
574
584
  }
575
585
  function sendRequest(texts, priority) {
586
+ return sendDaemonRequest({ texts, priority });
587
+ }
588
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
576
589
  return new Promise((resolve) => {
577
590
  if (!_socket || !_connected) {
578
591
  resolve({ error: "Not connected" });
@@ -582,10 +595,10 @@ function sendRequest(texts, priority) {
582
595
  const timer = setTimeout(() => {
583
596
  _pending.delete(id);
584
597
  resolve({ error: "Request timeout" });
585
- }, REQUEST_TIMEOUT_MS);
598
+ }, timeoutMs);
586
599
  _pending.set(id, { resolve, timer });
587
600
  try {
588
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
601
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
589
602
  } catch {
590
603
  clearTimeout(timer);
591
604
  _pending.delete(id);
@@ -595,30 +608,11 @@ function sendRequest(texts, priority) {
595
608
  }
596
609
  async function pingDaemon() {
597
610
  if (!_socket || !_connected) return null;
598
- return new Promise((resolve) => {
599
- const id = randomUUID();
600
- const timer = setTimeout(() => {
601
- _pending.delete(id);
602
- resolve(null);
603
- }, 5e3);
604
- _pending.set(id, {
605
- resolve: (data) => {
606
- if (data.health) {
607
- resolve(data.health);
608
- } else {
609
- resolve(null);
610
- }
611
- },
612
- timer
613
- });
614
- try {
615
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
616
- } catch {
617
- clearTimeout(timer);
618
- _pending.delete(id);
619
- resolve(null);
620
- }
621
- });
611
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
612
+ if (response.health) {
613
+ return response.health;
614
+ }
615
+ return null;
622
616
  }
623
617
  function killAndRespawnDaemon() {
624
618
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
@@ -703,6 +697,9 @@ function disconnectClient() {
703
697
  entry.resolve({ error: "Client disconnected" });
704
698
  }
705
699
  }
700
+ function isClientConnected() {
701
+ return _connected;
702
+ }
706
703
  var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
707
704
  var init_exe_daemon_client = __esm({
708
705
  "src/lib/exe-daemon-client.ts"() {
@@ -761,10 +758,10 @@ async function disposeEmbedder() {
761
758
  async function embedDirect(text) {
762
759
  const llamaCpp = await import("node-llama-cpp");
763
760
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
764
- const { existsSync: existsSync11 } = await import("fs");
765
- const path11 = await import("path");
766
- const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
767
- if (!existsSync11(modelPath)) {
761
+ const { existsSync: existsSync12 } = await import("fs");
762
+ const path12 = await import("path");
763
+ const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
764
+ if (!existsSync12(modelPath)) {
768
765
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
769
766
  }
770
767
  const llama = await llamaCpp.getLlama();
@@ -1300,6 +1297,7 @@ __export(employees_exports, {
1300
1297
  DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
1301
1298
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
1302
1299
  addEmployee: () => addEmployee,
1300
+ baseAgentName: () => baseAgentName,
1303
1301
  canCoordinate: () => canCoordinate,
1304
1302
  getCoordinatorEmployee: () => getCoordinatorEmployee,
1305
1303
  getCoordinatorName: () => getCoordinatorName,
@@ -1396,6 +1394,14 @@ function hasRole(agentName, role) {
1396
1394
  const emp = getEmployee(employees, agentName);
1397
1395
  return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
1398
1396
  }
1397
+ function baseAgentName(name, employees) {
1398
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
1399
+ if (!match) return name;
1400
+ const base = match[1];
1401
+ const roster = employees ?? loadEmployeesSync();
1402
+ if (getEmployee(roster, base)) return base;
1403
+ return name;
1404
+ }
1399
1405
  function isMultiInstance(agentName, employees) {
1400
1406
  const roster = employees ?? loadEmployeesSync();
1401
1407
  const emp = getEmployee(roster, agentName);
@@ -1491,6 +1497,205 @@ var init_employees = __esm({
1491
1497
  }
1492
1498
  });
1493
1499
 
1500
+ // src/lib/daemon-protocol.ts
1501
+ function serializeValue(v) {
1502
+ if (v === null || v === void 0) return null;
1503
+ if (typeof v === "bigint") return Number(v);
1504
+ if (typeof v === "boolean") return v ? 1 : 0;
1505
+ if (v instanceof Uint8Array) {
1506
+ return { __blob: Buffer.from(v).toString("base64") };
1507
+ }
1508
+ if (ArrayBuffer.isView(v)) {
1509
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
1510
+ }
1511
+ if (v instanceof ArrayBuffer) {
1512
+ return { __blob: Buffer.from(v).toString("base64") };
1513
+ }
1514
+ if (typeof v === "string" || typeof v === "number") return v;
1515
+ return String(v);
1516
+ }
1517
+ function deserializeValue(v) {
1518
+ if (v === null) return null;
1519
+ if (typeof v === "object" && v !== null && "__blob" in v) {
1520
+ const buf = Buffer.from(v.__blob, "base64");
1521
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1522
+ }
1523
+ return v;
1524
+ }
1525
+ function deserializeResultSet(srs) {
1526
+ const rows = srs.rows.map((obj) => {
1527
+ const values = srs.columns.map(
1528
+ (col) => deserializeValue(obj[col] ?? null)
1529
+ );
1530
+ const row = values;
1531
+ for (let i = 0; i < srs.columns.length; i++) {
1532
+ const col = srs.columns[i];
1533
+ if (col !== void 0) {
1534
+ row[col] = values[i] ?? null;
1535
+ }
1536
+ }
1537
+ Object.defineProperty(row, "length", {
1538
+ value: values.length,
1539
+ enumerable: false
1540
+ });
1541
+ return row;
1542
+ });
1543
+ return {
1544
+ columns: srs.columns,
1545
+ columnTypes: srs.columnTypes ?? [],
1546
+ rows,
1547
+ rowsAffected: srs.rowsAffected,
1548
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
1549
+ toJSON: () => ({
1550
+ columns: srs.columns,
1551
+ columnTypes: srs.columnTypes ?? [],
1552
+ rows: srs.rows,
1553
+ rowsAffected: srs.rowsAffected,
1554
+ lastInsertRowid: srs.lastInsertRowid
1555
+ })
1556
+ };
1557
+ }
1558
+ var init_daemon_protocol = __esm({
1559
+ "src/lib/daemon-protocol.ts"() {
1560
+ "use strict";
1561
+ }
1562
+ });
1563
+
1564
+ // src/lib/db-daemon-client.ts
1565
+ var db_daemon_client_exports = {};
1566
+ __export(db_daemon_client_exports, {
1567
+ createDaemonDbClient: () => createDaemonDbClient,
1568
+ initDaemonDbClient: () => initDaemonDbClient
1569
+ });
1570
+ function normalizeStatement(stmt) {
1571
+ if (typeof stmt === "string") {
1572
+ return { sql: stmt, args: [] };
1573
+ }
1574
+ const sql = stmt.sql;
1575
+ let args2 = [];
1576
+ if (Array.isArray(stmt.args)) {
1577
+ args2 = stmt.args.map((v) => serializeValue(v));
1578
+ } else if (stmt.args && typeof stmt.args === "object") {
1579
+ const named = {};
1580
+ for (const [key, val] of Object.entries(stmt.args)) {
1581
+ named[key] = serializeValue(val);
1582
+ }
1583
+ return { sql, args: named };
1584
+ }
1585
+ return { sql, args: args2 };
1586
+ }
1587
+ function createDaemonDbClient(fallbackClient) {
1588
+ let _useDaemon = false;
1589
+ const client = {
1590
+ async execute(stmt) {
1591
+ if (!_useDaemon || !isClientConnected()) {
1592
+ return fallbackClient.execute(stmt);
1593
+ }
1594
+ const { sql, args: args2 } = normalizeStatement(stmt);
1595
+ const response = await sendDaemonRequest({
1596
+ type: "db-execute",
1597
+ sql,
1598
+ args: args2
1599
+ });
1600
+ if (response.error) {
1601
+ const errMsg = String(response.error);
1602
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1603
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1604
+ `);
1605
+ return fallbackClient.execute(stmt);
1606
+ }
1607
+ throw new Error(errMsg);
1608
+ }
1609
+ if (response.db) {
1610
+ return deserializeResultSet(response.db);
1611
+ }
1612
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1613
+ return fallbackClient.execute(stmt);
1614
+ },
1615
+ async batch(stmts, mode) {
1616
+ if (!_useDaemon || !isClientConnected()) {
1617
+ return fallbackClient.batch(stmts, mode);
1618
+ }
1619
+ const statements = stmts.map(normalizeStatement);
1620
+ const response = await sendDaemonRequest({
1621
+ type: "db-batch",
1622
+ statements,
1623
+ mode: mode ?? "deferred"
1624
+ });
1625
+ if (response.error) {
1626
+ const errMsg = String(response.error);
1627
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1628
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1629
+ `);
1630
+ return fallbackClient.batch(stmts, mode);
1631
+ }
1632
+ throw new Error(errMsg);
1633
+ }
1634
+ const batchResults = response["db-batch"];
1635
+ if (batchResults) {
1636
+ return batchResults.map(deserializeResultSet);
1637
+ }
1638
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1639
+ return fallbackClient.batch(stmts, mode);
1640
+ },
1641
+ // Transaction support — delegate to fallback (transactions need direct connection)
1642
+ async transaction(mode) {
1643
+ return fallbackClient.transaction(mode);
1644
+ },
1645
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1646
+ async executeMultiple(sql) {
1647
+ return fallbackClient.executeMultiple(sql);
1648
+ },
1649
+ // migrate — delegate to fallback
1650
+ async migrate(stmts) {
1651
+ return fallbackClient.migrate(stmts);
1652
+ },
1653
+ // Sync mode — delegate to fallback
1654
+ sync() {
1655
+ return fallbackClient.sync();
1656
+ },
1657
+ close() {
1658
+ _useDaemon = false;
1659
+ },
1660
+ get closed() {
1661
+ return fallbackClient.closed;
1662
+ },
1663
+ get protocol() {
1664
+ return fallbackClient.protocol;
1665
+ }
1666
+ };
1667
+ return {
1668
+ ...client,
1669
+ /** Enable daemon routing (call after confirming daemon is connected) */
1670
+ _enableDaemon() {
1671
+ _useDaemon = true;
1672
+ },
1673
+ /** Check if daemon routing is active */
1674
+ _isDaemonActive() {
1675
+ return _useDaemon && isClientConnected();
1676
+ }
1677
+ };
1678
+ }
1679
+ async function initDaemonDbClient(fallbackClient) {
1680
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1681
+ const connected = await connectEmbedDaemon();
1682
+ if (!connected) {
1683
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1684
+ return null;
1685
+ }
1686
+ const client = createDaemonDbClient(fallbackClient);
1687
+ client._enableDaemon();
1688
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1689
+ return client;
1690
+ }
1691
+ var init_db_daemon_client = __esm({
1692
+ "src/lib/db-daemon-client.ts"() {
1693
+ "use strict";
1694
+ init_exe_daemon_client();
1695
+ init_daemon_protocol();
1696
+ }
1697
+ });
1698
+
1494
1699
  // src/lib/database.ts
1495
1700
  var database_exports = {};
1496
1701
  __export(database_exports, {
@@ -1499,6 +1704,7 @@ __export(database_exports, {
1499
1704
  ensureSchema: () => ensureSchema,
1500
1705
  getClient: () => getClient,
1501
1706
  getRawClient: () => getRawClient,
1707
+ initDaemonClient: () => initDaemonClient,
1502
1708
  initDatabase: () => initDatabase,
1503
1709
  initTurso: () => initTurso,
1504
1710
  isInitialized: () => isInitialized
@@ -1526,8 +1732,27 @@ function getClient() {
1526
1732
  if (!_resilientClient) {
1527
1733
  throw new Error("Database client not initialized. Call initDatabase() first.");
1528
1734
  }
1735
+ if (process.env.EXE_IS_DAEMON === "1") {
1736
+ return _resilientClient;
1737
+ }
1738
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1739
+ return _daemonClient;
1740
+ }
1529
1741
  return _resilientClient;
1530
1742
  }
1743
+ async function initDaemonClient() {
1744
+ if (process.env.EXE_IS_DAEMON === "1") return;
1745
+ if (!_resilientClient) return;
1746
+ try {
1747
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1748
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1749
+ } catch (err) {
1750
+ process.stderr.write(
1751
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1752
+ `
1753
+ );
1754
+ }
1755
+ }
1531
1756
  function getRawClient() {
1532
1757
  if (!_client) {
1533
1758
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -2014,6 +2239,12 @@ async function ensureSchema() {
2014
2239
  } catch {
2015
2240
  }
2016
2241
  }
2242
+ try {
2243
+ await client.execute(
2244
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
2245
+ );
2246
+ } catch {
2247
+ }
2017
2248
  await client.executeMultiple(`
2018
2249
  CREATE TABLE IF NOT EXISTS entities (
2019
2250
  id TEXT PRIMARY KEY,
@@ -2066,7 +2297,30 @@ async function ensureSchema() {
2066
2297
  entity_id TEXT NOT NULL,
2067
2298
  PRIMARY KEY (hyperedge_id, entity_id)
2068
2299
  );
2300
+
2301
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
2302
+ name,
2303
+ content=entities,
2304
+ content_rowid=rowid
2305
+ );
2306
+
2307
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
2308
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2309
+ END;
2310
+
2311
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
2312
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2313
+ END;
2314
+
2315
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
2316
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2317
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2318
+ END;
2069
2319
  `);
2320
+ try {
2321
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
2322
+ } catch {
2323
+ }
2070
2324
  await client.executeMultiple(`
2071
2325
  CREATE TABLE IF NOT EXISTS entity_aliases (
2072
2326
  alias TEXT NOT NULL PRIMARY KEY,
@@ -2247,6 +2501,33 @@ async function ensureSchema() {
2247
2501
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
2248
2502
  ON conversations(channel_id);
2249
2503
  `);
2504
+ await client.executeMultiple(`
2505
+ CREATE TABLE IF NOT EXISTS session_agent_map (
2506
+ session_uuid TEXT PRIMARY KEY,
2507
+ agent_id TEXT NOT NULL,
2508
+ session_name TEXT,
2509
+ task_id TEXT,
2510
+ project_name TEXT,
2511
+ started_at TEXT NOT NULL
2512
+ );
2513
+
2514
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2515
+ ON session_agent_map(agent_id);
2516
+ `);
2517
+ try {
2518
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2519
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
2520
+ await client.execute({
2521
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
2522
+ SELECT session_id, agent_id, '', MIN(timestamp)
2523
+ FROM memories
2524
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
2525
+ GROUP BY session_id, agent_id`,
2526
+ args: []
2527
+ });
2528
+ }
2529
+ } catch {
2530
+ }
2250
2531
  try {
2251
2532
  await client.execute({
2252
2533
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -2380,15 +2661,41 @@ async function ensureSchema() {
2380
2661
  });
2381
2662
  } catch {
2382
2663
  }
2664
+ for (const col of [
2665
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2666
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2667
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2668
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2669
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2670
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2671
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2672
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2673
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2674
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2675
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2676
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2677
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2678
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2679
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2680
+ ]) {
2681
+ try {
2682
+ await client.execute(col);
2683
+ } catch {
2684
+ }
2685
+ }
2383
2686
  }
2384
2687
  async function disposeDatabase() {
2688
+ if (_daemonClient) {
2689
+ _daemonClient.close();
2690
+ _daemonClient = null;
2691
+ }
2385
2692
  if (_client) {
2386
2693
  _client.close();
2387
2694
  _client = null;
2388
2695
  _resilientClient = null;
2389
2696
  }
2390
2697
  }
2391
- var _client, _resilientClient, initTurso, disposeTurso;
2698
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
2392
2699
  var init_database = __esm({
2393
2700
  "src/lib/database.ts"() {
2394
2701
  "use strict";
@@ -2396,6 +2703,7 @@ var init_database = __esm({
2396
2703
  init_employees();
2397
2704
  _client = null;
2398
2705
  _resilientClient = null;
2706
+ _daemonClient = null;
2399
2707
  initTurso = initDatabase;
2400
2708
  disposeTurso = disposeDatabase;
2401
2709
  }
@@ -2421,6 +2729,232 @@ var init_compress = __esm({
2421
2729
  }
2422
2730
  });
2423
2731
 
2732
+ // src/lib/crdt-sync.ts
2733
+ var crdt_sync_exports = {};
2734
+ __export(crdt_sync_exports, {
2735
+ _setStatePath: () => _setStatePath,
2736
+ applyRemoteUpdate: () => applyRemoteUpdate,
2737
+ destroyCrdtDoc: () => destroyCrdtDoc,
2738
+ getDiffUpdate: () => getDiffUpdate,
2739
+ getFullState: () => getFullState,
2740
+ getStateVector: () => getStateVector,
2741
+ importExistingBehaviors: () => importExistingBehaviors,
2742
+ importExistingMemories: () => importExistingMemories,
2743
+ initCrdtDoc: () => initCrdtDoc,
2744
+ isCrdtSyncEnabled: () => isCrdtSyncEnabled,
2745
+ onUpdate: () => onUpdate,
2746
+ readAllBehaviors: () => readAllBehaviors,
2747
+ readAllMemories: () => readAllMemories,
2748
+ rebuildFromDb: () => rebuildFromDb
2749
+ });
2750
+ import * as Y from "yjs";
2751
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync4 } from "fs";
2752
+ import path7 from "path";
2753
+ import { homedir } from "os";
2754
+ function getStatePath() {
2755
+ return _statePathOverride ?? DEFAULT_STATE_PATH;
2756
+ }
2757
+ function _setStatePath(p) {
2758
+ _statePathOverride = p;
2759
+ }
2760
+ function initCrdtDoc() {
2761
+ if (doc) return doc;
2762
+ doc = new Y.Doc();
2763
+ const sp = getStatePath();
2764
+ if (existsSync7(sp)) {
2765
+ try {
2766
+ const state = readFileSync5(sp);
2767
+ Y.applyUpdate(doc, new Uint8Array(state));
2768
+ } catch {
2769
+ console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
2770
+ try {
2771
+ unlinkSync4(sp);
2772
+ } catch {
2773
+ }
2774
+ rebuildFromDb().catch((err) => {
2775
+ console.warn("[crdt-sync] rebuild from DB failed:", err);
2776
+ });
2777
+ }
2778
+ }
2779
+ doc.on("update", () => {
2780
+ persistState();
2781
+ });
2782
+ return doc;
2783
+ }
2784
+ function getMemoriesMap() {
2785
+ const d = initCrdtDoc();
2786
+ return d.getMap("memories");
2787
+ }
2788
+ function getBehaviorsMap() {
2789
+ const d = initCrdtDoc();
2790
+ return d.getMap("behaviors");
2791
+ }
2792
+ function applyRemoteUpdate(update) {
2793
+ const d = initCrdtDoc();
2794
+ Y.applyUpdate(d, update);
2795
+ }
2796
+ function getFullState() {
2797
+ const d = initCrdtDoc();
2798
+ return Y.encodeStateAsUpdate(d);
2799
+ }
2800
+ function getDiffUpdate(remoteStateVector) {
2801
+ const d = initCrdtDoc();
2802
+ return Y.encodeStateAsUpdate(d, remoteStateVector);
2803
+ }
2804
+ function getStateVector() {
2805
+ const d = initCrdtDoc();
2806
+ return Y.encodeStateVector(d);
2807
+ }
2808
+ function importExistingMemories(memories) {
2809
+ const map = getMemoriesMap();
2810
+ const d = initCrdtDoc();
2811
+ let imported = 0;
2812
+ d.transact(() => {
2813
+ for (const mem of memories) {
2814
+ if (!mem.id) continue;
2815
+ if (map.has(mem.id)) continue;
2816
+ const entry = new Y.Map();
2817
+ entry.set("id", mem.id);
2818
+ entry.set("agent_id", mem.agent_id ?? null);
2819
+ entry.set("agent_role", mem.agent_role ?? null);
2820
+ entry.set("session_id", mem.session_id ?? null);
2821
+ entry.set("timestamp", mem.timestamp ?? null);
2822
+ entry.set("tool_name", mem.tool_name ?? null);
2823
+ entry.set("project_name", mem.project_name ?? null);
2824
+ entry.set("has_error", mem.has_error ?? 0);
2825
+ entry.set("raw_text", mem.raw_text ?? "");
2826
+ entry.set("version", mem.version ?? 0);
2827
+ entry.set("author_device_id", mem.author_device_id ?? null);
2828
+ entry.set("scope", mem.scope ?? "business");
2829
+ map.set(mem.id, entry);
2830
+ imported++;
2831
+ }
2832
+ });
2833
+ return imported;
2834
+ }
2835
+ function importExistingBehaviors(behaviors) {
2836
+ const map = getBehaviorsMap();
2837
+ const d = initCrdtDoc();
2838
+ let imported = 0;
2839
+ d.transact(() => {
2840
+ for (const beh of behaviors) {
2841
+ if (!beh.id) continue;
2842
+ if (map.has(beh.id)) continue;
2843
+ const entry = new Y.Map();
2844
+ entry.set("id", beh.id);
2845
+ entry.set("agent_id", beh.agent_id ?? null);
2846
+ entry.set("project_name", beh.project_name ?? null);
2847
+ entry.set("domain", beh.domain ?? null);
2848
+ entry.set("content", beh.content ?? null);
2849
+ entry.set("active", beh.active ?? 1);
2850
+ entry.set("priority", beh.priority ?? "p1");
2851
+ entry.set("created_at", beh.created_at ?? null);
2852
+ entry.set("updated_at", beh.updated_at ?? null);
2853
+ map.set(beh.id, entry);
2854
+ imported++;
2855
+ }
2856
+ });
2857
+ return imported;
2858
+ }
2859
+ function readAllMemories() {
2860
+ const map = getMemoriesMap();
2861
+ const records = [];
2862
+ map.forEach((entry, id) => {
2863
+ records.push({
2864
+ id,
2865
+ agent_id: entry.get("agent_id"),
2866
+ agent_role: entry.get("agent_role"),
2867
+ session_id: entry.get("session_id"),
2868
+ timestamp: entry.get("timestamp"),
2869
+ tool_name: entry.get("tool_name"),
2870
+ project_name: entry.get("project_name"),
2871
+ has_error: entry.get("has_error"),
2872
+ raw_text: entry.get("raw_text"),
2873
+ version: entry.get("version"),
2874
+ author_device_id: entry.get("author_device_id"),
2875
+ scope: entry.get("scope")
2876
+ });
2877
+ });
2878
+ return records;
2879
+ }
2880
+ function readAllBehaviors() {
2881
+ const map = getBehaviorsMap();
2882
+ const records = [];
2883
+ map.forEach((entry, id) => {
2884
+ records.push({
2885
+ id,
2886
+ agent_id: entry.get("agent_id"),
2887
+ project_name: entry.get("project_name"),
2888
+ domain: entry.get("domain"),
2889
+ content: entry.get("content"),
2890
+ active: entry.get("active"),
2891
+ priority: entry.get("priority"),
2892
+ created_at: entry.get("created_at"),
2893
+ updated_at: entry.get("updated_at")
2894
+ });
2895
+ });
2896
+ return records;
2897
+ }
2898
+ function onUpdate(callback) {
2899
+ const d = initCrdtDoc();
2900
+ const handler = (update) => callback(update);
2901
+ d.on("update", handler);
2902
+ return () => d.off("update", handler);
2903
+ }
2904
+ function persistState() {
2905
+ if (!doc) return;
2906
+ try {
2907
+ const sp = getStatePath();
2908
+ const dir = path7.dirname(sp);
2909
+ if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2910
+ const state = Y.encodeStateAsUpdate(doc);
2911
+ writeFileSync3(sp, Buffer.from(state));
2912
+ } catch {
2913
+ }
2914
+ }
2915
+ function isCrdtSyncEnabled() {
2916
+ return process.env.EXE_CRDT_SYNC !== "0";
2917
+ }
2918
+ async function rebuildFromDb() {
2919
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
2920
+ const client = getClient2();
2921
+ const result = await client.execute(
2922
+ "SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
2923
+ );
2924
+ const memories = result.rows.map((row) => ({
2925
+ id: String(row.id),
2926
+ agent_id: row.agent_id,
2927
+ agent_role: row.agent_role,
2928
+ session_id: row.session_id,
2929
+ timestamp: row.timestamp,
2930
+ tool_name: row.tool_name,
2931
+ project_name: row.project_name,
2932
+ has_error: row.has_error,
2933
+ raw_text: row.raw_text,
2934
+ version: row.version,
2935
+ author_device_id: row.author_device_id,
2936
+ scope: row.scope
2937
+ }));
2938
+ const count = importExistingMemories(memories);
2939
+ persistState();
2940
+ return count;
2941
+ }
2942
+ function destroyCrdtDoc() {
2943
+ if (doc) {
2944
+ doc.destroy();
2945
+ doc = null;
2946
+ }
2947
+ }
2948
+ var DEFAULT_STATE_PATH, _statePathOverride, doc;
2949
+ var init_crdt_sync = __esm({
2950
+ "src/lib/crdt-sync.ts"() {
2951
+ "use strict";
2952
+ DEFAULT_STATE_PATH = path7.join(homedir(), ".exe-os", "crdt-state.bin");
2953
+ _statePathOverride = null;
2954
+ doc = null;
2955
+ }
2956
+ });
2957
+
2424
2958
  // src/lib/cloud-sync.ts
2425
2959
  var cloud_sync_exports = {};
2426
2960
  __export(cloud_sync_exports, {
@@ -2449,16 +2983,16 @@ __export(cloud_sync_exports, {
2449
2983
  mergeRosterFromRemote: () => mergeRosterFromRemote,
2450
2984
  recordRosterDeletion: () => recordRosterDeletion
2451
2985
  });
2452
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
2986
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2 } from "fs";
2453
2987
  import crypto2 from "crypto";
2454
- import path7 from "path";
2455
- import { homedir } from "os";
2988
+ import path8 from "path";
2989
+ import { homedir as homedir2 } from "os";
2456
2990
  function sqlSafe(v) {
2457
2991
  return v === void 0 ? null : v;
2458
2992
  }
2459
2993
  function logError(msg) {
2460
2994
  try {
2461
- const logPath = path7.join(homedir(), ".exe-os", "workers.log");
2995
+ const logPath = path8.join(homedir2(), ".exe-os", "workers.log");
2462
2996
  appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2463
2997
  `);
2464
2998
  } catch {
@@ -2468,18 +3002,18 @@ async function withRosterLock(fn) {
2468
3002
  try {
2469
3003
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2470
3004
  closeSync2(fd);
2471
- writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
3005
+ writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
2472
3006
  } catch (err) {
2473
3007
  if (err.code === "EEXIST") {
2474
3008
  try {
2475
- const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
3009
+ const ts = parseInt(readFileSync6(ROSTER_LOCK_PATH, "utf-8"), 10);
2476
3010
  if (Date.now() - ts < LOCK_STALE_MS) {
2477
3011
  throw new Error("Roster merge already in progress \u2014 another sync is running");
2478
3012
  }
2479
- unlinkSync4(ROSTER_LOCK_PATH);
3013
+ unlinkSync5(ROSTER_LOCK_PATH);
2480
3014
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2481
3015
  closeSync2(fd);
2482
- writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
3016
+ writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
2483
3017
  } catch (retryErr) {
2484
3018
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
2485
3019
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -2492,7 +3026,7 @@ async function withRosterLock(fn) {
2492
3026
  return await fn();
2493
3027
  } finally {
2494
3028
  try {
2495
- unlinkSync4(ROSTER_LOCK_PATH);
3029
+ unlinkSync5(ROSTER_LOCK_PATH);
2496
3030
  } catch {
2497
3031
  }
2498
3032
  }
@@ -2637,29 +3171,75 @@ async function cloudSync(config) {
2637
3171
  const pullResult = await cloudPull(lastPullVersion, config);
2638
3172
  let pulled = 0;
2639
3173
  if (pullResult.records.length > 0) {
2640
- const stmts = pullResult.records.map((rec) => ({
2641
- sql: `INSERT OR REPLACE INTO memories
2642
- (id, agent_id, agent_role, session_id, timestamp,
2643
- tool_name, project_name, has_error, raw_text, version,
2644
- author_device_id, scope)
2645
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2646
- args: [
2647
- sqlSafe(rec.id),
2648
- sqlSafe(rec.agent_id),
2649
- sqlSafe(rec.agent_role),
2650
- sqlSafe(rec.session_id),
2651
- sqlSafe(rec.timestamp),
2652
- sqlSafe(rec.tool_name),
2653
- sqlSafe(rec.project_name),
2654
- sqlSafe(rec.has_error ?? 0),
2655
- sqlSafe(rec.raw_text ?? ""),
2656
- sqlSafe(rec.version ?? 0),
2657
- sqlSafe(rec.author_device_id),
2658
- sqlSafe(rec.scope ?? "business")
2659
- ]
2660
- }));
2661
- await client.batch(stmts, "write");
2662
- pulled = pullResult.records.length;
3174
+ if (isCrdtSyncEnabled()) {
3175
+ const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
3176
+ initCrdtDoc2();
3177
+ importExistingMemories2(
3178
+ pullResult.records.map((rec) => ({
3179
+ id: String(rec.id ?? ""),
3180
+ agent_id: rec.agent_id,
3181
+ agent_role: rec.agent_role,
3182
+ session_id: rec.session_id,
3183
+ timestamp: rec.timestamp,
3184
+ tool_name: rec.tool_name,
3185
+ project_name: rec.project_name,
3186
+ has_error: rec.has_error ?? 0,
3187
+ raw_text: rec.raw_text ?? "",
3188
+ version: rec.version ?? 0,
3189
+ author_device_id: rec.author_device_id,
3190
+ scope: rec.scope ?? "business"
3191
+ }))
3192
+ );
3193
+ const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
3194
+ const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
3195
+ const stmts = merged.map((rec) => ({
3196
+ sql: `INSERT OR REPLACE INTO memories
3197
+ (id, agent_id, agent_role, session_id, timestamp,
3198
+ tool_name, project_name, has_error, raw_text, version,
3199
+ author_device_id, scope)
3200
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3201
+ args: [
3202
+ sqlSafe(rec.id),
3203
+ sqlSafe(rec.agent_id),
3204
+ sqlSafe(rec.agent_role),
3205
+ sqlSafe(rec.session_id),
3206
+ sqlSafe(rec.timestamp),
3207
+ sqlSafe(rec.tool_name),
3208
+ sqlSafe(rec.project_name),
3209
+ sqlSafe(rec.has_error ?? 0),
3210
+ sqlSafe(rec.raw_text ?? ""),
3211
+ sqlSafe(rec.version ?? 0),
3212
+ sqlSafe(rec.author_device_id),
3213
+ sqlSafe(rec.scope ?? "business")
3214
+ ]
3215
+ }));
3216
+ if (stmts.length > 0) await client.batch(stmts, "write");
3217
+ pulled = pullResult.records.length;
3218
+ } else {
3219
+ const stmts = pullResult.records.map((rec) => ({
3220
+ sql: `INSERT OR REPLACE INTO memories
3221
+ (id, agent_id, agent_role, session_id, timestamp,
3222
+ tool_name, project_name, has_error, raw_text, version,
3223
+ author_device_id, scope)
3224
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3225
+ args: [
3226
+ sqlSafe(rec.id),
3227
+ sqlSafe(rec.agent_id),
3228
+ sqlSafe(rec.agent_role),
3229
+ sqlSafe(rec.session_id),
3230
+ sqlSafe(rec.timestamp),
3231
+ sqlSafe(rec.tool_name),
3232
+ sqlSafe(rec.project_name),
3233
+ sqlSafe(rec.has_error ?? 0),
3234
+ sqlSafe(rec.raw_text ?? ""),
3235
+ sqlSafe(rec.version ?? 0),
3236
+ sqlSafe(rec.author_device_id),
3237
+ sqlSafe(rec.scope ?? "business")
3238
+ ]
3239
+ }));
3240
+ await client.batch(stmts, "write");
3241
+ pulled = pullResult.records.length;
3242
+ }
2663
3243
  }
2664
3244
  if (pullResult.maxVersion > lastPullVersion) {
2665
3245
  await client.execute({
@@ -2807,8 +3387,8 @@ async function cloudSync(config) {
2807
3387
  try {
2808
3388
  const employees = await loadEmployees();
2809
3389
  rosterResult.employees = employees.length;
2810
- const idDir = path7.join(EXE_AI_DIR, "identity");
2811
- if (existsSync7(idDir)) {
3390
+ const idDir = path8.join(EXE_AI_DIR, "identity");
3391
+ if (existsSync8(idDir)) {
2812
3392
  rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
2813
3393
  }
2814
3394
  } catch {
@@ -2829,48 +3409,48 @@ async function cloudSync(config) {
2829
3409
  function recordRosterDeletion(name) {
2830
3410
  let deletions = [];
2831
3411
  try {
2832
- if (existsSync7(ROSTER_DELETIONS_PATH)) {
2833
- deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
3412
+ if (existsSync8(ROSTER_DELETIONS_PATH)) {
3413
+ deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
2834
3414
  }
2835
3415
  } catch {
2836
3416
  }
2837
3417
  if (!deletions.includes(name)) deletions.push(name);
2838
- writeFileSync3(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3418
+ writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
2839
3419
  }
2840
3420
  function consumeRosterDeletions() {
2841
3421
  try {
2842
- if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
2843
- const deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
2844
- writeFileSync3(ROSTER_DELETIONS_PATH, "[]");
3422
+ if (!existsSync8(ROSTER_DELETIONS_PATH)) return [];
3423
+ const deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
3424
+ writeFileSync4(ROSTER_DELETIONS_PATH, "[]");
2845
3425
  return deletions;
2846
3426
  } catch {
2847
3427
  return [];
2848
3428
  }
2849
3429
  }
2850
3430
  function buildRosterBlob(paths) {
2851
- const rosterPath = paths?.rosterPath ?? path7.join(EXE_AI_DIR, "exe-employees.json");
2852
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
2853
- const configPath = paths?.configPath ?? path7.join(EXE_AI_DIR, "config.json");
3431
+ const rosterPath = paths?.rosterPath ?? path8.join(EXE_AI_DIR, "exe-employees.json");
3432
+ const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
3433
+ const configPath = paths?.configPath ?? path8.join(EXE_AI_DIR, "config.json");
2854
3434
  let roster = [];
2855
- if (existsSync7(rosterPath)) {
3435
+ if (existsSync8(rosterPath)) {
2856
3436
  try {
2857
- roster = JSON.parse(readFileSync5(rosterPath, "utf-8"));
3437
+ roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
2858
3438
  } catch {
2859
3439
  }
2860
3440
  }
2861
3441
  const identities = {};
2862
- if (existsSync7(identityDir)) {
3442
+ if (existsSync8(identityDir)) {
2863
3443
  for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
2864
3444
  try {
2865
- identities[file] = readFileSync5(path7.join(identityDir, file), "utf-8");
3445
+ identities[file] = readFileSync6(path8.join(identityDir, file), "utf-8");
2866
3446
  } catch {
2867
3447
  }
2868
3448
  }
2869
3449
  }
2870
3450
  let config;
2871
- if (existsSync7(configPath)) {
3451
+ if (existsSync8(configPath)) {
2872
3452
  try {
2873
- config = JSON.parse(readFileSync5(configPath, "utf-8"));
3453
+ config = JSON.parse(readFileSync6(configPath, "utf-8"));
2874
3454
  } catch {
2875
3455
  }
2876
3456
  }
@@ -2946,23 +3526,23 @@ async function cloudPullRoster(config) {
2946
3526
  }
2947
3527
  }
2948
3528
  function mergeConfig(remoteConfig, configPath) {
2949
- const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
3529
+ const cfgPath = configPath ?? path8.join(EXE_AI_DIR, "config.json");
2950
3530
  let local = {};
2951
- if (existsSync7(cfgPath)) {
3531
+ if (existsSync8(cfgPath)) {
2952
3532
  try {
2953
- local = JSON.parse(readFileSync5(cfgPath, "utf-8"));
3533
+ local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
2954
3534
  } catch {
2955
3535
  }
2956
3536
  }
2957
3537
  const merged = { ...remoteConfig, ...local };
2958
- const dir = path7.dirname(cfgPath);
2959
- if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2960
- writeFileSync3(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3538
+ const dir = path8.dirname(cfgPath);
3539
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
3540
+ writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
2961
3541
  }
2962
3542
  async function mergeRosterFromRemote(remote, paths) {
2963
3543
  return withRosterLock(async () => {
2964
3544
  const rosterPath = paths?.rosterPath ?? void 0;
2965
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
3545
+ const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
2966
3546
  const localEmployees = await loadEmployees(rosterPath);
2967
3547
  const localNames = new Set(localEmployees.map((e) => e.name));
2968
3548
  let added = 0;
@@ -2983,15 +3563,15 @@ async function mergeRosterFromRemote(remote, paths) {
2983
3563
  ) ?? lookupKey;
2984
3564
  const remoteIdentity = remote.identities[matchedKey];
2985
3565
  if (remoteIdentity) {
2986
- if (!existsSync7(identityDir)) mkdirSync2(identityDir, { recursive: true });
2987
- const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
3566
+ if (!existsSync8(identityDir)) mkdirSync3(identityDir, { recursive: true });
3567
+ const idPath = path8.join(identityDir, `${remoteEmp.name}.md`);
2988
3568
  let localIdentity = null;
2989
3569
  try {
2990
- localIdentity = existsSync7(idPath) ? readFileSync5(idPath, "utf-8") : null;
3570
+ localIdentity = existsSync8(idPath) ? readFileSync6(idPath, "utf-8") : null;
2991
3571
  } catch {
2992
3572
  }
2993
3573
  if (localIdentity !== remoteIdentity) {
2994
- writeFileSync3(idPath, remoteIdentity, "utf-8");
3574
+ writeFileSync4(idPath, remoteIdentity, "utf-8");
2995
3575
  identitiesUpdated++;
2996
3576
  }
2997
3577
  }
@@ -3444,13 +4024,14 @@ var init_cloud_sync = __esm({
3444
4024
  init_compress();
3445
4025
  init_license();
3446
4026
  init_config();
4027
+ init_crdt_sync();
3447
4028
  init_employees();
3448
4029
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
3449
4030
  FETCH_TIMEOUT_MS = 3e4;
3450
4031
  PUSH_BATCH_SIZE = 5e3;
3451
- ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
4032
+ ROSTER_LOCK_PATH = path8.join(EXE_AI_DIR, "roster-merge.lock");
3452
4033
  LOCK_STALE_MS = 3e4;
3453
- ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
4034
+ ROSTER_DELETIONS_PATH = path8.join(EXE_AI_DIR, "roster-deletions.json");
3454
4035
  }
3455
4036
  });
3456
4037
 
@@ -4215,17 +4796,17 @@ __export(identity_exports, {
4215
4796
  listIdentities: () => listIdentities,
4216
4797
  updateIdentity: () => updateIdentity
4217
4798
  });
4218
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
4799
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
4219
4800
  import { readdirSync as readdirSync2 } from "fs";
4220
- import path8 from "path";
4801
+ import path9 from "path";
4221
4802
  import { createHash as createHash2 } from "crypto";
4222
4803
  function ensureDir() {
4223
- if (!existsSync8(IDENTITY_DIR)) {
4224
- mkdirSync3(IDENTITY_DIR, { recursive: true });
4804
+ if (!existsSync9(IDENTITY_DIR)) {
4805
+ mkdirSync4(IDENTITY_DIR, { recursive: true });
4225
4806
  }
4226
4807
  }
4227
4808
  function identityPath(agentId) {
4228
- return path8.join(IDENTITY_DIR, `${agentId}.md`);
4809
+ return path9.join(IDENTITY_DIR, `${agentId}.md`);
4229
4810
  }
4230
4811
  function parseFrontmatter(raw) {
4231
4812
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -4266,8 +4847,8 @@ function contentHash(content) {
4266
4847
  }
4267
4848
  function getIdentity(agentId) {
4268
4849
  const filePath = identityPath(agentId);
4269
- if (!existsSync8(filePath)) return null;
4270
- const raw = readFileSync6(filePath, "utf-8");
4850
+ if (!existsSync9(filePath)) return null;
4851
+ const raw = readFileSync7(filePath, "utf-8");
4271
4852
  const { frontmatter, body } = parseFrontmatter(raw);
4272
4853
  return {
4273
4854
  agentId,
@@ -4281,7 +4862,7 @@ async function updateIdentity(agentId, content, updatedBy) {
4281
4862
  ensureDir();
4282
4863
  const filePath = identityPath(agentId);
4283
4864
  const hash = contentHash(content);
4284
- writeFileSync4(filePath, content, "utf-8");
4865
+ writeFileSync5(filePath, content, "utf-8");
4285
4866
  try {
4286
4867
  const client = getClient();
4287
4868
  await client.execute({
@@ -4337,7 +4918,7 @@ var init_identity = __esm({
4337
4918
  "use strict";
4338
4919
  init_config();
4339
4920
  init_database();
4340
- IDENTITY_DIR = path8.join(EXE_AI_DIR, "identity");
4921
+ IDENTITY_DIR = path9.join(EXE_AI_DIR, "identity");
4341
4922
  }
4342
4923
  });
4343
4924
 
@@ -4877,36 +5458,36 @@ __export(session_wrappers_exports, {
4877
5458
  generateSessionWrappers: () => generateSessionWrappers
4878
5459
  });
4879
5460
  import {
4880
- existsSync as existsSync9,
4881
- readFileSync as readFileSync7,
4882
- writeFileSync as writeFileSync5,
4883
- mkdirSync as mkdirSync4,
5461
+ existsSync as existsSync10,
5462
+ readFileSync as readFileSync8,
5463
+ writeFileSync as writeFileSync6,
5464
+ mkdirSync as mkdirSync5,
4884
5465
  chmodSync,
4885
5466
  readdirSync as readdirSync3,
4886
- unlinkSync as unlinkSync5
5467
+ unlinkSync as unlinkSync6
4887
5468
  } from "fs";
4888
- import path9 from "path";
4889
- import { homedir as homedir2 } from "os";
5469
+ import path10 from "path";
5470
+ import { homedir as homedir3 } from "os";
4890
5471
  function generateSessionWrappers(packageRoot, homeDir) {
4891
- const home = homeDir ?? homedir2();
4892
- const binDir = path9.join(home, ".exe-os", "bin");
4893
- const rosterPath = path9.join(home, ".exe-os", "exe-employees.json");
4894
- mkdirSync4(binDir, { recursive: true });
4895
- const exeStartDst = path9.join(binDir, "exe-start");
5472
+ const home = homeDir ?? homedir3();
5473
+ const binDir = path10.join(home, ".exe-os", "bin");
5474
+ const rosterPath = path10.join(home, ".exe-os", "exe-employees.json");
5475
+ mkdirSync5(binDir, { recursive: true });
5476
+ const exeStartDst = path10.join(binDir, "exe-start");
4896
5477
  const candidates = [
4897
- path9.join(packageRoot, "dist", "bin", "exe-start.sh"),
4898
- path9.join(packageRoot, "src", "bin", "exe-start.sh")
5478
+ path10.join(packageRoot, "dist", "bin", "exe-start.sh"),
5479
+ path10.join(packageRoot, "src", "bin", "exe-start.sh")
4899
5480
  ];
4900
5481
  for (const src of candidates) {
4901
- if (existsSync9(src)) {
4902
- writeFileSync5(exeStartDst, readFileSync7(src));
5482
+ if (existsSync10(src)) {
5483
+ writeFileSync6(exeStartDst, readFileSync8(src));
4903
5484
  chmodSync(exeStartDst, 493);
4904
5485
  break;
4905
5486
  }
4906
5487
  }
4907
5488
  let employees = [];
4908
5489
  try {
4909
- employees = JSON.parse(readFileSync7(rosterPath, "utf8"));
5490
+ employees = JSON.parse(readFileSync8(rosterPath, "utf8"));
4910
5491
  } catch {
4911
5492
  return { created: 0, pathConfigured: false };
4912
5493
  }
@@ -4916,11 +5497,11 @@ function generateSessionWrappers(packageRoot, homeDir) {
4916
5497
  try {
4917
5498
  for (const f of readdirSync3(binDir)) {
4918
5499
  if (f === "exe-start") continue;
4919
- const fPath = path9.join(binDir, f);
5500
+ const fPath = path10.join(binDir, f);
4920
5501
  try {
4921
- const content = readFileSync7(fPath, "utf8");
5502
+ const content = readFileSync8(fPath, "utf8");
4922
5503
  if (content.includes("exe-start")) {
4923
- unlinkSync5(fPath);
5504
+ unlinkSync6(fPath);
4924
5505
  }
4925
5506
  } catch {
4926
5507
  }
@@ -4933,8 +5514,8 @@ exec "${exeStartDst}" "$0" "$@"
4933
5514
  `;
4934
5515
  for (const emp of employees) {
4935
5516
  for (let n = 1; n <= MAX_N; n++) {
4936
- const wrapperPath = path9.join(binDir, `${emp.name}${n}`);
4937
- writeFileSync5(wrapperPath, wrapperContent);
5517
+ const wrapperPath = path10.join(binDir, `${emp.name}${n}`);
5518
+ writeFileSync6(wrapperPath, wrapperContent);
4938
5519
  chmodSync(wrapperPath, 493);
4939
5520
  created++;
4940
5521
  }
@@ -4953,24 +5534,24 @@ export PATH="${binDir}:$PATH"
4953
5534
  const shell = process.env.SHELL ?? "/bin/bash";
4954
5535
  const profilePaths = [];
4955
5536
  if (shell.includes("zsh")) {
4956
- profilePaths.push(path9.join(home, ".zshrc"));
5537
+ profilePaths.push(path10.join(home, ".zshrc"));
4957
5538
  } else if (shell.includes("bash")) {
4958
- profilePaths.push(path9.join(home, ".bashrc"));
4959
- profilePaths.push(path9.join(home, ".bash_profile"));
5539
+ profilePaths.push(path10.join(home, ".bashrc"));
5540
+ profilePaths.push(path10.join(home, ".bash_profile"));
4960
5541
  } else {
4961
- profilePaths.push(path9.join(home, ".profile"));
5542
+ profilePaths.push(path10.join(home, ".profile"));
4962
5543
  }
4963
5544
  for (const profilePath of profilePaths) {
4964
5545
  try {
4965
5546
  let content = "";
4966
5547
  try {
4967
- content = readFileSync7(profilePath, "utf8");
5548
+ content = readFileSync8(profilePath, "utf8");
4968
5549
  } catch {
4969
5550
  }
4970
5551
  if (content.includes(".exe-os/bin")) {
4971
5552
  return false;
4972
5553
  }
4973
- writeFileSync5(profilePath, content + exportLine);
5554
+ writeFileSync6(profilePath, content + exportLine);
4974
5555
  return true;
4975
5556
  } catch {
4976
5557
  continue;
@@ -4990,9 +5571,9 @@ var init_session_wrappers = __esm({
4990
5571
  init_config();
4991
5572
  init_keychain();
4992
5573
  import crypto3 from "crypto";
4993
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync6 } from "fs";
5574
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync7 } from "fs";
4994
5575
  import os4 from "os";
4995
- import path10 from "path";
5576
+ import path11 from "path";
4996
5577
  import { createInterface } from "readline";
4997
5578
 
4998
5579
  // src/lib/model-downloader.ts
@@ -5084,36 +5665,36 @@ async function fileHash(filePath) {
5084
5665
 
5085
5666
  // src/lib/setup-wizard.ts
5086
5667
  function findPackageRoot2() {
5087
- let dir = path10.dirname(new URL(import.meta.url).pathname);
5088
- const root = path10.parse(dir).root;
5668
+ let dir = path11.dirname(new URL(import.meta.url).pathname);
5669
+ const root = path11.parse(dir).root;
5089
5670
  while (dir !== root) {
5090
- const pkgPath = path10.join(dir, "package.json");
5091
- if (existsSync10(pkgPath)) {
5671
+ const pkgPath = path11.join(dir, "package.json");
5672
+ if (existsSync11(pkgPath)) {
5092
5673
  try {
5093
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
5674
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
5094
5675
  if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
5095
5676
  } catch {
5096
5677
  }
5097
5678
  }
5098
- dir = path10.dirname(dir);
5679
+ dir = path11.dirname(dir);
5099
5680
  }
5100
5681
  return null;
5101
5682
  }
5102
- var SETUP_STATE_PATH = path10.join(os4.homedir(), ".exe-os", "setup-state.json");
5683
+ var SETUP_STATE_PATH = path11.join(os4.homedir(), ".exe-os", "setup-state.json");
5103
5684
  function loadSetupState() {
5104
5685
  try {
5105
- return JSON.parse(readFileSync8(SETUP_STATE_PATH, "utf8"));
5686
+ return JSON.parse(readFileSync9(SETUP_STATE_PATH, "utf8"));
5106
5687
  } catch {
5107
5688
  return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
5108
5689
  }
5109
5690
  }
5110
5691
  function saveSetupState(state) {
5111
- mkdirSync5(path10.dirname(SETUP_STATE_PATH), { recursive: true });
5112
- writeFileSync6(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
5692
+ mkdirSync6(path11.dirname(SETUP_STATE_PATH), { recursive: true });
5693
+ writeFileSync7(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
5113
5694
  }
5114
5695
  function clearSetupState() {
5115
5696
  try {
5116
- unlinkSync6(SETUP_STATE_PATH);
5697
+ unlinkSync7(SETUP_STATE_PATH);
5117
5698
  } catch {
5118
5699
  }
5119
5700
  }
@@ -5207,7 +5788,7 @@ async function runSetupWizard(opts = {}) {
5207
5788
  if (state.completedSteps.length > 0) {
5208
5789
  log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
5209
5790
  }
5210
- if (existsSync10(LEGACY_LANCE_PATH)) {
5791
+ if (existsSync11(LEGACY_LANCE_PATH)) {
5211
5792
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
5212
5793
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
5213
5794
  log(" The old directory will not be modified or deleted.");
@@ -5355,10 +5936,10 @@ async function runSetupWizard(opts = {}) {
5355
5936
  await saveConfig(config);
5356
5937
  log("");
5357
5938
  try {
5358
- const claudeJsonPath = path10.join(os4.homedir(), ".claude.json");
5939
+ const claudeJsonPath = path11.join(os4.homedir(), ".claude.json");
5359
5940
  let claudeJson = {};
5360
5941
  try {
5361
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5942
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
5362
5943
  } catch {
5363
5944
  }
5364
5945
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5367,7 +5948,7 @@ async function runSetupWizard(opts = {}) {
5367
5948
  if (!projects[dir]) projects[dir] = {};
5368
5949
  projects[dir].hasTrustDialogAccepted = true;
5369
5950
  }
5370
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5951
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5371
5952
  } catch {
5372
5953
  }
5373
5954
  state.completedSteps.push(5);
@@ -5406,7 +5987,7 @@ async function runSetupWizard(opts = {}) {
5406
5987
  let missingIdentities = [];
5407
5988
  for (const emp of roster) {
5408
5989
  const idPath = identityPath2(emp.name);
5409
- if (!existsSync10(idPath)) {
5990
+ if (!existsSync11(idPath)) {
5410
5991
  missingIdentities.push(emp.name);
5411
5992
  }
5412
5993
  }
@@ -5438,7 +6019,7 @@ async function runSetupWizard(opts = {}) {
5438
6019
  }
5439
6020
  missingIdentities = [];
5440
6021
  for (const emp of roster) {
5441
- if (!existsSync10(identityPath2(emp.name))) {
6022
+ if (!existsSync11(identityPath2(emp.name))) {
5442
6023
  missingIdentities.push(emp.name);
5443
6024
  }
5444
6025
  }
@@ -5469,11 +6050,12 @@ async function runSetupWizard(opts = {}) {
5469
6050
  saveSetupState(state);
5470
6051
  log("");
5471
6052
  } else if (!state.completedSteps.includes(6)) {
5472
- log("=== Your Team ===");
6053
+ log("=== Your First Hire ===");
5473
6054
  log("");
5474
- log("Every install starts with a COO \u2014 your right-hand operator.");
5475
- log("They hold the big picture: priorities, progress, and blockers.");
5476
- log("You talk to them. They coordinate everyone else.");
6055
+ log("Until now, you talked to one agent at a time.");
6056
+ log("Your COO changes that. You talk to them \u2014 they coordinate");
6057
+ log("your entire team. Engineers, marketers, specialists, all working");
6058
+ log("in parallel while you focus on what matters.");
5477
6059
  log("");
5478
6060
  const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
5479
6061
  cooName = (cooNameInput || "exe").toLowerCase();
@@ -5494,9 +6076,9 @@ async function runSetupWizard(opts = {}) {
5494
6076
  const cooIdentityContent = getIdentityTemplate("coo");
5495
6077
  if (cooIdentityContent) {
5496
6078
  const cooIdPath = identityPath2(cooName);
5497
- mkdirSync5(path10.dirname(cooIdPath), { recursive: true });
6079
+ mkdirSync6(path11.dirname(cooIdPath), { recursive: true });
5498
6080
  const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
5499
- writeFileSync6(cooIdPath, replaced, "utf-8");
6081
+ writeFileSync7(cooIdPath, replaced, "utf-8");
5500
6082
  }
5501
6083
  registerBinSymlinks2(cooName);
5502
6084
  createdEmployees.push({ name: cooName, role: "COO" });
@@ -5510,134 +6092,123 @@ async function runSetupWizard(opts = {}) {
5510
6092
  }
5511
6093
  log("");
5512
6094
  if (!state.completedSteps.includes(7)) {
5513
- log("=== Meet Your Specialists ===");
5514
- log("");
5515
- log("Your COO coordinates specialists. Here's who you can hire:");
5516
- log("");
5517
- log(" CTO (default: yoshi)");
5518
- log(" Your head of engineering. Architecture, code reviews, tech decisions.");
5519
- log(" Manages your projects and delegates to engineers when there's parallel work.");
5520
- log("");
5521
- log(" CMO (default: mari)");
5522
- log(" Design, brand, content, SEO. Builds your visual identity, writes");
5523
- log(" your copy, and gets you found online. Delegates to content specialists.");
5524
- log("");
5525
- log("Why separate roles instead of one AI that does everything?");
5526
- log("");
5527
- log("Memory saturates. One agent juggling architecture decisions AND landing page");
5528
- log("copy AND CI/CD AND social media loses context on all of them. Competing");
5529
- log("priorities \u2014 should I fix the auth bug or write the blog post? When you split");
5530
- log("responsibilities, each specialist stays sharp because they stay focused.");
5531
- log("Your COO connects them so nothing falls through the cracks.");
5532
- log("");
5533
- log("This is how real companies scale \u2014 you're just doing it with AI");
5534
- log("instead of headcount.");
5535
- log("");
5536
- await ask(rl, "Press Enter to continue. ");
5537
- state.completedSteps.push(7);
5538
- saveSetupState(state);
5539
- } else {
5540
- log("Step 7 already complete \u2014 skipping.");
5541
- }
5542
- if (!state.completedSteps.includes(8)) {
5543
- let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5544
6095
  let license;
5545
6096
  try {
5546
6097
  license = await checkLicense2();
5547
6098
  } catch {
5548
6099
  license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
5549
6100
  }
5550
- if (license.plan === "free") {
5551
- log("Your plan: Free \u2014 1 employee (your COO)");
5552
- log("");
5553
- log("The CTO and CMO are available on Solopreneur ($97/mo) \u2014 full engineering");
5554
- log("and marketing team, unlimited tasks, priority support.");
5555
- log("Get your key at https://askexe.com, then paste it here.");
5556
- log("");
5557
- const licenseInput = await ask(rl, "License key (or press Enter to skip): ");
5558
- if (licenseInput.startsWith("exe_sk_")) {
6101
+ let isLicensed = license.valid && license.plan !== "free";
6102
+ if (!isLicensed) {
6103
+ let licenseInput = await ask(rl, "Do you have an Exe OS license key? (press Enter to skip for free plan): ");
6104
+ if (licenseInput && !licenseInput.startsWith("exe_sk_")) {
6105
+ log("That doesn't look like a license key (should start with exe_sk_).");
6106
+ licenseInput = await ask(rl, "Try again (or press Enter to skip): ");
6107
+ }
6108
+ if (licenseInput && licenseInput.startsWith("exe_sk_")) {
5559
6109
  saveLicense2(licenseInput);
5560
6110
  mirrorLicenseKey2(licenseInput);
5561
6111
  try {
5562
- license = await validateLicense2(licenseInput);
6112
+ const result = await validateLicense2(licenseInput);
6113
+ if (result.valid && result.plan !== "free") {
6114
+ isLicensed = true;
6115
+ const planName = result.plan === "pro" ? "Solopreneur" : result.plan.charAt(0).toUpperCase() + result.plan.slice(1);
6116
+ log(`License activated. Plan: ${planName}`);
6117
+ } else if (!result.valid) {
6118
+ log("That license key is invalid or expired.");
6119
+ log("Get a new key at askexe.com.");
6120
+ } else {
6121
+ log("Key saved for cloud sync, but specialists require a paid plan.");
6122
+ log("Upgrade at askexe.com when you're ready.");
6123
+ }
5563
6124
  } catch {
5564
- log("Couldn't reach the license server \u2014 your key has been saved.");
5565
- log("Run exe-os --activate <key> anytime to finish activation.");
6125
+ isLicensed = true;
6126
+ log("Couldn't reach the license server \u2014 key saved, specialists enabled.");
5566
6127
  }
5567
- } else if (!licenseInput) {
5568
- log("You can activate anytime with: exe-os --activate <key>");
5569
- } else {
5570
- log("That doesn't look like a license key (should start with exe_sk_).");
5571
- log("You can activate anytime with: exe-os --activate <key>");
5572
6128
  }
5573
6129
  }
5574
- if (license.plan !== "free") {
5575
- const planName = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
5576
- log(`Your plan: ${planName} (up to ${license.employeeLimit} employees)`);
6130
+ if (!isLicensed) {
5577
6131
  log("");
5578
- const createCto = await ask(rl, "Create your CTO? (Y/n): ");
5579
- if (createCto.toLowerCase() !== "n") {
5580
- const ctoTemplate = getTemplateByRole2("CTO");
5581
- const ctoDefault = ctoTemplate?.name ?? "cto";
5582
- const ctoNameInput = await ask(rl, `Name your CTO (default: ${ctoDefault}): `);
5583
- const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
5584
- if (!employees.some((e) => e.name === ctoName)) {
5585
- const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5586
- const ctoEmployee = {
5587
- name: ctoName,
5588
- role: "CTO",
5589
- systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
5590
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
5591
- };
5592
- employees = addEmployee2(employees, ctoEmployee);
5593
- await saveEmployees2(employees, EMPLOYEES_PATH2);
5594
- }
5595
- const ctoIdentityContent = getIdentityTemplate("cto");
5596
- if (ctoIdentityContent) {
5597
- const ctoIdPath = identityPath2(ctoName);
5598
- mkdirSync5(path10.dirname(ctoIdPath), { recursive: true });
5599
- const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
5600
- writeFileSync6(ctoIdPath, replaced, "utf-8");
5601
- }
5602
- registerBinSymlinks2(ctoName);
5603
- createdEmployees.push({ name: ctoName, role: "CTO" });
5604
- log(`Created ${ctoName} (CTO)`);
5605
- }
5606
- const createCmo = await ask(rl, "Create your CMO? (Y/n): ");
5607
- if (createCmo.toLowerCase() !== "n") {
5608
- const cmoTemplate = getTemplateByRole2("CMO");
5609
- const cmoDefault = cmoTemplate?.name ?? "cmo";
5610
- const cmoNameInput = await ask(rl, `Name your CMO (default: ${cmoDefault}): `);
5611
- const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
5612
- if (!employees.some((e) => e.name === cmoName)) {
5613
- const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5614
- const cmoEmployee = {
5615
- name: cmoName,
5616
- role: "CMO",
5617
- systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
5618
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
5619
- };
5620
- employees = addEmployee2(employees, cmoEmployee);
5621
- await saveEmployees2(employees, EMPLOYEES_PATH2);
5622
- }
5623
- const cmoIdentityContent = getIdentityTemplate("cmo");
5624
- if (cmoIdentityContent) {
5625
- const cmoIdPath = identityPath2(cmoName);
5626
- mkdirSync5(path10.dirname(cmoIdPath), { recursive: true });
5627
- const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
5628
- writeFileSync6(cmoIdPath, replaced, "utf-8");
5629
- }
5630
- registerBinSymlinks2(cmoName);
5631
- createdEmployees.push({ name: cmoName, role: "CMO" });
5632
- log(`Created ${cmoName} (CMO)`);
5633
- }
6132
+ log("You're all set. Your COO handles everything from here.");
6133
+ log("When you're ready for specialists, add a license key at askexe.com.");
5634
6134
  log("");
6135
+ log(`Type your COO's name to start: /${cooName}`);
6136
+ state.completedSteps.push(7, 8);
6137
+ saveSetupState(state);
6138
+ } else {
6139
+ state.completedSteps.push(7);
6140
+ saveSetupState(state);
6141
+ }
6142
+ } else {
6143
+ log("Step 7 already complete \u2014 skipping.");
6144
+ }
6145
+ if (!state.completedSteps.includes(8)) {
6146
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
6147
+ log("=== Hire Your Team ===");
6148
+ log("");
6149
+ log("Your COO coordinates specialists. Each one stays focused on");
6150
+ log("their domain \u2014 no context switching, no competing priorities.");
6151
+ log("");
6152
+ const ctoTemplate = getTemplateByRole2("CTO");
6153
+ const ctoDefault = ctoTemplate?.name ?? "yoshi";
6154
+ log(` CTO \u2014 engineering, architecture, code reviews (default: ${ctoDefault})`);
6155
+ const cmoTemplate = getTemplateByRole2("CMO");
6156
+ const cmoDefault = cmoTemplate?.name ?? "mari";
6157
+ log(` CMO \u2014 design, brand, content, marketing (default: ${cmoDefault})`);
6158
+ log("");
6159
+ const ctoNameInput = await ask(rl, `Name your CTO (default: ${ctoDefault}): `);
6160
+ const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
6161
+ if (!employees.some((e) => e.name === ctoName)) {
6162
+ const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
6163
+ const ctoEmployee = {
6164
+ name: ctoName,
6165
+ role: "CTO",
6166
+ systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
6167
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6168
+ };
6169
+ employees = addEmployee2(employees, ctoEmployee);
6170
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5635
6171
  }
6172
+ const ctoIdentityContent = getIdentityTemplate("cto");
6173
+ if (ctoIdentityContent) {
6174
+ const ctoIdPath = identityPath2(ctoName);
6175
+ mkdirSync6(path11.dirname(ctoIdPath), { recursive: true });
6176
+ const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
6177
+ writeFileSync7(ctoIdPath, replaced, "utf-8");
6178
+ }
6179
+ registerBinSymlinks2(ctoName);
6180
+ createdEmployees.push({ name: ctoName, role: "CTO" });
6181
+ log(`Created ${ctoName} (CTO)`);
6182
+ const cmoNameInput = await ask(rl, `Name your CMO (default: ${cmoDefault}): `);
6183
+ const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
6184
+ if (!employees.some((e) => e.name === cmoName)) {
6185
+ const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
6186
+ const cmoEmployee = {
6187
+ name: cmoName,
6188
+ role: "CMO",
6189
+ systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
6190
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6191
+ };
6192
+ employees = addEmployee2(employees, cmoEmployee);
6193
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
6194
+ }
6195
+ const cmoIdentityContent = getIdentityTemplate("cmo");
6196
+ if (cmoIdentityContent) {
6197
+ const cmoIdPath = identityPath2(cmoName);
6198
+ mkdirSync6(path11.dirname(cmoIdPath), { recursive: true });
6199
+ const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
6200
+ writeFileSync7(cmoIdPath, replaced, "utf-8");
6201
+ }
6202
+ registerBinSymlinks2(cmoName);
6203
+ createdEmployees.push({ name: cmoName, role: "CMO" });
6204
+ log(`Created ${cmoName} (CMO)`);
6205
+ log("");
5636
6206
  state.completedSteps.push(8);
5637
6207
  saveSetupState(state);
5638
6208
  } else {
5639
6209
  log("Step 8 already complete \u2014 skipping.");
5640
6210
  }
6211
+ let pathJustConfigured = false;
5641
6212
  if (!pairingRosterPulled) {
5642
6213
  try {
5643
6214
  const { generateSessionWrappers: generateSessionWrappers2 } = await Promise.resolve().then(() => (init_session_wrappers(), session_wrappers_exports));
@@ -5647,6 +6218,11 @@ async function runSetupWizard(opts = {}) {
5647
6218
  if (wrapResult.created > 0) {
5648
6219
  log(`Session shortcuts generated (${cooName}1, ${cooName}2, ...)`);
5649
6220
  }
6221
+ if (wrapResult.pathConfigured) {
6222
+ const binDir = path11.join(os4.homedir(), ".exe-os", "bin");
6223
+ process.env.PATH = `${binDir}:${process.env.PATH ?? ""}`;
6224
+ pathJustConfigured = true;
6225
+ }
5650
6226
  }
5651
6227
  } catch {
5652
6228
  }
@@ -5679,12 +6255,35 @@ async function runSetupWizard(opts = {}) {
5679
6255
  if (createdEmployees.length > 0) {
5680
6256
  log("Team: " + createdEmployees.map((e) => `${e.name} (${e.role})`).join(", "));
5681
6257
  }
6258
+ let version = "";
6259
+ const pkgRoot2 = findPackageRoot2();
6260
+ if (pkgRoot2) {
6261
+ try {
6262
+ version = JSON.parse(readFileSync9(path11.join(pkgRoot2, "package.json"), "utf-8")).version;
6263
+ } catch {
6264
+ }
6265
+ }
6266
+ const W = 27;
6267
+ const center = (s) => {
6268
+ const pad = W - s.length;
6269
+ return " ".repeat(Math.floor(pad / 2)) + s + " ".repeat(Math.ceil(pad / 2));
6270
+ };
6271
+ log("");
6272
+ log(` ${"\u2554" + "\u2550".repeat(W) + "\u2557"}`);
6273
+ log(` ${"\u2551" + center("e x e O S") + "\u2551"}`);
6274
+ if (version) log(` ${"\u2551" + center(`v${version}`) + "\u2551"}`);
6275
+ log(` ${"\u255A" + "\u2550".repeat(W) + "\u255D"}`);
5682
6276
  log("");
5683
6277
  log("=== Next Steps ===");
5684
6278
  log("");
5685
- log(" 1. Restart your terminal (or run: source ~/.zshrc)");
5686
- log(` 2. cd into a project folder: cd ~/my-project`);
5687
- log(` 3. Launch your COO: ${cooName}1`);
6279
+ log(` cd into a project folder: cd ~/my-project`);
6280
+ log(` Launch your COO: ${cooName}1`);
6281
+ if (pathJustConfigured) {
6282
+ const shell = process.env.SHELL ?? "/bin/zsh";
6283
+ const rcFile = shell.includes("bash") ? "~/.bashrc" : "~/.zshrc";
6284
+ log("");
6285
+ log(` (Run \`source ${rcFile}\` first, or open a new terminal)`);
6286
+ }
5688
6287
  log("");
5689
6288
  } finally {
5690
6289
  rl.close();