@askexenow/exe-os 0.8.83 → 0.8.86

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 (103) 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 +154 -21
  5. package/dist/bin/cli.js +14678 -12676
  6. package/dist/bin/exe-agent-config.js +242 -0
  7. package/dist/bin/exe-agent.js +100 -91
  8. package/dist/bin/exe-assign.js +1003 -854
  9. package/dist/bin/exe-boot.js +1420 -485
  10. package/dist/bin/exe-call.js +10 -0
  11. package/dist/bin/exe-cloud.js +29 -6
  12. package/dist/bin/exe-dispatch.js +572 -271
  13. package/dist/bin/exe-doctor.js +403 -6
  14. package/dist/bin/exe-export-behaviors.js +175 -72
  15. package/dist/bin/exe-forget.js +102 -3
  16. package/dist/bin/exe-gateway.js +796 -292
  17. package/dist/bin/exe-healthcheck.js +134 -1
  18. package/dist/bin/exe-heartbeat.js +172 -36
  19. package/dist/bin/exe-kill.js +175 -72
  20. package/dist/bin/exe-launch-agent.js +189 -76
  21. package/dist/bin/exe-link.js +927 -82
  22. package/dist/bin/exe-new-employee.js +60 -8
  23. package/dist/bin/exe-pending-messages.js +151 -19
  24. package/dist/bin/exe-pending-notifications.js +97 -2
  25. package/dist/bin/exe-pending-reviews.js +155 -22
  26. package/dist/bin/exe-rename.js +564 -23
  27. package/dist/bin/exe-review.js +231 -73
  28. package/dist/bin/exe-search.js +995 -228
  29. package/dist/bin/exe-session-cleanup.js +4930 -1664
  30. package/dist/bin/exe-settings.js +20 -5
  31. package/dist/bin/exe-start-codex.js +2598 -0
  32. package/dist/bin/exe-start.sh +15 -3
  33. package/dist/bin/exe-status.js +154 -21
  34. package/dist/bin/exe-team.js +97 -2
  35. package/dist/bin/git-sweep.js +1180 -363
  36. package/dist/bin/graph-backfill.js +175 -72
  37. package/dist/bin/graph-export.js +175 -72
  38. package/dist/bin/install.js +60 -7
  39. package/dist/bin/list-providers.js +1 -0
  40. package/dist/bin/scan-tasks.js +1185 -367
  41. package/dist/bin/setup.js +914 -270
  42. package/dist/bin/shard-migrate.js +175 -72
  43. package/dist/bin/update.js +1 -0
  44. package/dist/bin/wiki-sync.js +175 -72
  45. package/dist/gateway/index.js +792 -285
  46. package/dist/hooks/bug-report-worker.js +445 -135
  47. package/dist/hooks/commit-complete.js +1178 -361
  48. package/dist/hooks/error-recall.js +994 -228
  49. package/dist/hooks/ingest-worker.js +1799 -1234
  50. package/dist/hooks/ingest.js +3 -0
  51. package/dist/hooks/instructions-loaded.js +707 -97
  52. package/dist/hooks/notification.js +699 -89
  53. package/dist/hooks/post-compact.js +757 -109
  54. package/dist/hooks/pre-compact.js +1061 -244
  55. package/dist/hooks/pre-tool-use.js +787 -130
  56. package/dist/hooks/prompt-ingest-worker.js +242 -101
  57. package/dist/hooks/prompt-submit.js +1121 -299
  58. package/dist/hooks/response-ingest-worker.js +242 -101
  59. package/dist/hooks/session-end.js +4063 -397
  60. package/dist/hooks/session-start.js +1071 -254
  61. package/dist/hooks/stop.js +768 -120
  62. package/dist/hooks/subagent-stop.js +757 -109
  63. package/dist/hooks/summary-worker.js +1706 -1011
  64. package/dist/index.js +1821 -1098
  65. package/dist/lib/agent-config.js +167 -0
  66. package/dist/lib/cloud-sync.js +932 -88
  67. package/dist/lib/consolidation.js +2 -1
  68. package/dist/lib/database.js +642 -87
  69. package/dist/lib/db-daemon-client.js +503 -0
  70. package/dist/lib/device-registry.js +547 -7
  71. package/dist/lib/embedder.js +14 -28
  72. package/dist/lib/employee-templates.js +84 -74
  73. package/dist/lib/employees.js +9 -0
  74. package/dist/lib/exe-daemon-client.js +16 -29
  75. package/dist/lib/exe-daemon.js +2733 -1575
  76. package/dist/lib/hybrid-search.js +995 -228
  77. package/dist/lib/identity.js +87 -67
  78. package/dist/lib/keychain.js +9 -1
  79. package/dist/lib/messaging.js +103 -40
  80. package/dist/lib/reminders.js +91 -74
  81. package/dist/lib/runtime-table.js +16 -0
  82. package/dist/lib/schedules.js +96 -2
  83. package/dist/lib/session-wrappers.js +22 -0
  84. package/dist/lib/skill-learning.js +103 -85
  85. package/dist/lib/store.js +234 -73
  86. package/dist/lib/tasks.js +348 -134
  87. package/dist/lib/tmux-routing.js +422 -208
  88. package/dist/lib/token-spend.js +273 -0
  89. package/dist/lib/ws-client.js +11 -0
  90. package/dist/mcp/server.js +5742 -696
  91. package/dist/mcp/tools/complete-reminder.js +94 -77
  92. package/dist/mcp/tools/create-reminder.js +94 -77
  93. package/dist/mcp/tools/create-task.js +375 -152
  94. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  95. package/dist/mcp/tools/list-reminders.js +94 -77
  96. package/dist/mcp/tools/list-tasks.js +99 -31
  97. package/dist/mcp/tools/send-message.js +108 -45
  98. package/dist/mcp/tools/update-task.js +162 -77
  99. package/dist/runtime/index.js +1075 -258
  100. package/dist/tui/App.js +1333 -506
  101. package/package.json +6 -1
  102. package/src/commands/exe/agent-config.md +27 -0
  103. package/src/commands/exe/cc-doctor.md +10 -0
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,55 +3409,63 @@ 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"));
3454
+ } catch {
3455
+ }
3456
+ }
3457
+ let agentConfig;
3458
+ const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
3459
+ if (existsSync8(agentConfigPath)) {
3460
+ try {
3461
+ agentConfig = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
2874
3462
  } catch {
2875
3463
  }
2876
3464
  }
