@askexenow/exe-os 0.8.80 → 0.8.82

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 (110) hide show
  1. package/dist/bin/backfill-conversations.js +359 -267
  2. package/dist/bin/backfill-responses.js +357 -265
  3. package/dist/bin/backfill-vectors.js +339 -264
  4. package/dist/bin/cleanup-stale-review-tasks.js +315 -256
  5. package/dist/bin/cli.js +494 -240
  6. package/dist/bin/exe-agent.js +141 -46
  7. package/dist/bin/exe-assign.js +151 -63
  8. package/dist/bin/exe-boot.js +294 -115
  9. package/dist/bin/exe-call.js +76 -51
  10. package/dist/bin/exe-cloud.js +58 -45
  11. package/dist/bin/exe-dispatch.js +434 -277
  12. package/dist/bin/exe-doctor.js +317 -246
  13. package/dist/bin/exe-export-behaviors.js +328 -248
  14. package/dist/bin/exe-forget.js +314 -231
  15. package/dist/bin/exe-gateway.js +2676 -1402
  16. package/dist/bin/exe-heartbeat.js +329 -264
  17. package/dist/bin/exe-kill.js +324 -244
  18. package/dist/bin/exe-launch-agent.js +574 -463
  19. package/dist/bin/exe-link.js +1055 -95
  20. package/dist/bin/exe-new-employee.js +49 -54
  21. package/dist/bin/exe-pending-messages.js +310 -253
  22. package/dist/bin/exe-pending-notifications.js +299 -228
  23. package/dist/bin/exe-pending-reviews.js +314 -245
  24. package/dist/bin/exe-rename.js +259 -195
  25. package/dist/bin/exe-review.js +140 -64
  26. package/dist/bin/exe-search.js +543 -356
  27. package/dist/bin/exe-session-cleanup.js +463 -382
  28. package/dist/bin/exe-settings.js +129 -99
  29. package/dist/bin/exe-start.sh +6 -6
  30. package/dist/bin/exe-status.js +95 -36
  31. package/dist/bin/exe-team.js +116 -51
  32. package/dist/bin/git-sweep.js +482 -307
  33. package/dist/bin/graph-backfill.js +357 -245
  34. package/dist/bin/graph-export.js +324 -244
  35. package/dist/bin/install.js +33 -10
  36. package/dist/bin/scan-tasks.js +481 -307
  37. package/dist/bin/setup.js +1147 -140
  38. package/dist/bin/shard-migrate.js +321 -241
  39. package/dist/bin/update.js +1 -7
  40. package/dist/bin/wiki-sync.js +318 -238
  41. package/dist/gateway/index.js +2656 -1383
  42. package/dist/hooks/bug-report-worker.js +641 -472
  43. package/dist/hooks/commit-complete.js +482 -307
  44. package/dist/hooks/error-recall.js +363 -135
  45. package/dist/hooks/exe-heartbeat-hook.js +97 -27
  46. package/dist/hooks/ingest-worker.js +584 -397
  47. package/dist/hooks/ingest.js +123 -58
  48. package/dist/hooks/instructions-loaded.js +212 -82
  49. package/dist/hooks/notification.js +200 -70
  50. package/dist/hooks/post-compact.js +199 -81
  51. package/dist/hooks/pre-compact.js +352 -140
  52. package/dist/hooks/pre-tool-use.js +416 -278
  53. package/dist/hooks/prompt-ingest-worker.js +376 -299
  54. package/dist/hooks/prompt-submit.js +414 -188
  55. package/dist/hooks/response-ingest-worker.js +408 -338
  56. package/dist/hooks/session-end.js +209 -83
  57. package/dist/hooks/session-start.js +382 -158
  58. package/dist/hooks/stop.js +209 -83
  59. package/dist/hooks/subagent-stop.js +209 -85
  60. package/dist/hooks/summary-worker.js +606 -510
  61. package/dist/index.js +2133 -855
  62. package/dist/lib/cloud-sync.js +1175 -184
  63. package/dist/lib/config.js +1 -9
  64. package/dist/lib/consolidation.js +71 -34
  65. package/dist/lib/database.js +166 -14
  66. package/dist/lib/device-registry.js +189 -117
  67. package/dist/lib/embedder.js +6 -10
  68. package/dist/lib/employee-templates.js +134 -39
  69. package/dist/lib/employees.js +30 -7
  70. package/dist/lib/exe-daemon-client.js +5 -7
  71. package/dist/lib/exe-daemon.js +514 -152
  72. package/dist/lib/hybrid-search.js +543 -356
  73. package/dist/lib/identity-templates.js +15 -15
  74. package/dist/lib/identity.js +19 -15
  75. package/dist/lib/license.js +1 -7
  76. package/dist/lib/messaging.js +157 -135
  77. package/dist/lib/reminders.js +97 -0
  78. package/dist/lib/schedules.js +302 -231
  79. package/dist/lib/skill-learning.js +33 -27
  80. package/dist/lib/status-brief.js +11 -14
  81. package/dist/lib/store.js +326 -237
  82. package/dist/lib/task-router.js +105 -1
  83. package/dist/lib/tasks.js +233 -116
  84. package/dist/lib/tmux-routing.js +173 -56
  85. package/dist/lib/ws-client.js +13 -3
  86. package/dist/mcp/server.js +2009 -1015
  87. package/dist/mcp/tools/complete-reminder.js +97 -0
  88. package/dist/mcp/tools/create-reminder.js +97 -0
  89. package/dist/mcp/tools/create-task.js +426 -262
  90. package/dist/mcp/tools/deactivate-behavior.js +119 -44
  91. package/dist/mcp/tools/list-reminders.js +97 -0
  92. package/dist/mcp/tools/list-tasks.js +56 -57
  93. package/dist/mcp/tools/send-message.js +206 -143
  94. package/dist/mcp/tools/update-task.js +259 -85
  95. package/dist/runtime/index.js +495 -316
  96. package/dist/tui/App.js +1128 -919
  97. package/package.json +2 -10
  98. package/src/commands/exe/afk.md +8 -8
  99. package/src/commands/exe/assign.md +1 -1
  100. package/src/commands/exe/build-adv.md +1 -1
  101. package/src/commands/exe/call.md +10 -10
  102. package/src/commands/exe/employee-heartbeat.md +9 -6
  103. package/src/commands/exe/heartbeat.md +5 -5
  104. package/src/commands/exe/intercom.md +26 -15
  105. package/src/commands/exe/launch.md +2 -2
  106. package/src/commands/exe/new-employee.md +1 -1
  107. package/src/commands/exe/review.md +2 -2
  108. package/src/commands/exe/schedule.md +1 -1
  109. package/src/commands/exe/sessions.md +2 -2
  110. package/src/commands/exe.md +22 -20
@@ -39,7 +39,6 @@ var config_exports = {};
39
39
  __export(config_exports, {
40
40
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
41
41
  CONFIG_PATH: () => CONFIG_PATH,
42
- COO_AGENT_NAME: () => COO_AGENT_NAME,
43
42
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
44
43
  DB_PATH: () => DB_PATH,
45
44
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -195,7 +194,7 @@ async function loadConfigFrom(configPath) {
195
194
  return { ...DEFAULT_CONFIG };
196
195
  }
197
196
  }
198
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
197
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
199
198
  var init_config = __esm({
200
199
  "src/lib/config.ts"() {
201
200
  "use strict";
@@ -203,7 +202,6 @@ var init_config = __esm({
203
202
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
204
203
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
205
204
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
206
- COO_AGENT_NAME = "exe";
207
205
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
208
206
  CURRENT_CONFIG_VERSION = 1;
209
207
  DEFAULT_CONFIG = {
@@ -239,13 +237,7 @@ var init_config = __esm({
239
237
  wikiUrl: "",
240
238
  wikiApiKey: "",
241
239
  wikiSyncIntervalMs: 30 * 60 * 1e3,
242
- wikiWorkspaceMapping: {
243
- exe: "Executive",
244
- yoshi: "Engineering",
245
- mari: "Marketing",
246
- tom: "Engineering",
247
- sasha: "Production"
248
- },
240
+ wikiWorkspaceMapping: {},
249
241
  wikiAutoUpdate: true,
250
242
  wikiAutoUpdateThreshold: 0.5,
251
243
  wikiAutoUpdateCreateNew: true,
@@ -370,6 +362,10 @@ function spawnDaemon() {
370
362
  stdio: ["ignore", "ignore", stderrFd],
371
363
  env: {
372
364
  ...process.env,
365
+ TMUX: void 0,
366
+ // Daemon is global — must not inherit session scope
367
+ TMUX_PANE: void 0,
368
+ // Prevents resolveExeSession() from scoping to one session
373
369
  EXE_DAEMON_SOCK: SOCKET_PATH,
374
370
  EXE_DAEMON_PID: PID_PATH
375
371
  }
@@ -730,7 +726,7 @@ function wrapWithRetry(client) {
730
726
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
731
727
  }
732
728
  if (prop === "batch") {
733
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
729
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
734
730
  }
735
731
  return Reflect.get(target, prop, receiver);
736
732
  }
@@ -746,6 +742,204 @@ var init_db_retry = __esm({
746
742
  }
747
743
  });
748
744
 
745
+ // src/lib/employees.ts
746
+ var employees_exports = {};
747
+ __export(employees_exports, {
748
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
749
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
750
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
751
+ addEmployee: () => addEmployee,
752
+ canCoordinate: () => canCoordinate,
753
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
754
+ getCoordinatorName: () => getCoordinatorName,
755
+ getEmployee: () => getEmployee,
756
+ getEmployeeByRole: () => getEmployeeByRole,
757
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
758
+ hasRole: () => hasRole,
759
+ isCoordinatorName: () => isCoordinatorName,
760
+ isCoordinatorRole: () => isCoordinatorRole,
761
+ isMultiInstance: () => isMultiInstance,
762
+ loadEmployees: () => loadEmployees,
763
+ loadEmployeesSync: () => loadEmployeesSync,
764
+ normalizeRole: () => normalizeRole,
765
+ normalizeRosterCase: () => normalizeRosterCase,
766
+ registerBinSymlinks: () => registerBinSymlinks,
767
+ saveEmployees: () => saveEmployees,
768
+ validateEmployeeName: () => validateEmployeeName
769
+ });
770
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
771
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync } from "fs";
772
+ import { execSync } from "child_process";
773
+ import path3 from "path";
774
+ import os2 from "os";
775
+ function normalizeRole(role) {
776
+ return (role ?? "").trim().toLowerCase();
777
+ }
778
+ function isCoordinatorRole(role) {
779
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
780
+ }
781
+ function getCoordinatorEmployee(employees) {
782
+ return employees.find((e) => isCoordinatorRole(e.role));
783
+ }
784
+ function getCoordinatorName(employees = loadEmployeesSync()) {
785
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
786
+ }
787
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
788
+ if (!agentName) return false;
789
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
790
+ }
791
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
792
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
793
+ }
794
+ function validateEmployeeName(name) {
795
+ if (!name) {
796
+ return { valid: false, error: "Name is required" };
797
+ }
798
+ if (name.length > 32) {
799
+ return { valid: false, error: "Name must be 32 characters or fewer" };
800
+ }
801
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
802
+ return {
803
+ valid: false,
804
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
805
+ };
806
+ }
807
+ return { valid: true };
808
+ }
809
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
810
+ if (!existsSync3(employeesPath)) {
811
+ return [];
812
+ }
813
+ const raw = await readFile2(employeesPath, "utf-8");
814
+ try {
815
+ return JSON.parse(raw);
816
+ } catch {
817
+ return [];
818
+ }
819
+ }
820
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
821
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
822
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
823
+ }
824
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
825
+ if (!existsSync3(employeesPath)) return [];
826
+ try {
827
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
828
+ } catch {
829
+ return [];
830
+ }
831
+ }
832
+ function getEmployee(employees, name) {
833
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
834
+ }
835
+ function getEmployeeByRole(employees, role) {
836
+ const lower = role.toLowerCase();
837
+ return employees.find((e) => e.role.toLowerCase() === lower);
838
+ }
839
+ function getEmployeeNamesByRole(employees, role) {
840
+ const lower = role.toLowerCase();
841
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
842
+ }
843
+ function hasRole(agentName, role) {
844
+ const employees = loadEmployeesSync();
845
+ const emp = getEmployee(employees, agentName);
846
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
847
+ }
848
+ function isMultiInstance(agentName, employees) {
849
+ const roster = employees ?? loadEmployeesSync();
850
+ const emp = getEmployee(roster, agentName);
851
+ if (!emp) return false;
852
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
853
+ }
854
+ function addEmployee(employees, employee) {
855
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
856
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
857
+ throw new Error(`Employee '${normalized.name}' already exists`);
858
+ }
859
+ return [...employees, normalized];
860
+ }
861
+ async function normalizeRosterCase(rosterPath) {
862
+ const employees = await loadEmployees(rosterPath);
863
+ let changed = false;
864
+ for (const emp of employees) {
865
+ if (emp.name !== emp.name.toLowerCase()) {
866
+ const oldName = emp.name;
867
+ emp.name = emp.name.toLowerCase();
868
+ changed = true;
869
+ try {
870
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
871
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
872
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
873
+ if (existsSync3(oldPath) && !existsSync3(newPath)) {
874
+ renameSync2(oldPath, newPath);
875
+ } else if (existsSync3(oldPath) && oldPath !== newPath) {
876
+ const content = readFileSync3(oldPath, "utf-8");
877
+ writeFileSync(newPath, content, "utf-8");
878
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
879
+ unlinkSync2(oldPath);
880
+ }
881
+ }
882
+ } catch {
883
+ }
884
+ }
885
+ }
886
+ if (changed) {
887
+ await saveEmployees(employees, rosterPath);
888
+ }
889
+ return changed;
890
+ }
891
+ function findExeBin() {
892
+ try {
893
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
894
+ } catch {
895
+ return null;
896
+ }
897
+ }
898
+ function registerBinSymlinks(name) {
899
+ const created = [];
900
+ const skipped = [];
901
+ const errors = [];
902
+ const exeBinPath = findExeBin();
903
+ if (!exeBinPath) {
904
+ errors.push("Could not find 'exe-os' in PATH");
905
+ return { created, skipped, errors };
906
+ }
907
+ const binDir = path3.dirname(exeBinPath);
908
+ let target;
909
+ try {
910
+ target = readlinkSync(exeBinPath);
911
+ } catch {
912
+ errors.push("Could not read 'exe' symlink");
913
+ return { created, skipped, errors };
914
+ }
915
+ for (const suffix of ["", "-opencode"]) {
916
+ const linkName = `${name}${suffix}`;
917
+ const linkPath = path3.join(binDir, linkName);
918
+ if (existsSync3(linkPath)) {
919
+ skipped.push(linkName);
920
+ continue;
921
+ }
922
+ try {
923
+ symlinkSync(target, linkPath);
924
+ created.push(linkName);
925
+ } catch (err) {
926
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
927
+ }
928
+ }
929
+ return { created, skipped, errors };
930
+ }
931
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
932
+ var init_employees = __esm({
933
+ "src/lib/employees.ts"() {
934
+ "use strict";
935
+ init_config();
936
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
937
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
938
+ COORDINATOR_ROLE = "COO";
939
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
940
+ }
941
+ });
942
+
749
943
  // src/lib/database.ts
750
944
  var database_exports = {};
