@askexenow/exe-os 0.8.53 → 0.8.55

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 (67) hide show
  1. package/dist/bin/backfill-conversations.js +113 -10
  2. package/dist/bin/backfill-responses.js +113 -10
  3. package/dist/bin/backfill-vectors.js +147 -13
  4. package/dist/bin/cleanup-stale-review-tasks.js +113 -10
  5. package/dist/bin/cli.js +337 -211
  6. package/dist/bin/exe-agent.js +99 -4
  7. package/dist/bin/exe-assign.js +113 -10
  8. package/dist/bin/exe-boot.js +276 -85
  9. package/dist/bin/exe-call.js +107 -5
  10. package/dist/bin/exe-doctor.js +183 -13
  11. package/dist/bin/exe-export-behaviors.js +113 -10
  12. package/dist/bin/exe-forget.js +113 -10
  13. package/dist/bin/exe-gateway.js +131 -12
  14. package/dist/bin/exe-heartbeat.js +121 -11
  15. package/dist/bin/exe-kill.js +113 -10
  16. package/dist/bin/exe-launch-agent.js +113 -10
  17. package/dist/bin/exe-link.js +10 -2
  18. package/dist/bin/exe-new-employee.js +95 -0
  19. package/dist/bin/exe-pending-messages.js +113 -10
  20. package/dist/bin/exe-pending-notifications.js +113 -10
  21. package/dist/bin/exe-pending-reviews.js +122 -11
  22. package/dist/bin/exe-rename.js +95 -0
  23. package/dist/bin/exe-review.js +113 -10
  24. package/dist/bin/exe-search.js +113 -10
  25. package/dist/bin/exe-session-cleanup.js +131 -12
  26. package/dist/bin/exe-status.js +113 -10
  27. package/dist/bin/exe-team.js +113 -10
  28. package/dist/bin/git-sweep.js +131 -12
  29. package/dist/bin/graph-backfill.js +113 -10
  30. package/dist/bin/graph-export.js +113 -10
  31. package/dist/bin/scan-tasks.js +131 -12
  32. package/dist/bin/setup.js +107 -5
  33. package/dist/bin/shard-migrate.js +113 -10
  34. package/dist/bin/wiki-sync.js +113 -10
  35. package/dist/gateway/index.js +131 -12
  36. package/dist/hooks/bug-report-worker.js +131 -12
  37. package/dist/hooks/commit-complete.js +131 -12
  38. package/dist/hooks/error-recall.js +113 -10
  39. package/dist/hooks/ingest-worker.js +131 -12
  40. package/dist/hooks/instructions-loaded.js +113 -10
  41. package/dist/hooks/notification.js +113 -10
  42. package/dist/hooks/post-compact.js +113 -10
  43. package/dist/hooks/pre-compact.js +131 -12
  44. package/dist/hooks/pre-tool-use.js +113 -10
  45. package/dist/hooks/prompt-ingest-worker.js +113 -10
  46. package/dist/hooks/prompt-submit.js +140 -14
  47. package/dist/hooks/response-ingest-worker.js +113 -10
  48. package/dist/hooks/session-end.js +113 -10
  49. package/dist/hooks/session-start.js +113 -10
  50. package/dist/hooks/stop.js +113 -10
  51. package/dist/hooks/subagent-stop.js +113 -10
  52. package/dist/hooks/summary-worker.js +231 -114
  53. package/dist/index.js +131 -12
  54. package/dist/lib/cloud-sync.js +10 -2
  55. package/dist/lib/employee-templates.js +99 -4
  56. package/dist/lib/exe-daemon.js +4859 -4706
  57. package/dist/lib/hybrid-search.js +113 -10
  58. package/dist/lib/schedules.js +113 -10
  59. package/dist/lib/store.js +113 -10
  60. package/dist/lib/tasks.js +18 -2
  61. package/dist/lib/tmux-routing.js +18 -2
  62. package/dist/mcp/server.js +214 -28
  63. package/dist/mcp/tools/create-task.js +18 -2
  64. package/dist/mcp/tools/list-tasks.js +18 -2
  65. package/dist/runtime/index.js +131 -12
  66. package/dist/tui/App.js +337 -211
  67. package/package.json +2 -2
@@ -1271,6 +1271,103 @@ var init_database = __esm({
1271
1271
  }
1272
1272
  });
1273
1273
 
