@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-call.js
CHANGED
|
@@ -112,13 +112,18 @@ __export(employees_exports, {
|
|
|
112
112
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
113
113
|
addEmployee: () => addEmployee,
|
|
114
114
|
getEmployee: () => getEmployee,
|
|
115
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
116
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
117
|
+
hasRole: () => hasRole,
|
|
118
|
+
isMultiInstance: () => isMultiInstance,
|
|
115
119
|
loadEmployees: () => loadEmployees,
|
|
120
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
116
121
|
registerBinSymlinks: () => registerBinSymlinks,
|
|
117
122
|
saveEmployees: () => saveEmployees,
|
|
118
123
|
validateEmployeeName: () => validateEmployeeName
|
|
119
124
|
});
|
|
120
125
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
121
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
|
|
126
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
122
127
|
import { execSync } from "child_process";
|
|
123
128
|
import path2 from "path";
|
|
124
129
|
function validateEmployeeName(name) {
|
|
@@ -151,9 +156,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
151
156
|
await mkdir2(path2.dirname(employeesPath), { recursive: true });
|
|
152
157
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
153
158
|
}
|
|
159
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
160
|
+
if (!existsSync2(employeesPath)) return [];
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
163
|
+
} catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
154
167
|
function getEmployee(employees, name) {
|
|
155
168
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
156
169
|
}
|
|
170
|
+
function getEmployeeByRole(employees, role) {
|
|
171
|
+
const lower = role.toLowerCase();
|
|
172
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
173
|
+
}
|
|
174
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
175
|
+
const lower = role.toLowerCase();
|
|
176
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
177
|
+
}
|
|
178
|
+
function hasRole(agentName, role) {
|
|
179
|
+
const employees = loadEmployeesSync();
|
|
180
|
+
const emp = getEmployee(employees, agentName);
|
|
181
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
182
|
+
}
|
|
183
|
+
function isMultiInstance(agentName, employees) {
|
|
184
|
+
const roster = employees ?? loadEmployeesSync();
|
|
185
|
+
const emp = getEmployee(roster, agentName);
|
|
186
|
+
if (!emp) return false;
|
|
187
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
188
|
+
}
|
|
157
189
|
function addEmployee(employees, employee) {
|
|
158
190
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
159
191
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -196,12 +228,13 @@ function registerBinSymlinks(name) {
|
|
|
196
228
|
}
|
|
197
229
|
return { created, skipped, errors };
|
|
198
230
|
}
|
|
199
|
-
var EMPLOYEES_PATH;
|
|
231
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
200
232
|
var init_employees = __esm({
|
|
201
233
|
"src/lib/employees.ts"() {
|
|
202
234
|
"use strict";
|
|
203
235
|
init_config();
|
|
204
236
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
237
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
205
238
|
}
|
|
206
239
|
});
|
|
207
240
|
|
|
@@ -226,7 +259,7 @@ function getSessionPrompt(storedPrompt) {
|
|
|
226
259
|
${BASE_OPERATING_PROCEDURES}`;
|
|
227
260
|
}
|
|
228
261
|
function buildCustomEmployeePrompt(name, role) {
|
|
229
|
-
return `You are ${name}, a ${role}. You report to
|
|
262
|
+
return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
|
|
230
263
|
}
|
|
231
264
|
function getTemplate(name) {
|
|
232
265
|
return TEMPLATES[name];
|
|
@@ -290,12 +323,12 @@ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of
|
|
|
290
323
|
|
|
291
324
|
OPERATING PROCEDURES (mandatory for all employees):
|
|
292
325
|
|
|
293
|
-
You report to
|
|
326
|
+
You report to the COO. All work flows through exe. These procedures are non-negotiable.
|
|
294
327
|
|
|
295
328
|
1. BEFORE starting work:
|
|
296
329
|
- 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.
|
|
297
330
|
- Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
|
|
298
|
-
- NEVER read, write, or modify files in another employee's folder
|
|
331
|
+
- 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.
|
|
299
332
|
- If you have open tasks, work on the highest priority one first
|
|
300
333
|
- 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.
|
|
301
334
|
- Update task status to "in_progress" when starting (use update_task MCP tool)
|
|
@@ -362,7 +395,7 @@ DO NOT keep working degraded. Instead:
|
|
|
362
395
|
3. Stop working immediately. Do not attempt to continue with degraded context.
|
|
363
396
|
|
|
364
397
|
COMMUNICATION CHAIN \u2014 who you talk to:
|
|
365
|
-
- You report to
|
|
398
|
+
- You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
|
|
366
399
|
- 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.
|
|
367
400
|
- Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
|
|
368
401
|
|
|
@@ -381,7 +414,7 @@ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resource
|
|
|
381
414
|
NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
|
|
382
415
|
|
|
383
416
|
CREATING TASKS FOR OTHER EMPLOYEES:
|
|
384
|
-
When you need to assign work to another employee (e.g.,
|
|
417
|
+
When you need to assign work to another employee (e.g., CTO assigns to an engineer):
|
|
385
418
|
- ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
|
|
386
419
|
- Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
|
|
387
420
|
- create_task creates both the .md file AND the DB row atomically.
|
|
@@ -395,7 +428,7 @@ When you need to assign work to another employee (e.g., yoshi assigns to tom):
|
|
|
395
428
|
|
|
396
429
|
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.
|
|
397
430
|
|
|
398
|
-
You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to
|
|
431
|
+
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.
|
|
399
432
|
|
|
400
433
|
After every specialist task: verify tests ran, behavior was checked, and a memory summary was stored. If not, flag it.
|
|
401
434
|
|
|
@@ -408,7 +441,7 @@ Use recall_my_memory and ask_team_memory constantly. Store your own summaries (d
|
|
|
408
441
|
yoshi: {
|
|
409
442
|
name: "yoshi",
|
|
410
443
|
role: "CTO",
|
|
411
|
-
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
|
|
444
|
+
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.
|
|
412
445
|
|
|
413
446
|
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.
|
|
414
447
|
|
|
@@ -467,18 +500,18 @@ Use this for any decomposable implementation work. Single tom for sequential or
|
|
|
467
500
|
|
|
468
501
|
Reviews route to the assigner: if you assign a task to an engineer, you review it.
|
|
469
502
|
If exe assigns a task to you, exe reviews it. The chain is:
|
|
470
|
-
|
|
503
|
+
COO \u2192 CTO (you review) \u2192 engineers (you review their work, COO reviews yours)
|
|
471
504
|
|
|
472
505
|
ROLE BOUNDARIES \u2014 stay in your lane:
|
|
473
|
-
- You do NOT create marketing content, slide decks, social media copy, or brand materials. That is
|
|
506
|
+
- You do NOT create marketing content, slide decks, social media copy, or brand materials. That is the CMO's job.
|
|
474
507
|
- 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.
|
|
475
|
-
- If a task asks you to "write content for slides" or "create social posts," produce a technical summary and note that
|
|
508
|
+
- 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.
|
|
476
509
|
- Your output is the INPUT for other specialists, not the final deliverable for external audiences.`
|
|
477
510
|
},
|
|
478
511
|
mari: {
|
|
479
512
|
name: "mari",
|
|
480
513
|
role: "CMO",
|
|
481
|
-
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
|
|
514
|
+
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.
|
|
482
515
|
|
|
483
516
|
Your domain:
|
|
484
517
|
|
|
@@ -550,7 +583,7 @@ DELEGATION:
|
|
|
550
583
|
tom: {
|
|
551
584
|
name: "tom",
|
|
552
585
|
role: "Principal Engineer",
|
|
553
|
-
systemPrompt: `You are tom, a principal engineer. You write production-grade code with zero shortcuts. You report to
|
|
586
|
+
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.
|
|
554
587
|
|
|
555
588
|
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.
|
|
556
589
|
|
|
@@ -592,23 +625,23 @@ Velocity:
|
|
|
592
625
|
- If the spec is ambiguous, check exe/ARCHITECTURE.md. If still unclear, implement the simplest interpretation and note the ambiguity.
|
|
593
626
|
- You are optimized for throughput. Fast, correct, clean \u2014 in that order. But never sacrifice correct for fast.
|
|
594
627
|
|
|
595
|
-
Working with
|
|
628
|
+
Working with the CTO:
|
|
596
629
|
- Yoshi writes specs and tests. You implement. If the spec is wrong, report it \u2014 don't silently deviate.
|
|
597
630
|
- If tests seem wrong, report it \u2014 don't modify them.
|
|
598
|
-
- Your review goes to whoever assigned the task (usually
|
|
631
|
+
- Your review goes to whoever assigned the task (usually the CTO). The CTO reviews your code, not the COO.
|
|
599
632
|
- 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.
|
|
600
633
|
|
|
601
634
|
What you do NOT do:
|
|
602
|
-
- Architecture decisions \u2014 that's
|
|
603
|
-
- Marketing, content, design \u2014 that's
|
|
635
|
+
- Architecture decisions \u2014 that's the CTO
|
|
636
|
+
- Marketing, content, design \u2014 that's the CMO
|
|
604
637
|
- Prioritization, coordination \u2014 that's exe
|
|
605
|
-
- Spec writing, test writing \u2014 that's
|
|
638
|
+
- Spec writing, test writing \u2014 that's the CTO (unless explicitly asked)
|
|
606
639
|
- You implement. That's it. Do it well.`
|
|
607
640
|
},
|
|
608
641
|
sasha: {
|
|
609
642
|
name: "sasha",
|
|
610
643
|
role: "Content Production Specialist",
|
|
611
|
-
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
|
|
644
|
+
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.
|
|
612
645
|
|
|
613
646
|
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.
|
|
614
647
|
|
|
@@ -655,15 +688,15 @@ PRODUCTION PRINCIPLES:
|
|
|
655
688
|
7. Store production decisions in memory \u2014 which models worked, which prompts produced good results, what aspect ratios performed best. This knowledge compounds.
|
|
656
689
|
|
|
657
690
|
WHAT YOU DO NOT DO:
|
|
658
|
-
- Marketing strategy, brand decisions, copywriting \u2014 that's
|
|
659
|
-
- Architecture, tool development, debugging \u2014 that's
|
|
691
|
+
- Marketing strategy, brand decisions, copywriting \u2014 that's the CMO
|
|
692
|
+
- Architecture, tool development, debugging \u2014 that's the CTO
|
|
660
693
|
- Prioritization, coordination \u2014 that's exe
|
|
661
694
|
- You produce. That's it. Do it well.`
|
|
662
695
|
},
|
|
663
696
|
gen: {
|
|
664
697
|
name: "gen",
|
|
665
698
|
role: "AI Product Lead",
|
|
666
|
-
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
|
|
699
|
+
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.
|
|
667
700
|
|
|
668
701
|
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.
|
|
669
702
|
|
|
@@ -681,11 +714,37 @@ When you analyze a repo:
|
|
|
681
714
|
2. Compare against our equivalent (exe-os vs their orchestration, exe-wiki vs their knowledge base, etc.)
|
|
682
715
|
3. Report: what to steal (with file paths), what they do worse (our moat), patterns worth adopting
|
|
683
716
|
4. Write to exe/output/competitive/{repo-name}.md
|
|
684
|
-
5. If a feature is worth building, create a task for
|
|
717
|
+
5. If a feature is worth building, create a task for the CTO with the spec
|
|
685
718
|
|
|
686
719
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
687
720
|
|
|
688
721
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
722
|
+
},
|
|
723
|
+
bob: {
|
|
724
|
+
name: "bob",
|
|
725
|
+
role: "Staff Code Reviewer",
|
|
726
|
+
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.
|
|
727
|
+
|
|
728
|
+
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?"
|
|
729
|
+
|
|
730
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
731
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
732
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
733
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
734
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
735
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
736
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
737
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
738
|
+
|
|
739
|
+
Audit method:
|
|
740
|
+
1. Read the actual source code \u2014 not summaries
|
|
741
|
+
2. Send to Codex MCP for initial sweep
|
|
742
|
+
3. Validate against ARCHITECTURE.md
|
|
743
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
744
|
+
5. Count matches before and after any claimed fix
|
|
745
|
+
6. Write structured report with PASS/FAIL per item
|
|
746
|
+
|
|
747
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
689
748
|
}
|
|
690
749
|
};
|
|
691
750
|
CLIENT_COO_TEMPLATE = `---
|
package/dist/bin/exe-dispatch.js
CHANGED
|
@@ -318,11 +318,19 @@ var init_intercom_queue = __esm({
|
|
|
318
318
|
}
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
+
// src/lib/db-retry.ts
|
|
322
|
+
var init_db_retry = __esm({
|
|
323
|
+
"src/lib/db-retry.ts"() {
|
|
324
|
+
"use strict";
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
321
328
|
// src/lib/database.ts
|
|
322
329
|
import { createClient } from "@libsql/client";
|
|
323
330
|
var init_database = __esm({
|
|
324
331
|
"src/lib/database.ts"() {
|
|
325
332
|
"use strict";
|
|
333
|
+
init_db_retry();
|
|
326
334
|
}
|
|
327
335
|
});
|
|
328
336
|
|
|
@@ -425,7 +433,7 @@ var init_config = __esm({
|
|
|
425
433
|
|
|
426
434
|
// src/lib/employees.ts
|
|
427
435
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
428
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
|
|
436
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
|
|
429
437
|
import { execSync as execSync3 } from "child_process";
|
|
430
438
|
import path4 from "path";
|
|
431
439
|
var EMPLOYEES_PATH;
|
|
@@ -438,7 +446,7 @@ var init_employees = __esm({
|
|
|
438
446
|
});
|
|
439
447
|
|
|
440
448
|
// src/lib/license.ts
|
|
441
|
-
import { readFileSync as
|
|
449
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
442
450
|
import { randomUUID } from "crypto";
|
|
443
451
|
import path5 from "path";
|
|
444
452
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -461,12 +469,12 @@ var init_license = __esm({
|
|
|
461
469
|
});
|
|
462
470
|
|
|
463
471
|
// src/lib/plan-limits.ts
|
|
464
|
-
import { readFileSync as
|
|
472
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
465
473
|
import path6 from "path";
|
|
466
474
|
function getLicenseSync() {
|
|
467
475
|
try {
|
|
468
476
|
if (!existsSync6(CACHE_PATH2)) return freeLicense();
|
|
469
|
-
const raw = JSON.parse(
|
|
477
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
470
478
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
471
479
|
const parts = raw.token.split(".");
|
|
472
480
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -505,7 +513,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
505
513
|
let count = 0;
|
|
506
514
|
try {
|
|
507
515
|
if (existsSync6(filePath)) {
|
|
508
|
-
const raw =
|
|
516
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
509
517
|
const employees = JSON.parse(raw);
|
|
510
518
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
511
519
|
}
|
|
@@ -540,7 +548,7 @@ var init_plan_limits = __esm({
|
|
|
540
548
|
|
|
541
549
|
// src/lib/tmux-routing.ts
|
|
542
550
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
543
|
-
import { readFileSync as
|
|
551
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
|
|
544
552
|
import path7 from "path";
|
|
545
553
|
import os4 from "os";
|
|
546
554
|
import { fileURLToPath } from "url";
|
|
@@ -589,7 +597,7 @@ function extractRootExe(name) {
|
|
|
589
597
|
}
|
|
590
598
|
function getParentExe(sessionKey) {
|
|
591
599
|
try {
|
|
592
|
-
const data = JSON.parse(
|
|
600
|
+
const data = JSON.parse(readFileSync7(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
593
601
|
return data.parentExe || null;
|
|
594
602
|
} catch {
|
|
595
603
|
return null;
|
|
@@ -623,7 +631,7 @@ function findFreeInstance(employeeName2, exeSession2, maxInstances = 10, isAlive
|
|
|
623
631
|
function readDebounceState() {
|
|
624
632
|
try {
|
|
625
633
|
if (!existsSync7(DEBOUNCE_FILE)) return {};
|
|
626
|
-
return JSON.parse(
|
|
634
|
+
return JSON.parse(readFileSync7(DEBOUNCE_FILE, "utf8"));
|
|
627
635
|
} catch {
|
|
628
636
|
return {};
|
|
629
637
|
}
|
|
@@ -797,7 +805,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
797
805
|
const claudeJsonPath = path7.join(os4.homedir(), ".claude.json");
|
|
798
806
|
let claudeJson = {};
|
|
799
807
|
try {
|
|
800
|
-
claudeJson = JSON.parse(
|
|
808
|
+
claudeJson = JSON.parse(readFileSync7(claudeJsonPath, "utf8"));
|
|
801
809
|
} catch {
|
|
802
810
|
}
|
|
803
811
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -815,7 +823,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
815
823
|
const settingsPath = path7.join(projSettingsDir, "settings.json");
|
|
816
824
|
let settings = {};
|
|
817
825
|
try {
|
|
818
|
-
settings = JSON.parse(
|
|
826
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
|
|
819
827
|
} catch {
|
|
820
828
|
}
|
|
821
829
|
const perms = settings.permissions ?? {};
|
package/dist/bin/exe-doctor.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 {
|
|
@@ -448,12 +448,65 @@ var init_shard_manager = __esm({
|
|
|
448
448
|
|
|
449
449
|
// src/lib/database.ts
|
|
450
450
|
import { createClient } from "@libsql/client";
|
|
451
|
+
|
|
452
|
+
// src/lib/db-retry.ts
|
|
453
|
+
var MAX_RETRIES = 3;
|
|
454
|
+
var BASE_DELAY_MS = 200;
|
|
455
|
+
var MAX_JITTER_MS = 300;
|
|
456
|
+
function isBusyError(err) {
|
|
457
|
+
if (err instanceof Error) {
|
|
458
|
+
const msg = err.message.toLowerCase();
|
|
459
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
function delay(ms) {
|
|
464
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
465
|
+
}
|
|
466
|
+
async function retryOnBusy(fn, label) {
|
|
467
|
+
let lastError;
|
|
468
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
469
|
+
try {
|
|
470
|
+
return await fn();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
lastError = err;
|
|
473
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
474
|
+
throw err;
|
|
475
|
+
}
|
|
476
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
477
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
480
|
+
`
|
|
481
|
+
);
|
|
482
|
+
await delay(backoff + jitter);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
throw lastError;
|
|
486
|
+
}
|
|
487
|
+
function wrapWithRetry(client) {
|
|
488
|
+
return new Proxy(client, {
|
|
489
|
+
get(target, prop, receiver) {
|
|
490
|
+
if (prop === "execute") {
|
|
491
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
492
|
+
}
|
|
493
|
+
if (prop === "batch") {
|
|
494
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
495
|
+
}
|
|
496
|
+
return Reflect.get(target, prop, receiver);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/lib/database.ts
|
|
451
502
|
var _client = null;
|
|
503
|
+
var _resilientClient = null;
|
|
452
504
|
var initTurso = initDatabase;
|
|
453
505
|
async function initDatabase(config) {
|
|
454
506
|
if (_client) {
|
|
455
507
|
_client.close();
|
|
456
508
|
_client = null;
|
|
509
|
+
_resilientClient = null;
|
|
457
510
|
}
|
|
458
511
|
const opts = {
|
|
459
512
|
url: `file:${config.dbPath}`
|
|
@@ -462,17 +515,24 @@ async function initDatabase(config) {
|
|
|
462
515
|
opts.encryptionKey = config.encryptionKey;
|
|
463
516
|
}
|
|
464
517
|
_client = createClient(opts);
|
|
518
|
+
_resilientClient = wrapWithRetry(_client);
|
|
465
519
|
}
|
|
466
520
|
function getClient() {
|
|
521
|
+
if (!_resilientClient) {
|
|
522
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
523
|
+
}
|
|
524
|
+
return _resilientClient;
|
|
525
|
+
}
|
|
526
|
+
function getRawClient() {
|
|
467
527
|
if (!_client) {
|
|
468
528
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
469
529
|
}
|
|
470
530
|
return _client;
|
|
471
531
|
}
|
|
472
532
|
async function ensureSchema() {
|
|
473
|
-
const client =
|
|
533
|
+
const client = getRawClient();
|
|
474
534
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
475
|
-
await client.execute("PRAGMA busy_timeout =
|
|
535
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
476
536
|
try {
|
|
477
537
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
478
538
|
} catch {
|
|
@@ -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 {
|
|
@@ -448,12 +448,65 @@ var init_shard_manager = __esm({
|
|
|
448
448
|
|
|
449
449
|
// src/lib/database.ts
|
|
450
450
|
import { createClient } from "@libsql/client";
|
|
451
|
+
|
|
452
|
+
// src/lib/db-retry.ts
|
|
453
|
+
var MAX_RETRIES = 3;
|
|
454
|
+
var BASE_DELAY_MS = 200;
|
|
455
|
+
var MAX_JITTER_MS = 300;
|
|
456
|
+
function isBusyError(err) {
|
|
457
|
+
if (err instanceof Error) {
|
|
458
|
+
const msg = err.message.toLowerCase();
|
|
459
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
function delay(ms) {
|
|
464
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
465
|
+
}
|
|
466
|
+
async function retryOnBusy(fn, label) {
|
|
467
|
+
let lastError;
|
|
468
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
469
|
+
try {
|
|
470
|
+
return await fn();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
lastError = err;
|
|
473
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
474
|
+
throw err;
|
|
475
|
+
}
|
|
476
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
477
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
480
|
+
`
|
|
481
|
+
);
|
|
482
|
+
await delay(backoff + jitter);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
throw lastError;
|
|
486
|
+
}
|
|
487
|
+
function wrapWithRetry(client) {
|
|
488
|
+
return new Proxy(client, {
|
|
489
|
+
get(target, prop, receiver) {
|
|
490
|
+
if (prop === "execute") {
|
|
491
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
492
|
+
}
|
|
493
|
+
if (prop === "batch") {
|
|
494
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
495
|
+
}
|
|
496
|
+
return Reflect.get(target, prop, receiver);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/lib/database.ts
|
|
451
502
|
var _client = null;
|
|
503
|
+
var _resilientClient = null;
|
|
452
504
|
var initTurso = initDatabase;
|
|
453
505
|
async function initDatabase(config) {
|
|
454
506
|
if (_client) {
|
|
455
507
|
_client.close();
|
|
456
508
|
_client = null;
|
|
509
|
+
_resilientClient = null;
|
|
457
510
|
}
|
|
458
511
|
const opts = {
|
|
459
512
|
url: `file:${config.dbPath}`
|
|
@@ -462,17 +515,24 @@ async function initDatabase(config) {
|
|
|
462
515
|
opts.encryptionKey = config.encryptionKey;
|
|
463
516
|
}
|
|
464
517
|
_client = createClient(opts);
|
|
518
|
+
_resilientClient = wrapWithRetry(_client);
|
|
465
519
|
}
|
|
466
520
|
function getClient() {
|
|
521
|
+
if (!_resilientClient) {
|
|
522
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
523
|
+
}
|
|
524
|
+
return _resilientClient;
|
|
525
|
+
}
|
|
526
|
+
function getRawClient() {
|
|
467
527
|
if (!_client) {
|
|
468
528
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
469
529
|
}
|
|
470
530
|
return _client;
|
|
471
531
|
}
|
|
472
532
|
async function ensureSchema() {
|
|
473
|
-
const client =
|
|
533
|
+
const client = getRawClient();
|
|
474
534
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
475
|
-
await client.execute("PRAGMA busy_timeout =
|
|
535
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
476
536
|
try {
|
|
477
537
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
478
538
|
} catch {
|
|
@@ -1266,6 +1326,7 @@ async function disposeDatabase() {
|
|
|
1266
1326
|
if (_client) {
|
|
1267
1327
|
_client.close();
|
|
1268
1328
|
_client = null;
|
|
1329
|
+
_resilientClient = null;
|
|
1269
1330
|
}
|
|
1270
1331
|
}
|
|
1271
1332
|
|