@askexenow/exe-os 0.8.104 → 0.8.105

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 (72) hide show
  1. package/dist/bin/backfill-conversations.js +12 -1
  2. package/dist/bin/backfill-responses.js +12 -1
  3. package/dist/bin/backfill-vectors.js +12 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +12 -1
  5. package/dist/bin/cli.js +32 -7
  6. package/dist/bin/exe-assign.js +12 -1
  7. package/dist/bin/exe-boot.js +84 -10
  8. package/dist/bin/exe-call.js +138 -10
  9. package/dist/bin/exe-dispatch.js +31 -6
  10. package/dist/bin/exe-doctor.js +12 -1
  11. package/dist/bin/exe-export-behaviors.js +12 -1
  12. package/dist/bin/exe-forget.js +65 -3
  13. package/dist/bin/exe-gateway.js +31 -6
  14. package/dist/bin/exe-heartbeat.js +16 -5
  15. package/dist/bin/exe-kill.js +12 -1
  16. package/dist/bin/exe-launch-agent.js +12 -1
  17. package/dist/bin/exe-link.js +12 -1
  18. package/dist/bin/exe-pending-messages.js +12 -1
  19. package/dist/bin/exe-pending-notifications.js +12 -1
  20. package/dist/bin/exe-pending-reviews.js +39 -6
  21. package/dist/bin/exe-rename.js +12 -1
  22. package/dist/bin/exe-review.js +12 -1
  23. package/dist/bin/exe-search.js +65 -3
  24. package/dist/bin/exe-session-cleanup.js +50 -8
  25. package/dist/bin/exe-start-codex.js +13 -2
  26. package/dist/bin/exe-start-opencode.js +12 -1
  27. package/dist/bin/exe-status.js +12 -1
  28. package/dist/bin/exe-team.js +12 -1
  29. package/dist/bin/git-sweep.js +31 -6
  30. package/dist/bin/graph-backfill.js +12 -1
  31. package/dist/bin/graph-export.js +12 -1
  32. package/dist/bin/scan-tasks.js +31 -6
  33. package/dist/bin/setup.js +12 -1
  34. package/dist/bin/shard-migrate.js +12 -1
  35. package/dist/bin/wiki-sync.js +12 -1
  36. package/dist/gateway/index.js +31 -6
  37. package/dist/hooks/bug-report-worker.js +31 -6
  38. package/dist/hooks/commit-complete.js +31 -6
  39. package/dist/hooks/error-recall.js +65 -3
  40. package/dist/hooks/ingest-worker.js +31 -6
  41. package/dist/hooks/instructions-loaded.js +12 -1
  42. package/dist/hooks/notification.js +12 -1
  43. package/dist/hooks/post-compact.js +12 -1
  44. package/dist/hooks/pre-compact.js +31 -6
  45. package/dist/hooks/pre-tool-use.js +12 -1
  46. package/dist/hooks/prompt-ingest-worker.js +12 -1
  47. package/dist/hooks/prompt-submit.js +120 -13
  48. package/dist/hooks/response-ingest-worker.js +12 -1
  49. package/dist/hooks/session-end.js +31 -6
  50. package/dist/hooks/session-start.js +65 -3
  51. package/dist/hooks/stop.js +12 -1
  52. package/dist/hooks/subagent-stop.js +12 -1
  53. package/dist/hooks/summary-worker.js +12 -1
  54. package/dist/index.js +31 -6
  55. package/dist/lib/cloud-sync.js +12 -1
  56. package/dist/lib/database.js +11 -0
  57. package/dist/lib/device-registry.js +12 -1
  58. package/dist/lib/exe-daemon.js +48 -6
  59. package/dist/lib/hybrid-search.js +65 -3
  60. package/dist/lib/messaging.js +30 -2
  61. package/dist/lib/schedules.js +12 -1
  62. package/dist/lib/status-brief.js +24 -0
  63. package/dist/lib/store.js +12 -1
  64. package/dist/lib/tasks.js +19 -5
  65. package/dist/lib/tmux-routing.js +19 -5
  66. package/dist/mcp/server.js +84 -8
  67. package/dist/mcp/tools/create-task.js +19 -5
  68. package/dist/mcp/tools/send-message.js +32 -4
  69. package/dist/mcp/tools/update-task.js +42 -10
  70. package/dist/runtime/index.js +31 -6
  71. package/dist/tui/App.js +31 -6
  72. package/package.json +4 -2
