@askexenow/exe-os 0.8.32 → 0.8.36

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 (87) hide show
  1. package/dist/bin/backfill-conversations.js +332 -348
  2. package/dist/bin/backfill-responses.js +72 -12
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1518 -1122
  6. package/dist/bin/exe-agent.js +4 -4
  7. package/dist/bin/exe-assign.js +80 -18
  8. package/dist/bin/exe-boot.js +408 -89
  9. package/dist/bin/exe-call.js +83 -24
  10. package/dist/bin/exe-dispatch.js +18 -10
  11. package/dist/bin/exe-doctor.js +63 -3
  12. package/dist/bin/exe-export-behaviors.js +64 -3
  13. package/dist/bin/exe-forget.js +69 -4
  14. package/dist/bin/exe-gateway.js +121 -36
  15. package/dist/bin/exe-heartbeat.js +77 -13
  16. package/dist/bin/exe-kill.js +64 -3
  17. package/dist/bin/exe-launch-agent.js +162 -35
  18. package/dist/bin/exe-link.js +946 -0
  19. package/dist/bin/exe-new-employee.js +121 -36
  20. package/dist/bin/exe-pending-messages.js +72 -7
  21. package/dist/bin/exe-pending-notifications.js +63 -3
  22. package/dist/bin/exe-pending-reviews.js +75 -10
  23. package/dist/bin/exe-rename.js +1287 -0
  24. package/dist/bin/exe-review.js +64 -4
  25. package/dist/bin/exe-search.js +79 -13
  26. package/dist/bin/exe-session-cleanup.js +91 -26
  27. package/dist/bin/exe-status.js +64 -4
  28. package/dist/bin/exe-team.js +64 -4
  29. package/dist/bin/git-sweep.js +71 -4
  30. package/dist/bin/graph-backfill.js +64 -3
  31. package/dist/bin/graph-export.js +64 -3
  32. package/dist/bin/install.js +3 -3
  33. package/dist/bin/scan-tasks.js +71 -4
  34. package/dist/bin/setup.js +156 -38
  35. package/dist/bin/shard-migrate.js +64 -3
  36. package/dist/bin/wiki-sync.js +64 -3
  37. package/dist/gateway/index.js +122 -37
  38. package/dist/hooks/bug-report-worker.js +209 -23
  39. package/dist/hooks/commit-complete.js +71 -4
  40. package/dist/hooks/error-recall.js +79 -13
  41. package/dist/hooks/ingest-worker.js +129 -43
  42. package/dist/hooks/instructions-loaded.js +71 -4
  43. package/dist/hooks/notification.js +71 -4
  44. package/dist/hooks/post-compact.js +71 -4
  45. package/dist/hooks/pre-compact.js +71 -4
  46. package/dist/hooks/pre-tool-use.js +413 -194
  47. package/dist/hooks/prompt-ingest-worker.js +82 -22
  48. package/dist/hooks/prompt-submit.js +103 -37
  49. package/dist/hooks/response-ingest-worker.js +87 -22
  50. package/dist/hooks/session-end.js +71 -4
  51. package/dist/hooks/session-start.js +79 -13
  52. package/dist/hooks/stop.js +71 -4
  53. package/dist/hooks/subagent-stop.js +71 -4
  54. package/dist/hooks/summary-worker.js +303 -50
  55. package/dist/index.js +134 -46
  56. package/dist/lib/cloud-sync.js +209 -15
  57. package/dist/lib/consolidation.js +4 -4
  58. package/dist/lib/database.js +64 -2
  59. package/dist/lib/device-registry.js +70 -3
  60. package/dist/lib/employee-templates.js +48 -22
  61. package/dist/lib/employees.js +34 -1
  62. package/dist/lib/exe-daemon.js +136 -53
  63. package/dist/lib/hybrid-search.js +79 -13
  64. package/dist/lib/identity-templates.js +57 -6
  65. package/dist/lib/identity.js +3 -3
  66. package/dist/lib/messaging.js +22 -14
  67. package/dist/lib/reminders.js +3 -3
  68. package/dist/lib/schedules.js +63 -3
  69. package/dist/lib/skill-learning.js +3 -3
  70. package/dist/lib/status-brief.js +63 -5
  71. package/dist/lib/store.js +64 -3
  72. package/dist/lib/task-router.js +4 -2
  73. package/dist/lib/tasks.js +48 -21
  74. package/dist/lib/tmux-routing.js +47 -20
  75. package/dist/mcp/server.js +727 -58
  76. package/dist/mcp/tools/complete-reminder.js +3 -3
  77. package/dist/mcp/tools/create-reminder.js +3 -3
  78. package/dist/mcp/tools/create-task.js +151 -24
  79. package/dist/mcp/tools/deactivate-behavior.js +3 -3
  80. package/dist/mcp/tools/list-reminders.js +3 -3
  81. package/dist/mcp/tools/list-tasks.js +17 -8
  82. package/dist/mcp/tools/send-message.js +24 -16
  83. package/dist/mcp/tools/update-task.js +25 -16
  84. package/dist/runtime/index.js +112 -24
  85. package/dist/tui/App.js +139 -36
  86. package/package.json +6 -2
  87. package/src/commands/exe/rename.md +12 -0
