@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
@@ -1588,6 +1588,103 @@ var init_shard_manager = __esm({
1588
1588
  }
1589
1589
  });
1590
1590
 
1591
+ // src/lib/platform-procedures.ts
1592
+ var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
1593
+ var init_platform_procedures = __esm({
1594
+ "src/lib/platform-procedures.ts"() {
1595
+ "use strict";
1596
+ PLATFORM_PROCEDURES = [
1597
+ // --- Foundation: what is exe-os ---
1598
+ {
1599
+ title: "What is exe-os \u2014 the operating model every agent must understand",
1600
+ domain: "architecture",
1601
+ priority: "p0",
1602
+ 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."
1603
+ },
1604
+ {
1605
+ title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
1606
+ domain: "architecture",
1607
+ priority: "p0",
1608
+ 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."
1609
+ },
1610
+ {
1611
+ title: "Sessions explained \u2014 what exeN means and how projects work",
1612
+ domain: "architecture",
1613
+ priority: "p0",
1614
+ 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."
1615
+ },
1616
+ // --- Hierarchy and dispatch ---
1617
+ {
1618
+ title: "Chain of command \u2014 who talks to whom",
1619
+ domain: "workflow",
1620
+ priority: "p0",
1621
+ 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."
1622
+ },
1623
+ {
1624
+ title: "Single dispatch path \u2014 create_task only",
1625
+ domain: "workflow",
1626
+ priority: "p0",
1627
+ 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."
1628
+ },
1629
+ // --- Session isolation ---
1630
+ {
1631
+ title: "Session scoping \u2014 stay in your exe boundary",
1632
+ domain: "security",
1633
+ priority: "p0",
1634
+ 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."
1635
+ },
1636
+ {
1637
+ title: "Session isolation \u2014 never touch another session's work",
1638
+ domain: "workflow",
1639
+ priority: "p0",
1640
+ 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.`
1641
+ },
1642
+ // --- Engineering: session scoping in code ---
1643
+ {
1644
+ title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
1645
+ domain: "architecture",
1646
+ priority: "p0",
1647
+ 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."
1648
+ },
1649
+ // --- Hard constraints ---
1650
+ {
1651
+ title: "What you CANNOT do in exe-os \u2014 hard constraints",
1652
+ domain: "security",
1653
+ priority: "p0",
1654
+ 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."
1655
+ },
1656
+ // --- Operations ---
1657
+ {
1658
+ title: "Managers must supervise deployed workers",
1659
+ domain: "workflow",
1660
+ priority: "p0",
1661
+ 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.`
1662
+ },
1663
+ {
1664
+ title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
1665
+ domain: "workflow",
1666
+ priority: "p0",
1667
+ 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."
1668
+ },
1669
+ {
1670
+ title: "exe-build-adv mandatory for 3+ files",
1671
+ domain: "workflow",
1672
+ priority: "p0",
1673
+ 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."
1674
+ },
1675
+ {
1676
+ title: "Desktop and TUI are the same product",
1677
+ domain: "architecture",
1678
+ priority: "p0",
1679
+ 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."
1680
+ }
1681
+ ];
1682
+ PLATFORM_PROCEDURE_TITLES = new Set(
1683
+ PLATFORM_PROCEDURES.map((p) => p.title)
1684
+ );
1685
+ }
1686
+ });
1687
+
1591
1688
  // src/lib/global-procedures.ts
1592
1689
  var global_procedures_exports = {};