@@ -304,6 +304,16 @@ async function initDatabase(config) {
304
304
  }
305
305
  _client = createClient(opts);
306
306
  _resilientClient = wrapWithRetry(_client);
307
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
308
+ });
309
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
310
+ });
311
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
312
+ _walCheckpointTimer = setInterval(() => {
313
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
314
+ });
315
+ }, 3e4);
316
+ _walCheckpointTimer.unref();
307
317
  }
308
318
  function getClient() {
309
319
  if (!_resilientClient) {
@@ -1248,7 +1258,7 @@ async function ensureSchema() {
1248
1258
  }
1249
1259
  }
1250
1260
  }
1251
- var _client, _resilientClient, _daemonClient, initTurso;
1261
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1252
1262
  var init_database = __esm({
1253
1263
  "src/lib/database.ts"() {
1254
1264
  "use strict";
@@ -1256,6 +1266,7 @@ var init_database = __esm({
1256
1266
  init_employees();
1257
1267
  _client = null;
1258
1268
  _resilientClient = null;
1269
+ _walCheckpointTimer = null;
1259
1270
  _daemonClient = null;
1260
1271
  initTurso = initDatabase;
1261
1272
  }
@@ -304,6 +304,16 @@ async function initDatabase(config) {
304
304
  }
305
305
  _client = createClient(opts);
306
306
  _resilientClient = wrapWithRetry(_client);
307
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
308
+ });
309
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
310
+ });
311
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
312
+ _walCheckpointTimer = setInterval(() => {
313
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
314
+ });
315
+ }, 3e4);
316
+ _walCheckpointTimer.unref();
307
317
  }
308
318
  function getClient() {
309
319
  if (!_resilientClient) {
@@ -1248,7 +1258,7 @@ async function ensureSchema() {
1248
1258
  }
1249
1259
  }
1250
1260
  }
1251
- var _client, _resilientClient, _daemonClient, initTurso;
1261
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1252
1262
  var init_database = __esm({
1253
1263
  "src/lib/database.ts"() {
1254
1264
  "use strict";
@@ -1256,6 +1266,7 @@ var init_database = __esm({
1256
1266
  init_employees();
1257
1267
  _client = null;
1258
1268
  _resilientClient = null;
1269
+ _walCheckpointTimer = null;
1259
1270
  _daemonClient = null;
1260
1271
  initTurso = initDatabase;
1261
1272
  }
@@ -306,6 +306,16 @@ async function initDatabase(config) {
306
306
  }
307
307
  _client = createClient(opts);
308
308
  _resilientClient = wrapWithRetry(_client);
309
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
310
+ });
311
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
312
+ });
313
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
314
+ _walCheckpointTimer = setInterval(() => {
315
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
316
+ });
317
+ }, 3e4);
318
+ _walCheckpointTimer.unref();
309
319
  }
310
320
  function getClient() {
311
321
  if (!_resilientClient) {
@@ -1250,7 +1260,7 @@ async function ensureSchema() {
1250
1260
  }
1251
1261
  }
1252
1262
  }
1253
- var _client, _resilientClient, _daemonClient, initTurso;
1263
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1254
1264
  var init_database = __esm({
1255
1265
  "src/lib/database.ts"() {
1256
1266
  "use strict";
@@ -1258,6 +1268,7 @@ var init_database = __esm({
1258
1268
  init_employees();
1259
1269
  _client = null;
1260
1270
  _resilientClient = null;
1271
+ _walCheckpointTimer = null;
1261
1272
  _daemonClient = null;
1262
1273
  initTurso = initDatabase;
1263
1274
  }
@@ -311,6 +311,16 @@ async function initDatabase(config) {
311
311
  }
312
312
  _client = createClient(opts);
313
313
  _resilientClient = wrapWithRetry(_client);
314
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
315
+ });
316
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
317
+ });
318
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
319
+ _walCheckpointTimer = setInterval(() => {
320
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
321
+ });
322
+ }, 3e4);
323
+ _walCheckpointTimer.unref();
314
324
  }
