@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/mcp/server.js
CHANGED
|
@@ -661,8 +661,8 @@ async function embedDirect(text) {
|
|
|
661
661
|
const llamaCpp = await import("node-llama-cpp");
|
|
662
662
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
663
663
|
const { existsSync: existsSync22 } = await import("fs");
|
|
664
|
-
const
|
|
665
|
-
const modelPath =
|
|
664
|
+
const path30 = await import("path");
|
|
665
|
+
const modelPath = path30.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
666
666
|
if (!existsSync22(modelPath)) {
|
|
667
667
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
668
668
|
}
|
|
@@ -1966,6 +1966,103 @@ var init_shard_manager = __esm({
|
|
|
1966
1966
|
}
|
|
1967
1967
|
});
|
|
1968
1968
|
|
|
1969
|
+
// src/lib/platform-procedures.ts
|
|
1970
|
+
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
1971
|
+
var init_platform_procedures = __esm({
|
|
1972
|
+
"src/lib/platform-procedures.ts"() {
|
|
1973
|
+
"use strict";
|
|
1974
|
+
PLATFORM_PROCEDURES = [
|
|
1975
|
+
// --- Foundation: what is exe-os ---
|
|
1976
|
+
{
|
|
1977
|
+
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1978
|
+
domain: "architecture",
|
|
1979
|
+
priority: "p0",
|
|
1980
|
+
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."
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1984
|
+
domain: "architecture",
|
|
1985
|
+
priority: "p0",
|
|
1986
|
+
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."
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
title: "Sessions explained \u2014 what exeN means and how projects work",
|
|
1990
|
+
domain: "architecture",
|
|
1991
|
+
priority: "p0",
|
|
1992
|
+
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."
|
|
1993
|
+
},
|
|
1994
|
+
// --- Hierarchy and dispatch ---
|
|
1995
|
+
{
|
|
1996
|
+
title: "Chain of command \u2014 who talks to whom",
|
|
1997
|
+
domain: "workflow",
|
|
1998
|
+
priority: "p0",
|
|
1999
|
+
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."
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
title: "Single dispatch path \u2014 create_task only",
|
|
2003
|
+
domain: "workflow",
|
|
2004
|
+
priority: "p0",
|
|
2005
|
+
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."
|
|
2006
|
+
},
|
|
2007
|
+
// --- Session isolation ---
|
|
2008
|
+
{
|
|
2009
|
+
title: "Session scoping \u2014 stay in your exe boundary",
|
|
2010
|
+
domain: "security",
|
|
2011
|
+
priority: "p0",
|
|
2012
|
+
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."
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
title: "Session isolation \u2014 never touch another session's work",
|
|
2016
|
+
domain: "workflow",
|
|
2017
|
+
priority: "p0",
|
|
2018
|
+
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.`
|
|
2019
|
+
},
|
|
2020
|
+
// --- Engineering: session scoping in code ---
|
|
2021
|
+
{
|
|
2022
|
+
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
2023
|
+
domain: "architecture",
|
|
2024
|
+
priority: "p0",
|
|
2025
|
+
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."
|
|
2026
|
+
},
|
|
2027
|
+
// --- Hard constraints ---
|
|
2028
|
+
{
|
|
2029
|
+
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
2030
|
+
domain: "security",
|
|
2031
|
+
priority: "p0",
|
|
2032
|
+
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."
|
|
2033
|
+
},
|
|
2034
|
+
// --- Operations ---
|
|
2035
|
+
{
|
|
2036
|
+
title: "Managers must supervise deployed workers",
|
|
2037
|
+
domain: "workflow",
|
|
2038
|
+
priority: "p0",
|
|
2039
|
+
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.`
|
|
2040
|
+
},
|
|
2041
|
+
{
|
|
2042
|
+
title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
|
|
2043
|
+
domain: "workflow",
|
|
2044
|
+
priority: "p0",
|
|
2045
|
+
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."
|
|
2046
|
+
},
|
|
2047
|
+
{
|
|
2048
|
+
title: "exe-build-adv mandatory for 3+ files",
|
|
2049
|
+
domain: "workflow",
|
|
2050
|
+
priority: "p0",
|
|
2051
|
+
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."
|
|
2052
|
+
},
|
|
2053
|
+
{
|
|
2054
|
+
title: "Desktop and TUI are the same product",
|
|
2055
|
+
domain: "architecture",
|
|
2056
|
+
priority: "p0",
|
|
2057
|
+
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."
|
|
2058
|
+
}
|
|
2059
|
+
];
|
|
2060
|
+
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
2061
|
+
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
|
|
1969
2066
|
// src/lib/global-procedures.ts
|
|
1970
2067
|
var global_procedures_exports = {};
|
|
1971
2068
|
__export(global_procedures_exports, {
|
|
@@ -1981,22 +2078,25 @@ async function loadGlobalProcedures() {
|
|
|
1981
2078
|
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
1982
2079
|
args: []
|
|
1983
2080
|
});
|
|
1984
|
-
const
|
|
1985
|
-
|
|
1986
|
-
|
|
2081
|
+
const allRows = result.rows;
|
|
2082
|
+
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
2083
|
+
if (customerOnly.length > 0) {
|
|
2084
|
+
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
1987
2085
|
${p.content}`).join("\n\n");
|
|
1988
2086
|
} else {
|
|
1989
|
-
|
|
2087
|
+
_customerCache = "";
|
|
1990
2088
|
}
|
|
1991
2089
|
_cacheLoaded = true;
|
|
1992
|
-
return
|
|
2090
|
+
return customerOnly;
|
|
1993
2091
|
}
|
|
1994
2092
|
function getGlobalProceduresBlock() {
|
|
1995
|
-
|
|
1996
|
-
if (
|
|
2093
|
+
const sections = [];
|
|
2094
|
+
if (_platformCache) sections.push(_platformCache);
|
|
2095
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
2096
|
+
if (sections.length === 0) return "";
|
|
1997
2097
|
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1998
2098
|
|
|
1999
|
-
${
|
|
2099
|
+
${sections.join("\n\n")}
|
|
2000
2100
|
`;
|
|
2001
2101
|
}
|
|
2002
2102
|
async function storeGlobalProcedure(input) {
|
|
@@ -2021,13 +2121,16 @@ async function deactivateGlobalProcedure(id) {
|
|
|
2021
2121
|
await loadGlobalProcedures();
|
|
2022
2122
|
return result.rowsAffected > 0;
|
|
2023
2123
|
}
|
|
2024
|
-
var
|
|
2124
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
2025
2125
|
var init_global_procedures = __esm({
|
|
2026
2126
|
"src/lib/global-procedures.ts"() {
|
|
2027
2127
|
"use strict";
|
|
2028
2128
|
init_database();
|
|
2029
|
-
|
|
2129
|
+
init_platform_procedures();
|
|
2130
|
+
_customerCache = "";
|
|
2030
2131
|
_cacheLoaded = false;
|
|
2132
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
2133
|
+
${p.content}`).join("\n\n");
|
|
2031
2134
|
}
|
|
2032
2135
|
});
|
|
2033
2136
|
|
|
@@ -5484,16 +5587,32 @@ var init_tasks_crud = __esm({
|
|
|
5484
5587
|
// src/lib/tasks-review.ts
|
|
5485
5588
|
import path19 from "path";
|
|
5486
5589
|
import { existsSync as existsSync15, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
5487
|
-
async function countPendingReviews() {
|
|
5590
|
+
async function countPendingReviews(sessionScope) {
|
|
5488
5591
|
const client = getClient();
|
|
5592
|
+
if (sessionScope) {
|
|
5593
|
+
const result2 = await client.execute({
|
|
5594
|
+
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
|
|
5595
|
+
args: [sessionScope]
|
|
5596
|
+
});
|
|
5597
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
5598
|
+
}
|
|
5489
5599
|
const result = await client.execute({
|
|
5490
5600
|
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
|
|
5491
5601
|
args: []
|
|
5492
5602
|
});
|
|
5493
5603
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5494
5604
|
}
|
|
5495
|
-
async function countNewPendingReviewsSince(sinceIso) {
|
|
5605
|
+
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
5496
5606
|
const client = getClient();
|
|
5607
|
+
if (sessionScope) {
|
|
5608
|
+
const result2 = await client.execute({
|
|
5609
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5610
|
+
WHERE status = 'needs_review' AND updated_at > ?
|
|
5611
|
+
AND (session_scope = ? OR session_scope IS NULL)`,
|
|
5612
|
+
args: [sinceIso, sessionScope]
|
|
5613
|
+
});
|
|
5614
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
5615
|
+
}
|
|
5497
5616
|
const result = await client.execute({
|
|
5498
5617
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5499
5618
|
WHERE status = 'needs_review' AND updated_at > ?`,
|
|
@@ -6950,6 +7069,64 @@ var init_consolidation = __esm({
|
|
|
6950
7069
|
}
|
|
6951
7070
|
});
|
|
6952
7071
|
|
|
7072
|
+
// src/lib/worker-gate.ts
|
|
7073
|
+
var worker_gate_exports = {};
|
|
7074
|
+
__export(worker_gate_exports, {
|
|
7075
|
+
MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
|
|
7076
|
+
cleanupWorkerPid: () => cleanupWorkerPid,
|
|
7077
|
+
registerWorkerPid: () => registerWorkerPid,
|
|
7078
|
+
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
7079
|
+
});
|
|
7080
|
+
import { readdirSync as readdirSync9, writeFileSync as writeFileSync12, unlinkSync as unlinkSync7, mkdirSync as mkdirSync11 } from "fs";
|
|
7081
|
+
import path28 from "path";
|
|
7082
|
+
function tryAcquireWorkerSlot() {
|
|
7083
|
+
try {
|
|
7084
|
+
mkdirSync11(WORKER_PID_DIR, { recursive: true });
|
|
7085
|
+
const files = readdirSync9(WORKER_PID_DIR);
|
|
7086
|
+
let alive = 0;
|
|
7087
|
+
for (const f of files) {
|
|
7088
|
+
if (!f.endsWith(".pid")) continue;
|
|
7089
|
+
const dashIdx = f.lastIndexOf("-");
|
|
7090
|
+
const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
|
|
7091
|
+
if (isNaN(pid)) continue;
|
|
7092
|
+
try {
|
|
7093
|
+
process.kill(pid, 0);
|
|
7094
|
+
alive++;
|
|
7095
|
+
} catch {
|
|
7096
|
+
try {
|
|
7097
|
+
unlinkSync7(path28.join(WORKER_PID_DIR, f));
|
|
7098
|
+
} catch {
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
return alive < MAX_CONCURRENT_WORKERS;
|
|
7103
|
+
} catch {
|
|
7104
|
+
return true;
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
function registerWorkerPid(pid) {
|
|
7108
|
+
try {
|
|
7109
|
+
mkdirSync11(WORKER_PID_DIR, { recursive: true });
|
|
7110
|
+
writeFileSync12(path28.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
7111
|
+
} catch {
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
function cleanupWorkerPid() {
|
|
7115
|
+
try {
|
|
7116
|
+
unlinkSync7(path28.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
7117
|
+
} catch {
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
|
|
7121
|
+
var init_worker_gate = __esm({
|
|
7122
|
+
"src/lib/worker-gate.ts"() {
|
|
7123
|
+
"use strict";
|
|
7124
|
+
init_config();
|
|
7125
|
+
WORKER_PID_DIR = path28.join(EXE_AI_DIR, "worker-pids");
|
|
7126
|
+
MAX_CONCURRENT_WORKERS = 3;
|
|
7127
|
+
}
|
|
7128
|
+
});
|
|
7129
|
+
|
|
6953
7130
|
// src/mcp/server.ts
|
|
6954
7131
|
init_embedder();
|
|
6955
7132
|
init_store();
|
|
@@ -6957,8 +7134,8 @@ init_database();
|
|
|
6957
7134
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6958
7135
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6959
7136
|
import { spawn as spawn2 } from "child_process";
|
|
6960
|
-
import { existsSync as existsSync21, openSync as openSync2, mkdirSync as
|
|
6961
|
-
import
|
|
7137
|
+
import { existsSync as existsSync21, openSync as openSync2, mkdirSync as mkdirSync12, closeSync as closeSync2 } from "fs";
|
|
7138
|
+
import path29 from "path";
|
|
6962
7139
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6963
7140
|
|
|
6964
7141
|
// src/mcp/tools/recall-my-memory.ts
|
|
@@ -11472,9 +11649,9 @@ var HostingerApiClient = class {
|
|
|
11472
11649
|
}
|
|
11473
11650
|
this.lastRequestTime = Date.now();
|
|
11474
11651
|
}
|
|
11475
|
-
async request(method,
|
|
11652
|
+
async request(method, path30, body) {
|
|
11476
11653
|
await this.rateLimit();
|
|
11477
|
-
const url = `${this.baseUrl}${
|
|
11654
|
+
const url = `${this.baseUrl}${path30}`;
|
|
11478
11655
|
const headers = {
|
|
11479
11656
|
Authorization: `Bearer ${this.apiKey}`,
|
|
11480
11657
|
"Content-Type": "application/json",
|
|
@@ -12007,32 +12184,34 @@ Domain: ${domain ?? "none"}`
|
|
|
12007
12184
|
|
|
12008
12185
|
// src/mcp/tools/list-global-procedures.ts
|
|
12009
12186
|
init_global_procedures();
|
|
12187
|
+
init_platform_procedures();
|
|
12010
12188
|
function registerListGlobalProcedures(server2) {
|
|
12011
12189
|
server2.registerTool(
|
|
12012
12190
|
"list_global_procedures",
|
|
12013
12191
|
{
|
|
12014
12192
|
title: "List Global Procedures",
|
|
12015
|
-
description: "List all active organization-wide procedures (Layer 0). These supersede identity, expertise, and experience.",
|
|
12193
|
+
description: "List all active organization-wide procedures (Layer 0). Shows platform procedures (shipped with exe-os, updated via exe-os update) and customer procedures (stored in DB, cloud-synced). These supersede identity, expertise, and experience.",
|
|
12016
12194
|
inputSchema: {}
|
|
12017
12195
|
},
|
|
12018
12196
|
async () => {
|
|
12019
|
-
const
|
|
12020
|
-
if (
|
|
12197
|
+
const customerProcs = await loadGlobalProcedures();
|
|
12198
|
+
if (customerProcs.length === 0) {
|
|
12021
12199
|
return {
|
|
12022
12200
|
content: [{
|
|
12023
12201
|
type: "text",
|
|
12024
|
-
text:
|
|
12202
|
+
text: `No custom procedures. ${PLATFORM_PROCEDURES.length} platform procedures are active (shipped with exe-os, not editable).
|
|
12203
|
+
Use store_global_procedure to add org-specific rules.`
|
|
12025
12204
|
}]
|
|
12026
12205
|
};
|
|
12027
12206
|
}
|
|
12028
|
-
const lines =
|
|
12207
|
+
const lines = customerProcs.map(
|
|
12029
12208
|
(p) => `[${p.id}] (${p.priority}) ${p.title}${p.domain ? ` [${p.domain}]` : ""}
|
|
12030
12209
|
${p.content}`
|
|
12031
12210
|
);
|
|
12032
12211
|
return {
|
|
12033
12212
|
content: [{
|
|
12034
12213
|
type: "text",
|
|
12035
|
-
text: `
|
|
12214
|
+
text: `Custom procedures (${customerProcs.length}) + ${PLATFORM_PROCEDURES.length} platform procedures (built-in):
|
|
12036
12215
|
|
|
12037
12216
|
${lines.join("\n\n")}`
|
|
12038
12217
|
}]
|
|
@@ -12238,19 +12417,25 @@ try {
|
|
|
12238
12417
|
);
|
|
12239
12418
|
const nullCount = Number(result.rows[0]?.cnt) || 0;
|
|
12240
12419
|
if (nullCount === 0) return;
|
|
12420
|
+
const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
12421
|
+
if (!tryAcquireWorkerSlot2()) {
|
|
12422
|
+
process.stderr.write(`[exe-os] Periodic backfill: ${nullCount} NULL vectors \u2014 skipped (worker gate full)
|
|
12423
|
+
`);
|
|
12424
|
+
return;
|
|
12425
|
+
}
|
|
12241
12426
|
process.stderr.write(
|
|
12242
12427
|
`[exe-os] Periodic backfill: ${nullCount} NULL vectors \u2014 spawning job
|
|
12243
12428
|
`
|
|
12244
12429
|
);
|
|
12245
12430
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
12246
|
-
const backfillPath =
|
|
12247
|
-
|
|
12431
|
+
const backfillPath = path29.resolve(
|
|
12432
|
+
path29.dirname(thisFile),
|
|
12248
12433
|
"../bin/backfill-vectors.js"
|
|
12249
12434
|
);
|
|
12250
12435
|
if (existsSync21(backfillPath)) {
|
|
12251
12436
|
const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
12252
|
-
const logPath =
|
|
12253
|
-
|
|
12437
|
+
const logPath = path29.join(exeDir, "workers.log");
|
|
12438
|
+
mkdirSync12(path29.dirname(logPath), { recursive: true });
|
|
12254
12439
|
let logFd = "ignore";
|
|
12255
12440
|
try {
|
|
12256
12441
|
logFd = openSync2(logPath, "a");
|
|
@@ -12261,6 +12446,7 @@ try {
|
|
|
12261
12446
|
stdio: ["ignore", "ignore", logFd]
|
|
12262
12447
|
});
|
|
12263
12448
|
child.unref();
|
|
12449
|
+
if (child.pid) registerWorkerPid2(child.pid);
|
|
12264
12450
|
if (typeof logFd === "number") try {
|
|
12265
12451
|
closeSync2(logFd);
|
|
12266
12452
|
} catch {
|
|
@@ -2317,16 +2317,32 @@ var init_tasks_crud = __esm({
|
|
|
2317
2317
|
// src/lib/tasks-review.ts
|
|
2318
2318
|
import path10 from "path";
|
|
2319
2319
|
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
2320
|
-
async function countPendingReviews() {
|
|
2320
|
+
async function countPendingReviews(sessionScope) {
|
|
2321
2321
|
const client = getClient();
|
|
2322
|
+
if (sessionScope) {
|
|
2323
|
+
const result2 = await client.execute({
|
|
2324
|
+
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
|
|
2325
|
+
args: [sessionScope]
|
|
2326
|
+
});
|
|
2327
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
2328
|
+
}
|
|
2322
2329
|
const result = await client.execute({
|
|
2323
2330
|
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
|
|
2324
2331
|
args: []
|
|
2325
2332
|
});
|
|
2326
2333
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2327
2334
|
}
|
|
2328
|
-
async function countNewPendingReviewsSince(sinceIso) {
|
|
2335
|
+
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2329
2336
|
const client = getClient();
|
|
2337
|
+
if (sessionScope) {
|
|
2338
|
+
const result2 = await client.execute({
|
|
2339
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2340
|
+
WHERE status = 'needs_review' AND updated_at > ?
|
|
2341
|
+
AND (session_scope = ? OR session_scope IS NULL)`,
|
|
2342
|
+
args: [sinceIso, sessionScope]
|
|
2343
|
+
});
|
|
2344
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
2345
|
+
}
|
|
2330
2346
|
const result = await client.execute({
|
|
2331
2347
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2332
2348
|
WHERE status = 'needs_review' AND updated_at > ?`,
|
|
@@ -2153,16 +2153,32 @@ var init_tasks_crud = __esm({
|
|
|
2153
2153
|
// src/lib/tasks-review.ts
|
|
2154
2154
|
import path10 from "path";
|
|
2155
2155
|
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
2156
|
-
async function countPendingReviews() {
|
|
2156
|
+
async function countPendingReviews(sessionScope) {
|
|
2157
2157
|
const client = getClient();
|
|
2158
|
+
if (sessionScope) {
|
|
2159
|
+
const result2 = await client.execute({
|
|
2160
|
+
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
|
|
2161
|
+
args: [sessionScope]
|
|
2162
|
+
});
|
|
2163
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
2164
|
+
}
|
|
2158
2165
|
const result = await client.execute({
|
|
2159
2166
|
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
|
|
2160
2167
|
args: []
|
|
2161
2168
|
});
|
|
2162
2169
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2163
2170
|
}
|
|
2164
|
-
async function countNewPendingReviewsSince(sinceIso) {
|
|
2171
|
+
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2165
2172
|
const client = getClient();
|
|
2173
|
+
if (sessionScope) {
|
|
2174
|
+
const result2 = await client.execute({
|
|
2175
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2176
|
+
WHERE status = 'needs_review' AND updated_at > ?
|
|
2177
|
+
AND (session_scope = ? OR session_scope IS NULL)`,
|
|
2178
|
+
args: [sinceIso, sessionScope]
|
|
2179
|
+
});
|
|
2180
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
2181
|
+
}
|
|
2166
2182
|
const result = await client.execute({
|
|
2167
2183
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2168
2184
|
WHERE status = 'needs_review' AND updated_at > ?`,
|
package/dist/runtime/index.js
CHANGED
|
@@ -2171,16 +2171,32 @@ var init_tasks_crud = __esm({
|
|
|
2171
2171
|
// src/lib/tasks-review.ts
|
|
2172
2172
|
import path10 from "path";
|
|
2173
2173
|
import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2174
|
-
async function countPendingReviews() {
|
|
2174
|
+
async function countPendingReviews(sessionScope) {
|
|
2175
2175
|
const client = getClient();
|
|
2176
|
+
if (sessionScope) {
|
|
2177
|
+
const result2 = await client.execute({
|
|
2178
|
+
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
|
|
2179
|
+
args: [sessionScope]
|
|
2180
|
+
});
|
|
2181
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
2182
|
+
}
|
|
2176
2183
|
const result = await client.execute({
|
|
2177
2184
|
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
|
|
2178
2185
|
args: []
|
|
2179
2186
|
});
|
|
2180
2187
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2181
2188
|
}
|
|
2182
|
-
async function countNewPendingReviewsSince(sinceIso) {
|
|
2189
|
+
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2183
2190
|
const client = getClient();
|
|
2191
|
+
if (sessionScope) {
|
|
2192
|
+
const result2 = await client.execute({
|
|
2193
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2194
|
+
WHERE status = 'needs_review' AND updated_at > ?
|
|
2195
|
+
AND (session_scope = ? OR session_scope IS NULL)`,
|
|
2196
|
+
args: [sinceIso, sessionScope]
|
|
2197
|
+
});
|
|
2198
|
+
return Number(result2.rows[0]?.cnt) || 0;
|
|
2199
|
+
}
|
|
2184
2200
|
const result = await client.execute({
|
|
2185
2201
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2186
2202
|
WHERE status = 'needs_review' AND updated_at > ?`,
|
|
@@ -4385,6 +4401,103 @@ var init_shard_manager = __esm({
|
|
|
4385
4401
|
}
|
|
4386
4402
|
});
|
|
4387
4403
|
|
|
4404
|
+
// src/lib/platform-procedures.ts
|
|
4405
|
+
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
4406
|
+
var init_platform_procedures = __esm({
|
|
4407
|
+
"src/lib/platform-procedures.ts"() {
|
|
4408
|
+
"use strict";
|
|
4409
|
+
PLATFORM_PROCEDURES = [
|
|
4410
|
+
// --- Foundation: what is exe-os ---
|
|
4411
|
+
{
|
|
4412
|
+
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
4413
|
+
domain: "architecture",
|
|
4414
|
+
priority: "p0",
|
|
4415
|
+
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."
|
|
4416
|
+
},
|
|
4417
|
+
{
|
|
4418
|
+
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
4419
|
+
domain: "architecture",
|
|
4420
|
+
priority: "p0",
|
|
4421
|
+
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."
|
|
4422
|
+
},
|
|
4423
|
+
{
|
|
4424
|
+
title: "Sessions explained \u2014 what exeN means and how projects work",
|
|
4425
|
+
domain: "architecture",
|
|
4426
|
+
priority: "p0",
|
|
4427
|
+
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."
|
|
4428
|
+
},
|
|
4429
|
+
// --- Hierarchy and dispatch ---
|
|
4430
|
+
{
|
|
4431
|
+
title: "Chain of command \u2014 who talks to whom",
|
|
4432
|
+
domain: "workflow",
|
|
4433
|
+
priority: "p0",
|
|
4434
|
+
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."
|
|
4435
|
+
},
|
|
4436
|
+
{
|
|
4437
|
+
title: "Single dispatch path \u2014 create_task only",
|
|
4438
|
+
domain: "workflow",
|
|
4439
|
+
priority: "p0",
|
|
4440
|
+
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."
|
|
4441
|
+
},
|
|
4442
|
+
// --- Session isolation ---
|
|
4443
|
+
{
|
|
4444
|
+
title: "Session scoping \u2014 stay in your exe boundary",
|
|
4445
|
+
domain: "security",
|
|
4446
|
+
priority: "p0",
|
|
4447
|
+
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."
|
|
4448
|
+
},
|
|
4449
|
+
{
|
|
4450
|
+
title: "Session isolation \u2014 never touch another session's work",
|
|
4451
|
+
domain: "workflow",
|
|
4452
|
+
priority: "p0",
|
|
4453
|
+
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.`
|
|
4454
|
+
},
|
|
4455
|
+
// --- Engineering: session scoping in code ---
|
|
4456
|
+
{
|
|
4457
|
+
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
4458
|
+
domain: "architecture",
|
|
4459
|
+
priority: "p0",
|
|
4460
|
+
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."
|
|
4461
|
+
},
|
|
4462
|
+
// --- Hard constraints ---
|
|
4463
|
+
{
|
|
4464
|
+
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
4465
|
+
domain: "security",
|
|
4466
|
+
priority: "p0",
|
|
4467
|
+
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."
|
|
4468
|
+
},
|
|
4469
|
+
// --- Operations ---
|
|
4470
|
+
{
|
|
4471
|
+
title: "Managers must supervise deployed workers",
|
|
4472
|
+
domain: "workflow",
|
|
4473
|
+
priority: "p0",
|
|
4474
|
+
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.`
|
|
4475
|
+
},
|
|
4476
|
+
{
|
|
4477
|
+
title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
|
|
4478
|
+
domain: "workflow",
|
|
4479
|
+
priority: "p0",
|
|
4480
|
+
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."
|
|
4481
|
+
},
|
|
4482
|
+
{
|
|
4483
|
+
title: "exe-build-adv mandatory for 3+ files",
|
|
4484
|
+
domain: "workflow",
|
|
4485
|
+
priority: "p0",
|
|
4486
|
+
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."
|
|
4487
|
+
},
|
|
4488
|
+
{
|
|
4489
|
+
title: "Desktop and TUI are the same product",
|
|
4490
|
+
domain: "architecture",
|
|
4491
|
+
priority: "p0",
|
|
4492
|
+
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."
|
|
4493
|
+
}
|
|
4494
|
+
];
|
|
4495
|
+
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
4496
|
+
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
4497
|
+
);
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
4500
|
+
|
|
4388
4501
|
// src/lib/global-procedures.ts
|
|
4389
4502
|
var global_procedures_exports = {};
|
|
4390
4503
|
__export(global_procedures_exports, {
|
|
@@ -4400,22 +4513,25 @@ async function loadGlobalProcedures() {
|
|
|
4400
4513
|
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
4401
4514
|
args: []
|
|
4402
4515
|
});
|
|
4403
|
-
const
|
|
4404
|
-
|
|
4405
|
-
|
|
4516
|
+
const allRows = result.rows;
|
|
4517
|
+
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
4518
|
+
if (customerOnly.length > 0) {
|
|
4519
|
+
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
4406
4520
|
${p.content}`).join("\n\n");
|
|
4407
4521
|
} else {
|
|
4408
|
-
|
|
4522
|
+
_customerCache = "";
|
|
4409
4523
|
}
|
|
4410
4524
|
_cacheLoaded = true;
|
|
4411
|
-
return
|
|
4525
|
+
return customerOnly;
|
|
4412
4526
|
}
|
|
4413
4527
|
function getGlobalProceduresBlock() {
|
|
4414
|
-
|
|
4415
|
-
if (
|
|
4528
|
+
const sections = [];
|
|
4529
|
+
if (_platformCache) sections.push(_platformCache);
|
|
4530
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
4531
|
+
if (sections.length === 0) return "";
|
|
4416
4532
|
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
4417
4533
|
|
|
4418
|
-
${
|
|
4534
|
+
${sections.join("\n\n")}
|
|
4419
4535
|
`;
|
|
4420
4536
|
}
|
|
4421
4537
|
async function storeGlobalProcedure(input) {
|
|
@@ -4440,13 +4556,16 @@ async function deactivateGlobalProcedure(id) {
|
|
|
4440
4556
|
await loadGlobalProcedures();
|
|
4441
4557
|
return result.rowsAffected > 0;
|
|
4442
4558
|
}
|
|
4443
|
-
var
|
|
4559
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
4444
4560
|
var init_global_procedures = __esm({
|
|
4445
4561
|
"src/lib/global-procedures.ts"() {
|
|
4446
4562
|
"use strict";
|
|
4447
4563
|
init_database();
|
|
4448
|
-
|
|
4564
|
+
init_platform_procedures();
|
|
4565
|
+
_customerCache = "";
|
|
4449
4566
|
_cacheLoaded = false;
|
|
4567
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
4568
|
+
${p.content}`).join("\n\n");
|
|
4450
4569
|
}
|
|
4451
4570
|
});
|
|
4452
4571
|
|