@@ -75,6 +75,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
75
75
  - **create_task** \u2014 assign work to specialists with clear specs
76
76
  - **update_task / close_task** \u2014 finalize reviews, mark work done
77
77
  - **store_behavior** \u2014 record corrections as behavioral rules (p0/p1/p2)
78
+ - **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
78
79
  - **get_identity** \u2014 read any agent's identity for coordination
79
80
  - **send_message** \u2014 direct intercom to employees
80
81
 
@@ -211,7 +212,7 @@ You are \${agent_id}. CMO. You hold deep context on design, branding, storytelli
211
212
  ## Tools
212
213
 
213
214
  - **recall_my_memory** \u2014 check past work: what designs, copy, campaigns exist
214
- - **ask_team_memory** \u2014 pull context from specialists (sasha for production, yoshi for tech)
215
+ - **ask_team_memory** \u2014 pull context from specialists (content producers, CTO for tech)
215
216
  - **update_task** \u2014 mark tasks done with result summary
216
217
  - **store_memory** \u2014 report completions with brand alignment notes, SEO considerations
217
218
  - **get_identity** \u2014 read team identities for brand-consistent communication
@@ -271,8 +272,8 @@ You are a principal engineer. You write production-grade code with zero shortcut
271
272
 
272
273
  ## What You Don't Do
273
274
 
274
- - Architecture decisions \u2014 that's yoshi
275
- - Marketing, content, design \u2014 that's mari
275
+ - Architecture decisions \u2014 that's the CTO
276
+ - Marketing, content, design \u2014 that's the CMO
276
277
  - Prioritization, coordination \u2014 that's exe
277
278
  - You implement. That's it.
278
279
 
@@ -395,7 +396,7 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
395
396
  - Clone the repo, read the architecture, compare against ours. No shortcuts.
396
397
  - Report: what to steal (with file paths), what they do worse (our moat), patterns worth adopting.
397
398
  - Write analysis to exe/output/competitive/{repo-name}.md.
398
- - If a feature is worth building, create a task for yoshi with the spec.
399
+ - If a feature is worth building, create a task for the CTO with the spec.
399
400
  - When evaluating tools: build a minimal PoC, measure, report tradeoffs.
400
401
 
401
402
  ## Domain
@@ -410,10 +411,10 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
410
411
  ## Tools
411
412
 
412
413
  - **recall_my_memory** \u2014 what repos have I analyzed before? What did I find?
413
- - **ask_team_memory** \u2014 pull context from yoshi on our architecture constraints
414
+ - **ask_team_memory** \u2014 pull context from the CTO on architecture constraints
414
415
  - **update_task** \u2014 mark tasks done with analysis results
