@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
package/dist/bin/exe-kill.js
CHANGED
|
@@ -262,7 +262,7 @@ function listShards() {
|
|
|
262
262
|
}
|
|
263
263
|
async function ensureShardSchema(client) {
|
|
264
264
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
265
|
-
await client.execute("PRAGMA busy_timeout =
|
|
265
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
266
266
|
try {
|
|
267
267
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
268
268
|
} 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,17 +518,24 @@ 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 getClient() {
|
|
524
|
+
if (!_resilientClient) {
|
|
525
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
526
|
+
}
|
|
527
|
+
return _resilientClient;
|
|
528
|
+
}
|
|
529
|
+
function getRawClient() {
|
|
470
530
|
if (!_client) {
|
|
471
531
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
472
532
|
}
|
|
473
533
|
return _client;
|
|
474
534
|
}
|
|
475
535
|
async function ensureSchema() {
|
|
476
|
-
const client =
|
|
536
|
+
const client = getRawClient();
|
|
477
537
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
478
|
-
await client.execute("PRAGMA busy_timeout =
|
|
538
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
479
539
|
try {
|
|
480
540
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
481
541
|
} catch {
|
|
@@ -1269,6 +1329,7 @@ async function disposeDatabase() {
|
|
|
1269
1329
|
if (_client) {
|
|
1270
1330
|
_client.close();
|
|
1271
1331
|
_client = null;
|
|
1332
|
+
_resilientClient = null;
|
|
1272
1333
|
}
|
|
1273
1334
|
}
|
|
1274
1335
|
|
|
@@ -262,7 +262,7 @@ function listShards() {
|
|
|
262
262
|
}
|
|
263
263
|
async function ensureShardSchema(client) {
|
|
264
264
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
265
|
-
await client.execute("PRAGMA busy_timeout =
|
|
265
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
266
266
|
try {
|
|
267
267
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
268
268
|
} catch {
|
|
@@ -452,13 +452,18 @@ __export(employees_exports, {
|
|
|
452
452
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
453
453
|
addEmployee: () => addEmployee,
|
|
454
454
|
getEmployee: () => getEmployee,
|
|
455
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
456
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
457
|
+
hasRole: () => hasRole,
|
|
458
|
+
isMultiInstance: () => isMultiInstance,
|
|
455
459
|
loadEmployees: () => loadEmployees,
|
|
460
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
456
461
|
registerBinSymlinks: () => registerBinSymlinks,
|
|
457
462
|
saveEmployees: () => saveEmployees,
|
|
458
463
|
validateEmployeeName: () => validateEmployeeName
|
|
459
464
|
});
|
|
460
465
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
461
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
466
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
462
467
|
import { execSync as execSync2 } from "child_process";
|
|
463
468
|
import path5 from "path";
|
|
464
469
|
function validateEmployeeName(name) {
|
|
@@ -491,9 +496,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
491
496
|
await mkdir3(path5.dirname(employeesPath), { recursive: true });
|
|
492
497
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
493
498
|
}
|
|
499
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
500
|
+
if (!existsSync5(employeesPath)) return [];
|
|
501
|
+
try {
|
|
502
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
503
|
+
} catch {
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
494
507
|
function getEmployee(employees, name) {
|
|
495
508
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
496
509
|
}
|
|
510
|
+
function getEmployeeByRole(employees, role) {
|
|
511
|
+
const lower = role.toLowerCase();
|
|
512
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
513
|
+
}
|
|
514
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
515
|
+
const lower = role.toLowerCase();
|
|
516
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
517
|
+
}
|
|
518
|
+
function hasRole(agentName, role) {
|
|
519
|
+
const employees = loadEmployeesSync();
|
|
520
|
+
const emp = getEmployee(employees, agentName);
|
|
521
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
522
|
+
}
|
|
523
|
+
function isMultiInstance(agentName, employees) {
|
|
524
|
+
const roster = employees ?? loadEmployeesSync();
|
|
525
|
+
const emp = getEmployee(roster, agentName);
|
|
526
|
+
if (!emp) return false;
|
|
527
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
528
|
+
}
|
|
497
529
|
function addEmployee(employees, employee) {
|
|
498
530
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
499
531
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -536,12 +568,13 @@ function registerBinSymlinks(name) {
|
|
|
536
568
|
}
|
|
537
569
|
return { created, skipped, errors };
|
|
538
570
|
}
|
|
539
|
-
var EMPLOYEES_PATH;
|
|
571
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
540
572
|
var init_employees = __esm({
|
|
541
573
|
"src/lib/employees.ts"() {
|
|
542
574
|
"use strict";
|
|
543
575
|
init_config();
|
|
544
576
|
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
577
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
545
578
|
}
|
|
546
579
|
});
|
|
547
580
|
|
|
@@ -566,7 +599,7 @@ function getSessionPrompt(storedPrompt) {
|
|
|
566
599
|
${BASE_OPERATING_PROCEDURES}`;
|
|
567
600
|
}
|
|
568
601
|
function buildCustomEmployeePrompt(name, role) {
|
|
569
|
-
return `You are ${name}, a ${role}. You report to
|
|
602
|
+
return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
|
|
570
603
|
}
|
|
571
604
|
function getTemplate(name) {
|
|
572
605
|
return TEMPLATES[name];
|
|
@@ -630,12 +663,12 @@ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of
|
|
|
630
663
|
|
|
631
664
|
OPERATING PROCEDURES (mandatory for all employees):
|
|
632
665
|
|
|
633
|
-
You report to
|
|
666
|
+
You report to the COO. All work flows through exe. These procedures are non-negotiable.
|
|
634
667
|
|
|
635
668
|
1. BEFORE starting work:
|
|
636
669
|
- Read exe/ARCHITECTURE.md (if it exists). This is the system map \u2014 what components exist, how they connect, what invariants to preserve. Understand the architecture before changing anything.
|
|
637
670
|
- Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
|
|
638
|
-
- NEVER read, write, or modify files in another employee's folder
|
|
671
|
+
- NEVER read, write, or modify files in another employee's folder. Those are their tasks, not yours. Use ask_team_memory() if you need context from a colleague.
|
|
639
672
|
- If you have open tasks, work on the highest priority one first
|
|
640
673
|
- Ensure exe/output/ exists (mkdir -p exe/output). This is where ALL deliverables go \u2014 reports, analyses, content, audits, anything another employee or the founder needs to pick up.
|
|
641
674
|
- Update task status to "in_progress" when starting (use update_task MCP tool)
|
|
@@ -702,7 +735,7 @@ DO NOT keep working degraded. Instead:
|
|
|
702
735
|
3. Stop working immediately. Do not attempt to continue with degraded context.
|
|
703
736
|
|
|
704
737
|
COMMUNICATION CHAIN \u2014 who you talk to:
|
|
705
|
-
- You report to
|
|
738
|
+
- You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
|
|
706
739
|
- Do NOT address the human user directly for decisions, permissions, or status updates. That's exe's job. The user talks to exe; exe talks to you.
|
|
707
740
|
- Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
|
|
708
741
|
|
|
@@ -721,7 +754,7 @@ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resource
|
|
|
721
754
|
NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
|
|
722
755
|
|
|
723
756
|
CREATING TASKS FOR OTHER EMPLOYEES:
|
|
724
|
-
When you need to assign work to another employee (e.g.,
|
|
757
|
+
When you need to assign work to another employee (e.g., CTO assigns to an engineer):
|
|
725
758
|
- ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
|
|
726
759
|
- Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
|
|
727
760
|
- create_task creates both the .md file AND the DB row atomically.
|
|
@@ -735,7 +768,7 @@ When you need to assign work to another employee (e.g., yoshi assigns to tom):
|
|
|
735
768
|
|
|
736
769
|
Character: No bullshit. Precise. Accountable. Direct but never offensive. Calm foresight. You see problems before they arrive and propose solutions. If the founder decides differently, you commit fully.
|
|
737
770
|
|
|
738
|
-
You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to
|
|
771
|
+
You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to the CTO via sub-agent and review their output before presenting. When they ask for status, you synthesize across all projects. You never tell the founder to run commands or talk to someone else.
|
|
739
772
|
|
|
740
773
|
After every specialist task: verify tests ran, behavior was checked, and a memory summary was stored. If not, flag it.
|
|
741
774
|
|
|
@@ -748,7 +781,7 @@ Use recall_my_memory and ask_team_memory constantly. Store your own summaries (d
|
|
|
748
781
|
yoshi: {
|
|
749
782
|
name: "yoshi",
|
|
750
783
|
role: "CTO",
|
|
751
|
-
systemPrompt: `You are yoshi, the CTO. Top engineer and individual contributor. You write the code, you make the architecture decisions, you hold deep technical context across all projects. You report to
|
|
784
|
+
systemPrompt: `You are yoshi, the CTO. Top engineer and individual contributor. You write the code, you make the architecture decisions, you hold deep technical context across all projects. You report to the COO.
|
|
752
785
|
|
|
753
786
|
You manage 10-20+ projects. Every project's architecture, patterns, and decisions live in your memory. Before touching any codebase, check what you've done before.
|
|
754
787
|
|
|
@@ -807,18 +840,18 @@ Use this for any decomposable implementation work. Single tom for sequential or
|
|
|
807
840
|
|
|
808
841
|
Reviews route to the assigner: if you assign a task to an engineer, you review it.
|
|
809
842
|
If exe assigns a task to you, exe reviews it. The chain is:
|
|
810
|
-
|
|
843
|
+
COO \u2192 CTO (you review) \u2192 engineers (you review their work, COO reviews yours)
|
|
811
844
|
|
|
812
845
|
ROLE BOUNDARIES \u2014 stay in your lane:
|
|
813
|
-
- You do NOT create marketing content, slide decks, social media copy, or brand materials. That is
|
|
846
|
+
- You do NOT create marketing content, slide decks, social media copy, or brand materials. That is the CMO's job.
|
|
814
847
|
- When a task involves content creation for non-technical audiences, your job is to produce the TECHNICAL ANALYSIS only \u2014 what the project does, how it works, what's unique. Stop there.
|
|
815
|
-
- If a task asks you to "write content for slides" or "create social posts," produce a technical summary and note that
|
|
848
|
+
- If a task asks you to "write content for slides" or "create social posts," produce a technical summary and note that the CMO should handle the content/design work. Do NOT write the slides yourself.
|
|
816
849
|
- Your output is the INPUT for other specialists, not the final deliverable for external audiences.`
|
|
817
850
|
},
|
|
818
851
|
mari: {
|
|
819
852
|
name: "mari",
|
|
820
853
|
role: "CMO",
|
|
821
|
-
systemPrompt: `You are mari, the CMO. You hold deep context on design, branding, storytelling, content, and digital marketing across all modern channels. You report to
|
|
854
|
+
systemPrompt: `You are mari, the CMO. You hold deep context on design, branding, storytelling, content, and digital marketing across all modern channels. You report to the COO.
|
|
822
855
|
|
|
823
856
|
Your domain:
|
|
824
857
|
|
|
@@ -890,7 +923,7 @@ DELEGATION:
|
|
|
890
923
|
tom: {
|
|
891
924
|
name: "tom",
|
|
892
925
|
role: "Principal Engineer",
|
|
893
|
-
systemPrompt: `You are tom, a principal engineer. You write production-grade code with zero shortcuts. You report to
|
|
926
|
+
systemPrompt: `You are tom, a principal engineer. You write production-grade code with zero shortcuts. You report to the CTO for technical tasks, and to the COO for organizational matters.
|
|
894
927
|
|
|
895
928
|
You are the hands. Yoshi architects and specs; you implement. You receive tasks with clear acceptance criteria and tests to pass. Your job is to make those tests green with code that a senior engineer would be proud to maintain.
|
|
896
929
|
|
|
@@ -932,23 +965,23 @@ Velocity:
|
|
|
932
965
|
- If the spec is ambiguous, check exe/ARCHITECTURE.md. If still unclear, implement the simplest interpretation and note the ambiguity.
|
|
933
966
|
- You are optimized for throughput. Fast, correct, clean \u2014 in that order. But never sacrifice correct for fast.
|
|
934
967
|
|
|
935
|
-
Working with
|
|
968
|
+
Working with the CTO:
|
|
936
969
|
- Yoshi writes specs and tests. You implement. If the spec is wrong, report it \u2014 don't silently deviate.
|
|
937
970
|
- If tests seem wrong, report it \u2014 don't modify them.
|
|
938
|
-
- Your review goes to whoever assigned the task (usually
|
|
971
|
+
- Your review goes to whoever assigned the task (usually the CTO). The CTO reviews your code, not the COO.
|
|
939
972
|
- Multiple toms can run in parallel. You may share a memory pool. If you discover something useful (a gotcha, a pattern, a workaround), store it \u2014 the next tom session benefits.
|
|
940
973
|
|
|
941
974
|
What you do NOT do:
|
|
942
|
-
- Architecture decisions \u2014 that's
|
|
943
|
-
- Marketing, content, design \u2014 that's
|
|
975
|
+
- Architecture decisions \u2014 that's the CTO
|
|
976
|
+
- Marketing, content, design \u2014 that's the CMO
|
|
944
977
|
- Prioritization, coordination \u2014 that's exe
|
|
945
|
-
- Spec writing, test writing \u2014 that's
|
|
978
|
+
- Spec writing, test writing \u2014 that's the CTO (unless explicitly asked)
|
|
946
979
|
- You implement. That's it. Do it well.`
|
|
947
980
|
},
|
|
948
981
|
sasha: {
|
|
949
982
|
name: "sasha",
|
|
950
983
|
role: "Content Production Specialist",
|
|
951
|
-
systemPrompt: `You are sasha, the content production specialist. You turn scripts and creative briefs into finished content using the exe-create platform. You report to
|
|
984
|
+
systemPrompt: `You are sasha, the content production specialist. You turn scripts and creative briefs into finished content using the exe-create platform. You report to the COO. For creative direction, you take input from the CMO.
|
|
952
985
|
|
|
953
986
|
You are the producer. Mari writes the script; you make it real. Yoshi builds the tools; you use them. You know every tool in the exe-create pipeline and how to get the best output from each one.
|
|
954
987
|
|
|
@@ -995,15 +1028,15 @@ PRODUCTION PRINCIPLES:
|
|
|
995
1028
|
7. Store production decisions in memory \u2014 which models worked, which prompts produced good results, what aspect ratios performed best. This knowledge compounds.
|
|
996
1029
|
|
|
997
1030
|
WHAT YOU DO NOT DO:
|
|
998
|
-
- Marketing strategy, brand decisions, copywriting \u2014 that's
|
|
999
|
-
- Architecture, tool development, debugging \u2014 that's
|
|
1031
|
+
- Marketing strategy, brand decisions, copywriting \u2014 that's the CMO
|
|
1032
|
+
- Architecture, tool development, debugging \u2014 that's the CTO
|
|
1000
1033
|
- Prioritization, coordination \u2014 that's exe
|
|
1001
1034
|
- You produce. That's it. Do it well.`
|
|
1002
1035
|
},
|
|
1003
1036
|
gen: {
|
|
1004
1037
|
name: "gen",
|
|
1005
1038
|
role: "AI Product Lead",
|
|
1006
|
-
systemPrompt: `You are gen, the AI Product Lead. You are the competitive intelligence engine. You study open source repos, new AI tools, and competitor products \u2014 then compare them against our codebase to find features we should steal, patterns we should adopt, and threats we should watch. You report to
|
|
1039
|
+
systemPrompt: `You are gen, the AI Product Lead. You are the competitive intelligence engine. You study open source repos, new AI tools, and competitor products \u2014 then compare them against our codebase to find features we should steal, patterns we should adopt, and threats we should watch. You report to the COO.
|
|
1007
1040
|
|
|
1008
1041
|
Your core job: someone hands you a repo or a tool. You clone it, read it cover to cover, and compare it against our products (exe-os, exe-wiki, exe-crm). You report what they do better, what we do better, and what's worth building.
|
|
1009
1042
|
|
|
@@ -1021,11 +1054,37 @@ When you analyze a repo:
|
|
|
1021
1054
|
2. Compare against our equivalent (exe-os vs their orchestration, exe-wiki vs their knowledge base, etc.)
|
|
1022
1055
|
3. Report: what to steal (with file paths), what they do worse (our moat), patterns worth adopting
|
|
1023
1056
|
4. Write to exe/output/competitive/{repo-name}.md
|
|
1024
|
-
5. If a feature is worth building, create a task for
|
|
1057
|
+
5. If a feature is worth building, create a task for the CTO with the spec
|
|
1025
1058
|
|
|
1026
1059
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
1027
1060
|
|
|
1028
1061
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
1062
|
+
},
|
|
1063
|
+
bob: {
|
|
1064
|
+
name: "bob",
|
|
1065
|
+
role: "Staff Code Reviewer",
|
|
1066
|
+
systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
|
|
1067
|
+
|
|
1068
|
+
Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
|
|
1069
|
+
|
|
1070
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
1071
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
1072
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
1073
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
1074
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
1075
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
1076
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
1077
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
1078
|
+
|
|
1079
|
+
Audit method:
|
|
1080
|
+
1. Read the actual source code \u2014 not summaries
|
|
1081
|
+
2. Send to Codex MCP for initial sweep
|
|
1082
|
+
3. Validate against ARCHITECTURE.md
|
|
1083
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
1084
|
+
5. Count matches before and after any claimed fix
|
|
1085
|
+
6. Write structured report with PASS/FAIL per item
|
|
1086
|
+
|
|
1087
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
1029
1088
|
}
|
|
1030
1089
|
};
|
|
1031
1090
|
CLIENT_COO_TEMPLATE = `---
|
|
@@ -1181,7 +1240,7 @@ __export(active_agent_exports, {
|
|
|
1181
1240
|
getAllActiveAgents: () => getAllActiveAgents,
|
|
1182
1241
|
writeActiveAgent: () => writeActiveAgent
|
|
1183
1242
|
});
|
|
1184
|
-
import { readFileSync as
|
|
1243
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
|
|
1185
1244
|
import { execSync as execSync4 } from "child_process";
|
|
1186
1245
|
import path6 from "path";
|
|
1187
1246
|
function getMarkerPath() {
|
|
@@ -1206,7 +1265,7 @@ function clearActiveAgent() {
|
|
|
1206
1265
|
function getActiveAgent() {
|
|
1207
1266
|
try {
|
|
1208
1267
|
const markerPath = getMarkerPath();
|
|
1209
|
-
const raw =
|
|
1268
|
+
const raw = readFileSync3(markerPath, "utf8");
|
|
1210
1269
|
const data = JSON.parse(raw);
|
|
1211
1270
|
if (data.agentId) {
|
|
1212
1271
|
if (data.startedAt) {
|
|
@@ -1259,7 +1318,7 @@ function getAllActiveAgents() {
|
|
|
1259
1318
|
const key = file.slice("active-agent-".length, -".json".length);
|
|
1260
1319
|
if (key === "undefined") continue;
|
|
1261
1320
|
try {
|
|
1262
|
-
const raw =
|
|
1321
|
+
const raw = readFileSync3(path6.join(CACHE_DIR, file), "utf8");
|
|
1263
1322
|
const data = JSON.parse(raw);
|
|
1264
1323
|
if (!data.agentId) continue;
|
|
1265
1324
|
if (data.startedAt) {
|
|
@@ -1311,17 +1370,70 @@ var init_active_agent = __esm({
|
|
|
1311
1370
|
// src/bin/exe-launch-agent.ts
|
|
1312
1371
|
import os3 from "os";
|
|
1313
1372
|
import path7 from "path";
|
|
1314
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1373
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
|
|
1315
1374
|
import { spawnSync } from "child_process";
|
|
1316
1375
|
|
|
1317
1376
|
// src/lib/database.ts
|
|
1318
1377
|
import { createClient } from "@libsql/client";
|
|
1378
|
+
|
|
1379
|
+
// src/lib/db-retry.ts
|
|
1380
|
+
var MAX_RETRIES = 3;
|
|
1381
|
+
var BASE_DELAY_MS = 200;
|
|
1382
|
+
var MAX_JITTER_MS = 300;
|
|
1383
|
+
function isBusyError(err) {
|
|
1384
|
+
if (err instanceof Error) {
|
|
1385
|
+
const msg = err.message.toLowerCase();
|
|
1386
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1387
|
+
}
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
function delay(ms) {
|
|
1391
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1392
|
+
}
|
|
1393
|
+
async function retryOnBusy(fn, label) {
|
|
1394
|
+
let lastError;
|
|
1395
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1396
|
+
try {
|
|
1397
|
+
return await fn();
|
|
1398
|
+
} catch (err) {
|
|
1399
|
+
lastError = err;
|
|
1400
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
1401
|
+
throw err;
|
|
1402
|
+
}
|
|
1403
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
1404
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
1405
|
+
process.stderr.write(
|
|
1406
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
1407
|
+
`
|
|
1408
|
+
);
|
|
1409
|
+
await delay(backoff + jitter);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
throw lastError;
|
|
1413
|
+
}
|
|
1414
|
+
function wrapWithRetry(client) {
|
|
1415
|
+
return new Proxy(client, {
|
|
1416
|
+
get(target, prop, receiver) {
|
|
1417
|
+
if (prop === "execute") {
|
|
1418
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
1419
|
+
}
|
|
1420
|
+
if (prop === "batch") {
|
|
1421
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
1422
|
+
}
|
|
1423
|
+
return Reflect.get(target, prop, receiver);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/lib/database.ts
|
|
1319
1429
|
var _client = null;
|
|
1430
|
+
var _resilientClient = null;
|
|
1320
1431
|
var initTurso = initDatabase;
|
|
1321
1432
|
async function initDatabase(config) {
|
|
1322
1433
|
if (_client) {
|
|
1323
1434
|
_client.close();
|
|
1324
1435
|
_client = null;
|
|
1436
|
+
_resilientClient = null;
|
|
1325
1437
|
}
|
|
1326
1438
|
const opts = {
|
|
1327
1439
|
url: `file:${config.dbPath}`
|
|
@@ -1330,17 +1442,24 @@ async function initDatabase(config) {
|
|
|
1330
1442
|
opts.encryptionKey = config.encryptionKey;
|
|
1331
1443
|
}
|
|
1332
1444
|
_client = createClient(opts);
|
|
1445
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1333
1446
|
}
|
|
1334
1447
|
function getClient() {
|
|
1448
|
+
if (!_resilientClient) {
|
|
1449
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1450
|
+
}
|
|
1451
|
+
return _resilientClient;
|
|
1452
|
+
}
|
|
1453
|
+
function getRawClient() {
|
|
1335
1454
|
if (!_client) {
|
|
1336
1455
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1337
1456
|
}
|
|
1338
1457
|
return _client;
|
|
1339
1458
|
}
|
|
1340
1459
|
async function ensureSchema() {
|
|
1341
|
-
const client =
|
|
1460
|
+
const client = getRawClient();
|
|
1342
1461
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1343
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1462
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1344
1463
|
try {
|
|
1345
1464
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1346
1465
|
} catch {
|
|
@@ -2134,6 +2253,7 @@ async function disposeDatabase() {
|
|
|
2134
2253
|
if (_client) {
|
|
2135
2254
|
_client.close();
|
|
2136
2255
|
_client = null;
|
|
2256
|
+
_resilientClient = null;
|
|
2137
2257
|
}
|
|
2138
2258
|
}
|
|
2139
2259
|
|
|
@@ -2512,7 +2632,13 @@ var PROVIDER_TABLE = {
|
|
|
2512
2632
|
var DEFAULT_PROVIDER = "default";
|
|
2513
2633
|
|
|
2514
2634
|
// src/bin/exe-launch-agent.ts
|
|
2515
|
-
|
|
2635
|
+
function getKnownAgents() {
|
|
2636
|
+
try {
|
|
2637
|
+
return loadEmployeesSync().map((e) => e.name);
|
|
2638
|
+
} catch {
|
|
2639
|
+
return ["exe"];
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2516
2642
|
function parseBasename(basename) {
|
|
2517
2643
|
const idx = basename.indexOf("-");
|
|
2518
2644
|
if (idx === -1) return { agent: basename, provider: DEFAULT_PROVIDER };
|
|
@@ -2538,7 +2664,8 @@ function resolveAgent(argv) {
|
|
|
2538
2664
|
return { agent, provider: DEFAULT_PROVIDER, passthrough };
|
|
2539
2665
|
}
|
|
2540
2666
|
async function isKnownAgent(agent) {
|
|
2541
|
-
|
|
2667
|
+
const knownAgents = getKnownAgents();
|
|
2668
|
+
if (knownAgents.some((a) => a.toLowerCase() === agent.toLowerCase())) return true;
|
|
2542
2669
|
try {
|
|
2543
2670
|
const employees = await loadEmployees();
|
|
2544
2671
|
return employees.some((e) => e.name.toLowerCase() === agent.toLowerCase());
|
|
@@ -2560,7 +2687,7 @@ function buildLaunchPlan(agent, behaviorsPath, passthrough, _hasAgentFlag, _prov
|
|
|
2560
2687
|
const effectiveIdPath = existsSync6(idPath) ? idPath : existsSync6(ccAgentPath) ? ccAgentPath : null;
|
|
2561
2688
|
if (effectiveIdPath) {
|
|
2562
2689
|
try {
|
|
2563
|
-
const identity =
|
|
2690
|
+
const identity = readFileSync4(effectiveIdPath, "utf-8");
|
|
2564
2691
|
args.push("--system-prompt", identity);
|
|
2565
2692
|
} catch {
|
|
2566
2693
|
args.push("--append-system-prompt-file", effectiveIdPath);
|
|
@@ -2695,7 +2822,7 @@ async function main() {
|
|
|
2695
2822
|
if (sourceFile) {
|
|
2696
2823
|
try {
|
|
2697
2824
|
mkdirSync4(ccAgentDir, { recursive: true });
|
|
2698
|
-
let content =
|
|
2825
|
+
let content = readFileSync4(sourceFile, "utf-8");
|
|
2699
2826
|
content = content.replace(/\$\{agent_id\}/g, agent);
|
|
2700
2827
|
writeFileSync3(ccAgentFile, content, "utf-8");
|
|
2701
2828
|
process.stderr.write(
|