315
325
  function getClient() {
316
326
  if (!_resilientClient) {
@@ -1255,7 +1265,7 @@ async function ensureSchema() {
1255
1265
  }
1256
1266
  }
1257
1267
  }
1258
- var _client, _resilientClient, _daemonClient, initTurso;
1268
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1259
1269
  var init_database = __esm({
1260
1270
  "src/lib/database.ts"() {
1261
1271
  "use strict";
@@ -1263,6 +1273,7 @@ var init_database = __esm({
1263
1273
  init_employees();
1264
1274
  _client = null;
1265
1275
  _resilientClient = null;
1276
+ _walCheckpointTimer = null;
1266
1277
  _daemonClient = null;
1267
1278
  initTurso = initDatabase;
1268
1279
  }
package/dist/bin/cli.js CHANGED
@@ -1967,6 +1967,16 @@ async function initDatabase(config) {
1967
1967
  }
1968
1968
  _client = createClient(opts);
1969
1969
  _resilientClient = wrapWithRetry(_client);
1970
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1971
+ });
1972
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
1973
+ });
1974
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
1975
+ _walCheckpointTimer = setInterval(() => {
1976
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
1977
+ });
1978
+ }, 3e4);
1979
+ _walCheckpointTimer.unref();
1970
1980
  }
1971
1981
  function isInitialized() {
1972
1982
  return _client !== null;
@@ -2938,7 +2948,7 @@ async function disposeDatabase() {
2938
2948
  _resilientClient = null;
2939
2949
  }
2940
2950
  }
2941
- var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
2951
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2942
2952
  var init_database = __esm({
2943
2953
  "src/lib/database.ts"() {
2944
2954
  "use strict";
@@ -2946,6 +2956,7 @@ var init_database = __esm({
2946
2956
  init_employees();
2947
2957
  _client = null;
2948
2958
  _resilientClient = null;
2959
+ _walCheckpointTimer = null;
2949
2960
  _daemonClient = null;
2950
2961
  initTurso = initDatabase;
2951
2962
  disposeTurso = disposeDatabase;
@@ -8054,6 +8065,10 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
8054
8065
  args: [input.status, now, taskId]
8055
8066
  });
8056
8067
  }
8068
+ try {
8069
+ await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
8070
+ } catch {
8071
+ }
8057
8072
  try {
8058
8073
  await writeCheckpoint({
8059
8074
  taskId,
@@ -8184,18 +8199,18 @@ async function listPendingReviews(limit, sessionScope) {
8184
8199
  const client = getClient();
8185
8200
  if (sessionScope) {
8186
8201
  const result2 = await client.execute({
8187
- sql: `SELECT title, assigned_to, project_name FROM tasks
8202
+ sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
8188
8203
  WHERE status = 'needs_review'
8189
8204
  AND session_scope = ?
8190
- ORDER BY priority ASC, created_at DESC LIMIT ?`,
8205
+ ORDER BY updated_at ASC LIMIT ?`,
8191
8206
  args: [sessionScope, limit]
8192
8207
  });
8193
8208
  return result2.rows;
8194
8209
  }
8195
8210
  const result = await client.execute({
8196
- sql: `SELECT title, assigned_to, project_name FROM tasks
8211
+ sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
8197
8212
  WHERE status = 'needs_review'
8198
- ORDER BY priority ASC, created_at DESC LIMIT ?`,
8213
+ ORDER BY updated_at ASC LIMIT ?`,
8199
8214
  args: [limit]
8200
8215
  });
8201
8216
  return result.rows;
@@ -9740,7 +9755,17 @@ function sendIntercom(targetSession) {
9740
9755
  logIntercom(`COPY_MODE \u2192 ${targetSession} (exiting copy mode first)`);
9741
9756
  transport.sendKeys(targetSession, "q");
9742
9757
  }
9743
- transport.sendKeys(targetSession, "/exe-intercom");
9758
+ const agentName = targetSession.split("-")[0] ?? targetSession;
9759
+ const rtConfig = getAgentRuntime(agentName);
9760
+ if (rtConfig.runtime === "codex" || rtConfig.runtime === "opencode") {
9761
+ transport.sendKeys(targetSession, "You have a new task assigned. Call list_tasks to see your open tasks, then get_task to read the highest priority one. Start working now.");
9762
+ try {
9763
+ execSync8(`tmux send-keys -t ${targetSession} Tab`, { timeout: 2e3 });
9764
+ } catch {
9765
+ }
9766
+ } else {
9767
+ transport.sendKeys(targetSession, "/exe-intercom");
9768
+ }
9744
9769
  const batched = recordDebounce(targetSession);
9745
9770
  logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
9746
9771
  return "delivered";
@@ -27521,7 +27546,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os15.homedir()) {
27521
27546
  {
27522
27547
  type: "command",
27523
27548
  command: `node "${path39.join(packageRoot, "dist", "hooks", "session-start.js")}"`,
27524
- timeout: 10,
27549
+ timeout: 30,
27525
27550
  statusMessage: "exe-os: loading memory brief"
27526
27551
  }
27527
27552
  ]
@@ -651,6 +651,16 @@ async function initDatabase(config) {
651
651
  }
652
652
  _client = createClient(opts);
653
653
  _resilientClient = wrapWithRetry(_client);
654
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
655
+ });
656
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
657
+ });
658
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
659
+ _walCheckpointTimer = setInterval(() => {
660
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
661
+ });
662
+ }, 3e4);
663
+ _walCheckpointTimer.unref();
654
664
  }