2877
3465
  const deletedNames = consumeRosterDeletions();
2878
- const content = JSON.stringify({ roster, identities, config, deletedNames });
3466
+ const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
2879
3467
  const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
2880
- return { roster, identities, config, deletedNames, version: hash };
3468
+ return { roster, identities, config, agentConfig, deletedNames, version: hash };
2881
3469
  }
2882
3470
  async function cloudPushRoster(config) {
2883
3471
  assertSecureEndpoint(config.endpoint);
@@ -2946,23 +3534,23 @@ async function cloudPullRoster(config) {
2946
3534
  }
2947
3535
  }
2948
3536
  function mergeConfig(remoteConfig, configPath) {
2949
- const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
3537
+ const cfgPath = configPath ?? path8.join(EXE_AI_DIR, "config.json");
2950
3538
  let local = {};
2951
- if (existsSync7(cfgPath)) {
3539
+ if (existsSync8(cfgPath)) {
2952
3540
  try {
2953
- local = JSON.parse(readFileSync5(cfgPath, "utf-8"));
3541
+ local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
2954
3542
  } catch {
2955
3543
  }
2956
3544
  }
2957
3545
  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");
3546
+ const dir = path8.dirname(cfgPath);
3547
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
3548
+ writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
2961
3549
  }
2962
3550
  async function mergeRosterFromRemote(remote, paths) {
2963
3551
  return withRosterLock(async () => {
2964
3552
  const rosterPath = paths?.rosterPath ?? void 0;
2965
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
3553
+ const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
2966
3554
  const localEmployees = await loadEmployees(rosterPath);
2967
3555
  const localNames = new Set(localEmployees.map((e) => e.name));
2968
3556
  let added = 0;
@@ -2983,15 +3571,15 @@ async function mergeRosterFromRemote(remote, paths) {
2983
3571
  ) ?? lookupKey;
2984
3572
  const remoteIdentity = remote.identities[matchedKey];
2985
3573
  if (remoteIdentity) {
2986
- if (!existsSync7(identityDir)) mkdirSync2(identityDir, { recursive: true });
2987
- const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
3574
+ if (!existsSync8(identityDir)) mkdirSync3(identityDir, { recursive: true });
3575
+ const idPath = path8.join(identityDir, `${remoteEmp.name}.md`);
2988
3576
  let localIdentity = null;
2989
3577
  try {
2990
- localIdentity = existsSync7(idPath) ? readFileSync5(idPath, "utf-8") : null;
3578
+ localIdentity = existsSync8(idPath) ? readFileSync6(idPath, "utf-8") : null;
2991
3579
  } catch {
2992
3580
  }
2993
3581
  if (localIdentity !== remoteIdentity) {
2994
- writeFileSync3(idPath, remoteIdentity, "utf-8");
3582
+ writeFileSync4(idPath, remoteIdentity, "utf-8");
2995
3583
  identitiesUpdated++;
2996
3584
  }
2997
3585
  }
@@ -3015,6 +3603,21 @@ async function mergeRosterFromRemote(remote, paths) {
3015
3603
  } catch {
3016
3604
  }
3017
3605
  }
3606
+ if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
3607
+ try {
3608
+ const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
3609
+ let local = {};
3610
+ if (existsSync8(agentConfigPath)) {
3611
+ try {
3612
+ local = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3613
+ } catch {
3614
+ }
3615
+ }
3616
+ const merged = { ...remote.agentConfig, ...local };
3617
+ writeFileSync4(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3618
+ } catch {
3619
+ }
3620
+ }
3018
3621
  return { added, identitiesUpdated };
3019
3622
  });
3020
3623
  }
