cascade-ai 0.2.1 → 0.2.11
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/README.md +33 -0
- package/dist/cli.cjs +1164 -359
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1164 -359
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1066 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -5
- package/dist/index.d.ts +72 -5
- package/dist/index.js +1065 -332
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -11,7 +11,7 @@ var chalk8 = require('chalk');
|
|
|
11
11
|
var dotenv = require('dotenv');
|
|
12
12
|
var fs7 = require('fs/promises');
|
|
13
13
|
var path17 = require('path');
|
|
14
|
-
var
|
|
14
|
+
var os3 = require('os');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var fs14 = require('fs');
|
|
17
17
|
var _ignoreModule = require('ignore');
|
|
@@ -20,6 +20,7 @@ var zod = require('zod');
|
|
|
20
20
|
var child_process = require('child_process');
|
|
21
21
|
var jsxRuntime = require('react/jsx-runtime');
|
|
22
22
|
var EventEmitter = require('events');
|
|
23
|
+
var glob = require('glob');
|
|
23
24
|
var util = require('util');
|
|
24
25
|
var simpleGit = require('simple-git');
|
|
25
26
|
var PDFDocument = require('pdfkit');
|
|
@@ -68,7 +69,7 @@ var chalk8__default = /*#__PURE__*/_interopDefault(chalk8);
|
|
|
68
69
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
69
70
|
var fs7__default = /*#__PURE__*/_interopDefault(fs7);
|
|
70
71
|
var path17__default = /*#__PURE__*/_interopDefault(path17);
|
|
71
|
-
var
|
|
72
|
+
var os3__default = /*#__PURE__*/_interopDefault(os3);
|
|
72
73
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
73
74
|
var fs14__default = /*#__PURE__*/_interopDefault(fs14);
|
|
74
75
|
var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
|
|
@@ -129,7 +130,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
129
130
|
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
130
131
|
var init_constants = __esm({
|
|
131
132
|
"src/constants.ts"() {
|
|
132
|
-
CASCADE_VERSION = "0.2.
|
|
133
|
+
CASCADE_VERSION = "0.2.11";
|
|
133
134
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
134
135
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
135
136
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -424,7 +425,8 @@ var init_constants = __esm({
|
|
|
424
425
|
IMAGE_ANALYZE: "image_analyze",
|
|
425
426
|
PDF_CREATE: "pdf_create",
|
|
426
427
|
RUN_CODE: "run_code",
|
|
427
|
-
PEER_MESSAGE: "peer_message"
|
|
428
|
+
PEER_MESSAGE: "peer_message",
|
|
429
|
+
WEB_SEARCH: "web_search"
|
|
428
430
|
};
|
|
429
431
|
DEFAULT_APPROVAL_REQUIRED = [
|
|
430
432
|
TOOL_NAMES.SHELL,
|
|
@@ -638,33 +640,61 @@ var init_anthropic = __esm({
|
|
|
638
640
|
}
|
|
639
641
|
}
|
|
640
642
|
convertMessages(messages) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
643
|
+
const result = [];
|
|
644
|
+
for (const m of messages) {
|
|
645
|
+
if (m.role === "system") continue;
|
|
646
|
+
if (m.role === "tool") {
|
|
647
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
648
|
+
result.push({
|
|
649
|
+
role: "user",
|
|
650
|
+
content: [{
|
|
651
|
+
type: "tool_result",
|
|
652
|
+
tool_use_id: m.toolCallId ?? "",
|
|
653
|
+
content: toolContent
|
|
654
|
+
}]
|
|
655
|
+
});
|
|
656
|
+
continue;
|
|
644
657
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
658
|
+
if (m.role === "assistant") {
|
|
659
|
+
const content = [];
|
|
660
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
661
|
+
if (text) content.push({ type: "text", text });
|
|
662
|
+
for (const tc of m.toolCalls ?? []) {
|
|
663
|
+
content.push({
|
|
664
|
+
type: "tool_use",
|
|
665
|
+
id: tc.id,
|
|
666
|
+
name: tc.name,
|
|
667
|
+
input: tc.input
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
if (content.length > 0) {
|
|
671
|
+
result.push({ role: "assistant", content });
|
|
672
|
+
}
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (m.role === "user") {
|
|
676
|
+
if (typeof m.content === "string") {
|
|
677
|
+
result.push({ role: "user", content: m.content });
|
|
678
|
+
} else {
|
|
679
|
+
const content = m.content.map((block) => {
|
|
680
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
681
|
+
if (block.type === "image") {
|
|
682
|
+
const img = block.image;
|
|
683
|
+
if (img.type === "base64") {
|
|
684
|
+
return {
|
|
685
|
+
type: "image",
|
|
686
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
687
|
+
};
|
|
656
688
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
};
|
|
689
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
690
|
+
}
|
|
691
|
+
return { type: "text", text: "" };
|
|
692
|
+
});
|
|
693
|
+
result.push({ role: "user", content });
|
|
663
694
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
668
698
|
}
|
|
669
699
|
};
|
|
670
700
|
}
|
|
@@ -962,7 +992,7 @@ var init_gemini = __esm({
|
|
|
962
992
|
for (const part of candidate?.content?.parts ?? []) {
|
|
963
993
|
if (part.functionCall) {
|
|
964
994
|
toolCalls.push({
|
|
965
|
-
id:
|
|
995
|
+
id: part.functionCall.name,
|
|
966
996
|
name: part.functionCall.name,
|
|
967
997
|
input: part.functionCall.args ?? {}
|
|
968
998
|
});
|
|
@@ -1050,10 +1080,70 @@ var init_gemini = __esm({
|
|
|
1050
1080
|
}
|
|
1051
1081
|
// ── Private ──────────────────────────────────
|
|
1052
1082
|
buildContents(messages, extraImages) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1083
|
+
const contents = [];
|
|
1084
|
+
for (const m of messages) {
|
|
1085
|
+
if (m.role === "system") {
|
|
1086
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
1087
|
+
if (!text.trim()) continue;
|
|
1088
|
+
const prev = contents[contents.length - 1];
|
|
1089
|
+
if (prev?.role === "user") {
|
|
1090
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
1091
|
+
|
|
1092
|
+
` });
|
|
1093
|
+
} else {
|
|
1094
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
1095
|
+
}
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (m.role === "tool") {
|
|
1099
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1100
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1101
|
+
contents.push({
|
|
1102
|
+
role: "user",
|
|
1103
|
+
parts: [{
|
|
1104
|
+
functionResponse: {
|
|
1105
|
+
name: functionName,
|
|
1106
|
+
response: { output: toolContent }
|
|
1107
|
+
}
|
|
1108
|
+
}]
|
|
1109
|
+
});
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (m.role === "assistant") {
|
|
1113
|
+
const parts = [];
|
|
1114
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1115
|
+
if (textContent) parts.push({ text: textContent });
|
|
1116
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1117
|
+
parts.push({
|
|
1118
|
+
functionCall: {
|
|
1119
|
+
name: tc.name,
|
|
1120
|
+
args: tc.input
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
if (parts.length > 0) {
|
|
1125
|
+
contents.push({ role: "model", parts });
|
|
1126
|
+
}
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (m.role === "user") {
|
|
1130
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1131
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1132
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1133
|
+
if (isLastUser) {
|
|
1134
|
+
for (const img of extraImages) {
|
|
1135
|
+
if (img.type === "base64") {
|
|
1136
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (parts.length > 0) {
|
|
1142
|
+
contents.push({ role: "user", parts });
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return contents;
|
|
1057
1147
|
}
|
|
1058
1148
|
convertMessageContent(msg, extraImages) {
|
|
1059
1149
|
const parts = [];
|
|
@@ -1615,9 +1705,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
1615
1705
|
constructor(dbPath) {
|
|
1616
1706
|
fs14__default.default.mkdirSync(path17__default.default.dirname(dbPath), { recursive: true });
|
|
1617
1707
|
try {
|
|
1618
|
-
this.db = new Database__default.default(dbPath);
|
|
1708
|
+
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
1619
1709
|
this.db.pragma("journal_mode = WAL");
|
|
1620
1710
|
this.db.pragma("foreign_keys = ON");
|
|
1711
|
+
this.db.pragma("synchronous = NORMAL");
|
|
1621
1712
|
this.migrate();
|
|
1622
1713
|
} catch (err) {
|
|
1623
1714
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -1631,6 +1722,38 @@ Original error: ${err.message}`
|
|
|
1631
1722
|
throw err;
|
|
1632
1723
|
}
|
|
1633
1724
|
}
|
|
1725
|
+
// ── Async Write Queue ─────────────────────────
|
|
1726
|
+
writeQueue = [];
|
|
1727
|
+
isProcessingQueue = false;
|
|
1728
|
+
async processQueue() {
|
|
1729
|
+
if (this.isProcessingQueue) return;
|
|
1730
|
+
this.isProcessingQueue = true;
|
|
1731
|
+
while (this.writeQueue.length > 0) {
|
|
1732
|
+
const op = this.writeQueue.shift();
|
|
1733
|
+
if (op) {
|
|
1734
|
+
let attempts = 0;
|
|
1735
|
+
while (attempts < 5) {
|
|
1736
|
+
try {
|
|
1737
|
+
op();
|
|
1738
|
+
break;
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
1741
|
+
attempts++;
|
|
1742
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
1743
|
+
} else {
|
|
1744
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
1745
|
+
break;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
this.isProcessingQueue = false;
|
|
1752
|
+
}
|
|
1753
|
+
enqueueWrite(op) {
|
|
1754
|
+
this.writeQueue.push(op);
|
|
1755
|
+
this.processQueue().catch(console.error);
|
|
1756
|
+
}
|
|
1634
1757
|
// ── Sessions ──────────────────────────────────
|
|
1635
1758
|
createSession(session) {
|
|
1636
1759
|
this.db.prepare(`
|
|
@@ -1717,26 +1840,28 @@ Original error: ${err.message}`
|
|
|
1717
1840
|
}
|
|
1718
1841
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
1719
1842
|
upsertRuntimeSession(session) {
|
|
1720
|
-
this.
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1843
|
+
this.enqueueWrite(() => {
|
|
1844
|
+
this.db.prepare(`
|
|
1845
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
1846
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1847
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
1848
|
+
title = excluded.title,
|
|
1849
|
+
workspace_path = excluded.workspace_path,
|
|
1850
|
+
status = excluded.status,
|
|
1851
|
+
updated_at = excluded.updated_at,
|
|
1852
|
+
latest_prompt = excluded.latest_prompt,
|
|
1853
|
+
is_global = excluded.is_global
|
|
1854
|
+
`).run(
|
|
1855
|
+
session.sessionId,
|
|
1856
|
+
session.title,
|
|
1857
|
+
session.workspacePath,
|
|
1858
|
+
session.status,
|
|
1859
|
+
session.startedAt,
|
|
1860
|
+
session.updatedAt,
|
|
1861
|
+
session.latestPrompt ?? null,
|
|
1862
|
+
session.isGlobal ? 1 : 0
|
|
1863
|
+
);
|
|
1864
|
+
});
|
|
1740
1865
|
}
|
|
1741
1866
|
listRuntimeSessions(limit = 100) {
|
|
1742
1867
|
const rows = this.db.prepare(`
|
|
@@ -1754,33 +1879,35 @@ Original error: ${err.message}`
|
|
|
1754
1879
|
}));
|
|
1755
1880
|
}
|
|
1756
1881
|
upsertRuntimeNode(node) {
|
|
1757
|
-
this.
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1882
|
+
this.enqueueWrite(() => {
|
|
1883
|
+
this.db.prepare(`
|
|
1884
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
1885
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1886
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
1887
|
+
session_id = excluded.session_id,
|
|
1888
|
+
parent_id = excluded.parent_id,
|
|
1889
|
+
role = excluded.role,
|
|
1890
|
+
label = excluded.label,
|
|
1891
|
+
status = excluded.status,
|
|
1892
|
+
current_action = excluded.current_action,
|
|
1893
|
+
progress_pct = excluded.progress_pct,
|
|
1894
|
+
updated_at = excluded.updated_at,
|
|
1895
|
+
workspace_path = excluded.workspace_path,
|
|
1896
|
+
is_global = excluded.is_global
|
|
1897
|
+
`).run(
|
|
1898
|
+
node.tierId,
|
|
1899
|
+
node.sessionId,
|
|
1900
|
+
node.parentId ?? null,
|
|
1901
|
+
node.role,
|
|
1902
|
+
node.label,
|
|
1903
|
+
node.status,
|
|
1904
|
+
node.currentAction ?? null,
|
|
1905
|
+
node.progressPct ?? null,
|
|
1906
|
+
node.updatedAt,
|
|
1907
|
+
node.workspacePath ?? null,
|
|
1908
|
+
node.isGlobal ? 1 : 0
|
|
1909
|
+
);
|
|
1910
|
+
});
|
|
1784
1911
|
}
|
|
1785
1912
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
1786
1913
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -1803,30 +1930,32 @@ Original error: ${err.message}`
|
|
|
1803
1930
|
}));
|
|
1804
1931
|
}
|
|
1805
1932
|
addRuntimeNodeLog(log) {
|
|
1806
|
-
this.
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1933
|
+
this.enqueueWrite(() => {
|
|
1934
|
+
this.db.prepare(`
|
|
1935
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
1936
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1937
|
+
`).run(
|
|
1938
|
+
log.id,
|
|
1939
|
+
log.sessionId,
|
|
1940
|
+
log.tierId,
|
|
1941
|
+
log.role,
|
|
1942
|
+
log.label,
|
|
1943
|
+
log.status,
|
|
1944
|
+
log.currentAction ?? null,
|
|
1945
|
+
log.progressPct ?? null,
|
|
1946
|
+
log.timestamp,
|
|
1947
|
+
log.workspacePath ?? null,
|
|
1948
|
+
log.isGlobal ? 1 : 0
|
|
1949
|
+
);
|
|
1950
|
+
this.db.prepare(`
|
|
1951
|
+
DELETE FROM runtime_node_logs
|
|
1952
|
+
WHERE id NOT IN (
|
|
1953
|
+
SELECT id FROM runtime_node_logs
|
|
1954
|
+
ORDER BY timestamp DESC
|
|
1955
|
+
LIMIT 2000
|
|
1956
|
+
)
|
|
1957
|
+
`).run();
|
|
1958
|
+
});
|
|
1830
1959
|
}
|
|
1831
1960
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
1832
1961
|
let rows;
|
|
@@ -1864,19 +1993,21 @@ Original error: ${err.message}`
|
|
|
1864
1993
|
}
|
|
1865
1994
|
// ── Messages ──────────────────────────────────
|
|
1866
1995
|
addMessage(message) {
|
|
1867
|
-
this.
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1996
|
+
this.enqueueWrite(() => {
|
|
1997
|
+
this.db.prepare(`
|
|
1998
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
1999
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2000
|
+
`).run(
|
|
2001
|
+
message.id,
|
|
2002
|
+
message.sessionId,
|
|
2003
|
+
message.role,
|
|
2004
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
2005
|
+
message.timestamp,
|
|
2006
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
2007
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
2008
|
+
);
|
|
2009
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
2010
|
+
});
|
|
1880
2011
|
}
|
|
1881
2012
|
getSessionMessages(sessionId) {
|
|
1882
2013
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -1973,10 +2104,12 @@ Original error: ${err.message}`
|
|
|
1973
2104
|
}
|
|
1974
2105
|
// ── Audit Log ─────────────────────────────────
|
|
1975
2106
|
addAuditEntry(entry) {
|
|
1976
|
-
this.
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2107
|
+
this.enqueueWrite(() => {
|
|
2108
|
+
this.db.prepare(`
|
|
2109
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
2110
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2111
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
2112
|
+
});
|
|
1980
2113
|
}
|
|
1981
2114
|
getAuditLog(sessionId, limit = 100) {
|
|
1982
2115
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -1991,10 +2124,12 @@ Original error: ${err.message}`
|
|
|
1991
2124
|
}
|
|
1992
2125
|
// ── File Snapshots ────────────────────────────
|
|
1993
2126
|
addFileSnapshot(sessionId, filePath, content) {
|
|
1994
|
-
this.
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2127
|
+
this.enqueueWrite(() => {
|
|
2128
|
+
this.db.prepare(`
|
|
2129
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
2130
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2131
|
+
`).run(crypto.randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
2132
|
+
});
|
|
1998
2133
|
}
|
|
1999
2134
|
getLatestFileSnapshots(sessionId) {
|
|
2000
2135
|
const rows = this.db.prepare(`
|
|
@@ -2298,12 +2433,24 @@ var McpServerConfigSchema = zod.z.object({
|
|
|
2298
2433
|
args: zod.z.array(zod.z.string()).optional(),
|
|
2299
2434
|
env: zod.z.record(zod.z.string()).optional()
|
|
2300
2435
|
});
|
|
2436
|
+
var WebSearchConfigSchema = zod.z.object({
|
|
2437
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
2438
|
+
searxngUrl: zod.z.string().optional(),
|
|
2439
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
2440
|
+
braveApiKey: zod.z.string().optional(),
|
|
2441
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
2442
|
+
tavilyApiKey: zod.z.string().optional(),
|
|
2443
|
+
/** Max results per search (default 5) */
|
|
2444
|
+
maxResults: zod.z.number().default(5)
|
|
2445
|
+
});
|
|
2301
2446
|
var ToolsConfigSchema = zod.z.object({
|
|
2302
2447
|
shellAllowlist: zod.z.array(zod.z.string()).default([]),
|
|
2303
2448
|
shellBlocklist: zod.z.array(zod.z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
2304
2449
|
requireApprovalFor: zod.z.array(zod.z.string()).default([]),
|
|
2305
2450
|
browserEnabled: zod.z.boolean().default(false),
|
|
2306
|
-
mcpServers: zod.z.array(McpServerConfigSchema).optional()
|
|
2451
|
+
mcpServers: zod.z.array(McpServerConfigSchema).optional(),
|
|
2452
|
+
/** Web search backends — at least one should be configured for best results */
|
|
2453
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
2307
2454
|
});
|
|
2308
2455
|
var HookDefinitionSchema = zod.z.object({
|
|
2309
2456
|
command: zod.z.string(),
|
|
@@ -2408,7 +2555,7 @@ var ConfigManager = class {
|
|
|
2408
2555
|
globalDir;
|
|
2409
2556
|
constructor(workspacePath = process.cwd()) {
|
|
2410
2557
|
this.workspacePath = workspacePath;
|
|
2411
|
-
this.globalDir = path17__default.default.join(
|
|
2558
|
+
this.globalDir = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
2412
2559
|
}
|
|
2413
2560
|
async load() {
|
|
2414
2561
|
this.config = await this.loadConfig();
|
|
@@ -3407,6 +3554,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
3407
3554
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
3408
3555
|
}
|
|
3409
3556
|
};
|
|
3557
|
+
|
|
3558
|
+
// src/utils/retry.ts
|
|
3559
|
+
var CascadeCancelledError = class extends Error {
|
|
3560
|
+
constructor(reason) {
|
|
3561
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
3562
|
+
this.name = "CascadeCancelledError";
|
|
3563
|
+
}
|
|
3564
|
+
};
|
|
3565
|
+
|
|
3566
|
+
// src/core/tiers/base.ts
|
|
3410
3567
|
var BaseTier = class extends EventEmitter__default.default {
|
|
3411
3568
|
id;
|
|
3412
3569
|
role;
|
|
@@ -3416,6 +3573,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3416
3573
|
label;
|
|
3417
3574
|
systemPromptOverride = "";
|
|
3418
3575
|
hierarchyContext = "";
|
|
3576
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
3577
|
+
signal;
|
|
3419
3578
|
constructor(role, id, parentId) {
|
|
3420
3579
|
super();
|
|
3421
3580
|
this.role = role;
|
|
@@ -3478,6 +3637,18 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3478
3637
|
log(message, data) {
|
|
3479
3638
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3480
3639
|
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
3642
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
3643
|
+
* to provide a fast, clean cancellation path.
|
|
3644
|
+
*/
|
|
3645
|
+
throwIfCancelled() {
|
|
3646
|
+
if (this.signal?.aborted) {
|
|
3647
|
+
throw new CascadeCancelledError(
|
|
3648
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
3649
|
+
);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3481
3652
|
};
|
|
3482
3653
|
|
|
3483
3654
|
// src/core/context/manager.ts
|
|
@@ -3679,6 +3850,7 @@ Rules:
|
|
|
3679
3850
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
3680
3851
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
3681
3852
|
- If the task asks for a file or artifact, you must actually create it in the workspace, verify that it exists, and inspect it before claiming success.
|
|
3853
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
3682
3854
|
- Use the "pdf_create" tool for PDF requests.
|
|
3683
3855
|
- Use the "run_code" tool for any file types (Excel, Zip, csv, etc.) or complex processing not covered by other tools. Always cleanup after code execution.
|
|
3684
3856
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -3722,7 +3894,8 @@ var T3Worker = class extends BaseTier {
|
|
|
3722
3894
|
this.store = store;
|
|
3723
3895
|
this.audit = new AuditLogger(store, sessionId);
|
|
3724
3896
|
}
|
|
3725
|
-
async execute(assignment, taskId) {
|
|
3897
|
+
async execute(assignment, taskId, signal) {
|
|
3898
|
+
this.signal = signal;
|
|
3726
3899
|
this.assignment = assignment;
|
|
3727
3900
|
this.taskId = taskId;
|
|
3728
3901
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -3854,14 +4027,13 @@ Now execute your subtask using this context where relevant.`
|
|
|
3854
4027
|
await this.peerBus.barrier(this.id, barrierName, total);
|
|
3855
4028
|
}
|
|
3856
4029
|
receivePeerSync(fromId, content) {
|
|
3857
|
-
|
|
3858
|
-
if (existing) {
|
|
3859
|
-
existing.content = content;
|
|
3860
|
-
existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3861
|
-
} else {
|
|
3862
|
-
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3863
|
-
}
|
|
4030
|
+
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3864
4031
|
this.emit("peer-sync-received", { fromId, content });
|
|
4032
|
+
this.context.addMessage({
|
|
4033
|
+
role: "user",
|
|
4034
|
+
content: `[SYSTEM_NOTIFICATION]: You received a new peer message from ${fromId}. Use the "peer_message" tool with action="receive" to read it.`
|
|
4035
|
+
}).catch(() => {
|
|
4036
|
+
});
|
|
3865
4037
|
}
|
|
3866
4038
|
// ── Private ──────────────────────────────────
|
|
3867
4039
|
async runAgentLoop(systemPrompt, tools) {
|
|
@@ -3873,6 +4045,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
3873
4045
|
tools = [...tools];
|
|
3874
4046
|
while (iterations < MAX_ITERATIONS) {
|
|
3875
4047
|
iterations++;
|
|
4048
|
+
this.throwIfCancelled();
|
|
3876
4049
|
const options = {
|
|
3877
4050
|
messages: this.context.getMessages(),
|
|
3878
4051
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -3893,21 +4066,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3893
4066
|
if (requiresArtifact) {
|
|
3894
4067
|
stalledArtifactIterations += 1;
|
|
3895
4068
|
if (stalledArtifactIterations >= 2) {
|
|
3896
|
-
if (
|
|
3897
|
-
|
|
3898
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
3899
|
-
this.assignment?.description ?? ""
|
|
3900
|
-
);
|
|
3901
|
-
if (toolName) {
|
|
3902
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
3903
|
-
this.sendStatusUpdate({
|
|
3904
|
-
progressPct: 50,
|
|
3905
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
3906
|
-
status: "IN_PROGRESS"
|
|
3907
|
-
});
|
|
3908
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
3909
|
-
continue;
|
|
3910
|
-
}
|
|
4069
|
+
if (stalledArtifactIterations === 2) {
|
|
4070
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
3911
4071
|
}
|
|
3912
4072
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
3913
4073
|
}
|
|
@@ -3992,7 +4152,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3992
4152
|
sendPeerSync: (to, syncType, content) => {
|
|
3993
4153
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
3994
4154
|
},
|
|
3995
|
-
getPeerMessages: () =>
|
|
4155
|
+
getPeerMessages: () => {
|
|
4156
|
+
const msgs = [...this.peerSyncBuffer];
|
|
4157
|
+
this.peerSyncBuffer = [];
|
|
4158
|
+
return msgs;
|
|
4159
|
+
}
|
|
3996
4160
|
});
|
|
3997
4161
|
if (this.audit) {
|
|
3998
4162
|
this.audit.toolCall(this.id, tc.name, tc.input);
|
|
@@ -4063,6 +4227,9 @@ ${assignment.expectedOutput}`;
|
|
|
4063
4227
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
4064
4228
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
4065
4229
|
const issues = [];
|
|
4230
|
+
const { exec: exec4 } = await import('child_process');
|
|
4231
|
+
const { promisify: promisify3 } = await import('util');
|
|
4232
|
+
const execAsync3 = promisify3(exec4);
|
|
4066
4233
|
for (const artifactPath of artifactPaths) {
|
|
4067
4234
|
const absolutePath = path17__default.default.resolve(process.cwd(), artifactPath);
|
|
4068
4235
|
try {
|
|
@@ -4079,9 +4246,27 @@ ${assignment.expectedOutput}`;
|
|
|
4079
4246
|
const content = await fs7__default.default.readFile(absolutePath, "utf-8");
|
|
4080
4247
|
if (!content.trim()) {
|
|
4081
4248
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
4249
|
+
continue;
|
|
4082
4250
|
}
|
|
4083
4251
|
} else if (stat.size < 100) {
|
|
4084
4252
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
4253
|
+
continue;
|
|
4254
|
+
}
|
|
4255
|
+
const ext = path17__default.default.extname(absolutePath).toLowerCase();
|
|
4256
|
+
try {
|
|
4257
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
4258
|
+
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
4259
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
4260
|
+
await execAsync3(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
4261
|
+
} else if (ext === ".py") {
|
|
4262
|
+
await execAsync3(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
4263
|
+
}
|
|
4264
|
+
} catch (err) {
|
|
4265
|
+
const stderr = err?.stderr || String(err);
|
|
4266
|
+
const stdout = err?.stdout || "";
|
|
4267
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
4268
|
+
${stderr}
|
|
4269
|
+
${stdout}`);
|
|
4085
4270
|
}
|
|
4086
4271
|
} catch {
|
|
4087
4272
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -4478,7 +4663,8 @@ var T2Manager = class extends BaseTier {
|
|
|
4478
4663
|
});
|
|
4479
4664
|
this.emit("peer-sync-received", { fromId, content });
|
|
4480
4665
|
}
|
|
4481
|
-
async execute(assignment, taskId) {
|
|
4666
|
+
async execute(assignment, taskId, signal) {
|
|
4667
|
+
this.signal = signal;
|
|
4482
4668
|
this.assignment = assignment;
|
|
4483
4669
|
this.taskId = taskId;
|
|
4484
4670
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -4490,12 +4676,14 @@ var T2Manager = class extends BaseTier {
|
|
|
4490
4676
|
});
|
|
4491
4677
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
4492
4678
|
try {
|
|
4679
|
+
this.throwIfCancelled();
|
|
4493
4680
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
4494
4681
|
this.sendStatusUpdate({
|
|
4495
4682
|
progressPct: 20,
|
|
4496
4683
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
4497
4684
|
status: "IN_PROGRESS"
|
|
4498
4685
|
});
|
|
4686
|
+
this.throwIfCancelled();
|
|
4499
4687
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
4500
4688
|
this.sendStatusUpdate({
|
|
4501
4689
|
progressPct: 90,
|
|
@@ -4534,13 +4722,17 @@ var T2Manager = class extends BaseTier {
|
|
|
4534
4722
|
}
|
|
4535
4723
|
// ── Private ──────────────────────────────────
|
|
4536
4724
|
async decomposeSection(assignment) {
|
|
4725
|
+
const peerPlans = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p) => `[Peer ${p.fromId} Plan]: ${p.content.sectionTitle} - ${p.content.subtaskTitles?.join(", ")}`).join("\n");
|
|
4537
4726
|
const prompt = `Decompose this section into 2-5 concrete subtasks for T3 workers.
|
|
4538
4727
|
|
|
4539
4728
|
Section: ${assignment.sectionTitle}
|
|
4540
4729
|
Description: ${assignment.description}
|
|
4541
4730
|
Expected output: ${assignment.expectedOutput}
|
|
4542
4731
|
Constraints: ${assignment.constraints.join("; ")}
|
|
4543
|
-
|
|
4732
|
+
${peerPlans ? `
|
|
4733
|
+
Context from sibling T2 plans (use this to align execution and avoid overlaps):
|
|
4734
|
+
${peerPlans}
|
|
4735
|
+
` : ""}
|
|
4544
4736
|
Return a JSON array of subtask objects, each with:
|
|
4545
4737
|
- subtaskId: string (unique)
|
|
4546
4738
|
- subtaskTitle: string
|
|
@@ -4658,11 +4850,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4658
4850
|
).join(", ")}`,
|
|
4659
4851
|
status: "IN_PROGRESS"
|
|
4660
4852
|
});
|
|
4853
|
+
this.throwIfCancelled();
|
|
4661
4854
|
const waveResults = await Promise.allSettled(
|
|
4662
4855
|
runnableIds.map(async (id) => {
|
|
4663
4856
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4664
4857
|
const worker = workerMap.get(id);
|
|
4665
|
-
const result = await worker.execute(assignment, taskId);
|
|
4858
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
4666
4859
|
resultMap.set(id, result);
|
|
4667
4860
|
return result;
|
|
4668
4861
|
})
|
|
@@ -4676,6 +4869,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4676
4869
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4677
4870
|
const retried = await this.retryT3(assignment, taskId);
|
|
4678
4871
|
resultMap.set(id, retried);
|
|
4872
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
4873
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4874
|
+
if (this.toolCreator) {
|
|
4875
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
4876
|
+
this.sendStatusUpdate({
|
|
4877
|
+
progressPct: 50,
|
|
4878
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
4879
|
+
status: "IN_PROGRESS"
|
|
4880
|
+
});
|
|
4881
|
+
const toolName = await this.toolCreator.createTool(
|
|
4882
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
4883
|
+
assignment.description
|
|
4884
|
+
);
|
|
4885
|
+
if (toolName) {
|
|
4886
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
4887
|
+
this.sendStatusUpdate({
|
|
4888
|
+
progressPct: 60,
|
|
4889
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
4890
|
+
status: "IN_PROGRESS"
|
|
4891
|
+
});
|
|
4892
|
+
try {
|
|
4893
|
+
const verifyResult = await this.router.generate("T2", {
|
|
4894
|
+
messages: [{ role: "user", content: `A new tool named "${toolName}" was just created dynamically to help with: ${assignment.description}. Based on its name and purpose, does this seem like a valid addition? Reply "VERIFIED" or "REJECTED".` }],
|
|
4895
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
4896
|
+
maxTokens: 50
|
|
4897
|
+
});
|
|
4898
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
4899
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
4900
|
+
const retried = await this.retryT3({
|
|
4901
|
+
...assignment,
|
|
4902
|
+
description: `${assignment.description}
|
|
4903
|
+
|
|
4904
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
4905
|
+
}, taskId);
|
|
4906
|
+
resultMap.set(id, retried);
|
|
4907
|
+
} else {
|
|
4908
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
4909
|
+
resultMap.set(id, r.value);
|
|
4910
|
+
}
|
|
4911
|
+
} catch {
|
|
4912
|
+
const retried = await this.retryT3({
|
|
4913
|
+
...assignment,
|
|
4914
|
+
description: `${assignment.description}
|
|
4915
|
+
|
|
4916
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
4917
|
+
}, taskId);
|
|
4918
|
+
resultMap.set(id, retried);
|
|
4919
|
+
}
|
|
4920
|
+
} else {
|
|
4921
|
+
resultMap.set(id, r.value);
|
|
4922
|
+
}
|
|
4923
|
+
} else {
|
|
4924
|
+
resultMap.set(id, r.value);
|
|
4925
|
+
}
|
|
4679
4926
|
}
|
|
4680
4927
|
for (const dependent of adj.get(id) ?? []) {
|
|
4681
4928
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -4739,7 +4986,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4739
4986
|
}));
|
|
4740
4987
|
return worker.execute(
|
|
4741
4988
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
4742
|
-
taskId
|
|
4989
|
+
taskId,
|
|
4990
|
+
this.signal
|
|
4743
4991
|
);
|
|
4744
4992
|
}
|
|
4745
4993
|
publishSectionOutput(result) {
|
|
@@ -4753,24 +5001,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4753
5001
|
async aggregateResults(assignment, results) {
|
|
4754
5002
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
4755
5003
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
4756
|
-
const
|
|
4757
|
-
const
|
|
5004
|
+
const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
|
|
5005
|
+
const peerContext = peerOutputs ? `
|
|
5006
|
+
|
|
5007
|
+
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
5008
|
+
${peerOutputs}` : "";
|
|
5009
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
5010
|
+
let currentSummary = "";
|
|
5011
|
+
let i = 0;
|
|
5012
|
+
while (i < completed.length) {
|
|
5013
|
+
let chunkText = "";
|
|
5014
|
+
let chunkEnd = i;
|
|
5015
|
+
while (chunkEnd < completed.length) {
|
|
5016
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
4758
5017
|
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
5018
|
+
`;
|
|
5019
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
5020
|
+
break;
|
|
5021
|
+
}
|
|
5022
|
+
chunkText += nextOutput;
|
|
5023
|
+
chunkEnd++;
|
|
5024
|
+
}
|
|
5025
|
+
i = chunkEnd;
|
|
5026
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
5027
|
+
${currentSummary ? `
|
|
5028
|
+
PREVIOUS SUMMARY SO FAR:
|
|
5029
|
+
${currentSummary}
|
|
5030
|
+
|
|
5031
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
5032
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
5033
|
+
const messages = [{ role: "user", content: prompt }];
|
|
5034
|
+
try {
|
|
5035
|
+
const result = await this.router.generate("T2", {
|
|
5036
|
+
messages,
|
|
5037
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
4765
5038
|
|
|
4766
5039
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
5040
|
+
maxTokens: 500
|
|
5041
|
+
});
|
|
5042
|
+
currentSummary = result.content;
|
|
5043
|
+
} catch (err) {
|
|
5044
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
5045
|
+
return currentSummary + "\n\n" + chunkText;
|
|
5046
|
+
}
|
|
4773
5047
|
}
|
|
5048
|
+
return currentSummary;
|
|
4774
5049
|
}
|
|
4775
5050
|
determineStatus(results) {
|
|
4776
5051
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -4898,10 +5173,10 @@ Rules:
|
|
|
4898
5173
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
4899
5174
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
4900
5175
|
|
|
4901
|
-
|
|
4902
|
-
-
|
|
4903
|
-
-
|
|
4904
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
5176
|
+
DEPENDENCY GUIDANCE:
|
|
5177
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
5178
|
+
- Populate "dependsOn" with section IDs ONLY when a later section strictly depends on the output of an earlier one (e.g. write code \u2192 then test it).
|
|
5179
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
4905
5180
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
4906
5181
|
|
|
4907
5182
|
QUALITY RULES:
|
|
@@ -4940,7 +5215,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
4940
5215
|
setToolCreator(creator) {
|
|
4941
5216
|
this.toolCreator = creator;
|
|
4942
5217
|
}
|
|
4943
|
-
async execute(userPrompt, images, systemContext) {
|
|
5218
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
5219
|
+
this.signal = signal;
|
|
4944
5220
|
this.taskId = crypto.randomUUID();
|
|
4945
5221
|
this.setLabel("Administrator");
|
|
4946
5222
|
this.setStatus("ACTIVE");
|
|
@@ -4951,10 +5227,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
4951
5227
|
status: "IN_PROGRESS"
|
|
4952
5228
|
});
|
|
4953
5229
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
5230
|
+
this.throwIfCancelled();
|
|
4954
5231
|
let enrichedPrompt = userPrompt;
|
|
4955
5232
|
if (images?.length) {
|
|
4956
5233
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
4957
5234
|
}
|
|
5235
|
+
this.throwIfCancelled();
|
|
4958
5236
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
4959
5237
|
this.sendStatusUpdate({
|
|
4960
5238
|
progressPct: 10,
|
|
@@ -4962,21 +5240,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
4962
5240
|
status: "IN_PROGRESS"
|
|
4963
5241
|
});
|
|
4964
5242
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
4965
|
-
|
|
5243
|
+
this.throwIfCancelled();
|
|
5244
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
5245
|
+
let pass = 1;
|
|
5246
|
+
const MAX_REPLAN_PASSES = 2;
|
|
5247
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
5248
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
5249
|
+
if (reviewResult.approved) {
|
|
5250
|
+
this.log("T1 Review passed.");
|
|
5251
|
+
break;
|
|
5252
|
+
}
|
|
5253
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
5254
|
+
this.sendStatusUpdate({
|
|
5255
|
+
progressPct: 80 + pass * 5,
|
|
5256
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
5257
|
+
status: "IN_PROGRESS"
|
|
5258
|
+
});
|
|
5259
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
5260
|
+
Review reason: ${reviewResult.reason}
|
|
5261
|
+
|
|
5262
|
+
Original goal: ${enrichedPrompt}
|
|
5263
|
+
|
|
5264
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
5265
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
5266
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
5267
|
+
pass++;
|
|
5268
|
+
}
|
|
4966
5269
|
this.sendStatusUpdate({
|
|
4967
5270
|
progressPct: 95,
|
|
4968
5271
|
currentAction: "Compiling final output",
|
|
4969
5272
|
status: "IN_PROGRESS"
|
|
4970
5273
|
});
|
|
4971
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
5274
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
4972
5275
|
this.setStatus("COMPLETED");
|
|
4973
5276
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
4974
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
5277
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
4975
5278
|
}
|
|
4976
5279
|
getEscalations() {
|
|
4977
5280
|
return [...this.escalations];
|
|
4978
5281
|
}
|
|
4979
5282
|
// ── Private ──────────────────────────────────
|
|
5283
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
5284
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
5285
|
+
if (failedSections.length > 0) {
|
|
5286
|
+
return {
|
|
5287
|
+
approved: false,
|
|
5288
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
5289
|
+
};
|
|
5290
|
+
}
|
|
5291
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
5292
|
+
${r.sectionSummary}`).join("\n\n");
|
|
5293
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
5294
|
+
Review the following execution outputs against the original user prompt.
|
|
5295
|
+
|
|
5296
|
+
Original Request: ${originalPrompt}
|
|
5297
|
+
|
|
5298
|
+
T2 Manager Summaries:
|
|
5299
|
+
${sectionsText}
|
|
5300
|
+
|
|
5301
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
5302
|
+
If yes, reply with exactly: "APPROVED".
|
|
5303
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
5304
|
+
try {
|
|
5305
|
+
const result = await this.router.generate("T1", {
|
|
5306
|
+
messages: [{ role: "user", content: prompt }],
|
|
5307
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
5308
|
+
maxTokens: 500,
|
|
5309
|
+
temperature: 0
|
|
5310
|
+
});
|
|
5311
|
+
const response = result.content.trim();
|
|
5312
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
5313
|
+
return { approved: true };
|
|
5314
|
+
}
|
|
5315
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
5316
|
+
} catch {
|
|
5317
|
+
return { approved: true };
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
4980
5320
|
async analyzeImages(prompt, images) {
|
|
4981
5321
|
const visionModel = this.router.getModelForTier("T1");
|
|
4982
5322
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -5005,29 +5345,35 @@ ${systemContext}` : "";
|
|
|
5005
5345
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
5006
5346
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
5007
5347
|
|
|
5008
|
-
Return JSON where
|
|
5348
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
5009
5349
|
{
|
|
5010
5350
|
"sections": [{
|
|
5351
|
+
"sectionId": "s1",
|
|
5352
|
+
"sectionTitle": "Setup Project",
|
|
5353
|
+
"description": "Initialize the project",
|
|
5354
|
+
"expectedOutput": "Basic structure created",
|
|
5355
|
+
"constraints": [],
|
|
5356
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
5011
5357
|
"t3Subtasks": [{
|
|
5012
5358
|
"subtaskId": "t1",
|
|
5013
|
-
"subtaskTitle": "
|
|
5014
|
-
"
|
|
5015
|
-
"
|
|
5016
|
-
|
|
5017
|
-
"
|
|
5018
|
-
"subtaskTitle": "Save Code to File",
|
|
5019
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
5020
|
-
"executionMode": "parallel"
|
|
5021
|
-
}, {
|
|
5022
|
-
"subtaskId": "t3",
|
|
5023
|
-
"subtaskTitle": "Execute and Verify",
|
|
5024
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
5025
|
-
"executionMode": "parallel"
|
|
5359
|
+
"subtaskTitle": "Init NPM",
|
|
5360
|
+
"description": "Run npm init",
|
|
5361
|
+
"expectedOutput": "package.json created",
|
|
5362
|
+
"constraints": [],
|
|
5363
|
+
"dependsOn": []
|
|
5026
5364
|
}]
|
|
5365
|
+
}, {
|
|
5366
|
+
"sectionId": "s2",
|
|
5367
|
+
"sectionTitle": "Write Tests",
|
|
5368
|
+
"description": "Write tests for the project",
|
|
5369
|
+
"expectedOutput": "Tests passing",
|
|
5370
|
+
"constraints": [],
|
|
5371
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
5372
|
+
"t3Subtasks": [...]
|
|
5027
5373
|
}]
|
|
5028
5374
|
}
|
|
5029
|
-
Use dependsOn when a
|
|
5030
|
-
Leave dependsOn empty for
|
|
5375
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
5376
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
5031
5377
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
5032
5378
|
const result = await this.router.generate("T1", {
|
|
5033
5379
|
messages,
|
|
@@ -5155,92 +5501,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
5155
5501
|
].filter(Boolean).join(" ");
|
|
5156
5502
|
m.setHierarchyContext(context);
|
|
5157
5503
|
});
|
|
5158
|
-
if (overlapSections.size > 0
|
|
5159
|
-
this.log("Overlap detected \u2014
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5504
|
+
if (overlapSections.size > 0) {
|
|
5505
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
5506
|
+
const overlapArray = Array.from(overlapSections);
|
|
5507
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
5508
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
5509
|
+
if (section) {
|
|
5510
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
5163
5511
|
}
|
|
5164
5512
|
}
|
|
5165
5513
|
}
|
|
5166
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
5167
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
5168
5514
|
const t2Results = [];
|
|
5169
5515
|
try {
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5516
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
5517
|
+
} finally {
|
|
5518
|
+
cleanup();
|
|
5519
|
+
}
|
|
5520
|
+
return t2Results;
|
|
5521
|
+
}
|
|
5522
|
+
/**
|
|
5523
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
5524
|
+
*/
|
|
5525
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
5526
|
+
const adj = /* @__PURE__ */ new Map();
|
|
5527
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
5528
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5529
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
5530
|
+
for (const s of sections) {
|
|
5531
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
5532
|
+
inDegree.set(s.sectionId, 0);
|
|
5533
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
5534
|
+
}
|
|
5535
|
+
for (const s of sections) {
|
|
5536
|
+
for (const dep of s.dependsOn ?? []) {
|
|
5537
|
+
adj.get(dep).add(s.sectionId);
|
|
5538
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5541
|
+
const queue = [];
|
|
5542
|
+
const degree = new Map(inDegree);
|
|
5543
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
5544
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5545
|
+
while (queue.length > 0) {
|
|
5546
|
+
const u = queue.shift();
|
|
5547
|
+
visited.add(u);
|
|
5548
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
5549
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
5550
|
+
degree.set(v, newDeg);
|
|
5551
|
+
if (newDeg === 0) queue.push(v);
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
5555
|
+
if (cycleNodes.length > 0) {
|
|
5556
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
5557
|
+
for (const s of sections) {
|
|
5558
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
5559
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
5560
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
5561
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
5562
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
5201
5563
|
}
|
|
5564
|
+
s.dependsOn = safeDeps;
|
|
5202
5565
|
}
|
|
5203
|
-
}
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
const totalSections = sections.length;
|
|
5569
|
+
let completedSections = 0;
|
|
5570
|
+
const executeWave = async () => {
|
|
5571
|
+
const readyIds = [];
|
|
5572
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
5573
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
5574
|
+
readyIds.push(id);
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
if (readyIds.length === 0) return;
|
|
5578
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
5579
|
+
resultMap.set(id, null);
|
|
5580
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
5581
|
+
const section = sections[index];
|
|
5582
|
+
const manager = managers[index];
|
|
5583
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
5584
|
+
this.sendStatusUpdate({
|
|
5585
|
+
progressPct,
|
|
5586
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
5587
|
+
status: "IN_PROGRESS"
|
|
5588
|
+
});
|
|
5589
|
+
this.throwIfCancelled();
|
|
5590
|
+
let result;
|
|
5591
|
+
try {
|
|
5592
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
5593
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
5594
|
+
if (result.status === "ESCALATED") {
|
|
5595
|
+
this.escalations.push({
|
|
5596
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
5597
|
+
sectionId: section.sectionId,
|
|
5598
|
+
attempted: result.issues,
|
|
5599
|
+
blocker: result.issues.join("; "),
|
|
5600
|
+
needs: "Human review required"
|
|
5236
5601
|
});
|
|
5237
5602
|
}
|
|
5603
|
+
} catch (err) {
|
|
5604
|
+
result = {
|
|
5605
|
+
sectionId: section.sectionId,
|
|
5606
|
+
sectionTitle: section.sectionTitle,
|
|
5607
|
+
status: "FAILED",
|
|
5608
|
+
t3Results: [],
|
|
5609
|
+
sectionSummary: "",
|
|
5610
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
5611
|
+
};
|
|
5238
5612
|
}
|
|
5613
|
+
resultMap.set(id, result);
|
|
5614
|
+
completedSections++;
|
|
5615
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
5616
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
5617
|
+
}
|
|
5618
|
+
}));
|
|
5619
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
5620
|
+
await executeWave();
|
|
5239
5621
|
}
|
|
5240
|
-
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
return t2Results;
|
|
5622
|
+
};
|
|
5623
|
+
await executeWave();
|
|
5624
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
5244
5625
|
}
|
|
5245
5626
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
5246
5627
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -5690,13 +6071,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
5690
6071
|
}
|
|
5691
6072
|
async execute(input, _options) {
|
|
5692
6073
|
const platform = input["platform"] ?? "github";
|
|
5693
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
5694
6074
|
const operation = input["operation"];
|
|
5695
6075
|
const repo = input["repo"];
|
|
5696
|
-
|
|
5697
|
-
|
|
6076
|
+
let token = input["token"];
|
|
6077
|
+
if (!token) {
|
|
6078
|
+
if (platform === "github") {
|
|
6079
|
+
token = process.env["GITHUB_TOKEN"];
|
|
6080
|
+
} else {
|
|
6081
|
+
token = process.env["GITLAB_TOKEN"];
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
if (!token) {
|
|
6085
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
6086
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
6087
|
+
}
|
|
6088
|
+
try {
|
|
6089
|
+
if (platform === "github") {
|
|
6090
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
6091
|
+
}
|
|
6092
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
6093
|
+
} catch (err) {
|
|
6094
|
+
const axiosErr = err;
|
|
6095
|
+
if (axiosErr?.response?.status) {
|
|
6096
|
+
const status = axiosErr.response.status;
|
|
6097
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
6098
|
+
switch (status) {
|
|
6099
|
+
case 401:
|
|
6100
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
6101
|
+
case 403:
|
|
6102
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
6103
|
+
case 404:
|
|
6104
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
6105
|
+
case 422:
|
|
6106
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
6107
|
+
case 429:
|
|
6108
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
6109
|
+
default:
|
|
6110
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
5698
6114
|
}
|
|
5699
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
5700
6115
|
}
|
|
5701
6116
|
async executeGitHub(operation, repo, token, input) {
|
|
5702
6117
|
const headers = {
|
|
@@ -5783,6 +6198,7 @@ ${response.data.description}`;
|
|
|
5783
6198
|
};
|
|
5784
6199
|
|
|
5785
6200
|
// src/tools/browser.ts
|
|
6201
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
5786
6202
|
var BrowserTool = class extends BaseTool {
|
|
5787
6203
|
name = "browser";
|
|
5788
6204
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -5791,7 +6207,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
5791
6207
|
properties: {
|
|
5792
6208
|
action: {
|
|
5793
6209
|
type: "string",
|
|
5794
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
6210
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
5795
6211
|
},
|
|
5796
6212
|
url: { type: "string", description: "URL to navigate to" },
|
|
5797
6213
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -5811,53 +6227,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
5811
6227
|
try {
|
|
5812
6228
|
playwright = await import('playwright');
|
|
5813
6229
|
} catch {
|
|
5814
|
-
|
|
6230
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
5815
6231
|
}
|
|
5816
|
-
if (!this.browser) {
|
|
5817
|
-
const pw = playwright;
|
|
5818
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
5819
|
-
const b = this.browser;
|
|
5820
|
-
this.page = await b.newPage();
|
|
5821
|
-
}
|
|
5822
|
-
const page = this.page;
|
|
5823
6232
|
const action = input["action"];
|
|
5824
6233
|
const timeout = input["timeout"] ?? 1e4;
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
case "evaluate": {
|
|
5843
|
-
const result = await page.evaluate(input["script"]);
|
|
5844
|
-
return JSON.stringify(result);
|
|
6234
|
+
if (action === "close") {
|
|
6235
|
+
await this.close();
|
|
6236
|
+
return "Browser closed.";
|
|
6237
|
+
}
|
|
6238
|
+
if (!this.browser || !this.page) {
|
|
6239
|
+
await this.close();
|
|
6240
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
6241
|
+
const timeoutPromise = new Promise(
|
|
6242
|
+
(_, reject) => setTimeout(() => reject(new Error(`Browser launch timed out after ${BROWSER_LAUNCH_TIMEOUT_MS}ms. Is Chromium installed? Run: npx playwright install chromium`)), BROWSER_LAUNCH_TIMEOUT_MS)
|
|
6243
|
+
);
|
|
6244
|
+
try {
|
|
6245
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
6246
|
+
this.page = await this.browser.newPage();
|
|
6247
|
+
} catch (err) {
|
|
6248
|
+
this.browser = null;
|
|
6249
|
+
this.page = null;
|
|
6250
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
5845
6251
|
}
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
6252
|
+
}
|
|
6253
|
+
const page = this.page;
|
|
6254
|
+
try {
|
|
6255
|
+
switch (action) {
|
|
6256
|
+
case "navigate": {
|
|
6257
|
+
await page.goto(input["url"], { timeout });
|
|
6258
|
+
const title = await page.title();
|
|
6259
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
6260
|
+
}
|
|
6261
|
+
case "click": {
|
|
6262
|
+
await page.click(input["selector"], { timeout });
|
|
6263
|
+
return `Clicked ${input["selector"]}`;
|
|
6264
|
+
}
|
|
6265
|
+
case "fill": {
|
|
6266
|
+
await page.fill(input["selector"], input["value"]);
|
|
6267
|
+
return `Filled ${input["selector"]} with value`;
|
|
6268
|
+
}
|
|
6269
|
+
case "screenshot": {
|
|
6270
|
+
const buf = await page.screenshot({ type: "png" });
|
|
6271
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
6272
|
+
}
|
|
6273
|
+
case "evaluate": {
|
|
6274
|
+
const result = await page.evaluate(input["script"]);
|
|
6275
|
+
return JSON.stringify(result);
|
|
6276
|
+
}
|
|
6277
|
+
case "extract_text": {
|
|
6278
|
+
const text = await page.locator("body").innerText();
|
|
6279
|
+
return text.slice(0, 1e4);
|
|
6280
|
+
}
|
|
6281
|
+
case "wait": {
|
|
6282
|
+
await page.waitForTimeout(timeout);
|
|
6283
|
+
return `Waited ${timeout}ms`;
|
|
6284
|
+
}
|
|
6285
|
+
default:
|
|
6286
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
5849
6287
|
}
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
6288
|
+
} catch (err) {
|
|
6289
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6290
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
6291
|
+
await this.close();
|
|
6292
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
5853
6293
|
}
|
|
5854
|
-
|
|
5855
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
6294
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
5856
6295
|
}
|
|
5857
6296
|
}
|
|
5858
6297
|
async close() {
|
|
5859
|
-
|
|
5860
|
-
|
|
6298
|
+
try {
|
|
6299
|
+
if (this.page) {
|
|
6300
|
+
await this.page.close().catch(() => {
|
|
6301
|
+
});
|
|
6302
|
+
this.page = null;
|
|
6303
|
+
}
|
|
6304
|
+
if (this.browser) {
|
|
6305
|
+
await this.browser.close().catch(() => {
|
|
6306
|
+
});
|
|
6307
|
+
this.browser = null;
|
|
6308
|
+
}
|
|
6309
|
+
} catch {
|
|
5861
6310
|
this.browser = null;
|
|
5862
6311
|
this.page = null;
|
|
5863
6312
|
}
|
|
@@ -5954,6 +6403,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
5954
6403
|
});
|
|
5955
6404
|
}
|
|
5956
6405
|
};
|
|
6406
|
+
function detectCommand(candidates2) {
|
|
6407
|
+
for (const cmd of candidates2) {
|
|
6408
|
+
try {
|
|
6409
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
6410
|
+
child_process.execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
6411
|
+
return cmd;
|
|
6412
|
+
} catch {
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
return null;
|
|
6416
|
+
}
|
|
6417
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
6418
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
5957
6419
|
var CodeInterpreterTool = class extends BaseTool {
|
|
5958
6420
|
name = "run_code";
|
|
5959
6421
|
description = "Execute a Python or Node.js script to perform complex tasks (data processing, file conversion, etc.). The script is automatically cleaned up after execution.";
|
|
@@ -5969,10 +6431,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5969
6431
|
isDangerous() {
|
|
5970
6432
|
return true;
|
|
5971
6433
|
}
|
|
5972
|
-
async execute(input,
|
|
6434
|
+
async execute(input, _options) {
|
|
5973
6435
|
const language = input["language"];
|
|
5974
6436
|
const code = input["code"];
|
|
5975
6437
|
const args = input["args"] ?? [];
|
|
6438
|
+
let cmdPrefix;
|
|
6439
|
+
if (language === "python") {
|
|
6440
|
+
if (!PYTHON_CMD) {
|
|
6441
|
+
return [
|
|
6442
|
+
"Error: Python interpreter not found.",
|
|
6443
|
+
"Please install Python and ensure it is in your PATH.",
|
|
6444
|
+
"Tried: python3, python"
|
|
6445
|
+
].join("\n");
|
|
6446
|
+
}
|
|
6447
|
+
cmdPrefix = PYTHON_CMD;
|
|
6448
|
+
} else {
|
|
6449
|
+
if (!NODE_CMD) {
|
|
6450
|
+
return [
|
|
6451
|
+
"Error: Node.js interpreter not found.",
|
|
6452
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
6453
|
+
"Tried: node"
|
|
6454
|
+
].join("\n");
|
|
6455
|
+
}
|
|
6456
|
+
cmdPrefix = NODE_CMD;
|
|
6457
|
+
}
|
|
5976
6458
|
const tmpDir = path17__default.default.join(process.cwd(), ".cascade", "tmp");
|
|
5977
6459
|
if (!fs14__default.default.existsSync(tmpDir)) {
|
|
5978
6460
|
fs14__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -5981,8 +6463,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5981
6463
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
5982
6464
|
const filePath = path17__default.default.join(tmpDir, fileName);
|
|
5983
6465
|
fs14__default.default.writeFileSync(filePath, code, "utf-8");
|
|
5984
|
-
const
|
|
5985
|
-
const
|
|
6466
|
+
const quotedPath = `"${filePath}"`;
|
|
6467
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
6468
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
5986
6469
|
return new Promise((resolve) => {
|
|
5987
6470
|
const startMs = Date.now();
|
|
5988
6471
|
child_process.exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -5995,10 +6478,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5995
6478
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
5996
6479
|
}
|
|
5997
6480
|
if (error) {
|
|
5998
|
-
|
|
6481
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
6482
|
+
if (timedOut) {
|
|
6483
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
6484
|
+
Partial stdout: ${stdout}
|
|
6485
|
+
Stderr: ${stderr}`);
|
|
6486
|
+
} else {
|
|
6487
|
+
resolve(`Execution failed (${duration}ms):
|
|
5999
6488
|
Error: ${error.message}
|
|
6000
6489
|
Stderr: ${stderr}
|
|
6001
6490
|
Stdout: ${stdout}`);
|
|
6491
|
+
}
|
|
6002
6492
|
} else {
|
|
6003
6493
|
resolve(`Execution successful (${duration}ms):
|
|
6004
6494
|
Stdout: ${stdout}
|
|
@@ -6063,6 +6553,186 @@ ${formatted}`;
|
|
|
6063
6553
|
}
|
|
6064
6554
|
};
|
|
6065
6555
|
|
|
6556
|
+
// src/tools/web-search.ts
|
|
6557
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
6558
|
+
const url = new URL("/search", baseUrl);
|
|
6559
|
+
url.searchParams.set("q", query);
|
|
6560
|
+
url.searchParams.set("format", "json");
|
|
6561
|
+
url.searchParams.set("categories", "general");
|
|
6562
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
6563
|
+
const resp = await fetch(url.toString(), {
|
|
6564
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
6565
|
+
signal: AbortSignal.timeout(1e4)
|
|
6566
|
+
});
|
|
6567
|
+
if (!resp.ok) {
|
|
6568
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
6569
|
+
}
|
|
6570
|
+
const data = await resp.json();
|
|
6571
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6572
|
+
title: r.title ?? "",
|
|
6573
|
+
url: r.url ?? "",
|
|
6574
|
+
snippet: r.content ?? "",
|
|
6575
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
6576
|
+
}));
|
|
6577
|
+
}
|
|
6578
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
6579
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
6580
|
+
const resp = await fetch(url, {
|
|
6581
|
+
headers: {
|
|
6582
|
+
"Accept": "application/json",
|
|
6583
|
+
"Accept-Encoding": "gzip",
|
|
6584
|
+
"X-Subscription-Token": apiKey
|
|
6585
|
+
},
|
|
6586
|
+
signal: AbortSignal.timeout(1e4)
|
|
6587
|
+
});
|
|
6588
|
+
if (!resp.ok) {
|
|
6589
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
6590
|
+
}
|
|
6591
|
+
const data = await resp.json();
|
|
6592
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6593
|
+
title: r.title ?? "",
|
|
6594
|
+
url: r.url ?? "",
|
|
6595
|
+
snippet: r.description ?? "",
|
|
6596
|
+
engine: "brave"
|
|
6597
|
+
}));
|
|
6598
|
+
}
|
|
6599
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
6600
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
6601
|
+
method: "POST",
|
|
6602
|
+
headers: {
|
|
6603
|
+
"Content-Type": "application/json",
|
|
6604
|
+
"Authorization": `Bearer ${apiKey}`
|
|
6605
|
+
},
|
|
6606
|
+
body: JSON.stringify({
|
|
6607
|
+
query,
|
|
6608
|
+
max_results: maxResults,
|
|
6609
|
+
search_depth: "basic",
|
|
6610
|
+
include_answer: false,
|
|
6611
|
+
include_raw_content: false
|
|
6612
|
+
}),
|
|
6613
|
+
signal: AbortSignal.timeout(15e3)
|
|
6614
|
+
});
|
|
6615
|
+
if (!resp.ok) {
|
|
6616
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
6617
|
+
}
|
|
6618
|
+
const data = await resp.json();
|
|
6619
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6620
|
+
title: r.title ?? "",
|
|
6621
|
+
url: r.url ?? "",
|
|
6622
|
+
snippet: r.content ?? "",
|
|
6623
|
+
engine: "tavily"
|
|
6624
|
+
}));
|
|
6625
|
+
}
|
|
6626
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
6627
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
6628
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
6629
|
+
signal: AbortSignal.timeout(1e4)
|
|
6630
|
+
});
|
|
6631
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
6632
|
+
const html = await resp.text();
|
|
6633
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
6634
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
6635
|
+
const links = [];
|
|
6636
|
+
const snippets = [];
|
|
6637
|
+
let m;
|
|
6638
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
6639
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
6640
|
+
}
|
|
6641
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
6642
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
6643
|
+
}
|
|
6644
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
6645
|
+
title: link.title,
|
|
6646
|
+
url: link.url,
|
|
6647
|
+
snippet: snippets[i] ?? "",
|
|
6648
|
+
engine: "duckduckgo-lite"
|
|
6649
|
+
}));
|
|
6650
|
+
}
|
|
6651
|
+
var WebSearchTool = class extends BaseTool {
|
|
6652
|
+
name = "web_search";
|
|
6653
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
6654
|
+
inputSchema = {
|
|
6655
|
+
type: "object",
|
|
6656
|
+
properties: {
|
|
6657
|
+
query: { type: "string", description: "The search query" },
|
|
6658
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
6659
|
+
},
|
|
6660
|
+
required: ["query"]
|
|
6661
|
+
};
|
|
6662
|
+
config;
|
|
6663
|
+
constructor(config = {}) {
|
|
6664
|
+
super();
|
|
6665
|
+
this.config = {
|
|
6666
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
6667
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
6668
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
6669
|
+
maxResults: config.maxResults ?? 5
|
|
6670
|
+
};
|
|
6671
|
+
}
|
|
6672
|
+
async execute(input, _options) {
|
|
6673
|
+
const query = input["query"];
|
|
6674
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
6675
|
+
const maxResults = Math.min(
|
|
6676
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
6677
|
+
10
|
|
6678
|
+
);
|
|
6679
|
+
const errors = [];
|
|
6680
|
+
let results = [];
|
|
6681
|
+
if (this.config.searxngUrl) {
|
|
6682
|
+
try {
|
|
6683
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
6684
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6685
|
+
errors.push("SearXNG: returned 0 results");
|
|
6686
|
+
} catch (err) {
|
|
6687
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
6688
|
+
}
|
|
6689
|
+
}
|
|
6690
|
+
if (this.config.braveApiKey) {
|
|
6691
|
+
try {
|
|
6692
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
6693
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6694
|
+
errors.push("Brave: returned 0 results");
|
|
6695
|
+
} catch (err) {
|
|
6696
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
if (this.config.tavilyApiKey) {
|
|
6700
|
+
try {
|
|
6701
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
6702
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6703
|
+
errors.push("Tavily: returned 0 results");
|
|
6704
|
+
} catch (err) {
|
|
6705
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6708
|
+
try {
|
|
6709
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
6710
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6711
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
6712
|
+
} catch (err) {
|
|
6713
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
6714
|
+
}
|
|
6715
|
+
const configHint = !this.config.searxngUrl && !this.config.braveApiKey && !this.config.tavilyApiKey ? "\nTip: Configure a search backend for better results:\n \u2022 Self-hosted: set SEARXNG_URL in your environment\n \u2022 Brave Search API: set BRAVE_SEARCH_API_KEY\n \u2022 Tavily API: set TAVILY_API_KEY" : "";
|
|
6716
|
+
return [
|
|
6717
|
+
`Web search for "${query}" failed across all backends:`,
|
|
6718
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
6719
|
+
configHint
|
|
6720
|
+
].join("\n");
|
|
6721
|
+
}
|
|
6722
|
+
formatResults(query, results) {
|
|
6723
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
6724
|
+
for (let i = 0; i < results.length; i++) {
|
|
6725
|
+
const r = results[i];
|
|
6726
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
6727
|
+
lines.push(` URL: ${r.url}`);
|
|
6728
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
6729
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
6730
|
+
lines.push("");
|
|
6731
|
+
}
|
|
6732
|
+
return lines.join("\n");
|
|
6733
|
+
}
|
|
6734
|
+
};
|
|
6735
|
+
|
|
6066
6736
|
// src/tools/mcp.ts
|
|
6067
6737
|
var McpToolWrapper = class extends BaseTool {
|
|
6068
6738
|
name;
|
|
@@ -6184,7 +6854,8 @@ var ToolRegistry = class {
|
|
|
6184
6854
|
new ImageAnalyzeTool(),
|
|
6185
6855
|
new PDFCreateTool(),
|
|
6186
6856
|
new CodeInterpreterTool(),
|
|
6187
|
-
new PeerCommunicationTool()
|
|
6857
|
+
new PeerCommunicationTool(),
|
|
6858
|
+
new WebSearchTool(this.config.webSearch)
|
|
6188
6859
|
];
|
|
6189
6860
|
for (const tool of tools) {
|
|
6190
6861
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -6208,8 +6879,23 @@ var ToolRegistry = class {
|
|
|
6208
6879
|
return this.ignoreMatcher.ignores(posixRel);
|
|
6209
6880
|
}
|
|
6210
6881
|
};
|
|
6211
|
-
var McpClient = class {
|
|
6882
|
+
var McpClient = class _McpClient {
|
|
6883
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
6884
|
+
/**
|
|
6885
|
+
* Forcefully kills all known MCP child processes.
|
|
6886
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
6887
|
+
*/
|
|
6888
|
+
static killAllProcesses() {
|
|
6889
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
6890
|
+
try {
|
|
6891
|
+
process.kill(pid, "SIGKILL");
|
|
6892
|
+
} catch {
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
_McpClient.activeProcessPids.clear();
|
|
6896
|
+
}
|
|
6212
6897
|
clients = /* @__PURE__ */ new Map();
|
|
6898
|
+
transports = /* @__PURE__ */ new Map();
|
|
6213
6899
|
tools = /* @__PURE__ */ new Map();
|
|
6214
6900
|
trustedServers;
|
|
6215
6901
|
approvalCallback;
|
|
@@ -6238,6 +6924,8 @@ var McpClient = class {
|
|
|
6238
6924
|
);
|
|
6239
6925
|
await client.connect(transport);
|
|
6240
6926
|
this.clients.set(server.name, client);
|
|
6927
|
+
this.transports.set(server.name, transport);
|
|
6928
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
6241
6929
|
const toolsResult = await client.listTools();
|
|
6242
6930
|
for (const tool of toolsResult.tools) {
|
|
6243
6931
|
for (const existing of this.tools.values()) {
|
|
@@ -6259,8 +6947,11 @@ var McpClient = class {
|
|
|
6259
6947
|
async disconnect(serverName) {
|
|
6260
6948
|
const client = this.clients.get(serverName);
|
|
6261
6949
|
if (client) {
|
|
6950
|
+
const transport = this.transports.get(serverName);
|
|
6951
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
6262
6952
|
await client.close();
|
|
6263
6953
|
this.clients.delete(serverName);
|
|
6954
|
+
this.transports.delete(serverName);
|
|
6264
6955
|
for (const key of this.tools.keys()) {
|
|
6265
6956
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
6266
6957
|
}
|
|
@@ -6288,6 +6979,13 @@ var McpClient = class {
|
|
|
6288
6979
|
getConnectedServers() {
|
|
6289
6980
|
return Array.from(this.clients.keys());
|
|
6290
6981
|
}
|
|
6982
|
+
getActivePids() {
|
|
6983
|
+
const pids = [];
|
|
6984
|
+
for (const transport of this.transports.values()) {
|
|
6985
|
+
if (transport.pid) pids.push(transport.pid);
|
|
6986
|
+
}
|
|
6987
|
+
return pids;
|
|
6988
|
+
}
|
|
6291
6989
|
isConnected(serverName) {
|
|
6292
6990
|
return this.clients.has(serverName);
|
|
6293
6991
|
}
|
|
@@ -6856,12 +7554,25 @@ var Cascade = class extends EventEmitter__default.default {
|
|
|
6856
7554
|
looksLikeSimpleArtifactTask(prompt) {
|
|
6857
7555
|
return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
|
|
6858
7556
|
}
|
|
6859
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
7557
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
6860
7558
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
6861
7559
|
return "Simple";
|
|
6862
7560
|
}
|
|
7561
|
+
let workspaceContext = "";
|
|
7562
|
+
try {
|
|
7563
|
+
const files = await glob.glob("**/*.*", {
|
|
7564
|
+
cwd: workspacePath,
|
|
7565
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
7566
|
+
nodir: true
|
|
7567
|
+
});
|
|
7568
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
7569
|
+
} catch {
|
|
7570
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
7571
|
+
}
|
|
6863
7572
|
const sysPrompt = `You are a routing classifier for a hierarchical AI system. Determine task complexity using BOTH the latest user message and the recent conversation context.
|
|
6864
7573
|
|
|
7574
|
+
${workspaceContext}
|
|
7575
|
+
|
|
6865
7576
|
Classification:
|
|
6866
7577
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
6867
7578
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -6936,7 +7647,7 @@ ${prompt}` : prompt;
|
|
|
6936
7647
|
}
|
|
6937
7648
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
6938
7649
|
});
|
|
6939
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
7650
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
6940
7651
|
this.telemetry.capture("cascade:session_start", {
|
|
6941
7652
|
complexity,
|
|
6942
7653
|
providerCount: this.config.providers.length,
|
|
@@ -7016,7 +7727,7 @@ ${prompt}` : prompt;
|
|
|
7016
7727
|
peerT3Ids: [],
|
|
7017
7728
|
parentT2: "root"
|
|
7018
7729
|
};
|
|
7019
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
7730
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
7020
7731
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
7021
7732
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
7022
7733
|
} else if (complexity === "Moderate") {
|
|
@@ -7039,7 +7750,7 @@ ${prompt}` : prompt;
|
|
|
7039
7750
|
constraints: [],
|
|
7040
7751
|
t3Subtasks: []
|
|
7041
7752
|
};
|
|
7042
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
7753
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
7043
7754
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
7044
7755
|
t2Results = [t2Result];
|
|
7045
7756
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -7061,13 +7772,22 @@ ${prompt}` : prompt;
|
|
|
7061
7772
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
7062
7773
|
bindTierEvents(t1);
|
|
7063
7774
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
7064
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
7775
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
7065
7776
|
finalOutput = result.output;
|
|
7066
7777
|
t2Results = result.t2Results;
|
|
7067
7778
|
}
|
|
7068
7779
|
} catch (err) {
|
|
7069
|
-
|
|
7070
|
-
|
|
7780
|
+
if (err instanceof CascadeCancelledError) {
|
|
7781
|
+
this.emit("run:cancelled", {
|
|
7782
|
+
taskId,
|
|
7783
|
+
reason: err.message,
|
|
7784
|
+
partialOutput: finalOutput || ""
|
|
7785
|
+
});
|
|
7786
|
+
runError = null;
|
|
7787
|
+
} else {
|
|
7788
|
+
runError = err;
|
|
7789
|
+
throw err;
|
|
7790
|
+
}
|
|
7071
7791
|
} finally {
|
|
7072
7792
|
try {
|
|
7073
7793
|
escalator.cancelAllPending();
|
|
@@ -7802,6 +8522,9 @@ var ModelsDisplay = ({
|
|
|
7802
8522
|
});
|
|
7803
8523
|
const title = step === "PROVIDER" ? "\u25C8 SELECT PROVIDER" : step === "TIER" ? `\u25C8 APPLY ${picked.provider === "auto" ? "AUTO" : String(picked.provider).toUpperCase()} TO WHICH TIER?` : `\u25C8 ${String(picked.provider).toUpperCase()} \u2192 SELECT MODEL FOR ${picked.tier}`;
|
|
7804
8524
|
const breadcrumb = step === "PROVIDER" ? "Step 1 / 3" : step === "TIER" ? `Step 2 / 3 \xB7 provider: ${picked.provider}` : `Step 3 / 3 \xB7 ${picked.provider} \u2192 ${picked.tier}`;
|
|
8525
|
+
const PAGE_SIZE = 8;
|
|
8526
|
+
const viewStart = Math.max(0, Math.min(cursor - Math.floor(PAGE_SIZE / 2), currentItems.length - PAGE_SIZE));
|
|
8527
|
+
const visibleItems = currentItems.slice(viewStart, viewStart + PAGE_SIZE);
|
|
7805
8528
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
7806
8529
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { justifyContent: "space-between", children: [
|
|
7807
8530
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "cyan", children: title }),
|
|
@@ -7811,16 +8534,29 @@ var ModelsDisplay = ({
|
|
|
7811
8534
|
breadcrumb,
|
|
7812
8535
|
" \xB7 \u2191/\u2193 navigate \xB7 1\u20139 jump"
|
|
7813
8536
|
] }) }),
|
|
7814
|
-
currentItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxRuntime.
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
8537
|
+
currentItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
8538
|
+
viewStart > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", dimColor: true, children: [
|
|
8539
|
+
" \u2191 ",
|
|
8540
|
+
viewStart,
|
|
8541
|
+
" more above"
|
|
8542
|
+
] }),
|
|
8543
|
+
visibleItems.map((item, i) => {
|
|
8544
|
+
const globalIdx = viewStart + i;
|
|
8545
|
+
const focused = globalIdx === cursor;
|
|
8546
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
|
|
8547
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? "green" : "gray", children: focused ? "\u276F " : " " }),
|
|
8548
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
8549
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? "white" : "gray", bold: focused, children: item.label }),
|
|
8550
|
+
item.sublabel && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", dimColor: true, children: ` ${item.sublabel}` })
|
|
8551
|
+
] })
|
|
8552
|
+
] }, `${step}-${item.value}-${globalIdx}`);
|
|
8553
|
+
}),
|
|
8554
|
+
viewStart + PAGE_SIZE < currentItems.length && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", dimColor: true, children: [
|
|
8555
|
+
" \u2193 ",
|
|
8556
|
+
currentItems.length - viewStart - PAGE_SIZE,
|
|
8557
|
+
" more below"
|
|
8558
|
+
] })
|
|
8559
|
+
] })
|
|
7824
8560
|
] });
|
|
7825
8561
|
};
|
|
7826
8562
|
function CostTracker({
|
|
@@ -8014,13 +8750,18 @@ function replReducer(state, action) {
|
|
|
8014
8750
|
async function refreshModelCache(store, providers) {
|
|
8015
8751
|
for (const provider of providers) {
|
|
8016
8752
|
try {
|
|
8017
|
-
const
|
|
8753
|
+
const dummyId = provider.type === "azure" ? provider.deploymentName || "azure-model" : "dummy";
|
|
8754
|
+
const dummyModel = { id: dummyId, name: dummyId, provider: provider.type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
8018
8755
|
let instance;
|
|
8019
8756
|
if (provider.type === "openai") instance = new OpenAIProvider(provider, dummyModel);
|
|
8020
8757
|
else if (provider.type === "gemini") instance = new GeminiProvider(provider, dummyModel);
|
|
8021
8758
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
8022
8759
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
8023
8760
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
8761
|
+
else if (provider.type === "azure") {
|
|
8762
|
+
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
8763
|
+
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
8764
|
+
}
|
|
8024
8765
|
if (instance) {
|
|
8025
8766
|
const fetched = await instance.listModels();
|
|
8026
8767
|
for (const m of fetched) store.upsertCachedModel(m);
|
|
@@ -8158,7 +8899,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
|
|
|
8158
8899
|
};
|
|
8159
8900
|
const store = new MemoryStore(path17__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
8160
8901
|
storeRef.current = store;
|
|
8161
|
-
globalStoreRef.current = new MemoryStore(path17__default.default.join(
|
|
8902
|
+
globalStoreRef.current = new MemoryStore(path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
8162
8903
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
8163
8904
|
setIdentities(identityRows);
|
|
8164
8905
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -8643,14 +9384,29 @@ Use /identity <name|id> to switch.`;
|
|
|
8643
9384
|
const isAutoScrollingRef = React5.useRef(true);
|
|
8644
9385
|
const width = stdout?.columns ?? 100;
|
|
8645
9386
|
const height = stdout?.rows ?? 24;
|
|
8646
|
-
const
|
|
9387
|
+
const hasActiveOrFailed2 = (node) => {
|
|
9388
|
+
if (node.status === "ACTIVE" || node.status === "FAILED") return true;
|
|
9389
|
+
return node.children?.some(hasActiveOrFailed2) ?? false;
|
|
9390
|
+
};
|
|
9391
|
+
let agentTreeHeight = 0;
|
|
9392
|
+
if (state.agentTree && hasActiveOrFailed2(state.agentTree)) {
|
|
9393
|
+
agentTreeHeight = 1;
|
|
9394
|
+
const childrenCount = state.agentTree.children?.length ?? 0;
|
|
9395
|
+
agentTreeHeight += Math.min(childrenCount, 6);
|
|
9396
|
+
if (childrenCount > 6) agentTreeHeight += 1;
|
|
9397
|
+
}
|
|
9398
|
+
let timelineHeight = 0;
|
|
9399
|
+
if (state.showDetails && treeNodesRef.current.size > 0) {
|
|
9400
|
+
timelineHeight = 1;
|
|
9401
|
+
timelineHeight += Math.min(3, treeNodesRef.current.size);
|
|
9402
|
+
}
|
|
9403
|
+
const statusHeight = agentTreeHeight + timelineHeight;
|
|
8647
9404
|
const costHeight = state.showCost ? 6 : 0;
|
|
8648
9405
|
const approvalHeight = state.approvalRequest ? 12 : 0;
|
|
8649
9406
|
const slashVisibleCount = Math.min(SLASH_PAGE_SIZE, slashEntries.length);
|
|
8650
9407
|
const slashHeight = slashVisibleCount > 0 ? slashVisibleCount + 2 : 0;
|
|
8651
9408
|
const chromeHeight = statusHeight + costHeight + approvalHeight + slashHeight + 7;
|
|
8652
|
-
const
|
|
8653
|
-
const availableHeight = Math.max(4, totalCap - chromeHeight);
|
|
9409
|
+
const availableHeight = Math.max(4, height - chromeHeight);
|
|
8654
9410
|
const allLines = formatToLines(
|
|
8655
9411
|
state.isStreaming ? [...state.messages, { id: "stream", role: "assistant", content: state.streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }] : state.messages,
|
|
8656
9412
|
width - 4,
|
|
@@ -9031,10 +9787,14 @@ function wizardReducer(state, action) {
|
|
|
9031
9787
|
return { ...state, currentEntryIdx: next };
|
|
9032
9788
|
}
|
|
9033
9789
|
case "ADD_AZURE": {
|
|
9790
|
+
const prevAzure = state.entries.find((e) => e.type === "azure");
|
|
9034
9791
|
const newEntry = {
|
|
9035
9792
|
id: crypto.randomUUID(),
|
|
9036
9793
|
type: "azure",
|
|
9037
|
-
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}
|
|
9794
|
+
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}`,
|
|
9795
|
+
baseUrl: prevAzure?.baseUrl,
|
|
9796
|
+
apiKey: prevAzure?.apiKey,
|
|
9797
|
+
apiVersion: prevAzure?.apiVersion
|
|
9038
9798
|
};
|
|
9039
9799
|
return {
|
|
9040
9800
|
...state,
|
|
@@ -9143,8 +9903,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9143
9903
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
9144
9904
|
} else if (type === "azure") {
|
|
9145
9905
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
9146
|
-
const
|
|
9147
|
-
const
|
|
9906
|
+
const actualModelId = deploymentName || `azure-${entry.id}`;
|
|
9907
|
+
const dummyModel = { id: actualModelId, name: actualModelId, provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
9908
|
+
const p = new AzureOpenAIProvider2({ type, apiKey, baseUrl, deploymentName, apiVersion: entry.apiVersion }, dummyModel);
|
|
9148
9909
|
const fetched = await p.listModels();
|
|
9149
9910
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
9150
9911
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
@@ -9165,7 +9926,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9165
9926
|
type: e.type,
|
|
9166
9927
|
...e.apiKey ? { apiKey: e.apiKey } : {},
|
|
9167
9928
|
...e.baseUrl ? { baseUrl: e.baseUrl } : {},
|
|
9168
|
-
...e.deploymentName ? { deploymentName: e.deploymentName } : {}
|
|
9929
|
+
...e.deploymentName ? { deploymentName: e.deploymentName } : {},
|
|
9930
|
+
...e.apiVersion ? { apiVersion: e.apiVersion } : {}
|
|
9169
9931
|
}));
|
|
9170
9932
|
const models = {};
|
|
9171
9933
|
if (state.tierT1 !== "auto") models["t1"] = state.tierT1;
|
|
@@ -9195,17 +9957,17 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9195
9957
|
if (key.return) {
|
|
9196
9958
|
if (state.selectedTypes.size === 0) return;
|
|
9197
9959
|
dispatch({ type: "CONFIRM_PROVIDERS" });
|
|
9198
|
-
|
|
9960
|
+
const firstType = [...state.selectedTypes][0];
|
|
9961
|
+
setFieldStage(firstType === "azure" ? "deploymentName" : firstType === "openai-compatible" ? "label" : firstType === "ollama" ? "baseUrl" : "apiKey");
|
|
9199
9962
|
setFieldBuffer("");
|
|
9200
9963
|
}
|
|
9201
9964
|
}
|
|
9202
9965
|
if (state.step === "TIER_ASSIGN") {
|
|
9203
|
-
if (key.tab || key.
|
|
9966
|
+
if (key.tab || key.rightArrow) {
|
|
9204
9967
|
const order = ["T1", "T2", "T3"];
|
|
9205
9968
|
const idx = order.indexOf(state.tierSelectFocus);
|
|
9206
9969
|
dispatch({ type: "SET_TIER_FOCUS", tier: order[(idx + 1) % 3] });
|
|
9207
9970
|
}
|
|
9208
|
-
if (key.return) dispatch({ type: "GO_SAVE" });
|
|
9209
9971
|
}
|
|
9210
9972
|
});
|
|
9211
9973
|
const currentEntry = state.entries[state.currentEntryIdx];
|
|
@@ -9215,7 +9977,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9215
9977
|
if (fieldStage === "deploymentName") {
|
|
9216
9978
|
dispatch({ type: "SET_ENTRY_FIELD", field: "deploymentName", value: val });
|
|
9217
9979
|
setFieldBuffer("");
|
|
9218
|
-
|
|
9980
|
+
if (currentEntry.baseUrl && currentEntry.apiKey && currentEntry.apiVersion) {
|
|
9981
|
+
setFieldStage("askMore");
|
|
9982
|
+
} else {
|
|
9983
|
+
setFieldStage("baseUrl");
|
|
9984
|
+
}
|
|
9219
9985
|
} else if (fieldStage === "baseUrl") {
|
|
9220
9986
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
|
|
9221
9987
|
setFieldBuffer("");
|
|
@@ -9223,6 +9989,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9223
9989
|
} else if (fieldStage === "apiKey") {
|
|
9224
9990
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9225
9991
|
setFieldBuffer("");
|
|
9992
|
+
setFieldStage("apiVersion");
|
|
9993
|
+
} else if (fieldStage === "apiVersion") {
|
|
9994
|
+
dispatch({ type: "SET_ENTRY_FIELD", field: "apiVersion", value: val || "2024-08-01-preview" });
|
|
9995
|
+
setFieldBuffer("");
|
|
9226
9996
|
setFieldStage("askMore");
|
|
9227
9997
|
}
|
|
9228
9998
|
} else if (currentEntry.type === "openai-compatible") {
|
|
@@ -9242,13 +10012,19 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9242
10012
|
} else if (currentEntry.type === "ollama") {
|
|
9243
10013
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
|
|
9244
10014
|
setFieldBuffer("");
|
|
10015
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10016
|
+
if (nextEntry) {
|
|
10017
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10018
|
+
}
|
|
9245
10019
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9246
|
-
setFieldStage("apiKey");
|
|
9247
10020
|
} else {
|
|
9248
10021
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9249
10022
|
setFieldBuffer("");
|
|
10023
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10024
|
+
if (nextEntry) {
|
|
10025
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10026
|
+
}
|
|
9250
10027
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9251
|
-
setFieldStage("apiKey");
|
|
9252
10028
|
}
|
|
9253
10029
|
}, [currentEntry, fieldStage]);
|
|
9254
10030
|
if (state.step === "PROVIDER_SELECT") {
|
|
@@ -9299,8 +10075,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9299
10075
|
setFieldStage(isAzure ? "deploymentName" : "label");
|
|
9300
10076
|
setFieldBuffer("");
|
|
9301
10077
|
} else {
|
|
10078
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10079
|
+
if (nextEntry) {
|
|
10080
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10081
|
+
}
|
|
9302
10082
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9303
|
-
setFieldStage("apiKey");
|
|
9304
10083
|
setFieldBuffer("");
|
|
9305
10084
|
}
|
|
9306
10085
|
}
|
|
@@ -9381,7 +10160,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9381
10160
|
SelectInput__default.default,
|
|
9382
10161
|
{
|
|
9383
10162
|
items: modelOptions,
|
|
9384
|
-
onSelect: (item) =>
|
|
10163
|
+
onSelect: (item) => {
|
|
10164
|
+
dispatch({ type: "SET_TIER", tier, value: item.value });
|
|
10165
|
+
const order = ["T1", "T2", "T3"];
|
|
10166
|
+
const idx = order.indexOf(tier);
|
|
10167
|
+
if (idx < 2) {
|
|
10168
|
+
dispatch({ type: "SET_TIER_FOCUS", tier: order[idx + 1] });
|
|
10169
|
+
} else {
|
|
10170
|
+
dispatch({ type: "GO_SAVE" });
|
|
10171
|
+
}
|
|
10172
|
+
},
|
|
9385
10173
|
indicatorComponent: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", children: isSelected ? "\u276F " : " " }),
|
|
9386
10174
|
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSelected ? "magenta" : "white", children: label })
|
|
9387
10175
|
}
|
|
@@ -9859,7 +10647,7 @@ var DashboardServer = class {
|
|
|
9859
10647
|
// ── Setup ─────────────────────────────────────
|
|
9860
10648
|
getGlobalStore() {
|
|
9861
10649
|
if (!this.globalStore) {
|
|
9862
|
-
const globalDbPath = path17__default.default.join(
|
|
10650
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9863
10651
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
9864
10652
|
}
|
|
9865
10653
|
return this.globalStore;
|
|
@@ -9921,7 +10709,7 @@ var DashboardServer = class {
|
|
|
9921
10709
|
}
|
|
9922
10710
|
watchRuntimeChanges() {
|
|
9923
10711
|
const workspaceDbPath = path17__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
9924
|
-
const globalDbPath = path17__default.default.join(
|
|
10712
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9925
10713
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
9926
10714
|
for (const watchPath of watchPaths) {
|
|
9927
10715
|
if (!fs14__default.default.existsSync(watchPath)) continue;
|
|
@@ -10029,7 +10817,7 @@ var DashboardServer = class {
|
|
|
10029
10817
|
const sessionId = req.params.id;
|
|
10030
10818
|
this.store.deleteSession(sessionId);
|
|
10031
10819
|
this.store.deleteRuntimeSession(sessionId);
|
|
10032
|
-
const globalDbPath = path17__default.default.join(
|
|
10820
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10033
10821
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10034
10822
|
try {
|
|
10035
10823
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -10043,7 +10831,7 @@ var DashboardServer = class {
|
|
|
10043
10831
|
});
|
|
10044
10832
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
10045
10833
|
const body = req.body;
|
|
10046
|
-
const globalDbPath = path17__default.default.join(
|
|
10834
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10047
10835
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
10048
10836
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10049
10837
|
try {
|
|
@@ -10066,7 +10854,7 @@ var DashboardServer = class {
|
|
|
10066
10854
|
});
|
|
10067
10855
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
10068
10856
|
this.store.deleteAllRuntimeNodes();
|
|
10069
|
-
const globalDbPath = path17__default.default.join(
|
|
10857
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10070
10858
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10071
10859
|
try {
|
|
10072
10860
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -10162,7 +10950,7 @@ var DashboardServer = class {
|
|
|
10162
10950
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
10163
10951
|
const scope = req.query["scope"] ?? "workspace";
|
|
10164
10952
|
if (scope === "global") {
|
|
10165
|
-
const globalDbPath = path17__default.default.join(
|
|
10953
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10166
10954
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10167
10955
|
try {
|
|
10168
10956
|
res.json({
|
|
@@ -10412,7 +11200,15 @@ async function exportCommand(options = {}) {
|
|
|
10412
11200
|
const spin = ora__default.default({ text: "Loading sessions\u2026", color: "magenta" }).start();
|
|
10413
11201
|
let store;
|
|
10414
11202
|
try {
|
|
10415
|
-
const
|
|
11203
|
+
const workspacePath = options.workspacePath ?? process.cwd();
|
|
11204
|
+
const workspaceDbPath = path17__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
11205
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
11206
|
+
let dbPath = globalDbPath;
|
|
11207
|
+
try {
|
|
11208
|
+
await fs7__default.default.access(workspaceDbPath);
|
|
11209
|
+
dbPath = workspaceDbPath;
|
|
11210
|
+
} catch {
|
|
11211
|
+
}
|
|
10416
11212
|
store = new MemoryStore(dbPath);
|
|
10417
11213
|
} catch (err) {
|
|
10418
11214
|
spin.fail(chalk8__default.default.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -10551,6 +11347,15 @@ async function telemetryCommand(action) {
|
|
|
10551
11347
|
|
|
10552
11348
|
// src/cli/index.ts
|
|
10553
11349
|
dotenv__default.default.config();
|
|
11350
|
+
process.on("exit", () => McpClient.killAllProcesses());
|
|
11351
|
+
process.on("SIGINT", () => {
|
|
11352
|
+
McpClient.killAllProcesses();
|
|
11353
|
+
process.exit(0);
|
|
11354
|
+
});
|
|
11355
|
+
process.on("SIGTERM", () => {
|
|
11356
|
+
McpClient.killAllProcesses();
|
|
11357
|
+
process.exit(0);
|
|
11358
|
+
});
|
|
10554
11359
|
var program = new commander.Command();
|
|
10555
11360
|
program.name("cascade").description("Multi-tier AI orchestration CLI").version(CASCADE_VERSION, "-v, --version").option("-p, --prompt <text>", "Run a single prompt non-interactively").option("-t, --theme <name>", "Color theme", DEFAULT_THEME).option("-w, --workspace <path>", "Workspace path", process.cwd()).option("-i, --identity <name>", "Identity name or ID").option("--no-color", "Disable colors").action(async (options) => {
|
|
10556
11361
|
await startRepl(options);
|