@askexenow/exe-os 0.8.33 → 0.8.37

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 (89) hide show
  1. package/dist/bin/backfill-conversations.js +341 -349
  2. package/dist/bin/backfill-responses.js +81 -13
  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 +1737 -1117
  6. package/dist/bin/exe-assign.js +89 -19
  7. package/dist/bin/exe-boot.js +951 -101
  8. package/dist/bin/exe-call.js +61 -2
  9. package/dist/bin/exe-dispatch.js +61 -13
  10. package/dist/bin/exe-doctor.js +63 -3
  11. package/dist/bin/exe-export-behaviors.js +71 -3
  12. package/dist/bin/exe-forget.js +69 -4
  13. package/dist/bin/exe-gateway.js +178 -45
  14. package/dist/bin/exe-heartbeat.js +79 -14
  15. package/dist/bin/exe-kill.js +71 -3
  16. package/dist/bin/exe-launch-agent.js +148 -14
  17. package/dist/bin/exe-link.js +1437 -0
  18. package/dist/bin/exe-new-employee.js +98 -13
  19. package/dist/bin/exe-pending-messages.js +74 -8
  20. package/dist/bin/exe-pending-notifications.js +63 -3
  21. package/dist/bin/exe-pending-reviews.js +77 -11
  22. package/dist/bin/exe-rename.js +1287 -0
  23. package/dist/bin/exe-review.js +73 -5
  24. package/dist/bin/exe-search.js +88 -14
  25. package/dist/bin/exe-session-cleanup.js +102 -28
  26. package/dist/bin/exe-status.js +64 -4
  27. package/dist/bin/exe-team.js +64 -4
  28. package/dist/bin/git-sweep.js +80 -5
  29. package/dist/bin/graph-backfill.js +71 -3
  30. package/dist/bin/graph-export.js +71 -3
  31. package/dist/bin/install.js +38 -8
  32. package/dist/bin/scan-tasks.js +80 -5
  33. package/dist/bin/setup.js +128 -10
  34. package/dist/bin/shard-migrate.js +71 -3
  35. package/dist/bin/wiki-sync.js +71 -3
  36. package/dist/gateway/index.js +179 -46
  37. package/dist/hooks/bug-report-worker.js +254 -28
  38. package/dist/hooks/commit-complete.js +80 -5
  39. package/dist/hooks/error-recall.js +89 -15
  40. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  41. package/dist/hooks/ingest-worker.js +185 -51
  42. package/dist/hooks/ingest.js +1 -1
  43. package/dist/hooks/instructions-loaded.js +81 -6
  44. package/dist/hooks/notification.js +81 -6
  45. package/dist/hooks/post-compact.js +81 -6
  46. package/dist/hooks/pre-compact.js +81 -6
  47. package/dist/hooks/pre-tool-use.js +423 -196
  48. package/dist/hooks/prompt-ingest-worker.js +91 -23
  49. package/dist/hooks/prompt-submit.js +159 -45
  50. package/dist/hooks/response-ingest-worker.js +96 -23
  51. package/dist/hooks/session-end.js +81 -6
  52. package/dist/hooks/session-start.js +89 -15
  53. package/dist/hooks/stop.js +81 -6
  54. package/dist/hooks/subagent-stop.js +81 -6
  55. package/dist/hooks/summary-worker.js +807 -55
  56. package/dist/index.js +198 -60
  57. package/dist/lib/cloud-sync.js +703 -18
  58. package/dist/lib/consolidation.js +4 -4
  59. package/dist/lib/database.js +64 -2
  60. package/dist/lib/device-registry.js +70 -3
  61. package/dist/lib/employee-templates.js +26 -0
  62. package/dist/lib/employees.js +34 -1
  63. package/dist/lib/exe-daemon.js +207 -74
  64. package/dist/lib/hybrid-search.js +88 -14
  65. package/dist/lib/identity-templates.js +51 -0
  66. package/dist/lib/identity.js +3 -3
  67. package/dist/lib/messaging.js +65 -17
  68. package/dist/lib/reminders.js +3 -3
  69. package/dist/lib/schedules.js +63 -3
  70. package/dist/lib/skill-learning.js +3 -3
  71. package/dist/lib/status-brief.js +63 -5
  72. package/dist/lib/store.js +73 -4
  73. package/dist/lib/task-router.js +4 -2
  74. package/dist/lib/tasks.js +95 -28
  75. package/dist/lib/tmux-routing.js +92 -23
  76. package/dist/mcp/server.js +800 -74
  77. package/dist/mcp/tools/complete-reminder.js +3 -3
  78. package/dist/mcp/tools/create-reminder.js +3 -3
  79. package/dist/mcp/tools/create-task.js +198 -31
  80. package/dist/mcp/tools/deactivate-behavior.js +4 -4
  81. package/dist/mcp/tools/list-reminders.js +3 -3
  82. package/dist/mcp/tools/list-tasks.js +19 -9
  83. package/dist/mcp/tools/send-message.js +69 -21
  84. package/dist/mcp/tools/update-task.js +28 -18
  85. package/dist/runtime/index.js +166 -28
  86. package/dist/tui/App.js +193 -40
  87. package/package.json +7 -3
  88. package/src/commands/exe/afk.md +116 -0
  89. package/src/commands/exe/rename.md +12 -0
@@ -271,7 +271,7 @@ var init_config = __esm({
271
271
 
272
272
  // src/lib/employees.ts
273
273
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
274
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
274
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
275
275
  import { execSync } from "child_process";
276
276
  import path2 from "path";
277
277
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
@@ -289,12 +289,65 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
289
289
  await mkdir2(path2.dirname(employeesPath), { recursive: true });
290
290
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
291
291
  }
292
- var EMPLOYEES_PATH;
292
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
293
+ if (!existsSync2(employeesPath)) return [];
294
+ try {
295
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
296
+ } catch {
297
+ return [];
298
+ }
299
+ }
300
+ function getEmployee(employees, name) {
301
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
302
+ }
303
+ function isMultiInstance(agentName, employees) {
304
+ const roster = employees ?? loadEmployeesSync();
305
+ const emp = getEmployee(roster, agentName);
306
+ if (!emp) return false;
307
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
308
+ }
309
+ function registerBinSymlinks(name) {
310
+ const created = [];
311
+ const skipped = [];
312
+ const errors = [];
313
+ let exeBinPath;
314
+ try {
315
+ exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
316
+ } catch {
317
+ errors.push("Could not find 'exe' in PATH");
318
+ return { created, skipped, errors };
319
+ }
320
+ const binDir = path2.dirname(exeBinPath);
321
+ let target;
322
+ try {
323
+ target = readlinkSync(exeBinPath);
324
+ } catch {
325
+ errors.push("Could not read 'exe' symlink");
326
+ return { created, skipped, errors };
327
+ }
328
+ for (const suffix of ["", "-opencode"]) {
329
+ const linkName = `${name}${suffix}`;
330
+ const linkPath = path2.join(binDir, linkName);
331
+ if (existsSync2(linkPath)) {
332
+ skipped.push(linkName);
333
+ continue;
334
+ }
335
+ try {
336
+ symlinkSync(target, linkPath);
337
+ created.push(linkName);
338
+ } catch (err) {
339
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
340
+ }
341
+ }
342
+ return { created, skipped, errors };
343
+ }
344
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
293
345
  var init_employees = __esm({
294
346
  "src/lib/employees.ts"() {
295
347
  "use strict";
296
348
  init_config();
297
349
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
350
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
298
351
  }
299
352
  });
300
353
 
@@ -305,12 +358,68 @@ var init_memory = __esm({
305
358
  }
306
359
  });
307
360
 
