@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
@@ -245,6 +245,27 @@ async function ensureSchema() {
245
245
  });
246
246
  } catch {
247
247
  }
248
+ try {
249
+ await client.execute({
250
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
251
+ args: []
252
+ });
253
+ } catch {
254
+ }
255
+ try {
256
+ await client.execute({
257
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
258
+ args: []
259
+ });
260
+ } catch {
261
+ }
262
+ try {
263
+ await client.execute({
264
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
265
+ args: []
266
+ });
267
+ } catch {
268
+ }
248
269
  try {
249
270
  await client.execute({
250
271
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -655,6 +676,15 @@ async function ensureSchema() {
655
676
  } catch {
656
677
  }
657
678
  }
679
+ for (const col of [
680
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
681
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
682
+ ]) {
683
+ try {
684
+ await client.execute(col);
685
+ } catch {
686
+ }
687
+ }
658
688
  await client.executeMultiple(`
659
689
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
660
690
  ON memories(workspace_id);
@@ -719,6 +749,34 @@ async function ensureSchema() {
719
749
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
720
750
  ON conversations(channel_id);
721
751
  `);
752
+ try {
753
+ await client.execute({
754
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
755
+ args: []
756
+ });
757
+ } catch {
758
+ }
759
+ try {
760
+ await client.execute({
761
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
762
+ args: []
763
+ });
764
+ } catch {
765
+ }
766
+ try {
767
+ await client.execute({
768
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
769
+ args: []
770
+ });
771
+ } catch {
772
+ }
773
+ try {
774
+ await client.execute({
775
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
776
+ args: []
777
+ });
778
+ } catch {
779
+ }
722
780
  await client.executeMultiple(`
723
781
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
724
782
  content_text,
@@ -745,6 +803,52 @@ async function ensureSchema() {
745
803
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
746
804
  END;
747
805
  `);
806
+ try {
807
+ await client.execute({
808
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
809
+ args: []
810
+ });
811
+ } catch {
812
+ }
813
+ try {
814
+ await client.execute(
815
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
816
+ );
817
+ } catch {
818
+ }
819
+ try {
820
+ await client.execute({
821
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
822
+ args: []
823
+ });
824
+ await client.execute({
825
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
826
+ args: []
827
+ });
828
+ } catch {
829
+ }
830
+ try {
831
+ await client.execute({
832
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
833
+ args: []
834
+ });
835
+ } catch {
836
+ }
837
+ try {
838
+ await client.execute(
839
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
840
+ );
841
+ } catch {
842
+ }
843
+ for (const col of [
844
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
845
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
846
+ ]) {
847
+ try {
848
+ await client.execute(col);
849
+ } catch {
850
+ }
851
+ }
748
852
  }
749
853
  async function disposeDatabase() {
750
854
  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"() {
@@ -1605,13 +1794,44 @@ __export(tasks_crud_exports, {
1605
1794
  listTasks: () => listTasks,
1606
1795
  resolveTask: () => resolveTask,
1607
1796
  slugify: () => slugify,
1608
- updateTaskStatus: () => updateTaskStatus
1797
+ updateTaskStatus: () => updateTaskStatus,
1798
+ writeCheckpoint: () => writeCheckpoint
1609
1799
  });
1610
1800
  import crypto2 from "crypto";
1611
1801
  import path4 from "path";
1612
1802
  import { execSync } from "child_process";
1613
1803
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1614
1804
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1805
+ async function writeCheckpoint(input) {
1806
+ const client = getClient();
1807
+ const row = await resolveTask(client, input.taskId);
1808
+ const taskId = String(row.id);
1809
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1810
+ const blockedByIds = [];
1811
+ if (row.blocked_by) {
1812
+ blockedByIds.push(String(row.blocked_by));
1813
+ }
1814
+ const checkpoint = {
1815
+ step: input.step,
1816
+ context_summary: input.contextSummary,
1817
+ files_touched: input.filesTouched ?? [],
1818
+ blocked_by_ids: blockedByIds,
1819
+ last_checkpoint_at: now
1820
+ };
1821
+ const result = await client.execute({
1822
+ sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
1823
+ args: [JSON.stringify(checkpoint), now, taskId]
1824
+ });
1825
+ if (result.rowsAffected === 0) {
1826
+ throw new Error(`Checkpoint write failed: task ${taskId} not found`);
1827
+ }
1828
+ const countResult = await client.execute({
1829
+ sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
1830
+ args: [taskId]
1831
+ });
1832
+ const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
1833
+ return { checkpointCount };
1834
+ }
1615
1835
  function extractParentFromContext(contextBody) {
1616
1836
  if (!contextBody) return null;
1617
1837
  const match = contextBody.match(
@@ -1718,9 +1938,10 @@ async function createTaskCore(input) {
1718
1938
  } catch {
1719
1939
  }
1720
1940
  }
1941
+ const complexity = input.complexity ?? "standard";
1721
1942
  await client.execute({
1722
- 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)
1723
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1943
+ 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)
1944
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1724
1945
  args: [
1725
1946
  id,
1726
1947
  input.title,
@@ -1734,6 +1955,11 @@ async function createTaskCore(input) {
1734
1955
  parentTaskId,
1735
1956
  input.reviewer ?? null,
1736
1957
  input.context,
1958
+ input.budgetTokens ?? null,
1959
+ input.budgetFallbackModel ?? null,
1960
+ 0,
1961
+ null,
1962
+ complexity,
1737
1963
  now,
1738
1964
  now
1739
1965
  ]
@@ -1749,7 +1975,11 @@ async function createTaskCore(input) {
1749
1975
  taskFile,
1750
1976
  createdAt: now,
1751
1977
  updatedAt: now,
1752
- warning
1978
+ warning,
1979
+ budgetTokens: input.budgetTokens ?? null,
1980
+ budgetFallbackModel: input.budgetFallbackModel ?? null,
1981
+ tokensUsed: 0,
1982
+ tokensWarnedAt: null
1753
1983
  };
1754
1984
  }
1755
1985
  async function listTasks(input) {
@@ -1789,7 +2019,12 @@ async function listTasks(input) {
1789
2019
  status: String(r.status),
1790
2020
  taskFile: String(r.task_file),
1791
2021
  createdAt: String(r.created_at),
1792
- updatedAt: String(r.updated_at)
2022
+ updatedAt: String(r.updated_at),
2023
+ checkpointCount: Number(r.checkpoint_count ?? 0),
2024
+ budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
2025
+ budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
2026
+ tokensUsed: Number(r.tokens_used ?? 0),
2027
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
1793
2028
  }));
1794
2029
  }
1795
2030
  function checkStaleCompletion(taskContext, taskCreatedAt) {
@@ -1797,8 +2032,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
1797
2032
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
1798
2033
  try {
1799
2034
  const since = new Date(taskCreatedAt).toISOString();
2035
+ const branch = execSync(
2036
+ "git rev-parse --abbrev-ref HEAD 2>/dev/null",
2037
+ { encoding: "utf8", timeout: 3e3 }
2038
+ ).trim();
2039
+ const branchArg = branch && branch !== "HEAD" ? branch : "";
1800
2040
  const commitCount = execSync(
1801
- `git log --oneline --since="${since}" 2>/dev/null | wc -l`,
2041
+ `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
1802
2042
  { encoding: "utf8", timeout: 5e3 }
1803
2043
  ).trim();
1804
2044
  const count = parseInt(commitCount, 10);
@@ -1857,6 +2097,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1857
2097
  const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
1858
2098
  throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
1859
2099
  }
2100
+ try {
2101
+ await writeCheckpoint({
2102
+ taskId,
2103
+ step: "claimed",
2104
+ contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
2105
+ });
2106
+ } catch {
2107
+ }
1860
2108
  return { row, taskFile, now, taskId };
1861
2109
  }
1862
2110
  if (input.result) {
@@ -1870,6 +2118,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1870
2118
  args: [input.status, now, taskId]
1871
2119
  });
1872
2120
  }
2121
+ try {
2122
+ await writeCheckpoint({
2123
+ taskId,
2124
+ step: `status_transition:${input.status}`,
2125
+ contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
2126
+ });
2127
+ } catch {
2128
+ }
1873
2129
  return { row, taskFile, now, taskId };
1874
2130
  }
1875
2131
  async function deleteTaskCore(taskId, _baseDir) {