@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
@@ -244,6 +244,27 @@ async function ensureSchema() {
244
244
  });
245
245
  } catch {
246
246
  }
247
+ try {
248
+ await client.execute({
249
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
250
+ args: []
251
+ });
252
+ } catch {
253
+ }
254
+ try {
255
+ await client.execute({
256
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
257
+ args: []
258
+ });
259
+ } catch {
260
+ }
261
+ try {
262
+ await client.execute({
263
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
264
+ args: []
265
+ });
266
+ } catch {
267
+ }
247
268
  try {
248
269
  await client.execute({
249
270
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -654,6 +675,15 @@ async function ensureSchema() {
654
675
  } catch {
655
676
  }
656
677
  }
678
+ for (const col of [
679
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
680
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
681
+ ]) {
682
+ try {
683
+ await client.execute(col);
684
+ } catch {
685
+ }
686
+ }
657
687
  await client.executeMultiple(`
658
688
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
659
689
  ON memories(workspace_id);
@@ -718,6 +748,34 @@ async function ensureSchema() {
718
748
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
719
749
  ON conversations(channel_id);
720
750
  `);
751
+ try {
752
+ await client.execute({
753
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
754
+ args: []
755
+ });
756
+ } catch {
757
+ }
758
+ try {
759
+ await client.execute({
760
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
761
+ args: []
762
+ });
763
+ } catch {
764
+ }
765
+ try {
766
+ await client.execute({
767
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
768
+ args: []
769
+ });
770
+ } catch {
771
+ }
772
+ try {
773
+ await client.execute({
774
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
775
+ args: []
776
+ });
777
+ } catch {
778
+ }
721
779
  await client.executeMultiple(`
722
780
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
723
781
  content_text,
@@ -744,6 +802,52 @@ async function ensureSchema() {
744
802
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
745
803
  END;
746
804
  `);
805
+ try {
806
+ await client.execute({
807
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
808
+ args: []
809
+ });
810
+ } catch {
811
+ }
812
+ try {
813
+ await client.execute(
814
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
815
+ );
816
+ } catch {
817
+ }
818
+ try {
819
+ await client.execute({
820
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
821
+ args: []
822
+ });
823
+ await client.execute({
824
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
825
+ args: []
826
+ });
827
+ } catch {
828
+ }
829
+ try {
830
+ await client.execute({
831
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
832
+ args: []
833
+ });
834
+ } catch {
835
+ }
836
+ try {
837
+ await client.execute(
838
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
839
+ );
840
+ } catch {
841
+ }
842
+ for (const col of [
843
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
844
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
845
+ ]) {
846
+ try {
847
+ await client.execute(col);
848
+ } catch {
849
+ }
850
+ }
747
851
  }
748
852
  async function disposeDatabase() {
749
853
  if (_client) {
@@ -876,6 +980,11 @@ function normalizeSessionLifecycle(raw) {
876
980
  const userSL = raw.sessionLifecycle ?? {};
877
981
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
878
982
  }
983
+ function normalizeAutoUpdate(raw) {
984
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
985
+ const userAU = raw.autoUpdate ?? {};
986
+ raw.autoUpdate = { ...defaultAU, ...userAU };
987
+ }
879
988
  async function loadConfig() {
880
989
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
881
990
  await mkdir2(dir, { recursive: true });
@@ -898,6 +1007,7 @@ async function loadConfig() {
898
1007
  }
899
1008
  normalizeScalingRoadmap(migratedCfg);
900
1009
  normalizeSessionLifecycle(migratedCfg);
1010
+ normalizeAutoUpdate(migratedCfg);
901
1011
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
902
1012
  if (config.dbPath.startsWith("~")) {
903
1013
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -973,6 +1083,11 @@ var init_config = __esm({
973
1083
  idleKillTicksRequired: 3,
974
1084
  idleKillIntercomAckWindowMs: 1e4,
975
1085
  maxAutoInstances: 10
1086
+ },
1087
+ autoUpdate: {
1088
+ checkOnBoot: true,
1089
+ autoInstall: false,
1090
+ checkIntervalMs: 24 * 60 * 60 * 1e3
976
1091
  }
977
1092
  };
978
1093
  CONFIG_MIGRATIONS = [
@@ -1106,13 +1221,27 @@ async function ensureShardSchema(client) {
1106
1221
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
1107
1222
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
1108
1223
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
1109
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
1224
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
1225
+ // Source provenance columns (must match database.ts)
1226
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1227
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1228
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1229
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1110
1230
  ]) {
1111
1231
  try {
1112
1232
  await client.execute(col);
1113
1233
  } catch {
1114
1234
  }
1115
1235
  }
1236
+ for (const idx of [
1237
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
1238
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
1239
+ ]) {
1240
+ try {
1241
+ await client.execute(idx);
1242
+ } catch {
1243
+ }
1244
+ }
1116
1245
  try {
1117
1246
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
1118
1247
  } catch {
@@ -1221,8 +1350,11 @@ var store_exports = {};
1221
1350
  __export(store_exports, {
1222
1351
  attachDocumentMetadata: () => attachDocumentMetadata,
1223
1352
  buildWikiScopeFilter: () => buildWikiScopeFilter,
1353
+ classifyTier: () => classifyTier,
1224
1354
  disposeStore: () => disposeStore,
1225
1355
  flushBatch: () => flushBatch,
1356
+ flushTier3: () => flushTier3,
1357
+ getMemoryCardinality: () => getMemoryCardinality,
1226
1358
  initStore: () => initStore,
1227
1359
  reserveVersions: () => reserveVersions,
1228
1360
  searchMemories: () => searchMemories,
@@ -1268,6 +1400,11 @@ async function initStore(options) {
1268
1400
  const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1269
1401
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1270
1402
  }
1403
+ function classifyTier(record) {
1404
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
1405
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1406
+ return 3;
1407
+ }
1271
1408
  async function writeMemory(record) {
1272
1409
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1273
1410
  throw new Error(
@@ -1295,7 +1432,11 @@ async function writeMemory(record) {
1295
1432
  document_id: record.document_id ?? null,
1296
1433
  user_id: record.user_id ?? null,
1297
1434
  char_offset: record.char_offset ?? null,
1298
- page_number: record.page_number ?? null
1435
+ page_number: record.page_number ?? null,
1436
+ source_path: record.source_path ?? null,
1437
+ source_type: record.source_type ?? null,
1438
+ tier: record.tier ?? classifyTier(record),
1439
+ supersedes_id: record.supersedes_id ?? null
1299
1440
  };
1300
1441
  _pendingRecords.push(dbRow);
1301
1442
  if (_flushTimer === null) {
@@ -1327,20 +1468,26 @@ async function flushBatch() {
1327
1468
  const userId = row.user_id ?? null;
1328
1469
  const charOffset = row.char_offset ?? null;
1329
1470
  const pageNumber = row.page_number ?? null;
1471
+ const sourcePath = row.source_path ?? null;
1472
+ const sourceType = row.source_type ?? null;
1473
+ const tier = row.tier ?? 3;
1474
+ const supersedesId = row.supersedes_id ?? null;
1330
1475
  return {
1331
1476
  sql: hasVector ? `INSERT OR IGNORE INTO memories
1332
1477
  (id, agent_id, agent_role, session_id, timestamp,
1333
1478
  tool_name, project_name,
1334
1479
  has_error, raw_text, vector, version, task_id, importance, status,
1335
1480
  confidence, last_accessed,
1336
- workspace_id, document_id, user_id, char_offset, page_number)
1337
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1481
+ workspace_id, document_id, user_id, char_offset, page_number,
1482
+ source_path, source_type, tier, supersedes_id)
1483
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1338
1484
  (id, agent_id, agent_role, session_id, timestamp,
1339
1485
  tool_name, project_name,
1340
1486
  has_error, raw_text, vector, version, task_id, importance, status,
1341
1487
  confidence, last_accessed,
1342
- workspace_id, document_id, user_id, char_offset, page_number)
1343
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1488
+ workspace_id, document_id, user_id, char_offset, page_number,
1489
+ source_path, source_type, tier, supersedes_id)
1490
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1344
1491
  args: hasVector ? [
1345
1492
  row.id,
1346
1493
  row.agent_id,
@@ -1362,7 +1509,11 @@ async function flushBatch() {
1362
1509
  documentId,
1363
1510
  userId,
1364
1511
  charOffset,
1365
- pageNumber
1512
+ pageNumber,
1513
+ sourcePath,
1514
+ sourceType,
1515
+ tier,
1516
+ supersedesId
1366
1517
  ] : [
1367
1518
  row.id,
1368
1519
  row.agent_id,
@@ -1383,7 +1534,11 @@ async function flushBatch() {
1383
1534
  documentId,
1384
1535
  userId,
1385
1536
  charOffset,
1386
- pageNumber
1537
+ pageNumber,
1538
+ sourcePath,
1539
+ sourceType,
1540
+ tier,
1541
+ supersedesId
1387
1542
  ]
1388
1543
  };
1389
1544
  };
@@ -1457,7 +1612,8 @@ async function searchMemories(queryVector, agentId, options) {
1457
1612
  has_error, raw_text, vector, importance, status,
1458
1613
  confidence, last_accessed,
1459
1614
  workspace_id, document_id, user_id,
1460
- char_offset, page_number
1615
+ char_offset, page_number,
1616
+ source_path, source_type
1461
1617
  FROM memories
1462
1618
  WHERE agent_id = ?
1463
1619
  AND vector IS NOT NULL${statusFilter}
@@ -1506,7 +1662,9 @@ async function searchMemories(queryVector, agentId, options) {
1506
1662
  document_id: row.document_id ?? null,
1507
1663
  user_id: row.user_id ?? null,
1508
1664
  char_offset: row.char_offset ?? null,
1509
- page_number: row.page_number ?? null
1665
+ page_number: row.page_number ?? null,
1666
+ source_path: row.source_path ?? null,
1667
+ source_type: row.source_type ?? null
1510
1668
  }));
1511
1669
  }
1512
1670
  async function attachDocumentMetadata(records) {
@@ -1544,6 +1702,25 @@ async function attachDocumentMetadata(records) {
1544
1702
  }
1545
1703
  return records;
1546
1704
  }
1705
+ async function flushTier3(agentId, options) {
1706
+ const client = getClient();
1707
+ const maxAge = options?.maxAgeHours ?? 72;
1708
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
1709
+ if (options?.dryRun) {
1710
+ const result2 = await client.execute({
1711
+ sql: `SELECT COUNT(*) as cnt FROM memories
1712
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1713
+ args: [agentId, cutoff]
1714
+ });
1715
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
1716
+ }
1717
+ const result = await client.execute({
1718
+ sql: `UPDATE memories SET status = 'archived'
1719
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
1720
+ args: [agentId, cutoff]
1721
+ });
1722
+ return { archived: result.rowsAffected };
1723
+ }
1547
1724
  async function disposeStore() {
1548
1725
  if (_flushTimer !== null) {
1549
1726
  clearInterval(_flushTimer);
@@ -1574,6 +1751,18 @@ function reserveVersions(count) {
1574
1751
  }
1575
1752
  return reserved;
1576
1753
  }
1754
+ async function getMemoryCardinality(agentId) {
1755
+ try {
1756
+ const client = getClient();
1757
+ const result = await client.execute({
1758
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
1759
+ args: [agentId]
1760
+ });
1761
+ return Number(result.rows[0]?.cnt) || 0;
1762
+ } catch {
1763
+ return 0;
1764
+ }
1765
+ }
1577
1766
  var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1578
1767
  var init_store = __esm({
1579
1768
  "src/lib/store.ts"() {
@@ -1604,13 +1793,44 @@ __export(tasks_crud_exports, {
1604
1793
  listTasks: () => listTasks,
1605
1794
  resolveTask: () => resolveTask,
1606
1795
  slugify: () => slugify,
1607
- updateTaskStatus: () => updateTaskStatus
1796
+ updateTaskStatus: () => updateTaskStatus,
1797
+ writeCheckpoint: () => writeCheckpoint
1608
1798
  });
1609
1799
  import crypto2 from "crypto";
1610
1800
  import path4 from "path";
1611
1801
  import { execSync as execSync2 } from "child_process";
1612
1802
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1613
1803
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1804
+ async function writeCheckpoint(input) {
1805
+ const client = getClient();
1806
+ const row = await resolveTask(client, input.taskId);
1807
+ const taskId = String(row.id);
1808
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1809
+ const blockedByIds = [];
1810
+ if (row.blocked_by) {
1811
+ blockedByIds.push(String(row.blocked_by));
1812
+ }
1813
+ const checkpoint = {
1814
+ step: input.step,
1815
+ context_summary: input.contextSummary,
1816
+ files_touched: input.filesTouched ?? [],
1817
+ blocked_by_ids: blockedByIds,
1818
+ last_checkpoint_at: now
1819
+ };
1820
+ const result = await client.execute({
1821
+ sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
1822
+ args: [JSON.stringify(checkpoint), now, taskId]
1823
+ });
1824
+ if (result.rowsAffected === 0) {
1825
+ throw new Error(`Checkpoint write failed: task ${taskId} not found`);
1826
+ }
1827
+ const countResult = await client.execute({
1828
+ sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
1829
+ args: [taskId]
1830
+ });
1831
+ const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
1832
+ return { checkpointCount };
1833
+ }
1614
1834
  function extractParentFromContext(contextBody) {
1615
1835
  if (!contextBody) return null;
1616
1836
  const match = contextBody.match(
@@ -1717,9 +1937,10 @@ async function createTaskCore(input) {
1717
1937
  } catch {
1718
1938
  }
1719
1939
  }
1940
+ const complexity = input.complexity ?? "standard";
1720
1941
  await client.execute({
1721
- 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)
1722
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1942
+ 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)
1943
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1723
1944
  args: [
1724
1945
  id,
1725
1946
  input.title,
@@ -1733,6 +1954,11 @@ async function createTaskCore(input) {
1733
1954
  parentTaskId,
1734
1955
  input.reviewer ?? null,
1735
1956
  input.context,
1957
+ input.budgetTokens ?? null,
1958
+ input.budgetFallbackModel ?? null,
1959
+ 0,
1960
+ null,
1961
+ complexity,
1736
1962
  now,
1737
1963
  now
1738
1964
  ]
@@ -1748,7 +1974,11 @@ async function createTaskCore(input) {
1748
1974
  taskFile,
1749
1975
  createdAt: now,
1750
1976
  updatedAt: now,
1751
- warning
1977
+ warning,
1978
+ budgetTokens: input.budgetTokens ?? null,
1979
+ budgetFallbackModel: input.budgetFallbackModel ?? null,
1980
+ tokensUsed: 0,
1981
+ tokensWarnedAt: null
1752
1982
  };
1753
1983
  }
1754
1984
  async function listTasks(input) {
@@ -1788,7 +2018,12 @@ async function listTasks(input) {
1788
2018
  status: String(r.status),
1789
2019
  taskFile: String(r.task_file),
1790
2020
  createdAt: String(r.created_at),
1791
- updatedAt: String(r.updated_at)
2021
+ updatedAt: String(r.updated_at),
2022
+ checkpointCount: Number(r.checkpoint_count ?? 0),
2023
+ budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
2024
+ budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
2025
+ tokensUsed: Number(r.tokens_used ?? 0),
2026
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
1792
2027
  }));
1793
2028
  }
1794
2029
  function checkStaleCompletion(taskContext, taskCreatedAt) {
@@ -1796,8 +2031,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
1796
2031
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
1797
2032
  try {
1798
2033
  const since = new Date(taskCreatedAt).toISOString();
2034
+ const branch = execSync2(
2035
+ "git rev-parse --abbrev-ref HEAD 2>/dev/null",
2036
+ { encoding: "utf8", timeout: 3e3 }
2037
+ ).trim();
2038
+ const branchArg = branch && branch !== "HEAD" ? branch : "";
1799
2039
  const commitCount = execSync2(
1800
- `git log --oneline --since="${since}" 2>/dev/null | wc -l`,
2040
+ `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
1801
2041
  { encoding: "utf8", timeout: 5e3 }
1802
2042
  ).trim();
1803
2043
  const count = parseInt(commitCount, 10);
@@ -1856,6 +2096,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1856
2096
  const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
1857
2097
  throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
1858
2098
  }
2099
+ try {
2100
+ await writeCheckpoint({
2101
+ taskId,
2102
+ step: "claimed",
2103
+ contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
2104
+ });
2105
+ } catch {
2106
+ }
1859
2107
  return { row, taskFile, now, taskId };
1860
2108
  }
1861
2109
  if (input.result) {
@@ -1869,6 +2117,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1869
2117
  args: [input.status, now, taskId]
1870
2118
  });
1871
2119
  }
2120
+ try {
2121
+ await writeCheckpoint({
2122
+ taskId,
2123
+ step: `status_transition:${input.status}`,
2124
+ contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
2125
+ });
2126
+ } catch {
2127
+ }
1872
2128
  return { row, taskFile, now, taskId };
1873
2129
  }
1874
2130
  async function deleteTaskCore(taskId, _baseDir) {