@askexenow/exe-os 0.8.32 → 0.8.36

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 (87) hide show
  1. package/dist/bin/backfill-conversations.js +332 -348
  2. package/dist/bin/backfill-responses.js +72 -12
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1518 -1122
  6. package/dist/bin/exe-agent.js +4 -4
  7. package/dist/bin/exe-assign.js +80 -18
  8. package/dist/bin/exe-boot.js +408 -89
  9. package/dist/bin/exe-call.js +83 -24
  10. package/dist/bin/exe-dispatch.js +18 -10
  11. package/dist/bin/exe-doctor.js +63 -3
  12. package/dist/bin/exe-export-behaviors.js +64 -3
  13. package/dist/bin/exe-forget.js +69 -4
  14. package/dist/bin/exe-gateway.js +121 -36
  15. package/dist/bin/exe-heartbeat.js +77 -13
  16. package/dist/bin/exe-kill.js +64 -3
  17. package/dist/bin/exe-launch-agent.js +162 -35
  18. package/dist/bin/exe-link.js +946 -0
  19. package/dist/bin/exe-new-employee.js +121 -36
  20. package/dist/bin/exe-pending-messages.js +72 -7
  21. package/dist/bin/exe-pending-notifications.js +63 -3
  22. package/dist/bin/exe-pending-reviews.js +75 -10
  23. package/dist/bin/exe-rename.js +1287 -0
  24. package/dist/bin/exe-review.js +64 -4
  25. package/dist/bin/exe-search.js +79 -13
  26. package/dist/bin/exe-session-cleanup.js +91 -26
  27. package/dist/bin/exe-status.js +64 -4
  28. package/dist/bin/exe-team.js +64 -4
  29. package/dist/bin/git-sweep.js +71 -4
  30. package/dist/bin/graph-backfill.js +64 -3
  31. package/dist/bin/graph-export.js +64 -3
  32. package/dist/bin/install.js +3 -3
  33. package/dist/bin/scan-tasks.js +71 -4
  34. package/dist/bin/setup.js +156 -38
  35. package/dist/bin/shard-migrate.js +64 -3
  36. package/dist/bin/wiki-sync.js +64 -3
  37. package/dist/gateway/index.js +122 -37
  38. package/dist/hooks/bug-report-worker.js +209 -23
  39. package/dist/hooks/commit-complete.js +71 -4
  40. package/dist/hooks/error-recall.js +79 -13
  41. package/dist/hooks/ingest-worker.js +129 -43
  42. package/dist/hooks/instructions-loaded.js +71 -4
  43. package/dist/hooks/notification.js +71 -4
  44. package/dist/hooks/post-compact.js +71 -4
  45. package/dist/hooks/pre-compact.js +71 -4
  46. package/dist/hooks/pre-tool-use.js +413 -194
  47. package/dist/hooks/prompt-ingest-worker.js +82 -22
  48. package/dist/hooks/prompt-submit.js +103 -37
  49. package/dist/hooks/response-ingest-worker.js +87 -22
  50. package/dist/hooks/session-end.js +71 -4
  51. package/dist/hooks/session-start.js +79 -13
  52. package/dist/hooks/stop.js +71 -4
  53. package/dist/hooks/subagent-stop.js +71 -4
  54. package/dist/hooks/summary-worker.js +303 -50
  55. package/dist/index.js +134 -46
  56. package/dist/lib/cloud-sync.js +209 -15
  57. package/dist/lib/consolidation.js +4 -4
  58. package/dist/lib/database.js +64 -2
  59. package/dist/lib/device-registry.js +70 -3
  60. package/dist/lib/employee-templates.js +48 -22
  61. package/dist/lib/employees.js +34 -1
  62. package/dist/lib/exe-daemon.js +136 -53
  63. package/dist/lib/hybrid-search.js +79 -13
  64. package/dist/lib/identity-templates.js +57 -6
  65. package/dist/lib/identity.js +3 -3
  66. package/dist/lib/messaging.js +22 -14
  67. package/dist/lib/reminders.js +3 -3
  68. package/dist/lib/schedules.js +63 -3
  69. package/dist/lib/skill-learning.js +3 -3
  70. package/dist/lib/status-brief.js +63 -5
  71. package/dist/lib/store.js +64 -3
  72. package/dist/lib/task-router.js +4 -2
  73. package/dist/lib/tasks.js +48 -21
  74. package/dist/lib/tmux-routing.js +47 -20
  75. package/dist/mcp/server.js +727 -58
  76. package/dist/mcp/tools/complete-reminder.js +3 -3
  77. package/dist/mcp/tools/create-reminder.js +3 -3
  78. package/dist/mcp/tools/create-task.js +151 -24
  79. package/dist/mcp/tools/deactivate-behavior.js +3 -3
  80. package/dist/mcp/tools/list-reminders.js +3 -3
  81. package/dist/mcp/tools/list-tasks.js +17 -8
  82. package/dist/mcp/tools/send-message.js +24 -16
  83. package/dist/mcp/tools/update-task.js +25 -16
  84. package/dist/runtime/index.js +112 -24
  85. package/dist/tui/App.js +139 -36
  86. package/package.json +6 -2
  87. package/src/commands/exe/rename.md +12 -0