1274
+ // src/lib/platform-procedures.ts
1275
+ var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
1276
+ var init_platform_procedures = __esm({
1277
+ "src/lib/platform-procedures.ts"() {
1278
+ "use strict";
1279
+ PLATFORM_PROCEDURES = [
1280
+ // --- Foundation: what is exe-os ---
1281
+ {
1282
+ title: "What is exe-os \u2014 the operating model every agent must understand",
1283
+ domain: "architecture",
1284
+ priority: "p0",
1285
+ content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO (exe), CTO (yoshi), CMO (mari), engineers (tom), content (sasha). Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
1286
+ },
1287
+ {
1288
+ title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
1289
+ domain: "architecture",
1290
+ priority: "p0",
1291
+ content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC, runs /exe to boot the COO. exe manages employees in tmux sessions. Each exeN is a separate CC window/project. Employees (yoshi, tom, mari) run in their own tmux panes via create_task auto-spawn. The founder talks to exe; exe orchestrates the team. CC is the shell, exe-os is the brain."
1292
+ },
1293
+ {
1294
+ title: "Sessions explained \u2014 what exeN means and how projects work",
1295
+ domain: "architecture",
1296
+ priority: "p0",
1297
+ content: "Each exeN (exe1, exe2, exe3) is an isolated project session. exe1 might be exe-os development, exe2 might be exe-wiki. Each session spawns its own employees: exe1\u2192yoshi-exe1\u2192tom-exe1. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
1298
+ },
1299
+ // --- Hierarchy and dispatch ---
1300
+ {
1301
+ title: "Chain of command \u2014 who talks to whom",
1302
+ domain: "workflow",
1303
+ priority: "p0",
1304
+ content: "Founder \u2192 exe (COO) \u2192 yoshi (CTO) / mari (CMO). Yoshi \u2192 tom (engineer). Mari \u2192 sasha (content). Never skip levels: exe never assigns directly to tom. Tom never reports directly to exe. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
1305
+ },
1306
+ {
1307
+ title: "Single dispatch path \u2014 create_task only",
1308
+ domain: "workflow",
1309
+ priority: "p0",
1310
+ content: "create_task is the ONLY way to dispatch work to another agent. No direct ensureEmployee calls, no manual tmux spawns, no send_message for actionable work. create_task \u2192 system auto-spawns \u2192 session correctly named. ONE PATH. No backdoors. No exceptions."
1311
+ },
1312
+ // --- Session isolation ---
1313
+ {
1314
+ title: "Session scoping \u2014 stay in your exe boundary",
1315
+ domain: "security",
1316
+ priority: "p0",
1317
+ content: "Session scoping is mandatory. Managers dispatch to workers within their own exe session ONLY. exe1\u2192yoshi-exe1\u2192tom-exe1. exe2\u2192yoshi-exe2\u2192tom2-exe2. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating exe session."
1318
+ },
1319
+ {
1320
+ title: "Session isolation \u2014 never touch another session's work",
1321
+ domain: "workflow",
1322
+ priority: "p0",
1323
+ content: `Sessions are isolated. exeN owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another exe session. (2) Never review work from a different session \u2014 report "belongs to exeN" and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: yoshi-exe1 works ONLY on exe1 tasks. Cross-session work is a system violation.`
1324
+ },
1325
+ // --- Engineering: session scoping in code ---
1326
+ {
1327
+ title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
1328
+ domain: "architecture",
1329
+ priority: "p0",
1330
+ content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current exeN. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ exe sessions simultaneously."
1331
+ },
1332
+ // --- Hard constraints ---
1333
+ {
1334
+ title: "What you CANNOT do in exe-os \u2014 hard constraints",
1335
+ domain: "security",
1336
+ priority: "p0",
1337
+ content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 exe reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
1338
+ },
1339
+ // --- Operations ---
1340
+ {
1341
+ title: "Managers must supervise deployed workers",
1342
+ domain: "workflow",
1343
+ priority: "p0",
1344
+ content: `Every manager (COO/CTO/CMO) who dispatches work to a worker MUST actively monitor them. Check tmux capture-pane every 10 minutes. Verify they're working, not stuck. If idle at prompt with in_progress task \u2192 send intercom. If stuck \u2192 unblock or escalate. "Standing by" without checking is negligence.`
1345
+ },
1346
+ {
1347
+ title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
1348
+ domain: "workflow",
1349
+ priority: "p0",
1350
+ content: "On every /exe boot, COO MUST check system health BEFORE other work: (1) daemon \u2014 is exed PID alive, (2) cloud sync \u2014 grep workers.log for recent cloud-sync errors, (3) memory count \u2014 total in DB, (4) sync delta \u2014 local vs cloud storage_bytes. Report as 4-line status table. If ANY check fails, surface to founder immediately. Do not proceed to tasks until health confirmed."
1351
+ },
1352
+ {
1353
+ title: "exe-build-adv mandatory for 3+ files",
1354
+ domain: "workflow",
1355
+ priority: "p0",
1356
+ content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
1357
+ },
1358
+ {
1359
+ title: "Desktop and TUI are the same product",
1360
+ domain: "architecture",
1361
+ priority: "p0",
1362
+ content: "Desktop and TUI are the SAME product in different renderers. Same data contracts, same interactions, same acceptance criteria. Desktop tab specs in ARCHITECTURE.md ARE the TUI specs. When building TUI, cross-reference Desktop spec. Different tab names, identical behavior. Never treat them as separate products."
1363
+ }
1364
+ ];
1365
+ PLATFORM_PROCEDURE_TITLES = new Set(
1366
+ PLATFORM_PROCEDURES.map((p) => p.title)
1367
+ );
1368
+ }
1369
+ });
1370
+
1274
1371
  // src/lib/global-procedures.ts