1593
1690
  __export(global_procedures_exports, {
@@ -1603,22 +1700,25 @@ async function loadGlobalProcedures() {
1603
1700
  sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
1604
1701
  args: []
1605
1702
  });
1606
- const procedures = result.rows;
1607
- if (procedures.length > 0) {
1608
- _cache = procedures.map((p) => `### ${p.title}
1703
+ const allRows = result.rows;
1704
+ const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
1705
+ if (customerOnly.length > 0) {
1706
+ _customerCache = customerOnly.map((p) => `### ${p.title}
1609
1707
  ${p.content}`).join("\n\n");
1610
1708
  } else {
1611
- _cache = "";
1709
+ _customerCache = "";
1612
1710
  }
1613
1711
  _cacheLoaded = true;
1614
- return procedures;
1712
+ return customerOnly;
1615
1713
  }
1616
1714
  function getGlobalProceduresBlock() {
1617
- if (!_cacheLoaded) return "";
1618
- if (!_cache) return "";
1715
+ const sections = [];
1716
+ if (_platformCache) sections.push(_platformCache);
1717
+ if (_cacheLoaded && _customerCache) sections.push(_customerCache);
1718
+ if (sections.length === 0) return "";
1619
1719
  return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
1620
1720
 
1621
- ${_cache}
1721
+ ${sections.join("\n\n")}
1622
1722
  `;
1623
1723
  }
1624
1724
  async function storeGlobalProcedure(input) {
@@ -1643,13 +1743,16 @@ async function deactivateGlobalProcedure(id) {
1643
1743
  await loadGlobalProcedures();
1644
1744
  return result.rowsAffected > 0;
1645
1745
  }
1646
- var _cache, _cacheLoaded;
1746
+ var _customerCache, _cacheLoaded, _platformCache;
1647
1747
  var init_global_procedures = __esm({
1648
1748
  "src/lib/global-procedures.ts"() {
1649
1749
  "use strict";
1650
1750
  init_database();
1651
- _cache = "";
1751
+ init_platform_procedures();
1752
+ _customerCache = "";
1652
1753
  _cacheLoaded = false;
1754
+ _platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
1755
+ ${p.content}`).join("\n\n");
1653
1756
  }
1654
1757
  });
1655
1758
 
@@ -2457,6 +2560,64 @@ var init_embedder = __esm({
2457
2560
  }
2458
2561
  });
2459
2562
 
2563
+ // src/lib/worker-gate.ts
2564
+ var worker_gate_exports = {};
2565
+ __export(worker_gate_exports, {
2566
+ MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
2567
+ cleanupWorkerPid: () => cleanupWorkerPid,
2568
+ registerWorkerPid: () => registerWorkerPid,
2569
+ tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
2570
+ });
2571
+ import { readdirSync as readdirSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3 } from "fs";
2572
+ import path9 from "path";
2573
+ function tryAcquireWorkerSlot() {
2574
+ try {
2575
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
2576
+ const files = readdirSync3(WORKER_PID_DIR);
2577
+ let alive = 0;
2578
+ for (const f of files) {
2579
+ if (!f.endsWith(".pid")) continue;
2580
+ const dashIdx = f.lastIndexOf("-");
2581
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
2582
+ if (isNaN(pid)) continue;
2583
+ try {
2584
+ process.kill(pid, 0);
2585
+ alive++;
2586
+ } catch {
2587
+ try {
2588
+ unlinkSync3(path9.join(WORKER_PID_DIR, f));
2589
+ } catch {
2590
+ }
2591
+ }
2592
+ }
2593
+ return alive < MAX_CONCURRENT_WORKERS;
2594
+ } catch {
2595
+ return true;
2596
+ }
2597
+ }
2598
+ function registerWorkerPid(pid) {
2599
+ try {
2600
+ mkdirSync3(WORKER_PID_DIR, { recursive: true });
2601
+ writeFileSync2(path9.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
2602
+ } catch {
2603
+ }
2604
+ }
2605
+ function cleanupWorkerPid() {
2606
+ try {
2607
+ unlinkSync3(path9.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
2608
+ } catch {
2609
+ }
2610
+ }
2611
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
2612
+ var init_worker_gate = __esm({
2613
+ "src/lib/worker-gate.ts"() {
2614
+ "use strict";
2615
+ init_config();
2616
+ WORKER_PID_DIR = path9.join(EXE_AI_DIR, "worker-pids");
2617
+ MAX_CONCURRENT_WORKERS = 3;
2618
+ }
2619
+ });
2620
+
2460
2621
  // src/lib/crypto.ts
2461
2622
  var crypto_exports = {};
2462
2623
  __export(crypto_exports, {
@@ -2564,13 +2725,13 @@ __export(cloud_sync_exports, {
2564
2725
  mergeRosterFromRemote: () => mergeRosterFromRemote,
2565
2726
  recordRosterDeletion: () => recordRosterDeletion
2566
2727
  });
2567
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync9, readdirSync as readdirSync3, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2 } from "fs";
2728
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync9, readdirSync as readdirSync4, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
2568
2729
  import crypto4 from "crypto";
2569
- import path9 from "path";
2730
+ import path10 from "path";
2570
2731
  import { homedir } from "os";
2571
2732
  function logError(msg) {
2572
2733
  try {
2573
- const logPath = path9.join(homedir(), ".exe-os", "workers.log");
2734
+ const logPath = path10.join(homedir(), ".exe-os", "workers.log");
2574
2735
  appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2575
2736
  `);
2576
2737
  } catch {
@@ -2580,7 +2741,7 @@ async function withRosterLock(fn) {
2580
2741
  try {
2581
2742
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2582
2743
  closeSync2(fd);
2583
- writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
2744
+ writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
2584
2745
  } catch (err) {
2585
2746
  if (err.code === "EEXIST") {
2586
2747
  try {
@@ -2588,10 +2749,10 @@ async function withRosterLock(fn) {
2588
2749
  if (Date.now() - ts < LOCK_STALE_MS) {
2589
2750
  throw new Error("Roster merge already in progress \u2014 another sync is running");
2590
2751
  }
2591
- unlinkSync3(ROSTER_LOCK_PATH);
2752
+ unlinkSync4(ROSTER_LOCK_PATH);
2592
2753
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2593
2754
  closeSync2(fd);
2594
- writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
2755
+ writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
2595
2756
  } catch (retryErr) {
2596
2757
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
2597
2758
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -2604,7 +2765,7 @@ async function withRosterLock(fn) {
2604
2765
  return await fn();
2605
2766
  } finally {
2606
2767
  try {
2607
- unlinkSync3(ROSTER_LOCK_PATH);
2768
+ unlinkSync4(ROSTER_LOCK_PATH);
2608
2769
  } catch {
2609
2770
  }
2610
2771
  }
@@ -2904,22 +3065,22 @@ function recordRosterDeletion(name) {
2904
3065
  } catch {
2905
3066
  }
2906
3067
  if (!deletions.includes(name)) deletions.push(name);
2907
- writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3068
+ writeFileSync3(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
2908
3069
  }
2909
3070
  function consumeRosterDeletions() {
2910
3071
  try {
2911
3072
  if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
2912
3073
  const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
2913
- writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
3074
+ writeFileSync3(ROSTER_DELETIONS_PATH, "[]");
2914
3075
  return deletions;
2915
3076
  } catch {
2916
3077
  return [];
2917
3078
  }
2918
3079
  }
2919
3080
  function buildRosterBlob(paths) {
2920
- const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
2921
- const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
2922
- const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
3081
+ const rosterPath = paths?.rosterPath ?? path10.join(EXE_AI_DIR, "exe-employees.json");
3082
+ const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
3083
+ const configPath = paths?.configPath ?? path10.join(EXE_AI_DIR, "config.json");
2923
3084
  let roster = [];
2924
3085
  if (existsSync9(rosterPath)) {
2925
3086
  try {
@@ -2929,9 +3090,9 @@ function buildRosterBlob(paths) {
2929
3090
  }
2930
3091
  const identities = {};
2931
3092
  if (existsSync9(identityDir)) {
2932
- for (const file of readdirSync3(identityDir).filter((f) => f.endsWith(".md"))) {
3093
+ for (const file of readdirSync4(identityDir).filter((f) => f.endsWith(".md"))) {
2933
3094
  try {
2934
- identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
3095
+ identities[file] = readFileSync7(path10.join(identityDir, file), "utf-8");
2935
3096
  } catch {
2936
3097
  }
2937
3098
  }
@@ -3015,7 +3176,7 @@ async function cloudPullRoster(config) {
3015
3176
  }
3016
3177
  }
3017
3178
  function mergeConfig(remoteConfig, configPath) {
3018
- const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
3179
+ const cfgPath = configPath ?? path10.join(EXE_AI_DIR, "config.json");
3019
3180
  let local = {};
3020
3181
  if (existsSync9(cfgPath)) {
3021
3182
  try {
@@ -3024,14 +3185,14 @@ function mergeConfig(remoteConfig, configPath) {
3024
3185
  }
3025
3186
  }
3026
3187
  const merged = { ...remoteConfig, ...local };
3027
- const dir = path9.dirname(cfgPath);
3028
- if (!existsSync9(dir)) mkdirSync3(dir, { recursive: true });
3029
- writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3188
+ const dir = path10.dirname(cfgPath);
3189
+ if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
3190
+ writeFileSync3(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3030
3191
  }
3031
3192
  async function mergeRosterFromRemote(remote, paths) {
3032
3193
  return withRosterLock(async () => {
3033
3194
  const rosterPath = paths?.rosterPath ?? void 0;
3034
- const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
3195
+ const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
3035
3196
  const localEmployees = await loadEmployees(rosterPath);
3036
3197
  const localNames = new Set(localEmployees.map((e) => e.name));
3037
3198
  let added = 0;
@@ -3041,10 +3202,10 @@ async function mergeRosterFromRemote(remote, paths) {
3041
3202
  localNames.add(remoteEmp.name);
3042
3203
  added++;
3043
3204
  if (remote.identities[`${remoteEmp.name}.md`]) {
3044
- if (!existsSync9(identityDir)) mkdirSync3(identityDir, { recursive: true });
3045
- const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
3205
+ if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
3206
+ const idPath = path10.join(identityDir, `${remoteEmp.name}.md`);
3046
3207
  if (!existsSync9(idPath)) {
3047
- writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
3208
+ writeFileSync3(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
3048
3209
  }
3049
3210
  }
3050
3211
  try {
@@ -3158,9 +3319,17 @@ async function cloudPullGlobalProcedures(config) {
3158
3319
  if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
3159
3320
  const client = getClient();
3160
3321
  const stmts = remoteProcs.map((p) => ({
3161
- sql: `INSERT OR IGNORE INTO global_procedures
3322
+ sql: `INSERT INTO global_procedures
3162
3323
  (id, title, content, priority, domain, active, created_at, updated_at)
3163
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3324
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3325
+ ON CONFLICT(id) DO UPDATE SET
3326
+ title = excluded.title,
3327
+ content = excluded.content,
3328
+ priority = excluded.priority,
3329
+ domain = excluded.domain,
3330
+ active = excluded.active,
3331
+ updated_at = excluded.updated_at
3332
+ WHERE excluded.updated_at > global_procedures.updated_at`,
3164
3333
  args: [
3165
3334
  p.id ?? null,
3166
3335
  p.title ?? null,
@@ -3496,67 +3665,9 @@ var init_cloud_sync = __esm({
3496
3665
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
3497
3666
  FETCH_TIMEOUT_MS = 3e4;
3498
3667
  PUSH_BATCH_SIZE = 5e3;
3499
- ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
3668
+ ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
3500
3669
  LOCK_STALE_MS = 3e4;
3501
- ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
3502
- }
3503
- });
3504
-
3505
- // src/lib/worker-gate.ts
3506
- var worker_gate_exports = {};
3507
- __export(worker_gate_exports, {
3508
- MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
3509
- cleanupWorkerPid: () => cleanupWorkerPid,
3510
- registerWorkerPid: () => registerWorkerPid,
3511
- tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
3512
- });
3513
- import { readdirSync as readdirSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync4 } from "fs";
3514
- import path10 from "path";
3515
- function tryAcquireWorkerSlot() {
3516
- try {
3517
- mkdirSync4(WORKER_PID_DIR, { recursive: true });
3518
- const files = readdirSync4(WORKER_PID_DIR);
3519
- let alive = 0;
3520
- for (const f of files) {
3521
- if (!f.endsWith(".pid")) continue;
3522
- const dashIdx = f.lastIndexOf("-");
3523
- const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
3524
- if (isNaN(pid)) continue;
3525
- try {
3526
- process.kill(pid, 0);
3527
- alive++;
3528
- } catch {
3529
- try {
3530
- unlinkSync4(path10.join(WORKER_PID_DIR, f));
3531
- } catch {
3532
- }
3533
- }
3534
- }
3535
- return alive < MAX_CONCURRENT_WORKERS;
3536
- } catch {
3537
- return true;
3538
- }
3539
- }
3540
- function registerWorkerPid(pid) {
3541
- try {
3542
- mkdirSync4(WORKER_PID_DIR, { recursive: true });
3543
- writeFileSync3(path10.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3544
- } catch {
3545
- }
3546
- }
3547
- function cleanupWorkerPid() {
3548
- try {
3549
- unlinkSync4(path10.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3550
- } catch {
3551
- }
3552
- }
3553
- var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
3554
- var init_worker_gate = __esm({
3555
- "src/lib/worker-gate.ts"() {
3556
- "use strict";
3557
- init_config();
3558
- WORKER_PID_DIR = path10.join(EXE_AI_DIR, "worker-pids");
3559
- MAX_CONCURRENT_WORKERS = 3;
3670
+ ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
3560
3671
  }
3561
3672
  });
3562
3673
 
@@ -4062,28 +4173,34 @@ async function main() {
4062
4173
  const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4063
4174
  const flagPath = path11.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
4064
4175
  if (existsSync10(flagPath)) {
4065
- const { spawn: spawn2 } = await import("child_process");
4066
- const { fileURLToPath: fileURLToPath2 } = await import("url");
4067
- const thisFile = fileURLToPath2(import.meta.url);
4068
- const backfillPath = path11.resolve(path11.dirname(thisFile), "backfill-vectors.js");
4069
- if (existsSync10(backfillPath)) {
4070
- const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4071
- const bLogPath = path11.join(exeDir2, "workers.log");
4072
- mkdirSync5(path11.dirname(bLogPath), { recursive: true });
4073
- const bLogFd = openSync3(bLogPath, "a");
4074
- const child = spawn2(process.execPath, [backfillPath], {
4075
- detached: true,
4076
- stdio: ["ignore", "ignore", bLogFd]
4077
- });
4078
- child.unref();
4079
- try {
4080
- closeSync3(bLogFd);
4081
- } catch {
4082
- }
4083
- process.stderr.write("[summary-worker] Spawned backfill job\n");
4176
+ const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
4177
+ if (!tryAcquireWorkerSlot2()) {
4178
+ process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
4084
4179
  } else {
4085
- process.stderr.write(`[summary-worker] WARN: backfill-vectors not found at ${backfillPath}
4180
+ const { spawn: spawn2 } = await import("child_process");
4181
+ const { fileURLToPath: fileURLToPath2 } = await import("url");
4182
+ const thisFile = fileURLToPath2(import.meta.url);
4183
+ const backfillPath = path11.resolve(path11.dirname(thisFile), "backfill-vectors.js");
4184
+ if (existsSync10(backfillPath)) {
4185
+ const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4186
+ const bLogPath = path11.join(exeDir2, "workers.log");
4187
+ mkdirSync5(path11.dirname(bLogPath), { recursive: true });
4188
+ const bLogFd = openSync3(bLogPath, "a");
4189
+ const child = spawn2(process.execPath, [backfillPath], {
4190
+ detached: true,
4191
+ stdio: ["ignore", "ignore", bLogFd]
4192
+ });
4193
+ child.unref();
4194
+ if (child.pid) registerWorkerPid2(child.pid);
4195
+ try {
4196
+ closeSync3(bLogFd);
4197
+ } catch {
4198
+ }
4199
+ process.stderr.write("[summary-worker] Spawned backfill job\n");
4200
+ } else {
4201
+ process.stderr.write(`[summary-worker] WARN: backfill-vectors not found at ${backfillPath}
4086
4202
  `);
4203
+ }
4087
4204
  }
4088
4205
  }
4089
4206
  } catch (err) {