@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
package/dist/lib/store.js CHANGED
@@ -80,6 +80,11 @@ function normalizeSessionLifecycle(raw) {
80
80
  const userSL = raw.sessionLifecycle ?? {};
81
81
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
82
82
  }
83
+ function normalizeAutoUpdate(raw) {
84
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
85
+ const userAU = raw.autoUpdate ?? {};
86
+ raw.autoUpdate = { ...defaultAU, ...userAU };
87
+ }
83
88
  async function loadConfig() {
84
89
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
85
90
  await mkdir2(dir, { recursive: true });
@@ -102,6 +107,7 @@ async function loadConfig() {
102
107
  }
103
108
  normalizeScalingRoadmap(migratedCfg);
104
109
  normalizeSessionLifecycle(migratedCfg);
110
+ normalizeAutoUpdate(migratedCfg);
105
111
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
106
112
  if (config.dbPath.startsWith("~")) {
107
113
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -177,6 +183,11 @@ var init_config = __esm({
177
183
  idleKillTicksRequired: 3,
178
184
  idleKillIntercomAckWindowMs: 1e4,
179
185
  maxAutoInstances: 10
186
+ },
187
+ autoUpdate: {
188
+ checkOnBoot: true,
189
+ autoInstall: false,
190
+ checkIntervalMs: 24 * 60 * 60 * 1e3
180
191
  }
181
192
  };
182
193
  CONFIG_MIGRATIONS = [
@@ -310,13 +321,27 @@ async function ensureShardSchema(client) {
310
321
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
311
322
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
312
323
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
313
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
324
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
325
+ // Source provenance columns (must match database.ts)
326
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
327
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
328
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
329
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
314
330
  ]) {
315
331
  try {
316
332
  await client.execute(col);
317
333
  } catch {
318
334
  }
319
335
  }
336
+ for (const idx of [
337
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
338
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
339
+ ]) {
340
+ try {
341
+ await client.execute(idx);
342
+ } catch {
343
+ }
344
+ }
320
345
  try {
321
346
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
322
347
  } catch {
@@ -633,6 +658,27 @@ async function ensureSchema() {
633
658
  });
634
659
  } catch {
635
660
  }
661
+ try {
662
+ await client.execute({
663
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
664
+ args: []
665
+ });
666
+ } catch {
667
+ }
668
+ try {
669
+ await client.execute({
670
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
671
+ args: []
672
+ });
673
+ } catch {
674
+ }
675
+ try {
676
+ await client.execute({
677
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
678
+ args: []
679
+ });
680
+ } catch {
681
+ }
636
682
  try {
637
683
  await client.execute({
638
684
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -1043,6 +1089,15 @@ async function ensureSchema() {
1043
1089
  } catch {
1044
1090
  }
1045
1091
  }
1092
+ for (const col of [
1093
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1094
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
1095
+ ]) {
1096
+ try {
1097
+ await client.execute(col);
1098
+ } catch {
1099
+ }
1100
+ }
1046
1101
  await client.executeMultiple(`
1047
1102
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
1048
1103
  ON memories(workspace_id);
@@ -1107,6 +1162,34 @@ async function ensureSchema() {
1107
1162
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1108
1163
  ON conversations(channel_id);
1109
1164
  `);
1165
+ try {
1166
+ await client.execute({
1167
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
1168
+ args: []
1169
+ });
1170
+ } catch {
1171
+ }
1172
+ try {
1173
+ await client.execute({
1174
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
1175
+ args: []
1176
+ });
1177
+ } catch {
1178
+ }
1179
+ try {
1180
+ await client.execute({
1181
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
1182
+ args: []
1183
+ });
1184
+ } catch {
1185
+ }
1186
+ try {
1187
+ await client.execute({
1188
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
1189
+ args: []
1190
+ });
1191
+ } catch {
1192
+ }
1110
1193
  await client.executeMultiple(`
1111
1194
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
1112
1195
  content_text,
@@ -1133,6 +1216,52 @@ async function ensureSchema() {
1133
1216
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1134
1217
  END;
1135
1218
  `);
1219
+ try {
1220
+ await client.execute({
1221
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
1222
+ args: []
1223
+ });
1224
+ } catch {
1225
+ }
1226
+ try {
1227
+ await client.execute(
1228
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1229
+ );
1230
+ } catch {
1231
+ }
1232
+ try {
1233
+ await client.execute({
1234
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1235
+ args: []
1236
+ });
1237
+ await client.execute({
1238
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1239
+ args: []
1240
+ });
1241
+ } catch {
1242
+ }
1243
+ try {
1244
+ await client.execute({
1245
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1246
+ args: []
1247
+ });
1248
+ } catch {
1249
+ }
1250
+ try {
1251
+ await client.execute(
1252
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1253
+ );
1254
+ } catch {
1255
+ }
1256
+ for (const col of [
1257
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1258
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1259
+ ]) {
1260
+ try {
1261
+ await client.execute(col);
1262
+ } catch {
1263
+ }
1264
+ }
1136
1265
  }
1137
1266
  var disposeTurso = disposeDatabase;
1138
1267
  async function disposeDatabase() {
@@ -1231,6 +1360,11 @@ async function initStore(options) {
1231
1360
  const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1232
1361
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1233
1362
  }
1363
+ function classifyTier(record) {
1364
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
1365
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1366
+ return 3;
1367
+ }
1234
1368
  async function writeMemory(record) {
1235
1369
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1236
1370
  throw new Error(
@@ -1258,7 +1392,11 @@ async function writeMemory(record) {
1258
1392
  document_id: record.document_id ?? null,
1259
1393
  user_id: record.user_id ?? null,
1260
1394
  char_offset: record.char_offset ?? null,
1261
- page_number: record.page_number ?? null
1395
+ page_number: record.page_number ?? null,
1396
+ source_path: record.source_path ?? null,
1397
+ source_type: record.source_type ?? null,
1398
+ tier: record.tier ?? classifyTier(record),
1399
+ supersedes_id: record.supersedes_id ?? null
1262
1400
  };
1263
1401
  _pendingRecords.push(dbRow);
1264
1402
  if (_flushTimer === null) {
@@ -1290,20 +1428,26 @@ async function flushBatch() {
1290
1428
  const userId = row.user_id ?? null;
1291
1429
  const charOffset = row.char_offset ?? null;
1292
1430
  const pageNumber = row.page_number ?? null;
1431
+ const sourcePath = row.source_path ?? null;
1432
+ const sourceType = row.source_type ?? null;
1433
+ const tier = row.tier ?? 3;
1434
+ const supersedesId = row.supersedes_id ?? null;
1293
1435
  return {
1294
1436
  sql: hasVector ? `INSERT OR IGNORE INTO memories
1295
1437
  (id, agent_id, agent_role, session_id, timestamp,
1296
1438
  tool_name, project_name,
1297
1439
  has_error, raw_text, vector, version, task_id, importance, status,
1298
1440
  confidence, last_accessed,
1299
- workspace_id, document_id, user_id, char_offset, page_number)
1300
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1441
+ workspace_id, document_id, user_id, char_offset, page_number,
1442
+ source_path, source_type, tier, supersedes_id)
1443
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1301
1444
  (id, agent_id, agent_role, session_id, timestamp,
1302
1445
  tool_name, project_name,
1303
1446
  has_error, raw_text, vector, version, task_id, importance, status,
1304
1447
  confidence, last_accessed,
1305
- workspace_id, document_id, user_id, char_offset, page_number)
1306
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1448
+ workspace_id, document_id, user_id, char_offset, page_number,
1449
+ source_path, source_type, tier, supersedes_id)
1450
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1307
1451
  args: hasVector ? [
1308
1452
  row.id,
1309
1453
  row.agent_id,
@@ -1325,7 +1469,11 @@ async function flushBatch() {
1325
1469
  documentId,
1326
1470
  userId,
1327
1471
  charOffset,
1328
- pageNumber
1472
+ pageNumber,
1473
+ sourcePath,
1474
+ sourceType,
1475
+ tier,
1476
+ supersedesId
1329
1477
  ] : [
1330
1478
  row.id,
1331
1479
  row.agent_id,
@@ -1346,7 +1494,11 @@ async function flushBatch() {
1346
1494
  documentId,
1347
1495
  userId,
1348
1496
  charOffset,
1349
- pageNumber
1497
+ pageNumber,
1498
+ sourcePath,
1499
+ sourceType,
1500
+ tier,
1501
+ supersedesId
1350
1502
  ]
1351
1503
  };
1352
1504
  };
@@ -1420,7 +1572,8 @@ async function searchMemories(queryVector, agentId, options) {
1420
1572
  has_error, raw_text, vector, importance, status,
1421
1573
  confidence, last_accessed,
1422
1574
  workspace_id, document_id, user_id,
1423
- char_offset, page_number
1575
+ char_offset, page_number,
1576
+ source_path, source_type
1424
1577
  FROM memories
1425
1578
  WHERE agent_id = ?
1426
1579
  AND vector IS NOT NULL${statusFilter}
@@ -1469,7 +1622,9 @@ async function searchMemories(queryVector, agentId, options) {
1469
1622
  document_id: row.document_id ?? null,
1470
1623
  user_id: row.user_id ?? null,
1471
1624
  char_offset: row.char_offset ?? null,
1472
- page_number: row.page_number ?? null
1625
+ page_number: row.page_number ?? null,
1626
+ source_path: row.source_path ?? null,
1627
+ source_type: row.source_type ?? null
1473
1628
  }));
1474
1629
  }
1475
1630
  async function attachDocumentMetadata(records) {
@@ -1507,6 +1662,25 @@ async function attachDocumentMetadata(records) {
1507
1662
  }
1508
1663
  return records;
1509
1664
  }
1665
+ async function flushTier3(agentId, options) {
1666
+ const client = getClient();
1667
+ const maxAge = options?.maxAgeHours ?? 72;
1668
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
1669
+ if (options?.dryRun) {
1670
+ const result2 = await client.execute({
1671
+ sql: `SELECT COUNT(*) as cnt FROM memories
1672
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1673
+ args: [agentId, cutoff]
1674
+ });
1675
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
1676
+ }
1677
+ const result = await client.execute({
1678
+ sql: `UPDATE memories SET status = 'archived'
1679
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1680
+ args: [agentId, cutoff]
1681
+ });
1682
+ return { archived: result.rowsAffected };
1683
+ }
1510
1684
  async function disposeStore() {
1511
1685
  if (_flushTimer !== null) {
1512
1686
  clearInterval(_flushTimer);
@@ -1537,11 +1711,26 @@ function reserveVersions(count) {
1537
1711
  }
1538
1712
  return reserved;
1539
1713
  }
1714
+ async function getMemoryCardinality(agentId) {
1715
+ try {
1716
+ const client = getClient();
1717
+ const result = await client.execute({
1718
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
1719
+ args: [agentId]
1720
+ });
1721
+ return Number(result.rows[0]?.cnt) || 0;
1722
+ } catch {
1723
+ return 0;
1724
+ }
1725
+ }
1540
1726
  export {
1541
1727
  attachDocumentMetadata,
1542
1728
  buildWikiScopeFilter,
1729
+ classifyTier,
1543
1730
  disposeStore,
1544
1731
  flushBatch,
1732
+ flushTier3,
1733
+ getMemoryCardinality,
1545
1734
  initStore,
1546
1735
  reserveVersions,
1547
1736
  searchMemories,
@@ -1,5 +1,46 @@
1
1
  // src/lib/task-router.ts
2
2
  import { randomUUID } from "crypto";
3
+ var DEFAULT_BLOOM_CONFIG = {
4
+ complexityToTier: {
5
+ routine: "junior",
6
+ standard: "standard",
7
+ complex: "senior",
8
+ critical: "specialist"
9
+ },
10
+ tierRules: {
11
+ junior: {
12
+ eligible: ["tom"],
13
+ reviewRequired: false,
14
+ manualOnly: false
15
+ },
16
+ standard: {
17
+ eligible: ["tom"],
18
+ reviewRequired: false,
19
+ manualOnly: false
20
+ },
21
+ senior: {
22
+ eligible: [],
23
+ // any specialist, but review required
24
+ reviewRequired: true,
25
+ manualOnly: false
26
+ },
27
+ specialist: {
28
+ eligible: [],
29
+ reviewRequired: true,
30
+ manualOnly: true
31
+ }
32
+ }
33
+ };
34
+ function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
35
+ const tier = config.complexityToTier[complexity];
36
+ const rule = config.tierRules[tier];
37
+ return {
38
+ tier,
39
+ reviewRequired: rule.reviewRequired,
40
+ manualOnly: rule.manualOnly,
41
+ eligible: rule.eligible
42
+ };
43
+ }
3
44
  async function scoreEmployee(taskVector, agentId, searchFn) {
4
45
  const results = await searchFn(taskVector, agentId, { limit: 5 });
5
46
  if (results.length === 0) {
@@ -12,13 +53,29 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
12
53
  }
13
54
  return { agentId, score: results.length / 5 };
14
55
  }
15
- async function routeTask(taskDescription, employees, embedFn, searchFn) {
16
- const specialists = employees.filter((e) => e.name !== "exe");
56
+ async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
57
+ let specialists = employees.filter((e) => e.name !== "exe");
17
58
  if (specialists.length === 0) {
18
59
  throw new Error(
19
60
  "No specialist employees available. Create one with /exe-new-employee."
20
61
  );
21
62
  }
63
+ let bloomRouting;
64
+ if (options?.complexity) {
65
+ bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
66
+ if (bloomRouting.manualOnly) {
67
+ throw new Error(
68
+ `Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
69
+ );
70
+ }
71
+ if (bloomRouting.eligible.length > 0) {
72
+ const eligible = new Set(bloomRouting.eligible);
73
+ const filtered = specialists.filter((e) => eligible.has(e.name));
74
+ if (filtered.length > 0) {
75
+ specialists = filtered;
76
+ }
77
+ }
78
+ }
22
79
  const taskVector = await embedFn(taskDescription);
23
80
  const scored = await Promise.all(
24
81
  specialists.map(async (emp) => {
@@ -27,7 +84,7 @@ async function routeTask(taskDescription, employees, embedFn, searchFn) {
27
84
  })
28
85
  );
29
86
  scored.sort((a, b) => b.score - a.score);
30
- return scored[0];
87
+ return { ...scored[0], bloomRouting };
31
88
  }
32
89
  function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
33
90
  return {
@@ -44,19 +101,28 @@ function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
44
101
  // Will be backfilled
45
102
  };
46
103
  }
47
- function formatAssignmentOutput(employee, task, score, mode) {
104
+ function formatAssignmentOutput(employee, task, score, mode, bloomRouting) {
48
105
  const modeLabel = mode === "auto" ? "auto-routed" : "direct";
49
106
  const percent = Math.round(score * 100);
50
- return [
107
+ const lines = [
51
108
  `Assigned to ${employee.name} (${employee.role})`,
52
109
  `Mode: ${modeLabel}`,
53
110
  `Relevance: ${percent}%`,
54
111
  `Task: ${task}`
55
- ].join("\n");
112
+ ];
113
+ if (bloomRouting) {
114
+ lines.push(`Complexity tier: ${bloomRouting.tier}`);
115
+ if (bloomRouting.reviewRequired) {
116
+ lines.push(`Review: required`);
117
+ }
118
+ }
119
+ return lines.join("\n");
56
120
  }
57
121
  export {
122
+ DEFAULT_BLOOM_CONFIG,
58
123
  createAssignmentMemory,
59
124
  formatAssignmentOutput,
125
+ resolveBloomRouting,
60
126
  routeTask,
61
127
  scoreEmployee
62
128
  };