655
665
  function getClient() {
656
666
  if (!_resilientClient) {
@@ -1595,7 +1605,7 @@ async function ensureSchema() {
1595
1605
  }
1596
1606
  }
1597
1607
  }
1598
- var _client, _resilientClient, _daemonClient, initTurso;
1608
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1599
1609
  var init_database = __esm({
1600
1610
  "src/lib/database.ts"() {
1601
1611
  "use strict";
@@ -1603,6 +1613,7 @@ var init_database = __esm({
1603
1613
  init_employees();
1604
1614
  _client = null;
1605
1615
  _resilientClient = null;
1616
+ _walCheckpointTimer = null;
1606
1617
  _daemonClient = null;
1607
1618
  initTurso = initDatabase;
1608
1619
  }
@@ -911,6 +911,16 @@ async function initDatabase(config) {
911
911
  }
912
912
  _client = createClient(opts);
913
913
  _resilientClient = wrapWithRetry(_client);
914
+ _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
915
+ });
916
+ _client.execute("PRAGMA journal_mode = WAL").catch(() => {
917
+ });
918
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
919
+ _walCheckpointTimer = setInterval(() => {
920
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
921
+ });
922
+ }, 3e4);
923
+ _walCheckpointTimer.unref();
914
924
  }
915
925
  function isInitialized() {
916
926
  return _client !== null;
@@ -1882,7 +1892,7 @@ async function disposeDatabase() {
1882
1892
  _resilientClient = null;
1883
1893
  }
1884
1894
  }
