@askexenow/exe-os 0.9.53 → 0.9.55

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 (67) hide show
  1. package/dist/bin/backfill-conversations.js +103 -0
  2. package/dist/bin/backfill-responses.js +103 -0
  3. package/dist/bin/backfill-vectors.js +103 -0
  4. package/dist/bin/cleanup-stale-review-tasks.js +103 -0
  5. package/dist/bin/cli.js +121 -10
  6. package/dist/bin/exe-assign.js +103 -0
  7. package/dist/bin/exe-boot.js +96 -10
  8. package/dist/bin/exe-call.js +25 -0
  9. package/dist/bin/exe-cloud.js +31 -10
  10. package/dist/bin/exe-dispatch.js +103 -0
  11. package/dist/bin/exe-doctor.js +152 -3
  12. package/dist/bin/exe-export-behaviors.js +103 -0
  13. package/dist/bin/exe-forget.js +103 -0
  14. package/dist/bin/exe-gateway.js +103 -0
  15. package/dist/bin/exe-heartbeat.js +103 -0
  16. package/dist/bin/exe-kill.js +103 -0
  17. package/dist/bin/exe-launch-agent.js +103 -0
  18. package/dist/bin/exe-link.js +31 -10
  19. package/dist/bin/exe-new-employee.js +25 -0
  20. package/dist/bin/exe-pending-messages.js +103 -0
  21. package/dist/bin/exe-pending-notifications.js +103 -0
  22. package/dist/bin/exe-pending-reviews.js +103 -0
  23. package/dist/bin/exe-rename.js +103 -0
  24. package/dist/bin/exe-review.js +103 -0
  25. package/dist/bin/exe-search.js +103 -0
  26. package/dist/bin/exe-session-cleanup.js +103 -0
  27. package/dist/bin/exe-start-codex.js +103 -0
  28. package/dist/bin/exe-start-opencode.js +103 -0
  29. package/dist/bin/exe-status.js +103 -0
  30. package/dist/bin/exe-team.js +103 -0
  31. package/dist/bin/git-sweep.js +103 -0
  32. package/dist/bin/graph-backfill.js +103 -0
  33. package/dist/bin/graph-export.js +103 -0
  34. package/dist/bin/intercom-check.js +103 -0
  35. package/dist/bin/scan-tasks.js +103 -0
  36. package/dist/bin/setup.js +56 -10
  37. package/dist/bin/shard-migrate.js +103 -0
  38. package/dist/gateway/index.js +103 -0
  39. package/dist/hooks/bug-report-worker.js +103 -0
  40. package/dist/hooks/codex-stop-task-finalizer.js +103 -0
  41. package/dist/hooks/commit-complete.js +103 -0
  42. package/dist/hooks/error-recall.js +103 -0
  43. package/dist/hooks/ingest.js +103 -0
  44. package/dist/hooks/instructions-loaded.js +103 -0
  45. package/dist/hooks/notification.js +103 -0
  46. package/dist/hooks/post-compact.js +103 -0
  47. package/dist/hooks/post-tool-combined.js +103 -0
  48. package/dist/hooks/pre-compact.js +103 -0
  49. package/dist/hooks/pre-tool-use.js +103 -0
  50. package/dist/hooks/prompt-submit.js +103 -0
  51. package/dist/hooks/session-end.js +103 -0
  52. package/dist/hooks/session-start.js +103 -0
  53. package/dist/hooks/stop.js +103 -0
  54. package/dist/hooks/subagent-stop.js +103 -0
  55. package/dist/hooks/summary-worker.js +96 -10
  56. package/dist/index.js +103 -0
  57. package/dist/lib/cloud-sync.js +31 -10
  58. package/dist/lib/employee-templates.js +25 -0
  59. package/dist/lib/exe-daemon.js +165 -14
  60. package/dist/lib/hybrid-search.js +103 -0
  61. package/dist/lib/keychain.js +31 -10
  62. package/dist/lib/schedules.js +103 -0
  63. package/dist/lib/store.js +103 -0
  64. package/dist/mcp/server.js +164 -13
  65. package/dist/runtime/index.js +103 -0
  66. package/dist/tui/App.js +96 -10
  67. package/package.json +1 -1
