@askexenow/exe-os 0.8.53 → 0.8.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +113 -10
- package/dist/bin/backfill-responses.js +113 -10
- package/dist/bin/backfill-vectors.js +147 -13
- package/dist/bin/cleanup-stale-review-tasks.js +113 -10
- package/dist/bin/cli.js +337 -211
- package/dist/bin/exe-agent.js +99 -4
- package/dist/bin/exe-assign.js +113 -10
- package/dist/bin/exe-boot.js +276 -85
- package/dist/bin/exe-call.js +107 -5
- package/dist/bin/exe-doctor.js +183 -13
- package/dist/bin/exe-export-behaviors.js +113 -10
- package/dist/bin/exe-forget.js +113 -10
- package/dist/bin/exe-gateway.js +131 -12
- package/dist/bin/exe-heartbeat.js +121 -11
- package/dist/bin/exe-kill.js +113 -10
- package/dist/bin/exe-launch-agent.js +113 -10
- package/dist/bin/exe-link.js +10 -2
- package/dist/bin/exe-new-employee.js +95 -0
- package/dist/bin/exe-pending-messages.js +113 -10
- package/dist/bin/exe-pending-notifications.js +113 -10
- package/dist/bin/exe-pending-reviews.js +122 -11
- package/dist/bin/exe-rename.js +95 -0
- package/dist/bin/exe-review.js +113 -10
- package/dist/bin/exe-search.js +113 -10
- package/dist/bin/exe-session-cleanup.js +131 -12
- package/dist/bin/exe-status.js +113 -10
- package/dist/bin/exe-team.js +113 -10
- package/dist/bin/git-sweep.js +131 -12
- package/dist/bin/graph-backfill.js +113 -10
- package/dist/bin/graph-export.js +113 -10
- package/dist/bin/scan-tasks.js +131 -12
- package/dist/bin/setup.js +107 -5
- package/dist/bin/shard-migrate.js +113 -10
- package/dist/bin/wiki-sync.js +113 -10
- package/dist/gateway/index.js +131 -12
- package/dist/hooks/bug-report-worker.js +131 -12
- package/dist/hooks/commit-complete.js +131 -12
- package/dist/hooks/error-recall.js +113 -10
- package/dist/hooks/ingest-worker.js +131 -12
- package/dist/hooks/instructions-loaded.js +113 -10
- package/dist/hooks/notification.js +113 -10
- package/dist/hooks/post-compact.js +113 -10
- package/dist/hooks/pre-compact.js +131 -12
- package/dist/hooks/pre-tool-use.js +113 -10
- package/dist/hooks/prompt-ingest-worker.js +113 -10
- package/dist/hooks/prompt-submit.js +140 -14
- package/dist/hooks/response-ingest-worker.js +113 -10
- package/dist/hooks/session-end.js +113 -10
- package/dist/hooks/session-start.js +113 -10
- package/dist/hooks/stop.js +113 -10
- package/dist/hooks/subagent-stop.js +113 -10
- package/dist/hooks/summary-worker.js +231 -114
- package/dist/index.js +131 -12
- package/dist/lib/cloud-sync.js +10 -2
- package/dist/lib/employee-templates.js +99 -4
- package/dist/lib/exe-daemon.js +4859 -4706
- package/dist/lib/hybrid-search.js +113 -10
- package/dist/lib/schedules.js +113 -10
- package/dist/lib/store.js +113 -10
- package/dist/lib/tasks.js +18 -2
- package/dist/lib/tmux-routing.js +18 -2
- package/dist/mcp/server.js +214 -28
- package/dist/mcp/tools/create-task.js +18 -2
- package/dist/mcp/tools/list-tasks.js +18 -2
- package/dist/runtime/index.js +131 -12
- package/dist/tui/App.js +337 -211
- package/package.json +2 -2
package/dist/bin/exe-boot.js
CHANGED
|
@@ -1271,6 +1271,103 @@ var init_database = __esm({
|
|
|
1271
1271
|
}
|
|
1272
1272
|
});
|
|
1273
1273
|
|
|
1274
|
+
// src/lib/platform-procedures.ts
|
|
1275
|
+
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
1276
|
+
var init_platform_procedures = __esm({
|
|
1277
|
+
"src/lib/platform-procedures.ts"() {
|
|
1278
|
+
"use strict";
|
|
1279
|
+
PLATFORM_PROCEDURES = [
|
|
1280
|
+
// --- Foundation: what is exe-os ---
|
|
1281
|
+
{
|
|
1282
|
+
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1283
|
+
domain: "architecture",
|
|
1284
|
+
priority: "p0",
|
|
1285
|
+
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO (exe), CTO (yoshi), CMO (mari), engineers (tom), content (sasha). Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1289
|
+
domain: "architecture",
|
|
1290
|
+
priority: "p0",
|
|
1291
|
+
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC, runs /exe to boot the COO. exe manages employees in tmux sessions. Each exeN is a separate CC window/project. Employees (yoshi, tom, mari) run in their own tmux panes via create_task auto-spawn. The founder talks to exe; exe orchestrates the team. CC is the shell, exe-os is the brain."
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
title: "Sessions explained \u2014 what exeN means and how projects work",
|
|
1295
|
+
domain: "architecture",
|
|
1296
|
+
priority: "p0",
|
|
1297
|
+
content: "Each exeN (exe1, exe2, exe3) is an isolated project session. exe1 might be exe-os development, exe2 might be exe-wiki. Each session spawns its own employees: exe1\u2192yoshi-exe1\u2192tom-exe1. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
|
|
1298
|
+
},
|
|
1299
|
+
// --- Hierarchy and dispatch ---
|
|
1300
|
+
{
|
|
1301
|
+
title: "Chain of command \u2014 who talks to whom",
|
|
1302
|
+
domain: "workflow",
|
|
1303
|
+
priority: "p0",
|
|
1304
|
+
content: "Founder \u2192 exe (COO) \u2192 yoshi (CTO) / mari (CMO). Yoshi \u2192 tom (engineer). Mari \u2192 sasha (content). Never skip levels: exe never assigns directly to tom. Tom never reports directly to exe. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
title: "Single dispatch path \u2014 create_task only",
|
|
1308
|
+
domain: "workflow",
|
|
1309
|
+
priority: "p0",
|
|
1310
|
+
content: "create_task is the ONLY way to dispatch work to another agent. No direct ensureEmployee calls, no manual tmux spawns, no send_message for actionable work. create_task \u2192 system auto-spawns \u2192 session correctly named. ONE PATH. No backdoors. No exceptions."
|
|
1311
|
+
},
|
|
1312
|
+
// --- Session isolation ---
|
|
1313
|
+
{
|
|
1314
|
+
title: "Session scoping \u2014 stay in your exe boundary",
|
|
1315
|
+
domain: "security",
|
|
1316
|
+
priority: "p0",
|
|
1317
|
+
content: "Session scoping is mandatory. Managers dispatch to workers within their own exe session ONLY. exe1\u2192yoshi-exe1\u2192tom-exe1. exe2\u2192yoshi-exe2\u2192tom2-exe2. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating exe session."
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
title: "Session isolation \u2014 never touch another session's work",
|
|
1321
|
+
domain: "workflow",
|
|
1322
|
+
priority: "p0",
|
|
1323
|
+
content: `Sessions are isolated. exeN owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another exe session. (2) Never review work from a different session \u2014 report "belongs to exeN" and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: yoshi-exe1 works ONLY on exe1 tasks. Cross-session work is a system violation.`
|
|
1324
|
+
},
|
|
1325
|
+
// --- Engineering: session scoping in code ---
|
|
1326
|
+
{
|
|
1327
|
+
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
1328
|
+
domain: "architecture",
|
|
1329
|
+
priority: "p0",
|
|
1330
|
+
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current exeN. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ exe sessions simultaneously."
|
|
1331
|
+
},
|
|
1332
|
+
// --- Hard constraints ---
|
|
1333
|
+
{
|
|
1334
|
+
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
1335
|
+
domain: "security",
|
|
1336
|
+
priority: "p0",
|
|
1337
|
+
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 exe reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
1338
|
+
},
|
|
1339
|
+
// --- Operations ---
|
|
1340
|
+
{
|
|
1341
|
+
title: "Managers must supervise deployed workers",
|
|
1342
|
+
domain: "workflow",
|
|
1343
|
+
priority: "p0",
|
|
1344
|
+
content: `Every manager (COO/CTO/CMO) who dispatches work to a worker MUST actively monitor them. Check tmux capture-pane every 10 minutes. Verify they're working, not stuck. If idle at prompt with in_progress task \u2192 send intercom. If stuck \u2192 unblock or escalate. "Standing by" without checking is negligence.`
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
|
|
1348
|
+
domain: "workflow",
|
|
1349
|
+
priority: "p0",
|
|
1350
|
+
content: "On every /exe boot, COO MUST check system health BEFORE other work: (1) daemon \u2014 is exed PID alive, (2) cloud sync \u2014 grep workers.log for recent cloud-sync errors, (3) memory count \u2014 total in DB, (4) sync delta \u2014 local vs cloud storage_bytes. Report as 4-line status table. If ANY check fails, surface to founder immediately. Do not proceed to tasks until health confirmed."
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
title: "exe-build-adv mandatory for 3+ files",
|
|
1354
|
+
domain: "workflow",
|
|
1355
|
+
priority: "p0",
|
|
1356
|
+
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
title: "Desktop and TUI are the same product",
|
|
1360
|
+
domain: "architecture",
|
|
1361
|
+
priority: "p0",
|
|
1362
|
+
content: "Desktop and TUI are the SAME product in different renderers. Same data contracts, same interactions, same acceptance criteria. Desktop tab specs in ARCHITECTURE.md ARE the TUI specs. When building TUI, cross-reference Desktop spec. Different tab names, identical behavior. Never treat them as separate products."
|
|
1363
|
+
}
|
|
1364
|
+
];
|
|
1365
|
+
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
1366
|
+
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1274
1371
|
// src/lib/global-procedures.ts
|
|
1275
1372
|
var global_procedures_exports = {};
|
|
1276
1373
|
__export(global_procedures_exports, {
|
|
@@ -1286,22 +1383,25 @@ async function loadGlobalProcedures() {
|
|
|
1286
1383
|
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
1287
1384
|
args: []
|
|
1288
1385
|
});
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1386
|
+
const allRows = result.rows;
|
|
1387
|
+
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
1388
|
+
if (customerOnly.length > 0) {
|
|
1389
|
+
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
1292
1390
|
${p.content}`).join("\n\n");
|
|
1293
1391
|
} else {
|
|
1294
|
-
|
|
1392
|
+
_customerCache = "";
|
|
1295
1393
|
}
|
|
1296
1394
|
_cacheLoaded = true;
|
|
1297
|
-
return
|
|
1395
|
+
return customerOnly;
|
|
1298
1396
|
}
|
|
1299
1397
|
function getGlobalProceduresBlock() {
|
|
1300
|
-
|
|
1301
|
-
if (
|
|
1398
|
+
const sections = [];
|
|
1399
|
+
if (_platformCache) sections.push(_platformCache);
|
|
1400
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
1401
|
+
if (sections.length === 0) return "";
|
|
1302
1402
|
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1303
1403
|
|
|
1304
|
-
${
|
|
1404
|
+
${sections.join("\n\n")}
|
|
1305
1405
|
`;
|
|
1306
1406
|
}
|
|
1307
1407
|
async function storeGlobalProcedure(input) {
|
|
@@ -1326,13 +1426,16 @@ async function deactivateGlobalProcedure(id) {
|
|
|
1326
1426
|
await loadGlobalProcedures();
|
|
1327
1427
|
return result.rowsAffected > 0;
|
|
1328
1428
|
}
|
|
1329
|
-
var
|
|
1429
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
1330
1430
|
var init_global_procedures = __esm({
|
|
1331
1431
|
"src/lib/global-procedures.ts"() {
|
|
1332
1432
|
"use strict";
|
|
1333
1433
|
init_database();
|
|
1334
|
-
|
|
1434
|
+
init_platform_procedures();
|
|
1435
|
+
_customerCache = "";
|
|
1335
1436
|
_cacheLoaded = false;
|
|
1437
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
1438
|
+
${p.content}`).join("\n\n");
|
|
1336
1439
|
}
|
|
1337
1440
|
});
|
|
1338
1441
|
|
|
@@ -3329,16 +3432,32 @@ var init_tasks_crud = __esm({
|
|
|
3329
3432
|
// src/lib/tasks-review.ts
|
|
3330
3433
|
import path12 from "path";
|
|
3331
3434
|
import { existsSync as existsSync11, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
3332
|
-
async function countPendingReviews() {
|
|
3435
|
+
async function countPendingReviews(sessionScope) {
|
|
3333
3436
|
const client = getClient();
|
|
3437
|
+
if (sessionScope) {
|
|
3438
|
+
const result2 = await client.execute({
|
|
3439
|
+
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
|
|
3440
|
+
args: [sessionScope]
|
|
3441
|
+
});
|
|
3442
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
3443
|
+
}
|
|
3334
3444
|
const result = await client.execute({
|
|
3335
3445
|
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
|
|
3336
3446
|
args: []
|
|
3337
3447
|
});
|
|
3338
3448
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3339
3449
|
}
|
|
3340
|
-
async function countNewPendingReviewsSince(sinceIso) {
|
|
3450
|
+
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3341
3451
|
const client = getClient();
|
|
3452
|
+
if (sessionScope) {
|
|
3453
|
+
const result2 = await client.execute({
|
|
3454
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3455
|
+
WHERE status = 'needs_review' AND updated_at > ?
|
|
3456
|
+
AND (session_scope = ? OR session_scope IS NULL)`,
|
|
3457
|
+
args: [sinceIso, sessionScope]
|
|
3458
|
+
});
|
|
3459
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
3460
|
+
}
|
|
3342
3461
|
const result = await client.execute({
|
|
3343
3462
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3344
3463
|
WHERE status = 'needs_review' AND updated_at > ?`,
|
|
@@ -5325,6 +5444,64 @@ var init_task_scanner = __esm({
|
|
|
5325
5444
|
}
|
|
5326
5445
|
});
|
|
5327
5446
|
|
|
5447
|
+
// src/lib/worker-gate.ts
|
|
5448
|
+
var worker_gate_exports = {};
|
|
5449
|
+
__export(worker_gate_exports, {
|
|
5450
|
+
MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
|
|
5451
|
+
cleanupWorkerPid: () => cleanupWorkerPid,
|
|
5452
|
+
registerWorkerPid: () => registerWorkerPid,
|
|
5453
|
+
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
5454
|
+
});
|
|
5455
|
+
import { readdirSync as readdirSync6, writeFileSync as writeFileSync7, unlinkSync as unlinkSync6, mkdirSync as mkdirSync8 } from "fs";
|
|
5456
|
+
import path18 from "path";
|
|
5457
|
+
function tryAcquireWorkerSlot() {
|
|
5458
|
+
try {
|
|
5459
|
+
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5460
|
+
const files = readdirSync6(WORKER_PID_DIR);
|
|
5461
|
+
let alive = 0;
|
|
5462
|
+
for (const f of files) {
|
|
5463
|
+
if (!f.endsWith(".pid")) continue;
|
|
5464
|
+
const dashIdx = f.lastIndexOf("-");
|
|
5465
|
+
const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
|
|
5466
|
+
if (isNaN(pid)) continue;
|
|
5467
|
+
try {
|
|
5468
|
+
process.kill(pid, 0);
|
|
5469
|
+
alive++;
|
|
5470
|
+
} catch {
|
|
5471
|
+
try {
|
|
5472
|
+
unlinkSync6(path18.join(WORKER_PID_DIR, f));
|
|
5473
|
+
} catch {
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
return alive < MAX_CONCURRENT_WORKERS;
|
|
5478
|
+
} catch {
|
|
5479
|
+
return true;
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
function registerWorkerPid(pid) {
|
|
5483
|
+
try {
|
|
5484
|
+
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5485
|
+
writeFileSync7(path18.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
5486
|
+
} catch {
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
function cleanupWorkerPid() {
|
|
5490
|
+
try {
|
|
5491
|
+
unlinkSync6(path18.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
5492
|
+
} catch {
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
|
|
5496
|
+
var init_worker_gate = __esm({
|
|
5497
|
+
"src/lib/worker-gate.ts"() {
|
|
5498
|
+
"use strict";
|
|
5499
|
+
init_config();
|
|
5500
|
+
WORKER_PID_DIR = path18.join(EXE_AI_DIR, "worker-pids");
|
|
5501
|
+
MAX_CONCURRENT_WORKERS = 3;
|
|
5502
|
+
}
|
|
5503
|
+
});
|
|
5504
|
+
|
|
5328
5505
|
// src/lib/crypto.ts
|
|
5329
5506
|
var crypto_exports = {};
|
|
5330
5507
|
__export(crypto_exports, {
|
|
@@ -5432,13 +5609,13 @@ __export(cloud_sync_exports, {
|
|
|
5432
5609
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
5433
5610
|
recordRosterDeletion: () => recordRosterDeletion
|
|
5434
5611
|
});
|
|
5435
|
-
import { readFileSync as readFileSync12, writeFileSync as
|
|
5612
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync14, readdirSync as readdirSync7, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2, unlinkSync as unlinkSync7, openSync, closeSync } from "fs";
|
|
5436
5613
|
import crypto8 from "crypto";
|
|
5437
|
-
import
|
|
5614
|
+
import path19 from "path";
|
|
5438
5615
|
import { homedir } from "os";
|
|
5439
5616
|
function logError(msg) {
|
|
5440
5617
|
try {
|
|
5441
|
-
const logPath =
|
|
5618
|
+
const logPath = path19.join(homedir(), ".exe-os", "workers.log");
|
|
5442
5619
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
5443
5620
|
`);
|
|
5444
5621
|
} catch {
|
|
@@ -5448,7 +5625,7 @@ async function withRosterLock(fn) {
|
|
|
5448
5625
|
try {
|
|
5449
5626
|
const fd = openSync(ROSTER_LOCK_PATH, "wx");
|
|
5450
5627
|
closeSync(fd);
|
|
5451
|
-
|
|
5628
|
+
writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
|
|
5452
5629
|
} catch (err) {
|
|
5453
5630
|
if (err.code === "EEXIST") {
|
|
5454
5631
|
try {
|
|
@@ -5456,10 +5633,10 @@ async function withRosterLock(fn) {
|
|
|
5456
5633
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
5457
5634
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
5458
5635
|
}
|
|
5459
|
-
|
|
5636
|
+
unlinkSync7(ROSTER_LOCK_PATH);
|
|
5460
5637
|
const fd = openSync(ROSTER_LOCK_PATH, "wx");
|
|
5461
5638
|
closeSync(fd);
|
|
5462
|
-
|
|
5639
|
+
writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
|
|
5463
5640
|
} catch (retryErr) {
|
|
5464
5641
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
5465
5642
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -5472,7 +5649,7 @@ async function withRosterLock(fn) {
|
|
|
5472
5649
|
return await fn();
|
|
5473
5650
|
} finally {
|
|
5474
5651
|
try {
|
|
5475
|
-
|
|
5652
|
+
unlinkSync7(ROSTER_LOCK_PATH);
|
|
5476
5653
|
} catch {
|
|
5477
5654
|
}
|
|
5478
5655
|
}
|
|
@@ -5772,22 +5949,22 @@ function recordRosterDeletion(name) {
|
|
|
5772
5949
|
} catch {
|
|
5773
5950
|
}
|
|
5774
5951
|
if (!deletions.includes(name)) deletions.push(name);
|
|
5775
|
-
|
|
5952
|
+
writeFileSync8(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
5776
5953
|
}
|
|
5777
5954
|
function consumeRosterDeletions() {
|
|
5778
5955
|
try {
|
|
5779
5956
|
if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
|
|
5780
5957
|
const deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
5781
|
-
|
|
5958
|
+
writeFileSync8(ROSTER_DELETIONS_PATH, "[]");
|
|
5782
5959
|
return deletions;
|
|
5783
5960
|
} catch {
|
|
5784
5961
|
return [];
|
|
5785
5962
|
}
|
|
5786
5963
|
}
|
|
5787
5964
|
function buildRosterBlob(paths) {
|
|
5788
|
-
const rosterPath = paths?.rosterPath ??
|
|
5789
|
-
const identityDir = paths?.identityDir ??
|
|
5790
|
-
const configPath = paths?.configPath ??
|
|
5965
|
+
const rosterPath = paths?.rosterPath ?? path19.join(EXE_AI_DIR, "exe-employees.json");
|
|
5966
|
+
const identityDir = paths?.identityDir ?? path19.join(EXE_AI_DIR, "identity");
|
|
5967
|
+
const configPath = paths?.configPath ?? path19.join(EXE_AI_DIR, "config.json");
|
|
5791
5968
|
let roster = [];
|
|
5792
5969
|
if (existsSync14(rosterPath)) {
|
|
5793
5970
|
try {
|
|
@@ -5797,9 +5974,9 @@ function buildRosterBlob(paths) {
|
|
|
5797
5974
|
}
|
|
5798
5975
|
const identities = {};
|
|
5799
5976
|
if (existsSync14(identityDir)) {
|
|
5800
|
-
for (const file of
|
|
5977
|
+
for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
5801
5978
|
try {
|
|
5802
|
-
identities[file] = readFileSync12(
|
|
5979
|
+
identities[file] = readFileSync12(path19.join(identityDir, file), "utf-8");
|
|
5803
5980
|
} catch {
|
|
5804
5981
|
}
|
|
5805
5982
|
}
|
|
@@ -5883,7 +6060,7 @@ async function cloudPullRoster(config) {
|
|
|
5883
6060
|
}
|
|
5884
6061
|
}
|
|
5885
6062
|
function mergeConfig(remoteConfig, configPath) {
|
|
5886
|
-
const cfgPath = configPath ??
|
|
6063
|
+
const cfgPath = configPath ?? path19.join(EXE_AI_DIR, "config.json");
|
|
5887
6064
|
let local = {};
|
|
5888
6065
|
if (existsSync14(cfgPath)) {
|
|
5889
6066
|
try {
|
|
@@ -5892,14 +6069,14 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
5892
6069
|
}
|
|
5893
6070
|
}
|
|
5894
6071
|
const merged = { ...remoteConfig, ...local };
|
|
5895
|
-
const dir =
|
|
5896
|
-
if (!existsSync14(dir))
|
|
5897
|
-
|
|
6072
|
+
const dir = path19.dirname(cfgPath);
|
|
6073
|
+
if (!existsSync14(dir)) mkdirSync9(dir, { recursive: true });
|
|
6074
|
+
writeFileSync8(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
5898
6075
|
}
|
|
5899
6076
|
async function mergeRosterFromRemote(remote, paths) {
|
|
5900
6077
|
return withRosterLock(async () => {
|
|
5901
6078
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
5902
|
-
const identityDir = paths?.identityDir ??
|
|
6079
|
+
const identityDir = paths?.identityDir ?? path19.join(EXE_AI_DIR, "identity");
|
|
5903
6080
|
const localEmployees = await loadEmployees(rosterPath);
|
|
5904
6081
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
5905
6082
|
let added = 0;
|
|
@@ -5909,10 +6086,10 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
5909
6086
|
localNames.add(remoteEmp.name);
|
|
5910
6087
|
added++;
|
|
5911
6088
|
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
5912
|
-
if (!existsSync14(identityDir))
|
|
5913
|
-
const idPath =
|
|
6089
|
+
if (!existsSync14(identityDir)) mkdirSync9(identityDir, { recursive: true });
|
|
6090
|
+
const idPath = path19.join(identityDir, `${remoteEmp.name}.md`);
|
|
5914
6091
|
if (!existsSync14(idPath)) {
|
|
5915
|
-
|
|
6092
|
+
writeFileSync8(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
5916
6093
|
}
|
|
5917
6094
|
}
|
|
5918
6095
|
try {
|
|
@@ -6026,9 +6203,17 @@ async function cloudPullGlobalProcedures(config) {
|
|
|
6026
6203
|
if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
|
|
6027
6204
|
const client = getClient();
|
|
6028
6205
|
const stmts = remoteProcs.map((p) => ({
|
|
6029
|
-
sql: `INSERT
|
|
6206
|
+
sql: `INSERT INTO global_procedures
|
|
6030
6207
|
(id, title, content, priority, domain, active, created_at, updated_at)
|
|
6031
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6208
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6209
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
6210
|
+
title = excluded.title,
|
|
6211
|
+
content = excluded.content,
|
|
6212
|
+
priority = excluded.priority,
|
|
6213
|
+
domain = excluded.domain,
|
|
6214
|
+
active = excluded.active,
|
|
6215
|
+
updated_at = excluded.updated_at
|
|
6216
|
+
WHERE excluded.updated_at > global_procedures.updated_at`,
|
|
6032
6217
|
args: [
|
|
6033
6218
|
p.id ?? null,
|
|
6034
6219
|
p.title ?? null,
|
|
@@ -6364,9 +6549,9 @@ var init_cloud_sync = __esm({
|
|
|
6364
6549
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
6365
6550
|
FETCH_TIMEOUT_MS = 3e4;
|
|
6366
6551
|
PUSH_BATCH_SIZE = 5e3;
|
|
6367
|
-
ROSTER_LOCK_PATH =
|
|
6552
|
+
ROSTER_LOCK_PATH = path19.join(EXE_AI_DIR, "roster-merge.lock");
|
|
6368
6553
|
LOCK_STALE_MS = 3e4;
|
|
6369
|
-
ROSTER_DELETIONS_PATH =
|
|
6554
|
+
ROSTER_DELETIONS_PATH = path19.join(EXE_AI_DIR, "roster-deletions.json");
|
|
6370
6555
|
}
|
|
6371
6556
|
});
|
|
6372
6557
|
|
|
@@ -6548,9 +6733,9 @@ var init_schedules = __esm({
|
|
|
6548
6733
|
|
|
6549
6734
|
// src/bin/exe-boot.ts
|
|
6550
6735
|
init_employees();
|
|
6551
|
-
import
|
|
6736
|
+
import path20 from "path";
|
|
6552
6737
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
6553
|
-
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as
|
|
6738
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync8, unlinkSync as unlinkSync8 } from "fs";
|
|
6554
6739
|
import os7 from "os";
|
|
6555
6740
|
|
|
6556
6741
|
// src/lib/employee-templates.ts
|
|
@@ -7138,18 +7323,18 @@ async function boot(options) {
|
|
|
7138
7323
|
} catch {
|
|
7139
7324
|
}
|
|
7140
7325
|
try {
|
|
7141
|
-
const { readdirSync:
|
|
7326
|
+
const { readdirSync: readdirSync9, readFileSync: readFs } = await import("fs");
|
|
7142
7327
|
const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
|
|
7143
7328
|
const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
7144
7329
|
const exeDir = "exe";
|
|
7145
|
-
const entries =
|
|
7330
|
+
const entries = readdirSync9(exeDir, { withFileTypes: true });
|
|
7146
7331
|
const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
|
|
7147
7332
|
for (const dir of employeeDirs) {
|
|
7148
7333
|
const employee = dir.name;
|
|
7149
|
-
const taskDir =
|
|
7334
|
+
const taskDir = path20.join(exeDir, employee);
|
|
7150
7335
|
let files;
|
|
7151
7336
|
try {
|
|
7152
|
-
files =
|
|
7337
|
+
files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
|
|
7153
7338
|
} catch {
|
|
7154
7339
|
continue;
|
|
7155
7340
|
}
|
|
@@ -7157,7 +7342,7 @@ async function boot(options) {
|
|
|
7157
7342
|
const taskFilePath = `exe/${employee}/${file}`;
|
|
7158
7343
|
let content;
|
|
7159
7344
|
try {
|
|
7160
|
-
content = readFs(
|
|
7345
|
+
content = readFs(path20.join(taskDir, file), "utf8");
|
|
7161
7346
|
} catch {
|
|
7162
7347
|
continue;
|
|
7163
7348
|
}
|
|
@@ -7241,12 +7426,12 @@ async function boot(options) {
|
|
|
7241
7426
|
} catch {
|
|
7242
7427
|
}
|
|
7243
7428
|
try {
|
|
7244
|
-
const exeExeDir =
|
|
7429
|
+
const exeExeDir = path20.join(process.cwd(), "exe", "exe");
|
|
7245
7430
|
if (existsSync15(exeExeDir)) {
|
|
7246
|
-
for (const f of
|
|
7431
|
+
for (const f of readdirSync8(exeExeDir)) {
|
|
7247
7432
|
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
7248
7433
|
try {
|
|
7249
|
-
|
|
7434
|
+
unlinkSync8(path20.join(exeExeDir, f));
|
|
7250
7435
|
} catch {
|
|
7251
7436
|
}
|
|
7252
7437
|
}
|
|
@@ -7290,12 +7475,12 @@ async function boot(options) {
|
|
|
7290
7475
|
});
|
|
7291
7476
|
const taskFile = String(r.task_file);
|
|
7292
7477
|
try {
|
|
7293
|
-
const filePath =
|
|
7478
|
+
const filePath = path20.join(process.cwd(), taskFile);
|
|
7294
7479
|
if (existsSync15(filePath)) {
|
|
7295
7480
|
let content = readFileSync13(filePath, "utf8");
|
|
7296
7481
|
content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
|
|
7297
|
-
const { writeFileSync:
|
|
7298
|
-
|
|
7482
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
7483
|
+
writeFileSync9(filePath, content);
|
|
7299
7484
|
}
|
|
7300
7485
|
} catch {
|
|
7301
7486
|
}
|
|
@@ -7755,8 +7940,8 @@ async function boot(options) {
|
|
|
7755
7940
|
})()
|
|
7756
7941
|
]);
|
|
7757
7942
|
try {
|
|
7758
|
-
const configPath =
|
|
7759
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
7943
|
+
const configPath = path20.join(
|
|
7944
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os7.homedir(), ".exe-os"),
|
|
7760
7945
|
"config.json"
|
|
7761
7946
|
);
|
|
7762
7947
|
if (existsSync15(configPath)) {
|
|
@@ -7766,7 +7951,7 @@ async function boot(options) {
|
|
|
7766
7951
|
} catch {
|
|
7767
7952
|
}
|
|
7768
7953
|
try {
|
|
7769
|
-
const backfillFlagPath =
|
|
7954
|
+
const backfillFlagPath = path20.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
7770
7955
|
const isBackfillNeeded = () => existsSync15(backfillFlagPath);
|
|
7771
7956
|
const coverageResult = await client.execute({
|
|
7772
7957
|
sql: `SELECT COUNT(*) as total,
|
|
@@ -7789,7 +7974,7 @@ async function boot(options) {
|
|
|
7789
7974
|
let daemonRunning = false;
|
|
7790
7975
|
let daemonUptime;
|
|
7791
7976
|
let daemonRequestsServed;
|
|
7792
|
-
const socketPath =
|
|
7977
|
+
const socketPath = path20.join(EXE_AI_DIR, "exed.sock");
|
|
7793
7978
|
if (existsSync15(socketPath)) {
|
|
7794
7979
|
try {
|
|
7795
7980
|
const net = await import("net");
|
|
@@ -7832,7 +8017,7 @@ async function boot(options) {
|
|
|
7832
8017
|
}
|
|
7833
8018
|
}
|
|
7834
8019
|
if (!daemonRunning) {
|
|
7835
|
-
const pidPath =
|
|
8020
|
+
const pidPath = path20.join(EXE_AI_DIR, "exed.pid");
|
|
7836
8021
|
if (existsSync15(pidPath)) {
|
|
7837
8022
|
try {
|
|
7838
8023
|
const pid = parseInt(readFileSync13(pidPath, "utf8").trim(), 10);
|
|
@@ -7846,10 +8031,10 @@ async function boot(options) {
|
|
|
7846
8031
|
}
|
|
7847
8032
|
if (nullCount === 0) {
|
|
7848
8033
|
try {
|
|
7849
|
-
const flagPath =
|
|
8034
|
+
const flagPath = path20.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
7850
8035
|
if (existsSync15(flagPath)) {
|
|
7851
|
-
const { unlinkSync:
|
|
7852
|
-
|
|
8036
|
+
const { unlinkSync: unlinkSync9 } = await import("fs");
|
|
8037
|
+
unlinkSync9(flagPath);
|
|
7853
8038
|
}
|
|
7854
8039
|
} catch {
|
|
7855
8040
|
}
|
|
@@ -7866,28 +8051,34 @@ async function boot(options) {
|
|
|
7866
8051
|
};
|
|
7867
8052
|
if (nullCount > 0) {
|
|
7868
8053
|
try {
|
|
7869
|
-
const {
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
const {
|
|
7875
|
-
const
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
8054
|
+
const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
8055
|
+
if (!tryAcquireWorkerSlot2()) {
|
|
8056
|
+
process.stderr.write("[exe-boot] Backfill needed but worker gate full \u2014 skipping\n");
|
|
8057
|
+
} else {
|
|
8058
|
+
const { spawn } = await import("child_process");
|
|
8059
|
+
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
8060
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
8061
|
+
const backfillPath = path20.resolve(path20.dirname(thisFile), "backfill-vectors.js");
|
|
8062
|
+
if (existsSync15(backfillPath)) {
|
|
8063
|
+
const { openSync: openSync2, closeSync: closeSync2 } = await import("fs");
|
|
8064
|
+
const workerLogPath = path20.join(EXE_AI_DIR, "workers.log");
|
|
8065
|
+
let stderrFd = "ignore";
|
|
8066
|
+
try {
|
|
8067
|
+
stderrFd = openSync2(workerLogPath, "a");
|
|
8068
|
+
} catch {
|
|
8069
|
+
}
|
|
8070
|
+
const child = spawn(process.execPath, [backfillPath], {
|
|
8071
|
+
detached: true,
|
|
8072
|
+
stdio: ["ignore", "ignore", stderrFd]
|
|
8073
|
+
});
|
|
8074
|
+
child.unref();
|
|
8075
|
+
if (child.pid) registerWorkerPid2(child.pid);
|
|
8076
|
+
if (typeof stderrFd === "number") try {
|
|
8077
|
+
closeSync2(stderrFd);
|
|
8078
|
+
} catch {
|
|
8079
|
+
}
|
|
8080
|
+
briefData.embedding.backfillRunning = true;
|
|
7889
8081
|
}
|
|
7890
|
-
briefData.embedding.backfillRunning = true;
|
|
7891
8082
|
}
|
|
7892
8083
|
} catch {
|
|
7893
8084
|
}
|
|
@@ -7900,7 +8091,7 @@ async function boot(options) {
|
|
|
7900
8091
|
const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
|
|
7901
8092
|
const missing = [];
|
|
7902
8093
|
for (const bin of criticalBinaries) {
|
|
7903
|
-
const binPath =
|
|
8094
|
+
const binPath = path20.resolve(path20.dirname(thisFile), bin);
|
|
7904
8095
|
if (!existsSync15(binPath)) {
|
|
7905
8096
|
missing.push(`dist/bin/${bin}`);
|
|
7906
8097
|
}
|
|
@@ -7931,7 +8122,7 @@ async function boot(options) {
|
|
|
7931
8122
|
return;
|
|
7932
8123
|
}
|
|
7933
8124
|
const exeEmployee = employees.find((e) => e.name === "exe") ?? DEFAULT_EXE;
|
|
7934
|
-
const sessionDir =
|
|
8125
|
+
const sessionDir = path20.join(EXE_AI_DIR, "sessions", "exe");
|
|
7935
8126
|
await mkdir5(sessionDir, { recursive: true });
|
|
7936
8127
|
const claudeMdContent = `${getSessionPrompt(exeEmployee.systemPrompt)}
|
|
7937
8128
|
|
|
@@ -7940,7 +8131,7 @@ async function boot(options) {
|
|
|
7940
8131
|
# Status Brief
|
|
7941
8132
|
|
|
7942
8133
|
${brief}`;
|
|
7943
|
-
await writeFile6(
|
|
8134
|
+
await writeFile6(path20.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
|
|
7944
8135
|
const unread = await readUnreadNotifications();
|
|
7945
8136
|
if (unread.length > 0) {
|
|
7946
8137
|
console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
|
|
@@ -7949,8 +8140,8 @@ ${brief}`;
|
|
|
7949
8140
|
await cleanupOldNotifications();
|
|
7950
8141
|
console.log(brief);
|
|
7951
8142
|
try {
|
|
7952
|
-
const configPath2 =
|
|
7953
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
8143
|
+
const configPath2 = path20.join(
|
|
8144
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os7.homedir(), ".exe-os"),
|
|
7954
8145
|
"config.json"
|
|
7955
8146
|
);
|
|
7956
8147
|
if (existsSync15(configPath2)) {
|