@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.
- package/dist/bin/backfill-conversations.js +332 -348
- package/dist/bin/backfill-responses.js +72 -12
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1518 -1122
- package/dist/bin/exe-agent.js +4 -4
- package/dist/bin/exe-assign.js +80 -18
- package/dist/bin/exe-boot.js +408 -89
- package/dist/bin/exe-call.js +83 -24
- package/dist/bin/exe-dispatch.js +18 -10
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +64 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +121 -36
- package/dist/bin/exe-heartbeat.js +77 -13
- package/dist/bin/exe-kill.js +64 -3
- package/dist/bin/exe-launch-agent.js +162 -35
- package/dist/bin/exe-link.js +946 -0
- package/dist/bin/exe-new-employee.js +121 -36
- package/dist/bin/exe-pending-messages.js +72 -7
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +75 -10
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +64 -4
- package/dist/bin/exe-search.js +79 -13
- package/dist/bin/exe-session-cleanup.js +91 -26
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +71 -4
- package/dist/bin/graph-backfill.js +64 -3
- package/dist/bin/graph-export.js +64 -3
- package/dist/bin/install.js +3 -3
- package/dist/bin/scan-tasks.js +71 -4
- package/dist/bin/setup.js +156 -38
- package/dist/bin/shard-migrate.js +64 -3
- package/dist/bin/wiki-sync.js +64 -3
- package/dist/gateway/index.js +122 -37
- package/dist/hooks/bug-report-worker.js +209 -23
- package/dist/hooks/commit-complete.js +71 -4
- package/dist/hooks/error-recall.js +79 -13
- package/dist/hooks/ingest-worker.js +129 -43
- package/dist/hooks/instructions-loaded.js +71 -4
- package/dist/hooks/notification.js +71 -4
- package/dist/hooks/post-compact.js +71 -4
- package/dist/hooks/pre-compact.js +71 -4
- package/dist/hooks/pre-tool-use.js +413 -194
- package/dist/hooks/prompt-ingest-worker.js +82 -22
- package/dist/hooks/prompt-submit.js +103 -37
- package/dist/hooks/response-ingest-worker.js +87 -22
- package/dist/hooks/session-end.js +71 -4
- package/dist/hooks/session-start.js +79 -13
- package/dist/hooks/stop.js +71 -4
- package/dist/hooks/subagent-stop.js +71 -4
- package/dist/hooks/summary-worker.js +303 -50
- package/dist/index.js +134 -46
- package/dist/lib/cloud-sync.js +209 -15
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +48 -22
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +136 -53
- package/dist/lib/hybrid-search.js +79 -13
- package/dist/lib/identity-templates.js +57 -6
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +22 -14
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +64 -3
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +48 -21
- package/dist/lib/tmux-routing.js +47 -20
- package/dist/mcp/server.js +727 -58
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +151 -24
- package/dist/mcp/tools/deactivate-behavior.js +3 -3
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +17 -8
- package/dist/mcp/tools/send-message.js +24 -16
- package/dist/mcp/tools/update-task.js +25 -16
- package/dist/runtime/index.js +112 -24
- package/dist/tui/App.js +139 -36
- package/package.json +6 -2
- 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 (
|
|
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
|
|
275
|
-
- Marketing, content, design \u2014 that's
|
|
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
|
|
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
|
|
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
|
|
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 {
|
package/dist/lib/identity.js
CHANGED
|
@@ -97,12 +97,12 @@ var DEFAULT_CONFIG = {
|
|
|
97
97
|
|
|
98
98
|
// src/lib/database.ts
|
|
99
99
|
import { createClient } from "@libsql/client";
|
|
100
|
-
var
|
|
100
|
+
var _resilientClient = null;
|
|
101
101
|
function getClient() {
|
|
102
|
-
if (!
|
|
102
|
+
if (!_resilientClient) {
|
|
103
103
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
104
104
|
}
|
|
105
|
-
return
|
|
105
|
+
return _resilientClient;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// src/lib/identity.ts
|
package/dist/lib/messaging.js
CHANGED
|
@@ -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 (!
|
|
32
|
+
if (!_resilientClient) {
|
|
26
33
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
27
34
|
}
|
|
28
|
-
return
|
|
35
|
+
return _resilientClient;
|
|
29
36
|
}
|
|
30
|
-
var
|
|
37
|
+
var _resilientClient;
|
|
31
38
|
var init_database = __esm({
|
|
32
39
|
"src/lib/database.ts"() {
|
|
33
40
|
"use strict";
|
|
34
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
833
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
|
|
826
834
|
} catch {
|
|
827
835
|
}
|
|
828
836
|
const perms = settings.permissions ?? {};
|
package/dist/lib/reminders.js
CHANGED
|
@@ -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
|
|
6
|
+
var _resilientClient = null;
|
|
7
7
|
function getClient() {
|
|
8
|
-
if (!
|
|
8
|
+
if (!_resilientClient) {
|
|
9
9
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
10
10
|
}
|
|
11
|
-
return
|
|
11
|
+
return _resilientClient;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// src/lib/reminders.ts
|
package/dist/lib/schedules.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 =
|
|
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 =
|
|
539
|
+
const client = getRawClient();
|
|
480
540
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
481
|
-
await client.execute("PRAGMA busy_timeout =
|
|
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
|
|
6
|
+
var _resilientClient = null;
|
|
7
7
|
function getClient() {
|
|
8
|
-
if (!
|
|
8
|
+
if (!_resilientClient) {
|
|
9
9
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
10
10
|
}
|
|
11
|
-
return
|
|
11
|
+
return _resilientClient;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// src/lib/behaviors.ts
|
package/dist/lib/status-brief.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
535
|
+
const client = getRawClient();
|
|
476
536
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
477
|
-
await client.execute("PRAGMA busy_timeout =
|
|
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
|
|
package/dist/lib/task-router.js
CHANGED
|
@@ -9,12 +9,14 @@ var DEFAULT_BLOOM_CONFIG = {
|
|
|
9
9
|
},
|
|
10
10
|
tierRules: {
|
|
11
11
|
junior: {
|
|
12
|
-
eligible: [
|
|
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: [
|
|
18
|
+
eligible: [],
|
|
19
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
18
20
|
reviewRequired: false,
|
|
19
21
|
manualOnly: false
|
|
20
22
|
},
|