@askexenow/exe-os 0.8.83 → 0.8.86
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +375 -152
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
package/dist/gateway/index.js
CHANGED
|
@@ -632,6 +632,7 @@ __export(employees_exports, {
|
|
|
632
632
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
633
633
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
634
634
|
addEmployee: () => addEmployee,
|
|
635
|
+
baseAgentName: () => baseAgentName,
|
|
635
636
|
canCoordinate: () => canCoordinate,
|
|
636
637
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
637
638
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -728,6 +729,14 @@ function hasRole(agentName, role) {
|
|
|
728
729
|
const emp = getEmployee(employees, agentName);
|
|
729
730
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
730
731
|
}
|
|
732
|
+
function baseAgentName(name, employees) {
|
|
733
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
734
|
+
if (!match) return name;
|
|
735
|
+
const base = match[1];
|
|
736
|
+
const roster = employees ?? loadEmployeesSync();
|
|
737
|
+
if (getEmployee(roster, base)) return base;
|
|
738
|
+
return name;
|
|
739
|
+
}
|
|
731
740
|
function isMultiInstance(agentName, employees) {
|
|
732
741
|
const roster = employees ?? loadEmployeesSync();
|
|
733
742
|
const emp = getEmployee(roster, agentName);
|
|
@@ -844,6 +853,12 @@ function getClient() {
|
|
|
844
853
|
if (!_resilientClient) {
|
|
845
854
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
846
855
|
}
|
|
856
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
857
|
+
return _resilientClient;
|
|
858
|
+
}
|
|
859
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
860
|
+
return _daemonClient;
|
|
861
|
+
}
|
|
847
862
|
return _resilientClient;
|
|
848
863
|
}
|
|
849
864
|
function getRawClient() {
|
|
@@ -1332,6 +1347,12 @@ async function ensureSchema() {
|
|
|
1332
1347
|
} catch {
|
|
1333
1348
|
}
|
|
1334
1349
|
}
|
|
1350
|
+
try {
|
|
1351
|
+
await client.execute(
|
|
1352
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1353
|
+
);
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1335
1356
|
await client.executeMultiple(`
|
|
1336
1357
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1337
1358
|
id TEXT PRIMARY KEY,
|
|
@@ -1384,7 +1405,30 @@ async function ensureSchema() {
|
|
|
1384
1405
|
entity_id TEXT NOT NULL,
|
|
1385
1406
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1386
1407
|
);
|
|
1408
|
+
|
|
1409
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1410
|
+
name,
|
|
1411
|
+
content=entities,
|
|
1412
|
+
content_rowid=rowid
|
|
1413
|
+
);
|
|
1414
|
+
|
|
1415
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1416
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1417
|
+
END;
|
|
1418
|
+
|
|
1419
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1420
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1421
|
+
END;
|
|
1422
|
+
|
|
1423
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1424
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1425
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1426
|
+
END;
|
|
1387
1427
|
`);
|
|
1428
|
+
try {
|
|
1429
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1430
|
+
} catch {
|
|
1431
|
+
}
|
|
1388
1432
|
await client.executeMultiple(`
|
|
1389
1433
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1390
1434
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1565,6 +1609,33 @@ async function ensureSchema() {
|
|
|
1565
1609
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1566
1610
|
ON conversations(channel_id);
|
|
1567
1611
|
`);
|
|
1612
|
+
await client.executeMultiple(`
|
|
1613
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1614
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1615
|
+
agent_id TEXT NOT NULL,
|
|
1616
|
+
session_name TEXT,
|
|
1617
|
+
task_id TEXT,
|
|
1618
|
+
project_name TEXT,
|
|
1619
|
+
started_at TEXT NOT NULL
|
|
1620
|
+
);
|
|
1621
|
+
|
|
1622
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1623
|
+
ON session_agent_map(agent_id);
|
|
1624
|
+
`);
|
|
1625
|
+
try {
|
|
1626
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1627
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1628
|
+
await client.execute({
|
|
1629
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1630
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1631
|
+
FROM memories
|
|
1632
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1633
|
+
GROUP BY session_id, agent_id`,
|
|
1634
|
+
args: []
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
} catch {
|
|
1638
|
+
}
|
|
1568
1639
|
try {
|
|
1569
1640
|
await client.execute({
|
|
1570
1641
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1698,15 +1769,41 @@ async function ensureSchema() {
|
|
|
1698
1769
|
});
|
|
1699
1770
|
} catch {
|
|
1700
1771
|
}
|
|
1772
|
+
for (const col of [
|
|
1773
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1774
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1775
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1776
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1777
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1778
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1779
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1780
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1781
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1782
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1783
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1784
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1785
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1786
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1787
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1788
|
+
]) {
|
|
1789
|
+
try {
|
|
1790
|
+
await client.execute(col);
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1701
1794
|
}
|
|
1702
1795
|
async function disposeDatabase() {
|
|
1796
|
+
if (_daemonClient) {
|
|
1797
|
+
_daemonClient.close();
|
|
1798
|
+
_daemonClient = null;
|
|
1799
|
+
}
|
|
1703
1800
|
if (_client) {
|
|
1704
1801
|
_client.close();
|
|
1705
1802
|
_client = null;
|
|
1706
1803
|
_resilientClient = null;
|
|
1707
1804
|
}
|
|
1708
1805
|
}
|
|
1709
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1806
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1710
1807
|
var init_database = __esm({
|
|
1711
1808
|
"src/lib/database.ts"() {
|
|
1712
1809
|
"use strict";
|
|
@@ -1714,6 +1811,7 @@ var init_database = __esm({
|
|
|
1714
1811
|
init_employees();
|
|
1715
1812
|
_client = null;
|
|
1716
1813
|
_resilientClient = null;
|
|
1814
|
+
_daemonClient = null;
|
|
1717
1815
|
initTurso = initDatabase;
|
|
1718
1816
|
disposeTurso = disposeDatabase;
|
|
1719
1817
|
}
|
|
@@ -1748,10 +1846,12 @@ function handleData(chunk) {
|
|
|
1748
1846
|
if (!line) continue;
|
|
1749
1847
|
try {
|
|
1750
1848
|
const response = JSON.parse(line);
|
|
1751
|
-
const
|
|
1849
|
+
const id = response.id;
|
|
1850
|
+
if (!id) continue;
|
|
1851
|
+
const entry = _pending.get(id);
|
|
1752
1852
|
if (entry) {
|
|
1753
1853
|
clearTimeout(entry.timer);
|
|
1754
|
-
_pending.delete(
|
|
1854
|
+
_pending.delete(id);
|
|
1755
1855
|
entry.resolve(response);
|
|
1756
1856
|
}
|
|
1757
1857
|
} catch {
|
|
@@ -1922,6 +2022,9 @@ async function connectEmbedDaemon() {
|
|
|
1922
2022
|
return false;
|
|
1923
2023
|
}
|
|
1924
2024
|
function sendRequest(texts, priority) {
|
|
2025
|
+
return sendDaemonRequest({ texts, priority });
|
|
2026
|
+
}
|
|
2027
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
1925
2028
|
return new Promise((resolve) => {
|
|
1926
2029
|
if (!_socket || !_connected) {
|
|
1927
2030
|
resolve({ error: "Not connected" });
|
|
@@ -1931,10 +2034,10 @@ function sendRequest(texts, priority) {
|
|
|
1931
2034
|
const timer = setTimeout(() => {
|
|
1932
2035
|
_pending.delete(id);
|
|
1933
2036
|
resolve({ error: "Request timeout" });
|
|
1934
|
-
},
|
|
2037
|
+
}, timeoutMs);
|
|
1935
2038
|
_pending.set(id, { resolve, timer });
|
|
1936
2039
|
try {
|
|
1937
|
-
_socket.write(JSON.stringify({ id,
|
|
2040
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1938
2041
|
} catch {
|
|
1939
2042
|
clearTimeout(timer);
|
|
1940
2043
|
_pending.delete(id);
|
|
@@ -1944,30 +2047,11 @@ function sendRequest(texts, priority) {
|
|
|
1944
2047
|
}
|
|
1945
2048
|
async function pingDaemon() {
|
|
1946
2049
|
if (!_socket || !_connected) return null;
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
}, 5e3);
|
|
1953
|
-
_pending.set(id, {
|
|
1954
|
-
resolve: (data) => {
|
|
1955
|
-
if (data.health) {
|
|
1956
|
-
resolve(data.health);
|
|
1957
|
-
} else {
|
|
1958
|
-
resolve(null);
|
|
1959
|
-
}
|
|
1960
|
-
},
|
|
1961
|
-
timer
|
|
1962
|
-
});
|
|
1963
|
-
try {
|
|
1964
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
1965
|
-
} catch {
|
|
1966
|
-
clearTimeout(timer);
|
|
1967
|
-
_pending.delete(id);
|
|
1968
|
-
resolve(null);
|
|
1969
|
-
}
|
|
1970
|
-
});
|
|
2050
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
2051
|
+
if (response.health) {
|
|
2052
|
+
return response.health;
|
|
2053
|
+
}
|
|
2054
|
+
return null;
|
|
1971
2055
|
}
|
|
1972
2056
|
function killAndRespawnDaemon() {
|
|
1973
2057
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
@@ -2110,10 +2194,10 @@ async function disposeEmbedder() {
|
|
|
2110
2194
|
async function embedDirect(text) {
|
|
2111
2195
|
const llamaCpp = await import("node-llama-cpp");
|
|
2112
2196
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2113
|
-
const { existsSync:
|
|
2114
|
-
const
|
|
2115
|
-
const modelPath =
|
|
2116
|
-
if (!
|
|
2197
|
+
const { existsSync: existsSync16 } = await import("fs");
|
|
2198
|
+
const path20 = await import("path");
|
|
2199
|
+
const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
2200
|
+
if (!existsSync16(modelPath)) {
|
|
2117
2201
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2118
2202
|
}
|
|
2119
2203
|
const llama = await llamaCpp.getLlama();
|
|
@@ -2172,12 +2256,20 @@ async function getMasterKey() {
|
|
|
2172
2256
|
}
|
|
2173
2257
|
const keyPath = getKeyPath();
|
|
2174
2258
|
if (!existsSync4(keyPath)) {
|
|
2259
|
+
process.stderr.write(
|
|
2260
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2261
|
+
`
|
|
2262
|
+
);
|
|
2175
2263
|
return null;
|
|
2176
2264
|
}
|
|
2177
2265
|
try {
|
|
2178
2266
|
const content = await readFile3(keyPath, "utf-8");
|
|
2179
2267
|
return Buffer.from(content.trim(), "base64");
|
|
2180
|
-
} catch {
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
process.stderr.write(
|
|
2270
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2271
|
+
`
|
|
2272
|
+
);
|
|
2181
2273
|
return null;
|
|
2182
2274
|
}
|
|
2183
2275
|
}
|
|
@@ -2633,6 +2725,7 @@ __export(store_exports, {
|
|
|
2633
2725
|
vectorToBlob: () => vectorToBlob,
|
|
2634
2726
|
writeMemory: () => writeMemory
|
|
2635
2727
|
});
|
|
2728
|
+
import { createHash } from "crypto";
|
|
2636
2729
|
function isBusyError2(err) {
|
|
2637
2730
|
if (err instanceof Error) {
|
|
2638
2731
|
const msg = err.message.toLowerCase();
|
|
@@ -2706,12 +2799,52 @@ function classifyTier(record) {
|
|
|
2706
2799
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
2707
2800
|
return 3;
|
|
2708
2801
|
}
|
|
2802
|
+
function inferFilePaths(record) {
|
|
2803
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
2804
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
2805
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
2806
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
2807
|
+
}
|
|
2808
|
+
function inferCommitHash(record) {
|
|
2809
|
+
if (record.tool_name !== "Bash") return null;
|
|
2810
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
2811
|
+
return match ? match[1] : null;
|
|
2812
|
+
}
|
|
2813
|
+
function inferLanguageType(record) {
|
|
2814
|
+
const text = record.raw_text;
|
|
2815
|
+
if (!text || text.length < 10) return null;
|
|
2816
|
+
const trimmed = text.trimStart();
|
|
2817
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
2818
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
2819
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
2820
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
2821
|
+
return "mixed";
|
|
2822
|
+
}
|
|
2823
|
+
function inferDomain(record) {
|
|
2824
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
2825
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
2826
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
2827
|
+
return null;
|
|
2828
|
+
}
|
|
2709
2829
|
async function writeMemory(record) {
|
|
2710
2830
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
2711
2831
|
throw new Error(
|
|
2712
2832
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
2713
2833
|
);
|
|
2714
2834
|
}
|
|
2835
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
2836
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
try {
|
|
2840
|
+
const client = getClient();
|
|
2841
|
+
const existing = await client.execute({
|
|
2842
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
2843
|
+
args: [contentHash, record.agent_id]
|
|
2844
|
+
});
|
|
2845
|
+
if (existing.rows.length > 0) return;
|
|
2846
|
+
} catch {
|
|
2847
|
+
}
|
|
2715
2848
|
const dbRow = {
|
|
2716
2849
|
id: record.id,
|
|
2717
2850
|
agent_id: record.agent_id,
|
|
@@ -2741,7 +2874,23 @@ async function writeMemory(record) {
|
|
|
2741
2874
|
supersedes_id: record.supersedes_id ?? null,
|
|
2742
2875
|
draft: record.draft ? 1 : 0,
|
|
2743
2876
|
memory_type: record.memory_type ?? "raw",
|
|
2744
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
2877
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
2878
|
+
content_hash: contentHash,
|
|
2879
|
+
intent: record.intent ?? null,
|
|
2880
|
+
outcome: record.outcome ?? null,
|
|
2881
|
+
domain: record.domain ?? inferDomain(record),
|
|
2882
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
2883
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
2884
|
+
chain_position: record.chain_position ?? null,
|
|
2885
|
+
review_status: record.review_status ?? null,
|
|
2886
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
2887
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
2888
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
2889
|
+
duration_ms: record.duration_ms ?? null,
|
|
2890
|
+
token_cost: record.token_cost ?? null,
|
|
2891
|
+
audience: record.audience ?? null,
|
|
2892
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
2893
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
2745
2894
|
};
|
|
2746
2895
|
_pendingRecords.push(dbRow);
|
|
2747
2896
|
orgBus.emit({
|
|
@@ -2799,80 +2948,85 @@ async function flushBatch() {
|
|
|
2799
2948
|
const draft = row.draft ? 1 : 0;
|
|
2800
2949
|
const memoryType = row.memory_type ?? "raw";
|
|
2801
2950
|
const trajectory = row.trajectory ?? null;
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2951
|
+
const contentHash = row.content_hash ?? null;
|
|
2952
|
+
const intent = row.intent ?? null;
|
|
2953
|
+
const outcome = row.outcome ?? null;
|
|
2954
|
+
const domain = row.domain ?? null;
|
|
2955
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
2956
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
2957
|
+
const chainPosition = row.chain_position ?? null;
|
|
2958
|
+
const reviewStatus = row.review_status ?? null;
|
|
2959
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
2960
|
+
const filePaths = row.file_paths ?? null;
|
|
2961
|
+
const commitHash = row.commit_hash ?? null;
|
|
2962
|
+
const durationMs = row.duration_ms ?? null;
|
|
2963
|
+
const tokenCost = row.token_cost ?? null;
|
|
2964
|
+
const audience = row.audience ?? null;
|
|
2965
|
+
const languageType = row.language_type ?? null;
|
|
2966
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
2967
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
2812
2968
|
tool_name, project_name,
|
|
2813
2969
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2814
2970
|
confidence, last_accessed,
|
|
2815
2971
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2816
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
trajectory
|
|
2875
|
-
]
|
|
2972
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
2973
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
2974
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
2975
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
2976
|
+
const metaArgs = [
|
|
2977
|
+
intent,
|
|
2978
|
+
outcome,
|
|
2979
|
+
domain,
|
|
2980
|
+
referencedEntities,
|
|
2981
|
+
retrievalCount,
|
|
2982
|
+
chainPosition,
|
|
2983
|
+
reviewStatus,
|
|
2984
|
+
contextWindowPct,
|
|
2985
|
+
filePaths,
|
|
2986
|
+
commitHash,
|
|
2987
|
+
durationMs,
|
|
2988
|
+
tokenCost,
|
|
2989
|
+
audience,
|
|
2990
|
+
languageType,
|
|
2991
|
+
parentMemoryId
|
|
2992
|
+
];
|
|
2993
|
+
const baseArgs = [
|
|
2994
|
+
row.id,
|
|
2995
|
+
row.agent_id,
|
|
2996
|
+
row.agent_role,
|
|
2997
|
+
row.session_id,
|
|
2998
|
+
row.timestamp,
|
|
2999
|
+
row.tool_name,
|
|
3000
|
+
row.project_name,
|
|
3001
|
+
row.has_error,
|
|
3002
|
+
row.raw_text
|
|
3003
|
+
];
|
|
3004
|
+
const sharedArgs = [
|
|
3005
|
+
row.version,
|
|
3006
|
+
taskId,
|
|
3007
|
+
importance,
|
|
3008
|
+
status,
|
|
3009
|
+
confidence,
|
|
3010
|
+
lastAccessed,
|
|
3011
|
+
workspaceId,
|
|
3012
|
+
documentId,
|
|
3013
|
+
userId,
|
|
3014
|
+
charOffset,
|
|
3015
|
+
pageNumber,
|
|
3016
|
+
sourcePath,
|
|
3017
|
+
sourceType,
|
|
3018
|
+
tier,
|
|
3019
|
+
supersedesId,
|
|
3020
|
+
draft,
|
|
3021
|
+
memoryType,
|
|
3022
|
+
trajectory,
|
|
3023
|
+
contentHash
|
|
3024
|
+
];
|
|
3025
|
+
return {
|
|
3026
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
3027
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
3028
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3029
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
2876
3030
|
};
|
|
2877
3031
|
};
|
|
2878
3032
|
const globalClient = getClient();
|
|
@@ -3131,8 +3285,8 @@ __export(wiki_client_exports, {
|
|
|
3131
3285
|
listDocuments: () => listDocuments,
|
|
3132
3286
|
listWorkspaces: () => listWorkspaces
|
|
3133
3287
|
});
|
|
3134
|
-
async function wikiFetch(config2,
|
|
3135
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
3288
|
+
async function wikiFetch(config2, path20, method = "GET", body) {
|
|
3289
|
+
const url = `${config2.baseUrl}/api/v1${path20}`;
|
|
3136
3290
|
const headers = {
|
|
3137
3291
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
3138
3292
|
"Content-Type": "application/json"
|
|
@@ -3165,7 +3319,7 @@ async function wikiFetch(config2, path19, method = "GET", body) {
|
|
|
3165
3319
|
}
|
|
3166
3320
|
}
|
|
3167
3321
|
if (!response.ok) {
|
|
3168
|
-
throw new Error(`Wiki API ${method} ${
|
|
3322
|
+
throw new Error(`Wiki API ${method} ${path20}: ${response.status} ${response.statusText}`);
|
|
3169
3323
|
}
|
|
3170
3324
|
return response.json();
|
|
3171
3325
|
} finally {
|
|
@@ -3259,7 +3413,7 @@ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
|
|
|
3259
3413
|
var init_wiki_client = __esm({
|
|
3260
3414
|
"src/lib/wiki-client.ts"() {
|
|
3261
3415
|
"use strict";
|
|
3262
|
-
LOCAL_WIKI_URL = "http://localhost:3001";
|
|
3416
|
+
LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
|
|
3263
3417
|
REQUEST_TIMEOUT_MS2 = 8e3;
|
|
3264
3418
|
}
|
|
3265
3419
|
});
|
|
@@ -4480,18 +4634,69 @@ var init_provider_table = __esm({
|
|
|
4480
4634
|
}
|
|
4481
4635
|
});
|
|
4482
4636
|
|
|
4483
|
-
// src/lib/
|
|
4484
|
-
|
|
4637
|
+
// src/lib/runtime-table.ts
|
|
4638
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
4639
|
+
var init_runtime_table = __esm({
|
|
4640
|
+
"src/lib/runtime-table.ts"() {
|
|
4641
|
+
"use strict";
|
|
4642
|
+
RUNTIME_TABLE = {
|
|
4643
|
+
codex: {
|
|
4644
|
+
binary: "codex",
|
|
4645
|
+
launchMode: "exec",
|
|
4646
|
+
autoApproveFlag: "--full-auto",
|
|
4647
|
+
inlineFlag: "--no-alt-screen",
|
|
4648
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
4649
|
+
defaultModel: "gpt-5.4"
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
DEFAULT_RUNTIME = "claude";
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4655
|
+
|
|
4656
|
+
// src/lib/agent-config.ts
|
|
4657
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
4485
4658
|
import path8 from "path";
|
|
4659
|
+
function loadAgentConfig() {
|
|
4660
|
+
if (!existsSync7(AGENT_CONFIG_PATH)) return {};
|
|
4661
|
+
try {
|
|
4662
|
+
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
4663
|
+
} catch {
|
|
4664
|
+
return {};
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
function getAgentRuntime(agentId) {
|
|
4668
|
+
const config2 = loadAgentConfig();
|
|
4669
|
+
const entry = config2[agentId];
|
|
4670
|
+
if (entry) return entry;
|
|
4671
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
4672
|
+
}
|
|
4673
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
4674
|
+
var init_agent_config = __esm({
|
|
4675
|
+
"src/lib/agent-config.ts"() {
|
|
4676
|
+
"use strict";
|
|
4677
|
+
init_config();
|
|
4678
|
+
init_runtime_table();
|
|
4679
|
+
AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
|
|
4680
|
+
DEFAULT_MODELS = {
|
|
4681
|
+
claude: "claude-opus-4",
|
|
4682
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
4683
|
+
opencode: "minimax-m2.7"
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
});
|
|
4687
|
+
|
|
4688
|
+
// src/lib/intercom-queue.ts
|
|
4689
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
4690
|
+
import path9 from "path";
|
|
4486
4691
|
import os6 from "os";
|
|
4487
4692
|
function ensureDir() {
|
|
4488
|
-
const dir =
|
|
4489
|
-
if (!
|
|
4693
|
+
const dir = path9.dirname(QUEUE_PATH);
|
|
4694
|
+
if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
|
|
4490
4695
|
}
|
|
4491
4696
|
function readQueue() {
|
|
4492
4697
|
try {
|
|
4493
|
-
if (!
|
|
4494
|
-
return JSON.parse(
|
|
4698
|
+
if (!existsSync8(QUEUE_PATH)) return [];
|
|
4699
|
+
return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
|
|
4495
4700
|
} catch {
|
|
4496
4701
|
return [];
|
|
4497
4702
|
}
|
|
@@ -4499,7 +4704,7 @@ function readQueue() {
|
|
|
4499
4704
|
function writeQueue(queue) {
|
|
4500
4705
|
ensureDir();
|
|
4501
4706
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
4502
|
-
|
|
4707
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
4503
4708
|
renameSync3(tmp, QUEUE_PATH);
|
|
4504
4709
|
}
|
|
4505
4710
|
function queueIntercom(targetSession, reason) {
|
|
@@ -4523,25 +4728,25 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
4523
4728
|
var init_intercom_queue = __esm({
|
|
4524
4729
|
"src/lib/intercom-queue.ts"() {
|
|
4525
4730
|
"use strict";
|
|
4526
|
-
QUEUE_PATH =
|
|
4731
|
+
QUEUE_PATH = path9.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
4527
4732
|
TTL_MS = 60 * 60 * 1e3;
|
|
4528
|
-
INTERCOM_LOG =
|
|
4733
|
+
INTERCOM_LOG = path9.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
4529
4734
|
}
|
|
4530
4735
|
});
|
|
4531
4736
|
|
|
4532
4737
|
// src/lib/license.ts
|
|
4533
|
-
import { readFileSync as
|
|
4738
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
4534
4739
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
4535
|
-
import
|
|
4740
|
+
import path10 from "path";
|
|
4536
4741
|
import { jwtVerify, importSPKI } from "jose";
|
|
4537
4742
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
4538
4743
|
var init_license = __esm({
|
|
4539
4744
|
"src/lib/license.ts"() {
|
|
4540
4745
|
"use strict";
|
|
4541
4746
|
init_config();
|
|
4542
|
-
LICENSE_PATH =
|
|
4543
|
-
CACHE_PATH =
|
|
4544
|
-
DEVICE_ID_PATH =
|
|
4747
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
4748
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
4749
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
4545
4750
|
PLAN_LIMITS = {
|
|
4546
4751
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
4547
4752
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -4553,12 +4758,12 @@ var init_license = __esm({
|
|
|
4553
4758
|
});
|
|
4554
4759
|
|
|
4555
4760
|
// src/lib/plan-limits.ts
|
|
4556
|
-
import { readFileSync as
|
|
4557
|
-
import
|
|
4761
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
4762
|
+
import path11 from "path";
|
|
4558
4763
|
function getLicenseSync() {
|
|
4559
4764
|
try {
|
|
4560
|
-
if (!
|
|
4561
|
-
const raw = JSON.parse(
|
|
4765
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
4766
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
4562
4767
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
4563
4768
|
const parts = raw.token.split(".");
|
|
4564
4769
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -4596,8 +4801,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
4596
4801
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
4597
4802
|
let count = 0;
|
|
4598
4803
|
try {
|
|
4599
|
-
if (
|
|
4600
|
-
const raw =
|
|
4804
|
+
if (existsSync10(filePath)) {
|
|
4805
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
4601
4806
|
const employees = JSON.parse(raw);
|
|
4602
4807
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
4603
4808
|
}
|
|
@@ -4626,19 +4831,19 @@ var init_plan_limits = __esm({
|
|
|
4626
4831
|
this.name = "PlanLimitError";
|
|
4627
4832
|
}
|
|
4628
4833
|
};
|
|
4629
|
-
CACHE_PATH2 =
|
|
4834
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
4630
4835
|
}
|
|
4631
4836
|
});
|
|
4632
4837
|
|
|
4633
4838
|
// src/lib/notifications.ts
|
|
4634
4839
|
import crypto3 from "crypto";
|
|
4635
|
-
import
|
|
4840
|
+
import path12 from "path";
|
|
4636
4841
|
import os7 from "os";
|
|
4637
4842
|
import {
|
|
4638
|
-
readFileSync as
|
|
4843
|
+
readFileSync as readFileSync10,
|
|
4639
4844
|
readdirSync as readdirSync2,
|
|
4640
4845
|
unlinkSync as unlinkSync3,
|
|
4641
|
-
existsSync as
|
|
4846
|
+
existsSync as existsSync11,
|
|
4642
4847
|
rmdirSync
|
|
4643
4848
|
} from "fs";
|
|
4644
4849
|
async function writeNotification(notification) {
|
|
@@ -4742,10 +4947,11 @@ var init_task_scope = __esm({
|
|
|
4742
4947
|
|
|
4743
4948
|
// src/lib/tasks-crud.ts
|
|
4744
4949
|
import crypto5 from "crypto";
|
|
4745
|
-
import
|
|
4950
|
+
import path13 from "path";
|
|
4951
|
+
import os8 from "os";
|
|
4746
4952
|
import { execSync as execSync4 } from "child_process";
|
|
4747
4953
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
4748
|
-
import { existsSync as
|
|
4954
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
4749
4955
|
async function writeCheckpoint(input) {
|
|
4750
4956
|
const client = getClient();
|
|
4751
4957
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -4786,6 +4992,35 @@ function extractParentFromContext(contextBody) {
|
|
|
4786
4992
|
function slugify(title) {
|
|
4787
4993
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4788
4994
|
}
|
|
4995
|
+
function buildKeywordIndex() {
|
|
4996
|
+
const idx = /* @__PURE__ */ new Map();
|
|
4997
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
4998
|
+
for (const kw of keywords) {
|
|
4999
|
+
const existing = idx.get(kw) ?? [];
|
|
5000
|
+
existing.push(role);
|
|
5001
|
+
idx.set(kw, existing);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
return idx;
|
|
5005
|
+
}
|
|
5006
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
5007
|
+
const employees = loadEmployeesSync();
|
|
5008
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
5009
|
+
if (!employee) return void 0;
|
|
5010
|
+
const assigneeRole = employee.role;
|
|
5011
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
5012
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
5013
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
5014
|
+
if (text.includes(keyword)) {
|
|
5015
|
+
for (const role of roles) matchedRoles.add(role);
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
if (matchedRoles.size === 0) return void 0;
|
|
5019
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
5020
|
+
if (assigneeRole === "COO") return void 0;
|
|
5021
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
5022
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
5023
|
+
}
|
|
4789
5024
|
async function resolveTask(client, identifier, scopeSession) {
|
|
4790
5025
|
const scope = sessionScopeFilter(scopeSession);
|
|
4791
5026
|
let result = await client.execute({
|
|
@@ -4835,7 +5070,14 @@ async function createTaskCore(input) {
|
|
|
4835
5070
|
const id = crypto5.randomUUID();
|
|
4836
5071
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4837
5072
|
const slug = slugify(input.title);
|
|
4838
|
-
|
|
5073
|
+
let earlySessionScope = null;
|
|
5074
|
+
try {
|
|
5075
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
5076
|
+
earlySessionScope = resolveExeSession2();
|
|
5077
|
+
} catch {
|
|
5078
|
+
}
|
|
5079
|
+
const scope = earlySessionScope ?? "default";
|
|
5080
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
4839
5081
|
let blockedById = null;
|
|
4840
5082
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
4841
5083
|
if (input.blockedBy) {
|
|
@@ -4875,22 +5117,24 @@ async function createTaskCore(input) {
|
|
|
4875
5117
|
if (dupCheck.rows.length > 0) {
|
|
4876
5118
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
4877
5119
|
}
|
|
5120
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
5121
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
5122
|
+
if (laneWarning) {
|
|
5123
|
+
warning = warning ? `${warning}
|
|
5124
|
+
${laneWarning}` : laneWarning;
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
4878
5127
|
if (input.baseDir) {
|
|
4879
5128
|
try {
|
|
4880
|
-
await mkdir4(
|
|
4881
|
-
await mkdir4(
|
|
5129
|
+
await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
5130
|
+
await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
4882
5131
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
4883
5132
|
await ensureGitignoreExe(input.baseDir);
|
|
4884
5133
|
} catch {
|
|
4885
5134
|
}
|
|
4886
5135
|
}
|
|
4887
5136
|
const complexity = input.complexity ?? "standard";
|
|
4888
|
-
|
|
4889
|
-
try {
|
|
4890
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4891
|
-
sessionScope = resolveExeSession2();
|
|
4892
|
-
} catch {
|
|
4893
|
-
}
|
|
5137
|
+
const sessionScope = earlySessionScope;
|
|
4894
5138
|
await client.execute({
|
|
4895
5139
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
4896
5140
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -4917,6 +5161,43 @@ async function createTaskCore(input) {
|
|
|
4917
5161
|
now
|
|
4918
5162
|
]
|
|
4919
5163
|
});
|
|
5164
|
+
if (input.baseDir) {
|
|
5165
|
+
try {
|
|
5166
|
+
const EXE_OS_DIR = path13.join(os8.homedir(), ".exe-os");
|
|
5167
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
5168
|
+
const mdDir = path13.dirname(mdPath);
|
|
5169
|
+
if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
5170
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
5171
|
+
const mdContent = `# ${input.title}
|
|
5172
|
+
|
|
5173
|
+
**ID:** ${id}
|
|
5174
|
+
**Status:** ${initialStatus}
|
|
5175
|
+
**Priority:** ${input.priority}
|
|
5176
|
+
**Assigned by:** ${input.assignedBy}
|
|
5177
|
+
**Assigned to:** ${input.assignedTo}
|
|
5178
|
+
**Project:** ${input.projectName}
|
|
5179
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
5180
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
5181
|
+
**Reviewer:** ${reviewer}
|
|
5182
|
+
|
|
5183
|
+
## Context
|
|
5184
|
+
|
|
5185
|
+
${input.context}
|
|
5186
|
+
|
|
5187
|
+
## MANDATORY: When done
|
|
5188
|
+
|
|
5189
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
5190
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
5191
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
5192
|
+
`;
|
|
5193
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
5194
|
+
} catch (err) {
|
|
5195
|
+
process.stderr.write(
|
|
5196
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
5197
|
+
`
|
|
5198
|
+
);
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
4920
5201
|
return {
|
|
4921
5202
|
id,
|
|
4922
5203
|
title: input.title,
|
|
@@ -5109,7 +5390,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
5109
5390
|
return { row, taskFile, now, taskId };
|
|
5110
5391
|
}
|
|
5111
5392
|
}
|
|
5112
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
5393
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
5113
5394
|
process.stderr.write(
|
|
5114
5395
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
5115
5396
|
`
|
|
@@ -5174,9 +5455,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
5174
5455
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
5175
5456
|
}
|
|
5176
5457
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
5177
|
-
const archPath =
|
|
5458
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
5178
5459
|
try {
|
|
5179
|
-
if (
|
|
5460
|
+
if (existsSync12(archPath)) return;
|
|
5180
5461
|
const template = [
|
|
5181
5462
|
`# ${projectName} \u2014 System Architecture`,
|
|
5182
5463
|
"",
|
|
@@ -5209,10 +5490,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
5209
5490
|
}
|
|
5210
5491
|
}
|
|
5211
5492
|
async function ensureGitignoreExe(baseDir) {
|
|
5212
|
-
const gitignorePath =
|
|
5493
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
5213
5494
|
try {
|
|
5214
|
-
if (
|
|
5215
|
-
const content =
|
|
5495
|
+
if (existsSync12(gitignorePath)) {
|
|
5496
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
5216
5497
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
5217
5498
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
5218
5499
|
} else {
|
|
@@ -5221,20 +5502,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
5221
5502
|
} catch {
|
|
5222
5503
|
}
|
|
5223
5504
|
}
|
|
5224
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
5505
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
5225
5506
|
var init_tasks_crud = __esm({
|
|
5226
5507
|
"src/lib/tasks-crud.ts"() {
|
|
5227
5508
|
"use strict";
|
|
5228
5509
|
init_database();
|
|
5229
5510
|
init_task_scope();
|
|
5511
|
+
init_employees();
|
|
5512
|
+
LANE_KEYWORDS = {
|
|
5513
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
5514
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
5515
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
5516
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
5517
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
5518
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
5519
|
+
};
|
|
5520
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
5230
5521
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
5231
5522
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
5232
5523
|
}
|
|
5233
5524
|
});
|
|
5234
5525
|
|
|
5235
5526
|
// src/lib/tasks-review.ts
|
|
5236
|
-
import
|
|
5237
|
-
import { existsSync as
|
|
5527
|
+
import path14 from "path";
|
|
5528
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
5238
5529
|
async function countPendingReviews(sessionScope) {
|
|
5239
5530
|
const client = getClient();
|
|
5240
5531
|
if (sessionScope) {
|
|
@@ -5256,7 +5547,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
5256
5547
|
const result2 = await client.execute({
|
|
5257
5548
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5258
5549
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
5259
|
-
AND
|
|
5550
|
+
AND session_scope = ?`,
|
|
5260
5551
|
args: [sinceIso, sessionScope]
|
|
5261
5552
|
});
|
|
5262
5553
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -5274,7 +5565,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
5274
5565
|
const result2 = await client.execute({
|
|
5275
5566
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
5276
5567
|
WHERE status = 'needs_review'
|
|
5277
|
-
AND
|
|
5568
|
+
AND session_scope = ?
|
|
5278
5569
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
5279
5570
|
args: [sessionScope, limit]
|
|
5280
5571
|
});
|
|
@@ -5395,14 +5686,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5395
5686
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
5396
5687
|
const agent = parts[1];
|
|
5397
5688
|
const slug = parts.slice(2).join("-");
|
|
5398
|
-
const
|
|
5689
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
5399
5690
|
const result = await client.execute({
|
|
5400
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
5401
|
-
args: [now,
|
|
5691
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
5692
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
5402
5693
|
});
|
|
5403
5694
|
if (result.rowsAffected > 0) {
|
|
5404
5695
|
process.stderr.write(
|
|
5405
|
-
`[review-cleanup] Cascaded original task to done
|
|
5696
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
5406
5697
|
`
|
|
5407
5698
|
);
|
|
5408
5699
|
}
|
|
@@ -5415,11 +5706,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5415
5706
|
);
|
|
5416
5707
|
}
|
|
5417
5708
|
try {
|
|
5418
|
-
const cacheDir =
|
|
5419
|
-
if (
|
|
5709
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
5710
|
+
if (existsSync13(cacheDir)) {
|
|
5420
5711
|
for (const f of readdirSync3(cacheDir)) {
|
|
5421
5712
|
if (f.startsWith("review-notified-")) {
|
|
5422
|
-
unlinkSync4(
|
|
5713
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
5423
5714
|
}
|
|
5424
5715
|
}
|
|
5425
5716
|
}
|
|
@@ -5440,7 +5731,7 @@ var init_tasks_review = __esm({
|
|
|
5440
5731
|
});
|
|
5441
5732
|
|
|
5442
5733
|
// src/lib/tasks-chain.ts
|
|
5443
|
-
import
|
|
5734
|
+
import path15 from "path";
|
|
5444
5735
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
5445
5736
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
5446
5737
|
const client = getClient();
|
|
@@ -5457,7 +5748,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5457
5748
|
});
|
|
5458
5749
|
for (const ur of unblockedRows.rows) {
|
|
5459
5750
|
try {
|
|
5460
|
-
const ubFile =
|
|
5751
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
5461
5752
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
5462
5753
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
5463
5754
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -5526,7 +5817,7 @@ var init_tasks_chain = __esm({
|
|
|
5526
5817
|
|
|
5527
5818
|
// src/lib/project-name.ts
|
|
5528
5819
|
import { execSync as execSync5 } from "child_process";
|
|
5529
|
-
import
|
|
5820
|
+
import path16 from "path";
|
|
5530
5821
|
function getProjectName(cwd) {
|
|
5531
5822
|
const dir = cwd ?? process.cwd();
|
|
5532
5823
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -5539,7 +5830,7 @@ function getProjectName(cwd) {
|
|
|
5539
5830
|
timeout: 2e3,
|
|
5540
5831
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5541
5832
|
}).trim();
|
|
5542
|
-
repoRoot =
|
|
5833
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
5543
5834
|
} catch {
|
|
5544
5835
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
5545
5836
|
cwd: dir,
|
|
@@ -5548,11 +5839,11 @@ function getProjectName(cwd) {
|
|
|
5548
5839
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5549
5840
|
}).trim();
|
|
5550
5841
|
}
|
|
5551
|
-
_cached2 =
|
|
5842
|
+
_cached2 = path16.basename(repoRoot);
|
|
5552
5843
|
_cachedCwd = dir;
|
|
5553
5844
|
return _cached2;
|
|
5554
5845
|
} catch {
|
|
5555
|
-
_cached2 =
|
|
5846
|
+
_cached2 = path16.basename(dir);
|
|
5556
5847
|
_cachedCwd = dir;
|
|
5557
5848
|
return _cached2;
|
|
5558
5849
|
}
|
|
@@ -5584,7 +5875,7 @@ function findSessionForProject(projectName) {
|
|
|
5584
5875
|
const sessions = listSessions();
|
|
5585
5876
|
for (const s of sessions) {
|
|
5586
5877
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
5587
|
-
if (proj === projectName &&
|
|
5878
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
5588
5879
|
}
|
|
5589
5880
|
return null;
|
|
5590
5881
|
}
|
|
@@ -5630,7 +5921,7 @@ var init_session_scope = __esm({
|
|
|
5630
5921
|
|
|
5631
5922
|
// src/lib/tasks-notify.ts
|
|
5632
5923
|
async function dispatchTaskToEmployee(input) {
|
|
5633
|
-
if (
|
|
5924
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
5634
5925
|
let crossProject = false;
|
|
5635
5926
|
if (input.projectName) {
|
|
5636
5927
|
try {
|
|
@@ -6025,8 +6316,8 @@ __export(tasks_exports, {
|
|
|
6025
6316
|
updateTaskStatus: () => updateTaskStatus,
|
|
6026
6317
|
writeCheckpoint: () => writeCheckpoint
|
|
6027
6318
|
});
|
|
6028
|
-
import
|
|
6029
|
-
import { writeFileSync as
|
|
6319
|
+
import path17 from "path";
|
|
6320
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
6030
6321
|
async function createTask(input) {
|
|
6031
6322
|
const result = await createTaskCore(input);
|
|
6032
6323
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -6045,11 +6336,11 @@ async function updateTask(input) {
|
|
|
6045
6336
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
6046
6337
|
try {
|
|
6047
6338
|
const agent = String(row.assigned_to);
|
|
6048
|
-
const cacheDir =
|
|
6049
|
-
const cachePath =
|
|
6339
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
6340
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
6050
6341
|
if (input.status === "in_progress") {
|
|
6051
|
-
|
|
6052
|
-
|
|
6342
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
6343
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
6053
6344
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
6054
6345
|
try {
|
|
6055
6346
|
unlinkSync5(cachePath);
|
|
@@ -6109,7 +6400,7 @@ async function updateTask(input) {
|
|
|
6109
6400
|
}
|
|
6110
6401
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6111
6402
|
if (isTerminal) {
|
|
6112
|
-
const isCoordinator =
|
|
6403
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
6113
6404
|
if (!isCoordinator) {
|
|
6114
6405
|
notifyTaskDone();
|
|
6115
6406
|
}
|
|
@@ -6134,7 +6425,7 @@ async function updateTask(input) {
|
|
|
6134
6425
|
}
|
|
6135
6426
|
}
|
|
6136
6427
|
}
|
|
6137
|
-
if (input.status === "done" &&
|
|
6428
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6138
6429
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6139
6430
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
6140
6431
|
taskId,
|
|
@@ -6150,7 +6441,7 @@ async function updateTask(input) {
|
|
|
6150
6441
|
});
|
|
6151
6442
|
}
|
|
6152
6443
|
let nextTask;
|
|
6153
|
-
if (isTerminal &&
|
|
6444
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
6154
6445
|
try {
|
|
6155
6446
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
6156
6447
|
} catch {
|
|
@@ -6516,13 +6807,13 @@ __export(tmux_routing_exports, {
|
|
|
6516
6807
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
6517
6808
|
});
|
|
6518
6809
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
6519
|
-
import { readFileSync as
|
|
6520
|
-
import
|
|
6521
|
-
import
|
|
6810
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, existsSync as existsSync14, appendFileSync } from "fs";
|
|
6811
|
+
import path18 from "path";
|
|
6812
|
+
import os9 from "os";
|
|
6522
6813
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6523
6814
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
6524
6815
|
function spawnLockPath(sessionName) {
|
|
6525
|
-
return
|
|
6816
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
6526
6817
|
}
|
|
6527
6818
|
function isProcessAlive(pid) {
|
|
6528
6819
|
try {
|
|
@@ -6533,13 +6824,13 @@ function isProcessAlive(pid) {
|
|
|
6533
6824
|
}
|
|
6534
6825
|
}
|
|
6535
6826
|
function acquireSpawnLock2(sessionName) {
|
|
6536
|
-
if (!
|
|
6537
|
-
|
|
6827
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
6828
|
+
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
6538
6829
|
}
|
|
6539
6830
|
const lockFile = spawnLockPath(sessionName);
|
|
6540
|
-
if (
|
|
6831
|
+
if (existsSync14(lockFile)) {
|
|
6541
6832
|
try {
|
|
6542
|
-
const lock = JSON.parse(
|
|
6833
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
6543
6834
|
const age = Date.now() - lock.timestamp;
|
|
6544
6835
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
6545
6836
|
return false;
|
|
@@ -6547,7 +6838,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
6547
6838
|
} catch {
|
|
6548
6839
|
}
|
|
6549
6840
|
}
|
|
6550
|
-
|
|
6841
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
6551
6842
|
return true;
|
|
6552
6843
|
}
|
|
6553
6844
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -6559,13 +6850,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
6559
6850
|
function resolveBehaviorsExporterScript() {
|
|
6560
6851
|
try {
|
|
6561
6852
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6562
|
-
const scriptPath =
|
|
6563
|
-
|
|
6853
|
+
const scriptPath = path18.join(
|
|
6854
|
+
path18.dirname(thisFile),
|
|
6564
6855
|
"..",
|
|
6565
6856
|
"bin",
|
|
6566
6857
|
"exe-export-behaviors.js"
|
|
6567
6858
|
);
|
|
6568
|
-
return
|
|
6859
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
6569
6860
|
} catch {
|
|
6570
6861
|
return null;
|
|
6571
6862
|
}
|
|
@@ -6631,12 +6922,12 @@ function extractRootExe(name) {
|
|
|
6631
6922
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
6632
6923
|
}
|
|
6633
6924
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
6634
|
-
if (!
|
|
6635
|
-
|
|
6925
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
6926
|
+
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
6636
6927
|
}
|
|
6637
6928
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
6638
|
-
const filePath =
|
|
6639
|
-
|
|
6929
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
6930
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
6640
6931
|
parentExe: rootExe,
|
|
6641
6932
|
dispatchedBy: dispatchedBy || rootExe,
|
|
6642
6933
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6644,7 +6935,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
6644
6935
|
}
|
|
6645
6936
|
function getParentExe(sessionKey) {
|
|
6646
6937
|
try {
|
|
6647
|
-
const data = JSON.parse(
|
|
6938
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
6648
6939
|
return data.parentExe || null;
|
|
6649
6940
|
} catch {
|
|
6650
6941
|
return null;
|
|
@@ -6652,8 +6943,8 @@ function getParentExe(sessionKey) {
|
|
|
6652
6943
|
}
|
|
6653
6944
|
function getDispatchedBy(sessionKey) {
|
|
6654
6945
|
try {
|
|
6655
|
-
const data = JSON.parse(
|
|
6656
|
-
|
|
6946
|
+
const data = JSON.parse(readFileSync12(
|
|
6947
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
6657
6948
|
"utf8"
|
|
6658
6949
|
));
|
|
6659
6950
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -6714,32 +7005,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
6714
7005
|
}
|
|
6715
7006
|
function readDebounceState() {
|
|
6716
7007
|
try {
|
|
6717
|
-
if (!
|
|
6718
|
-
|
|
7008
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
7009
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
7010
|
+
const state = {};
|
|
7011
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
7012
|
+
if (typeof val === "number") {
|
|
7013
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
7014
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
7015
|
+
state[key] = val;
|
|
7016
|
+
}
|
|
7017
|
+
}
|
|
7018
|
+
return state;
|
|
6719
7019
|
} catch {
|
|
6720
7020
|
return {};
|
|
6721
7021
|
}
|
|
6722
7022
|
}
|
|
6723
7023
|
function writeDebounceState(state) {
|
|
6724
7024
|
try {
|
|
6725
|
-
if (!
|
|
6726
|
-
|
|
7025
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
7026
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
6727
7027
|
} catch {
|
|
6728
7028
|
}
|
|
6729
7029
|
}
|
|
6730
7030
|
function isDebounced(targetSession) {
|
|
6731
7031
|
const state = readDebounceState();
|
|
6732
|
-
const
|
|
6733
|
-
|
|
7032
|
+
const entry = state[targetSession];
|
|
7033
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
7034
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
7035
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
7036
|
+
state[targetSession].pending++;
|
|
7037
|
+
writeDebounceState(state);
|
|
7038
|
+
return true;
|
|
7039
|
+
}
|
|
7040
|
+
return false;
|
|
6734
7041
|
}
|
|
6735
7042
|
function recordDebounce(targetSession) {
|
|
6736
7043
|
const state = readDebounceState();
|
|
6737
|
-
state[targetSession]
|
|
7044
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
7045
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
6738
7046
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
6739
7047
|
for (const key of Object.keys(state)) {
|
|
6740
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
7048
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
6741
7049
|
}
|
|
6742
7050
|
writeDebounceState(state);
|
|
7051
|
+
return batched;
|
|
6743
7052
|
}
|
|
6744
7053
|
function logIntercom(msg) {
|
|
6745
7054
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -6784,7 +7093,7 @@ function sendIntercom(targetSession) {
|
|
|
6784
7093
|
return "skipped_exe";
|
|
6785
7094
|
}
|
|
6786
7095
|
if (isDebounced(targetSession)) {
|
|
6787
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
7096
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
6788
7097
|
return "debounced";
|
|
6789
7098
|
}
|
|
6790
7099
|
try {
|
|
@@ -6796,14 +7105,14 @@ function sendIntercom(targetSession) {
|
|
|
6796
7105
|
const sessionState = getSessionState(targetSession);
|
|
6797
7106
|
if (sessionState === "no_claude") {
|
|
6798
7107
|
queueIntercom(targetSession, "claude not running in session");
|
|
6799
|
-
recordDebounce(targetSession);
|
|
6800
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
7108
|
+
const batched2 = recordDebounce(targetSession);
|
|
7109
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
6801
7110
|
return "queued";
|
|
6802
7111
|
}
|
|
6803
7112
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
6804
7113
|
queueIntercom(targetSession, "session busy at send time");
|
|
6805
|
-
recordDebounce(targetSession);
|
|
6806
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
7114
|
+
const batched2 = recordDebounce(targetSession);
|
|
7115
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
6807
7116
|
return "queued";
|
|
6808
7117
|
}
|
|
6809
7118
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -6811,8 +7120,8 @@ function sendIntercom(targetSession) {
|
|
|
6811
7120
|
transport.sendKeys(targetSession, "q");
|
|
6812
7121
|
}
|
|
6813
7122
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
6814
|
-
recordDebounce(targetSession);
|
|
6815
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
7123
|
+
const batched = recordDebounce(targetSession);
|
|
7124
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
6816
7125
|
return "delivered";
|
|
6817
7126
|
} catch {
|
|
6818
7127
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -6842,7 +7151,7 @@ function notifyParentExe(sessionKey) {
|
|
|
6842
7151
|
return true;
|
|
6843
7152
|
}
|
|
6844
7153
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
6845
|
-
if (
|
|
7154
|
+
if (isCoordinatorName(employeeName)) {
|
|
6846
7155
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
6847
7156
|
}
|
|
6848
7157
|
try {
|
|
@@ -6914,26 +7223,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6914
7223
|
const transport = getTransport();
|
|
6915
7224
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
6916
7225
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6917
|
-
const logDir =
|
|
6918
|
-
const logFile =
|
|
6919
|
-
if (!
|
|
6920
|
-
|
|
7226
|
+
const logDir = path18.join(os9.homedir(), ".exe-os", "session-logs");
|
|
7227
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
7228
|
+
if (!existsSync14(logDir)) {
|
|
7229
|
+
mkdirSync8(logDir, { recursive: true });
|
|
6921
7230
|
}
|
|
6922
7231
|
transport.kill(sessionName);
|
|
6923
7232
|
let cleanupSuffix = "";
|
|
6924
7233
|
try {
|
|
6925
7234
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6926
|
-
const cleanupScript =
|
|
6927
|
-
if (
|
|
7235
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
7236
|
+
if (existsSync14(cleanupScript)) {
|
|
6928
7237
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
6929
7238
|
}
|
|
6930
7239
|
} catch {
|
|
6931
7240
|
}
|
|
6932
7241
|
try {
|
|
6933
|
-
const claudeJsonPath =
|
|
7242
|
+
const claudeJsonPath = path18.join(os9.homedir(), ".claude.json");
|
|
6934
7243
|
let claudeJson = {};
|
|
6935
7244
|
try {
|
|
6936
|
-
claudeJson = JSON.parse(
|
|
7245
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6937
7246
|
} catch {
|
|
6938
7247
|
}
|
|
6939
7248
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6941,17 +7250,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6941
7250
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6942
7251
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6943
7252
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6944
|
-
|
|
7253
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6945
7254
|
} catch {
|
|
6946
7255
|
}
|
|
6947
7256
|
try {
|
|
6948
|
-
const settingsDir =
|
|
7257
|
+
const settingsDir = path18.join(os9.homedir(), ".claude", "projects");
|
|
6949
7258
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6950
|
-
const projSettingsDir =
|
|
6951
|
-
const settingsPath =
|
|
7259
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
7260
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
6952
7261
|
let settings = {};
|
|
6953
7262
|
try {
|
|
6954
|
-
settings = JSON.parse(
|
|
7263
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6955
7264
|
} catch {
|
|
6956
7265
|
}
|
|
6957
7266
|
const perms = settings.permissions ?? {};
|
|
@@ -6979,21 +7288,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6979
7288
|
if (changed) {
|
|
6980
7289
|
perms.allow = allow;
|
|
6981
7290
|
settings.permissions = perms;
|
|
6982
|
-
|
|
6983
|
-
|
|
7291
|
+
mkdirSync8(projSettingsDir, { recursive: true });
|
|
7292
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6984
7293
|
}
|
|
6985
7294
|
} catch {
|
|
6986
7295
|
}
|
|
6987
7296
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
6988
7297
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
6989
|
-
const
|
|
7298
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
7299
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
7300
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
7301
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
6990
7302
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
6991
7303
|
let identityFlag = "";
|
|
6992
7304
|
let behaviorsFlag = "";
|
|
6993
7305
|
let legacyFallbackWarned = false;
|
|
6994
7306
|
if (!useExeAgent && !useBinSymlink) {
|
|
6995
|
-
const identityPath =
|
|
6996
|
-
|
|
7307
|
+
const identityPath = path18.join(
|
|
7308
|
+
os9.homedir(),
|
|
6997
7309
|
".exe-os",
|
|
6998
7310
|
"identity",
|
|
6999
7311
|
`${employeeName}.md`
|
|
@@ -7002,13 +7314,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7002
7314
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
7003
7315
|
if (hasAgentFlag) {
|
|
7004
7316
|
identityFlag = ` --agent ${employeeName}`;
|
|
7005
|
-
} else if (
|
|
7317
|
+
} else if (existsSync14(identityPath)) {
|
|
7006
7318
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
7007
7319
|
legacyFallbackWarned = true;
|
|
7008
7320
|
}
|
|
7009
7321
|
const behaviorsFile = exportBehaviorsSync(
|
|
7010
7322
|
employeeName,
|
|
7011
|
-
|
|
7323
|
+
path18.basename(spawnCwd),
|
|
7012
7324
|
sessionName
|
|
7013
7325
|
);
|
|
7014
7326
|
if (behaviorsFile) {
|
|
@@ -7023,16 +7335,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7023
7335
|
}
|
|
7024
7336
|
let sessionContextFlag = "";
|
|
7025
7337
|
try {
|
|
7026
|
-
const ctxDir =
|
|
7027
|
-
|
|
7028
|
-
const ctxFile =
|
|
7338
|
+
const ctxDir = path18.join(os9.homedir(), ".exe-os", "session-cache");
|
|
7339
|
+
mkdirSync8(ctxDir, { recursive: true });
|
|
7340
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
7029
7341
|
const ctxContent = [
|
|
7030
7342
|
`## Session Context`,
|
|
7031
7343
|
`You are running in tmux session: ${sessionName}.`,
|
|
7032
7344
|
`Your parent coordinator session is ${exeSession}.`,
|
|
7033
7345
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
7034
7346
|
].join("\n");
|
|
7035
|
-
|
|
7347
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
7036
7348
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7037
7349
|
} catch {
|
|
7038
7350
|
}
|
|
@@ -7046,9 +7358,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7046
7358
|
}
|
|
7047
7359
|
}
|
|
7048
7360
|
}
|
|
7361
|
+
if (useCodex) {
|
|
7362
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
7363
|
+
if (codexCfg?.apiKeyEnv) {
|
|
7364
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
7365
|
+
if (keyVal) {
|
|
7366
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
7370
|
+
}
|
|
7371
|
+
if (useOpencode) {
|
|
7372
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
7373
|
+
if (ocCfg?.apiKeyEnv) {
|
|
7374
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
7375
|
+
if (keyVal) {
|
|
7376
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
7377
|
+
}
|
|
7378
|
+
}
|
|
7379
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
7380
|
+
}
|
|
7381
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7382
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
7383
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
7384
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7049
7387
|
let spawnCommand;
|
|
7050
7388
|
if (useExeAgent) {
|
|
7051
7389
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
7390
|
+
} else if (useCodex) {
|
|
7391
|
+
process.stderr.write(
|
|
7392
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
7393
|
+
`
|
|
7394
|
+
);
|
|
7395
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
7396
|
+
} else if (useOpencode) {
|
|
7397
|
+
const binName = `${employeeName}-opencode`;
|
|
7398
|
+
process.stderr.write(
|
|
7399
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
7400
|
+
`
|
|
7401
|
+
);
|
|
7402
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
7052
7403
|
} else if (useBinSymlink) {
|
|
7053
7404
|
const binName = `${employeeName}-${ccProvider}`;
|
|
7054
7405
|
process.stderr.write(
|
|
@@ -7070,11 +7421,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7070
7421
|
transport.pipeLog(sessionName, logFile);
|
|
7071
7422
|
try {
|
|
7072
7423
|
const mySession = getMySession();
|
|
7073
|
-
const dispatchInfo =
|
|
7074
|
-
|
|
7424
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
7425
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
7075
7426
|
dispatchedBy: mySession,
|
|
7076
7427
|
rootExe: exeSession,
|
|
7077
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
7428
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
7429
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
7430
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
7078
7431
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7079
7432
|
}));
|
|
7080
7433
|
} catch {
|
|
@@ -7092,6 +7445,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7092
7445
|
booted = true;
|
|
7093
7446
|
break;
|
|
7094
7447
|
}
|
|
7448
|
+
} else if (useCodex) {
|
|
7449
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
7450
|
+
booted = true;
|
|
7451
|
+
break;
|
|
7452
|
+
}
|
|
7095
7453
|
} else {
|
|
7096
7454
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
7097
7455
|
booted = true;
|
|
@@ -7103,9 +7461,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7103
7461
|
}
|
|
7104
7462
|
if (!booted) {
|
|
7105
7463
|
releaseSpawnLock2(sessionName);
|
|
7106
|
-
|
|
7464
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
7465
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
7107
7466
|
}
|
|
7108
|
-
if (!useExeAgent) {
|
|
7467
|
+
if (!useExeAgent && !useCodex) {
|
|
7109
7468
|
try {
|
|
7110
7469
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
7111
7470
|
} catch {
|
|
@@ -7132,17 +7491,19 @@ var init_tmux_routing = __esm({
|
|
|
7132
7491
|
init_cc_agent_support();
|
|
7133
7492
|
init_mcp_prefix();
|
|
7134
7493
|
init_provider_table();
|
|
7494
|
+
init_agent_config();
|
|
7495
|
+
init_runtime_table();
|
|
7135
7496
|
init_intercom_queue();
|
|
7136
7497
|
init_plan_limits();
|
|
7137
7498
|
init_employees();
|
|
7138
|
-
SPAWN_LOCK_DIR =
|
|
7139
|
-
SESSION_CACHE =
|
|
7499
|
+
SPAWN_LOCK_DIR = path18.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
7500
|
+
SESSION_CACHE = path18.join(os9.homedir(), ".exe-os", "session-cache");
|
|
7140
7501
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7141
7502
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
7142
7503
|
VERIFY_PANE_LINES = 200;
|
|
7143
7504
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7144
|
-
INTERCOM_LOG2 =
|
|
7145
|
-
DEBOUNCE_FILE =
|
|
7505
|
+
INTERCOM_LOG2 = path18.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
7506
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
7146
7507
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
7147
7508
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
7148
7509
|
}
|
|
@@ -8051,10 +8412,49 @@ function buildPermissionContext(platform, permissions) {
|
|
|
8051
8412
|
return `[${platform.toUpperCase()} \u2014 allowed: ${parts.join(", ")}]`;
|
|
8052
8413
|
}
|
|
8053
8414
|
|
|
8415
|
+
// src/gateway/bot-errors.ts
|
|
8416
|
+
var FatalBotError = class extends Error {
|
|
8417
|
+
constructor(message, cause) {
|
|
8418
|
+
super(message);
|
|
8419
|
+
this.cause = cause;
|
|
8420
|
+
this.name = "FatalBotError";
|
|
8421
|
+
}
|
|
8422
|
+
fatal = true;
|
|
8423
|
+
};
|
|
8424
|
+
var RecoverableBotError = class extends Error {
|
|
8425
|
+
constructor(message, toolName, cause) {
|
|
8426
|
+
super(message);
|
|
8427
|
+
this.toolName = toolName;
|
|
8428
|
+
this.cause = cause;
|
|
8429
|
+
this.name = "RecoverableBotError";
|
|
8430
|
+
}
|
|
8431
|
+
recoverable = true;
|
|
8432
|
+
};
|
|
8433
|
+
var MaxStepsError = class extends Error {
|
|
8434
|
+
constructor(stepsTaken, maxSteps) {
|
|
8435
|
+
super(
|
|
8436
|
+
`Reached maximum steps (${stepsTaken}/${maxSteps}). Returning partial result.`
|
|
8437
|
+
);
|
|
8438
|
+
this.stepsTaken = stepsTaken;
|
|
8439
|
+
this.maxSteps = maxSteps;
|
|
8440
|
+
this.name = "MaxStepsError";
|
|
8441
|
+
}
|
|
8442
|
+
};
|
|
8443
|
+
function classifyError(err, toolName) {
|
|
8444
|
+
if (err instanceof FatalBotError) return err;
|
|
8445
|
+
if (err instanceof RecoverableBotError) return err;
|
|
8446
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8447
|
+
if (message.includes("401") || message.includes("403") || message.includes("authentication") || message.includes("rate_limit")) {
|
|
8448
|
+
return new FatalBotError(message, err);
|
|
8449
|
+
}
|
|
8450
|
+
return new RecoverableBotError(message, toolName, err);
|
|
8451
|
+
}
|
|
8452
|
+
|
|
8054
8453
|
// src/gateway/bot-runtime.ts
|
|
8055
8454
|
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
8056
8455
|
var MAX_TURNS = 10;
|
|
8057
8456
|
var MAX_HISTORY = 50;
|
|
8457
|
+
var DEFAULT_PLANNING_INTERVAL = 3;
|
|
8058
8458
|
function buildExecAssistantSystemPrompt(platform, permissions) {
|
|
8059
8459
|
const permContext = buildPermissionContext(platform, permissions);
|
|
8060
8460
|
return `You are the founder's executive assistant (agent_id: "ea").
|
|
@@ -8106,7 +8506,7 @@ var BotRuntime = class {
|
|
|
8106
8506
|
async processMessage(msg, permissions) {
|
|
8107
8507
|
const sessionKey = msg.chatType === "group" ? msg.channelId : msg.senderId;
|
|
8108
8508
|
const history = this.getHistory(sessionKey);
|
|
8109
|
-
history.push({ role: "user", content: msg.text });
|
|
8509
|
+
history.push({ role: "user", content: msg.text, frameType: "task" });
|
|
8110
8510
|
const systemPrompt = this.config.systemPrompt + "\n\n" + buildPermissionContext(msg.platform, permissions);
|
|
8111
8511
|
const allowedTools = filterToolsForPermissions(
|
|
8112
8512
|
this.config.tools,
|
|
@@ -8114,29 +8514,54 @@ var BotRuntime = class {
|
|
|
8114
8514
|
);
|
|
8115
8515
|
const model = this.config.model ?? DEFAULT_MODEL;
|
|
8116
8516
|
const maxTurns = this.config.maxTurns ?? MAX_TURNS;
|
|
8517
|
+
const planningInterval = this.config.planningInterval ?? DEFAULT_PLANNING_INTERVAL;
|
|
8117
8518
|
let turns = 0;
|
|
8118
8519
|
while (turns < maxTurns) {
|
|
8119
8520
|
turns++;
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8521
|
+
if (planningInterval > 0 && turns > 1 && turns % planningInterval === 1 && maxTurns - turns >= 2) {
|
|
8522
|
+
history.push({
|
|
8523
|
+
role: "user",
|
|
8524
|
+
content: "[Planning checkpoint] Review what you know so far. What facts have you gathered? What is your plan for the remaining steps? Be concise.",
|
|
8525
|
+
frameType: "planning"
|
|
8526
|
+
});
|
|
8527
|
+
}
|
|
8528
|
+
let response;
|
|
8529
|
+
try {
|
|
8530
|
+
response = await this.client.messages.create({
|
|
8531
|
+
model,
|
|
8532
|
+
max_tokens: 4096,
|
|
8533
|
+
system: systemPrompt,
|
|
8534
|
+
messages: history.map((m) => ({
|
|
8535
|
+
role: m.role,
|
|
8536
|
+
content: m.content
|
|
8537
|
+
})),
|
|
8538
|
+
tools: allowedTools.map((t) => ({
|
|
8539
|
+
name: t.name,
|
|
8540
|
+
description: t.description,
|
|
8541
|
+
input_schema: t.input_schema
|
|
8542
|
+
}))
|
|
8543
|
+
});
|
|
8544
|
+
} catch (err) {
|
|
8545
|
+
const classified = classifyError(err);
|
|
8546
|
+
if (classified instanceof FatalBotError) {
|
|
8547
|
+
const errorMsg = `Bot error: ${classified.message}`;
|
|
8548
|
+
history.push({ role: "assistant", content: errorMsg, frameType: "error" });
|
|
8549
|
+
this.trimHistory(sessionKey);
|
|
8550
|
+
return errorMsg;
|
|
8551
|
+
}
|
|
8552
|
+
history.push({
|
|
8553
|
+
role: "assistant",
|
|
8554
|
+
content: `[API error \u2014 retrying] ${classified.message}`,
|
|
8555
|
+
frameType: "error"
|
|
8556
|
+
});
|
|
8557
|
+
continue;
|
|
8558
|
+
}
|
|
8134
8559
|
const toolUseBlocks = response.content.filter(
|
|
8135
8560
|
(b) => b.type === "tool_use"
|
|
8136
8561
|
);
|
|
8137
8562
|
if (toolUseBlocks.length === 0) {
|
|
8138
8563
|
const textContent = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
8139
|
-
history.push({ role: "assistant", content: textContent });
|
|
8564
|
+
history.push({ role: "assistant", content: textContent, frameType: "assistant" });
|
|
8140
8565
|
this.trimHistory(sessionKey);
|
|
8141
8566
|
return textContent;
|
|
8142
8567
|
}
|
|
@@ -8146,9 +8571,11 @@ var BotRuntime = class {
|
|
|
8146
8571
|
);
|
|
8147
8572
|
history.push({
|
|
8148
8573
|
role: "assistant",
|
|
8149
|
-
content: response.content
|
|
8574
|
+
content: response.content,
|
|
8575
|
+
frameType: "assistant"
|
|
8150
8576
|
});
|
|
8151
8577
|
const toolResults = [];
|
|
8578
|
+
let hadFatalError = false;
|
|
8152
8579
|
for (const block of allowed) {
|
|
8153
8580
|
try {
|
|
8154
8581
|
const result = await this.config.toolExecutor(
|
|
@@ -8161,12 +8588,23 @@ var BotRuntime = class {
|
|
|
8161
8588
|
content: result
|
|
8162
8589
|
});
|
|
8163
8590
|
} catch (err) {
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8591
|
+
const classified = classifyError(err, block.name);
|
|
8592
|
+
if (classified instanceof FatalBotError) {
|
|
8593
|
+
toolResults.push({
|
|
8594
|
+
type: "tool_result",
|
|
8595
|
+
tool_use_id: block.id,
|
|
8596
|
+
content: `Fatal error: ${classified.message}`,
|
|
8597
|
+
is_error: true
|
|
8598
|
+
});
|
|
8599
|
+
hadFatalError = true;
|
|
8600
|
+
} else {
|
|
8601
|
+
toolResults.push({
|
|
8602
|
+
type: "tool_result",
|
|
8603
|
+
tool_use_id: block.id,
|
|
8604
|
+
content: `Error (recoverable): ${classified.message}`,
|
|
8605
|
+
is_error: true
|
|
8606
|
+
});
|
|
8607
|
+
}
|
|
8170
8608
|
}
|
|
8171
8609
|
}
|
|
8172
8610
|
for (const { block, check } of blocked) {
|
|
@@ -8179,10 +8617,22 @@ var BotRuntime = class {
|
|
|
8179
8617
|
}
|
|
8180
8618
|
history.push({
|
|
8181
8619
|
role: "user",
|
|
8182
|
-
content: toolResults
|
|
8620
|
+
content: toolResults,
|
|
8621
|
+
frameType: hadFatalError ? "error" : "tool_result"
|
|
8183
8622
|
});
|
|
8623
|
+
if (hadFatalError) {
|
|
8624
|
+
this.trimHistory(sessionKey);
|
|
8625
|
+
return `A fatal error occurred during tool execution. The bot loop has been stopped.`;
|
|
8626
|
+
}
|
|
8184
8627
|
}
|
|
8185
|
-
|
|
8628
|
+
const maxErr = new MaxStepsError(turns, maxTurns);
|
|
8629
|
+
history.push({
|
|
8630
|
+
role: "assistant",
|
|
8631
|
+
content: maxErr.message,
|
|
8632
|
+
frameType: "error"
|
|
8633
|
+
});
|
|
8634
|
+
this.trimHistory(sessionKey);
|
|
8635
|
+
return maxErr.message;
|
|
8186
8636
|
}
|
|
8187
8637
|
getHistory(sessionKey) {
|
|
8188
8638
|
if (!this.conversations.has(sessionKey)) {
|
|
@@ -8192,9 +8642,19 @@ var BotRuntime = class {
|
|
|
8192
8642
|
}
|
|
8193
8643
|
trimHistory(sessionKey) {
|
|
8194
8644
|
const history = this.conversations.get(sessionKey);
|
|
8195
|
-
if (history
|
|
8196
|
-
|
|
8645
|
+
if (!history || history.length <= MAX_HISTORY) return;
|
|
8646
|
+
const firstTaskIdx = history.findIndex((m) => m.frameType === "task");
|
|
8647
|
+
let trimmed = history.filter(
|
|
8648
|
+
(m, i) => m.frameType !== "planning" || i >= history.length - MAX_HISTORY
|
|
8649
|
+
);
|
|
8650
|
+
if (trimmed.length > MAX_HISTORY) {
|
|
8651
|
+
const tail = trimmed.slice(-MAX_HISTORY);
|
|
8652
|
+
if (firstTaskIdx >= 0 && !tail.includes(history[firstTaskIdx])) {
|
|
8653
|
+
tail[0] = history[firstTaskIdx];
|
|
8654
|
+
}
|
|
8655
|
+
trimmed = tail;
|
|
8197
8656
|
}
|
|
8657
|
+
this.conversations.set(sessionKey, trimmed);
|
|
8198
8658
|
}
|
|
8199
8659
|
/** Clear conversation history for a session */
|
|
8200
8660
|
clearHistory(sessionKey) {
|
|
@@ -8900,8 +9360,19 @@ import { randomUUID as randomUUID5 } from "crypto";
|
|
|
8900
9360
|
import { homedir } from "os";
|
|
8901
9361
|
import { join } from "path";
|
|
8902
9362
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
8903
|
-
var
|
|
9363
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
9364
|
+
var MAX_BACKOFF_MS = 3e5;
|
|
9365
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
9366
|
+
var JITTER_FACTOR = 0.25;
|
|
8904
9367
|
var AUTH_DIR = join(homedir(), ".exe-os", "whatsapp-auth");
|
|
9368
|
+
function calculateBackoff(retryCount) {
|
|
9369
|
+
const base = Math.min(
|
|
9370
|
+
INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
|
|
9371
|
+
MAX_BACKOFF_MS
|
|
9372
|
+
);
|
|
9373
|
+
const jitter = base * JITTER_FACTOR * (2 * Math.random() - 1);
|
|
9374
|
+
return Math.max(INITIAL_BACKOFF_MS, Math.round(base + jitter));
|
|
9375
|
+
}
|
|
8905
9376
|
var WhatsAppAdapter = class {
|
|
8906
9377
|
platform = "whatsapp";
|
|
8907
9378
|
sock = null;
|
|
@@ -8909,6 +9380,9 @@ var WhatsAppAdapter = class {
|
|
|
8909
9380
|
connected = false;
|
|
8910
9381
|
abortController = null;
|
|
8911
9382
|
authDir = AUTH_DIR;
|
|
9383
|
+
// Resilience state
|
|
9384
|
+
retryCount = 0;
|
|
9385
|
+
disconnectedAt = 0;
|
|
8912
9386
|
async connect(config2) {
|
|
8913
9387
|
this.authDir = config2.credentials.authDir ?? AUTH_DIR;
|
|
8914
9388
|
mkdirSync2(this.authDir, { recursive: true });
|
|
@@ -8917,6 +9391,20 @@ var WhatsAppAdapter = class {
|
|
|
8917
9391
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
8918
9392
|
const { version } = await fetchLatestBaileysVersion();
|
|
8919
9393
|
this.abortController = new AbortController();
|
|
9394
|
+
let agent;
|
|
9395
|
+
const socksProxy = config2.credentials.socksProxy;
|
|
9396
|
+
if (socksProxy) {
|
|
9397
|
+
try {
|
|
9398
|
+
const modName = "socks-proxy-agent";
|
|
9399
|
+
const mod = await import(modName);
|
|
9400
|
+
const SocksProxyAgent = mod.SocksProxyAgent ?? mod.default;
|
|
9401
|
+
agent = new SocksProxyAgent(socksProxy);
|
|
9402
|
+
console.log(`[whatsapp] Using SOCKS proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")}`);
|
|
9403
|
+
} catch {
|
|
9404
|
+
console.error("[whatsapp] socks-proxy-agent not installed \u2014 run: npm i socks-proxy-agent");
|
|
9405
|
+
throw new Error("SOCKS proxy configured but socks-proxy-agent package not installed");
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
8920
9408
|
const sock = makeWASocket({
|
|
8921
9409
|
auth: {
|
|
8922
9410
|
creds: state.creds,
|
|
@@ -8926,7 +9414,8 @@ var WhatsAppAdapter = class {
|
|
|
8926
9414
|
printQRInTerminal: true,
|
|
8927
9415
|
browser: ["exe-os", "cli", "1.0"],
|
|
8928
9416
|
syncFullHistory: false,
|
|
8929
|
-
markOnlineOnConnect: false
|
|
9417
|
+
markOnlineOnConnect: false,
|
|
9418
|
+
...agent ? { agent } : {}
|
|
8930
9419
|
});
|
|
8931
9420
|
this.sock = sock;
|
|
8932
9421
|
sock.ev.on("creds.update", saveCreds);
|
|
@@ -8934,18 +9423,32 @@ var WhatsAppAdapter = class {
|
|
|
8934
9423
|
const { connection, lastDisconnect } = update;
|
|
8935
9424
|
if (connection === "close") {
|
|
8936
9425
|
this.connected = false;
|
|
9426
|
+
if (this.disconnectedAt === 0) this.disconnectedAt = Date.now();
|
|
8937
9427
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
8938
9428
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
8939
9429
|
if (shouldReconnect && !this.abortController?.signal.aborted) {
|
|
8940
|
-
|
|
8941
|
-
|
|
9430
|
+
const delay2 = calculateBackoff(this.retryCount);
|
|
9431
|
+
this.retryCount++;
|
|
9432
|
+
console.log(
|
|
9433
|
+
`[whatsapp] Connection closed (code=${statusCode}), retry #${this.retryCount} in ${(delay2 / 1e3).toFixed(1)}s` + (socksProxy ? ` (proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")})` : "")
|
|
9434
|
+
);
|
|
9435
|
+
setTimeout(() => void this.connect(config2), delay2);
|
|
8942
9436
|
} else {
|
|
8943
9437
|
console.log("[whatsapp] Logged out \u2014 clear auth and re-scan QR");
|
|
8944
9438
|
}
|
|
8945
9439
|
}
|
|
8946
9440
|
if (connection === "open") {
|
|
9441
|
+
if (this.retryCount > 0) {
|
|
9442
|
+
const downtimeSec = this.disconnectedAt > 0 ? ((Date.now() - this.disconnectedAt) / 1e3).toFixed(1) : "?";
|
|
9443
|
+
console.log(
|
|
9444
|
+
`[whatsapp] Reconnected after ${this.retryCount} retries (${downtimeSec}s downtime)`
|
|
9445
|
+
);
|
|
9446
|
+
} else {
|
|
9447
|
+
console.log("[whatsapp] Connected via Baileys (linked device)");
|
|
9448
|
+
}
|
|
8947
9449
|
this.connected = true;
|
|
8948
|
-
|
|
9450
|
+
this.retryCount = 0;
|
|
9451
|
+
this.disconnectedAt = 0;
|
|
8949
9452
|
}
|
|
8950
9453
|
});
|
|
8951
9454
|
sock.ev.on("messages.upsert", (upsert) => {
|
|
@@ -11032,11 +11535,11 @@ async function ensureCRMContact(info) {
|
|
|
11032
11535
|
}
|
|
11033
11536
|
|
|
11034
11537
|
// src/automation/trigger-engine.ts
|
|
11035
|
-
import { readFileSync as
|
|
11538
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
|
|
11036
11539
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
11037
|
-
import
|
|
11038
|
-
import
|
|
11039
|
-
var TRIGGERS_PATH =
|
|
11540
|
+
import path19 from "path";
|
|
11541
|
+
import os10 from "os";
|
|
11542
|
+
var TRIGGERS_PATH = path19.join(os10.homedir(), ".exe-os", "triggers.json");
|
|
11040
11543
|
var GRAPH_API_VERSION = "v21.0";
|
|
11041
11544
|
function substituteTemplate(template, record) {
|
|
11042
11545
|
return template.replace(
|
|
@@ -11090,9 +11593,9 @@ function evaluateConditions(conditions, record) {
|
|
|
11090
11593
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
11091
11594
|
}
|
|
11092
11595
|
function loadTriggers(project) {
|
|
11093
|
-
if (!
|
|
11596
|
+
if (!existsSync15(TRIGGERS_PATH)) return [];
|
|
11094
11597
|
try {
|
|
11095
|
-
const raw =
|
|
11598
|
+
const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
|
|
11096
11599
|
const all = JSON.parse(raw);
|
|
11097
11600
|
if (!Array.isArray(all)) return [];
|
|
11098
11601
|
if (project) {
|
|
@@ -11390,13 +11893,16 @@ export {
|
|
|
11390
11893
|
FULL_ACCESS,
|
|
11391
11894
|
FailoverCascade,
|
|
11392
11895
|
FailoverExhaustedError,
|
|
11896
|
+
FatalBotError,
|
|
11393
11897
|
Gateway,
|
|
11394
11898
|
IMessageAdapter,
|
|
11899
|
+
MaxStepsError,
|
|
11395
11900
|
OllamaProvider,
|
|
11396
11901
|
OpenAICompatProvider,
|
|
11397
11902
|
READ_ONLY,
|
|
11398
11903
|
READ_TOOLS,
|
|
11399
11904
|
RateLimiter,
|
|
11905
|
+
RecoverableBotError,
|
|
11400
11906
|
SessionStore,
|
|
11401
11907
|
SignalAdapter,
|
|
11402
11908
|
SlackAdapter,
|
|
@@ -11408,6 +11914,7 @@ export {
|
|
|
11408
11914
|
buildExecAssistantTools,
|
|
11409
11915
|
buildPermissionContext,
|
|
11410
11916
|
checkToolPermission,
|
|
11917
|
+
classifyError,
|
|
11411
11918
|
createCRMWebhookHandler,
|
|
11412
11919
|
createPerson,
|
|
11413
11920
|
createReceptionist,
|