cascade-ai 0.2.2 → 0.2.12
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 +1222 -375
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1222 -375
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1100 -332
- 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 +1099 -333
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
package/dist/cli.cjs
CHANGED
|
@@ -11,7 +11,7 @@ var chalk8 = require('chalk');
|
|
|
11
11
|
var dotenv = require('dotenv');
|
|
12
12
|
var fs7 = require('fs/promises');
|
|
13
13
|
var path17 = require('path');
|
|
14
|
-
var
|
|
14
|
+
var os3 = require('os');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var fs14 = require('fs');
|
|
17
17
|
var _ignoreModule = require('ignore');
|
|
@@ -20,6 +20,7 @@ var zod = require('zod');
|
|
|
20
20
|
var child_process = require('child_process');
|
|
21
21
|
var jsxRuntime = require('react/jsx-runtime');
|
|
22
22
|
var EventEmitter = require('events');
|
|
23
|
+
var glob = require('glob');
|
|
23
24
|
var util = require('util');
|
|
24
25
|
var simpleGit = require('simple-git');
|
|
25
26
|
var PDFDocument = require('pdfkit');
|
|
@@ -68,7 +69,7 @@ var chalk8__default = /*#__PURE__*/_interopDefault(chalk8);
|
|
|
68
69
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
69
70
|
var fs7__default = /*#__PURE__*/_interopDefault(fs7);
|
|
70
71
|
var path17__default = /*#__PURE__*/_interopDefault(path17);
|
|
71
|
-
var
|
|
72
|
+
var os3__default = /*#__PURE__*/_interopDefault(os3);
|
|
72
73
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
73
74
|
var fs14__default = /*#__PURE__*/_interopDefault(fs14);
|
|
74
75
|
var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
|
|
@@ -129,7 +130,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
129
130
|
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
130
131
|
var init_constants = __esm({
|
|
131
132
|
"src/constants.ts"() {
|
|
132
|
-
CASCADE_VERSION = "0.2.
|
|
133
|
+
CASCADE_VERSION = "0.2.12";
|
|
133
134
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
134
135
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
135
136
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -424,7 +425,8 @@ var init_constants = __esm({
|
|
|
424
425
|
IMAGE_ANALYZE: "image_analyze",
|
|
425
426
|
PDF_CREATE: "pdf_create",
|
|
426
427
|
RUN_CODE: "run_code",
|
|
427
|
-
PEER_MESSAGE: "peer_message"
|
|
428
|
+
PEER_MESSAGE: "peer_message",
|
|
429
|
+
WEB_SEARCH: "web_search"
|
|
428
430
|
};
|
|
429
431
|
DEFAULT_APPROVAL_REQUIRED = [
|
|
430
432
|
TOOL_NAMES.SHELL,
|
|
@@ -567,17 +569,38 @@ var init_anthropic = __esm({
|
|
|
567
569
|
messages,
|
|
568
570
|
tools: tools?.length ? tools : void 0
|
|
569
571
|
});
|
|
572
|
+
let isThinking = false;
|
|
570
573
|
for await (const event of stream) {
|
|
571
|
-
if (event.type === "content_block_delta"
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
574
|
+
if (event.type === "content_block_delta") {
|
|
575
|
+
if (event.delta.type === "thinking_delta") {
|
|
576
|
+
if (!isThinking) {
|
|
577
|
+
isThinking = true;
|
|
578
|
+
fullContent += "<think>\n";
|
|
579
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
580
|
+
}
|
|
581
|
+
const text = event.delta.thinking;
|
|
582
|
+
fullContent += text;
|
|
583
|
+
onChunk({ text, finishReason: null });
|
|
584
|
+
} else if (event.delta.type === "text_delta") {
|
|
585
|
+
if (isThinking) {
|
|
586
|
+
isThinking = false;
|
|
587
|
+
fullContent += "\n</think>\n\n";
|
|
588
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
589
|
+
}
|
|
590
|
+
const text = event.delta.text;
|
|
591
|
+
fullContent += text;
|
|
592
|
+
onChunk({ text, finishReason: null });
|
|
593
|
+
}
|
|
575
594
|
} else if (event.type === "message_delta" && event.usage) {
|
|
576
595
|
outputTokens = event.usage.output_tokens;
|
|
577
596
|
} else if (event.type === "message_start" && event.message.usage) {
|
|
578
597
|
inputTokens = event.message.usage.input_tokens;
|
|
579
598
|
}
|
|
580
599
|
}
|
|
600
|
+
if (isThinking) {
|
|
601
|
+
fullContent += "\n</think>\n\n";
|
|
602
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
603
|
+
}
|
|
581
604
|
const finalMessage = await stream.finalMessage();
|
|
582
605
|
const toolCalls = finalMessage.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
583
606
|
id: b.id,
|
|
@@ -638,33 +661,61 @@ var init_anthropic = __esm({
|
|
|
638
661
|
}
|
|
639
662
|
}
|
|
640
663
|
convertMessages(messages) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
664
|
+
const result = [];
|
|
665
|
+
for (const m of messages) {
|
|
666
|
+
if (m.role === "system") continue;
|
|
667
|
+
if (m.role === "tool") {
|
|
668
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
669
|
+
result.push({
|
|
670
|
+
role: "user",
|
|
671
|
+
content: [{
|
|
672
|
+
type: "tool_result",
|
|
673
|
+
tool_use_id: m.toolCallId ?? "",
|
|
674
|
+
content: toolContent
|
|
675
|
+
}]
|
|
676
|
+
});
|
|
677
|
+
continue;
|
|
644
678
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
679
|
+
if (m.role === "assistant") {
|
|
680
|
+
const content = [];
|
|
681
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
682
|
+
if (text) content.push({ type: "text", text });
|
|
683
|
+
for (const tc of m.toolCalls ?? []) {
|
|
684
|
+
content.push({
|
|
685
|
+
type: "tool_use",
|
|
686
|
+
id: tc.id,
|
|
687
|
+
name: tc.name,
|
|
688
|
+
input: tc.input
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
if (content.length > 0) {
|
|
692
|
+
result.push({ role: "assistant", content });
|
|
693
|
+
}
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (m.role === "user") {
|
|
697
|
+
if (typeof m.content === "string") {
|
|
698
|
+
result.push({ role: "user", content: m.content });
|
|
699
|
+
} else {
|
|
700
|
+
const content = m.content.map((block) => {
|
|
701
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
702
|
+
if (block.type === "image") {
|
|
703
|
+
const img = block.image;
|
|
704
|
+
if (img.type === "base64") {
|
|
705
|
+
return {
|
|
706
|
+
type: "image",
|
|
707
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
708
|
+
};
|
|
656
709
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
};
|
|
710
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
711
|
+
}
|
|
712
|
+
return { type: "text", text: "" };
|
|
713
|
+
});
|
|
714
|
+
result.push({ role: "user", content });
|
|
663
715
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return result;
|
|
668
719
|
}
|
|
669
720
|
};
|
|
670
721
|
}
|
|
@@ -733,9 +784,25 @@ var init_openai = __esm({
|
|
|
733
784
|
}
|
|
734
785
|
}
|
|
735
786
|
const toolCallsMap = {};
|
|
787
|
+
let isThinking = false;
|
|
736
788
|
for await (const chunk of stream) {
|
|
737
789
|
const delta = chunk.choices[0]?.delta;
|
|
790
|
+
const reasoningContent = delta?.reasoning_content;
|
|
791
|
+
if (reasoningContent) {
|
|
792
|
+
if (!isThinking) {
|
|
793
|
+
isThinking = true;
|
|
794
|
+
fullContent += "<think>\n";
|
|
795
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
796
|
+
}
|
|
797
|
+
fullContent += reasoningContent;
|
|
798
|
+
onChunk({ text: reasoningContent, finishReason: null });
|
|
799
|
+
}
|
|
738
800
|
if (delta?.content) {
|
|
801
|
+
if (isThinking) {
|
|
802
|
+
isThinking = false;
|
|
803
|
+
fullContent += "\n</think>\n\n";
|
|
804
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
805
|
+
}
|
|
739
806
|
fullContent += delta.content;
|
|
740
807
|
onChunk({ text: delta.content, finishReason: null });
|
|
741
808
|
}
|
|
@@ -758,6 +825,10 @@ var init_openai = __esm({
|
|
|
758
825
|
outputTokens = chunk.usage.completion_tokens;
|
|
759
826
|
}
|
|
760
827
|
}
|
|
828
|
+
if (isThinking) {
|
|
829
|
+
fullContent += "\n</think>\n\n";
|
|
830
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
831
|
+
}
|
|
761
832
|
const toolCalls = Object.values(toolCallsMap).map((tc) => {
|
|
762
833
|
let input = {};
|
|
763
834
|
try {
|
|
@@ -962,7 +1033,7 @@ var init_gemini = __esm({
|
|
|
962
1033
|
for (const part of candidate?.content?.parts ?? []) {
|
|
963
1034
|
if (part.functionCall) {
|
|
964
1035
|
toolCalls.push({
|
|
965
|
-
id:
|
|
1036
|
+
id: part.functionCall.name,
|
|
966
1037
|
name: part.functionCall.name,
|
|
967
1038
|
input: part.functionCall.args ?? {}
|
|
968
1039
|
});
|
|
@@ -1050,10 +1121,70 @@ var init_gemini = __esm({
|
|
|
1050
1121
|
}
|
|
1051
1122
|
// ── Private ──────────────────────────────────
|
|
1052
1123
|
buildContents(messages, extraImages) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1124
|
+
const contents = [];
|
|
1125
|
+
for (const m of messages) {
|
|
1126
|
+
if (m.role === "system") {
|
|
1127
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
1128
|
+
if (!text.trim()) continue;
|
|
1129
|
+
const prev = contents[contents.length - 1];
|
|
1130
|
+
if (prev?.role === "user") {
|
|
1131
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
1132
|
+
|
|
1133
|
+
` });
|
|
1134
|
+
} else {
|
|
1135
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
1136
|
+
}
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (m.role === "tool") {
|
|
1140
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1141
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1142
|
+
contents.push({
|
|
1143
|
+
role: "user",
|
|
1144
|
+
parts: [{
|
|
1145
|
+
functionResponse: {
|
|
1146
|
+
name: functionName,
|
|
1147
|
+
response: { output: toolContent }
|
|
1148
|
+
}
|
|
1149
|
+
}]
|
|
1150
|
+
});
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
if (m.role === "assistant") {
|
|
1154
|
+
const parts = [];
|
|
1155
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1156
|
+
if (textContent) parts.push({ text: textContent });
|
|
1157
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1158
|
+
parts.push({
|
|
1159
|
+
functionCall: {
|
|
1160
|
+
name: tc.name,
|
|
1161
|
+
args: tc.input
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
if (parts.length > 0) {
|
|
1166
|
+
contents.push({ role: "model", parts });
|
|
1167
|
+
}
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (m.role === "user") {
|
|
1171
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1172
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1173
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1174
|
+
if (isLastUser) {
|
|
1175
|
+
for (const img of extraImages) {
|
|
1176
|
+
if (img.type === "base64") {
|
|
1177
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (parts.length > 0) {
|
|
1183
|
+
contents.push({ role: "user", parts });
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return contents;
|
|
1057
1188
|
}
|
|
1058
1189
|
convertMessageContent(msg, extraImages) {
|
|
1059
1190
|
const parts = [];
|
|
@@ -1615,9 +1746,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
1615
1746
|
constructor(dbPath) {
|
|
1616
1747
|
fs14__default.default.mkdirSync(path17__default.default.dirname(dbPath), { recursive: true });
|
|
1617
1748
|
try {
|
|
1618
|
-
this.db = new Database__default.default(dbPath);
|
|
1749
|
+
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
1619
1750
|
this.db.pragma("journal_mode = WAL");
|
|
1620
1751
|
this.db.pragma("foreign_keys = ON");
|
|
1752
|
+
this.db.pragma("synchronous = NORMAL");
|
|
1621
1753
|
this.migrate();
|
|
1622
1754
|
} catch (err) {
|
|
1623
1755
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -1631,6 +1763,38 @@ Original error: ${err.message}`
|
|
|
1631
1763
|
throw err;
|
|
1632
1764
|
}
|
|
1633
1765
|
}
|
|
1766
|
+
// ── Async Write Queue ─────────────────────────
|
|
1767
|
+
writeQueue = [];
|
|
1768
|
+
isProcessingQueue = false;
|
|
1769
|
+
async processQueue() {
|
|
1770
|
+
if (this.isProcessingQueue) return;
|
|
1771
|
+
this.isProcessingQueue = true;
|
|
1772
|
+
while (this.writeQueue.length > 0) {
|
|
1773
|
+
const op = this.writeQueue.shift();
|
|
1774
|
+
if (op) {
|
|
1775
|
+
let attempts = 0;
|
|
1776
|
+
while (attempts < 5) {
|
|
1777
|
+
try {
|
|
1778
|
+
op();
|
|
1779
|
+
break;
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
1782
|
+
attempts++;
|
|
1783
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
1784
|
+
} else {
|
|
1785
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
this.isProcessingQueue = false;
|
|
1793
|
+
}
|
|
1794
|
+
enqueueWrite(op) {
|
|
1795
|
+
this.writeQueue.push(op);
|
|
1796
|
+
this.processQueue().catch(console.error);
|
|
1797
|
+
}
|
|
1634
1798
|
// ── Sessions ──────────────────────────────────
|
|
1635
1799
|
createSession(session) {
|
|
1636
1800
|
this.db.prepare(`
|
|
@@ -1717,26 +1881,28 @@ Original error: ${err.message}`
|
|
|
1717
1881
|
}
|
|
1718
1882
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
1719
1883
|
upsertRuntimeSession(session) {
|
|
1720
|
-
this.
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1884
|
+
this.enqueueWrite(() => {
|
|
1885
|
+
this.db.prepare(`
|
|
1886
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
1887
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1888
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
1889
|
+
title = excluded.title,
|
|
1890
|
+
workspace_path = excluded.workspace_path,
|
|
1891
|
+
status = excluded.status,
|
|
1892
|
+
updated_at = excluded.updated_at,
|
|
1893
|
+
latest_prompt = excluded.latest_prompt,
|
|
1894
|
+
is_global = excluded.is_global
|
|
1895
|
+
`).run(
|
|
1896
|
+
session.sessionId,
|
|
1897
|
+
session.title,
|
|
1898
|
+
session.workspacePath,
|
|
1899
|
+
session.status,
|
|
1900
|
+
session.startedAt,
|
|
1901
|
+
session.updatedAt,
|
|
1902
|
+
session.latestPrompt ?? null,
|
|
1903
|
+
session.isGlobal ? 1 : 0
|
|
1904
|
+
);
|
|
1905
|
+
});
|
|
1740
1906
|
}
|
|
1741
1907
|
listRuntimeSessions(limit = 100) {
|
|
1742
1908
|
const rows = this.db.prepare(`
|
|
@@ -1754,33 +1920,35 @@ Original error: ${err.message}`
|
|
|
1754
1920
|
}));
|
|
1755
1921
|
}
|
|
1756
1922
|
upsertRuntimeNode(node) {
|
|
1757
|
-
this.
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1923
|
+
this.enqueueWrite(() => {
|
|
1924
|
+
this.db.prepare(`
|
|
1925
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
1926
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1927
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
1928
|
+
session_id = excluded.session_id,
|
|
1929
|
+
parent_id = excluded.parent_id,
|
|
1930
|
+
role = excluded.role,
|
|
1931
|
+
label = excluded.label,
|
|
1932
|
+
status = excluded.status,
|
|
1933
|
+
current_action = excluded.current_action,
|
|
1934
|
+
progress_pct = excluded.progress_pct,
|
|
1935
|
+
updated_at = excluded.updated_at,
|
|
1936
|
+
workspace_path = excluded.workspace_path,
|
|
1937
|
+
is_global = excluded.is_global
|
|
1938
|
+
`).run(
|
|
1939
|
+
node.tierId,
|
|
1940
|
+
node.sessionId,
|
|
1941
|
+
node.parentId ?? null,
|
|
1942
|
+
node.role,
|
|
1943
|
+
node.label,
|
|
1944
|
+
node.status,
|
|
1945
|
+
node.currentAction ?? null,
|
|
1946
|
+
node.progressPct ?? null,
|
|
1947
|
+
node.updatedAt,
|
|
1948
|
+
node.workspacePath ?? null,
|
|
1949
|
+
node.isGlobal ? 1 : 0
|
|
1950
|
+
);
|
|
1951
|
+
});
|
|
1784
1952
|
}
|
|
1785
1953
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
1786
1954
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -1803,30 +1971,32 @@ Original error: ${err.message}`
|
|
|
1803
1971
|
}));
|
|
1804
1972
|
}
|
|
1805
1973
|
addRuntimeNodeLog(log) {
|
|
1806
|
-
this.
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1974
|
+
this.enqueueWrite(() => {
|
|
1975
|
+
this.db.prepare(`
|
|
1976
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
1977
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1978
|
+
`).run(
|
|
1979
|
+
log.id,
|
|
1980
|
+
log.sessionId,
|
|
1981
|
+
log.tierId,
|
|
1982
|
+
log.role,
|
|
1983
|
+
log.label,
|
|
1984
|
+
log.status,
|
|
1985
|
+
log.currentAction ?? null,
|
|
1986
|
+
log.progressPct ?? null,
|
|
1987
|
+
log.timestamp,
|
|
1988
|
+
log.workspacePath ?? null,
|
|
1989
|
+
log.isGlobal ? 1 : 0
|
|
1990
|
+
);
|
|
1991
|
+
this.db.prepare(`
|
|
1992
|
+
DELETE FROM runtime_node_logs
|
|
1993
|
+
WHERE id NOT IN (
|
|
1994
|
+
SELECT id FROM runtime_node_logs
|
|
1995
|
+
ORDER BY timestamp DESC
|
|
1996
|
+
LIMIT 2000
|
|
1997
|
+
)
|
|
1998
|
+
`).run();
|
|
1999
|
+
});
|
|
1830
2000
|
}
|
|
1831
2001
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
1832
2002
|
let rows;
|
|
@@ -1864,19 +2034,21 @@ Original error: ${err.message}`
|
|
|
1864
2034
|
}
|
|
1865
2035
|
// ── Messages ──────────────────────────────────
|
|
1866
2036
|
addMessage(message) {
|
|
1867
|
-
this.
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2037
|
+
this.enqueueWrite(() => {
|
|
2038
|
+
this.db.prepare(`
|
|
2039
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
2040
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2041
|
+
`).run(
|
|
2042
|
+
message.id,
|
|
2043
|
+
message.sessionId,
|
|
2044
|
+
message.role,
|
|
2045
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
2046
|
+
message.timestamp,
|
|
2047
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
2048
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
2049
|
+
);
|
|
2050
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
2051
|
+
});
|
|
1880
2052
|
}
|
|
1881
2053
|
getSessionMessages(sessionId) {
|
|
1882
2054
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -1973,10 +2145,12 @@ Original error: ${err.message}`
|
|
|
1973
2145
|
}
|
|
1974
2146
|
// ── Audit Log ─────────────────────────────────
|
|
1975
2147
|
addAuditEntry(entry) {
|
|
1976
|
-
this.
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2148
|
+
this.enqueueWrite(() => {
|
|
2149
|
+
this.db.prepare(`
|
|
2150
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
2151
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2152
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
2153
|
+
});
|
|
1980
2154
|
}
|
|
1981
2155
|
getAuditLog(sessionId, limit = 100) {
|
|
1982
2156
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -1991,10 +2165,12 @@ Original error: ${err.message}`
|
|
|
1991
2165
|
}
|
|
1992
2166
|
// ── File Snapshots ────────────────────────────
|
|
1993
2167
|
addFileSnapshot(sessionId, filePath, content) {
|
|
1994
|
-
this.
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2168
|
+
this.enqueueWrite(() => {
|
|
2169
|
+
this.db.prepare(`
|
|
2170
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
2171
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2172
|
+
`).run(crypto.randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
2173
|
+
});
|
|
1998
2174
|
}
|
|
1999
2175
|
getLatestFileSnapshots(sessionId) {
|
|
2000
2176
|
const rows = this.db.prepare(`
|
|
@@ -2298,12 +2474,24 @@ var McpServerConfigSchema = zod.z.object({
|
|
|
2298
2474
|
args: zod.z.array(zod.z.string()).optional(),
|
|
2299
2475
|
env: zod.z.record(zod.z.string()).optional()
|
|
2300
2476
|
});
|
|
2477
|
+
var WebSearchConfigSchema = zod.z.object({
|
|
2478
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
2479
|
+
searxngUrl: zod.z.string().optional(),
|
|
2480
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
2481
|
+
braveApiKey: zod.z.string().optional(),
|
|
2482
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
2483
|
+
tavilyApiKey: zod.z.string().optional(),
|
|
2484
|
+
/** Max results per search (default 5) */
|
|
2485
|
+
maxResults: zod.z.number().default(5)
|
|
2486
|
+
});
|
|
2301
2487
|
var ToolsConfigSchema = zod.z.object({
|
|
2302
2488
|
shellAllowlist: zod.z.array(zod.z.string()).default([]),
|
|
2303
2489
|
shellBlocklist: zod.z.array(zod.z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
2304
2490
|
requireApprovalFor: zod.z.array(zod.z.string()).default([]),
|
|
2305
2491
|
browserEnabled: zod.z.boolean().default(false),
|
|
2306
|
-
mcpServers: zod.z.array(McpServerConfigSchema).optional()
|
|
2492
|
+
mcpServers: zod.z.array(McpServerConfigSchema).optional(),
|
|
2493
|
+
/** Web search backends — at least one should be configured for best results */
|
|
2494
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
2307
2495
|
});
|
|
2308
2496
|
var HookDefinitionSchema = zod.z.object({
|
|
2309
2497
|
command: zod.z.string(),
|
|
@@ -2408,7 +2596,7 @@ var ConfigManager = class {
|
|
|
2408
2596
|
globalDir;
|
|
2409
2597
|
constructor(workspacePath = process.cwd()) {
|
|
2410
2598
|
this.workspacePath = workspacePath;
|
|
2411
|
-
this.globalDir = path17__default.default.join(
|
|
2599
|
+
this.globalDir = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
2412
2600
|
}
|
|
2413
2601
|
async load() {
|
|
2414
2602
|
this.config = await this.loadConfig();
|
|
@@ -2476,6 +2664,7 @@ var ConfigManager = class {
|
|
|
2476
2664
|
}
|
|
2477
2665
|
}
|
|
2478
2666
|
async injectEnvKeys() {
|
|
2667
|
+
const isFirstRun = this.config.providers.length === 0;
|
|
2479
2668
|
const envProviders = [
|
|
2480
2669
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
2481
2670
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
@@ -2486,10 +2675,13 @@ var ConfigManager = class {
|
|
|
2486
2675
|
const key = process.env[env];
|
|
2487
2676
|
if (!key) continue;
|
|
2488
2677
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
2489
|
-
if (!existing)
|
|
2490
|
-
|
|
2678
|
+
if (!existing && isFirstRun) {
|
|
2679
|
+
this.config.providers.push({ type, apiKey: key });
|
|
2680
|
+
} else if (existing && !existing.apiKey) {
|
|
2681
|
+
existing.apiKey = key;
|
|
2682
|
+
}
|
|
2491
2683
|
}
|
|
2492
|
-
if (!this.config.providers.find((p) => p.type === "ollama")) {
|
|
2684
|
+
if (isFirstRun && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
2493
2685
|
this.config.providers.push({ type: "ollama" });
|
|
2494
2686
|
}
|
|
2495
2687
|
}
|
|
@@ -3407,6 +3599,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
3407
3599
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
3408
3600
|
}
|
|
3409
3601
|
};
|
|
3602
|
+
|
|
3603
|
+
// src/utils/retry.ts
|
|
3604
|
+
var CascadeCancelledError = class extends Error {
|
|
3605
|
+
constructor(reason) {
|
|
3606
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
3607
|
+
this.name = "CascadeCancelledError";
|
|
3608
|
+
}
|
|
3609
|
+
};
|
|
3610
|
+
|
|
3611
|
+
// src/core/tiers/base.ts
|
|
3410
3612
|
var BaseTier = class extends EventEmitter__default.default {
|
|
3411
3613
|
id;
|
|
3412
3614
|
role;
|
|
@@ -3416,6 +3618,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3416
3618
|
label;
|
|
3417
3619
|
systemPromptOverride = "";
|
|
3418
3620
|
hierarchyContext = "";
|
|
3621
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
3622
|
+
signal;
|
|
3419
3623
|
constructor(role, id, parentId) {
|
|
3420
3624
|
super();
|
|
3421
3625
|
this.role = role;
|
|
@@ -3478,6 +3682,18 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3478
3682
|
log(message, data) {
|
|
3479
3683
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3480
3684
|
}
|
|
3685
|
+
/**
|
|
3686
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
3687
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
3688
|
+
* to provide a fast, clean cancellation path.
|
|
3689
|
+
*/
|
|
3690
|
+
throwIfCancelled() {
|
|
3691
|
+
if (this.signal?.aborted) {
|
|
3692
|
+
throw new CascadeCancelledError(
|
|
3693
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3481
3697
|
};
|
|
3482
3698
|
|
|
3483
3699
|
// src/core/context/manager.ts
|
|
@@ -3679,6 +3895,7 @@ Rules:
|
|
|
3679
3895
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
3680
3896
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
3681
3897
|
- 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.
|
|
3898
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
3682
3899
|
- Use the "pdf_create" tool for PDF requests.
|
|
3683
3900
|
- Use the "run_code" tool for any file types (Excel, Zip, csv, etc.) or complex processing not covered by other tools. Always cleanup after code execution.
|
|
3684
3901
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -3722,7 +3939,8 @@ var T3Worker = class extends BaseTier {
|
|
|
3722
3939
|
this.store = store;
|
|
3723
3940
|
this.audit = new AuditLogger(store, sessionId);
|
|
3724
3941
|
}
|
|
3725
|
-
async execute(assignment, taskId) {
|
|
3942
|
+
async execute(assignment, taskId, signal) {
|
|
3943
|
+
this.signal = signal;
|
|
3726
3944
|
this.assignment = assignment;
|
|
3727
3945
|
this.taskId = taskId;
|
|
3728
3946
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -3872,6 +4090,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
3872
4090
|
tools = [...tools];
|
|
3873
4091
|
while (iterations < MAX_ITERATIONS) {
|
|
3874
4092
|
iterations++;
|
|
4093
|
+
this.throwIfCancelled();
|
|
3875
4094
|
const options = {
|
|
3876
4095
|
messages: this.context.getMessages(),
|
|
3877
4096
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -3892,21 +4111,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3892
4111
|
if (requiresArtifact) {
|
|
3893
4112
|
stalledArtifactIterations += 1;
|
|
3894
4113
|
if (stalledArtifactIterations >= 2) {
|
|
3895
|
-
if (
|
|
3896
|
-
|
|
3897
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
3898
|
-
this.assignment?.description ?? ""
|
|
3899
|
-
);
|
|
3900
|
-
if (toolName) {
|
|
3901
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
3902
|
-
this.sendStatusUpdate({
|
|
3903
|
-
progressPct: 50,
|
|
3904
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
3905
|
-
status: "IN_PROGRESS"
|
|
3906
|
-
});
|
|
3907
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
3908
|
-
continue;
|
|
3909
|
-
}
|
|
4114
|
+
if (stalledArtifactIterations === 2) {
|
|
4115
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
3910
4116
|
}
|
|
3911
4117
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
3912
4118
|
}
|
|
@@ -4066,6 +4272,9 @@ ${assignment.expectedOutput}`;
|
|
|
4066
4272
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
4067
4273
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
4068
4274
|
const issues = [];
|
|
4275
|
+
const { exec: exec4 } = await import('child_process');
|
|
4276
|
+
const { promisify: promisify3 } = await import('util');
|
|
4277
|
+
const execAsync3 = promisify3(exec4);
|
|
4069
4278
|
for (const artifactPath of artifactPaths) {
|
|
4070
4279
|
const absolutePath = path17__default.default.resolve(process.cwd(), artifactPath);
|
|
4071
4280
|
try {
|
|
@@ -4082,9 +4291,27 @@ ${assignment.expectedOutput}`;
|
|
|
4082
4291
|
const content = await fs7__default.default.readFile(absolutePath, "utf-8");
|
|
4083
4292
|
if (!content.trim()) {
|
|
4084
4293
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
4294
|
+
continue;
|
|
4085
4295
|
}
|
|
4086
4296
|
} else if (stat.size < 100) {
|
|
4087
4297
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
4298
|
+
continue;
|
|
4299
|
+
}
|
|
4300
|
+
const ext = path17__default.default.extname(absolutePath).toLowerCase();
|
|
4301
|
+
try {
|
|
4302
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
4303
|
+
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
4304
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
4305
|
+
await execAsync3(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
4306
|
+
} else if (ext === ".py") {
|
|
4307
|
+
await execAsync3(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
4308
|
+
}
|
|
4309
|
+
} catch (err) {
|
|
4310
|
+
const stderr = err?.stderr || String(err);
|
|
4311
|
+
const stdout = err?.stdout || "";
|
|
4312
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
4313
|
+
${stderr}
|
|
4314
|
+
${stdout}`);
|
|
4088
4315
|
}
|
|
4089
4316
|
} catch {
|
|
4090
4317
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -4481,7 +4708,8 @@ var T2Manager = class extends BaseTier {
|
|
|
4481
4708
|
});
|
|
4482
4709
|
this.emit("peer-sync-received", { fromId, content });
|
|
4483
4710
|
}
|
|
4484
|
-
async execute(assignment, taskId) {
|
|
4711
|
+
async execute(assignment, taskId, signal) {
|
|
4712
|
+
this.signal = signal;
|
|
4485
4713
|
this.assignment = assignment;
|
|
4486
4714
|
this.taskId = taskId;
|
|
4487
4715
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -4493,12 +4721,14 @@ var T2Manager = class extends BaseTier {
|
|
|
4493
4721
|
});
|
|
4494
4722
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
4495
4723
|
try {
|
|
4724
|
+
this.throwIfCancelled();
|
|
4496
4725
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
4497
4726
|
this.sendStatusUpdate({
|
|
4498
4727
|
progressPct: 20,
|
|
4499
4728
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
4500
4729
|
status: "IN_PROGRESS"
|
|
4501
4730
|
});
|
|
4731
|
+
this.throwIfCancelled();
|
|
4502
4732
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
4503
4733
|
this.sendStatusUpdate({
|
|
4504
4734
|
progressPct: 90,
|
|
@@ -4665,11 +4895,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4665
4895
|
).join(", ")}`,
|
|
4666
4896
|
status: "IN_PROGRESS"
|
|
4667
4897
|
});
|
|
4898
|
+
this.throwIfCancelled();
|
|
4668
4899
|
const waveResults = await Promise.allSettled(
|
|
4669
4900
|
runnableIds.map(async (id) => {
|
|
4670
4901
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4671
4902
|
const worker = workerMap.get(id);
|
|
4672
|
-
const result = await worker.execute(assignment, taskId);
|
|
4903
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
4673
4904
|
resultMap.set(id, result);
|
|
4674
4905
|
return result;
|
|
4675
4906
|
})
|
|
@@ -4683,6 +4914,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4683
4914
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4684
4915
|
const retried = await this.retryT3(assignment, taskId);
|
|
4685
4916
|
resultMap.set(id, retried);
|
|
4917
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
4918
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4919
|
+
if (this.toolCreator) {
|
|
4920
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
4921
|
+
this.sendStatusUpdate({
|
|
4922
|
+
progressPct: 50,
|
|
4923
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
4924
|
+
status: "IN_PROGRESS"
|
|
4925
|
+
});
|
|
4926
|
+
const toolName = await this.toolCreator.createTool(
|
|
4927
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
4928
|
+
assignment.description
|
|
4929
|
+
);
|
|
4930
|
+
if (toolName) {
|
|
4931
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
4932
|
+
this.sendStatusUpdate({
|
|
4933
|
+
progressPct: 60,
|
|
4934
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
4935
|
+
status: "IN_PROGRESS"
|
|
4936
|
+
});
|
|
4937
|
+
try {
|
|
4938
|
+
const verifyResult = await this.router.generate("T2", {
|
|
4939
|
+
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".` }],
|
|
4940
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
4941
|
+
maxTokens: 50
|
|
4942
|
+
});
|
|
4943
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
4944
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
4945
|
+
const retried = await this.retryT3({
|
|
4946
|
+
...assignment,
|
|
4947
|
+
description: `${assignment.description}
|
|
4948
|
+
|
|
4949
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
4950
|
+
}, taskId);
|
|
4951
|
+
resultMap.set(id, retried);
|
|
4952
|
+
} else {
|
|
4953
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
4954
|
+
resultMap.set(id, r.value);
|
|
4955
|
+
}
|
|
4956
|
+
} catch {
|
|
4957
|
+
const retried = await this.retryT3({
|
|
4958
|
+
...assignment,
|
|
4959
|
+
description: `${assignment.description}
|
|
4960
|
+
|
|
4961
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
4962
|
+
}, taskId);
|
|
4963
|
+
resultMap.set(id, retried);
|
|
4964
|
+
}
|
|
4965
|
+
} else {
|
|
4966
|
+
resultMap.set(id, r.value);
|
|
4967
|
+
}
|
|
4968
|
+
} else {
|
|
4969
|
+
resultMap.set(id, r.value);
|
|
4970
|
+
}
|
|
4686
4971
|
}
|
|
4687
4972
|
for (const dependent of adj.get(id) ?? []) {
|
|
4688
4973
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -4746,7 +5031,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4746
5031
|
}));
|
|
4747
5032
|
return worker.execute(
|
|
4748
5033
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
4749
|
-
taskId
|
|
5034
|
+
taskId,
|
|
5035
|
+
this.signal
|
|
4750
5036
|
);
|
|
4751
5037
|
}
|
|
4752
5038
|
publishSectionOutput(result) {
|
|
@@ -4760,29 +5046,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4760
5046
|
async aggregateResults(assignment, results) {
|
|
4761
5047
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
4762
5048
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
4763
|
-
const outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
|
|
4764
5049
|
const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
|
|
4765
|
-
const
|
|
4766
|
-
|
|
4767
|
-
${outputs}
|
|
4768
|
-
${peerOutputs ? `
|
|
5050
|
+
const peerContext = peerOutputs ? `
|
|
4769
5051
|
|
|
4770
5052
|
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
4771
|
-
${peerOutputs}` : ""
|
|
4772
|
-
const
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
5053
|
+
${peerOutputs}` : "";
|
|
5054
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
5055
|
+
let currentSummary = "";
|
|
5056
|
+
let i = 0;
|
|
5057
|
+
while (i < completed.length) {
|
|
5058
|
+
let chunkText = "";
|
|
5059
|
+
let chunkEnd = i;
|
|
5060
|
+
while (chunkEnd < completed.length) {
|
|
5061
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
5062
|
+
|
|
5063
|
+
`;
|
|
5064
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
5065
|
+
break;
|
|
5066
|
+
}
|
|
5067
|
+
chunkText += nextOutput;
|
|
5068
|
+
chunkEnd++;
|
|
5069
|
+
}
|
|
5070
|
+
i = chunkEnd;
|
|
5071
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
5072
|
+
${currentSummary ? `
|
|
5073
|
+
PREVIOUS SUMMARY SO FAR:
|
|
5074
|
+
${currentSummary}
|
|
5075
|
+
|
|
5076
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
5077
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
5078
|
+
const messages = [{ role: "user", content: prompt }];
|
|
5079
|
+
try {
|
|
5080
|
+
const result = await this.router.generate("T2", {
|
|
5081
|
+
messages,
|
|
5082
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
4777
5083
|
|
|
4778
5084
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
5085
|
+
maxTokens: 500
|
|
5086
|
+
});
|
|
5087
|
+
currentSummary = result.content;
|
|
5088
|
+
} catch (err) {
|
|
5089
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
5090
|
+
return currentSummary + "\n\n" + chunkText;
|
|
5091
|
+
}
|
|
4785
5092
|
}
|
|
5093
|
+
return currentSummary;
|
|
4786
5094
|
}
|
|
4787
5095
|
determineStatus(results) {
|
|
4788
5096
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -4910,10 +5218,10 @@ Rules:
|
|
|
4910
5218
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
4911
5219
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
4912
5220
|
|
|
4913
|
-
|
|
4914
|
-
-
|
|
4915
|
-
-
|
|
4916
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
5221
|
+
DEPENDENCY GUIDANCE:
|
|
5222
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
5223
|
+
- 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).
|
|
5224
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
4917
5225
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
4918
5226
|
|
|
4919
5227
|
QUALITY RULES:
|
|
@@ -4952,7 +5260,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
4952
5260
|
setToolCreator(creator) {
|
|
4953
5261
|
this.toolCreator = creator;
|
|
4954
5262
|
}
|
|
4955
|
-
async execute(userPrompt, images, systemContext) {
|
|
5263
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
5264
|
+
this.signal = signal;
|
|
4956
5265
|
this.taskId = crypto.randomUUID();
|
|
4957
5266
|
this.setLabel("Administrator");
|
|
4958
5267
|
this.setStatus("ACTIVE");
|
|
@@ -4963,10 +5272,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
4963
5272
|
status: "IN_PROGRESS"
|
|
4964
5273
|
});
|
|
4965
5274
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
5275
|
+
this.throwIfCancelled();
|
|
4966
5276
|
let enrichedPrompt = userPrompt;
|
|
4967
5277
|
if (images?.length) {
|
|
4968
5278
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
4969
5279
|
}
|
|
5280
|
+
this.throwIfCancelled();
|
|
4970
5281
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
4971
5282
|
this.sendStatusUpdate({
|
|
4972
5283
|
progressPct: 10,
|
|
@@ -4974,21 +5285,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
4974
5285
|
status: "IN_PROGRESS"
|
|
4975
5286
|
});
|
|
4976
5287
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
4977
|
-
|
|
5288
|
+
this.throwIfCancelled();
|
|
5289
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
5290
|
+
let pass = 1;
|
|
5291
|
+
const MAX_REPLAN_PASSES = 2;
|
|
5292
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
5293
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
5294
|
+
if (reviewResult.approved) {
|
|
5295
|
+
this.log("T1 Review passed.");
|
|
5296
|
+
break;
|
|
5297
|
+
}
|
|
5298
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
5299
|
+
this.sendStatusUpdate({
|
|
5300
|
+
progressPct: 80 + pass * 5,
|
|
5301
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
5302
|
+
status: "IN_PROGRESS"
|
|
5303
|
+
});
|
|
5304
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
5305
|
+
Review reason: ${reviewResult.reason}
|
|
5306
|
+
|
|
5307
|
+
Original goal: ${enrichedPrompt}
|
|
5308
|
+
|
|
5309
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
5310
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
5311
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
5312
|
+
pass++;
|
|
5313
|
+
}
|
|
4978
5314
|
this.sendStatusUpdate({
|
|
4979
5315
|
progressPct: 95,
|
|
4980
5316
|
currentAction: "Compiling final output",
|
|
4981
5317
|
status: "IN_PROGRESS"
|
|
4982
5318
|
});
|
|
4983
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
5319
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
4984
5320
|
this.setStatus("COMPLETED");
|
|
4985
5321
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
4986
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
5322
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
4987
5323
|
}
|
|
4988
5324
|
getEscalations() {
|
|
4989
5325
|
return [...this.escalations];
|
|
4990
5326
|
}
|
|
4991
5327
|
// ── Private ──────────────────────────────────
|
|
5328
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
5329
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
5330
|
+
if (failedSections.length > 0) {
|
|
5331
|
+
return {
|
|
5332
|
+
approved: false,
|
|
5333
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
5337
|
+
${r.sectionSummary}`).join("\n\n");
|
|
5338
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
5339
|
+
Review the following execution outputs against the original user prompt.
|
|
5340
|
+
|
|
5341
|
+
Original Request: ${originalPrompt}
|
|
5342
|
+
|
|
5343
|
+
T2 Manager Summaries:
|
|
5344
|
+
${sectionsText}
|
|
5345
|
+
|
|
5346
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
5347
|
+
If yes, reply with exactly: "APPROVED".
|
|
5348
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
5349
|
+
try {
|
|
5350
|
+
const result = await this.router.generate("T1", {
|
|
5351
|
+
messages: [{ role: "user", content: prompt }],
|
|
5352
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
5353
|
+
maxTokens: 500,
|
|
5354
|
+
temperature: 0
|
|
5355
|
+
});
|
|
5356
|
+
const response = result.content.trim();
|
|
5357
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
5358
|
+
return { approved: true };
|
|
5359
|
+
}
|
|
5360
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
5361
|
+
} catch {
|
|
5362
|
+
return { approved: true };
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
4992
5365
|
async analyzeImages(prompt, images) {
|
|
4993
5366
|
const visionModel = this.router.getModelForTier("T1");
|
|
4994
5367
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -5017,29 +5390,35 @@ ${systemContext}` : "";
|
|
|
5017
5390
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
5018
5391
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
5019
5392
|
|
|
5020
|
-
Return JSON where
|
|
5393
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
5021
5394
|
{
|
|
5022
5395
|
"sections": [{
|
|
5396
|
+
"sectionId": "s1",
|
|
5397
|
+
"sectionTitle": "Setup Project",
|
|
5398
|
+
"description": "Initialize the project",
|
|
5399
|
+
"expectedOutput": "Basic structure created",
|
|
5400
|
+
"constraints": [],
|
|
5401
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
5023
5402
|
"t3Subtasks": [{
|
|
5024
5403
|
"subtaskId": "t1",
|
|
5025
|
-
"subtaskTitle": "
|
|
5026
|
-
"
|
|
5027
|
-
"
|
|
5028
|
-
|
|
5029
|
-
"
|
|
5030
|
-
"subtaskTitle": "Save Code to File",
|
|
5031
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
5032
|
-
"executionMode": "parallel"
|
|
5033
|
-
}, {
|
|
5034
|
-
"subtaskId": "t3",
|
|
5035
|
-
"subtaskTitle": "Execute and Verify",
|
|
5036
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
5037
|
-
"executionMode": "parallel"
|
|
5404
|
+
"subtaskTitle": "Init NPM",
|
|
5405
|
+
"description": "Run npm init",
|
|
5406
|
+
"expectedOutput": "package.json created",
|
|
5407
|
+
"constraints": [],
|
|
5408
|
+
"dependsOn": []
|
|
5038
5409
|
}]
|
|
5410
|
+
}, {
|
|
5411
|
+
"sectionId": "s2",
|
|
5412
|
+
"sectionTitle": "Write Tests",
|
|
5413
|
+
"description": "Write tests for the project",
|
|
5414
|
+
"expectedOutput": "Tests passing",
|
|
5415
|
+
"constraints": [],
|
|
5416
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
5417
|
+
"t3Subtasks": [...]
|
|
5039
5418
|
}]
|
|
5040
5419
|
}
|
|
5041
|
-
Use dependsOn when a
|
|
5042
|
-
Leave dependsOn empty for
|
|
5420
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
5421
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
5043
5422
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
5044
5423
|
const result = await this.router.generate("T1", {
|
|
5045
5424
|
messages,
|
|
@@ -5167,92 +5546,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
5167
5546
|
].filter(Boolean).join(" ");
|
|
5168
5547
|
m.setHierarchyContext(context);
|
|
5169
5548
|
});
|
|
5170
|
-
if (overlapSections.size > 0
|
|
5171
|
-
this.log("Overlap detected \u2014
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5549
|
+
if (overlapSections.size > 0) {
|
|
5550
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
5551
|
+
const overlapArray = Array.from(overlapSections);
|
|
5552
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
5553
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
5554
|
+
if (section) {
|
|
5555
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
5175
5556
|
}
|
|
5176
5557
|
}
|
|
5177
5558
|
}
|
|
5178
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
5179
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
5180
5559
|
const t2Results = [];
|
|
5181
5560
|
try {
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5561
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
5562
|
+
} finally {
|
|
5563
|
+
cleanup();
|
|
5564
|
+
}
|
|
5565
|
+
return t2Results;
|
|
5566
|
+
}
|
|
5567
|
+
/**
|
|
5568
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
5569
|
+
*/
|
|
5570
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
5571
|
+
const adj = /* @__PURE__ */ new Map();
|
|
5572
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
5573
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5574
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
5575
|
+
for (const s of sections) {
|
|
5576
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
5577
|
+
inDegree.set(s.sectionId, 0);
|
|
5578
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
5579
|
+
}
|
|
5580
|
+
for (const s of sections) {
|
|
5581
|
+
for (const dep of s.dependsOn ?? []) {
|
|
5582
|
+
adj.get(dep).add(s.sectionId);
|
|
5583
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
const queue = [];
|
|
5587
|
+
const degree = new Map(inDegree);
|
|
5588
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
5589
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5590
|
+
while (queue.length > 0) {
|
|
5591
|
+
const u = queue.shift();
|
|
5592
|
+
visited.add(u);
|
|
5593
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
5594
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
5595
|
+
degree.set(v, newDeg);
|
|
5596
|
+
if (newDeg === 0) queue.push(v);
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
5600
|
+
if (cycleNodes.length > 0) {
|
|
5601
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
5602
|
+
for (const s of sections) {
|
|
5603
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
5604
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
5605
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
5606
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
5607
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
5213
5608
|
}
|
|
5609
|
+
s.dependsOn = safeDeps;
|
|
5214
5610
|
}
|
|
5215
|
-
}
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
const totalSections = sections.length;
|
|
5614
|
+
let completedSections = 0;
|
|
5615
|
+
const executeWave = async () => {
|
|
5616
|
+
const readyIds = [];
|
|
5617
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
5618
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
5619
|
+
readyIds.push(id);
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
if (readyIds.length === 0) return;
|
|
5623
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
5624
|
+
resultMap.set(id, null);
|
|
5625
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
5626
|
+
const section = sections[index];
|
|
5627
|
+
const manager = managers[index];
|
|
5628
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
5629
|
+
this.sendStatusUpdate({
|
|
5630
|
+
progressPct,
|
|
5631
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
5632
|
+
status: "IN_PROGRESS"
|
|
5633
|
+
});
|
|
5634
|
+
this.throwIfCancelled();
|
|
5635
|
+
let result;
|
|
5636
|
+
try {
|
|
5637
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
5638
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
5639
|
+
if (result.status === "ESCALATED") {
|
|
5640
|
+
this.escalations.push({
|
|
5641
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
5642
|
+
sectionId: section.sectionId,
|
|
5643
|
+
attempted: result.issues,
|
|
5644
|
+
blocker: result.issues.join("; "),
|
|
5645
|
+
needs: "Human review required"
|
|
5248
5646
|
});
|
|
5249
5647
|
}
|
|
5648
|
+
} catch (err) {
|
|
5649
|
+
result = {
|
|
5650
|
+
sectionId: section.sectionId,
|
|
5651
|
+
sectionTitle: section.sectionTitle,
|
|
5652
|
+
status: "FAILED",
|
|
5653
|
+
t3Results: [],
|
|
5654
|
+
sectionSummary: "",
|
|
5655
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
5656
|
+
};
|
|
5657
|
+
}
|
|
5658
|
+
resultMap.set(id, result);
|
|
5659
|
+
completedSections++;
|
|
5660
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
5661
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
5250
5662
|
}
|
|
5663
|
+
}));
|
|
5664
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
5665
|
+
await executeWave();
|
|
5251
5666
|
}
|
|
5252
|
-
}
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
return t2Results;
|
|
5667
|
+
};
|
|
5668
|
+
await executeWave();
|
|
5669
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
5256
5670
|
}
|
|
5257
5671
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
5258
5672
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -5702,13 +6116,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
5702
6116
|
}
|
|
5703
6117
|
async execute(input, _options) {
|
|
5704
6118
|
const platform = input["platform"] ?? "github";
|
|
5705
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
5706
6119
|
const operation = input["operation"];
|
|
5707
6120
|
const repo = input["repo"];
|
|
5708
|
-
|
|
5709
|
-
|
|
6121
|
+
let token = input["token"];
|
|
6122
|
+
if (!token) {
|
|
6123
|
+
if (platform === "github") {
|
|
6124
|
+
token = process.env["GITHUB_TOKEN"];
|
|
6125
|
+
} else {
|
|
6126
|
+
token = process.env["GITLAB_TOKEN"];
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
if (!token) {
|
|
6130
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
6131
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
6132
|
+
}
|
|
6133
|
+
try {
|
|
6134
|
+
if (platform === "github") {
|
|
6135
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
6136
|
+
}
|
|
6137
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
6138
|
+
} catch (err) {
|
|
6139
|
+
const axiosErr = err;
|
|
6140
|
+
if (axiosErr?.response?.status) {
|
|
6141
|
+
const status = axiosErr.response.status;
|
|
6142
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
6143
|
+
switch (status) {
|
|
6144
|
+
case 401:
|
|
6145
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
6146
|
+
case 403:
|
|
6147
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
6148
|
+
case 404:
|
|
6149
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
6150
|
+
case 422:
|
|
6151
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
6152
|
+
case 429:
|
|
6153
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
6154
|
+
default:
|
|
6155
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
5710
6159
|
}
|
|
5711
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
5712
6160
|
}
|
|
5713
6161
|
async executeGitHub(operation, repo, token, input) {
|
|
5714
6162
|
const headers = {
|
|
@@ -5795,6 +6243,7 @@ ${response.data.description}`;
|
|
|
5795
6243
|
};
|
|
5796
6244
|
|
|
5797
6245
|
// src/tools/browser.ts
|
|
6246
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
5798
6247
|
var BrowserTool = class extends BaseTool {
|
|
5799
6248
|
name = "browser";
|
|
5800
6249
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -5803,7 +6252,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
5803
6252
|
properties: {
|
|
5804
6253
|
action: {
|
|
5805
6254
|
type: "string",
|
|
5806
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
6255
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
5807
6256
|
},
|
|
5808
6257
|
url: { type: "string", description: "URL to navigate to" },
|
|
5809
6258
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -5823,53 +6272,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
5823
6272
|
try {
|
|
5824
6273
|
playwright = await import('playwright');
|
|
5825
6274
|
} catch {
|
|
5826
|
-
|
|
6275
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
5827
6276
|
}
|
|
5828
|
-
if (!this.browser) {
|
|
5829
|
-
const pw = playwright;
|
|
5830
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
5831
|
-
const b = this.browser;
|
|
5832
|
-
this.page = await b.newPage();
|
|
5833
|
-
}
|
|
5834
|
-
const page = this.page;
|
|
5835
6277
|
const action = input["action"];
|
|
5836
6278
|
const timeout = input["timeout"] ?? 1e4;
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
case "evaluate": {
|
|
5855
|
-
const result = await page.evaluate(input["script"]);
|
|
5856
|
-
return JSON.stringify(result);
|
|
6279
|
+
if (action === "close") {
|
|
6280
|
+
await this.close();
|
|
6281
|
+
return "Browser closed.";
|
|
6282
|
+
}
|
|
6283
|
+
if (!this.browser || !this.page) {
|
|
6284
|
+
await this.close();
|
|
6285
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
6286
|
+
const timeoutPromise = new Promise(
|
|
6287
|
+
(_, 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)
|
|
6288
|
+
);
|
|
6289
|
+
try {
|
|
6290
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
6291
|
+
this.page = await this.browser.newPage();
|
|
6292
|
+
} catch (err) {
|
|
6293
|
+
this.browser = null;
|
|
6294
|
+
this.page = null;
|
|
6295
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
5857
6296
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
6297
|
+
}
|
|
6298
|
+
const page = this.page;
|
|
6299
|
+
try {
|
|
6300
|
+
switch (action) {
|
|
6301
|
+
case "navigate": {
|
|
6302
|
+
await page.goto(input["url"], { timeout });
|
|
6303
|
+
const title = await page.title();
|
|
6304
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
6305
|
+
}
|
|
6306
|
+
case "click": {
|
|
6307
|
+
await page.click(input["selector"], { timeout });
|
|
6308
|
+
return `Clicked ${input["selector"]}`;
|
|
6309
|
+
}
|
|
6310
|
+
case "fill": {
|
|
6311
|
+
await page.fill(input["selector"], input["value"]);
|
|
6312
|
+
return `Filled ${input["selector"]} with value`;
|
|
6313
|
+
}
|
|
6314
|
+
case "screenshot": {
|
|
6315
|
+
const buf = await page.screenshot({ type: "png" });
|
|
6316
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
6317
|
+
}
|
|
6318
|
+
case "evaluate": {
|
|
6319
|
+
const result = await page.evaluate(input["script"]);
|
|
6320
|
+
return JSON.stringify(result);
|
|
6321
|
+
}
|
|
6322
|
+
case "extract_text": {
|
|
6323
|
+
const text = await page.locator("body").innerText();
|
|
6324
|
+
return text.slice(0, 1e4);
|
|
6325
|
+
}
|
|
6326
|
+
case "wait": {
|
|
6327
|
+
await page.waitForTimeout(timeout);
|
|
6328
|
+
return `Waited ${timeout}ms`;
|
|
6329
|
+
}
|
|
6330
|
+
default:
|
|
6331
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
5861
6332
|
}
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
6333
|
+
} catch (err) {
|
|
6334
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6335
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
6336
|
+
await this.close();
|
|
6337
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
5865
6338
|
}
|
|
5866
|
-
|
|
5867
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
6339
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
5868
6340
|
}
|
|
5869
6341
|
}
|
|
5870
6342
|
async close() {
|
|
5871
|
-
|
|
5872
|
-
|
|
6343
|
+
try {
|
|
6344
|
+
if (this.page) {
|
|
6345
|
+
await this.page.close().catch(() => {
|
|
6346
|
+
});
|
|
6347
|
+
this.page = null;
|
|
6348
|
+
}
|
|
6349
|
+
if (this.browser) {
|
|
6350
|
+
await this.browser.close().catch(() => {
|
|
6351
|
+
});
|
|
6352
|
+
this.browser = null;
|
|
6353
|
+
}
|
|
6354
|
+
} catch {
|
|
5873
6355
|
this.browser = null;
|
|
5874
6356
|
this.page = null;
|
|
5875
6357
|
}
|
|
@@ -5966,6 +6448,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
5966
6448
|
});
|
|
5967
6449
|
}
|
|
5968
6450
|
};
|
|
6451
|
+
function detectCommand(candidates2) {
|
|
6452
|
+
for (const cmd of candidates2) {
|
|
6453
|
+
try {
|
|
6454
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
6455
|
+
child_process.execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
6456
|
+
return cmd;
|
|
6457
|
+
} catch {
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
return null;
|
|
6461
|
+
}
|
|
6462
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
6463
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
5969
6464
|
var CodeInterpreterTool = class extends BaseTool {
|
|
5970
6465
|
name = "run_code";
|
|
5971
6466
|
description = "Execute a Python or Node.js script to perform complex tasks (data processing, file conversion, etc.). The script is automatically cleaned up after execution.";
|
|
@@ -5981,10 +6476,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5981
6476
|
isDangerous() {
|
|
5982
6477
|
return true;
|
|
5983
6478
|
}
|
|
5984
|
-
async execute(input,
|
|
6479
|
+
async execute(input, _options) {
|
|
5985
6480
|
const language = input["language"];
|
|
5986
6481
|
const code = input["code"];
|
|
5987
6482
|
const args = input["args"] ?? [];
|
|
6483
|
+
let cmdPrefix;
|
|
6484
|
+
if (language === "python") {
|
|
6485
|
+
if (!PYTHON_CMD) {
|
|
6486
|
+
return [
|
|
6487
|
+
"Error: Python interpreter not found.",
|
|
6488
|
+
"Please install Python and ensure it is in your PATH.",
|
|
6489
|
+
"Tried: python3, python"
|
|
6490
|
+
].join("\n");
|
|
6491
|
+
}
|
|
6492
|
+
cmdPrefix = PYTHON_CMD;
|
|
6493
|
+
} else {
|
|
6494
|
+
if (!NODE_CMD) {
|
|
6495
|
+
return [
|
|
6496
|
+
"Error: Node.js interpreter not found.",
|
|
6497
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
6498
|
+
"Tried: node"
|
|
6499
|
+
].join("\n");
|
|
6500
|
+
}
|
|
6501
|
+
cmdPrefix = NODE_CMD;
|
|
6502
|
+
}
|
|
5988
6503
|
const tmpDir = path17__default.default.join(process.cwd(), ".cascade", "tmp");
|
|
5989
6504
|
if (!fs14__default.default.existsSync(tmpDir)) {
|
|
5990
6505
|
fs14__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -5993,8 +6508,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5993
6508
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
5994
6509
|
const filePath = path17__default.default.join(tmpDir, fileName);
|
|
5995
6510
|
fs14__default.default.writeFileSync(filePath, code, "utf-8");
|
|
5996
|
-
const
|
|
5997
|
-
const
|
|
6511
|
+
const quotedPath = `"${filePath}"`;
|
|
6512
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
6513
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
5998
6514
|
return new Promise((resolve) => {
|
|
5999
6515
|
const startMs = Date.now();
|
|
6000
6516
|
child_process.exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -6007,10 +6523,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
6007
6523
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
6008
6524
|
}
|
|
6009
6525
|
if (error) {
|
|
6010
|
-
|
|
6526
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
6527
|
+
if (timedOut) {
|
|
6528
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
6529
|
+
Partial stdout: ${stdout}
|
|
6530
|
+
Stderr: ${stderr}`);
|
|
6531
|
+
} else {
|
|
6532
|
+
resolve(`Execution failed (${duration}ms):
|
|
6011
6533
|
Error: ${error.message}
|
|
6012
6534
|
Stderr: ${stderr}
|
|
6013
6535
|
Stdout: ${stdout}`);
|
|
6536
|
+
}
|
|
6014
6537
|
} else {
|
|
6015
6538
|
resolve(`Execution successful (${duration}ms):
|
|
6016
6539
|
Stdout: ${stdout}
|
|
@@ -6075,6 +6598,186 @@ ${formatted}`;
|
|
|
6075
6598
|
}
|
|
6076
6599
|
};
|
|
6077
6600
|
|
|
6601
|
+
// src/tools/web-search.ts
|
|
6602
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
6603
|
+
const url = new URL("/search", baseUrl);
|
|
6604
|
+
url.searchParams.set("q", query);
|
|
6605
|
+
url.searchParams.set("format", "json");
|
|
6606
|
+
url.searchParams.set("categories", "general");
|
|
6607
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
6608
|
+
const resp = await fetch(url.toString(), {
|
|
6609
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
6610
|
+
signal: AbortSignal.timeout(1e4)
|
|
6611
|
+
});
|
|
6612
|
+
if (!resp.ok) {
|
|
6613
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
6614
|
+
}
|
|
6615
|
+
const data = await resp.json();
|
|
6616
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6617
|
+
title: r.title ?? "",
|
|
6618
|
+
url: r.url ?? "",
|
|
6619
|
+
snippet: r.content ?? "",
|
|
6620
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
6621
|
+
}));
|
|
6622
|
+
}
|
|
6623
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
6624
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
6625
|
+
const resp = await fetch(url, {
|
|
6626
|
+
headers: {
|
|
6627
|
+
"Accept": "application/json",
|
|
6628
|
+
"Accept-Encoding": "gzip",
|
|
6629
|
+
"X-Subscription-Token": apiKey
|
|
6630
|
+
},
|
|
6631
|
+
signal: AbortSignal.timeout(1e4)
|
|
6632
|
+
});
|
|
6633
|
+
if (!resp.ok) {
|
|
6634
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
6635
|
+
}
|
|
6636
|
+
const data = await resp.json();
|
|
6637
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6638
|
+
title: r.title ?? "",
|
|
6639
|
+
url: r.url ?? "",
|
|
6640
|
+
snippet: r.description ?? "",
|
|
6641
|
+
engine: "brave"
|
|
6642
|
+
}));
|
|
6643
|
+
}
|
|
6644
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
6645
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
6646
|
+
method: "POST",
|
|
6647
|
+
headers: {
|
|
6648
|
+
"Content-Type": "application/json",
|
|
6649
|
+
"Authorization": `Bearer ${apiKey}`
|
|
6650
|
+
},
|
|
6651
|
+
body: JSON.stringify({
|
|
6652
|
+
query,
|
|
6653
|
+
max_results: maxResults,
|
|
6654
|
+
search_depth: "basic",
|
|
6655
|
+
include_answer: false,
|
|
6656
|
+
include_raw_content: false
|
|
6657
|
+
}),
|
|
6658
|
+
signal: AbortSignal.timeout(15e3)
|
|
6659
|
+
});
|
|
6660
|
+
if (!resp.ok) {
|
|
6661
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
6662
|
+
}
|
|
6663
|
+
const data = await resp.json();
|
|
6664
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6665
|
+
title: r.title ?? "",
|
|
6666
|
+
url: r.url ?? "",
|
|
6667
|
+
snippet: r.content ?? "",
|
|
6668
|
+
engine: "tavily"
|
|
6669
|
+
}));
|
|
6670
|
+
}
|
|
6671
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
6672
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
6673
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
6674
|
+
signal: AbortSignal.timeout(1e4)
|
|
6675
|
+
});
|
|
6676
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
6677
|
+
const html = await resp.text();
|
|
6678
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
6679
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
6680
|
+
const links = [];
|
|
6681
|
+
const snippets = [];
|
|
6682
|
+
let m;
|
|
6683
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
6684
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
6685
|
+
}
|
|
6686
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
6687
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
6688
|
+
}
|
|
6689
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
6690
|
+
title: link.title,
|
|
6691
|
+
url: link.url,
|
|
6692
|
+
snippet: snippets[i] ?? "",
|
|
6693
|
+
engine: "duckduckgo-lite"
|
|
6694
|
+
}));
|
|
6695
|
+
}
|
|
6696
|
+
var WebSearchTool = class extends BaseTool {
|
|
6697
|
+
name = "web_search";
|
|
6698
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
6699
|
+
inputSchema = {
|
|
6700
|
+
type: "object",
|
|
6701
|
+
properties: {
|
|
6702
|
+
query: { type: "string", description: "The search query" },
|
|
6703
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
6704
|
+
},
|
|
6705
|
+
required: ["query"]
|
|
6706
|
+
};
|
|
6707
|
+
config;
|
|
6708
|
+
constructor(config = {}) {
|
|
6709
|
+
super();
|
|
6710
|
+
this.config = {
|
|
6711
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
6712
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
6713
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
6714
|
+
maxResults: config.maxResults ?? 5
|
|
6715
|
+
};
|
|
6716
|
+
}
|
|
6717
|
+
async execute(input, _options) {
|
|
6718
|
+
const query = input["query"];
|
|
6719
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
6720
|
+
const maxResults = Math.min(
|
|
6721
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
6722
|
+
10
|
|
6723
|
+
);
|
|
6724
|
+
const errors = [];
|
|
6725
|
+
let results = [];
|
|
6726
|
+
if (this.config.searxngUrl) {
|
|
6727
|
+
try {
|
|
6728
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
6729
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6730
|
+
errors.push("SearXNG: returned 0 results");
|
|
6731
|
+
} catch (err) {
|
|
6732
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
if (this.config.braveApiKey) {
|
|
6736
|
+
try {
|
|
6737
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
6738
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6739
|
+
errors.push("Brave: returned 0 results");
|
|
6740
|
+
} catch (err) {
|
|
6741
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
6742
|
+
}
|
|
6743
|
+
}
|
|
6744
|
+
if (this.config.tavilyApiKey) {
|
|
6745
|
+
try {
|
|
6746
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
6747
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6748
|
+
errors.push("Tavily: returned 0 results");
|
|
6749
|
+
} catch (err) {
|
|
6750
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
try {
|
|
6754
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
6755
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6756
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
6757
|
+
} catch (err) {
|
|
6758
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
6759
|
+
}
|
|
6760
|
+
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" : "";
|
|
6761
|
+
return [
|
|
6762
|
+
`Web search for "${query}" failed across all backends:`,
|
|
6763
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
6764
|
+
configHint
|
|
6765
|
+
].join("\n");
|
|
6766
|
+
}
|
|
6767
|
+
formatResults(query, results) {
|
|
6768
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
6769
|
+
for (let i = 0; i < results.length; i++) {
|
|
6770
|
+
const r = results[i];
|
|
6771
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
6772
|
+
lines.push(` URL: ${r.url}`);
|
|
6773
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
6774
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
6775
|
+
lines.push("");
|
|
6776
|
+
}
|
|
6777
|
+
return lines.join("\n");
|
|
6778
|
+
}
|
|
6779
|
+
};
|
|
6780
|
+
|
|
6078
6781
|
// src/tools/mcp.ts
|
|
6079
6782
|
var McpToolWrapper = class extends BaseTool {
|
|
6080
6783
|
name;
|
|
@@ -6196,7 +6899,8 @@ var ToolRegistry = class {
|
|
|
6196
6899
|
new ImageAnalyzeTool(),
|
|
6197
6900
|
new PDFCreateTool(),
|
|
6198
6901
|
new CodeInterpreterTool(),
|
|
6199
|
-
new PeerCommunicationTool()
|
|
6902
|
+
new PeerCommunicationTool(),
|
|
6903
|
+
new WebSearchTool(this.config.webSearch)
|
|
6200
6904
|
];
|
|
6201
6905
|
for (const tool of tools) {
|
|
6202
6906
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -6220,8 +6924,23 @@ var ToolRegistry = class {
|
|
|
6220
6924
|
return this.ignoreMatcher.ignores(posixRel);
|
|
6221
6925
|
}
|
|
6222
6926
|
};
|
|
6223
|
-
var McpClient = class {
|
|
6927
|
+
var McpClient = class _McpClient {
|
|
6928
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
6929
|
+
/**
|
|
6930
|
+
* Forcefully kills all known MCP child processes.
|
|
6931
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
6932
|
+
*/
|
|
6933
|
+
static killAllProcesses() {
|
|
6934
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
6935
|
+
try {
|
|
6936
|
+
process.kill(pid, "SIGKILL");
|
|
6937
|
+
} catch {
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
_McpClient.activeProcessPids.clear();
|
|
6941
|
+
}
|
|
6224
6942
|
clients = /* @__PURE__ */ new Map();
|
|
6943
|
+
transports = /* @__PURE__ */ new Map();
|
|
6225
6944
|
tools = /* @__PURE__ */ new Map();
|
|
6226
6945
|
trustedServers;
|
|
6227
6946
|
approvalCallback;
|
|
@@ -6250,6 +6969,8 @@ var McpClient = class {
|
|
|
6250
6969
|
);
|
|
6251
6970
|
await client.connect(transport);
|
|
6252
6971
|
this.clients.set(server.name, client);
|
|
6972
|
+
this.transports.set(server.name, transport);
|
|
6973
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
6253
6974
|
const toolsResult = await client.listTools();
|
|
6254
6975
|
for (const tool of toolsResult.tools) {
|
|
6255
6976
|
for (const existing of this.tools.values()) {
|
|
@@ -6271,8 +6992,11 @@ var McpClient = class {
|
|
|
6271
6992
|
async disconnect(serverName) {
|
|
6272
6993
|
const client = this.clients.get(serverName);
|
|
6273
6994
|
if (client) {
|
|
6995
|
+
const transport = this.transports.get(serverName);
|
|
6996
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
6274
6997
|
await client.close();
|
|
6275
6998
|
this.clients.delete(serverName);
|
|
6999
|
+
this.transports.delete(serverName);
|
|
6276
7000
|
for (const key of this.tools.keys()) {
|
|
6277
7001
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
6278
7002
|
}
|
|
@@ -6300,6 +7024,13 @@ var McpClient = class {
|
|
|
6300
7024
|
getConnectedServers() {
|
|
6301
7025
|
return Array.from(this.clients.keys());
|
|
6302
7026
|
}
|
|
7027
|
+
getActivePids() {
|
|
7028
|
+
const pids = [];
|
|
7029
|
+
for (const transport of this.transports.values()) {
|
|
7030
|
+
if (transport.pid) pids.push(transport.pid);
|
|
7031
|
+
}
|
|
7032
|
+
return pids;
|
|
7033
|
+
}
|
|
6303
7034
|
isConnected(serverName) {
|
|
6304
7035
|
return this.clients.has(serverName);
|
|
6305
7036
|
}
|
|
@@ -6868,12 +7599,25 @@ var Cascade = class extends EventEmitter__default.default {
|
|
|
6868
7599
|
looksLikeSimpleArtifactTask(prompt) {
|
|
6869
7600
|
return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
|
|
6870
7601
|
}
|
|
6871
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
7602
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
6872
7603
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
6873
7604
|
return "Simple";
|
|
6874
7605
|
}
|
|
7606
|
+
let workspaceContext = "";
|
|
7607
|
+
try {
|
|
7608
|
+
const files = await glob.glob("**/*.*", {
|
|
7609
|
+
cwd: workspacePath,
|
|
7610
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
7611
|
+
nodir: true
|
|
7612
|
+
});
|
|
7613
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
7614
|
+
} catch {
|
|
7615
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
7616
|
+
}
|
|
6875
7617
|
const sysPrompt = `You are a routing classifier for a hierarchical AI system. Determine task complexity using BOTH the latest user message and the recent conversation context.
|
|
6876
7618
|
|
|
7619
|
+
${workspaceContext}
|
|
7620
|
+
|
|
6877
7621
|
Classification:
|
|
6878
7622
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
6879
7623
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -6948,7 +7692,7 @@ ${prompt}` : prompt;
|
|
|
6948
7692
|
}
|
|
6949
7693
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
6950
7694
|
});
|
|
6951
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
7695
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
6952
7696
|
this.telemetry.capture("cascade:session_start", {
|
|
6953
7697
|
complexity,
|
|
6954
7698
|
providerCount: this.config.providers.length,
|
|
@@ -7028,7 +7772,7 @@ ${prompt}` : prompt;
|
|
|
7028
7772
|
peerT3Ids: [],
|
|
7029
7773
|
parentT2: "root"
|
|
7030
7774
|
};
|
|
7031
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
7775
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
7032
7776
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
7033
7777
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
7034
7778
|
} else if (complexity === "Moderate") {
|
|
@@ -7051,7 +7795,7 @@ ${prompt}` : prompt;
|
|
|
7051
7795
|
constraints: [],
|
|
7052
7796
|
t3Subtasks: []
|
|
7053
7797
|
};
|
|
7054
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
7798
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
7055
7799
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
7056
7800
|
t2Results = [t2Result];
|
|
7057
7801
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -7073,13 +7817,22 @@ ${prompt}` : prompt;
|
|
|
7073
7817
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
7074
7818
|
bindTierEvents(t1);
|
|
7075
7819
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
7076
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
7820
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
7077
7821
|
finalOutput = result.output;
|
|
7078
7822
|
t2Results = result.t2Results;
|
|
7079
7823
|
}
|
|
7080
7824
|
} catch (err) {
|
|
7081
|
-
|
|
7082
|
-
|
|
7825
|
+
if (err instanceof CascadeCancelledError) {
|
|
7826
|
+
this.emit("run:cancelled", {
|
|
7827
|
+
taskId,
|
|
7828
|
+
reason: err.message,
|
|
7829
|
+
partialOutput: finalOutput || ""
|
|
7830
|
+
});
|
|
7831
|
+
runError = null;
|
|
7832
|
+
} else {
|
|
7833
|
+
runError = err;
|
|
7834
|
+
throw err;
|
|
7835
|
+
}
|
|
7083
7836
|
} finally {
|
|
7084
7837
|
try {
|
|
7085
7838
|
escalator.cancelAllPending();
|
|
@@ -7394,11 +8147,6 @@ var SlashCommandRegistry = class {
|
|
|
7394
8147
|
description: "Show active models per tier",
|
|
7395
8148
|
handler: (_args, ctx) => ({ output: ctx.onModelInfo(), handled: true })
|
|
7396
8149
|
});
|
|
7397
|
-
this.register({
|
|
7398
|
-
command: "/models",
|
|
7399
|
-
description: "Browse available models by provider",
|
|
7400
|
-
handler: (_args, ctx) => ({ output: ctx.onModelsInfo(), handled: true })
|
|
7401
|
-
});
|
|
7402
8150
|
this.register({
|
|
7403
8151
|
command: "/providers",
|
|
7404
8152
|
description: "Show configured providers",
|
|
@@ -7814,6 +8562,9 @@ var ModelsDisplay = ({
|
|
|
7814
8562
|
});
|
|
7815
8563
|
const title = step === "PROVIDER" ? "\u25C8 SELECT PROVIDER" : step === "TIER" ? `\u25C8 APPLY ${picked.provider === "auto" ? "AUTO" : String(picked.provider).toUpperCase()} TO WHICH TIER?` : `\u25C8 ${String(picked.provider).toUpperCase()} \u2192 SELECT MODEL FOR ${picked.tier}`;
|
|
7816
8564
|
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}`;
|
|
8565
|
+
const PAGE_SIZE = 8;
|
|
8566
|
+
const viewStart = Math.max(0, Math.min(cursor - Math.floor(PAGE_SIZE / 2), currentItems.length - PAGE_SIZE));
|
|
8567
|
+
const visibleItems = currentItems.slice(viewStart, viewStart + PAGE_SIZE);
|
|
7817
8568
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
7818
8569
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { justifyContent: "space-between", children: [
|
|
7819
8570
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "cyan", children: title }),
|
|
@@ -7823,16 +8574,29 @@ var ModelsDisplay = ({
|
|
|
7823
8574
|
breadcrumb,
|
|
7824
8575
|
" \xB7 \u2191/\u2193 navigate \xB7 1\u20139 jump"
|
|
7825
8576
|
] }) }),
|
|
7826
|
-
currentItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxRuntime.
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
8577
|
+
currentItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
8578
|
+
viewStart > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", dimColor: true, children: [
|
|
8579
|
+
" \u2191 ",
|
|
8580
|
+
viewStart,
|
|
8581
|
+
" more above"
|
|
8582
|
+
] }),
|
|
8583
|
+
visibleItems.map((item, i) => {
|
|
8584
|
+
const globalIdx = viewStart + i;
|
|
8585
|
+
const focused = globalIdx === cursor;
|
|
8586
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
|
|
8587
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? "green" : "gray", children: focused ? "\u276F " : " " }),
|
|
8588
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
8589
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? "white" : "gray", bold: focused, children: item.label }),
|
|
8590
|
+
item.sublabel && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", dimColor: true, children: ` ${item.sublabel}` })
|
|
8591
|
+
] })
|
|
8592
|
+
] }, `${step}-${item.value}-${globalIdx}`);
|
|
8593
|
+
}),
|
|
8594
|
+
viewStart + PAGE_SIZE < currentItems.length && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", dimColor: true, children: [
|
|
8595
|
+
" \u2193 ",
|
|
8596
|
+
currentItems.length - viewStart - PAGE_SIZE,
|
|
8597
|
+
" more below"
|
|
8598
|
+
] })
|
|
8599
|
+
] })
|
|
7836
8600
|
] });
|
|
7837
8601
|
};
|
|
7838
8602
|
function CostTracker({
|
|
@@ -8026,13 +8790,18 @@ function replReducer(state, action) {
|
|
|
8026
8790
|
async function refreshModelCache(store, providers) {
|
|
8027
8791
|
for (const provider of providers) {
|
|
8028
8792
|
try {
|
|
8029
|
-
const
|
|
8793
|
+
const dummyId = provider.type === "azure" ? provider.deploymentName || "azure-model" : "dummy";
|
|
8794
|
+
const dummyModel = { id: dummyId, name: dummyId, provider: provider.type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
8030
8795
|
let instance;
|
|
8031
8796
|
if (provider.type === "openai") instance = new OpenAIProvider(provider, dummyModel);
|
|
8032
8797
|
else if (provider.type === "gemini") instance = new GeminiProvider(provider, dummyModel);
|
|
8033
8798
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
8034
8799
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
8035
8800
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
8801
|
+
else if (provider.type === "azure") {
|
|
8802
|
+
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
8803
|
+
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
8804
|
+
}
|
|
8036
8805
|
if (instance) {
|
|
8037
8806
|
const fetched = await instance.listModels();
|
|
8038
8807
|
for (const m of fetched) store.upsertCachedModel(m);
|
|
@@ -8170,7 +8939,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
|
|
|
8170
8939
|
};
|
|
8171
8940
|
const store = new MemoryStore(path17__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
8172
8941
|
storeRef.current = store;
|
|
8173
|
-
globalStoreRef.current = new MemoryStore(path17__default.default.join(
|
|
8942
|
+
globalStoreRef.current = new MemoryStore(path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
8174
8943
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
8175
8944
|
setIdentities(identityRows);
|
|
8176
8945
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -8655,14 +9424,29 @@ Use /identity <name|id> to switch.`;
|
|
|
8655
9424
|
const isAutoScrollingRef = React5.useRef(true);
|
|
8656
9425
|
const width = stdout?.columns ?? 100;
|
|
8657
9426
|
const height = stdout?.rows ?? 24;
|
|
8658
|
-
const
|
|
9427
|
+
const hasActiveOrFailed2 = (node) => {
|
|
9428
|
+
if (node.status === "ACTIVE" || node.status === "FAILED") return true;
|
|
9429
|
+
return node.children?.some(hasActiveOrFailed2) ?? false;
|
|
9430
|
+
};
|
|
9431
|
+
let agentTreeHeight = 0;
|
|
9432
|
+
if (state.agentTree && hasActiveOrFailed2(state.agentTree)) {
|
|
9433
|
+
agentTreeHeight = 1;
|
|
9434
|
+
const childrenCount = state.agentTree.children?.length ?? 0;
|
|
9435
|
+
agentTreeHeight += Math.min(childrenCount, 6);
|
|
9436
|
+
if (childrenCount > 6) agentTreeHeight += 1;
|
|
9437
|
+
}
|
|
9438
|
+
let timelineHeight = 0;
|
|
9439
|
+
if (state.showDetails && treeNodesRef.current.size > 0) {
|
|
9440
|
+
timelineHeight = 1;
|
|
9441
|
+
timelineHeight += Math.min(3, treeNodesRef.current.size);
|
|
9442
|
+
}
|
|
9443
|
+
const statusHeight = agentTreeHeight + timelineHeight;
|
|
8659
9444
|
const costHeight = state.showCost ? 6 : 0;
|
|
8660
9445
|
const approvalHeight = state.approvalRequest ? 12 : 0;
|
|
8661
|
-
|
|
8662
|
-
const slashHeight =
|
|
9446
|
+
Math.min(SLASH_PAGE_SIZE, slashEntries.length);
|
|
9447
|
+
const slashHeight = isTypingCommand ? SLASH_PAGE_SIZE + 2 : 0;
|
|
8663
9448
|
const chromeHeight = statusHeight + costHeight + approvalHeight + slashHeight + 7;
|
|
8664
|
-
const
|
|
8665
|
-
const availableHeight = Math.max(4, totalCap - chromeHeight);
|
|
9449
|
+
const availableHeight = Math.max(4, height - chromeHeight);
|
|
8666
9450
|
const allLines = formatToLines(
|
|
8667
9451
|
state.isStreaming ? [...state.messages, { id: "stream", role: "assistant", content: state.streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }] : state.messages,
|
|
8668
9452
|
width - 4,
|
|
@@ -9043,10 +9827,14 @@ function wizardReducer(state, action) {
|
|
|
9043
9827
|
return { ...state, currentEntryIdx: next };
|
|
9044
9828
|
}
|
|
9045
9829
|
case "ADD_AZURE": {
|
|
9830
|
+
const prevAzure = state.entries.find((e) => e.type === "azure");
|
|
9046
9831
|
const newEntry = {
|
|
9047
9832
|
id: crypto.randomUUID(),
|
|
9048
9833
|
type: "azure",
|
|
9049
|
-
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}
|
|
9834
|
+
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}`,
|
|
9835
|
+
baseUrl: prevAzure?.baseUrl,
|
|
9836
|
+
apiKey: prevAzure?.apiKey,
|
|
9837
|
+
apiVersion: prevAzure?.apiVersion
|
|
9050
9838
|
};
|
|
9051
9839
|
return {
|
|
9052
9840
|
...state,
|
|
@@ -9068,8 +9856,21 @@ function wizardReducer(state, action) {
|
|
|
9068
9856
|
addingAnotherCompat: false
|
|
9069
9857
|
};
|
|
9070
9858
|
}
|
|
9859
|
+
case "ADD_OLLAMA": {
|
|
9860
|
+
const newEntry = {
|
|
9861
|
+
id: crypto.randomUUID(),
|
|
9862
|
+
type: "ollama",
|
|
9863
|
+
label: `Ollama endpoint ${state.entries.filter((e) => e.type === "ollama").length + 1}`
|
|
9864
|
+
};
|
|
9865
|
+
return {
|
|
9866
|
+
...state,
|
|
9867
|
+
entries: [...state.entries, newEntry],
|
|
9868
|
+
currentEntryIdx: state.entries.length,
|
|
9869
|
+
addingAnotherOllama: false
|
|
9870
|
+
};
|
|
9871
|
+
}
|
|
9071
9872
|
case "SKIP_MORE":
|
|
9072
|
-
return { ...state, addingAnotherAzure: false, addingAnotherCompat: false, step: "FETCH_MODELS", currentEntryIdx: 0 };
|
|
9873
|
+
return { ...state, addingAnotherAzure: false, addingAnotherCompat: false, addingAnotherOllama: false, step: "FETCH_MODELS", currentEntryIdx: 0 };
|
|
9073
9874
|
case "GO_FETCH":
|
|
9074
9875
|
return { ...state, step: "FETCH_MODELS", fetchLog: [], fetchedModels: [] };
|
|
9075
9876
|
case "SET_FETCH_LOG":
|
|
@@ -9102,6 +9903,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9102
9903
|
currentEntryIdx: 0,
|
|
9103
9904
|
addingAnotherAzure: false,
|
|
9104
9905
|
addingAnotherCompat: false,
|
|
9906
|
+
addingAnotherOllama: false,
|
|
9105
9907
|
fetchedModels: [],
|
|
9106
9908
|
fetchLog: [],
|
|
9107
9909
|
tierT1: "auto",
|
|
@@ -9155,8 +9957,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9155
9957
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
9156
9958
|
} else if (type === "azure") {
|
|
9157
9959
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
9158
|
-
const
|
|
9159
|
-
const
|
|
9960
|
+
const actualModelId = deploymentName || `azure-${entry.id}`;
|
|
9961
|
+
const dummyModel = { id: actualModelId, name: actualModelId, provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
9962
|
+
const p = new AzureOpenAIProvider2({ type, apiKey, baseUrl, deploymentName, apiVersion: entry.apiVersion }, dummyModel);
|
|
9160
9963
|
const fetched = await p.listModels();
|
|
9161
9964
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
9162
9965
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
@@ -9177,7 +9980,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9177
9980
|
type: e.type,
|
|
9178
9981
|
...e.apiKey ? { apiKey: e.apiKey } : {},
|
|
9179
9982
|
...e.baseUrl ? { baseUrl: e.baseUrl } : {},
|
|
9180
|
-
...e.deploymentName ? { deploymentName: e.deploymentName } : {}
|
|
9983
|
+
...e.deploymentName ? { deploymentName: e.deploymentName } : {},
|
|
9984
|
+
...e.apiVersion ? { apiVersion: e.apiVersion } : {}
|
|
9181
9985
|
}));
|
|
9182
9986
|
const models = {};
|
|
9183
9987
|
if (state.tierT1 !== "auto") models["t1"] = state.tierT1;
|
|
@@ -9199,25 +10003,28 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9199
10003
|
}, [state.step, state.entries, state.tierT1, state.tierT2, state.tierT3, workspacePath, onComplete, exit]);
|
|
9200
10004
|
ink.useInput((_input, key) => {
|
|
9201
10005
|
if (state.step === "PROVIDER_SELECT") {
|
|
9202
|
-
if (key.upArrow) setProviderCursor((p) =>
|
|
9203
|
-
if (key.downArrow) setProviderCursor((p) =>
|
|
10006
|
+
if (key.upArrow) setProviderCursor((p) => p <= 0 ? providerOrder.length - 1 : p - 1);
|
|
10007
|
+
if (key.downArrow) setProviderCursor((p) => p >= providerOrder.length - 1 ? 0 : p + 1);
|
|
9204
10008
|
if (_input === " ") dispatch({ type: "TOGGLE_PROVIDER", provider: providerOrder[providerCursor] });
|
|
9205
10009
|
if (_input === "a") dispatch({ type: "TOGGLE_ALL" });
|
|
9206
10010
|
if (_input === "i") dispatch({ type: "INVERT_SELECTION" });
|
|
9207
10011
|
if (key.return) {
|
|
9208
|
-
if (state.selectedTypes.size === 0)
|
|
10012
|
+
if (state.selectedTypes.size === 0) {
|
|
10013
|
+
dispatch({ type: "SET_ERROR", error: "Please select at least one provider using <space> before pressing <enter>." });
|
|
10014
|
+
return;
|
|
10015
|
+
}
|
|
9209
10016
|
dispatch({ type: "CONFIRM_PROVIDERS" });
|
|
9210
|
-
|
|
10017
|
+
const firstType = [...state.selectedTypes][0];
|
|
10018
|
+
setFieldStage(firstType === "azure" ? "deploymentName" : firstType === "openai-compatible" ? "label" : firstType === "ollama" ? "baseUrl" : "apiKey");
|
|
9211
10019
|
setFieldBuffer("");
|
|
9212
10020
|
}
|
|
9213
10021
|
}
|
|
9214
10022
|
if (state.step === "TIER_ASSIGN") {
|
|
9215
|
-
if (key.tab || key.
|
|
10023
|
+
if (key.tab || key.rightArrow) {
|
|
9216
10024
|
const order = ["T1", "T2", "T3"];
|
|
9217
10025
|
const idx = order.indexOf(state.tierSelectFocus);
|
|
9218
10026
|
dispatch({ type: "SET_TIER_FOCUS", tier: order[(idx + 1) % 3] });
|
|
9219
10027
|
}
|
|
9220
|
-
if (key.return) dispatch({ type: "GO_SAVE" });
|
|
9221
10028
|
}
|
|
9222
10029
|
});
|
|
9223
10030
|
const currentEntry = state.entries[state.currentEntryIdx];
|
|
@@ -9227,7 +10034,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9227
10034
|
if (fieldStage === "deploymentName") {
|
|
9228
10035
|
dispatch({ type: "SET_ENTRY_FIELD", field: "deploymentName", value: val });
|
|
9229
10036
|
setFieldBuffer("");
|
|
9230
|
-
|
|
10037
|
+
if (currentEntry.baseUrl && currentEntry.apiKey && currentEntry.apiVersion) {
|
|
10038
|
+
setFieldStage("askMore");
|
|
10039
|
+
} else {
|
|
10040
|
+
setFieldStage("baseUrl");
|
|
10041
|
+
}
|
|
9231
10042
|
} else if (fieldStage === "baseUrl") {
|
|
9232
10043
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
|
|
9233
10044
|
setFieldBuffer("");
|
|
@@ -9235,6 +10046,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9235
10046
|
} else if (fieldStage === "apiKey") {
|
|
9236
10047
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9237
10048
|
setFieldBuffer("");
|
|
10049
|
+
setFieldStage("apiVersion");
|
|
10050
|
+
} else if (fieldStage === "apiVersion") {
|
|
10051
|
+
dispatch({ type: "SET_ENTRY_FIELD", field: "apiVersion", value: val || "2024-08-01-preview" });
|
|
10052
|
+
setFieldBuffer("");
|
|
9238
10053
|
setFieldStage("askMore");
|
|
9239
10054
|
}
|
|
9240
10055
|
} else if (currentEntry.type === "openai-compatible") {
|
|
@@ -9254,13 +10069,15 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9254
10069
|
} else if (currentEntry.type === "ollama") {
|
|
9255
10070
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
|
|
9256
10071
|
setFieldBuffer("");
|
|
9257
|
-
|
|
9258
|
-
setFieldStage("apiKey");
|
|
10072
|
+
setFieldStage("askMore");
|
|
9259
10073
|
} else {
|
|
9260
10074
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9261
10075
|
setFieldBuffer("");
|
|
10076
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10077
|
+
if (nextEntry) {
|
|
10078
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10079
|
+
}
|
|
9262
10080
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9263
|
-
setFieldStage("apiKey");
|
|
9264
10081
|
}
|
|
9265
10082
|
}, [currentEntry, fieldStage]);
|
|
9266
10083
|
if (state.step === "PROVIDER_SELECT") {
|
|
@@ -9293,7 +10110,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9293
10110
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
9294
10111
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, children: [
|
|
9295
10112
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", bold: true, children: "? " }),
|
|
9296
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: isAzure ? "Add another Azure deployment? (y/n)" : "Add another custom endpoint? (y/n)" })
|
|
10113
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: isAzure ? "Add another Azure deployment? (y/n)" : isOllama ? "Add another Ollama endpoint? (y/n)" : "Add another custom endpoint? (y/n)" })
|
|
9297
10114
|
] }),
|
|
9298
10115
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9299
10116
|
SelectInput__default.default,
|
|
@@ -9307,12 +10124,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9307
10124
|
onSelect: (item) => {
|
|
9308
10125
|
if (item.value === "yes") {
|
|
9309
10126
|
if (isAzure) dispatch({ type: "ADD_AZURE" });
|
|
10127
|
+
else if (isOllama) dispatch({ type: "ADD_OLLAMA" });
|
|
9310
10128
|
else dispatch({ type: "ADD_COMPAT" });
|
|
9311
|
-
setFieldStage(isAzure ? "deploymentName" : "label");
|
|
10129
|
+
setFieldStage(isAzure ? "deploymentName" : isOllama ? "baseUrl" : "label");
|
|
9312
10130
|
setFieldBuffer("");
|
|
9313
10131
|
} else {
|
|
10132
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10133
|
+
if (nextEntry) {
|
|
10134
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10135
|
+
}
|
|
9314
10136
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9315
|
-
setFieldStage("apiKey");
|
|
9316
10137
|
setFieldBuffer("");
|
|
9317
10138
|
}
|
|
9318
10139
|
}
|
|
@@ -9321,7 +10142,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9321
10142
|
] });
|
|
9322
10143
|
}
|
|
9323
10144
|
const prompt = isAzure && fieldStage === "deploymentName" ? `Azure deployment name (${currentEntry.label})` : isAzure && fieldStage === "baseUrl" ? `Azure endpoint URL` : isCompat && fieldStage === "label" ? `Name for this endpoint (e.g. Groq)` : isCompat && fieldStage === "baseUrl" ? `Base URL (e.g. https://api.groq.com/openai/v1)` : isOllama ? `Ollama URL (Enter for http://localhost:11434)` : `${currentEntry.label} API Key`;
|
|
9324
|
-
const isMasked = fieldStage === "apiKey";
|
|
10145
|
+
const isMasked = fieldStage === "apiKey" && !isOllama;
|
|
9325
10146
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
9326
10147
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, children: [
|
|
9327
10148
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", bold: true, children: "? " }),
|
|
@@ -9393,7 +10214,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9393
10214
|
SelectInput__default.default,
|
|
9394
10215
|
{
|
|
9395
10216
|
items: modelOptions,
|
|
9396
|
-
onSelect: (item) =>
|
|
10217
|
+
onSelect: (item) => {
|
|
10218
|
+
dispatch({ type: "SET_TIER", tier, value: item.value });
|
|
10219
|
+
const order = ["T1", "T2", "T3"];
|
|
10220
|
+
const idx = order.indexOf(tier);
|
|
10221
|
+
if (idx < 2) {
|
|
10222
|
+
dispatch({ type: "SET_TIER_FOCUS", tier: order[idx + 1] });
|
|
10223
|
+
} else {
|
|
10224
|
+
dispatch({ type: "GO_SAVE" });
|
|
10225
|
+
}
|
|
10226
|
+
},
|
|
9397
10227
|
indicatorComponent: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", children: isSelected ? "\u276F " : " " }),
|
|
9398
10228
|
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSelected ? "magenta" : "white", children: label })
|
|
9399
10229
|
}
|
|
@@ -9871,7 +10701,7 @@ var DashboardServer = class {
|
|
|
9871
10701
|
// ── Setup ─────────────────────────────────────
|
|
9872
10702
|
getGlobalStore() {
|
|
9873
10703
|
if (!this.globalStore) {
|
|
9874
|
-
const globalDbPath = path17__default.default.join(
|
|
10704
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9875
10705
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
9876
10706
|
}
|
|
9877
10707
|
return this.globalStore;
|
|
@@ -9933,7 +10763,7 @@ var DashboardServer = class {
|
|
|
9933
10763
|
}
|
|
9934
10764
|
watchRuntimeChanges() {
|
|
9935
10765
|
const workspaceDbPath = path17__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
9936
|
-
const globalDbPath = path17__default.default.join(
|
|
10766
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9937
10767
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
9938
10768
|
for (const watchPath of watchPaths) {
|
|
9939
10769
|
if (!fs14__default.default.existsSync(watchPath)) continue;
|
|
@@ -10041,7 +10871,7 @@ var DashboardServer = class {
|
|
|
10041
10871
|
const sessionId = req.params.id;
|
|
10042
10872
|
this.store.deleteSession(sessionId);
|
|
10043
10873
|
this.store.deleteRuntimeSession(sessionId);
|
|
10044
|
-
const globalDbPath = path17__default.default.join(
|
|
10874
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10045
10875
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10046
10876
|
try {
|
|
10047
10877
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -10055,7 +10885,7 @@ var DashboardServer = class {
|
|
|
10055
10885
|
});
|
|
10056
10886
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
10057
10887
|
const body = req.body;
|
|
10058
|
-
const globalDbPath = path17__default.default.join(
|
|
10888
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10059
10889
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
10060
10890
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10061
10891
|
try {
|
|
@@ -10078,7 +10908,7 @@ var DashboardServer = class {
|
|
|
10078
10908
|
});
|
|
10079
10909
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
10080
10910
|
this.store.deleteAllRuntimeNodes();
|
|
10081
|
-
const globalDbPath = path17__default.default.join(
|
|
10911
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10082
10912
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10083
10913
|
try {
|
|
10084
10914
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -10174,7 +11004,7 @@ var DashboardServer = class {
|
|
|
10174
11004
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
10175
11005
|
const scope = req.query["scope"] ?? "workspace";
|
|
10176
11006
|
if (scope === "global") {
|
|
10177
|
-
const globalDbPath = path17__default.default.join(
|
|
11007
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10178
11008
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10179
11009
|
try {
|
|
10180
11010
|
res.json({
|
|
@@ -10424,7 +11254,15 @@ async function exportCommand(options = {}) {
|
|
|
10424
11254
|
const spin = ora__default.default({ text: "Loading sessions\u2026", color: "magenta" }).start();
|
|
10425
11255
|
let store;
|
|
10426
11256
|
try {
|
|
10427
|
-
const
|
|
11257
|
+
const workspacePath = options.workspacePath ?? process.cwd();
|
|
11258
|
+
const workspaceDbPath = path17__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
11259
|
+
const globalDbPath = path17__default.default.join(os3__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
11260
|
+
let dbPath = globalDbPath;
|
|
11261
|
+
try {
|
|
11262
|
+
await fs7__default.default.access(workspaceDbPath);
|
|
11263
|
+
dbPath = workspaceDbPath;
|
|
11264
|
+
} catch {
|
|
11265
|
+
}
|
|
10428
11266
|
store = new MemoryStore(dbPath);
|
|
10429
11267
|
} catch (err) {
|
|
10430
11268
|
spin.fail(chalk8__default.default.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -10563,6 +11401,15 @@ async function telemetryCommand(action) {
|
|
|
10563
11401
|
|
|
10564
11402
|
// src/cli/index.ts
|
|
10565
11403
|
dotenv__default.default.config();
|
|
11404
|
+
process.on("exit", () => McpClient.killAllProcesses());
|
|
11405
|
+
process.on("SIGINT", () => {
|
|
11406
|
+
McpClient.killAllProcesses();
|
|
11407
|
+
process.exit(0);
|
|
11408
|
+
});
|
|
11409
|
+
process.on("SIGTERM", () => {
|
|
11410
|
+
McpClient.killAllProcesses();
|
|
11411
|
+
process.exit(0);
|
|
11412
|
+
});
|
|
10566
11413
|
var program = new commander.Command();
|
|
10567
11414
|
program.name("cascade").description("Multi-tier AI orchestration CLI").version(CASCADE_VERSION, "-v, --version").option("-p, --prompt <text>", "Run a single prompt non-interactively").option("-t, --theme <name>", "Color theme", DEFAULT_THEME).option("-w, --workspace <path>", "Workspace path", process.cwd()).option("-i, --identity <name>", "Identity name or ID").option("--no-color", "Disable colors").action(async (options) => {
|
|
10568
11415
|
await startRepl(options);
|