@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/bin/cli.js CHANGED
@@ -31,6 +31,7 @@ var config_exports = {};
31
31
  __export(config_exports, {
32
32
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
33
33
  CONFIG_PATH: () => CONFIG_PATH,
34
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
34
35
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
35
36
  DB_PATH: () => DB_PATH,
36
37
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -186,7 +187,7 @@ async function loadConfigFrom(configPath) {
186
187
  return { ...DEFAULT_CONFIG };
187
188
  }
188
189
  }
189
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
190
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
190
191
  var init_config = __esm({
191
192
  "src/lib/config.ts"() {
192
193
  "use strict";
@@ -194,6 +195,7 @@ var init_config = __esm({
194
195
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
195
196
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
196
197
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
198
+ COO_AGENT_NAME = "exe";
197
199
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
198
200
  CURRENT_CONFIG_VERSION = 1;
199
201
  DEFAULT_CONFIG = {
@@ -12246,6 +12248,7 @@ var init_terminal = __esm({
12246
12248
  });
12247
12249
 
12248
12250
  // src/tui/Sidebar.tsx
12251
+ import React14 from "react";
12249
12252
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
12250
12253
  function Sidebar({
12251
12254
  active,
@@ -12576,7 +12579,7 @@ var init_tmux_status = __esm({
12576
12579
  });
12577
12580
 
12578
12581
  // src/tui/Footer.tsx
12579
- import { useState as useState4, useEffect as useEffect6 } from "react";
12582
+ import React15, { useState as useState4, useEffect as useEffect6 } from "react";
12580
12583
  import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
12581
12584
  function Footer() {
12582
12585
  const demo = useDemo();
@@ -12709,6 +12712,7 @@ var init_Footer = __esm({
12709
12712
  });
12710
12713
 
12711
12714
  // src/tui/components/StatusDot.tsx
12715
+ import React16 from "react";
12712
12716
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
12713
12717
  function StatusDot({ status, showLabel = true }) {
12714
12718
  const { char, dotColor, label, labelColor } = STATUS_CONFIG[status];
@@ -16060,7 +16064,7 @@ var init_CommandCenter = __esm({
16060
16064
  });
16061
16065
 
16062
16066
  // src/tui/components/TmuxPane.tsx
16063
- import { useState as useState7, useEffect as useEffect9 } from "react";
16067
+ import React18, { useState as useState7, useEffect as useEffect9 } from "react";
16064
16068
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
16065
16069
  function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDetach }) {
16066
16070
  const demo = useDemo();
@@ -16899,7 +16903,7 @@ async function listTasks(input) {
16899
16903
  }
16900
16904
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
16901
16905
  const result = await client.execute({
16902
- 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`,
16906
+ 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`,
16903
16907
  args: args2
16904
16908
  });
16905
16909
  return result.rows.map((r) => ({
@@ -17120,6 +17124,34 @@ async function listPendingReviews(limit) {
17120
17124
  });
17121
17125
  return result.rows;
17122
17126
  }
17127
+ async function cleanupOrphanedReviews() {
17128
+ const client = getClient();
17129
+ const now = (/* @__PURE__ */ new Date()).toISOString();
17130
+ const r1 = await client.execute({
17131
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
17132
+ WHERE status = 'needs_review'
17133
+ AND assigned_by = 'system'
17134
+ AND title LIKE 'Review:%'
17135
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
17136
+ args: [now]
17137
+ });
17138
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
17139
+ const r2 = await client.execute({
17140
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
17141
+ WHERE status = 'needs_review'
17142
+ AND result IS NOT NULL
17143
+ AND updated_at < ?`,
17144
+ args: [now, staleThreshold]
17145
+ });
17146
+ const total = r1.rowsAffected + r2.rowsAffected;
17147
+ if (total > 0) {
17148
+ process.stderr.write(
17149
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
17150
+ `
17151
+ );
17152
+ }
17153
+ return total;
17154
+ }
17123
17155
  function getReviewChecklist(role, agent, taskSlug) {
17124
17156
  const roleLower = role.toLowerCase();
17125
17157
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -17799,6 +17831,7 @@ var init_skill_learning = __esm({
17799
17831
  // src/lib/tasks.ts
17800
17832
  var tasks_exports = {};
17801
17833
  __export(tasks_exports, {
17834
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
17802
17835
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
17803
17836
  countPendingReviews: () => countPendingReviews,
17804
17837
  createTask: () => createTask,
@@ -17864,6 +17897,21 @@ async function updateTask(input) {
17864
17897
  });
17865
17898
  } catch {
17866
17899
  }
17900
+ try {
17901
+ const client = getClient();
17902
+ const cascaded = await client.execute({
17903
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
17904
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
17905
+ args: [now, taskId]
17906
+ });
17907
+ if (cascaded.rowsAffected > 0) {
17908
+ process.stderr.write(
17909
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
17910
+ `
17911
+ );
17912
+ }
17913
+ } catch {
17914
+ }
17867
17915
  }
17868
17916
  const isTerminal = input.status === "done" || input.status === "needs_review";
17869
17917
  if (isTerminal) {
@@ -19226,7 +19274,7 @@ var init_useOrchestrator = __esm({
19226
19274
  });
19227
19275
 
19228
19276
  // src/tui/views/Sessions.tsx
19229
- import { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
19277
+ import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
19230
19278
  import path31 from "path";
19231
19279
  import { homedir as homedir4 } from "os";
19232
19280
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -19661,7 +19709,7 @@ var init_Sessions = __esm({
19661
19709
  });
19662
19710
 
19663
19711
  // src/tui/views/Tasks.tsx
19664
- import { useState as useState10, useEffect as useEffect12, useMemo as useMemo5 } from "react";
19712
+ import React20, { useState as useState10, useEffect as useEffect12, useMemo as useMemo5 } from "react";
19665
19713
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
19666
19714
  function relativeTime(iso) {
19667
19715
  const now = Date.now();
@@ -20245,7 +20293,7 @@ var init_gateway_client = __esm({
20245
20293
  });
20246
20294
 
20247
20295
  // src/tui/views/Gateway.tsx
20248
- import { useState as useState11, useEffect as useEffect13, useRef as useRef6, useMemo as useMemo6 } from "react";
20296
+ import React21, { useState as useState11, useEffect as useEffect13, useRef as useRef6, useMemo as useMemo6 } from "react";
20249
20297
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
20250
20298
  function connectionStatusColor(status) {
20251
20299
  switch (status) {
@@ -20847,7 +20895,7 @@ var init_agent_status = __esm({
20847
20895
  });
20848
20896
 
20849
20897
  // src/tui/views/Team.tsx
20850
- import { useState as useState12, useEffect as useEffect14 } from "react";
20898
+ import React22, { useState as useState12, useEffect as useEffect14 } from "react";
20851
20899
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
20852
20900
  function TeamView({ onBack }) {
20853
20901
  const demo = useDemo();
@@ -21258,7 +21306,7 @@ var init_wiki_client = __esm({
21258
21306
  });
21259
21307
 
21260
21308
  // src/tui/views/Wiki.tsx
21261
- import { useState as useState13, useEffect as useEffect15 } from "react";
21309
+ import React23, { useState as useState13, useEffect as useEffect15 } from "react";
21262
21310
  import TextInput2 from "ink-text-input";
21263
21311
  import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
21264
21312
  function WikiView({ onBack }) {
@@ -21569,7 +21617,7 @@ var init_Wiki = __esm({
21569
21617
  });
21570
21618
 
21571
21619
  // src/tui/views/Settings.tsx
21572
- import { useState as useState14, useEffect as useEffect16 } from "react";
21620
+ import React24, { useState as useState14, useEffect as useEffect16 } from "react";
21573
21621
  import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
21574
21622
  function SettingsView({ onBack }) {
21575
21623
  const [providers, setProviders] = useState14([]);
@@ -31,6 +31,7 @@ var config_exports = {};
31
31
  __export(config_exports, {
32
32
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
33
33
  CONFIG_PATH: () => CONFIG_PATH,
34
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
34
35
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
35
36
  DB_PATH: () => DB_PATH,
36
37
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -186,7 +187,7 @@ async function loadConfigFrom(configPath) {
186
187
  return { ...DEFAULT_CONFIG };
187
188
  }
188
189
  }
189
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
190
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
190
191
  var init_config = __esm({
191
192
  "src/lib/config.ts"() {
192
193
  "use strict";
@@ -194,6 +195,7 @@ var init_config = __esm({
194
195
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
195
196
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
196
197
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
198
+ COO_AGENT_NAME = "exe";
197
199
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
198
200
  CURRENT_CONFIG_VERSION = 1;
199
201
  DEFAULT_CONFIG = {
@@ -3588,7 +3590,7 @@ async function listTasks(input) {
3588
3590
  }
3589
3591
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3590
3592
  const result = await client.execute({
3591
- 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`,
3593
+ 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`,
3592
3594
  args
3593
3595
  });
3594
3596
  return result.rows.map((r) => ({
@@ -3809,6 +3811,34 @@ async function listPendingReviews(limit) {
3809
3811
  });
3810
3812
  return result.rows;
3811
3813
  }
3814
+ async function cleanupOrphanedReviews() {
3815
+ const client = getClient();
3816
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3817
+ const r1 = await client.execute({
3818
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3819
+ WHERE status = 'needs_review'
3820
+ AND assigned_by = 'system'
3821
+ AND title LIKE 'Review:%'
3822
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3823
+ args: [now]
3824
+ });
3825
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
3826
+ const r2 = await client.execute({
3827
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
3828
+ WHERE status = 'needs_review'
3829
+ AND result IS NOT NULL
3830
+ AND updated_at < ?`,
3831
+ args: [now, staleThreshold]
3832
+ });
3833
+ const total = r1.rowsAffected + r2.rowsAffected;
3834
+ if (total > 0) {
3835
+ process.stderr.write(
3836
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
3837
+ `
3838
+ );
3839
+ }
3840
+ return total;
3841
+ }
3812
3842
  function getReviewChecklist(role, agent, taskSlug) {
3813
3843
  const roleLower = role.toLowerCase();
3814
3844
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -4446,6 +4476,7 @@ var init_skill_learning = __esm({
4446
4476
  // src/lib/tasks.ts
4447
4477
  var tasks_exports = {};
4448
4478
  __export(tasks_exports, {
4479
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
4449
4480
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
4450
4481
  countPendingReviews: () => countPendingReviews,
4451
4482
  createTask: () => createTask,
@@ -4511,6 +4542,21 @@ async function updateTask(input) {
4511
4542
  });
4512
4543
  } catch {
4513
4544
  }
4545
+ try {
4546
+ const client = getClient();
4547
+ const cascaded = await client.execute({
4548
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
4549
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
4550
+ args: [now, taskId]
4551
+ });
4552
+ if (cascaded.rowsAffected > 0) {
4553
+ process.stderr.write(
4554
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
4555
+ `
4556
+ );
4557
+ }
4558
+ } catch {
4559
+ }
4514
4560
  }
4515
4561
  const isTerminal = input.status === "done" || input.status === "needs_review";
4516
4562
  if (isTerminal) {
@@ -4809,7 +4855,8 @@ __export(cloud_sync_exports, {
4809
4855
  mergeRosterFromRemote: () => mergeRosterFromRemote,
4810
4856
  recordRosterDeletion: () => recordRosterDeletion
4811
4857
  });
4812
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync6, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6 } from "fs";
4858
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync6, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync, closeSync } from "fs";
4859
+ import crypto8 from "crypto";
4813
4860
  import path18 from "path";
4814
4861
  import { homedir } from "os";
4815
4862
  function logError(msg) {
@@ -4821,17 +4868,29 @@ function logError(msg) {
4821
4868
  }
4822
4869
  }
4823
4870
  async function withRosterLock(fn) {
4824
- if (existsSync14(ROSTER_LOCK_PATH)) {
4825
- try {
4826
- const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
4827
- if (Date.now() - ts < LOCK_STALE_MS) {
4871
+ try {
4872
+ const fd = openSync(ROSTER_LOCK_PATH, "wx");
4873
+ closeSync(fd);
4874
+ writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
4875
+ } catch (err) {
4876
+ if (err.code === "EEXIST") {
4877
+ try {
4878
+ const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
4879
+ if (Date.now() - ts < LOCK_STALE_MS) {
4880
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
4881
+ }
4882
+ unlinkSync6(ROSTER_LOCK_PATH);
4883
+ const fd = openSync(ROSTER_LOCK_PATH, "wx");
4884
+ closeSync(fd);
4885
+ writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
4886
+ } catch (retryErr) {
4887
+ if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
4828
4888
  throw new Error("Roster merge already in progress \u2014 another sync is running");
4829
4889
  }
4830
- } catch (err) {
4831
- if (err instanceof Error && err.message.includes("already in progress")) throw err;
4890
+ } else {
4891
+ throw err;
4832
4892
  }
4833
4893
  }
4834
- writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
4835
4894
  try {
4836
4895
  return await fn();
4837
4896
  } finally {
@@ -5153,7 +5212,7 @@ function buildRosterBlob(paths) {
5153
5212
  }
5154
5213
  const deletedNames = consumeRosterDeletions();
5155
5214
  const content = JSON.stringify({ roster, identities, config, deletedNames });
5156
- const hash = Buffer.from(content).length;
5215
+ const hash = crypto8.createHash("sha256").update(content).digest("hex").slice(0, 16);
5157
5216
  return { roster, identities, config, deletedNames, version: hash };
5158
5217
  }
5159
5218
  async function cloudPushRoster(config) {
@@ -5682,7 +5741,7 @@ __export(schedules_exports, {
5682
5741
  listSchedules: () => listSchedules,
5683
5742
  parseHumanCron: () => parseHumanCron
5684
5743
  });
5685
- import crypto8 from "crypto";
5744
+ import crypto9 from "crypto";
5686
5745
  import { execSync as execSync10 } from "child_process";
5687
5746
  async function ensureDb() {
5688
5747
  if (!isInitialized()) {
@@ -5751,7 +5810,7 @@ function parseHumanCron(input) {
5751
5810
  async function createSchedule(input) {
5752
5811
  await ensureDb();
5753
5812
  const client = getClient();
5754
- const id = crypto8.randomUUID().slice(0, 8);
5813
+ const id = crypto9.randomUUID().slice(0, 8);
5755
5814
  const now = (/* @__PURE__ */ new Date()).toISOString();
5756
5815
  const prompt = input.prompt ?? input.description;
5757
5816
  await client.execute({
@@ -7036,11 +7095,11 @@ async function boot(options) {
7036
7095
  const thisFile = fileURLToPath3(import.meta.url);
7037
7096
  const backfillPath = path19.resolve(path19.dirname(thisFile), "backfill-vectors.js");
7038
7097
  if (existsSync15(backfillPath)) {
7039
- const { openSync, closeSync } = await import("fs");
7098
+ const { openSync: openSync2, closeSync: closeSync2 } = await import("fs");
7040
7099
  const workerLogPath = path19.join(EXE_AI_DIR, "workers.log");
7041
7100
  let stderrFd = "ignore";
7042
7101
  try {
7043
- stderrFd = openSync(workerLogPath, "a");
7102
+ stderrFd = openSync2(workerLogPath, "a");
7044
7103
  } catch {
7045
7104
  }
7046
7105
  const child = spawn(process.execPath, [backfillPath], {
@@ -7049,7 +7108,7 @@ async function boot(options) {
7049
7108
  });
7050
7109
  child.unref();
7051
7110
  if (typeof stderrFd === "number") try {
7052
- closeSync(stderrFd);
7111
+ closeSync2(stderrFd);
7053
7112
  } catch {
7054
7113
  }
7055
7114
  briefData.embedding.backfillRunning = true;
@@ -710,7 +710,8 @@ function isMainModule(importMetaUrl) {
710
710
  }
711
711
 
712
712
  // src/lib/cloud-sync.ts
713
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
713
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
714
+ import crypto4 from "crypto";
714
715
  import path6 from "path";
715
716
  import { homedir } from "os";
716
717
 
@@ -897,8 +898,17 @@ async function syncMode() {
897
898
  console.log("");
898
899
  console.log("Run /exe-cloud on the new device and paste these when prompted:\n");
899
900
  console.log(" 24-word recovery phrase:");
900
- console.log(` ${mnemonic}
901
+ const showFull = process.argv.includes("--show-full");
902
+ if (showFull) {
903
+ console.log(` ${mnemonic}
901
904
  `);
905
+ } else {
906
+ const words = mnemonic.split(" ");
907
+ const masked = words.length > 4 ? [...words.slice(0, 2), ...words.slice(2, -2).map(() => "****"), ...words.slice(-2)].join(" ") : mnemonic;
908
+ console.log(` ${masked}
909
+ `);
910
+ console.log(" To reveal full phrase: run with --show-full\n");
911
+ }
902
912
  console.log(" \u26A0 Clear your terminal history after copying.\n");
903
913
  if (config.cloud?.apiKey) {
904
914
  const maskedKey = config.cloud.apiKey.slice(0, 10) + "..." + config.cloud.apiKey.slice(-4);
@@ -1542,7 +1542,7 @@ async function auditDuplicates(client, flags) {
1542
1542
  if (flags.agent) filterClause += " AND agent_id = ?";
1543
1543
  if (flags.project) filterClause += " AND project_name = ?";
1544
1544
  const ids = await client.execute({
1545
- sql: `SELECT id FROM memories${filterClause} ORDER BY timestamp DESC`,
1545
+ sql: `SELECT id FROM memories${filterClause} ORDER BY timestamp DESC LIMIT 10000`,
1546
1546
  args: filterArgs
1547
1547
  });
1548
1548
  const allIds = ids.rows.map((r) => r.id);
@@ -1192,6 +1192,7 @@ var config_exports = {};
1192
1192
  __export(config_exports, {
1193
1193
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
1194
1194
  CONFIG_PATH: () => CONFIG_PATH,
1195
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
1195
1196
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
1196
1197
  DB_PATH: () => DB_PATH,
1197
1198
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -1347,7 +1348,7 @@ async function loadConfigFrom(configPath) {
1347
1348
  return { ...DEFAULT_CONFIG };
1348
1349
  }
1349
1350
  }
1350
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1351
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1351
1352
  var init_config = __esm({
1352
1353
  "src/lib/config.ts"() {
1353
1354
  "use strict";
@@ -1355,6 +1356,7 @@ var init_config = __esm({
1355
1356
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
1356
1357
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
1357
1358
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
1359
+ COO_AGENT_NAME = "exe";
1358
1360
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
1359
1361
  CURRENT_CONFIG_VERSION = 1;
1360
1362
  DEFAULT_CONFIG = {
@@ -5849,7 +5851,7 @@ async function listTasks(input) {
5849
5851
  }
5850
5852
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
5851
5853
  const result = await client.execute({
5852
- 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`,
5854
+ 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`,
5853
5855
  args
5854
5856
  });
5855
5857
  return result.rows.map((r) => ({
@@ -6070,6 +6072,34 @@ async function listPendingReviews(limit) {
6070
6072
  });
6071
6073
  return result.rows;
6072
6074
  }
6075
+ async function cleanupOrphanedReviews() {
6076
+ const client = getClient();
6077
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6078
+ const r1 = await client.execute({
6079
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
6080
+ WHERE status = 'needs_review'
6081
+ AND assigned_by = 'system'
6082
+ AND title LIKE 'Review:%'
6083
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
6084
+ args: [now]
6085
+ });
6086
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
6087
+ const r2 = await client.execute({
6088
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
6089
+ WHERE status = 'needs_review'
6090
+ AND result IS NOT NULL
6091
+ AND updated_at < ?`,
6092
+ args: [now, staleThreshold]
6093
+ });
6094
+ const total = r1.rowsAffected + r2.rowsAffected;
6095
+ if (total > 0) {
6096
+ process.stderr.write(
6097
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
6098
+ `
6099
+ );
6100
+ }
6101
+ return total;
6102
+ }
6073
6103
  function getReviewChecklist(role, agent, taskSlug) {
6074
6104
  const roleLower = role.toLowerCase();
6075
6105
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -6749,6 +6779,7 @@ var init_skill_learning = __esm({
6749
6779
  // src/lib/tasks.ts
6750
6780
  var tasks_exports = {};
6751
6781
  __export(tasks_exports, {
6782
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
6752
6783
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
6753
6784
  countPendingReviews: () => countPendingReviews,
6754
6785
  createTask: () => createTask,
@@ -6814,6 +6845,21 @@ async function updateTask(input) {
6814
6845
  });
6815
6846
  } catch {
6816
6847
  }
6848
+ try {
6849
+ const client = getClient();
6850
+ const cascaded = await client.execute({
6851
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
6852
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
6853
+ args: [now, taskId]
6854
+ });
6855
+ if (cascaded.rowsAffected > 0) {
6856
+ process.stderr.write(
6857
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
6858
+ `
6859
+ );
6860
+ }
6861
+ } catch {
6862
+ }
6817
6863
  }
6818
6864
  const isTerminal = input.status === "done" || input.status === "needs_review";
6819
6865
  if (isTerminal) {
@@ -20,6 +20,7 @@ var config_exports = {};
20
20
  __export(config_exports, {
21
21
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
22
22
  CONFIG_PATH: () => CONFIG_PATH,
23
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
23
24
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
24
25
  DB_PATH: () => DB_PATH,
25
26
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -175,7 +176,7 @@ async function loadConfigFrom(configPath) {
175
176
  return { ...DEFAULT_CONFIG };
176
177
  }
177
178
  }
178
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
179
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
179
180
  var init_config = __esm({
180
181
  "src/lib/config.ts"() {
181
182
  "use strict";
@@ -183,6 +184,7 @@ var init_config = __esm({
183
184
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
184
185
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
185
186
  CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
187
+ COO_AGENT_NAME = "exe";
186
188
  LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
187
189
  CURRENT_CONFIG_VERSION = 1;
188
190
  DEFAULT_CONFIG = {
@@ -671,7 +673,8 @@ __export(cloud_sync_exports, {
671
673
  mergeRosterFromRemote: () => mergeRosterFromRemote,
672
674
  recordRosterDeletion: () => recordRosterDeletion
673
675
  });
674
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
676
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
677
+ import crypto3 from "crypto";
675
678
  import path6 from "path";
676
679
  import { homedir } from "os";
677
680
  function logError(msg) {
@@ -683,17 +686,29 @@ function logError(msg) {
683
686
  }
684
687
  }
685
688
  async function withRosterLock(fn) {
686
- if (existsSync6(ROSTER_LOCK_PATH)) {
687
- try {
688
- const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
689
- if (Date.now() - ts < LOCK_STALE_MS) {
689
+ try {
690
+ const fd = openSync(ROSTER_LOCK_PATH, "wx");
691
+ closeSync(fd);
692
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
693
+ } catch (err) {
694
+ if (err.code === "EEXIST") {
695
+ try {
696
+ const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
697
+ if (Date.now() - ts < LOCK_STALE_MS) {
698
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
699
+ }
700
+ unlinkSync(ROSTER_LOCK_PATH);
701
+ const fd = openSync(ROSTER_LOCK_PATH, "wx");
702
+ closeSync(fd);
703
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
704
+ } catch (retryErr) {
705
+ if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
690
706
  throw new Error("Roster merge already in progress \u2014 another sync is running");
691
707
  }
692
- } catch (err) {
693
- if (err instanceof Error && err.message.includes("already in progress")) throw err;
708
+ } else {
709
+ throw err;
694
710
  }
695
711
  }
696
- writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
697
712
  try {
698
713
  return await fn();
699
714
  } finally {
@@ -1015,7 +1030,7 @@ function buildRosterBlob(paths) {
1015
1030
  }
1016
1031
  const deletedNames = consumeRosterDeletions();
1017
1032
  const content = JSON.stringify({ roster, identities, config, deletedNames });
1018
- const hash = Buffer.from(content).length;
1033
+ const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
1019
1034
  return { roster, identities, config, deletedNames, version: hash };
1020
1035
  }
1021
1036
  async function cloudPushRoster(config) {
@@ -1684,8 +1699,17 @@ async function main() {
1684
1699
  }
1685
1700
  const mnemonic = exportMnemonic(key);
1686
1701
  console.log("Your 24-word recovery phrase:\n");
1687
- console.log(` ${mnemonic}
1702
+ const showFull = process.argv.includes("--show-full");
1703
+ if (showFull) {
1704
+ console.log(` ${mnemonic}
1688
1705
  `);
1706
+ } else {
1707
+ const words = mnemonic.split(" ");
1708
+ const masked = words.length > 4 ? [...words.slice(0, 2), ...words.slice(2, -2).map(() => "****"), ...words.slice(-2)].join(" ") : mnemonic;
1709
+ console.log(` ${masked}
1710
+ `);
1711
+ console.log("To reveal full phrase: run with --show-full\n");
1712
+ }
1689
1713
  console.log("Write this down and enter it on your new device with /exe-link import.");
1690
1714
  console.log("Anyone with this phrase can decrypt your memories.");
1691
1715
  console.log("\u26A0 Clear your terminal history after copying.");
@@ -1509,6 +1509,34 @@ async function listPendingReviews(limit) {
1509
1509
  });
1510
1510
  return result.rows;
1511
1511
  }
1512
+ async function cleanupOrphanedReviews() {
1513
+ const client = getClient();
1514
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1515
+ const r1 = await client.execute({
1516
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1517
+ WHERE status = 'needs_review'
1518
+ AND assigned_by = 'system'
1519
+ AND title LIKE 'Review:%'
1520
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
1521
+ args: [now]
1522
+ });
1523
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
1524
+ const r2 = await client.execute({
1525
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
1526
+ WHERE status = 'needs_review'
1527
+ AND result IS NOT NULL
1528
+ AND updated_at < ?`,
1529
+ args: [now, staleThreshold]
1530
+ });
1531
+ const total = r1.rowsAffected + r2.rowsAffected;
1532
+ if (total > 0) {
1533
+ process.stderr.write(
1534
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
1535
+ `
1536
+ );
1537
+ }
1538
+ return total;
1539
+ }
1512
1540
  var init_tasks_review = __esm({
1513
1541
  "src/lib/tasks-review.ts"() {
1514
1542
  "use strict";
@@ -1662,6 +1690,7 @@ init_tasks_review();
1662
1690
  var PENDING_REVIEW_LIMIT = 10;
1663
1691
  async function main() {
1664
1692
  await initStore();
1693
+ await cleanupOrphanedReviews();
1665
1694
  const rows = await listPendingReviews(PENDING_REVIEW_LIMIT);
1666
1695
  if (rows.length > 0) {
1667
1696
  console.log("[REVIEW NOTIFICATIONS]");
@@ -969,6 +969,7 @@ var config_exports = {};
969
969
  __export(config_exports, {
970
970
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
971
971
  CONFIG_PATH: () => CONFIG_PATH,
972
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
972
973
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
973
974
  DB_PATH: () => DB_PATH,
974
975
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -1124,7 +1125,7 @@ async function loadConfigFrom(configPath) {
1124
1125
  return { ...DEFAULT_CONFIG };
1125
1126
  }
1126
1127
  }
1127
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1128
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1128
1129
  var init_config = __esm({
1129
1130
  "src/lib/config.ts"() {
1130
1131
  "use strict";
@@ -1132,6 +1133,7 @@ var init_config = __esm({
1132
1133
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
1133
1134
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
1134
1135
  CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
1136
+ COO_AGENT_NAME = "exe";
1135
1137
  LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
1136
1138
  CURRENT_CONFIG_VERSION = 1;
1137
1139
  DEFAULT_CONFIG = {