@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
@@ -236,6 +236,27 @@ async function ensureSchema() {
236
236
  });
237
237
  } catch {
238
238
  }
239
+ try {
240
+ await client.execute({
241
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
242
+ args: []
243
+ });
244
+ } catch {
245
+ }
246
+ try {
247
+ await client.execute({
248
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
249
+ args: []
250
+ });
251
+ } catch {
252
+ }
253
+ try {
254
+ await client.execute({
255
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
256
+ args: []
257
+ });
258
+ } catch {
259
+ }
239
260
  try {
240
261
  await client.execute({
241
262
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -646,6 +667,15 @@ async function ensureSchema() {
646
667
  } catch {
647
668
  }
648
669
  }
670
+ for (const col of [
671
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
672
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
673
+ ]) {
674
+ try {
675
+ await client.execute(col);
676
+ } catch {
677
+ }
678
+ }
649
679
  await client.executeMultiple(`
650
680
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
651
681
  ON memories(workspace_id);
@@ -710,6 +740,34 @@ async function ensureSchema() {
710
740
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
711
741
  ON conversations(channel_id);
712
742
  `);
743
+ try {
744
+ await client.execute({
745
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
746
+ args: []
747
+ });
748
+ } catch {
749
+ }
750
+ try {
751
+ await client.execute({
752
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
753
+ args: []
754
+ });
755
+ } catch {
756
+ }
757
+ try {
758
+ await client.execute({
759
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
760
+ args: []
761
+ });
762
+ } catch {
763
+ }
764
+ try {
765
+ await client.execute({
766
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
767
+ args: []
768
+ });
769
+ } catch {
770
+ }
713
771
  await client.executeMultiple(`
714
772
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
715
773
  content_text,
@@ -736,6 +794,52 @@ async function ensureSchema() {
736
794
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
737
795
  END;
738
796
  `);
797
+ try {
798
+ await client.execute({
799
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
800
+ args: []
801
+ });
802
+ } catch {
803
+ }
804
+ try {
805
+ await client.execute(
806
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
807
+ );
808
+ } catch {
809
+ }
810
+ try {
811
+ await client.execute({
812
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
813
+ args: []
814
+ });
815
+ await client.execute({
816
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
817
+ args: []
818
+ });
819
+ } catch {
820
+ }
821
+ try {
822
+ await client.execute({
823
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
824
+ args: []
825
+ });
826
+ } catch {
827
+ }
828
+ try {
829
+ await client.execute(
830
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
831
+ );
832
+ } catch {
833
+ }
834
+ for (const col of [
835
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
836
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
837
+ ]) {
838
+ try {
839
+ await client.execute(col);
840
+ } catch {
841
+ }
842
+ }
739
843
  }
740
844
  async function disposeDatabase() {
741
845
  if (_client) {
@@ -877,6 +981,11 @@ function normalizeSessionLifecycle(raw) {
877
981
  const userSL = raw.sessionLifecycle ?? {};
878
982
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
879
983
  }
984
+ function normalizeAutoUpdate(raw) {
985
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
986
+ const userAU = raw.autoUpdate ?? {};
987
+ raw.autoUpdate = { ...defaultAU, ...userAU };
988
+ }
880
989
  async function loadConfig() {
881
990
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
882
991
  await mkdir2(dir, { recursive: true });
@@ -899,6 +1008,7 @@ async function loadConfig() {
899
1008
  }
900
1009
  normalizeScalingRoadmap(migratedCfg);
901
1010
  normalizeSessionLifecycle(migratedCfg);
1011
+ normalizeAutoUpdate(migratedCfg);
902
1012
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
903
1013
  if (config.dbPath.startsWith("~")) {
904
1014
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -974,6 +1084,11 @@ var init_config = __esm({
974
1084
  idleKillTicksRequired: 3,
975
1085
  idleKillIntercomAckWindowMs: 1e4,
976
1086
  maxAutoInstances: 10
1087
+ },
1088
+ autoUpdate: {
1089
+ checkOnBoot: true,
1090
+ autoInstall: false,
1091
+ checkIntervalMs: 24 * 60 * 60 * 1e3
977
1092
  }
978
1093
  };
979
1094
  CONFIG_MIGRATIONS = [
@@ -1107,13 +1222,27 @@ async function ensureShardSchema(client) {
1107
1222
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
1108
1223
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
1109
1224
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
1110
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
1225
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
1226
+ // Source provenance columns (must match database.ts)
1227
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1228
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1229
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1230
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1111
1231
  ]) {
1112
1232
  try {
1113
1233
  await client.execute(col);
1114
1234
  } catch {
1115
1235
  }
1116
1236
  }
1237
+ for (const idx of [
1238
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
1239
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
1240
+ ]) {
1241
+ try {
1242
+ await client.execute(idx);
1243
+ } catch {
1244
+ }
1245
+ }
1117
1246
  try {
1118
1247
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
1119
1248
  } catch {
@@ -1222,8 +1351,11 @@ var store_exports = {};
1222
1351
  __export(store_exports, {
1223
1352
  attachDocumentMetadata: () => attachDocumentMetadata,
1224
1353
  buildWikiScopeFilter: () => buildWikiScopeFilter,
1354
+ classifyTier: () => classifyTier,
1225
1355
  disposeStore: () => disposeStore,
1226
1356
  flushBatch: () => flushBatch,
1357
+ flushTier3: () => flushTier3,
1358
+ getMemoryCardinality: () => getMemoryCardinality,
1227
1359
  initStore: () => initStore,
1228
1360
  reserveVersions: () => reserveVersions,
1229
1361
  searchMemories: () => searchMemories,
@@ -1269,6 +1401,11 @@ async function initStore(options) {
1269
1401
  const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1270
1402
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1271
1403
  }
1404
+ function classifyTier(record) {
1405
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
1406
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1407
+ return 3;
1408
+ }
1272
1409
  async function writeMemory(record) {
1273
1410
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1274
1411
  throw new Error(
@@ -1296,7 +1433,11 @@ async function writeMemory(record) {
1296
1433
  document_id: record.document_id ?? null,
1297
1434
  user_id: record.user_id ?? null,
1298
1435
  char_offset: record.char_offset ?? null,
1299
- page_number: record.page_number ?? null
1436
+ page_number: record.page_number ?? null,
1437
+ source_path: record.source_path ?? null,
1438
+ source_type: record.source_type ?? null,
1439
+ tier: record.tier ?? classifyTier(record),
1440
+ supersedes_id: record.supersedes_id ?? null
1300
1441
  };
1301
1442
  _pendingRecords.push(dbRow);
1302
1443
  if (_flushTimer === null) {
@@ -1328,20 +1469,26 @@ async function flushBatch() {
1328
1469
  const userId = row.user_id ?? null;
1329
1470
  const charOffset = row.char_offset ?? null;
1330
1471
  const pageNumber = row.page_number ?? null;
1472
+ const sourcePath = row.source_path ?? null;
1473
+ const sourceType = row.source_type ?? null;
1474
+ const tier = row.tier ?? 3;
1475
+ const supersedesId = row.supersedes_id ?? null;
1331
1476
  return {
1332
1477
  sql: hasVector ? `INSERT OR IGNORE INTO memories
1333
1478
  (id, agent_id, agent_role, session_id, timestamp,
1334
1479
  tool_name, project_name,
1335
1480
  has_error, raw_text, vector, version, task_id, importance, status,
1336
1481
  confidence, last_accessed,
1337
- workspace_id, document_id, user_id, char_offset, page_number)
1338
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1482
+ workspace_id, document_id, user_id, char_offset, page_number,
1483
+ source_path, source_type, tier, supersedes_id)
1484
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1339
1485
  (id, agent_id, agent_role, session_id, timestamp,
1340
1486
  tool_name, project_name,
1341
1487
  has_error, raw_text, vector, version, task_id, importance, status,
1342
1488
  confidence, last_accessed,
1343
- workspace_id, document_id, user_id, char_offset, page_number)
1344
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1489
+ workspace_id, document_id, user_id, char_offset, page_number,
1490
+ source_path, source_type, tier, supersedes_id)
1491
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1345
1492
  args: hasVector ? [
1346
1493
  row.id,
1347
1494
  row.agent_id,
@@ -1363,7 +1510,11 @@ async function flushBatch() {
1363
1510
  documentId,
1364
1511
  userId,
1365
1512
  charOffset,
1366
- pageNumber
1513
+ pageNumber,
1514
+ sourcePath,
1515
+ sourceType,
1516
+ tier,
1517
+ supersedesId
1367
1518
  ] : [
1368
1519
  row.id,
1369
1520
  row.agent_id,
@@ -1384,7 +1535,11 @@ async function flushBatch() {
1384
1535
  documentId,
1385
1536
  userId,
1386
1537
  charOffset,
1387
- pageNumber
1538
+ pageNumber,
1539
+ sourcePath,
1540
+ sourceType,
1541
+ tier,
1542
+ supersedesId
1388
1543
  ]
1389
1544
  };
1390
1545
  };
@@ -1458,7 +1613,8 @@ async function searchMemories(queryVector, agentId, options) {
1458
1613
  has_error, raw_text, vector, importance, status,
1459
1614
  confidence, last_accessed,
1460
1615
  workspace_id, document_id, user_id,
1461
- char_offset, page_number
1616
+ char_offset, page_number,
1617
+ source_path, source_type
1462
1618
  FROM memories
1463
1619
  WHERE agent_id = ?
1464
1620
  AND vector IS NOT NULL${statusFilter}
@@ -1507,7 +1663,9 @@ async function searchMemories(queryVector, agentId, options) {
1507
1663
  document_id: row.document_id ?? null,
1508
1664
  user_id: row.user_id ?? null,
1509
1665
  char_offset: row.char_offset ?? null,
1510
- page_number: row.page_number ?? null
1666
+ page_number: row.page_number ?? null,
1667
+ source_path: row.source_path ?? null,
1668
+ source_type: row.source_type ?? null
1511
1669
  }));
1512
1670
  }
1513
1671
  async function attachDocumentMetadata(records) {
@@ -1545,6 +1703,25 @@ async function attachDocumentMetadata(records) {
1545
1703
  }
1546
1704
  return records;
1547
1705
  }
1706
+ async function flushTier3(agentId, options) {
1707
+ const client = getClient();
1708
+ const maxAge = options?.maxAgeHours ?? 72;
1709
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
1710
+ if (options?.dryRun) {
1711
+ const result2 = await client.execute({
1712
+ sql: `SELECT COUNT(*) as cnt FROM memories
1713
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1714
+ args: [agentId, cutoff]
1715
+ });
1716
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
1717
+ }
1718
+ const result = await client.execute({
1719
+ sql: `UPDATE memories SET status = 'archived'
1720
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1721
+ args: [agentId, cutoff]
1722
+ });
1723
+ return { archived: result.rowsAffected };
1724
+ }
1548
1725
  async function disposeStore() {
1549
1726
  if (_flushTimer !== null) {
1550
1727
  clearInterval(_flushTimer);
@@ -1575,6 +1752,18 @@ function reserveVersions(count) {
1575
1752
  }
1576
1753
  return reserved;
1577
1754
  }
1755
+ async function getMemoryCardinality(agentId) {
1756
+ try {
1757
+ const client = getClient();
1758
+ const result = await client.execute({
1759
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
1760
+ args: [agentId]
1761
+ });
1762
+ return Number(result.rows[0]?.cnt) || 0;
1763
+ } catch {
1764
+ return 0;
1765
+ }
1766
+ }
1578
1767
  var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1579
1768
  var init_store = __esm({
1580
1769
  "src/lib/store.ts"() {
@@ -1604,12 +1793,23 @@ function getProjectName(cwd) {
1604
1793
  const dir = cwd ?? process.cwd();
1605
1794
  if (_cached && _cachedCwd === dir) return _cached;
1606
1795
  try {
1607
- const repoRoot = execSync("git rev-parse --show-toplevel", {
1608
- cwd: dir,
1609
- encoding: "utf8",
1610
- timeout: 2e3,
1611
- stdio: ["pipe", "pipe", "pipe"]
1612
- }).trim();
1796
+ let repoRoot;
1797
+ try {
1798
+ const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
1799
+ cwd: dir,
1800
+ encoding: "utf8",
1801
+ timeout: 2e3,
1802
+ stdio: ["pipe", "pipe", "pipe"]
1803
+ }).trim();
1804
+ repoRoot = path4.dirname(gitCommonDir);
1805
+ } catch {
1806
+ repoRoot = execSync("git rev-parse --show-toplevel", {
1807
+ cwd: dir,
1808
+ encoding: "utf8",
1809
+ timeout: 2e3,
1810
+ stdio: ["pipe", "pipe", "pipe"]
1811
+ }).trim();
1812
+ }
1613
1813
  _cached = path4.basename(repoRoot);
1614
1814
  _cachedCwd = dir;
1615
1815
  return _cached;
@@ -1686,10 +1886,10 @@ async function scanFromDb(agentId, tmuxSession, projectName) {
1686
1886
  } catch {
1687
1887
  }
1688
1888
  }
1689
- const sql = resolvedProject ? `SELECT id, title, priority, status, assigned_tmux FROM tasks
1889
+ const sql = resolvedProject ? `SELECT id, title, priority, status, assigned_tmux, checkpoint, checkpoint_count FROM tasks
1690
1890
  WHERE assigned_to = ? AND project_name = ? AND status IN ('open', 'in_progress', 'blocked', 'needs_review')
1691
1891
  ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 ELSE 3 END,
1692
- priority ASC, created_at DESC` : `SELECT id, title, priority, status, assigned_tmux FROM tasks
1892
+ priority ASC, created_at DESC` : `SELECT id, title, priority, status, assigned_tmux, checkpoint, checkpoint_count FROM tasks
1693
1893
  WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'blocked', 'needs_review')
1694
1894
  ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 ELSE 3 END,
1695
1895
  priority ASC, created_at DESC`;
@@ -1705,7 +1905,9 @@ async function scanFromDb(agentId, tmuxSession, projectName) {
1705
1905
  id: String(row.id),
1706
1906
  title: String(row.title),
1707
1907
  priority: String(row.priority),
1708
- status
1908
+ status,
1909
+ checkpoint: row.checkpoint == null ? null : String(row.checkpoint),
1910
+ checkpointCount: Number(row.checkpoint_count ?? 0)
1709
1911
  };
1710
1912
  if (status === "in_progress") {
1711
1913
  if (claimedBy && claimedBy !== "unknown" && tmuxSession) {
@@ -1748,6 +1950,23 @@ function formatMandatory(_agentId, result) {
1748
1950
  if (inProgress.length > 0) {
1749
1951
  const current = inProgress[0];
1750
1952
  lines.push(`Continue working on: ${current.title} [${current.priority}]`);
1953
+ if (current.checkpoint) {
1954
+ try {
1955
+ const cp = JSON.parse(current.checkpoint);
1956
+ lines.push("");
1957
+ lines.push("CRASH RECOVERY \u2014 Last checkpoint:");
1958
+ lines.push(` Step: ${cp.step}`);
1959
+ lines.push(` Summary: ${cp.context_summary}`);
1960
+ if (cp.files_touched.length > 0) {
1961
+ lines.push(` Files touched: ${cp.files_touched.join(", ")}`);
1962
+ }
1963
+ lines.push(` Checkpointed at: ${cp.last_checkpoint_at}`);
1964
+ lines.push(` Total checkpoints: ${current.checkpointCount ?? "?"}`);
1965
+ lines.push("");
1966
+ lines.push("Resume from where you left off. Do NOT restart from scratch.");
1967
+ } catch {
1968
+ }
1969
+ }
1751
1970
  lines.push(`Use get_task to read the full task context.`);
1752
1971
  if (open.length > 0) {
1753
1972
  lines.push("Queued: " + open.map((t) => `${t.title} [${t.priority}]`).join(", "));