@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/bin/exe-gateway.js
CHANGED
|
@@ -639,6 +639,7 @@ __export(employees_exports, {
|
|
|
639
639
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
640
640
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
641
641
|
addEmployee: () => addEmployee,
|
|
642
|
+
baseAgentName: () => baseAgentName,
|
|
642
643
|
canCoordinate: () => canCoordinate,
|
|
643
644
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
644
645
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -735,6 +736,14 @@ function hasRole(agentName, role) {
|
|
|
735
736
|
const emp = getEmployee(employees, agentName);
|
|
736
737
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
737
738
|
}
|
|
739
|
+
function baseAgentName(name, employees) {
|
|
740
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
741
|
+
if (!match) return name;
|
|
742
|
+
const base = match[1];
|
|
743
|
+
const roster = employees ?? loadEmployeesSync();
|
|
744
|
+
if (getEmployee(roster, base)) return base;
|
|
745
|
+
return name;
|
|
746
|
+
}
|
|
738
747
|
function isMultiInstance(agentName, employees) {
|
|
739
748
|
const roster = employees ?? loadEmployeesSync();
|
|
740
749
|
const emp = getEmployee(roster, agentName);
|
|
@@ -851,6 +860,12 @@ function getClient() {
|
|
|
851
860
|
if (!_resilientClient) {
|
|
852
861
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
853
862
|
}
|
|
863
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
864
|
+
return _resilientClient;
|
|
865
|
+
}
|
|
866
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
867
|
+
return _daemonClient;
|
|
868
|
+
}
|
|
854
869
|
return _resilientClient;
|
|
855
870
|
}
|
|
856
871
|
function getRawClient() {
|
|
@@ -1339,6 +1354,12 @@ async function ensureSchema() {
|
|
|
1339
1354
|
} catch {
|
|
1340
1355
|
}
|
|
1341
1356
|
}
|
|
1357
|
+
try {
|
|
1358
|
+
await client.execute(
|
|
1359
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1360
|
+
);
|
|
1361
|
+
} catch {
|
|
1362
|
+
}
|
|
1342
1363
|
await client.executeMultiple(`
|
|
1343
1364
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1344
1365
|
id TEXT PRIMARY KEY,
|
|
@@ -1391,7 +1412,30 @@ async function ensureSchema() {
|
|
|
1391
1412
|
entity_id TEXT NOT NULL,
|
|
1392
1413
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1393
1414
|
);
|
|
1415
|
+
|
|
1416
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1417
|
+
name,
|
|
1418
|
+
content=entities,
|
|
1419
|
+
content_rowid=rowid
|
|
1420
|
+
);
|
|
1421
|
+
|
|
1422
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1423
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1424
|
+
END;
|
|
1425
|
+
|
|
1426
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1427
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1428
|
+
END;
|
|
1429
|
+
|
|
1430
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1431
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1432
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1433
|
+
END;
|
|
1394
1434
|
`);
|
|
1435
|
+
try {
|
|
1436
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1437
|
+
} catch {
|
|
1438
|
+
}
|
|
1395
1439
|
await client.executeMultiple(`
|
|
1396
1440
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1397
1441
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1572,6 +1616,33 @@ async function ensureSchema() {
|
|
|
1572
1616
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1573
1617
|
ON conversations(channel_id);
|
|
1574
1618
|
`);
|
|
1619
|
+
await client.executeMultiple(`
|
|
1620
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1621
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1622
|
+
agent_id TEXT NOT NULL,
|
|
1623
|
+
session_name TEXT,
|
|
1624
|
+
task_id TEXT,
|
|
1625
|
+
project_name TEXT,
|
|
1626
|
+
started_at TEXT NOT NULL
|
|
1627
|
+
);
|
|
1628
|
+
|
|
1629
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1630
|
+
ON session_agent_map(agent_id);
|
|
1631
|
+
`);
|
|
1632
|
+
try {
|
|
1633
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1634
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1635
|
+
await client.execute({
|
|
1636
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1637
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1638
|
+
FROM memories
|
|
1639
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1640
|
+
GROUP BY session_id, agent_id`,
|
|
1641
|
+
args: []
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
} catch {
|
|
1645
|
+
}
|
|
1575
1646
|
try {
|
|
1576
1647
|
await client.execute({
|
|
1577
1648
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1705,15 +1776,41 @@ async function ensureSchema() {
|
|
|
1705
1776
|
});
|
|
1706
1777
|
} catch {
|
|
1707
1778
|
}
|
|
1779
|
+
for (const col of [
|
|
1780
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1781
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1782
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1783
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1784
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1785
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1786
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1787
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1788
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1789
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1790
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1791
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1792
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1793
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1794
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1795
|
+
]) {
|
|
1796
|
+
try {
|
|
1797
|
+
await client.execute(col);
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1708
1801
|
}
|
|
1709
1802
|
async function disposeDatabase() {
|
|
1803
|
+
if (_daemonClient) {
|
|
1804
|
+
_daemonClient.close();
|
|
1805
|
+
_daemonClient = null;
|
|
1806
|
+
}
|
|
1710
1807
|
if (_client) {
|
|
1711
1808
|
_client.close();
|
|
1712
1809
|
_client = null;
|
|
1713
1810
|
_resilientClient = null;
|
|
1714
1811
|
}
|
|
1715
1812
|
}
|
|
1716
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1813
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1717
1814
|
var init_database = __esm({
|
|
1718
1815
|
"src/lib/database.ts"() {
|
|
1719
1816
|
"use strict";
|
|
@@ -1721,6 +1818,7 @@ var init_database = __esm({
|
|
|
1721
1818
|
init_employees();
|
|
1722
1819
|
_client = null;
|
|
1723
1820
|
_resilientClient = null;
|
|
1821
|
+
_daemonClient = null;
|
|
1724
1822
|
initTurso = initDatabase;
|
|
1725
1823
|
disposeTurso = disposeDatabase;
|
|
1726
1824
|
}
|
|
@@ -1755,10 +1853,12 @@ function handleData(chunk) {
|
|
|
1755
1853
|
if (!line) continue;
|
|
1756
1854
|
try {
|
|
1757
1855
|
const response = JSON.parse(line);
|
|
1758
|
-
const
|
|
1856
|
+
const id = response.id;
|
|
1857
|
+
if (!id) continue;
|
|
1858
|
+
const entry = _pending.get(id);
|
|
1759
1859
|
if (entry) {
|
|
1760
1860
|
clearTimeout(entry.timer);
|
|
1761
|
-
_pending.delete(
|
|
1861
|
+
_pending.delete(id);
|
|
1762
1862
|
entry.resolve(response);
|
|
1763
1863
|
}
|
|
1764
1864
|
} catch {
|
|
@@ -1929,6 +2029,9 @@ async function connectEmbedDaemon() {
|
|
|
1929
2029
|
return false;
|
|
1930
2030
|
}
|
|
1931
2031
|
function sendRequest(texts, priority) {
|
|
2032
|
+
return sendDaemonRequest({ texts, priority });
|
|
2033
|
+
}
|
|
2034
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
1932
2035
|
return new Promise((resolve) => {
|
|
1933
2036
|
if (!_socket || !_connected) {
|
|
1934
2037
|
resolve({ error: "Not connected" });
|
|
@@ -1938,10 +2041,10 @@ function sendRequest(texts, priority) {
|
|
|
1938
2041
|
const timer = setTimeout(() => {
|
|
1939
2042
|
_pending.delete(id);
|
|
1940
2043
|
resolve({ error: "Request timeout" });
|
|
1941
|
-
},
|
|
2044
|
+
}, timeoutMs);
|
|
1942
2045
|
_pending.set(id, { resolve, timer });
|
|
1943
2046
|
try {
|
|
1944
|
-
_socket.write(JSON.stringify({ id,
|
|
2047
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1945
2048
|
} catch {
|
|
1946
2049
|
clearTimeout(timer);
|
|
1947
2050
|
_pending.delete(id);
|
|
@@ -1951,30 +2054,11 @@ function sendRequest(texts, priority) {
|
|
|
1951
2054
|
}
|
|
1952
2055
|
async function pingDaemon() {
|
|
1953
2056
|
if (!_socket || !_connected) return null;
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
}, 5e3);
|
|
1960
|
-
_pending.set(id, {
|
|
1961
|
-
resolve: (data) => {
|
|
1962
|
-
if (data.health) {
|
|
1963
|
-
resolve(data.health);
|
|
1964
|
-
} else {
|
|
1965
|
-
resolve(null);
|
|
1966
|
-
}
|
|
1967
|
-
},
|
|
1968
|
-
timer
|
|
1969
|
-
});
|
|
1970
|
-
try {
|
|
1971
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
1972
|
-
} catch {
|
|
1973
|
-
clearTimeout(timer);
|
|
1974
|
-
_pending.delete(id);
|
|
1975
|
-
resolve(null);
|
|
1976
|
-
}
|
|
1977
|
-
});
|
|
2057
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
2058
|
+
if (response.health) {
|
|
2059
|
+
return response.health;
|
|
2060
|
+
}
|
|
2061
|
+
return null;
|
|
1978
2062
|
}
|
|
1979
2063
|
function killAndRespawnDaemon() {
|
|
1980
2064
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
@@ -2117,10 +2201,10 @@ async function disposeEmbedder() {
|
|
|
2117
2201
|
async function embedDirect(text) {
|
|
2118
2202
|
const llamaCpp = await import("node-llama-cpp");
|
|
2119
2203
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2120
|
-
const { existsSync:
|
|
2121
|
-
const
|
|
2122
|
-
const modelPath =
|
|
2123
|
-
if (!
|
|
2204
|
+
const { existsSync: existsSync17 } = await import("fs");
|
|
2205
|
+
const path21 = await import("path");
|
|
2206
|
+
const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
2207
|
+
if (!existsSync17(modelPath)) {
|
|
2124
2208
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2125
2209
|
}
|
|
2126
2210
|
const llama = await llamaCpp.getLlama();
|
|
@@ -2179,12 +2263,20 @@ async function getMasterKey() {
|
|
|
2179
2263
|
}
|
|
2180
2264
|
const keyPath = getKeyPath();
|
|
2181
2265
|
if (!existsSync4(keyPath)) {
|
|
2266
|
+
process.stderr.write(
|
|
2267
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2268
|
+
`
|
|
2269
|
+
);
|
|
2182
2270
|
return null;
|
|
2183
2271
|
}
|
|
2184
2272
|
try {
|
|
2185
2273
|
const content = await readFile3(keyPath, "utf-8");
|
|
2186
2274
|
return Buffer.from(content.trim(), "base64");
|
|
2187
|
-
} catch {
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
process.stderr.write(
|
|
2277
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2278
|
+
`
|
|
2279
|
+
);
|
|
2188
2280
|
return null;
|
|
2189
2281
|
}
|
|
2190
2282
|
}
|
|
@@ -2640,6 +2732,7 @@ __export(store_exports, {
|
|
|
2640
2732
|
vectorToBlob: () => vectorToBlob,
|
|
2641
2733
|
writeMemory: () => writeMemory
|
|
2642
2734
|
});
|
|
2735
|
+
import { createHash } from "crypto";
|
|
2643
2736
|
function isBusyError2(err) {
|
|
2644
2737
|
if (err instanceof Error) {
|
|
2645
2738
|
const msg = err.message.toLowerCase();
|
|
@@ -2713,12 +2806,52 @@ function classifyTier(record) {
|
|
|
2713
2806
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
2714
2807
|
return 3;
|
|
2715
2808
|
}
|
|
2809
|
+
function inferFilePaths(record) {
|
|
2810
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
2811
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
2812
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
2813
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
2814
|
+
}
|
|
2815
|
+
function inferCommitHash(record) {
|
|
2816
|
+
if (record.tool_name !== "Bash") return null;
|
|
2817
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
2818
|
+
return match ? match[1] : null;
|
|
2819
|
+
}
|
|
2820
|
+
function inferLanguageType(record) {
|
|
2821
|
+
const text = record.raw_text;
|
|
2822
|
+
if (!text || text.length < 10) return null;
|
|
2823
|
+
const trimmed = text.trimStart();
|
|
2824
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
2825
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
2826
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
2827
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
2828
|
+
return "mixed";
|
|
2829
|
+
}
|
|
2830
|
+
function inferDomain(record) {
|
|
2831
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
2832
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
2833
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2716
2836
|
async function writeMemory(record) {
|
|
2717
2837
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
2718
2838
|
throw new Error(
|
|
2719
2839
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
2720
2840
|
);
|
|
2721
2841
|
}
|
|
2842
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
2843
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
try {
|
|
2847
|
+
const client = getClient();
|
|
2848
|
+
const existing = await client.execute({
|
|
2849
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
2850
|
+
args: [contentHash, record.agent_id]
|
|
2851
|
+
});
|
|
2852
|
+
if (existing.rows.length > 0) return;
|
|
2853
|
+
} catch {
|
|
2854
|
+
}
|
|
2722
2855
|
const dbRow = {
|
|
2723
2856
|
id: record.id,
|
|
2724
2857
|
agent_id: record.agent_id,
|
|
@@ -2748,7 +2881,23 @@ async function writeMemory(record) {
|
|
|
2748
2881
|
supersedes_id: record.supersedes_id ?? null,
|
|
2749
2882
|
draft: record.draft ? 1 : 0,
|
|
2750
2883
|
memory_type: record.memory_type ?? "raw",
|
|
2751
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
2884
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
2885
|
+
content_hash: contentHash,
|
|
2886
|
+
intent: record.intent ?? null,
|
|
2887
|
+
outcome: record.outcome ?? null,
|
|
2888
|
+
domain: record.domain ?? inferDomain(record),
|
|
2889
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
2890
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
2891
|
+
chain_position: record.chain_position ?? null,
|
|
2892
|
+
review_status: record.review_status ?? null,
|
|
2893
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
2894
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
2895
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
2896
|
+
duration_ms: record.duration_ms ?? null,
|
|
2897
|
+
token_cost: record.token_cost ?? null,
|
|
2898
|
+
audience: record.audience ?? null,
|
|
2899
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
2900
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
2752
2901
|
};
|
|
2753
2902
|
_pendingRecords.push(dbRow);
|
|
2754
2903
|
orgBus.emit({
|
|
@@ -2806,80 +2955,85 @@ async function flushBatch() {
|
|
|
2806
2955
|
const draft = row.draft ? 1 : 0;
|
|
2807
2956
|
const memoryType = row.memory_type ?? "raw";
|
|
2808
2957
|
const trajectory = row.trajectory ?? null;
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2958
|
+
const contentHash = row.content_hash ?? null;
|
|
2959
|
+
const intent = row.intent ?? null;
|
|
2960
|
+
const outcome = row.outcome ?? null;
|
|
2961
|
+
const domain = row.domain ?? null;
|
|
2962
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
2963
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
2964
|
+
const chainPosition = row.chain_position ?? null;
|
|
2965
|
+
const reviewStatus = row.review_status ?? null;
|
|
2966
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
2967
|
+
const filePaths = row.file_paths ?? null;
|
|
2968
|
+
const commitHash = row.commit_hash ?? null;
|
|
2969
|
+
const durationMs = row.duration_ms ?? null;
|
|
2970
|
+
const tokenCost = row.token_cost ?? null;
|
|
2971
|
+
const audience = row.audience ?? null;
|
|
2972
|
+
const languageType = row.language_type ?? null;
|
|
2973
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
2974
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
2812
2975
|
tool_name, project_name,
|
|
2813
2976
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2814
2977
|
confidence, last_accessed,
|
|
2815
2978
|
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
|
-
pageNumber,
|
|
2875
|
-
sourcePath,
|
|
2876
|
-
sourceType,
|
|
2877
|
-
tier,
|
|
2878
|
-
supersedesId,
|
|
2879
|
-
draft,
|
|
2880
|
-
memoryType,
|
|
2881
|
-
trajectory
|
|
2882
|
-
]
|
|
2979
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
2980
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
2981
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
2982
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
2983
|
+
const metaArgs = [
|
|
2984
|
+
intent,
|
|
2985
|
+
outcome,
|
|
2986
|
+
domain,
|
|
2987
|
+
referencedEntities,
|
|
2988
|
+
retrievalCount,
|
|
2989
|
+
chainPosition,
|
|
2990
|
+
reviewStatus,
|
|
2991
|
+
contextWindowPct,
|
|
2992
|
+
filePaths,
|
|
2993
|
+
commitHash,
|
|
2994
|
+
durationMs,
|
|
2995
|
+
tokenCost,
|
|
2996
|
+
audience,
|
|
2997
|
+
languageType,
|
|
2998
|
+
parentMemoryId
|
|
2999
|
+
];
|
|
3000
|
+
const baseArgs = [
|
|
3001
|
+
row.id,
|
|
3002
|
+
row.agent_id,
|
|
3003
|
+
row.agent_role,
|
|
3004
|
+
row.session_id,
|
|
3005
|
+
row.timestamp,
|
|
3006
|
+
row.tool_name,
|
|
3007
|
+
row.project_name,
|
|
3008
|
+
row.has_error,
|
|
3009
|
+
row.raw_text
|
|
3010
|
+
];
|
|
3011
|
+
const sharedArgs = [
|
|
3012
|
+
row.version,
|
|
3013
|
+
taskId,
|
|
3014
|
+
importance,
|
|
3015
|
+
status,
|
|
3016
|
+
confidence,
|
|
3017
|
+
lastAccessed,
|
|
3018
|
+
workspaceId,
|
|
3019
|
+
documentId,
|
|
3020
|
+
userId,
|
|
3021
|
+
charOffset,
|
|
3022
|
+
pageNumber,
|
|
3023
|
+
sourcePath,
|
|
3024
|
+
sourceType,
|
|
3025
|
+
tier,
|
|
3026
|
+
supersedesId,
|
|
3027
|
+
draft,
|
|
3028
|
+
memoryType,
|
|
3029
|
+
trajectory,
|
|
3030
|
+
contentHash
|
|
3031
|
+
];
|
|
3032
|
+
return {
|
|
3033
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
3034
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
3035
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3036
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
2883
3037
|
};
|
|
2884
3038
|
};
|
|
2885
3039
|
const globalClient = getClient();
|
|
@@ -3138,8 +3292,8 @@ __export(wiki_client_exports, {
|
|
|
3138
3292
|
listDocuments: () => listDocuments,
|
|
3139
3293
|
listWorkspaces: () => listWorkspaces
|
|
3140
3294
|
});
|
|
3141
|
-
async function wikiFetch(config2,
|
|
3142
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
3295
|
+
async function wikiFetch(config2, path21, method = "GET", body) {
|
|
3296
|
+
const url = `${config2.baseUrl}/api/v1${path21}`;
|
|
3143
3297
|
const headers = {
|
|
3144
3298
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
3145
3299
|
"Content-Type": "application/json"
|
|
@@ -3172,7 +3326,7 @@ async function wikiFetch(config2, path20, method = "GET", body) {
|
|
|
3172
3326
|
}
|
|
3173
3327
|
}
|
|
3174
3328
|
if (!response.ok) {
|
|
3175
|
-
throw new Error(`Wiki API ${method} ${
|
|
3329
|
+
throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
|
|
3176
3330
|
}
|
|
3177
3331
|
return response.json();
|
|
3178
3332
|
} finally {
|
|
@@ -3266,7 +3420,7 @@ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
|
|
|
3266
3420
|
var init_wiki_client = __esm({
|
|
3267
3421
|
"src/lib/wiki-client.ts"() {
|
|
3268
3422
|
"use strict";
|
|
3269
|
-
LOCAL_WIKI_URL = "http://localhost:3001";
|
|
3423
|
+
LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
|
|
3270
3424
|
REQUEST_TIMEOUT_MS2 = 8e3;
|
|
3271
3425
|
}
|
|
3272
3426
|
});
|
|
@@ -4574,17 +4728,29 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
4574
4728
|
// src/gateway/adapters/whatsapp.ts
|
|
4575
4729
|
var whatsapp_exports = {};
|
|
4576
4730
|
__export(whatsapp_exports, {
|
|
4577
|
-
WhatsAppAdapter: () => WhatsAppAdapter
|
|
4731
|
+
WhatsAppAdapter: () => WhatsAppAdapter,
|
|
4732
|
+
calculateBackoff: () => calculateBackoff
|
|
4578
4733
|
});
|
|
4579
4734
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4580
4735
|
import { homedir } from "os";
|
|
4581
4736
|
import { join } from "path";
|
|
4582
4737
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
4583
|
-
|
|
4738
|
+
function calculateBackoff(retryCount) {
|
|
4739
|
+
const base = Math.min(
|
|
4740
|
+
INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
|
|
4741
|
+
MAX_BACKOFF_MS
|
|
4742
|
+
);
|
|
4743
|
+
const jitter = base * JITTER_FACTOR * (2 * Math.random() - 1);
|
|
4744
|
+
return Math.max(INITIAL_BACKOFF_MS, Math.round(base + jitter));
|
|
4745
|
+
}
|
|
4746
|
+
var INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, BACKOFF_MULTIPLIER, JITTER_FACTOR, AUTH_DIR, WhatsAppAdapter;
|
|
4584
4747
|
var init_whatsapp = __esm({
|
|
4585
4748
|
"src/gateway/adapters/whatsapp.ts"() {
|
|
4586
4749
|
"use strict";
|
|
4587
|
-
|
|
4750
|
+
INITIAL_BACKOFF_MS = 1e3;
|
|
4751
|
+
MAX_BACKOFF_MS = 3e5;
|
|
4752
|
+
BACKOFF_MULTIPLIER = 2;
|
|
4753
|
+
JITTER_FACTOR = 0.25;
|
|
4588
4754
|
AUTH_DIR = join(homedir(), ".exe-os", "whatsapp-auth");
|
|
4589
4755
|
WhatsAppAdapter = class {
|
|
4590
4756
|
platform = "whatsapp";
|
|
@@ -4593,6 +4759,9 @@ var init_whatsapp = __esm({
|
|
|
4593
4759
|
connected = false;
|
|
4594
4760
|
abortController = null;
|
|
4595
4761
|
authDir = AUTH_DIR;
|
|
4762
|
+
// Resilience state
|
|
4763
|
+
retryCount = 0;
|
|
4764
|
+
disconnectedAt = 0;
|
|
4596
4765
|
async connect(config2) {
|
|
4597
4766
|
this.authDir = config2.credentials.authDir ?? AUTH_DIR;
|
|
4598
4767
|
mkdirSync3(this.authDir, { recursive: true });
|
|
@@ -4601,6 +4770,20 @@ var init_whatsapp = __esm({
|
|
|
4601
4770
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
4602
4771
|
const { version } = await fetchLatestBaileysVersion();
|
|
4603
4772
|
this.abortController = new AbortController();
|
|
4773
|
+
let agent;
|
|
4774
|
+
const socksProxy = config2.credentials.socksProxy;
|
|
4775
|
+
if (socksProxy) {
|
|
4776
|
+
try {
|
|
4777
|
+
const modName = "socks-proxy-agent";
|
|
4778
|
+
const mod = await import(modName);
|
|
4779
|
+
const SocksProxyAgent = mod.SocksProxyAgent ?? mod.default;
|
|
4780
|
+
agent = new SocksProxyAgent(socksProxy);
|
|
4781
|
+
console.log(`[whatsapp] Using SOCKS proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")}`);
|
|
4782
|
+
} catch {
|
|
4783
|
+
console.error("[whatsapp] socks-proxy-agent not installed \u2014 run: npm i socks-proxy-agent");
|
|
4784
|
+
throw new Error("SOCKS proxy configured but socks-proxy-agent package not installed");
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4604
4787
|
const sock = makeWASocket({
|
|
4605
4788
|
auth: {
|
|
4606
4789
|
creds: state.creds,
|
|
@@ -4610,7 +4793,8 @@ var init_whatsapp = __esm({
|
|
|
4610
4793
|
printQRInTerminal: true,
|
|
4611
4794
|
browser: ["exe-os", "cli", "1.0"],
|
|
4612
4795
|
syncFullHistory: false,
|
|
4613
|
-
markOnlineOnConnect: false
|
|
4796
|
+
markOnlineOnConnect: false,
|
|
4797
|
+
...agent ? { agent } : {}
|
|
4614
4798
|
});
|
|
4615
4799
|
this.sock = sock;
|
|
4616
4800
|
sock.ev.on("creds.update", saveCreds);
|
|
@@ -4618,18 +4802,32 @@ var init_whatsapp = __esm({
|
|
|
4618
4802
|
const { connection, lastDisconnect } = update;
|
|
4619
4803
|
if (connection === "close") {
|
|
4620
4804
|
this.connected = false;
|
|
4805
|
+
if (this.disconnectedAt === 0) this.disconnectedAt = Date.now();
|
|
4621
4806
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
4622
4807
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
4623
4808
|
if (shouldReconnect && !this.abortController?.signal.aborted) {
|
|
4624
|
-
|
|
4625
|
-
|
|
4809
|
+
const delay2 = calculateBackoff(this.retryCount);
|
|
4810
|
+
this.retryCount++;
|
|
4811
|
+
console.log(
|
|
4812
|
+
`[whatsapp] Connection closed (code=${statusCode}), retry #${this.retryCount} in ${(delay2 / 1e3).toFixed(1)}s` + (socksProxy ? ` (proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")})` : "")
|
|
4813
|
+
);
|
|
4814
|
+
setTimeout(() => void this.connect(config2), delay2);
|
|
4626
4815
|
} else {
|
|
4627
4816
|
console.log("[whatsapp] Logged out \u2014 clear auth and re-scan QR");
|
|
4628
4817
|
}
|
|
4629
4818
|
}
|
|
4630
4819
|
if (connection === "open") {
|
|
4820
|
+
if (this.retryCount > 0) {
|
|
4821
|
+
const downtimeSec = this.disconnectedAt > 0 ? ((Date.now() - this.disconnectedAt) / 1e3).toFixed(1) : "?";
|
|
4822
|
+
console.log(
|
|
4823
|
+
`[whatsapp] Reconnected after ${this.retryCount} retries (${downtimeSec}s downtime)`
|
|
4824
|
+
);
|
|
4825
|
+
} else {
|
|
4826
|
+
console.log("[whatsapp] Connected via Baileys (linked device)");
|
|
4827
|
+
}
|
|
4631
4828
|
this.connected = true;
|
|
4632
|
-
|
|
4829
|
+
this.retryCount = 0;
|
|
4830
|
+
this.disconnectedAt = 0;
|
|
4633
4831
|
}
|
|
4634
4832
|
});
|
|
4635
4833
|
sock.ev.on("messages.upsert", (upsert) => {
|
|
@@ -5718,9 +5916,9 @@ __export(webhook_exports, {
|
|
|
5718
5916
|
WebhookAdapter: () => WebhookAdapter
|
|
5719
5917
|
});
|
|
5720
5918
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
5721
|
-
function resolvePath(obj,
|
|
5919
|
+
function resolvePath(obj, path21) {
|
|
5722
5920
|
let current = obj;
|
|
5723
|
-
for (const segment of
|
|
5921
|
+
for (const segment of path21.split(".")) {
|
|
5724
5922
|
if (current == null || typeof current !== "object") return void 0;
|
|
5725
5923
|
current = current[segment];
|
|
5726
5924
|
}
|
|
@@ -6117,18 +6315,69 @@ var init_provider_table = __esm({
|
|
|
6117
6315
|
}
|
|
6118
6316
|
});
|
|
6119
6317
|
|
|
6120
|
-
// src/lib/
|
|
6121
|
-
|
|
6318
|
+
// src/lib/runtime-table.ts
|
|
6319
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
6320
|
+
var init_runtime_table = __esm({
|
|
6321
|
+
"src/lib/runtime-table.ts"() {
|
|
6322
|
+
"use strict";
|
|
6323
|
+
RUNTIME_TABLE = {
|
|
6324
|
+
codex: {
|
|
6325
|
+
binary: "codex",
|
|
6326
|
+
launchMode: "exec",
|
|
6327
|
+
autoApproveFlag: "--full-auto",
|
|
6328
|
+
inlineFlag: "--no-alt-screen",
|
|
6329
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
6330
|
+
defaultModel: "gpt-5.4"
|
|
6331
|
+
}
|
|
6332
|
+
};
|
|
6333
|
+
DEFAULT_RUNTIME = "claude";
|
|
6334
|
+
}
|
|
6335
|
+
});
|
|
6336
|
+
|
|
6337
|
+
// src/lib/agent-config.ts
|
|
6338
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
6122
6339
|
import path9 from "path";
|
|
6340
|
+
function loadAgentConfig() {
|
|
6341
|
+
if (!existsSync8(AGENT_CONFIG_PATH)) return {};
|
|
6342
|
+
try {
|
|
6343
|
+
return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
|
|
6344
|
+
} catch {
|
|
6345
|
+
return {};
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
function getAgentRuntime(agentId) {
|
|
6349
|
+
const config2 = loadAgentConfig();
|
|
6350
|
+
const entry = config2[agentId];
|
|
6351
|
+
if (entry) return entry;
|
|
6352
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
6353
|
+
}
|
|
6354
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
6355
|
+
var init_agent_config = __esm({
|
|
6356
|
+
"src/lib/agent-config.ts"() {
|
|
6357
|
+
"use strict";
|
|
6358
|
+
init_config();
|
|
6359
|
+
init_runtime_table();
|
|
6360
|
+
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
6361
|
+
DEFAULT_MODELS = {
|
|
6362
|
+
claude: "claude-opus-4",
|
|
6363
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
6364
|
+
opencode: "minimax-m2.7"
|
|
6365
|
+
};
|
|
6366
|
+
}
|
|
6367
|
+
});
|
|
6368
|
+
|
|
6369
|
+
// src/lib/intercom-queue.ts
|
|
6370
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
6371
|
+
import path10 from "path";
|
|
6123
6372
|
import os6 from "os";
|
|
6124
6373
|
function ensureDir() {
|
|
6125
|
-
const dir =
|
|
6126
|
-
if (!
|
|
6374
|
+
const dir = path10.dirname(QUEUE_PATH);
|
|
6375
|
+
if (!existsSync9(dir)) mkdirSync6(dir, { recursive: true });
|
|
6127
6376
|
}
|
|
6128
6377
|
function readQueue() {
|
|
6129
6378
|
try {
|
|
6130
|
-
if (!
|
|
6131
|
-
return JSON.parse(
|
|
6379
|
+
if (!existsSync9(QUEUE_PATH)) return [];
|
|
6380
|
+
return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
|
|
6132
6381
|
} catch {
|
|
6133
6382
|
return [];
|
|
6134
6383
|
}
|
|
@@ -6136,7 +6385,7 @@ function readQueue() {
|
|
|
6136
6385
|
function writeQueue(queue) {
|
|
6137
6386
|
ensureDir();
|
|
6138
6387
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
6139
|
-
|
|
6388
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
6140
6389
|
renameSync3(tmp, QUEUE_PATH);
|
|
6141
6390
|
}
|
|
6142
6391
|
function queueIntercom(targetSession, reason) {
|
|
@@ -6160,19 +6409,19 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
6160
6409
|
var init_intercom_queue = __esm({
|
|
6161
6410
|
"src/lib/intercom-queue.ts"() {
|
|
6162
6411
|
"use strict";
|
|
6163
|
-
QUEUE_PATH =
|
|
6412
|
+
QUEUE_PATH = path10.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
6164
6413
|
TTL_MS = 60 * 60 * 1e3;
|
|
6165
|
-
INTERCOM_LOG =
|
|
6414
|
+
INTERCOM_LOG = path10.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
6166
6415
|
}
|
|
6167
6416
|
});
|
|
6168
6417
|
|
|
6169
6418
|
// src/lib/plan-limits.ts
|
|
6170
|
-
import { readFileSync as
|
|
6171
|
-
import
|
|
6419
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
6420
|
+
import path11 from "path";
|
|
6172
6421
|
function getLicenseSync() {
|
|
6173
6422
|
try {
|
|
6174
|
-
if (!
|
|
6175
|
-
const raw = JSON.parse(
|
|
6423
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
6424
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
6176
6425
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
6177
6426
|
const parts = raw.token.split(".");
|
|
6178
6427
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -6210,8 +6459,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
6210
6459
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
6211
6460
|
let count = 0;
|
|
6212
6461
|
try {
|
|
6213
|
-
if (
|
|
6214
|
-
const raw =
|
|
6462
|
+
if (existsSync10(filePath)) {
|
|
6463
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
6215
6464
|
const employees = JSON.parse(raw);
|
|
6216
6465
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
6217
6466
|
}
|
|
@@ -6240,19 +6489,19 @@ var init_plan_limits = __esm({
|
|
|
6240
6489
|
this.name = "PlanLimitError";
|
|
6241
6490
|
}
|
|
6242
6491
|
};
|
|
6243
|
-
CACHE_PATH2 =
|
|
6492
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
6244
6493
|
}
|
|
6245
6494
|
});
|
|
6246
6495
|
|
|
6247
6496
|
// src/lib/notifications.ts
|
|
6248
6497
|
import crypto3 from "crypto";
|
|
6249
|
-
import
|
|
6498
|
+
import path12 from "path";
|
|
6250
6499
|
import os7 from "os";
|
|
6251
6500
|
import {
|
|
6252
|
-
readFileSync as
|
|
6501
|
+
readFileSync as readFileSync10,
|
|
6253
6502
|
readdirSync as readdirSync2,
|
|
6254
6503
|
unlinkSync as unlinkSync3,
|
|
6255
|
-
existsSync as
|
|
6504
|
+
existsSync as existsSync11,
|
|
6256
6505
|
rmdirSync
|
|
6257
6506
|
} from "fs";
|
|
6258
6507
|
async function writeNotification(notification) {
|
|
@@ -6356,10 +6605,11 @@ var init_task_scope = __esm({
|
|
|
6356
6605
|
|
|
6357
6606
|
// src/lib/tasks-crud.ts
|
|
6358
6607
|
import crypto5 from "crypto";
|
|
6359
|
-
import
|
|
6608
|
+
import path13 from "path";
|
|
6609
|
+
import os8 from "os";
|
|
6360
6610
|
import { execSync as execSync4 } from "child_process";
|
|
6361
6611
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
6362
|
-
import { existsSync as
|
|
6612
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
6363
6613
|
async function writeCheckpoint(input) {
|
|
6364
6614
|
const client = getClient();
|
|
6365
6615
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -6400,6 +6650,35 @@ function extractParentFromContext(contextBody) {
|
|
|
6400
6650
|
function slugify(title) {
|
|
6401
6651
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
6402
6652
|
}
|
|
6653
|
+
function buildKeywordIndex() {
|
|
6654
|
+
const idx = /* @__PURE__ */ new Map();
|
|
6655
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
6656
|
+
for (const kw of keywords) {
|
|
6657
|
+
const existing = idx.get(kw) ?? [];
|
|
6658
|
+
existing.push(role);
|
|
6659
|
+
idx.set(kw, existing);
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
return idx;
|
|
6663
|
+
}
|
|
6664
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
6665
|
+
const employees = loadEmployeesSync();
|
|
6666
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
6667
|
+
if (!employee) return void 0;
|
|
6668
|
+
const assigneeRole = employee.role;
|
|
6669
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
6670
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
6671
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
6672
|
+
if (text.includes(keyword)) {
|
|
6673
|
+
for (const role of roles) matchedRoles.add(role);
|
|
6674
|
+
}
|
|
6675
|
+
}
|
|
6676
|
+
if (matchedRoles.size === 0) return void 0;
|
|
6677
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
6678
|
+
if (assigneeRole === "COO") return void 0;
|
|
6679
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
6680
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
6681
|
+
}
|
|
6403
6682
|
async function resolveTask(client, identifier, scopeSession) {
|
|
6404
6683
|
const scope = sessionScopeFilter(scopeSession);
|
|
6405
6684
|
let result = await client.execute({
|
|
@@ -6449,7 +6728,14 @@ async function createTaskCore(input) {
|
|
|
6449
6728
|
const id = crypto5.randomUUID();
|
|
6450
6729
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6451
6730
|
const slug = slugify(input.title);
|
|
6452
|
-
|
|
6731
|
+
let earlySessionScope = null;
|
|
6732
|
+
try {
|
|
6733
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6734
|
+
earlySessionScope = resolveExeSession2();
|
|
6735
|
+
} catch {
|
|
6736
|
+
}
|
|
6737
|
+
const scope = earlySessionScope ?? "default";
|
|
6738
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
6453
6739
|
let blockedById = null;
|
|
6454
6740
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
6455
6741
|
if (input.blockedBy) {
|
|
@@ -6489,22 +6775,24 @@ async function createTaskCore(input) {
|
|
|
6489
6775
|
if (dupCheck.rows.length > 0) {
|
|
6490
6776
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
6491
6777
|
}
|
|
6778
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
6779
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
6780
|
+
if (laneWarning) {
|
|
6781
|
+
warning = warning ? `${warning}
|
|
6782
|
+
${laneWarning}` : laneWarning;
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6492
6785
|
if (input.baseDir) {
|
|
6493
6786
|
try {
|
|
6494
|
-
await mkdir4(
|
|
6495
|
-
await mkdir4(
|
|
6787
|
+
await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
6788
|
+
await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
6496
6789
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
6497
6790
|
await ensureGitignoreExe(input.baseDir);
|
|
6498
6791
|
} catch {
|
|
6499
6792
|
}
|
|
6500
6793
|
}
|
|
6501
6794
|
const complexity = input.complexity ?? "standard";
|
|
6502
|
-
|
|
6503
|
-
try {
|
|
6504
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6505
|
-
sessionScope = resolveExeSession2();
|
|
6506
|
-
} catch {
|
|
6507
|
-
}
|
|
6795
|
+
const sessionScope = earlySessionScope;
|
|
6508
6796
|
await client.execute({
|
|
6509
6797
|
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)
|
|
6510
6798
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -6531,6 +6819,43 @@ async function createTaskCore(input) {
|
|
|
6531
6819
|
now
|
|
6532
6820
|
]
|
|
6533
6821
|
});
|
|
6822
|
+
if (input.baseDir) {
|
|
6823
|
+
try {
|
|
6824
|
+
const EXE_OS_DIR = path13.join(os8.homedir(), ".exe-os");
|
|
6825
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
6826
|
+
const mdDir = path13.dirname(mdPath);
|
|
6827
|
+
if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
6828
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
6829
|
+
const mdContent = `# ${input.title}
|
|
6830
|
+
|
|
6831
|
+
**ID:** ${id}
|
|
6832
|
+
**Status:** ${initialStatus}
|
|
6833
|
+
**Priority:** ${input.priority}
|
|
6834
|
+
**Assigned by:** ${input.assignedBy}
|
|
6835
|
+
**Assigned to:** ${input.assignedTo}
|
|
6836
|
+
**Project:** ${input.projectName}
|
|
6837
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
6838
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
6839
|
+
**Reviewer:** ${reviewer}
|
|
6840
|
+
|
|
6841
|
+
## Context
|
|
6842
|
+
|
|
6843
|
+
${input.context}
|
|
6844
|
+
|
|
6845
|
+
## MANDATORY: When done
|
|
6846
|
+
|
|
6847
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
6848
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
6849
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
6850
|
+
`;
|
|
6851
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
6852
|
+
} catch (err) {
|
|
6853
|
+
process.stderr.write(
|
|
6854
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
6855
|
+
`
|
|
6856
|
+
);
|
|
6857
|
+
}
|
|
6858
|
+
}
|
|
6534
6859
|
return {
|
|
6535
6860
|
id,
|
|
6536
6861
|
title: input.title,
|
|
@@ -6723,7 +7048,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
6723
7048
|
return { row, taskFile, now, taskId };
|
|
6724
7049
|
}
|
|
6725
7050
|
}
|
|
6726
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
7051
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
6727
7052
|
process.stderr.write(
|
|
6728
7053
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
6729
7054
|
`
|
|
@@ -6788,9 +7113,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
6788
7113
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
6789
7114
|
}
|
|
6790
7115
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
6791
|
-
const archPath =
|
|
7116
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
6792
7117
|
try {
|
|
6793
|
-
if (
|
|
7118
|
+
if (existsSync12(archPath)) return;
|
|
6794
7119
|
const template = [
|
|
6795
7120
|
`# ${projectName} \u2014 System Architecture`,
|
|
6796
7121
|
"",
|
|
@@ -6823,10 +7148,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
6823
7148
|
}
|
|
6824
7149
|
}
|
|
6825
7150
|
async function ensureGitignoreExe(baseDir) {
|
|
6826
|
-
const gitignorePath =
|
|
7151
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
6827
7152
|
try {
|
|
6828
|
-
if (
|
|
6829
|
-
const content =
|
|
7153
|
+
if (existsSync12(gitignorePath)) {
|
|
7154
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
6830
7155
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
6831
7156
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
6832
7157
|
} else {
|
|
@@ -6835,20 +7160,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
6835
7160
|
} catch {
|
|
6836
7161
|
}
|
|
6837
7162
|
}
|
|
6838
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
7163
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
6839
7164
|
var init_tasks_crud = __esm({
|
|
6840
7165
|
"src/lib/tasks-crud.ts"() {
|
|
6841
7166
|
"use strict";
|
|
6842
7167
|
init_database();
|
|
6843
7168
|
init_task_scope();
|
|
7169
|
+
init_employees();
|
|
7170
|
+
LANE_KEYWORDS = {
|
|
7171
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
7172
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
7173
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
7174
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
7175
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
7176
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
7177
|
+
};
|
|
7178
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
6844
7179
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
6845
7180
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
6846
7181
|
}
|
|
6847
7182
|
});
|
|
6848
7183
|
|
|
6849
7184
|
// src/lib/tasks-review.ts
|
|
6850
|
-
import
|
|
6851
|
-
import { existsSync as
|
|
7185
|
+
import path14 from "path";
|
|
7186
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
6852
7187
|
async function countPendingReviews(sessionScope) {
|
|
6853
7188
|
const client = getClient();
|
|
6854
7189
|
if (sessionScope) {
|
|
@@ -6870,7 +7205,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
6870
7205
|
const result2 = await client.execute({
|
|
6871
7206
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
6872
7207
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
6873
|
-
AND
|
|
7208
|
+
AND session_scope = ?`,
|
|
6874
7209
|
args: [sinceIso, sessionScope]
|
|
6875
7210
|
});
|
|
6876
7211
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -6888,7 +7223,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
6888
7223
|
const result2 = await client.execute({
|
|
6889
7224
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
6890
7225
|
WHERE status = 'needs_review'
|
|
6891
|
-
AND
|
|
7226
|
+
AND session_scope = ?
|
|
6892
7227
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
6893
7228
|
args: [sessionScope, limit]
|
|
6894
7229
|
});
|
|
@@ -7009,14 +7344,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
7009
7344
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
7010
7345
|
const agent = parts[1];
|
|
7011
7346
|
const slug = parts.slice(2).join("-");
|
|
7012
|
-
const
|
|
7347
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
7013
7348
|
const result = await client.execute({
|
|
7014
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
7015
|
-
args: [now,
|
|
7349
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
7350
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
7016
7351
|
});
|
|
7017
7352
|
if (result.rowsAffected > 0) {
|
|
7018
7353
|
process.stderr.write(
|
|
7019
|
-
`[review-cleanup] Cascaded original task to done
|
|
7354
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
7020
7355
|
`
|
|
7021
7356
|
);
|
|
7022
7357
|
}
|
|
@@ -7029,11 +7364,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
7029
7364
|
);
|
|
7030
7365
|
}
|
|
7031
7366
|
try {
|
|
7032
|
-
const cacheDir =
|
|
7033
|
-
if (
|
|
7367
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
7368
|
+
if (existsSync13(cacheDir)) {
|
|
7034
7369
|
for (const f of readdirSync3(cacheDir)) {
|
|
7035
7370
|
if (f.startsWith("review-notified-")) {
|
|
7036
|
-
unlinkSync4(
|
|
7371
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
7037
7372
|
}
|
|
7038
7373
|
}
|
|
7039
7374
|
}
|
|
@@ -7054,7 +7389,7 @@ var init_tasks_review = __esm({
|
|
|
7054
7389
|
});
|
|
7055
7390
|
|
|
7056
7391
|
// src/lib/tasks-chain.ts
|
|
7057
|
-
import
|
|
7392
|
+
import path15 from "path";
|
|
7058
7393
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
7059
7394
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
7060
7395
|
const client = getClient();
|
|
@@ -7071,7 +7406,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7071
7406
|
});
|
|
7072
7407
|
for (const ur of unblockedRows.rows) {
|
|
7073
7408
|
try {
|
|
7074
|
-
const ubFile =
|
|
7409
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
7075
7410
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
7076
7411
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
7077
7412
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -7140,7 +7475,7 @@ var init_tasks_chain = __esm({
|
|
|
7140
7475
|
|
|
7141
7476
|
// src/lib/project-name.ts
|
|
7142
7477
|
import { execSync as execSync5 } from "child_process";
|
|
7143
|
-
import
|
|
7478
|
+
import path16 from "path";
|
|
7144
7479
|
function getProjectName(cwd) {
|
|
7145
7480
|
const dir = cwd ?? process.cwd();
|
|
7146
7481
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -7153,7 +7488,7 @@ function getProjectName(cwd) {
|
|
|
7153
7488
|
timeout: 2e3,
|
|
7154
7489
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7155
7490
|
}).trim();
|
|
7156
|
-
repoRoot =
|
|
7491
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
7157
7492
|
} catch {
|
|
7158
7493
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
7159
7494
|
cwd: dir,
|
|
@@ -7162,11 +7497,11 @@ function getProjectName(cwd) {
|
|
|
7162
7497
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7163
7498
|
}).trim();
|
|
7164
7499
|
}
|
|
7165
|
-
_cached2 =
|
|
7500
|
+
_cached2 = path16.basename(repoRoot);
|
|
7166
7501
|
_cachedCwd = dir;
|
|
7167
7502
|
return _cached2;
|
|
7168
7503
|
} catch {
|
|
7169
|
-
_cached2 =
|
|
7504
|
+
_cached2 = path16.basename(dir);
|
|
7170
7505
|
_cachedCwd = dir;
|
|
7171
7506
|
return _cached2;
|
|
7172
7507
|
}
|
|
@@ -7198,7 +7533,7 @@ function findSessionForProject(projectName) {
|
|
|
7198
7533
|
const sessions = listSessions();
|
|
7199
7534
|
for (const s of sessions) {
|
|
7200
7535
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
7201
|
-
if (proj === projectName &&
|
|
7536
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
7202
7537
|
}
|
|
7203
7538
|
return null;
|
|
7204
7539
|
}
|
|
@@ -7244,7 +7579,7 @@ var init_session_scope = __esm({
|
|
|
7244
7579
|
|
|
7245
7580
|
// src/lib/tasks-notify.ts
|
|
7246
7581
|
async function dispatchTaskToEmployee(input) {
|
|
7247
|
-
if (
|
|
7582
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
7248
7583
|
let crossProject = false;
|
|
7249
7584
|
if (input.projectName) {
|
|
7250
7585
|
try {
|
|
@@ -7639,8 +7974,8 @@ __export(tasks_exports, {
|
|
|
7639
7974
|
updateTaskStatus: () => updateTaskStatus,
|
|
7640
7975
|
writeCheckpoint: () => writeCheckpoint
|
|
7641
7976
|
});
|
|
7642
|
-
import
|
|
7643
|
-
import { writeFileSync as
|
|
7977
|
+
import path17 from "path";
|
|
7978
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
7644
7979
|
async function createTask(input) {
|
|
7645
7980
|
const result = await createTaskCore(input);
|
|
7646
7981
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -7659,11 +7994,11 @@ async function updateTask(input) {
|
|
|
7659
7994
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
7660
7995
|
try {
|
|
7661
7996
|
const agent = String(row.assigned_to);
|
|
7662
|
-
const cacheDir =
|
|
7663
|
-
const cachePath =
|
|
7997
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
7998
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
7664
7999
|
if (input.status === "in_progress") {
|
|
7665
|
-
|
|
7666
|
-
|
|
8000
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
8001
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
7667
8002
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
7668
8003
|
try {
|
|
7669
8004
|
unlinkSync5(cachePath);
|
|
@@ -7723,7 +8058,7 @@ async function updateTask(input) {
|
|
|
7723
8058
|
}
|
|
7724
8059
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
7725
8060
|
if (isTerminal) {
|
|
7726
|
-
const isCoordinator =
|
|
8061
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
7727
8062
|
if (!isCoordinator) {
|
|
7728
8063
|
notifyTaskDone();
|
|
7729
8064
|
}
|
|
@@ -7748,7 +8083,7 @@ async function updateTask(input) {
|
|
|
7748
8083
|
}
|
|
7749
8084
|
}
|
|
7750
8085
|
}
|
|
7751
|
-
if (input.status === "done" &&
|
|
8086
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
7752
8087
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
7753
8088
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
7754
8089
|
taskId,
|
|
@@ -7764,7 +8099,7 @@ async function updateTask(input) {
|
|
|
7764
8099
|
});
|
|
7765
8100
|
}
|
|
7766
8101
|
let nextTask;
|
|
7767
|
-
if (isTerminal &&
|
|
8102
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
7768
8103
|
try {
|
|
7769
8104
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
7770
8105
|
} catch {
|
|
@@ -8130,13 +8465,13 @@ __export(tmux_routing_exports, {
|
|
|
8130
8465
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
8131
8466
|
});
|
|
8132
8467
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
8133
|
-
import { readFileSync as
|
|
8134
|
-
import
|
|
8135
|
-
import
|
|
8468
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, existsSync as existsSync14, appendFileSync } from "fs";
|
|
8469
|
+
import path18 from "path";
|
|
8470
|
+
import os9 from "os";
|
|
8136
8471
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8137
8472
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
8138
8473
|
function spawnLockPath(sessionName) {
|
|
8139
|
-
return
|
|
8474
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
8140
8475
|
}
|
|
8141
8476
|
function isProcessAlive(pid) {
|
|
8142
8477
|
try {
|
|
@@ -8147,13 +8482,13 @@ function isProcessAlive(pid) {
|
|
|
8147
8482
|
}
|
|
8148
8483
|
}
|
|
8149
8484
|
function acquireSpawnLock2(sessionName) {
|
|
8150
|
-
if (!
|
|
8151
|
-
|
|
8485
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
8486
|
+
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
8152
8487
|
}
|
|
8153
8488
|
const lockFile = spawnLockPath(sessionName);
|
|
8154
|
-
if (
|
|
8489
|
+
if (existsSync14(lockFile)) {
|
|
8155
8490
|
try {
|
|
8156
|
-
const lock = JSON.parse(
|
|
8491
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
8157
8492
|
const age = Date.now() - lock.timestamp;
|
|
8158
8493
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
8159
8494
|
return false;
|
|
@@ -8161,7 +8496,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
8161
8496
|
} catch {
|
|
8162
8497
|
}
|
|
8163
8498
|
}
|
|
8164
|
-
|
|
8499
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
8165
8500
|
return true;
|
|
8166
8501
|
}
|
|
8167
8502
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -8173,13 +8508,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
8173
8508
|
function resolveBehaviorsExporterScript() {
|
|
8174
8509
|
try {
|
|
8175
8510
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
8176
|
-
const scriptPath =
|
|
8177
|
-
|
|
8511
|
+
const scriptPath = path18.join(
|
|
8512
|
+
path18.dirname(thisFile),
|
|
8178
8513
|
"..",
|
|
8179
8514
|
"bin",
|
|
8180
8515
|
"exe-export-behaviors.js"
|
|
8181
8516
|
);
|
|
8182
|
-
return
|
|
8517
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
8183
8518
|
} catch {
|
|
8184
8519
|
return null;
|
|
8185
8520
|
}
|
|
@@ -8245,12 +8580,12 @@ function extractRootExe(name) {
|
|
|
8245
8580
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
8246
8581
|
}
|
|
8247
8582
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
8248
|
-
if (!
|
|
8249
|
-
|
|
8583
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
8584
|
+
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
8250
8585
|
}
|
|
8251
8586
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
8252
|
-
const filePath =
|
|
8253
|
-
|
|
8587
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
8588
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
8254
8589
|
parentExe: rootExe,
|
|
8255
8590
|
dispatchedBy: dispatchedBy || rootExe,
|
|
8256
8591
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8258,7 +8593,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
8258
8593
|
}
|
|
8259
8594
|
function getParentExe(sessionKey) {
|
|
8260
8595
|
try {
|
|
8261
|
-
const data = JSON.parse(
|
|
8596
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
8262
8597
|
return data.parentExe || null;
|
|
8263
8598
|
} catch {
|
|
8264
8599
|
return null;
|
|
@@ -8266,8 +8601,8 @@ function getParentExe(sessionKey) {
|
|
|
8266
8601
|
}
|
|
8267
8602
|
function getDispatchedBy(sessionKey) {
|
|
8268
8603
|
try {
|
|
8269
|
-
const data = JSON.parse(
|
|
8270
|
-
|
|
8604
|
+
const data = JSON.parse(readFileSync12(
|
|
8605
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
8271
8606
|
"utf8"
|
|
8272
8607
|
));
|
|
8273
8608
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -8328,32 +8663,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
8328
8663
|
}
|
|
8329
8664
|
function readDebounceState() {
|
|
8330
8665
|
try {
|
|
8331
|
-
if (!
|
|
8332
|
-
|
|
8666
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
8667
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
8668
|
+
const state = {};
|
|
8669
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
8670
|
+
if (typeof val === "number") {
|
|
8671
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
8672
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
8673
|
+
state[key] = val;
|
|
8674
|
+
}
|
|
8675
|
+
}
|
|
8676
|
+
return state;
|
|
8333
8677
|
} catch {
|
|
8334
8678
|
return {};
|
|
8335
8679
|
}
|
|
8336
8680
|
}
|
|
8337
8681
|
function writeDebounceState(state) {
|
|
8338
8682
|
try {
|
|
8339
|
-
if (!
|
|
8340
|
-
|
|
8683
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
8684
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
8341
8685
|
} catch {
|
|
8342
8686
|
}
|
|
8343
8687
|
}
|
|
8344
8688
|
function isDebounced(targetSession) {
|
|
8345
8689
|
const state = readDebounceState();
|
|
8346
|
-
const
|
|
8347
|
-
|
|
8690
|
+
const entry = state[targetSession];
|
|
8691
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
8692
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
8693
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
8694
|
+
state[targetSession].pending++;
|
|
8695
|
+
writeDebounceState(state);
|
|
8696
|
+
return true;
|
|
8697
|
+
}
|
|
8698
|
+
return false;
|
|
8348
8699
|
}
|
|
8349
8700
|
function recordDebounce(targetSession) {
|
|
8350
8701
|
const state = readDebounceState();
|
|
8351
|
-
state[targetSession]
|
|
8702
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
8703
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
8352
8704
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
8353
8705
|
for (const key of Object.keys(state)) {
|
|
8354
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
8706
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
8355
8707
|
}
|
|
8356
8708
|
writeDebounceState(state);
|
|
8709
|
+
return batched;
|
|
8357
8710
|
}
|
|
8358
8711
|
function logIntercom(msg) {
|
|
8359
8712
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -8398,7 +8751,7 @@ function sendIntercom(targetSession) {
|
|
|
8398
8751
|
return "skipped_exe";
|
|
8399
8752
|
}
|
|
8400
8753
|
if (isDebounced(targetSession)) {
|
|
8401
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
8754
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
8402
8755
|
return "debounced";
|
|
8403
8756
|
}
|
|
8404
8757
|
try {
|
|
@@ -8410,14 +8763,14 @@ function sendIntercom(targetSession) {
|
|
|
8410
8763
|
const sessionState = getSessionState(targetSession);
|
|
8411
8764
|
if (sessionState === "no_claude") {
|
|
8412
8765
|
queueIntercom(targetSession, "claude not running in session");
|
|
8413
|
-
recordDebounce(targetSession);
|
|
8414
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
8766
|
+
const batched2 = recordDebounce(targetSession);
|
|
8767
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
8415
8768
|
return "queued";
|
|
8416
8769
|
}
|
|
8417
8770
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
8418
8771
|
queueIntercom(targetSession, "session busy at send time");
|
|
8419
|
-
recordDebounce(targetSession);
|
|
8420
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
8772
|
+
const batched2 = recordDebounce(targetSession);
|
|
8773
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
8421
8774
|
return "queued";
|
|
8422
8775
|
}
|
|
8423
8776
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -8425,8 +8778,8 @@ function sendIntercom(targetSession) {
|
|
|
8425
8778
|
transport.sendKeys(targetSession, "q");
|
|
8426
8779
|
}
|
|
8427
8780
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
8428
|
-
recordDebounce(targetSession);
|
|
8429
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
8781
|
+
const batched = recordDebounce(targetSession);
|
|
8782
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
8430
8783
|
return "delivered";
|
|
8431
8784
|
} catch {
|
|
8432
8785
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -8456,7 +8809,7 @@ function notifyParentExe(sessionKey) {
|
|
|
8456
8809
|
return true;
|
|
8457
8810
|
}
|
|
8458
8811
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
8459
|
-
if (
|
|
8812
|
+
if (isCoordinatorName(employeeName)) {
|
|
8460
8813
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
8461
8814
|
}
|
|
8462
8815
|
try {
|
|
@@ -8528,26 +8881,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8528
8881
|
const transport = getTransport();
|
|
8529
8882
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
8530
8883
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
8531
|
-
const logDir =
|
|
8532
|
-
const logFile =
|
|
8533
|
-
if (!
|
|
8534
|
-
|
|
8884
|
+
const logDir = path18.join(os9.homedir(), ".exe-os", "session-logs");
|
|
8885
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
8886
|
+
if (!existsSync14(logDir)) {
|
|
8887
|
+
mkdirSync8(logDir, { recursive: true });
|
|
8535
8888
|
}
|
|
8536
8889
|
transport.kill(sessionName);
|
|
8537
8890
|
let cleanupSuffix = "";
|
|
8538
8891
|
try {
|
|
8539
8892
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
8540
|
-
const cleanupScript =
|
|
8541
|
-
if (
|
|
8893
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
8894
|
+
if (existsSync14(cleanupScript)) {
|
|
8542
8895
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
8543
8896
|
}
|
|
8544
8897
|
} catch {
|
|
8545
8898
|
}
|
|
8546
8899
|
try {
|
|
8547
|
-
const claudeJsonPath =
|
|
8900
|
+
const claudeJsonPath = path18.join(os9.homedir(), ".claude.json");
|
|
8548
8901
|
let claudeJson = {};
|
|
8549
8902
|
try {
|
|
8550
|
-
claudeJson = JSON.parse(
|
|
8903
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
8551
8904
|
} catch {
|
|
8552
8905
|
}
|
|
8553
8906
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -8555,17 +8908,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8555
8908
|
const trustDir = opts?.cwd ?? projectDir;
|
|
8556
8909
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
8557
8910
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
8558
|
-
|
|
8911
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
8559
8912
|
} catch {
|
|
8560
8913
|
}
|
|
8561
8914
|
try {
|
|
8562
|
-
const settingsDir =
|
|
8915
|
+
const settingsDir = path18.join(os9.homedir(), ".claude", "projects");
|
|
8563
8916
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
8564
|
-
const projSettingsDir =
|
|
8565
|
-
const settingsPath =
|
|
8917
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
8918
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
8566
8919
|
let settings = {};
|
|
8567
8920
|
try {
|
|
8568
|
-
settings = JSON.parse(
|
|
8921
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
8569
8922
|
} catch {
|
|
8570
8923
|
}
|
|
8571
8924
|
const perms = settings.permissions ?? {};
|
|
@@ -8593,21 +8946,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8593
8946
|
if (changed) {
|
|
8594
8947
|
perms.allow = allow;
|
|
8595
8948
|
settings.permissions = perms;
|
|
8596
|
-
|
|
8597
|
-
|
|
8949
|
+
mkdirSync8(projSettingsDir, { recursive: true });
|
|
8950
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
8598
8951
|
}
|
|
8599
8952
|
} catch {
|
|
8600
8953
|
}
|
|
8601
8954
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
8602
8955
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
8603
|
-
const
|
|
8956
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
8957
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
8958
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
8959
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
8604
8960
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
8605
8961
|
let identityFlag = "";
|
|
8606
8962
|
let behaviorsFlag = "";
|
|
8607
8963
|
let legacyFallbackWarned = false;
|
|
8608
8964
|
if (!useExeAgent && !useBinSymlink) {
|
|
8609
|
-
const identityPath =
|
|
8610
|
-
|
|
8965
|
+
const identityPath = path18.join(
|
|
8966
|
+
os9.homedir(),
|
|
8611
8967
|
".exe-os",
|
|
8612
8968
|
"identity",
|
|
8613
8969
|
`${employeeName}.md`
|
|
@@ -8616,13 +8972,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8616
8972
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
8617
8973
|
if (hasAgentFlag) {
|
|
8618
8974
|
identityFlag = ` --agent ${employeeName}`;
|
|
8619
|
-
} else if (
|
|
8975
|
+
} else if (existsSync14(identityPath)) {
|
|
8620
8976
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
8621
8977
|
legacyFallbackWarned = true;
|
|
8622
8978
|
}
|
|
8623
8979
|
const behaviorsFile = exportBehaviorsSync(
|
|
8624
8980
|
employeeName,
|
|
8625
|
-
|
|
8981
|
+
path18.basename(spawnCwd),
|
|
8626
8982
|
sessionName
|
|
8627
8983
|
);
|
|
8628
8984
|
if (behaviorsFile) {
|
|
@@ -8637,16 +8993,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8637
8993
|
}
|
|
8638
8994
|
let sessionContextFlag = "";
|
|
8639
8995
|
try {
|
|
8640
|
-
const ctxDir =
|
|
8641
|
-
|
|
8642
|
-
const ctxFile =
|
|
8996
|
+
const ctxDir = path18.join(os9.homedir(), ".exe-os", "session-cache");
|
|
8997
|
+
mkdirSync8(ctxDir, { recursive: true });
|
|
8998
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
8643
8999
|
const ctxContent = [
|
|
8644
9000
|
`## Session Context`,
|
|
8645
9001
|
`You are running in tmux session: ${sessionName}.`,
|
|
8646
9002
|
`Your parent coordinator session is ${exeSession}.`,
|
|
8647
9003
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
8648
9004
|
].join("\n");
|
|
8649
|
-
|
|
9005
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
8650
9006
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
8651
9007
|
} catch {
|
|
8652
9008
|
}
|
|
@@ -8660,9 +9016,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8660
9016
|
}
|
|
8661
9017
|
}
|
|
8662
9018
|
}
|
|
9019
|
+
if (useCodex) {
|
|
9020
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
9021
|
+
if (codexCfg?.apiKeyEnv) {
|
|
9022
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
9023
|
+
if (keyVal) {
|
|
9024
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
9025
|
+
}
|
|
9026
|
+
}
|
|
9027
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
9028
|
+
}
|
|
9029
|
+
if (useOpencode) {
|
|
9030
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
9031
|
+
if (ocCfg?.apiKeyEnv) {
|
|
9032
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
9033
|
+
if (keyVal) {
|
|
9034
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
9035
|
+
}
|
|
9036
|
+
}
|
|
9037
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
9038
|
+
}
|
|
9039
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
9040
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
9041
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
9042
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
8663
9045
|
let spawnCommand;
|
|
8664
9046
|
if (useExeAgent) {
|
|
8665
9047
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
9048
|
+
} else if (useCodex) {
|
|
9049
|
+
process.stderr.write(
|
|
9050
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
9051
|
+
`
|
|
9052
|
+
);
|
|
9053
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
9054
|
+
} else if (useOpencode) {
|
|
9055
|
+
const binName = `${employeeName}-opencode`;
|
|
9056
|
+
process.stderr.write(
|
|
9057
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
9058
|
+
`
|
|
9059
|
+
);
|
|
9060
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
8666
9061
|
} else if (useBinSymlink) {
|
|
8667
9062
|
const binName = `${employeeName}-${ccProvider}`;
|
|
8668
9063
|
process.stderr.write(
|
|
@@ -8684,11 +9079,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8684
9079
|
transport.pipeLog(sessionName, logFile);
|
|
8685
9080
|
try {
|
|
8686
9081
|
const mySession = getMySession();
|
|
8687
|
-
const dispatchInfo =
|
|
8688
|
-
|
|
9082
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
9083
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
8689
9084
|
dispatchedBy: mySession,
|
|
8690
9085
|
rootExe: exeSession,
|
|
8691
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
9086
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
9087
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
9088
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
8692
9089
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8693
9090
|
}));
|
|
8694
9091
|
} catch {
|
|
@@ -8706,6 +9103,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8706
9103
|
booted = true;
|
|
8707
9104
|
break;
|
|
8708
9105
|
}
|
|
9106
|
+
} else if (useCodex) {
|
|
9107
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
9108
|
+
booted = true;
|
|
9109
|
+
break;
|
|
9110
|
+
}
|
|
8709
9111
|
} else {
|
|
8710
9112
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
8711
9113
|
booted = true;
|
|
@@ -8717,9 +9119,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8717
9119
|
}
|
|
8718
9120
|
if (!booted) {
|
|
8719
9121
|
releaseSpawnLock2(sessionName);
|
|
8720
|
-
|
|
9122
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
9123
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
8721
9124
|
}
|
|
8722
|
-
if (!useExeAgent) {
|
|
9125
|
+
if (!useExeAgent && !useCodex) {
|
|
8723
9126
|
try {
|
|
8724
9127
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
8725
9128
|
} catch {
|
|
@@ -8746,17 +9149,19 @@ var init_tmux_routing = __esm({
|
|
|
8746
9149
|
init_cc_agent_support();
|
|
8747
9150
|
init_mcp_prefix();
|
|
8748
9151
|
init_provider_table();
|
|
9152
|
+
init_agent_config();
|
|
9153
|
+
init_runtime_table();
|
|
8749
9154
|
init_intercom_queue();
|
|
8750
9155
|
init_plan_limits();
|
|
8751
9156
|
init_employees();
|
|
8752
|
-
SPAWN_LOCK_DIR =
|
|
8753
|
-
SESSION_CACHE =
|
|
9157
|
+
SPAWN_LOCK_DIR = path18.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
9158
|
+
SESSION_CACHE = path18.join(os9.homedir(), ".exe-os", "session-cache");
|
|
8754
9159
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
8755
9160
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8756
9161
|
VERIFY_PANE_LINES = 200;
|
|
8757
9162
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
8758
|
-
INTERCOM_LOG2 =
|
|
8759
|
-
DEBOUNCE_FILE =
|
|
9163
|
+
INTERCOM_LOG2 = path18.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
9164
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
8760
9165
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
8761
9166
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
8762
9167
|
}
|
|
@@ -9014,10 +9419,10 @@ var init_messaging = __esm({
|
|
|
9014
9419
|
});
|
|
9015
9420
|
|
|
9016
9421
|
// src/automation/trigger-engine.ts
|
|
9017
|
-
import { readFileSync as
|
|
9422
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
|
|
9018
9423
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9019
|
-
import
|
|
9020
|
-
import
|
|
9424
|
+
import path19 from "path";
|
|
9425
|
+
import os10 from "os";
|
|
9021
9426
|
function substituteTemplate(template, record) {
|
|
9022
9427
|
return template.replace(
|
|
9023
9428
|
/\{\{(\w+(?:\.\w+)*)\}\}/g,
|
|
@@ -9070,9 +9475,9 @@ function evaluateConditions(conditions, record) {
|
|
|
9070
9475
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
9071
9476
|
}
|
|
9072
9477
|
function loadTriggers(project) {
|
|
9073
|
-
if (!
|
|
9478
|
+
if (!existsSync15(TRIGGERS_PATH)) return [];
|
|
9074
9479
|
try {
|
|
9075
|
-
const raw =
|
|
9480
|
+
const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
|
|
9076
9481
|
const all = JSON.parse(raw);
|
|
9077
9482
|
if (!Array.isArray(all)) return [];
|
|
9078
9483
|
if (project) {
|
|
@@ -9312,7 +9717,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
|
|
|
9312
9717
|
var init_trigger_engine = __esm({
|
|
9313
9718
|
"src/automation/trigger-engine.ts"() {
|
|
9314
9719
|
"use strict";
|
|
9315
|
-
TRIGGERS_PATH =
|
|
9720
|
+
TRIGGERS_PATH = path19.join(os10.homedir(), ".exe-os", "triggers.json");
|
|
9316
9721
|
GRAPH_API_VERSION = "v21.0";
|
|
9317
9722
|
}
|
|
9318
9723
|
});
|
|
@@ -9375,15 +9780,15 @@ var init_crm_webhook = __esm({
|
|
|
9375
9780
|
});
|
|
9376
9781
|
|
|
9377
9782
|
// src/bin/exe-gateway.ts
|
|
9378
|
-
import { existsSync as
|
|
9379
|
-
import
|
|
9380
|
-
import
|
|
9783
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
9784
|
+
import path20 from "path";
|
|
9785
|
+
import os11 from "os";
|
|
9381
9786
|
|
|
9382
9787
|
// src/gateway/webhook-server.ts
|
|
9383
9788
|
import {
|
|
9384
9789
|
createServer
|
|
9385
9790
|
} from "http";
|
|
9386
|
-
var DEFAULT_HOST = "127.0.0.1";
|
|
9791
|
+
var DEFAULT_HOST = process.env.EXE_WEBHOOK_HOST || "127.0.0.1";
|
|
9387
9792
|
var BODY_SIZE_LIMIT = 1048576;
|
|
9388
9793
|
var WebhookServer = class {
|
|
9389
9794
|
constructor(config2) {
|
|
@@ -10069,10 +10474,49 @@ function buildPermissionContext(platform, permissions) {
|
|
|
10069
10474
|
return `[${platform.toUpperCase()} \u2014 allowed: ${parts.join(", ")}]`;
|
|
10070
10475
|
}
|
|
10071
10476
|
|
|
10477
|
+
// src/gateway/bot-errors.ts
|
|
10478
|
+
var FatalBotError = class extends Error {
|
|
10479
|
+
constructor(message, cause) {
|
|
10480
|
+
super(message);
|
|
10481
|
+
this.cause = cause;
|
|
10482
|
+
this.name = "FatalBotError";
|
|
10483
|
+
}
|
|
10484
|
+
fatal = true;
|
|
10485
|
+
};
|
|
10486
|
+
var RecoverableBotError = class extends Error {
|
|
10487
|
+
constructor(message, toolName, cause) {
|
|
10488
|
+
super(message);
|
|
10489
|
+
this.toolName = toolName;
|
|
10490
|
+
this.cause = cause;
|
|
10491
|
+
this.name = "RecoverableBotError";
|
|
10492
|
+
}
|
|
10493
|
+
recoverable = true;
|
|
10494
|
+
};
|
|
10495
|
+
var MaxStepsError = class extends Error {
|
|
10496
|
+
constructor(stepsTaken, maxSteps) {
|
|
10497
|
+
super(
|
|
10498
|
+
`Reached maximum steps (${stepsTaken}/${maxSteps}). Returning partial result.`
|
|
10499
|
+
);
|
|
10500
|
+
this.stepsTaken = stepsTaken;
|
|
10501
|
+
this.maxSteps = maxSteps;
|
|
10502
|
+
this.name = "MaxStepsError";
|
|
10503
|
+
}
|
|
10504
|
+
};
|
|
10505
|
+
function classifyError(err, toolName) {
|
|
10506
|
+
if (err instanceof FatalBotError) return err;
|
|
10507
|
+
if (err instanceof RecoverableBotError) return err;
|
|
10508
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10509
|
+
if (message.includes("401") || message.includes("403") || message.includes("authentication") || message.includes("rate_limit")) {
|
|
10510
|
+
return new FatalBotError(message, err);
|
|
10511
|
+
}
|
|
10512
|
+
return new RecoverableBotError(message, toolName, err);
|
|
10513
|
+
}
|
|
10514
|
+
|
|
10072
10515
|
// src/gateway/bot-runtime.ts
|
|
10073
10516
|
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
10074
10517
|
var MAX_TURNS = 10;
|
|
10075
10518
|
var MAX_HISTORY = 50;
|
|
10519
|
+
var DEFAULT_PLANNING_INTERVAL = 3;
|
|
10076
10520
|
function filterToolsForPermissions(tools, permissions) {
|
|
10077
10521
|
const allAllowed = /* @__PURE__ */ new Set();
|
|
10078
10522
|
if (permissions.canRead) {
|
|
@@ -10101,7 +10545,7 @@ var BotRuntime = class {
|
|
|
10101
10545
|
async processMessage(msg, permissions) {
|
|
10102
10546
|
const sessionKey = msg.chatType === "group" ? msg.channelId : msg.senderId;
|
|
10103
10547
|
const history = this.getHistory(sessionKey);
|
|
10104
|
-
history.push({ role: "user", content: msg.text });
|
|
10548
|
+
history.push({ role: "user", content: msg.text, frameType: "task" });
|
|
10105
10549
|
const systemPrompt = this.config.systemPrompt + "\n\n" + buildPermissionContext(msg.platform, permissions);
|
|
10106
10550
|
const allowedTools = filterToolsForPermissions(
|
|
10107
10551
|
this.config.tools,
|
|
@@ -10109,29 +10553,54 @@ var BotRuntime = class {
|
|
|
10109
10553
|
);
|
|
10110
10554
|
const model = this.config.model ?? DEFAULT_MODEL;
|
|
10111
10555
|
const maxTurns = this.config.maxTurns ?? MAX_TURNS;
|
|
10556
|
+
const planningInterval = this.config.planningInterval ?? DEFAULT_PLANNING_INTERVAL;
|
|
10112
10557
|
let turns = 0;
|
|
10113
10558
|
while (turns < maxTurns) {
|
|
10114
10559
|
turns++;
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10560
|
+
if (planningInterval > 0 && turns > 1 && turns % planningInterval === 1 && maxTurns - turns >= 2) {
|
|
10561
|
+
history.push({
|
|
10562
|
+
role: "user",
|
|
10563
|
+
content: "[Planning checkpoint] Review what you know so far. What facts have you gathered? What is your plan for the remaining steps? Be concise.",
|
|
10564
|
+
frameType: "planning"
|
|
10565
|
+
});
|
|
10566
|
+
}
|
|
10567
|
+
let response;
|
|
10568
|
+
try {
|
|
10569
|
+
response = await this.client.messages.create({
|
|
10570
|
+
model,
|
|
10571
|
+
max_tokens: 4096,
|
|
10572
|
+
system: systemPrompt,
|
|
10573
|
+
messages: history.map((m) => ({
|
|
10574
|
+
role: m.role,
|
|
10575
|
+
content: m.content
|
|
10576
|
+
})),
|
|
10577
|
+
tools: allowedTools.map((t) => ({
|
|
10578
|
+
name: t.name,
|
|
10579
|
+
description: t.description,
|
|
10580
|
+
input_schema: t.input_schema
|
|
10581
|
+
}))
|
|
10582
|
+
});
|
|
10583
|
+
} catch (err) {
|
|
10584
|
+
const classified = classifyError(err);
|
|
10585
|
+
if (classified instanceof FatalBotError) {
|
|
10586
|
+
const errorMsg = `Bot error: ${classified.message}`;
|
|
10587
|
+
history.push({ role: "assistant", content: errorMsg, frameType: "error" });
|
|
10588
|
+
this.trimHistory(sessionKey);
|
|
10589
|
+
return errorMsg;
|
|
10590
|
+
}
|
|
10591
|
+
history.push({
|
|
10592
|
+
role: "assistant",
|
|
10593
|
+
content: `[API error \u2014 retrying] ${classified.message}`,
|
|
10594
|
+
frameType: "error"
|
|
10595
|
+
});
|
|
10596
|
+
continue;
|
|
10597
|
+
}
|
|
10129
10598
|
const toolUseBlocks = response.content.filter(
|
|
10130
10599
|
(b) => b.type === "tool_use"
|
|
10131
10600
|
);
|
|
10132
10601
|
if (toolUseBlocks.length === 0) {
|
|
10133
10602
|
const textContent = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
10134
|
-
history.push({ role: "assistant", content: textContent });
|
|
10603
|
+
history.push({ role: "assistant", content: textContent, frameType: "assistant" });
|
|
10135
10604
|
this.trimHistory(sessionKey);
|
|
10136
10605
|
return textContent;
|
|
10137
10606
|
}
|
|
@@ -10141,9 +10610,11 @@ var BotRuntime = class {
|
|
|
10141
10610
|
);
|
|
10142
10611
|
history.push({
|
|
10143
10612
|
role: "assistant",
|
|
10144
|
-
content: response.content
|
|
10613
|
+
content: response.content,
|
|
10614
|
+
frameType: "assistant"
|
|
10145
10615
|
});
|
|
10146
10616
|
const toolResults = [];
|
|
10617
|
+
let hadFatalError = false;
|
|
10147
10618
|
for (const block of allowed) {
|
|
10148
10619
|
try {
|
|
10149
10620
|
const result = await this.config.toolExecutor(
|
|
@@ -10156,12 +10627,23 @@ var BotRuntime = class {
|
|
|
10156
10627
|
content: result
|
|
10157
10628
|
});
|
|
10158
10629
|
} catch (err) {
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10630
|
+
const classified = classifyError(err, block.name);
|
|
10631
|
+
if (classified instanceof FatalBotError) {
|
|
10632
|
+
toolResults.push({
|
|
10633
|
+
type: "tool_result",
|
|
10634
|
+
tool_use_id: block.id,
|
|
10635
|
+
content: `Fatal error: ${classified.message}`,
|
|
10636
|
+
is_error: true
|
|
10637
|
+
});
|
|
10638
|
+
hadFatalError = true;
|
|
10639
|
+
} else {
|
|
10640
|
+
toolResults.push({
|
|
10641
|
+
type: "tool_result",
|
|
10642
|
+
tool_use_id: block.id,
|
|
10643
|
+
content: `Error (recoverable): ${classified.message}`,
|
|
10644
|
+
is_error: true
|
|
10645
|
+
});
|
|
10646
|
+
}
|
|
10165
10647
|
}
|
|
10166
10648
|
}
|
|
10167
10649
|
for (const { block, check } of blocked) {
|
|
@@ -10174,10 +10656,22 @@ var BotRuntime = class {
|
|
|
10174
10656
|
}
|
|
10175
10657
|
history.push({
|
|
10176
10658
|
role: "user",
|
|
10177
|
-
content: toolResults
|
|
10659
|
+
content: toolResults,
|
|
10660
|
+
frameType: hadFatalError ? "error" : "tool_result"
|
|
10178
10661
|
});
|
|
10662
|
+
if (hadFatalError) {
|
|
10663
|
+
this.trimHistory(sessionKey);
|
|
10664
|
+
return `A fatal error occurred during tool execution. The bot loop has been stopped.`;
|
|
10665
|
+
}
|
|
10179
10666
|
}
|
|
10180
|
-
|
|
10667
|
+
const maxErr = new MaxStepsError(turns, maxTurns);
|
|
10668
|
+
history.push({
|
|
10669
|
+
role: "assistant",
|
|
10670
|
+
content: maxErr.message,
|
|
10671
|
+
frameType: "error"
|
|
10672
|
+
});
|
|
10673
|
+
this.trimHistory(sessionKey);
|
|
10674
|
+
return maxErr.message;
|
|
10181
10675
|
}
|
|
10182
10676
|
getHistory(sessionKey) {
|
|
10183
10677
|
if (!this.conversations.has(sessionKey)) {
|
|
@@ -10187,9 +10681,19 @@ var BotRuntime = class {
|
|
|
10187
10681
|
}
|
|
10188
10682
|
trimHistory(sessionKey) {
|
|
10189
10683
|
const history = this.conversations.get(sessionKey);
|
|
10190
|
-
if (history
|
|
10191
|
-
|
|
10684
|
+
if (!history || history.length <= MAX_HISTORY) return;
|
|
10685
|
+
const firstTaskIdx = history.findIndex((m) => m.frameType === "task");
|
|
10686
|
+
let trimmed = history.filter(
|
|
10687
|
+
(m, i) => m.frameType !== "planning" || i >= history.length - MAX_HISTORY
|
|
10688
|
+
);
|
|
10689
|
+
if (trimmed.length > MAX_HISTORY) {
|
|
10690
|
+
const tail = trimmed.slice(-MAX_HISTORY);
|
|
10691
|
+
if (firstTaskIdx >= 0 && !tail.includes(history[firstTaskIdx])) {
|
|
10692
|
+
tail[0] = history[firstTaskIdx];
|
|
10693
|
+
}
|
|
10694
|
+
trimmed = tail;
|
|
10192
10695
|
}
|
|
10696
|
+
this.conversations.set(sessionKey, trimmed);
|
|
10193
10697
|
}
|
|
10194
10698
|
/** Clear conversation history for a session */
|
|
10195
10699
|
clearHistory(sessionKey) {
|
|
@@ -10244,18 +10748,18 @@ var BotRegistry = class {
|
|
|
10244
10748
|
|
|
10245
10749
|
// src/bin/exe-gateway.ts
|
|
10246
10750
|
init_employees();
|
|
10247
|
-
var CONFIG_DIR =
|
|
10248
|
-
var CONFIG_PATH3 =
|
|
10751
|
+
var CONFIG_DIR = path20.join(os11.homedir(), ".exe-os");
|
|
10752
|
+
var CONFIG_PATH3 = path20.join(CONFIG_DIR, "gateway.json");
|
|
10249
10753
|
var DEFAULT_PORT = 3100;
|
|
10250
10754
|
function loadConfig2() {
|
|
10251
|
-
if (!
|
|
10755
|
+
if (!existsSync16(CONFIG_PATH3)) {
|
|
10252
10756
|
console.log(
|
|
10253
10757
|
`[exe-gateway] No config at ${CONFIG_PATH3} \u2014 using defaults (port ${DEFAULT_PORT})`
|
|
10254
10758
|
);
|
|
10255
10759
|
return {};
|
|
10256
10760
|
}
|
|
10257
10761
|
try {
|
|
10258
|
-
const raw =
|
|
10762
|
+
const raw = readFileSync14(CONFIG_PATH3, "utf-8");
|
|
10259
10763
|
return JSON.parse(raw);
|
|
10260
10764
|
} catch (err) {
|
|
10261
10765
|
console.error(
|