751
945
  __export(database_exports, {
@@ -893,22 +1087,24 @@ async function ensureSchema() {
893
1087
  ON behaviors(agent_id, active);
894
1088
  `);
895
1089
  try {
1090
+ const coordinatorName = getCoordinatorName();
896
1091
  const existing = await client.execute({
897
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
898
- args: []
1092
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1093
+ args: [coordinatorName]
899
1094
  });
900
1095
  if (Number(existing.rows[0]?.cnt) === 0) {
901
- await client.executeMultiple(`
902
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
903
- VALUES
904
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
905
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
906
- VALUES
907
- (hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
908
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
909
- VALUES
910
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
911
- `);
1096
+ const seededAt = "2026-03-25T00:00:00Z";
1097
+ for (const [domain, content] of [
1098
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1099
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1100
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1101
+ ]) {
1102
+ await client.execute({
1103
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1104
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1105
+ args: [coordinatorName, domain, content, seededAt, seededAt]
1106
+ });
1107
+ }
912
1108
  }
913
1109
  } catch {
914
1110
  }
@@ -1600,6 +1796,39 @@ async function ensureSchema() {
1600
1796
  } catch {
1601
1797
  }
1602
1798
  }
1799
+ try {
1800
+ await client.execute({
1801
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1802
+ args: []
1803
+ });
1804
+ } catch {
1805
+ }
1806
+ try {
1807
+ await client.execute(
1808
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1809
+ );
1810
+ } catch {
1811
+ }
1812
+ try {
1813
+ await client.execute({
1814
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1815
+ args: []
1816
+ });
1817
+ } catch {
1818
+ }
1819
+ try {
1820
+ await client.execute(
1821
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1822
+ );
1823
+ } catch {
1824
+ }
1825
+ try {
1826
+ await client.execute({
1827
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1828
+ args: []
1829
+ });
1830
+ } catch {
1831
+ }
1603
1832
  }
1604
1833
  async function disposeDatabase() {
1605
1834
  if (_client) {
@@ -1613,6 +1842,7 @@ var init_database = __esm({
1613
1842
  "src/lib/database.ts"() {
1614
1843
  "use strict";
1615
1844
  init_db_retry();
1845
+ init_employees();
1616
1846
  _client = null;
1617
1847
  _resilientClient = null;
1618
1848
  initTurso = initDatabase;
@@ -1621,15 +1851,15 @@ var init_database = __esm({
1621
1851
  });
1622
1852
 
1623
1853
  // src/lib/keychain.ts
1624
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
1625
- import { existsSync as existsSync3 } from "fs";
1626
- import path3 from "path";
1627
- import os2 from "os";
1854
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1855
+ import { existsSync as existsSync4 } from "fs";
1856
+ import path4 from "path";
1857
+ import os3 from "os";
1628
1858
  function getKeyDir() {
1629
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os2.homedir(), ".exe-os");
1859
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os3.homedir(), ".exe-os");
1630
1860
  }
1631
1861
  function getKeyPath() {
1632
- return path3.join(getKeyDir(), "master.key");
1862
+ return path4.join(getKeyDir(), "master.key");
1633
1863
  }
1634
1864
  async function tryKeytar() {
1635
1865
  try {
@@ -1650,11 +1880,11 @@ async function getMasterKey() {
1650
1880
  }
1651
1881
  }
1652
1882
  const keyPath = getKeyPath();
1653
- if (!existsSync3(keyPath)) {
1883
+ if (!existsSync4(keyPath)) {
1654
1884
  return null;
1655
1885
  }
1656
1886
  try {
1657
- const content = await readFile2(keyPath, "utf-8");
1887
+ const content = await readFile3(keyPath, "utf-8");
1658
1888
  return Buffer.from(content.trim(), "base64");
1659
1889
  } catch {
1660
1890
  return null;
@@ -1737,12 +1967,12 @@ __export(shard_manager_exports, {
1737
1967
  listShards: () => listShards,
1738
1968
  shardExists: () => shardExists
1739
1969
  });
1740
- import path4 from "path";
1741
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1970
+ import path5 from "path";
1971
+ import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
1742
1972
  import { createClient as createClient2 } from "@libsql/client";
1743
1973
  function initShardManager(encryptionKey) {
1744
1974
  _encryptionKey = encryptionKey;
1745
- if (!existsSync4(SHARDS_DIR)) {
1975
+ if (!existsSync5(SHARDS_DIR)) {
1746
1976
  mkdirSync(SHARDS_DIR, { recursive: true });
1747
1977
  }
1748
1978
  _shardingEnabled = true;
@@ -1763,7 +1993,7 @@ function getShardClient(projectName) {
1763
1993
  }
1764
1994
  const cached = _shards.get(safeName);
1765
1995
  if (cached) return cached;
1766
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
1996
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1767
1997
  const client = createClient2({
1768
1998
  url: `file:${dbPath}`,
1769
1999
  encryptionKey: _encryptionKey
@@ -1773,10 +2003,10 @@ function getShardClient(projectName) {
1773
2003
  }
1774
2004
  function shardExists(projectName) {
1775
2005
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1776
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2006
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1777
2007
  }
1778
2008
  function listShards() {
1779
- if (!existsSync4(SHARDS_DIR)) return [];
2009
+ if (!existsSync5(SHARDS_DIR)) return [];
1780
2010
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1781
2011
  }
1782
2012
  async function ensureShardSchema(client) {
@@ -1846,7 +2076,11 @@ async function ensureShardSchema(client) {
1846
2076
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
1847
2077
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1848
2078
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1849
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
2079
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
2080
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2081
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2082
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2083
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
1850
2084
  ]) {
1851
2085
  try {
1852
2086
  await client.execute(col);
@@ -1958,7 +2192,7 @@ var init_shard_manager = __esm({
1958
2192
  "src/lib/shard-manager.ts"() {
1959
2193
  "use strict";
1960
2194
  init_config();
1961
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2195
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
1962
2196
  _shards = /* @__PURE__ */ new Map();
1963
2197
  _encryptionKey = null;
1964
2198
  _shardingEnabled = false;
@@ -1976,26 +2210,26 @@ var init_platform_procedures = __esm({
1976
2210
  title: "What is exe-os \u2014 the operating model every agent must understand",
1977
2211
  domain: "architecture",
1978
2212
  priority: "p0",
1979
- 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."
2213
+ content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. 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."
1980
2214
  },
1981
2215
  {
1982
2216
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
1983
2217
  domain: "architecture",
1984
2218
  priority: "p0",
1985
- 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."
2219
+ content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
1986
2220
  },
1987
2221
  {
1988
- title: "Sessions explained \u2014 what exeN means and how projects work",
2222
+ title: "Sessions explained \u2014 coordinator session names and projects",
1989
2223
  domain: "architecture",
1990
2224
  priority: "p0",
1991
- 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."
2225
+ content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. 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."
1992
2226
  },
1993
2227
  // --- Hierarchy and dispatch ---
1994
2228
  {
1995
2229
  title: "Chain of command \u2014 who talks to whom",
1996
2230
  domain: "workflow",
1997
2231
  priority: "p0",
1998
- 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."
2232
+ content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. 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."
1999
2233
  },
2000
2234
  {
2001
2235
  title: "Single dispatch path \u2014 create_task only",
@@ -2005,30 +2239,30 @@ var init_platform_procedures = __esm({
2005
2239
  },
2006
2240
  // --- Session isolation ---
2007
2241
  {
2008
- title: "Session scoping \u2014 stay in your exe boundary",
2242
+ title: "Session scoping \u2014 stay in your coordinator boundary",
2009
2243
  domain: "security",
2010
2244
  priority: "p0",
2011
- 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."
2245
+ content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
2012
2246
  },
2013
2247
  {
2014
2248
  title: "Session isolation \u2014 never touch another session's work",
2015
2249
  domain: "workflow",
2016
2250
  priority: "p0",
2017
- 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.`
2251
+ content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
2018
2252
  },
2019
2253
  // --- Engineering: session scoping in code ---
2020
2254
  {
2021
2255
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
2022
2256
  domain: "architecture",
2023
2257
  priority: "p0",
2024
- 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."
2258
+ content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (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+ coordinator sessions simultaneously."
2025
2259
  },
2026
2260
  // --- Hard constraints ---
2027
2261
  {
2028
2262
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
2029
2263
  domain: "security",
2030
2264
  priority: "p0",
2031
- 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."
2265
+ 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 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
2032
2266
  },
2033
2267
  // --- Operations ---
2034
2268
  {
@@ -2268,7 +2502,10 @@ async function writeMemory(record) {
2268
2502
  source_path: record.source_path ?? null,
2269
2503
  source_type: record.source_type ?? null,
2270
2504
  tier: record.tier ?? classifyTier(record),
2271
- supersedes_id: record.supersedes_id ?? null
2505
+ supersedes_id: record.supersedes_id ?? null,
2506
+ draft: record.draft ? 1 : 0,
2507
+ memory_type: record.memory_type ?? "raw",
2508
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
2272
2509
  };
2273
2510
  _pendingRecords.push(dbRow);
2274
2511
  orgBus.emit({
@@ -2323,6 +2560,9 @@ async function flushBatch() {
2323
2560
  const sourceType = row.source_type ?? null;
2324
2561
  const tier = row.tier ?? 3;
2325
2562
  const supersedesId = row.supersedes_id ?? null;
2563
+ const draft = row.draft ? 1 : 0;
2564
+ const memoryType = row.memory_type ?? "raw";
2565
+ const trajectory = row.trajectory ?? null;
2326
2566
  return {
2327
2567
  sql: hasVector ? `INSERT OR IGNORE INTO memories
2328
2568
  (id, agent_id, agent_role, session_id, timestamp,
@@ -2330,15 +2570,15 @@ async function flushBatch() {
2330
2570
  has_error, raw_text, vector, version, task_id, importance, status,
2331
2571
  confidence, last_accessed,
2332
2572
  workspace_id, document_id, user_id, char_offset, page_number,
2333
- source_path, source_type, tier, supersedes_id)
2334
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
2573
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2574
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
2335
2575
  (id, agent_id, agent_role, session_id, timestamp,
2336
2576
  tool_name, project_name,
2337
2577
  has_error, raw_text, vector, version, task_id, importance, status,
2338
2578
  confidence, last_accessed,
2339
2579
  workspace_id, document_id, user_id, char_offset, page_number,
2340
- source_path, source_type, tier, supersedes_id)
2341
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2580
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2581
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2342
2582
  args: hasVector ? [
2343
2583
  row.id,
2344
2584
  row.agent_id,
@@ -2364,7 +2604,10 @@ async function flushBatch() {
2364
2604
  sourcePath,
2365
2605
  sourceType,
2366
2606
  tier,
2367
- supersedesId
2607
+ supersedesId,
2608
+ draft,
2609
+ memoryType,
2610
+ trajectory
2368
2611
  ] : [
2369
2612
  row.id,
2370
2613
  row.agent_id,
@@ -2389,7 +2632,10 @@ async function flushBatch() {
2389
2632
  sourcePath,
2390
2633
  sourceType,
2391
2634
  tier,
2392
- supersedesId
2635
+ supersedesId,
2636
+ draft,
2637
+ memoryType,
2638
+ trajectory
2393
2639
  ]
2394
2640
  };
2395
2641
  };
@@ -2458,6 +2704,8 @@ async function searchMemories(queryVector, agentId, options) {
2458
2704
  const limit = options?.limit ?? 10;
2459
2705
  const statusFilter = options?.includeArchived ? "" : `
2460
2706
  AND COALESCE(status, 'active') = 'active'`;
2707
+ const draftFilter = options?.includeDrafts ? "" : `
2708
+ AND (draft = 0 OR draft IS NULL)`;
2461
2709
  let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
2462
2710
  tool_name, project_name,
2463
2711
  has_error, raw_text, vector, importance, status,
@@ -2467,7 +2715,7 @@ async function searchMemories(queryVector, agentId, options) {
2467
2715
  source_path, source_type
2468
2716
  FROM memories
2469
2717
  WHERE agent_id = ?
2470
- AND vector IS NOT NULL${statusFilter}
2718
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
2471
2719
  AND COALESCE(confidence, 0.7) >= 0.3`;
2472
2720
  const args = [agentId];
2473
2721
  const scope = buildWikiScopeFilter(options, "");
@@ -2489,6 +2737,10 @@ async function searchMemories(queryVector, agentId, options) {
2489
2737
  sql += ` AND timestamp >= ?`;
2490
2738
  args.push(options.since);
2491
2739
  }
2740
+ if (options?.memoryType) {
2741
+ sql += ` AND memory_type = ?`;
2742
+ args.push(options.memoryType);
2743
+ }
2492
2744
  sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
2493
2745
  args.push(vectorToBlob(queryVector));
2494
2746
  sql += ` LIMIT ?`;
@@ -2714,7 +2966,7 @@ var init_self_query_router = __esm({
2714
2966
  },
2715
2967
  is_broad_query: {
2716
2968
  type: "boolean",
2717
- description: "True if the query is exploratory/broad (e.g., 'what has yoshi been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
2969
+ description: "True if the query is exploratory/broad (e.g., 'what has the CTO been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
2718
2970
  }
2719
2971
  },
2720
2972
  required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
@@ -2729,34 +2981,34 @@ __export(project_name_exports, {
2729
2981
  _resetCache: () => _resetCache,
2730
2982
  getProjectName: () => getProjectName
2731
2983
  });
2732
- import { execSync } from "child_process";
2733
- import path5 from "path";
2984
+ import { execSync as execSync2 } from "child_process";
2985
+ import path6 from "path";
2734
2986
  function getProjectName(cwd) {
2735
2987
  const dir = cwd ?? process.cwd();
2736
2988
  if (_cached && _cachedCwd === dir) return _cached;
2737
2989
  try {
2738
2990
  let repoRoot;
2739
2991
  try {
2740
- const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
2992
+ const gitCommonDir = execSync2("git rev-parse --path-format=absolute --git-common-dir", {
2741
2993
  cwd: dir,
2742
2994
  encoding: "utf8",
2743
2995
  timeout: 2e3,
2744
2996
  stdio: ["pipe", "pipe", "pipe"]
2745
2997
  }).trim();
2746
- repoRoot = path5.dirname(gitCommonDir);
2998
+ repoRoot = path6.dirname(gitCommonDir);
2747
2999
  } catch {
2748
- repoRoot = execSync("git rev-parse --show-toplevel", {
3000
+ repoRoot = execSync2("git rev-parse --show-toplevel", {
2749
3001
  cwd: dir,
2750
3002
  encoding: "utf8",
2751
3003
  timeout: 2e3,
2752
3004
  stdio: ["pipe", "pipe", "pipe"]
2753
3005
  }).trim();
2754
3006
  }
2755
- _cached = path5.basename(repoRoot);
3007
+ _cached = path6.basename(repoRoot);
2756
3008
  _cachedCwd = dir;
2757
3009
  return _cached;
2758
3010
  } catch {
2759
- _cached = path5.basename(dir);
3011
+ _cached = path6.basename(dir);
2760
3012
  _cachedCwd = dir;
2761
3013
  return _cached;
2762
3014
  }
@@ -2779,14 +3031,14 @@ var file_grep_exports = {};
2779
3031
  __export(file_grep_exports, {
2780
3032
  grepProjectFiles: () => grepProjectFiles
2781
3033
  });
2782
- import { execSync as execSync2 } from "child_process";
2783
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync5 } from "fs";
2784
- import path6 from "path";
3034
+ import { execSync as execSync3 } from "child_process";
3035
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync6 } from "fs";
3036
+ import path7 from "path";
2785
3037
  import crypto from "crypto";
2786
3038
  function hasRipgrep() {
2787
3039
  if (_hasRg === null) {
2788
3040
  try {
2789
- execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
3041
+ execSync3("rg --version", { stdio: "ignore", timeout: 2e3 });
2790
3042
  _hasRg = true;
2791
3043
  } catch {
2792
3044
  _hasRg = false;
@@ -2821,7 +3073,7 @@ async function grepProjectFiles(query, projectRoot, options) {
2821
3073
  session_id: "file-grep",
2822
3074
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2823
3075
  tool_name: "file_grep",
2824
- project_name: path6.basename(projectRoot),
3076
+ project_name: path7.basename(projectRoot),
2825
3077
  has_error: false,
2826
3078
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
2827
3079
  vector: null,
@@ -2833,7 +3085,7 @@ function getChunkContext(filePath, lineNumber) {
2833
3085
  try {
2834
3086
  const ext = filePath.split(".").pop()?.toLowerCase();
2835
3087
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
2836
- const source = readFileSync3(filePath, "utf8");
3088
+ const source = readFileSync4(filePath, "utf8");
2837
3089
  const lines = source.split("\n");
2838
3090
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
2839
3091
  const line = lines[i];
@@ -2852,7 +3104,7 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
2852
3104
  const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
2853
3105
  const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
2854
3106
  const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
2855
- const output = execSync2(cmd, {
3107
+ const output = execSync3(cmd, {
2856
3108
  cwd: projectRoot,
2857
3109
  encoding: "utf8",
2858
3110
  timeout: 3e3,
@@ -2867,12 +3119,12 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
2867
3119
  const matchCount = parseInt(line.slice(colonIdx + 1));
2868
3120
  if (isNaN(matchCount) || matchCount === 0) continue;
2869
3121
  try {
2870
- const firstMatch = execSync2(
3122
+ const firstMatch = execSync3(
2871
3123
  `rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
2872
3124
  { cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
2873
3125
  ).trim();
2874
3126
  const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
2875
- const totalLines = execSync2(`wc -l < '${filePath}'`, {
3127
+ const totalLines = execSync3(`wc -l < '${filePath}'`, {
2876
3128
  cwd: projectRoot,
2877
3129
  encoding: "utf8",
2878
3130
  timeout: 1e3
@@ -2895,11 +3147,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
2895
3147
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
2896
3148
  const hits = [];
2897
3149
  for (const filePath of files.slice(0, MAX_FILES)) {
2898
- const absPath = path6.join(projectRoot, filePath);
3150
+ const absPath = path7.join(projectRoot, filePath);
2899
3151
  try {
2900
3152
  const stat = statSync2(absPath);
2901
3153
  if (stat.size > MAX_FILE_SIZE) continue;
2902
- const content = readFileSync3(absPath, "utf8");
3154
+ const content = readFileSync4(absPath, "utf8");
2903
3155
  const lines = content.split("\n");
2904
3156
  const matches = content.match(regex);
2905
3157
  if (!matches || matches.length === 0) continue;
@@ -2922,15 +3174,15 @@ function collectFiles(root, patterns) {
2922
3174
  const files = [];
2923
3175
  function walk(dir, relative) {
2924
3176
  if (files.length >= MAX_FILES) return;
2925
- const basename = path6.basename(dir);
3177
+ const basename = path7.basename(dir);
2926
3178
  if (EXCLUDE_DIRS.includes(basename)) return;
2927
3179
  try {
2928
3180
  const entries = readdirSync2(dir, { withFileTypes: true });
2929
3181
  for (const entry of entries) {
2930
3182
  if (files.length >= MAX_FILES) return;
2931
- const rel = path6.join(relative, entry.name);
3183
+ const rel = path7.join(relative, entry.name);
2932
3184
  if (entry.isDirectory()) {
2933
- walk(path6.join(dir, entry.name), rel);
3185
+ walk(path7.join(dir, entry.name), rel);
2934
3186
  } else if (entry.isFile()) {
2935
3187
  for (const pat of patterns) {
2936
3188
  if (matchGlob(rel, pat)) {
@@ -2962,7 +3214,7 @@ function matchGlob(filePath, pattern) {
2962
3214
  if (slashIdx !== -1) {
2963
3215
  const dir = pattern.slice(0, slashIdx);
2964
3216
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
2965
- const fileDir = path6.dirname(filePath);
3217
+ const fileDir = path7.dirname(filePath);
2966
3218
  return fileDir === dir && filePath.endsWith(ext2);
2967
3219
  }
2968
3220
  const ext = pattern.replace("*", "");
@@ -2970,9 +3222,9 @@ function matchGlob(filePath, pattern) {
2970
3222
  }
2971
3223
  function buildSnippet(hit, projectRoot) {
2972
3224
  try {
2973
- const absPath = path6.join(projectRoot, hit.filePath);
2974
- if (!existsSync5(absPath)) return hit.matchLine;
2975
- const lines = readFileSync3(absPath, "utf8").split("\n");
3225
+ const absPath = path7.join(projectRoot, hit.filePath);
3226
+ if (!existsSync6(absPath)) return hit.matchLine;
3227
+ const lines = readFileSync4(absPath, "utf8").split("\n");
2976
3228
  const start = Math.max(0, hit.lineNumber - 3);
2977
3229
  const end = Math.min(lines.length, hit.lineNumber + 2);
2978
3230
  return lines.slice(start, end).join("\n").slice(0, 500);
@@ -3006,8 +3258,8 @@ __export(reranker_exports, {
3006
3258
  rerank: () => rerank,
3007
3259
  rerankWithScores: () => rerankWithScores
3008
3260
  });
3009
- import path7 from "path";
3010
- import { existsSync as existsSync6 } from "fs";
3261
+ import path8 from "path";
3262
+ import { existsSync as existsSync7 } from "fs";
3011
3263
  function resetIdleTimer() {
3012
3264
  if (_idleTimer) clearTimeout(_idleTimer);
3013
3265
  _idleTimer = setTimeout(() => {
@@ -3018,18 +3270,18 @@ function resetIdleTimer() {
3018
3270
  }
3019
3271
  }
3020
3272
  function isRerankerAvailable() {
3021
- return existsSync6(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
3273
+ return existsSync7(path8.join(MODELS_DIR, RERANKER_MODEL_FILE));
3022
3274
  }
3023
3275
  function getRerankerModelPath() {
3024
- return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3276
+ return path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3025
3277
  }
3026
3278
  async function ensureLoaded() {
3027
3279
  if (_rerankerContext) {
3028
3280
  resetIdleTimer();
3029
3281
  return;
3030
3282
  }
3031
- const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3032
- if (!existsSync6(modelPath)) {
3283
+ const modelPath = path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3284
+ if (!existsSync7(modelPath)) {
3033
3285
  throw new Error(
3034
3286
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3035
3287
  );
@@ -3105,541 +3357,881 @@ var init_reranker = __esm({
3105
3357
  }
3106
3358
  });
3107
3359
 
3108
- // src/lib/session-key.ts
3109
- import { execSync as execSync3 } from "child_process";
3110
- function getSessionKey() {
3111
- if (_cached2) return _cached2;
3112
- let pid = process.ppid;
3113
- for (let i = 0; i < 10; i++) {
3360
+ // src/lib/hybrid-search.ts
3361
+ var hybrid_search_exports = {};
3362
+ __export(hybrid_search_exports, {
3363
+ estimateCardinality: () => estimateCardinality,
3364
+ hybridSearch: () => hybridSearch,
3365
+ lightweightSearch: () => lightweightSearch,
3366
+ rrfMerge: () => rrfMerge,
3367
+ rrfMergeMulti: () => rrfMergeMulti
3368
+ });
3369
+ async function hybridSearch(queryText, agentId, options) {
3370
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3371
+ const config2 = await loadConfig2();
3372
+ if (config2.searchMode === "fts") {
3373
+ return lightweightSearch(queryText, agentId, options);
3374
+ }
3375
+ const limit = options?.limit ?? 10;
3376
+ const trajectoryResults = await trajectoryBypass(queryText, agentId, options, limit);
3377
+ if (trajectoryResults !== null) return trajectoryResults;
3378
+ let effectiveQuery = queryText;
3379
+ let effectiveOptions = { ...options };
3380
+ let _isBroadQuery = false;
3381
+ if (config2.selfQueryRouter && process.env.ANTHROPIC_API_KEY) {
3114
3382
  try {
3115
- const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
3116
- encoding: "utf8",
3117
- timeout: 2e3
3118
- }).trim();
3119
- const match = info.match(/^\s*(\d+)\s+(.+)$/);
3120
- if (!match) break;
3121
- const [, ppid, cmd] = match;
3122
- if (cmd === "claude" || cmd.endsWith("/claude")) {
3123
- _cached2 = String(pid);
3124
- return _cached2;
3383
+ const { routeQuery: routeQuery2 } = await Promise.resolve().then(() => (init_self_query_router(), self_query_router_exports));
3384
+ const routed = await routeQuery2(queryText, config2.selfQueryModel);
3385
+ effectiveQuery = routed.semanticQuery;
3386
+ _isBroadQuery = routed.isBroadQuery;
3387
+ if (routed.projectFilter && !effectiveOptions.projectName) {
3388
+ effectiveOptions.projectName = routed.projectFilter;
3389
+ }
3390
+ if (routed.timeFilter && !effectiveOptions.since) {
3391
+ effectiveOptions.since = routed.timeFilter;
3125
3392
  }
3126
- pid = parseInt(ppid, 10);
3127
- if (pid <= 1) break;
3128
3393
  } catch {
3129
- break;
3130
3394
  }
3131
3395
  }
3132
- _cached2 = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
3133
- return _cached2;
3134
- }
3135
- var _cached2;
3136
- var init_session_key = __esm({
3137
- "src/lib/session-key.ts"() {
3138
- "use strict";
3139
- _cached2 = null;
3140
- }
3141
- });
3142
-
3143
- // src/adapters/claude/session-key.ts
3144
- var init_session_key2 = __esm({
3145
- "src/adapters/claude/session-key.ts"() {
3146
- "use strict";
3147
- init_session_key();
3148
- }
3149
- });
3150
-
3151
- // src/adapters/claude/active-agent.ts
3152
- var active_agent_exports = {};
3153
- __export(active_agent_exports, {
3154
- cleanupSessionMarkers: () => cleanupSessionMarkers,
3155
- clearActiveAgent: () => clearActiveAgent,
3156
- getActiveAgent: () => getActiveAgent,
3157
- getAllActiveAgents: () => getAllActiveAgents,
3158
- writeActiveAgent: () => writeActiveAgent
3159
- });
3160
- import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync3 } from "fs";
3161
- import { execSync as execSync4 } from "child_process";
3162
- import path8 from "path";
3163
- function getMarkerPath() {
3164
- return path8.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
3165
- }
3166
- function writeActiveAgent(agentId, agentRole) {
3167
- try {
3168
- mkdirSync2(CACHE_DIR, { recursive: true });
3169
- writeFileSync(
3170
- getMarkerPath(),
3171
- JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
3396
+ const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
3397
+ const cardinality = await getMemoryCardinality2(agentId);
3398
+ const { rerankerAutoTrigger } = config2.scalingRoadmap ?? {};
3399
+ const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
3400
+ const useNarrowPath = cardinality < 1e4;
3401
+ const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
3402
+ const effectiveIsBroad = useBroadPath && !useNarrowPath;
3403
+ if (effectiveIsBroad !== _isBroadQuery) {
3404
+ process.stderr.write(
3405
+ `[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
3406
+ `
3172
3407
  );
3173
- } catch {
3174
3408
  }
3175
- }
3176
- function clearActiveAgent() {
3409
+ const broadFetchTopK = config2.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
3410
+ const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
3411
+ const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
3412
+ let queryVector = null;
3177
3413
  try {
3178
- unlinkSync2(getMarkerPath());
3414
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
3415
+ queryVector = await embed2(effectiveQuery);
3179
3416
  } catch {
3417
+ process.stderr.write("[hybrid-search] Embed daemon unavailable \u2014 FTS-only mode\n");
3180
3418
  }
3181
- }
3182
- function getActiveAgent() {
3183
- try {
3184
- const markerPath = getMarkerPath();
3185
- const raw = readFileSync4(markerPath, "utf8");
3186
- const data = JSON.parse(raw);
3187
- if (data.agentId) {
3188
- if (data.startedAt) {
3189
- const age = Date.now() - new Date(data.startedAt).getTime();
3190
- if (age > STALE_MS) {
3191
- try {
3192
- unlinkSync2(markerPath);
3193
- } catch {
3194
- }
3195
- } else {
3196
- return {
3197
- agentId: data.agentId,
3198
- agentRole: data.agentRole || "employee"
3199
- };
3200
- }
3201
- } else {
3202
- return {
3203
- agentId: data.agentId,
3204
- agentRole: data.agentRole || "employee"
3205
- };
3419
+ let grepPromise = Promise.resolve([]);
3420
+ if (config2.fileGrepEnabled !== false) {
3421
+ try {
3422
+ const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
3423
+ const projectRoot = process.cwd();
3424
+ const projectName = getProjectName2(projectRoot);
3425
+ if (projectName && projectName !== "tmp") {
3426
+ const { grepProjectFiles: grepProjectFiles2 } = await Promise.resolve().then(() => (init_file_grep(), file_grep_exports));
3427
+ grepPromise = grepProjectFiles2(effectiveQuery, projectRoot, {
3428
+ maxResults: 10
3429
+ }).catch(() => []);
3206
3430
  }
3431
+ } catch {
3207
3432
  }
3208
- } catch {
3209
3433
  }
3210
- try {
3211
- const sessionName = execSync4(
3212
- "tmux display-message -p '#{session_name}' 2>/dev/null",
3213
- { encoding: "utf8", timeout: 2e3 }
3214
- ).trim();
3215
- const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
3216
- if (empMatch && empMatch[1] !== "exe") {
3217
- return { agentId: empMatch[1], agentRole: "employee" };
3218
- }
3219
- if (/^exe\d+$/.test(sessionName)) {
3220
- return { agentId: "exe", agentRole: "COO" };
3221
- }
3222
- } catch {
3434
+ const [ftsResults, vectorResults, grepResults] = await Promise.all([
3435
+ lightweightSearch(effectiveQuery, agentId, fetchOptions),
3436
+ queryVector ? searchMemories(queryVector, agentId, fetchOptions) : Promise.resolve([]),
3437
+ grepPromise
3438
+ ]);
3439
+ const lists = [];
3440
+ const weights = [];
3441
+ if (ftsResults.length > 0) {
3442
+ lists.push(ftsResults);
3443
+ weights.push(1);
3223
3444
  }
3224
- return {
3225
- agentId: process.env.AGENT_ID || "default",
3226
- agentRole: process.env.AGENT_ROLE || "employee"
3445
+ if (vectorResults.length > 0) {
3446
+ lists.push(vectorResults);
3447
+ weights.push(1);
3448
+ }
3449
+ if (grepResults.length > 0) {
3450
+ lists.push(grepResults);
3451
+ weights.push(0.5);
3452
+ }
3453
+ if (lists.length === 0) return [];
3454
+ if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
3455
+ const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
3456
+ const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
3457
+ const auto = config2.scalingRoadmap?.rerankerAutoTrigger ?? {
3458
+ enabled: config2.rerankerEnabled ?? true,
3459
+ broadQueryMinCardinality: 5e4,
3460
+ fetchTopK: 150,
3461
+ returnTopK: 5
3227
3462
  };
3228
- }
3229
- function getAllActiveAgents() {
3230
- try {
3231
- const files = readdirSync3(CACHE_DIR);
3232
- const sessions = [];
3233
- for (const file of files) {
3234
- if (!file.startsWith("active-agent-") || !file.endsWith(".json")) continue;
3235
- const key = file.slice("active-agent-".length, -".json".length);
3236
- if (key === "undefined") continue;
3463
+ let rerankedAndBlended = null;
3464
+ if (effectiveIsBroad && auto.enabled) {
3465
+ const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
3466
+ if (cardinality2 > auto.broadQueryMinCardinality) {
3237
3467
  try {
3238
- const raw = readFileSync4(path8.join(CACHE_DIR, file), "utf8");
3239
- const data = JSON.parse(raw);
3240
- if (!data.agentId) continue;
3241
- if (data.startedAt) {
3242
- const age = Date.now() - new Date(data.startedAt).getTime();
3243
- if (age > STALE_MS) {
3244
- try {
3245
- unlinkSync2(path8.join(CACHE_DIR, file));
3246
- } catch {
3247
- }
3248
- continue;
3468
+ const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
3469
+ if (isRerankerAvailable2()) {
3470
+ const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
3471
+ if (reranked.length > 0) {
3472
+ rerankedAndBlended = rrfMergeMulti(
3473
+ [reranked],
3474
+ auto.returnTopK,
3475
+ RRF_K
3476
+ );
3249
3477
  }
3250
3478
  }
3251
- sessions.push({
3252
- agentId: data.agentId,
3253
- agentRole: data.agentRole || "employee",
3254
- startedAt: data.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
3255
- sessionKey: key
3256
- });
3257
3479
  } catch {
3258
3480
  }
3259
3481
  }
3260
- return sessions;
3261
- } catch {
3262
- return [];
3263
3482
  }
3264
- }
3265
- function cleanupSessionMarkers() {
3266
- const key = getSessionKey();
3267
- try {
3268
- unlinkSync2(path8.join(CACHE_DIR, `active-agent-${key}.json`));
3269
- } catch {
3483
+ const finalResults = (rerankedAndBlended ?? merged).slice(
3484
+ 0,
3485
+ rerankedAndBlended ? auto.returnTopK : limit
3486
+ );
3487
+ if (options?.includeSource && finalResults.length > 0) {
3488
+ await attachDocumentMetadata(finalResults);
3270
3489
  }
3271
- try {
3272
- unlinkSync2(path8.join(CACHE_DIR, "active-agent-undefined.json"));
3273
- } catch {
3490
+ if (finalResults.length > 0) {
3491
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3492
+ const ids = finalResults.map((r) => r.id);
3493
+ const placeholders = ids.map(() => "?").join(",");
3494
+ try {
3495
+ const client = getClient();
3496
+ void client.execute({
3497
+ sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
3498
+ args: [now, ...ids]
3499
+ }).catch(() => {
3500
+ });
3501
+ } catch {
3502
+ }
3274
3503
  }
3504
+ return finalResults;
3275
3505
  }
3276
- var CACHE_DIR, STALE_MS;
3277
- var init_active_agent = __esm({
3278
- "src/adapters/claude/active-agent.ts"() {
3279
- "use strict";
3280
- init_config();
3281
- init_session_key2();
3282
- CACHE_DIR = path8.join(EXE_AI_DIR, "session-cache");
3283
- STALE_MS = 24 * 60 * 60 * 1e3;
3284
- }
3285
- });
3286
-
3287
- // src/lib/employees.ts
3288
- var employees_exports = {};
3289
- __export(employees_exports, {
3290
- EMPLOYEES_PATH: () => EMPLOYEES_PATH,
3291
- addEmployee: () => addEmployee,
3292
- getEmployee: () => getEmployee,
3293
- getEmployeeByRole: () => getEmployeeByRole,
3294
- getEmployeeNamesByRole: () => getEmployeeNamesByRole,
3295
- hasRole: () => hasRole,
3296
- isMultiInstance: () => isMultiInstance,
3297
- loadEmployees: () => loadEmployees,
3298
- loadEmployeesSync: () => loadEmployeesSync,
3299
- normalizeRosterCase: () => normalizeRosterCase,
3300
- registerBinSymlinks: () => registerBinSymlinks,
3301
- saveEmployees: () => saveEmployees,
3302
- validateEmployeeName: () => validateEmployeeName
3303
- });
3304
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3305
- import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
3306
- import { execSync as execSync5 } from "child_process";
3307
- import path9 from "path";
3308
- import os3 from "os";
3309
- function validateEmployeeName(name) {
3310
- if (!name) {
3311
- return { valid: false, error: "Name is required" };
3506
+ async function estimateCardinality(agentId, options) {
3507
+ const client = getClient();
3508
+ let sql = `SELECT COUNT(*) as cnt FROM memories
3509
+ WHERE agent_id = ?
3510
+ AND COALESCE(status, 'active') = 'active'
3511
+ AND COALESCE(confidence, 0.7) >= 0.3`;
3512
+ const args = [agentId];
3513
+ if (options?.projectName) {
3514
+ sql += ` AND project_name = ?`;
3515
+ args.push(options.projectName);
3312
3516
  }
3313
- if (name.length > 32) {
3314
- return { valid: false, error: "Name must be 32 characters or fewer" };
3517
+ if (options?.toolName) {
3518
+ sql += ` AND tool_name = ?`;
3519
+ args.push(options.toolName);
3315
3520
  }
3316
- if (!/^[a-z][a-z0-9]*$/.test(name)) {
3317
- return {
3318
- valid: false,
3319
- error: "Name must start with a letter and contain only lowercase alphanumeric characters"
3320
- };
3521
+ if (options?.hasError !== void 0) {
3522
+ sql += ` AND has_error = ?`;
3523
+ args.push(options.hasError ? 1 : 0);
3321
3524
  }
3322
- return { valid: true };
3323
- }
3324
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
3325
- if (!existsSync7(employeesPath)) {
3326
- return [];
3525
+ if (options?.since) {
3526
+ sql += ` AND timestamp >= ?`;
3527
+ args.push(options.since);
3327
3528
  }
3328
- const raw = await readFile3(employeesPath, "utf-8");
3329
3529
  try {
3330
- return JSON.parse(raw);
3530
+ const result = await client.execute({ sql, args });
3531
+ return Number(result.rows[0]?.cnt) || 0;
3331
3532
  } catch {
3332
- return [];
3533
+ return 0;
3333
3534
  }
3334
3535
  }
3335
- async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
3336
- await mkdir3(path9.dirname(employeesPath), { recursive: true });
3337
- await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
3536
+ function rrfMerge(listA, listB, limit, k = RRF_K) {
3537
+ return rrfMergeMulti([listA, listB], limit, k);
3338
3538
  }
3339
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
3340
- if (!existsSync7(employeesPath)) return [];
3341
- try {
3342
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
3343
- } catch {
3344
- return [];
3345
- }
3346
- }
3347
- function getEmployee(employees, name) {
3348
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
3349
- }
3350
- function getEmployeeByRole(employees, role) {
3351
- const lower = role.toLowerCase();
3352
- return employees.find((e) => e.role.toLowerCase() === lower);
3353
- }
3354
- function getEmployeeNamesByRole(employees, role) {
3355
- const lower = role.toLowerCase();
3356
- return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
3539
+ function recencyScore(timestamp) {
3540
+ const daysSince = (Date.now() - new Date(timestamp).getTime()) / (1e3 * 60 * 60 * 24);
3541
+ return 1 / (1 + daysSince * 0.01);
3357
3542
  }
3358
- function hasRole(agentName, role) {
3359
- const employees = loadEmployeesSync();
3360
- const emp = getEmployee(employees, agentName);
3361
- return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
3543
+ function normalizedImportance(importance) {
3544
+ return ((importance ?? 5) - 1) / 9;
3362
3545
  }
3363
- function isMultiInstance(agentName, employees) {
3364
- const roster = employees ?? loadEmployeesSync();
3365
- const emp = getEmployee(roster, agentName);
3366
- if (!emp) return false;
3367
- return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
3546
+ function frecencyBoost(lastAccessed, timestamp) {
3547
+ const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
3548
+ const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
3549
+ return Math.exp(-0.01 * hoursSince);
3368
3550
  }
3369
- function addEmployee(employees, employee) {
3370
- const normalized = { ...employee, name: employee.name.toLowerCase() };
3371
- if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
3372
- throw new Error(`Employee '${normalized.name}' already exists`);
3551
+ function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
3552
+ const scores = /* @__PURE__ */ new Map();
3553
+ for (let listIdx = 0; listIdx < lists.length; listIdx++) {
3554
+ const list = lists[listIdx];
3555
+ const weight = weights?.[listIdx] ?? 1;
3556
+ for (let i = 0; i < list.length; i++) {
3557
+ const rec = list[i];
3558
+ const entry = scores.get(rec.id) ?? { rrfScore: 0, record: rec };
3559
+ entry.rrfScore += weight * (1 / (k + i + 1));
3560
+ scores.set(rec.id, entry);
3561
+ }
3373
3562
  }
3374
- return [...employees, normalized];
3563
+ const entries = Array.from(scores.values()).map((e) => {
3564
+ const recency = recencyScore(e.record.timestamp);
3565
+ const importance = normalizedImportance(e.record.importance);
3566
+ const confidence = e.record.confidence ?? 0.7;
3567
+ const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
3568
+ const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
3569
+ const finalScore = baseScore * (1 + 0.3 * frecency);
3570
+ return { score: finalScore, record: e.record };
3571
+ });
3572
+ return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
3375
3573
  }
3376
- async function normalizeRosterCase(rosterPath) {
3377
- const employees = await loadEmployees(rosterPath);
3378
- let changed = false;
3379
- for (const emp of employees) {
3380
- if (emp.name !== emp.name.toLowerCase()) {
3381
- const oldName = emp.name;
3382
- emp.name = emp.name.toLowerCase();
3383
- changed = true;
3384
- try {
3385
- const identityDir = path9.join(os3.homedir(), ".exe-os", "identity");
3386
- const oldPath = path9.join(identityDir, `${oldName}.md`);
3387
- const newPath = path9.join(identityDir, `${emp.name}.md`);
3388
- if (existsSync7(oldPath) && !existsSync7(newPath)) {
3389
- renameSync2(oldPath, newPath);
3390
- } else if (existsSync7(oldPath) && oldPath !== newPath) {
3391
- const content = readFileSync5(oldPath, "utf-8");
3392
- writeFileSync2(newPath, content, "utf-8");
3393
- if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
3394
- unlinkSync3(oldPath);
3395
- }
3396
- }
3397
- } catch {
3574
+ async function lightweightSearch(queryText, agentId, options) {
3575
+ const client = getClient();
3576
+ const limit = options?.limit ?? 5;
3577
+ const terms = queryText.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_]/g, "")).filter((t) => t.length >= 3);
3578
+ if (terms.length === 0) {
3579
+ return recentRecords(agentId, options, limit);
3580
+ }
3581
+ const prefixTerms = terms.map((t) => `${t}*`);
3582
+ const useAnd = terms.length >= 3;
3583
+ const matchExpr = useAnd ? prefixTerms.join(" AND ") : prefixTerms.join(" OR ");
3584
+ const results = await ftsQuery(client, matchExpr, agentId, options, limit);
3585
+ if (useAnd && results.length < limit) {
3586
+ const orExpr = prefixTerms.join(" OR ");
3587
+ const orResults = await ftsQuery(client, orExpr, agentId, options, limit);
3588
+ const seen = new Set(results.map((r) => r.id));
3589
+ for (const r of orResults) {
3590
+ if (!seen.has(r.id) && results.length < limit) {
3591
+ results.push(r);
3592
+ seen.add(r.id);
3398
3593
  }
3399
3594
  }
3400
3595
  }
3401
- if (changed) {
3402
- await saveEmployees(employees, rosterPath);
3596
+ if (options?.includeSource && results.length > 0) {
3597
+ await attachDocumentMetadata(results);
3403
3598
  }
3404
- return changed;
3599
+ return results;
3405
3600
  }
3406
- function findExeBin() {
3407
- try {
3408
- return execSync5(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
3409
- } catch {
3410
- return null;
3601
+ async function ftsQuery(client, matchExpr, agentId, options, limit) {
3602
+ const statusFilter = options?.includeArchived ? "" : `
3603
+ AND COALESCE(m.status, 'active') = 'active'`;
3604
+ const draftFilter = options?.includeDrafts ? "" : `
3605
+ AND (m.draft = 0 OR m.draft IS NULL)`;
3606
+ let sql = `SELECT m.id, m.agent_id, m.agent_role, m.session_id, m.timestamp,
3607
+ m.tool_name, m.project_name,
3608
+ m.has_error, m.raw_text, m.vector, m.task_id,
3609
+ m.importance, m.status, m.confidence, m.last_accessed,
3610
+ m.workspace_id, m.document_id, m.user_id,
3611
+ m.char_offset, m.page_number,
3612
+ m.source_path, m.source_type
3613
+ FROM memories m
3614
+ JOIN memories_fts fts ON m.rowid = fts.rowid
3615
+ WHERE memories_fts MATCH ?
3616
+ AND m.agent_id = ?${statusFilter}${draftFilter}
3617
+ AND COALESCE(m.confidence, 0.7) >= 0.3`;
3618
+ const args = [matchExpr, agentId];
3619
+ const scope = buildWikiScopeFilter(options, "m.");
3620
+ sql += scope.clause;
3621
+ args.push(...scope.args);
3622
+ if (options?.projectName) {
3623
+ sql += ` AND m.project_name = ?`;
3624
+ args.push(options.projectName);
3411
3625
  }
3412
- }
3413
- function registerBinSymlinks(name) {
3414
- const created = [];
3415
- const skipped = [];
3416
- const errors = [];
3417
- const exeBinPath = findExeBin();
3418
- if (!exeBinPath) {
3419
- errors.push("Could not find 'exe-os' in PATH");
3420
- return { created, skipped, errors };
3626
+ if (options?.toolName) {
3627
+ sql += ` AND m.tool_name = ?`;
3628
+ args.push(options.toolName);
3421
3629
  }
3422
- const binDir = path9.dirname(exeBinPath);
3423
- let target;
3424
- try {
3425
- target = readlinkSync(exeBinPath);
3426
- } catch {
3427
- errors.push("Could not read 'exe' symlink");
3428
- return { created, skipped, errors };
3630
+ if (options?.hasError !== void 0) {
3631
+ sql += ` AND m.has_error = ?`;
3632
+ args.push(options.hasError ? 1 : 0);
3429
3633
  }
3430
- for (const suffix of ["", "-opencode"]) {
3431
- const linkName = `${name}${suffix}`;
3432
- const linkPath = path9.join(binDir, linkName);
3433
- if (existsSync7(linkPath)) {
3434
- skipped.push(linkName);
3435
- continue;
3436
- }
3437
- try {
3438
- symlinkSync(target, linkPath);
3439
- created.push(linkName);
3440
- } catch (err) {
3441
- errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
3442
- }
3634
+ if (options?.since) {
3635
+ sql += ` AND m.timestamp >= ?`;
3636
+ args.push(options.since);
3443
3637
  }
3444
- return { created, skipped, errors };
3638
+ if (options?.memoryType) {
3639
+ sql += ` AND m.memory_type = ?`;
3640
+ args.push(options.memoryType);
3641
+ }
3642
+ sql += ` ORDER BY rank LIMIT ?`;
3643
+ args.push(limit);
3644
+ const result = await client.execute({ sql, args });
3645
+ return result.rows.map((row) => ({
3646
+ id: row.id,
3647
+ agent_id: row.agent_id,
3648
+ agent_role: row.agent_role,
3649
+ session_id: row.session_id,
3650
+ timestamp: row.timestamp,
3651
+ tool_name: row.tool_name,
3652
+ project_name: row.project_name,
3653
+ has_error: row.has_error === 1,
3654
+ raw_text: row.raw_text,
3655
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
3656
+ task_id: row.task_id ?? null,
3657
+ importance: row.importance ?? 5,
3658
+ status: row.status ?? "active",
3659
+ confidence: row.confidence ?? 0.7,
3660
+ last_accessed: row.last_accessed ?? row.timestamp,
3661
+ workspace_id: row.workspace_id ?? null,
3662
+ document_id: row.document_id ?? null,
3663
+ user_id: row.user_id ?? null,
3664
+ char_offset: row.char_offset ?? null,
3665
+ page_number: row.page_number ?? null,
3666
+ source_path: row.source_path ?? null,
3667
+ source_type: row.source_type ?? null
3668
+ }));
3445
3669
  }
3446
- var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
3447
- var init_employees = __esm({
3448
- "src/lib/employees.ts"() {
3449
- "use strict";
3450
- init_config();
3451
- EMPLOYEES_PATH = path9.join(EXE_AI_DIR, "exe-employees.json");
3452
- MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
3670
+ async function recentRecords(agentId, options, limit) {
3671
+ const client = getClient();
3672
+ const statusFilter = options?.includeArchived ? "" : `
3673
+ AND COALESCE(status, 'active') = 'active'`;
3674
+ const draftFilter = options?.includeDrafts ? "" : `
3675
+ AND (draft = 0 OR draft IS NULL)`;
3676
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
3677
+ tool_name, project_name,
3678
+ has_error, raw_text, vector, task_id,
3679
+ importance, status, confidence, last_accessed,
3680
+ workspace_id, document_id, user_id,
3681
+ char_offset, page_number,
3682
+ source_path, source_type
3683
+ FROM memories
3684
+ WHERE agent_id = ?${statusFilter}${draftFilter}
3685
+ AND COALESCE(confidence, 0.7) >= 0.3`;
3686
+ const args = [agentId];
3687
+ const scope = buildWikiScopeFilter(options, "");
3688
+ sql += scope.clause;
3689
+ args.push(...scope.args);
3690
+ if (options?.projectName) {
3691
+ sql += ` AND project_name = ?`;
3692
+ args.push(options.projectName);
3453
3693
  }
3454
- });
3455
-
3456
- // src/lib/license.ts
3457
- var license_exports = {};
3458
- __export(license_exports, {
3459
- LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
3460
- PLAN_LIMITS: () => PLAN_LIMITS,
3461
- assertVpsLicense: () => assertVpsLicense,
3462
- checkLicense: () => checkLicense,
3463
- getCachedLicense: () => getCachedLicense,
3464
- isFeatureAllowed: () => isFeatureAllowed,
3465
- loadDeviceId: () => loadDeviceId,
3466
- loadLicense: () => loadLicense,
3467
- mirrorLicenseKey: () => mirrorLicenseKey,
3468
- saveLicense: () => saveLicense,
3469
- startLicenseRevalidation: () => startLicenseRevalidation,
3470
- stopLicenseRevalidation: () => stopLicenseRevalidation,
3471
- validateLicense: () => validateLicense
3472
- });
3473
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3474
- import { randomUUID as randomUUID3 } from "crypto";
3475
- import path10 from "path";
3476
- import { jwtVerify, importSPKI } from "jose";
3477
- async function fetchRetry(url, init) {
3478
- try {
3479
- return await fetch(url, init);
3480
- } catch {
3481
- await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
3482
- return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
3694
+ if (options?.toolName) {
3695
+ sql += ` AND tool_name = ?`;
3696
+ args.push(options.toolName);
3483
3697
  }
3484
- }
3485
- function loadDeviceId() {
3486
- const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
3487
- try {
3488
- if (existsSync8(deviceJsonPath)) {
3489
- const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
3490
- if (data.deviceId) return data.deviceId;
3491
- }
3492
- } catch {
3698
+ if (options?.hasError !== void 0) {
3699
+ sql += ` AND has_error = ?`;
3700
+ args.push(options.hasError ? 1 : 0);
3493
3701
  }
3494
- try {
3495
- if (existsSync8(DEVICE_ID_PATH)) {
3496
- const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
3497
- if (id2) return id2;
3498
- }
3499
- } catch {
3702
+ if (options?.since) {
3703
+ sql += ` AND timestamp >= ?`;
3704
+ args.push(options.since);
3500
3705
  }
3501
- const id = randomUUID3();
3502
- mkdirSync3(EXE_AI_DIR, { recursive: true });
3503
- writeFileSync3(DEVICE_ID_PATH, id, "utf8");
3504
- return id;
3706
+ if (options?.memoryType) {
3707
+ sql += ` AND memory_type = ?`;
3708
+ args.push(options.memoryType);
3709
+ }
3710
+ sql += ` ORDER BY timestamp DESC LIMIT ?`;
3711
+ args.push(limit);
3712
+ const result = await client.execute({ sql, args });
3713
+ return result.rows.map((row) => ({
3714
+ id: row.id,
3715
+ agent_id: row.agent_id,
3716
+ agent_role: row.agent_role,
3717
+ session_id: row.session_id,
3718
+ timestamp: row.timestamp,
3719
+ tool_name: row.tool_name,
3720
+ project_name: row.project_name,
3721
+ has_error: row.has_error === 1,
3722
+ raw_text: row.raw_text,
3723
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
3724
+ task_id: row.task_id ?? null,
3725
+ importance: row.importance ?? 5,
3726
+ status: row.status ?? "active",
3727
+ confidence: row.confidence ?? 0.7,
3728
+ last_accessed: row.last_accessed ?? row.timestamp,
3729
+ workspace_id: row.workspace_id ?? null,
3730
+ document_id: row.document_id ?? null,
3731
+ user_id: row.user_id ?? null,
3732
+ char_offset: row.char_offset ?? null,
3733
+ page_number: row.page_number ?? null,
3734
+ source_path: row.source_path ?? null,
3735
+ source_type: row.source_type ?? null
3736
+ }));
3505
3737
  }
3506
- function loadLicense() {
3738
+ function detectToolQuery(query) {
3739
+ const lower = query.toLowerCase().trim();
3740
+ for (const tool of KNOWN_TOOLS) {
3741
+ if (lower === tool.toLowerCase()) return tool;
3742
+ if (lower.startsWith(tool.toLowerCase() + " ")) return tool;
3743
+ if (lower.includes(`tool:${tool.toLowerCase()}`)) return tool;
3744
+ }
3745
+ return null;
3746
+ }
3747
+ async function trajectoryBypass(queryText, agentId, options, limit) {
3748
+ const toolName = detectToolQuery(queryText);
3749
+ if (!toolName) return null;
3507
3750
  try {
3508
- if (!existsSync8(LICENSE_PATH)) return null;
3509
- return readFileSync6(LICENSE_PATH, "utf8").trim();
3751
+ const client = getClient();
3752
+ const statusFilter = options?.includeArchived ? "" : `
3753
+ AND COALESCE(status, 'active') = 'active'`;
3754
+ const draftFilter = options?.includeDrafts ? "" : `
3755
+ AND (draft = 0 OR draft IS NULL)`;
3756
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
3757
+ tool_name, project_name,
3758
+ has_error, raw_text, vector, task_id,
3759
+ importance, status, confidence, last_accessed,
3760
+ workspace_id, document_id, user_id,
3761
+ char_offset, page_number,
3762
+ source_path, source_type
3763
+ FROM memories
3764
+ WHERE trajectory IS NOT NULL
3765
+ AND json_extract(trajectory, '$.tool') = ?
3766
+ AND agent_id = ?${statusFilter}${draftFilter}`;
3767
+ const args = [toolName, agentId];
3768
+ if (options?.projectName) {
3769
+ sql += ` AND project_name = ?`;
3770
+ args.push(options.projectName);
3771
+ }
3772
+ if (options?.since) {
3773
+ sql += ` AND timestamp >= ?`;
3774
+ args.push(options.since);
3775
+ }
3776
+ sql += ` ORDER BY timestamp DESC LIMIT ?`;
3777
+ args.push(limit);
3778
+ const result = await client.execute({ sql, args });
3779
+ if (result.rows.length < 3) return null;
3780
+ return result.rows.map((row) => ({
3781
+ id: row.id,
3782
+ agent_id: row.agent_id,
3783
+ agent_role: row.agent_role,
3784
+ session_id: row.session_id,
3785
+ timestamp: row.timestamp,
3786
+ tool_name: row.tool_name,
3787
+ project_name: row.project_name,
3788
+ has_error: row.has_error === 1,
3789
+ raw_text: row.raw_text,
3790
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
3791
+ task_id: row.task_id ?? null,
3792
+ importance: row.importance ?? 5,
3793
+ status: row.status ?? "active",
3794
+ confidence: row.confidence ?? 0.7,
3795
+ last_accessed: row.last_accessed ?? row.timestamp,
3796
+ workspace_id: row.workspace_id ?? null,
3797
+ document_id: row.document_id ?? null,
3798
+ user_id: row.user_id ?? null,
3799
+ char_offset: row.char_offset ?? null,
3800
+ page_number: row.page_number ?? null,
3801
+ source_path: row.source_path ?? null,
3802
+ source_type: row.source_type ?? null
3803
+ }));
3510
3804
  } catch {
3511
3805
  return null;
3512
3806
  }
3513
3807
  }
3514
- function saveLicense(apiKey) {
3515
- mkdirSync3(EXE_AI_DIR, { recursive: true });
3516
- writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3808
+ var RRF_K, KNOWN_TOOLS;
3809
+ var init_hybrid_search = __esm({
3810
+ "src/lib/hybrid-search.ts"() {
3811
+ "use strict";
3812
+ init_store();
3813
+ init_database();
3814
+ RRF_K = 60;
3815
+ KNOWN_TOOLS = /* @__PURE__ */ new Set([
3816
+ "Bash",
3817
+ "Read",
3818
+ "Write",
3819
+ "Edit",
3820
+ "Grep",
3821
+ "Glob",
3822
+ "Agent",
3823
+ "Skill",
3824
+ "WebSearch",
3825
+ "WebFetch"
3826
+ ]);
3827
+ }
3828
+ });
3829
+
3830
+ // src/lib/session-key.ts
3831
+ import { execSync as execSync4 } from "child_process";
3832
+ function getSessionKey() {
3833
+ if (_cached2) return _cached2;
3834
+ let pid = process.ppid;
3835
+ for (let i = 0; i < 10; i++) {
3836
+ try {
3837
+ const info = execSync4(`ps -p ${pid} -o ppid=,comm=`, {
3838
+ encoding: "utf8",
3839
+ timeout: 2e3
3840
+ }).trim();
3841
+ const match = info.match(/^\s*(\d+)\s+(.+)$/);
3842
+ if (!match) break;
3843
+ const [, ppid, cmd] = match;
3844
+ if (cmd === "claude" || cmd.endsWith("/claude")) {
3845
+ _cached2 = String(pid);
3846
+ return _cached2;
3847
+ }
3848
+ pid = parseInt(ppid, 10);
3849
+ if (pid <= 1) break;
3850
+ } catch {
3851
+ break;
3852
+ }
3853
+ }
3854
+ _cached2 = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
3855
+ return _cached2;
3517
3856
  }
3518
- async function verifyLicenseJwt(token) {
3519
- try {
3520
- const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
3521
- const { payload } = await jwtVerify(token, key, {
3522
- algorithms: [LICENSE_JWT_ALG]
3523
- });
3524
- const plan = payload.plan ?? "free";
3525
- const email = payload.sub ?? "";
3526
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3527
- return {
3528
- valid: true,
3529
- plan,
3530
- email,
3531
- expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3532
- deviceLimit: limits.devices,
3533
- employeeLimit: limits.employees,
3534
- memoryLimit: limits.memories
3535
- };
3536
- } catch {
3537
- return null;
3857
+ var _cached2;
3858
+ var init_session_key = __esm({
3859
+ "src/lib/session-key.ts"() {
3860
+ "use strict";
3861
+ _cached2 = null;
3862
+ }
3863
+ });
3864
+
3865
+ // src/adapters/claude/session-key.ts
3866
+ var init_session_key2 = __esm({
3867
+ "src/adapters/claude/session-key.ts"() {
3868
+ "use strict";
3869
+ init_session_key();
3538
3870
  }
3871
+ });
3872
+
3873
+ // src/adapters/claude/active-agent.ts
3874
+ var active_agent_exports = {};
3875
+ __export(active_agent_exports, {
3876
+ cleanupSessionMarkers: () => cleanupSessionMarkers,
3877
+ clearActiveAgent: () => clearActiveAgent,
3878
+ getActiveAgent: () => getActiveAgent,
3879
+ getAllActiveAgents: () => getAllActiveAgents,
3880
+ resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
3881
+ writeActiveAgent: () => writeActiveAgent
3882
+ });
3883
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
3884
+ import { execSync as execSync5 } from "child_process";
3885
+ import path9 from "path";
3886
+ function isNameWithOptionalInstance(candidate, baseName) {
3887
+ if (candidate === baseName) return true;
3888
+ if (!candidate.startsWith(baseName)) return false;
3889
+ return /^\d+$/.test(candidate.slice(baseName.length));
3539
3890
  }
3540
- async function getCachedLicense() {
3541
- try {
3542
- if (!existsSync8(CACHE_PATH)) return null;
3543
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3544
- if (!raw.token || typeof raw.token !== "string") return null;
3545
- return await verifyLicenseJwt(raw.token);
3546
- } catch {
3547
- return null;
3891
+ function resolveEmployeeFromSessionPrefix(prefix, employees) {
3892
+ const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
3893
+ for (const employee of sorted) {
3894
+ if (isNameWithOptionalInstance(prefix, employee.name)) {
3895
+ return { agentId: employee.name, agentRole: employee.role };
3896
+ }
3548
3897
  }
3898
+ return null;
3549
3899
  }
3550
- function readCachedToken() {
3551
- try {
3552
- if (!existsSync8(CACHE_PATH)) return null;
3553
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3554
- return typeof raw.token === "string" ? raw.token : null;
3555
- } catch {
3556
- return null;
3900
+ function resolveActiveAgentFromTmuxSession(sessionName) {
3901
+ const employees = loadEmployeesSync();
3902
+ const coordinator = getCoordinatorEmployee(employees);
3903
+ const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
3904
+ if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
3905
+ return {
3906
+ agentId: coordinatorName,
3907
+ agentRole: coordinator?.role ?? "COO"
3908
+ };
3909
+ }
3910
+ if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
3911
+ return {
3912
+ agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
3913
+ agentRole: coordinator?.role ?? "COO"
3914
+ };
3915
+ }
3916
+ if (sessionName.includes("-")) {
3917
+ const prefix = sessionName.split("-")[0] ?? "";
3918
+ const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
3919
+ if (employee) return employee;
3920
+ const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
3921
+ if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
3922
+ const emp = getEmployee(employees, legacy[1]);
3923
+ return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
3924
+ }
3557
3925
  }
3926
+ return null;
3558
3927
  }
3559
- function getRawCachedPlan() {
3928
+ function getMarkerPath() {
3929
+ return path9.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
3930
+ }
3931
+ function writeActiveAgent(agentId, agentRole) {
3560
3932
  try {
3561
- const token = readCachedToken();
3562
- if (!token) return null;
3563
- const parts = token.split(".");
3564
- if (parts.length !== 3) return null;
3565
- const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3566
- const plan = payload.plan ?? "free";
3567
- const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3568
- process.stderr.write(
3569
- `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
3570
- `
3933
+ mkdirSync2(CACHE_DIR, { recursive: true });
3934
+ writeFileSync2(
3935
+ getMarkerPath(),
3936
+ JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
3571
3937
  );
3572
- return {
3573
- valid: true,
3574
- plan,
3575
- email: payload.sub ?? "",
3576
- expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3577
- deviceLimit: limits.devices,
3578
- employeeLimit: limits.employees,
3579
- memoryLimit: limits.memories
3580
- };
3581
3938
  } catch {
3582
- return null;
3583
3939
  }
3584
3940
  }
3585
- function cacheResponse(token) {
3941
+ function clearActiveAgent() {
3586
3942
  try {
3587
- writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
3943
+ unlinkSync3(getMarkerPath());
3588
3944
  } catch {
3589
3945
  }
3590
3946
  }
3591
- async function validateLicense(apiKey, deviceId) {
3592
- const did = deviceId ?? loadDeviceId();
3947
+ function getActiveAgent() {
3593
3948
  try {
3594
- const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3595
- method: "POST",
3596
- headers: { "Content-Type": "application/json" },
3597
- body: JSON.stringify({ apiKey, deviceId: did }),
3598
- signal: AbortSignal.timeout(1e4)
3599
- });
3600
- if (res.ok) {
3601
- const data = await res.json();
3602
- if (data.error === "device_limit_exceeded") {
3603
- const cached2 = await getCachedLicense();
3604
- if (cached2) return cached2;
3605
- const raw2 = getRawCachedPlan();
3606
- if (raw2) return { ...raw2, valid: false };
3607
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3608
- }
3609
- if (data.token) {
3610
- cacheResponse(data.token);
3611
- const verified = await verifyLicenseJwt(data.token);
3612
- if (verified) return verified;
3949
+ const markerPath = getMarkerPath();
3950
+ const raw = readFileSync5(markerPath, "utf8");
3951
+ const data = JSON.parse(raw);
3952
+ if (data.agentId) {
3953
+ if (data.startedAt) {
3954
+ const age = Date.now() - new Date(data.startedAt).getTime();
3955
+ if (age > STALE_MS) {
3956
+ try {
3957
+ unlinkSync3(markerPath);
3958
+ } catch {
3959
+ }
3960
+ } else {
3961
+ return {
3962
+ agentId: data.agentId,
3963
+ agentRole: data.agentRole || "employee"
3964
+ };
3965
+ }
3966
+ } else {
3967
+ return {
3968
+ agentId: data.agentId,
3969
+ agentRole: data.agentRole || "employee"
3970
+ };
3613
3971
  }
3614
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3615
- return {
3616
- valid: data.valid,
3617
- plan: data.plan,
3618
- email: data.email,
3619
- expiresAt: data.expiresAt,
3620
- deviceLimit: limits.devices,
3621
- employeeLimit: limits.employees,
3622
- memoryLimit: limits.memories
3623
- };
3624
3972
  }
3625
- const cached = await getCachedLicense();
3626
- if (cached) return cached;
3627
- const raw = getRawCachedPlan();
3628
- if (raw) return raw;
3629
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3630
3973
  } catch {
3631
- const cached = await getCachedLicense();
3632
- if (cached) return cached;
3633
- const rawFallback = getRawCachedPlan();
3634
- if (rawFallback) return rawFallback;
3635
- return { ...FREE_LICENSE, valid: false, error: "offline" };
3636
3974
  }
3637
- }
3638
- function getCacheAgeMs() {
3639
3975
  try {
3640
- const { statSync: statSync4 } = __require("fs");
3641
- const s = statSync4(CACHE_PATH);
3642
- return Date.now() - s.mtimeMs;
3976
+ const sessionName = execSync5(
3977
+ "tmux display-message -p '#{session_name}' 2>/dev/null",
3978
+ { encoding: "utf8", timeout: 2e3 }
3979
+ ).trim();
3980
+ const resolved = resolveActiveAgentFromTmuxSession(sessionName);
3981
+ if (resolved) return resolved;
3982
+ } catch {
3983
+ }
3984
+ return {
3985
+ agentId: process.env.AGENT_ID || "default",
3986
+ agentRole: process.env.AGENT_ROLE || "employee"
3987
+ };
3988
+ }
3989
+ function getAllActiveAgents() {
3990
+ try {
3991
+ const files = readdirSync3(CACHE_DIR);
3992
+ const sessions = [];
3993
+ for (const file of files) {
3994
+ if (!file.startsWith("active-agent-") || !file.endsWith(".json")) continue;
3995
+ const key = file.slice("active-agent-".length, -".json".length);
3996
+ if (key === "undefined") continue;
3997
+ try {
3998
+ const raw = readFileSync5(path9.join(CACHE_DIR, file), "utf8");
3999
+ const data = JSON.parse(raw);
4000
+ if (!data.agentId) continue;
4001
+ if (data.startedAt) {
4002
+ const age = Date.now() - new Date(data.startedAt).getTime();
4003
+ if (age > STALE_MS) {
4004
+ try {
4005
+ unlinkSync3(path9.join(CACHE_DIR, file));
4006
+ } catch {
4007
+ }
4008
+ continue;
4009
+ }
4010
+ }
4011
+ sessions.push({
4012
+ agentId: data.agentId,
4013
+ agentRole: data.agentRole || "employee",
4014
+ startedAt: data.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
4015
+ sessionKey: key
4016
+ });
4017
+ } catch {
4018
+ }
4019
+ }
4020
+ return sessions;
4021
+ } catch {
4022
+ return [];
4023
+ }
4024
+ }
4025
+ function cleanupSessionMarkers() {
4026
+ const key = getSessionKey();
4027
+ try {
4028
+ unlinkSync3(path9.join(CACHE_DIR, `active-agent-${key}.json`));
4029
+ } catch {
4030
+ }
4031
+ try {
4032
+ unlinkSync3(path9.join(CACHE_DIR, "active-agent-undefined.json"));
4033
+ } catch {
4034
+ }
4035
+ }
4036
+ var CACHE_DIR, STALE_MS;
4037
+ var init_active_agent = __esm({
4038
+ "src/adapters/claude/active-agent.ts"() {
4039
+ "use strict";
4040
+ init_config();
4041
+ init_session_key2();
4042
+ init_employees();
4043
+ CACHE_DIR = path9.join(EXE_AI_DIR, "session-cache");
4044
+ STALE_MS = 24 * 60 * 60 * 1e3;
4045
+ }
4046
+ });
4047
+
4048
+ // src/lib/license.ts
4049
+ var license_exports = {};
4050
+ __export(license_exports, {
4051
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
4052
+ PLAN_LIMITS: () => PLAN_LIMITS,
4053
+ assertVpsLicense: () => assertVpsLicense,
4054
+ checkLicense: () => checkLicense,
4055
+ getCachedLicense: () => getCachedLicense,
4056
+ isFeatureAllowed: () => isFeatureAllowed,
4057
+ loadDeviceId: () => loadDeviceId,
4058
+ loadLicense: () => loadLicense,
4059
+ mirrorLicenseKey: () => mirrorLicenseKey,
4060
+ saveLicense: () => saveLicense,
4061
+ startLicenseRevalidation: () => startLicenseRevalidation,
4062
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
4063
+ validateLicense: () => validateLicense
4064
+ });
4065
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
4066
+ import { randomUUID as randomUUID3 } from "crypto";
4067
+ import path10 from "path";
4068
+ import { jwtVerify, importSPKI } from "jose";
4069
+ async function fetchRetry(url, init) {
4070
+ try {
4071
+ return await fetch(url, init);
4072
+ } catch {
4073
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
4074
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
4075
+ }
4076
+ }
4077
+ function loadDeviceId() {
4078
+ const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
4079
+ try {
4080
+ if (existsSync8(deviceJsonPath)) {
4081
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
4082
+ if (data.deviceId) return data.deviceId;
4083
+ }
4084
+ } catch {
4085
+ }
4086
+ try {
4087
+ if (existsSync8(DEVICE_ID_PATH)) {
4088
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
4089
+ if (id2) return id2;
4090
+ }
4091
+ } catch {
4092
+ }
4093
+ const id = randomUUID3();
4094
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
4095
+ writeFileSync3(DEVICE_ID_PATH, id, "utf8");
4096
+ return id;
4097
+ }
4098
+ function loadLicense() {
4099
+ try {
4100
+ if (!existsSync8(LICENSE_PATH)) return null;
4101
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
4102
+ } catch {
4103
+ return null;
4104
+ }
4105
+ }
4106
+ function saveLicense(apiKey) {
4107
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
4108
+ writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
4109
+ }
4110
+ async function verifyLicenseJwt(token) {
4111
+ try {
4112
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4113
+ const { payload } = await jwtVerify(token, key, {
4114
+ algorithms: [LICENSE_JWT_ALG]
4115
+ });
4116
+ const plan = payload.plan ?? "free";
4117
+ const email = payload.sub ?? "";
4118
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4119
+ return {
4120
+ valid: true,
4121
+ plan,
4122
+ email,
4123
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4124
+ deviceLimit: limits.devices,
4125
+ employeeLimit: limits.employees,
4126
+ memoryLimit: limits.memories
4127
+ };
4128
+ } catch {
4129
+ return null;
4130
+ }
4131
+ }
4132
+ async function getCachedLicense() {
4133
+ try {
4134
+ if (!existsSync8(CACHE_PATH)) return null;
4135
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
4136
+ if (!raw.token || typeof raw.token !== "string") return null;
4137
+ return await verifyLicenseJwt(raw.token);
4138
+ } catch {
4139
+ return null;
4140
+ }
4141
+ }
4142
+ function readCachedToken() {
4143
+ try {
4144
+ if (!existsSync8(CACHE_PATH)) return null;
4145
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
4146
+ return typeof raw.token === "string" ? raw.token : null;
4147
+ } catch {
4148
+ return null;
4149
+ }
4150
+ }
4151
+ function getRawCachedPlan() {
4152
+ try {
4153
+ const token = readCachedToken();
4154
+ if (!token) return null;
4155
+ const parts = token.split(".");
4156
+ if (parts.length !== 3) return null;
4157
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
4158
+ const plan = payload.plan ?? "free";
4159
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4160
+ process.stderr.write(
4161
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
4162
+ `
4163
+ );
4164
+ return {
4165
+ valid: true,
4166
+ plan,
4167
+ email: payload.sub ?? "",
4168
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4169
+ deviceLimit: limits.devices,
4170
+ employeeLimit: limits.employees,
4171
+ memoryLimit: limits.memories
4172
+ };
4173
+ } catch {
4174
+ return null;
4175
+ }
4176
+ }
4177
+ function cacheResponse(token) {
4178
+ try {
4179
+ writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
4180
+ } catch {
4181
+ }
4182
+ }
4183
+ async function validateLicense(apiKey, deviceId) {
4184
+ const did = deviceId ?? loadDeviceId();
4185
+ try {
4186
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4187
+ method: "POST",
4188
+ headers: { "Content-Type": "application/json" },
4189
+ body: JSON.stringify({ apiKey, deviceId: did }),
4190
+ signal: AbortSignal.timeout(1e4)
4191
+ });
4192
+ if (res.ok) {
4193
+ const data = await res.json();
4194
+ if (data.error === "device_limit_exceeded") {
4195
+ const cached2 = await getCachedLicense();
4196
+ if (cached2) return cached2;
4197
+ const raw2 = getRawCachedPlan();
4198
+ if (raw2) return { ...raw2, valid: false };
4199
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
4200
+ }
4201
+ if (data.token) {
4202
+ cacheResponse(data.token);
4203
+ const verified = await verifyLicenseJwt(data.token);
4204
+ if (verified) return verified;
4205
+ }
4206
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4207
+ return {
4208
+ valid: data.valid,
4209
+ plan: data.plan,
4210
+ email: data.email,
4211
+ expiresAt: data.expiresAt,
4212
+ deviceLimit: limits.devices,
4213
+ employeeLimit: limits.employees,
4214
+ memoryLimit: limits.memories
4215
+ };
4216
+ }
4217
+ const cached = await getCachedLicense();
4218
+ if (cached) return cached;
4219
+ const raw = getRawCachedPlan();
4220
+ if (raw) return raw;
4221
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
4222
+ } catch {
4223
+ const cached = await getCachedLicense();
4224
+ if (cached) return cached;
4225
+ const rawFallback = getRawCachedPlan();
4226
+ if (rawFallback) return rawFallback;
4227
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
4228
+ }
4229
+ }
4230
+ function getCacheAgeMs() {
4231
+ try {
4232
+ const { statSync: statSync4 } = __require("fs");
4233
+ const s = statSync4(CACHE_PATH);
4234
+ return Date.now() - s.mtimeMs;
3643
4235
  } catch {
3644
4236
  return Infinity;
3645
4237
  }
@@ -4390,7 +4982,7 @@ function _resetLastRelaunchCache() {
4390
4982
  }
4391
4983
  async function lastResumeCreatedAtMs(agentId) {
4392
4984
  const client = getClient();
4393
- const cmScope = sessionScopeFilter();
4985
+ const cmScope = sessionScopeFilter(null);
4394
4986
  const result = await client.execute({
4395
4987
  sql: `SELECT MAX(created_at) AS last_created_at
4396
4988
  FROM tasks
@@ -4415,7 +5007,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
4415
5007
  const client = getClient();
4416
5008
  const now = (/* @__PURE__ */ new Date()).toISOString();
4417
5009
  const context = buildResumeContext(agentId, openTasks);
4418
- const rdScope = sessionScopeFilter();
5010
+ const rdScope = sessionScopeFilter(null);
4419
5011
  const existing = await client.execute({
4420
5012
  sql: `SELECT id FROM tasks
4421
5013
  WHERE assigned_to = ?
@@ -4449,7 +5041,7 @@ async function pollCapacityDead() {
4449
5041
  const transport = getTransport();
4450
5042
  const relaunched = [];
4451
5043
  const registered = listSessions().filter(
4452
- (s) => s.agentId !== "exe"
5044
+ (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
4453
5045
  );
4454
5046
  if (registered.length === 0) return [];
4455
5047
  let liveSessions;
@@ -4509,7 +5101,7 @@ async function pollCapacityDead() {
4509
5101
  reason: "capacity"
4510
5102
  });
4511
5103
  const client = getClient();
4512
- const rlScope = sessionScopeFilter();
5104
+ const rlScope = sessionScopeFilter(null);
4513
5105
  const openTasks = await client.execute({
4514
5106
  sql: `SELECT id, title, priority, task_file, status
4515
5107
  FROM tasks
@@ -4563,6 +5155,7 @@ var init_capacity_monitor = __esm({
4563
5155
  init_session_kill_telemetry();
4564
5156
  init_tmux_routing();
4565
5157
  init_task_scope();
5158
+ init_employees();
4566
5159
  CAPACITY_PATTERNS = [
4567
5160
  /conversation is too long/i,
4568
5161
  /maximum context length/i,
@@ -4712,7 +5305,7 @@ function employeeSessionName(employee, exeSession, instance) {
4712
5305
  exeSession = root;
4713
5306
  } else {
4714
5307
  throw new Error(
4715
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
5308
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
4716
5309
  );
4717
5310
  }
4718
5311
  }
@@ -4732,8 +5325,10 @@ function parseParentExe(sessionName, agentId) {
4732
5325
  return match?.[1] ?? null;
4733
5326
  }
4734
5327
  function extractRootExe(name) {
4735
- const match = name.match(/(exe\d+)$/);
4736
- return match?.[1] ?? null;
5328
+ if (!name) return null;
5329
+ if (!name.includes("-")) return name;
5330
+ const parts = name.split("-").filter(Boolean);
5331
+ return parts.length > 0 ? parts[parts.length - 1] : null;
4737
5332
  }
4738
5333
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4739
5334
  if (!existsSync13(SESSION_CACHE)) {
@@ -4878,12 +5473,14 @@ function isSessionBusy(sessionName) {
4878
5473
  return state === "thinking" || state === "tool";
4879
5474
  }
4880
5475
  function isExeSession(sessionName) {
4881
- return /^exe\d*$/.test(sessionName);
5476
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
5477
+ const coordinatorName = getCoordinatorName();
5478
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
4882
5479
  }
4883
5480
  function sendIntercom(targetSession) {
4884
5481
  const transport = getTransport();
4885
5482
  if (isExeSession(targetSession)) {
4886
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
5483
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
4887
5484
  return "skipped_exe";
4888
5485
  }
4889
5486
  if (isDebounced(targetSession)) {
@@ -4935,7 +5532,7 @@ function notifyParentExe(sessionKey) {
4935
5532
  if (result === "failed") {
4936
5533
  const rootExe = resolveExeSession();
4937
5534
  if (rootExe && rootExe !== target) {
4938
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
5535
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
4939
5536
  `);
4940
5537
  const fallback = sendIntercom(rootExe);
4941
5538
  return fallback !== "failed";
@@ -4945,8 +5542,8 @@ function notifyParentExe(sessionKey) {
4945
5542
  return true;
4946
5543
  }
4947
5544
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4948
- if (employeeName === "exe") {
4949
- return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
5545
+ if (employeeName === "exe" || isCoordinatorName(employeeName)) {
5546
+ return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
4950
5547
  }
4951
5548
  try {
4952
5549
  assertEmployeeLimitSync();
@@ -4955,8 +5552,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4955
5552
  return { status: "failed", sessionName: "", error: err.message };
4956
5553
  }
4957
5554
  }
4958
- if (/-exe\d*$/.test(employeeName)) {
4959
- const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
5555
+ if (employeeName.includes("-")) {
5556
+ const bare = employeeName.split("-")[0].replace(/\d+$/, "");
4960
5557
  return {
4961
5558
  status: "failed",
4962
5559
  sessionName: "",
@@ -4975,7 +5572,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4975
5572
  return {
4976
5573
  status: "failed",
4977
5574
  sessionName: "",
4978
- error: `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
5575
+ error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
4979
5576
  };
4980
5577
  }
4981
5578
  }
@@ -5132,8 +5729,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5132
5729
  const ctxContent = [
5133
5730
  `## Session Context`,
5134
5731
  `You are running in tmux session: ${sessionName}.`,
5135
- `Your parent exe session is ${exeSession}.`,
5136
- `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
5732
+ `Your parent coordinator session is ${exeSession}.`,
5733
+ `Your employees (if any) use the -${exeSession} suffix.`
5137
5734
  ].join("\n");
5138
5735
  writeFileSync8(ctxFile, ctxContent);
5139
5736
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
@@ -5237,6 +5834,7 @@ var init_tmux_routing = __esm({
5237
5834
  init_provider_table();
5238
5835
  init_intercom_queue();
5239
5836
  init_plan_limits();
5837
+ init_employees();
5240
5838
  SPAWN_LOCK_DIR = path17.join(os7.homedir(), ".exe-os", "spawn-locks");
5241
5839
  SESSION_CACHE = path17.join(os7.homedir(), ".exe-os", "session-cache");
5242
5840
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
@@ -5275,11 +5873,27 @@ var init_task_scope = __esm({
5275
5873
  });
5276
5874
 
5277
5875
  // src/lib/tasks-crud.ts
5278
- import crypto6 from "crypto";
5279
- import path18 from "path";
5280
- import { execSync as execSync8 } from "child_process";
5281
- import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5282
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
5876
+ var tasks_crud_exports = {};
5877
+ __export(tasks_crud_exports, {
5878
+ TASK_ALREADY_CLAIMED_PREFIX: () => TASK_ALREADY_CLAIMED_PREFIX,
5879
+ checkStaleCompletion: () => checkStaleCompletion,
5880
+ createTaskCore: () => createTaskCore,
5881
+ deleteTaskCore: () => deleteTaskCore,
5882
+ ensureArchitectureDoc: () => ensureArchitectureDoc,
5883
+ ensureGitignoreExe: () => ensureGitignoreExe,
5884
+ extractParentFromContext: () => extractParentFromContext,
5885
+ isTmuxSessionAlive: () => isTmuxSessionAlive,
5886
+ listTasks: () => listTasks,
5887
+ resolveTask: () => resolveTask,
5888
+ slugify: () => slugify,
5889
+ updateTaskStatus: () => updateTaskStatus,
5890
+ writeCheckpoint: () => writeCheckpoint
5891
+ });
5892
+ import crypto6 from "crypto";
5893
+ import path18 from "path";
5894
+ import { execSync as execSync8 } from "child_process";
5895
+ import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5896
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
5283
5897
  async function writeCheckpoint(input) {
5284
5898
  const client = getClient();
5285
5899
  const row = await resolveTask(client, input.taskId);
@@ -5519,6 +6133,36 @@ async function listTasks(input) {
5519
6133
  tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
5520
6134
  }));
5521
6135
  }
6136
+ function isTmuxSessionAlive(identifier) {
6137
+ if (!identifier || identifier === "unknown") return true;
6138
+ try {
6139
+ if (identifier.startsWith("%")) {
6140
+ const output = execSync8("tmux list-panes -a -F '#{pane_id}'", {
6141
+ timeout: 2e3,
6142
+ encoding: "utf8",
6143
+ stdio: ["pipe", "pipe", "pipe"]
6144
+ });
6145
+ return output.split("\n").some((l) => l.trim() === identifier);
6146
+ } else {
6147
+ execSync8(`tmux has-session -t ${JSON.stringify(identifier)}`, {
6148
+ timeout: 2e3,
6149
+ stdio: ["pipe", "pipe", "pipe"]
6150
+ });
6151
+ return true;
6152
+ }
6153
+ } catch {
6154
+ if (identifier.startsWith("%")) return true;
6155
+ try {
6156
+ execSync8("tmux list-sessions", {
6157
+ timeout: 2e3,
6158
+ stdio: ["pipe", "pipe", "pipe"]
6159
+ });
6160
+ return false;
6161
+ } catch {
6162
+ return true;
6163
+ }
6164
+ }
6165
+ }
5522
6166
  function checkStaleCompletion(taskContext, taskCreatedAt) {
5523
6167
  if (!taskContext) return null;
5524
6168
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -5581,13 +6225,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
5581
6225
  });
5582
6226
  if (claim.rowsAffected === 0) {
5583
6227
  const current = await client.execute({
5584
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
6228
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
5585
6229
  args: [taskId]
5586
6230
  });
5587
6231
  const cur = current.rows[0];
5588
- const status = cur?.status ?? "unknown";
5589
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
5590
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
6232
+ const curStatus = cur?.status ?? "unknown";
6233
+ const claimedBySession = cur?.assigned_tmux ?? "";
6234
+ const assignedBy = cur?.assigned_by ?? "";
6235
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
6236
+ process.stderr.write(
6237
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
6238
+ `
6239
+ );
6240
+ await client.execute({
6241
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
6242
+ args: [now, taskId]
6243
+ });
6244
+ const retried = await client.execute({
6245
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
6246
+ args: [tmuxSession, now, taskId]
6247
+ });
6248
+ if (retried.rowsAffected > 0) {
6249
+ try {
6250
+ await writeCheckpoint({
6251
+ taskId,
6252
+ step: "reclaimed_dead_session",
6253
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
6254
+ });
6255
+ } catch {
6256
+ }
6257
+ return { row, taskFile, now, taskId };
6258
+ }
6259
+ }
6260
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
6261
+ process.stderr.write(
6262
+ `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
6263
+ `
6264
+ );
6265
+ await client.execute({
6266
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
6267
+ args: [tmuxSession, now, taskId]
6268
+ });
6269
+ try {
6270
+ await writeCheckpoint({
6271
+ taskId,
6272
+ step: "assigner_override",
6273
+ contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
6274
+ });
6275
+ } catch {
6276
+ }
6277
+ return { row, taskFile, now, taskId };
6278
+ }
6279
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
6280
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
5591
6281
  }
5592
6282
  try {
5593
6283
  await writeCheckpoint({
@@ -5685,7 +6375,7 @@ var init_tasks_crud = __esm({
5685
6375
  "use strict";
5686
6376
  init_database();
5687
6377
  init_task_scope();
5688
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
6378
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
5689
6379
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
5690
6380
  }
5691
6381
  });
@@ -6000,7 +6690,7 @@ function findSessionForProject(projectName) {
6000
6690
  const sessions = listSessions();
6001
6691
  for (const s of sessions) {
6002
6692
  const proj = s.projectDir.split("/").filter(Boolean).pop();
6003
- if (proj === projectName && s.agentId === "exe") return s;
6693
+ if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
6004
6694
  }
6005
6695
  return null;
6006
6696
  }
@@ -6040,12 +6730,13 @@ var init_session_scope = __esm({
6040
6730
  init_session_registry();
6041
6731
  init_project_name();
6042
6732
  init_tmux_routing();
6733
+ init_employees();
6043
6734
  }
6044
6735
  });
6045
6736
 
6046
6737
  // src/lib/tasks-notify.ts
6047
6738
  async function dispatchTaskToEmployee(input) {
6048
- if (input.assignedTo === "exe") return { dispatched: "skipped" };
6739
+ if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
6049
6740
  let crossProject = false;
6050
6741
  if (input.projectName) {
6051
6742
  try {
@@ -6516,6 +7207,24 @@ async function updateTask(input) {
6516
7207
  });
6517
7208
  } catch {
6518
7209
  }
7210
+ const assignedAgent = String(row.assigned_to);
7211
+ if (!isCoordinatorName(assignedAgent)) {
7212
+ try {
7213
+ const draftClient = getClient();
7214
+ if (input.status === "done") {
7215
+ await draftClient.execute({
7216
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
7217
+ args: [assignedAgent]
7218
+ });
7219
+ } else if (input.status === "cancelled") {
7220
+ await draftClient.execute({
7221
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
7222
+ args: [assignedAgent]
7223
+ });
7224
+ }
7225
+ } catch {
7226
+ }
7227
+ }
6519
7228
  try {
6520
7229
  const client = getClient();
6521
7230
  const cascaded = await client.execute({
@@ -6534,8 +7243,8 @@ async function updateTask(input) {
6534
7243
  }
6535
7244
  const isTerminal = input.status === "done" || input.status === "needs_review";
6536
7245
  if (isTerminal) {
6537
- const isExe = String(row.assigned_to) === "exe";
6538
- if (!isExe) {
7246
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
7247
+ if (!isCoordinator) {
6539
7248
  notifyTaskDone();
6540
7249
  }
6541
7250
  await markTaskNotificationsRead(taskFile);
@@ -6559,7 +7268,7 @@ async function updateTask(input) {
6559
7268
  }
6560
7269
  }
6561
7270
  }
6562
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
7271
+ if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6563
7272
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6564
7273
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
6565
7274
  taskId,
@@ -6575,7 +7284,7 @@ async function updateTask(input) {
6575
7284
  });
6576
7285
  }
6577
7286
  let nextTask;
6578
- if (isTerminal && String(row.assigned_to) !== "exe") {
7287
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
6579
7288
  try {
6580
7289
  nextTask = await findNextTask(String(row.assigned_to));
6581
7290
  } catch {
@@ -6602,12 +7311,14 @@ async function updateTask(input) {
6602
7311
  async function deleteTask(taskId, baseDir) {
6603
7312
  const client = getClient();
6604
7313
  const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
6605
- const reviewer = assignedBy || "exe";
7314
+ const coordinatorName = getCoordinatorName();
7315
+ const reviewer = assignedBy || coordinatorName;
6606
7316
  const reviewSlug = `review-${assignedTo}-${taskSlug}`;
6607
7317
  const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
7318
+ const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
6608
7319
  await client.execute({
6609
- sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
6610
- args: [reviewFile, `exe/exe/${reviewSlug}.md`]
7320
+ sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
7321
+ args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
6611
7322
  });
6612
7323
  await markAsReadByTaskFile(taskFile);
6613
7324
  await markAsReadByTaskFile(reviewFile);
@@ -6619,6 +7330,7 @@ var init_tasks = __esm({
6619
7330
  init_config();
6620
7331
  init_notifications();
6621
7332
  init_state_bus();
7333
+ init_employees();
6622
7334
  init_tasks_crud();
6623
7335
  init_tasks_review();
6624
7336
  init_tasks_crud();
@@ -6733,7 +7445,7 @@ async function deliverLocalMessage(messageId) {
6733
7445
  try {
6734
7446
  const exeSession = resolveExeSession();
6735
7447
  if (!exeSession) {
6736
- throw new Error("No exe session found");
7448
+ throw new Error("No coordinator session found");
6737
7449
  }
6738
7450
  const sessionName = employeeSessionName(targetAgent, exeSession);
6739
7451
  if (!isEmployeeAlive(sessionName)) {
@@ -7065,8 +7777,8 @@ async function runConsolidation(client, options) {
7065
7777
  if (clustersProcessed >= options.maxCalls) break;
7066
7778
  if (cluster.memories.length < 3) continue;
7067
7779
  try {
7068
- const isExe = cluster.agentId === "exe";
7069
- if (isExe) {
7780
+ const isCoordinator = cluster.agentId === "exe" || isCoordinatorName(cluster.agentId);
7781
+ if (isCoordinator) {
7070
7782
  const synthesis = await consolidateCluster(cluster, options.model);
7071
7783
  if (!synthesis.trim()) continue;
7072
7784
  const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
@@ -7095,7 +7807,7 @@ async function runConsolidation(client, options) {
7095
7807
  if (dedupCount === 0) continue;
7096
7808
  }
7097
7809
  clustersProcessed++;
7098
- memoriesConsolidated += isExe ? cluster.memories.length : 0;
7810
+ memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
7099
7811
  } catch (err) {
7100
7812
  process.stderr.write(
7101
7813
  `[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
@@ -7182,10 +7894,153 @@ var init_consolidation = __esm({
7182
7894
  "src/lib/consolidation.ts"() {
7183
7895
  "use strict";
7184
7896
  init_store();
7897
+ init_employees();
7185
7898
  WIKI_FETCH_TIMEOUT_MS = 1e4;
7186
7899
  }
7187
7900
  });
7188
7901
 
7902
+ // src/lib/wiki-client.ts
7903
+ var wiki_client_exports = {};
7904
+ __export(wiki_client_exports, {
7905
+ chatInWorkspace: () => chatInWorkspace,
7906
+ createWikiClient: () => createWikiClient,
7907
+ getChatHistory: () => getChatHistory,
7908
+ listDocuments: () => listDocuments,
7909
+ listWorkspaces: () => listWorkspaces
7910
+ });
7911
+ async function wikiFetch(config2, path30, method = "GET", body) {
7912
+ const url = `${config2.baseUrl}/api/v1${path30}`;
7913
+ const headers = {
7914
+ Authorization: `Bearer ${config2.apiKey}`,
7915
+ "Content-Type": "application/json"
7916
+ };
7917
+ const controller = new AbortController();
7918
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
7919
+ try {
7920
+ let response;
7921
+ try {
7922
+ response = await fetch(url, {
7923
+ method,
7924
+ headers,
7925
+ body: body ? JSON.stringify(body) : void 0,
7926
+ signal: controller.signal
7927
+ });
7928
+ } catch {
7929
+ clearTimeout(timeout);
7930
+ const retryController = new AbortController();
7931
+ const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
7932
+ try {
7933
+ await new Promise((r) => setTimeout(r, 500));
7934
+ response = await fetch(url, {
7935
+ method,
7936
+ headers,
7937
+ body: body ? JSON.stringify(body) : void 0,
7938
+ signal: retryController.signal
7939
+ });
7940
+ } finally {
7941
+ clearTimeout(retryTimeout);
7942
+ }
7943
+ }
7944
+ if (!response.ok) {
7945
+ throw new Error(`Wiki API ${method} ${path30}: ${response.status} ${response.statusText}`);
7946
+ }
7947
+ return response.json();
7948
+ } finally {
7949
+ clearTimeout(timeout);
7950
+ }
7951
+ }
7952
+ async function resolveWikiUrl(configUrl) {
7953
+ try {
7954
+ const controller = new AbortController();
7955
+ const timeout = setTimeout(() => controller.abort(), 3e3);
7956
+ const res = await fetch(`${LOCAL_WIKI_URL}/api/v1/auth`, { signal: controller.signal });
7957
+ clearTimeout(timeout);
7958
+ if (res.ok || res.status === 401 || res.status === 403) {
7959
+ return LOCAL_WIKI_URL;
7960
+ }
7961
+ } catch {
7962
+ }
7963
+ if (configUrl && configUrl !== LOCAL_WIKI_URL) {
7964
+ try {
7965
+ const controller = new AbortController();
7966
+ const timeout = setTimeout(() => controller.abort(), 3e3);
7967
+ const res = await fetch(`${configUrl}/api/v1/auth`, { signal: controller.signal });
7968
+ clearTimeout(timeout);
7969
+ if (res.ok || res.status === 401 || res.status === 403) {
7970
+ return configUrl;
7971
+ }
7972
+ } catch {
7973
+ }
7974
+ }
7975
+ return null;
7976
+ }
7977
+ async function createWikiClient() {
7978
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
7979
+ const config2 = await loadConfig2();
7980
+ const baseUrl = await resolveWikiUrl(config2.wikiUrl);
7981
+ if (!baseUrl) return null;
7982
+ return {
7983
+ baseUrl,
7984
+ apiKey: config2.wikiApiKey
7985
+ };
7986
+ }
7987
+ async function listWorkspaces(client) {
7988
+ const data = await wikiFetch(client, "/workspaces");
7989
+ return (data.workspaces ?? []).map((w) => ({
7990
+ slug: w.slug,
7991
+ name: w.name,
7992
+ createdAt: w.createdAt ?? "",
7993
+ threads: w.threads ?? []
7994
+ }));
7995
+ }
7996
+ async function listDocuments(client, workspaceSlug) {
7997
+ const data = await wikiFetch(
7998
+ client,
7999
+ `/workspace/${encodeURIComponent(workspaceSlug)}`
8000
+ );
8001
+ const docs = Array.isArray(data.workspace) ? data.workspace[0]?.documents ?? [] : [];
8002
+ return docs.map((d) => ({
8003
+ filename: d.filename ?? "untitled",
8004
+ docpath: d.docpath,
8005
+ workspace: workspaceSlug,
8006
+ pinned: d.pinned ?? false
8007
+ }));
8008
+ }
8009
+ async function chatInWorkspace(client, workspaceSlug, message, mode = "chat") {
8010
+ const data = await wikiFetch(
8011
+ client,
8012
+ `/workspace/${encodeURIComponent(workspaceSlug)}/chat`,
8013
+ "POST",
8014
+ { message, mode }
8015
+ );
8016
+ return {
8017
+ id: data.id ?? "",
8018
+ type: data.type ?? "textResponse",
8019
+ textResponse: data.textResponse ?? "",
8020
+ sources: data.sources ?? [],
8021
+ error: data.error ?? null
8022
+ };
8023
+ }
8024
+ async function getChatHistory(client, workspaceSlug, limit = 50) {
8025
+ const data = await wikiFetch(
8026
+ client,
8027
+ `/workspace/${encodeURIComponent(workspaceSlug)}/chats?limit=${limit}&orderBy=desc`
8028
+ );
8029
+ return (data.history ?? []).reverse().map((h) => ({
8030
+ role: h.role === "user" ? "user" : "assistant",
8031
+ content: h.content,
8032
+ sentAt: h.sentAt ?? 0
8033
+ }));
8034
+ }
8035
+ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
8036
+ var init_wiki_client = __esm({
8037
+ "src/lib/wiki-client.ts"() {
8038
+ "use strict";
8039
+ LOCAL_WIKI_URL = "http://localhost:3001";
8040
+ REQUEST_TIMEOUT_MS2 = 8e3;
8041
+ }
8042
+ });
8043
+
7189
8044
  // src/lib/worker-gate.ts
7190
8045
  var worker_gate_exports = {};
7191
8046
  __export(worker_gate_exports, {
@@ -7271,404 +8126,46 @@ function tryAcquireBackfillLock() {
7271
8126
  }
7272
8127
  }
7273
8128
  } catch {
7274
- }
7275
- }
7276
- writeFileSync13(BACKFILL_LOCK, String(process.pid));
7277
- return true;
7278
- } catch {
7279
- return true;
7280
- }
7281
- }
7282
- function releaseBackfillLock() {
7283
- try {
7284
- unlinkSync8(BACKFILL_LOCK);
7285
- } catch {
7286
- }
7287
- }
7288
- var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
7289
- var init_worker_gate = __esm({
7290
- "src/lib/worker-gate.ts"() {
7291
- "use strict";
7292
- init_config();
7293
- WORKER_PID_DIR = path28.join(EXE_AI_DIR, "worker-pids");
7294
- MAX_CONCURRENT_WORKERS = 3;
7295
- BACKFILL_LOCK = path28.join(WORKER_PID_DIR, "backfill.lock");
7296
- }
7297
- });
7298
-
7299
- // src/mcp/server.ts
7300
- init_embedder();
7301
- init_store();
7302
- init_database();
7303
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7304
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7305
- import { spawn as spawn2 } from "child_process";
7306
- import { existsSync as existsSync22, openSync as openSync2, mkdirSync as mkdirSync12, closeSync as closeSync2 } from "fs";
7307
- import path29 from "path";
7308
- import { fileURLToPath as fileURLToPath4 } from "url";
7309
-
7310
- // src/mcp/tools/recall-my-memory.ts
7311
- import { z } from "zod";
7312
-
7313
- // src/lib/hybrid-search.ts
7314
- init_store();
7315
- init_database();
7316
- var RRF_K = 60;
7317
- async function hybridSearch(queryText, agentId, options) {
7318
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
7319
- const config2 = await loadConfig2();
7320
- if (config2.searchMode === "fts") {
7321
- return lightweightSearch(queryText, agentId, options);
7322
- }
7323
- const limit = options?.limit ?? 10;
7324
- let effectiveQuery = queryText;
7325
- let effectiveOptions = { ...options };
7326
- let _isBroadQuery = false;
7327
- if (config2.selfQueryRouter && process.env.ANTHROPIC_API_KEY) {
7328
- try {
7329
- const { routeQuery: routeQuery2 } = await Promise.resolve().then(() => (init_self_query_router(), self_query_router_exports));
7330
- const routed = await routeQuery2(queryText, config2.selfQueryModel);
7331
- effectiveQuery = routed.semanticQuery;
7332
- _isBroadQuery = routed.isBroadQuery;
7333
- if (routed.projectFilter && !effectiveOptions.projectName) {
7334
- effectiveOptions.projectName = routed.projectFilter;
7335
- }
7336
- if (routed.timeFilter && !effectiveOptions.since) {
7337
- effectiveOptions.since = routed.timeFilter;
7338
- }
7339
- } catch {
7340
- }
7341
- }
7342
- const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
7343
- const cardinality = await getMemoryCardinality2(agentId);
7344
- const { rerankerAutoTrigger } = config2.scalingRoadmap ?? {};
7345
- const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
7346
- const useNarrowPath = cardinality < 1e4;
7347
- const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
7348
- const effectiveIsBroad = useBroadPath && !useNarrowPath;
7349
- if (effectiveIsBroad !== _isBroadQuery) {
7350
- process.stderr.write(
7351
- `[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
7352
- `
7353
- );
7354
- }
7355
- const broadFetchTopK = config2.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
7356
- const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
7357
- const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
7358
- let queryVector = null;
7359
- try {
7360
- const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
7361
- queryVector = await embed2(effectiveQuery);
7362
- } catch {
7363
- process.stderr.write("[hybrid-search] Embed daemon unavailable \u2014 FTS-only mode\n");
7364
- }
7365
- let grepPromise = Promise.resolve([]);
7366
- if (config2.fileGrepEnabled !== false) {
7367
- try {
7368
- const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
7369
- const projectRoot = process.cwd();
7370
- const projectName = getProjectName2(projectRoot);
7371
- if (projectName && projectName !== "tmp") {
7372
- const { grepProjectFiles: grepProjectFiles2 } = await Promise.resolve().then(() => (init_file_grep(), file_grep_exports));
7373
- grepPromise = grepProjectFiles2(effectiveQuery, projectRoot, {
7374
- maxResults: 10
7375
- }).catch(() => []);
7376
- }
7377
- } catch {
7378
- }
7379
- }
7380
- const [ftsResults, vectorResults, grepResults] = await Promise.all([
7381
- lightweightSearch(effectiveQuery, agentId, fetchOptions),
7382
- queryVector ? searchMemories(queryVector, agentId, fetchOptions) : Promise.resolve([]),
7383
- grepPromise
7384
- ]);
7385
- const lists = [];
7386
- const weights = [];
7387
- if (ftsResults.length > 0) {
7388
- lists.push(ftsResults);
7389
- weights.push(1);
7390
- }
7391
- if (vectorResults.length > 0) {
7392
- lists.push(vectorResults);
7393
- weights.push(1);
7394
- }
7395
- if (grepResults.length > 0) {
7396
- lists.push(grepResults);
7397
- weights.push(0.5);
7398
- }
7399
- if (lists.length === 0) return [];
7400
- if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
7401
- const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
7402
- const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
7403
- const auto = config2.scalingRoadmap?.rerankerAutoTrigger ?? {
7404
- enabled: config2.rerankerEnabled ?? true,
7405
- broadQueryMinCardinality: 5e4,
7406
- fetchTopK: 150,
7407
- returnTopK: 5
7408
- };
7409
- let rerankedAndBlended = null;
7410
- if (effectiveIsBroad && auto.enabled) {
7411
- const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
7412
- if (cardinality2 > auto.broadQueryMinCardinality) {
7413
- try {
7414
- const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
7415
- if (isRerankerAvailable2()) {
7416
- const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
7417
- if (reranked.length > 0) {
7418
- rerankedAndBlended = rrfMergeMulti(
7419
- [reranked],
7420
- auto.returnTopK,
7421
- RRF_K
7422
- );
7423
- }
7424
- }
7425
- } catch {
7426
- }
7427
- }
7428
- }
7429
- const finalResults = (rerankedAndBlended ?? merged).slice(
7430
- 0,
7431
- rerankedAndBlended ? auto.returnTopK : limit
7432
- );
7433
- if (options?.includeSource && finalResults.length > 0) {
7434
- await attachDocumentMetadata(finalResults);
7435
- }
7436
- if (finalResults.length > 0) {
7437
- const now = (/* @__PURE__ */ new Date()).toISOString();
7438
- const ids = finalResults.map((r) => r.id);
7439
- const placeholders = ids.map(() => "?").join(",");
7440
- try {
7441
- const client = getClient();
7442
- void client.execute({
7443
- sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
7444
- args: [now, ...ids]
7445
- }).catch(() => {
7446
- });
7447
- } catch {
7448
- }
7449
- }
7450
- return finalResults;
7451
- }
7452
- async function estimateCardinality(agentId, options) {
7453
- const client = getClient();
7454
- let sql = `SELECT COUNT(*) as cnt FROM memories
7455
- WHERE agent_id = ?
7456
- AND COALESCE(status, 'active') = 'active'
7457
- AND COALESCE(confidence, 0.7) >= 0.3`;
7458
- const args = [agentId];
7459
- if (options?.projectName) {
7460
- sql += ` AND project_name = ?`;
7461
- args.push(options.projectName);
7462
- }
7463
- if (options?.toolName) {
7464
- sql += ` AND tool_name = ?`;
7465
- args.push(options.toolName);
7466
- }
7467
- if (options?.hasError !== void 0) {
7468
- sql += ` AND has_error = ?`;
7469
- args.push(options.hasError ? 1 : 0);
7470
- }
7471
- if (options?.since) {
7472
- sql += ` AND timestamp >= ?`;
7473
- args.push(options.since);
7474
- }
7475
- try {
7476
- const result = await client.execute({ sql, args });
7477
- return Number(result.rows[0]?.cnt) || 0;
7478
- } catch {
7479
- return 0;
7480
- }
7481
- }
7482
- function recencyScore(timestamp) {
7483
- const daysSince = (Date.now() - new Date(timestamp).getTime()) / (1e3 * 60 * 60 * 24);
7484
- return 1 / (1 + daysSince * 0.01);
7485
- }
7486
- function normalizedImportance(importance) {
7487
- return ((importance ?? 5) - 1) / 9;
7488
- }
7489
- function frecencyBoost(lastAccessed, timestamp) {
7490
- const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
7491
- const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
7492
- return Math.exp(-0.01 * hoursSince);
7493
- }
7494
- function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
7495
- const scores = /* @__PURE__ */ new Map();
7496
- for (let listIdx = 0; listIdx < lists.length; listIdx++) {
7497
- const list = lists[listIdx];
7498
- const weight = weights?.[listIdx] ?? 1;
7499
- for (let i = 0; i < list.length; i++) {
7500
- const rec = list[i];
7501
- const entry = scores.get(rec.id) ?? { rrfScore: 0, record: rec };
7502
- entry.rrfScore += weight * (1 / (k + i + 1));
7503
- scores.set(rec.id, entry);
7504
- }
7505
- }
7506
- const entries = Array.from(scores.values()).map((e) => {
7507
- const recency = recencyScore(e.record.timestamp);
7508
- const importance = normalizedImportance(e.record.importance);
7509
- const confidence = e.record.confidence ?? 0.7;
7510
- const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
7511
- const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
7512
- const finalScore = baseScore * (1 + 0.3 * frecency);
7513
- return { score: finalScore, record: e.record };
7514
- });
7515
- return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
7516
- }
7517
- async function lightweightSearch(queryText, agentId, options) {
7518
- const client = getClient();
7519
- const limit = options?.limit ?? 5;
7520
- const terms = queryText.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_]/g, "")).filter((t) => t.length >= 3);
7521
- if (terms.length === 0) {
7522
- return recentRecords(agentId, options, limit);
7523
- }
7524
- const prefixTerms = terms.map((t) => `${t}*`);
7525
- const useAnd = terms.length >= 3;
7526
- const matchExpr = useAnd ? prefixTerms.join(" AND ") : prefixTerms.join(" OR ");
7527
- const results = await ftsQuery(client, matchExpr, agentId, options, limit);
7528
- if (useAnd && results.length < limit) {
7529
- const orExpr = prefixTerms.join(" OR ");
7530
- const orResults = await ftsQuery(client, orExpr, agentId, options, limit);
7531
- const seen = new Set(results.map((r) => r.id));
7532
- for (const r of orResults) {
7533
- if (!seen.has(r.id) && results.length < limit) {
7534
- results.push(r);
7535
- seen.add(r.id);
7536
- }
7537
- }
7538
- }
7539
- if (options?.includeSource && results.length > 0) {
7540
- await attachDocumentMetadata(results);
7541
- }
7542
- return results;
7543
- }
7544
- async function ftsQuery(client, matchExpr, agentId, options, limit) {
7545
- const statusFilter = options?.includeArchived ? "" : `
7546
- AND COALESCE(m.status, 'active') = 'active'`;
7547
- let sql = `SELECT m.id, m.agent_id, m.agent_role, m.session_id, m.timestamp,
7548
- m.tool_name, m.project_name,
7549
- m.has_error, m.raw_text, m.vector, m.task_id,
7550
- m.importance, m.status, m.confidence, m.last_accessed,
7551
- m.workspace_id, m.document_id, m.user_id,
7552
- m.char_offset, m.page_number,
7553
- m.source_path, m.source_type
7554
- FROM memories m
7555
- JOIN memories_fts fts ON m.rowid = fts.rowid
7556
- WHERE memories_fts MATCH ?
7557
- AND m.agent_id = ?${statusFilter}
7558
- AND COALESCE(m.confidence, 0.7) >= 0.3`;
7559
- const args = [matchExpr, agentId];
7560
- const scope = buildWikiScopeFilter(options, "m.");
7561
- sql += scope.clause;
7562
- args.push(...scope.args);
7563
- if (options?.projectName) {
7564
- sql += ` AND m.project_name = ?`;
7565
- args.push(options.projectName);
7566
- }
7567
- if (options?.toolName) {
7568
- sql += ` AND m.tool_name = ?`;
7569
- args.push(options.toolName);
7570
- }
7571
- if (options?.hasError !== void 0) {
7572
- sql += ` AND m.has_error = ?`;
7573
- args.push(options.hasError ? 1 : 0);
7574
- }
7575
- if (options?.since) {
7576
- sql += ` AND m.timestamp >= ?`;
7577
- args.push(options.since);
7578
- }
7579
- sql += ` ORDER BY rank LIMIT ?`;
7580
- args.push(limit);
7581
- const result = await client.execute({ sql, args });
7582
- return result.rows.map((row) => ({
7583
- id: row.id,
7584
- agent_id: row.agent_id,
7585
- agent_role: row.agent_role,
7586
- session_id: row.session_id,
7587
- timestamp: row.timestamp,
7588
- tool_name: row.tool_name,
7589
- project_name: row.project_name,
7590
- has_error: row.has_error === 1,
7591
- raw_text: row.raw_text,
7592
- vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
7593
- task_id: row.task_id ?? null,
7594
- importance: row.importance ?? 5,
7595
- status: row.status ?? "active",
7596
- confidence: row.confidence ?? 0.7,
7597
- last_accessed: row.last_accessed ?? row.timestamp,
7598
- workspace_id: row.workspace_id ?? null,
7599
- document_id: row.document_id ?? null,
7600
- user_id: row.user_id ?? null,
7601
- char_offset: row.char_offset ?? null,
7602
- page_number: row.page_number ?? null,
7603
- source_path: row.source_path ?? null,
7604
- source_type: row.source_type ?? null
7605
- }));
7606
- }
7607
- async function recentRecords(agentId, options, limit) {
7608
- const client = getClient();
7609
- const statusFilter = options?.includeArchived ? "" : `
7610
- AND COALESCE(status, 'active') = 'active'`;
7611
- let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
7612
- tool_name, project_name,
7613
- has_error, raw_text, vector, task_id,
7614
- importance, status, confidence, last_accessed,
7615
- workspace_id, document_id, user_id,
7616
- char_offset, page_number,
7617
- source_path, source_type
7618
- FROM memories
7619
- WHERE agent_id = ?${statusFilter}
7620
- AND COALESCE(confidence, 0.7) >= 0.3`;
7621
- const args = [agentId];
7622
- const scope = buildWikiScopeFilter(options, "");
7623
- sql += scope.clause;
7624
- args.push(...scope.args);
7625
- if (options?.projectName) {
7626
- sql += ` AND project_name = ?`;
7627
- args.push(options.projectName);
7628
- }
7629
- if (options?.toolName) {
7630
- sql += ` AND tool_name = ?`;
7631
- args.push(options.toolName);
7632
- }
7633
- if (options?.hasError !== void 0) {
7634
- sql += ` AND has_error = ?`;
7635
- args.push(options.hasError ? 1 : 0);
7636
- }
7637
- if (options?.since) {
7638
- sql += ` AND timestamp >= ?`;
7639
- args.push(options.since);
7640
- }
7641
- sql += ` ORDER BY timestamp DESC LIMIT ?`;
7642
- args.push(limit);
7643
- const result = await client.execute({ sql, args });
7644
- return result.rows.map((row) => ({
7645
- id: row.id,
7646
- agent_id: row.agent_id,
7647
- agent_role: row.agent_role,
7648
- session_id: row.session_id,
7649
- timestamp: row.timestamp,
7650
- tool_name: row.tool_name,
7651
- project_name: row.project_name,
7652
- has_error: row.has_error === 1,
7653
- raw_text: row.raw_text,
7654
- vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
7655
- task_id: row.task_id ?? null,
7656
- importance: row.importance ?? 5,
7657
- status: row.status ?? "active",
7658
- confidence: row.confidence ?? 0.7,
7659
- last_accessed: row.last_accessed ?? row.timestamp,
7660
- workspace_id: row.workspace_id ?? null,
7661
- document_id: row.document_id ?? null,
7662
- user_id: row.user_id ?? null,
7663
- char_offset: row.char_offset ?? null,
7664
- page_number: row.page_number ?? null,
7665
- source_path: row.source_path ?? null,
7666
- source_type: row.source_type ?? null
7667
- }));
8129
+ }
8130
+ }
8131
+ writeFileSync13(BACKFILL_LOCK, String(process.pid));
8132
+ return true;
8133
+ } catch {
8134
+ return true;
8135
+ }
8136
+ }
8137
+ function releaseBackfillLock() {
8138
+ try {
8139
+ unlinkSync8(BACKFILL_LOCK);
8140
+ } catch {
8141
+ }
7668
8142
  }
8143
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
8144
+ var init_worker_gate = __esm({
8145
+ "src/lib/worker-gate.ts"() {
8146
+ "use strict";
8147
+ init_config();
8148
+ WORKER_PID_DIR = path28.join(EXE_AI_DIR, "worker-pids");
8149
+ MAX_CONCURRENT_WORKERS = 3;
8150
+ BACKFILL_LOCK = path28.join(WORKER_PID_DIR, "backfill.lock");
8151
+ }
8152
+ });
8153
+
8154
+ // src/mcp/server.ts
8155
+ init_embedder();
8156
+ init_store();
8157
+ init_database();
8158
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8159
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8160
+ import { spawn as spawn2 } from "child_process";
8161
+ import { existsSync as existsSync22, openSync as openSync2, mkdirSync as mkdirSync12, closeSync as closeSync2 } from "fs";
8162
+ import path29 from "path";
8163
+ import { fileURLToPath as fileURLToPath4 } from "url";
7669
8164
 
7670
8165
  // src/mcp/tools/recall-my-memory.ts
8166
+ init_hybrid_search();
7671
8167
  init_active_agent();
8168
+ import { z } from "zod";
7672
8169
  function formatSourceLine(record) {
7673
8170
  const doc = record.document_metadata;
7674
8171
  if (!doc) return "";
@@ -7727,6 +8224,7 @@ function registerRecallMyMemory(server2) {
7727
8224
  includeArchived: include_archived,
7728
8225
  workspaceId: workspace_id,
7729
8226
  includeSource: include_source,
8227
+ includeDrafts: true,
7730
8228
  ...user_id !== void 0 ? { userId: user_id } : {}
7731
8229
  };
7732
8230
  const results = await hybridSearch(query, agentId, searchOptions);
@@ -7774,7 +8272,21 @@ ${formatted}`
7774
8272
  }
7775
8273
 
7776
8274
  // src/mcp/tools/ask-team-memory.ts
8275
+ init_hybrid_search();
8276
+ init_active_agent();
8277
+ init_employees();
7777
8278
  import { z as z2 } from "zod";
8279
+ function canSeeRaw(queryingRole, targetRole) {
8280
+ if (queryingRole === "COO") return true;
8281
+ if (queryingRole === "CTO" && [
8282
+ "Principal Engineer",
8283
+ "Staff Code Reviewer",
8284
+ "Content Production Specialist",
8285
+ "CMO"
8286
+ ].includes(targetRole))
8287
+ return true;
8288
+ return false;
8289
+ }
7778
8290
  function registerAskTeamMemory(server2) {
7779
8291
  server2.registerTool(
7780
8292
  "ask_team_memory",
@@ -7783,7 +8295,7 @@ function registerAskTeamMemory(server2) {
7783
8295
  description: "Search another employee's memories. Use this to find what a team member worked on, learned, or solved.",
7784
8296
  inputSchema: {
7785
8297
  team_member: z2.string().describe(
7786
- "Name of the team member to query (e.g., 'yoshi', 'mari', 'gen')"
8298
+ "Name of the team member to query"
7787
8299
  ),
7788
8300
  query: z2.string().describe("What to search for"),
7789
8301
  project_name: z2.string().optional().describe("Filter by project name"),
@@ -7791,16 +8303,28 @@ function registerAskTeamMemory(server2) {
7791
8303
  since: z2.string().optional().describe("ISO 8601 timestamp \u2014 only return memories at or after this time"),
7792
8304
  include_archived: z2.boolean().optional().default(false).describe(
7793
8305
  "Include deprecated (archived) memories alongside active ones. Default false."
8306
+ ),
8307
+ include_raw: z2.boolean().optional().default(false).describe(
8308
+ "Include raw technical memories (default: ADR summaries only for cross-agent reads)"
7794
8309
  )
7795
8310
  }
7796
8311
  },
7797
- async ({ team_member, query, project_name, limit, since, include_archived }) => {
8312
+ async ({ team_member, query, project_name, limit, since, include_archived, include_raw }) => {
7798
8313
  try {
8314
+ const { agentId: queryingAgentId, agentRole: queryingAgentRole } = getActiveAgent();
8315
+ const employees = loadEmployeesSync();
8316
+ const targetEmployee = getEmployee(employees, team_member);
8317
+ const queryingRole = queryingAgentRole ?? employees.find((e) => e.name === queryingAgentId)?.role ?? "";
8318
+ const targetRole = targetEmployee?.role ?? "";
8319
+ const hasRawAccess = canSeeRaw(queryingRole, targetRole);
8320
+ const effectiveIncludeRaw = include_raw && hasRawAccess;
7799
8321
  const results = await hybridSearch(query, team_member, {
7800
8322
  projectName: project_name,
7801
8323
  limit,
7802
8324
  since,
7803
- includeArchived: include_archived
8325
+ includeArchived: include_archived,
8326
+ includeDrafts: false,
8327
+ memoryType: effectiveIncludeRaw ? void 0 : "adr"
7804
8328
  });
7805
8329
  if (results.length === 0) {
7806
8330
  return {
@@ -8015,6 +8539,7 @@ init_embedder();
8015
8539
  init_store();
8016
8540
  init_plan_limits();
8017
8541
  init_active_agent();
8542
+ init_employees();
8018
8543
  import { z as z5 } from "zod";
8019
8544
  import crypto3 from "crypto";
8020
8545
  import { writeFileSync as writeFileSync5 } from "fs";
@@ -8067,6 +8592,7 @@ function registerCommitMemory(server2) {
8067
8592
  }
8068
8593
  const memoryId = crypto3.randomUUID();
8069
8594
  await assertMemoryLimit();
8595
+ const memoryType = canCoordinate(agentId, agentRole) ? "adr" : "raw";
8070
8596
  await writeMemory({
8071
8597
  id: memoryId,
8072
8598
  agent_id: agentId,
@@ -8080,7 +8606,8 @@ function registerCommitMemory(server2) {
8080
8606
  vector,
8081
8607
  importance: importanceOverride ?? 9,
8082
8608
  status: "active",
8083
- supersedes_id: supersedes_id ?? null
8609
+ supersedes_id: supersedes_id ?? null,
8610
+ memory_type: memoryType
8084
8611
  });
8085
8612
  await flushBatch();
8086
8613
  if (supersedes_id) {
@@ -8259,10 +8786,10 @@ function registerQueryRelationships(server2) {
8259
8786
  "query_relationships",
8260
8787
  {
8261
8788
  title: "Query Relationships",
8262
- description: "Query the knowledge graph for entity relationships, decision chains, and connections between concepts, people, and projects. Use this to answer relational questions like 'How did our auth strategy evolve?' or 'What did yoshi work on this week?'",
8789
+ description: "Query the knowledge graph for entity relationships, decision chains, and connections between concepts, people, and projects. Use this to answer relational questions like 'How did our auth strategy evolve?' or 'What did the CTO work on this week?'",
8263
8790
  inputSchema: {
8264
8791
  query: z6.string().optional().describe("What relationships to find (natural language)"),
8265
- entity_name: z6.string().optional().describe("Specific entity to explore (e.g., 'yoshi', 'exe-os', 'Jina v3')"),
8792
+ entity_name: z6.string().optional().describe("Specific entity to explore (for example an agent, project, or vendor name)"),
8266
8793
  relationship_type: z6.string().optional().describe("Filter by relationship type (e.g., 'implemented', 'depends_on', 'worked_on')"),
8267
8794
  max_depth: z6.number().default(2).describe("How many hops to traverse (default: 2)")
8268
8795
  }
@@ -8527,6 +9054,7 @@ ${lines.join("\n")}`
8527
9054
  init_tasks();
8528
9055
  init_database();
8529
9056
  init_tasks_crud();
9057
+ init_employees();
8530
9058
  import { z as z9 } from "zod";
8531
9059
  function registerUpdateTask(server2) {
8532
9060
  server2.registerTool(
@@ -8546,7 +9074,7 @@ function registerUpdateTask(server2) {
8546
9074
  try {
8547
9075
  const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
8548
9076
  const agent = getActiveAgent2();
8549
- if (agent.agentId !== "exe" && agent.agentId !== "default") {
9077
+ if (!canCoordinate(agent.agentId, agent.agentRole)) {
8550
9078
  let isOwnReview = false;
8551
9079
  try {
8552
9080
  const client = getClient();
@@ -8573,7 +9101,7 @@ function registerUpdateTask(server2) {
8573
9101
  if (assignedBy === "system" && taskFile.includes("review-")) {
8574
9102
  const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
8575
9103
  const agent = getActiveAgent2();
8576
- if (agent.agentId !== assignedTo && agent.agentId !== "exe" && agent.agentId !== "default") {
9104
+ if (agent.agentId !== assignedTo && !canCoordinate(agent.agentId, agent.agentRole)) {
8577
9105
  process.stderr.write(
8578
9106
  `[update_task] BLOCKED: ${agent.agentId} tried to close review "${String(row.title)}" assigned to ${assignedTo}
8579
9107
  `
@@ -8596,13 +9124,20 @@ function registerUpdateTask(server2) {
8596
9124
  };
8597
9125
  }
8598
9126
  }
9127
+ let callerAgentId;
9128
+ try {
9129
+ const { getActiveAgent: getAgent } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
9130
+ callerAgentId = getAgent().agentId;
9131
+ } catch {
9132
+ }
8599
9133
  let task;
8600
9134
  try {
8601
9135
  task = await updateTask({
8602
9136
  taskId: task_id,
8603
9137
  status,
8604
9138
  result,
8605
- baseDir: process.cwd()
9139
+ baseDir: process.cwd(),
9140
+ callerAgentId
8606
9141
  });
8607
9142
  } catch (err) {
8608
9143
  const msg = err instanceof Error ? err.message : String(err);
@@ -8651,8 +9186,9 @@ All tasks complete. No more open tasks in your queue.`;
8651
9186
  init_tasks();
8652
9187
  init_active_agent();
8653
9188
  init_tmux_routing();
9189
+ init_employees();
8654
9190
  import { z as z10 } from "zod";
8655
- var CLOSE_TASK_ALLOWED_ROLES = /* @__PURE__ */ new Set(["COO", "CTO"]);
9191
+ var CLOSE_TASK_ALLOWED_ROLES = /* @__PURE__ */ new Set(["CTO"]);
8656
9192
  function registerCloseTask(server2) {
8657
9193
  server2.registerTool(
8658
9194
  "close_task",
@@ -8667,12 +9203,13 @@ function registerCloseTask(server2) {
8667
9203
  },
8668
9204
  async ({ task_id, result, status }) => {
8669
9205
  const agent = getActiveAgent();
8670
- if (agent.agentId && agent.agentId !== "default" && !CLOSE_TASK_ALLOWED_ROLES.has(agent.agentRole ?? "")) {
9206
+ const canClose = canCoordinate(agent.agentId, agent.agentRole) || CLOSE_TASK_ALLOWED_ROLES.has(agent.agentRole ?? "");
9207
+ if (agent.agentId && !canClose) {
8671
9208
  return {
8672
9209
  content: [
8673
9210
  {
8674
9211
  type: "text",
8675
- text: `close_task is for reviewers only (exe). Use update_task with status "done" and your result summary to complete your work. This triggers a review task for exe.`
9212
+ text: `close_task is for reviewers and coordinators only. Use update_task with status "done" and your result summary to complete your work. This triggers a review task for the coordinator.`
8676
9213
  }
8677
9214
  ],
8678
9215
  isError: true
@@ -8691,7 +9228,7 @@ function registerCloseTask(server2) {
8691
9228
  return {
8692
9229
  content: [{
8693
9230
  type: "text",
8694
- text: `\u26A0\uFE0F Session scope mismatch: this task belongs to ${taskScope}, but you are ${mySession}. Reviews should be processed by the exe session that dispatched the task. Switch to ${taskScope} to review this task.`
9231
+ text: `\u26A0\uFE0F Session scope mismatch: this task belongs to ${taskScope}, but you are ${mySession}. Reviews should be processed by the coordinator session that dispatched the task. Switch to ${taskScope} to review this task.`
8695
9232
  }],
8696
9233
  isError: true
8697
9234
  };
@@ -8738,6 +9275,7 @@ All tasks complete. No more open tasks in your queue.`;
8738
9275
  // src/mcp/tools/get-task.ts
8739
9276
  init_tasks();
8740
9277
  init_database();
9278
+ init_employees();
8741
9279
  import { z as z11 } from "zod";
8742
9280
  function registerGetTask(server2) {
8743
9281
  server2.registerTool(
@@ -8817,13 +9355,13 @@ function registerGetTask(server2) {
8817
9355
  } catch {
8818
9356
  }
8819
9357
  }
8820
- if (String(row.assigned_to) !== "exe" && !String(row.title).startsWith("Review:") && String(row.status) !== "done" && String(row.status) !== "cancelled") {
9358
+ if (String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !String(row.title).startsWith("Review:") && String(row.status) !== "done" && String(row.status) !== "cancelled") {
8821
9359
  lines.push(
8822
9360
  "",
8823
9361
  "## MANDATORY: When done",
8824
9362
  "",
8825
9363
  'You MUST call update_task with status "done" and a result summary when finished.',
8826
- "If you skip this, exe will never know you're done and your work won't be reviewed.",
9364
+ "If you skip this, your reviewer will not know you're done and your work won't be reviewed.",
8827
9365
  "Do NOT let a failed commit or any error prevent you from calling update_task(done)."
8828
9366
  );
8829
9367
  }
@@ -8887,7 +9425,7 @@ function registerResumeEmployee(server2) {
8887
9425
  title: "Resume Employee",
8888
9426
  description: "Create or refresh a RESUME task after an employee hits context capacity. Dedupes against any existing active RESUME for the same agent so repeated capacity hits never produce duplicate rows. Preferred over raw create_task for the context-full handler.",
8889
9427
  inputSchema: {
8890
- agent_id: z13.string().describe("Employee name to resume (e.g. 'yoshi')"),
9428
+ agent_id: z13.string().describe("Employee name to resume"),
8891
9429
  project_name: z13.string().describe("Project name \u2014 used to namespace the task")
8892
9430
  }
8893
9431
  },
@@ -8895,9 +9433,9 @@ function registerResumeEmployee(server2) {
8895
9433
  const client = getClient();
8896
9434
  const reScope = sessionScopeFilter();
8897
9435
  const openTasks = await client.execute({
8898
- sql: `SELECT id, title, priority, task_file, status
9436
+ sql: `SELECT id, title, priority, task_file, status, assigned_tmux
8899
9437
  FROM tasks
8900
- WHERE assigned_to = ? AND status IN ('open', 'in_progress')${reScope.sql}
9438
+ WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'blocked')${reScope.sql}
8901
9439
  ORDER BY
8902
9440
  CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
8903
9441
  created_at ASC
@@ -8914,6 +9452,25 @@ function registerResumeEmployee(server2) {
8914
9452
  ]
8915
9453
  };
8916
9454
  }
9455
+ try {
9456
+ const { isTmuxSessionAlive: isTmuxSessionAlive2 } = await Promise.resolve().then(() => (init_tasks_crud(), tasks_crud_exports));
9457
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9458
+ for (const row of openTasks.rows) {
9459
+ const status = String(row.status);
9460
+ const assignedTmux = row.assigned_tmux != null ? String(row.assigned_tmux) : null;
9461
+ if (status === "in_progress" && assignedTmux && !isTmuxSessionAlive2(assignedTmux)) {
9462
+ await client.execute({
9463
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
9464
+ args: [now, String(row.id)]
9465
+ });
9466
+ process.stderr.write(
9467
+ `[resume_employee] Released dead claim on "${String(row.title)}" (was ${assignedTmux})
9468
+ `
9469
+ );
9470
+ }
9471
+ }
9472
+ } catch {
9473
+ }
8917
9474
  const { created, taskId } = await createOrRefreshResumeTask(
8918
9475
  agent_id,
8919
9476
  process.cwd(),
@@ -9002,7 +9559,7 @@ function registerSendMessage(server2) {
9002
9559
  title: "Send Message",
9003
9560
  description: "Send a structured message to another agent. Messages are queued and delivered via intercom. NOTE: messages are fire-and-forget \u2014 do NOT use for actionable work dispatch. Use create_task instead to assign work to employees.",
9004
9561
  inputSchema: {
9005
- target_agent: z15.string().describe("Recipient agent name (e.g., 'yoshi', 'tom', 'exe')"),
9562
+ target_agent: z15.string().describe("Recipient agent name"),
9006
9563
  content: z15.string().describe("Message content"),
9007
9564
  target_project: z15.string().optional().describe("Project context (optional)"),
9008
9565
  priority: z15.enum(["normal", "urgent"]).default("normal").describe("Message priority (default: normal)"),
@@ -9408,12 +9965,13 @@ ${identity.body}`
9408
9965
  // src/mcp/tools/update-identity.ts
9409
9966
  import { z as z21 } from "zod";
9410
9967
  init_active_agent();
9968
+ init_employees();
9411
9969
  function registerUpdateIdentity(server2) {
9412
9970
  server2.registerTool(
9413
9971
  "update_identity",
9414
9972
  {
9415
9973
  title: "Update Identity",
9416
- description: "Write an agent's exe.md identity. RESTRICTED: only exe or founder sessions can use this. Agents cannot rewrite their own identity. Content should be markdown with YAML frontmatter.",
9974
+ description: "Write an agent's exe.md identity. RESTRICTED: only coordinator or founder sessions can use this. Agents cannot rewrite their own identity. Content should be markdown with YAML frontmatter.",
9417
9975
  inputSchema: {
9418
9976
  agent_id: z21.string().describe("Target agent whose identity to write"),
9419
9977
  content: z21.string().describe(
@@ -9423,12 +9981,12 @@ function registerUpdateIdentity(server2) {
9423
9981
  },
9424
9982
  async ({ agent_id, content }) => {
9425
9983
  const caller = getActiveAgent();
9426
- const allowed = caller.agentId === "default" || caller.agentRole === "COO";
9984
+ const allowed = canCoordinate(caller.agentId, caller.agentRole);
9427
9985
  if (!allowed) {
9428
9986
  return {
9429
9987
  content: [{
9430
9988
  type: "text",
9431
- text: `Permission denied. Only exe or founder sessions can update identities. You are "${caller.agentId}". Agents cannot rewrite their own identity \u2014 this is an architectural constraint, not a bug.`
9989
+ text: `Permission denied. Only coordinator or founder sessions can update identities. You are "${caller.agentId}". Agents cannot rewrite their own identity \u2014 this is an architectural constraint, not a bug.`
9432
9990
  }],
9433
9991
  isError: true
9434
9992
  };
@@ -9459,25 +10017,26 @@ function registerUpdateIdentity(server2) {
9459
10017
  init_behaviors();
9460
10018
  init_active_agent();
9461
10019
  init_database();
10020
+ init_employees();
9462
10021
  import { z as z22 } from "zod";
9463
10022
  function registerDeactivateBehavior(server2) {
9464
10023
  server2.registerTool(
9465
10024
  "deactivate_behavior",
9466
10025
  {
9467
10026
  title: "Deactivate Behavior",
9468
- description: "Soft-delete a behavior by setting active = 0. RESTRICTED: only exe or founder sessions can use this. Use list_behaviors to find the behavior ID first.",
10027
+ description: "Soft-delete a behavior by setting active = 0. RESTRICTED: only coordinator or founder sessions can use this. Use list_behaviors to find the behavior ID first.",
9469
10028
  inputSchema: {
9470
10029
  behavior_id: z22.string().describe("UUID of the behavior to deactivate")
9471
10030
  }
9472
10031
  },
9473
10032
  async ({ behavior_id }) => {
9474
10033
  const caller = getActiveAgent();
9475
- const allowed = caller.agentId === "default" || caller.agentRole === "COO";
10034
+ const allowed = canCoordinate(caller.agentId, caller.agentRole);
9476
10035
  if (!allowed) {
9477
10036
  return {
9478
10037
  content: [{
9479
10038
  type: "text",
9480
- text: `Permission denied. Only exe or founder sessions can deactivate behaviors. You are "${caller.agentId}".`
10039
+ text: `Permission denied. Only the coordinator or founder sessions can deactivate behaviors. You are "${caller.agentId}".`
9481
10040
  }],
9482
10041
  isError: true
9483
10042
  };
@@ -9971,7 +10530,7 @@ function registerAcknowledgeMessages(server2) {
9971
10530
  },
9972
10531
  async () => {
9973
10532
  const agent = getActiveAgent();
9974
- const agentId = agent.agentId || "exe";
10533
+ const agentId = agent.agentId || "default";
9975
10534
  const client = getClient();
9976
10535
  const result = await client.execute({
9977
10536
  sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
@@ -12307,13 +12866,14 @@ Consolidated summaries stored as tier-1 (importance=9) memories.`
12307
12866
  // src/mcp/tools/store-global-procedure.ts
12308
12867
  init_global_procedures();
12309
12868
  init_active_agent();
12869
+ init_employees();
12310
12870
  import { z as z41 } from "zod";
12311
12871
  function registerStoreGlobalProcedure(server2) {
12312
12872
  server2.registerTool(
12313
12873
  "store_global_procedure",
12314
12874
  {
12315
12875
  title: "Store Global Procedure",
12316
- description: "Create an organization-wide procedure (Layer 0) that supersedes identity, expertise, and experience. Use for hard rules that every employee must follow. RESTRICTED: only exe or founder sessions.",
12876
+ description: "Create an organization-wide procedure (Layer 0) that supersedes identity, expertise, and experience. Use for hard rules that every employee must follow. RESTRICTED: only coordinator or founder sessions.",
12317
12877
  inputSchema: {
12318
12878
  title: z41.string().describe("Short title for the procedure"),
12319
12879
  content: z41.string().max(500).describe("The procedure content \u2014 clear, actionable instruction"),
@@ -12323,12 +12883,12 @@ function registerStoreGlobalProcedure(server2) {
12323
12883
  },
12324
12884
  async ({ title, content, priority, domain }) => {
12325
12885
  const caller = getActiveAgent();
12326
- const allowed = caller.agentId === "default" || caller.agentRole === "COO";
12886
+ const allowed = canCoordinate(caller.agentId, caller.agentRole);
12327
12887
  if (!allowed) {
12328
12888
  return {
12329
12889
  content: [{
12330
12890
  type: "text",
12331
- text: `Permission denied. Only exe or founder sessions can create global procedures. You are "${caller.agentId}".`
12891
+ text: `Permission denied. Only the coordinator or founder sessions can create global procedures. You are "${caller.agentId}".`
12332
12892
  }],
12333
12893
  isError: true
12334
12894
  };
@@ -12395,25 +12955,26 @@ ${lines.join("\n\n")}`
12395
12955
  init_global_procedures();
12396
12956
  init_active_agent();
12397
12957
  init_database();
12958
+ init_employees();
12398
12959
  import { z as z42 } from "zod";
12399
12960
  function registerDeactivateGlobalProcedure(server2) {
12400
12961
  server2.registerTool(
12401
12962
  "deactivate_global_procedure",
12402
12963
  {
12403
12964
  title: "Deactivate Global Procedure",
12404
- description: "Soft-delete a global procedure by setting active = 0. RESTRICTED: only exe or founder sessions. Use list_global_procedures to find the procedure ID first.",
12965
+ description: "Soft-delete a global procedure by setting active = 0. RESTRICTED: only coordinator or founder sessions. Use list_global_procedures to find the procedure ID first.",
12405
12966
  inputSchema: {
12406
12967
  procedure_id: z42.string().describe("UUID of the global procedure to deactivate")
12407
12968
  }
12408
12969
  },
12409
12970
  async ({ procedure_id }) => {
12410
12971
  const caller = getActiveAgent();
12411
- const allowed = caller.agentId === "default" || caller.agentRole === "COO";
12972
+ const allowed = canCoordinate(caller.agentId, caller.agentRole);
12412
12973
  if (!allowed) {
12413
12974
  return {
12414
12975
  content: [{
12415
12976
  type: "text",
12416
- text: `Permission denied. Only exe or founder sessions can deactivate global procedures. You are "${caller.agentId}".`
12977
+ text: `Permission denied. Only coordinator or founder sessions can deactivate global procedures. You are "${caller.agentId}".`
12417
12978
  }],
12418
12979
  isError: true
12419
12980
  };
@@ -12457,6 +13018,392 @@ Content: ${row.content}`
12457
13018
  );
12458
13019
  }
12459
13020
 
13021
+ // src/mcp/tools/search-everything.ts
13022
+ import { z as z43 } from "zod";
13023
+
13024
+ // src/lib/unified-search.ts
13025
+ var DEFAULT_LIMIT = 10;
13026
+ var MAX_SNIPPET = 500;
13027
+ var ALL_SOURCES = ["memory", "conversations", "wiki"];
13028
+ function truncate(text, max) {
13029
+ if (text.length <= max) return text;
13030
+ return text.slice(0, max - 1) + "\u2026";
13031
+ }
13032
+ async function searchMemories2(query, agentId, limit) {
13033
+ const { hybridSearch: hybridSearch2 } = await Promise.resolve().then(() => (init_hybrid_search(), hybrid_search_exports));
13034
+ const results = await hybridSearch2(query, agentId, { limit });
13035
+ return results.map((r, i) => ({
13036
+ source: "memory",
13037
+ score: 1 - i / Math.max(results.length, 1),
13038
+ // rank-based score
13039
+ timestamp: r.timestamp,
13040
+ snippet: truncate(r.raw_text, MAX_SNIPPET),
13041
+ entityLinks: [],
13042
+ metadata: {
13043
+ id: r.id,
13044
+ tool_name: r.tool_name,
13045
+ project_name: r.project_name,
13046
+ agent_id: r.agent_id,
13047
+ has_error: r.has_error
13048
+ }
13049
+ }));
13050
+ }
13051
+ async function searchConversations(query, limit) {
13052
+ try {
13053
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
13054
+ const client = getClient2();
13055
+ const result = await client.execute({
13056
+ sql: `SELECT c.id, c.platform, c.sender_id, c.sender_name,
13057
+ c.content_text, c.agent_response, c.agent_name, c.timestamp
13058
+ FROM conversations c
13059
+ WHERE c.rowid IN (SELECT rowid FROM conversations_fts WHERE conversations_fts MATCH ?)
13060
+ ORDER BY c.timestamp DESC
13061
+ LIMIT ?`,
13062
+ args: [query, limit]
13063
+ });
13064
+ return result.rows.map((row, i) => ({
13065
+ source: "conversations",
13066
+ score: 1 - i / Math.max(result.rows.length, 1),
13067
+ timestamp: row.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
13068
+ snippet: truncate(
13069
+ (row.content_text ?? "") + (row.agent_response ? ` \u2192 ${row.agent_response}` : ""),
13070
+ MAX_SNIPPET
13071
+ ),
13072
+ entityLinks: [],
13073
+ metadata: {
13074
+ id: row.id,
13075
+ platform: row.platform,
13076
+ sender_id: row.sender_id,
13077
+ sender_name: row.sender_name,
13078
+ agent_name: row.agent_name
13079
+ }
13080
+ }));
13081
+ } catch {
13082
+ return [];
13083
+ }
13084
+ }
13085
+ async function searchWiki(query, limit) {
13086
+ try {
13087
+ const { createWikiClient: createWikiClient2, chatInWorkspace: chatInWorkspace2, listWorkspaces: listWorkspaces2 } = await Promise.resolve().then(() => (init_wiki_client(), wiki_client_exports));
13088
+ const client = await createWikiClient2();
13089
+ if (!client) return [];
13090
+ const workspaces = await listWorkspaces2(client);
13091
+ if (workspaces.length === 0) return [];
13092
+ const results = [];
13093
+ const wsToQuery = workspaces.slice(0, 3);
13094
+ for (const ws of wsToQuery) {
13095
+ if (results.length >= limit) break;
13096
+ try {
13097
+ const response = await chatInWorkspace2(client, ws.slug, query, "query");
13098
+ if (response.textResponse && !response.error) {
13099
+ results.push({
13100
+ source: "wiki",
13101
+ score: 0.8,
13102
+ // wiki query mode relevance is implicit
13103
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13104
+ snippet: truncate(response.textResponse, MAX_SNIPPET),
13105
+ entityLinks: [],
13106
+ metadata: {
13107
+ workspace: ws.slug,
13108
+ workspace_name: ws.name,
13109
+ sources: response.sources.map((s) => s.title)
13110
+ }
13111
+ });
13112
+ }
13113
+ } catch {
13114
+ }
13115
+ }
13116
+ return results.slice(0, limit);
13117
+ } catch {
13118
+ return [];
13119
+ }
13120
+ }
13121
+ async function enrichWithEntities(results) {
13122
+ try {
13123
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
13124
+ const client = getClient2();
13125
+ const memoryIds = results.filter((r) => r.source === "memory" && r.metadata.id).map((r) => r.metadata.id);
13126
+ if (memoryIds.length === 0) return;
13127
+ const placeholders = memoryIds.map(() => "?").join(",");
13128
+ const entityResult = await client.execute({
13129
+ sql: `SELECT em.memory_id, e.name, e.entity_type
13130
+ FROM entity_memories em
13131
+ JOIN entities e ON e.id = em.entity_id
13132
+ WHERE em.memory_id IN (${placeholders})`,
13133
+ args: memoryIds
13134
+ });
13135
+ const entityMap = /* @__PURE__ */ new Map();
13136
+ for (const row of entityResult.rows) {
13137
+ const mid = row.memory_id;
13138
+ if (!entityMap.has(mid)) entityMap.set(mid, []);
13139
+ entityMap.get(mid).push({
13140
+ name: row.name,
13141
+ type: row.entity_type
13142
+ });
13143
+ }
13144
+ for (const r of results) {
13145
+ if (r.source === "memory" && r.metadata.id) {
13146
+ r.entityLinks = entityMap.get(r.metadata.id) ?? [];
13147
+ }
13148
+ }
13149
+ } catch {
13150
+ }
13151
+ }
13152
+ async function unifiedSearch(query, agentId, opts) {
13153
+ const limit = opts?.limit ?? DEFAULT_LIMIT;
13154
+ const sources = opts?.sources ?? ALL_SOURCES;
13155
+ const perSourceLimit = Math.max(Math.ceil(limit * 1.5), 5);
13156
+ const promises = [];
13157
+ if (sources.includes("memory")) {
13158
+ promises.push(searchMemories2(query, agentId, perSourceLimit));
13159
+ }
13160
+ if (sources.includes("conversations")) {
13161
+ promises.push(searchConversations(query, perSourceLimit));
13162
+ }
13163
+ if (sources.includes("wiki")) {
13164
+ promises.push(searchWiki(query, perSourceLimit));
13165
+ }
13166
+ const allResults = (await Promise.all(promises)).flat();
13167
+ const seen = /* @__PURE__ */ new Set();
13168
+ const deduped = allResults.filter((r) => {
13169
+ const key = r.snippet.slice(0, 100);
13170
+ if (seen.has(key)) return false;
13171
+ seen.add(key);
13172
+ return true;
13173
+ });
13174
+ deduped.sort((a, b) => b.score - a.score);
13175
+ const trimmed = deduped.slice(0, limit);
13176
+ await enrichWithEntities(trimmed);
13177
+ return trimmed;
13178
+ }
13179
+
13180
+ // src/mcp/tools/search-everything.ts
13181
+ init_active_agent();
13182
+ function registerSearchEverything(server2) {
13183
+ server2.registerTool(
13184
+ "search_everything",
13185
+ {
13186
+ title: "Search Everything",
13187
+ description: "Search across all data stores \u2014 memories, conversations, and wiki \u2014 in one query. Returns merged results sorted by relevance with entity links.",
13188
+ inputSchema: {
13189
+ query: z43.string().describe("What to search for across all data stores"),
13190
+ sources: z43.array(z43.enum(["memory", "conversations", "wiki"])).optional().describe(
13191
+ "Which sources to search (default: all). Options: memory, conversations, wiki"
13192
+ ),
13193
+ limit: z43.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10, max 50)")
13194
+ }
13195
+ },
13196
+ async (params) => {
13197
+ try {
13198
+ const { agentId } = getActiveAgent();
13199
+ const sources = params.sources;
13200
+ const results = await unifiedSearch(params.query, agentId, {
13201
+ limit: params.limit,
13202
+ sources
13203
+ });
13204
+ if (results.length === 0) {
13205
+ return {
13206
+ content: [
13207
+ {
13208
+ type: "text",
13209
+ text: "No results found across any data store."
13210
+ }
13211
+ ]
13212
+ };
13213
+ }
13214
+ const formatted = results.map((r) => {
13215
+ const entityStr = r.entityLinks.length > 0 ? ` [entities: ${r.entityLinks.map((e) => `${e.name} (${e.type})`).join(", ")}]` : "";
13216
+ return `[${r.source}] (${(r.score * 100).toFixed(0)}%) ${r.timestamp}
13217
+ ${r.snippet}${entityStr}`;
13218
+ });
13219
+ const sourceSummary = [...new Set(results.map((r) => r.source))].join(
13220
+ ", "
13221
+ );
13222
+ return {
13223
+ content: [
13224
+ {
13225
+ type: "text",
13226
+ text: `Found ${results.length} results across ${sourceSummary}:
13227
+
13228
+ ${formatted.join("\n\n---\n\n")}`
13229
+ }
13230
+ ]
13231
+ };
13232
+ } catch (err) {
13233
+ const msg = err instanceof Error ? err.message : String(err);
13234
+ return {
13235
+ content: [
13236
+ {
13237
+ type: "text",
13238
+ text: `Error searching: ${msg}`
13239
+ }
13240
+ ],
13241
+ isError: true
13242
+ };
13243
+ }
13244
+ }
13245
+ );
13246
+ }
13247
+
13248
+ // src/mcp/tools/store-decision.ts
13249
+ init_embedder();
13250
+ init_store();
13251
+ init_active_agent();
13252
+ init_database();
13253
+ init_plan_limits();
13254
+ import { z as z44 } from "zod";
13255
+ import crypto14 from "crypto";
13256
+ function registerStoreDecision(server2) {
13257
+ server2.registerTool(
13258
+ "store_decision",
13259
+ {
13260
+ title: "Store Decision",
13261
+ description: "Store an authoritative decision keyed by domain. Use this when a decision is made that should be canonical \u2014 future lookups via get_decision return the latest decision for that domain. Supports supersession chains.",
13262
+ inputSchema: {
13263
+ domain: z44.string().describe(
13264
+ "Domain key, e.g. 'auth-strategy', 'db-migration-approach', 'api-versioning'"
13265
+ ),
13266
+ decision: z44.string().describe("The decision text \u2014 what was decided"),
13267
+ rationale: z44.string().optional().describe("Why this decision was made \u2014 constraints, trade-offs, context"),
13268
+ supersedes: z44.string().optional().describe("UUID of the decision this supersedes (previous decision for this domain)"),
13269
+ project_name: z44.string().optional().describe("Project name")
13270
+ }
13271
+ },
13272
+ async ({ domain, decision, rationale, supersedes, project_name }) => {
13273
+ const { agentId, agentRole } = getActiveAgent();
13274
+ try {
13275
+ await assertMemoryLimit();
13276
+ } catch (err) {
13277
+ if (err instanceof PlanLimitError) {
13278
+ return {
13279
+ content: [{ type: "text", text: err.message }],
13280
+ isError: true
13281
+ };
13282
+ }
13283
+ }
13284
+ const parts = [`[decision:${domain}] ${decision}`];
13285
+ if (rationale) parts.push(`Rationale: ${rationale}`);
13286
+ const rawText = parts.join("\n\n");
13287
+ let vector;
13288
+ try {
13289
+ vector = await embed(rawText);
13290
+ } catch {
13291
+ vector = null;
13292
+ }
13293
+ const memoryId = crypto14.randomUUID();
13294
+ if (supersedes) {
13295
+ try {
13296
+ const client = getClient();
13297
+ await client.execute({
13298
+ sql: "UPDATE memories SET status = 'archived' WHERE id = ? AND memory_type = 'decision'",
13299
+ args: [supersedes]
13300
+ });
13301
+ } catch {
13302
+ }
13303
+ }
13304
+ await writeMemory({
13305
+ id: memoryId,
13306
+ agent_id: agentId,
13307
+ agent_role: agentRole,
13308
+ session_id: process.env.SESSION_ID ?? "manual",
13309
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13310
+ tool_name: "store_decision",
13311
+ project_name: project_name ?? "unknown",
13312
+ has_error: false,
13313
+ raw_text: rawText,
13314
+ vector,
13315
+ source_path: domain,
13316
+ memory_type: "decision",
13317
+ supersedes_id: supersedes ?? null,
13318
+ tier: 1,
13319
+ importance: 8
13320
+ });
13321
+ await flushBatch();
13322
+ return {
13323
+ content: [
13324
+ {
13325
+ type: "text",
13326
+ text: `Decision stored for domain "${domain}". ID: ${memoryId}${supersedes ? `
13327
+ Supersedes: ${supersedes}` : ""}`
13328
+ }
13329
+ ]
13330
+ };
13331
+ }
13332
+ );
13333
+ }
13334
+
13335
+ // src/mcp/tools/get-decision.ts
13336
+ init_database();
13337
+ import { z as z45 } from "zod";
13338
+ function registerGetDecision(server2) {
13339
+ server2.registerTool(
13340
+ "get_decision",
13341
+ {
13342
+ title: "Get Decision",
13343
+ description: "Retrieve the latest authoritative decision for a domain. Returns the current active decision and the supersession history.",
13344
+ inputSchema: {
13345
+ domain: z45.string().describe(
13346
+ "Domain key to look up, e.g. 'auth-strategy', 'db-migration-approach'"
13347
+ )
13348
+ }
13349
+ },
13350
+ async ({ domain }) => {
13351
+ const client = getClient();
13352
+ const result = await client.execute({
13353
+ sql: `SELECT id, agent_id, timestamp, raw_text, status, supersedes_id, project_name
13354
+ FROM memories
13355
+ WHERE memory_type = 'decision' AND source_path = ?
13356
+ ORDER BY timestamp DESC`,
13357
+ args: [domain]
13358
+ });
13359
+ if (result.rows.length === 0) {
13360
+ return {
13361
+ content: [
13362
+ {
13363
+ type: "text",
13364
+ text: `No decisions found for domain "${domain}".`
13365
+ }
13366
+ ]
13367
+ };
13368
+ }
13369
+ const decisions = result.rows.map((r) => ({
13370
+ id: String(r.id),
13371
+ agent_id: String(r.agent_id),
13372
+ timestamp: String(r.timestamp),
13373
+ raw_text: String(r.raw_text),
13374
+ status: String(r.status ?? "active"),
13375
+ supersedes_id: r.supersedes_id ? String(r.supersedes_id) : null,
13376
+ project_name: String(r.project_name)
13377
+ }));
13378
+ const current = decisions.find((d) => d.status === "active") ?? decisions[0];
13379
+ const history = decisions.filter((d) => d.id !== current.id);
13380
+ const lines = [];
13381
+ lines.push(`## Current Decision: ${domain}`);
13382
+ lines.push(`ID: ${current.id}`);
13383
+ lines.push(`By: ${current.agent_id} | ${current.timestamp}`);
13384
+ lines.push(`Project: ${current.project_name}`);
13385
+ lines.push(`Status: ${current.status}`);
13386
+ lines.push("");
13387
+ lines.push(current.raw_text);
13388
+ if (history.length > 0) {
13389
+ lines.push("");
13390
+ lines.push(`## Supersession History (${history.length} prior)`);
13391
+ for (const h of history) {
13392
+ lines.push("");
13393
+ lines.push(`---`);
13394
+ lines.push(`ID: ${h.id} | ${h.timestamp} | ${h.status}`);
13395
+ lines.push(`By: ${h.agent_id}`);
13396
+ if (h.supersedes_id) lines.push(`Supersedes: ${h.supersedes_id}`);
13397
+ lines.push(h.raw_text);
13398
+ }
13399
+ }
13400
+ return {
13401
+ content: [{ type: "text", text: lines.join("\n") }]
13402
+ };
13403
+ }
13404
+ );
13405
+ }
13406
+
12460
13407
  // src/lib/telemetry.ts
12461
13408
  var ENABLED = process.env.EXE_TELEMETRY === "1";
12462
13409
  var initialized = false;
@@ -12519,6 +13466,8 @@ var server = new McpServer({
12519
13466
  version: "1.3.0"
12520
13467
  });
12521
13468
  var _backfillTimer = null;
13469
+ var _ppidWatchdog = null;
13470
+ var _shuttingDown = false;
12522
13471
  instrumentServer(server);
12523
13472
  registerRecallMyMemory(server);
12524
13473
  registerAskTeamMemory(server);
@@ -12564,6 +13513,9 @@ registerConsolidateMemories(server);
12564
13513
  registerStoreGlobalProcedure(server);
12565
13514
  registerListGlobalProcedures(server);
12566
13515
  registerDeactivateGlobalProcedure(server);
13516
+ registerSearchEverything(server);
13517
+ registerStoreDecision(server);
13518
+ registerGetDecision(server);
12567
13519
  try {
12568
13520
  await initStore();
12569
13521
  process.stderr.write("[exe-os] MCP server starting...\n");
@@ -12579,6 +13531,31 @@ try {
12579
13531
  const transport = new StdioServerTransport();
12580
13532
  await server.connect(transport);
12581
13533
  process.stderr.write("[exe-os] MCP server connected.\n");
13534
+ process.stdin.on("end", () => {
13535
+ process.stderr.write("[exe-os] stdin closed \u2014 parent process died. Exiting.\n");
13536
+ void shutdown("stdin_end");
13537
+ });
13538
+ process.stdin.on("close", () => {
13539
+ process.stderr.write("[exe-os] stdin pipe closed. Exiting.\n");
13540
+ void shutdown("stdin_close");
13541
+ });
13542
+ process.stdout.on("error", (err) => {
13543
+ if (err.code === "EPIPE") {
13544
+ process.stderr.write("[exe-os] stdout EPIPE \u2014 client disconnected. Exiting.\n");
13545
+ void shutdown("epipe");
13546
+ }
13547
+ });
13548
+ const originalPpid = process.ppid;
13549
+ _ppidWatchdog = setInterval(() => {
13550
+ if (process.ppid !== originalPpid) {
13551
+ process.stderr.write(
13552
+ `[exe-os] Parent PID changed (${originalPpid} \u2192 ${process.ppid}). Orphaned \u2014 exiting.
13553
+ `
13554
+ );
13555
+ void shutdown("ppid_changed");
13556
+ }
13557
+ }, 3e4);
13558
+ _ppidWatchdog.unref();
12582
13559
  const BACKFILL_CHECK_MS = 5 * 60 * 1e3;
12583
13560
  _backfillTimer = setInterval(async () => {
12584
13561
  try {
@@ -12638,14 +13615,31 @@ try {
12638
13615
  );
12639
13616
  process.exit(1);
12640
13617
  }
12641
- async function shutdown() {
13618
+ async function shutdown(reason = "signal") {
13619
+ if (_shuttingDown) return;
13620
+ _shuttingDown = true;
13621
+ process.stderr.write(`[exe-os] Shutdown initiated (reason: ${reason})
13622
+ `);
12642
13623
  if (_backfillTimer) clearInterval(_backfillTimer);
12643
- await disposeStore();
12644
- await disposeEmbedder();
13624
+ if (_ppidWatchdog) clearInterval(_ppidWatchdog);
13625
+ const hardExit = setTimeout(() => {
13626
+ process.stderr.write("[exe-os] Shutdown timeout (5s) \u2014 forcing exit.\n");
13627
+ process.exit(1);
13628
+ }, 5e3);
13629
+ hardExit.unref();
13630
+ try {
13631
+ await disposeStore();
13632
+ await disposeEmbedder();
13633
+ } catch (err) {
13634
+ process.stderr.write(
13635
+ `[exe-os] Shutdown cleanup error: ${err instanceof Error ? err.message : String(err)}
13636
+ `
13637
+ );
13638
+ }
12645
13639
  process.exit(0);
12646
13640
  }
12647
- process.on("SIGINT", () => void shutdown());
12648
- process.on("SIGTERM", () => void shutdown());
13641
+ process.on("SIGINT", () => void shutdown("SIGINT"));
13642
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
12649
13643
  process.on("uncaughtException", (err) => {
12650
13644
  process.stderr.write(
12651
13645
  `[exe-os] uncaughtException (non-fatal): ${err.message}