@askexenow/exe-os 0.8.0 → 0.8.1

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 (90) hide show
  1. package/README.md +178 -79
  2. package/dist/bin/backfill-responses.js +160 -8
  3. package/dist/bin/backfill-vectors.js +130 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +130 -1
  5. package/dist/bin/cli.js +10111 -7540
  6. package/dist/bin/exe-agent.js +159 -1
  7. package/dist/bin/exe-assign.js +235 -16
  8. package/dist/bin/exe-boot.js +344 -472
  9. package/dist/bin/exe-call.js +145 -1
  10. package/dist/bin/exe-cloud.js +11 -0
  11. package/dist/bin/exe-dispatch.js +37 -24
  12. package/dist/bin/exe-doctor.js +130 -1
  13. package/dist/bin/exe-export-behaviors.js +150 -7
  14. package/dist/bin/exe-forget.js +822 -665
  15. package/dist/bin/exe-gateway.js +470 -62
  16. package/dist/bin/exe-heartbeat.js +133 -2
  17. package/dist/bin/exe-kill.js +150 -7
  18. package/dist/bin/exe-launch-agent.js +150 -7
  19. package/dist/bin/exe-new-employee.js +756 -224
  20. package/dist/bin/exe-pending-messages.js +132 -2
  21. package/dist/bin/exe-pending-notifications.js +130 -1
  22. package/dist/bin/exe-pending-reviews.js +132 -2
  23. package/dist/bin/exe-review.js +160 -8
  24. package/dist/bin/exe-search.js +2473 -2008
  25. package/dist/bin/exe-session-cleanup.js +238 -51
  26. package/dist/bin/exe-settings.js +11 -0
  27. package/dist/bin/exe-status.js +130 -1
  28. package/dist/bin/exe-team.js +130 -1
  29. package/dist/bin/git-sweep.js +272 -16
  30. package/dist/bin/graph-backfill.js +150 -7
  31. package/dist/bin/graph-export.js +150 -7
  32. package/dist/bin/install.js +5 -0
  33. package/dist/bin/scan-tasks.js +238 -19
  34. package/dist/bin/setup.js +1776 -10
  35. package/dist/bin/shard-migrate.js +150 -7
  36. package/dist/bin/update.js +9 -6
  37. package/dist/bin/wiki-sync.js +150 -7
  38. package/dist/gateway/index.js +470 -62
  39. package/dist/hooks/bug-report-worker.js +195 -35
  40. package/dist/hooks/commit-complete.js +272 -16
  41. package/dist/hooks/error-recall.js +2313 -1847
  42. package/dist/hooks/exe-heartbeat-hook.js +5 -0
  43. package/dist/hooks/ingest-worker.js +330 -58
  44. package/dist/hooks/ingest.js +11 -0
  45. package/dist/hooks/instructions-loaded.js +199 -10
  46. package/dist/hooks/notification.js +199 -10
  47. package/dist/hooks/post-compact.js +199 -10
  48. package/dist/hooks/pre-compact.js +199 -10
  49. package/dist/hooks/pre-tool-use.js +199 -10
  50. package/dist/hooks/prompt-ingest-worker.js +179 -14
  51. package/dist/hooks/prompt-submit.js +781 -285
  52. package/dist/hooks/response-ingest-worker.js +1900 -1405
  53. package/dist/hooks/session-end.js +456 -12
  54. package/dist/hooks/session-start.js +2188 -1724
  55. package/dist/hooks/stop.js +200 -10
  56. package/dist/hooks/subagent-stop.js +199 -10
  57. package/dist/hooks/summary-worker.js +604 -334
  58. package/dist/index.js +554 -61
  59. package/dist/lib/cloud-sync.js +5 -0
  60. package/dist/lib/config.js +13 -0
  61. package/dist/lib/consolidation.js +5 -0
  62. package/dist/lib/database.js +104 -0
  63. package/dist/lib/device-registry.js +109 -0
  64. package/dist/lib/embedder.js +13 -0
  65. package/dist/lib/employee-templates.js +53 -26
  66. package/dist/lib/employees.js +5 -0
  67. package/dist/lib/exe-daemon-client.js +5 -0
  68. package/dist/lib/exe-daemon.js +493 -79
  69. package/dist/lib/file-grep.js +20 -4
  70. package/dist/lib/hybrid-search.js +1435 -190
  71. package/dist/lib/identity-templates.js +126 -5
  72. package/dist/lib/identity.js +5 -0
  73. package/dist/lib/license.js +5 -0
  74. package/dist/lib/messaging.js +37 -24
  75. package/dist/lib/schedules.js +130 -1
  76. package/dist/lib/skill-learning.js +11 -0
  77. package/dist/lib/status-brief.js +5 -0
  78. package/dist/lib/store.js +199 -10
  79. package/dist/lib/task-router.js +72 -6
  80. package/dist/lib/tasks.js +179 -50
  81. package/dist/lib/tmux-routing.js +179 -46
  82. package/dist/mcp/server.js +2129 -1855
  83. package/dist/mcp/tools/create-task.js +86 -36
  84. package/dist/mcp/tools/deactivate-behavior.js +5 -0
  85. package/dist/mcp/tools/list-tasks.js +39 -11
  86. package/dist/mcp/tools/send-message.js +37 -24
  87. package/dist/mcp/tools/update-task.js +153 -38
  88. package/dist/runtime/index.js +451 -59
  89. package/dist/tui/App.js +454 -59
  90. package/package.json +1 -1
@@ -106,6 +106,11 @@ function normalizeSessionLifecycle(raw) {
106
106
  const userSL = raw.sessionLifecycle ?? {};
107
107
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
108
108
  }