@@ -23,12 +23,68 @@ var init_memory = __esm({
23
23
  }
24
24
  });
25
25
 
26
+ // src/lib/db-retry.ts
27
+ function isBusyError(err) {
28
+ if (err instanceof Error) {
29
+ const msg = err.message.toLowerCase();
30
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
31
+ }
32
+ return false;
33
+ }
34
+ function delay(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+ async function retryOnBusy(fn, label) {
38
+ let lastError;
39
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
40
+ try {
41
+ return await fn();
42
+ } catch (err) {
43
+ lastError = err;
44
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
45
+ throw err;
46
+ }
47
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
48
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
49
+ process.stderr.write(
50
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
51
+ `
52
+ );
53
+ await delay(backoff + jitter);
54
+ }
55
+ }
56
+ throw lastError;
57
+ }
58
+ function wrapWithRetry(client) {
59
+ return new Proxy(client, {
60
+ get(target, prop, receiver) {
61
+ if (prop === "execute") {
62
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
63
+ }
64
+ if (prop === "batch") {
65
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
66
+ }
67
+ return Reflect.get(target, prop, receiver);
68
+ }
69
+ });
70
+ }
71
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
72
+ var init_db_retry = __esm({
73
+ "src/lib/db-retry.ts"() {
74
+ "use strict";
75
+ MAX_RETRIES = 3;
76
+ BASE_DELAY_MS = 200;
77
+ MAX_JITTER_MS = 300;
78
+ }
79
+ });
80
+
26
81
  // src/lib/database.ts
27
82
  import { createClient } from "@libsql/client";
28
83
  async function initDatabase(config) {
29
84
  if (_client) {
30
85
  _client.close();
31
86
  _client = null;
87
+ _resilientClient = null;
32
88
  }
33
89
  const opts = {
34
90
  url: `file:${config.dbPath}`
@@ -37,20 +93,27 @@ async function initDatabase(config) {
37
93
  opts.encryptionKey = config.encryptionKey;
38
94
  }
39
95
  _client = createClient(opts);
96
+ _resilientClient = wrapWithRetry(_client);
40
97
  }
41
98
  function isInitialized() {
42
99
  return _client !== null;
43
100
  }
44
101
  function getClient() {
102
+ if (!_resilientClient) {
103
+ throw new Error("Database client not initialized. Call initDatabase() first.");
104
+ }
105
+ return _resilientClient;
106
+ }
107
+ function getRawClient() {
45
108
  if (!_client) {
46
109
  throw new Error("Database client not initialized. Call initDatabase() first.");
47
110
  }
48
111
  return _client;
49
112
  }
50
113
  async function ensureSchema() {
51
- const client = getClient();
114
+ const client = getRawClient();
52
115
  await client.execute("PRAGMA journal_mode = WAL");
53
- await client.execute("PRAGMA busy_timeout = 5000");
116
+ await client.execute("PRAGMA busy_timeout = 30000");
54
117
  try {
55
118
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
56
119
  } catch {
@@ -839,11 +902,13 @@ async function ensureSchema() {
839
902
  }
840
903
  }
841
904
  }
842
- var _client, initTurso;
905
+ var _client, _resilientClient, initTurso;
843
906
  var init_database = __esm({
844
907
  "src/lib/database.ts"() {
845
908
  "use strict";
909
+ init_db_retry();
846
910
  _client = null;
911
+ _resilientClient = null;
847
912
  initTurso = initDatabase;
848
913
  }
849
914
  });
@@ -1290,12 +1355,12 @@ function shardExists(projectName) {
1290
1355
  }
1291
1356
  function listShards() {
1292
1357
  if (!existsSync3(SHARDS_DIR)) return [];
1293
- const { readdirSync: readdirSync2 } = __require("fs");
1294
- return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1358
+ const { readdirSync: readdirSync3 } = __require("fs");
1359
+ return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1295
1360
  }
1296
1361
  async function ensureShardSchema(client) {
1297
1362
  await client.execute("PRAGMA journal_mode = WAL");
1298
- await client.execute("PRAGMA busy_timeout = 5000");
1363
+ await client.execute("PRAGMA busy_timeout = 30000");
1299
1364
  try {
1300
1365
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1301
1366
  } catch {
@@ -1481,7 +1546,7 @@ var init_shard_manager = __esm({
1481
1546
 
1482
1547
  // src/lib/employees.ts
1483
1548
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1484
- import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
1549
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
1485
1550
  import { execSync } from "child_process";
1486
1551
  import path5 from "path";
1487
1552
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
@@ -1495,6 +1560,45 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1495
1560
  return [];
1496
1561
  }
1497
1562
  }
1563
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1564
+ await mkdir3(path5.dirname(employeesPath), { recursive: true });
1565
+ await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1566
+ }
1567
+ function registerBinSymlinks(name) {
1568
+ const created = [];
1569
+ const skipped = [];
1570
+ const errors = [];
1571
+ let exeBinPath;
1572
+ try {
1573
+ exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
1574
+ } catch {
1575
+ errors.push("Could not find 'exe' in PATH");
1576
+ return { created, skipped, errors };
1577
+ }
1578
+ const binDir = path5.dirname(exeBinPath);
1579
+ let target;
1580
+ try {
1581
+ target = readlinkSync(exeBinPath);
1582
+ } catch {
1583
+ errors.push("Could not read 'exe' symlink");
1584
+ return { created, skipped, errors };
1585
+ }
1586
+ for (const suffix of ["", "-opencode"]) {
1587
+ const linkName = `${name}${suffix}`;
1588
+ const linkPath = path5.join(binDir, linkName);
1589
+ if (existsSync5(linkPath)) {
1590
+ skipped.push(linkName);
1591
+ continue;
1592
+ }
1593
+ try {
1594
+ symlinkSync(target, linkPath);
1595
+ created.push(linkName);
1596
+ } catch (err) {
1597
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1598
+ }
1599
+ }
1600
+ return { created, skipped, errors };
1601
+ }
1498
1602
  var EMPLOYEES_PATH;
1499
1603
  var init_employees = __esm({
1500
1604
  "src/lib/employees.ts"() {
@@ -1505,7 +1609,7 @@ var init_employees = __esm({
1505
1609
  });
1506
1610
 
1507
1611
  // src/lib/license.ts
1508
- import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
1612
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
1509
1613
  import { randomUUID } from "crypto";
1510
1614
  import path6 from "path";
1511
1615
  import { jwtVerify, importSPKI } from "jose";
@@ -1513,14 +1617,14 @@ function loadDeviceId() {
1513
1617
  const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
1514
1618
  try {
1515
1619
  if (existsSync6(deviceJsonPath)) {
1516
- const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
1620
+ const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
1517
1621
  if (data.deviceId) return data.deviceId;
1518
1622
  }
1519
1623
  } catch {
1520
1624
  }
1521
1625
  try {
1522
1626
  if (existsSync6(DEVICE_ID_PATH)) {
1523
- const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
1627
+ const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
1524
1628
  if (id2) return id2;
1525
1629
  }
1526
1630
  } catch {
@@ -1533,7 +1637,7 @@ function loadDeviceId() {
1533
1637
  function loadLicense() {
1534
1638
  try {
1535
1639
  if (!existsSync6(LICENSE_PATH)) return null;
1536
- return readFileSync3(LICENSE_PATH, "utf8").trim();
1640
+ return readFileSync4(LICENSE_PATH, "utf8").trim();
1537
1641
  } catch {
1538
1642
  return null;
1539
1643
  }
@@ -1563,7 +1667,7 @@ async function verifyLicenseJwt(token) {
1563
1667
  async function getCachedLicense() {
1564
1668
  try {
1565
1669
  if (!existsSync6(CACHE_PATH)) return null;
1566
- const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
1670
+ const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
1567
1671
  if (!raw.token || typeof raw.token !== "string") return null;
1568
1672
  return await verifyLicenseJwt(raw.token);
1569
1673
  } catch {
@@ -1679,12 +1783,12 @@ __export(plan_limits_exports, {
1679
1783
  countActiveMemories: () => countActiveMemories,
1680
1784
  getLicenseSync: () => getLicenseSync
1681
1785
  });
1682
- import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
1786
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
1683
1787
  import path7 from "path";
1684
1788
  function getLicenseSync() {
1685
1789
  try {
1686
1790
  if (!existsSync7(CACHE_PATH2)) return freeLicense();
1687
- const raw = JSON.parse(readFileSync4(CACHE_PATH2, "utf8"));
1791
+ const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
1688
1792
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1689
1793
  const parts = raw.token.split(".");
1690
1794
  if (parts.length !== 3) return freeLicense();
@@ -1752,7 +1856,7 @@ function assertEmployeeLimitSync(rosterPath) {
1752
1856
  let count = 0;
1753
1857
  try {
1754
1858
  if (existsSync7(filePath)) {
1755
- const raw = readFileSync4(filePath, "utf8");
1859
+ const raw = readFileSync5(filePath, "utf8");
1756
1860
  const employees = JSON.parse(raw);
1757
1861
  count = Array.isArray(employees) ? employees.length : 0;
1758
1862
  }
@@ -1797,7 +1901,7 @@ var init_plan_limits = __esm({
1797
1901
  import net from "net";
1798
1902
  import { spawn } from "child_process";
1799
1903
  import { randomUUID as randomUUID2 } from "crypto";
1800
- import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1904
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1801
1905
  import path8 from "path";
1802
1906
  import { fileURLToPath } from "url";
1803
1907
  function handleData(chunk) {
@@ -1822,7 +1926,7 @@ function handleData(chunk) {
1822
1926
  function cleanupStaleFiles() {
1823
1927
  if (existsSync8(PID_PATH)) {
1824
1928
  try {
1825
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1929
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1826
1930
  if (pid > 0) {
1827
1931
  try {
1828
1932
  process.kill(pid, 0);
@@ -1970,11 +2074,11 @@ async function connectEmbedDaemon() {
1970
2074
  }
1971
2075
  }
1972
2076
  const start = Date.now();
1973
- let delay = 100;
2077
+ let delay2 = 100;
1974
2078
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1975
- await new Promise((r) => setTimeout(r, delay));
2079
+ await new Promise((r) => setTimeout(r, delay2));
1976
2080
  if (await connectToSocket()) return true;
1977
- delay = Math.min(delay * 2, 3e3);
2081
+ delay2 = Math.min(delay2 * 2, 3e3);
1978
2082
  }
1979
2083
  return false;
1980
2084
  }
@@ -2030,7 +2134,7 @@ function killAndRespawnDaemon() {
2030
2134
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2031
2135
  if (existsSync8(PID_PATH)) {
2032
2136
  try {
2033
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2137
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
2034
2138
  if (pid > 0) {
2035
2139
  try {
2036
2140
  process.kill(pid, "SIGKILL");
@@ -2066,11 +2170,11 @@ async function embedViaClient(text, priority = "high") {
2066
2170
  `);
2067
2171
  killAndRespawnDaemon();
2068
2172
  const start = Date.now();
2069
- let delay = 200;
2173
+ let delay2 = 200;
2070
2174
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2071
- await new Promise((r) => setTimeout(r, delay));
2175
+ await new Promise((r) => setTimeout(r, delay2));
2072
2176
  if (await connectToSocket()) break;
2073
- delay = Math.min(delay * 2, 3e3);
2177
+ delay2 = Math.min(delay2 * 2, 3e3);
2074
2178
  }
2075
2179
  if (!_connected) return null;
2076
2180
  }
