@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
@@ -243,6 +243,27 @@ async function ensureSchema() {
243
243
  });
244
244
  } catch {
245
245
  }
246
+ try {
247
+ await client.execute({
248
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
249
+ args: []
250
+ });
251
+ } catch {
252
+ }
253
+ try {
254
+ await client.execute({
255
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
256
+ args: []
257
+ });
258
+ } catch {
259
+ }
260
+ try {
261
+ await client.execute({
262
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
263
+ args: []
264
+ });
265
+ } catch {
266
+ }
246
267
  try {
247
268
  await client.execute({
248
269
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -653,6 +674,15 @@ async function ensureSchema() {
653
674
  } catch {
654
675
  }
655
676
  }
677
+ for (const col of [
678
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
679
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
680
+ ]) {
681
+ try {
682
+ await client.execute(col);
683
+ } catch {
684
+ }
685
+ }
656
686
  await client.executeMultiple(`
657
687
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
658
688
  ON memories(workspace_id);
@@ -717,6 +747,34 @@ async function ensureSchema() {
717
747
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
718
748
  ON conversations(channel_id);
719
749
  `);
750
+ try {
751
+ await client.execute({
752
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
753
+ args: []
754
+ });
755
+ } catch {
756
+ }
757
+ try {
758
+ await client.execute({
759
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
760
+ args: []
761
+ });
762
+ } catch {
763
+ }
764
+ try {
765
+ await client.execute({
766
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
767
+ args: []
768
+ });
769
+ } catch {
770
+ }
771
+ try {
772
+ await client.execute({
773
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
774
+ args: []
775
+ });
776
+ } catch {
777
+ }
720
778
  await client.executeMultiple(`
721
779
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
722
780
  content_text,
@@ -743,6 +801,52 @@ async function ensureSchema() {
743
801
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
744
802
  END;
745
803
  `);
804
+ try {
805
+ await client.execute({
806
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
807
+ args: []
808
+ });
809
+ } catch {
810
+ }
811
+ try {
812
+ await client.execute(
813
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
814
+ );
815
+ } catch {
816
+ }
817
+ try {
818
+ await client.execute({
819
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
820
+ args: []
821
+ });
822
+ await client.execute({
823
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
824
+ args: []
825
+ });
826
+ } catch {
827
+ }
828
+ try {
829
+ await client.execute({
830
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
831
+ args: []
832
+ });
833
+ } catch {
834
+ }
835
+ try {
836
+ await client.execute(
837
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
838
+ );
839
+ } catch {
840
+ }
841
+ for (const col of [
842
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
843
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
844
+ ]) {
845
+ try {
846
+ await client.execute(col);
847
+ } catch {
848
+ }
849
+ }
746
850
  }
747
851
  var _client, initTurso;
748
852
  var init_database = __esm({
@@ -834,6 +938,11 @@ function normalizeSessionLifecycle(raw) {
834
938
  const userSL = raw.sessionLifecycle ?? {};
835
939
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
836
940
  }
941
+ function normalizeAutoUpdate(raw) {
942
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
943
+ const userAU = raw.autoUpdate ?? {};
944
+ raw.autoUpdate = { ...defaultAU, ...userAU };
945
+ }
837
946
  async function loadConfig() {
838
947
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
839
948
  await mkdir2(dir, { recursive: true });
@@ -856,6 +965,7 @@ async function loadConfig() {
856
965
  }
857
966
  normalizeScalingRoadmap(migratedCfg);
858
967
  normalizeSessionLifecycle(migratedCfg);
968
+ normalizeAutoUpdate(migratedCfg);
859
969
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
860
970
  if (config.dbPath.startsWith("~")) {
861
971
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -878,6 +988,7 @@ function loadConfigSync() {
878
988
  const { config: migratedCfg } = migrateConfig(parsed);
879
989
  normalizeScalingRoadmap(migratedCfg);
880
990
  normalizeSessionLifecycle(migratedCfg);
991
+ normalizeAutoUpdate(migratedCfg);
881
992
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
882
993
  } catch {
883
994
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
@@ -897,6 +1008,7 @@ async function loadConfigFrom(configPath) {
897
1008
  const { config: migratedCfg } = migrateConfig(parsed);
898
1009
  normalizeScalingRoadmap(migratedCfg);
899
1010
  normalizeSessionLifecycle(migratedCfg);
1011
+ normalizeAutoUpdate(migratedCfg);
900
1012
  return { ...DEFAULT_CONFIG, ...migratedCfg };
901
1013
  } catch {
902
1014
  return { ...DEFAULT_CONFIG };
@@ -968,6 +1080,11 @@ var init_config = __esm({
968
1080
  idleKillTicksRequired: 3,
969
1081
  idleKillIntercomAckWindowMs: 1e4,
970
1082
  maxAutoInstances: 10
1083
+ },
1084
+ autoUpdate: {
1085
+ checkOnBoot: true,
1086
+ autoInstall: false,
1087
+ checkIntervalMs: 24 * 60 * 60 * 1e3
971
1088
  }
972
1089
  };
973
1090
  CONFIG_MIGRATIONS = [
@@ -1101,13 +1218,27 @@ async function ensureShardSchema(client) {
1101
1218
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
1102
1219
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
1103
1220
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
1104
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
1221
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
1222
+ // Source provenance columns (must match database.ts)
1223
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1224
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1225
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1226
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1105
1227
  ]) {
1106
1228
  try {
1107
1229
  await client.execute(col);
1108
1230
  } catch {
1109
1231
  }
1110
1232
  }
1233
+ for (const idx of [
1234
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
1235
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
1236
+ ]) {
1237
+ try {
1238
+ await client.execute(idx);
1239
+ } catch {
1240
+ }
1241
+ }
1111
1242
  try {
1112
1243
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
1113
1244
  } catch {
@@ -1218,12 +1349,23 @@ function getProjectName(cwd) {
1218
1349
  const dir = cwd ?? process.cwd();
1219
1350
  if (_cached && _cachedCwd === dir) return _cached;
1220
1351
  try {
1221
- const repoRoot = execSync("git rev-parse --show-toplevel", {
1222
- cwd: dir,
1223
- encoding: "utf8",
1224
- timeout: 2e3,
1225
- stdio: ["pipe", "pipe", "pipe"]
1226
- }).trim();
1352
+ let repoRoot;
1353
+ try {
1354
+ const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
1355
+ cwd: dir,
1356
+ encoding: "utf8",
1357
+ timeout: 2e3,
1358
+ stdio: ["pipe", "pipe", "pipe"]
1359
+ }).trim();
1360
+ repoRoot = path4.dirname(gitCommonDir);
1361
+ } catch {
1362
+ repoRoot = execSync("git rev-parse --show-toplevel", {
1363
+ cwd: dir,
1364
+ encoding: "utf8",
1365
+ timeout: 2e3,
1366
+ stdio: ["pipe", "pipe", "pipe"]
1367
+ }).trim();
1368
+ }
1227
1369
  _cached = path4.basename(repoRoot);
1228
1370
  _cachedCwd = dir;
1229
1371
  return _cached;
@@ -1828,9 +1970,10 @@ async function createTaskCore(input) {
1828
1970
  } catch {
1829
1971
  }
1830
1972
  }
1973
+ const complexity = input.complexity ?? "standard";
1831
1974
  await client.execute({
1832
- 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)
1833
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1975
+ 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)
1976
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1834
1977
  args: [
1835
1978
  id,
1836
1979
  input.title,
@@ -1844,6 +1987,11 @@ async function createTaskCore(input) {
1844
1987
  parentTaskId,
1845
1988
  input.reviewer ?? null,
1846
1989
  input.context,
1990
+ input.budgetTokens ?? null,
1991
+ input.budgetFallbackModel ?? null,
1992
+ 0,
1993
+ null,
1994
+ complexity,
1847
1995
  now,
1848
1996
  now
1849
1997
  ]
@@ -1859,7 +2007,11 @@ async function createTaskCore(input) {
1859
2007
  taskFile,
1860
2008
  createdAt: now,
1861
2009
  updatedAt: now,
1862
- warning
2010
+ warning,
2011
+ budgetTokens: input.budgetTokens ?? null,
2012
+ budgetFallbackModel: input.budgetFallbackModel ?? null,
2013
+ tokensUsed: 0,
2014
+ tokensWarnedAt: null
1863
2015
  };
1864
2016
  }
1865
2017
  async function ensureArchitectureDoc(baseDir, projectName) {
@@ -2138,11 +2290,12 @@ function queueIntercom(targetSession, reason) {
2138
2290
  }
2139
2291
  writeQueue(queue);
2140
2292
  }
2141
- var QUEUE_PATH, INTERCOM_LOG;
2293
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2142
2294
  var init_intercom_queue = __esm({
2143
2295
  "src/lib/intercom-queue.ts"() {
2144
2296
  "use strict";
2145
2297
  QUEUE_PATH = path10.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2298
+ TTL_MS = 60 * 60 * 1e3;
2146
2299
  INTERCOM_LOG = path10.join(os4.homedir(), ".exe-os", "intercom.log");
2147
2300
  }
2148
2301
  });
@@ -2390,7 +2543,6 @@ var init_worktree = __esm({
2390
2543
  });
2391
2544
 
2392
2545
  // src/lib/tmux-routing.ts
2393
- import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
2394
2546
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, existsSync as existsSync12, appendFileSync as appendFileSync2 } from "fs";
2395
2547
  import path14 from "path";
2396
2548
  import os5 from "os";
@@ -2447,6 +2599,11 @@ function getSessionState(sessionName) {
2447
2599
  if (!transport.isAlive(sessionName)) return "offline";
2448
2600
  try {
2449
2601
  const pane = transport.capturePane(sessionName, 5);
2602
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
2603
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
2604
+ return "no_claude";
2605
+ }
2606
+ }
2450
2607
  if (/Running…/.test(pane)) return "tool";
2451
2608
  if (BUSY_PATTERN.test(pane)) return "thinking";
2452
2609
  return "idle";
@@ -2454,10 +2611,6 @@ function getSessionState(sessionName) {
2454
2611
  return "offline";
2455
2612
  }
2456
2613
  }
2457
- function isSessionBusy(sessionName) {
2458
- const state = getSessionState(sessionName);
2459
- return state === "thinking" || state === "tool";
2460
- }
2461
2614
  function isExeSession(sessionName) {
2462
2615
  return /^exe\d*$/.test(sessionName);
2463
2616
  }
@@ -2477,7 +2630,14 @@ function sendIntercom(targetSession) {
2477
2630
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
2478
2631
  return "failed";
2479
2632
  }
2480
- if (isSessionBusy(targetSession)) {
2633
+ const sessionState = getSessionState(targetSession);
2634
+ if (sessionState === "no_claude") {
2635
+ queueIntercom(targetSession, "claude not running in session");
2636
+ recordDebounce(targetSession);
2637
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
2638
+ return "queued";
2639
+ }
2640
+ if (sessionState === "thinking" || sessionState === "tool") {
2481
2641
  queueIntercom(targetSession, "session busy at send time");
2482
2642
  recordDebounce(targetSession);
2483
2643
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -2489,25 +2649,14 @@ function sendIntercom(targetSession) {
2489
2649
  }
2490
2650
  transport.sendKeys(targetSession, "/exe-intercom");
2491
2651
  recordDebounce(targetSession);
2492
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
2493
- try {
2494
- execSync7(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
2495
- } catch {
2496
- }
2497
- const state = getSessionState(targetSession);
2498
- if (state === "thinking" || state === "tool") {
2499
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
2500
- return "acknowledged";
2501
- }
2502
- }
2503
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
2652
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
2504
2653
  return "delivered";
2505
2654
  } catch {
2506
2655
  logIntercom(`FAIL \u2192 ${targetSession}`);
2507
2656
  return "failed";
2508
2657
  }
2509
2658
  }
2510
- var SESSION_CACHE, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
2659
+ var SESSION_CACHE, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
2511
2660
  var init_tmux_routing = __esm({
2512
2661
  "src/lib/tmux-routing.ts"() {
2513
2662
  "use strict";
@@ -2526,8 +2675,6 @@ var init_tmux_routing = __esm({
2526
2675
  DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
2527
2676
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2528
2677
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
2529
- INTERCOM_POLL_INTERVAL_S = 1;
2530
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
2531
2678
  }
2532
2679
  });
2533
2680
 
@@ -2664,6 +2811,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
2664
2811
  "- **Approved:** mark this review task as done",
2665
2812
  "- **Needs work:** re-open the original task with notes, mark this review as done"
2666
2813
  ].join("\n");
2814
+ const originalTaskId = String(row.id);
2667
2815
  const reviewTask = await createTaskCore({
2668
2816
  title: `Review: ${agent} completed "${taskTitle}"`,
2669
2817
  assignedTo: reviewer,
@@ -2672,6 +2820,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
2672
2820
  priority: "p1",
2673
2821
  context: reviewContext,
2674
2822
  taskFile: reviewFile,
2823
+ parentTaskId: originalTaskId,
2675
2824
  skipDispatch: true
2676
2825
  });
2677
2826
  const reviewId = reviewTask.id;
@@ -2710,23 +2859,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2710
2859
  if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
2711
2860
  try {
2712
2861
  const client = getClient();
2713
- const fileName = taskFile.split("/").pop() ?? "";
2714
- const reviewPrefix = fileName.replace(".md", "");
2715
- const parts = reviewPrefix.split("-");
2716
- if (parts.length >= 3 && parts[0] === "review") {
2717
- const agent = parts[1];
2718
- const slug = parts.slice(2).join("-");
2719
- const originalTaskFile = `exe/${agent}/${slug}.md`;
2862
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2863
+ const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
2864
+ if (parentId) {
2720
2865
  const result = await client.execute({
2721
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2722
- args: [(/* @__PURE__ */ new Date()).toISOString(), originalTaskFile]
2866
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
2867
+ args: [now, parentId]
2723
2868
  });
2724
2869
  if (result.rowsAffected > 0) {
2725
2870
  process.stderr.write(
2726
- `[review-cleanup] Cascaded original task to done: ${originalTaskFile}
2871
+ `[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
2727
2872
  `
2728
2873
  );
2729
2874
  }
2875
+ } else {
2876
+ const fileName = taskFile.split("/").pop() ?? "";
2877
+ const reviewPrefix = fileName.replace(".md", "");
2878
+ const parts = reviewPrefix.split("-");
2879
+ if (parts.length >= 3 && parts[0] === "review") {
2880
+ const agent = parts[1];
2881
+ const slug = parts.slice(2).join("-");
2882
+ const originalTaskFile = `exe/${agent}/${slug}.md`;
2883
+ const result = await client.execute({
2884
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2885
+ args: [now, originalTaskFile]
2886
+ });
2887
+ if (result.rowsAffected > 0) {
2888
+ process.stderr.write(
2889
+ `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
2890
+ `
2891
+ );
2892
+ }
2893
+ }
2730
2894
  }
2731
2895
  } catch (err) {
2732
2896
  process.stderr.write(
@@ -2761,7 +2925,7 @@ var init_tasks_review = __esm({
2761
2925
 
2762
2926
  // src/bin/exe-session-cleanup.ts
2763
2927
  import crypto4 from "crypto";
2764
- import { execSync as execSync8 } from "child_process";
2928
+ import { execSync as execSync7 } from "child_process";
2765
2929
 
2766
2930
  // src/lib/store.ts
2767
2931
  init_memory();
@@ -2856,6 +3020,11 @@ async function initStore(options) {
2856
3020
  const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2857
3021
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2858
3022
  }
3023
+ function classifyTier(record) {
3024
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
3025
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
3026
+ return 3;
3027
+ }
2859
3028
  async function writeMemory(record) {
2860
3029
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
2861
3030
  throw new Error(
@@ -2883,7 +3052,11 @@ async function writeMemory(record) {
2883
3052
  document_id: record.document_id ?? null,
2884
3053
  user_id: record.user_id ?? null,
2885
3054
  char_offset: record.char_offset ?? null,
2886
- page_number: record.page_number ?? null
3055
+ page_number: record.page_number ?? null,
3056
+ source_path: record.source_path ?? null,
3057
+ source_type: record.source_type ?? null,
3058
+ tier: record.tier ?? classifyTier(record),
3059
+ supersedes_id: record.supersedes_id ?? null
2887
3060
  };
2888
3061
  _pendingRecords.push(dbRow);
2889
3062
  if (_flushTimer === null) {
@@ -2915,20 +3088,26 @@ async function flushBatch() {
2915
3088
  const userId = row.user_id ?? null;
2916
3089
  const charOffset = row.char_offset ?? null;
2917
3090
  const pageNumber = row.page_number ?? null;
3091
+ const sourcePath = row.source_path ?? null;
3092
+ const sourceType = row.source_type ?? null;
3093
+ const tier = row.tier ?? 3;
3094
+ const supersedesId = row.supersedes_id ?? null;
2918
3095
  return {
2919
3096
  sql: hasVector ? `INSERT OR IGNORE INTO memories
2920
3097
  (id, agent_id, agent_role, session_id, timestamp,
2921
3098
  tool_name, project_name,
2922
3099
  has_error, raw_text, vector, version, task_id, importance, status,
2923
3100
  confidence, last_accessed,
2924
- workspace_id, document_id, user_id, char_offset, page_number)
2925
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
3101
+ workspace_id, document_id, user_id, char_offset, page_number,
3102
+ source_path, source_type, tier, supersedes_id)
3103
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
2926
3104
  (id, agent_id, agent_role, session_id, timestamp,
2927
3105
  tool_name, project_name,
2928
3106
  has_error, raw_text, vector, version, task_id, importance, status,
2929
3107
  confidence, last_accessed,
2930
- workspace_id, document_id, user_id, char_offset, page_number)
2931
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3108
+ workspace_id, document_id, user_id, char_offset, page_number,
3109
+ source_path, source_type, tier, supersedes_id)
3110
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2932
3111
  args: hasVector ? [
2933
3112
  row.id,
2934
3113
  row.agent_id,
@@ -2950,7 +3129,11 @@ async function flushBatch() {
2950
3129
  documentId,
2951
3130
  userId,
2952
3131
  charOffset,
2953
- pageNumber
3132
+ pageNumber,
3133
+ sourcePath,
3134
+ sourceType,
3135
+ tier,
3136
+ supersedesId
2954
3137
  ] : [
2955
3138
  row.id,
2956
3139
  row.agent_id,
@@ -2971,7 +3154,11 @@ async function flushBatch() {
2971
3154
  documentId,
2972
3155
  userId,
2973
3156
  charOffset,
2974
- pageNumber
3157
+ pageNumber,
3158
+ sourcePath,
3159
+ sourceType,
3160
+ tier,
3161
+ supersedesId
2975
3162
  ]
2976
3163
  };
2977
3164
  };
@@ -3150,7 +3337,7 @@ try {
3150
3337
  const taskList = result.rows.map((r) => `"${String(r.title)}"`).join(", ");
3151
3338
  const msg = `session-ended: ${agentName} session terminated unexpectedly. ${count} task(s) marked blocked: ${taskList}`;
3152
3339
  try {
3153
- execSync8(`tmux send-keys -t ${JSON.stringify(exeSession)} ${JSON.stringify(msg)} Enter`, {
3340
+ execSync7(`tmux send-keys -t ${JSON.stringify(exeSession)} ${JSON.stringify(msg)} Enter`, {
3154
3341
  timeout: 3e3
3155
3342
  });
3156
3343
  } catch {
@@ -84,6 +84,11 @@ var DEFAULT_CONFIG = {
84
84
  idleKillTicksRequired: 3,
85
85
  idleKillIntercomAckWindowMs: 1e4,
86
86
  maxAutoInstances: 10
87
+ },
88
+ autoUpdate: {
89
+ checkOnBoot: true,
90
+ autoInstall: false,
91
+ checkIntervalMs: 24 * 60 * 60 * 1e3
87
92
  }
88
93
  };
89
94
  function migrateLegacyConfig(raw) {
@@ -141,6 +146,11 @@ function normalizeSessionLifecycle(raw) {
141
146
  const userSL = raw.sessionLifecycle ?? {};
142
147
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
143
148
  }
149
+ function normalizeAutoUpdate(raw) {
150
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
151
+ const userAU = raw.autoUpdate ?? {};
152
+ raw.autoUpdate = { ...defaultAU, ...userAU };
153
+ }
144
154
  async function loadConfig() {
145
155
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
146
156
  await mkdir(dir, { recursive: true });
@@ -163,6 +173,7 @@ async function loadConfig() {
163
173
  }
164
174
  normalizeScalingRoadmap(migratedCfg);
165
175
  normalizeSessionLifecycle(migratedCfg);
176
+ normalizeAutoUpdate(migratedCfg);
166
177
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
167
178
  if (config.dbPath.startsWith("~")) {
168
179
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());