1885
- var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1895
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
1886
1896
  var init_database = __esm({
1887
1897
  "src/lib/database.ts"() {
1888
1898
  "use strict";
@@ -1890,6 +1900,7 @@ var init_database = __esm({
1890
1900
  init_employees();
1891
1901
  _client = null;
1892
1902
  _resilientClient = null;
1903
+ _walCheckpointTimer = null;
1893
1904
  _daemonClient = null;
1894
1905
  initTurso = initDatabase;
1895
1906
  disposeTurso = disposeDatabase;
@@ -4218,6 +4229,10 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4218
4229
  args: [input.status, now, taskId]
4219
4230
  });
4220
4231
  }
4232
+ try {
4233
+ await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4234
+ } catch {
4235
+ }
4221
4236
  try {
4222
4237
  await writeCheckpoint({
4223
4238
  taskId,
@@ -4348,18 +4363,18 @@ async function listPendingReviews(limit, sessionScope) {
4348
4363
  const client = getClient();
4349
4364
  if (sessionScope) {
4350
4365
  const result2 = await client.execute({
4351
- sql: `SELECT title, assigned_to, project_name FROM tasks
4366
+ sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4352
4367
  WHERE status = 'needs_review'
4353
4368
  AND session_scope = ?
4354
- ORDER BY priority ASC, created_at DESC LIMIT ?`,
4369
+ ORDER BY updated_at ASC LIMIT ?`,
4355
4370
  args: [sessionScope, limit]
4356
4371
  });
4357
4372
  return result2.rows;
4358
4373
  }
4359
4374
  const result = await client.execute({
4360
- sql: `SELECT title, assigned_to, project_name FROM tasks
4375
+ sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4361
4376
  WHERE status = 'needs_review'
4362
- ORDER BY priority ASC, created_at DESC LIMIT ?`,
4377
+ ORDER BY updated_at ASC LIMIT ?`,
4363
4378
  args: [limit]
4364
4379
  });
4365
4380
  return result.rows;
@@ -5913,7 +5928,17 @@ function sendIntercom(targetSession) {
5913
5928
  logIntercom(`COPY_MODE \u2192 ${targetSession} (exiting copy mode first)`);
5914
5929
  transport.sendKeys(targetSession, "q");
5915
5930
  }
5916
- transport.sendKeys(targetSession, "/exe-intercom");
5931
+ const agentName = targetSession.split("-")[0] ?? targetSession;
5932
+ const rtConfig = getAgentRuntime(agentName);
5933
+ if (rtConfig.runtime === "codex" || rtConfig.runtime === "opencode") {
5934
+ transport.sendKeys(targetSession, "You have a new task assigned. Call list_tasks to see your open tasks, then get_task to read the highest priority one. Start working now.");
5935
+ try {
5936
+ execSync7(`tmux send-keys -t ${targetSession} Tab`, { timeout: 2e3 });
5937
+ } catch {
5938
+ }
5939
+ } else {
5940
+ transport.sendKeys(targetSession, "/exe-intercom");
5941
+ }
5917
5942
  const batched = recordDebounce(targetSession);
5918
5943
  logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
5919
5944
  return "delivered";
@@ -8337,6 +8362,30 @@ function buildReminders(data) {
8337
8362
  function buildActionRequired(data) {
8338
8363
  const lines = [];
8339
8364
  let hasIssues = false;
8365
+ const STALE_REVIEW_MS = 24 * 60 * 60 * 1e3;
8366
+ const staleReviews = data.pendingReviews.filter(
8367
+ (r) => r.updatedAt && Date.now() - new Date(r.updatedAt).getTime() > STALE_REVIEW_MS
8368
+ );
8369
+ if (staleReviews.length > 0) {
8370
+ hasIssues = true;
8371
+ const oldest = staleReviews.reduce(
8372
+ (a, b) => new Date(a.updatedAt).getTime() < new Date(b.updatedAt).getTime() ? a : b
8373
+ );
8374
+ const oldestAge = ((Date.now() - new Date(oldest.updatedAt).getTime()) / 36e5).toFixed(0);
8375
+ lines.push(` \u{1F534} ${staleReviews.length} STALE REVIEW${staleReviews.length === 1 ? "" : "S"} (>24h, oldest: ${oldestAge}h) \u2014 run /exe-review`);
8376
+ }
8377
+ if (data.pendingReviews.length > 0 && staleReviews.length === 0) {
8378
+ const oldest = data.pendingReviews.reduce((a, b) => {
8379
+ if (!a.updatedAt || !b.updatedAt) return a;
8380
+ return new Date(a.updatedAt).getTime() < new Date(b.updatedAt).getTime() ? a : b;
8381
+ });
8382
+ const ageStr = oldest.updatedAt ? (() => {
8383
+ const hrs = (Date.now() - new Date(oldest.updatedAt).getTime()) / 36e5;
8384
+ return hrs < 1 ? `${Math.floor(hrs * 60)}m` : `${hrs.toFixed(0)}h`;
8385
+ })() : "";
8386
+ lines.push(` \u{1F4CB} ${data.pendingReviews.length} review${data.pendingReviews.length === 1 ? "" : "s"} pending${ageStr ? ` (oldest: ${ageStr})` : ""}`);
8387
+ hasIssues = true;
8388
+ }
8340
8389
  const blockedTasks = data.globalTasks.filter((t) => t.status === "blocked");
8341
8390
  if (blockedTasks.length > 0) {
8342
8391
  hasIssues = true;
@@ -8829,7 +8878,7 @@ async function boot(options) {
8829
8878
  const revScope = sessionScopeFilter();
8830
8879
  try {
8831
8880
  const result = await client.execute({
8832
- sql: `SELECT t.title, t.priority, t.created_at,
8881
+ sql: `SELECT t.title, t.priority, t.created_at, t.updated_at,
8833
8882
  COALESCE(
8834
8883
  (SELECT orig.assigned_to FROM tasks orig WHERE orig.task_file = REPLACE(REPLACE(t.task_file, 'review-', ''), 'exe/exe/', 'exe/' || orig.assigned_to || '/')),
8835
8884
  REPLACE(SUBSTR(t.title, INSTR(t.title, 'review-') + 7), SUBSTR(t.title, INSTR(t.title, '-')), '')
@@ -8844,12 +8893,13 @@ async function boot(options) {
8844
8893
  title: String(row.title),
8845
8894
  priority: String(row.priority),
8846
8895
  originalAssignee: String(row.original_assignee || "unknown"),
8847
- createdAt: String(row.created_at || "")
8896
+ createdAt: String(row.created_at || ""),
8897
+ updatedAt: String(row.updated_at || row.created_at || "")
8848
8898
  }));
8849
8899
  } catch {
8850
8900
  try {
8851
8901
  const result = await client.execute({
8852
- sql: `SELECT title, priority, created_at
8902
+ sql: `SELECT title, priority, created_at, updated_at
8853
8903
  FROM tasks
8854
8904
  WHERE (assigned_to = ? OR assigned_to = 'exe') AND status IN ('open', 'in_progress')
8855
8905
  AND task_file LIKE '%review-%'${revScope.sql}
@@ -8863,13 +8913,37 @@ async function boot(options) {
8863
8913
  title,
8864
8914
  priority: String(row.priority),
8865
8915
  originalAssignee: match?.[1] ?? "unknown",
8866
- createdAt: String(row.created_at || "")
8916
+ createdAt: String(row.created_at || ""),
8917
+ updatedAt: String(row.updated_at || row.created_at || "")
8867
8918
  };
8868
8919
  });
8869
8920
  } catch {
8870
8921
  briefData.pendingReviews = [];
8871
8922
  }
8872
8923
  }
8924
+ try {
8925
+ const nrScope = sessionScopeFilter();
8926
+ const nrResult = await client.execute({
8927
+ sql: `SELECT title, priority, assigned_to, created_at, updated_at
8928
+ FROM tasks WHERE status = 'needs_review'${nrScope.sql}
8929
+ ORDER BY updated_at ASC`,
8930
+ args: [...nrScope.args]
8931
+ });
8932
+ const existingTitles = new Set(briefData.pendingReviews.map((r) => r.title));
8933
+ for (const row of nrResult.rows) {
8934
+ const title = String(row.title);
8935
+ if (!existingTitles.has(title)) {
8936
+ briefData.pendingReviews.push({
8937
+ title,
8938
+ priority: String(row.priority),
8939
+ originalAssignee: String(row.assigned_to || "unknown"),
8940
+ createdAt: String(row.created_at || ""),
8941
+ updatedAt: String(row.updated_at || row.created_at || "")
8942
+ });
8943
+ }
8944
+ }
8945
+ } catch {
8946
+ }
8873
8947
  })(),
8874
8948
  // Global task queue — all open/in_progress/blocked tasks
8875
8949
  (async () => {
@@ -1082,10 +1082,122 @@ All memory, tasks, behaviors, documents, and wiki content belonging to {{company
1082
1082
  }
1083
1083
  });
1084
1084
 
1085
+ // src/lib/runtime-table.ts
1086
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
1087
+ var init_runtime_table = __esm({
1088
+ "src/lib/runtime-table.ts"() {
1089
+ "use strict";
1090
+ RUNTIME_TABLE = {
1091
+ codex: {
1092
+ binary: "codex",
1093
+ launchMode: "interactive",
1094
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
1095
+ inlineFlag: "--no-alt-screen",
1096
+ apiKeyEnv: "OPENAI_API_KEY",
1097
+ defaultModel: "gpt-5.4"
1098
+ },
1099
+ opencode: {
1100
+ binary: "opencode",
1101
+ launchMode: "exec",
1102
+ autoApproveFlag: "--dangerously-skip-permissions",
1103
+ inlineFlag: "",
1104
+ apiKeyEnv: "ANTHROPIC_API_KEY",
1105
+ defaultModel: "anthropic/claude-sonnet-4-6"
1106
+ }
1107
+ };
1108
+ DEFAULT_RUNTIME = "claude";
1109
+ }
1110
+ });
1111
+
1112
+ // src/lib/agent-config.ts
1113
+ var agent_config_exports = {};
1114
+ __export(agent_config_exports, {
1115
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
1116
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
1117
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
1118
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
1119
+ clearAgentRuntime: () => clearAgentRuntime,
1120
+ getAgentRuntime: () => getAgentRuntime,
1121
+ loadAgentConfig: () => loadAgentConfig,
1122
+ saveAgentConfig: () => saveAgentConfig,
1123
+ setAgentRuntime: () => setAgentRuntime
1124
+ });
1125
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync } from "fs";
1126
+ import path3 from "path";
1127
+ function loadAgentConfig() {
1128
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
1129
+ try {
1130
+ return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
1131
+ } catch {
1132
+ return {};
1133
+ }
1134
+ }
1135
+ function saveAgentConfig(config) {
1136
+ const dir = path3.dirname(AGENT_CONFIG_PATH);
1137
+ if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
1138
+ writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
1139
+ }
1140
+ function getAgentRuntime(agentId) {
1141
+ const config = loadAgentConfig();
1142
+ const entry = config[agentId];
1143
+ if (entry) return entry;
1144
+ const orgDefault = config["default"];
1145
+ if (orgDefault) return orgDefault;
1146
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
1147
+ }
1148
+ function setAgentRuntime(agentId, runtime, model) {
1149
+ const knownModels = KNOWN_RUNTIMES[runtime];
1150
+ if (!knownModels) {
1151
+ return {
1152
+ ok: false,
1153
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
1154
+ };
1155
+ }
1156
+ if (!knownModels.includes(model)) {
1157
+ return {
1158
+ ok: false,
1159
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
1160
+ };
1161
+ }
1162
+ const config = loadAgentConfig();
1163
+ config[agentId] = { runtime, model };
1164
+ saveAgentConfig(config);
1165
+ return { ok: true };
1166
+ }
1167
+ function clearAgentRuntime(agentId) {
1168
+ const config = loadAgentConfig();
1169
+ delete config[agentId];
1170
+ saveAgentConfig(config);
1171
+ }
1172
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
1173
+ var init_agent_config = __esm({
1174
+ "src/lib/agent-config.ts"() {
1175
+ "use strict";
1176
+ init_config();
1177
+ init_runtime_table();
1178
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
1179
+ KNOWN_RUNTIMES = {
1180
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
1181
+ codex: ["gpt-5.4", "gpt-5.5", "o3", "o4-mini"],
1182
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
1183
+ };
1184
+ RUNTIME_LABELS = {
1185
+ claude: "Claude Code (Anthropic)",
1186
+ codex: "Codex (OpenAI)",
1187
+ opencode: "OpenCode (open source)"
1188
+ };
1189
+ DEFAULT_MODELS = {
1190
+ claude: "claude-opus-4",
1191
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
1192
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
1193
+ };
1194
+ }
1195
+ });
1196
+
1085
1197
  // src/bin/exe-call.ts
1086
1198
  init_employees();
1087
1199
  init_config();
1088
- import path3 from "path";
1200
+ import path4 from "path";
1089
1201
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
1090
1202
  import { execSync as execSync2 } from "child_process";
1091
1203
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1129,10 +1241,10 @@ function buildSessionEnv(employee, sessionDir) {
1129
1241
  env["AGENT_ROLE"] = employee.role;
1130
1242
  return env;
1131
1243
  }
1132
- async function prepareSessionDir(name, systemPrompt, sessionsBase = path3.join(EXE_AI_DIR, "sessions")) {
1133
- const sessionDir = path3.join(sessionsBase, name);
1244
+ async function prepareSessionDir(name, systemPrompt, sessionsBase = path4.join(EXE_AI_DIR, "sessions")) {
1245
+ const sessionDir = path4.join(sessionsBase, name);
1134
1246
  await mkdir3(sessionDir, { recursive: true });
1135
- await writeFile3(path3.join(sessionDir, "CLAUDE.md"), systemPrompt, "utf-8");
1247
+ await writeFile3(path4.join(sessionDir, "CLAUDE.md"), systemPrompt, "utf-8");
1136
1248
  return sessionDir;
1137
1249
  }
1138
1250
  if (isMainModule(import.meta.url)) {
@@ -1140,8 +1252,8 @@ if (isMainModule(import.meta.url)) {
1140
1252
  const employees = await loadEmployees();
1141
1253
  const coordinatorName = getCoordinatorName(employees);
1142
1254
  if (!name || name === coordinatorName) {
1143
- const __dirname = path3.dirname(fileURLToPath2(import.meta.url));
1144
- const bootPath = path3.join(__dirname, "exe-boot.js");
1255
+ const __dirname = path4.dirname(fileURLToPath2(import.meta.url));
1256
+ const bootPath = path4.join(__dirname, "exe-boot.js");
1145
1257
  try {
1146
1258
  execSync2(`node "${bootPath}"`, { stdio: "inherit" });
1147
1259
  } catch (err) {
@@ -1176,10 +1288,26 @@ if (isMainModule(import.meta.url)) {
1176
1288
  }
1177
1289
  const sessionDir = await prepareSessionDir(name, getSessionPrompt(prompt));
1178
1290
  const env = buildSessionEnv(employee, sessionDir);
1179
- console.log(
1180
- `Launching ${employee.name} (${employee.role}) session...`
1181
- );
1182
- execSync2("claude --dangerously-skip-permissions", { stdio: "inherit", env });
1291
+ const { getAgentRuntime: getAgentRuntime2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1292
+ const rtConfig = getAgentRuntime2(employee.name);
1293
+ if (rtConfig.runtime === "codex") {
1294
+ console.log(
1295
+ `Launching ${employee.name} (${employee.role}) on Codex (${rtConfig.model})...`
1296
+ );
1297
+ const codexLauncher = path4.join(path4.dirname(fileURLToPath2(import.meta.url)), "exe-start-codex.js");
1298
+ execSync2(`node "${codexLauncher}" --agent ${employee.name}`, { stdio: "inherit", env });
1299
+ } else if (rtConfig.runtime === "opencode") {
1300
+ console.log(
1301
+ `Launching ${employee.name} (${employee.role}) on OpenCode (${rtConfig.model})...`
1302
+ );
1303
+ const opencodeLauncher = path4.join(path4.dirname(fileURLToPath2(import.meta.url)), "exe-start-opencode.js");
1304
+ execSync2(`node "${opencodeLauncher}" --agent ${employee.name}`, { stdio: "inherit", env });
1305
+ } else {
1306
+ console.log(
1307
+ `Launching ${employee.name} (${employee.role}) on Claude Code...`
1308
+ );
1309
+ execSync2("claude --dangerously-skip-permissions", { stdio: "inherit", env });
1310
+ }
1183
1311
  } catch (err) {
1184
1312
  console.error(`Failed to launch employee session: ${err instanceof Error ? err.message : String(err)}`);
1185
1313
  console.error("Try running: exe-os team");