cascade-ai 0.2.2 → 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 +1146 -353
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1146 -353
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1048 -325
- 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 +1047 -326
- 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);
|
|
@@ -3872,6 +4045,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
3872
4045
|
tools = [...tools];
|
|
3873
4046
|
while (iterations < MAX_ITERATIONS) {
|
|
3874
4047
|
iterations++;
|
|
4048
|
+
this.throwIfCancelled();
|
|
3875
4049
|
const options = {
|
|
3876
4050
|
messages: this.context.getMessages(),
|
|
3877
4051
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -3892,21 +4066,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3892
4066
|
if (requiresArtifact) {
|
|
3893
4067
|
stalledArtifactIterations += 1;
|
|
3894
4068
|
if (stalledArtifactIterations >= 2) {
|
|
3895
|
-
if (
|
|
3896
|
-
|
|
3897
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
3898
|
-
this.assignment?.description ?? ""
|
|
3899
|
-
);
|
|
3900
|
-
if (toolName) {
|
|
3901
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
3902
|
-
this.sendStatusUpdate({
|
|
3903
|
-
progressPct: 50,
|
|
3904
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
3905
|
-
status: "IN_PROGRESS"
|
|
3906
|
-
});
|
|
3907
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
3908
|
-
continue;
|
|
3909
|
-
}
|
|
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"}`);
|
|
3910
4071
|
}
|
|
3911
4072
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
3912
4073
|
}
|
|
@@ -4066,6 +4227,9 @@ ${assignment.expectedOutput}`;
|
|
|
4066
4227
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
4067
4228
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
4068
4229
|
const issues = [];
|
|
4230
|
+
const { exec: exec4 } = await import('child_process');
|
|
4231
|
+
const { promisify: promisify3 } = await import('util');
|
|
4232
|
+
const execAsync3 = promisify3(exec4);
|
|
4069
4233
|
for (const artifactPath of artifactPaths) {
|
|
4070
4234
|
const absolutePath = path17__default.default.resolve(process.cwd(), artifactPath);
|
|
4071
4235
|
try {
|
|
@@ -4082,9 +4246,27 @@ ${assignment.expectedOutput}`;
|
|
|
4082
4246
|
const content = await fs7__default.default.readFile(absolutePath, "utf-8");
|
|
4083
4247
|
if (!content.trim()) {
|
|
4084
4248
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
4249
|
+
continue;
|
|
4085
4250
|
}
|
|
4086
4251
|
} else if (stat.size < 100) {
|
|
4087
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}`);
|
|
4088
4270
|
}
|
|
4089
4271
|
} catch {
|
|
4090
4272
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -4481,7 +4663,8 @@ var T2Manager = class extends BaseTier {
|
|
|
4481
4663
|
});
|
|
4482
4664
|
this.emit("peer-sync-received", { fromId, content });
|
|
4483
4665
|
}
|
|
4484
|
-
async execute(assignment, taskId) {
|
|
4666
|
+
async execute(assignment, taskId, signal) {
|
|
4667
|
+
this.signal = signal;
|
|
4485
4668
|
this.assignment = assignment;
|
|
4486
4669
|
this.taskId = taskId;
|
|
4487
4670
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -4493,12 +4676,14 @@ var T2Manager = class extends BaseTier {
|
|
|
4493
4676
|
});
|
|
4494
4677
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
4495
4678
|
try {
|
|
4679
|
+
this.throwIfCancelled();
|
|
4496
4680
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
4497
4681
|
this.sendStatusUpdate({
|
|
4498
4682
|
progressPct: 20,
|
|
4499
4683
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
4500
4684
|
status: "IN_PROGRESS"
|
|
4501
4685
|
});
|
|
4686
|
+
this.throwIfCancelled();
|
|
4502
4687
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
4503
4688
|
this.sendStatusUpdate({
|
|
4504
4689
|
progressPct: 90,
|
|
@@ -4665,11 +4850,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4665
4850
|
).join(", ")}`,
|
|
4666
4851
|
status: "IN_PROGRESS"
|
|
4667
4852
|
});
|
|
4853
|
+
this.throwIfCancelled();
|
|
4668
4854
|
const waveResults = await Promise.allSettled(
|
|
4669
4855
|
runnableIds.map(async (id) => {
|
|
4670
4856
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4671
4857
|
const worker = workerMap.get(id);
|
|
4672
|
-
const result = await worker.execute(assignment, taskId);
|
|
4858
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
4673
4859
|
resultMap.set(id, result);
|
|
4674
4860
|
return result;
|
|
4675
4861
|
})
|
|
@@ -4683,6 +4869,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4683
4869
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4684
4870
|
const retried = await this.retryT3(assignment, taskId);
|
|
4685
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
|
+
}
|
|
4686
4926
|
}
|
|
4687
4927
|
for (const dependent of adj.get(id) ?? []) {
|
|
4688
4928
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -4746,7 +4986,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4746
4986
|
}));
|
|
4747
4987
|
return worker.execute(
|
|
4748
4988
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
4749
|
-
taskId
|
|
4989
|
+
taskId,
|
|
4990
|
+
this.signal
|
|
4750
4991
|
);
|
|
4751
4992
|
}
|
|
4752
4993
|
publishSectionOutput(result) {
|
|
@@ -4760,29 +5001,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4760
5001
|
async aggregateResults(assignment, results) {
|
|
4761
5002
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
4762
5003
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
4763
|
-
const outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
|
|
4764
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");
|
|
4765
|
-
const
|
|
4766
|
-
|
|
4767
|
-
${outputs}
|
|
4768
|
-
${peerOutputs ? `
|
|
5005
|
+
const peerContext = peerOutputs ? `
|
|
4769
5006
|
|
|
4770
5007
|
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
4771
|
-
${peerOutputs}` : ""
|
|
4772
|
-
const
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
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}
|
|
5017
|
+
|
|
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 ? `
|
|
4777
5038
|
|
|
4778
5039
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
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
|
+
}
|
|
4785
5047
|
}
|
|
5048
|
+
return currentSummary;
|
|
4786
5049
|
}
|
|
4787
5050
|
determineStatus(results) {
|
|
4788
5051
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -4910,10 +5173,10 @@ Rules:
|
|
|
4910
5173
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
4911
5174
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
4912
5175
|
|
|
4913
|
-
|
|
4914
|
-
-
|
|
4915
|
-
-
|
|
4916
|
-
- 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.
|
|
4917
5180
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
4918
5181
|
|
|
4919
5182
|
QUALITY RULES:
|
|
@@ -4952,7 +5215,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
4952
5215
|
setToolCreator(creator) {
|
|
4953
5216
|
this.toolCreator = creator;
|
|
4954
5217
|
}
|
|
4955
|
-
async execute(userPrompt, images, systemContext) {
|
|
5218
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
5219
|
+
this.signal = signal;
|
|
4956
5220
|
this.taskId = crypto.randomUUID();
|
|
4957
5221
|
this.setLabel("Administrator");
|
|
4958
5222
|
this.setStatus("ACTIVE");
|
|
@@ -4963,10 +5227,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
4963
5227
|
status: "IN_PROGRESS"
|
|
4964
5228
|
});
|
|
4965
5229
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
5230
|
+
this.throwIfCancelled();
|
|
4966
5231
|
let enrichedPrompt = userPrompt;
|
|
4967
5232
|
if (images?.length) {
|
|
4968
5233
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
4969
5234
|
}
|
|
5235
|
+
this.throwIfCancelled();
|
|
4970
5236
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
4971
5237
|
this.sendStatusUpdate({
|
|
4972
5238
|
progressPct: 10,
|
|
@@ -4974,21 +5240,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
4974
5240
|
status: "IN_PROGRESS"
|
|
4975
5241
|
});
|
|
4976
5242
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
4977
|
-
|
|
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
|
+
}
|
|
4978
5269
|
this.sendStatusUpdate({
|
|
4979
5270
|
progressPct: 95,
|
|
4980
5271
|
currentAction: "Compiling final output",
|
|
4981
5272
|
status: "IN_PROGRESS"
|
|
4982
5273
|
});
|
|
4983
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
5274
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
4984
5275
|
this.setStatus("COMPLETED");
|
|
4985
5276
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
4986
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
5277
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
4987
5278
|
}
|
|
4988
5279
|
getEscalations() {
|
|
4989
5280
|
return [...this.escalations];
|
|
4990
5281
|
}
|
|
4991
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
|
+
}
|
|
4992
5320
|
async analyzeImages(prompt, images) {
|
|
4993
5321
|
const visionModel = this.router.getModelForTier("T1");
|
|
4994
5322
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -5017,29 +5345,35 @@ ${systemContext}` : "";
|
|
|
5017
5345
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
5018
5346
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
5019
5347
|
|
|
5020
|
-
Return JSON where
|
|
5348
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
5021
5349
|
{
|
|
5022
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
|
|
5023
5357
|
"t3Subtasks": [{
|
|
5024
5358
|
"subtaskId": "t1",
|
|
5025
|
-
"subtaskTitle": "
|
|
5026
|
-
"
|
|
5027
|
-
"
|
|
5028
|
-
|
|
5029
|
-
"
|
|
5030
|
-
"subtaskTitle": "Save Code to File",
|
|
5031
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
5032
|
-
"executionMode": "parallel"
|
|
5033
|
-
}, {
|
|
5034
|
-
"subtaskId": "t3",
|
|
5035
|
-
"subtaskTitle": "Execute and Verify",
|
|
5036
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
5037
|
-
"executionMode": "parallel"
|
|
5359
|
+
"subtaskTitle": "Init NPM",
|
|
5360
|
+
"description": "Run npm init",
|
|
5361
|
+
"expectedOutput": "package.json created",
|
|
5362
|
+
"constraints": [],
|
|
5363
|
+
"dependsOn": []
|
|
5038
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": [...]
|
|
5039
5373
|
}]
|
|
5040
5374
|
}
|
|
5041
|
-
Use dependsOn when a
|
|
5042
|
-
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.`;
|
|
5043
5377
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
5044
5378
|
const result = await this.router.generate("T1", {
|
|
5045
5379
|
messages,
|
|
@@ -5167,92 +5501,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
5167
5501
|
].filter(Boolean).join(" ");
|
|
5168
5502
|
m.setHierarchyContext(context);
|
|
5169
5503
|
});
|
|
5170
|
-
if (overlapSections.size > 0
|
|
5171
|
-
this.log("Overlap detected \u2014
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
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]];
|
|
5175
5511
|
}
|
|
5176
5512
|
}
|
|
5177
5513
|
}
|
|
5178
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
5179
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
5180
5514
|
const t2Results = [];
|
|
5181
5515
|
try {
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
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);
|
|
5213
5563
|
}
|
|
5564
|
+
s.dependsOn = safeDeps;
|
|
5214
5565
|
}
|
|
5215
|
-
}
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
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"
|
|
5248
5601
|
});
|
|
5249
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
|
+
};
|
|
5250
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();
|
|
5251
5621
|
}
|
|
5252
|
-
}
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
return t2Results;
|
|
5622
|
+
};
|
|
5623
|
+
await executeWave();
|
|
5624
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
5256
5625
|
}
|
|
5257
5626
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
5258
5627
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -5702,13 +6071,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
5702
6071
|
}
|
|
5703
6072
|
async execute(input, _options) {
|
|
5704
6073
|
const platform = input["platform"] ?? "github";
|
|
5705
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
5706
6074
|
const operation = input["operation"];
|
|
5707
6075
|
const repo = input["repo"];
|
|
5708
|
-
|
|
5709
|
-
|
|
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)}`;
|
|
5710
6114
|
}
|
|
5711
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
5712
6115
|
}
|
|
5713
6116
|
async executeGitHub(operation, repo, token, input) {
|
|
5714
6117
|
const headers = {
|
|
@@ -5795,6 +6198,7 @@ ${response.data.description}`;
|
|
|
5795
6198
|
};
|
|
5796
6199
|
|
|
5797
6200
|
// src/tools/browser.ts
|
|
6201
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
5798
6202
|
var BrowserTool = class extends BaseTool {
|
|
5799
6203
|
name = "browser";
|
|
5800
6204
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -5803,7 +6207,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
5803
6207
|
properties: {
|
|
5804
6208
|
action: {
|
|
5805
6209
|
type: "string",
|
|
5806
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
6210
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
5807
6211
|
},
|
|
5808
6212
|
url: { type: "string", description: "URL to navigate to" },
|
|
5809
6213
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -5823,53 +6227,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
5823
6227
|
try {
|
|
5824
6228
|
playwright = await import('playwright');
|
|
5825
6229
|
} catch {
|
|
5826
|
-
|
|
6230
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
5827
6231
|
}
|
|
5828
|
-
if (!this.browser) {
|
|
5829
|
-
const pw = playwright;
|
|
5830
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
5831
|
-
const b = this.browser;
|
|
5832
|
-
this.page = await b.newPage();
|
|
5833
|
-
}
|
|
5834
|
-
const page = this.page;
|
|
5835
6232
|
const action = input["action"];
|
|
5836
6233
|
const timeout = input["timeout"] ?? 1e4;
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
case "evaluate": {
|
|
5855
|
-
const result = await page.evaluate(input["script"]);
|
|
5856
|
-
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)}`;
|
|
5857
6251
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
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`;
|
|
5861
6287
|
}
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
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}`;
|
|
5865
6293
|
}
|
|
5866
|
-
|
|
5867
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
6294
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
5868
6295
|
}
|
|
5869
6296
|
}
|
|
5870
6297
|
async close() {
|
|
5871
|
-
|
|
5872
|
-
|
|
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 {
|
|
5873
6310
|
this.browser = null;
|
|
5874
6311
|
this.page = null;
|
|
5875
6312
|
}
|
|
@@ -5966,6 +6403,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
5966
6403
|
});
|
|
5967
6404
|
}
|
|
5968
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"]);
|
|
5969
6419
|
var CodeInterpreterTool = class extends BaseTool {
|
|
5970
6420
|
name = "run_code";
|
|
5971
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.";
|
|
@@ -5981,10 +6431,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5981
6431
|
isDangerous() {
|
|
5982
6432
|
return true;
|
|
5983
6433
|
}
|
|
5984
|
-
async execute(input,
|
|
6434
|
+
async execute(input, _options) {
|
|
5985
6435
|
const language = input["language"];
|
|
5986
6436
|
const code = input["code"];
|
|
5987
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
|
+
}
|
|
5988
6458
|
const tmpDir = path17__default.default.join(process.cwd(), ".cascade", "tmp");
|
|
5989
6459
|
if (!fs14__default.default.existsSync(tmpDir)) {
|
|
5990
6460
|
fs14__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -5993,8 +6463,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5993
6463
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
5994
6464
|
const filePath = path17__default.default.join(tmpDir, fileName);
|
|
5995
6465
|
fs14__default.default.writeFileSync(filePath, code, "utf-8");
|
|
5996
|
-
const
|
|
5997
|
-
const
|
|
6466
|
+
const quotedPath = `"${filePath}"`;
|
|
6467
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
6468
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
5998
6469
|
return new Promise((resolve) => {
|
|
5999
6470
|
const startMs = Date.now();
|
|
6000
6471
|
child_process.exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -6007,10 +6478,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
6007
6478
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
6008
6479
|
}
|
|
6009
6480
|
if (error) {
|
|
6010
|
-
|
|
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):
|
|
6011
6488
|
Error: ${error.message}
|
|
6012
6489
|
Stderr: ${stderr}
|
|
6013
6490
|
Stdout: ${stdout}`);
|
|
6491
|
+
}
|
|
6014
6492
|
} else {
|
|
6015
6493
|
resolve(`Execution successful (${duration}ms):
|
|
6016
6494
|
Stdout: ${stdout}
|
|
@@ -6075,6 +6553,186 @@ ${formatted}`;
|
|
|
6075
6553
|
}
|
|
6076
6554
|
};
|
|
6077
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
|
+
|
|
6078
6736
|
// src/tools/mcp.ts
|
|
6079
6737
|
var McpToolWrapper = class extends BaseTool {
|
|
6080
6738
|
name;
|
|
@@ -6196,7 +6854,8 @@ var ToolRegistry = class {
|
|
|
6196
6854
|
new ImageAnalyzeTool(),
|
|
6197
6855
|
new PDFCreateTool(),
|
|
6198
6856
|
new CodeInterpreterTool(),
|
|
6199
|
-
new PeerCommunicationTool()
|
|
6857
|
+
new PeerCommunicationTool(),
|
|
6858
|
+
new WebSearchTool(this.config.webSearch)
|
|
6200
6859
|
];
|
|
6201
6860
|
for (const tool of tools) {
|
|
6202
6861
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -6220,8 +6879,23 @@ var ToolRegistry = class {
|
|
|
6220
6879
|
return this.ignoreMatcher.ignores(posixRel);
|
|
6221
6880
|
}
|
|
6222
6881
|
};
|
|
6223
|
-
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
|
+
}
|
|
6224
6897
|
clients = /* @__PURE__ */ new Map();
|
|
6898
|
+
transports = /* @__PURE__ */ new Map();
|
|
6225
6899
|
tools = /* @__PURE__ */ new Map();
|
|
6226
6900
|
trustedServers;
|
|
6227
6901
|
approvalCallback;
|
|
@@ -6250,6 +6924,8 @@ var McpClient = class {
|
|
|
6250
6924
|
);
|
|
6251
6925
|
await client.connect(transport);
|
|
6252
6926
|
this.clients.set(server.name, client);
|
|
6927
|
+
this.transports.set(server.name, transport);
|
|
6928
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
6253
6929
|
const toolsResult = await client.listTools();
|
|
6254
6930
|
for (const tool of toolsResult.tools) {
|
|
6255
6931
|
for (const existing of this.tools.values()) {
|
|
@@ -6271,8 +6947,11 @@ var McpClient = class {
|
|
|
6271
6947
|
async disconnect(serverName) {
|
|
6272
6948
|
const client = this.clients.get(serverName);
|
|
6273
6949
|
if (client) {
|
|
6950
|
+
const transport = this.transports.get(serverName);
|
|
6951
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
6274
6952
|
await client.close();
|
|
6275
6953
|
this.clients.delete(serverName);
|
|
6954
|
+
this.transports.delete(serverName);
|
|
6276
6955
|
for (const key of this.tools.keys()) {
|
|
6277
6956
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
6278
6957
|
}
|
|
@@ -6300,6 +6979,13 @@ var McpClient = class {
|
|
|
6300
6979
|
getConnectedServers() {
|
|
6301
6980
|
return Array.from(this.clients.keys());
|
|
6302
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
|
+
}
|
|
6303
6989
|
isConnected(serverName) {
|
|
6304
6990
|
return this.clients.has(serverName);
|
|
6305
6991
|
}
|
|
@@ -6868,12 +7554,25 @@ var Cascade = class extends EventEmitter__default.default {
|
|
|
6868
7554
|
looksLikeSimpleArtifactTask(prompt) {
|
|
6869
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);
|
|
6870
7556
|
}
|
|
6871
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
7557
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
6872
7558
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
6873
7559
|
return "Simple";
|
|
6874
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
|
+
}
|
|
6875
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.
|
|
6876
7573
|
|
|
7574
|
+
${workspaceContext}
|
|
7575
|
+
|
|
6877
7576
|
Classification:
|
|
6878
7577
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
6879
7578
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -6948,7 +7647,7 @@ ${prompt}` : prompt;
|
|
|
6948
7647
|
}
|
|
6949
7648
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
6950
7649
|
});
|
|
6951
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
7650
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
6952
7651
|
this.telemetry.capture("cascade:session_start", {
|
|
6953
7652
|
complexity,
|
|
6954
7653
|
providerCount: this.config.providers.length,
|
|
@@ -7028,7 +7727,7 @@ ${prompt}` : prompt;
|
|
|
7028
7727
|
peerT3Ids: [],
|
|
7029
7728
|
parentT2: "root"
|
|
7030
7729
|
};
|
|
7031
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
7730
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
7032
7731
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
7033
7732
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
7034
7733
|
} else if (complexity === "Moderate") {
|
|
@@ -7051,7 +7750,7 @@ ${prompt}` : prompt;
|
|
|
7051
7750
|
constraints: [],
|
|
7052
7751
|
t3Subtasks: []
|
|
7053
7752
|
};
|
|
7054
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
7753
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
7055
7754
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
7056
7755
|
t2Results = [t2Result];
|
|
7057
7756
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -7073,13 +7772,22 @@ ${prompt}` : prompt;
|
|
|
7073
7772
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
7074
7773
|
bindTierEvents(t1);
|
|
7075
7774
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
7076
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
7775
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
7077
7776
|
finalOutput = result.output;
|
|
7078
7777
|
t2Results = result.t2Results;
|
|
7079
7778
|
}
|
|
7080
7779
|
} catch (err) {
|
|
7081
|
-
|
|
7082
|
-
|
|
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
|
+
}
|
|
7083
7791
|
} finally {
|
|
7084
7792
|
try {
|
|
7085
7793
|
escalator.cancelAllPending();
|
|
@@ -7814,6 +8522,9 @@ var ModelsDisplay = ({
|
|
|
7814
8522
|
});
|
|
7815
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}`;
|
|
7816
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);
|
|
7817
8528
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
7818
8529
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { justifyContent: "space-between", children: [
|
|
7819
8530
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "cyan", children: title }),
|
|
@@ -7823,16 +8534,29 @@ var ModelsDisplay = ({
|
|
|
7823
8534
|
breadcrumb,
|
|
7824
8535
|
" \xB7 \u2191/\u2193 navigate \xB7 1\u20139 jump"
|
|
7825
8536
|
] }) }),
|
|
7826
|
-
currentItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxRuntime.
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
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
|
+
] })
|
|
7836
8560
|
] });
|
|
7837
8561
|
};
|
|
7838
8562
|
function CostTracker({
|
|
@@ -8026,13 +8750,18 @@ function replReducer(state, action) {
|
|
|
8026
8750
|
async function refreshModelCache(store, providers) {
|
|
8027
8751
|
for (const provider of providers) {
|
|
8028
8752
|
try {
|
|
8029
|
-
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 };
|
|
8030
8755
|
let instance;
|
|
8031
8756
|
if (provider.type === "openai") instance = new OpenAIProvider(provider, dummyModel);
|
|
8032
8757
|
else if (provider.type === "gemini") instance = new GeminiProvider(provider, dummyModel);
|
|
8033
8758
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
8034
8759
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
8035
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
|
+
}
|
|
8036
8765
|
if (instance) {
|
|
8037
8766
|
const fetched = await instance.listModels();
|
|
8038
8767
|
for (const m of fetched) store.upsertCachedModel(m);
|
|
@@ -8170,7 +8899,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
|
|
|
8170
8899
|
};
|
|
8171
8900
|
const store = new MemoryStore(path17__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
8172
8901
|
storeRef.current = store;
|
|
8173
|
-
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));
|
|
8174
8903
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
8175
8904
|
setIdentities(identityRows);
|
|
8176
8905
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -8655,14 +9384,29 @@ Use /identity <name|id> to switch.`;
|
|
|
8655
9384
|
const isAutoScrollingRef = React5.useRef(true);
|
|
8656
9385
|
const width = stdout?.columns ?? 100;
|
|
8657
9386
|
const height = stdout?.rows ?? 24;
|
|
8658
|
-
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;
|
|
8659
9404
|
const costHeight = state.showCost ? 6 : 0;
|
|
8660
9405
|
const approvalHeight = state.approvalRequest ? 12 : 0;
|
|
8661
9406
|
const slashVisibleCount = Math.min(SLASH_PAGE_SIZE, slashEntries.length);
|
|
8662
9407
|
const slashHeight = slashVisibleCount > 0 ? slashVisibleCount + 2 : 0;
|
|
8663
9408
|
const chromeHeight = statusHeight + costHeight + approvalHeight + slashHeight + 7;
|
|
8664
|
-
const
|
|
8665
|
-
const availableHeight = Math.max(4, totalCap - chromeHeight);
|
|
9409
|
+
const availableHeight = Math.max(4, height - chromeHeight);
|
|
8666
9410
|
const allLines = formatToLines(
|
|
8667
9411
|
state.isStreaming ? [...state.messages, { id: "stream", role: "assistant", content: state.streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }] : state.messages,
|
|
8668
9412
|
width - 4,
|
|
@@ -9043,10 +9787,14 @@ function wizardReducer(state, action) {
|
|
|
9043
9787
|
return { ...state, currentEntryIdx: next };
|
|
9044
9788
|
}
|
|
9045
9789
|
case "ADD_AZURE": {
|
|
9790
|
+
const prevAzure = state.entries.find((e) => e.type === "azure");
|
|
9046
9791
|
const newEntry = {
|
|
9047
9792
|
id: crypto.randomUUID(),
|
|
9048
9793
|
type: "azure",
|
|
9049
|
-
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
|
|
9050
9798
|
};
|
|
9051
9799
|
return {
|
|
9052
9800
|
...state,
|
|
@@ -9155,8 +9903,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9155
9903
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
9156
9904
|
} else if (type === "azure") {
|
|
9157
9905
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
9158
|
-
const
|
|
9159
|
-
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);
|
|
9160
9909
|
const fetched = await p.listModels();
|
|
9161
9910
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
9162
9911
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
@@ -9177,7 +9926,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9177
9926
|
type: e.type,
|
|
9178
9927
|
...e.apiKey ? { apiKey: e.apiKey } : {},
|
|
9179
9928
|
...e.baseUrl ? { baseUrl: e.baseUrl } : {},
|
|
9180
|
-
...e.deploymentName ? { deploymentName: e.deploymentName } : {}
|
|
9929
|
+
...e.deploymentName ? { deploymentName: e.deploymentName } : {},
|
|
9930
|
+
...e.apiVersion ? { apiVersion: e.apiVersion } : {}
|
|
9181
9931
|
}));
|
|
9182
9932
|
const models = {};
|
|
9183
9933
|
if (state.tierT1 !== "auto") models["t1"] = state.tierT1;
|
|
@@ -9207,17 +9957,17 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9207
9957
|
if (key.return) {
|
|
9208
9958
|
if (state.selectedTypes.size === 0) return;
|
|
9209
9959
|
dispatch({ type: "CONFIRM_PROVIDERS" });
|
|
9210
|
-
|
|
9960
|
+
const firstType = [...state.selectedTypes][0];
|
|
9961
|
+
setFieldStage(firstType === "azure" ? "deploymentName" : firstType === "openai-compatible" ? "label" : firstType === "ollama" ? "baseUrl" : "apiKey");
|
|
9211
9962
|
setFieldBuffer("");
|
|
9212
9963
|
}
|
|
9213
9964
|
}
|
|
9214
9965
|
if (state.step === "TIER_ASSIGN") {
|
|
9215
|
-
if (key.tab || key.
|
|
9966
|
+
if (key.tab || key.rightArrow) {
|
|
9216
9967
|
const order = ["T1", "T2", "T3"];
|
|
9217
9968
|
const idx = order.indexOf(state.tierSelectFocus);
|
|
9218
9969
|
dispatch({ type: "SET_TIER_FOCUS", tier: order[(idx + 1) % 3] });
|
|
9219
9970
|
}
|
|
9220
|
-
if (key.return) dispatch({ type: "GO_SAVE" });
|
|
9221
9971
|
}
|
|
9222
9972
|
});
|
|
9223
9973
|
const currentEntry = state.entries[state.currentEntryIdx];
|
|
@@ -9227,7 +9977,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9227
9977
|
if (fieldStage === "deploymentName") {
|
|
9228
9978
|
dispatch({ type: "SET_ENTRY_FIELD", field: "deploymentName", value: val });
|
|
9229
9979
|
setFieldBuffer("");
|
|
9230
|
-
|
|
9980
|
+
if (currentEntry.baseUrl && currentEntry.apiKey && currentEntry.apiVersion) {
|
|
9981
|
+
setFieldStage("askMore");
|
|
9982
|
+
} else {
|
|
9983
|
+
setFieldStage("baseUrl");
|
|
9984
|
+
}
|
|
9231
9985
|
} else if (fieldStage === "baseUrl") {
|
|
9232
9986
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
|
|
9233
9987
|
setFieldBuffer("");
|
|
@@ -9235,6 +9989,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9235
9989
|
} else if (fieldStage === "apiKey") {
|
|
9236
9990
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9237
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("");
|
|
9238
9996
|
setFieldStage("askMore");
|
|
9239
9997
|
}
|
|
9240
9998
|
} else if (currentEntry.type === "openai-compatible") {
|
|
@@ -9254,13 +10012,19 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9254
10012
|
} else if (currentEntry.type === "ollama") {
|
|
9255
10013
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
|
|
9256
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
|
+
}
|
|
9257
10019
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9258
|
-
setFieldStage("apiKey");
|
|
9259
10020
|
} else {
|
|
9260
10021
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9261
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
|
+
}
|
|
9262
10027
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9263
|
-
setFieldStage("apiKey");
|
|
9264
10028
|
}
|
|
9265
10029
|
}, [currentEntry, fieldStage]);
|
|
9266
10030
|
if (state.step === "PROVIDER_SELECT") {
|
|
@@ -9311,8 +10075,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9311
10075
|
setFieldStage(isAzure ? "deploymentName" : "label");
|
|
9312
10076
|
setFieldBuffer("");
|
|
9313
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
|
+
}
|
|
9314
10082
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9315
|
-
setFieldStage("apiKey");
|
|
9316
10083
|
setFieldBuffer("");
|
|
9317
10084
|
}
|
|
9318
10085
|
}
|
|
@@ -9393,7 +10160,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9393
10160
|
SelectInput__default.default,
|
|
9394
10161
|
{
|
|
9395
10162
|
items: modelOptions,
|
|
9396
|
-
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
|
+
},
|
|
9397
10173
|
indicatorComponent: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", children: isSelected ? "\u276F " : " " }),
|
|
9398
10174
|
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSelected ? "magenta" : "white", children: label })
|
|
9399
10175
|
}
|
|
@@ -9871,7 +10647,7 @@ var DashboardServer = class {
|
|
|
9871
10647
|
// ── Setup ─────────────────────────────────────
|
|
9872
10648
|
getGlobalStore() {
|
|
9873
10649
|
if (!this.globalStore) {
|
|
9874
|
-
const globalDbPath = path17__default.default.join(
|
|
10650
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9875
10651
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
9876
10652
|
}
|
|
9877
10653
|
return this.globalStore;
|
|
@@ -9933,7 +10709,7 @@ var DashboardServer = class {
|
|
|
9933
10709
|
}
|
|
9934
10710
|
watchRuntimeChanges() {
|
|
9935
10711
|
const workspaceDbPath = path17__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
9936
|
-
const globalDbPath = path17__default.default.join(
|
|
10712
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9937
10713
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
9938
10714
|
for (const watchPath of watchPaths) {
|
|
9939
10715
|
if (!fs14__default.default.existsSync(watchPath)) continue;
|
|
@@ -10041,7 +10817,7 @@ var DashboardServer = class {
|
|
|
10041
10817
|
const sessionId = req.params.id;
|
|
10042
10818
|
this.store.deleteSession(sessionId);
|
|
10043
10819
|
this.store.deleteRuntimeSession(sessionId);
|
|
10044
|
-
const globalDbPath = path17__default.default.join(
|
|
10820
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10045
10821
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10046
10822
|
try {
|
|
10047
10823
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -10055,7 +10831,7 @@ var DashboardServer = class {
|
|
|
10055
10831
|
});
|
|
10056
10832
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
10057
10833
|
const body = req.body;
|
|
10058
|
-
const globalDbPath = path17__default.default.join(
|
|
10834
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10059
10835
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
10060
10836
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10061
10837
|
try {
|
|
@@ -10078,7 +10854,7 @@ var DashboardServer = class {
|
|
|
10078
10854
|
});
|
|
10079
10855
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
10080
10856
|
this.store.deleteAllRuntimeNodes();
|
|
10081
|
-
const globalDbPath = path17__default.default.join(
|
|
10857
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10082
10858
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10083
10859
|
try {
|
|
10084
10860
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -10174,7 +10950,7 @@ var DashboardServer = class {
|
|
|
10174
10950
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
10175
10951
|
const scope = req.query["scope"] ?? "workspace";
|
|
10176
10952
|
if (scope === "global") {
|
|
10177
|
-
const globalDbPath = path17__default.default.join(
|
|
10953
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10178
10954
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10179
10955
|
try {
|
|
10180
10956
|
res.json({
|
|
@@ -10424,7 +11200,15 @@ async function exportCommand(options = {}) {
|
|
|
10424
11200
|
const spin = ora__default.default({ text: "Loading sessions\u2026", color: "magenta" }).start();
|
|
10425
11201
|
let store;
|
|
10426
11202
|
try {
|
|
10427
|
-
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
|
+
}
|
|
10428
11212
|
store = new MemoryStore(dbPath);
|
|
10429
11213
|
} catch (err) {
|
|
10430
11214
|
spin.fail(chalk8__default.default.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -10563,6 +11347,15 @@ async function telemetryCommand(action) {
|
|
|
10563
11347
|
|
|
10564
11348
|
// src/cli/index.ts
|
|
10565
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
|
+
});
|
|
10566
11359
|
var program = new commander.Command();
|
|
10567
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) => {
|
|
10568
11361
|
await startRepl(options);
|