@@ -2082,11 +2186,11 @@ async function embedViaClient(text, priority = "high") {
2082
2186
  `);
2083
2187
  killAndRespawnDaemon();
2084
2188
  const start = Date.now();
2085
- let delay = 200;
2189
+ let delay2 = 200;
2086
2190
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2087
- await new Promise((r) => setTimeout(r, delay));
2191
+ await new Promise((r) => setTimeout(r, delay2));
2088
2192
  if (await connectToSocket()) break;
2089
- delay = Math.min(delay * 2, 3e3);
2193
+ delay2 = Math.min(delay2 * 2, 3e3);
2090
2194
  }
2091
2195
  if (!_connected) return null;
2092
2196
  const retry = await sendRequest([text], priority);
@@ -2166,10 +2270,10 @@ async function disposeEmbedder() {
2166
2270
  async function embedDirect(text) {
2167
2271
  const llamaCpp = await import("node-llama-cpp");
2168
2272
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2169
- const { existsSync: existsSync10 } = await import("fs");
2170
- const path10 = await import("path");
2171
- const modelPath = path10.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2172
- if (!existsSync10(modelPath)) {
2273
+ const { existsSync: existsSync11 } = await import("fs");
2274
+ const path11 = await import("path");
2275
+ const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2276
+ if (!existsSync11(modelPath)) {
2173
2277
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
2174
2278
  }
2175
2279
  const llama = await llamaCpp.getLlama();
@@ -2279,10 +2383,25 @@ var init_compress = __esm({
2279
2383
  // src/lib/cloud-sync.ts
2280
2384
  var cloud_sync_exports = {};
2281
2385
  __export(cloud_sync_exports, {
2386
+ buildRosterBlob: () => buildRosterBlob,
2282
2387
  cloudPull: () => cloudPull,
2388
+ cloudPullRoster: () => cloudPullRoster,
2283
2389
  cloudPush: () => cloudPush,
2284
- cloudSync: () => cloudSync
2390
+ cloudPushRoster: () => cloudPushRoster,
2391
+ cloudSync: () => cloudSync,
2392
+ mergeRosterFromRemote: () => mergeRosterFromRemote
2285
2393
  });
2394
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync9, readdirSync as readdirSync2, mkdirSync as mkdirSync3, appendFileSync } from "fs";
2395
+ import path9 from "path";
2396
+ import { homedir } from "os";
2397
+ function logError(msg) {
2398
+ try {
2399
+ const logPath = path9.join(homedir(), ".exe-os", "workers.log");
2400
+ appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2401
+ `);
2402
+ } catch {
2403
+ }
2404
+ }
2286
2405
  function assertSecureEndpoint(endpoint) {
2287
2406
  if (endpoint.startsWith("https://")) return;
2288
2407
  if (endpoint.startsWith("http://")) {
@@ -2315,8 +2434,7 @@ async function cloudPush(records, maxVersion, config) {
2315
2434
  });
2316
2435
  return resp.ok;
2317
2436
  } catch (err) {
2318
- process.stderr.write(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
2319
- `);
2437
+ logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
2320
2438
  return false;
2321
2439
  }
2322
2440
  }
@@ -2347,8 +2465,7 @@ async function cloudPull(sinceVersion, config) {
2347
2465
  }
2348
2466
  return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
2349
2467
  } catch (err) {
2350
- process.stderr.write(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}
2351
- `);
2468
+ logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
2352
2469
  return { records: [], maxVersion: sinceVersion };
2353
2470
  }
