@askexenow/exe-os 0.8.83 → 0.8.85
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 +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- 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 +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- 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 +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- 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 +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- 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 +120 -22
- 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 +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
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");
|
|
@@ -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();
|
|
@@ -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) => {
|
|
@@ -6357,6 +6555,7 @@ var init_task_scope = __esm({
|
|
|
6357
6555
|
// src/lib/tasks-crud.ts
|
|
6358
6556
|
import crypto5 from "crypto";
|
|
6359
6557
|
import path12 from "path";
|
|
6558
|
+
import os8 from "os";
|
|
6360
6559
|
import { execSync as execSync4 } from "child_process";
|
|
6361
6560
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
6362
6561
|
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
@@ -6400,6 +6599,35 @@ function extractParentFromContext(contextBody) {
|
|
|
6400
6599
|
function slugify(title) {
|
|
6401
6600
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
6402
6601
|
}
|
|
6602
|
+
function buildKeywordIndex() {
|
|
6603
|
+
const idx = /* @__PURE__ */ new Map();
|
|
6604
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
6605
|
+
for (const kw of keywords) {
|
|
6606
|
+
const existing = idx.get(kw) ?? [];
|
|
6607
|
+
existing.push(role);
|
|
6608
|
+
idx.set(kw, existing);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
return idx;
|
|
6612
|
+
}
|
|
6613
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
6614
|
+
const employees = loadEmployeesSync();
|
|
6615
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
6616
|
+
if (!employee) return void 0;
|
|
6617
|
+
const assigneeRole = employee.role;
|
|
6618
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
6619
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
6620
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
6621
|
+
if (text.includes(keyword)) {
|
|
6622
|
+
for (const role of roles) matchedRoles.add(role);
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
if (matchedRoles.size === 0) return void 0;
|
|
6626
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
6627
|
+
if (assigneeRole === "COO") return void 0;
|
|
6628
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
6629
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
6630
|
+
}
|
|
6403
6631
|
async function resolveTask(client, identifier, scopeSession) {
|
|
6404
6632
|
const scope = sessionScopeFilter(scopeSession);
|
|
6405
6633
|
let result = await client.execute({
|
|
@@ -6449,7 +6677,14 @@ async function createTaskCore(input) {
|
|
|
6449
6677
|
const id = crypto5.randomUUID();
|
|
6450
6678
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6451
6679
|
const slug = slugify(input.title);
|
|
6452
|
-
|
|
6680
|
+
let earlySessionScope = null;
|
|
6681
|
+
try {
|
|
6682
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6683
|
+
earlySessionScope = resolveExeSession2();
|
|
6684
|
+
} catch {
|
|
6685
|
+
}
|
|
6686
|
+
const scope = earlySessionScope ?? "default";
|
|
6687
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
6453
6688
|
let blockedById = null;
|
|
6454
6689
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
6455
6690
|
if (input.blockedBy) {
|
|
@@ -6489,6 +6724,13 @@ async function createTaskCore(input) {
|
|
|
6489
6724
|
if (dupCheck.rows.length > 0) {
|
|
6490
6725
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
6491
6726
|
}
|
|
6727
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
6728
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
6729
|
+
if (laneWarning) {
|
|
6730
|
+
warning = warning ? `${warning}
|
|
6731
|
+
${laneWarning}` : laneWarning;
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6492
6734
|
if (input.baseDir) {
|
|
6493
6735
|
try {
|
|
6494
6736
|
await mkdir4(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
@@ -6499,12 +6741,7 @@ async function createTaskCore(input) {
|
|
|
6499
6741
|
}
|
|
6500
6742
|
}
|
|
6501
6743
|
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
|
-
}
|
|
6744
|
+
const sessionScope = earlySessionScope;
|
|
6508
6745
|
await client.execute({
|
|
6509
6746
|
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
6747
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -6531,6 +6768,39 @@ async function createTaskCore(input) {
|
|
|
6531
6768
|
now
|
|
6532
6769
|
]
|
|
6533
6770
|
});
|
|
6771
|
+
if (input.baseDir) {
|
|
6772
|
+
try {
|
|
6773
|
+
const EXE_OS_DIR = path12.join(os8.homedir(), ".exe-os");
|
|
6774
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
6775
|
+
const mdDir = path12.dirname(mdPath);
|
|
6776
|
+
if (!existsSync11(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
6777
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
6778
|
+
const mdContent = `# ${input.title}
|
|
6779
|
+
|
|
6780
|
+
**ID:** ${id}
|
|
6781
|
+
**Status:** ${initialStatus}
|
|
6782
|
+
**Priority:** ${input.priority}
|
|
6783
|
+
**Assigned by:** ${input.assignedBy}
|
|
6784
|
+
**Assigned to:** ${input.assignedTo}
|
|
6785
|
+
**Project:** ${input.projectName}
|
|
6786
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
6787
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
6788
|
+
**Reviewer:** ${reviewer}
|
|
6789
|
+
|
|
6790
|
+
## Context
|
|
6791
|
+
|
|
6792
|
+
${input.context}
|
|
6793
|
+
|
|
6794
|
+
## MANDATORY: When done
|
|
6795
|
+
|
|
6796
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
6797
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
6798
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
6799
|
+
`;
|
|
6800
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
6801
|
+
} catch {
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6534
6804
|
return {
|
|
6535
6805
|
id,
|
|
6536
6806
|
title: input.title,
|
|
@@ -6723,7 +6993,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
6723
6993
|
return { row, taskFile, now, taskId };
|
|
6724
6994
|
}
|
|
6725
6995
|
}
|
|
6726
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
6996
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
6727
6997
|
process.stderr.write(
|
|
6728
6998
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
6729
6999
|
`
|
|
@@ -6835,12 +7105,22 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
6835
7105
|
} catch {
|
|
6836
7106
|
}
|
|
6837
7107
|
}
|
|
6838
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
7108
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
6839
7109
|
var init_tasks_crud = __esm({
|
|
6840
7110
|
"src/lib/tasks-crud.ts"() {
|
|
6841
7111
|
"use strict";
|
|
6842
7112
|
init_database();
|
|
6843
7113
|
init_task_scope();
|
|
7114
|
+
init_employees();
|
|
7115
|
+
LANE_KEYWORDS = {
|
|
7116
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
7117
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
7118
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
7119
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
7120
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
7121
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
7122
|
+
};
|
|
7123
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
6844
7124
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
6845
7125
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
6846
7126
|
}
|
|
@@ -6870,7 +7150,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
6870
7150
|
const result2 = await client.execute({
|
|
6871
7151
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
6872
7152
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
6873
|
-
AND
|
|
7153
|
+
AND session_scope = ?`,
|
|
6874
7154
|
args: [sinceIso, sessionScope]
|
|
6875
7155
|
});
|
|
6876
7156
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -6888,7 +7168,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
6888
7168
|
const result2 = await client.execute({
|
|
6889
7169
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
6890
7170
|
WHERE status = 'needs_review'
|
|
6891
|
-
AND
|
|
7171
|
+
AND session_scope = ?
|
|
6892
7172
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
6893
7173
|
args: [sessionScope, limit]
|
|
6894
7174
|
});
|
|
@@ -7009,14 +7289,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
7009
7289
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
7010
7290
|
const agent = parts[1];
|
|
7011
7291
|
const slug = parts.slice(2).join("-");
|
|
7012
|
-
const
|
|
7292
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
7013
7293
|
const result = await client.execute({
|
|
7014
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
7015
|
-
args: [now,
|
|
7294
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
7295
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
7016
7296
|
});
|
|
7017
7297
|
if (result.rowsAffected > 0) {
|
|
7018
7298
|
process.stderr.write(
|
|
7019
|
-
`[review-cleanup] Cascaded original task to done
|
|
7299
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
7020
7300
|
`
|
|
7021
7301
|
);
|
|
7022
7302
|
}
|
|
@@ -7198,7 +7478,7 @@ function findSessionForProject(projectName) {
|
|
|
7198
7478
|
const sessions = listSessions();
|
|
7199
7479
|
for (const s of sessions) {
|
|
7200
7480
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
7201
|
-
if (proj === projectName &&
|
|
7481
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
7202
7482
|
}
|
|
7203
7483
|
return null;
|
|
7204
7484
|
}
|
|
@@ -7244,7 +7524,7 @@ var init_session_scope = __esm({
|
|
|
7244
7524
|
|
|
7245
7525
|
// src/lib/tasks-notify.ts
|
|
7246
7526
|
async function dispatchTaskToEmployee(input) {
|
|
7247
|
-
if (
|
|
7527
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
7248
7528
|
let crossProject = false;
|
|
7249
7529
|
if (input.projectName) {
|
|
7250
7530
|
try {
|
|
@@ -7723,7 +8003,7 @@ async function updateTask(input) {
|
|
|
7723
8003
|
}
|
|
7724
8004
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
7725
8005
|
if (isTerminal) {
|
|
7726
|
-
const isCoordinator =
|
|
8006
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
7727
8007
|
if (!isCoordinator) {
|
|
7728
8008
|
notifyTaskDone();
|
|
7729
8009
|
}
|
|
@@ -7748,7 +8028,7 @@ async function updateTask(input) {
|
|
|
7748
8028
|
}
|
|
7749
8029
|
}
|
|
7750
8030
|
}
|
|
7751
|
-
if (input.status === "done" &&
|
|
8031
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
7752
8032
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
7753
8033
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
7754
8034
|
taskId,
|
|
@@ -7764,7 +8044,7 @@ async function updateTask(input) {
|
|
|
7764
8044
|
});
|
|
7765
8045
|
}
|
|
7766
8046
|
let nextTask;
|
|
7767
|
-
if (isTerminal &&
|
|
8047
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
7768
8048
|
try {
|
|
7769
8049
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
7770
8050
|
} catch {
|
|
@@ -8132,7 +8412,7 @@ __export(tmux_routing_exports, {
|
|
|
8132
8412
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
8133
8413
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, existsSync as existsSync13, appendFileSync } from "fs";
|
|
8134
8414
|
import path17 from "path";
|
|
8135
|
-
import
|
|
8415
|
+
import os9 from "os";
|
|
8136
8416
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8137
8417
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
8138
8418
|
function spawnLockPath(sessionName) {
|
|
@@ -8456,7 +8736,7 @@ function notifyParentExe(sessionKey) {
|
|
|
8456
8736
|
return true;
|
|
8457
8737
|
}
|
|
8458
8738
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
8459
|
-
if (
|
|
8739
|
+
if (isCoordinatorName(employeeName)) {
|
|
8460
8740
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
8461
8741
|
}
|
|
8462
8742
|
try {
|
|
@@ -8528,7 +8808,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8528
8808
|
const transport = getTransport();
|
|
8529
8809
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
8530
8810
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
8531
|
-
const logDir = path17.join(
|
|
8811
|
+
const logDir = path17.join(os9.homedir(), ".exe-os", "session-logs");
|
|
8532
8812
|
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
8533
8813
|
if (!existsSync13(logDir)) {
|
|
8534
8814
|
mkdirSync7(logDir, { recursive: true });
|
|
@@ -8544,7 +8824,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8544
8824
|
} catch {
|
|
8545
8825
|
}
|
|
8546
8826
|
try {
|
|
8547
|
-
const claudeJsonPath = path17.join(
|
|
8827
|
+
const claudeJsonPath = path17.join(os9.homedir(), ".claude.json");
|
|
8548
8828
|
let claudeJson = {};
|
|
8549
8829
|
try {
|
|
8550
8830
|
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
@@ -8559,7 +8839,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8559
8839
|
} catch {
|
|
8560
8840
|
}
|
|
8561
8841
|
try {
|
|
8562
|
-
const settingsDir = path17.join(
|
|
8842
|
+
const settingsDir = path17.join(os9.homedir(), ".claude", "projects");
|
|
8563
8843
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
8564
8844
|
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
8565
8845
|
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
@@ -8607,7 +8887,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8607
8887
|
let legacyFallbackWarned = false;
|
|
8608
8888
|
if (!useExeAgent && !useBinSymlink) {
|
|
8609
8889
|
const identityPath = path17.join(
|
|
8610
|
-
|
|
8890
|
+
os9.homedir(),
|
|
8611
8891
|
".exe-os",
|
|
8612
8892
|
"identity",
|
|
8613
8893
|
`${employeeName}.md`
|
|
@@ -8637,7 +8917,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8637
8917
|
}
|
|
8638
8918
|
let sessionContextFlag = "";
|
|
8639
8919
|
try {
|
|
8640
|
-
const ctxDir = path17.join(
|
|
8920
|
+
const ctxDir = path17.join(os9.homedir(), ".exe-os", "session-cache");
|
|
8641
8921
|
mkdirSync7(ctxDir, { recursive: true });
|
|
8642
8922
|
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
8643
8923
|
const ctxContent = [
|
|
@@ -8749,13 +9029,13 @@ var init_tmux_routing = __esm({
|
|
|
8749
9029
|
init_intercom_queue();
|
|
8750
9030
|
init_plan_limits();
|
|
8751
9031
|
init_employees();
|
|
8752
|
-
SPAWN_LOCK_DIR = path17.join(
|
|
8753
|
-
SESSION_CACHE = path17.join(
|
|
9032
|
+
SPAWN_LOCK_DIR = path17.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
9033
|
+
SESSION_CACHE = path17.join(os9.homedir(), ".exe-os", "session-cache");
|
|
8754
9034
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
8755
9035
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8756
9036
|
VERIFY_PANE_LINES = 200;
|
|
8757
9037
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
8758
|
-
INTERCOM_LOG2 = path17.join(
|
|
9038
|
+
INTERCOM_LOG2 = path17.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
8759
9039
|
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
8760
9040
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
8761
9041
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
@@ -9017,7 +9297,7 @@ var init_messaging = __esm({
|
|
|
9017
9297
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
9018
9298
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9019
9299
|
import path18 from "path";
|
|
9020
|
-
import
|
|
9300
|
+
import os10 from "os";
|
|
9021
9301
|
function substituteTemplate(template, record) {
|
|
9022
9302
|
return template.replace(
|
|
9023
9303
|
/\{\{(\w+(?:\.\w+)*)\}\}/g,
|
|
@@ -9312,7 +9592,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
|
|
|
9312
9592
|
var init_trigger_engine = __esm({
|
|
9313
9593
|
"src/automation/trigger-engine.ts"() {
|
|
9314
9594
|
"use strict";
|
|
9315
|
-
TRIGGERS_PATH = path18.join(
|
|
9595
|
+
TRIGGERS_PATH = path18.join(os10.homedir(), ".exe-os", "triggers.json");
|
|
9316
9596
|
GRAPH_API_VERSION = "v21.0";
|
|
9317
9597
|
}
|
|
9318
9598
|
});
|
|
@@ -9377,13 +9657,13 @@ var init_crm_webhook = __esm({
|
|
|
9377
9657
|
// src/bin/exe-gateway.ts
|
|
9378
9658
|
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
9379
9659
|
import path19 from "path";
|
|
9380
|
-
import
|
|
9660
|
+
import os11 from "os";
|
|
9381
9661
|
|
|
9382
9662
|
// src/gateway/webhook-server.ts
|
|
9383
9663
|
import {
|
|
9384
9664
|
createServer
|
|
9385
9665
|
} from "http";
|
|
9386
|
-
var DEFAULT_HOST = "127.0.0.1";
|
|
9666
|
+
var DEFAULT_HOST = process.env.EXE_WEBHOOK_HOST || "127.0.0.1";
|
|
9387
9667
|
var BODY_SIZE_LIMIT = 1048576;
|
|
9388
9668
|
var WebhookServer = class {
|
|
9389
9669
|
constructor(config2) {
|
|
@@ -10069,10 +10349,49 @@ function buildPermissionContext(platform, permissions) {
|
|
|
10069
10349
|
return `[${platform.toUpperCase()} \u2014 allowed: ${parts.join(", ")}]`;
|
|
10070
10350
|
}
|
|
10071
10351
|
|
|
10352
|
+
// src/gateway/bot-errors.ts
|
|
10353
|
+
var FatalBotError = class extends Error {
|
|
10354
|
+
constructor(message, cause) {
|
|
10355
|
+
super(message);
|
|
10356
|
+
this.cause = cause;
|
|
10357
|
+
this.name = "FatalBotError";
|
|
10358
|
+
}
|
|
10359
|
+
fatal = true;
|
|
10360
|
+
};
|
|
10361
|
+
var RecoverableBotError = class extends Error {
|
|
10362
|
+
constructor(message, toolName, cause) {
|
|
10363
|
+
super(message);
|
|
10364
|
+
this.toolName = toolName;
|
|
10365
|
+
this.cause = cause;
|
|
10366
|
+
this.name = "RecoverableBotError";
|
|
10367
|
+
}
|
|
10368
|
+
recoverable = true;
|
|
10369
|
+
};
|
|
10370
|
+
var MaxStepsError = class extends Error {
|
|
10371
|
+
constructor(stepsTaken, maxSteps) {
|
|
10372
|
+
super(
|
|
10373
|
+
`Reached maximum steps (${stepsTaken}/${maxSteps}). Returning partial result.`
|
|
10374
|
+
);
|
|
10375
|
+
this.stepsTaken = stepsTaken;
|
|
10376
|
+
this.maxSteps = maxSteps;
|
|
10377
|
+
this.name = "MaxStepsError";
|
|
10378
|
+
}
|
|
10379
|
+
};
|
|
10380
|
+
function classifyError(err, toolName) {
|
|
10381
|
+
if (err instanceof FatalBotError) return err;
|
|
10382
|
+
if (err instanceof RecoverableBotError) return err;
|
|
10383
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10384
|
+
if (message.includes("401") || message.includes("403") || message.includes("authentication") || message.includes("rate_limit")) {
|
|
10385
|
+
return new FatalBotError(message, err);
|
|
10386
|
+
}
|
|
10387
|
+
return new RecoverableBotError(message, toolName, err);
|
|
10388
|
+
}
|
|
10389
|
+
|
|
10072
10390
|
// src/gateway/bot-runtime.ts
|
|
10073
10391
|
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
10074
10392
|
var MAX_TURNS = 10;
|
|
10075
10393
|
var MAX_HISTORY = 50;
|
|
10394
|
+
var DEFAULT_PLANNING_INTERVAL = 3;
|
|
10076
10395
|
function filterToolsForPermissions(tools, permissions) {
|
|
10077
10396
|
const allAllowed = /* @__PURE__ */ new Set();
|
|
10078
10397
|
if (permissions.canRead) {
|
|
@@ -10101,7 +10420,7 @@ var BotRuntime = class {
|
|
|
10101
10420
|
async processMessage(msg, permissions) {
|
|
10102
10421
|
const sessionKey = msg.chatType === "group" ? msg.channelId : msg.senderId;
|
|
10103
10422
|
const history = this.getHistory(sessionKey);
|
|
10104
|
-
history.push({ role: "user", content: msg.text });
|
|
10423
|
+
history.push({ role: "user", content: msg.text, frameType: "task" });
|
|
10105
10424
|
const systemPrompt = this.config.systemPrompt + "\n\n" + buildPermissionContext(msg.platform, permissions);
|
|
10106
10425
|
const allowedTools = filterToolsForPermissions(
|
|
10107
10426
|
this.config.tools,
|
|
@@ -10109,29 +10428,54 @@ var BotRuntime = class {
|
|
|
10109
10428
|
);
|
|
10110
10429
|
const model = this.config.model ?? DEFAULT_MODEL;
|
|
10111
10430
|
const maxTurns = this.config.maxTurns ?? MAX_TURNS;
|
|
10431
|
+
const planningInterval = this.config.planningInterval ?? DEFAULT_PLANNING_INTERVAL;
|
|
10112
10432
|
let turns = 0;
|
|
10113
10433
|
while (turns < maxTurns) {
|
|
10114
10434
|
turns++;
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10435
|
+
if (planningInterval > 0 && turns > 1 && turns % planningInterval === 1 && maxTurns - turns >= 2) {
|
|
10436
|
+
history.push({
|
|
10437
|
+
role: "user",
|
|
10438
|
+
content: "[Planning checkpoint] Review what you know so far. What facts have you gathered? What is your plan for the remaining steps? Be concise.",
|
|
10439
|
+
frameType: "planning"
|
|
10440
|
+
});
|
|
10441
|
+
}
|
|
10442
|
+
let response;
|
|
10443
|
+
try {
|
|
10444
|
+
response = await this.client.messages.create({
|
|
10445
|
+
model,
|
|
10446
|
+
max_tokens: 4096,
|
|
10447
|
+
system: systemPrompt,
|
|
10448
|
+
messages: history.map((m) => ({
|
|
10449
|
+
role: m.role,
|
|
10450
|
+
content: m.content
|
|
10451
|
+
})),
|
|
10452
|
+
tools: allowedTools.map((t) => ({
|
|
10453
|
+
name: t.name,
|
|
10454
|
+
description: t.description,
|
|
10455
|
+
input_schema: t.input_schema
|
|
10456
|
+
}))
|
|
10457
|
+
});
|
|
10458
|
+
} catch (err) {
|
|
10459
|
+
const classified = classifyError(err);
|
|
10460
|
+
if (classified instanceof FatalBotError) {
|
|
10461
|
+
const errorMsg = `Bot error: ${classified.message}`;
|
|
10462
|
+
history.push({ role: "assistant", content: errorMsg, frameType: "error" });
|
|
10463
|
+
this.trimHistory(sessionKey);
|
|
10464
|
+
return errorMsg;
|
|
10465
|
+
}
|
|
10466
|
+
history.push({
|
|
10467
|
+
role: "assistant",
|
|
10468
|
+
content: `[API error \u2014 retrying] ${classified.message}`,
|
|
10469
|
+
frameType: "error"
|
|
10470
|
+
});
|
|
10471
|
+
continue;
|
|
10472
|
+
}
|
|
10129
10473
|
const toolUseBlocks = response.content.filter(
|
|
10130
10474
|
(b) => b.type === "tool_use"
|
|
10131
10475
|
);
|
|
10132
10476
|
if (toolUseBlocks.length === 0) {
|
|
10133
10477
|
const textContent = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
10134
|
-
history.push({ role: "assistant", content: textContent });
|
|
10478
|
+
history.push({ role: "assistant", content: textContent, frameType: "assistant" });
|
|
10135
10479
|
this.trimHistory(sessionKey);
|
|
10136
10480
|
return textContent;
|
|
10137
10481
|
}
|
|
@@ -10141,9 +10485,11 @@ var BotRuntime = class {
|
|
|
10141
10485
|
);
|
|
10142
10486
|
history.push({
|
|
10143
10487
|
role: "assistant",
|
|
10144
|
-
content: response.content
|
|
10488
|
+
content: response.content,
|
|
10489
|
+
frameType: "assistant"
|
|
10145
10490
|
});
|
|
10146
10491
|
const toolResults = [];
|
|
10492
|
+
let hadFatalError = false;
|
|
10147
10493
|
for (const block of allowed) {
|
|
10148
10494
|
try {
|
|
10149
10495
|
const result = await this.config.toolExecutor(
|
|
@@ -10156,12 +10502,23 @@ var BotRuntime = class {
|
|
|
10156
10502
|
content: result
|
|
10157
10503
|
});
|
|
10158
10504
|
} catch (err) {
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10505
|
+
const classified = classifyError(err, block.name);
|
|
10506
|
+
if (classified instanceof FatalBotError) {
|
|
10507
|
+
toolResults.push({
|
|
10508
|
+
type: "tool_result",
|
|
10509
|
+
tool_use_id: block.id,
|
|
10510
|
+
content: `Fatal error: ${classified.message}`,
|
|
10511
|
+
is_error: true
|
|
10512
|
+
});
|
|
10513
|
+
hadFatalError = true;
|
|
10514
|
+
} else {
|
|
10515
|
+
toolResults.push({
|
|
10516
|
+
type: "tool_result",
|
|
10517
|
+
tool_use_id: block.id,
|
|
10518
|
+
content: `Error (recoverable): ${classified.message}`,
|
|
10519
|
+
is_error: true
|
|
10520
|
+
});
|
|
10521
|
+
}
|
|
10165
10522
|
}
|
|
10166
10523
|
}
|
|
10167
10524
|
for (const { block, check } of blocked) {
|
|
@@ -10174,10 +10531,22 @@ var BotRuntime = class {
|
|
|
10174
10531
|
}
|
|
10175
10532
|
history.push({
|
|
10176
10533
|
role: "user",
|
|
10177
|
-
content: toolResults
|
|
10534
|
+
content: toolResults,
|
|
10535
|
+
frameType: hadFatalError ? "error" : "tool_result"
|
|
10178
10536
|
});
|
|
10537
|
+
if (hadFatalError) {
|
|
10538
|
+
this.trimHistory(sessionKey);
|
|
10539
|
+
return `A fatal error occurred during tool execution. The bot loop has been stopped.`;
|
|
10540
|
+
}
|
|
10179
10541
|
}
|
|
10180
|
-
|
|
10542
|
+
const maxErr = new MaxStepsError(turns, maxTurns);
|
|
10543
|
+
history.push({
|
|
10544
|
+
role: "assistant",
|
|
10545
|
+
content: maxErr.message,
|
|
10546
|
+
frameType: "error"
|
|
10547
|
+
});
|
|
10548
|
+
this.trimHistory(sessionKey);
|
|
10549
|
+
return maxErr.message;
|
|
10181
10550
|
}
|
|
10182
10551
|
getHistory(sessionKey) {
|
|
10183
10552
|
if (!this.conversations.has(sessionKey)) {
|
|
@@ -10187,9 +10556,19 @@ var BotRuntime = class {
|
|
|
10187
10556
|
}
|
|
10188
10557
|
trimHistory(sessionKey) {
|
|
10189
10558
|
const history = this.conversations.get(sessionKey);
|
|
10190
|
-
if (history
|
|
10191
|
-
|
|
10559
|
+
if (!history || history.length <= MAX_HISTORY) return;
|
|
10560
|
+
const firstTaskIdx = history.findIndex((m) => m.frameType === "task");
|
|
10561
|
+
let trimmed = history.filter(
|
|
10562
|
+
(m, i) => m.frameType !== "planning" || i >= history.length - MAX_HISTORY
|
|
10563
|
+
);
|
|
10564
|
+
if (trimmed.length > MAX_HISTORY) {
|
|
10565
|
+
const tail = trimmed.slice(-MAX_HISTORY);
|
|
10566
|
+
if (firstTaskIdx >= 0 && !tail.includes(history[firstTaskIdx])) {
|
|
10567
|
+
tail[0] = history[firstTaskIdx];
|
|
10568
|
+
}
|
|
10569
|
+
trimmed = tail;
|
|
10192
10570
|
}
|
|
10571
|
+
this.conversations.set(sessionKey, trimmed);
|
|
10193
10572
|
}
|
|
10194
10573
|
/** Clear conversation history for a session */
|
|
10195
10574
|
clearHistory(sessionKey) {
|
|
@@ -10244,7 +10623,7 @@ var BotRegistry = class {
|
|
|
10244
10623
|
|
|
10245
10624
|
// src/bin/exe-gateway.ts
|
|
10246
10625
|
init_employees();
|
|
10247
|
-
var CONFIG_DIR = path19.join(
|
|
10626
|
+
var CONFIG_DIR = path19.join(os11.homedir(), ".exe-os");
|
|
10248
10627
|
var CONFIG_PATH3 = path19.join(CONFIG_DIR, "gateway.json");
|
|
10249
10628
|
var DEFAULT_PORT = 3100;
|
|
10250
10629
|
function loadConfig2() {
|