@@ -3444,13 +4047,14 @@ var init_cloud_sync = __esm({
3444
4047
  init_compress();
3445
4048
  init_license();
3446
4049
  init_config();
4050
+ init_crdt_sync();
3447
4051
  init_employees();
3448
4052
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
3449
4053
  FETCH_TIMEOUT_MS = 3e4;
3450
4054
  PUSH_BATCH_SIZE = 5e3;
3451
- ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
4055
+ ROSTER_LOCK_PATH = path8.join(EXE_AI_DIR, "roster-merge.lock");
3452
4056
  LOCK_STALE_MS = 3e4;
3453
- ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
4057
+ ROSTER_DELETIONS_PATH = path8.join(EXE_AI_DIR, "roster-deletions.json");
3454
4058
  }
3455
4059
  });
3456
4060
 
@@ -4215,17 +4819,17 @@ __export(identity_exports, {
4215
4819
  listIdentities: () => listIdentities,
4216
4820
  updateIdentity: () => updateIdentity
4217
4821
  });
4218
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
4822
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
4219
4823
  import { readdirSync as readdirSync2 } from "fs";
4220
- import path8 from "path";
4824
+ import path9 from "path";
4221
4825
  import { createHash as createHash2 } from "crypto";
4222
4826
  function ensureDir() {
4223
- if (!existsSync8(IDENTITY_DIR)) {
4224
- mkdirSync3(IDENTITY_DIR, { recursive: true });
4827
+ if (!existsSync9(IDENTITY_DIR)) {
4828
+ mkdirSync4(IDENTITY_DIR, { recursive: true });
4225
4829
  }
4226
4830
  }
4227
4831
  function identityPath(agentId) {
4228
- return path8.join(IDENTITY_DIR, `${agentId}.md`);
4832
+ return path9.join(IDENTITY_DIR, `${agentId}.md`);
4229
4833
  }
4230
4834
  function parseFrontmatter(raw) {
4231
4835
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -4266,8 +4870,8 @@ function contentHash(content) {
4266
4870
  }
4267
4871
  function getIdentity(agentId) {
4268
4872
  const filePath = identityPath(agentId);
4269
- if (!existsSync8(filePath)) return null;
4270
- const raw = readFileSync6(filePath, "utf-8");
4873
+ if (!existsSync9(filePath)) return null;
4874
+ const raw = readFileSync7(filePath, "utf-8");
4271
4875
  const { frontmatter, body } = parseFrontmatter(raw);
4272
4876
  return {
4273
4877
  agentId,
@@ -4281,7 +4885,7 @@ async function updateIdentity(agentId, content, updatedBy) {
4281
4885
  ensureDir();
4282
4886
  const filePath = identityPath(agentId);
4283
4887
  const hash = contentHash(content);
4284
- writeFileSync4(filePath, content, "utf-8");
4888
+ writeFileSync5(filePath, content, "utf-8");
4285
4889
  try {
4286
4890
  const client = getClient();
4287
4891
  await client.execute({
@@ -4337,7 +4941,7 @@ var init_identity = __esm({
4337
4941
  "use strict";
4338
4942
  init_config();
4339
4943
  init_database();
4340
- IDENTITY_DIR = path8.join(EXE_AI_DIR, "identity");
4944
+ IDENTITY_DIR = path9.join(EXE_AI_DIR, "identity");
4341
4945
  }
4342
4946
  });
4343
4947
 
@@ -4877,36 +5481,36 @@ __export(session_wrappers_exports, {
4877
5481
  generateSessionWrappers: () => generateSessionWrappers
4878
5482
  });
4879
5483
  import {
4880
- existsSync as existsSync9,
4881
- readFileSync as readFileSync7,
4882
- writeFileSync as writeFileSync5,
4883
- mkdirSync as mkdirSync4,
5484
+ existsSync as existsSync10,
5485
+ readFileSync as readFileSync8,
5486
+ writeFileSync as writeFileSync6,
5487
+ mkdirSync as mkdirSync5,
4884
5488
  chmodSync,
4885
5489
  readdirSync as readdirSync3,
4886
- unlinkSync as unlinkSync5
5490
+ unlinkSync as unlinkSync6
4887
5491
  } from "fs";
4888
- import path9 from "path";
4889
- import { homedir as homedir2 } from "os";
5492
+ import path10 from "path";
5493
+ import { homedir as homedir3 } from "os";
4890
5494
  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");
5495
+ const home = homeDir ?? homedir3();
5496
+ const binDir = path10.join(home, ".exe-os", "bin");
5497
+ const rosterPath = path10.join(home, ".exe-os", "exe-employees.json");
5498
+ mkdirSync5(binDir, { recursive: true });
5499
+ const exeStartDst = path10.join(binDir, "exe-start");
4896
5500
  const candidates = [
4897
- path9.join(packageRoot, "dist", "bin", "exe-start.sh"),
4898
- path9.join(packageRoot, "src", "bin", "exe-start.sh")
5501
+ path10.join(packageRoot, "dist", "bin", "exe-start.sh"),
5502
+ path10.join(packageRoot, "src", "bin", "exe-start.sh")
4899
5503
  ];
4900
5504
  for (const src of candidates) {
4901
- if (existsSync9(src)) {
4902
- writeFileSync5(exeStartDst, readFileSync7(src));
5505
+ if (existsSync10(src)) {
5506
+ writeFileSync6(exeStartDst, readFileSync8(src));
4903
5507
  chmodSync(exeStartDst, 493);
4904
5508
  break;
4905
5509
  }
4906
5510
  }
4907
5511
  let employees = [];
4908
5512
  try {
4909
- employees = JSON.parse(readFileSync7(rosterPath, "utf8"));
5513
+ employees = JSON.parse(readFileSync8(rosterPath, "utf8"));
4910
5514
  } catch {
4911
5515
  return { created: 0, pathConfigured: false };
4912
5516
  }
@@ -4916,11 +5520,11 @@ function generateSessionWrappers(packageRoot, homeDir) {
4916
5520
  try {
4917
5521
  for (const f of readdirSync3(binDir)) {
4918
5522
  if (f === "exe-start") continue;
4919
- const fPath = path9.join(binDir, f);
5523
+ const fPath = path10.join(binDir, f);
4920
5524
  try {
4921
- const content = readFileSync7(fPath, "utf8");
5525
+ const content = readFileSync8(fPath, "utf8");
4922
5526
  if (content.includes("exe-start")) {
4923
- unlinkSync5(fPath);
5527
+ unlinkSync6(fPath);
4924
5528
  }
4925
5529
  } catch {
4926
5530
  }
@@ -4933,8 +5537,30 @@ exec "${exeStartDst}" "$0" "$@"
4933
5537
  `;
4934
5538
  for (const emp of employees) {
4935
5539
  for (let n = 1; n <= MAX_N; n++) {
4936
- const wrapperPath = path9.join(binDir, `${emp.name}${n}`);
4937
- writeFileSync5(wrapperPath, wrapperContent);
5540
+ const wrapperPath = path10.join(binDir, `${emp.name}${n}`);
5541
+ writeFileSync6(wrapperPath, wrapperContent);
5542
+ chmodSync(wrapperPath, 493);
5543
+ created++;
5544
+ }
5545
+ }
5546
+ const codexLauncherCandidates = [
5547
+ path10.join(packageRoot, "dist", "bin", "exe-start-codex.js"),
5548
+ path10.join(packageRoot, "src", "bin", "exe-start-codex.ts")
5549
+ ];
5550
+ let codexLauncher = null;
5551
+ for (const c of codexLauncherCandidates) {
5552
+ if (existsSync10(c)) {
5553
+ codexLauncher = c;
5554
+ break;
5555
+ }
5556
+ }
5557
+ if (codexLauncher) {
5558
+ for (const emp of employees) {
5559
+ const wrapperPath = path10.join(binDir, `${emp.name}-codex`);
5560
+ const content = `#!/bin/bash
5561
+ exec node "${codexLauncher}" --agent ${emp.name} "$@"
5562
+ `;
5563
+ writeFileSync6(wrapperPath, content);
4938
5564
  chmodSync(wrapperPath, 493);
4939
5565
  created++;
4940
5566
  }
@@ -4953,24 +5579,24 @@ export PATH="${binDir}:$PATH"
4953
5579
  const shell = process.env.SHELL ?? "/bin/bash";
4954
5580
  const profilePaths = [];
4955
5581
  if (shell.includes("zsh")) {
4956
- profilePaths.push(path9.join(home, ".zshrc"));
5582
+ profilePaths.push(path10.join(home, ".zshrc"));
4957
5583
  } else if (shell.includes("bash")) {
4958
- profilePaths.push(path9.join(home, ".bashrc"));
4959
- profilePaths.push(path9.join(home, ".bash_profile"));
5584
+ profilePaths.push(path10.join(home, ".bashrc"));
5585
+ profilePaths.push(path10.join(home, ".bash_profile"));
4960
5586
  } else {
4961
- profilePaths.push(path9.join(home, ".profile"));
5587
+ profilePaths.push(path10.join(home, ".profile"));
4962
5588
  }
4963
5589
  for (const profilePath of profilePaths) {
4964
5590
  try {
4965
5591
  let content = "";
4966
5592
  try {
4967
- content = readFileSync7(profilePath, "utf8");
5593
+ content = readFileSync8(profilePath, "utf8");
4968
5594
  } catch {
4969
5595
  }
4970
5596
  if (content.includes(".exe-os/bin")) {
4971
5597
  return false;
4972
5598
  }
4973
- writeFileSync5(profilePath, content + exportLine);
5599
+ writeFileSync6(profilePath, content + exportLine);
4974
5600
  return true;
4975
5601
  } catch {
4976
5602
  continue;
@@ -4990,9 +5616,9 @@ var init_session_wrappers = __esm({
4990
5616
  init_config();
4991
5617
  init_keychain();
4992
5618
  import crypto3 from "crypto";
4993
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync6 } from "fs";
5619
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync7 } from "fs";
4994
5620
  import os4 from "os";
4995
- import path10 from "path";
5621
+ import path11 from "path";
4996
5622
  import { createInterface } from "readline";
4997
5623
 
4998
5624
  // src/lib/model-downloader.ts
@@ -5084,36 +5710,36 @@ async function fileHash(filePath) {
5084
5710
 
5085
5711
  // src/lib/setup-wizard.ts
5086
5712
  function findPackageRoot2() {
5087
- let dir = path10.dirname(new URL(import.meta.url).pathname);
5088
- const root = path10.parse(dir).root;
5713
+ let dir = path11.dirname(new URL(import.meta.url).pathname);
5714
+ const root = path11.parse(dir).root;
5089
5715
  while (dir !== root) {
5090
- const pkgPath = path10.join(dir, "package.json");
5091
- if (existsSync10(pkgPath)) {
5716
+ const pkgPath = path11.join(dir, "package.json");
5717
+ if (existsSync11(pkgPath)) {
5092
5718
  try {
5093
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
5719
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
5094
5720
  if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
5095
5721
  } catch {
5096
5722
  }
5097
5723
  }
5098
- dir = path10.dirname(dir);
5724
+ dir = path11.dirname(dir);
5099
5725
  }
5100
5726
  return null;
5101
5727
  }
5102
- var SETUP_STATE_PATH = path10.join(os4.homedir(), ".exe-os", "setup-state.json");
5728
+ var SETUP_STATE_PATH = path11.join(os4.homedir(), ".exe-os", "setup-state.json");
5103
5729
  function loadSetupState() {
5104
5730
  try {
5105
- return JSON.parse(readFileSync8(SETUP_STATE_PATH, "utf8"));
5731
+ return JSON.parse(readFileSync9(SETUP_STATE_PATH, "utf8"));
5106
5732
  } catch {
5107
5733
  return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
5108
5734
  }
5109
5735
  }
5110
5736
  function saveSetupState(state) {
5111
- mkdirSync5(path10.dirname(SETUP_STATE_PATH), { recursive: true });
5112
- writeFileSync6(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
5737
+ mkdirSync6(path11.dirname(SETUP_STATE_PATH), { recursive: true });
5738
+ writeFileSync7(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
5113
5739
  }
5114
5740
  function clearSetupState() {
5115
5741
  try {
5116
- unlinkSync6(SETUP_STATE_PATH);
5742
+ unlinkSync7(SETUP_STATE_PATH);
5117
5743
  } catch {
5118
5744
  }
5119
5745
  }
@@ -5207,7 +5833,7 @@ async function runSetupWizard(opts = {}) {
5207
5833
  if (state.completedSteps.length > 0) {
5208
5834
  log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
5209
5835
  }
5210
- if (existsSync10(LEGACY_LANCE_PATH)) {
5836
+ if (existsSync11(LEGACY_LANCE_PATH)) {
5211
5837
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
5212
5838
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
5213
5839
  log(" The old directory will not be modified or deleted.");
@@ -5355,10 +5981,10 @@ async function runSetupWizard(opts = {}) {
5355
5981
  await saveConfig(config);
5356
5982
  log("");
5357
5983
  try {
5358
- const claudeJsonPath = path10.join(os4.homedir(), ".claude.json");
5984
+ const claudeJsonPath = path11.join(os4.homedir(), ".claude.json");
5359
5985
  let claudeJson = {};
5360
5986
  try {
5361
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5987
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
5362
5988
  } catch {
5363
5989
  }
5364
5990
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5367,7 +5993,7 @@ async function runSetupWizard(opts = {}) {
5367
5993
  if (!projects[dir]) projects[dir] = {};
5368
5994
  projects[dir].hasTrustDialogAccepted = true;
5369
5995
  }
5370
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5996
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5371
5997
  } catch {
5372
5998
  }
5373
5999
  state.completedSteps.push(5);
@@ -5406,7 +6032,7 @@ async function runSetupWizard(opts = {}) {
5406
6032
  let missingIdentities = [];
5407
6033
  for (const emp of roster) {
5408
6034
  const idPath = identityPath2(emp.name);
5409
- if (!existsSync10(idPath)) {
6035
+ if (!existsSync11(idPath)) {
5410
6036
  missingIdentities.push(emp.name);
5411
6037
  }
5412
6038
  }
@@ -5438,7 +6064,7 @@ async function runSetupWizard(opts = {}) {
5438
6064
  }
5439
6065
  missingIdentities = [];
5440
6066
  for (const emp of roster) {
5441
- if (!existsSync10(identityPath2(emp.name))) {
6067
+ if (!existsSync11(identityPath2(emp.name))) {
5442
6068
  missingIdentities.push(emp.name);
5443
6069
  }
5444
6070
  }
@@ -5469,11 +6095,12 @@ async function runSetupWizard(opts = {}) {
5469
6095
  saveSetupState(state);
5470
6096
  log("");
5471
6097
  } else if (!state.completedSteps.includes(6)) {
5472
- log("=== Your Team ===");
6098
+ log("=== Your First Hire ===");
5473
6099
  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.");
6100
+ log("Until now, you talked to one agent at a time.");
6101
+ log("Your COO changes that. You talk to them \u2014 they coordinate");
6102
+ log("your entire team. Engineers, marketers, specialists, all working");
6103
+ log("in parallel while you focus on what matters.");
5477
6104
  log("");
5478
6105
  const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
5479
6106
  cooName = (cooNameInput || "exe").toLowerCase();
@@ -5494,9 +6121,9 @@ async function runSetupWizard(opts = {}) {
5494
6121
  const cooIdentityContent = getIdentityTemplate("coo");
5495
6122
  if (cooIdentityContent) {
5496
6123
  const cooIdPath = identityPath2(cooName);
5497
- mkdirSync5(path10.dirname(cooIdPath), { recursive: true });
6124
+ mkdirSync6(path11.dirname(cooIdPath), { recursive: true });
5498
6125
  const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
5499
- writeFileSync6(cooIdPath, replaced, "utf-8");
6126
+ writeFileSync7(cooIdPath, replaced, "utf-8");
5500
6127
  }
5501
6128
  registerBinSymlinks2(cooName);
5502
6129
  createdEmployees.push({ name: cooName, role: "COO" });
@@ -5510,134 +6137,123 @@ async function runSetupWizard(opts = {}) {
5510
6137
  }
5511
6138
  log("");
5512
6139
  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
6140
  let license;
5545
6141
  try {
5546
6142
  license = await checkLicense2();
5547
6143
  } catch {
5548
6144
  license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
5549
6145
  }
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_")) {
6146
+ let isLicensed = license.valid && license.plan !== "free";
6147
+ if (!isLicensed) {
6148
+ let licenseInput = await ask(rl, "Do you have an Exe OS license key? (press Enter to skip for free plan): ");
6149
+ if (licenseInput && !licenseInput.startsWith("exe_sk_")) {
6150
+ log("That doesn't look like a license key (should start with exe_sk_).");
6151
+ licenseInput = await ask(rl, "Try again (or press Enter to skip): ");
6152
+ }
6153
+ if (licenseInput && licenseInput.startsWith("exe_sk_")) {
5559
6154
  saveLicense2(licenseInput);
5560
6155
  mirrorLicenseKey2(licenseInput);
5561
6156
  try {
5562
- license = await validateLicense2(licenseInput);
6157
+ const result = await validateLicense2(licenseInput);
6158
+ if (result.valid && result.plan !== "free") {
6159
+ isLicensed = true;
6160
+ const planName = result.plan === "pro" ? "Solopreneur" : result.plan.charAt(0).toUpperCase() + result.plan.slice(1);
6161
+ log(`License activated. Plan: ${planName}`);
6162
+ } else if (!result.valid) {
6163
+ log("That license key is invalid or expired.");
6164
+ log("Get a new key at askexe.com.");
6165
+ } else {
6166
+ log("Key saved for cloud sync, but specialists require a paid plan.");
6167
+ log("Upgrade at askexe.com when you're ready.");
6168
+ }
5563
6169
  } 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.");
6170
+ isLicensed = true;
6171
+ log("Couldn't reach the license server \u2014 key saved, specialists enabled.");
5566
6172
  }
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
6173
  }
5573
6174
  }
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)`);
6175
+ if (!isLicensed) {
5577
6176
  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
- }
6177
+ log("You're all set. Your COO handles everything from here.");
6178
+ log("When you're ready for specialists, add a license key at askexe.com.");
5634
6179
  log("");
6180
+ log(`Type your COO's name to start: /${cooName}`);
6181
+ state.completedSteps.push(7, 8);
6182
+ saveSetupState(state);
6183
+ } else {
6184
+ state.completedSteps.push(7);
6185
+ saveSetupState(state);
6186
+ }
6187
+ } else {
6188
+ log("Step 7 already complete \u2014 skipping.");
6189
+ }
6190
+ if (!state.completedSteps.includes(8)) {
6191
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
6192
+ log("=== Hire Your Team ===");
6193
+ log("");
6194
+ log("Your COO coordinates specialists. Each one stays focused on");
6195
+ log("their domain \u2014 no context switching, no competing priorities.");
6196
+ log("");
6197
+ const ctoTemplate = getTemplateByRole2("CTO");
6198
+ const ctoDefault = ctoTemplate?.name ?? "yoshi";
6199
+ log(` CTO \u2014 engineering, architecture, code reviews (default: ${ctoDefault})`);
6200
+ const cmoTemplate = getTemplateByRole2("CMO");
6201
+ const cmoDefault = cmoTemplate?.name ?? "mari";
6202
+ log(` CMO \u2014 design, brand, content, marketing (default: ${cmoDefault})`);
6203
+ log("");
6204
+ const ctoNameInput = await ask(rl, `Name your CTO (default: ${ctoDefault}): `);
6205
+ const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
6206
+ if (!employees.some((e) => e.name === ctoName)) {
6207
+ const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
6208
+ const ctoEmployee = {
6209
+ name: ctoName,
6210
+ role: "CTO",
6211
+ systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
6212
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6213
+ };
6214
+ employees = addEmployee2(employees, ctoEmployee);
6215
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
6216
+ }
6217
+ const ctoIdentityContent = getIdentityTemplate("cto");
6218
+ if (ctoIdentityContent) {
6219
+ const ctoIdPath = identityPath2(ctoName);
6220
+ mkdirSync6(path11.dirname(ctoIdPath), { recursive: true });
6221
+ const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
6222
+ writeFileSync7(ctoIdPath, replaced, "utf-8");
6223
+ }
6224
+ registerBinSymlinks2(ctoName);
6225
+ createdEmployees.push({ name: ctoName, role: "CTO" });
6226
+ log(`Created ${ctoName} (CTO)`);
6227
+ const cmoNameInput = await ask(rl, `Name your CMO (default: ${cmoDefault}): `);
6228
+ const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
6229
+ if (!employees.some((e) => e.name === cmoName)) {
6230
+ const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
6231
+ const cmoEmployee = {
6232
+ name: cmoName,
6233
+ role: "CMO",
6234
+ systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
6235
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6236
+ };
6237
+ employees = addEmployee2(employees, cmoEmployee);
6238
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
6239
+ }
6240
+ const cmoIdentityContent = getIdentityTemplate("cmo");
6241
+ if (cmoIdentityContent) {
6242
+ const cmoIdPath = identityPath2(cmoName);
6243
+ mkdirSync6(path11.dirname(cmoIdPath), { recursive: true });
6244
+ const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
6245
+ writeFileSync7(cmoIdPath, replaced, "utf-8");
5635
6246
  }
6247
+ registerBinSymlinks2(cmoName);
6248
+ createdEmployees.push({ name: cmoName, role: "CMO" });
6249
+ log(`Created ${cmoName} (CMO)`);
6250
+ log("");
5636
6251
  state.completedSteps.push(8);
5637
6252
  saveSetupState(state);
5638
6253
  } else {
5639
6254
  log("Step 8 already complete \u2014 skipping.");
5640
6255
  }
6256
+ let pathJustConfigured = false;
5641
6257
  if (!pairingRosterPulled) {
5642
6258
  try {
5643
6259
  const { generateSessionWrappers: generateSessionWrappers2 } = await Promise.resolve().then(() => (init_session_wrappers(), session_wrappers_exports));
@@ -5647,6 +6263,11 @@ async function runSetupWizard(opts = {}) {
5647
6263
  if (wrapResult.created > 0) {
5648
6264
  log(`Session shortcuts generated (${cooName}1, ${cooName}2, ...)`);
5649
6265
  }
6266
+ if (wrapResult.pathConfigured) {
6267
+ const binDir = path11.join(os4.homedir(), ".exe-os", "bin");
6268
+ process.env.PATH = `${binDir}:${process.env.PATH ?? ""}`;
6269
+ pathJustConfigured = true;
6270
+ }
5650
6271
  }
5651
6272
  } catch {
5652
6273
  }
@@ -5679,12 +6300,35 @@ async function runSetupWizard(opts = {}) {
5679
6300
  if (createdEmployees.length > 0) {
5680
6301
  log("Team: " + createdEmployees.map((e) => `${e.name} (${e.role})`).join(", "));
5681
6302
  }
6303
+ let version = "";
6304
+ const pkgRoot2 = findPackageRoot2();
6305
+ if (pkgRoot2) {
6306
+ try {
6307
+ version = JSON.parse(readFileSync9(path11.join(pkgRoot2, "package.json"), "utf-8")).version;
6308
+ } catch {
6309
+ }
6310
+ }
6311
+ const W = 27;
6312
+ const center = (s) => {
6313
+ const pad = W - s.length;
6314
+ return " ".repeat(Math.floor(pad / 2)) + s + " ".repeat(Math.ceil(pad / 2));
6315
+ };
6316
+ log("");
6317
+ log(` ${"\u2554" + "\u2550".repeat(W) + "\u2557"}`);
6318
+ log(` ${"\u2551" + center("e x e O S") + "\u2551"}`);
6319
+ if (version) log(` ${"\u2551" + center(`v${version}`) + "\u2551"}`);
6320
+ log(` ${"\u255A" + "\u2550".repeat(W) + "\u255D"}`);
5682
6321
  log("");
5683
6322
  log("=== Next Steps ===");
5684
6323
  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`);
6324
+ log(` cd into a project folder: cd ~/my-project`);
6325
+ log(` Launch your COO: ${cooName}1`);
6326
+ if (pathJustConfigured) {
6327
+ const shell = process.env.SHELL ?? "/bin/zsh";
6328
+ const rcFile = shell.includes("bash") ? "~/.bashrc" : "~/.zshrc";
6329
+ log("");
6330
+ log(` (Run \`source ${rcFile}\` first, or open a new terminal)`);
6331
+ }
5688
6332
  log("");
5689
6333
  } finally {
5690
6334
  rl.close();