2354
2471
  }
@@ -2437,8 +2554,137 @@ async function cloudSync(config) {
2437
2554
  pushed = records.length;
2438
2555
  }
2439
2556
  }
2557
+ try {
2558
+ await cloudPushRoster(config);
2559
+ } catch (err) {
2560
+ process.stderr.write(`[cloud-sync] Roster push warning: ${err instanceof Error ? err.message : String(err)}
2561
+ `);
2562
+ }
2563
+ try {
2564
+ await cloudPullRoster(config);
2565
+ } catch (err) {
2566
+ process.stderr.write(`[cloud-sync] Roster pull warning: ${err instanceof Error ? err.message : String(err)}
2567
+ `);
2568
+ }
2440
2569
  return { pushed, pulled };
2441
2570
  }
2571
+ function buildRosterBlob(paths) {
2572
+ const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
2573
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
2574
+ let roster = [];
2575
+ if (existsSync9(rosterPath)) {
2576
+ try {
2577
+ roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
2578
+ } catch {
2579
+ }
2580
+ }
2581
+ const identities = {};
2582
+ if (existsSync9(identityDir)) {
2583
+ for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
2584
+ try {
2585
+ identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
2586
+ } catch {
2587
+ }
2588
+ }
2589
+ }
2590
+ const content = JSON.stringify({ roster, identities });
2591
+ const hash = Buffer.from(content).length;
2592
+ return { roster, identities, version: hash };
2593
+ }
2594
+ async function cloudPushRoster(config) {
2595
+ assertSecureEndpoint(config.endpoint);
2596
+ const blob = buildRosterBlob();
2597
+ if (blob.roster.length === 0) return true;
2598
+ try {
2599
+ const client = getClient();
2600
+ const meta = await client.execute(
2601
+ "SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
2602
+ );
2603
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
2604
+ if (blob.version === lastVersion) return true;
2605
+ } catch {
2606
+ }
2607
+ try {
2608
+ const json = JSON.stringify(blob);
2609
+ const compressed = compress(Buffer.from(json, "utf8"));
2610
+ const encrypted = encryptSyncBlob(compressed);
2611
+ const resp = await fetch(`${config.endpoint}/sync/push-roster`, {
2612
+ method: "POST",
2613
+ headers: {
2614
+ Authorization: `Bearer ${config.apiKey}`,
2615
+ "Content-Type": "application/json",
2616
+ "X-Device-Id": loadDeviceId()
2617
+ },
2618
+ body: JSON.stringify({ blob: encrypted })
2619
+ });
2620
+ if (resp.ok) {
2621
+ try {
2622
+ const client = getClient();
2623
+ await client.execute({
2624
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
2625
+ args: [String(blob.version)]
2626
+ });
2627
+ } catch {
2628
+ }
2629
+ }
2630
+ return resp.ok;
2631
+ } catch (err) {
2632
+ process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
2633
+ `);
2634
+ return false;
2635
+ }
2636
+ }
2637
+ async function cloudPullRoster(config) {
2638
+ assertSecureEndpoint(config.endpoint);
2639
+ try {
2640
+ const resp = await fetch(`${config.endpoint}/sync/pull-roster`, {
2641
+ method: "GET",
2642
+ headers: {
2643
+ Authorization: `Bearer ${config.apiKey}`,
2644
+ "X-Device-Id": loadDeviceId()
2645
+ }
2646
+ });
2647
+ if (!resp.ok) return { added: 0 };
2648
+ const data = await resp.json();
2649
+ if (!data.blob) return { added: 0 };
2650
+ const compressed = decryptSyncBlob(data.blob);
2651
+ const json = decompress(compressed).toString("utf8");
2652
+ const remote = JSON.parse(json);
2653
+ return mergeRosterFromRemote(remote);
2654
+ } catch (err) {
2655
+ process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
2656
+ `);
2657
+ return { added: 0 };
2658
+ }
2659
+ }
2660
+ async function mergeRosterFromRemote(remote, paths) {
2661
+ const rosterPath = paths?.rosterPath ?? void 0;
2662
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
2663
+ const localEmployees = await loadEmployees(rosterPath);
2664
+ const localNames = new Set(localEmployees.map((e) => e.name));
2665
+ let added = 0;
2666
+ for (const remoteEmp of remote.roster) {
2667
+ if (localNames.has(remoteEmp.name)) continue;
2668
+ localEmployees.push(remoteEmp);
2669
+ localNames.add(remoteEmp.name);
2670
+ added++;
2671
+ if (remote.identities[`${remoteEmp.name}.md`]) {
2672
+ if (!existsSync9(identityDir)) mkdirSync3(identityDir, { recursive: true });
2673
+ const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
2674
+ if (!existsSync9(idPath)) {
2675
+ writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
2676
+ }
2677
+ }
2678
+ try {
2679
+ registerBinSymlinks(remoteEmp.name);
2680
+ } catch {
2681
+ }
2682
+ }
2683
+ if (added > 0) {
2684
+ await saveEmployees(localEmployees, rosterPath);
2685
+ }
2686
+ return { added };
2687
+ }
2442
2688
  var LOCALHOST_PATTERNS;
