@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
@@ -30,7 +30,6 @@ var config_exports = {};
30
30
  __export(config_exports, {
31
31
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
32
32
  CONFIG_PATH: () => CONFIG_PATH,
33
- COO_AGENT_NAME: () => COO_AGENT_NAME,
34
33
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
35
34
  DB_PATH: () => DB_PATH,
36
35
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -186,7 +185,7 @@ async function loadConfigFrom(configPath) {
186
185
  return { ...DEFAULT_CONFIG };
187
186
  }
188
187
  }
189
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
188
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
190
189
  var init_config = __esm({
191
190
  "src/lib/config.ts"() {
192
191
  "use strict";
@@ -194,7 +193,6 @@ var init_config = __esm({
194
193
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
195
194
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
196
195
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
197
- COO_AGENT_NAME = "exe";
198
196
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
199
197
  CURRENT_CONFIG_VERSION = 1;
200
198
  DEFAULT_CONFIG = {
@@ -230,13 +228,7 @@ var init_config = __esm({
230
228
  wikiUrl: "",
231
229
  wikiApiKey: "",
232
230
  wikiSyncIntervalMs: 30 * 60 * 1e3,
233
- wikiWorkspaceMapping: {
234
- exe: "Executive",
235
- yoshi: "Engineering",
236
- mari: "Marketing",
237
- tom: "Engineering",
238
- sasha: "Production"
239
- },
231
+ wikiWorkspaceMapping: {},
240
232
  wikiAutoUpdate: true,
241
233
  wikiAutoUpdateThreshold: 0.5,
242
234
  wikiAutoUpdateCreateNew: true,
@@ -321,7 +313,7 @@ function wrapWithRetry(client) {
321
313
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
322
314
  }
323
315
  if (prop === "batch") {
324
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
316
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
325
317
  }
326
318
  return Reflect.get(target, prop, receiver);
327
319
  }
@@ -337,6 +329,64 @@ var init_db_retry = __esm({
337
329
  }
338
330
  });
339
331
 
332
+ // src/lib/employees.ts
333
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
334
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
335
+ import { execSync } from "child_process";
336
+ import path2 from "path";
337
+ import os2 from "os";
338
+ function normalizeRole(role) {
339
+ return (role ?? "").trim().toLowerCase();
340
+ }
341
+ function isCoordinatorRole(role) {
342
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
343
+ }
344
+ function getCoordinatorEmployee(employees) {
345
+ return employees.find((e) => isCoordinatorRole(e.role));
346
+ }
347
+ function getCoordinatorName(employees = loadEmployeesSync()) {
348
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
349
+ }
350
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
351
+ if (!agentName) return false;
352
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
353
+ }
354
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
355
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
356
+ }
357
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
358
+ if (!existsSync2(employeesPath)) {
359
+ return [];
360
+ }
361
+ const raw = await readFile2(employeesPath, "utf-8");
362
+ try {
363
+ return JSON.parse(raw);
364
+ } catch {
365
+ return [];
366
+ }
367
+ }
368
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
369
+ if (!existsSync2(employeesPath)) return [];
370
+ try {
371
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
372
+ } catch {
373
+ return [];
374
+ }
375
+ }
376
+ function getEmployee(employees, name) {
377
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
378
+ }
379
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
380
+ var init_employees = __esm({
381
+ "src/lib/employees.ts"() {
382
+ "use strict";
383
+ init_config();
384
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
385
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
386
+ COORDINATOR_ROLE = "COO";
387
+ }
388
+ });
389
+
340
390
  // src/lib/database.ts
341
391
  import { createClient } from "@libsql/client";
342
392
  async function initDatabase(config) {
@@ -470,22 +520,24 @@ async function ensureSchema() {
470
520
  ON behaviors(agent_id, active);
471
521
  `);
472
522
  try {
523
+ const coordinatorName = getCoordinatorName();
473
524
  const existing = await client.execute({
474
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
475
- args: []
525
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
526
+ args: [coordinatorName]
476
527
  });
477
528
  if (Number(existing.rows[0]?.cnt) === 0) {
478
- await client.executeMultiple(`
479
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
480
- VALUES
481
- (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');
482
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
483
- VALUES
484
- (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');
485
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
486
- VALUES
487
- (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');
488
- `);
529
+ const seededAt = "2026-03-25T00:00:00Z";
530
+ for (const [domain, content] of [
531
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
532
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
533
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
534
+ ]) {
535
+ await client.execute({
536
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
537
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
538
+ args: [coordinatorName, domain, content, seededAt, seededAt]
539
+ });
540
+ }
489
541
  }
490
542
  } catch {
491
543
  }
@@ -1177,6 +1229,39 @@ async function ensureSchema() {
1177
1229
  } catch {
1178
1230
  }
1179
1231
  }
1232
+ try {
1233
+ await client.execute({
1234
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1235
+ args: []
1236
+ });
1237
+ } catch {
1238
+ }
1239
+ try {
1240
+ await client.execute(
1241
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1242
+ );
1243
+ } catch {
1244
+ }
1245
+ try {
1246
+ await client.execute({
1247
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1248
+ args: []
1249
+ });
1250
+ } catch {
1251
+ }
1252
+ try {
1253
+ await client.execute(
1254
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1255
+ );
1256
+ } catch {
1257
+ }
1258
+ try {
1259
+ await client.execute({
1260
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1261
+ args: []
1262
+ });
1263
+ } catch {
1264
+ }
1180
1265
  }
1181
1266
  async function disposeDatabase() {
1182
1267
  if (_client) {
@@ -1190,6 +1275,7 @@ var init_database = __esm({
1190
1275
  "src/lib/database.ts"() {
1191
1276
  "use strict";
1192
1277
  init_db_retry();
1278
+ init_employees();
1193
1279
  _client = null;
1194
1280
  _resilientClient = null;
1195
1281
  initTurso = initDatabase;
@@ -1198,15 +1284,15 @@ var init_database = __esm({
1198
1284
  });
1199
1285
 
1200
1286
  // src/lib/keychain.ts
1201
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
1202
- import { existsSync as existsSync2 } from "fs";
1203
- import path2 from "path";
1204
- import os2 from "os";
1287
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1288
+ import { existsSync as existsSync3 } from "fs";
1289
+ import path3 from "path";
1290
+ import os3 from "os";
1205
1291
  function getKeyDir() {
1206
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(os2.homedir(), ".exe-os");
1292
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
1207
1293
  }
1208
1294
  function getKeyPath() {
1209
- return path2.join(getKeyDir(), "master.key");
1295
+ return path3.join(getKeyDir(), "master.key");
1210
1296
  }
1211
1297
  async function tryKeytar() {
1212
1298
  try {
@@ -1227,11 +1313,11 @@ async function getMasterKey() {
1227
1313
  }
1228
1314
  }
1229
1315
  const keyPath = getKeyPath();
1230
- if (!existsSync2(keyPath)) {
1316
+ if (!existsSync3(keyPath)) {
1231
1317
  return null;
1232
1318
  }
1233
1319
  try {
1234
- const content = await readFile2(keyPath, "utf-8");
1320
+ const content = await readFile3(keyPath, "utf-8");
1235
1321
  return Buffer.from(content.trim(), "base64");
1236
1322
  } catch {
1237
1323
  return null;
@@ -1314,12 +1400,12 @@ __export(shard_manager_exports, {
1314
1400
  listShards: () => listShards,
1315
1401
  shardExists: () => shardExists
1316
1402
  });
1317
- import path3 from "path";
1318
- import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
1403
+ import path4 from "path";
1404
+ import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1319
1405
  import { createClient as createClient2 } from "@libsql/client";
1320
1406
  function initShardManager(encryptionKey) {
1321
1407
  _encryptionKey = encryptionKey;
1322
- if (!existsSync3(SHARDS_DIR)) {
1408
+ if (!existsSync4(SHARDS_DIR)) {
1323
1409
  mkdirSync(SHARDS_DIR, { recursive: true });
1324
1410
  }
1325
1411
  _shardingEnabled = true;
@@ -1340,7 +1426,7 @@ function getShardClient(projectName) {
1340
1426
  }
1341
1427
  const cached = _shards.get(safeName);
1342
1428
  if (cached) return cached;
1343
- const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
1429
+ const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
1344
1430
  const client = createClient2({
1345
1431
  url: `file:${dbPath}`,
1346
1432
  encryptionKey: _encryptionKey
@@ -1350,10 +1436,10 @@ function getShardClient(projectName) {
1350
1436
  }
1351
1437
  function shardExists(projectName) {
1352
1438
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1353
- return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
1439
+ return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
1354
1440
  }
1355
1441
  function listShards() {
1356
- if (!existsSync3(SHARDS_DIR)) return [];
1442
+ if (!existsSync4(SHARDS_DIR)) return [];
1357
1443
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1358
1444
  }
1359
1445
  async function ensureShardSchema(client) {
@@ -1423,7 +1509,11 @@ async function ensureShardSchema(client) {
1423
1509
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
1424
1510
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1425
1511
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1426
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1512
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
1513
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1514
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1515
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1516
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
1427
1517
  ]) {
1428
1518
  try {
1429
1519
  await client.execute(col);
@@ -1535,7 +1625,7 @@ var init_shard_manager = __esm({
1535
1625
  "src/lib/shard-manager.ts"() {
1536
1626
  "use strict";
1537
1627
  init_config();
1538
- SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
1628
+ SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
1539
1629
  _shards = /* @__PURE__ */ new Map();
1540
1630
  _encryptionKey = null;
1541
1631
  _shardingEnabled = false;
@@ -1553,26 +1643,26 @@ var init_platform_procedures = __esm({
1553
1643
  title: "What is exe-os \u2014 the operating model every agent must understand",
1554
1644
  domain: "architecture",
1555
1645
  priority: "p0",
1556
- 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."
1646
+ 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."
1557
1647
  },
1558
1648
  {
1559
1649
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
1560
1650
  domain: "architecture",
1561
1651
  priority: "p0",
1562
- 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."
1652
+ 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."
1563
1653
  },
1564
1654
  {
1565
- title: "Sessions explained \u2014 what exeN means and how projects work",
1655
+ title: "Sessions explained \u2014 coordinator session names and projects",
1566
1656
  domain: "architecture",
1567
1657
  priority: "p0",
1568
- 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."
1658
+ 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."
1569
1659
  },
1570
1660
  // --- Hierarchy and dispatch ---
1571
1661
  {
1572
1662
  title: "Chain of command \u2014 who talks to whom",
1573
1663
  domain: "workflow",
1574
1664
  priority: "p0",
1575
- 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."
1665
+ 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."
1576
1666
  },
1577
1667
  {
1578
1668
  title: "Single dispatch path \u2014 create_task only",
@@ -1582,30 +1672,30 @@ var init_platform_procedures = __esm({
1582
1672
  },
1583
1673
  // --- Session isolation ---
1584
1674
  {
1585
- title: "Session scoping \u2014 stay in your exe boundary",
1675
+ title: "Session scoping \u2014 stay in your coordinator boundary",
1586
1676
  domain: "security",
1587
1677
  priority: "p0",
1588
- 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."
1678
+ 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."
1589
1679
  },
1590
1680
  {
1591
1681
  title: "Session isolation \u2014 never touch another session's work",
1592
1682
  domain: "workflow",
1593
1683
  priority: "p0",
1594
- 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.`
1684
+ 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."
1595
1685
  },
1596
1686
  // --- Engineering: session scoping in code ---
1597
1687
  {
1598
1688
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
1599
1689
  domain: "architecture",
1600
1690
  priority: "p0",
1601
- 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."
1691
+ 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."
1602
1692
  },
1603
1693
  // --- Hard constraints ---
1604
1694
  {
1605
1695
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
1606
1696
  domain: "security",
1607
1697
  priority: "p0",
1608
- 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."
1698
+ 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."
1609
1699
  },
1610
1700
  // --- Operations ---
1611
1701
  {
@@ -1845,7 +1935,10 @@ async function writeMemory(record) {
1845
1935
  source_path: record.source_path ?? null,
1846
1936
  source_type: record.source_type ?? null,
1847
1937
  tier: record.tier ?? classifyTier(record),
1848
- supersedes_id: record.supersedes_id ?? null
1938
+ supersedes_id: record.supersedes_id ?? null,
1939
+ draft: record.draft ? 1 : 0,
1940
+ memory_type: record.memory_type ?? "raw",
1941
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
1849
1942
  };
1850
1943
  _pendingRecords.push(dbRow);
1851
1944
  orgBus.emit({
@@ -1900,6 +1993,9 @@ async function flushBatch() {
1900
1993
  const sourceType = row.source_type ?? null;
1901
1994
  const tier = row.tier ?? 3;
1902
1995
  const supersedesId = row.supersedes_id ?? null;
1996
+ const draft = row.draft ? 1 : 0;
1997
+ const memoryType = row.memory_type ?? "raw";
1998
+ const trajectory = row.trajectory ?? null;
1903
1999
  return {
1904
2000
  sql: hasVector ? `INSERT OR IGNORE INTO memories
1905
2001
  (id, agent_id, agent_role, session_id, timestamp,
@@ -1907,15 +2003,15 @@ async function flushBatch() {
1907
2003
  has_error, raw_text, vector, version, task_id, importance, status,
1908
2004
  confidence, last_accessed,
1909
2005
  workspace_id, document_id, user_id, char_offset, page_number,
1910
- source_path, source_type, tier, supersedes_id)
1911
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
2006
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2007
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1912
2008
  (id, agent_id, agent_role, session_id, timestamp,
1913
2009
  tool_name, project_name,
1914
2010
  has_error, raw_text, vector, version, task_id, importance, status,
1915
2011
  confidence, last_accessed,
1916
2012
  workspace_id, document_id, user_id, char_offset, page_number,
1917
- source_path, source_type, tier, supersedes_id)
1918
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2013
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2014
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1919
2015
  args: hasVector ? [
1920
2016
  row.id,
1921
2017
  row.agent_id,
@@ -1941,7 +2037,10 @@ async function flushBatch() {
1941
2037
  sourcePath,
1942
2038
  sourceType,
1943
2039
  tier,
1944
- supersedesId
2040
+ supersedesId,
2041
+ draft,
2042
+ memoryType,
2043
+ trajectory
1945
2044
  ] : [
1946
2045
  row.id,
1947
2046
  row.agent_id,
@@ -1966,7 +2065,10 @@ async function flushBatch() {
1966
2065
  sourcePath,
1967
2066
  sourceType,
1968
2067
  tier,
1969
- supersedesId
2068
+ supersedesId,
2069
+ draft,
2070
+ memoryType,
2071
+ trajectory
1970
2072
  ]
1971
2073
  };
1972
2074
  };
@@ -2035,6 +2137,8 @@ async function searchMemories(queryVector, agentId, options) {
2035
2137
  const limit = options?.limit ?? 10;
2036
2138
  const statusFilter = options?.includeArchived ? "" : `
2037
2139
  AND COALESCE(status, 'active') = 'active'`;
2140
+ const draftFilter = options?.includeDrafts ? "" : `
2141
+ AND (draft = 0 OR draft IS NULL)`;
2038
2142
  let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
2039
2143
  tool_name, project_name,
2040
2144
  has_error, raw_text, vector, importance, status,
@@ -2044,7 +2148,7 @@ async function searchMemories(queryVector, agentId, options) {
2044
2148
  source_path, source_type
2045
2149
  FROM memories
2046
2150
  WHERE agent_id = ?
2047
- AND vector IS NOT NULL${statusFilter}
2151
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
2048
2152
  AND COALESCE(confidence, 0.7) >= 0.3`;
2049
2153
  const args = [agentId];
2050
2154
  const scope = buildWikiScopeFilter(options, "");
@@ -2066,6 +2170,10 @@ async function searchMemories(queryVector, agentId, options) {
2066
2170
  sql += ` AND timestamp >= ?`;
2067
2171
  args.push(options.since);
2068
2172
  }
2173
+ if (options?.memoryType) {
2174
+ sql += ` AND memory_type = ?`;
2175
+ args.push(options.memoryType);
2176
+ }
2069
2177
  sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
2070
2178
  args.push(vectorToBlob(queryVector));
2071
2179
  sql += ` LIMIT ?`;
@@ -2291,7 +2399,7 @@ var init_self_query_router = __esm({
2291
2399
  },
2292
2400
  is_broad_query: {
2293
2401
  type: "boolean",
2294
- 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')."
2402
+ 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')."
2295
2403
  }
2296
2404
  },
2297
2405
  required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
@@ -2304,8 +2412,8 @@ var init_self_query_router = __esm({
2304
2412
  import net from "net";
2305
2413
  import { spawn } from "child_process";
2306
2414
  import { randomUUID as randomUUID2 } from "crypto";
2307
- import { existsSync as existsSync4, unlinkSync, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
2308
- import path4 from "path";
2415
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2416
+ import path5 from "path";
2309
2417
  import { fileURLToPath } from "url";
2310
2418
  function handleData(chunk) {
2311
2419
  _buffer += chunk.toString();
@@ -2331,9 +2439,9 @@ function handleData(chunk) {
2331
2439
  }
2332
2440
  }
2333
2441
  function cleanupStaleFiles() {
2334
- if (existsSync4(PID_PATH)) {
2442
+ if (existsSync5(PID_PATH)) {
2335
2443
  try {
2336
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
2444
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2337
2445
  if (pid > 0) {
2338
2446
  try {
2339
2447
  process.kill(pid, 0);
@@ -2344,21 +2452,21 @@ function cleanupStaleFiles() {
2344
2452
  } catch {
2345
2453
  }
2346
2454
  try {
2347
- unlinkSync(PID_PATH);
2455
+ unlinkSync2(PID_PATH);
2348
2456
  } catch {
2349
2457
  }
2350
2458
  try {
2351
- unlinkSync(SOCKET_PATH);
2459
+ unlinkSync2(SOCKET_PATH);
2352
2460
  } catch {
2353
2461
  }
2354
2462
  }
2355
2463
  }
2356
2464
  function findPackageRoot() {
2357
- let dir = path4.dirname(fileURLToPath(import.meta.url));
2358
- const { root } = path4.parse(dir);
2465
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
2466
+ const { root } = path5.parse(dir);
2359
2467
  while (dir !== root) {
2360
- if (existsSync4(path4.join(dir, "package.json"))) return dir;
2361
- dir = path4.dirname(dir);
2468
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
2469
+ dir = path5.dirname(dir);
2362
2470
  }
2363
2471
  return null;
2364
2472
  }
@@ -2368,8 +2476,8 @@ function spawnDaemon() {
2368
2476
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2369
2477
  return;
2370
2478
  }
2371
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2372
- if (!existsSync4(daemonPath)) {
2479
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2480
+ if (!existsSync5(daemonPath)) {
2373
2481
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2374
2482
  `);
2375
2483
  return;
@@ -2377,7 +2485,7 @@ function spawnDaemon() {
2377
2485
  const resolvedPath = daemonPath;
2378
2486
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2379
2487
  `);
2380
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
2488
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2381
2489
  let stderrFd = "ignore";
2382
2490
  try {
2383
2491
  stderrFd = openSync(logPath, "a");
@@ -2388,6 +2496,10 @@ function spawnDaemon() {
2388
2496
  stdio: ["ignore", "ignore", stderrFd],
2389
2497
  env: {
2390
2498
  ...process.env,
2499
+ TMUX: void 0,
2500
+ // Daemon is global — must not inherit session scope
2501
+ TMUX_PANE: void 0,
2502
+ // Prevents resolveExeSession() from scoping to one session
2391
2503
  EXE_DAEMON_SOCK: SOCKET_PATH,
2392
2504
  EXE_DAEMON_PID: PID_PATH
2393
2505
  }
@@ -2410,7 +2522,7 @@ function acquireSpawnLock() {
2410
2522
  const stat = statSync(SPAWN_LOCK_PATH);
2411
2523
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
2412
2524
  try {
2413
- unlinkSync(SPAWN_LOCK_PATH);
2525
+ unlinkSync2(SPAWN_LOCK_PATH);
2414
2526
  } catch {
2415
2527
  }
2416
2528
  try {
@@ -2427,7 +2539,7 @@ function acquireSpawnLock() {
2427
2539
  }
2428
2540
  function releaseSpawnLock() {
2429
2541
  try {
2430
- unlinkSync(SPAWN_LOCK_PATH);
2542
+ unlinkSync2(SPAWN_LOCK_PATH);
2431
2543
  } catch {
2432
2544
  }
2433
2545
  }
@@ -2539,9 +2651,9 @@ async function pingDaemon() {
2539
2651
  }
2540
2652
  function killAndRespawnDaemon() {
2541
2653
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2542
- if (existsSync4(PID_PATH)) {
2654
+ if (existsSync5(PID_PATH)) {
2543
2655
  try {
2544
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
2656
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2545
2657
  if (pid > 0) {
2546
2658
  try {
2547
2659
  process.kill(pid, "SIGKILL");
@@ -2558,11 +2670,11 @@ function killAndRespawnDaemon() {
2558
2670
  _connected = false;
2559
2671
  _buffer = "";
2560
2672
  try {
2561
- unlinkSync(PID_PATH);
2673
+ unlinkSync2(PID_PATH);
2562
2674
  } catch {
2563
2675
  }
2564
2676
  try {
2565
- unlinkSync(SOCKET_PATH);
2677
+ unlinkSync2(SOCKET_PATH);
2566
2678
  } catch {
2567
2679
  }
2568
2680
  spawnDaemon();
@@ -2625,9 +2737,9 @@ var init_exe_daemon_client = __esm({
2625
2737
  "src/lib/exe-daemon-client.ts"() {
2626
2738
  "use strict";
2627
2739
  init_config();
2628
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
2629
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
2630
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
2740
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
2741
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
2742
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2631
2743
  SPAWN_LOCK_STALE_MS = 3e4;
2632
2744
  CONNECT_TIMEOUT_MS = 15e3;
2633
2745
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2715,34 +2827,34 @@ __export(project_name_exports, {
2715
2827
  _resetCache: () => _resetCache,
2716
2828
  getProjectName: () => getProjectName
2717
2829
  });
2718
- import { execSync } from "child_process";
2719
- import path5 from "path";
2830
+ import { execSync as execSync2 } from "child_process";
2831
+ import path6 from "path";
2720
2832
  function getProjectName(cwd) {
2721
2833
  const dir = cwd ?? process.cwd();
2722
2834
  if (_cached && _cachedCwd === dir) return _cached;
2723
2835
  try {
2724
2836
  let repoRoot;
2725
2837
  try {
2726
- const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
2838
+ const gitCommonDir = execSync2("git rev-parse --path-format=absolute --git-common-dir", {
2727
2839
  cwd: dir,
2728
2840
  encoding: "utf8",
2729
2841
  timeout: 2e3,
2730
2842
  stdio: ["pipe", "pipe", "pipe"]
2731
2843
  }).trim();
2732
- repoRoot = path5.dirname(gitCommonDir);
2844
+ repoRoot = path6.dirname(gitCommonDir);
2733
2845
  } catch {
2734
- repoRoot = execSync("git rev-parse --show-toplevel", {
2846
+ repoRoot = execSync2("git rev-parse --show-toplevel", {
2735
2847
  cwd: dir,
2736
2848
  encoding: "utf8",
2737
2849
  timeout: 2e3,
2738
2850
  stdio: ["pipe", "pipe", "pipe"]
2739
2851
  }).trim();
2740
2852
  }
2741
- _cached = path5.basename(repoRoot);
2853
+ _cached = path6.basename(repoRoot);
2742
2854
  _cachedCwd = dir;
2743
2855
  return _cached;
2744
2856
  } catch {
2745
- _cached = path5.basename(dir);
2857
+ _cached = path6.basename(dir);
2746
2858
  _cachedCwd = dir;
2747
2859
  return _cached;
2748
2860
  }
@@ -2765,14 +2877,14 @@ var file_grep_exports = {};
2765
2877
  __export(file_grep_exports, {
2766
2878
  grepProjectFiles: () => grepProjectFiles
2767
2879
  });
2768
- import { execSync as execSync2 } from "child_process";
2769
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync5 } from "fs";
2770
- import path6 from "path";
2880
+ import { execSync as execSync3 } from "child_process";
2881
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync6 } from "fs";
2882
+ import path7 from "path";
2771
2883
  import crypto from "crypto";
2772
2884
  function hasRipgrep() {
2773
2885
  if (_hasRg === null) {
2774
2886
  try {
2775
- execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
2887
+ execSync3("rg --version", { stdio: "ignore", timeout: 2e3 });
2776
2888
  _hasRg = true;
2777
2889
  } catch {
2778
2890
  _hasRg = false;
@@ -2807,7 +2919,7 @@ async function grepProjectFiles(query, projectRoot, options) {
2807
2919
  session_id: "file-grep",
2808
2920
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2809
2921
  tool_name: "file_grep",
2810
- project_name: path6.basename(projectRoot),
2922
+ project_name: path7.basename(projectRoot),
2811
2923
  has_error: false,
2812
2924
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
2813
2925
  vector: null,
@@ -2819,7 +2931,7 @@ function getChunkContext(filePath, lineNumber) {
2819
2931
  try {
2820
2932
  const ext = filePath.split(".").pop()?.toLowerCase();
2821
2933
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
2822
- const source = readFileSync3(filePath, "utf8");
2934
+ const source = readFileSync4(filePath, "utf8");
2823
2935
  const lines = source.split("\n");
2824
2936
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
2825
2937
  const line = lines[i];
@@ -2838,7 +2950,7 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
2838
2950
  const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
2839
2951
  const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
2840
2952
  const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
2841
- const output = execSync2(cmd, {
2953
+ const output = execSync3(cmd, {
2842
2954
  cwd: projectRoot,
2843
2955
  encoding: "utf8",
2844
2956
  timeout: 3e3,
@@ -2853,12 +2965,12 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
2853
2965
  const matchCount = parseInt(line.slice(colonIdx + 1));
2854
2966
  if (isNaN(matchCount) || matchCount === 0) continue;
2855
2967
  try {
2856
- const firstMatch = execSync2(
2968
+ const firstMatch = execSync3(
2857
2969
  `rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
2858
2970
  { cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
2859
2971
  ).trim();
2860
2972
  const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
2861
- const totalLines = execSync2(`wc -l < '${filePath}'`, {
2973
+ const totalLines = execSync3(`wc -l < '${filePath}'`, {
2862
2974
  cwd: projectRoot,
2863
2975
  encoding: "utf8",
2864
2976
  timeout: 1e3
@@ -2881,11 +2993,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
2881
2993
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
2882
2994
  const hits = [];
2883
2995
  for (const filePath of files.slice(0, MAX_FILES)) {
2884
- const absPath = path6.join(projectRoot, filePath);
2996
+ const absPath = path7.join(projectRoot, filePath);
2885
2997
  try {
2886
2998
  const stat = statSync2(absPath);
2887
2999
  if (stat.size > MAX_FILE_SIZE) continue;
2888
- const content = readFileSync3(absPath, "utf8");
3000
+ const content = readFileSync4(absPath, "utf8");
2889
3001
  const lines = content.split("\n");
2890
3002
  const matches = content.match(regex);
2891
3003
  if (!matches || matches.length === 0) continue;
@@ -2908,15 +3020,15 @@ function collectFiles(root, patterns) {
2908
3020
  const files = [];
2909
3021
  function walk(dir, relative) {
2910
3022
  if (files.length >= MAX_FILES) return;
2911
- const basename = path6.basename(dir);
3023
+ const basename = path7.basename(dir);
2912
3024
  if (EXCLUDE_DIRS.includes(basename)) return;
2913
3025
  try {
2914
3026
  const entries = readdirSync2(dir, { withFileTypes: true });
2915
3027
  for (const entry of entries) {
2916
3028
  if (files.length >= MAX_FILES) return;
2917
- const rel = path6.join(relative, entry.name);
3029
+ const rel = path7.join(relative, entry.name);
2918
3030
  if (entry.isDirectory()) {
2919
- walk(path6.join(dir, entry.name), rel);
3031
+ walk(path7.join(dir, entry.name), rel);
2920
3032
  } else if (entry.isFile()) {
2921
3033
  for (const pat of patterns) {
2922
3034
  if (matchGlob(rel, pat)) {
@@ -2948,7 +3060,7 @@ function matchGlob(filePath, pattern) {
2948
3060
  if (slashIdx !== -1) {
2949
3061
  const dir = pattern.slice(0, slashIdx);
2950
3062
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
2951
- const fileDir = path6.dirname(filePath);
3063
+ const fileDir = path7.dirname(filePath);
2952
3064
  return fileDir === dir && filePath.endsWith(ext2);
2953
3065
  }
2954
3066
  const ext = pattern.replace("*", "");
@@ -2956,9 +3068,9 @@ function matchGlob(filePath, pattern) {
2956
3068
  }
2957
3069
  function buildSnippet(hit, projectRoot) {
2958
3070
  try {
2959
- const absPath = path6.join(projectRoot, hit.filePath);
2960
- if (!existsSync5(absPath)) return hit.matchLine;
2961
- const lines = readFileSync3(absPath, "utf8").split("\n");
3071
+ const absPath = path7.join(projectRoot, hit.filePath);
3072
+ if (!existsSync6(absPath)) return hit.matchLine;
3073
+ const lines = readFileSync4(absPath, "utf8").split("\n");
2962
3074
  const start = Math.max(0, hit.lineNumber - 3);
2963
3075
  const end = Math.min(lines.length, hit.lineNumber + 2);
2964
3076
  return lines.slice(start, end).join("\n").slice(0, 500);
@@ -2992,8 +3104,8 @@ __export(reranker_exports, {
2992
3104
  rerank: () => rerank,
2993
3105
  rerankWithScores: () => rerankWithScores
2994
3106
  });
2995
- import path7 from "path";
2996
- import { existsSync as existsSync6 } from "fs";
3107
+ import path8 from "path";
3108
+ import { existsSync as existsSync7 } from "fs";
2997
3109
  function resetIdleTimer() {
2998
3110
  if (_idleTimer) clearTimeout(_idleTimer);
2999
3111
  _idleTimer = setTimeout(() => {
@@ -3004,18 +3116,18 @@ function resetIdleTimer() {
3004
3116
  }
3005
3117
  }
3006
3118
  function isRerankerAvailable() {
3007
- return existsSync6(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
3119
+ return existsSync7(path8.join(MODELS_DIR, RERANKER_MODEL_FILE));
3008
3120
  }
3009
3121
  function getRerankerModelPath() {
3010
- return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3122
+ return path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3011
3123
  }
3012
3124
  async function ensureLoaded() {
3013
3125
  if (_rerankerContext) {
3014
3126
  resetIdleTimer();
3015
3127
  return;
3016
3128
  }
3017
- const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3018
- if (!existsSync6(modelPath)) {
3129
+ const modelPath = path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3130
+ if (!existsSync7(modelPath)) {
3019
3131
  throw new Error(
3020
3132
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3021
3133
  );
@@ -3092,13 +3204,13 @@ var init_reranker = __esm({
3092
3204
  });
3093
3205
 
3094
3206
  // src/lib/session-key.ts
3095
- import { execSync as execSync3 } from "child_process";
3207
+ import { execSync as execSync4 } from "child_process";
3096
3208
  function getSessionKey() {
3097
3209
  if (_cached2) return _cached2;
3098
3210
  let pid = process.ppid;
3099
3211
  for (let i = 0; i < 10; i++) {
3100
3212
  try {
3101
- const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
3213
+ const info = execSync4(`ps -p ${pid} -o ppid=,comm=`, {
3102
3214
  encoding: "utf8",
3103
3215
  timeout: 2e3
3104
3216
  }).trim();
@@ -3127,13 +3239,13 @@ var init_session_key = __esm({
3127
3239
  });
3128
3240
 
3129
3241
  // src/lib/session-registry.ts
3130
- import path9 from "path";
3131
- import os3 from "os";
3242
+ import path10 from "path";
3243
+ import os4 from "os";
3132
3244
  var REGISTRY_PATH;
3133
3245
  var init_session_registry = __esm({
3134
3246
  "src/lib/session-registry.ts"() {
3135
3247
  "use strict";
3136
- REGISTRY_PATH = path9.join(os3.homedir(), ".exe-os", "session-registry.json");
3248
+ REGISTRY_PATH = path10.join(os4.homedir(), ".exe-os", "session-registry.json");
3137
3249
  }
3138
3250
  });
3139
3251
 
@@ -3253,7 +3365,7 @@ var init_transport = __esm({
3253
3365
  });
3254
3366
 
3255
3367
  // src/lib/cc-agent-support.ts
3256
- import { execSync as execSync5 } from "child_process";
3368
+ import { execSync as execSync6 } from "child_process";
3257
3369
  var init_cc_agent_support = __esm({
3258
3370
  "src/lib/cc-agent-support.ts"() {
3259
3371
  "use strict";
@@ -3289,17 +3401,17 @@ __export(intercom_queue_exports, {
3289
3401
  queueIntercom: () => queueIntercom,
3290
3402
  readQueue: () => readQueue
3291
3403
  });
3292
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
3293
- import path10 from "path";
3294
- import os4 from "os";
3404
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3405
+ import path11 from "path";
3406
+ import os5 from "os";
3295
3407
  function ensureDir() {
3296
- const dir = path10.dirname(QUEUE_PATH);
3297
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
3408
+ const dir = path11.dirname(QUEUE_PATH);
3409
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
3298
3410
  }
3299
3411
  function readQueue() {
3300
3412
  try {
3301
- if (!existsSync7(QUEUE_PATH)) return [];
3302
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
3413
+ if (!existsSync8(QUEUE_PATH)) return [];
3414
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
3303
3415
  } catch {
3304
3416
  return [];
3305
3417
  }
@@ -3307,8 +3419,8 @@ function readQueue() {
3307
3419
  function writeQueue(queue) {
3308
3420
  ensureDir();
3309
3421
  const tmp = `${QUEUE_PATH}.tmp`;
3310
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
3311
- renameSync2(tmp, QUEUE_PATH);
3422
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
3423
+ renameSync3(tmp, QUEUE_PATH);
3312
3424
  }
3313
3425
  function queueIntercom(targetSession, reason) {
3314
3426
  const queue = readQueue();
@@ -3390,39 +3502,10 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
3390
3502
  var init_intercom_queue = __esm({
3391
3503
  "src/lib/intercom-queue.ts"() {
3392
3504
  "use strict";
3393
- QUEUE_PATH = path10.join(os4.homedir(), ".exe-os", "intercom-queue.json");
3505
+ QUEUE_PATH = path11.join(os5.homedir(), ".exe-os", "intercom-queue.json");
3394
3506
  MAX_RETRIES2 = 5;
3395
3507
  TTL_MS = 60 * 60 * 1e3;
3396
- INTERCOM_LOG = path10.join(os4.homedir(), ".exe-os", "intercom.log");
3397
- }
3398
- });
3399
-
3400
- // src/lib/employees.ts
3401
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3402
- import { existsSync as existsSync8, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
3403
- import { execSync as execSync6 } from "child_process";
3404
- import path11 from "path";
3405
- import os5 from "os";
3406
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
3407
- if (!existsSync8(employeesPath)) {
3408
- return [];
3409
- }
3410
- const raw = await readFile3(employeesPath, "utf-8");
3411
- try {
3412
- return JSON.parse(raw);
3413
- } catch {
3414
- return [];
3415
- }
3416
- }
3417
- function getEmployee(employees, name) {
3418
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
3419
- }
3420
- var EMPLOYEES_PATH;
3421
- var init_employees = __esm({
3422
- "src/lib/employees.ts"() {
3423
- "use strict";
3424
- init_config();
3425
- EMPLOYEES_PATH = path11.join(EXE_AI_DIR, "exe-employees.json");
3508
+ INTERCOM_LOG = path11.join(os5.homedir(), ".exe-os", "intercom.log");
3426
3509
  }
3427
3510
  });
3428
3511
 
@@ -3479,7 +3562,7 @@ function employeeSessionName(employee, exeSession, instance) {
3479
3562
  exeSession = root;
3480
3563
  } else {
3481
3564
  throw new Error(
3482
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
3565
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
3483
3566
  );
3484
3567
  }
3485
3568
  }
@@ -3493,8 +3576,10 @@ function employeeSessionName(employee, exeSession, instance) {
3493
3576
  return name;
3494
3577
  }
3495
3578
  function extractRootExe(name) {
3496
- const match = name.match(/(exe\d+)$/);
3497
- return match?.[1] ?? null;
3579
+ if (!name) return null;
3580
+ if (!name.includes("-")) return name;
3581
+ const parts = name.split("-").filter(Boolean);
3582
+ return parts.length > 0 ? parts[parts.length - 1] : null;
3498
3583
  }
3499
3584
  function getParentExe(sessionKey) {
3500
3585
  try {
@@ -3577,12 +3662,14 @@ function getSessionState(sessionName) {
3577
3662
  }
3578
3663
  }
3579
3664
  function isExeSession(sessionName) {
3580
- return /^exe\d*$/.test(sessionName);
3665
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
3666
+ const coordinatorName = getCoordinatorName();
3667
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
3581
3668
  }
3582
3669
  function sendIntercom(targetSession) {
3583
3670
  const transport = getTransport();
3584
3671
  if (isExeSession(targetSession)) {
3585
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
3672
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
3586
3673
  return "skipped_exe";
3587
3674
  }
3588
3675
  if (isDebounced(targetSession)) {
@@ -3633,6 +3720,7 @@ var init_tmux_routing = __esm({
3633
3720
  init_provider_table();
3634
3721
  init_intercom_queue();
3635
3722
  init_plan_limits();
3723
+ init_employees();
3636
3724
  SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
3637
3725
  SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
3638
3726
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
@@ -3768,7 +3856,7 @@ async function deliverLocalMessage(messageId) {
3768
3856
  try {
3769
3857
  const exeSession = resolveExeSession();
3770
3858
  if (!exeSession) {
3771
- throw new Error("No exe session found");
3859
+ throw new Error("No coordinator session found");
3772
3860
  }
3773
3861
  const sessionName = employeeSessionName(targetAgent, exeSession);
3774
3862
  if (!isEmployeeAlive(sessionName)) {
@@ -4088,21 +4176,23 @@ function getReviewChecklist(role, agent, taskSlug) {
4088
4176
  }
4089
4177
  async function createReviewForCompletedTask(row, result, _baseDir, now) {
4090
4178
  const taskFile = String(row.task_file);
4091
- if (String(row.assigned_to) === "exe") return;
4179
+ const employees = await loadEmployees();
4180
+ const coordinatorName = getCoordinatorName(employees);
4181
+ if (String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to), employees)) return;
4092
4182
  if (String(row.title).startsWith("Review:")) return;
4093
4183
  const fileName = taskFile.split("/").pop() ?? "";
4094
4184
  if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
4095
4185
  if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
4096
4186
  const client = getClient();
4097
4187
  const agent = String(row.assigned_to);
4098
- const reviewer = String(row.reviewer || row.assigned_by) || "exe";
4188
+ const rawReviewer = row.reviewer || row.assigned_by;
4189
+ const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
4099
4190
  const currentStatus = String(row.status ?? "");
4100
4191
  if (currentStatus === "done") return;
4101
4192
  const existingResult = String(row.result ?? "");
4102
4193
  if (existingResult.includes("## Review notes")) return;
4103
4194
  let reviewerRole = "unknown";
4104
4195
  try {
4105
- const employees = await loadEmployees();
4106
4196
  const emp = getEmployee(employees, reviewer);
4107
4197
  if (emp) reviewerRole = emp.role;
4108
4198
  } catch {
@@ -4147,7 +4237,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
4147
4237
  agentRole: String(row.assigned_to),
4148
4238
  event: "task_complete",
4149
4239
  project: String(row.project_name),
4150
- summary: `completed "${taskTitle}" \u2014 review task created`,
4240
+ summary: `completed "${taskTitle}" \u2014 ready for review`,
4151
4241
  taskFile
4152
4242
  });
4153
4243
  const originalPriority = String(row.priority).toLowerCase();
@@ -4261,6 +4351,8 @@ async function hybridSearch(queryText, agentId, options) {
4261
4351
  return lightweightSearch(queryText, agentId, options);
4262
4352
  }
4263
4353
  const limit = options?.limit ?? 10;
4354
+ const trajectoryResults = await trajectoryBypass(queryText, agentId, options, limit);
4355
+ if (trajectoryResults !== null) return trajectoryResults;
4264
4356
  let effectiveQuery = queryText;
4265
4357
  let effectiveOptions = { ...options };
4266
4358
  let _isBroadQuery = false;
@@ -4484,6 +4576,8 @@ async function lightweightSearch(queryText, agentId, options) {
4484
4576
  async function ftsQuery(client, matchExpr, agentId, options, limit) {
4485
4577
  const statusFilter = options?.includeArchived ? "" : `
4486
4578
  AND COALESCE(m.status, 'active') = 'active'`;
4579
+ const draftFilter = options?.includeDrafts ? "" : `
4580
+ AND (m.draft = 0 OR m.draft IS NULL)`;
4487
4581
  let sql = `SELECT m.id, m.agent_id, m.agent_role, m.session_id, m.timestamp,
4488
4582
  m.tool_name, m.project_name,
4489
4583
  m.has_error, m.raw_text, m.vector, m.task_id,
@@ -4494,7 +4588,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
4494
4588
  FROM memories m
4495
4589
  JOIN memories_fts fts ON m.rowid = fts.rowid
4496
4590
  WHERE memories_fts MATCH ?
4497
- AND m.agent_id = ?${statusFilter}
4591
+ AND m.agent_id = ?${statusFilter}${draftFilter}
4498
4592
  AND COALESCE(m.confidence, 0.7) >= 0.3`;
4499
4593
  const args = [matchExpr, agentId];
4500
4594
  const scope = buildWikiScopeFilter(options, "m.");
@@ -4516,6 +4610,10 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
4516
4610
  sql += ` AND m.timestamp >= ?`;
4517
4611
  args.push(options.since);
4518
4612
  }
4613
+ if (options?.memoryType) {
4614
+ sql += ` AND m.memory_type = ?`;
4615
+ args.push(options.memoryType);
4616
+ }
4519
4617
  sql += ` ORDER BY rank LIMIT ?`;
4520
4618
  args.push(limit);
4521
4619
  const result = await client.execute({ sql, args });
@@ -4548,6 +4646,8 @@ async function recentRecords(agentId, options, limit) {
4548
4646
  const client = getClient();
4549
4647
  const statusFilter = options?.includeArchived ? "" : `
4550
4648
  AND COALESCE(status, 'active') = 'active'`;
4649
+ const draftFilter = options?.includeDrafts ? "" : `
4650
+ AND (draft = 0 OR draft IS NULL)`;
4551
4651
  let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4552
4652
  tool_name, project_name,
4553
4653
  has_error, raw_text, vector, task_id,
@@ -4556,7 +4656,7 @@ async function recentRecords(agentId, options, limit) {
4556
4656
  char_offset, page_number,
4557
4657
  source_path, source_type
4558
4658
  FROM memories
4559
- WHERE agent_id = ?${statusFilter}
4659
+ WHERE agent_id = ?${statusFilter}${draftFilter}
4560
4660
  AND COALESCE(confidence, 0.7) >= 0.3`;
4561
4661
  const args = [agentId];
4562
4662
  const scope = buildWikiScopeFilter(options, "");
@@ -4578,6 +4678,10 @@ async function recentRecords(agentId, options, limit) {
4578
4678
  sql += ` AND timestamp >= ?`;
4579
4679
  args.push(options.since);
4580
4680
  }
4681
+ if (options?.memoryType) {
4682
+ sql += ` AND memory_type = ?`;
4683
+ args.push(options.memoryType);
4684
+ }
4581
4685
  sql += ` ORDER BY timestamp DESC LIMIT ?`;
4582
4686
  args.push(limit);
4583
4687
  const result = await client.execute({ sql, args });
@@ -4606,33 +4710,158 @@ async function recentRecords(agentId, options, limit) {
4606
4710
  source_type: row.source_type ?? null
4607
4711
  }));
4608
4712
  }
4713
+ var KNOWN_TOOLS = /* @__PURE__ */ new Set([
4714
+ "Bash",
4715
+ "Read",
4716
+ "Write",
4717
+ "Edit",
4718
+ "Grep",
4719
+ "Glob",
4720
+ "Agent",
4721
+ "Skill",
4722
+ "WebSearch",
4723
+ "WebFetch"
4724
+ ]);
4725
+ function detectToolQuery(query) {
4726
+ const lower = query.toLowerCase().trim();
4727
+ for (const tool of KNOWN_TOOLS) {
4728
+ if (lower === tool.toLowerCase()) return tool;
4729
+ if (lower.startsWith(tool.toLowerCase() + " ")) return tool;
4730
+ if (lower.includes(`tool:${tool.toLowerCase()}`)) return tool;
4731
+ }
4732
+ return null;
4733
+ }
4734
+ async function trajectoryBypass(queryText, agentId, options, limit) {
4735
+ const toolName = detectToolQuery(queryText);
4736
+ if (!toolName) return null;
4737
+ try {
4738
+ const client = getClient();
4739
+ const statusFilter = options?.includeArchived ? "" : `
4740
+ AND COALESCE(status, 'active') = 'active'`;
4741
+ const draftFilter = options?.includeDrafts ? "" : `
4742
+ AND (draft = 0 OR draft IS NULL)`;
4743
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4744
+ tool_name, project_name,
4745
+ has_error, raw_text, vector, task_id,
4746
+ importance, status, confidence, last_accessed,
4747
+ workspace_id, document_id, user_id,
4748
+ char_offset, page_number,
4749
+ source_path, source_type
4750
+ FROM memories
4751
+ WHERE trajectory IS NOT NULL
4752
+ AND json_extract(trajectory, '$.tool') = ?
4753
+ AND agent_id = ?${statusFilter}${draftFilter}`;
4754
+ const args = [toolName, agentId];
4755
+ if (options?.projectName) {
4756
+ sql += ` AND project_name = ?`;
4757
+ args.push(options.projectName);
4758
+ }
4759
+ if (options?.since) {
4760
+ sql += ` AND timestamp >= ?`;
4761
+ args.push(options.since);
4762
+ }
4763
+ sql += ` ORDER BY timestamp DESC LIMIT ?`;
4764
+ args.push(limit);
4765
+ const result = await client.execute({ sql, args });
4766
+ if (result.rows.length < 3) return null;
4767
+ return result.rows.map((row) => ({
4768
+ id: row.id,
4769
+ agent_id: row.agent_id,
4770
+ agent_role: row.agent_role,
4771
+ session_id: row.session_id,
4772
+ timestamp: row.timestamp,
4773
+ tool_name: row.tool_name,
4774
+ project_name: row.project_name,
4775
+ has_error: row.has_error === 1,
4776
+ raw_text: row.raw_text,
4777
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
4778
+ task_id: row.task_id ?? null,
4779
+ importance: row.importance ?? 5,
4780
+ status: row.status ?? "active",
4781
+ confidence: row.confidence ?? 0.7,
4782
+ last_accessed: row.last_accessed ?? row.timestamp,
4783
+ workspace_id: row.workspace_id ?? null,
4784
+ document_id: row.document_id ?? null,
4785
+ user_id: row.user_id ?? null,
4786
+ char_offset: row.char_offset ?? null,
4787
+ page_number: row.page_number ?? null,
4788
+ source_path: row.source_path ?? null,
4789
+ source_type: row.source_type ?? null
4790
+ }));
4791
+ } catch {
4792
+ return null;
4793
+ }
4794
+ }
4609
4795
 
4610
4796
  // src/adapters/claude/active-agent.ts
4611
4797
  init_config();
4612
- import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync3 } from "fs";
4613
- import { execSync as execSync4 } from "child_process";
4614
- import path8 from "path";
4798
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
4799
+ import { execSync as execSync5 } from "child_process";
4800
+ import path9 from "path";
4615
4801
 
4616
4802
  // src/adapters/claude/session-key.ts
4617
4803
  init_session_key();
4618
4804
 
4619
4805
  // src/adapters/claude/active-agent.ts
4620
- var CACHE_DIR = path8.join(EXE_AI_DIR, "session-cache");
4806
+ init_employees();
4807
+ var CACHE_DIR = path9.join(EXE_AI_DIR, "session-cache");
4621
4808
  var STALE_MS = 24 * 60 * 60 * 1e3;
4809
+ function isNameWithOptionalInstance(candidate, baseName) {
4810
+ if (candidate === baseName) return true;
4811
+ if (!candidate.startsWith(baseName)) return false;
4812
+ return /^\d+$/.test(candidate.slice(baseName.length));
4813
+ }
4814
+ function resolveEmployeeFromSessionPrefix(prefix, employees) {
4815
+ const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
4816
+ for (const employee of sorted) {
4817
+ if (isNameWithOptionalInstance(prefix, employee.name)) {
4818
+ return { agentId: employee.name, agentRole: employee.role };
4819
+ }
4820
+ }
4821
+ return null;
4822
+ }
4823
+ function resolveActiveAgentFromTmuxSession(sessionName) {
4824
+ const employees = loadEmployeesSync();
4825
+ const coordinator = getCoordinatorEmployee(employees);
4826
+ const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
4827
+ if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
4828
+ return {
4829
+ agentId: coordinatorName,
4830
+ agentRole: coordinator?.role ?? "COO"
4831
+ };
4832
+ }
4833
+ if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
4834
+ return {
4835
+ agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
4836
+ agentRole: coordinator?.role ?? "COO"
4837
+ };
4838
+ }
4839
+ if (sessionName.includes("-")) {
4840
+ const prefix = sessionName.split("-")[0] ?? "";
4841
+ const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
4842
+ if (employee) return employee;
4843
+ const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
4844
+ if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
4845
+ const emp = getEmployee(employees, legacy[1]);
4846
+ return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
4847
+ }
4848
+ }
4849
+ return null;
4850
+ }
4622
4851
  function getMarkerPath() {
4623
- return path8.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
4852
+ return path9.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
4624
4853
  }
4625
4854
  function getActiveAgent() {
4626
4855
  try {
4627
4856
  const markerPath = getMarkerPath();
4628
- const raw = readFileSync4(markerPath, "utf8");
4857
+ const raw = readFileSync5(markerPath, "utf8");
4629
4858
  const data = JSON.parse(raw);
4630
4859
  if (data.agentId) {
4631
4860
  if (data.startedAt) {
4632
4861
  const age = Date.now() - new Date(data.startedAt).getTime();
4633
4862
  if (age > STALE_MS) {
4634
4863
  try {
4635
- unlinkSync2(markerPath);
4864
+ unlinkSync3(markerPath);
4636
4865
  } catch {
4637
4866
  }
4638
4867
  } else {
@@ -4651,17 +4880,12 @@ function getActiveAgent() {
4651
4880
  } catch {
4652
4881
  }
4653
4882
  try {
4654
- const sessionName = execSync4(
4883
+ const sessionName = execSync5(
4655
4884
  "tmux display-message -p '#{session_name}' 2>/dev/null",
4656
4885
  { encoding: "utf8", timeout: 2e3 }
4657
4886
  ).trim();
4658
- const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
4659
- if (empMatch && empMatch[1] !== "exe") {
4660
- return { agentId: empMatch[1], agentRole: "employee" };
4661
- }
4662
- if (/^exe\d+$/.test(sessionName)) {
4663
- return { agentId: "exe", agentRole: "COO" };
4664
- }
4887
+ const resolved = resolveActiveAgentFromTmuxSession(sessionName);
4888
+ if (resolved) return resolved;
4665
4889
  } catch {
4666
4890
  }
4667
4891
  return {
@@ -4671,6 +4895,8 @@ function getActiveAgent() {
4671
4895
  }
4672
4896
 
4673
4897
  // src/adapters/claude/hooks/prompt-submit.ts
4898
+ init_employees();
4899
+ init_tmux_routing();
4674
4900
  if (!process.env.AGENT_ID) {
4675
4901
  process.env.AGENT_ID = "default";
4676
4902
  process.env.AGENT_ROLE = "employee";
@@ -4727,7 +4953,7 @@ process.stdin.on("end", async () => {
4727
4953
  process.exit(0);
4728
4954
  }
4729
4955
  await initStore();
4730
- if (agent.agentId !== "default" && agent.agentId !== "exe") {
4956
+ if (!canCoordinate(agent.agentId, agent.agentRole)) {
4731
4957
  try {
4732
4958
  const { getReadMessages: getReadMessages2, markAcknowledged: markAcknowledged2 } = await Promise.resolve().then(() => (init_messaging(), messaging_exports));
4733
4959
  const readMsgs = await getReadMessages2(agent.agentId);
@@ -4778,7 +5004,7 @@ ${fresh.map(
4778
5004
  }
4779
5005
  }
4780
5006
  let reviewContext = "";
4781
- if (agent.agentId === COO_AGENT_NAME || agent.agentId === "default") {
5007
+ if (canCoordinate(agent.agentId, agent.agentRole)) {
4782
5008
  try {
4783
5009
  const { countPendingReviews: countPendingReviews2, countNewPendingReviewsSince: countNewPendingReviewsSince2 } = await Promise.resolve().then(() => (init_tasks_review(), tasks_review_exports));
4784
5010
  const sessionKey = getSessionKey();
@@ -4787,7 +5013,7 @@ ${fresh.map(
4787
5013
  try {
4788
5014
  const { execSync: execSync7 } = await import("child_process");
4789
5015
  const tmuxSession = execSync7("tmux display-message -p '#{session_name}'", { encoding: "utf8", timeout: 2e3 }).trim();
4790
- if (/^exe\d+$/.test(tmuxSession)) sessionScope = tmuxSession;
5016
+ if (isExeSession(tmuxSession)) sessionScope = tmuxSession;
4791
5017
  } catch {
4792
5018
  }
4793
5019
  let lastCheckedAt = "";