@askexenow/exe-os 0.8.39 → 0.8.41

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.
package/dist/index.js CHANGED
@@ -1274,6 +1274,7 @@ var config_exports = {};
1274
1274
  __export(config_exports, {
1275
1275
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
1276
1276
  CONFIG_PATH: () => CONFIG_PATH,
1277
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
1277
1278
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
1278
1279
  DB_PATH: () => DB_PATH,
1279
1280
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -1429,7 +1430,7 @@ async function loadConfigFrom(configPath) {
1429
1430
  return { ...DEFAULT_CONFIG };
1430
1431
  }
1431
1432
  }
1432
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1433
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1433
1434
  var init_config = __esm({
1434
1435
  "src/lib/config.ts"() {
1435
1436
  "use strict";
@@ -1437,6 +1438,7 @@ var init_config = __esm({
1437
1438
  DB_PATH = path4.join(EXE_AI_DIR, "memories.db");
1438
1439
  MODELS_DIR = path4.join(EXE_AI_DIR, "models");
1439
1440
  CONFIG_PATH = path4.join(EXE_AI_DIR, "config.json");
1441
+ COO_AGENT_NAME = "exe";
1440
1442
  LEGACY_LANCE_PATH = path4.join(EXE_AI_DIR, "local.lance");
1441
1443
  CURRENT_CONFIG_VERSION = 1;
1442
1444
  DEFAULT_CONFIG = {
@@ -1944,7 +1946,7 @@ async function listTasks(input) {
1944
1946
  }
1945
1947
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1946
1948
  const result = await client.execute({
1947
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
1949
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
1948
1950
  args
1949
1951
  });
1950
1952
  return result.rows.map((r) => ({
@@ -2165,6 +2167,34 @@ async function listPendingReviews(limit) {
2165
2167
  });
2166
2168
  return result.rows;
2167
2169
  }
2170
+ async function cleanupOrphanedReviews() {
2171
+ const client = getClient();
2172
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2173
+ const r1 = await client.execute({
2174
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
2175
+ WHERE status = 'needs_review'
2176
+ AND assigned_by = 'system'
2177
+ AND title LIKE 'Review:%'
2178
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
2179
+ args: [now]
2180
+ });
2181
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
2182
+ const r2 = await client.execute({
2183
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
2184
+ WHERE status = 'needs_review'
2185
+ AND result IS NOT NULL
2186
+ AND updated_at < ?`,
2187
+ args: [now, staleThreshold]
2188
+ });
2189
+ const total = r1.rowsAffected + r2.rowsAffected;
2190
+ if (total > 0) {
2191
+ process.stderr.write(
2192
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
2193
+ `
2194
+ );
2195
+ }
2196
+ return total;
2197
+ }
2168
2198
  function getReviewChecklist(role, agent, taskSlug) {
2169
2199
  const roleLower = role.toLowerCase();
2170
2200
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -2906,6 +2936,7 @@ var init_skill_learning = __esm({
2906
2936
  // src/lib/tasks.ts
2907
2937
  var tasks_exports = {};
2908
2938
  __export(tasks_exports, {
2939
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
2909
2940
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
2910
2941
  countPendingReviews: () => countPendingReviews,
2911
2942
  createTask: () => createTask,
@@ -2971,6 +3002,21 @@ async function updateTask(input) {
2971
3002
  });
2972
3003
  } catch {
2973
3004
  }
3005
+ try {
3006
+ const client = getClient();
3007
+ const cascaded = await client.execute({
3008
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3009
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
3010
+ args: [now, taskId]
3011
+ });
3012
+ if (cascaded.rowsAffected > 0) {
3013
+ process.stderr.write(
3014
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
3015
+ `
3016
+ );
3017
+ }
3018
+ } catch {
3019
+ }
2974
3020
  }
2975
3021
  const isTerminal = input.status === "done" || input.status === "needs_review";
2976
3022
  if (isTerminal) {
@@ -6,7 +6,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/lib/cloud-sync.ts
9
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync5, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
9
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync5, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
10
+ import crypto2 from "crypto";
10
11
  import path5 from "path";
11
12
  import { homedir } from "os";
12
13
 
@@ -436,17 +437,29 @@ var PUSH_BATCH_SIZE = 5e3;
436
437
  var ROSTER_LOCK_PATH = path5.join(EXE_AI_DIR, "roster-merge.lock");
437
438
  var LOCK_STALE_MS = 3e4;
438
439
  async function withRosterLock(fn) {
439
- if (existsSync5(ROSTER_LOCK_PATH)) {
440
- try {
441
- const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
442
- if (Date.now() - ts < LOCK_STALE_MS) {
440
+ try {
441
+ const fd = openSync(ROSTER_LOCK_PATH, "wx");
442
+ closeSync(fd);
443
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
444
+ } catch (err) {
445
+ if (err.code === "EEXIST") {
446
+ try {
447
+ const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
448
+ if (Date.now() - ts < LOCK_STALE_MS) {
449
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
450
+ }
451
+ unlinkSync(ROSTER_LOCK_PATH);
452
+ const fd = openSync(ROSTER_LOCK_PATH, "wx");
453
+ closeSync(fd);
454
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
455
+ } catch (retryErr) {
456
+ if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
443
457
  throw new Error("Roster merge already in progress \u2014 another sync is running");
444
458
  }
445
- } catch (err) {
446
- if (err instanceof Error && err.message.includes("already in progress")) throw err;
459
+ } else {
460
+ throw err;
447
461
  }
448
462
  }
449
- writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
450
463
  try {
451
464
  return await fn();
452
465
  } finally {
@@ -769,7 +782,7 @@ function buildRosterBlob(paths) {
769
782
  }
770
783
  const deletedNames = consumeRosterDeletions();
771
784
  const content = JSON.stringify({ roster, identities, config, deletedNames });
772
- const hash = Buffer.from(content).length;
785
+ const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
773
786
  return { roster, identities, config, deletedNames, version: hash };
774
787
  }
775
788
  async function cloudPushRoster(config) {
@@ -23,6 +23,7 @@ var EXE_AI_DIR = resolveDataDir();
23
23
  var DB_PATH = path.join(EXE_AI_DIR, "memories.db");
24
24
  var MODELS_DIR = path.join(EXE_AI_DIR, "models");
25
25
  var CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
26
+ var COO_AGENT_NAME = "exe";
26
27
  var LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
27
28
  var CURRENT_CONFIG_VERSION = 1;
28
29
  var DEFAULT_CONFIG = {
@@ -225,6 +226,7 @@ async function loadConfigFrom(configPath) {
225
226
  export {
226
227
  CONFIG_MIGRATIONS,
227
228
  CONFIG_PATH,
229
+ COO_AGENT_NAME,
228
230
  CURRENT_CONFIG_VERSION,
229
231
  DB_PATH,
230
232
  EXE_AI_DIR,
@@ -13,6 +13,7 @@ var config_exports = {};
13
13
  __export(config_exports, {
14
14
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
15
15
  CONFIG_PATH: () => CONFIG_PATH,
16
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
16
17
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
17
18
  DB_PATH: () => DB_PATH,
18
19
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -168,7 +169,7 @@ async function loadConfigFrom(configPath) {
168
169
  return { ...DEFAULT_CONFIG };
169
170
  }
170
171
  }
171
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
172
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
172
173
  var init_config = __esm({
173
174
  "src/lib/config.ts"() {
174
175
  "use strict";
@@ -176,6 +177,7 @@ var init_config = __esm({
176
177
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
177
178
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
178
179
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
180
+ COO_AGENT_NAME = "exe";
179
181
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
180
182
  CURRENT_CONFIG_VERSION = 1;
181
183
  DEFAULT_CONFIG = {
@@ -30,6 +30,7 @@ var config_exports = {};
30
30
  __export(config_exports, {
31
31
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
32
32
  CONFIG_PATH: () => CONFIG_PATH,
33
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
33
34
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
34
35
  DB_PATH: () => DB_PATH,
35
36
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -185,7 +186,7 @@ async function loadConfigFrom(configPath) {
185
186
  return { ...DEFAULT_CONFIG };
186
187
  }
187
188
  }
188
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
189
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
189
190
  var init_config = __esm({
190
191
  "src/lib/config.ts"() {
191
192
  "use strict";
@@ -193,6 +194,7 @@ var init_config = __esm({
193
194
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
194
195
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
195
196
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
197
+ COO_AGENT_NAME = "exe";
196
198
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
197
199
  CURRENT_CONFIG_VERSION = 1;
198
200
  DEFAULT_CONFIG = {
@@ -2963,7 +2965,7 @@ async function listTasks(input) {
2963
2965
  }
2964
2966
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2965
2967
  const result = await client.execute({
2966
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
2968
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
2967
2969
  args
2968
2970
  });
2969
2971
  return result.rows.map((r) => ({
@@ -3157,6 +3159,7 @@ var init_tasks_crud = __esm({
3157
3159
  // src/lib/tasks-review.ts
3158
3160
  var tasks_review_exports = {};
3159
3161
  __export(tasks_review_exports, {
3162
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
3160
3163
  cleanupReviewFile: () => cleanupReviewFile,
3161
3164
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
3162
3165
  countPendingReviews: () => countPendingReviews,
@@ -3193,6 +3196,34 @@ async function listPendingReviews(limit) {
3193
3196
  });
3194
3197
  return result.rows;
3195
3198
  }
3199
+ async function cleanupOrphanedReviews() {
3200
+ const client = getClient();
3201
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3202
+ const r1 = await client.execute({
3203
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3204
+ WHERE status = 'needs_review'
3205
+ AND assigned_by = 'system'
3206
+ AND title LIKE 'Review:%'
3207
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3208
+ args: [now]
3209
+ });
3210
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
3211
+ const r2 = await client.execute({
3212
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3213
+ WHERE status = 'needs_review'
3214
+ AND result IS NOT NULL
3215
+ AND updated_at < ?`,
3216
+ args: [now, staleThreshold]
3217
+ });
3218
+ const total = r1.rowsAffected + r2.rowsAffected;
3219
+ if (total > 0) {
3220
+ process.stderr.write(
3221
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
3222
+ `
3223
+ );
3224
+ }
3225
+ return total;
3226
+ }
3196
3227
  function getReviewChecklist(role, agent, taskSlug) {
3197
3228
  const roleLower = role.toLowerCase();
3198
3229
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -3962,6 +3993,7 @@ var init_skill_learning = __esm({
3962
3993
  // src/lib/tasks.ts
3963
3994
  var tasks_exports = {};
3964
3995
  __export(tasks_exports, {
3996
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
3965
3997
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
3966
3998
  countPendingReviews: () => countPendingReviews,
3967
3999
  createTask: () => createTask,
@@ -4027,6 +4059,21 @@ async function updateTask(input) {
4027
4059
  });
4028
4060
  } catch {
4029
4061
  }
4062
+ try {
4063
+ const client = getClient();
4064
+ const cascaded = await client.execute({
4065
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
4066
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
4067
+ args: [now, taskId]
4068
+ });
4069
+ if (cascaded.rowsAffected > 0) {
4070
+ process.stderr.write(
4071
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
4072
+ `
4073
+ );
4074
+ }
4075
+ } catch {
4076
+ }
4030
4077
  }
4031
4078
  const isTerminal = input.status === "done" || input.status === "needs_review";
4032
4079
  if (isTerminal) {
@@ -968,6 +968,7 @@ var config_exports = {};
968
968
  __export(config_exports, {
969
969
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
970
970
  CONFIG_PATH: () => CONFIG_PATH,
971
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
971
972
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
972
973
  DB_PATH: () => DB_PATH,
973
974
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -1123,7 +1124,7 @@ async function loadConfigFrom(configPath) {
1123
1124
  return { ...DEFAULT_CONFIG };
1124
1125
  }
1125
1126
  }
1126
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1127
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1127
1128
  var init_config = __esm({
1128
1129
  "src/lib/config.ts"() {
1129
1130
  "use strict";
@@ -1131,6 +1132,7 @@ var init_config = __esm({
1131
1132
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
1132
1133
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
1133
1134
  CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
1135
+ COO_AGENT_NAME = "exe";
1134
1136
  LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
1135
1137
  CURRENT_CONFIG_VERSION = 1;
1136
1138
  DEFAULT_CONFIG = {
package/dist/lib/tasks.js CHANGED
@@ -494,7 +494,7 @@ async function listTasks(input) {
494
494
  }
495
495
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
496
496
  const result = await client.execute({
497
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
497
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
498
498
  args
499
499
  });
500
500
  return result.rows.map((r) => ({
@@ -1686,6 +1686,34 @@ async function listPendingReviews(limit) {
1686
1686
  });
1687
1687
  return result.rows;
1688
1688
  }
1689
+ async function cleanupOrphanedReviews() {
1690
+ const client = getClient();
1691
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1692
+ const r1 = await client.execute({
1693
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1694
+ WHERE status = 'needs_review'
1695
+ AND assigned_by = 'system'
1696
+ AND title LIKE 'Review:%'
1697
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
1698
+ args: [now]
1699
+ });
1700
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
1701
+ const r2 = await client.execute({
1702
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1703
+ WHERE status = 'needs_review'
1704
+ AND result IS NOT NULL
1705
+ AND updated_at < ?`,
1706
+ args: [now, staleThreshold]
1707
+ });
1708
+ const total = r1.rowsAffected + r2.rowsAffected;
1709
+ if (total > 0) {
1710
+ process.stderr.write(
1711
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
1712
+ `
1713
+ );
1714
+ }
1715
+ return total;
1716
+ }
1689
1717
  function getReviewChecklist(role, agent, taskSlug) {
1690
1718
  const roleLower = role.toLowerCase();
1691
1719
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -2411,6 +2439,21 @@ async function updateTask(input) {
2411
2439
  });
2412
2440
  } catch {
2413
2441
  }
2442
+ try {
2443
+ const client = getClient();
2444
+ const cascaded = await client.execute({
2445
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
2446
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
2447
+ args: [now, taskId]
2448
+ });
2449
+ if (cascaded.rowsAffected > 0) {
2450
+ process.stderr.write(
2451
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
2452
+ `
2453
+ );
2454
+ }
2455
+ } catch {
2456
+ }
2414
2457
  }
2415
2458
  const isTerminal = input.status === "done" || input.status === "needs_review";
2416
2459
  if (isTerminal) {
@@ -2500,6 +2543,7 @@ var init_tasks = __esm({
2500
2543
  });
2501
2544
  init_tasks();
2502
2545
  export {
2546
+ cleanupOrphanedReviews,
2503
2547
  countNewPendingReviewsSince,
2504
2548
  countPendingReviews,
2505
2549
  createTask,
@@ -959,7 +959,7 @@ async function listTasks(input) {
959
959
  }
960
960
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
961
961
  const result = await client.execute({
962
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
962
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
963
963
  args
964
964
  });
965
965
  return result.rows.map((r) => ({
@@ -1180,6 +1180,34 @@ async function listPendingReviews(limit) {
1180
1180
  });
1181
1181
  return result.rows;
1182
1182
  }
1183
+ async function cleanupOrphanedReviews() {
1184
+ const client = getClient();
1185
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1186
+ const r1 = await client.execute({
1187
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1188
+ WHERE status = 'needs_review'
1189
+ AND assigned_by = 'system'
1190
+ AND title LIKE 'Review:%'
1191
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
1192
+ args: [now]
1193
+ });
1194
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
1195
+ const r2 = await client.execute({
1196
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1197
+ WHERE status = 'needs_review'
1198
+ AND result IS NOT NULL
1199
+ AND updated_at < ?`,
1200
+ args: [now, staleThreshold]
1201
+ });
1202
+ const total = r1.rowsAffected + r2.rowsAffected;
1203
+ if (total > 0) {
1204
+ process.stderr.write(
1205
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
1206
+ `
1207
+ );
1208
+ }
1209
+ return total;
1210
+ }
1183
1211
  function getReviewChecklist(role, agent, taskSlug) {
1184
1212
  const roleLower = role.toLowerCase();
1185
1213
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -1859,6 +1887,7 @@ var init_skill_learning = __esm({
1859
1887
  // src/lib/tasks.ts
1860
1888
  var tasks_exports = {};
1861
1889
  __export(tasks_exports, {
1890
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
1862
1891
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
1863
1892
  countPendingReviews: () => countPendingReviews,
1864
1893
  createTask: () => createTask,
@@ -1924,6 +1953,21 @@ async function updateTask(input) {
1924
1953
  });
1925
1954
  } catch {
1926
1955
  }
1956
+ try {
1957
+ const client = getClient();
1958
+ const cascaded = await client.execute({
1959
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1960
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
1961
+ args: [now, taskId]
1962
+ });
1963
+ if (cascaded.rowsAffected > 0) {
1964
+ process.stderr.write(
1965
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
1966
+ `
1967
+ );
1968
+ }
1969
+ } catch {
1970
+ }
1927
1971
  }
1928
1972
  const isTerminal = input.status === "done" || input.status === "needs_review";
1929
1973
  if (isTerminal) {
@@ -39,6 +39,7 @@ var config_exports = {};
39
39
  __export(config_exports, {
40
40
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
41
41
  CONFIG_PATH: () => CONFIG_PATH,
42
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
42
43
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
43
44
  DB_PATH: () => DB_PATH,
44
45
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -194,7 +195,7 @@ async function loadConfigFrom(configPath) {
194
195
  return { ...DEFAULT_CONFIG };
195
196
  }
196
197
  }
197
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
198
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
198
199
  var init_config = __esm({
199
200
  "src/lib/config.ts"() {
200
201
  "use strict";
@@ -202,6 +203,7 @@ var init_config = __esm({
202
203
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
203
204
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
204
205
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
206
+ COO_AGENT_NAME = "exe";
205
207
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
206
208
  CURRENT_CONFIG_VERSION = 1;
207
209
  DEFAULT_CONFIG = {
@@ -3870,7 +3872,7 @@ async function listTasks(input) {
3870
3872
  }
3871
3873
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3872
3874
  const result = await client.execute({
3873
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
3875
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
3874
3876
  args
3875
3877
  });
3876
3878
  return result.rows.map((r) => ({
@@ -4894,6 +4896,34 @@ async function listPendingReviews(limit) {
4894
4896
  });
4895
4897
  return result.rows;
4896
4898
  }
4899
+ async function cleanupOrphanedReviews() {
4900
+ const client = getClient();
4901
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4902
+ const r1 = await client.execute({
4903
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
4904
+ WHERE status = 'needs_review'
4905
+ AND assigned_by = 'system'
4906
+ AND title LIKE 'Review:%'
4907
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4908
+ args: [now]
4909
+ });
4910
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
4911
+ const r2 = await client.execute({
4912
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
4913
+ WHERE status = 'needs_review'
4914
+ AND result IS NOT NULL
4915
+ AND updated_at < ?`,
4916
+ args: [now, staleThreshold]
4917
+ });
4918
+ const total = r1.rowsAffected + r2.rowsAffected;
4919
+ if (total > 0) {
4920
+ process.stderr.write(
4921
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
4922
+ `
4923
+ );
4924
+ }
4925
+ return total;
4926
+ }
4897
4927
  function getReviewChecklist(role, agent, taskSlug) {
4898
4928
  const roleLower = role.toLowerCase();
4899
4929
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -5559,6 +5589,7 @@ var init_skill_learning = __esm({
5559
5589
  // src/lib/tasks.ts
5560
5590
  var tasks_exports = {};
5561
5591
  __export(tasks_exports, {
5592
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
5562
5593
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
5563
5594
  countPendingReviews: () => countPendingReviews,
5564
5595
  createTask: () => createTask,
@@ -5624,6 +5655,21 @@ async function updateTask(input) {
5624
5655
  });
5625
5656
  } catch {
5626
5657
  }
5658
+ try {
5659
+ const client = getClient();
5660
+ const cascaded = await client.execute({
5661
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
5662
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
5663
+ args: [now, taskId]
5664
+ });
5665
+ if (cascaded.rowsAffected > 0) {
5666
+ process.stderr.write(
5667
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
5668
+ `
5669
+ );
5670
+ }
5671
+ } catch {
5672
+ }
5627
5673
  }
5628
5674
  const isTerminal = input.status === "done" || input.status === "needs_review";
5629
5675
  if (isTerminal) {
@@ -48,6 +48,7 @@ var config_exports = {};
48
48
  __export(config_exports, {
49
49
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
50
50
  CONFIG_PATH: () => CONFIG_PATH,
51
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
51
52
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
52
53
  DB_PATH: () => DB_PATH,
53
54
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -203,7 +204,7 @@ async function loadConfigFrom(configPath) {
203
204
  return { ...DEFAULT_CONFIG };
204
205
  }
205
206
  }
206
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
207
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
207
208
  var init_config = __esm({
208
209
  "src/lib/config.ts"() {
209
210
  "use strict";
@@ -211,6 +212,7 @@ var init_config = __esm({
211
212
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
212
213
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
213
214
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
215
+ COO_AGENT_NAME = "exe";
214
216
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
215
217
  CURRENT_CONFIG_VERSION = 1;
216
218
  DEFAULT_CONFIG = {
@@ -172,7 +172,7 @@ async function listTasks(input) {
172
172
  }
173
173
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
174
174
  const result = await client.execute({
175
- sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC`,
175
+ sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
176
176
  args
177
177
  });
178
178
  return result.rows.map((r) => ({
@@ -1461,6 +1461,21 @@ async function updateTask(input) {
1461
1461
  });
1462
1462
  } catch {
1463
1463
  }
1464
+ try {
1465
+ const client = getClient();
1466
+ const cascaded = await client.execute({
1467
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1468
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
1469
+ args: [now, taskId]
1470
+ });
1471
+ if (cascaded.rowsAffected > 0) {
1472
+ process.stderr.write(
1473
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
1474
+ `
1475
+ );
1476
+ }
1477
+ } catch {
1478
+ }
1464
1479
  }
1465
1480
  const isTerminal = input.status === "done" || input.status === "needs_review";
1466
1481
  if (isTerminal) {