2443
2689
  var init_cloud_sync = __esm({
2444
2690
  "src/lib/cloud-sync.ts"() {
@@ -2448,6 +2694,8 @@ var init_cloud_sync = __esm({
2448
2694
  init_compress();
2449
2695
  init_plan_limits();
2450
2696
  init_license();
2697
+ init_config();
2698
+ init_employees();
2451
2699
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
2452
2700
  }
2453
2701
  });
@@ -2724,8 +2972,8 @@ async function writeNotification(notification) {
2724
2972
 
2725
2973
  // src/adapters/claude/hooks/summary-worker.ts
2726
2974
  import { execSync as execSync2 } from "child_process";
2727
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, openSync as openSync2, closeSync as closeSync2 } from "fs";
2728
- import path9 from "path";
2975
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
2976
+ import path10 from "path";
2729
2977
  async function main() {
2730
2978
  const agentId = process.env.AGENT_ID ?? "default";
2731
2979
  const agentRole = process.env.AGENT_ROLE ?? "employee";
@@ -2832,28 +3080,33 @@ async function main() {
2832
3080
  try {
2833
3081
  const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
2834
3082
  const vector = await embed2(summaryText);
2835
- await client.execute({
2836
- sql: `UPDATE memories SET vector = vector32(?) WHERE rowid = (
2837
- SELECT rowid FROM memories WHERE agent_id = ? AND tool_name = 'auto-summary'
2838
- ORDER BY timestamp DESC LIMIT 1
2839
- )`,
2840
- args: [vectorToBlob(vector), agentId]
3083
+ const rowResult = await client.execute({
3084
+ sql: `SELECT rowid FROM memories WHERE agent_id = ? AND tool_name = 'auto-summary'
3085
+ ORDER BY timestamp DESC LIMIT 1`,
3086
+ args: [agentId]
2841
3087
  });
3088
+ const targetRowid = rowResult.rows[0]?.rowid;
3089
+ if (targetRowid != null) {
3090
+ await client.execute({
3091
+ sql: `UPDATE memories SET vector = vector32(?) WHERE rowid = ?`,
3092
+ args: [vectorToBlob(vector), targetRowid]
3093
+ });
3094
+ }
2842
3095
  } catch (err) {
2843
3096
  process.stderr.write("[summary-worker] embed failed: " + (err instanceof Error ? err.message : String(err)) + "\n");
2844
3097
  }
2845
3098
  try {
2846
3099
  const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2847
- const flagPath = path9.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
2848
- if (existsSync9(flagPath)) {
3100
+ const flagPath = path10.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
3101
+ if (existsSync10(flagPath)) {
2849
3102
  const { spawn: spawn2 } = await import("child_process");
2850
3103
  const { fileURLToPath: fileURLToPath2 } = await import("url");
2851
3104
  const thisFile = fileURLToPath2(import.meta.url);
2852
- const backfillPath = path9.resolve(path9.dirname(thisFile), "backfill-vectors.js");
2853
- if (existsSync9(backfillPath)) {
3105
+ const backfillPath = path10.resolve(path10.dirname(thisFile), "backfill-vectors.js");
3106
+ if (existsSync10(backfillPath)) {
2854
3107
  const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2855
- const bLogPath = path9.join(exeDir2, "workers.log");
2856
- mkdirSync3(path9.dirname(bLogPath), { recursive: true });
3108
+ const bLogPath = path10.join(exeDir2, "workers.log");
3109
+ mkdirSync4(path10.dirname(bLogPath), { recursive: true });
2857
3110
  const bLogFd = openSync2(bLogPath, "a");
2858
3111
  const child = spawn2(process.execPath, [backfillPath], {
2859
3112
  detached: true,