109
+ function normalizeAutoUpdate(raw) {
110
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
111
+ const userAU = raw.autoUpdate ?? {};
112
+ raw.autoUpdate = { ...defaultAU, ...userAU };
113
+ }
109
114
  async function loadConfig() {
110
115
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
111
116
  await mkdir(dir, { recursive: true });
@@ -128,6 +133,7 @@ async function loadConfig() {
128
133
  }
129
134
  normalizeScalingRoadmap(migratedCfg);
130
135
  normalizeSessionLifecycle(migratedCfg);
136
+ normalizeAutoUpdate(migratedCfg);
131
137
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
132
138
  if (config.dbPath.startsWith("~")) {
133
139
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -150,6 +156,7 @@ function loadConfigSync() {
150
156
  const { config: migratedCfg } = migrateConfig(parsed);
151
157
  normalizeScalingRoadmap(migratedCfg);
152
158
  normalizeSessionLifecycle(migratedCfg);
159
+ normalizeAutoUpdate(migratedCfg);
153
160
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
154
161
  } catch {
155
162
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
@@ -169,6 +176,7 @@ async function loadConfigFrom(configPath) {
169
176
  const { config: migratedCfg } = migrateConfig(parsed);
170
177
  normalizeScalingRoadmap(migratedCfg);
171
178
  normalizeSessionLifecycle(migratedCfg);
179
+ normalizeAutoUpdate(migratedCfg);
172
180
  return { ...DEFAULT_CONFIG, ...migratedCfg };
173
181
  } catch {
174
182
  return { ...DEFAULT_CONFIG };
@@ -240,6 +248,11 @@ var init_config = __esm({
240
248
  idleKillTicksRequired: 3,
241
249
  idleKillIntercomAckWindowMs: 1e4,
242
250
  maxAutoInstances: 10
251
+ },
252
+ autoUpdate: {
253
+ checkOnBoot: true,
254
+ autoInstall: false,
255
+ checkIntervalMs: 24 * 60 * 60 * 1e3
243
256
  }
244
257
  };
245
258
  CONFIG_MIGRATIONS = [
@@ -472,6 +485,27 @@ async function ensureSchema() {
472
485
  });
473
486
  } catch {
474
487
  }
488
+ try {
489
+ await client.execute({
490
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
491
+ args: []
492
+ });
493
+ } catch {
494
+ }
495
+ try {
496
+ await client.execute({
497
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
498
+ args: []
499
+ });
500
+ } catch {
501
+ }
502
+ try {
503
+ await client.execute({
504
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
505
+ args: []
506
+ });
507
+ } catch {
508
+ }
475
509
  try {
476
510
  await client.execute({
477
511
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -882,6 +916,15 @@ async function ensureSchema() {
882
916
  } catch {
883
917
  }
884
918
  }
919
+ for (const col of [
920
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
921
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
922
+ ]) {
923
+ try {
924
+ await client.execute(col);
925
+ } catch {
926
+ }
927
+ }
885
928
  await client.executeMultiple(`
886
929
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
887
930
  ON memories(workspace_id);
@@ -946,6 +989,34 @@ async function ensureSchema() {
946
989
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
947
990
  ON conversations(channel_id);
948
991
  `);
992
+ try {
993
+ await client.execute({
994
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
995
+ args: []
996
+ });
997
+ } catch {
998
+ }
999
+ try {
1000
+ await client.execute({
1001
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
1002
+ args: []
1003
+ });
1004
+ } catch {
1005
+ }
1006
+ try {
1007
+ await client.execute({
1008
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
1009
+ args: []
1010
+ });
1011
+ } catch {
1012
+ }
1013
+ try {
1014
+ await client.execute({
1015
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
1016
+ args: []
1017
+ });
1018
+ } catch {
1019
+ }
949
1020
  await client.executeMultiple(`
950
1021
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
951
1022
  content_text,
@@ -972,13 +1043,115 @@ async function ensureSchema() {
972
1043
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
973
1044
  END;
974
1045
  `);
1046
+ try {
1047
+ await client.execute({
1048
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
1049
+ args: []
1050
+ });
1051
+ } catch {
1052
+ }
1053
+ try {
1054
+ await client.execute(
1055
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1056
+ );
1057
+ } catch {
1058
+ }
1059
+ try {
1060
+ await client.execute({
1061
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1062
+ args: []
1063
+ });
1064
+ await client.execute({
1065
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1066
+ args: []
1067
+ });
1068
+ } catch {
1069
+ }
1070
+ try {
1071
+ await client.execute({
1072
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1073
+ args: []
1074
+ });
1075
+ } catch {
1076
+ }
1077
+ try {
1078
+ await client.execute(
1079
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1080
+ );
1081
+ } catch {
1082
+ }
1083
+ for (const col of [
1084
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1085
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1086
+ ]) {
1087
+ try {
1088
+ await client.execute(col);
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ }
1093
+ async function disposeDatabase() {
1094
+ if (_client) {
1095
+ _client.close();
1096
+ _client = null;
1097
+ }
975
1098
  }
976
- var _client, initTurso;
1099
+ var _client, initTurso, disposeTurso;
977
1100
  var init_database = __esm({
978
1101
  "src/lib/database.ts"() {
979
1102
  "use strict";
980
1103
  _client = null;
981
1104
  initTurso = initDatabase;
1105
+ disposeTurso = disposeDatabase;
1106
+ }
1107
+ });
1108
+
1109
+ // src/lib/keychain.ts
1110
+ import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
1111
+ import { existsSync as existsSync2 } from "fs";
1112
+ import path2 from "path";
1113
+ import crypto from "crypto";
1114
+ function getKeyDir() {
1115
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
1116
+ }
1117
+ function getKeyPath() {
1118
+ return path2.join(getKeyDir(), "master.key");
1119
+ }
1120
+ async function tryKeytar() {
1121
+ try {
1122
+ return await import("keytar");
1123
+ } catch {
1124
+ return null;
1125
+ }
1126
+ }
1127
+ async function getMasterKey() {
1128
+ const keytar = await tryKeytar();
1129
+ if (keytar) {
1130
+ try {
1131
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
1132
+ if (stored) {
1133
+ return Buffer.from(stored, "base64");
1134
+ }
1135
+ } catch {
1136
+ }
1137
+ }
1138
+ const keyPath = getKeyPath();
1139
+ if (!existsSync2(keyPath)) {
1140
+ return null;
1141
+ }
1142
+ try {
1143
+ const content = await readFile2(keyPath, "utf-8");
1144
+ return Buffer.from(content.trim(), "base64");
1145
+ } catch {
1146
+ return null;
1147
+ }
1148
+ }
1149
+ var SERVICE, ACCOUNT;
1150
+ var init_keychain = __esm({
1151
+ "src/lib/keychain.ts"() {
1152
+ "use strict";
1153
+ SERVICE = "exe-mem";
1154
+ ACCOUNT = "master-key";
982
1155
  }
983
1156
  });
984
1157
 
@@ -1100,13 +1273,27 @@ async function ensureShardSchema(client) {
1100
1273
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
1101
1274
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
1102
1275
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
1103
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
1276
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
1277
+ // Source provenance columns (must match database.ts)
1278
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1279
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1280
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1281
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1104
1282
  ]) {
1105
1283
  try {
1106
1284
  await client.execute(col);
1107
1285
  } catch {
1108
1286
  }
1109
1287
  }
1288
+ for (const idx of [
1289
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
1290
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
1291
+ ]) {
1292
+ try {
1293
+ await client.execute(idx);
1294
+ } catch {
1295
+ }
1296
+ }
1110
1297
  try {
1111
1298
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
1112
1299
  } catch {
@@ -1210,6 +1397,441 @@ var init_shard_manager = __esm({
1210
1397
  }
1211
1398
  });
1212
1399
 
1400
+ // src/lib/store.ts
1401
+ var store_exports = {};
1402
+ __export(store_exports, {
1403
+ attachDocumentMetadata: () => attachDocumentMetadata,
1404
+ buildWikiScopeFilter: () => buildWikiScopeFilter,
1405
+ classifyTier: () => classifyTier,
1406
+ disposeStore: () => disposeStore,
1407
+ flushBatch: () => flushBatch,
1408
+ flushTier3: () => flushTier3,
1409
+ getMemoryCardinality: () => getMemoryCardinality,
1410
+ initStore: () => initStore,
1411
+ reserveVersions: () => reserveVersions,
1412
+ searchMemories: () => searchMemories,
1413
+ updateMemoryStatus: () => updateMemoryStatus,
1414
+ vectorToBlob: () => vectorToBlob,
1415
+ writeMemory: () => writeMemory
1416
+ });
1417
+ async function initStore(options) {
1418
+ if (_flushTimer !== null) {
1419
+ clearInterval(_flushTimer);
1420
+ _flushTimer = null;
1421
+ }
1422
+ _pendingRecords = [];
1423
+ _flushing = false;
1424
+ _batchSize = options?.batchSize ?? 20;
1425
+ _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
1426
+ let dbPath = options?.dbPath;
1427
+ if (!dbPath) {
1428
+ const config = await loadConfig();
1429
+ dbPath = config.dbPath;
1430
+ }
1431
+ let masterKey = options?.masterKey ?? null;
1432
+ if (!masterKey) {
1433
+ masterKey = await getMasterKey();
1434
+ if (!masterKey) {
1435
+ throw new Error(
1436
+ "No encryption key found. Run /exe-setup to generate one."
1437
+ );
1438
+ }
1439
+ }
1440
+ const hexKey = masterKey.toString("hex");
1441
+ await initTurso({
1442
+ dbPath,
1443
+ encryptionKey: hexKey
1444
+ });
1445
+ await ensureSchema();
1446
+ try {
1447
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1448
+ initShardManager2(hexKey);
1449
+ } catch {
1450
+ }
1451
+ const client = getClient();
1452
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1453
+ _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1454
+ }
1455
+ function classifyTier(record) {
1456
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
1457
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1458
+ return 3;
1459
+ }
1460
+ async function writeMemory(record) {
1461
+ if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1462
+ throw new Error(
1463
+ `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
1464
+ );
1465
+ }
1466
+ const dbRow = {
1467
+ id: record.id,
1468
+ agent_id: record.agent_id,
1469
+ agent_role: record.agent_role,
1470
+ session_id: record.session_id,
1471
+ timestamp: record.timestamp,
1472
+ tool_name: record.tool_name,
1473
+ project_name: record.project_name,
1474
+ has_error: record.has_error ? 1 : 0,
1475
+ raw_text: record.raw_text,
1476
+ vector: record.vector,
1477
+ version: _nextVersion++,
1478
+ task_id: record.task_id ?? null,
1479
+ importance: record.importance ?? 5,
1480
+ status: record.status ?? "active",
1481
+ confidence: record.confidence ?? 0.7,
1482
+ last_accessed: record.last_accessed ?? record.timestamp,
1483
+ workspace_id: record.workspace_id ?? null,
1484
+ document_id: record.document_id ?? null,
1485
+ user_id: record.user_id ?? null,
1486
+ char_offset: record.char_offset ?? null,
1487
+ page_number: record.page_number ?? null,
1488
+ source_path: record.source_path ?? null,
1489
+ source_type: record.source_type ?? null,
1490
+ tier: record.tier ?? classifyTier(record),
1491
+ supersedes_id: record.supersedes_id ?? null
1492
+ };
1493
+ _pendingRecords.push(dbRow);
1494
+ if (_flushTimer === null) {
1495
+ _flushTimer = setInterval(() => {
1496
+ void flushBatch();
1497
+ }, _flushIntervalMs);
1498
+ if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
1499
+ _flushTimer.unref();
1500
+ }
1501
+ }
1502
+ if (_pendingRecords.length >= _batchSize) {
1503
+ await flushBatch();
1504
+ }
1505
+ }
1506
+ async function flushBatch() {
1507
+ if (_flushing || _pendingRecords.length === 0) return 0;
1508
+ _flushing = true;
1509
+ try {
1510
+ const batch = _pendingRecords.slice(0);
1511
+ const buildStmt = (row) => {
1512
+ const hasVector = row.vector !== null;
1513
+ const taskId = row.task_id ?? null;
1514
+ const importance = row.importance ?? 5;
1515
+ const status = row.status ?? "active";
1516
+ const confidence = row.confidence ?? 0.7;
1517
+ const lastAccessed = row.last_accessed ?? row.timestamp;
1518
+ const workspaceId = row.workspace_id ?? null;
1519
+ const documentId = row.document_id ?? null;
1520
+ const userId = row.user_id ?? null;
1521
+ const charOffset = row.char_offset ?? null;
1522
+ const pageNumber = row.page_number ?? null;
1523
+ const sourcePath = row.source_path ?? null;
1524
+ const sourceType = row.source_type ?? null;
1525
+ const tier = row.tier ?? 3;
1526
+ const supersedesId = row.supersedes_id ?? null;
1527
+ return {
1528
+ sql: hasVector ? `INSERT OR IGNORE INTO memories
1529
+ (id, agent_id, agent_role, session_id, timestamp,
1530
+ tool_name, project_name,
1531
+ has_error, raw_text, vector, version, task_id, importance, status,
1532
+ confidence, last_accessed,
1533
+ workspace_id, document_id, user_id, char_offset, page_number,
1534
+ source_path, source_type, tier, supersedes_id)
1535
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1536
+ (id, agent_id, agent_role, session_id, timestamp,
1537
+ tool_name, project_name,
1538
+ has_error, raw_text, vector, version, task_id, importance, status,
1539
+ confidence, last_accessed,
1540
+ workspace_id, document_id, user_id, char_offset, page_number,
1541
+ source_path, source_type, tier, supersedes_id)
1542
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1543
+ args: hasVector ? [
1544
+ row.id,
1545
+ row.agent_id,
1546
+ row.agent_role,
1547
+ row.session_id,
1548
+ row.timestamp,
1549
+ row.tool_name,
1550
+ row.project_name,
1551
+ row.has_error,
1552
+ row.raw_text,
1553
+ vectorToBlob(row.vector),
1554
+ row.version,
1555
+ taskId,
1556
+ importance,
1557
+ status,
1558
+ confidence,
1559
+ lastAccessed,
1560
+ workspaceId,
1561
+ documentId,
1562
+ userId,
1563
+ charOffset,
1564
+ pageNumber,
1565
+ sourcePath,
1566
+ sourceType,
1567
+ tier,
1568
+ supersedesId
1569
+ ] : [
1570
+ row.id,
1571
+ row.agent_id,
1572
+ row.agent_role,
1573
+ row.session_id,
1574
+ row.timestamp,
1575
+ row.tool_name,
1576
+ row.project_name,
1577
+ row.has_error,
1578
+ row.raw_text,
1579
+ row.version,
1580
+ taskId,
1581
+ importance,
1582
+ status,
1583
+ confidence,
1584
+ lastAccessed,
1585
+ workspaceId,
1586
+ documentId,
1587
+ userId,
1588
+ charOffset,
1589
+ pageNumber,
1590
+ sourcePath,
1591
+ sourceType,
1592
+ tier,
1593
+ supersedesId
1594
+ ]
1595
+ };
1596
+ };
1597
+ const globalClient = getClient();
1598
+ const globalStmts = batch.map(buildStmt);
1599
+ await globalClient.batch(globalStmts, "write");
1600
+ _pendingRecords.splice(0, batch.length);
1601
+ try {
1602
+ const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1603
+ if (isShardingEnabled2()) {
1604
+ const byProject = /* @__PURE__ */ new Map();
1605
+ for (const row of batch) {
1606
+ const proj = row.project_name || "unknown";
1607
+ if (!byProject.has(proj)) byProject.set(proj, []);
1608
+ byProject.get(proj).push(row);
1609
+ }
1610
+ for (const [project, rows] of byProject) {
1611
+ try {
1612
+ const shardClient = await getReadyShardClient2(project);
1613
+ const shardStmts = rows.map(buildStmt);
1614
+ await shardClient.batch(shardStmts, "write");
1615
+ } catch (err) {
1616
+ process.stderr.write(
1617
+ `[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
1618
+ `
1619
+ );
1620
+ }
1621
+ }
1622
+ }
1623
+ } catch {
1624
+ }
1625
+ return batch.length;
1626
+ } finally {
1627
+ _flushing = false;
1628
+ }
1629
+ }
1630
+ function buildWikiScopeFilter(options, columnPrefix) {
1631
+ const args = [];
1632
+ let clause = "";
1633
+ if (options?.workspaceId !== void 0) {
1634
+ clause += ` AND ${columnPrefix}workspace_id = ?`;
1635
+ args.push(options.workspaceId);
1636
+ }
1637
+ if (options?.userId === void 0) {
1638
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
1639
+ } else if (options.userId === null) {
1640
+ clause += ` AND ${columnPrefix}user_id IS NULL`;
1641
+ } else {
1642
+ clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
1643
+ args.push(options.userId);
1644
+ }
1645
+ return { clause, args };
1646
+ }
1647
+ async function searchMemories(queryVector, agentId, options) {
1648
+ let client;
1649
+ try {
1650
+ const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1651
+ if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
1652
+ client = await getReadyShardClient2(options.projectName);
1653
+ } else {
1654
+ client = getClient();
1655
+ }
1656
+ } catch {
1657
+ client = getClient();
1658
+ }
1659
+ const limit = options?.limit ?? 10;
1660
+ const statusFilter = options?.includeArchived ? "" : `
1661
+ AND COALESCE(status, 'active') = 'active'`;
1662
+ let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
1663
+ tool_name, project_name,
1664
+ has_error, raw_text, vector, importance, status,
1665
+ confidence, last_accessed,
1666
+ workspace_id, document_id, user_id,
1667
+ char_offset, page_number,
1668
+ source_path, source_type
1669
+ FROM memories
1670
+ WHERE agent_id = ?
1671
+ AND vector IS NOT NULL${statusFilter}
1672
+ AND COALESCE(confidence, 0.7) >= 0.3`;
1673
+ const args = [agentId];
1674
+ const scope = buildWikiScopeFilter(options, "");
1675
+ sql += scope.clause;
1676
+ args.push(...scope.args);
1677
+ if (options?.projectName) {
1678
+ sql += ` AND project_name = ?`;
1679
+ args.push(options.projectName);
1680
+ }
1681
+ if (options?.toolName) {
1682
+ sql += ` AND tool_name = ?`;
1683
+ args.push(options.toolName);
1684
+ }
1685
+ if (options?.hasError !== void 0) {
1686
+ sql += ` AND has_error = ?`;
1687
+ args.push(options.hasError ? 1 : 0);
1688
+ }
1689
+ if (options?.since) {
1690
+ sql += ` AND timestamp >= ?`;
1691
+ args.push(options.since);
1692
+ }
1693
+ sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
1694
+ args.push(vectorToBlob(queryVector));
1695
+ sql += ` LIMIT ?`;
1696
+ args.push(limit);
1697
+ const result = await client.execute({ sql, args });
1698
+ return result.rows.map((row) => ({
1699
+ id: row.id,
1700
+ agent_id: row.agent_id,
1701
+ agent_role: row.agent_role,
1702
+ session_id: row.session_id,
1703
+ timestamp: row.timestamp,
1704
+ tool_name: row.tool_name,
1705
+ project_name: row.project_name,
1706
+ has_error: row.has_error === 1,
1707
+ raw_text: row.raw_text,
1708
+ vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
1709
+ importance: row.importance ?? 5,
1710
+ status: row.status ?? "active",
1711
+ confidence: row.confidence ?? 0.7,
1712
+ last_accessed: row.last_accessed ?? row.timestamp,
1713
+ workspace_id: row.workspace_id ?? null,
1714
+ document_id: row.document_id ?? null,
1715
+ user_id: row.user_id ?? null,
1716
+ char_offset: row.char_offset ?? null,
1717
+ page_number: row.page_number ?? null,
1718
+ source_path: row.source_path ?? null,
1719
+ source_type: row.source_type ?? null
1720
+ }));
1721
+ }
1722
+ async function attachDocumentMetadata(records) {
1723
+ const docIds = [
1724
+ ...new Set(
1725
+ records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
1726
+ )
1727
+ ];
1728
+ if (docIds.length === 0) return records;
1729
+ try {
1730
+ const client = getClient();
1731
+ const placeholders = docIds.map(() => "?").join(",");
1732
+ const result = await client.execute({
1733
+ sql: `SELECT id, filename, mime, source_type, uploaded_at
1734
+ FROM documents
1735
+ WHERE id IN (${placeholders})`,
1736
+ args: docIds
1737
+ });
1738
+ const byId = /* @__PURE__ */ new Map();
1739
+ for (const row of result.rows) {
1740
+ const id = row.id;
1741
+ byId.set(id, {
1742
+ document_id: id,
1743
+ filename: row.filename,
1744
+ mime: row.mime ?? null,
1745
+ source_type: row.source_type ?? null,
1746
+ uploaded_at: row.uploaded_at
1747
+ });
1748
+ }
1749
+ for (const record of records) {
1750
+ if (!record.document_id) continue;
1751
+ record.document_metadata = byId.get(record.document_id) ?? null;
1752
+ }
1753
+ } catch {
1754
+ }
1755
+ return records;
1756
+ }
1757
+ async function flushTier3(agentId, options) {
1758
+ const client = getClient();
1759
+ const maxAge = options?.maxAgeHours ?? 72;
1760
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
1761
+ if (options?.dryRun) {
1762
+ const result2 = await client.execute({
1763
+ sql: `SELECT COUNT(*) as cnt FROM memories
1764
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1765
+ args: [agentId, cutoff]
1766
+ });
1767
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
1768
+ }
1769
+ const result = await client.execute({
1770
+ sql: `UPDATE memories SET status = 'archived'
1771
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1772
+ args: [agentId, cutoff]
1773
+ });
1774
+ return { archived: result.rowsAffected };
1775
+ }
1776
+ async function disposeStore() {
1777
+ if (_flushTimer !== null) {
1778
+ clearInterval(_flushTimer);
1779
+ _flushTimer = null;
1780
+ }
1781
+ if (_pendingRecords.length > 0) {
1782
+ await flushBatch();
1783
+ }
1784
+ await disposeTurso();
1785
+ _pendingRecords = [];
1786
+ _nextVersion = 1;
1787
+ }
1788
+ function vectorToBlob(vector) {
1789
+ const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
1790
+ return JSON.stringify(Array.from(f32));
1791
+ }
1792
+ async function updateMemoryStatus(id, status) {
1793
+ const client = getClient();
1794
+ await client.execute({
1795
+ sql: `UPDATE memories SET status = ? WHERE id = ?`,
1796
+ args: [status, id]
1797
+ });
1798
+ }
1799
+ function reserveVersions(count) {
1800
+ const reserved = [];
1801
+ for (let i = 0; i < count; i++) {
1802
+ reserved.push(_nextVersion++);
1803
+ }
1804
+ return reserved;
1805
+ }
1806
+ async function getMemoryCardinality(agentId) {
1807
+ try {
1808
+ const client = getClient();
1809
+ const result = await client.execute({
1810
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
1811
+ args: [agentId]
1812
+ });
1813
+ return Number(result.rows[0]?.cnt) || 0;
1814
+ } catch {
1815
+ return 0;
1816
+ }
1817
+ }
1818
+ var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1819
+ var init_store = __esm({
1820
+ "src/lib/store.ts"() {
1821
+ "use strict";
1822
+ init_memory();
1823
+ init_database();
1824
+ init_keychain();
1825
+ init_config();
1826
+ _pendingRecords = [];
1827
+ _batchSize = 20;
1828
+ _flushIntervalMs = 1e4;
1829
+ _flushTimer = null;
1830
+ _flushing = false;
1831
+ _nextVersion = 1;
1832
+ }
1833
+ });
1834
+
1213
1835
  // src/lib/self-query-router.ts
1214
1836
  var self_query_router_exports = {};
1215
1837
  __export(self_query_router_exports, {
@@ -1715,12 +2337,23 @@ function getProjectName(cwd) {
1715
2337
  const dir = cwd ?? process.cwd();
1716
2338
  if (_cached && _cachedCwd === dir) return _cached;
1717
2339
  try {
1718
- const repoRoot = execSync("git rev-parse --show-toplevel", {
1719
- cwd: dir,
1720
- encoding: "utf8",
1721
- timeout: 2e3,
1722
- stdio: ["pipe", "pipe", "pipe"]
1723
- }).trim();
2340
+ let repoRoot;
2341
+ try {
2342
+ const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
2343
+ cwd: dir,
2344
+ encoding: "utf8",
2345
+ timeout: 2e3,
2346
+ stdio: ["pipe", "pipe", "pipe"]
2347
+ }).trim();
2348
+ repoRoot = path5.dirname(gitCommonDir);
2349
+ } catch {
2350
+ repoRoot = execSync("git rev-parse --show-toplevel", {
2351
+ cwd: dir,
2352
+ encoding: "utf8",
2353
+ timeout: 2e3,
2354
+ stdio: ["pipe", "pipe", "pipe"]
2355
+ }).trim();
2356
+ }
1724
2357
  _cached = path5.basename(repoRoot);
1725
2358
  _cachedCwd = dir;
1726
2359
  return _cached;
@@ -1752,15 +2385,30 @@ import { execSync as execSync2 } from "child_process";
1752
2385
  import { readFileSync as readFileSync3, readdirSync, statSync as statSync2, existsSync as existsSync5 } from "fs";
1753
2386
  import path6 from "path";
1754
2387
  import crypto2 from "crypto";
2388
+ function hasRipgrep() {
2389
+ if (_hasRg === null) {
2390
+ try {
2391
+ execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
2392
+ _hasRg = true;
2393
+ } catch {
2394
+ _hasRg = false;
2395
+ }
2396
+ }
2397
+ return _hasRg;
2398
+ }
1755
2399
  async function grepProjectFiles(query, projectRoot, options) {
1756
2400
  const maxResults = options?.maxResults ?? 10;
1757
2401
  const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_-]/g, "")).filter((t) => t.length >= 3);
1758
2402
  if (terms.length === 0) return [];
1759
2403
  const pattern = terms.join("|");
1760
2404
  let hits;
1761
- try {
1762
- hits = grepWithRipgrep(pattern, projectRoot, options?.patterns);
1763
- } catch {
2405
+ if (hasRipgrep()) {
2406
+ try {
2407
+ hits = grepWithRipgrep(pattern, projectRoot, options?.patterns);
2408
+ } catch {
2409
+ hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
2410
+ }
2411
+ } else {
1764
2412
  hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
1765
2413
  }
1766
2414
  hits.sort((a, b) => b.density - a.density);
@@ -1805,7 +2453,7 @@ function getChunkContext(filePath, lineNumber) {
1805
2453
  function grepWithRipgrep(pattern, projectRoot, patterns) {
1806
2454
  const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
1807
2455
  const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
1808
- const cmd = `rg -i -c --hidden '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
2456
+ const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
1809
2457
  const output = execSync2(cmd, {
1810
2458
  cwd: projectRoot,
1811
2459
  encoding: "utf8",
@@ -1934,10 +2582,11 @@ function buildSnippet(hit, projectRoot) {
1934
2582
  return hit.matchLine;
1935
2583
  }
1936
2584
  }
1937
- var DEFAULT_PATTERNS, EXCLUDE_DIRS, MAX_FILE_SIZE, MAX_FILES;
2585
+ var _hasRg, DEFAULT_PATTERNS, EXCLUDE_DIRS, MAX_FILE_SIZE, MAX_FILES;
1938
2586
  var init_file_grep = __esm({
1939
2587
  "src/lib/file-grep.ts"() {
1940
2588
  "use strict";
2589
+ _hasRg = null;
1941
2590
  DEFAULT_PATTERNS = [
1942
2591
  ".planning/*.md",
1943
2592
  "exe/output/*.md",
@@ -2361,15 +3010,21 @@ function queueIntercom(targetSession, reason) {
2361
3010
  }
2362
3011
  writeQueue(queue);
2363
3012
  }
2364
- function drainQueue(isSessionBusy2, sendKeys) {
3013
+ function drainQueue(isSessionBusy, sendKeys) {
2365
3014
  const queue = readQueue();
2366
3015
  if (queue.length === 0) return { drained: 0, failed: 0 };
2367
3016
  const remaining = [];
2368
3017
  let drained = 0;
2369
3018
  let failed = 0;
2370
3019
  for (const item of queue) {
3020
+ const age = Date.now() - new Date(item.queuedAt).getTime();
3021
+ if (age > TTL_MS) {
3022
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
3023
+ failed++;
3024
+ continue;
3025
+ }
2371
3026
  try {
2372
- if (!isSessionBusy2(item.targetSession)) {
3027
+ if (!isSessionBusy(item.targetSession)) {
2373
3028
  const success = sendKeys(item.targetSession);
2374
3029
  if (success) {
2375
3030
  logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
@@ -2414,12 +3069,13 @@ function logQueue(msg) {
2414
3069
  } catch {
2415
3070
  }
2416
3071
  }
2417
- var QUEUE_PATH, MAX_RETRIES, INTERCOM_LOG;
3072
+ var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
2418
3073
  var init_intercom_queue = __esm({
2419
3074
  "src/lib/intercom-queue.ts"() {
2420
3075
  "use strict";
2421
3076
  QUEUE_PATH = path10.join(os3.homedir(), ".exe-os", "intercom-queue.json");
2422
3077
  MAX_RETRIES = 5;
3078
+ TTL_MS = 60 * 60 * 1e3;
2423
3079
  INTERCOM_LOG = path10.join(os3.homedir(), ".exe-os", "intercom.log");
2424
3080
  }
2425
3081
  });
@@ -2570,6 +3226,17 @@ function getGitRoot(dir) {
2570
3226
  return null;
2571
3227
  }
2572
3228
  }
3229
+ function getMainRepoRoot(dir) {
3230
+ try {
3231
+ const commonDir = execSync7(
3232
+ "git rev-parse --path-format=absolute --git-common-dir",
3233
+ { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
3234
+ ).trim();
3235
+ return realpath(path14.dirname(commonDir));
3236
+ } catch {
3237
+ return null;
3238
+ }
3239
+ }
2573
3240
  function worktreePath(repoRoot, employeeName, instance) {
2574
3241
  const label = instanceLabel(employeeName, instance);
2575
3242
  return path14.join(repoRoot, ".worktrees", label);
@@ -2795,6 +3462,11 @@ function getSessionState(sessionName) {
2795
3462
  if (!transport.isAlive(sessionName)) return "offline";
2796
3463
  try {
2797
3464
  const pane = transport.capturePane(sessionName, 5);
3465
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
3466
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
3467
+ return "no_claude";
3468
+ }
3469
+ }
2798
3470
  if (/Running…/.test(pane)) return "tool";
2799
3471
  if (BUSY_PATTERN.test(pane)) return "thinking";
2800
3472
  return "idle";
@@ -2802,10 +3474,6 @@ function getSessionState(sessionName) {
2802
3474
  return "offline";
2803
3475
  }
2804
3476
  }
2805
- function isSessionBusy(sessionName) {
2806
- const state = getSessionState(sessionName);
2807
- return state === "thinking" || state === "tool";
2808
- }
2809
3477
  function isExeSession(sessionName) {
2810
3478
  return /^exe\d*$/.test(sessionName);
2811
3479
  }
@@ -2825,7 +3493,14 @@ function sendIntercom(targetSession) {
2825
3493
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
2826
3494
  return "failed";
2827
3495
  }
2828
- if (isSessionBusy(targetSession)) {
3496
+ const sessionState = getSessionState(targetSession);
3497
+ if (sessionState === "no_claude") {
3498
+ queueIntercom(targetSession, "claude not running in session");
3499
+ recordDebounce(targetSession);
3500
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
3501
+ return "queued";
3502
+ }
3503
+ if (sessionState === "thinking" || sessionState === "tool") {
2829
3504
  queueIntercom(targetSession, "session busy at send time");
2830
3505
  recordDebounce(targetSession);
2831
3506
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -2837,18 +3512,7 @@ function sendIntercom(targetSession) {
2837
3512
  }
2838
3513
  transport.sendKeys(targetSession, "/exe-intercom");
2839
3514
  recordDebounce(targetSession);
2840
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
2841
- try {
2842
- execSync8(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
2843
- } catch {
2844
- }
2845
- const state = getSessionState(targetSession);
2846
- if (state === "thinking" || state === "tool") {
2847
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
2848
- return "acknowledged";
2849
- }
2850
- }
2851
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
3515
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
2852
3516
  return "delivered";
2853
3517
  } catch {
2854
3518
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -2902,7 +3566,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
2902
3566
  return { status: "failed", sessionName, error: "intercom delivery failed" };
2903
3567
  }
2904
3568
  const spawnOpts = { ...opts, instance: effectiveInstance };
2905
- const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
3569
+ const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
3570
+ const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
2906
3571
  if (wtPath) {
2907
3572
  spawnOpts.cwd = wtPath;
2908
3573
  }
@@ -3083,7 +3748,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3083
3748
  let booted = false;
3084
3749
  for (let i = 0; i < 30; i++) {
3085
3750
  try {
3086
- execSync8("sleep 1");
3751
+ execSync8("sleep 0.5");
3087
3752
  } catch {
3088
3753
  }
3089
3754
  try {
@@ -3103,7 +3768,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3103
3768
  }
3104
3769
  }
3105
3770
  if (!booted) {
3106
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
3771
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3107
3772
  }
3108
3773
  if (!useExeAgent) {
3109
3774
  try {
@@ -3121,7 +3786,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3121
3786
  });
3122
3787
  return { sessionName };
3123
3788
  }
3124
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
3789
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3125
3790
  var init_tmux_routing = __esm({
3126
3791
  "src/lib/tmux-routing.ts"() {
3127
3792
  "use strict";
@@ -3141,8 +3806,6 @@ var init_tmux_routing = __esm({
3141
3806
  DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
3142
3807
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3143
3808
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
3144
- INTERCOM_POLL_INTERVAL_S = 1;
3145
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
3146
3809
  }
3147
3810
  });
3148
3811
 
@@ -3550,9 +4213,10 @@ async function createTaskCore(input2) {
3550
4213
  } catch {
3551
4214
  }
3552
4215
  }
4216
+ const complexity = input2.complexity ?? "standard";
3553
4217
  await client.execute({
3554
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
3555
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4218
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
4219
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3556
4220
  args: [
3557
4221
  id,
3558
4222
  input2.title,
@@ -3566,6 +4230,11 @@ async function createTaskCore(input2) {
3566
4230
  parentTaskId,
3567
4231
  input2.reviewer ?? null,
3568
4232
  input2.context,
4233
+ input2.budgetTokens ?? null,
4234
+ input2.budgetFallbackModel ?? null,
4235
+ 0,
4236
+ null,
4237
+ complexity,
3569
4238
  now,
3570
4239
  now
3571
4240
  ]
@@ -3581,7 +4250,11 @@ async function createTaskCore(input2) {
3581
4250
  taskFile,
3582
4251
  createdAt: now,
3583
4252
  updatedAt: now,
3584
- warning
4253
+ warning,
4254
+ budgetTokens: input2.budgetTokens ?? null,
4255
+ budgetFallbackModel: input2.budgetFallbackModel ?? null,
4256
+ tokensUsed: 0,
4257
+ tokensWarnedAt: null
3585
4258
  };
3586
4259
  }
3587
4260
  async function ensureArchitectureDoc(baseDir, projectName) {
@@ -3772,6 +4445,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
3772
4445
  "- **Approved:** mark this review task as done",
3773
4446
  "- **Needs work:** re-open the original task with notes, mark this review as done"
3774
4447
  ].join("\n");
4448
+ const originalTaskId = String(row.id);
3775
4449
  const reviewTask = await createTaskCore({
3776
4450
  title: `Review: ${agent} completed "${taskTitle}"`,
3777
4451
  assignedTo: reviewer,
@@ -3780,6 +4454,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
3780
4454
  priority: "p1",
3781
4455
  context: reviewContext,
3782
4456
  taskFile: reviewFile,
4457
+ parentTaskId: originalTaskId,
3783
4458
  skipDispatch: true
3784
4459
  });
3785
4460
  const reviewId = reviewTask.id;
@@ -3818,23 +4493,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3818
4493
  if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
3819
4494
  try {
3820
4495
  const client = getClient();
3821
- const fileName = taskFile.split("/").pop() ?? "";
3822
- const reviewPrefix = fileName.replace(".md", "");
3823
- const parts = reviewPrefix.split("-");
3824
- if (parts.length >= 3 && parts[0] === "review") {
3825
- const agent = parts[1];
3826
- const slug = parts.slice(2).join("-");
3827
- const originalTaskFile = `exe/${agent}/${slug}.md`;
4496
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4497
+ const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
4498
+ if (parentId) {
3828
4499
  const result = await client.execute({
3829
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
3830
- args: [(/* @__PURE__ */ new Date()).toISOString(), originalTaskFile]
4500
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
4501
+ args: [now, parentId]
3831
4502
  });
3832
4503
  if (result.rowsAffected > 0) {
3833
4504
  process.stderr.write(
3834
- `[review-cleanup] Cascaded original task to done: ${originalTaskFile}
4505
+ `[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
3835
4506
  `
3836
4507
  );
3837
4508
  }
4509
+ } else {
4510
+ const fileName = taskFile.split("/").pop() ?? "";
4511
+ const reviewPrefix = fileName.replace(".md", "");
4512
+ const parts = reviewPrefix.split("-");
4513
+ if (parts.length >= 3 && parts[0] === "review") {
4514
+ const agent = parts[1];
4515
+ const slug = parts.slice(2).join("-");
4516
+ const originalTaskFile = `exe/${agent}/${slug}.md`;
4517
+ const result = await client.execute({
4518
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
4519
+ args: [now, originalTaskFile]
4520
+ });
4521
+ if (result.rowsAffected > 0) {
4522
+ process.stderr.write(
4523
+ `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
4524
+ `
4525
+ );
4526
+ }
4527
+ }
3838
4528
  }
3839
4529
  } catch (err) {
3840
4530
  process.stderr.write(
@@ -3870,234 +4560,14 @@ var init_tasks_review = __esm({
3870
4560
  // src/adapters/claude/hooks/prompt-submit.ts
3871
4561
  init_config();
3872
4562
  init_config();
4563
+ init_store();
3873
4564
  import { spawn as spawn2 } from "child_process";
3874
4565
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, existsSync as existsSync17, openSync as openSync2, closeSync as closeSync2 } from "fs";
3875
4566
  import path19 from "path";
3876
4567
  import { fileURLToPath as fileURLToPath3 } from "url";
3877
4568
 
3878
- // src/lib/store.ts
3879
- init_memory();
3880
- init_database();
3881
-
3882
- // src/lib/keychain.ts
3883
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
3884
- import { existsSync as existsSync2 } from "fs";
3885
- import path2 from "path";
3886
- import crypto from "crypto";
3887
- var SERVICE = "exe-mem";
3888
- var ACCOUNT = "master-key";
3889
- function getKeyDir() {
3890
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
3891
- }
3892
- function getKeyPath() {
3893
- return path2.join(getKeyDir(), "master.key");
3894
- }
3895
- async function tryKeytar() {
3896
- try {
3897
- return await import("keytar");
3898
- } catch {
3899
- return null;
3900
- }
3901
- }
3902
- async function getMasterKey() {
3903
- const keytar = await tryKeytar();
3904
- if (keytar) {
3905
- try {
3906
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3907
- if (stored) {
3908
- return Buffer.from(stored, "base64");
3909
- }
3910
- } catch {
3911
- }
3912
- }
3913
- const keyPath = getKeyPath();
3914
- if (!existsSync2(keyPath)) {
3915
- return null;
3916
- }
3917
- try {
3918
- const content = await readFile2(keyPath, "utf-8");
3919
- return Buffer.from(content.trim(), "base64");
3920
- } catch {
3921
- return null;
3922
- }
3923
- }
3924
-
3925
- // src/lib/store.ts
3926
- init_config();
3927
- var _pendingRecords = [];
3928
- var _batchSize = 20;
3929
- var _flushIntervalMs = 1e4;
3930
- var _flushTimer = null;
3931
- var _flushing = false;
3932
- var _nextVersion = 1;
3933
- async function initStore(options) {
3934
- if (_flushTimer !== null) {
3935
- clearInterval(_flushTimer);
3936
- _flushTimer = null;
3937
- }
3938
- _pendingRecords = [];
3939
- _flushing = false;
3940
- _batchSize = options?.batchSize ?? 20;
3941
- _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
3942
- let dbPath = options?.dbPath;
3943
- if (!dbPath) {
3944
- const config = await loadConfig();
3945
- dbPath = config.dbPath;
3946
- }
3947
- let masterKey = options?.masterKey ?? null;
3948
- if (!masterKey) {
3949
- masterKey = await getMasterKey();
3950
- if (!masterKey) {
3951
- throw new Error(
3952
- "No encryption key found. Run /exe-setup to generate one."
3953
- );
3954
- }
3955
- }
3956
- const hexKey = masterKey.toString("hex");
3957
- await initTurso({
3958
- dbPath,
3959
- encryptionKey: hexKey
3960
- });
3961
- await ensureSchema();
3962
- try {
3963
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3964
- initShardManager2(hexKey);
3965
- } catch {
3966
- }
3967
- const client = getClient();
3968
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
3969
- _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
3970
- }
3971
- function buildWikiScopeFilter(options, columnPrefix) {
3972
- const args = [];
3973
- let clause = "";
3974
- if (options?.workspaceId !== void 0) {
3975
- clause += ` AND ${columnPrefix}workspace_id = ?`;
3976
- args.push(options.workspaceId);
3977
- }
3978
- if (options?.userId === void 0) {
3979
- clause += ` AND ${columnPrefix}user_id IS NULL`;
3980
- } else if (options.userId === null) {
3981
- clause += ` AND ${columnPrefix}user_id IS NULL`;
3982
- } else {
3983
- clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
3984
- args.push(options.userId);
3985
- }
3986
- return { clause, args };
3987
- }
3988
- async function searchMemories(queryVector, agentId, options) {
3989
- let client;
3990
- try {
3991
- const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3992
- if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
3993
- client = await getReadyShardClient2(options.projectName);
3994
- } else {
3995
- client = getClient();
3996
- }
3997
- } catch {
3998
- client = getClient();
3999
- }
4000
- const limit = options?.limit ?? 10;
4001
- const statusFilter = options?.includeArchived ? "" : `
4002
- AND COALESCE(status, 'active') = 'active'`;
4003
- let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4004
- tool_name, project_name,
4005
- has_error, raw_text, vector, importance, status,
4006
- confidence, last_accessed,
4007
- workspace_id, document_id, user_id,
4008
- char_offset, page_number
4009
- FROM memories
4010
- WHERE agent_id = ?
4011
- AND vector IS NOT NULL${statusFilter}
4012
- AND COALESCE(confidence, 0.7) >= 0.3`;
4013
- const args = [agentId];
4014
- const scope = buildWikiScopeFilter(options, "");
4015
- sql += scope.clause;
4016
- args.push(...scope.args);
4017
- if (options?.projectName) {
4018
- sql += ` AND project_name = ?`;
4019
- args.push(options.projectName);
4020
- }
4021
- if (options?.toolName) {
4022
- sql += ` AND tool_name = ?`;
4023
- args.push(options.toolName);
4024
- }
4025
- if (options?.hasError !== void 0) {
4026
- sql += ` AND has_error = ?`;
4027
- args.push(options.hasError ? 1 : 0);
4028
- }
4029
- if (options?.since) {
4030
- sql += ` AND timestamp >= ?`;
4031
- args.push(options.since);
4032
- }
4033
- sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
4034
- args.push(vectorToBlob(queryVector));
4035
- sql += ` LIMIT ?`;
4036
- args.push(limit);
4037
- const result = await client.execute({ sql, args });
4038
- return result.rows.map((row) => ({
4039
- id: row.id,
4040
- agent_id: row.agent_id,
4041
- agent_role: row.agent_role,
4042
- session_id: row.session_id,
4043
- timestamp: row.timestamp,
4044
- tool_name: row.tool_name,
4045
- project_name: row.project_name,
4046
- has_error: row.has_error === 1,
4047
- raw_text: row.raw_text,
4048
- vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
4049
- importance: row.importance ?? 5,
4050
- status: row.status ?? "active",
4051
- confidence: row.confidence ?? 0.7,
4052
- last_accessed: row.last_accessed ?? row.timestamp,
4053
- workspace_id: row.workspace_id ?? null,
4054
- document_id: row.document_id ?? null,
4055
- user_id: row.user_id ?? null,
4056
- char_offset: row.char_offset ?? null,
4057
- page_number: row.page_number ?? null
4058
- }));
4059
- }
4060
- async function attachDocumentMetadata(records) {
4061
- const docIds = [
4062
- ...new Set(
4063
- records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
4064
- )
4065
- ];
4066
- if (docIds.length === 0) return records;
4067
- try {
4068
- const client = getClient();
4069
- const placeholders = docIds.map(() => "?").join(",");
4070
- const result = await client.execute({
4071
- sql: `SELECT id, filename, mime, source_type, uploaded_at
4072
- FROM documents
4073
- WHERE id IN (${placeholders})`,
4074
- args: docIds
4075
- });
4076
- const byId = /* @__PURE__ */ new Map();
4077
- for (const row of result.rows) {
4078
- const id = row.id;
4079
- byId.set(id, {
4080
- document_id: id,
4081
- filename: row.filename,
4082
- mime: row.mime ?? null,
4083
- source_type: row.source_type ?? null,
4084
- uploaded_at: row.uploaded_at
4085
- });
4086
- }
4087
- for (const record of records) {
4088
- if (!record.document_id) continue;
4089
- record.document_metadata = byId.get(record.document_id) ?? null;
4090
- }
4091
- } catch {
4092
- }
4093
- return records;
4094
- }
4095
- function vectorToBlob(vector) {
4096
- const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
4097
- return JSON.stringify(Array.from(f32));
4098
- }
4099
-
4100
4569
  // src/lib/hybrid-search.ts
4570
+ init_store();
4101
4571
  init_database();
4102
4572
  var RRF_K = 60;
4103
4573
  async function hybridSearch(queryText, agentId, options) {
@@ -4125,8 +4595,21 @@ async function hybridSearch(queryText, agentId, options) {
4125
4595
  } catch {
4126
4596
  }
4127
4597
  }
4598
+ const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
4599
+ const cardinality = await getMemoryCardinality2(agentId);
4600
+ const { rerankerAutoTrigger } = config.scalingRoadmap ?? {};
4601
+ const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
4602
+ const useNarrowPath = cardinality < 1e4;
4603
+ const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
4604
+ const effectiveIsBroad = useBroadPath && !useNarrowPath;
4605
+ if (effectiveIsBroad !== _isBroadQuery) {
4606
+ process.stderr.write(
4607
+ `[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
4608
+ `
4609
+ );
4610
+ }
4128
4611
  const broadFetchTopK = config.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
4129
- const fetchLimit = _isBroadQuery ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
4612
+ const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
4130
4613
  const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
4131
4614
  let queryVector = null;
4132
4615
  try {
@@ -4170,8 +4653,8 @@ async function hybridSearch(queryText, agentId, options) {
4170
4653
  weights.push(0.5);
4171
4654
  }
4172
4655
  if (lists.length === 0) return [];
4173
- if (lists.length === 1 && !_isBroadQuery) return lists[0].slice(0, limit);
4174
- const rrfLimit = _isBroadQuery ? Math.max(limit * 5, 150) : limit;
4656
+ if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
4657
+ const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
4175
4658
  const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4176
4659
  const auto = config.scalingRoadmap?.rerankerAutoTrigger ?? {
4177
4660
  enabled: config.rerankerEnabled ?? true,
@@ -4180,9 +4663,9 @@ async function hybridSearch(queryText, agentId, options) {
4180
4663
  returnTopK: 5
4181
4664
  };
4182
4665
  let rerankedAndBlended = null;
4183
- if (_isBroadQuery && auto.enabled) {
4184
- const cardinality = await estimateCardinality(agentId, effectiveOptions);
4185
- if (cardinality > auto.broadQueryMinCardinality) {
4666
+ if (effectiveIsBroad && auto.enabled) {
4667
+ const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
4668
+ if (cardinality2 > auto.broadQueryMinCardinality) {
4186
4669
  try {
4187
4670
  const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4188
4671
  if (isRerankerAvailable2()) {
@@ -4259,6 +4742,11 @@ function recencyScore(timestamp) {
4259
4742
  function normalizedImportance(importance) {
4260
4743
  return ((importance ?? 5) - 1) / 9;
4261
4744
  }
4745
+ function frecencyBoost(lastAccessed, timestamp) {
4746
+ const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
4747
+ const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
4748
+ return Math.exp(-0.01 * hoursSince);
4749
+ }
4262
4750
  function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
4263
4751
  const scores = /* @__PURE__ */ new Map();
4264
4752
  for (let listIdx = 0; listIdx < lists.length; listIdx++) {
@@ -4275,7 +4763,9 @@ function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
4275
4763
  const recency = recencyScore(e.record.timestamp);
4276
4764
  const importance = normalizedImportance(e.record.importance);
4277
4765
  const confidence = e.record.confidence ?? 0.7;
4278
- const finalScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
4766
+ const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
4767
+ const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
4768
+ const finalScore = baseScore * (1 + 0.3 * frecency);
4279
4769
  return { score: finalScore, record: e.record };
4280
4770
  });
4281
4771
  return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
@@ -4315,7 +4805,8 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
4315
4805
  m.has_error, m.raw_text, m.vector, m.task_id,
4316
4806
  m.importance, m.status, m.confidence, m.last_accessed,
4317
4807
  m.workspace_id, m.document_id, m.user_id,
4318
- m.char_offset, m.page_number
4808
+ m.char_offset, m.page_number,
4809
+ m.source_path, m.source_type
4319
4810
  FROM memories m
4320
4811
  JOIN memories_fts fts ON m.rowid = fts.rowid
4321
4812
  WHERE memories_fts MATCH ?
@@ -4364,7 +4855,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
4364
4855
  document_id: row.document_id ?? null,
4365
4856
  user_id: row.user_id ?? null,
4366
4857
  char_offset: row.char_offset ?? null,
4367
- page_number: row.page_number ?? null
4858
+ page_number: row.page_number ?? null,
4859
+ source_path: row.source_path ?? null,
4860
+ source_type: row.source_type ?? null
4368
4861
  }));
4369
4862
  }
4370
4863
  async function recentRecords(agentId, options, limit) {
@@ -4376,7 +4869,8 @@ async function recentRecords(agentId, options, limit) {
4376
4869
  has_error, raw_text, vector, task_id,
4377
4870
  importance, status, confidence, last_accessed,
4378
4871
  workspace_id, document_id, user_id,
4379
- char_offset, page_number
4872
+ char_offset, page_number,
4873
+ source_path, source_type
4380
4874
  FROM memories
4381
4875
  WHERE agent_id = ?${statusFilter}
4382
4876
  AND COALESCE(confidence, 0.7) >= 0.3`;
@@ -4423,7 +4917,9 @@ async function recentRecords(agentId, options, limit) {
4423
4917
  document_id: row.document_id ?? null,
4424
4918
  user_id: row.user_id ?? null,
4425
4919
  char_offset: row.char_offset ?? null,
4426
- page_number: row.page_number ?? null
4920
+ page_number: row.page_number ?? null,
4921
+ source_path: row.source_path ?? null,
4922
+ source_type: row.source_type ?? null
4427
4923
  }));
4428
4924
  }
4429
4925