@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
|
@@ -1588,6 +1588,103 @@ var init_shard_manager = __esm({
|
|
|
1588
1588
|
}
|
|
1589
1589
|
});
|
|
1590
1590
|
|
|
1591
|
+
// src/lib/platform-procedures.ts
|
|
1592
|
+
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
1593
|
+
var init_platform_procedures = __esm({
|
|
1594
|
+
"src/lib/platform-procedures.ts"() {
|
|
1595
|
+
"use strict";
|
|
1596
|
+
PLATFORM_PROCEDURES = [
|
|
1597
|
+
// --- Foundation: what is exe-os ---
|
|
1598
|
+
{
|
|
1599
|
+
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1600
|
+
domain: "architecture",
|
|
1601
|
+
priority: "p0",
|
|
1602
|
+
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."
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1606
|
+
domain: "architecture",
|
|
1607
|
+
priority: "p0",
|
|
1608
|
+
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."
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
title: "Sessions explained \u2014 what exeN means and how projects work",
|
|
1612
|
+
domain: "architecture",
|
|
1613
|
+
priority: "p0",
|
|
1614
|
+
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."
|
|
1615
|
+
},
|
|
1616
|
+
// --- Hierarchy and dispatch ---
|
|
1617
|
+
{
|
|
1618
|
+
title: "Chain of command \u2014 who talks to whom",
|
|
1619
|
+
domain: "workflow",
|
|
1620
|
+
priority: "p0",
|
|
1621
|
+
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."
|
|
1622
|
+
},
|
|
1623
|
+
{
|
|
1624
|
+
title: "Single dispatch path \u2014 create_task only",
|
|
1625
|
+
domain: "workflow",
|
|
1626
|
+
priority: "p0",
|
|
1627
|
+
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."
|
|
1628
|
+
},
|
|
1629
|
+
// --- Session isolation ---
|
|
1630
|
+
{
|
|
1631
|
+
title: "Session scoping \u2014 stay in your exe boundary",
|
|
1632
|
+
domain: "security",
|
|
1633
|
+
priority: "p0",
|
|
1634
|
+
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."
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
title: "Session isolation \u2014 never touch another session's work",
|
|
1638
|
+
domain: "workflow",
|
|
1639
|
+
priority: "p0",
|
|
1640
|
+
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.`
|
|
1641
|
+
},
|
|
1642
|
+
// --- Engineering: session scoping in code ---
|
|
1643
|
+
{
|
|
1644
|
+
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
1645
|
+
domain: "architecture",
|
|
1646
|
+
priority: "p0",
|
|
1647
|
+
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."
|
|
1648
|
+
},
|
|
1649
|
+
// --- Hard constraints ---
|
|
1650
|
+
{
|
|
1651
|
+
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
1652
|
+
domain: "security",
|
|
1653
|
+
priority: "p0",
|
|
1654
|
+
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."
|
|
1655
|
+
},
|
|
1656
|
+
// --- Operations ---
|
|
1657
|
+
{
|
|
1658
|
+
title: "Managers must supervise deployed workers",
|
|
1659
|
+
domain: "workflow",
|
|
1660
|
+
priority: "p0",
|
|
1661
|
+
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.`
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
|
|
1665
|
+
domain: "workflow",
|
|
1666
|
+
priority: "p0",
|
|
1667
|
+
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."
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
title: "exe-build-adv mandatory for 3+ files",
|
|
1671
|
+
domain: "workflow",
|
|
1672
|
+
priority: "p0",
|
|
1673
|
+
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."
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
title: "Desktop and TUI are the same product",
|
|
1677
|
+
domain: "architecture",
|
|
1678
|
+
priority: "p0",
|
|
1679
|
+
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."
|
|
1680
|
+
}
|
|
1681
|
+
];
|
|
1682
|
+
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
1683
|
+
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1591
1688
|
// src/lib/global-procedures.ts
|
|
1592
1689
|
var global_procedures_exports = {};
|
|
1593
1690
|
__export(global_procedures_exports, {
|
|
@@ -1603,22 +1700,25 @@ async function loadGlobalProcedures() {
|
|
|
1603
1700
|
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
1604
1701
|
args: []
|
|
1605
1702
|
});
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1703
|
+
const allRows = result.rows;
|
|
1704
|
+
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
1705
|
+
if (customerOnly.length > 0) {
|
|
1706
|
+
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
1609
1707
|
${p.content}`).join("\n\n");
|
|
1610
1708
|
} else {
|
|
1611
|
-
|
|
1709
|
+
_customerCache = "";
|
|
1612
1710
|
}
|
|
1613
1711
|
_cacheLoaded = true;
|
|
1614
|
-
return
|
|
1712
|
+
return customerOnly;
|
|
1615
1713
|
}
|
|
1616
1714
|
function getGlobalProceduresBlock() {
|
|
1617
|
-
|
|
1618
|
-
if (
|
|
1715
|
+
const sections = [];
|
|
1716
|
+
if (_platformCache) sections.push(_platformCache);
|
|
1717
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
1718
|
+
if (sections.length === 0) return "";
|
|
1619
1719
|
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1620
1720
|
|
|
1621
|
-
${
|
|
1721
|
+
${sections.join("\n\n")}
|
|
1622
1722
|
`;
|
|
1623
1723
|
}
|
|
1624
1724
|
async function storeGlobalProcedure(input) {
|
|
@@ -1643,13 +1743,16 @@ async function deactivateGlobalProcedure(id) {
|
|
|
1643
1743
|
await loadGlobalProcedures();
|
|
1644
1744
|
return result.rowsAffected > 0;
|
|
1645
1745
|
}
|
|
1646
|
-
var
|
|
1746
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
1647
1747
|
var init_global_procedures = __esm({
|
|
1648
1748
|
"src/lib/global-procedures.ts"() {
|
|
1649
1749
|
"use strict";
|
|
1650
1750
|
init_database();
|
|
1651
|
-
|
|
1751
|
+
init_platform_procedures();
|
|
1752
|
+
_customerCache = "";
|
|
1652
1753
|
_cacheLoaded = false;
|
|
1754
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
1755
|
+
${p.content}`).join("\n\n");
|
|
1653
1756
|
}
|
|
1654
1757
|
});
|
|
1655
1758
|
|
|
@@ -2457,6 +2560,64 @@ var init_embedder = __esm({
|
|
|
2457
2560
|
}
|
|
2458
2561
|
});
|
|
2459
2562
|
|
|
2563
|
+
// src/lib/worker-gate.ts
|
|
2564
|
+
var worker_gate_exports = {};
|
|
2565
|
+
__export(worker_gate_exports, {
|
|
2566
|
+
MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
|
|
2567
|
+
cleanupWorkerPid: () => cleanupWorkerPid,
|
|
2568
|
+
registerWorkerPid: () => registerWorkerPid,
|
|
2569
|
+
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
2570
|
+
});
|
|
2571
|
+
import { readdirSync as readdirSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
2572
|
+
import path9 from "path";
|
|
2573
|
+
function tryAcquireWorkerSlot() {
|
|
2574
|
+
try {
|
|
2575
|
+
mkdirSync3(WORKER_PID_DIR, { recursive: true });
|
|
2576
|
+
const files = readdirSync3(WORKER_PID_DIR);
|
|
2577
|
+
let alive = 0;
|
|
2578
|
+
for (const f of files) {
|
|
2579
|
+
if (!f.endsWith(".pid")) continue;
|
|
2580
|
+
const dashIdx = f.lastIndexOf("-");
|
|
2581
|
+
const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
|
|
2582
|
+
if (isNaN(pid)) continue;
|
|
2583
|
+
try {
|
|
2584
|
+
process.kill(pid, 0);
|
|
2585
|
+
alive++;
|
|
2586
|
+
} catch {
|
|
2587
|
+
try {
|
|
2588
|
+
unlinkSync3(path9.join(WORKER_PID_DIR, f));
|
|
2589
|
+
} catch {
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
return alive < MAX_CONCURRENT_WORKERS;
|
|
2594
|
+
} catch {
|
|
2595
|
+
return true;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
function registerWorkerPid(pid) {
|
|
2599
|
+
try {
|
|
2600
|
+
mkdirSync3(WORKER_PID_DIR, { recursive: true });
|
|
2601
|
+
writeFileSync2(path9.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
2602
|
+
} catch {
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
function cleanupWorkerPid() {
|
|
2606
|
+
try {
|
|
2607
|
+
unlinkSync3(path9.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
2608
|
+
} catch {
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
|
|
2612
|
+
var init_worker_gate = __esm({
|
|
2613
|
+
"src/lib/worker-gate.ts"() {
|
|
2614
|
+
"use strict";
|
|
2615
|
+
init_config();
|
|
2616
|
+
WORKER_PID_DIR = path9.join(EXE_AI_DIR, "worker-pids");
|
|
2617
|
+
MAX_CONCURRENT_WORKERS = 3;
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
|
|
2460
2621
|
// src/lib/crypto.ts
|
|
2461
2622
|
var crypto_exports = {};
|
|
2462
2623
|
__export(crypto_exports, {
|
|
@@ -2564,13 +2725,13 @@ __export(cloud_sync_exports, {
|
|
|
2564
2725
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
2565
2726
|
recordRosterDeletion: () => recordRosterDeletion
|
|
2566
2727
|
});
|
|
2567
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
2728
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync9, readdirSync as readdirSync4, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
2568
2729
|
import crypto4 from "crypto";
|
|
2569
|
-
import
|
|
2730
|
+
import path10 from "path";
|
|
2570
2731
|
import { homedir } from "os";
|
|
2571
2732
|
function logError(msg) {
|
|
2572
2733
|
try {
|
|
2573
|
-
const logPath =
|
|
2734
|
+
const logPath = path10.join(homedir(), ".exe-os", "workers.log");
|
|
2574
2735
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
2575
2736
|
`);
|
|
2576
2737
|
} catch {
|
|
@@ -2580,7 +2741,7 @@ async function withRosterLock(fn) {
|
|
|
2580
2741
|
try {
|
|
2581
2742
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2582
2743
|
closeSync2(fd);
|
|
2583
|
-
|
|
2744
|
+
writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2584
2745
|
} catch (err) {
|
|
2585
2746
|
if (err.code === "EEXIST") {
|
|
2586
2747
|
try {
|
|
@@ -2588,10 +2749,10 @@ async function withRosterLock(fn) {
|
|
|
2588
2749
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
2589
2750
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
2590
2751
|
}
|
|
2591
|
-
|
|
2752
|
+
unlinkSync4(ROSTER_LOCK_PATH);
|
|
2592
2753
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2593
2754
|
closeSync2(fd);
|
|
2594
|
-
|
|
2755
|
+
writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2595
2756
|
} catch (retryErr) {
|
|
2596
2757
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
2597
2758
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -2604,7 +2765,7 @@ async function withRosterLock(fn) {
|
|
|
2604
2765
|
return await fn();
|
|
2605
2766
|
} finally {
|
|
2606
2767
|
try {
|
|
2607
|
-
|
|
2768
|
+
unlinkSync4(ROSTER_LOCK_PATH);
|
|
2608
2769
|
} catch {
|
|
2609
2770
|
}
|
|
2610
2771
|
}
|
|
@@ -2904,22 +3065,22 @@ function recordRosterDeletion(name) {
|
|
|
2904
3065
|
} catch {
|
|
2905
3066
|
}
|
|
2906
3067
|
if (!deletions.includes(name)) deletions.push(name);
|
|
2907
|
-
|
|
3068
|
+
writeFileSync3(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
2908
3069
|
}
|
|
2909
3070
|
function consumeRosterDeletions() {
|
|
2910
3071
|
try {
|
|
2911
3072
|
if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
|
|
2912
3073
|
const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
2913
|
-
|
|
3074
|
+
writeFileSync3(ROSTER_DELETIONS_PATH, "[]");
|
|
2914
3075
|
return deletions;
|
|
2915
3076
|
} catch {
|
|
2916
3077
|
return [];
|
|
2917
3078
|
}
|
|
2918
3079
|
}
|
|
2919
3080
|
function buildRosterBlob(paths) {
|
|
2920
|
-
const rosterPath = paths?.rosterPath ??
|
|
2921
|
-
const identityDir = paths?.identityDir ??
|
|
2922
|
-
const configPath = paths?.configPath ??
|
|
3081
|
+
const rosterPath = paths?.rosterPath ?? path10.join(EXE_AI_DIR, "exe-employees.json");
|
|
3082
|
+
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
3083
|
+
const configPath = paths?.configPath ?? path10.join(EXE_AI_DIR, "config.json");
|
|
2923
3084
|
let roster = [];
|
|
2924
3085
|
if (existsSync9(rosterPath)) {
|
|
2925
3086
|
try {
|
|
@@ -2929,9 +3090,9 @@ function buildRosterBlob(paths) {
|
|
|
2929
3090
|
}
|
|
2930
3091
|
const identities = {};
|
|
2931
3092
|
if (existsSync9(identityDir)) {
|
|
2932
|
-
for (const file of
|
|
3093
|
+
for (const file of readdirSync4(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
2933
3094
|
try {
|
|
2934
|
-
identities[file] = readFileSync7(
|
|
3095
|
+
identities[file] = readFileSync7(path10.join(identityDir, file), "utf-8");
|
|
2935
3096
|
} catch {
|
|
2936
3097
|
}
|
|
2937
3098
|
}
|
|
@@ -3015,7 +3176,7 @@ async function cloudPullRoster(config) {
|
|
|
3015
3176
|
}
|
|
3016
3177
|
}
|
|
3017
3178
|
function mergeConfig(remoteConfig, configPath) {
|
|
3018
|
-
const cfgPath = configPath ??
|
|
3179
|
+
const cfgPath = configPath ?? path10.join(EXE_AI_DIR, "config.json");
|
|
3019
3180
|
let local = {};
|
|
3020
3181
|
if (existsSync9(cfgPath)) {
|
|
3021
3182
|
try {
|
|
@@ -3024,14 +3185,14 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
3024
3185
|
}
|
|
3025
3186
|
}
|
|
3026
3187
|
const merged = { ...remoteConfig, ...local };
|
|
3027
|
-
const dir =
|
|
3028
|
-
if (!existsSync9(dir))
|
|
3029
|
-
|
|
3188
|
+
const dir = path10.dirname(cfgPath);
|
|
3189
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
3190
|
+
writeFileSync3(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3030
3191
|
}
|
|
3031
3192
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3032
3193
|
return withRosterLock(async () => {
|
|
3033
3194
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3034
|
-
const identityDir = paths?.identityDir ??
|
|
3195
|
+
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
3035
3196
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3036
3197
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3037
3198
|
let added = 0;
|
|
@@ -3041,10 +3202,10 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3041
3202
|
localNames.add(remoteEmp.name);
|
|
3042
3203
|
added++;
|
|
3043
3204
|
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
3044
|
-
if (!existsSync9(identityDir))
|
|
3045
|
-
const idPath =
|
|
3205
|
+
if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
|
|
3206
|
+
const idPath = path10.join(identityDir, `${remoteEmp.name}.md`);
|
|
3046
3207
|
if (!existsSync9(idPath)) {
|
|
3047
|
-
|
|
3208
|
+
writeFileSync3(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
3048
3209
|
}
|
|
3049
3210
|
}
|
|
3050
3211
|
try {
|
|
@@ -3158,9 +3319,17 @@ async function cloudPullGlobalProcedures(config) {
|
|
|
3158
3319
|
if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
|
|
3159
3320
|
const client = getClient();
|
|
3160
3321
|
const stmts = remoteProcs.map((p) => ({
|
|
3161
|
-
sql: `INSERT
|
|
3322
|
+
sql: `INSERT INTO global_procedures
|
|
3162
3323
|
(id, title, content, priority, domain, active, created_at, updated_at)
|
|
3163
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3324
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3325
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
3326
|
+
title = excluded.title,
|
|
3327
|
+
content = excluded.content,
|
|
3328
|
+
priority = excluded.priority,
|
|
3329
|
+
domain = excluded.domain,
|
|
3330
|
+
active = excluded.active,
|
|
3331
|
+
updated_at = excluded.updated_at
|
|
3332
|
+
WHERE excluded.updated_at > global_procedures.updated_at`,
|
|
3164
3333
|
args: [
|
|
3165
3334
|
p.id ?? null,
|
|
3166
3335
|
p.title ?? null,
|
|
@@ -3496,67 +3665,9 @@ var init_cloud_sync = __esm({
|
|
|
3496
3665
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
3497
3666
|
FETCH_TIMEOUT_MS = 3e4;
|
|
3498
3667
|
PUSH_BATCH_SIZE = 5e3;
|
|
3499
|
-
ROSTER_LOCK_PATH =
|
|
3668
|
+
ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
|
|
3500
3669
|
LOCK_STALE_MS = 3e4;
|
|
3501
|
-
ROSTER_DELETIONS_PATH =
|
|
3502
|
-
}
|
|
3503
|
-
});
|
|
3504
|
-
|
|
3505
|
-
// src/lib/worker-gate.ts
|
|
3506
|
-
var worker_gate_exports = {};
|
|
3507
|
-
__export(worker_gate_exports, {
|
|
3508
|
-
MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
|
|
3509
|
-
cleanupWorkerPid: () => cleanupWorkerPid,
|
|
3510
|
-
registerWorkerPid: () => registerWorkerPid,
|
|
3511
|
-
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
3512
|
-
});
|
|
3513
|
-
import { readdirSync as readdirSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
3514
|
-
import path10 from "path";
|
|
3515
|
-
function tryAcquireWorkerSlot() {
|
|
3516
|
-
try {
|
|
3517
|
-
mkdirSync4(WORKER_PID_DIR, { recursive: true });
|
|
3518
|
-
const files = readdirSync4(WORKER_PID_DIR);
|
|
3519
|
-
let alive = 0;
|
|
3520
|
-
for (const f of files) {
|
|
3521
|
-
if (!f.endsWith(".pid")) continue;
|
|
3522
|
-
const dashIdx = f.lastIndexOf("-");
|
|
3523
|
-
const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
|
|
3524
|
-
if (isNaN(pid)) continue;
|
|
3525
|
-
try {
|
|
3526
|
-
process.kill(pid, 0);
|
|
3527
|
-
alive++;
|
|
3528
|
-
} catch {
|
|
3529
|
-
try {
|
|
3530
|
-
unlinkSync4(path10.join(WORKER_PID_DIR, f));
|
|
3531
|
-
} catch {
|
|
3532
|
-
}
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
return alive < MAX_CONCURRENT_WORKERS;
|
|
3536
|
-
} catch {
|
|
3537
|
-
return true;
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
function registerWorkerPid(pid) {
|
|
3541
|
-
try {
|
|
3542
|
-
mkdirSync4(WORKER_PID_DIR, { recursive: true });
|
|
3543
|
-
writeFileSync3(path10.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
3544
|
-
} catch {
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
function cleanupWorkerPid() {
|
|
3548
|
-
try {
|
|
3549
|
-
unlinkSync4(path10.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
3550
|
-
} catch {
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3553
|
-
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
|
|
3554
|
-
var init_worker_gate = __esm({
|
|
3555
|
-
"src/lib/worker-gate.ts"() {
|
|
3556
|
-
"use strict";
|
|
3557
|
-
init_config();
|
|
3558
|
-
WORKER_PID_DIR = path10.join(EXE_AI_DIR, "worker-pids");
|
|
3559
|
-
MAX_CONCURRENT_WORKERS = 3;
|
|
3670
|
+
ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
|
|
3560
3671
|
}
|
|
3561
3672
|
});
|
|
3562
3673
|
|
|
@@ -4062,28 +4173,34 @@ async function main() {
|
|
|
4062
4173
|
const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4063
4174
|
const flagPath = path11.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
|
|
4064
4175
|
if (existsSync10(flagPath)) {
|
|
4065
|
-
const {
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
const backfillPath = path11.resolve(path11.dirname(thisFile), "backfill-vectors.js");
|
|
4069
|
-
if (existsSync10(backfillPath)) {
|
|
4070
|
-
const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4071
|
-
const bLogPath = path11.join(exeDir2, "workers.log");
|
|
4072
|
-
mkdirSync5(path11.dirname(bLogPath), { recursive: true });
|
|
4073
|
-
const bLogFd = openSync3(bLogPath, "a");
|
|
4074
|
-
const child = spawn2(process.execPath, [backfillPath], {
|
|
4075
|
-
detached: true,
|
|
4076
|
-
stdio: ["ignore", "ignore", bLogFd]
|
|
4077
|
-
});
|
|
4078
|
-
child.unref();
|
|
4079
|
-
try {
|
|
4080
|
-
closeSync3(bLogFd);
|
|
4081
|
-
} catch {
|
|
4082
|
-
}
|
|
4083
|
-
process.stderr.write("[summary-worker] Spawned backfill job\n");
|
|
4176
|
+
const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
4177
|
+
if (!tryAcquireWorkerSlot2()) {
|
|
4178
|
+
process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
|
|
4084
4179
|
} else {
|
|
4085
|
-
|
|
4180
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
4181
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
4182
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4183
|
+
const backfillPath = path11.resolve(path11.dirname(thisFile), "backfill-vectors.js");
|
|
4184
|
+
if (existsSync10(backfillPath)) {
|
|
4185
|
+
const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4186
|
+
const bLogPath = path11.join(exeDir2, "workers.log");
|
|
4187
|
+
mkdirSync5(path11.dirname(bLogPath), { recursive: true });
|
|
4188
|
+
const bLogFd = openSync3(bLogPath, "a");
|
|
4189
|
+
const child = spawn2(process.execPath, [backfillPath], {
|
|
4190
|
+
detached: true,
|
|
4191
|
+
stdio: ["ignore", "ignore", bLogFd]
|
|
4192
|
+
});
|
|
4193
|
+
child.unref();
|
|
4194
|
+
if (child.pid) registerWorkerPid2(child.pid);
|
|
4195
|
+
try {
|
|
4196
|
+
closeSync3(bLogFd);
|
|
4197
|
+
} catch {
|
|
4198
|
+
}
|
|
4199
|
+
process.stderr.write("[summary-worker] Spawned backfill job\n");
|
|
4200
|
+
} else {
|
|
4201
|
+
process.stderr.write(`[summary-worker] WARN: backfill-vectors not found at ${backfillPath}
|
|
4086
4202
|
`);
|
|
4203
|
+
}
|
|
4087
4204
|
}
|
|
4088
4205
|
}
|
|
4089
4206
|
} catch (err) {
|