@@ -155,6 +155,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
155
155
  return null;
156
156
  }
157
157
  }
158
+ async function writeMachineBoundFileFallback(b64) {
159
+ const dir = getKeyDir();
160
+ await mkdir(dir, { recursive: true });
161
+ const keyPath = getKeyPath();
162
+ const machineKey = deriveMachineKey();
163
+ if (machineKey) {
164
+ const encrypted = encryptWithMachineKey(b64, machineKey);
165
+ await writeFile(keyPath, encrypted + "\n", "utf-8");
166
+ await chmod(keyPath, 384);
167
+ return "encrypted";
168
+ }
169
+ await writeFile(keyPath, b64 + "\n", "utf-8");
170
+ await chmod(keyPath, 384);
171
+ return "plaintext";
172
+ }
158
173
  async function getMasterKey() {
159
174
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
160
175
  if (nativeValue) {
@@ -206,6 +221,20 @@ async function getMasterKey() {
206
221
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
207
222
  if (migrated) {
208
223
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
224
+ try {
225
+ await unlink(keyPath);
226
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
227
+ } catch {
228
+ }
229
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
230
+ const fallback = await writeMachineBoundFileFallback(b64Value);
231
+ if (fallback === "encrypted") {
232
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
233
+ } else {
234
+ process.stderr.write(
235
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
236
+ );
237
+ }
209
238
  }
210
239
  return key;
211
240
  } catch (err) {
@@ -229,18 +258,10 @@ async function setMasterKey(key) {
229
258
  } catch {
230
259
  }
231
260
  }
232
- const dir = getKeyDir();
233
- await mkdir(dir, { recursive: true });
234
- const keyPath = getKeyPath();
235
- const machineKey = deriveMachineKey();
236
- if (machineKey) {
237
- const encrypted = encryptWithMachineKey(b64, machineKey);
238
- await writeFile(keyPath, encrypted + "\n", "utf-8");
239
- await chmod(keyPath, 384);
261
+ const fallback = await writeMachineBoundFileFallback(b64);
262
+ if (fallback === "encrypted") {
240
263
  process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
241
264
  } else {
242
- await writeFile(keyPath, b64 + "\n", "utf-8");
243
- await chmod(keyPath, 384);
244
265
  process.stderr.write(
245
266
  "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
246
267
  );
@@ -2588,6 +2588,7 @@ var init_database = __esm({
2588
2588
  // src/lib/shard-manager.ts
2589
2589
  var shard_manager_exports = {};
2590
2590
  __export(shard_manager_exports, {
2591
+ auditShardHealth: () => auditShardHealth,
2591
2592
  disposeShards: () => disposeShards,
2592
2593
  ensureShardSchema: () => ensureShardSchema,
2593
2594
  getOpenShardCount: () => getOpenShardCount,
@@ -2654,6 +2655,70 @@ function listShards() {
2654
2655
  if (!existsSync7(SHARDS_DIR)) return [];
2655
2656
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2656
2657
  }
2658
+ async function auditShardHealth(options = {}) {
2659
+ if (!_encryptionKey) {
2660
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2661
+ }
2662
+ const repair = options.repair === true;
2663
+ const dryRun = options.dryRun === true;
2664
+ const names = listShards();
2665
+ const shards = [];
2666
+ for (const name of names) {
2667
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2668
+ const stat = statSync2(dbPath);
2669
+ const item = {
2670
+ name,
2671
+ path: dbPath,
2672
+ ok: false,
2673
+ unreadable: false,
2674
+ error: null,
2675
+ size: stat.size,
2676
+ mtime: stat.mtime.toISOString(),
2677
+ memoryCount: null
2678
+ };
2679
+ const client = createClient2({
2680
+ url: `file:${dbPath}`,
2681
+ encryptionKey: _encryptionKey
2682
+ });
2683
+ try {
2684
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2685
+ const hasMemories = await client.execute(
2686
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2687
+ );
2688
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2689
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2690
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2691
+ }
2692
+ item.ok = true;
2693
+ } catch (err) {
2694
+ const message = err instanceof Error ? err.message : String(err);
2695
+ item.error = message;
2696
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2697
+ if (item.unreadable && repair && !dryRun) {
2698
+ client.close();
2699
+ _shards.delete(name);
2700
+ _shardLastAccess.delete(name);
2701
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2702
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2703
+ renameSync3(dbPath, archivedPath);
2704
+ item.archivedPath = archivedPath;
2705
+ }
2706
+ } finally {
2707
+ try {
2708
+ client.close();
2709
+ } catch {
2710
+ }
2711
+ }
2712
+ shards.push(item);
2713
+ }
2714
+ return {
2715
+ total: shards.length,
2716
+ ok: shards.filter((s) => s.ok).length,
2717
+ unreadable: shards.filter((s) => s.unreadable).length,
2718
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2719
+ shards
2720
+ };
2721
+ }
2657
2722
  async function ensureShardSchema(client) {
2658
2723
  await client.execute("PRAGMA journal_mode = WAL");
2659
2724
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3298,6 +3363,15 @@ function readMachineId() {
3298
3363
  return "";
3299
3364
  }
3300
3365
  }
3366
+ function encryptWithMachineKey(plaintext, machineKey) {
3367
+ const crypto3 = __require("crypto");
3368
+ const iv = crypto3.randomBytes(12);
3369
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3370
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3371
+ encrypted += cipher.final("base64");
3372
+ const authTag = cipher.getAuthTag().toString("base64");
3373
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3374
+ }
3301
3375
  function decryptWithMachineKey(encrypted, machineKey) {
3302
3376
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3303
3377
  try {
@@ -3316,6 +3390,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3316
3390
  return null;
3317
3391
  }
3318
3392
  }
3393
+ async function writeMachineBoundFileFallback(b64) {
3394
+ const dir = getKeyDir();
3395
+ await mkdir3(dir, { recursive: true });
3396
+ const keyPath = getKeyPath();
3397
+ const machineKey = deriveMachineKey();
3398
+ if (machineKey) {
3399
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3400
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3401
+ await chmod2(keyPath, 384);
3402
+ return "encrypted";
3403
+ }
3404
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3405
+ await chmod2(keyPath, 384);
3406
+ return "plaintext";
3407
+ }
3319
3408
  async function getMasterKey() {
3320
3409
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3321
3410
  if (nativeValue) {
@@ -3367,6 +3456,20 @@ async function getMasterKey() {
3367
3456
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3368
3457
  if (migrated) {
3369
3458
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3459
+ try {
3460
+ await unlink(keyPath);
3461
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3462
+ } catch {
3463
+ }
3464
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3465
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3466
+ if (fallback === "encrypted") {
3467
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3468
+ } else {
3469
+ process.stderr.write(
3470
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3471
+ );
3472
+ }
3370
3473
  }
3371
3474
  return key;
3372
3475
  } catch (err) {
package/dist/lib/store.js CHANGED
@@ -2588,6 +2588,7 @@ var init_database = __esm({
2588
2588
  // src/lib/shard-manager.ts
2589
2589
  var shard_manager_exports = {};
2590
2590
  __export(shard_manager_exports, {
2591
+ auditShardHealth: () => auditShardHealth,
2591
2592
  disposeShards: () => disposeShards,
2592
2593
  ensureShardSchema: () => ensureShardSchema,
2593
2594
  getOpenShardCount: () => getOpenShardCount,
@@ -2654,6 +2655,70 @@ function listShards() {
2654
2655
  if (!existsSync7(SHARDS_DIR)) return [];
2655
2656
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2656
2657
  }
2658
+ async function auditShardHealth(options = {}) {
2659
+ if (!_encryptionKey) {
2660
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2661
+ }
2662
+ const repair = options.repair === true;
2663
+ const dryRun = options.dryRun === true;
2664
+ const names = listShards();
2665
+ const shards = [];
2666
+ for (const name of names) {
2667
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2668
+ const stat = statSync2(dbPath);
2669
+ const item = {
2670
+ name,
2671
+ path: dbPath,
2672
+ ok: false,
2673
+ unreadable: false,
2674
+ error: null,
2675
+ size: stat.size,
2676
+ mtime: stat.mtime.toISOString(),
2677
+ memoryCount: null
2678
+ };
2679
+ const client = createClient2({
2680
+ url: `file:${dbPath}`,
2681
+ encryptionKey: _encryptionKey
2682
+ });
2683
+ try {
2684
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2685
+ const hasMemories = await client.execute(
2686
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2687
+ );
2688
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2689
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2690
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2691
+ }
2692
+ item.ok = true;
2693
+ } catch (err) {
2694
+ const message = err instanceof Error ? err.message : String(err);
2695
+ item.error = message;
2696
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2697
+ if (item.unreadable && repair && !dryRun) {
2698
+ client.close();
2699
+ _shards.delete(name);
2700
+ _shardLastAccess.delete(name);
2701
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2702
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2703
+ renameSync3(dbPath, archivedPath);
2704
+ item.archivedPath = archivedPath;
2705
+ }
2706
+ } finally {
2707
+ try {
2708
+ client.close();
2709
+ } catch {
2710
+ }
2711
+ }
2712
+ shards.push(item);
2713
+ }
2714
+ return {
2715
+ total: shards.length,
2716
+ ok: shards.filter((s) => s.ok).length,
2717
+ unreadable: shards.filter((s) => s.unreadable).length,
2718
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2719
+ shards
2720
+ };
2721
+ }
2657
2722
  async function ensureShardSchema(client) {
2658
2723
  await client.execute("PRAGMA journal_mode = WAL");
2659
2724
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3293,6 +3358,15 @@ function readMachineId() {
3293
3358
  return "";
3294
3359
  }
3295
3360
  }
3361
+ function encryptWithMachineKey(plaintext, machineKey) {
3362
+ const crypto2 = __require("crypto");
3363
+ const iv = crypto2.randomBytes(12);
3364
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3365
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3366
+ encrypted += cipher.final("base64");
3367
+ const authTag = cipher.getAuthTag().toString("base64");
3368
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3369
+ }
3296
3370
  function decryptWithMachineKey(encrypted, machineKey) {
3297
3371
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3298
3372
  try {
@@ -3311,6 +3385,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3311
3385
  return null;
3312
3386
  }
3313
3387
  }
3388
+ async function writeMachineBoundFileFallback(b64) {
3389
+ const dir = getKeyDir();
3390
+ await mkdir3(dir, { recursive: true });
3391
+ const keyPath = getKeyPath();
3392
+ const machineKey = deriveMachineKey();
3393
+ if (machineKey) {
3394
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3395
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3396
+ await chmod2(keyPath, 384);
3397
+ return "encrypted";
3398
+ }
3399
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3400
+ await chmod2(keyPath, 384);
3401
+ return "plaintext";
3402
+ }
3314
3403
  async function getMasterKey() {
3315
3404
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3316
3405
  if (nativeValue) {
@@ -3362,6 +3451,20 @@ async function getMasterKey() {
3362
3451
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3363
3452
  if (migrated) {
3364
3453
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3454
+ try {
3455
+ await unlink(keyPath);
3456
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3457
+ } catch {
3458
+ }
3459
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3460
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3461
+ if (fallback === "encrypted") {
3462
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3463
+ } else {
3464
+ process.stderr.write(
3465
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3466
+ );
3467
+ }
3365
3468
  }
3366
3469
  return key;
3367
3470
  } catch (err) {
@@ -3389,6 +3389,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3389
3389
  return null;
3390
3390
  }
3391
3391
  }
3392
+ async function writeMachineBoundFileFallback(b64) {
3393
+ const dir = getKeyDir();
3394
+ await mkdir3(dir, { recursive: true });
3395
+ const keyPath = getKeyPath();
3396
+ const machineKey = deriveMachineKey();
3397
+ if (machineKey) {
3398
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3399
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3400
+ await chmod2(keyPath, 384);
3401
+ return "encrypted";
3402
+ }
3403
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3404
+ await chmod2(keyPath, 384);
3405
+ return "plaintext";
3406
+ }
3392
3407
  async function getMasterKey() {
3393
3408
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3394
3409
  if (nativeValue) {
@@ -3440,6 +3455,20 @@ async function getMasterKey() {
3440
3455
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3441
3456
  if (migrated) {
3442
3457
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3458
+ try {
3459
+ await unlink(keyPath);
3460
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3461
+ } catch {
3462
+ }
3463
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3464
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3465
+ if (fallback === "encrypted") {
3466
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3467
+ } else {
3468
+ process.stderr.write(
3469
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3470
+ );
3471
+ }
3443
3472
  }
3444
3473
  return key;
3445
3474
  } catch (err) {
@@ -3463,18 +3492,10 @@ async function setMasterKey(key) {
3463
3492
  } catch {
3464
3493
  }
3465
3494
  }
3466
- const dir = getKeyDir();
3467
- await mkdir3(dir, { recursive: true });
3468
- const keyPath = getKeyPath();
3469
- const machineKey = deriveMachineKey();
3470
- if (machineKey) {
3471
- const encrypted = encryptWithMachineKey(b64, machineKey);
3472
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3473
- await chmod2(keyPath, 384);
3495
+ const fallback = await writeMachineBoundFileFallback(b64);
3496
+ if (fallback === "encrypted") {
3474
3497
  process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
3475
3498
  } else {
3476
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3477
- await chmod2(keyPath, 384);
3478
3499
  process.stderr.write(
3479
3500
  "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
3480
3501
  );
@@ -3786,6 +3807,7 @@ var init_memory_write_governor = __esm({
3786
3807
  // src/lib/shard-manager.ts
3787
3808
  var shard_manager_exports = {};
3788
3809
  __export(shard_manager_exports, {
3810
+ auditShardHealth: () => auditShardHealth,
3789
3811
  disposeShards: () => disposeShards,
3790
3812
  ensureShardSchema: () => ensureShardSchema,
3791
3813
  getOpenShardCount: () => getOpenShardCount,
@@ -3852,6 +3874,70 @@ function listShards() {
3852
3874
  if (!existsSync8(SHARDS_DIR)) return [];
3853
3875
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3854
3876
  }
3877
+ async function auditShardHealth(options = {}) {
3878
+ if (!_encryptionKey) {
3879
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3880
+ }
3881
+ const repair = options.repair === true;
3882
+ const dryRun = options.dryRun === true;
3883
+ const names = listShards();
3884
+ const shards = [];
3885
+ for (const name of names) {
3886
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
3887
+ const stat = statSync2(dbPath);
3888
+ const item = {
3889
+ name,
3890
+ path: dbPath,
3891
+ ok: false,
3892
+ unreadable: false,
3893
+ error: null,
3894
+ size: stat.size,
3895
+ mtime: stat.mtime.toISOString(),
3896
+ memoryCount: null
3897
+ };
3898
+ const client = createClient2({
3899
+ url: `file:${dbPath}`,
3900
+ encryptionKey: _encryptionKey
3901
+ });
3902
+ try {
3903
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3904
+ const hasMemories = await client.execute(
3905
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3906
+ );
3907
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3908
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3909
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3910
+ }
3911
+ item.ok = true;
3912
+ } catch (err) {
3913
+ const message = err instanceof Error ? err.message : String(err);
3914
+ item.error = message;
3915
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3916
+ if (item.unreadable && repair && !dryRun) {
3917
+ client.close();
3918
+ _shards.delete(name);
3919
+ _shardLastAccess.delete(name);
3920
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3921
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3922
+ renameSync3(dbPath, archivedPath);
3923
+ item.archivedPath = archivedPath;
3924
+ }
3925
+ } finally {
3926
+ try {
3927
+ client.close();
3928
+ } catch {
3929
+ }
3930
+ }
3931
+ shards.push(item);
3932
+ }
3933
+ return {
3934
+ total: shards.length,
3935
+ ok: shards.filter((s) => s.ok).length,
3936
+ unreadable: shards.filter((s) => s.unreadable).length,
3937
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3938
+ shards
3939
+ };
3940
+ }
3855
3941
  async function ensureShardSchema(client) {
3856
3942
  await client.execute("PRAGMA journal_mode = WAL");
3857
3943
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -18712,15 +18798,31 @@ function auditHookHealth() {
18712
18798
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
18713
18799
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
18714
18800
  }
18801
+ async function auditShards() {
18802
+ try {
18803
+ const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
18804
+ const report = await auditShardHealth2();
18805
+ return {
18806
+ total: report.total,
18807
+ ok: report.ok,
18808
+ unreadable: report.unreadable,
18809
+ archived: report.archived,
18810
+ unreadableNames: report.shards.filter((s) => s.unreadable).map((s) => s.name)
18811
+ };
18812
+ } catch {
18813
+ return { total: 0, ok: 0, unreadable: 0, archived: 0, unreadableNames: [] };
18814
+ }
18815
+ }
18715
18816
  async function runAudit(client, flags) {
18716
18817
  const runConflicts = flags.conflicts || process.env.EXE_AUDIT_CONFLICTS === "1";
18717
- const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects] = await Promise.all([
18818
+ const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, shards] = await Promise.all([
18718
18819
  auditStats(client, flags),
18719
18820
  auditNullVectors(client, flags),
18720
18821
  auditDuplicates(client, flags),
18721
18822
  auditBloated(client, flags),
18722
18823
  auditFts(client),
18723
- auditOrphanedProjects(client)
18824
+ auditOrphanedProjects(client),
18825
+ auditShards()
18724
18826
  ]);
18725
18827
  let conflicts;
18726
18828
  if (runConflicts) {
@@ -18740,7 +18842,7 @@ async function runAudit(client, flags) {
18740
18842
  }
18741
18843
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
18742
18844
  const hookHealth = auditHookHealth();
18743
- return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth };
18845
+ return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
18744
18846
  }
18745
18847
  function indicator(value, warn) {
18746
18848
  if (value === 0) return "\u{1F7E2}";
@@ -18819,6 +18921,15 @@ function formatReport(report, flags) {
18819
18921
  lines.push(` ${p.count}x: ${p.pattern}`);
18820
18922
  }
18821
18923
  }
18924
+ const sh = report.shards;
18925
+ if (sh.total > 0) {
18926
+ if (sh.unreadable === 0) {
18927
+ lines.push(`\u{1F7E2} Shards: ${fmtNum(sh.ok)} / ${fmtNum(sh.total)} readable`);
18928
+ } else {
18929
+ const names = sh.unreadableNames.slice(0, 5).join(", ");
18930
+ lines.push(`\u{1F534} Shards: ${fmtNum(sh.unreadable)} unreadable (${names}${sh.unreadableNames.length > 5 ? ", ..." : ""})`);
18931
+ }
18932
+ }
18822
18933
  lines.push("");
18823
18934
  if (flags.verbose) {
18824
18935
  if (report.duplicates.length > 0) {
@@ -18866,6 +18977,9 @@ function formatReport(report, flags) {
18866
18977
  if (!report.fts.inSync) {
18867
18978
  recs.push("Run --fix to rebuild FTS index");
18868
18979
  }
18980
+ if (report.shards.unreadable > 0) {
18981
+ recs.push(`Run --fix to archive ${fmtNum(report.shards.unreadable)} unreadable shard(s); global DB remains authoritative`);
18982
+ }
18869
18983
  if (report.orphanedProjects.length > 0) {
18870
18984
  const names = report.orphanedProjects.map((o) => `"${o.project_name}"`).join(", ");
18871
18985
  recs.push(`Consider /exe-forget for orphaned project${report.orphanedProjects.length > 1 ? "s" : ""} ${names}`);
@@ -18971,6 +19085,17 @@ async function fixBloated(client, bloated, dryRun) {
18971
19085
  }
18972
19086
  return chunksCreated;
18973
19087
  }
19088
+ async function fixShards(dryRun) {
19089
+ const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
19090
+ const report = await auditShardHealth2({ repair: true, dryRun });
19091
+ return {
19092
+ total: report.total,
19093
+ ok: report.ok,
19094
+ unreadable: report.unreadable,
19095
+ archived: report.archived,
19096
+ unreadableNames: report.shards.filter((s) => s.unreadable).map((s) => s.name)
19097
+ };
19098
+ }
18974
19099
  function splitAtSentences(text3, maxChunkSize) {
18975
19100
  if (text3.length <= maxChunkSize) return [text3];
18976
19101
  const chunks = [];
@@ -19046,6 +19171,11 @@ ${mode} Applying repairs...
19046
19171
  console.log(" Done.");
19047
19172
  }
19048
19173
  }
19174
+ if (report.shards.unreadable > 0) {
19175
+ console.log(`${mode} Archiving ${fmtNum(report.shards.unreadable)} unreadable shard(s)...`);
19176
+ const fixed = await fixShards(flags.dryRun);
19177
+ console.log(` ${flags.dryRun ? "Would archive" : "Archived"} ${fmtNum(flags.dryRun ? fixed.unreadable : fixed.archived)} shard(s).`);
19178
+ }
19049
19179
  console.log(`
19050
19180
  ${mode} Complete.`);
19051
19181
  }
@@ -24963,9 +25093,30 @@ var TOOL_CATEGORIES = {
24963
25093
  registerListGlobalProcedures: "orchestration",
24964
25094
  registerDeactivateGlobalProcedure: "orchestration"
24965
25095
  };
25096
+ var CHIEF_OF_STAFF_READ_TOOLS = /* @__PURE__ */ new Set([
25097
+ "registerRecallMyMemory",
25098
+ "registerAskTeamMemory",
25099
+ "registerGetMemoryById",
25100
+ "registerGetSessionContext",
25101
+ "registerSearchEverything",
25102
+ "registerGetDecision",
25103
+ "registerGetIdentity",
25104
+ "registerListTasks",
25105
+ "registerGetTask",
25106
+ "registerListReminders",
25107
+ "registerQueryConversations",
25108
+ "registerQueryCompanyBrain"
25109
+ ]);
25110
+ function isChiefOfStaffRole(role) {
25111
+ const normalized = (role ?? "").trim().toLowerCase();
25112
+ return normalized === "chief of staff" || normalized === "executive assistant";
25113
+ }
24966
25114
  function isToolAllowed(registerFnName) {
24967
25115
  const role = process.env.AGENT_ROLE;
24968
25116
  if (!role) return true;
25117
+ if (isChiefOfStaffRole(role)) {
25118
+ return CHIEF_OF_STAFF_READ_TOOLS.has(registerFnName);
25119
+ }
24969
25120
  const category = TOOL_CATEGORIES[registerFnName];
24970
25121
  if (!category) return true;
24971
25122
  const allowedRoles = TOOL_GATES[category];