cascade-ai 0.2.1 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/cli.cjs +1164 -359
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1164 -359
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1066 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -5
- package/dist/index.d.ts +72 -5
- package/dist/index.js +1065 -332
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -9,16 +9,17 @@ import chalk8 from 'chalk';
|
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
import fs7 from 'fs/promises';
|
|
11
11
|
import path17 from 'path';
|
|
12
|
-
import
|
|
12
|
+
import os3 from 'os';
|
|
13
13
|
import crypto, { randomUUID, timingSafeEqual } from 'crypto';
|
|
14
14
|
import fs14 from 'fs';
|
|
15
15
|
import * as _ignoreModule from 'ignore';
|
|
16
16
|
import _ignoreModule__default from 'ignore';
|
|
17
17
|
import Database from 'better-sqlite3';
|
|
18
18
|
import { z } from 'zod';
|
|
19
|
-
import { exec, spawnSync } from 'child_process';
|
|
19
|
+
import { exec, execSync, spawnSync } from 'child_process';
|
|
20
20
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
21
21
|
import EventEmitter from 'events';
|
|
22
|
+
import { glob } from 'glob';
|
|
22
23
|
import { promisify } from 'util';
|
|
23
24
|
import { simpleGit } from 'simple-git';
|
|
24
25
|
import PDFDocument from 'pdfkit';
|
|
@@ -82,7 +83,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
82
83
|
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;
|
|
83
84
|
var init_constants = __esm({
|
|
84
85
|
"src/constants.ts"() {
|
|
85
|
-
CASCADE_VERSION = "0.2.
|
|
86
|
+
CASCADE_VERSION = "0.2.11";
|
|
86
87
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
87
88
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
88
89
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -377,7 +378,8 @@ var init_constants = __esm({
|
|
|
377
378
|
IMAGE_ANALYZE: "image_analyze",
|
|
378
379
|
PDF_CREATE: "pdf_create",
|
|
379
380
|
RUN_CODE: "run_code",
|
|
380
|
-
PEER_MESSAGE: "peer_message"
|
|
381
|
+
PEER_MESSAGE: "peer_message",
|
|
382
|
+
WEB_SEARCH: "web_search"
|
|
381
383
|
};
|
|
382
384
|
DEFAULT_APPROVAL_REQUIRED = [
|
|
383
385
|
TOOL_NAMES.SHELL,
|
|
@@ -591,33 +593,61 @@ var init_anthropic = __esm({
|
|
|
591
593
|
}
|
|
592
594
|
}
|
|
593
595
|
convertMessages(messages) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
596
|
+
const result = [];
|
|
597
|
+
for (const m of messages) {
|
|
598
|
+
if (m.role === "system") continue;
|
|
599
|
+
if (m.role === "tool") {
|
|
600
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
601
|
+
result.push({
|
|
602
|
+
role: "user",
|
|
603
|
+
content: [{
|
|
604
|
+
type: "tool_result",
|
|
605
|
+
tool_use_id: m.toolCallId ?? "",
|
|
606
|
+
content: toolContent
|
|
607
|
+
}]
|
|
608
|
+
});
|
|
609
|
+
continue;
|
|
597
610
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
611
|
+
if (m.role === "assistant") {
|
|
612
|
+
const content = [];
|
|
613
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
614
|
+
if (text) content.push({ type: "text", text });
|
|
615
|
+
for (const tc of m.toolCalls ?? []) {
|
|
616
|
+
content.push({
|
|
617
|
+
type: "tool_use",
|
|
618
|
+
id: tc.id,
|
|
619
|
+
name: tc.name,
|
|
620
|
+
input: tc.input
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (content.length > 0) {
|
|
624
|
+
result.push({ role: "assistant", content });
|
|
625
|
+
}
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (m.role === "user") {
|
|
629
|
+
if (typeof m.content === "string") {
|
|
630
|
+
result.push({ role: "user", content: m.content });
|
|
631
|
+
} else {
|
|
632
|
+
const content = m.content.map((block) => {
|
|
633
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
634
|
+
if (block.type === "image") {
|
|
635
|
+
const img = block.image;
|
|
636
|
+
if (img.type === "base64") {
|
|
637
|
+
return {
|
|
638
|
+
type: "image",
|
|
639
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
640
|
+
};
|
|
609
641
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
};
|
|
642
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
643
|
+
}
|
|
644
|
+
return { type: "text", text: "" };
|
|
645
|
+
});
|
|
646
|
+
result.push({ role: "user", content });
|
|
616
647
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return result;
|
|
621
651
|
}
|
|
622
652
|
};
|
|
623
653
|
}
|
|
@@ -915,7 +945,7 @@ var init_gemini = __esm({
|
|
|
915
945
|
for (const part of candidate?.content?.parts ?? []) {
|
|
916
946
|
if (part.functionCall) {
|
|
917
947
|
toolCalls.push({
|
|
918
|
-
id:
|
|
948
|
+
id: part.functionCall.name,
|
|
919
949
|
name: part.functionCall.name,
|
|
920
950
|
input: part.functionCall.args ?? {}
|
|
921
951
|
});
|
|
@@ -1003,10 +1033,70 @@ var init_gemini = __esm({
|
|
|
1003
1033
|
}
|
|
1004
1034
|
// ── Private ──────────────────────────────────
|
|
1005
1035
|
buildContents(messages, extraImages) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1036
|
+
const contents = [];
|
|
1037
|
+
for (const m of messages) {
|
|
1038
|
+
if (m.role === "system") {
|
|
1039
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
1040
|
+
if (!text.trim()) continue;
|
|
1041
|
+
const prev = contents[contents.length - 1];
|
|
1042
|
+
if (prev?.role === "user") {
|
|
1043
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
1044
|
+
|
|
1045
|
+
` });
|
|
1046
|
+
} else {
|
|
1047
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
1048
|
+
}
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (m.role === "tool") {
|
|
1052
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1053
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1054
|
+
contents.push({
|
|
1055
|
+
role: "user",
|
|
1056
|
+
parts: [{
|
|
1057
|
+
functionResponse: {
|
|
1058
|
+
name: functionName,
|
|
1059
|
+
response: { output: toolContent }
|
|
1060
|
+
}
|
|
1061
|
+
}]
|
|
1062
|
+
});
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (m.role === "assistant") {
|
|
1066
|
+
const parts = [];
|
|
1067
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1068
|
+
if (textContent) parts.push({ text: textContent });
|
|
1069
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1070
|
+
parts.push({
|
|
1071
|
+
functionCall: {
|
|
1072
|
+
name: tc.name,
|
|
1073
|
+
args: tc.input
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
if (parts.length > 0) {
|
|
1078
|
+
contents.push({ role: "model", parts });
|
|
1079
|
+
}
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
if (m.role === "user") {
|
|
1083
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1084
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1085
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1086
|
+
if (isLastUser) {
|
|
1087
|
+
for (const img of extraImages) {
|
|
1088
|
+
if (img.type === "base64") {
|
|
1089
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (parts.length > 0) {
|
|
1095
|
+
contents.push({ role: "user", parts });
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return contents;
|
|
1010
1100
|
}
|
|
1011
1101
|
convertMessageContent(msg, extraImages) {
|
|
1012
1102
|
const parts = [];
|
|
@@ -1568,9 +1658,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
1568
1658
|
constructor(dbPath) {
|
|
1569
1659
|
fs14.mkdirSync(path17.dirname(dbPath), { recursive: true });
|
|
1570
1660
|
try {
|
|
1571
|
-
this.db = new Database(dbPath);
|
|
1661
|
+
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
1572
1662
|
this.db.pragma("journal_mode = WAL");
|
|
1573
1663
|
this.db.pragma("foreign_keys = ON");
|
|
1664
|
+
this.db.pragma("synchronous = NORMAL");
|
|
1574
1665
|
this.migrate();
|
|
1575
1666
|
} catch (err) {
|
|
1576
1667
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -1584,6 +1675,38 @@ Original error: ${err.message}`
|
|
|
1584
1675
|
throw err;
|
|
1585
1676
|
}
|
|
1586
1677
|
}
|
|
1678
|
+
// ── Async Write Queue ─────────────────────────
|
|
1679
|
+
writeQueue = [];
|
|
1680
|
+
isProcessingQueue = false;
|
|
1681
|
+
async processQueue() {
|
|
1682
|
+
if (this.isProcessingQueue) return;
|
|
1683
|
+
this.isProcessingQueue = true;
|
|
1684
|
+
while (this.writeQueue.length > 0) {
|
|
1685
|
+
const op = this.writeQueue.shift();
|
|
1686
|
+
if (op) {
|
|
1687
|
+
let attempts = 0;
|
|
1688
|
+
while (attempts < 5) {
|
|
1689
|
+
try {
|
|
1690
|
+
op();
|
|
1691
|
+
break;
|
|
1692
|
+
} catch (err) {
|
|
1693
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
1694
|
+
attempts++;
|
|
1695
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
1696
|
+
} else {
|
|
1697
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
1698
|
+
break;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
this.isProcessingQueue = false;
|
|
1705
|
+
}
|
|
1706
|
+
enqueueWrite(op) {
|
|
1707
|
+
this.writeQueue.push(op);
|
|
1708
|
+
this.processQueue().catch(console.error);
|
|
1709
|
+
}
|
|
1587
1710
|
// ── Sessions ──────────────────────────────────
|
|
1588
1711
|
createSession(session) {
|
|
1589
1712
|
this.db.prepare(`
|
|
@@ -1670,26 +1793,28 @@ Original error: ${err.message}`
|
|
|
1670
1793
|
}
|
|
1671
1794
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
1672
1795
|
upsertRuntimeSession(session) {
|
|
1673
|
-
this.
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1796
|
+
this.enqueueWrite(() => {
|
|
1797
|
+
this.db.prepare(`
|
|
1798
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
1799
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1800
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
1801
|
+
title = excluded.title,
|
|
1802
|
+
workspace_path = excluded.workspace_path,
|
|
1803
|
+
status = excluded.status,
|
|
1804
|
+
updated_at = excluded.updated_at,
|
|
1805
|
+
latest_prompt = excluded.latest_prompt,
|
|
1806
|
+
is_global = excluded.is_global
|
|
1807
|
+
`).run(
|
|
1808
|
+
session.sessionId,
|
|
1809
|
+
session.title,
|
|
1810
|
+
session.workspacePath,
|
|
1811
|
+
session.status,
|
|
1812
|
+
session.startedAt,
|
|
1813
|
+
session.updatedAt,
|
|
1814
|
+
session.latestPrompt ?? null,
|
|
1815
|
+
session.isGlobal ? 1 : 0
|
|
1816
|
+
);
|
|
1817
|
+
});
|
|
1693
1818
|
}
|
|
1694
1819
|
listRuntimeSessions(limit = 100) {
|
|
1695
1820
|
const rows = this.db.prepare(`
|
|
@@ -1707,33 +1832,35 @@ Original error: ${err.message}`
|
|
|
1707
1832
|
}));
|
|
1708
1833
|
}
|
|
1709
1834
|
upsertRuntimeNode(node) {
|
|
1710
|
-
this.
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1835
|
+
this.enqueueWrite(() => {
|
|
1836
|
+
this.db.prepare(`
|
|
1837
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
1838
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1839
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
1840
|
+
session_id = excluded.session_id,
|
|
1841
|
+
parent_id = excluded.parent_id,
|
|
1842
|
+
role = excluded.role,
|
|
1843
|
+
label = excluded.label,
|
|
1844
|
+
status = excluded.status,
|
|
1845
|
+
current_action = excluded.current_action,
|
|
1846
|
+
progress_pct = excluded.progress_pct,
|
|
1847
|
+
updated_at = excluded.updated_at,
|
|
1848
|
+
workspace_path = excluded.workspace_path,
|
|
1849
|
+
is_global = excluded.is_global
|
|
1850
|
+
`).run(
|
|
1851
|
+
node.tierId,
|
|
1852
|
+
node.sessionId,
|
|
1853
|
+
node.parentId ?? null,
|
|
1854
|
+
node.role,
|
|
1855
|
+
node.label,
|
|
1856
|
+
node.status,
|
|
1857
|
+
node.currentAction ?? null,
|
|
1858
|
+
node.progressPct ?? null,
|
|
1859
|
+
node.updatedAt,
|
|
1860
|
+
node.workspacePath ?? null,
|
|
1861
|
+
node.isGlobal ? 1 : 0
|
|
1862
|
+
);
|
|
1863
|
+
});
|
|
1737
1864
|
}
|
|
1738
1865
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
1739
1866
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -1756,30 +1883,32 @@ Original error: ${err.message}`
|
|
|
1756
1883
|
}));
|
|
1757
1884
|
}
|
|
1758
1885
|
addRuntimeNodeLog(log) {
|
|
1759
|
-
this.
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1886
|
+
this.enqueueWrite(() => {
|
|
1887
|
+
this.db.prepare(`
|
|
1888
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
1889
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1890
|
+
`).run(
|
|
1891
|
+
log.id,
|
|
1892
|
+
log.sessionId,
|
|
1893
|
+
log.tierId,
|
|
1894
|
+
log.role,
|
|
1895
|
+
log.label,
|
|
1896
|
+
log.status,
|
|
1897
|
+
log.currentAction ?? null,
|
|
1898
|
+
log.progressPct ?? null,
|
|
1899
|
+
log.timestamp,
|
|
1900
|
+
log.workspacePath ?? null,
|
|
1901
|
+
log.isGlobal ? 1 : 0
|
|
1902
|
+
);
|
|
1903
|
+
this.db.prepare(`
|
|
1904
|
+
DELETE FROM runtime_node_logs
|
|
1905
|
+
WHERE id NOT IN (
|
|
1906
|
+
SELECT id FROM runtime_node_logs
|
|
1907
|
+
ORDER BY timestamp DESC
|
|
1908
|
+
LIMIT 2000
|
|
1909
|
+
)
|
|
1910
|
+
`).run();
|
|
1911
|
+
});
|
|
1783
1912
|
}
|
|
1784
1913
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
1785
1914
|
let rows;
|
|
@@ -1817,19 +1946,21 @@ Original error: ${err.message}`
|
|
|
1817
1946
|
}
|
|
1818
1947
|
// ── Messages ──────────────────────────────────
|
|
1819
1948
|
addMessage(message) {
|
|
1820
|
-
this.
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1949
|
+
this.enqueueWrite(() => {
|
|
1950
|
+
this.db.prepare(`
|
|
1951
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
1952
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1953
|
+
`).run(
|
|
1954
|
+
message.id,
|
|
1955
|
+
message.sessionId,
|
|
1956
|
+
message.role,
|
|
1957
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
1958
|
+
message.timestamp,
|
|
1959
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
1960
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
1961
|
+
);
|
|
1962
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
1963
|
+
});
|
|
1833
1964
|
}
|
|
1834
1965
|
getSessionMessages(sessionId) {
|
|
1835
1966
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -1926,10 +2057,12 @@ Original error: ${err.message}`
|
|
|
1926
2057
|
}
|
|
1927
2058
|
// ── Audit Log ─────────────────────────────────
|
|
1928
2059
|
addAuditEntry(entry) {
|
|
1929
|
-
this.
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2060
|
+
this.enqueueWrite(() => {
|
|
2061
|
+
this.db.prepare(`
|
|
2062
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
2063
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2064
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
2065
|
+
});
|
|
1933
2066
|
}
|
|
1934
2067
|
getAuditLog(sessionId, limit = 100) {
|
|
1935
2068
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -1944,10 +2077,12 @@ Original error: ${err.message}`
|
|
|
1944
2077
|
}
|
|
1945
2078
|
// ── File Snapshots ────────────────────────────
|
|
1946
2079
|
addFileSnapshot(sessionId, filePath, content) {
|
|
1947
|
-
this.
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2080
|
+
this.enqueueWrite(() => {
|
|
2081
|
+
this.db.prepare(`
|
|
2082
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
2083
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2084
|
+
`).run(randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
2085
|
+
});
|
|
1951
2086
|
}
|
|
1952
2087
|
getLatestFileSnapshots(sessionId) {
|
|
1953
2088
|
const rows = this.db.prepare(`
|
|
@@ -2251,12 +2386,24 @@ var McpServerConfigSchema = z.object({
|
|
|
2251
2386
|
args: z.array(z.string()).optional(),
|
|
2252
2387
|
env: z.record(z.string()).optional()
|
|
2253
2388
|
});
|
|
2389
|
+
var WebSearchConfigSchema = z.object({
|
|
2390
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
2391
|
+
searxngUrl: z.string().optional(),
|
|
2392
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
2393
|
+
braveApiKey: z.string().optional(),
|
|
2394
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
2395
|
+
tavilyApiKey: z.string().optional(),
|
|
2396
|
+
/** Max results per search (default 5) */
|
|
2397
|
+
maxResults: z.number().default(5)
|
|
2398
|
+
});
|
|
2254
2399
|
var ToolsConfigSchema = z.object({
|
|
2255
2400
|
shellAllowlist: z.array(z.string()).default([]),
|
|
2256
2401
|
shellBlocklist: z.array(z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
2257
2402
|
requireApprovalFor: z.array(z.string()).default([]),
|
|
2258
2403
|
browserEnabled: z.boolean().default(false),
|
|
2259
|
-
mcpServers: z.array(McpServerConfigSchema).optional()
|
|
2404
|
+
mcpServers: z.array(McpServerConfigSchema).optional(),
|
|
2405
|
+
/** Web search backends — at least one should be configured for best results */
|
|
2406
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
2260
2407
|
});
|
|
2261
2408
|
var HookDefinitionSchema = z.object({
|
|
2262
2409
|
command: z.string(),
|
|
@@ -2361,7 +2508,7 @@ var ConfigManager = class {
|
|
|
2361
2508
|
globalDir;
|
|
2362
2509
|
constructor(workspacePath = process.cwd()) {
|
|
2363
2510
|
this.workspacePath = workspacePath;
|
|
2364
|
-
this.globalDir = path17.join(
|
|
2511
|
+
this.globalDir = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR);
|
|
2365
2512
|
}
|
|
2366
2513
|
async load() {
|
|
2367
2514
|
this.config = await this.loadConfig();
|
|
@@ -3360,6 +3507,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
3360
3507
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
3361
3508
|
}
|
|
3362
3509
|
};
|
|
3510
|
+
|
|
3511
|
+
// src/utils/retry.ts
|
|
3512
|
+
var CascadeCancelledError = class extends Error {
|
|
3513
|
+
constructor(reason) {
|
|
3514
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
3515
|
+
this.name = "CascadeCancelledError";
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
|
|
3519
|
+
// src/core/tiers/base.ts
|
|
3363
3520
|
var BaseTier = class extends EventEmitter {
|
|
3364
3521
|
id;
|
|
3365
3522
|
role;
|
|
@@ -3369,6 +3526,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
3369
3526
|
label;
|
|
3370
3527
|
systemPromptOverride = "";
|
|
3371
3528
|
hierarchyContext = "";
|
|
3529
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
3530
|
+
signal;
|
|
3372
3531
|
constructor(role, id, parentId) {
|
|
3373
3532
|
super();
|
|
3374
3533
|
this.role = role;
|
|
@@ -3431,6 +3590,18 @@ var BaseTier = class extends EventEmitter {
|
|
|
3431
3590
|
log(message, data) {
|
|
3432
3591
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3433
3592
|
}
|
|
3593
|
+
/**
|
|
3594
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
3595
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
3596
|
+
* to provide a fast, clean cancellation path.
|
|
3597
|
+
*/
|
|
3598
|
+
throwIfCancelled() {
|
|
3599
|
+
if (this.signal?.aborted) {
|
|
3600
|
+
throw new CascadeCancelledError(
|
|
3601
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
3602
|
+
);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3434
3605
|
};
|
|
3435
3606
|
|
|
3436
3607
|
// src/core/context/manager.ts
|
|
@@ -3632,6 +3803,7 @@ Rules:
|
|
|
3632
3803
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
3633
3804
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
3634
3805
|
- 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.
|
|
3806
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
3635
3807
|
- Use the "pdf_create" tool for PDF requests.
|
|
3636
3808
|
- 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.
|
|
3637
3809
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -3675,7 +3847,8 @@ var T3Worker = class extends BaseTier {
|
|
|
3675
3847
|
this.store = store;
|
|
3676
3848
|
this.audit = new AuditLogger(store, sessionId);
|
|
3677
3849
|
}
|
|
3678
|
-
async execute(assignment, taskId) {
|
|
3850
|
+
async execute(assignment, taskId, signal) {
|
|
3851
|
+
this.signal = signal;
|
|
3679
3852
|
this.assignment = assignment;
|
|
3680
3853
|
this.taskId = taskId;
|
|
3681
3854
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -3807,14 +3980,13 @@ Now execute your subtask using this context where relevant.`
|
|
|
3807
3980
|
await this.peerBus.barrier(this.id, barrierName, total);
|
|
3808
3981
|
}
|
|
3809
3982
|
receivePeerSync(fromId, content) {
|
|
3810
|
-
|
|
3811
|
-
if (existing) {
|
|
3812
|
-
existing.content = content;
|
|
3813
|
-
existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3814
|
-
} else {
|
|
3815
|
-
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3816
|
-
}
|
|
3983
|
+
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3817
3984
|
this.emit("peer-sync-received", { fromId, content });
|
|
3985
|
+
this.context.addMessage({
|
|
3986
|
+
role: "user",
|
|
3987
|
+
content: `[SYSTEM_NOTIFICATION]: You received a new peer message from ${fromId}. Use the "peer_message" tool with action="receive" to read it.`
|
|
3988
|
+
}).catch(() => {
|
|
3989
|
+
});
|
|
3818
3990
|
}
|
|
3819
3991
|
// ── Private ──────────────────────────────────
|
|
3820
3992
|
async runAgentLoop(systemPrompt, tools) {
|
|
@@ -3826,6 +3998,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
3826
3998
|
tools = [...tools];
|
|
3827
3999
|
while (iterations < MAX_ITERATIONS) {
|
|
3828
4000
|
iterations++;
|
|
4001
|
+
this.throwIfCancelled();
|
|
3829
4002
|
const options = {
|
|
3830
4003
|
messages: this.context.getMessages(),
|
|
3831
4004
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -3846,21 +4019,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3846
4019
|
if (requiresArtifact) {
|
|
3847
4020
|
stalledArtifactIterations += 1;
|
|
3848
4021
|
if (stalledArtifactIterations >= 2) {
|
|
3849
|
-
if (
|
|
3850
|
-
|
|
3851
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
3852
|
-
this.assignment?.description ?? ""
|
|
3853
|
-
);
|
|
3854
|
-
if (toolName) {
|
|
3855
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
3856
|
-
this.sendStatusUpdate({
|
|
3857
|
-
progressPct: 50,
|
|
3858
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
3859
|
-
status: "IN_PROGRESS"
|
|
3860
|
-
});
|
|
3861
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
3862
|
-
continue;
|
|
3863
|
-
}
|
|
4022
|
+
if (stalledArtifactIterations === 2) {
|
|
4023
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
3864
4024
|
}
|
|
3865
4025
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
3866
4026
|
}
|
|
@@ -3945,7 +4105,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3945
4105
|
sendPeerSync: (to, syncType, content) => {
|
|
3946
4106
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
3947
4107
|
},
|
|
3948
|
-
getPeerMessages: () =>
|
|
4108
|
+
getPeerMessages: () => {
|
|
4109
|
+
const msgs = [...this.peerSyncBuffer];
|
|
4110
|
+
this.peerSyncBuffer = [];
|
|
4111
|
+
return msgs;
|
|
4112
|
+
}
|
|
3949
4113
|
});
|
|
3950
4114
|
if (this.audit) {
|
|
3951
4115
|
this.audit.toolCall(this.id, tc.name, tc.input);
|
|
@@ -4016,6 +4180,9 @@ ${assignment.expectedOutput}`;
|
|
|
4016
4180
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
4017
4181
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
4018
4182
|
const issues = [];
|
|
4183
|
+
const { exec: exec4 } = await import('child_process');
|
|
4184
|
+
const { promisify: promisify3 } = await import('util');
|
|
4185
|
+
const execAsync3 = promisify3(exec4);
|
|
4019
4186
|
for (const artifactPath of artifactPaths) {
|
|
4020
4187
|
const absolutePath = path17.resolve(process.cwd(), artifactPath);
|
|
4021
4188
|
try {
|
|
@@ -4032,9 +4199,27 @@ ${assignment.expectedOutput}`;
|
|
|
4032
4199
|
const content = await fs7.readFile(absolutePath, "utf-8");
|
|
4033
4200
|
if (!content.trim()) {
|
|
4034
4201
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
4202
|
+
continue;
|
|
4035
4203
|
}
|
|
4036
4204
|
} else if (stat.size < 100) {
|
|
4037
4205
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
4206
|
+
continue;
|
|
4207
|
+
}
|
|
4208
|
+
const ext = path17.extname(absolutePath).toLowerCase();
|
|
4209
|
+
try {
|
|
4210
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
4211
|
+
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
4212
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
4213
|
+
await execAsync3(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
4214
|
+
} else if (ext === ".py") {
|
|
4215
|
+
await execAsync3(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
4216
|
+
}
|
|
4217
|
+
} catch (err) {
|
|
4218
|
+
const stderr = err?.stderr || String(err);
|
|
4219
|
+
const stdout = err?.stdout || "";
|
|
4220
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
4221
|
+
${stderr}
|
|
4222
|
+
${stdout}`);
|
|
4038
4223
|
}
|
|
4039
4224
|
} catch {
|
|
4040
4225
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -4431,7 +4616,8 @@ var T2Manager = class extends BaseTier {
|
|
|
4431
4616
|
});
|
|
4432
4617
|
this.emit("peer-sync-received", { fromId, content });
|
|
4433
4618
|
}
|
|
4434
|
-
async execute(assignment, taskId) {
|
|
4619
|
+
async execute(assignment, taskId, signal) {
|
|
4620
|
+
this.signal = signal;
|
|
4435
4621
|
this.assignment = assignment;
|
|
4436
4622
|
this.taskId = taskId;
|
|
4437
4623
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -4443,12 +4629,14 @@ var T2Manager = class extends BaseTier {
|
|
|
4443
4629
|
});
|
|
4444
4630
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
4445
4631
|
try {
|
|
4632
|
+
this.throwIfCancelled();
|
|
4446
4633
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
4447
4634
|
this.sendStatusUpdate({
|
|
4448
4635
|
progressPct: 20,
|
|
4449
4636
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
4450
4637
|
status: "IN_PROGRESS"
|
|
4451
4638
|
});
|
|
4639
|
+
this.throwIfCancelled();
|
|
4452
4640
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
4453
4641
|
this.sendStatusUpdate({
|
|
4454
4642
|
progressPct: 90,
|
|
@@ -4487,13 +4675,17 @@ var T2Manager = class extends BaseTier {
|
|
|
4487
4675
|
}
|
|
4488
4676
|
// ── Private ──────────────────────────────────
|
|
4489
4677
|
async decomposeSection(assignment) {
|
|
4678
|
+
const peerPlans = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p) => `[Peer ${p.fromId} Plan]: ${p.content.sectionTitle} - ${p.content.subtaskTitles?.join(", ")}`).join("\n");
|
|
4490
4679
|
const prompt = `Decompose this section into 2-5 concrete subtasks for T3 workers.
|
|
4491
4680
|
|
|
4492
4681
|
Section: ${assignment.sectionTitle}
|
|
4493
4682
|
Description: ${assignment.description}
|
|
4494
4683
|
Expected output: ${assignment.expectedOutput}
|
|
4495
4684
|
Constraints: ${assignment.constraints.join("; ")}
|
|
4496
|
-
|
|
4685
|
+
${peerPlans ? `
|
|
4686
|
+
Context from sibling T2 plans (use this to align execution and avoid overlaps):
|
|
4687
|
+
${peerPlans}
|
|
4688
|
+
` : ""}
|
|
4497
4689
|
Return a JSON array of subtask objects, each with:
|
|
4498
4690
|
- subtaskId: string (unique)
|
|
4499
4691
|
- subtaskTitle: string
|
|
@@ -4611,11 +4803,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4611
4803
|
).join(", ")}`,
|
|
4612
4804
|
status: "IN_PROGRESS"
|
|
4613
4805
|
});
|
|
4806
|
+
this.throwIfCancelled();
|
|
4614
4807
|
const waveResults = await Promise.allSettled(
|
|
4615
4808
|
runnableIds.map(async (id) => {
|
|
4616
4809
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4617
4810
|
const worker = workerMap.get(id);
|
|
4618
|
-
const result = await worker.execute(assignment, taskId);
|
|
4811
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
4619
4812
|
resultMap.set(id, result);
|
|
4620
4813
|
return result;
|
|
4621
4814
|
})
|
|
@@ -4629,6 +4822,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4629
4822
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4630
4823
|
const retried = await this.retryT3(assignment, taskId);
|
|
4631
4824
|
resultMap.set(id, retried);
|
|
4825
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
4826
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4827
|
+
if (this.toolCreator) {
|
|
4828
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
4829
|
+
this.sendStatusUpdate({
|
|
4830
|
+
progressPct: 50,
|
|
4831
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
4832
|
+
status: "IN_PROGRESS"
|
|
4833
|
+
});
|
|
4834
|
+
const toolName = await this.toolCreator.createTool(
|
|
4835
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
4836
|
+
assignment.description
|
|
4837
|
+
);
|
|
4838
|
+
if (toolName) {
|
|
4839
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
4840
|
+
this.sendStatusUpdate({
|
|
4841
|
+
progressPct: 60,
|
|
4842
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
4843
|
+
status: "IN_PROGRESS"
|
|
4844
|
+
});
|
|
4845
|
+
try {
|
|
4846
|
+
const verifyResult = await this.router.generate("T2", {
|
|
4847
|
+
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".` }],
|
|
4848
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
4849
|
+
maxTokens: 50
|
|
4850
|
+
});
|
|
4851
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
4852
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
4853
|
+
const retried = await this.retryT3({
|
|
4854
|
+
...assignment,
|
|
4855
|
+
description: `${assignment.description}
|
|
4856
|
+
|
|
4857
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
4858
|
+
}, taskId);
|
|
4859
|
+
resultMap.set(id, retried);
|
|
4860
|
+
} else {
|
|
4861
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
4862
|
+
resultMap.set(id, r.value);
|
|
4863
|
+
}
|
|
4864
|
+
} catch {
|
|
4865
|
+
const retried = await this.retryT3({
|
|
4866
|
+
...assignment,
|
|
4867
|
+
description: `${assignment.description}
|
|
4868
|
+
|
|
4869
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
4870
|
+
}, taskId);
|
|
4871
|
+
resultMap.set(id, retried);
|
|
4872
|
+
}
|
|
4873
|
+
} else {
|
|
4874
|
+
resultMap.set(id, r.value);
|
|
4875
|
+
}
|
|
4876
|
+
} else {
|
|
4877
|
+
resultMap.set(id, r.value);
|
|
4878
|
+
}
|
|
4632
4879
|
}
|
|
4633
4880
|
for (const dependent of adj.get(id) ?? []) {
|
|
4634
4881
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -4692,7 +4939,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4692
4939
|
}));
|
|
4693
4940
|
return worker.execute(
|
|
4694
4941
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
4695
|
-
taskId
|
|
4942
|
+
taskId,
|
|
4943
|
+
this.signal
|
|
4696
4944
|
);
|
|
4697
4945
|
}
|
|
4698
4946
|
publishSectionOutput(result) {
|
|
@@ -4706,24 +4954,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4706
4954
|
async aggregateResults(assignment, results) {
|
|
4707
4955
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
4708
4956
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
4709
|
-
const
|
|
4710
|
-
const
|
|
4957
|
+
const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
|
|
4958
|
+
const peerContext = peerOutputs ? `
|
|
4959
|
+
|
|
4960
|
+
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
4961
|
+
${peerOutputs}` : "";
|
|
4962
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
4963
|
+
let currentSummary = "";
|
|
4964
|
+
let i = 0;
|
|
4965
|
+
while (i < completed.length) {
|
|
4966
|
+
let chunkText = "";
|
|
4967
|
+
let chunkEnd = i;
|
|
4968
|
+
while (chunkEnd < completed.length) {
|
|
4969
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
4711
4970
|
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4971
|
+
`;
|
|
4972
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
4973
|
+
break;
|
|
4974
|
+
}
|
|
4975
|
+
chunkText += nextOutput;
|
|
4976
|
+
chunkEnd++;
|
|
4977
|
+
}
|
|
4978
|
+
i = chunkEnd;
|
|
4979
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
4980
|
+
${currentSummary ? `
|
|
4981
|
+
PREVIOUS SUMMARY SO FAR:
|
|
4982
|
+
${currentSummary}
|
|
4983
|
+
|
|
4984
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
4985
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
4986
|
+
const messages = [{ role: "user", content: prompt }];
|
|
4987
|
+
try {
|
|
4988
|
+
const result = await this.router.generate("T2", {
|
|
4989
|
+
messages,
|
|
4990
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
4718
4991
|
|
|
4719
4992
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4993
|
+
maxTokens: 500
|
|
4994
|
+
});
|
|
4995
|
+
currentSummary = result.content;
|
|
4996
|
+
} catch (err) {
|
|
4997
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
4998
|
+
return currentSummary + "\n\n" + chunkText;
|
|
4999
|
+
}
|
|
4726
5000
|
}
|
|
5001
|
+
return currentSummary;
|
|
4727
5002
|
}
|
|
4728
5003
|
determineStatus(results) {
|
|
4729
5004
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -4851,10 +5126,10 @@ Rules:
|
|
|
4851
5126
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
4852
5127
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
4853
5128
|
|
|
4854
|
-
|
|
4855
|
-
-
|
|
4856
|
-
-
|
|
4857
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
5129
|
+
DEPENDENCY GUIDANCE:
|
|
5130
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
5131
|
+
- 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).
|
|
5132
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
4858
5133
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
4859
5134
|
|
|
4860
5135
|
QUALITY RULES:
|
|
@@ -4893,7 +5168,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
4893
5168
|
setToolCreator(creator) {
|
|
4894
5169
|
this.toolCreator = creator;
|
|
4895
5170
|
}
|
|
4896
|
-
async execute(userPrompt, images, systemContext) {
|
|
5171
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
5172
|
+
this.signal = signal;
|
|
4897
5173
|
this.taskId = randomUUID();
|
|
4898
5174
|
this.setLabel("Administrator");
|
|
4899
5175
|
this.setStatus("ACTIVE");
|
|
@@ -4904,10 +5180,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
4904
5180
|
status: "IN_PROGRESS"
|
|
4905
5181
|
});
|
|
4906
5182
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
5183
|
+
this.throwIfCancelled();
|
|
4907
5184
|
let enrichedPrompt = userPrompt;
|
|
4908
5185
|
if (images?.length) {
|
|
4909
5186
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
4910
5187
|
}
|
|
5188
|
+
this.throwIfCancelled();
|
|
4911
5189
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
4912
5190
|
this.sendStatusUpdate({
|
|
4913
5191
|
progressPct: 10,
|
|
@@ -4915,21 +5193,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
4915
5193
|
status: "IN_PROGRESS"
|
|
4916
5194
|
});
|
|
4917
5195
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
4918
|
-
|
|
5196
|
+
this.throwIfCancelled();
|
|
5197
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
5198
|
+
let pass = 1;
|
|
5199
|
+
const MAX_REPLAN_PASSES = 2;
|
|
5200
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
5201
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
5202
|
+
if (reviewResult.approved) {
|
|
5203
|
+
this.log("T1 Review passed.");
|
|
5204
|
+
break;
|
|
5205
|
+
}
|
|
5206
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
5207
|
+
this.sendStatusUpdate({
|
|
5208
|
+
progressPct: 80 + pass * 5,
|
|
5209
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
5210
|
+
status: "IN_PROGRESS"
|
|
5211
|
+
});
|
|
5212
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
5213
|
+
Review reason: ${reviewResult.reason}
|
|
5214
|
+
|
|
5215
|
+
Original goal: ${enrichedPrompt}
|
|
5216
|
+
|
|
5217
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
5218
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
5219
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
5220
|
+
pass++;
|
|
5221
|
+
}
|
|
4919
5222
|
this.sendStatusUpdate({
|
|
4920
5223
|
progressPct: 95,
|
|
4921
5224
|
currentAction: "Compiling final output",
|
|
4922
5225
|
status: "IN_PROGRESS"
|
|
4923
5226
|
});
|
|
4924
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
5227
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
4925
5228
|
this.setStatus("COMPLETED");
|
|
4926
5229
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
4927
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
5230
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
4928
5231
|
}
|
|
4929
5232
|
getEscalations() {
|
|
4930
5233
|
return [...this.escalations];
|
|
4931
5234
|
}
|
|
4932
5235
|
// ── Private ──────────────────────────────────
|
|
5236
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
5237
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
5238
|
+
if (failedSections.length > 0) {
|
|
5239
|
+
return {
|
|
5240
|
+
approved: false,
|
|
5241
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
5242
|
+
};
|
|
5243
|
+
}
|
|
5244
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
5245
|
+
${r.sectionSummary}`).join("\n\n");
|
|
5246
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
5247
|
+
Review the following execution outputs against the original user prompt.
|
|
5248
|
+
|
|
5249
|
+
Original Request: ${originalPrompt}
|
|
5250
|
+
|
|
5251
|
+
T2 Manager Summaries:
|
|
5252
|
+
${sectionsText}
|
|
5253
|
+
|
|
5254
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
5255
|
+
If yes, reply with exactly: "APPROVED".
|
|
5256
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
5257
|
+
try {
|
|
5258
|
+
const result = await this.router.generate("T1", {
|
|
5259
|
+
messages: [{ role: "user", content: prompt }],
|
|
5260
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
5261
|
+
maxTokens: 500,
|
|
5262
|
+
temperature: 0
|
|
5263
|
+
});
|
|
5264
|
+
const response = result.content.trim();
|
|
5265
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
5266
|
+
return { approved: true };
|
|
5267
|
+
}
|
|
5268
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
5269
|
+
} catch {
|
|
5270
|
+
return { approved: true };
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
4933
5273
|
async analyzeImages(prompt, images) {
|
|
4934
5274
|
const visionModel = this.router.getModelForTier("T1");
|
|
4935
5275
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -4958,29 +5298,35 @@ ${systemContext}` : "";
|
|
|
4958
5298
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
4959
5299
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
4960
5300
|
|
|
4961
|
-
Return JSON where
|
|
5301
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
4962
5302
|
{
|
|
4963
5303
|
"sections": [{
|
|
5304
|
+
"sectionId": "s1",
|
|
5305
|
+
"sectionTitle": "Setup Project",
|
|
5306
|
+
"description": "Initialize the project",
|
|
5307
|
+
"expectedOutput": "Basic structure created",
|
|
5308
|
+
"constraints": [],
|
|
5309
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
4964
5310
|
"t3Subtasks": [{
|
|
4965
5311
|
"subtaskId": "t1",
|
|
4966
|
-
"subtaskTitle": "
|
|
4967
|
-
"
|
|
4968
|
-
"
|
|
4969
|
-
|
|
4970
|
-
"
|
|
4971
|
-
"subtaskTitle": "Save Code to File",
|
|
4972
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
4973
|
-
"executionMode": "parallel"
|
|
4974
|
-
}, {
|
|
4975
|
-
"subtaskId": "t3",
|
|
4976
|
-
"subtaskTitle": "Execute and Verify",
|
|
4977
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
4978
|
-
"executionMode": "parallel"
|
|
5312
|
+
"subtaskTitle": "Init NPM",
|
|
5313
|
+
"description": "Run npm init",
|
|
5314
|
+
"expectedOutput": "package.json created",
|
|
5315
|
+
"constraints": [],
|
|
5316
|
+
"dependsOn": []
|
|
4979
5317
|
}]
|
|
5318
|
+
}, {
|
|
5319
|
+
"sectionId": "s2",
|
|
5320
|
+
"sectionTitle": "Write Tests",
|
|
5321
|
+
"description": "Write tests for the project",
|
|
5322
|
+
"expectedOutput": "Tests passing",
|
|
5323
|
+
"constraints": [],
|
|
5324
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
5325
|
+
"t3Subtasks": [...]
|
|
4980
5326
|
}]
|
|
4981
5327
|
}
|
|
4982
|
-
Use dependsOn when a
|
|
4983
|
-
Leave dependsOn empty for
|
|
5328
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
5329
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
4984
5330
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
4985
5331
|
const result = await this.router.generate("T1", {
|
|
4986
5332
|
messages,
|
|
@@ -5108,92 +5454,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
5108
5454
|
].filter(Boolean).join(" ");
|
|
5109
5455
|
m.setHierarchyContext(context);
|
|
5110
5456
|
});
|
|
5111
|
-
if (overlapSections.size > 0
|
|
5112
|
-
this.log("Overlap detected \u2014
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5457
|
+
if (overlapSections.size > 0) {
|
|
5458
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
5459
|
+
const overlapArray = Array.from(overlapSections);
|
|
5460
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
5461
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
5462
|
+
if (section) {
|
|
5463
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
5116
5464
|
}
|
|
5117
5465
|
}
|
|
5118
5466
|
}
|
|
5119
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
5120
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
5121
5467
|
const t2Results = [];
|
|
5122
5468
|
try {
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5469
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
5470
|
+
} finally {
|
|
5471
|
+
cleanup();
|
|
5472
|
+
}
|
|
5473
|
+
return t2Results;
|
|
5474
|
+
}
|
|
5475
|
+
/**
|
|
5476
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
5477
|
+
*/
|
|
5478
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
5479
|
+
const adj = /* @__PURE__ */ new Map();
|
|
5480
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
5481
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5482
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
5483
|
+
for (const s of sections) {
|
|
5484
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
5485
|
+
inDegree.set(s.sectionId, 0);
|
|
5486
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
5487
|
+
}
|
|
5488
|
+
for (const s of sections) {
|
|
5489
|
+
for (const dep of s.dependsOn ?? []) {
|
|
5490
|
+
adj.get(dep).add(s.sectionId);
|
|
5491
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
const queue = [];
|
|
5495
|
+
const degree = new Map(inDegree);
|
|
5496
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
5497
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5498
|
+
while (queue.length > 0) {
|
|
5499
|
+
const u = queue.shift();
|
|
5500
|
+
visited.add(u);
|
|
5501
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
5502
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
5503
|
+
degree.set(v, newDeg);
|
|
5504
|
+
if (newDeg === 0) queue.push(v);
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
5508
|
+
if (cycleNodes.length > 0) {
|
|
5509
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
5510
|
+
for (const s of sections) {
|
|
5511
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
5512
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
5513
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
5514
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
5515
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
5154
5516
|
}
|
|
5517
|
+
s.dependsOn = safeDeps;
|
|
5155
5518
|
}
|
|
5156
|
-
}
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
const totalSections = sections.length;
|
|
5522
|
+
let completedSections = 0;
|
|
5523
|
+
const executeWave = async () => {
|
|
5524
|
+
const readyIds = [];
|
|
5525
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
5526
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
5527
|
+
readyIds.push(id);
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
if (readyIds.length === 0) return;
|
|
5531
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
5532
|
+
resultMap.set(id, null);
|
|
5533
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
5534
|
+
const section = sections[index];
|
|
5535
|
+
const manager = managers[index];
|
|
5536
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
5537
|
+
this.sendStatusUpdate({
|
|
5538
|
+
progressPct,
|
|
5539
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
5540
|
+
status: "IN_PROGRESS"
|
|
5541
|
+
});
|
|
5542
|
+
this.throwIfCancelled();
|
|
5543
|
+
let result;
|
|
5544
|
+
try {
|
|
5545
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
5546
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
5547
|
+
if (result.status === "ESCALATED") {
|
|
5548
|
+
this.escalations.push({
|
|
5549
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
5550
|
+
sectionId: section.sectionId,
|
|
5551
|
+
attempted: result.issues,
|
|
5552
|
+
blocker: result.issues.join("; "),
|
|
5553
|
+
needs: "Human review required"
|
|
5189
5554
|
});
|
|
5190
5555
|
}
|
|
5556
|
+
} catch (err) {
|
|
5557
|
+
result = {
|
|
5558
|
+
sectionId: section.sectionId,
|
|
5559
|
+
sectionTitle: section.sectionTitle,
|
|
5560
|
+
status: "FAILED",
|
|
5561
|
+
t3Results: [],
|
|
5562
|
+
sectionSummary: "",
|
|
5563
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
5564
|
+
};
|
|
5191
5565
|
}
|
|
5566
|
+
resultMap.set(id, result);
|
|
5567
|
+
completedSections++;
|
|
5568
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
5569
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
5570
|
+
}
|
|
5571
|
+
}));
|
|
5572
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
5573
|
+
await executeWave();
|
|
5192
5574
|
}
|
|
5193
|
-
}
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
return t2Results;
|
|
5575
|
+
};
|
|
5576
|
+
await executeWave();
|
|
5577
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
5197
5578
|
}
|
|
5198
5579
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
5199
5580
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -5643,13 +6024,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
5643
6024
|
}
|
|
5644
6025
|
async execute(input, _options) {
|
|
5645
6026
|
const platform = input["platform"] ?? "github";
|
|
5646
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
5647
6027
|
const operation = input["operation"];
|
|
5648
6028
|
const repo = input["repo"];
|
|
5649
|
-
|
|
5650
|
-
|
|
6029
|
+
let token = input["token"];
|
|
6030
|
+
if (!token) {
|
|
6031
|
+
if (platform === "github") {
|
|
6032
|
+
token = process.env["GITHUB_TOKEN"];
|
|
6033
|
+
} else {
|
|
6034
|
+
token = process.env["GITLAB_TOKEN"];
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
if (!token) {
|
|
6038
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
6039
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
6040
|
+
}
|
|
6041
|
+
try {
|
|
6042
|
+
if (platform === "github") {
|
|
6043
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
6044
|
+
}
|
|
6045
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
6046
|
+
} catch (err) {
|
|
6047
|
+
const axiosErr = err;
|
|
6048
|
+
if (axiosErr?.response?.status) {
|
|
6049
|
+
const status = axiosErr.response.status;
|
|
6050
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
6051
|
+
switch (status) {
|
|
6052
|
+
case 401:
|
|
6053
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
6054
|
+
case 403:
|
|
6055
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
6056
|
+
case 404:
|
|
6057
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
6058
|
+
case 422:
|
|
6059
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
6060
|
+
case 429:
|
|
6061
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
6062
|
+
default:
|
|
6063
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
5651
6067
|
}
|
|
5652
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
5653
6068
|
}
|
|
5654
6069
|
async executeGitHub(operation, repo, token, input) {
|
|
5655
6070
|
const headers = {
|
|
@@ -5736,6 +6151,7 @@ ${response.data.description}`;
|
|
|
5736
6151
|
};
|
|
5737
6152
|
|
|
5738
6153
|
// src/tools/browser.ts
|
|
6154
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
5739
6155
|
var BrowserTool = class extends BaseTool {
|
|
5740
6156
|
name = "browser";
|
|
5741
6157
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -5744,7 +6160,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
5744
6160
|
properties: {
|
|
5745
6161
|
action: {
|
|
5746
6162
|
type: "string",
|
|
5747
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
6163
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
5748
6164
|
},
|
|
5749
6165
|
url: { type: "string", description: "URL to navigate to" },
|
|
5750
6166
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -5764,53 +6180,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
5764
6180
|
try {
|
|
5765
6181
|
playwright = await import('playwright');
|
|
5766
6182
|
} catch {
|
|
5767
|
-
|
|
6183
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
5768
6184
|
}
|
|
5769
|
-
if (!this.browser) {
|
|
5770
|
-
const pw = playwright;
|
|
5771
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
5772
|
-
const b = this.browser;
|
|
5773
|
-
this.page = await b.newPage();
|
|
5774
|
-
}
|
|
5775
|
-
const page = this.page;
|
|
5776
6185
|
const action = input["action"];
|
|
5777
6186
|
const timeout = input["timeout"] ?? 1e4;
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
case "evaluate": {
|
|
5796
|
-
const result = await page.evaluate(input["script"]);
|
|
5797
|
-
return JSON.stringify(result);
|
|
6187
|
+
if (action === "close") {
|
|
6188
|
+
await this.close();
|
|
6189
|
+
return "Browser closed.";
|
|
6190
|
+
}
|
|
6191
|
+
if (!this.browser || !this.page) {
|
|
6192
|
+
await this.close();
|
|
6193
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
6194
|
+
const timeoutPromise = new Promise(
|
|
6195
|
+
(_, 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)
|
|
6196
|
+
);
|
|
6197
|
+
try {
|
|
6198
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
6199
|
+
this.page = await this.browser.newPage();
|
|
6200
|
+
} catch (err) {
|
|
6201
|
+
this.browser = null;
|
|
6202
|
+
this.page = null;
|
|
6203
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
5798
6204
|
}
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
6205
|
+
}
|
|
6206
|
+
const page = this.page;
|
|
6207
|
+
try {
|
|
6208
|
+
switch (action) {
|
|
6209
|
+
case "navigate": {
|
|
6210
|
+
await page.goto(input["url"], { timeout });
|
|
6211
|
+
const title = await page.title();
|
|
6212
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
6213
|
+
}
|
|
6214
|
+
case "click": {
|
|
6215
|
+
await page.click(input["selector"], { timeout });
|
|
6216
|
+
return `Clicked ${input["selector"]}`;
|
|
6217
|
+
}
|
|
6218
|
+
case "fill": {
|
|
6219
|
+
await page.fill(input["selector"], input["value"]);
|
|
6220
|
+
return `Filled ${input["selector"]} with value`;
|
|
6221
|
+
}
|
|
6222
|
+
case "screenshot": {
|
|
6223
|
+
const buf = await page.screenshot({ type: "png" });
|
|
6224
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
6225
|
+
}
|
|
6226
|
+
case "evaluate": {
|
|
6227
|
+
const result = await page.evaluate(input["script"]);
|
|
6228
|
+
return JSON.stringify(result);
|
|
6229
|
+
}
|
|
6230
|
+
case "extract_text": {
|
|
6231
|
+
const text = await page.locator("body").innerText();
|
|
6232
|
+
return text.slice(0, 1e4);
|
|
6233
|
+
}
|
|
6234
|
+
case "wait": {
|
|
6235
|
+
await page.waitForTimeout(timeout);
|
|
6236
|
+
return `Waited ${timeout}ms`;
|
|
6237
|
+
}
|
|
6238
|
+
default:
|
|
6239
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
5802
6240
|
}
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6241
|
+
} catch (err) {
|
|
6242
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6243
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
6244
|
+
await this.close();
|
|
6245
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
5806
6246
|
}
|
|
5807
|
-
|
|
5808
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
6247
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
5809
6248
|
}
|
|
5810
6249
|
}
|
|
5811
6250
|
async close() {
|
|
5812
|
-
|
|
5813
|
-
|
|
6251
|
+
try {
|
|
6252
|
+
if (this.page) {
|
|
6253
|
+
await this.page.close().catch(() => {
|
|
6254
|
+
});
|
|
6255
|
+
this.page = null;
|
|
6256
|
+
}
|
|
6257
|
+
if (this.browser) {
|
|
6258
|
+
await this.browser.close().catch(() => {
|
|
6259
|
+
});
|
|
6260
|
+
this.browser = null;
|
|
6261
|
+
}
|
|
6262
|
+
} catch {
|
|
5814
6263
|
this.browser = null;
|
|
5815
6264
|
this.page = null;
|
|
5816
6265
|
}
|
|
@@ -5907,6 +6356,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
5907
6356
|
});
|
|
5908
6357
|
}
|
|
5909
6358
|
};
|
|
6359
|
+
function detectCommand(candidates2) {
|
|
6360
|
+
for (const cmd of candidates2) {
|
|
6361
|
+
try {
|
|
6362
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
6363
|
+
execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
6364
|
+
return cmd;
|
|
6365
|
+
} catch {
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6368
|
+
return null;
|
|
6369
|
+
}
|
|
6370
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
6371
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
5910
6372
|
var CodeInterpreterTool = class extends BaseTool {
|
|
5911
6373
|
name = "run_code";
|
|
5912
6374
|
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.";
|
|
@@ -5922,10 +6384,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5922
6384
|
isDangerous() {
|
|
5923
6385
|
return true;
|
|
5924
6386
|
}
|
|
5925
|
-
async execute(input,
|
|
6387
|
+
async execute(input, _options) {
|
|
5926
6388
|
const language = input["language"];
|
|
5927
6389
|
const code = input["code"];
|
|
5928
6390
|
const args = input["args"] ?? [];
|
|
6391
|
+
let cmdPrefix;
|
|
6392
|
+
if (language === "python") {
|
|
6393
|
+
if (!PYTHON_CMD) {
|
|
6394
|
+
return [
|
|
6395
|
+
"Error: Python interpreter not found.",
|
|
6396
|
+
"Please install Python and ensure it is in your PATH.",
|
|
6397
|
+
"Tried: python3, python"
|
|
6398
|
+
].join("\n");
|
|
6399
|
+
}
|
|
6400
|
+
cmdPrefix = PYTHON_CMD;
|
|
6401
|
+
} else {
|
|
6402
|
+
if (!NODE_CMD) {
|
|
6403
|
+
return [
|
|
6404
|
+
"Error: Node.js interpreter not found.",
|
|
6405
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
6406
|
+
"Tried: node"
|
|
6407
|
+
].join("\n");
|
|
6408
|
+
}
|
|
6409
|
+
cmdPrefix = NODE_CMD;
|
|
6410
|
+
}
|
|
5929
6411
|
const tmpDir = path17.join(process.cwd(), ".cascade", "tmp");
|
|
5930
6412
|
if (!fs14.existsSync(tmpDir)) {
|
|
5931
6413
|
fs14.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -5934,8 +6416,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5934
6416
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
5935
6417
|
const filePath = path17.join(tmpDir, fileName);
|
|
5936
6418
|
fs14.writeFileSync(filePath, code, "utf-8");
|
|
5937
|
-
const
|
|
5938
|
-
const
|
|
6419
|
+
const quotedPath = `"${filePath}"`;
|
|
6420
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
6421
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
5939
6422
|
return new Promise((resolve) => {
|
|
5940
6423
|
const startMs = Date.now();
|
|
5941
6424
|
exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -5948,10 +6431,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5948
6431
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
5949
6432
|
}
|
|
5950
6433
|
if (error) {
|
|
5951
|
-
|
|
6434
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
6435
|
+
if (timedOut) {
|
|
6436
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
6437
|
+
Partial stdout: ${stdout}
|
|
6438
|
+
Stderr: ${stderr}`);
|
|
6439
|
+
} else {
|
|
6440
|
+
resolve(`Execution failed (${duration}ms):
|
|
5952
6441
|
Error: ${error.message}
|
|
5953
6442
|
Stderr: ${stderr}
|
|
5954
6443
|
Stdout: ${stdout}`);
|
|
6444
|
+
}
|
|
5955
6445
|
} else {
|
|
5956
6446
|
resolve(`Execution successful (${duration}ms):
|
|
5957
6447
|
Stdout: ${stdout}
|
|
@@ -6016,6 +6506,186 @@ ${formatted}`;
|
|
|
6016
6506
|
}
|
|
6017
6507
|
};
|
|
6018
6508
|
|
|
6509
|
+
// src/tools/web-search.ts
|
|
6510
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
6511
|
+
const url = new URL("/search", baseUrl);
|
|
6512
|
+
url.searchParams.set("q", query);
|
|
6513
|
+
url.searchParams.set("format", "json");
|
|
6514
|
+
url.searchParams.set("categories", "general");
|
|
6515
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
6516
|
+
const resp = await fetch(url.toString(), {
|
|
6517
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
6518
|
+
signal: AbortSignal.timeout(1e4)
|
|
6519
|
+
});
|
|
6520
|
+
if (!resp.ok) {
|
|
6521
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
6522
|
+
}
|
|
6523
|
+
const data = await resp.json();
|
|
6524
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6525
|
+
title: r.title ?? "",
|
|
6526
|
+
url: r.url ?? "",
|
|
6527
|
+
snippet: r.content ?? "",
|
|
6528
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
6529
|
+
}));
|
|
6530
|
+
}
|
|
6531
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
6532
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
6533
|
+
const resp = await fetch(url, {
|
|
6534
|
+
headers: {
|
|
6535
|
+
"Accept": "application/json",
|
|
6536
|
+
"Accept-Encoding": "gzip",
|
|
6537
|
+
"X-Subscription-Token": apiKey
|
|
6538
|
+
},
|
|
6539
|
+
signal: AbortSignal.timeout(1e4)
|
|
6540
|
+
});
|
|
6541
|
+
if (!resp.ok) {
|
|
6542
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
6543
|
+
}
|
|
6544
|
+
const data = await resp.json();
|
|
6545
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6546
|
+
title: r.title ?? "",
|
|
6547
|
+
url: r.url ?? "",
|
|
6548
|
+
snippet: r.description ?? "",
|
|
6549
|
+
engine: "brave"
|
|
6550
|
+
}));
|
|
6551
|
+
}
|
|
6552
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
6553
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
6554
|
+
method: "POST",
|
|
6555
|
+
headers: {
|
|
6556
|
+
"Content-Type": "application/json",
|
|
6557
|
+
"Authorization": `Bearer ${apiKey}`
|
|
6558
|
+
},
|
|
6559
|
+
body: JSON.stringify({
|
|
6560
|
+
query,
|
|
6561
|
+
max_results: maxResults,
|
|
6562
|
+
search_depth: "basic",
|
|
6563
|
+
include_answer: false,
|
|
6564
|
+
include_raw_content: false
|
|
6565
|
+
}),
|
|
6566
|
+
signal: AbortSignal.timeout(15e3)
|
|
6567
|
+
});
|
|
6568
|
+
if (!resp.ok) {
|
|
6569
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
6570
|
+
}
|
|
6571
|
+
const data = await resp.json();
|
|
6572
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6573
|
+
title: r.title ?? "",
|
|
6574
|
+
url: r.url ?? "",
|
|
6575
|
+
snippet: r.content ?? "",
|
|
6576
|
+
engine: "tavily"
|
|
6577
|
+
}));
|
|
6578
|
+
}
|
|
6579
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
6580
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
6581
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
6582
|
+
signal: AbortSignal.timeout(1e4)
|
|
6583
|
+
});
|
|
6584
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
6585
|
+
const html = await resp.text();
|
|
6586
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
6587
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
6588
|
+
const links = [];
|
|
6589
|
+
const snippets = [];
|
|
6590
|
+
let m;
|
|
6591
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
6592
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
6593
|
+
}
|
|
6594
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
6595
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
6596
|
+
}
|
|
6597
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
6598
|
+
title: link.title,
|
|
6599
|
+
url: link.url,
|
|
6600
|
+
snippet: snippets[i] ?? "",
|
|
6601
|
+
engine: "duckduckgo-lite"
|
|
6602
|
+
}));
|
|
6603
|
+
}
|
|
6604
|
+
var WebSearchTool = class extends BaseTool {
|
|
6605
|
+
name = "web_search";
|
|
6606
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
6607
|
+
inputSchema = {
|
|
6608
|
+
type: "object",
|
|
6609
|
+
properties: {
|
|
6610
|
+
query: { type: "string", description: "The search query" },
|
|
6611
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
6612
|
+
},
|
|
6613
|
+
required: ["query"]
|
|
6614
|
+
};
|
|
6615
|
+
config;
|
|
6616
|
+
constructor(config = {}) {
|
|
6617
|
+
super();
|
|
6618
|
+
this.config = {
|
|
6619
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
6620
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
6621
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
6622
|
+
maxResults: config.maxResults ?? 5
|
|
6623
|
+
};
|
|
6624
|
+
}
|
|
6625
|
+
async execute(input, _options) {
|
|
6626
|
+
const query = input["query"];
|
|
6627
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
6628
|
+
const maxResults = Math.min(
|
|
6629
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
6630
|
+
10
|
|
6631
|
+
);
|
|
6632
|
+
const errors = [];
|
|
6633
|
+
let results = [];
|
|
6634
|
+
if (this.config.searxngUrl) {
|
|
6635
|
+
try {
|
|
6636
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
6637
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6638
|
+
errors.push("SearXNG: returned 0 results");
|
|
6639
|
+
} catch (err) {
|
|
6640
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
if (this.config.braveApiKey) {
|
|
6644
|
+
try {
|
|
6645
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
6646
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6647
|
+
errors.push("Brave: returned 0 results");
|
|
6648
|
+
} catch (err) {
|
|
6649
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
if (this.config.tavilyApiKey) {
|
|
6653
|
+
try {
|
|
6654
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
6655
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6656
|
+
errors.push("Tavily: returned 0 results");
|
|
6657
|
+
} catch (err) {
|
|
6658
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
try {
|
|
6662
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
6663
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6664
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
6665
|
+
} catch (err) {
|
|
6666
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
6667
|
+
}
|
|
6668
|
+
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" : "";
|
|
6669
|
+
return [
|
|
6670
|
+
`Web search for "${query}" failed across all backends:`,
|
|
6671
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
6672
|
+
configHint
|
|
6673
|
+
].join("\n");
|
|
6674
|
+
}
|
|
6675
|
+
formatResults(query, results) {
|
|
6676
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
6677
|
+
for (let i = 0; i < results.length; i++) {
|
|
6678
|
+
const r = results[i];
|
|
6679
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
6680
|
+
lines.push(` URL: ${r.url}`);
|
|
6681
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
6682
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
6683
|
+
lines.push("");
|
|
6684
|
+
}
|
|
6685
|
+
return lines.join("\n");
|
|
6686
|
+
}
|
|
6687
|
+
};
|
|
6688
|
+
|
|
6019
6689
|
// src/tools/mcp.ts
|
|
6020
6690
|
var McpToolWrapper = class extends BaseTool {
|
|
6021
6691
|
name;
|
|
@@ -6137,7 +6807,8 @@ var ToolRegistry = class {
|
|
|
6137
6807
|
new ImageAnalyzeTool(),
|
|
6138
6808
|
new PDFCreateTool(),
|
|
6139
6809
|
new CodeInterpreterTool(),
|
|
6140
|
-
new PeerCommunicationTool()
|
|
6810
|
+
new PeerCommunicationTool(),
|
|
6811
|
+
new WebSearchTool(this.config.webSearch)
|
|
6141
6812
|
];
|
|
6142
6813
|
for (const tool of tools) {
|
|
6143
6814
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -6161,8 +6832,23 @@ var ToolRegistry = class {
|
|
|
6161
6832
|
return this.ignoreMatcher.ignores(posixRel);
|
|
6162
6833
|
}
|
|
6163
6834
|
};
|
|
6164
|
-
var McpClient = class {
|
|
6835
|
+
var McpClient = class _McpClient {
|
|
6836
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
6837
|
+
/**
|
|
6838
|
+
* Forcefully kills all known MCP child processes.
|
|
6839
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
6840
|
+
*/
|
|
6841
|
+
static killAllProcesses() {
|
|
6842
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
6843
|
+
try {
|
|
6844
|
+
process.kill(pid, "SIGKILL");
|
|
6845
|
+
} catch {
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
_McpClient.activeProcessPids.clear();
|
|
6849
|
+
}
|
|
6165
6850
|
clients = /* @__PURE__ */ new Map();
|
|
6851
|
+
transports = /* @__PURE__ */ new Map();
|
|
6166
6852
|
tools = /* @__PURE__ */ new Map();
|
|
6167
6853
|
trustedServers;
|
|
6168
6854
|
approvalCallback;
|
|
@@ -6191,6 +6877,8 @@ var McpClient = class {
|
|
|
6191
6877
|
);
|
|
6192
6878
|
await client.connect(transport);
|
|
6193
6879
|
this.clients.set(server.name, client);
|
|
6880
|
+
this.transports.set(server.name, transport);
|
|
6881
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
6194
6882
|
const toolsResult = await client.listTools();
|
|
6195
6883
|
for (const tool of toolsResult.tools) {
|
|
6196
6884
|
for (const existing of this.tools.values()) {
|
|
@@ -6212,8 +6900,11 @@ var McpClient = class {
|
|
|
6212
6900
|
async disconnect(serverName) {
|
|
6213
6901
|
const client = this.clients.get(serverName);
|
|
6214
6902
|
if (client) {
|
|
6903
|
+
const transport = this.transports.get(serverName);
|
|
6904
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
6215
6905
|
await client.close();
|
|
6216
6906
|
this.clients.delete(serverName);
|
|
6907
|
+
this.transports.delete(serverName);
|
|
6217
6908
|
for (const key of this.tools.keys()) {
|
|
6218
6909
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
6219
6910
|
}
|
|
@@ -6241,6 +6932,13 @@ var McpClient = class {
|
|
|
6241
6932
|
getConnectedServers() {
|
|
6242
6933
|
return Array.from(this.clients.keys());
|
|
6243
6934
|
}
|
|
6935
|
+
getActivePids() {
|
|
6936
|
+
const pids = [];
|
|
6937
|
+
for (const transport of this.transports.values()) {
|
|
6938
|
+
if (transport.pid) pids.push(transport.pid);
|
|
6939
|
+
}
|
|
6940
|
+
return pids;
|
|
6941
|
+
}
|
|
6244
6942
|
isConnected(serverName) {
|
|
6245
6943
|
return this.clients.has(serverName);
|
|
6246
6944
|
}
|
|
@@ -6809,12 +7507,25 @@ var Cascade = class extends EventEmitter {
|
|
|
6809
7507
|
looksLikeSimpleArtifactTask(prompt) {
|
|
6810
7508
|
return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
|
|
6811
7509
|
}
|
|
6812
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
7510
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
6813
7511
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
6814
7512
|
return "Simple";
|
|
6815
7513
|
}
|
|
7514
|
+
let workspaceContext = "";
|
|
7515
|
+
try {
|
|
7516
|
+
const files = await glob("**/*.*", {
|
|
7517
|
+
cwd: workspacePath,
|
|
7518
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
7519
|
+
nodir: true
|
|
7520
|
+
});
|
|
7521
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
7522
|
+
} catch {
|
|
7523
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
7524
|
+
}
|
|
6816
7525
|
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.
|
|
6817
7526
|
|
|
7527
|
+
${workspaceContext}
|
|
7528
|
+
|
|
6818
7529
|
Classification:
|
|
6819
7530
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
6820
7531
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -6889,7 +7600,7 @@ ${prompt}` : prompt;
|
|
|
6889
7600
|
}
|
|
6890
7601
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
6891
7602
|
});
|
|
6892
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
7603
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
6893
7604
|
this.telemetry.capture("cascade:session_start", {
|
|
6894
7605
|
complexity,
|
|
6895
7606
|
providerCount: this.config.providers.length,
|
|
@@ -6969,7 +7680,7 @@ ${prompt}` : prompt;
|
|
|
6969
7680
|
peerT3Ids: [],
|
|
6970
7681
|
parentT2: "root"
|
|
6971
7682
|
};
|
|
6972
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
7683
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
6973
7684
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
6974
7685
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
6975
7686
|
} else if (complexity === "Moderate") {
|
|
@@ -6992,7 +7703,7 @@ ${prompt}` : prompt;
|
|
|
6992
7703
|
constraints: [],
|
|
6993
7704
|
t3Subtasks: []
|
|
6994
7705
|
};
|
|
6995
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
7706
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
6996
7707
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
6997
7708
|
t2Results = [t2Result];
|
|
6998
7709
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -7014,13 +7725,22 @@ ${prompt}` : prompt;
|
|
|
7014
7725
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
7015
7726
|
bindTierEvents(t1);
|
|
7016
7727
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
7017
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
7728
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
7018
7729
|
finalOutput = result.output;
|
|
7019
7730
|
t2Results = result.t2Results;
|
|
7020
7731
|
}
|
|
7021
7732
|
} catch (err) {
|
|
7022
|
-
|
|
7023
|
-
|
|
7733
|
+
if (err instanceof CascadeCancelledError) {
|
|
7734
|
+
this.emit("run:cancelled", {
|
|
7735
|
+
taskId,
|
|
7736
|
+
reason: err.message,
|
|
7737
|
+
partialOutput: finalOutput || ""
|
|
7738
|
+
});
|
|
7739
|
+
runError = null;
|
|
7740
|
+
} else {
|
|
7741
|
+
runError = err;
|
|
7742
|
+
throw err;
|
|
7743
|
+
}
|
|
7024
7744
|
} finally {
|
|
7025
7745
|
try {
|
|
7026
7746
|
escalator.cancelAllPending();
|
|
@@ -7755,6 +8475,9 @@ var ModelsDisplay = ({
|
|
|
7755
8475
|
});
|
|
7756
8476
|
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}`;
|
|
7757
8477
|
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}`;
|
|
8478
|
+
const PAGE_SIZE = 8;
|
|
8479
|
+
const viewStart = Math.max(0, Math.min(cursor - Math.floor(PAGE_SIZE / 2), currentItems.length - PAGE_SIZE));
|
|
8480
|
+
const visibleItems = currentItems.slice(viewStart, viewStart + PAGE_SIZE);
|
|
7758
8481
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
7759
8482
|
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
7760
8483
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: title }),
|
|
@@ -7764,16 +8487,29 @@ var ModelsDisplay = ({
|
|
|
7764
8487
|
breadcrumb,
|
|
7765
8488
|
" \xB7 \u2191/\u2193 navigate \xB7 1\u20139 jump"
|
|
7766
8489
|
] }) }),
|
|
7767
|
-
currentItems.length === 0 ? /* @__PURE__ */ jsx(Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
8490
|
+
currentItems.length === 0 ? /* @__PURE__ */ jsx(Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
8491
|
+
viewStart > 0 && /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
|
|
8492
|
+
" \u2191 ",
|
|
8493
|
+
viewStart,
|
|
8494
|
+
" more above"
|
|
8495
|
+
] }),
|
|
8496
|
+
visibleItems.map((item, i) => {
|
|
8497
|
+
const globalIdx = viewStart + i;
|
|
8498
|
+
const focused = globalIdx === cursor;
|
|
8499
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
8500
|
+
/* @__PURE__ */ jsx(Text, { color: focused ? "green" : "gray", children: focused ? "\u276F " : " " }),
|
|
8501
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
8502
|
+
/* @__PURE__ */ jsx(Text, { color: focused ? "white" : "gray", bold: focused, children: item.label }),
|
|
8503
|
+
item.sublabel && /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: ` ${item.sublabel}` })
|
|
8504
|
+
] })
|
|
8505
|
+
] }, `${step}-${item.value}-${globalIdx}`);
|
|
8506
|
+
}),
|
|
8507
|
+
viewStart + PAGE_SIZE < currentItems.length && /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
|
|
8508
|
+
" \u2193 ",
|
|
8509
|
+
currentItems.length - viewStart - PAGE_SIZE,
|
|
8510
|
+
" more below"
|
|
8511
|
+
] })
|
|
8512
|
+
] })
|
|
7777
8513
|
] });
|
|
7778
8514
|
};
|
|
7779
8515
|
function CostTracker({
|
|
@@ -7967,13 +8703,18 @@ function replReducer(state, action) {
|
|
|
7967
8703
|
async function refreshModelCache(store, providers) {
|
|
7968
8704
|
for (const provider of providers) {
|
|
7969
8705
|
try {
|
|
7970
|
-
const
|
|
8706
|
+
const dummyId = provider.type === "azure" ? provider.deploymentName || "azure-model" : "dummy";
|
|
8707
|
+
const dummyModel = { id: dummyId, name: dummyId, provider: provider.type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
7971
8708
|
let instance;
|
|
7972
8709
|
if (provider.type === "openai") instance = new OpenAIProvider(provider, dummyModel);
|
|
7973
8710
|
else if (provider.type === "gemini") instance = new GeminiProvider(provider, dummyModel);
|
|
7974
8711
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
7975
8712
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
7976
8713
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
8714
|
+
else if (provider.type === "azure") {
|
|
8715
|
+
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
8716
|
+
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
8717
|
+
}
|
|
7977
8718
|
if (instance) {
|
|
7978
8719
|
const fetched = await instance.listModels();
|
|
7979
8720
|
for (const m of fetched) store.upsertCachedModel(m);
|
|
@@ -8111,7 +8852,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
|
|
|
8111
8852
|
};
|
|
8112
8853
|
const store = new MemoryStore(path17.join(workspacePath, CASCADE_DB_FILE));
|
|
8113
8854
|
storeRef.current = store;
|
|
8114
|
-
globalStoreRef.current = new MemoryStore(path17.join(
|
|
8855
|
+
globalStoreRef.current = new MemoryStore(path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
8115
8856
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
8116
8857
|
setIdentities(identityRows);
|
|
8117
8858
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -8596,14 +9337,29 @@ Use /identity <name|id> to switch.`;
|
|
|
8596
9337
|
const isAutoScrollingRef = useRef(true);
|
|
8597
9338
|
const width = stdout?.columns ?? 100;
|
|
8598
9339
|
const height = stdout?.rows ?? 24;
|
|
8599
|
-
const
|
|
9340
|
+
const hasActiveOrFailed2 = (node) => {
|
|
9341
|
+
if (node.status === "ACTIVE" || node.status === "FAILED") return true;
|
|
9342
|
+
return node.children?.some(hasActiveOrFailed2) ?? false;
|
|
9343
|
+
};
|
|
9344
|
+
let agentTreeHeight = 0;
|
|
9345
|
+
if (state.agentTree && hasActiveOrFailed2(state.agentTree)) {
|
|
9346
|
+
agentTreeHeight = 1;
|
|
9347
|
+
const childrenCount = state.agentTree.children?.length ?? 0;
|
|
9348
|
+
agentTreeHeight += Math.min(childrenCount, 6);
|
|
9349
|
+
if (childrenCount > 6) agentTreeHeight += 1;
|
|
9350
|
+
}
|
|
9351
|
+
let timelineHeight = 0;
|
|
9352
|
+
if (state.showDetails && treeNodesRef.current.size > 0) {
|
|
9353
|
+
timelineHeight = 1;
|
|
9354
|
+
timelineHeight += Math.min(3, treeNodesRef.current.size);
|
|
9355
|
+
}
|
|
9356
|
+
const statusHeight = agentTreeHeight + timelineHeight;
|
|
8600
9357
|
const costHeight = state.showCost ? 6 : 0;
|
|
8601
9358
|
const approvalHeight = state.approvalRequest ? 12 : 0;
|
|
8602
9359
|
const slashVisibleCount = Math.min(SLASH_PAGE_SIZE, slashEntries.length);
|
|
8603
9360
|
const slashHeight = slashVisibleCount > 0 ? slashVisibleCount + 2 : 0;
|
|
8604
9361
|
const chromeHeight = statusHeight + costHeight + approvalHeight + slashHeight + 7;
|
|
8605
|
-
const
|
|
8606
|
-
const availableHeight = Math.max(4, totalCap - chromeHeight);
|
|
9362
|
+
const availableHeight = Math.max(4, height - chromeHeight);
|
|
8607
9363
|
const allLines = formatToLines(
|
|
8608
9364
|
state.isStreaming ? [...state.messages, { id: "stream", role: "assistant", content: state.streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }] : state.messages,
|
|
8609
9365
|
width - 4,
|
|
@@ -8984,10 +9740,14 @@ function wizardReducer(state, action) {
|
|
|
8984
9740
|
return { ...state, currentEntryIdx: next };
|
|
8985
9741
|
}
|
|
8986
9742
|
case "ADD_AZURE": {
|
|
9743
|
+
const prevAzure = state.entries.find((e) => e.type === "azure");
|
|
8987
9744
|
const newEntry = {
|
|
8988
9745
|
id: randomUUID(),
|
|
8989
9746
|
type: "azure",
|
|
8990
|
-
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}
|
|
9747
|
+
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}`,
|
|
9748
|
+
baseUrl: prevAzure?.baseUrl,
|
|
9749
|
+
apiKey: prevAzure?.apiKey,
|
|
9750
|
+
apiVersion: prevAzure?.apiVersion
|
|
8991
9751
|
};
|
|
8992
9752
|
return {
|
|
8993
9753
|
...state,
|
|
@@ -9096,8 +9856,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9096
9856
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
9097
9857
|
} else if (type === "azure") {
|
|
9098
9858
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
9099
|
-
const
|
|
9100
|
-
const
|
|
9859
|
+
const actualModelId = deploymentName || `azure-${entry.id}`;
|
|
9860
|
+
const dummyModel = { id: actualModelId, name: actualModelId, provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
9861
|
+
const p = new AzureOpenAIProvider2({ type, apiKey, baseUrl, deploymentName, apiVersion: entry.apiVersion }, dummyModel);
|
|
9101
9862
|
const fetched = await p.listModels();
|
|
9102
9863
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
9103
9864
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
@@ -9118,7 +9879,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9118
9879
|
type: e.type,
|
|
9119
9880
|
...e.apiKey ? { apiKey: e.apiKey } : {},
|
|
9120
9881
|
...e.baseUrl ? { baseUrl: e.baseUrl } : {},
|
|
9121
|
-
...e.deploymentName ? { deploymentName: e.deploymentName } : {}
|
|
9882
|
+
...e.deploymentName ? { deploymentName: e.deploymentName } : {},
|
|
9883
|
+
...e.apiVersion ? { apiVersion: e.apiVersion } : {}
|
|
9122
9884
|
}));
|
|
9123
9885
|
const models = {};
|
|
9124
9886
|
if (state.tierT1 !== "auto") models["t1"] = state.tierT1;
|
|
@@ -9148,17 +9910,17 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9148
9910
|
if (key.return) {
|
|
9149
9911
|
if (state.selectedTypes.size === 0) return;
|
|
9150
9912
|
dispatch({ type: "CONFIRM_PROVIDERS" });
|
|
9151
|
-
|
|
9913
|
+
const firstType = [...state.selectedTypes][0];
|
|
9914
|
+
setFieldStage(firstType === "azure" ? "deploymentName" : firstType === "openai-compatible" ? "label" : firstType === "ollama" ? "baseUrl" : "apiKey");
|
|
9152
9915
|
setFieldBuffer("");
|
|
9153
9916
|
}
|
|
9154
9917
|
}
|
|
9155
9918
|
if (state.step === "TIER_ASSIGN") {
|
|
9156
|
-
if (key.tab || key.
|
|
9919
|
+
if (key.tab || key.rightArrow) {
|
|
9157
9920
|
const order = ["T1", "T2", "T3"];
|
|
9158
9921
|
const idx = order.indexOf(state.tierSelectFocus);
|
|
9159
9922
|
dispatch({ type: "SET_TIER_FOCUS", tier: order[(idx + 1) % 3] });
|
|
9160
9923
|
}
|
|
9161
|
-
if (key.return) dispatch({ type: "GO_SAVE" });
|
|
9162
9924
|
}
|
|
9163
9925
|
});
|
|
9164
9926
|
const currentEntry = state.entries[state.currentEntryIdx];
|
|
@@ -9168,7 +9930,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9168
9930
|
if (fieldStage === "deploymentName") {
|
|
9169
9931
|
dispatch({ type: "SET_ENTRY_FIELD", field: "deploymentName", value: val });
|
|
9170
9932
|
setFieldBuffer("");
|
|
9171
|
-
|
|
9933
|
+
if (currentEntry.baseUrl && currentEntry.apiKey && currentEntry.apiVersion) {
|
|
9934
|
+
setFieldStage("askMore");
|
|
9935
|
+
} else {
|
|
9936
|
+
setFieldStage("baseUrl");
|
|
9937
|
+
}
|
|
9172
9938
|
} else if (fieldStage === "baseUrl") {
|
|
9173
9939
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
|
|
9174
9940
|
setFieldBuffer("");
|
|
@@ -9176,6 +9942,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9176
9942
|
} else if (fieldStage === "apiKey") {
|
|
9177
9943
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9178
9944
|
setFieldBuffer("");
|
|
9945
|
+
setFieldStage("apiVersion");
|
|
9946
|
+
} else if (fieldStage === "apiVersion") {
|
|
9947
|
+
dispatch({ type: "SET_ENTRY_FIELD", field: "apiVersion", value: val || "2024-08-01-preview" });
|
|
9948
|
+
setFieldBuffer("");
|
|
9179
9949
|
setFieldStage("askMore");
|
|
9180
9950
|
}
|
|
9181
9951
|
} else if (currentEntry.type === "openai-compatible") {
|
|
@@ -9195,13 +9965,19 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9195
9965
|
} else if (currentEntry.type === "ollama") {
|
|
9196
9966
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
|
|
9197
9967
|
setFieldBuffer("");
|
|
9968
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
9969
|
+
if (nextEntry) {
|
|
9970
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
9971
|
+
}
|
|
9198
9972
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9199
|
-
setFieldStage("apiKey");
|
|
9200
9973
|
} else {
|
|
9201
9974
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9202
9975
|
setFieldBuffer("");
|
|
9976
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
9977
|
+
if (nextEntry) {
|
|
9978
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
9979
|
+
}
|
|
9203
9980
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9204
|
-
setFieldStage("apiKey");
|
|
9205
9981
|
}
|
|
9206
9982
|
}, [currentEntry, fieldStage]);
|
|
9207
9983
|
if (state.step === "PROVIDER_SELECT") {
|
|
@@ -9252,8 +10028,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9252
10028
|
setFieldStage(isAzure ? "deploymentName" : "label");
|
|
9253
10029
|
setFieldBuffer("");
|
|
9254
10030
|
} else {
|
|
10031
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10032
|
+
if (nextEntry) {
|
|
10033
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10034
|
+
}
|
|
9255
10035
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9256
|
-
setFieldStage("apiKey");
|
|
9257
10036
|
setFieldBuffer("");
|
|
9258
10037
|
}
|
|
9259
10038
|
}
|
|
@@ -9334,7 +10113,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9334
10113
|
SelectInput,
|
|
9335
10114
|
{
|
|
9336
10115
|
items: modelOptions,
|
|
9337
|
-
onSelect: (item) =>
|
|
10116
|
+
onSelect: (item) => {
|
|
10117
|
+
dispatch({ type: "SET_TIER", tier, value: item.value });
|
|
10118
|
+
const order = ["T1", "T2", "T3"];
|
|
10119
|
+
const idx = order.indexOf(tier);
|
|
10120
|
+
if (idx < 2) {
|
|
10121
|
+
dispatch({ type: "SET_TIER_FOCUS", tier: order[idx + 1] });
|
|
10122
|
+
} else {
|
|
10123
|
+
dispatch({ type: "GO_SAVE" });
|
|
10124
|
+
}
|
|
10125
|
+
},
|
|
9338
10126
|
indicatorComponent: ({ isSelected }) => /* @__PURE__ */ jsx(Text, { color: "magenta", children: isSelected ? "\u276F " : " " }),
|
|
9339
10127
|
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ jsx(Text, { color: isSelected ? "magenta" : "white", children: label })
|
|
9340
10128
|
}
|
|
@@ -9812,7 +10600,7 @@ var DashboardServer = class {
|
|
|
9812
10600
|
// ── Setup ─────────────────────────────────────
|
|
9813
10601
|
getGlobalStore() {
|
|
9814
10602
|
if (!this.globalStore) {
|
|
9815
|
-
const globalDbPath = path17.join(
|
|
10603
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9816
10604
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
9817
10605
|
}
|
|
9818
10606
|
return this.globalStore;
|
|
@@ -9874,7 +10662,7 @@ var DashboardServer = class {
|
|
|
9874
10662
|
}
|
|
9875
10663
|
watchRuntimeChanges() {
|
|
9876
10664
|
const workspaceDbPath = path17.join(this.workspacePath, CASCADE_DB_FILE);
|
|
9877
|
-
const globalDbPath = path17.join(
|
|
10665
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9878
10666
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
9879
10667
|
for (const watchPath of watchPaths) {
|
|
9880
10668
|
if (!fs14.existsSync(watchPath)) continue;
|
|
@@ -9982,7 +10770,7 @@ var DashboardServer = class {
|
|
|
9982
10770
|
const sessionId = req.params.id;
|
|
9983
10771
|
this.store.deleteSession(sessionId);
|
|
9984
10772
|
this.store.deleteRuntimeSession(sessionId);
|
|
9985
|
-
const globalDbPath = path17.join(
|
|
10773
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9986
10774
|
const globalStore = new MemoryStore(globalDbPath);
|
|
9987
10775
|
try {
|
|
9988
10776
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -9996,7 +10784,7 @@ var DashboardServer = class {
|
|
|
9996
10784
|
});
|
|
9997
10785
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
9998
10786
|
const body = req.body;
|
|
9999
|
-
const globalDbPath = path17.join(
|
|
10787
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10000
10788
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
10001
10789
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10002
10790
|
try {
|
|
@@ -10019,7 +10807,7 @@ var DashboardServer = class {
|
|
|
10019
10807
|
});
|
|
10020
10808
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
10021
10809
|
this.store.deleteAllRuntimeNodes();
|
|
10022
|
-
const globalDbPath = path17.join(
|
|
10810
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10023
10811
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10024
10812
|
try {
|
|
10025
10813
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -10115,7 +10903,7 @@ var DashboardServer = class {
|
|
|
10115
10903
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
10116
10904
|
const scope = req.query["scope"] ?? "workspace";
|
|
10117
10905
|
if (scope === "global") {
|
|
10118
|
-
const globalDbPath = path17.join(
|
|
10906
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10119
10907
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10120
10908
|
try {
|
|
10121
10909
|
res.json({
|
|
@@ -10365,7 +11153,15 @@ async function exportCommand(options = {}) {
|
|
|
10365
11153
|
const spin = ora({ text: "Loading sessions\u2026", color: "magenta" }).start();
|
|
10366
11154
|
let store;
|
|
10367
11155
|
try {
|
|
10368
|
-
const
|
|
11156
|
+
const workspacePath = options.workspacePath ?? process.cwd();
|
|
11157
|
+
const workspaceDbPath = path17.join(workspacePath, CASCADE_DB_FILE);
|
|
11158
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
11159
|
+
let dbPath = globalDbPath;
|
|
11160
|
+
try {
|
|
11161
|
+
await fs7.access(workspaceDbPath);
|
|
11162
|
+
dbPath = workspaceDbPath;
|
|
11163
|
+
} catch {
|
|
11164
|
+
}
|
|
10369
11165
|
store = new MemoryStore(dbPath);
|
|
10370
11166
|
} catch (err) {
|
|
10371
11167
|
spin.fail(chalk8.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -10504,6 +11300,15 @@ async function telemetryCommand(action) {
|
|
|
10504
11300
|
|
|
10505
11301
|
// src/cli/index.ts
|
|
10506
11302
|
dotenv.config();
|
|
11303
|
+
process.on("exit", () => McpClient.killAllProcesses());
|
|
11304
|
+
process.on("SIGINT", () => {
|
|
11305
|
+
McpClient.killAllProcesses();
|
|
11306
|
+
process.exit(0);
|
|
11307
|
+
});
|
|
11308
|
+
process.on("SIGTERM", () => {
|
|
11309
|
+
McpClient.killAllProcesses();
|
|
11310
|
+
process.exit(0);
|
|
11311
|
+
});
|
|
10507
11312
|
var program = new Command();
|
|
10508
11313
|
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) => {
|
|
10509
11314
|
await startRepl(options);
|