361
+ // src/lib/db-retry.ts
362
+ function isBusyError(err) {
363
+ if (err instanceof Error) {
364
+ const msg = err.message.toLowerCase();
365
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
366
+ }
367
+ return false;
368
+ }
369
+ function delay(ms) {
370
+ return new Promise((resolve) => setTimeout(resolve, ms));
371
+ }
372
+ async function retryOnBusy(fn, label) {
373
+ let lastError;
374
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
375
+ try {
376
+ return await fn();
377
+ } catch (err) {
378
+ lastError = err;
379
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
380
+ throw err;
381
+ }
382
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
383
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
384
+ process.stderr.write(
385
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
386
+ `
387
+ );
388
+ await delay(backoff + jitter);
389
+ }
390
+ }
391
+ throw lastError;
392
+ }
393
+ function wrapWithRetry(client) {
394
+ return new Proxy(client, {
395
+ get(target, prop, receiver) {
396
+ if (prop === "execute") {
397
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
398
+ }
399
+ if (prop === "batch") {
400
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
401
+ }
402
+ return Reflect.get(target, prop, receiver);
403
+ }
404
+ });
405
+ }
406
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
407
+ var init_db_retry = __esm({
408
+ "src/lib/db-retry.ts"() {
409
+ "use strict";
410
+ MAX_RETRIES = 3;
411
+ BASE_DELAY_MS = 200;
412
+ MAX_JITTER_MS = 300;
413
+ }
414
+ });
415
+
308
416
  // src/lib/database.ts
309
417
  import { createClient } from "@libsql/client";
310
418
  async function initDatabase(config) {
311
419
  if (_client) {
312
420
  _client.close();
313
421
  _client = null;
422
+ _resilientClient = null;
314
423
  }
315
424
  const opts = {
316
425
  url: `file:${config.dbPath}`
@@ -319,20 +428,27 @@ async function initDatabase(config) {
319
428
  opts.encryptionKey = config.encryptionKey;
320
429
  }
321
430
  _client = createClient(opts);
431
+ _resilientClient = wrapWithRetry(_client);
322
432
  }
323
433
  function isInitialized() {
324
434
  return _client !== null;
325
435
  }
326
436
  function getClient() {
437
+ if (!_resilientClient) {
438
+ throw new Error("Database client not initialized. Call initDatabase() first.");
439
+ }
440
+ return _resilientClient;
441
+ }
442
+ function getRawClient() {
327
443
  if (!_client) {
328
444
  throw new Error("Database client not initialized. Call initDatabase() first.");
329
445
  }
330
446
  return _client;
331
447
  }
332
448
  async function ensureSchema() {
333
- const client = getClient();
449
+ const client = getRawClient();
334
450
  await client.execute("PRAGMA journal_mode = WAL");
335
- await client.execute("PRAGMA busy_timeout = 5000");
451
+ await client.execute("PRAGMA busy_timeout = 30000");
336
452
  try {
337
453
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
338
454
  } catch {
@@ -1121,11 +1237,13 @@ async function ensureSchema() {
1121
1237
  }
1122
1238
  }
1123
1239
  }
1124
- var _client, initTurso;
1240
+ var _client, _resilientClient, initTurso;
1125
1241
  var init_database = __esm({
1126
1242
  "src/lib/database.ts"() {
1127
1243
  "use strict";
1244
+ init_db_retry();
1128
1245
  _client = null;
1246
+ _resilientClient = null;
1129
1247
  initTurso = initDatabase;
1130
1248
  }
1131
1249
  });
@@ -1329,12 +1447,12 @@ function shardExists(projectName) {
1329
1447
  }
1330
1448
  function listShards() {
1331
1449
  if (!existsSync4(SHARDS_DIR)) return [];
1332
- const { readdirSync: readdirSync6 } = __require("fs");
1333
- return readdirSync6(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1450
+ const { readdirSync: readdirSync7 } = __require("fs");
1451
+ return readdirSync7(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1334
1452
  }
1335
1453
  async function ensureShardSchema(client) {
1336
1454
  await client.execute("PRAGMA journal_mode = WAL");
1337
- await client.execute("PRAGMA busy_timeout = 5000");
1455
+ await client.execute("PRAGMA busy_timeout = 30000");
1338
1456
  try {
1339
1457
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1340
1458
  } catch {
@@ -1579,7 +1697,7 @@ import crypto2 from "crypto";
1579
1697
  import path5 from "path";
1580
1698
  import os2 from "os";
1581
1699
  import {
1582
- readFileSync as readFileSync2,
1700
+ readFileSync as readFileSync3,
1583
1701
  readdirSync,
1584
1702
  unlinkSync,
1585
1703
  existsSync as existsSync5,
@@ -1706,7 +1824,7 @@ async function migrateJsonNotifications() {
1706
1824
  for (const file of files) {
1707
1825
  try {
1708
1826
  const filePath = path5.join(notifDir, file);
1709
- const data = JSON.parse(readFileSync2(filePath, "utf8"));
1827
+ const data = JSON.parse(readFileSync3(filePath, "utf8"));
1710
1828
  await client.execute({
1711
1829
  sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
1712
1830
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -1789,7 +1907,7 @@ __export(session_registry_exports, {
1789
1907
  pruneStaleSessions: () => pruneStaleSessions,
1790
1908
  registerSession: () => registerSession
1791
1909
  });
1792
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
1910
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
1793
1911
  import { execSync as execSync4 } from "child_process";
1794
1912
  import path7 from "path";
1795
1913
  import os3 from "os";
@@ -1809,7 +1927,7 @@ function registerSession(entry) {
1809
1927
  }
1810
1928
  function listSessions() {
1811
1929
  try {
1812
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
1930
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
1813
1931
  return JSON.parse(raw);
1814
1932
  } catch {
1815
1933
  return [];
@@ -2023,7 +2141,7 @@ var init_provider_table = __esm({
2023
2141
  });
2024
2142
 
2025
2143
  // src/lib/intercom-queue.ts
2026
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2144
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2027
2145
  import path8 from "path";
2028
2146
  import os4 from "os";
2029
2147
  function ensureDir() {
@@ -2033,7 +2151,7 @@ function ensureDir() {
2033
2151
  function readQueue() {
2034
2152
  try {
2035
2153
  if (!existsSync7(QUEUE_PATH)) return [];
2036
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
2154
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
2037
2155
  } catch {
2038
2156
  return [];
2039
2157
  }
@@ -2086,7 +2204,7 @@ __export(license_exports, {
2086
2204
  saveLicense: () => saveLicense,
2087
2205
  validateLicense: () => validateLicense
2088
2206
  });
2089
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2207
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2090
2208
  import { randomUUID } from "crypto";
2091
2209
  import path9 from "path";
2092
2210
  import { jwtVerify, importSPKI } from "jose";
@@ -2094,14 +2212,14 @@ function loadDeviceId() {
2094
2212
  const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
2095
2213
  try {
2096
2214
  if (existsSync8(deviceJsonPath)) {
2097
- const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
2215
+ const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
2098
2216
  if (data.deviceId) return data.deviceId;
2099
2217
  }
2100
2218
  } catch {
2101
2219
  }
2102
2220
  try {
2103
2221
  if (existsSync8(DEVICE_ID_PATH)) {
2104
- const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
2222
+ const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
2105
2223
  if (id2) return id2;
2106
2224
  }
2107
2225
  } catch {
@@ -2114,7 +2232,7 @@ function loadDeviceId() {
2114
2232
  function loadLicense() {
2115
2233
  try {
2116
2234
  if (!existsSync8(LICENSE_PATH)) return null;
2117
- return readFileSync6(LICENSE_PATH, "utf8").trim();
2235
+ return readFileSync7(LICENSE_PATH, "utf8").trim();
2118
2236
  } catch {
2119
2237
  return null;
2120
2238
  }
@@ -2148,7 +2266,7 @@ async function verifyLicenseJwt(token) {
2148
2266
  async function getCachedLicense() {
2149
2267
  try {
2150
2268
  if (!existsSync8(CACHE_PATH)) return null;
2151
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
2269
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
2152
2270
  if (!raw.token || typeof raw.token !== "string") return null;
2153
2271
  return await verifyLicenseJwt(raw.token);
2154
2272
  } catch {
@@ -2158,7 +2276,7 @@ async function getCachedLicense() {
2158
2276
  function readCachedToken() {
2159
2277
  try {
2160
2278
  if (!existsSync8(CACHE_PATH)) return null;
2161
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
2279
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
2162
2280
  return typeof raw.token === "string" ? raw.token : null;
2163
2281
  } catch {
2164
2282
  return null;
@@ -2370,12 +2488,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2370
2488
  });
2371
2489
 
2372
2490
  // src/lib/plan-limits.ts
2373
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
2491
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
2374
2492
  import path10 from "path";
2375
2493
  function getLicenseSync() {
2376
2494
  try {
2377
2495
  if (!existsSync9(CACHE_PATH2)) return freeLicense();
2378
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2496
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2379
2497
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2380
2498
  const parts = raw.token.split(".");
2381
2499
  if (parts.length !== 3) return freeLicense();
@@ -2414,7 +2532,7 @@ function assertEmployeeLimitSync(rosterPath) {
2414
2532
  let count = 0;
2415
2533
  try {
2416
2534
  if (existsSync9(filePath)) {
2417
- const raw = readFileSync7(filePath, "utf8");
2535
+ const raw = readFileSync8(filePath, "utf8");
2418
2536
  const employees = JSON.parse(raw);
2419
2537
  count = Array.isArray(employees) ? employees.length : 0;
2420
2538
  }
@@ -2457,10 +2575,46 @@ var init_plan_limits = __esm({
2457
2575
 
2458
2576
  // src/lib/tmux-routing.ts
2459
2577
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2460
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
2578
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
2461
2579
  import path11 from "path";
2462
2580
  import os5 from "os";
2463
2581
  import { fileURLToPath as fileURLToPath2 } from "url";
2582
+ import { unlinkSync as unlinkSync3 } from "fs";
2583
+ function spawnLockPath(sessionName) {
2584
+ return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2585
+ }
2586
+ function isProcessAlive(pid) {
2587
+ try {
2588
+ process.kill(pid, 0);
2589
+ return true;
2590
+ } catch {
2591
+ return false;
2592
+ }
2593
+ }
2594
+ function acquireSpawnLock(sessionName) {
2595
+ if (!existsSync10(SPAWN_LOCK_DIR)) {
2596
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
2597
+ }
2598
+ const lockFile = spawnLockPath(sessionName);
2599
+ if (existsSync10(lockFile)) {
2600
+ try {
2601
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
2602
+ const age = Date.now() - lock.timestamp;
2603
+ if (isProcessAlive(lock.pid) && age < 6e4) {
2604
+ return false;
2605
+ }
2606
+ } catch {
2607
+ }
2608
+ }
2609
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2610
+ return true;
2611
+ }
2612
+ function releaseSpawnLock(sessionName) {
2613
+ try {
2614
+ unlinkSync3(spawnLockPath(sessionName));
2615
+ } catch {
2616
+ }
2617
+ }
2464
2618
  function resolveBehaviorsExporterScript() {
2465
2619
  try {
2466
2620
  const thisFile = fileURLToPath2(import.meta.url);
@@ -2506,7 +2660,7 @@ function extractRootExe(name) {
2506
2660
  }
2507
2661
  function getParentExe(sessionKey) {
2508
2662
  try {
2509
- const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2663
+ const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2510
2664
  return data.parentExe || null;
2511
2665
  } catch {
2512
2666
  return null;
@@ -2514,7 +2668,7 @@ function getParentExe(sessionKey) {
2514
2668
  }
2515
2669
  function getDispatchedBy(sessionKey) {
2516
2670
  try {
2517
- const data = JSON.parse(readFileSync8(
2671
+ const data = JSON.parse(readFileSync9(
2518
2672
  path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2519
2673
  "utf8"
2520
2674
  ));
@@ -2541,17 +2695,17 @@ function isEmployeeAlive(sessionName) {
2541
2695
  }
2542
2696
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
2543
2697
  const base = employeeSessionName(employeeName, exeSession);
2544
- if (!isAlive(base)) return 0;
2698
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
2545
2699
  for (let i = 2; i <= maxInstances; i++) {
2546
2700
  const candidate = employeeSessionName(employeeName, exeSession, i);
2547
- if (!isAlive(candidate)) return i;
2701
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
2548
2702
  }
2549
2703
  return null;
2550
2704
  }
2551
2705
  function readDebounceState() {
2552
2706
  try {
2553
2707
  if (!existsSync10(DEBOUNCE_FILE)) return {};
2554
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
2708
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
2555
2709
  } catch {
2556
2710
  return {};
2557
2711
  }
@@ -2747,7 +2901,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2747
2901
  const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
2748
2902
  let claudeJson = {};
2749
2903
  try {
2750
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
2904
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
2751
2905
  } catch {
2752
2906
  }
2753
2907
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -2765,7 +2919,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2765
2919
  const settingsPath = path11.join(projSettingsDir, "settings.json");
2766
2920
  let settings = {};
2767
2921
  try {
2768
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
2922
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
2769
2923
  } catch {
2770
2924
  }
2771
2925
  const perms = settings.permissions ?? {};
@@ -2878,6 +3032,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2878
3032
  command: spawnCommand
2879
3033
  });
2880
3034
  if (spawnResult.error) {
3035
+ releaseSpawnLock(sessionName);
2881
3036
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
2882
3037
  }
2883
3038
  transport.pipeLog(sessionName, logFile);
@@ -2915,6 +3070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2915
3070
  }
2916
3071
  }
2917
3072
  if (!booted) {
3073
+ releaseSpawnLock(sessionName);
2918
3074
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
2919
3075
  }
2920
3076
  if (!useExeAgent) {
@@ -2931,9 +3087,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2931
3087
  pid: 0,
2932
3088
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
2933
3089
  });
3090
+ releaseSpawnLock(sessionName);
2934
3091
  return { sessionName };
2935
3092
  }
2936
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3093
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
2937
3094
  var init_tmux_routing = __esm({
2938
3095
  "src/lib/tmux-routing.ts"() {
2939
3096
  "use strict";
@@ -2945,6 +3102,7 @@ var init_tmux_routing = __esm({
2945
3102
  init_provider_table();
2946
3103
  init_intercom_queue();
2947
3104
  init_plan_limits();
3105
+ SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
2948
3106
  SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
2949
3107
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
2950
3108
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -2966,7 +3124,7 @@ __export(task_scanner_exports, {
2966
3124
  formatText: () => formatText,
2967
3125
  scanAgentTasks: () => scanAgentTasks
2968
3126
  });
2969
- import { readdirSync as readdirSync3, readFileSync as readFileSync9, existsSync as existsSync11, statSync } from "fs";
3127
+ import { readdirSync as readdirSync3, readFileSync as readFileSync10, existsSync as existsSync11, statSync } from "fs";
2970
3128
  import { execSync as execSync7 } from "child_process";
2971
3129
  import path12 from "path";
2972
3130
  function getProjectRoot() {
@@ -2992,7 +3150,7 @@ function scanAgentTasks(agentId) {
2992
3150
  total = files.length;
2993
3151
  for (const f of files) {
2994
3152
  try {
2995
- const content = readFileSync9(path12.join(taskDir, f), "utf8");
3153
+ const content = readFileSync10(path12.join(taskDir, f), "utf8");
2996
3154
  const statusMatch = content.match(STATUS_RE);
2997
3155
  const status = statusMatch ? statusMatch[1].toLowerCase() : null;
2998
3156
  if (status === "done") {
@@ -3146,7 +3304,7 @@ import crypto3 from "crypto";
3146
3304
  import path14 from "path";
3147
3305
  import { execSync as execSync9 } from "child_process";
3148
3306
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3149
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
3307
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3150
3308
  async function writeCheckpoint(input) {
3151
3309
  const client = getClient();
3152
3310
  const row = await resolveTask(client, input.taskId);
@@ -3523,7 +3681,7 @@ async function ensureGitignoreExe(baseDir) {
3523
3681
  const gitignorePath = path14.join(baseDir, ".gitignore");
3524
3682
  try {
3525
3683
  if (existsSync12(gitignorePath)) {
3526
- const content = readFileSync10(gitignorePath, "utf-8");
3684
+ const content = readFileSync11(gitignorePath, "utf-8");
3527
3685
  if (/^\/?exe\/?$/m.test(content)) return;
3528
3686
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3529
3687
  } else {
@@ -3544,7 +3702,7 @@ var init_tasks_crud = __esm({
3544
3702
 
3545
3703
  // src/lib/tasks-review.ts
3546
3704
  import path15 from "path";
3547
- import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "fs";
3705
+ import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
3548
3706
  async function countPendingReviews() {
3549
3707
  const client = getClient();
3550
3708
  const result = await client.execute({
@@ -3668,7 +3826,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3668
3826
  if (existsSync13(cacheDir)) {
3669
3827
  for (const f of readdirSync4(cacheDir)) {
3670
3828
  if (f.startsWith("review-notified-")) {
3671
- unlinkSync3(path15.join(cacheDir, f));
3829
+ unlinkSync4(path15.join(cacheDir, f));
3672
3830
  }
3673
3831
  }
3674
3832
  }
@@ -3857,7 +4015,7 @@ async function dispatchTaskToEmployee(input) {
3857
4015
  } else {
3858
4016
  const projectDir = input.projectDir ?? process.cwd();
3859
4017
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
3860
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
4018
+ autoInstance: isMultiInstance(input.assignedTo)
3861
4019
  });
3862
4020
  if (result.status === "failed") {
3863
4021
  process.stderr.write(
@@ -3892,6 +4050,7 @@ var init_tasks_notify = __esm({
3892
4050
  init_session_key();
3893
4051
  init_notifications();
3894
4052
  init_transport();
4053
+ init_employees();
3895
4054
  }
3896
4055
  });
3897
4056
 
@@ -4226,7 +4385,7 @@ __export(tasks_exports, {
4226
4385
  writeCheckpoint: () => writeCheckpoint
4227
4386
  });
4228
4387
  import path17 from "path";
4229
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "fs";
4388
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
4230
4389
  async function createTask(input) {
4231
4390
  const result = await createTaskCore(input);
4232
4391
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4252,7 +4411,7 @@ async function updateTask(input) {
4252
4411
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4253
4412
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4254
4413
  try {
4255
- unlinkSync4(cachePath);
4414
+ unlinkSync5(cachePath);
4256
4415
  } catch {
4257
4416
  }
4258
4417
  }
@@ -4548,10 +4707,49 @@ var init_compress = __esm({
4548
4707
  // src/lib/cloud-sync.ts
4549
4708
  var cloud_sync_exports = {};
4550
4709
  __export(cloud_sync_exports, {
4710
+ buildRosterBlob: () => buildRosterBlob,
4551
4711
  cloudPull: () => cloudPull,
4712
+ cloudPullBehaviors: () => cloudPullBehaviors,
4713
+ cloudPullBlob: () => cloudPullBlob,
4714
+ cloudPullConversations: () => cloudPullConversations,
4715
+ cloudPullDocuments: () => cloudPullDocuments,
4716
+ cloudPullGraphRAG: () => cloudPullGraphRAG,
4717
+ cloudPullRoster: () => cloudPullRoster,
4718
+ cloudPullTasks: () => cloudPullTasks,
4552
4719
  cloudPush: () => cloudPush,
4553
- cloudSync: () => cloudSync
4720
+ cloudPushBehaviors: () => cloudPushBehaviors,
4721
+ cloudPushBlob: () => cloudPushBlob,
4722
+ cloudPushConversations: () => cloudPushConversations,
4723
+ cloudPushDocuments: () => cloudPushDocuments,
4724
+ cloudPushGraphRAG: () => cloudPushGraphRAG,
4725
+ cloudPushRoster: () => cloudPushRoster,
4726
+ cloudPushTasks: () => cloudPushTasks,
4727
+ cloudSync: () => cloudSync,
4728
+ mergeConfig: () => mergeConfig,
4729
+ mergeRosterFromRemote: () => mergeRosterFromRemote
4554
4730
  });
4731
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
4732
+ import path18 from "path";
4733
+ import { homedir } from "os";
4734
+ function logError(msg) {
4735
+ try {
4736
+ const logPath = path18.join(homedir(), ".exe-os", "workers.log");
4737
+ appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
4738
+ `);
4739
+ } catch {
4740
+ }
4741
+ }
4742
+ async function fetchWithRetry(url, init) {
4743
+ const attempt = async () => {
4744
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
4745
+ return fetch(url, { ...init, signal });
4746
+ };
4747
+ const resp = await attempt();
4748
+ if (resp.status >= 500) {
4749
+ return attempt();
4750
+ }
4751
+ return resp;
4752
+ }
4555
4753
  function assertSecureEndpoint(endpoint) {
4556
4754
  if (endpoint.startsWith("https://")) return;
4557
4755
  if (endpoint.startsWith("http://")) {
@@ -4573,7 +4771,7 @@ async function cloudPush(records, maxVersion, config) {
4573
4771
  const json = JSON.stringify(records);
4574
4772
  const compressed = compress(Buffer.from(json, "utf8"));
4575
4773
  const blob = encryptSyncBlob(compressed);
4576
- const resp = await fetch(`${config.endpoint}/sync/push`, {
4774
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
4577
4775
  method: "POST",
4578
4776
  headers: {
4579
4777
  Authorization: `Bearer ${config.apiKey}`,
@@ -4584,15 +4782,14 @@ async function cloudPush(records, maxVersion, config) {
4584
4782
  });
4585
4783
  return resp.ok;
4586
4784
  } catch (err) {
4587
- process.stderr.write(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
4588
- `);
4785
+ logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
4589
4786
  return false;
4590
4787
  }
4591
4788
  }
4592
4789
  async function cloudPull(sinceVersion, config) {
4593
4790
  assertSecureEndpoint(config.endpoint);
4594
4791
  try {
4595
- const response = await fetch(`${config.endpoint}/sync/pull`, {
4792
+ const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
4596
4793
  method: "POST",
4597
4794
  headers: {
4598
4795
  Authorization: `Bearer ${config.apiKey}`,
@@ -4616,8 +4813,7 @@ async function cloudPull(sinceVersion, config) {
4616
4813
  }
4617
4814
  return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
4618
4815
  } catch (err) {
4619
- process.stderr.write(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}
4620
- `);
4816
+ logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
4621
4817
  return { records: [], maxVersion: sinceVersion };
4622
4818
  }
4623
4819
  }
@@ -4706,9 +4902,604 @@ async function cloudSync(config) {
4706
4902
  pushed = records.length;
4707
4903
  }
4708
4904
  }
4709
- return { pushed, pulled };
4905
+ try {
4906
+ await cloudPushRoster(config);
4907
+ } catch (err) {
4908
+ logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
4909
+ }
4910
+ try {
4911
+ await cloudPullRoster(config);
4912
+ } catch (err) {
4913
+ logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
4914
+ }
4915
+ let behaviorsResult = { pushed: false, pulled: 0 };
4916
+ try {
4917
+ behaviorsResult.pushed = await cloudPushBehaviors(config);
4918
+ } catch (err) {
4919
+ logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
4920
+ }
4921
+ try {
4922
+ const pullResult2 = await cloudPullBehaviors(config);
4923
+ behaviorsResult.pulled = pullResult2.pulled;
4924
+ } catch (err) {
4925
+ logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
4926
+ }
4927
+ let graphragResult = { pushed: false, pulled: 0 };
4928
+ try {
4929
+ graphragResult.pushed = await cloudPushGraphRAG(config);
4930
+ } catch (err) {
4931
+ logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
4932
+ }
4933
+ try {
4934
+ const pullResult2 = await cloudPullGraphRAG(config);
4935
+ graphragResult.pulled = pullResult2.pulled;
4936
+ } catch (err) {
4937
+ logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
4938
+ }
4939
+ let tasksResult = { pushed: false, pulled: 0 };
4940
+ try {
4941
+ tasksResult.pushed = await cloudPushTasks(config);
4942
+ } catch (err) {
4943
+ logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
4944
+ }
4945
+ try {
4946
+ const pullResult2 = await cloudPullTasks(config);
4947
+ tasksResult.pulled = pullResult2.pulled;
4948
+ } catch (err) {
4949
+ logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
4950
+ }
4951
+ let conversationsResult = { pushed: false, pulled: 0 };
4952
+ try {
4953
+ conversationsResult.pushed = await cloudPushConversations(config);
4954
+ } catch (err) {
4955
+ logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
4956
+ }
4957
+ try {
4958
+ const pullResult2 = await cloudPullConversations(config);
4959
+ conversationsResult.pulled = pullResult2.pulled;
4960
+ } catch (err) {
4961
+ logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
4962
+ }
4963
+ let documentsResult = { pushed: false, pulled: 0 };
4964
+ try {
4965
+ documentsResult.pushed = await cloudPushDocuments(config);
4966
+ } catch (err) {
4967
+ logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
4968
+ }
4969
+ try {
4970
+ const pullResult2 = await cloudPullDocuments(config);
4971
+ documentsResult.pulled = pullResult2.pulled;
4972
+ } catch (err) {
4973
+ logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
4974
+ }
4975
+ return {
4976
+ pushed,
4977
+ pulled,
4978
+ behaviors: behaviorsResult,
4979
+ graphrag: graphragResult,
4980
+ tasks: tasksResult,
4981
+ conversations: conversationsResult,
4982
+ documents: documentsResult
4983
+ };
4984
+ }
4985
+ function buildRosterBlob(paths) {
4986
+ const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
4987
+ const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
4988
+ const configPath = paths?.configPath ?? path18.join(EXE_AI_DIR, "config.json");
4989
+ let roster = [];
4990
+ if (existsSync14(rosterPath)) {
4991
+ try {
4992
+ roster = JSON.parse(readFileSync12(rosterPath, "utf-8"));
4993
+ } catch {
4994
+ }
4995
+ }
4996
+ const identities = {};
4997
+ if (existsSync14(identityDir)) {
4998
+ for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
4999
+ try {
5000
+ identities[file] = readFileSync12(path18.join(identityDir, file), "utf-8");
5001
+ } catch {
5002
+ }
5003
+ }
5004
+ }
5005
+ let config;
5006
+ if (existsSync14(configPath)) {
5007
+ try {
5008
+ config = JSON.parse(readFileSync12(configPath, "utf-8"));
5009
+ } catch {
5010
+ }
5011
+ }
5012
+ const content = JSON.stringify({ roster, identities, config });
5013
+ const hash = Buffer.from(content).length;
5014
+ return { roster, identities, config, version: hash };
5015
+ }
5016
+ async function cloudPushRoster(config) {
5017
+ assertSecureEndpoint(config.endpoint);
5018
+ const blob = buildRosterBlob();
5019
+ if (blob.roster.length === 0) return true;
5020
+ try {
5021
+ const client = getClient();
5022
+ const meta = await client.execute(
5023
+ "SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
5024
+ );
5025
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
5026
+ if (blob.version === lastVersion) return true;
5027
+ } catch {
5028
+ }
5029
+ try {
5030
+ const json = JSON.stringify(blob);
5031
+ const compressed = compress(Buffer.from(json, "utf8"));
5032
+ const encrypted = encryptSyncBlob(compressed);
5033
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
5034
+ method: "POST",
5035
+ headers: {
5036
+ Authorization: `Bearer ${config.apiKey}`,
5037
+ "Content-Type": "application/json",
5038
+ "X-Device-Id": loadDeviceId()
5039
+ },
5040
+ body: JSON.stringify({ blob: encrypted })
5041
+ });
5042
+ if (resp.ok) {
5043
+ try {
5044
+ const client = getClient();
5045
+ await client.execute({
5046
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
5047
+ args: [String(blob.version)]
5048
+ });
5049
+ } catch {
5050
+ }
5051
+ }
5052
+ return resp.ok;
5053
+ } catch (err) {
5054
+ process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
5055
+ `);
5056
+ return false;
5057
+ }
5058
+ }
5059
+ async function cloudPullRoster(config) {
5060
+ assertSecureEndpoint(config.endpoint);
5061
+ try {
5062
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
5063
+ method: "GET",
5064
+ headers: {
5065
+ Authorization: `Bearer ${config.apiKey}`,
5066
+ "X-Device-Id": loadDeviceId()
5067
+ }
5068
+ });
5069
+ if (!resp.ok) return { added: 0 };
5070
+ const data = await resp.json();
5071
+ if (!data.blob) return { added: 0 };
5072
+ const compressed = decryptSyncBlob(data.blob);
5073
+ const json = decompress(compressed).toString("utf8");
5074
+ const remote = JSON.parse(json);
5075
+ return mergeRosterFromRemote(remote);
5076
+ } catch (err) {
5077
+ process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
5078
+ `);
5079
+ return { added: 0 };
5080
+ }
5081
+ }
5082
+ function mergeConfig(remoteConfig, configPath) {
5083
+ const cfgPath = configPath ?? path18.join(EXE_AI_DIR, "config.json");
5084
+ let local = {};
5085
+ if (existsSync14(cfgPath)) {
5086
+ try {
5087
+ local = JSON.parse(readFileSync12(cfgPath, "utf-8"));
5088
+ } catch {
5089
+ }
5090
+ }
5091
+ const merged = { ...remoteConfig, ...local };
5092
+ const dir = path18.dirname(cfgPath);
5093
+ if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
5094
+ writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5095
+ }
5096
+ async function mergeRosterFromRemote(remote, paths) {
5097
+ const rosterPath = paths?.rosterPath ?? void 0;
5098
+ const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
5099
+ const localEmployees = await loadEmployees(rosterPath);
5100
+ const localNames = new Set(localEmployees.map((e) => e.name));
5101
+ let added = 0;
5102
+ for (const remoteEmp of remote.roster) {
5103
+ if (localNames.has(remoteEmp.name)) continue;
5104
+ localEmployees.push(remoteEmp);
5105
+ localNames.add(remoteEmp.name);
5106
+ added++;
5107
+ if (remote.identities[`${remoteEmp.name}.md`]) {
5108
+ if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
5109
+ const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
5110
+ if (!existsSync14(idPath)) {
5111
+ writeFileSync7(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
5112
+ }
5113
+ }
5114
+ try {
5115
+ registerBinSymlinks(remoteEmp.name);
5116
+ } catch {
5117
+ }
5118
+ }
5119
+ if (added > 0) {
5120
+ await saveEmployees(localEmployees, rosterPath);
5121
+ }
5122
+ if (remote.config && Object.keys(remote.config).length > 0) {
5123
+ try {
5124
+ mergeConfig(remote.config, paths?.configPath);
5125
+ } catch {
5126
+ }
5127
+ }
5128
+ return { added };
5129
+ }
5130
+ async function cloudPushBlob(route, data, metaKey, config) {
5131
+ if (data.length === 0) return { ok: true };
5132
+ assertSecureEndpoint(config.endpoint);
5133
+ const json = JSON.stringify(data);
5134
+ const version = Buffer.from(json).length;
5135
+ try {
5136
+ const client = getClient();
5137
+ const meta = await client.execute({
5138
+ sql: "SELECT value FROM sync_meta WHERE key = ?",
5139
+ args: [metaKey]
5140
+ });
5141
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
5142
+ if (version === lastVersion) return { ok: true };
5143
+ } catch {
5144
+ }
5145
+ try {
5146
+ const compressed = compress(Buffer.from(json, "utf8"));
5147
+ const encrypted = encryptSyncBlob(compressed);
5148
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
5149
+ method: "POST",
5150
+ headers: {
5151
+ Authorization: `Bearer ${config.apiKey}`,
5152
+ "Content-Type": "application/json",
5153
+ "X-Device-Id": loadDeviceId()
5154
+ },
5155
+ body: JSON.stringify({ blob: encrypted })
5156
+ });
5157
+ if (resp.ok) {
5158
+ try {
5159
+ const client = getClient();
5160
+ await client.execute({
5161
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
5162
+ args: [metaKey, String(version)]
5163
+ });
5164
+ } catch {
5165
+ }
5166
+ }
5167
+ return { ok: resp.ok };
5168
+ } catch (err) {
5169
+ logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
5170
+ return { ok: false };
5171
+ }
5172
+ }
5173
+ async function cloudPullBlob(route, config) {
5174
+ assertSecureEndpoint(config.endpoint);
5175
+ try {
5176
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
5177
+ method: "GET",
5178
+ headers: {
5179
+ Authorization: `Bearer ${config.apiKey}`,
5180
+ "X-Device-Id": loadDeviceId()
5181
+ }
5182
+ });
5183
+ if (!resp.ok) return null;
5184
+ const data = await resp.json();
5185
+ if (!data.blob) return null;
5186
+ const compressed = decryptSyncBlob(data.blob);
5187
+ const json = decompress(compressed).toString("utf8");
5188
+ return JSON.parse(json);
5189
+ } catch (err) {
5190
+ logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
5191
+ return null;
5192
+ }
5193
+ }
5194
+ async function cloudPushBehaviors(config) {
5195
+ const client = getClient();
5196
+ const result = await client.execute("SELECT * FROM behaviors");
5197
+ const rows = result.rows;
5198
+ const { ok } = await cloudPushBlob(
5199
+ "/sync/push-behaviors",
5200
+ rows,
5201
+ "last_behaviors_push_version",
5202
+ config
5203
+ );
5204
+ return ok;
5205
+ }
5206
+ async function cloudPullBehaviors(config) {
5207
+ const remoteBehaviors = await cloudPullBlob(
5208
+ "/sync/pull-behaviors",
5209
+ config
5210
+ );
5211
+ if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
5212
+ const client = getClient();
5213
+ let pulled = 0;
5214
+ for (const behavior of remoteBehaviors) {
5215
+ const existing = await client.execute({
5216
+ sql: `SELECT COUNT(*) as cnt FROM behaviors
5217
+ WHERE agent_id = ? AND content = ?`,
5218
+ args: [behavior.agent_id, behavior.content]
5219
+ });
5220
+ if (Number(existing.rows[0]?.cnt) > 0) continue;
5221
+ await client.execute({
5222
+ sql: `INSERT OR IGNORE INTO behaviors
5223
+ (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
5224
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5225
+ args: [
5226
+ behavior.id,
5227
+ behavior.agent_id,
5228
+ behavior.project_name ?? null,
5229
+ behavior.domain ?? null,
5230
+ behavior.content,
5231
+ behavior.active,
5232
+ behavior.priority ?? "p1",
5233
+ behavior.created_at,
5234
+ behavior.updated_at
5235
+ ]
5236
+ });
5237
+ pulled++;
5238
+ }
5239
+ return { pulled };
4710
5240
  }
4711
- var LOCALHOST_PATTERNS;
5241
+ async function cloudPushGraphRAG(config) {
5242
+ const client = getClient();
5243
+ const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
5244
+ client.execute("SELECT * FROM entities"),
5245
+ client.execute("SELECT * FROM relationships"),
5246
+ client.execute("SELECT * FROM entity_aliases"),
5247
+ client.execute("SELECT * FROM entity_memories"),
5248
+ client.execute("SELECT * FROM relationship_memories"),
5249
+ client.execute("SELECT * FROM hyperedges"),
5250
+ client.execute("SELECT * FROM hyperedge_nodes")
5251
+ ]);
5252
+ const blob = {
5253
+ entities: entities.rows,
5254
+ relationships: relationships.rows,
5255
+ entity_aliases: aliases.rows,
5256
+ entity_memories: entityMems.rows,
5257
+ relationship_memories: relMems.rows,
5258
+ hyperedges: hyperedges.rows,
5259
+ hyperedge_nodes: hyperedgeNodes.rows
5260
+ };
5261
+ const { ok } = await cloudPushBlob(
5262
+ "/sync/push-graphrag",
5263
+ [blob],
5264
+ "last_graphrag_push_version",
5265
+ config
5266
+ );
5267
+ return ok;
5268
+ }
5269
+ async function cloudPullGraphRAG(config) {
5270
+ const data = await cloudPullBlob(
5271
+ "/sync/pull-graphrag",
5272
+ config
5273
+ );
5274
+ if (!data || data.length === 0) return { pulled: 0 };
5275
+ const blob = data[0];
5276
+ const client = getClient();
5277
+ let pulled = 0;
5278
+ if (blob.entities.length > 0) {
5279
+ const stmts = blob.entities.map((e) => ({
5280
+ sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
5281
+ VALUES (?, ?, ?, ?, ?, ?)`,
5282
+ args: [e.id, e.name, e.type, e.first_seen, e.last_seen, e.properties ?? "{}"]
5283
+ }));
5284
+ await client.batch(stmts, "write");
5285
+ pulled += stmts.length;
5286
+ }
5287
+ if (blob.relationships.length > 0) {
5288
+ const stmts = blob.relationships.map((r) => ({
5289
+ sql: `INSERT OR IGNORE INTO relationships
5290
+ (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
5291
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5292
+ args: [
5293
+ r.id,
5294
+ r.source_entity_id,
5295
+ r.target_entity_id,
5296
+ r.type,
5297
+ r.weight ?? 1,
5298
+ r.timestamp,
5299
+ r.properties ?? "{}",
5300
+ r.confidence ?? 1,
5301
+ r.confidence_label ?? "extracted"
5302
+ ]
5303
+ }));
5304
+ await client.batch(stmts, "write");
5305
+ pulled += stmts.length;
5306
+ }
5307
+ if (blob.entity_aliases.length > 0) {
5308
+ const stmts = blob.entity_aliases.map((a) => ({
5309
+ sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
5310
+ args: [a.alias, a.canonical_entity_id]
5311
+ }));
5312
+ await client.batch(stmts, "write");
5313
+ pulled += stmts.length;
5314
+ }
5315
+ if (blob.entity_memories.length > 0) {
5316
+ const stmts = blob.entity_memories.map((em) => ({
5317
+ sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
5318
+ args: [em.entity_id, em.memory_id]
5319
+ }));
5320
+ await client.batch(stmts, "write");
5321
+ pulled += stmts.length;
5322
+ }
5323
+ if (blob.relationship_memories.length > 0) {
5324
+ const stmts = blob.relationship_memories.map((rm) => ({
5325
+ sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
5326
+ args: [rm.relationship_id, rm.memory_id]
5327
+ }));
5328
+ await client.batch(stmts, "write");
5329
+ pulled += stmts.length;
5330
+ }
5331
+ if (blob.hyperedges.length > 0) {
5332
+ const stmts = blob.hyperedges.map((h) => ({
5333
+ sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
5334
+ VALUES (?, ?, ?, ?, ?)`,
5335
+ args: [h.id, h.label, h.relation, h.confidence ?? 1, h.timestamp]
5336
+ }));
5337
+ await client.batch(stmts, "write");
5338
+ pulled += stmts.length;
5339
+ }
5340
+ if (blob.hyperedge_nodes.length > 0) {
5341
+ const stmts = blob.hyperedge_nodes.map((hn) => ({
5342
+ sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
5343
+ args: [hn.hyperedge_id, hn.entity_id]
5344
+ }));
5345
+ await client.batch(stmts, "write");
5346
+ pulled += stmts.length;
5347
+ }
5348
+ return { pulled };
5349
+ }
5350
+ async function cloudPushTasks(config) {
5351
+ const client = getClient();
5352
+ const result = await client.execute("SELECT * FROM tasks");
5353
+ const rows = result.rows;
5354
+ const { ok } = await cloudPushBlob(
5355
+ "/sync/push-tasks",
5356
+ rows,
5357
+ "last_tasks_push_version",
5358
+ config
5359
+ );
5360
+ return ok;
5361
+ }
5362
+ async function cloudPullTasks(config) {
5363
+ const remoteTasks = await cloudPullBlob(
5364
+ "/sync/pull-tasks",
5365
+ config
5366
+ );
5367
+ if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
5368
+ const client = getClient();
5369
+ const stmts = remoteTasks.map((t) => ({
5370
+ sql: `INSERT OR IGNORE INTO tasks
5371
+ (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
5372
+ blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
5373
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5374
+ args: [
5375
+ t.id,
5376
+ t.title,
5377
+ t.assigned_to,
5378
+ t.assigned_by,
5379
+ t.project_name,
5380
+ t.priority ?? "p1",
5381
+ t.status ?? "open",
5382
+ t.task_file ?? null,
5383
+ t.created_at,
5384
+ t.updated_at,
5385
+ t.blocked_by ?? null,
5386
+ t.parent_task_id ?? null,
5387
+ t.budget_tokens ?? null,
5388
+ t.budget_fallback_model ?? null,
5389
+ t.tokens_used ?? 0,
5390
+ t.tokens_warned_at ?? null
5391
+ ]
5392
+ }));
5393
+ await client.batch(stmts, "write");
5394
+ return { pulled: remoteTasks.length };
5395
+ }
5396
+ async function cloudPushConversations(config) {
5397
+ const client = getClient();
5398
+ const result = await client.execute("SELECT * FROM conversations");
5399
+ const rows = result.rows;
5400
+ const { ok } = await cloudPushBlob(
5401
+ "/sync/push-conversations",
5402
+ rows,
5403
+ "last_conversations_push_version",
5404
+ config
5405
+ );
5406
+ return ok;
5407
+ }
5408
+ async function cloudPullConversations(config) {
5409
+ const remoteConvos = await cloudPullBlob(
5410
+ "/sync/pull-conversations",
5411
+ config
5412
+ );
5413
+ if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
5414
+ const client = getClient();
5415
+ const stmts = remoteConvos.map((c) => ({
5416
+ sql: `INSERT OR IGNORE INTO conversations
5417
+ (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
5418
+ recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
5419
+ content_metadata, agent_response, agent_name, timestamp, ingested_at)
5420
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5421
+ args: [
5422
+ c.id,
5423
+ c.platform,
5424
+ c.external_id ?? null,
5425
+ c.sender_id,
5426
+ c.sender_name ?? null,
5427
+ c.sender_phone ?? null,
5428
+ c.sender_email ?? null,
5429
+ c.recipient_id ?? null,
5430
+ c.channel_id,
5431
+ c.thread_id ?? null,
5432
+ c.reply_to_id ?? null,
5433
+ c.content_text ?? null,
5434
+ c.content_media ?? null,
5435
+ c.content_metadata ?? null,
5436
+ c.agent_response ?? null,
5437
+ c.agent_name ?? null,
5438
+ c.timestamp,
5439
+ c.ingested_at
5440
+ ]
5441
+ }));
5442
+ await client.batch(stmts, "write");
5443
+ return { pulled: remoteConvos.length };
5444
+ }
5445
+ async function cloudPushDocuments(config) {
5446
+ const client = getClient();
5447
+ const [workspaces, documents] = await Promise.all([
5448
+ client.execute("SELECT * FROM workspaces"),
5449
+ client.execute("SELECT * FROM documents")
5450
+ ]);
5451
+ const blob = {
5452
+ workspaces: workspaces.rows,
5453
+ documents: documents.rows
5454
+ };
5455
+ const { ok } = await cloudPushBlob(
5456
+ "/sync/push-documents",
5457
+ [blob],
5458
+ "last_documents_push_version",
5459
+ config
5460
+ );
5461
+ return ok;
5462
+ }
5463
+ async function cloudPullDocuments(config) {
5464
+ const data = await cloudPullBlob(
5465
+ "/sync/pull-documents",
5466
+ config
5467
+ );
5468
+ if (!data || data.length === 0) return { pulled: 0 };
5469
+ const blob = data[0];
5470
+ const client = getClient();
5471
+ let pulled = 0;
5472
+ if (blob.workspaces.length > 0) {
5473
+ const stmts = blob.workspaces.map((w) => ({
5474
+ sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
5475
+ VALUES (?, ?, ?, ?, ?, ?)`,
5476
+ args: [w.id, w.slug, w.name, w.owner_agent_id ?? null, w.created_at, w.metadata ?? null]
5477
+ }));
5478
+ await client.batch(stmts, "write");
5479
+ pulled += stmts.length;
5480
+ }
5481
+ if (blob.documents.length > 0) {
5482
+ const stmts = blob.documents.map((d) => ({
5483
+ sql: `INSERT OR IGNORE INTO documents
5484
+ (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
5485
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
5486
+ args: [
5487
+ d.id,
5488
+ d.workspace_id,
5489
+ d.filename,
5490
+ d.mime ?? null,
5491
+ d.source_type ?? null,
5492
+ d.user_id ?? null,
5493
+ d.uploaded_at,
5494
+ d.metadata ?? null
5495
+ ]
5496
+ }));
5497
+ await client.batch(stmts, "write");
5498
+ pulled += stmts.length;
5499
+ }
5500
+ return { pulled };
5501
+ }
5502
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
4712
5503
  var init_cloud_sync = __esm({
4713
5504
  "src/lib/cloud-sync.ts"() {
4714
5505
  "use strict";
@@ -4717,7 +5508,10 @@ var init_cloud_sync = __esm({
4717
5508
  init_compress();
4718
5509
  init_plan_limits();
4719
5510
  init_license();
5511
+ init_config();
5512
+ init_employees();
4720
5513
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
5514
+ FETCH_TIMEOUT_MS = 3e4;
4721
5515
  }
4722
5516
  });
4723
5517
 
@@ -4899,9 +5693,9 @@ var init_schedules = __esm({
4899
5693
 
4900
5694
  // src/bin/exe-boot.ts
4901
5695
  init_employees();
4902
- import path18 from "path";
5696
+ import path19 from "path";
4903
5697
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
4904
- import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
5698
+ import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync as unlinkSync6 } from "fs";
4905
5699
  import os6 from "os";
4906
5700
 
4907
5701
  // src/lib/employee-templates.ts
@@ -4955,8 +5749,22 @@ function boxMid(w) {
4955
5749
  function boxBot(w) {
4956
5750
  return `\u2514${"\u2500".repeat(w)}\u2518`;
4957
5751
  }
5752
+ function truncateToWidth(str, maxW) {
5753
+ if (displayWidth(str) <= maxW) return str;
5754
+ let w = 0;
5755
+ let i = 0;
5756
+ for (const ch of str) {
5757
+ const cw = ch.codePointAt(0) > 255 ? 2 : 1;
5758
+ if (w + cw + 1 > maxW) break;
5759
+ w += cw;
5760
+ i += ch.length;
5761
+ }
5762
+ return str.slice(0, i) + "\u2026";
5763
+ }
4958
5764
  function boxRow(content, w) {
4959
- return `\u2502 ${padRight(content, w - 2)} \u2502`;
5765
+ const maxContent = w - 2;
5766
+ const truncated = truncateToWidth(content, maxContent);
5767
+ return `\u2502 ${padRight(truncated, maxContent)} \u2502`;
4960
5768
  }
4961
5769
  function formatUptime(seconds) {
4962
5770
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
@@ -4966,6 +5774,9 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
4966
5774
  const now = /* @__PURE__ */ new Date();
4967
5775
  const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
4968
5776
  const sessionTag = data.exeSession ? ` [${data.exeSession}]` : "";
5777
+ if (data.embedding && data.embedding.totalMemories < 5 && data.globalTasks.length === 0 && (data.projects?.length ?? 0) === 0) {
5778
+ return buildFirstBootBrief(employees, dateStr, sessionTag);
5779
+ }
4969
5780
  const sections = [];
4970
5781
  sections.push([` EXE STATUS BRIEF \u2014 ${dateStr}${sessionTag}`]);
4971
5782
  const reminderLines = buildReminders(data);
@@ -4975,6 +5786,8 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
4975
5786
  sections.push(buildProjects(data));
4976
5787
  sections.push(buildTeam(employees, data));
4977
5788
  sections.push(buildHealth(data));
5789
+ const termCols = process.stdout.columns || 80;
5790
+ const maxInnerW = termCols - 2;
4978
5791
  let maxW = 0;
4979
5792
  for (const sec of sections) {
4980
5793
  for (const line of sec) {
@@ -4982,7 +5795,7 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
4982
5795
  if (w > maxW) maxW = w;
4983
5796
  }
4984
5797
  }
4985
- const innerW = maxW + 4;
5798
+ const innerW = Math.min(maxW + 4, maxInnerW);
4986
5799
  const out = [];
4987
5800
  out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
4988
5801
  for (let i = 0; i < sections.length; i++) {
@@ -4994,6 +5807,39 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
4994
5807
  out.push(boxBot(innerW));
4995
5808
  return out.join("\n");
4996
5809
  }
5810
+ function buildFirstBootBrief(employees, dateStr, sessionTag) {
5811
+ const termCols = process.stdout.columns || 80;
5812
+ const maxInnerW = termCols - 2;
5813
+ const lines = [];
5814
+ lines.push(` WELCOME TO EXE OS \u2014 ${dateStr}${sessionTag}`);
5815
+ const bodyLines = [];
5816
+ bodyLines.push(" \u{1F44B} First time? Here's your team:");
5817
+ bodyLines.push("");
5818
+ for (const emp of employees) {
5819
+ const emoji = EMPLOYEE_EMOJIS[emp.name] ?? "\u{1F464}";
5820
+ const role = emp.role ? ` (${emp.role})` : "";
5821
+ bodyLines.push(` ${emoji} ${emp.name}${role}`);
5822
+ }
5823
+ bodyLines.push("");
5824
+ bodyLines.push(" \u{1F4A1} Quick start:");
5825
+ bodyLines.push(" \u2022 Run `exe-os backfill-conversations` to import Claude Code history");
5826
+ bodyLines.push(" \u2022 Say `/exe` to launch your COO with a full status brief");
5827
+ bodyLines.push(" \u2022 Say `/exe-team` to manage your team");
5828
+ let maxW = 0;
5829
+ for (const line of [...lines, ...bodyLines]) {
5830
+ const w = displayWidth(line);
5831
+ if (w > maxW) maxW = w;
5832
+ }
5833
+ const innerW = Math.min(maxW + 4, maxInnerW);
5834
+ const out = [];
5835
+ out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
5836
+ out.push(boxTop(innerW));
5837
+ for (const line of lines) out.push(boxRow(line, innerW));
5838
+ out.push(boxMid(innerW));
5839
+ for (const line of bodyLines) out.push(boxRow(line, innerW));
5840
+ out.push(boxBot(innerW));
5841
+ return out.join("\n");
5842
+ }
4997
5843
  function buildReminders(data) {
4998
5844
  if (!data.reminders || data.reminders.length === 0) return [];
4999
5845
  const lines = [];
@@ -5061,7 +5907,10 @@ function buildProjects(data) {
5061
5907
  idle.push(p);
5062
5908
  }
5063
5909
  }
5064
- for (const p of active) {
5910
+ const MAX_SHOWN_PROJECTS = 6;
5911
+ const shown = active.slice(0, MAX_SHOWN_PROJECTS);
5912
+ const hidden = active.length - shown.length;
5913
+ for (const p of shown) {
5065
5914
  const blocked = p.blockedCount ?? 0;
5066
5915
  const statusEmoji = blocked > 0 ? "\u{1F534}" : "\u{1F7E2}";
5067
5916
  const sessionStr = p.sessionName ? ` (${p.sessionName})` : p.isCurrent ? " (current)" : "";
@@ -5083,7 +5932,9 @@ function buildProjects(data) {
5083
5932
  if (blocked > 0) {
5084
5933
  lines.push(` Blocked: ${blocked}`);
5085
5934
  }
5086
- lines.push("");
5935
+ }
5936
+ if (hidden > 0) {
5937
+ lines.push(` ... and ${hidden} more project${hidden === 1 ? "" : "s"}`);
5087
5938
  }
5088
5939
  if (idle.length > 0) {
5089
5940
  const idleNames = idle.map((p) => p.projectName);
@@ -5143,7 +5994,8 @@ function buildHealth(data) {
5143
5994
  }
5144
5995
  }
5145
5996
  }
5146
- lines.push(` \u2601\uFE0F Cloud: ${data.cloudConnected ? "\u{1F7E2}" : "not configured"}`);
5997
+ const cloudLabel = data.cloudConnected ? "\u{1F7E2} synced" : data.cloudConnected === false && data.plan && data.plan !== "free" ? "\u{1F534} offline" : "not configured";
5998
+ lines.push(` \u2601\uFE0F Cloud: ${cloudLabel}`);
5147
5999
  if (data.sessionsKilledToday !== void 0) {
5148
6000
  lines.push(` \u{1F480} Sessions killed today: ${data.sessionsKilledToday}`);
5149
6001
  }
@@ -5182,7 +6034,7 @@ init_notifications();
5182
6034
 
5183
6035
  // src/adapters/claude/active-agent.ts
5184
6036
  init_config();
5185
- import { readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
6037
+ import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
5186
6038
  import { execSync as execSync3 } from "child_process";
5187
6039
  import path6 from "path";
5188
6040
 
@@ -5292,18 +6144,18 @@ async function boot(options) {
5292
6144
  } catch {
5293
6145
  }
5294
6146
  try {
5295
- const { readdirSync: readdirSync6, readFileSync: readFs } = await import("fs");
6147
+ const { readdirSync: readdirSync7, readFileSync: readFs } = await import("fs");
5296
6148
  const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
5297
6149
  const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
5298
6150
  const exeDir = "exe";
5299
- const entries = readdirSync6(exeDir, { withFileTypes: true });
6151
+ const entries = readdirSync7(exeDir, { withFileTypes: true });
5300
6152
  const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
5301
6153
  for (const dir of employeeDirs) {
5302
6154
  const employee = dir.name;
5303
- const taskDir = path18.join(exeDir, employee);
6155
+ const taskDir = path19.join(exeDir, employee);
5304
6156
  let files;
5305
6157
  try {
5306
- files = readdirSync6(taskDir).filter((f) => f.endsWith(".md"));
6158
+ files = readdirSync7(taskDir).filter((f) => f.endsWith(".md"));
5307
6159
  } catch {
5308
6160
  continue;
5309
6161
  }
@@ -5311,7 +6163,7 @@ async function boot(options) {
5311
6163
  const taskFilePath = `exe/${employee}/${file}`;
5312
6164
  let content;
5313
6165
  try {
5314
- content = readFs(path18.join(taskDir, file), "utf8");
6166
+ content = readFs(path19.join(taskDir, file), "utf8");
5315
6167
  } catch {
5316
6168
  continue;
5317
6169
  }
@@ -5395,12 +6247,12 @@ async function boot(options) {
5395
6247
  } catch {
5396
6248
  }
5397
6249
  try {
5398
- const exeExeDir = path18.join(process.cwd(), "exe", "exe");
5399
- if (existsSync14(exeExeDir)) {
5400
- for (const f of readdirSync5(exeExeDir)) {
6250
+ const exeExeDir = path19.join(process.cwd(), "exe", "exe");
6251
+ if (existsSync15(exeExeDir)) {
6252
+ for (const f of readdirSync6(exeExeDir)) {
5401
6253
  if (f.startsWith("review-") && f.endsWith(".md")) {
5402
6254
  try {
5403
- unlinkSync5(path18.join(exeExeDir, f));
6255
+ unlinkSync6(path19.join(exeExeDir, f));
5404
6256
  } catch {
5405
6257
  }
5406
6258
  }
@@ -5444,12 +6296,12 @@ async function boot(options) {
5444
6296
  });
5445
6297
  const taskFile = String(r.task_file);
5446
6298
  try {
5447
- const filePath = path18.join(process.cwd(), taskFile);
5448
- if (existsSync14(filePath)) {
5449
- let content = readFileSync11(filePath, "utf8");
6299
+ const filePath = path19.join(process.cwd(), taskFile);
6300
+ if (existsSync15(filePath)) {
6301
+ let content = readFileSync13(filePath, "utf8");
5450
6302
  content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
5451
- const { writeFileSync: writeFileSync7 } = await import("fs");
5452
- writeFileSync7(filePath, content);
6303
+ const { writeFileSync: writeFileSync8 } = await import("fs");
6304
+ writeFileSync8(filePath, content);
5453
6305
  }
5454
6306
  } catch {
5455
6307
  }
@@ -5909,19 +6761,19 @@ async function boot(options) {
5909
6761
  })()
5910
6762
  ]);
5911
6763
  try {
5912
- const configPath = path18.join(
5913
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os6.homedir(), ".exe-os"),
6764
+ const configPath = path19.join(
6765
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
5914
6766
  "config.json"
5915
6767
  );
5916
- if (existsSync14(configPath)) {
5917
- const raw = JSON.parse(readFileSync11(configPath, "utf8"));
6768
+ if (existsSync15(configPath)) {
6769
+ const raw = JSON.parse(readFileSync13(configPath, "utf8"));
5918
6770
  briefData.cloudConnected = !!(raw.cloud || raw.turso);
5919
6771
  }
5920
6772
  } catch {
5921
6773
  }
5922
6774
  try {
5923
- const backfillFlagPath = path18.join(EXE_AI_DIR, "session-cache", "needs-backfill");
5924
- const isBackfillNeeded = () => existsSync14(backfillFlagPath);
6775
+ const backfillFlagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
6776
+ const isBackfillNeeded = () => existsSync15(backfillFlagPath);
5925
6777
  const coverageResult = await client.execute({
5926
6778
  sql: `SELECT COUNT(*) as total,
5927
6779
  SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
@@ -5943,8 +6795,8 @@ async function boot(options) {
5943
6795
  let daemonRunning = false;
5944
6796
  let daemonUptime;
5945
6797
  let daemonRequestsServed;
5946
- const socketPath = path18.join(EXE_AI_DIR, "exed.sock");
5947
- if (existsSync14(socketPath)) {
6798
+ const socketPath = path19.join(EXE_AI_DIR, "exed.sock");
6799
+ if (existsSync15(socketPath)) {
5948
6800
  try {
5949
6801
  const net = await import("net");
5950
6802
  const health = await new Promise((resolve) => {
@@ -5986,10 +6838,10 @@ async function boot(options) {
5986
6838
  }
5987
6839
  }
5988
6840
  if (!daemonRunning) {
5989
- const pidPath = path18.join(EXE_AI_DIR, "exed.pid");
5990
- if (existsSync14(pidPath)) {
6841
+ const pidPath = path19.join(EXE_AI_DIR, "exed.pid");
6842
+ if (existsSync15(pidPath)) {
5991
6843
  try {
5992
- const pid = parseInt(readFileSync11(pidPath, "utf8").trim(), 10);
6844
+ const pid = parseInt(readFileSync13(pidPath, "utf8").trim(), 10);
5993
6845
  if (pid > 0) {
5994
6846
  process.kill(pid, 0);
5995
6847
  daemonRunning = true;
@@ -6000,10 +6852,10 @@ async function boot(options) {
6000
6852
  }
6001
6853
  if (nullCount === 0) {
6002
6854
  try {
6003
- const flagPath = path18.join(EXE_AI_DIR, "session-cache", "needs-backfill");
6004
- if (existsSync14(flagPath)) {
6005
- const { unlinkSync: unlinkSync6 } = await import("fs");
6006
- unlinkSync6(flagPath);
6855
+ const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
6856
+ if (existsSync15(flagPath)) {
6857
+ const { unlinkSync: unlinkSync7 } = await import("fs");
6858
+ unlinkSync7(flagPath);
6007
6859
  }
6008
6860
  } catch {
6009
6861
  }
@@ -6023,10 +6875,10 @@ async function boot(options) {
6023
6875
  const { spawn } = await import("child_process");
6024
6876
  const { fileURLToPath: fileURLToPath3 } = await import("url");
6025
6877
  const thisFile = fileURLToPath3(import.meta.url);
6026
- const backfillPath = path18.resolve(path18.dirname(thisFile), "backfill-vectors.js");
6027
- if (existsSync14(backfillPath)) {
6878
+ const backfillPath = path19.resolve(path19.dirname(thisFile), "backfill-vectors.js");
6879
+ if (existsSync15(backfillPath)) {
6028
6880
  const { openSync, closeSync } = await import("fs");
6029
- const workerLogPath = path18.join(EXE_AI_DIR, "workers.log");
6881
+ const workerLogPath = path19.join(EXE_AI_DIR, "workers.log");
6030
6882
  let stderrFd = "ignore";
6031
6883
  try {
6032
6884
  stderrFd = openSync(workerLogPath, "a");
@@ -6054,8 +6906,8 @@ async function boot(options) {
6054
6906
  const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
6055
6907
  const missing = [];
6056
6908
  for (const bin of criticalBinaries) {
6057
- const binPath = path18.resolve(path18.dirname(thisFile), bin);
6058
- if (!existsSync14(binPath)) {
6909
+ const binPath = path19.resolve(path19.dirname(thisFile), bin);
6910
+ if (!existsSync15(binPath)) {
6059
6911
  missing.push(`dist/bin/${bin}`);
6060
6912
  }
6061
6913
  }
@@ -6085,7 +6937,7 @@ async function boot(options) {
6085
6937
  return;
6086
6938
  }
6087
6939
  const exeEmployee = employees.find((e) => e.name === "exe") ?? DEFAULT_EXE;
6088
- const sessionDir = path18.join(EXE_AI_DIR, "sessions", "exe");
6940
+ const sessionDir = path19.join(EXE_AI_DIR, "sessions", "exe");
6089
6941
  await mkdir5(sessionDir, { recursive: true });
6090
6942
  const claudeMdContent = `${exeEmployee.systemPrompt}
6091
6943
 
@@ -6094,7 +6946,7 @@ async function boot(options) {
6094
6946
  # Status Brief
6095
6947
 
6096
6948
  ${brief}`;
6097
- await writeFile6(path18.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
6949
+ await writeFile6(path19.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
6098
6950
  const unread = await readUnreadNotifications();
6099
6951
  if (unread.length > 0) {
6100
6952
  console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
@@ -6103,12 +6955,12 @@ ${brief}`;
6103
6955
  await cleanupOldNotifications();
6104
6956
  console.log(brief);
6105
6957
  try {
6106
- const configPath2 = path18.join(
6107
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os6.homedir(), ".exe-os"),
6958
+ const configPath2 = path19.join(
6959
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
6108
6960
  "config.json"
6109
6961
  );
6110
- if (existsSync14(configPath2)) {
6111
- const rawCfg = JSON.parse(readFileSync11(configPath2, "utf8"));
6962
+ if (existsSync15(configPath2)) {
6963
+ const rawCfg = JSON.parse(readFileSync13(configPath2, "utf8"));
6112
6964
  const cloudCfg = rawCfg.cloud;
6113
6965
  if (cloudCfg?.apiKey) {
6114
6966
  const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
@@ -6118,9 +6970,7 @@ ${brief}`;
6118
6970
  if (mk) initSyncCrypto2(mk);
6119
6971
  }
6120
6972
  const { cloudSync: cloudSync2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
6121
- cloudSync2({ apiKey: cloudCfg.apiKey, endpoint: cloudCfg.endpoint ?? "" }).catch((err) => {
6122
- process.stderr.write(`[boot] cloud sync failed: ${err instanceof Error ? err.message : String(err)}
6123
- `);
6973
+ cloudSync2({ apiKey: cloudCfg.apiKey, endpoint: cloudCfg.endpoint ?? "" }).catch(() => {
6124
6974
  });
6125
6975
  }
6126
6976
  }