1275
1372
  var global_procedures_exports = {};
1276
1373
  __export(global_procedures_exports, {
@@ -1286,22 +1383,25 @@ async function loadGlobalProcedures() {
1286
1383
  sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
1287
1384
  args: []
1288
1385
  });
1289
- const procedures = result.rows;
1290
- if (procedures.length > 0) {
1291
- _cache = procedures.map((p) => `### ${p.title}
1386
+ const allRows = result.rows;
1387
+ const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
1388
+ if (customerOnly.length > 0) {
1389
+ _customerCache = customerOnly.map((p) => `### ${p.title}
1292
1390
  ${p.content}`).join("\n\n");
1293
1391
  } else {
1294
- _cache = "";
1392
+ _customerCache = "";
1295
1393
  }
1296
1394
  _cacheLoaded = true;
1297
- return procedures;
1395
+ return customerOnly;
1298
1396
  }
1299
1397
  function getGlobalProceduresBlock() {
1300
- if (!_cacheLoaded) return "";
1301
- if (!_cache) return "";
1398
+ const sections = [];
1399
+ if (_platformCache) sections.push(_platformCache);
1400
+ if (_cacheLoaded && _customerCache) sections.push(_customerCache);
1401
+ if (sections.length === 0) return "";
1302
1402
  return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
1303
1403
 
1304
- ${_cache}
1404
+ ${sections.join("\n\n")}
1305
1405
  `;
1306
1406
  }
1307
1407
  async function storeGlobalProcedure(input) {
@@ -1326,13 +1426,16 @@ async function deactivateGlobalProcedure(id) {
1326
1426
  await loadGlobalProcedures();
1327
1427
  return result.rowsAffected > 0;
1328
1428
  }
1329
- var _cache, _cacheLoaded;
1429
+ var _customerCache, _cacheLoaded, _platformCache;
1330
1430
  var init_global_procedures = __esm({
1331
1431
  "src/lib/global-procedures.ts"() {
1332
1432
  "use strict";
1333
1433
  init_database();
1334
- _cache = "";
1434
+ init_platform_procedures();
1435
+ _customerCache = "";
1335
1436
  _cacheLoaded = false;
1437
+ _platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
1438
+ ${p.content}`).join("\n\n");
1336
1439
  }
1337
1440
  });
1338
1441
 
@@ -3329,16 +3432,32 @@ var init_tasks_crud = __esm({
3329
3432
  // src/lib/tasks-review.ts
3330
3433
  import path12 from "path";
3331
3434
  import { existsSync as existsSync11, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "fs";
3332
- async function countPendingReviews() {
3435
+ async function countPendingReviews(sessionScope) {
3333
3436
  const client = getClient();
3437
+ if (sessionScope) {
3438
+ const result2 = await client.execute({
3439
+ sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3440
+ args: [sessionScope]
3441
+ });
3442
+ return Number(result2.rows[0]?.cnt) || 0;
3443
+ }
3334
3444
  const result = await client.execute({
3335
3445
  sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3336
3446
  args: []
3337
3447
  });
3338
3448
  return Number(result.rows[0]?.cnt) || 0;
3339
3449
  }
3340
- async function countNewPendingReviewsSince(sinceIso) {
3450
+ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3341
3451
  const client = getClient();
3452
+ if (sessionScope) {
3453
+ const result2 = await client.execute({
3454
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3455
+ WHERE status = 'needs_review' AND updated_at > ?
3456
+ AND (session_scope = ? OR session_scope IS NULL)`,
3457
+ args: [sinceIso, sessionScope]
3458
+ });
3459
+ return Number(result2.rows[0]?.cnt) || 0;
3460
+ }
3342
3461
  const result = await client.execute({
3343
3462
  sql: `SELECT COUNT(*) as cnt FROM tasks
3344
3463
  WHERE status = 'needs_review' AND updated_at > ?`,
@@ -5325,6 +5444,64 @@ var init_task_scanner = __esm({
5325
5444
  }
5326
5445
  });
5327
5446
 
5447
+ // src/lib/worker-gate.ts
5448
+ var worker_gate_exports = {};
5449
+ __export(worker_gate_exports, {
5450
+ MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
5451
+ cleanupWorkerPid: () => cleanupWorkerPid,
5452
+ registerWorkerPid: () => registerWorkerPid,
5453
+ tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
5454
+ });
5455
+ import { readdirSync as readdirSync6, writeFileSync as writeFileSync7, unlinkSync as unlinkSync6, mkdirSync as mkdirSync8 } from "fs";
5456
+ import path18 from "path";
5457
+ function tryAcquireWorkerSlot() {
5458
+ try {
5459
+ mkdirSync8(WORKER_PID_DIR, { recursive: true });
5460
+ const files = readdirSync6(WORKER_PID_DIR);
5461
+ let alive = 0;
5462
+ for (const f of files) {
5463
+ if (!f.endsWith(".pid")) continue;
5464
+ const dashIdx = f.lastIndexOf("-");
5465
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
5466
+ if (isNaN(pid)) continue;
5467
+ try {
5468
+ process.kill(pid, 0);
5469
+ alive++;
5470
+ } catch {
5471
+ try {
5472
+ unlinkSync6(path18.join(WORKER_PID_DIR, f));
5473
+ } catch {
5474
+ }
5475
+ }
5476
+ }
5477
+ return alive < MAX_CONCURRENT_WORKERS;
5478
+ } catch {
5479
+ return true;
5480
+ }
5481
+ }
5482
+ function registerWorkerPid(pid) {
5483
+ try {
5484
+ mkdirSync8(WORKER_PID_DIR, { recursive: true });
5485
+ writeFileSync7(path18.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
5486
+ } catch {
5487
+ }
5488
+ }
5489
+ function cleanupWorkerPid() {
5490
+ try {
5491
+ unlinkSync6(path18.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
5492
+ } catch {
5493
+ }
5494
+ }
5495
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
5496
+ var init_worker_gate = __esm({
5497
+ "src/lib/worker-gate.ts"() {
5498
+ "use strict";
5499
+ init_config();
5500
+ WORKER_PID_DIR = path18.join(EXE_AI_DIR, "worker-pids");
5501
+ MAX_CONCURRENT_WORKERS = 3;
5502
+ }
5503
+ });
5504
+
5328
5505
  // src/lib/crypto.ts
5329
5506
  var crypto_exports = {};
5330
5507
  __export(crypto_exports, {
@@ -5432,13 +5609,13 @@ __export(cloud_sync_exports, {
5432
5609
  mergeRosterFromRemote: () => mergeRosterFromRemote,
5433
5610
  recordRosterDeletion: () => recordRosterDeletion
5434
5611
  });
5435
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync6, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync, closeSync } from "fs";
5612
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync14, readdirSync as readdirSync7, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2, unlinkSync as unlinkSync7, openSync, closeSync } from "fs";
5436
5613
  import crypto8 from "crypto";
5437
- import path18 from "path";
5614
+ import path19 from "path";
5438
5615
  import { homedir } from "os";
5439
5616
  function logError(msg) {
5440
5617
  try {
5441
- const logPath = path18.join(homedir(), ".exe-os", "workers.log");
5618
+ const logPath = path19.join(homedir(), ".exe-os", "workers.log");
5442
5619
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
5443
5620
  `);
5444
5621
  } catch {
@@ -5448,7 +5625,7 @@ async function withRosterLock(fn) {
5448
5625
  try {
5449
5626
  const fd = openSync(ROSTER_LOCK_PATH, "wx");
5450
5627
  closeSync(fd);
5451
- writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
5628
+ writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
5452
5629
  } catch (err) {
5453
5630
  if (err.code === "EEXIST") {
5454
5631
  try {
@@ -5456,10 +5633,10 @@ async function withRosterLock(fn) {
5456
5633
  if (Date.now() - ts < LOCK_STALE_MS) {
5457
5634
  throw new Error("Roster merge already in progress \u2014 another sync is running");
5458
5635
  }
5459
- unlinkSync6(ROSTER_LOCK_PATH);
5636
+ unlinkSync7(ROSTER_LOCK_PATH);
5460
5637
  const fd = openSync(ROSTER_LOCK_PATH, "wx");
5461
5638
  closeSync(fd);
5462
- writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
5639
+ writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
5463
5640
  } catch (retryErr) {
5464
5641
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
5465
5642
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -5472,7 +5649,7 @@ async function withRosterLock(fn) {
5472
5649
  return await fn();
5473
5650
  } finally {
5474
5651
  try {
5475
- unlinkSync6(ROSTER_LOCK_PATH);
5652
+ unlinkSync7(ROSTER_LOCK_PATH);
5476
5653
  } catch {
5477
5654
  }
5478
5655
  }
@@ -5772,22 +5949,22 @@ function recordRosterDeletion(name) {
5772
5949
  } catch {
5773
5950
  }
5774
5951
  if (!deletions.includes(name)) deletions.push(name);
5775
- writeFileSync7(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
5952
+ writeFileSync8(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
5776
5953
  }
5777
5954
  function consumeRosterDeletions() {
5778
5955
  try {
5779
5956
  if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
5780
5957
  const deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
5781
- writeFileSync7(ROSTER_DELETIONS_PATH, "[]");
5958
+ writeFileSync8(ROSTER_DELETIONS_PATH, "[]");
5782
5959
  return deletions;
5783
5960
  } catch {
5784
5961
  return [];
5785
5962
  }
5786
5963
  }
5787
5964
  function buildRosterBlob(paths) {
5788
- const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
5789
- const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
5790
- const configPath = paths?.configPath ?? path18.join(EXE_AI_DIR, "config.json");
5965
+ const rosterPath = paths?.rosterPath ?? path19.join(EXE_AI_DIR, "exe-employees.json");
5966
+ const identityDir = paths?.identityDir ?? path19.join(EXE_AI_DIR, "identity");
5967
+ const configPath = paths?.configPath ?? path19.join(EXE_AI_DIR, "config.json");
5791
5968
  let roster = [];
5792
5969
  if (existsSync14(rosterPath)) {
5793
5970
  try {
@@ -5797,9 +5974,9 @@ function buildRosterBlob(paths) {
5797
5974
  }
5798
5975
  const identities = {};
5799
5976
  if (existsSync14(identityDir)) {
5800
- for (const file of readdirSync6(identityDir).filter((f) => f.endsWith(".md"))) {
5977
+ for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
5801
5978
  try {
5802
- identities[file] = readFileSync12(path18.join(identityDir, file), "utf-8");
5979
+ identities[file] = readFileSync12(path19.join(identityDir, file), "utf-8");
5803
5980
  } catch {
5804
5981
  }
5805
5982
  }
@@ -5883,7 +6060,7 @@ async function cloudPullRoster(config) {
5883
6060
  }
5884
6061
  }
5885
6062
  function mergeConfig(remoteConfig, configPath) {
5886
- const cfgPath = configPath ?? path18.join(EXE_AI_DIR, "config.json");
6063
+ const cfgPath = configPath ?? path19.join(EXE_AI_DIR, "config.json");
5887
6064
  let local = {};
5888
6065
  if (existsSync14(cfgPath)) {
5889
6066
  try {
@@ -5892,14 +6069,14 @@ function mergeConfig(remoteConfig, configPath) {
5892
6069
  }
5893
6070
  }
5894
6071
  const merged = { ...remoteConfig, ...local };
5895
- const dir = path18.dirname(cfgPath);
5896
- if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
5897
- writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
6072
+ const dir = path19.dirname(cfgPath);
6073
+ if (!existsSync14(dir)) mkdirSync9(dir, { recursive: true });
6074
+ writeFileSync8(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5898
6075
  }
5899
6076
  async function mergeRosterFromRemote(remote, paths) {
5900
6077
  return withRosterLock(async () => {
5901
6078
  const rosterPath = paths?.rosterPath ?? void 0;
5902
- const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
6079
+ const identityDir = paths?.identityDir ?? path19.join(EXE_AI_DIR, "identity");
5903
6080
  const localEmployees = await loadEmployees(rosterPath);
5904
6081
  const localNames = new Set(localEmployees.map((e) => e.name));
5905
6082
  let added = 0;
@@ -5909,10 +6086,10 @@ async function mergeRosterFromRemote(remote, paths) {
5909
6086
  localNames.add(remoteEmp.name);
5910
6087
  added++;
5911
6088
  if (remote.identities[`${remoteEmp.name}.md`]) {
5912
- if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
5913
- const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
6089
+ if (!existsSync14(identityDir)) mkdirSync9(identityDir, { recursive: true });
6090
+ const idPath = path19.join(identityDir, `${remoteEmp.name}.md`);
5914
6091
  if (!existsSync14(idPath)) {
5915
- writeFileSync7(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
6092
+ writeFileSync8(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
5916
6093
  }
5917
6094
  }
5918
6095
  try {
@@ -6026,9 +6203,17 @@ async function cloudPullGlobalProcedures(config) {
6026
6203
  if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
6027
6204
  const client = getClient();
6028
6205
  const stmts = remoteProcs.map((p) => ({
6029
- sql: `INSERT OR IGNORE INTO global_procedures
6206
+ sql: `INSERT INTO global_procedures
6030
6207
  (id, title, content, priority, domain, active, created_at, updated_at)
6031
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
6208
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
6209
+ ON CONFLICT(id) DO UPDATE SET
6210
+ title = excluded.title,
6211
+ content = excluded.content,
6212
+ priority = excluded.priority,
6213
+ domain = excluded.domain,
6214
+ active = excluded.active,
6215
+ updated_at = excluded.updated_at
6216
+ WHERE excluded.updated_at > global_procedures.updated_at`,
6032
6217
  args: [
6033
6218
  p.id ?? null,
6034
6219
  p.title ?? null,
@@ -6364,9 +6549,9 @@ var init_cloud_sync = __esm({
6364
6549
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
6365
6550
  FETCH_TIMEOUT_MS = 3e4;
6366
6551
  PUSH_BATCH_SIZE = 5e3;
6367
- ROSTER_LOCK_PATH = path18.join(EXE_AI_DIR, "roster-merge.lock");
6552
+ ROSTER_LOCK_PATH = path19.join(EXE_AI_DIR, "roster-merge.lock");
6368
6553
  LOCK_STALE_MS = 3e4;
6369
- ROSTER_DELETIONS_PATH = path18.join(EXE_AI_DIR, "roster-deletions.json");
6554
+ ROSTER_DELETIONS_PATH = path19.join(EXE_AI_DIR, "roster-deletions.json");
6370
6555
  }
6371
6556
  });
6372
6557
 
@@ -6548,9 +6733,9 @@ var init_schedules = __esm({
6548
6733
 
6549
6734
  // src/bin/exe-boot.ts
6550
6735
  init_employees();
6551
- import path19 from "path";
6736
+ import path20 from "path";
6552
6737
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
6553
- import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync7, unlinkSync as unlinkSync7 } from "fs";
6738
+ import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync8, unlinkSync as unlinkSync8 } from "fs";
6554
6739
  import os7 from "os";
6555
6740
 
6556
6741
  // src/lib/employee-templates.ts
@@ -7138,18 +7323,18 @@ async function boot(options) {
7138
7323
  } catch {
7139
7324
  }
7140
7325
  try {
7141
- const { readdirSync: readdirSync8, readFileSync: readFs } = await import("fs");
7326
+ const { readdirSync: readdirSync9, readFileSync: readFs } = await import("fs");
7142
7327
  const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
7143
7328
  const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
7144
7329
  const exeDir = "exe";
7145
- const entries = readdirSync8(exeDir, { withFileTypes: true });
7330
+ const entries = readdirSync9(exeDir, { withFileTypes: true });
7146
7331
  const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
7147
7332
  for (const dir of employeeDirs) {
7148
7333
  const employee = dir.name;
7149
- const taskDir = path19.join(exeDir, employee);
7334
+ const taskDir = path20.join(exeDir, employee);
7150
7335
  let files;
7151
7336
  try {
7152
- files = readdirSync8(taskDir).filter((f) => f.endsWith(".md"));
7337
+ files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
7153
7338
  } catch {
7154
7339
  continue;
7155
7340
  }
@@ -7157,7 +7342,7 @@ async function boot(options) {
7157
7342
  const taskFilePath = `exe/${employee}/${file}`;
7158
7343
  let content;
7159
7344
  try {
7160
- content = readFs(path19.join(taskDir, file), "utf8");
7345
+ content = readFs(path20.join(taskDir, file), "utf8");
7161
7346
  } catch {
7162
7347
  continue;
7163
7348
  }
@@ -7241,12 +7426,12 @@ async function boot(options) {
7241
7426
  } catch {
7242
7427
  }
7243
7428
  try {
7244
- const exeExeDir = path19.join(process.cwd(), "exe", "exe");
7429
+ const exeExeDir = path20.join(process.cwd(), "exe", "exe");
7245
7430
  if (existsSync15(exeExeDir)) {
7246
- for (const f of readdirSync7(exeExeDir)) {
7431
+ for (const f of readdirSync8(exeExeDir)) {
7247
7432
  if (f.startsWith("review-") && f.endsWith(".md")) {
7248
7433
  try {
7249
- unlinkSync7(path19.join(exeExeDir, f));
7434
+ unlinkSync8(path20.join(exeExeDir, f));
7250
7435
  } catch {
7251
7436
  }
7252
7437
  }
@@ -7290,12 +7475,12 @@ async function boot(options) {
7290
7475
  });
7291
7476
  const taskFile = String(r.task_file);
7292
7477
  try {
7293
- const filePath = path19.join(process.cwd(), taskFile);
7478
+ const filePath = path20.join(process.cwd(), taskFile);
7294
7479
  if (existsSync15(filePath)) {
7295
7480
  let content = readFileSync13(filePath, "utf8");
7296
7481
  content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
7297
- const { writeFileSync: writeFileSync8 } = await import("fs");
7298
- writeFileSync8(filePath, content);
7482
+ const { writeFileSync: writeFileSync9 } = await import("fs");
7483
+ writeFileSync9(filePath, content);
7299
7484
  }
7300
7485
  } catch {
7301
7486
  }
@@ -7755,8 +7940,8 @@ async function boot(options) {
7755
7940
  })()
7756
7941
  ]);
7757
7942
  try {
7758
- const configPath = path19.join(
7759
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os7.homedir(), ".exe-os"),
7943
+ const configPath = path20.join(
7944
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os7.homedir(), ".exe-os"),
7760
7945
  "config.json"
7761
7946
  );
7762
7947
  if (existsSync15(configPath)) {
@@ -7766,7 +7951,7 @@ async function boot(options) {
7766
7951
  } catch {
7767
7952
  }
7768
7953
  try {
7769
- const backfillFlagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
7954
+ const backfillFlagPath = path20.join(EXE_AI_DIR, "session-cache", "needs-backfill");
7770
7955
  const isBackfillNeeded = () => existsSync15(backfillFlagPath);
7771
7956
  const coverageResult = await client.execute({
7772
7957
  sql: `SELECT COUNT(*) as total,
@@ -7789,7 +7974,7 @@ async function boot(options) {
7789
7974
  let daemonRunning = false;
7790
7975
  let daemonUptime;
7791
7976
  let daemonRequestsServed;
7792
- const socketPath = path19.join(EXE_AI_DIR, "exed.sock");
7977
+ const socketPath = path20.join(EXE_AI_DIR, "exed.sock");
7793
7978
  if (existsSync15(socketPath)) {
7794
7979
  try {
7795
7980
  const net = await import("net");
@@ -7832,7 +8017,7 @@ async function boot(options) {
7832
8017
  }
7833
8018
  }
7834
8019
  if (!daemonRunning) {
7835
- const pidPath = path19.join(EXE_AI_DIR, "exed.pid");
8020
+ const pidPath = path20.join(EXE_AI_DIR, "exed.pid");
7836
8021
  if (existsSync15(pidPath)) {
7837
8022
  try {
7838
8023
  const pid = parseInt(readFileSync13(pidPath, "utf8").trim(), 10);
@@ -7846,10 +8031,10 @@ async function boot(options) {
7846
8031
  }
7847
8032
  if (nullCount === 0) {
7848
8033
  try {
7849
- const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
8034
+ const flagPath = path20.join(EXE_AI_DIR, "session-cache", "needs-backfill");
7850
8035
  if (existsSync15(flagPath)) {
7851
- const { unlinkSync: unlinkSync8 } = await import("fs");
7852
- unlinkSync8(flagPath);
8036
+ const { unlinkSync: unlinkSync9 } = await import("fs");
8037
+ unlinkSync9(flagPath);
7853
8038
  }
7854
8039
  } catch {
7855
8040
  }
@@ -7866,28 +8051,34 @@ async function boot(options) {
7866
8051
  };
7867
8052
  if (nullCount > 0) {
7868
8053
  try {
7869
- const { spawn } = await import("child_process");
7870
- const { fileURLToPath: fileURLToPath3 } = await import("url");
7871
- const thisFile = fileURLToPath3(import.meta.url);
7872
- const backfillPath = path19.resolve(path19.dirname(thisFile), "backfill-vectors.js");
7873
- if (existsSync15(backfillPath)) {
7874
- const { openSync: openSync2, closeSync: closeSync2 } = await import("fs");
7875
- const workerLogPath = path19.join(EXE_AI_DIR, "workers.log");
7876
- let stderrFd = "ignore";
7877
- try {
7878
- stderrFd = openSync2(workerLogPath, "a");
7879
- } catch {
7880
- }
7881
- const child = spawn(process.execPath, [backfillPath], {
7882
- detached: true,
7883
- stdio: ["ignore", "ignore", stderrFd]
7884
- });
7885
- child.unref();
7886
- if (typeof stderrFd === "number") try {
7887
- closeSync2(stderrFd);
7888
- } catch {
8054
+ const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
8055
+ if (!tryAcquireWorkerSlot2()) {
8056
+ process.stderr.write("[exe-boot] Backfill needed but worker gate full \u2014 skipping\n");
8057
+ } else {
8058
+ const { spawn } = await import("child_process");
8059
+ const { fileURLToPath: fileURLToPath3 } = await import("url");
8060
+ const thisFile = fileURLToPath3(import.meta.url);
8061
+ const backfillPath = path20.resolve(path20.dirname(thisFile), "backfill-vectors.js");
8062
+ if (existsSync15(backfillPath)) {
8063
+ const { openSync: openSync2, closeSync: closeSync2 } = await import("fs");
8064
+ const workerLogPath = path20.join(EXE_AI_DIR, "workers.log");
8065
+ let stderrFd = "ignore";
8066
+ try {
8067
+ stderrFd = openSync2(workerLogPath, "a");
8068
+ } catch {
8069
+ }
8070
+ const child = spawn(process.execPath, [backfillPath], {
8071
+ detached: true,
8072
+ stdio: ["ignore", "ignore", stderrFd]
8073
+ });
8074
+ child.unref();
8075
+ if (child.pid) registerWorkerPid2(child.pid);
8076
+ if (typeof stderrFd === "number") try {
8077
+ closeSync2(stderrFd);
8078
+ } catch {
8079
+ }
8080
+ briefData.embedding.backfillRunning = true;
7889
8081
  }
7890
- briefData.embedding.backfillRunning = true;
7891
8082
  }
7892
8083
  } catch {
7893
8084
  }
@@ -7900,7 +8091,7 @@ async function boot(options) {
7900
8091
  const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
7901
8092
  const missing = [];
7902
8093
  for (const bin of criticalBinaries) {
7903
- const binPath = path19.resolve(path19.dirname(thisFile), bin);
8094
+ const binPath = path20.resolve(path20.dirname(thisFile), bin);
7904
8095
  if (!existsSync15(binPath)) {
7905
8096
  missing.push(`dist/bin/${bin}`);
7906
8097
  }
@@ -7931,7 +8122,7 @@ async function boot(options) {
7931
8122
  return;
7932
8123
  }
7933
8124
  const exeEmployee = employees.find((e) => e.name === "exe") ?? DEFAULT_EXE;
7934
- const sessionDir = path19.join(EXE_AI_DIR, "sessions", "exe");
8125
+ const sessionDir = path20.join(EXE_AI_DIR, "sessions", "exe");
7935
8126
  await mkdir5(sessionDir, { recursive: true });
7936
8127
  const claudeMdContent = `${getSessionPrompt(exeEmployee.systemPrompt)}
7937
8128
 
@@ -7940,7 +8131,7 @@ async function boot(options) {
7940
8131
  # Status Brief
7941
8132
 
7942
8133
  ${brief}`;
7943
- await writeFile6(path19.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
8134
+ await writeFile6(path20.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
7944
8135
  const unread = await readUnreadNotifications();
7945
8136
  if (unread.length > 0) {
7946
8137
  console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
@@ -7949,8 +8140,8 @@ ${brief}`;
7949
8140
  await cleanupOldNotifications();
7950
8141
  console.log(brief);
7951
8142
  try {
7952
- const configPath2 = path19.join(
7953
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os7.homedir(), ".exe-os"),
8143
+ const configPath2 = path20.join(
8144
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os7.homedir(), ".exe-os"),
7954
8145
  "config.json"
7955
8146
  );
7956
8147
  if (existsSync15(configPath2)) {