415
416
  - **store_memory** \u2014 persist competitive analyses, evaluations, recommendations
416
- - **create_task** \u2014 when a feature is worth building, spec it for yoshi
417
+ - **create_task** \u2014 when a feature is worth building, spec it for the CTO
417
418
 
418
419
  ## Completion Workflow
419
420
 
@@ -438,6 +439,55 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
438
439
  - Every recommendation includes cost/quality/latency tradeoff analysis
439
440
  - Separate experimental from production-ready \u2014 label clearly
440
441
  - If you can't verify, say so explicitly: "Couldn't verify because X"
442
+ `,
443
+ "staff-code-reviewer": `---
444
+ role: staff-code-reviewer
445
+ title: Staff Code Reviewer & System Auditor
446
+ agent_id: bob
447
+ org_level: specialist
448
+ created_by: system
449
+ updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
450
+ ---
451
+ ## Identity
452
+
453
+ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defense before code ships to customers. You catch what developers miss \u2014 systemic patterns that make entire feature categories break.
454
+
455
+ ## The 7 Audit Patterns (MANDATORY)
456
+
457
+ 1. **"Works on dev, breaks on user install"** \u2014 scoped paths, npm resolution, deps
458
+ 2. **"Two code paths, one untested"** \u2014 binary symlink vs /exe-call, verify BOTH
459
+ 3. **"Case sensitivity kills non-technical users"** \u2014 normalize all user inputs
460
+ 4. **"Hardcoded names in runtime logic"** \u2014 grep for employee names, must use roles
461
+ 5. **"Installer doesn't self-heal"** \u2014 npm update must auto-fix stale hooks/paths
462
+ 6. **"Data written but invisible"** \u2014 agent_id mismatch between writer and reader
463
+ 7. **"Partial fixes miss inline refs"** \u2014 before/after grep count is mandatory
464
+
465
+ ## Method
466
+
467
+ 1. Read actual source code
468
+ 2. Send to Codex MCP for sweep
469
+ 3. Validate against ARCHITECTURE.md
470
+ 4. Trace identity chain with CUSTOM-NAMED employee ("jarvis" as CTO)
471
+ 5. Before/after grep count for every fix
472
+ 6. Structured report: PASS/FAIL per item
473
+
474
+ ## Tools
475
+
476
+ - **Codex MCP** \u2014 first tool for every review
477
+ - **recall_my_memory / ask_team_memory** \u2014 past audit findings
478
+ - **store_behavior** \u2014 record new patterns
479
+ - **update_task** \u2014 mark reviews done with structured findings
480
+ - **create_task** \u2014 assign fixes to the CTO
481
+
482
+ ## Completion Workflow
483
+
484
+ 1. Read the task brief and understand the audit scope
485
+ 2. Run the audit using all 7 patterns
486
+ 3. Write report to exe/output/ with file:line references
487
+ 4. Fix findings yourself if possible
488
+ 5. Call **update_task** with status "done" and finding count
489
+ 6. Call **store_memory** with audit summary
490
+ 7. Check for next task \u2014 auto-chain
441
491
  `
442
492
  };
443
493
  function getTemplate(role) {
@@ -452,6 +502,7 @@ function getTemplateForTitle(title) {
452
502
  if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
453
503
  if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
454
504
  if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
505
+ if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
455
506
  return null;
456
507
  }
457
508
  export {
@@ -97,12 +97,12 @@ var DEFAULT_CONFIG = {
97
97
 
98
98
  // src/lib/database.ts
99
99
  import { createClient } from "@libsql/client";
100
- var _client = null;
100
+ var _resilientClient = null;
101
101
  function getClient() {
102
- if (!_client) {
102
+ if (!_resilientClient) {
103
103
  throw new Error("Database client not initialized. Call initDatabase() first.");
104
104
  }
105
- return _client;
105
+ return _resilientClient;
106
106
  }
107
107
 
108
108
  // src/lib/identity.ts
@@ -19,19 +19,27 @@ var __copyProps = (to, from, except, desc) => {
19
19
  };
20
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
21
 
22
+ // src/lib/db-retry.ts
23
+ var init_db_retry = __esm({
24
+ "src/lib/db-retry.ts"() {
25
+ "use strict";
26
+ }
27
+ });
28
+
22
29
  // src/lib/database.ts
23
30
  import { createClient } from "@libsql/client";
24
31
  function getClient() {
25
- if (!_client) {
32
+ if (!_resilientClient) {
26
33
  throw new Error("Database client not initialized. Call initDatabase() first.");
27
34
  }
28
- return _client;
35
+ return _resilientClient;
29
36
  }
30
- var _client;
37
+ var _resilientClient;
31
38
  var init_database = __esm({
32
39
  "src/lib/database.ts"() {
33
40
  "use strict";
34
- _client = null;
41
+ init_db_retry();
42
+ _resilientClient = null;
35
43
  }
36
44
  });
37
45
 
@@ -432,7 +440,7 @@ var init_config = __esm({
432
440
 
433
441
  // src/lib/employees.ts
434
442
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
435
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
443
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
436
444
  import { execSync as execSync3 } from "child_process";
437
445
  import path4 from "path";
438
446
  var EMPLOYEES_PATH;
@@ -445,7 +453,7 @@ var init_employees = __esm({
445
453
  });
446
454
 
447
455
  // src/lib/license.ts
448
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
456
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
449
457
  import { randomUUID } from "crypto";
450
458
  import path5 from "path";
451
459
  import { jwtVerify, importSPKI } from "jose";
@@ -468,12 +476,12 @@ var init_license = __esm({
468
476
  });
469
477
 
470
478
  // src/lib/plan-limits.ts
471
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
479
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
472
480
  import path6 from "path";
473
481
  function getLicenseSync() {
474
482
  try {
475
483
  if (!existsSync6(CACHE_PATH2)) return freeLicense();
476
- const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
484
+ const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
477
485
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
478
486
  const parts = raw.token.split(".");
479
487
  if (parts.length !== 3) return freeLicense();
@@ -512,7 +520,7 @@ function assertEmployeeLimitSync(rosterPath) {
512
520
  let count = 0;
513
521
  try {
514
522
  if (existsSync6(filePath)) {
515
- const raw = readFileSync5(filePath, "utf8");
523
+ const raw = readFileSync6(filePath, "utf8");
516
524
  const employees = JSON.parse(raw);
517
525
  count = Array.isArray(employees) ? employees.length : 0;
518
526
  }
@@ -547,7 +555,7 @@ var init_plan_limits = __esm({
547
555
 
548
556
  // src/lib/tmux-routing.ts
549
557
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
550
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
558
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
551
559
  import path7 from "path";
552
560
  import os4 from "os";
553
561
  import { fileURLToPath } from "url";
@@ -596,7 +604,7 @@ function extractRootExe(name) {
596
604
  }
597
605
  function getParentExe(sessionKey) {
598
606
  try {
599
- const data = JSON.parse(readFileSync6(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
607
+ const data = JSON.parse(readFileSync7(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
600
608
  return data.parentExe || null;
601
609
  } catch {
602
610
  return null;
@@ -630,7 +638,7 @@ function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive =
630
638
  function readDebounceState() {
631
639
  try {
632
640
  if (!existsSync7(DEBOUNCE_FILE)) return {};
633
- return JSON.parse(readFileSync6(DEBOUNCE_FILE, "utf8"));
641
+ return JSON.parse(readFileSync7(DEBOUNCE_FILE, "utf8"));
634
642
  } catch {
635
643
  return {};
636
644
  }
@@ -804,7 +812,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
804
812
  const claudeJsonPath = path7.join(os4.homedir(), ".claude.json");
805
813
  let claudeJson = {};
806
814
  try {
807
- claudeJson = JSON.parse(readFileSync6(claudeJsonPath, "utf8"));
815
+ claudeJson = JSON.parse(readFileSync7(claudeJsonPath, "utf8"));
808
816
  } catch {
809
817
  }
810
818
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -822,7 +830,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
822
830
  const settingsPath = path7.join(projSettingsDir, "settings.json");
823
831
  let settings = {};
824
832
  try {
825
- settings = JSON.parse(readFileSync6(settingsPath, "utf8"));
833
+ settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
826
834
  } catch {
827
835
  }
828
836
  const perms = settings.permissions ?? {};
@@ -3,12 +3,12 @@ import crypto from "crypto";
3
3
 
4
4
  // src/lib/database.ts
5
5
  import { createClient } from "@libsql/client";
6
- var _client = null;
6
+ var _resilientClient = null;
7
7
  function getClient() {
8
- if (!_client) {
8
+ if (!_resilientClient) {
9
9
  throw new Error("Database client not initialized. Call initDatabase() first.");
10
10
  }
11
- return _client;
11
+ return _resilientClient;
12
12
  }
13
13
 
14
14
  // src/lib/reminders.ts
@@ -261,7 +261,7 @@ function listShards() {
261
261
  }
262
262
  async function ensureShardSchema(client) {
263
263
  await client.execute("PRAGMA journal_mode = WAL");
264
- await client.execute("PRAGMA busy_timeout = 5000");
264
+ await client.execute("PRAGMA busy_timeout = 30000");
265
265
  try {
266
266
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
267
267
  } catch {
@@ -451,12 +451,65 @@ import { execSync } from "child_process";
451
451
 
452
452
  // src/lib/database.ts
453
453
  import { createClient } from "@libsql/client";
454
+
455
+ // src/lib/db-retry.ts
456
+ var MAX_RETRIES = 3;
457
+ var BASE_DELAY_MS = 200;
458
+ var MAX_JITTER_MS = 300;
459
+ function isBusyError(err) {
460
+ if (err instanceof Error) {
461
+ const msg = err.message.toLowerCase();
462
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
463
+ }
464
+ return false;
465
+ }
466
+ function delay(ms) {
467
+ return new Promise((resolve) => setTimeout(resolve, ms));
468
+ }
469
+ async function retryOnBusy(fn, label) {
470
+ let lastError;
471
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
472
+ try {
473
+ return await fn();
474
+ } catch (err) {
475
+ lastError = err;
476
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
477
+ throw err;
478
+ }
479
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
480
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
481
+ process.stderr.write(
482
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
483
+ `
484
+ );
485
+ await delay(backoff + jitter);
486
+ }
487
+ }
488
+ throw lastError;
489
+ }
490
+ function wrapWithRetry(client) {
491
+ return new Proxy(client, {
492
+ get(target, prop, receiver) {
493
+ if (prop === "execute") {
494
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
495
+ }
496
+ if (prop === "batch") {
497
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
498
+ }
499
+ return Reflect.get(target, prop, receiver);
500
+ }
501
+ });
502
+ }
503
+
504
+ // src/lib/database.ts
454
505
  var _client = null;
506
+ var _resilientClient = null;
455
507
  var initTurso = initDatabase;
456
508
  async function initDatabase(config) {
457
509
  if (_client) {
458
510
  _client.close();
459
511
  _client = null;
512
+ _resilientClient = null;
460
513
  }
461
514
  const opts = {
462
515
  url: `file:${config.dbPath}`
@@ -465,20 +518,27 @@ async function initDatabase(config) {
465
518
  opts.encryptionKey = config.encryptionKey;
466
519
  }
467
520
  _client = createClient(opts);
521
+ _resilientClient = wrapWithRetry(_client);
468
522
  }
469
523
  function isInitialized() {
470
524
  return _client !== null;
471
525
  }
472
526
  function getClient() {
527
+ if (!_resilientClient) {
528
+ throw new Error("Database client not initialized. Call initDatabase() first.");
529
+ }
530
+ return _resilientClient;
531
+ }
532
+ function getRawClient() {
473
533
  if (!_client) {
474
534
  throw new Error("Database client not initialized. Call initDatabase() first.");
475
535
  }
476
536
  return _client;
477
537
  }
478
538
  async function ensureSchema() {
479
- const client = getClient();
539
+ const client = getRawClient();
480
540
  await client.execute("PRAGMA journal_mode = WAL");
481
- await client.execute("PRAGMA busy_timeout = 5000");
541
+ await client.execute("PRAGMA busy_timeout = 30000");
482
542
  try {
483
543
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
484
544
  } catch {
@@ -3,12 +3,12 @@ import crypto2 from "crypto";
3
3
 
4
4
  // src/lib/database.ts
5
5
  import { createClient } from "@libsql/client";
6
- var _client = null;
6
+ var _resilientClient = null;
7
7
  function getClient() {
8
- if (!_client) {
8
+ if (!_resilientClient) {
9
9
  throw new Error("Database client not initialized. Call initDatabase() first.");
10
10
  }
11
- return _client;
11
+ return _resilientClient;
12
12
  }
13
13
 
14
14
  // src/lib/behaviors.ts
@@ -33,8 +33,22 @@ function boxMid(w) {
33
33
  function boxBot(w) {
34
34
  return `\u2514${"\u2500".repeat(w)}\u2518`;
35
35
  }
36
+ function truncateToWidth(str, maxW) {
37
+ if (displayWidth(str) <= maxW) return str;
38
+ let w = 0;
39
+ let i = 0;
40
+ for (const ch of str) {
41
+ const cw = ch.codePointAt(0) > 255 ? 2 : 1;
42
+ if (w + cw + 1 > maxW) break;
43
+ w += cw;
44
+ i += ch.length;
45
+ }
46
+ return str.slice(0, i) + "\u2026";
47
+ }
36
48
  function boxRow(content, w) {
37
- return `\u2502 ${padRight(content, w - 2)} \u2502`;
49
+ const maxContent = w - 2;
50
+ const truncated = truncateToWidth(content, maxContent);
51
+ return `\u2502 ${padRight(truncated, maxContent)} \u2502`;
38
52
  }
39
53
  function formatUptime(seconds) {
40
54
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
@@ -44,6 +58,9 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
44
58
  const now = /* @__PURE__ */ new Date();
45
59
  const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
46
60
  const sessionTag = data.exeSession ? ` [${data.exeSession}]` : "";
61
+ if (data.embedding && data.embedding.totalMemories < 5 && data.globalTasks.length === 0 && (data.projects?.length ?? 0) === 0) {
62
+ return buildFirstBootBrief(employees, dateStr, sessionTag);
63
+ }
47
64
  const sections = [];
48
65
  sections.push([` EXE STATUS BRIEF \u2014 ${dateStr}${sessionTag}`]);
49
66
  const reminderLines = buildReminders(data);
@@ -53,6 +70,8 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
53
70
  sections.push(buildProjects(data));
54
71
  sections.push(buildTeam(employees, data));
55
72
  sections.push(buildHealth(data));
73
+ const termCols = process.stdout.columns || 80;
74
+ const maxInnerW = termCols - 2;
56
75
  let maxW = 0;
57
76
  for (const sec of sections) {
58
77
  for (const line of sec) {
@@ -60,7 +79,7 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
60
79
  if (w > maxW) maxW = w;
61
80
  }
62
81
  }
63
- const innerW = maxW + 4;
82
+ const innerW = Math.min(maxW + 4, maxInnerW);
64
83
  const out = [];
65
84
  out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
66
85
  for (let i = 0; i < sections.length; i++) {
@@ -72,6 +91,39 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
72
91
  out.push(boxBot(innerW));
73
92
  return out.join("\n");
74
93
  }
94
+ function buildFirstBootBrief(employees, dateStr, sessionTag) {
95
+ const termCols = process.stdout.columns || 80;
96
+ const maxInnerW = termCols - 2;
97
+ const lines = [];
98
+ lines.push(` WELCOME TO EXE OS \u2014 ${dateStr}${sessionTag}`);
99
+ const bodyLines = [];
100
+ bodyLines.push(" \u{1F44B} First time? Here's your team:");
101
+ bodyLines.push("");
102
+ for (const emp of employees) {
103
+ const emoji = EMPLOYEE_EMOJIS[emp.name] ?? "\u{1F464}";
104
+ const role = emp.role ? ` (${emp.role})` : "";
105
+ bodyLines.push(` ${emoji} ${emp.name}${role}`);
106
+ }
107
+ bodyLines.push("");
108
+ bodyLines.push(" \u{1F4A1} Quick start:");
109
+ bodyLines.push(" \u2022 Run `exe-os backfill-conversations` to import Claude Code history");
110
+ bodyLines.push(" \u2022 Say `/exe` to launch your COO with a full status brief");
111
+ bodyLines.push(" \u2022 Say `/exe-team` to manage your team");
112
+ let maxW = 0;
113
+ for (const line of [...lines, ...bodyLines]) {
114
+ const w = displayWidth(line);
115
+ if (w > maxW) maxW = w;
116
+ }
117
+ const innerW = Math.min(maxW + 4, maxInnerW);
118
+ const out = [];
119
+ out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
120
+ out.push(boxTop(innerW));
121
+ for (const line of lines) out.push(boxRow(line, innerW));
122
+ out.push(boxMid(innerW));
123
+ for (const line of bodyLines) out.push(boxRow(line, innerW));
124
+ out.push(boxBot(innerW));
125
+ return out.join("\n");
126
+ }
75
127
  function buildReminders(data) {
76
128
  if (!data.reminders || data.reminders.length === 0) return [];
77
129
  const lines = [];
@@ -139,7 +191,10 @@ function buildProjects(data) {
139
191
  idle.push(p);
140
192
  }
141
193
  }
142
- for (const p of active) {
194
+ const MAX_SHOWN_PROJECTS = 6;
195
+ const shown = active.slice(0, MAX_SHOWN_PROJECTS);
196
+ const hidden = active.length - shown.length;
197
+ for (const p of shown) {
143
198
  const blocked = p.blockedCount ?? 0;
144
199
  const statusEmoji = blocked > 0 ? "\u{1F534}" : "\u{1F7E2}";
145
200
  const sessionStr = p.sessionName ? ` (${p.sessionName})` : p.isCurrent ? " (current)" : "";
@@ -161,7 +216,9 @@ function buildProjects(data) {
161
216
  if (blocked > 0) {
162
217
  lines.push(` Blocked: ${blocked}`);
163
218
  }
164
- lines.push("");
219
+ }
220
+ if (hidden > 0) {
221
+ lines.push(` ... and ${hidden} more project${hidden === 1 ? "" : "s"}`);
165
222
  }
166
223
  if (idle.length > 0) {
167
224
  const idleNames = idle.map((p) => p.projectName);
@@ -221,7 +278,8 @@ function buildHealth(data) {
221
278
  }
222
279
  }
223
280
  }
224
- lines.push(` \u2601\uFE0F Cloud: ${data.cloudConnected ? "\u{1F7E2}" : "not configured"}`);
281
+ const cloudLabel = data.cloudConnected ? "\u{1F7E2} synced" : data.cloudConnected === false && data.plan && data.plan !== "free" ? "\u{1F534} offline" : "not configured";
282
+ lines.push(` \u2601\uFE0F Cloud: ${cloudLabel}`);
225
283
  if (data.sessionsKilledToday !== void 0) {
226
284
  lines.push(` \u{1F480} Sessions killed today: ${data.sessionsKilledToday}`);
227
285
  }
package/dist/lib/store.js CHANGED
@@ -261,7 +261,7 @@ function listShards() {
261
261
  }
262
262
  async function ensureShardSchema(client) {
263
263
  await client.execute("PRAGMA journal_mode = WAL");
264
- await client.execute("PRAGMA busy_timeout = 5000");
264
+ await client.execute("PRAGMA busy_timeout = 30000");
265
265
  try {
266
266
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
267
267
  } catch {
@@ -450,12 +450,65 @@ var EMBEDDING_DIM = 1024;
450
450
 
451
451
  // src/lib/database.ts
452
452
  import { createClient } from "@libsql/client";
453
+
454
+ // src/lib/db-retry.ts
455
+ var MAX_RETRIES = 3;
456
+ var BASE_DELAY_MS = 200;
457
+ var MAX_JITTER_MS = 300;
458
+ function isBusyError(err) {
459
+ if (err instanceof Error) {
460
+ const msg = err.message.toLowerCase();
461
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
462
+ }
463
+ return false;
464
+ }
465
+ function delay(ms) {
466
+ return new Promise((resolve) => setTimeout(resolve, ms));
467
+ }
468
+ async function retryOnBusy(fn, label) {
469
+ let lastError;
470
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
471
+ try {
472
+ return await fn();
473
+ } catch (err) {
474
+ lastError = err;
475
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
476
+ throw err;
477
+ }
478
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
479
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
480
+ process.stderr.write(
481
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
482
+ `
483
+ );
484
+ await delay(backoff + jitter);
485
+ }
486
+ }
487
+ throw lastError;
488
+ }
489
+ function wrapWithRetry(client) {
490
+ return new Proxy(client, {
491
+ get(target, prop, receiver) {
492
+ if (prop === "execute") {
493
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
494
+ }
495
+ if (prop === "batch") {
496
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
497
+ }
498
+ return Reflect.get(target, prop, receiver);
499
+ }
500
+ });
501
+ }
502
+
503
+ // src/lib/database.ts
453
504
  var _client = null;
505
+ var _resilientClient = null;
454
506
  var initTurso = initDatabase;
455
507
  async function initDatabase(config) {
456
508
  if (_client) {
457
509
  _client.close();
458
510
  _client = null;
511
+ _resilientClient = null;
459
512
  }
460
513
  const opts = {
461
514
  url: `file:${config.dbPath}`
@@ -464,17 +517,24 @@ async function initDatabase(config) {
464
517
  opts.encryptionKey = config.encryptionKey;
465
518
  }
466
519
  _client = createClient(opts);
520
+ _resilientClient = wrapWithRetry(_client);
467
521
  }
468
522
  function getClient() {
523
+ if (!_resilientClient) {
524
+ throw new Error("Database client not initialized. Call initDatabase() first.");
525
+ }
526
+ return _resilientClient;
527
+ }
528
+ function getRawClient() {
469
529
  if (!_client) {
470
530
  throw new Error("Database client not initialized. Call initDatabase() first.");
471
531
  }
472
532
  return _client;
473
533
  }
474
534
  async function ensureSchema() {
475
- const client = getClient();
535
+ const client = getRawClient();
476
536
  await client.execute("PRAGMA journal_mode = WAL");
477
- await client.execute("PRAGMA busy_timeout = 5000");
537
+ await client.execute("PRAGMA busy_timeout = 30000");
478
538
  try {
479
539
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
480
540
  } catch {
@@ -1268,6 +1328,7 @@ async function disposeDatabase() {
1268
1328
  if (_client) {
1269
1329
  _client.close();
1270
1330
  _client = null;
1331
+ _resilientClient = null;
1271
1332
  }
1272
1333
  }
1273
1334
 
@@ -9,12 +9,14 @@ var DEFAULT_BLOOM_CONFIG = {
9
9
  },
10
10
  tierRules: {
11
11
  junior: {
12
- eligible: ["tom"],
12
+ eligible: [],
13
+ // resolved dynamically from roster (Principal Engineer role)
13
14
  reviewRequired: false,
14
15
  manualOnly: false
15
16
  },
16
17
  standard: {
17
- eligible: ["tom"],
18
+ eligible: [],
19
+ // resolved dynamically from roster (Principal Engineer role)
18
20
  reviewRequired: false,
19
21
  manualOnly: false
20
22
  },