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/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var EventEmitter = require('events');
|
|
4
4
|
var crypto = require('crypto');
|
|
5
|
+
var glob = require('glob');
|
|
5
6
|
var Anthropic = require('@anthropic-ai/sdk');
|
|
6
7
|
var OpenAI = require('openai');
|
|
7
8
|
var genai = require('@google/genai');
|
|
@@ -18,7 +19,7 @@ var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
|
18
19
|
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
19
20
|
var zod = require('zod');
|
|
20
21
|
var vm = require('vm');
|
|
21
|
-
var
|
|
22
|
+
var os2 = require('os');
|
|
22
23
|
var Database = require('better-sqlite3');
|
|
23
24
|
var http = require('http');
|
|
24
25
|
var url = require('url');
|
|
@@ -61,7 +62,7 @@ var path13__default = /*#__PURE__*/_interopDefault(path13);
|
|
|
61
62
|
var ignoreFactory__namespace = /*#__PURE__*/_interopNamespace(ignoreFactory);
|
|
62
63
|
var fs11__default = /*#__PURE__*/_interopDefault(fs11);
|
|
63
64
|
var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
|
|
64
|
-
var
|
|
65
|
+
var os2__default = /*#__PURE__*/_interopDefault(os2);
|
|
65
66
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
66
67
|
var express__default = /*#__PURE__*/_interopDefault(express);
|
|
67
68
|
var rateLimit__default = /*#__PURE__*/_interopDefault(rateLimit);
|
|
@@ -164,7 +165,7 @@ var require_keytar2 = __commonJS({
|
|
|
164
165
|
});
|
|
165
166
|
|
|
166
167
|
// src/constants.ts
|
|
167
|
-
var CASCADE_VERSION = "0.2.
|
|
168
|
+
var CASCADE_VERSION = "0.2.12";
|
|
168
169
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
169
170
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
170
171
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -467,7 +468,8 @@ var TOOL_NAMES = {
|
|
|
467
468
|
IMAGE_ANALYZE: "image_analyze",
|
|
468
469
|
PDF_CREATE: "pdf_create",
|
|
469
470
|
RUN_CODE: "run_code",
|
|
470
|
-
PEER_MESSAGE: "peer_message"
|
|
471
|
+
PEER_MESSAGE: "peer_message",
|
|
472
|
+
WEB_SEARCH: "web_search"
|
|
471
473
|
};
|
|
472
474
|
var DEFAULT_APPROVAL_REQUIRED = [
|
|
473
475
|
TOOL_NAMES.SHELL,
|
|
@@ -545,17 +547,38 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
545
547
|
messages,
|
|
546
548
|
tools: tools?.length ? tools : void 0
|
|
547
549
|
});
|
|
550
|
+
let isThinking = false;
|
|
548
551
|
for await (const event of stream) {
|
|
549
|
-
if (event.type === "content_block_delta"
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
552
|
+
if (event.type === "content_block_delta") {
|
|
553
|
+
if (event.delta.type === "thinking_delta") {
|
|
554
|
+
if (!isThinking) {
|
|
555
|
+
isThinking = true;
|
|
556
|
+
fullContent += "<think>\n";
|
|
557
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
558
|
+
}
|
|
559
|
+
const text = event.delta.thinking;
|
|
560
|
+
fullContent += text;
|
|
561
|
+
onChunk({ text, finishReason: null });
|
|
562
|
+
} else if (event.delta.type === "text_delta") {
|
|
563
|
+
if (isThinking) {
|
|
564
|
+
isThinking = false;
|
|
565
|
+
fullContent += "\n</think>\n\n";
|
|
566
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
567
|
+
}
|
|
568
|
+
const text = event.delta.text;
|
|
569
|
+
fullContent += text;
|
|
570
|
+
onChunk({ text, finishReason: null });
|
|
571
|
+
}
|
|
553
572
|
} else if (event.type === "message_delta" && event.usage) {
|
|
554
573
|
outputTokens = event.usage.output_tokens;
|
|
555
574
|
} else if (event.type === "message_start" && event.message.usage) {
|
|
556
575
|
inputTokens = event.message.usage.input_tokens;
|
|
557
576
|
}
|
|
558
577
|
}
|
|
578
|
+
if (isThinking) {
|
|
579
|
+
fullContent += "\n</think>\n\n";
|
|
580
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
581
|
+
}
|
|
559
582
|
const finalMessage = await stream.finalMessage();
|
|
560
583
|
const toolCalls = finalMessage.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
561
584
|
id: b.id,
|
|
@@ -616,33 +639,61 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
616
639
|
}
|
|
617
640
|
}
|
|
618
641
|
convertMessages(messages) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
642
|
+
const result = [];
|
|
643
|
+
for (const m of messages) {
|
|
644
|
+
if (m.role === "system") continue;
|
|
645
|
+
if (m.role === "tool") {
|
|
646
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
647
|
+
result.push({
|
|
648
|
+
role: "user",
|
|
649
|
+
content: [{
|
|
650
|
+
type: "tool_result",
|
|
651
|
+
tool_use_id: m.toolCallId ?? "",
|
|
652
|
+
content: toolContent
|
|
653
|
+
}]
|
|
654
|
+
});
|
|
655
|
+
continue;
|
|
622
656
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
657
|
+
if (m.role === "assistant") {
|
|
658
|
+
const content = [];
|
|
659
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
660
|
+
if (text) content.push({ type: "text", text });
|
|
661
|
+
for (const tc of m.toolCalls ?? []) {
|
|
662
|
+
content.push({
|
|
663
|
+
type: "tool_use",
|
|
664
|
+
id: tc.id,
|
|
665
|
+
name: tc.name,
|
|
666
|
+
input: tc.input
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (content.length > 0) {
|
|
670
|
+
result.push({ role: "assistant", content });
|
|
671
|
+
}
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (m.role === "user") {
|
|
675
|
+
if (typeof m.content === "string") {
|
|
676
|
+
result.push({ role: "user", content: m.content });
|
|
677
|
+
} else {
|
|
678
|
+
const content = m.content.map((block) => {
|
|
679
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
680
|
+
if (block.type === "image") {
|
|
681
|
+
const img = block.image;
|
|
682
|
+
if (img.type === "base64") {
|
|
683
|
+
return {
|
|
684
|
+
type: "image",
|
|
685
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
686
|
+
};
|
|
634
687
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
};
|
|
688
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
689
|
+
}
|
|
690
|
+
return { type: "text", text: "" };
|
|
691
|
+
});
|
|
692
|
+
result.push({ role: "user", content });
|
|
641
693
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return result;
|
|
646
697
|
}
|
|
647
698
|
};
|
|
648
699
|
var OpenAIProvider = class extends BaseProvider {
|
|
@@ -698,9 +749,25 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
698
749
|
}
|
|
699
750
|
}
|
|
700
751
|
const toolCallsMap = {};
|
|
752
|
+
let isThinking = false;
|
|
701
753
|
for await (const chunk of stream) {
|
|
702
754
|
const delta = chunk.choices[0]?.delta;
|
|
755
|
+
const reasoningContent = delta?.reasoning_content;
|
|
756
|
+
if (reasoningContent) {
|
|
757
|
+
if (!isThinking) {
|
|
758
|
+
isThinking = true;
|
|
759
|
+
fullContent += "<think>\n";
|
|
760
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
761
|
+
}
|
|
762
|
+
fullContent += reasoningContent;
|
|
763
|
+
onChunk({ text: reasoningContent, finishReason: null });
|
|
764
|
+
}
|
|
703
765
|
if (delta?.content) {
|
|
766
|
+
if (isThinking) {
|
|
767
|
+
isThinking = false;
|
|
768
|
+
fullContent += "\n</think>\n\n";
|
|
769
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
770
|
+
}
|
|
704
771
|
fullContent += delta.content;
|
|
705
772
|
onChunk({ text: delta.content, finishReason: null });
|
|
706
773
|
}
|
|
@@ -723,6 +790,10 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
723
790
|
outputTokens = chunk.usage.completion_tokens;
|
|
724
791
|
}
|
|
725
792
|
}
|
|
793
|
+
if (isThinking) {
|
|
794
|
+
fullContent += "\n</think>\n\n";
|
|
795
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
796
|
+
}
|
|
726
797
|
const toolCalls = Object.values(toolCallsMap).map((tc) => {
|
|
727
798
|
let input = {};
|
|
728
799
|
try {
|
|
@@ -903,7 +974,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
903
974
|
for (const part of candidate?.content?.parts ?? []) {
|
|
904
975
|
if (part.functionCall) {
|
|
905
976
|
toolCalls.push({
|
|
906
|
-
id:
|
|
977
|
+
id: part.functionCall.name,
|
|
907
978
|
name: part.functionCall.name,
|
|
908
979
|
input: part.functionCall.args ?? {}
|
|
909
980
|
});
|
|
@@ -991,10 +1062,70 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
991
1062
|
}
|
|
992
1063
|
// ── Private ──────────────────────────────────
|
|
993
1064
|
buildContents(messages, extraImages) {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1065
|
+
const contents = [];
|
|
1066
|
+
for (const m of messages) {
|
|
1067
|
+
if (m.role === "system") {
|
|
1068
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
1069
|
+
if (!text.trim()) continue;
|
|
1070
|
+
const prev = contents[contents.length - 1];
|
|
1071
|
+
if (prev?.role === "user") {
|
|
1072
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
1073
|
+
|
|
1074
|
+
` });
|
|
1075
|
+
} else {
|
|
1076
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
1077
|
+
}
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (m.role === "tool") {
|
|
1081
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1082
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1083
|
+
contents.push({
|
|
1084
|
+
role: "user",
|
|
1085
|
+
parts: [{
|
|
1086
|
+
functionResponse: {
|
|
1087
|
+
name: functionName,
|
|
1088
|
+
response: { output: toolContent }
|
|
1089
|
+
}
|
|
1090
|
+
}]
|
|
1091
|
+
});
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (m.role === "assistant") {
|
|
1095
|
+
const parts = [];
|
|
1096
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1097
|
+
if (textContent) parts.push({ text: textContent });
|
|
1098
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1099
|
+
parts.push({
|
|
1100
|
+
functionCall: {
|
|
1101
|
+
name: tc.name,
|
|
1102
|
+
args: tc.input
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
if (parts.length > 0) {
|
|
1107
|
+
contents.push({ role: "model", parts });
|
|
1108
|
+
}
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
if (m.role === "user") {
|
|
1112
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1113
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1114
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1115
|
+
if (isLastUser) {
|
|
1116
|
+
for (const img of extraImages) {
|
|
1117
|
+
if (img.type === "base64") {
|
|
1118
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (parts.length > 0) {
|
|
1124
|
+
contents.push({ role: "user", parts });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return contents;
|
|
998
1129
|
}
|
|
999
1130
|
convertMessageContent(msg, extraImages) {
|
|
1000
1131
|
const parts = [];
|
|
@@ -1861,6 +1992,29 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
1861
1992
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
1862
1993
|
}
|
|
1863
1994
|
};
|
|
1995
|
+
|
|
1996
|
+
// src/utils/retry.ts
|
|
1997
|
+
var CascadeCancelledError = class extends Error {
|
|
1998
|
+
constructor(reason) {
|
|
1999
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
2000
|
+
this.name = "CascadeCancelledError";
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
var CascadeToolError = class extends Error {
|
|
2004
|
+
/** A friendly message to show the user / T3 */
|
|
2005
|
+
userMessage;
|
|
2006
|
+
/** Whether this error class is retryable by default */
|
|
2007
|
+
retryable;
|
|
2008
|
+
constructor(userMessage, cause, retryable = false) {
|
|
2009
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause);
|
|
2010
|
+
super(`${userMessage}: ${causeMsg}`);
|
|
2011
|
+
this.name = "CascadeToolError";
|
|
2012
|
+
this.userMessage = userMessage;
|
|
2013
|
+
this.retryable = retryable;
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
|
|
2017
|
+
// src/core/tiers/base.ts
|
|
1864
2018
|
var BaseTier = class extends EventEmitter__default.default {
|
|
1865
2019
|
id;
|
|
1866
2020
|
role;
|
|
@@ -1870,6 +2024,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
1870
2024
|
label;
|
|
1871
2025
|
systemPromptOverride = "";
|
|
1872
2026
|
hierarchyContext = "";
|
|
2027
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
2028
|
+
signal;
|
|
1873
2029
|
constructor(role, id, parentId) {
|
|
1874
2030
|
super();
|
|
1875
2031
|
this.role = role;
|
|
@@ -1932,6 +2088,18 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
1932
2088
|
log(message, data) {
|
|
1933
2089
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1934
2090
|
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
2093
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
2094
|
+
* to provide a fast, clean cancellation path.
|
|
2095
|
+
*/
|
|
2096
|
+
throwIfCancelled() {
|
|
2097
|
+
if (this.signal?.aborted) {
|
|
2098
|
+
throw new CascadeCancelledError(
|
|
2099
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
1935
2103
|
};
|
|
1936
2104
|
|
|
1937
2105
|
// src/core/context/manager.ts
|
|
@@ -2132,6 +2300,7 @@ Rules:
|
|
|
2132
2300
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
2133
2301
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
2134
2302
|
- 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.
|
|
2303
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
2135
2304
|
- Use the "pdf_create" tool for PDF requests.
|
|
2136
2305
|
- 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.
|
|
2137
2306
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -2175,7 +2344,8 @@ var T3Worker = class extends BaseTier {
|
|
|
2175
2344
|
this.store = store;
|
|
2176
2345
|
this.audit = new AuditLogger(store, sessionId);
|
|
2177
2346
|
}
|
|
2178
|
-
async execute(assignment, taskId) {
|
|
2347
|
+
async execute(assignment, taskId, signal) {
|
|
2348
|
+
this.signal = signal;
|
|
2179
2349
|
this.assignment = assignment;
|
|
2180
2350
|
this.taskId = taskId;
|
|
2181
2351
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -2325,6 +2495,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
2325
2495
|
tools = [...tools];
|
|
2326
2496
|
while (iterations < MAX_ITERATIONS) {
|
|
2327
2497
|
iterations++;
|
|
2498
|
+
this.throwIfCancelled();
|
|
2328
2499
|
const options = {
|
|
2329
2500
|
messages: this.context.getMessages(),
|
|
2330
2501
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -2345,21 +2516,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2345
2516
|
if (requiresArtifact) {
|
|
2346
2517
|
stalledArtifactIterations += 1;
|
|
2347
2518
|
if (stalledArtifactIterations >= 2) {
|
|
2348
|
-
if (
|
|
2349
|
-
|
|
2350
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
2351
|
-
this.assignment?.description ?? ""
|
|
2352
|
-
);
|
|
2353
|
-
if (toolName) {
|
|
2354
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
2355
|
-
this.sendStatusUpdate({
|
|
2356
|
-
progressPct: 50,
|
|
2357
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
2358
|
-
status: "IN_PROGRESS"
|
|
2359
|
-
});
|
|
2360
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
2361
|
-
continue;
|
|
2362
|
-
}
|
|
2519
|
+
if (stalledArtifactIterations === 2) {
|
|
2520
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
2363
2521
|
}
|
|
2364
2522
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
2365
2523
|
}
|
|
@@ -2519,6 +2677,9 @@ ${assignment.expectedOutput}`;
|
|
|
2519
2677
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
2520
2678
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
2521
2679
|
const issues = [];
|
|
2680
|
+
const { exec: exec3 } = await import('child_process');
|
|
2681
|
+
const { promisify: promisify3 } = await import('util');
|
|
2682
|
+
const execAsync2 = promisify3(exec3);
|
|
2522
2683
|
for (const artifactPath of artifactPaths) {
|
|
2523
2684
|
const absolutePath = path13__default.default.resolve(process.cwd(), artifactPath);
|
|
2524
2685
|
try {
|
|
@@ -2535,9 +2696,27 @@ ${assignment.expectedOutput}`;
|
|
|
2535
2696
|
const content = await fs2__default.default.readFile(absolutePath, "utf-8");
|
|
2536
2697
|
if (!content.trim()) {
|
|
2537
2698
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
2699
|
+
continue;
|
|
2538
2700
|
}
|
|
2539
2701
|
} else if (stat.size < 100) {
|
|
2540
2702
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
2703
|
+
continue;
|
|
2704
|
+
}
|
|
2705
|
+
const ext = path13__default.default.extname(absolutePath).toLowerCase();
|
|
2706
|
+
try {
|
|
2707
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
2708
|
+
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
2709
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
2710
|
+
await execAsync2(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
2711
|
+
} else if (ext === ".py") {
|
|
2712
|
+
await execAsync2(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
2713
|
+
}
|
|
2714
|
+
} catch (err) {
|
|
2715
|
+
const stderr = err?.stderr || String(err);
|
|
2716
|
+
const stdout = err?.stdout || "";
|
|
2717
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
2718
|
+
${stderr}
|
|
2719
|
+
${stdout}`);
|
|
2541
2720
|
}
|
|
2542
2721
|
} catch {
|
|
2543
2722
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -2934,7 +3113,8 @@ var T2Manager = class extends BaseTier {
|
|
|
2934
3113
|
});
|
|
2935
3114
|
this.emit("peer-sync-received", { fromId, content });
|
|
2936
3115
|
}
|
|
2937
|
-
async execute(assignment, taskId) {
|
|
3116
|
+
async execute(assignment, taskId, signal) {
|
|
3117
|
+
this.signal = signal;
|
|
2938
3118
|
this.assignment = assignment;
|
|
2939
3119
|
this.taskId = taskId;
|
|
2940
3120
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -2946,12 +3126,14 @@ var T2Manager = class extends BaseTier {
|
|
|
2946
3126
|
});
|
|
2947
3127
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
2948
3128
|
try {
|
|
3129
|
+
this.throwIfCancelled();
|
|
2949
3130
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
2950
3131
|
this.sendStatusUpdate({
|
|
2951
3132
|
progressPct: 20,
|
|
2952
3133
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
2953
3134
|
status: "IN_PROGRESS"
|
|
2954
3135
|
});
|
|
3136
|
+
this.throwIfCancelled();
|
|
2955
3137
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
2956
3138
|
this.sendStatusUpdate({
|
|
2957
3139
|
progressPct: 90,
|
|
@@ -3118,11 +3300,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3118
3300
|
).join(", ")}`,
|
|
3119
3301
|
status: "IN_PROGRESS"
|
|
3120
3302
|
});
|
|
3303
|
+
this.throwIfCancelled();
|
|
3121
3304
|
const waveResults = await Promise.allSettled(
|
|
3122
3305
|
runnableIds.map(async (id) => {
|
|
3123
3306
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3124
3307
|
const worker = workerMap.get(id);
|
|
3125
|
-
const result = await worker.execute(assignment, taskId);
|
|
3308
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
3126
3309
|
resultMap.set(id, result);
|
|
3127
3310
|
return result;
|
|
3128
3311
|
})
|
|
@@ -3136,6 +3319,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3136
3319
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3137
3320
|
const retried = await this.retryT3(assignment, taskId);
|
|
3138
3321
|
resultMap.set(id, retried);
|
|
3322
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
3323
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3324
|
+
if (this.toolCreator) {
|
|
3325
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
3326
|
+
this.sendStatusUpdate({
|
|
3327
|
+
progressPct: 50,
|
|
3328
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
3329
|
+
status: "IN_PROGRESS"
|
|
3330
|
+
});
|
|
3331
|
+
const toolName = await this.toolCreator.createTool(
|
|
3332
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
3333
|
+
assignment.description
|
|
3334
|
+
);
|
|
3335
|
+
if (toolName) {
|
|
3336
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
3337
|
+
this.sendStatusUpdate({
|
|
3338
|
+
progressPct: 60,
|
|
3339
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
3340
|
+
status: "IN_PROGRESS"
|
|
3341
|
+
});
|
|
3342
|
+
try {
|
|
3343
|
+
const verifyResult = await this.router.generate("T2", {
|
|
3344
|
+
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".` }],
|
|
3345
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
3346
|
+
maxTokens: 50
|
|
3347
|
+
});
|
|
3348
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
3349
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
3350
|
+
const retried = await this.retryT3({
|
|
3351
|
+
...assignment,
|
|
3352
|
+
description: `${assignment.description}
|
|
3353
|
+
|
|
3354
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
3355
|
+
}, taskId);
|
|
3356
|
+
resultMap.set(id, retried);
|
|
3357
|
+
} else {
|
|
3358
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
3359
|
+
resultMap.set(id, r.value);
|
|
3360
|
+
}
|
|
3361
|
+
} catch {
|
|
3362
|
+
const retried = await this.retryT3({
|
|
3363
|
+
...assignment,
|
|
3364
|
+
description: `${assignment.description}
|
|
3365
|
+
|
|
3366
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
3367
|
+
}, taskId);
|
|
3368
|
+
resultMap.set(id, retried);
|
|
3369
|
+
}
|
|
3370
|
+
} else {
|
|
3371
|
+
resultMap.set(id, r.value);
|
|
3372
|
+
}
|
|
3373
|
+
} else {
|
|
3374
|
+
resultMap.set(id, r.value);
|
|
3375
|
+
}
|
|
3139
3376
|
}
|
|
3140
3377
|
for (const dependent of adj.get(id) ?? []) {
|
|
3141
3378
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -3199,7 +3436,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3199
3436
|
}));
|
|
3200
3437
|
return worker.execute(
|
|
3201
3438
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
3202
|
-
taskId
|
|
3439
|
+
taskId,
|
|
3440
|
+
this.signal
|
|
3203
3441
|
);
|
|
3204
3442
|
}
|
|
3205
3443
|
publishSectionOutput(result) {
|
|
@@ -3213,29 +3451,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3213
3451
|
async aggregateResults(assignment, results) {
|
|
3214
3452
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
3215
3453
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
3216
|
-
const outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
|
|
3217
3454
|
const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
|
|
3218
|
-
const
|
|
3219
|
-
|
|
3220
|
-
${outputs}
|
|
3221
|
-
${peerOutputs ? `
|
|
3455
|
+
const peerContext = peerOutputs ? `
|
|
3222
3456
|
|
|
3223
3457
|
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
3224
|
-
${peerOutputs}` : ""
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3458
|
+
${peerOutputs}` : "";
|
|
3459
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
3460
|
+
let currentSummary = "";
|
|
3461
|
+
let i = 0;
|
|
3462
|
+
while (i < completed.length) {
|
|
3463
|
+
let chunkText = "";
|
|
3464
|
+
let chunkEnd = i;
|
|
3465
|
+
while (chunkEnd < completed.length) {
|
|
3466
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
3467
|
+
|
|
3468
|
+
`;
|
|
3469
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
3470
|
+
break;
|
|
3471
|
+
}
|
|
3472
|
+
chunkText += nextOutput;
|
|
3473
|
+
chunkEnd++;
|
|
3474
|
+
}
|
|
3475
|
+
i = chunkEnd;
|
|
3476
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
3477
|
+
${currentSummary ? `
|
|
3478
|
+
PREVIOUS SUMMARY SO FAR:
|
|
3479
|
+
${currentSummary}
|
|
3480
|
+
|
|
3481
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
3482
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
3483
|
+
const messages = [{ role: "user", content: prompt }];
|
|
3484
|
+
try {
|
|
3485
|
+
const result = await this.router.generate("T2", {
|
|
3486
|
+
messages,
|
|
3487
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
3230
3488
|
|
|
3231
3489
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3490
|
+
maxTokens: 500
|
|
3491
|
+
});
|
|
3492
|
+
currentSummary = result.content;
|
|
3493
|
+
} catch (err) {
|
|
3494
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3495
|
+
return currentSummary + "\n\n" + chunkText;
|
|
3496
|
+
}
|
|
3238
3497
|
}
|
|
3498
|
+
return currentSummary;
|
|
3239
3499
|
}
|
|
3240
3500
|
determineStatus(results) {
|
|
3241
3501
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -3360,10 +3620,10 @@ Rules:
|
|
|
3360
3620
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
3361
3621
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
3362
3622
|
|
|
3363
|
-
|
|
3364
|
-
-
|
|
3365
|
-
-
|
|
3366
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
3623
|
+
DEPENDENCY GUIDANCE:
|
|
3624
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
3625
|
+
- 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).
|
|
3626
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
3367
3627
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
3368
3628
|
|
|
3369
3629
|
QUALITY RULES:
|
|
@@ -3402,7 +3662,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
3402
3662
|
setToolCreator(creator) {
|
|
3403
3663
|
this.toolCreator = creator;
|
|
3404
3664
|
}
|
|
3405
|
-
async execute(userPrompt, images, systemContext) {
|
|
3665
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
3666
|
+
this.signal = signal;
|
|
3406
3667
|
this.taskId = crypto.randomUUID();
|
|
3407
3668
|
this.setLabel("Administrator");
|
|
3408
3669
|
this.setStatus("ACTIVE");
|
|
@@ -3413,10 +3674,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
3413
3674
|
status: "IN_PROGRESS"
|
|
3414
3675
|
});
|
|
3415
3676
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
3677
|
+
this.throwIfCancelled();
|
|
3416
3678
|
let enrichedPrompt = userPrompt;
|
|
3417
3679
|
if (images?.length) {
|
|
3418
3680
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
3419
3681
|
}
|
|
3682
|
+
this.throwIfCancelled();
|
|
3420
3683
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
3421
3684
|
this.sendStatusUpdate({
|
|
3422
3685
|
progressPct: 10,
|
|
@@ -3424,21 +3687,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
3424
3687
|
status: "IN_PROGRESS"
|
|
3425
3688
|
});
|
|
3426
3689
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
3427
|
-
|
|
3690
|
+
this.throwIfCancelled();
|
|
3691
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
3692
|
+
let pass = 1;
|
|
3693
|
+
const MAX_REPLAN_PASSES = 2;
|
|
3694
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
3695
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
3696
|
+
if (reviewResult.approved) {
|
|
3697
|
+
this.log("T1 Review passed.");
|
|
3698
|
+
break;
|
|
3699
|
+
}
|
|
3700
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
3701
|
+
this.sendStatusUpdate({
|
|
3702
|
+
progressPct: 80 + pass * 5,
|
|
3703
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
3704
|
+
status: "IN_PROGRESS"
|
|
3705
|
+
});
|
|
3706
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
3707
|
+
Review reason: ${reviewResult.reason}
|
|
3708
|
+
|
|
3709
|
+
Original goal: ${enrichedPrompt}
|
|
3710
|
+
|
|
3711
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
3712
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
3713
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
3714
|
+
pass++;
|
|
3715
|
+
}
|
|
3428
3716
|
this.sendStatusUpdate({
|
|
3429
3717
|
progressPct: 95,
|
|
3430
3718
|
currentAction: "Compiling final output",
|
|
3431
3719
|
status: "IN_PROGRESS"
|
|
3432
3720
|
});
|
|
3433
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
3721
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
3434
3722
|
this.setStatus("COMPLETED");
|
|
3435
3723
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
3436
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3724
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3437
3725
|
}
|
|
3438
3726
|
getEscalations() {
|
|
3439
3727
|
return [...this.escalations];
|
|
3440
3728
|
}
|
|
3441
3729
|
// ── Private ──────────────────────────────────
|
|
3730
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
3731
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
3732
|
+
if (failedSections.length > 0) {
|
|
3733
|
+
return {
|
|
3734
|
+
approved: false,
|
|
3735
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
3739
|
+
${r.sectionSummary}`).join("\n\n");
|
|
3740
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
3741
|
+
Review the following execution outputs against the original user prompt.
|
|
3742
|
+
|
|
3743
|
+
Original Request: ${originalPrompt}
|
|
3744
|
+
|
|
3745
|
+
T2 Manager Summaries:
|
|
3746
|
+
${sectionsText}
|
|
3747
|
+
|
|
3748
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
3749
|
+
If yes, reply with exactly: "APPROVED".
|
|
3750
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
3751
|
+
try {
|
|
3752
|
+
const result = await this.router.generate("T1", {
|
|
3753
|
+
messages: [{ role: "user", content: prompt }],
|
|
3754
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
3755
|
+
maxTokens: 500,
|
|
3756
|
+
temperature: 0
|
|
3757
|
+
});
|
|
3758
|
+
const response = result.content.trim();
|
|
3759
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
3760
|
+
return { approved: true };
|
|
3761
|
+
}
|
|
3762
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
3763
|
+
} catch {
|
|
3764
|
+
return { approved: true };
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3442
3767
|
async analyzeImages(prompt, images) {
|
|
3443
3768
|
const visionModel = this.router.getModelForTier("T1");
|
|
3444
3769
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -3467,29 +3792,35 @@ ${systemContext}` : "";
|
|
|
3467
3792
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
3468
3793
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
3469
3794
|
|
|
3470
|
-
Return JSON where
|
|
3795
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
3471
3796
|
{
|
|
3472
3797
|
"sections": [{
|
|
3798
|
+
"sectionId": "s1",
|
|
3799
|
+
"sectionTitle": "Setup Project",
|
|
3800
|
+
"description": "Initialize the project",
|
|
3801
|
+
"expectedOutput": "Basic structure created",
|
|
3802
|
+
"constraints": [],
|
|
3803
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
3473
3804
|
"t3Subtasks": [{
|
|
3474
3805
|
"subtaskId": "t1",
|
|
3475
|
-
"subtaskTitle": "
|
|
3476
|
-
"
|
|
3477
|
-
"
|
|
3478
|
-
|
|
3479
|
-
"
|
|
3480
|
-
"subtaskTitle": "Save Code to File",
|
|
3481
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
3482
|
-
"executionMode": "parallel"
|
|
3483
|
-
}, {
|
|
3484
|
-
"subtaskId": "t3",
|
|
3485
|
-
"subtaskTitle": "Execute and Verify",
|
|
3486
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
3487
|
-
"executionMode": "parallel"
|
|
3806
|
+
"subtaskTitle": "Init NPM",
|
|
3807
|
+
"description": "Run npm init",
|
|
3808
|
+
"expectedOutput": "package.json created",
|
|
3809
|
+
"constraints": [],
|
|
3810
|
+
"dependsOn": []
|
|
3488
3811
|
}]
|
|
3812
|
+
}, {
|
|
3813
|
+
"sectionId": "s2",
|
|
3814
|
+
"sectionTitle": "Write Tests",
|
|
3815
|
+
"description": "Write tests for the project",
|
|
3816
|
+
"expectedOutput": "Tests passing",
|
|
3817
|
+
"constraints": [],
|
|
3818
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
3819
|
+
"t3Subtasks": [...]
|
|
3489
3820
|
}]
|
|
3490
3821
|
}
|
|
3491
|
-
Use dependsOn when a
|
|
3492
|
-
Leave dependsOn empty for
|
|
3822
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
3823
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
3493
3824
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
3494
3825
|
const result = await this.router.generate("T1", {
|
|
3495
3826
|
messages,
|
|
@@ -3617,92 +3948,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
3617
3948
|
].filter(Boolean).join(" ");
|
|
3618
3949
|
m.setHierarchyContext(context);
|
|
3619
3950
|
});
|
|
3620
|
-
if (overlapSections.size > 0
|
|
3621
|
-
this.log("Overlap detected \u2014
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3951
|
+
if (overlapSections.size > 0) {
|
|
3952
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
3953
|
+
const overlapArray = Array.from(overlapSections);
|
|
3954
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
3955
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
3956
|
+
if (section) {
|
|
3957
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
3625
3958
|
}
|
|
3626
3959
|
}
|
|
3627
3960
|
}
|
|
3628
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
3629
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
3630
3961
|
const t2Results = [];
|
|
3631
3962
|
try {
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3963
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
3964
|
+
} finally {
|
|
3965
|
+
cleanup();
|
|
3966
|
+
}
|
|
3967
|
+
return t2Results;
|
|
3968
|
+
}
|
|
3969
|
+
/**
|
|
3970
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
3971
|
+
*/
|
|
3972
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
3973
|
+
const adj = /* @__PURE__ */ new Map();
|
|
3974
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
3975
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
3976
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
3977
|
+
for (const s of sections) {
|
|
3978
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
3979
|
+
inDegree.set(s.sectionId, 0);
|
|
3980
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
3981
|
+
}
|
|
3982
|
+
for (const s of sections) {
|
|
3983
|
+
for (const dep of s.dependsOn ?? []) {
|
|
3984
|
+
adj.get(dep).add(s.sectionId);
|
|
3985
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
const queue = [];
|
|
3989
|
+
const degree = new Map(inDegree);
|
|
3990
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
3991
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3992
|
+
while (queue.length > 0) {
|
|
3993
|
+
const u = queue.shift();
|
|
3994
|
+
visited.add(u);
|
|
3995
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
3996
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
3997
|
+
degree.set(v, newDeg);
|
|
3998
|
+
if (newDeg === 0) queue.push(v);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
4002
|
+
if (cycleNodes.length > 0) {
|
|
4003
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
4004
|
+
for (const s of sections) {
|
|
4005
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
4006
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
4007
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
4008
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
4009
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
3663
4010
|
}
|
|
4011
|
+
s.dependsOn = safeDeps;
|
|
3664
4012
|
}
|
|
3665
|
-
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
const totalSections = sections.length;
|
|
4016
|
+
let completedSections = 0;
|
|
4017
|
+
const executeWave = async () => {
|
|
4018
|
+
const readyIds = [];
|
|
4019
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
4020
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
4021
|
+
readyIds.push(id);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
if (readyIds.length === 0) return;
|
|
4025
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
4026
|
+
resultMap.set(id, null);
|
|
4027
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
4028
|
+
const section = sections[index];
|
|
4029
|
+
const manager = managers[index];
|
|
4030
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
4031
|
+
this.sendStatusUpdate({
|
|
4032
|
+
progressPct,
|
|
4033
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
4034
|
+
status: "IN_PROGRESS"
|
|
4035
|
+
});
|
|
4036
|
+
this.throwIfCancelled();
|
|
4037
|
+
let result;
|
|
4038
|
+
try {
|
|
4039
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
4040
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
4041
|
+
if (result.status === "ESCALATED") {
|
|
4042
|
+
this.escalations.push({
|
|
4043
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
4044
|
+
sectionId: section.sectionId,
|
|
4045
|
+
attempted: result.issues,
|
|
4046
|
+
blocker: result.issues.join("; "),
|
|
4047
|
+
needs: "Human review required"
|
|
3698
4048
|
});
|
|
3699
4049
|
}
|
|
4050
|
+
} catch (err) {
|
|
4051
|
+
result = {
|
|
4052
|
+
sectionId: section.sectionId,
|
|
4053
|
+
sectionTitle: section.sectionTitle,
|
|
4054
|
+
status: "FAILED",
|
|
4055
|
+
t3Results: [],
|
|
4056
|
+
sectionSummary: "",
|
|
4057
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
4058
|
+
};
|
|
3700
4059
|
}
|
|
4060
|
+
resultMap.set(id, result);
|
|
4061
|
+
completedSections++;
|
|
4062
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
4063
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
4064
|
+
}
|
|
4065
|
+
}));
|
|
4066
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
4067
|
+
await executeWave();
|
|
3701
4068
|
}
|
|
3702
|
-
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
return t2Results;
|
|
4069
|
+
};
|
|
4070
|
+
await executeWave();
|
|
4071
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
3706
4072
|
}
|
|
3707
4073
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
3708
4074
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -4149,13 +4515,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
4149
4515
|
}
|
|
4150
4516
|
async execute(input, _options) {
|
|
4151
4517
|
const platform = input["platform"] ?? "github";
|
|
4152
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
4153
4518
|
const operation = input["operation"];
|
|
4154
4519
|
const repo = input["repo"];
|
|
4155
|
-
|
|
4156
|
-
|
|
4520
|
+
let token = input["token"];
|
|
4521
|
+
if (!token) {
|
|
4522
|
+
if (platform === "github") {
|
|
4523
|
+
token = process.env["GITHUB_TOKEN"];
|
|
4524
|
+
} else {
|
|
4525
|
+
token = process.env["GITLAB_TOKEN"];
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
if (!token) {
|
|
4529
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
4530
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
4531
|
+
}
|
|
4532
|
+
try {
|
|
4533
|
+
if (platform === "github") {
|
|
4534
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
4535
|
+
}
|
|
4536
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
4537
|
+
} catch (err) {
|
|
4538
|
+
const axiosErr = err;
|
|
4539
|
+
if (axiosErr?.response?.status) {
|
|
4540
|
+
const status = axiosErr.response.status;
|
|
4541
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
4542
|
+
switch (status) {
|
|
4543
|
+
case 401:
|
|
4544
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
4545
|
+
case 403:
|
|
4546
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
4547
|
+
case 404:
|
|
4548
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
4549
|
+
case 422:
|
|
4550
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
4551
|
+
case 429:
|
|
4552
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
4553
|
+
default:
|
|
4554
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
4157
4558
|
}
|
|
4158
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
4159
4559
|
}
|
|
4160
4560
|
async executeGitHub(operation, repo, token, input) {
|
|
4161
4561
|
const headers = {
|
|
@@ -4242,6 +4642,7 @@ ${response.data.description}`;
|
|
|
4242
4642
|
};
|
|
4243
4643
|
|
|
4244
4644
|
// src/tools/browser.ts
|
|
4645
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
4245
4646
|
var BrowserTool = class extends BaseTool {
|
|
4246
4647
|
name = "browser";
|
|
4247
4648
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -4250,7 +4651,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
4250
4651
|
properties: {
|
|
4251
4652
|
action: {
|
|
4252
4653
|
type: "string",
|
|
4253
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
4654
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
4254
4655
|
},
|
|
4255
4656
|
url: { type: "string", description: "URL to navigate to" },
|
|
4256
4657
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -4270,53 +4671,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
4270
4671
|
try {
|
|
4271
4672
|
playwright = await import('playwright');
|
|
4272
4673
|
} catch {
|
|
4273
|
-
|
|
4274
|
-
}
|
|
4275
|
-
if (!this.browser) {
|
|
4276
|
-
const pw = playwright;
|
|
4277
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
4278
|
-
const b = this.browser;
|
|
4279
|
-
this.page = await b.newPage();
|
|
4674
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
4280
4675
|
}
|
|
4281
|
-
const page = this.page;
|
|
4282
4676
|
const action = input["action"];
|
|
4283
4677
|
const timeout = input["timeout"] ?? 1e4;
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
case "evaluate": {
|
|
4302
|
-
const result = await page.evaluate(input["script"]);
|
|
4303
|
-
return JSON.stringify(result);
|
|
4678
|
+
if (action === "close") {
|
|
4679
|
+
await this.close();
|
|
4680
|
+
return "Browser closed.";
|
|
4681
|
+
}
|
|
4682
|
+
if (!this.browser || !this.page) {
|
|
4683
|
+
await this.close();
|
|
4684
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
4685
|
+
const timeoutPromise = new Promise(
|
|
4686
|
+
(_, 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)
|
|
4687
|
+
);
|
|
4688
|
+
try {
|
|
4689
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
4690
|
+
this.page = await this.browser.newPage();
|
|
4691
|
+
} catch (err) {
|
|
4692
|
+
this.browser = null;
|
|
4693
|
+
this.page = null;
|
|
4694
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
4304
4695
|
}
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4696
|
+
}
|
|
4697
|
+
const page = this.page;
|
|
4698
|
+
try {
|
|
4699
|
+
switch (action) {
|
|
4700
|
+
case "navigate": {
|
|
4701
|
+
await page.goto(input["url"], { timeout });
|
|
4702
|
+
const title = await page.title();
|
|
4703
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
4704
|
+
}
|
|
4705
|
+
case "click": {
|
|
4706
|
+
await page.click(input["selector"], { timeout });
|
|
4707
|
+
return `Clicked ${input["selector"]}`;
|
|
4708
|
+
}
|
|
4709
|
+
case "fill": {
|
|
4710
|
+
await page.fill(input["selector"], input["value"]);
|
|
4711
|
+
return `Filled ${input["selector"]} with value`;
|
|
4712
|
+
}
|
|
4713
|
+
case "screenshot": {
|
|
4714
|
+
const buf = await page.screenshot({ type: "png" });
|
|
4715
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
4716
|
+
}
|
|
4717
|
+
case "evaluate": {
|
|
4718
|
+
const result = await page.evaluate(input["script"]);
|
|
4719
|
+
return JSON.stringify(result);
|
|
4720
|
+
}
|
|
4721
|
+
case "extract_text": {
|
|
4722
|
+
const text = await page.locator("body").innerText();
|
|
4723
|
+
return text.slice(0, 1e4);
|
|
4724
|
+
}
|
|
4725
|
+
case "wait": {
|
|
4726
|
+
await page.waitForTimeout(timeout);
|
|
4727
|
+
return `Waited ${timeout}ms`;
|
|
4728
|
+
}
|
|
4729
|
+
default:
|
|
4730
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
4308
4731
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4732
|
+
} catch (err) {
|
|
4733
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4734
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
4735
|
+
await this.close();
|
|
4736
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
4312
4737
|
}
|
|
4313
|
-
|
|
4314
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
4738
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
4315
4739
|
}
|
|
4316
4740
|
}
|
|
4317
4741
|
async close() {
|
|
4318
|
-
|
|
4319
|
-
|
|
4742
|
+
try {
|
|
4743
|
+
if (this.page) {
|
|
4744
|
+
await this.page.close().catch(() => {
|
|
4745
|
+
});
|
|
4746
|
+
this.page = null;
|
|
4747
|
+
}
|
|
4748
|
+
if (this.browser) {
|
|
4749
|
+
await this.browser.close().catch(() => {
|
|
4750
|
+
});
|
|
4751
|
+
this.browser = null;
|
|
4752
|
+
}
|
|
4753
|
+
} catch {
|
|
4320
4754
|
this.browser = null;
|
|
4321
4755
|
this.page = null;
|
|
4322
4756
|
}
|
|
@@ -4413,6 +4847,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
4413
4847
|
});
|
|
4414
4848
|
}
|
|
4415
4849
|
};
|
|
4850
|
+
function detectCommand(candidates) {
|
|
4851
|
+
for (const cmd of candidates) {
|
|
4852
|
+
try {
|
|
4853
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
4854
|
+
child_process.execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
4855
|
+
return cmd;
|
|
4856
|
+
} catch {
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
return null;
|
|
4860
|
+
}
|
|
4861
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
4862
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
4416
4863
|
var CodeInterpreterTool = class extends BaseTool {
|
|
4417
4864
|
name = "run_code";
|
|
4418
4865
|
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.";
|
|
@@ -4428,10 +4875,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4428
4875
|
isDangerous() {
|
|
4429
4876
|
return true;
|
|
4430
4877
|
}
|
|
4431
|
-
async execute(input,
|
|
4878
|
+
async execute(input, _options) {
|
|
4432
4879
|
const language = input["language"];
|
|
4433
4880
|
const code = input["code"];
|
|
4434
4881
|
const args = input["args"] ?? [];
|
|
4882
|
+
let cmdPrefix;
|
|
4883
|
+
if (language === "python") {
|
|
4884
|
+
if (!PYTHON_CMD) {
|
|
4885
|
+
return [
|
|
4886
|
+
"Error: Python interpreter not found.",
|
|
4887
|
+
"Please install Python and ensure it is in your PATH.",
|
|
4888
|
+
"Tried: python3, python"
|
|
4889
|
+
].join("\n");
|
|
4890
|
+
}
|
|
4891
|
+
cmdPrefix = PYTHON_CMD;
|
|
4892
|
+
} else {
|
|
4893
|
+
if (!NODE_CMD) {
|
|
4894
|
+
return [
|
|
4895
|
+
"Error: Node.js interpreter not found.",
|
|
4896
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
4897
|
+
"Tried: node"
|
|
4898
|
+
].join("\n");
|
|
4899
|
+
}
|
|
4900
|
+
cmdPrefix = NODE_CMD;
|
|
4901
|
+
}
|
|
4435
4902
|
const tmpDir = path13__default.default.join(process.cwd(), ".cascade", "tmp");
|
|
4436
4903
|
if (!fs11__default.default.existsSync(tmpDir)) {
|
|
4437
4904
|
fs11__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -4440,8 +4907,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4440
4907
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
4441
4908
|
const filePath = path13__default.default.join(tmpDir, fileName);
|
|
4442
4909
|
fs11__default.default.writeFileSync(filePath, code, "utf-8");
|
|
4443
|
-
const
|
|
4444
|
-
const
|
|
4910
|
+
const quotedPath = `"${filePath}"`;
|
|
4911
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
4912
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
4445
4913
|
return new Promise((resolve) => {
|
|
4446
4914
|
const startMs = Date.now();
|
|
4447
4915
|
child_process.exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -4454,10 +4922,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4454
4922
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
4455
4923
|
}
|
|
4456
4924
|
if (error) {
|
|
4457
|
-
|
|
4925
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
4926
|
+
if (timedOut) {
|
|
4927
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
4928
|
+
Partial stdout: ${stdout}
|
|
4929
|
+
Stderr: ${stderr}`);
|
|
4930
|
+
} else {
|
|
4931
|
+
resolve(`Execution failed (${duration}ms):
|
|
4458
4932
|
Error: ${error.message}
|
|
4459
4933
|
Stderr: ${stderr}
|
|
4460
4934
|
Stdout: ${stdout}`);
|
|
4935
|
+
}
|
|
4461
4936
|
} else {
|
|
4462
4937
|
resolve(`Execution successful (${duration}ms):
|
|
4463
4938
|
Stdout: ${stdout}
|
|
@@ -4522,6 +4997,186 @@ ${formatted}`;
|
|
|
4522
4997
|
}
|
|
4523
4998
|
};
|
|
4524
4999
|
|
|
5000
|
+
// src/tools/web-search.ts
|
|
5001
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
5002
|
+
const url = new URL("/search", baseUrl);
|
|
5003
|
+
url.searchParams.set("q", query);
|
|
5004
|
+
url.searchParams.set("format", "json");
|
|
5005
|
+
url.searchParams.set("categories", "general");
|
|
5006
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
5007
|
+
const resp = await fetch(url.toString(), {
|
|
5008
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
5009
|
+
signal: AbortSignal.timeout(1e4)
|
|
5010
|
+
});
|
|
5011
|
+
if (!resp.ok) {
|
|
5012
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
5013
|
+
}
|
|
5014
|
+
const data = await resp.json();
|
|
5015
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
5016
|
+
title: r.title ?? "",
|
|
5017
|
+
url: r.url ?? "",
|
|
5018
|
+
snippet: r.content ?? "",
|
|
5019
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
5020
|
+
}));
|
|
5021
|
+
}
|
|
5022
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
5023
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
5024
|
+
const resp = await fetch(url, {
|
|
5025
|
+
headers: {
|
|
5026
|
+
"Accept": "application/json",
|
|
5027
|
+
"Accept-Encoding": "gzip",
|
|
5028
|
+
"X-Subscription-Token": apiKey
|
|
5029
|
+
},
|
|
5030
|
+
signal: AbortSignal.timeout(1e4)
|
|
5031
|
+
});
|
|
5032
|
+
if (!resp.ok) {
|
|
5033
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
5034
|
+
}
|
|
5035
|
+
const data = await resp.json();
|
|
5036
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
5037
|
+
title: r.title ?? "",
|
|
5038
|
+
url: r.url ?? "",
|
|
5039
|
+
snippet: r.description ?? "",
|
|
5040
|
+
engine: "brave"
|
|
5041
|
+
}));
|
|
5042
|
+
}
|
|
5043
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
5044
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
5045
|
+
method: "POST",
|
|
5046
|
+
headers: {
|
|
5047
|
+
"Content-Type": "application/json",
|
|
5048
|
+
"Authorization": `Bearer ${apiKey}`
|
|
5049
|
+
},
|
|
5050
|
+
body: JSON.stringify({
|
|
5051
|
+
query,
|
|
5052
|
+
max_results: maxResults,
|
|
5053
|
+
search_depth: "basic",
|
|
5054
|
+
include_answer: false,
|
|
5055
|
+
include_raw_content: false
|
|
5056
|
+
}),
|
|
5057
|
+
signal: AbortSignal.timeout(15e3)
|
|
5058
|
+
});
|
|
5059
|
+
if (!resp.ok) {
|
|
5060
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
5061
|
+
}
|
|
5062
|
+
const data = await resp.json();
|
|
5063
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
5064
|
+
title: r.title ?? "",
|
|
5065
|
+
url: r.url ?? "",
|
|
5066
|
+
snippet: r.content ?? "",
|
|
5067
|
+
engine: "tavily"
|
|
5068
|
+
}));
|
|
5069
|
+
}
|
|
5070
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
5071
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
5072
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
5073
|
+
signal: AbortSignal.timeout(1e4)
|
|
5074
|
+
});
|
|
5075
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
5076
|
+
const html = await resp.text();
|
|
5077
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
5078
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
5079
|
+
const links = [];
|
|
5080
|
+
const snippets = [];
|
|
5081
|
+
let m;
|
|
5082
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
5083
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
5084
|
+
}
|
|
5085
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
5086
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
5087
|
+
}
|
|
5088
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
5089
|
+
title: link.title,
|
|
5090
|
+
url: link.url,
|
|
5091
|
+
snippet: snippets[i] ?? "",
|
|
5092
|
+
engine: "duckduckgo-lite"
|
|
5093
|
+
}));
|
|
5094
|
+
}
|
|
5095
|
+
var WebSearchTool = class extends BaseTool {
|
|
5096
|
+
name = "web_search";
|
|
5097
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
5098
|
+
inputSchema = {
|
|
5099
|
+
type: "object",
|
|
5100
|
+
properties: {
|
|
5101
|
+
query: { type: "string", description: "The search query" },
|
|
5102
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
5103
|
+
},
|
|
5104
|
+
required: ["query"]
|
|
5105
|
+
};
|
|
5106
|
+
config;
|
|
5107
|
+
constructor(config = {}) {
|
|
5108
|
+
super();
|
|
5109
|
+
this.config = {
|
|
5110
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
5111
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
5112
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
5113
|
+
maxResults: config.maxResults ?? 5
|
|
5114
|
+
};
|
|
5115
|
+
}
|
|
5116
|
+
async execute(input, _options) {
|
|
5117
|
+
const query = input["query"];
|
|
5118
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
5119
|
+
const maxResults = Math.min(
|
|
5120
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
5121
|
+
10
|
|
5122
|
+
);
|
|
5123
|
+
const errors = [];
|
|
5124
|
+
let results = [];
|
|
5125
|
+
if (this.config.searxngUrl) {
|
|
5126
|
+
try {
|
|
5127
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
5128
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5129
|
+
errors.push("SearXNG: returned 0 results");
|
|
5130
|
+
} catch (err) {
|
|
5131
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
if (this.config.braveApiKey) {
|
|
5135
|
+
try {
|
|
5136
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
5137
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5138
|
+
errors.push("Brave: returned 0 results");
|
|
5139
|
+
} catch (err) {
|
|
5140
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
if (this.config.tavilyApiKey) {
|
|
5144
|
+
try {
|
|
5145
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
5146
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5147
|
+
errors.push("Tavily: returned 0 results");
|
|
5148
|
+
} catch (err) {
|
|
5149
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
try {
|
|
5153
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
5154
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5155
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
5156
|
+
} catch (err) {
|
|
5157
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
5158
|
+
}
|
|
5159
|
+
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" : "";
|
|
5160
|
+
return [
|
|
5161
|
+
`Web search for "${query}" failed across all backends:`,
|
|
5162
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
5163
|
+
configHint
|
|
5164
|
+
].join("\n");
|
|
5165
|
+
}
|
|
5166
|
+
formatResults(query, results) {
|
|
5167
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
5168
|
+
for (let i = 0; i < results.length; i++) {
|
|
5169
|
+
const r = results[i];
|
|
5170
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
5171
|
+
lines.push(` URL: ${r.url}`);
|
|
5172
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
5173
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
5174
|
+
lines.push("");
|
|
5175
|
+
}
|
|
5176
|
+
return lines.join("\n");
|
|
5177
|
+
}
|
|
5178
|
+
};
|
|
5179
|
+
|
|
4525
5180
|
// src/tools/mcp.ts
|
|
4526
5181
|
var McpToolWrapper = class extends BaseTool {
|
|
4527
5182
|
name;
|
|
@@ -4643,7 +5298,8 @@ var ToolRegistry = class {
|
|
|
4643
5298
|
new ImageAnalyzeTool(),
|
|
4644
5299
|
new PDFCreateTool(),
|
|
4645
5300
|
new CodeInterpreterTool(),
|
|
4646
|
-
new PeerCommunicationTool()
|
|
5301
|
+
new PeerCommunicationTool(),
|
|
5302
|
+
new WebSearchTool(this.config.webSearch)
|
|
4647
5303
|
];
|
|
4648
5304
|
for (const tool of tools) {
|
|
4649
5305
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -4667,8 +5323,23 @@ var ToolRegistry = class {
|
|
|
4667
5323
|
return this.ignoreMatcher.ignores(posixRel);
|
|
4668
5324
|
}
|
|
4669
5325
|
};
|
|
4670
|
-
var McpClient = class {
|
|
5326
|
+
var McpClient = class _McpClient {
|
|
5327
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
5328
|
+
/**
|
|
5329
|
+
* Forcefully kills all known MCP child processes.
|
|
5330
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
5331
|
+
*/
|
|
5332
|
+
static killAllProcesses() {
|
|
5333
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
5334
|
+
try {
|
|
5335
|
+
process.kill(pid, "SIGKILL");
|
|
5336
|
+
} catch {
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
_McpClient.activeProcessPids.clear();
|
|
5340
|
+
}
|
|
4671
5341
|
clients = /* @__PURE__ */ new Map();
|
|
5342
|
+
transports = /* @__PURE__ */ new Map();
|
|
4672
5343
|
tools = /* @__PURE__ */ new Map();
|
|
4673
5344
|
trustedServers;
|
|
4674
5345
|
approvalCallback;
|
|
@@ -4697,6 +5368,8 @@ var McpClient = class {
|
|
|
4697
5368
|
);
|
|
4698
5369
|
await client.connect(transport);
|
|
4699
5370
|
this.clients.set(server.name, client);
|
|
5371
|
+
this.transports.set(server.name, transport);
|
|
5372
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
4700
5373
|
const toolsResult = await client.listTools();
|
|
4701
5374
|
for (const tool of toolsResult.tools) {
|
|
4702
5375
|
for (const existing of this.tools.values()) {
|
|
@@ -4718,8 +5391,11 @@ var McpClient = class {
|
|
|
4718
5391
|
async disconnect(serverName) {
|
|
4719
5392
|
const client = this.clients.get(serverName);
|
|
4720
5393
|
if (client) {
|
|
5394
|
+
const transport = this.transports.get(serverName);
|
|
5395
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
4721
5396
|
await client.close();
|
|
4722
5397
|
this.clients.delete(serverName);
|
|
5398
|
+
this.transports.delete(serverName);
|
|
4723
5399
|
for (const key of this.tools.keys()) {
|
|
4724
5400
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
4725
5401
|
}
|
|
@@ -4747,6 +5423,13 @@ var McpClient = class {
|
|
|
4747
5423
|
getConnectedServers() {
|
|
4748
5424
|
return Array.from(this.clients.keys());
|
|
4749
5425
|
}
|
|
5426
|
+
getActivePids() {
|
|
5427
|
+
const pids = [];
|
|
5428
|
+
for (const transport of this.transports.values()) {
|
|
5429
|
+
if (transport.pid) pids.push(transport.pid);
|
|
5430
|
+
}
|
|
5431
|
+
return pids;
|
|
5432
|
+
}
|
|
4750
5433
|
isConnected(serverName) {
|
|
4751
5434
|
return this.clients.has(serverName);
|
|
4752
5435
|
}
|
|
@@ -4885,12 +5568,24 @@ var McpServerConfigSchema = zod.z.object({
|
|
|
4885
5568
|
args: zod.z.array(zod.z.string()).optional(),
|
|
4886
5569
|
env: zod.z.record(zod.z.string()).optional()
|
|
4887
5570
|
});
|
|
5571
|
+
var WebSearchConfigSchema = zod.z.object({
|
|
5572
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
5573
|
+
searxngUrl: zod.z.string().optional(),
|
|
5574
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
5575
|
+
braveApiKey: zod.z.string().optional(),
|
|
5576
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
5577
|
+
tavilyApiKey: zod.z.string().optional(),
|
|
5578
|
+
/** Max results per search (default 5) */
|
|
5579
|
+
maxResults: zod.z.number().default(5)
|
|
5580
|
+
});
|
|
4888
5581
|
var ToolsConfigSchema = zod.z.object({
|
|
4889
5582
|
shellAllowlist: zod.z.array(zod.z.string()).default([]),
|
|
4890
5583
|
shellBlocklist: zod.z.array(zod.z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
4891
5584
|
requireApprovalFor: zod.z.array(zod.z.string()).default([]),
|
|
4892
5585
|
browserEnabled: zod.z.boolean().default(false),
|
|
4893
|
-
mcpServers: zod.z.array(McpServerConfigSchema).optional()
|
|
5586
|
+
mcpServers: zod.z.array(McpServerConfigSchema).optional(),
|
|
5587
|
+
/** Web search backends — at least one should be configured for best results */
|
|
5588
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
4894
5589
|
});
|
|
4895
5590
|
var HookDefinitionSchema = zod.z.object({
|
|
4896
5591
|
command: zod.z.string(),
|
|
@@ -5432,12 +6127,25 @@ var Cascade = class extends EventEmitter__default.default {
|
|
|
5432
6127
|
looksLikeSimpleArtifactTask(prompt) {
|
|
5433
6128
|
return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
|
|
5434
6129
|
}
|
|
5435
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
6130
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
5436
6131
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
5437
6132
|
return "Simple";
|
|
5438
6133
|
}
|
|
6134
|
+
let workspaceContext = "";
|
|
6135
|
+
try {
|
|
6136
|
+
const files = await glob.glob("**/*.*", {
|
|
6137
|
+
cwd: workspacePath,
|
|
6138
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
6139
|
+
nodir: true
|
|
6140
|
+
});
|
|
6141
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
6142
|
+
} catch {
|
|
6143
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
6144
|
+
}
|
|
5439
6145
|
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.
|
|
5440
6146
|
|
|
6147
|
+
${workspaceContext}
|
|
6148
|
+
|
|
5441
6149
|
Classification:
|
|
5442
6150
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
5443
6151
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -5512,7 +6220,7 @@ ${prompt}` : prompt;
|
|
|
5512
6220
|
}
|
|
5513
6221
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
5514
6222
|
});
|
|
5515
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
6223
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
5516
6224
|
this.telemetry.capture("cascade:session_start", {
|
|
5517
6225
|
complexity,
|
|
5518
6226
|
providerCount: this.config.providers.length,
|
|
@@ -5592,7 +6300,7 @@ ${prompt}` : prompt;
|
|
|
5592
6300
|
peerT3Ids: [],
|
|
5593
6301
|
parentT2: "root"
|
|
5594
6302
|
};
|
|
5595
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
6303
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
5596
6304
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
5597
6305
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
5598
6306
|
} else if (complexity === "Moderate") {
|
|
@@ -5615,7 +6323,7 @@ ${prompt}` : prompt;
|
|
|
5615
6323
|
constraints: [],
|
|
5616
6324
|
t3Subtasks: []
|
|
5617
6325
|
};
|
|
5618
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
6326
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
5619
6327
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
5620
6328
|
t2Results = [t2Result];
|
|
5621
6329
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -5637,13 +6345,22 @@ ${prompt}` : prompt;
|
|
|
5637
6345
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
5638
6346
|
bindTierEvents(t1);
|
|
5639
6347
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
5640
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
6348
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
5641
6349
|
finalOutput = result.output;
|
|
5642
6350
|
t2Results = result.t2Results;
|
|
5643
6351
|
}
|
|
5644
6352
|
} catch (err) {
|
|
5645
|
-
|
|
5646
|
-
|
|
6353
|
+
if (err instanceof CascadeCancelledError) {
|
|
6354
|
+
this.emit("run:cancelled", {
|
|
6355
|
+
taskId,
|
|
6356
|
+
reason: err.message,
|
|
6357
|
+
partialOutput: finalOutput || ""
|
|
6358
|
+
});
|
|
6359
|
+
runError = null;
|
|
6360
|
+
} else {
|
|
6361
|
+
runError = err;
|
|
6362
|
+
throw err;
|
|
6363
|
+
}
|
|
5647
6364
|
} finally {
|
|
5648
6365
|
try {
|
|
5649
6366
|
escalator.cancelAllPending();
|
|
@@ -5965,9 +6682,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
5965
6682
|
constructor(dbPath) {
|
|
5966
6683
|
fs11__default.default.mkdirSync(path13__default.default.dirname(dbPath), { recursive: true });
|
|
5967
6684
|
try {
|
|
5968
|
-
this.db = new Database__default.default(dbPath);
|
|
6685
|
+
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
5969
6686
|
this.db.pragma("journal_mode = WAL");
|
|
5970
6687
|
this.db.pragma("foreign_keys = ON");
|
|
6688
|
+
this.db.pragma("synchronous = NORMAL");
|
|
5971
6689
|
this.migrate();
|
|
5972
6690
|
} catch (err) {
|
|
5973
6691
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -5981,6 +6699,38 @@ Original error: ${err.message}`
|
|
|
5981
6699
|
throw err;
|
|
5982
6700
|
}
|
|
5983
6701
|
}
|
|
6702
|
+
// ── Async Write Queue ─────────────────────────
|
|
6703
|
+
writeQueue = [];
|
|
6704
|
+
isProcessingQueue = false;
|
|
6705
|
+
async processQueue() {
|
|
6706
|
+
if (this.isProcessingQueue) return;
|
|
6707
|
+
this.isProcessingQueue = true;
|
|
6708
|
+
while (this.writeQueue.length > 0) {
|
|
6709
|
+
const op = this.writeQueue.shift();
|
|
6710
|
+
if (op) {
|
|
6711
|
+
let attempts = 0;
|
|
6712
|
+
while (attempts < 5) {
|
|
6713
|
+
try {
|
|
6714
|
+
op();
|
|
6715
|
+
break;
|
|
6716
|
+
} catch (err) {
|
|
6717
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
6718
|
+
attempts++;
|
|
6719
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
6720
|
+
} else {
|
|
6721
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
6722
|
+
break;
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
this.isProcessingQueue = false;
|
|
6729
|
+
}
|
|
6730
|
+
enqueueWrite(op) {
|
|
6731
|
+
this.writeQueue.push(op);
|
|
6732
|
+
this.processQueue().catch(console.error);
|
|
6733
|
+
}
|
|
5984
6734
|
// ── Sessions ──────────────────────────────────
|
|
5985
6735
|
createSession(session) {
|
|
5986
6736
|
this.db.prepare(`
|
|
@@ -6067,26 +6817,28 @@ Original error: ${err.message}`
|
|
|
6067
6817
|
}
|
|
6068
6818
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
6069
6819
|
upsertRuntimeSession(session) {
|
|
6070
|
-
this.
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6820
|
+
this.enqueueWrite(() => {
|
|
6821
|
+
this.db.prepare(`
|
|
6822
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
6823
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6824
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
6825
|
+
title = excluded.title,
|
|
6826
|
+
workspace_path = excluded.workspace_path,
|
|
6827
|
+
status = excluded.status,
|
|
6828
|
+
updated_at = excluded.updated_at,
|
|
6829
|
+
latest_prompt = excluded.latest_prompt,
|
|
6830
|
+
is_global = excluded.is_global
|
|
6831
|
+
`).run(
|
|
6832
|
+
session.sessionId,
|
|
6833
|
+
session.title,
|
|
6834
|
+
session.workspacePath,
|
|
6835
|
+
session.status,
|
|
6836
|
+
session.startedAt,
|
|
6837
|
+
session.updatedAt,
|
|
6838
|
+
session.latestPrompt ?? null,
|
|
6839
|
+
session.isGlobal ? 1 : 0
|
|
6840
|
+
);
|
|
6841
|
+
});
|
|
6090
6842
|
}
|
|
6091
6843
|
listRuntimeSessions(limit = 100) {
|
|
6092
6844
|
const rows = this.db.prepare(`
|
|
@@ -6104,33 +6856,35 @@ Original error: ${err.message}`
|
|
|
6104
6856
|
}));
|
|
6105
6857
|
}
|
|
6106
6858
|
upsertRuntimeNode(node) {
|
|
6107
|
-
this.
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6859
|
+
this.enqueueWrite(() => {
|
|
6860
|
+
this.db.prepare(`
|
|
6861
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
6862
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6863
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
6864
|
+
session_id = excluded.session_id,
|
|
6865
|
+
parent_id = excluded.parent_id,
|
|
6866
|
+
role = excluded.role,
|
|
6867
|
+
label = excluded.label,
|
|
6868
|
+
status = excluded.status,
|
|
6869
|
+
current_action = excluded.current_action,
|
|
6870
|
+
progress_pct = excluded.progress_pct,
|
|
6871
|
+
updated_at = excluded.updated_at,
|
|
6872
|
+
workspace_path = excluded.workspace_path,
|
|
6873
|
+
is_global = excluded.is_global
|
|
6874
|
+
`).run(
|
|
6875
|
+
node.tierId,
|
|
6876
|
+
node.sessionId,
|
|
6877
|
+
node.parentId ?? null,
|
|
6878
|
+
node.role,
|
|
6879
|
+
node.label,
|
|
6880
|
+
node.status,
|
|
6881
|
+
node.currentAction ?? null,
|
|
6882
|
+
node.progressPct ?? null,
|
|
6883
|
+
node.updatedAt,
|
|
6884
|
+
node.workspacePath ?? null,
|
|
6885
|
+
node.isGlobal ? 1 : 0
|
|
6886
|
+
);
|
|
6887
|
+
});
|
|
6134
6888
|
}
|
|
6135
6889
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
6136
6890
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -6153,30 +6907,32 @@ Original error: ${err.message}`
|
|
|
6153
6907
|
}));
|
|
6154
6908
|
}
|
|
6155
6909
|
addRuntimeNodeLog(log) {
|
|
6156
|
-
this.
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6910
|
+
this.enqueueWrite(() => {
|
|
6911
|
+
this.db.prepare(`
|
|
6912
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
6913
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6914
|
+
`).run(
|
|
6915
|
+
log.id,
|
|
6916
|
+
log.sessionId,
|
|
6917
|
+
log.tierId,
|
|
6918
|
+
log.role,
|
|
6919
|
+
log.label,
|
|
6920
|
+
log.status,
|
|
6921
|
+
log.currentAction ?? null,
|
|
6922
|
+
log.progressPct ?? null,
|
|
6923
|
+
log.timestamp,
|
|
6924
|
+
log.workspacePath ?? null,
|
|
6925
|
+
log.isGlobal ? 1 : 0
|
|
6926
|
+
);
|
|
6927
|
+
this.db.prepare(`
|
|
6928
|
+
DELETE FROM runtime_node_logs
|
|
6929
|
+
WHERE id NOT IN (
|
|
6930
|
+
SELECT id FROM runtime_node_logs
|
|
6931
|
+
ORDER BY timestamp DESC
|
|
6932
|
+
LIMIT 2000
|
|
6933
|
+
)
|
|
6934
|
+
`).run();
|
|
6935
|
+
});
|
|
6180
6936
|
}
|
|
6181
6937
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
6182
6938
|
let rows;
|
|
@@ -6214,19 +6970,21 @@ Original error: ${err.message}`
|
|
|
6214
6970
|
}
|
|
6215
6971
|
// ── Messages ──────────────────────────────────
|
|
6216
6972
|
addMessage(message) {
|
|
6217
|
-
this.
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6973
|
+
this.enqueueWrite(() => {
|
|
6974
|
+
this.db.prepare(`
|
|
6975
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
6976
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6977
|
+
`).run(
|
|
6978
|
+
message.id,
|
|
6979
|
+
message.sessionId,
|
|
6980
|
+
message.role,
|
|
6981
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
6982
|
+
message.timestamp,
|
|
6983
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
6984
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
6985
|
+
);
|
|
6986
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
6987
|
+
});
|
|
6230
6988
|
}
|
|
6231
6989
|
getSessionMessages(sessionId) {
|
|
6232
6990
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -6323,10 +7081,12 @@ Original error: ${err.message}`
|
|
|
6323
7081
|
}
|
|
6324
7082
|
// ── Audit Log ─────────────────────────────────
|
|
6325
7083
|
addAuditEntry(entry) {
|
|
6326
|
-
this.
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
7084
|
+
this.enqueueWrite(() => {
|
|
7085
|
+
this.db.prepare(`
|
|
7086
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
7087
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7088
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
7089
|
+
});
|
|
6330
7090
|
}
|
|
6331
7091
|
getAuditLog(sessionId, limit = 100) {
|
|
6332
7092
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -6341,10 +7101,12 @@ Original error: ${err.message}`
|
|
|
6341
7101
|
}
|
|
6342
7102
|
// ── File Snapshots ────────────────────────────
|
|
6343
7103
|
addFileSnapshot(sessionId, filePath, content) {
|
|
6344
|
-
this.
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
7104
|
+
this.enqueueWrite(() => {
|
|
7105
|
+
this.db.prepare(`
|
|
7106
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
7107
|
+
VALUES (?, ?, ?, ?, ?)
|
|
7108
|
+
`).run(crypto.randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
7109
|
+
});
|
|
6348
7110
|
}
|
|
6349
7111
|
getLatestFileSnapshots(sessionId) {
|
|
6350
7112
|
const rows = this.db.prepare(`
|
|
@@ -6640,7 +7402,7 @@ var ConfigManager = class {
|
|
|
6640
7402
|
globalDir;
|
|
6641
7403
|
constructor(workspacePath = process.cwd()) {
|
|
6642
7404
|
this.workspacePath = workspacePath;
|
|
6643
|
-
this.globalDir = path13__default.default.join(
|
|
7405
|
+
this.globalDir = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
6644
7406
|
}
|
|
6645
7407
|
async load() {
|
|
6646
7408
|
this.config = await this.loadConfig();
|
|
@@ -6708,6 +7470,7 @@ var ConfigManager = class {
|
|
|
6708
7470
|
}
|
|
6709
7471
|
}
|
|
6710
7472
|
async injectEnvKeys() {
|
|
7473
|
+
const isFirstRun = this.config.providers.length === 0;
|
|
6711
7474
|
const envProviders = [
|
|
6712
7475
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
6713
7476
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
@@ -6718,10 +7481,13 @@ var ConfigManager = class {
|
|
|
6718
7481
|
const key = process.env[env];
|
|
6719
7482
|
if (!key) continue;
|
|
6720
7483
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
6721
|
-
if (!existing)
|
|
6722
|
-
|
|
7484
|
+
if (!existing && isFirstRun) {
|
|
7485
|
+
this.config.providers.push({ type, apiKey: key });
|
|
7486
|
+
} else if (existing && !existing.apiKey) {
|
|
7487
|
+
existing.apiKey = key;
|
|
7488
|
+
}
|
|
6723
7489
|
}
|
|
6724
|
-
if (!this.config.providers.find((p) => p.type === "ollama")) {
|
|
7490
|
+
if (isFirstRun && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
6725
7491
|
this.config.providers.push({ type: "ollama" });
|
|
6726
7492
|
}
|
|
6727
7493
|
}
|
|
@@ -7010,7 +7776,7 @@ var DashboardServer = class {
|
|
|
7010
7776
|
// ── Setup ─────────────────────────────────────
|
|
7011
7777
|
getGlobalStore() {
|
|
7012
7778
|
if (!this.globalStore) {
|
|
7013
|
-
const globalDbPath = path13__default.default.join(
|
|
7779
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7014
7780
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
7015
7781
|
}
|
|
7016
7782
|
return this.globalStore;
|
|
@@ -7072,7 +7838,7 @@ var DashboardServer = class {
|
|
|
7072
7838
|
}
|
|
7073
7839
|
watchRuntimeChanges() {
|
|
7074
7840
|
const workspaceDbPath = path13__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
7075
|
-
const globalDbPath = path13__default.default.join(
|
|
7841
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7076
7842
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
7077
7843
|
for (const watchPath of watchPaths) {
|
|
7078
7844
|
if (!fs11__default.default.existsSync(watchPath)) continue;
|
|
@@ -7180,7 +7946,7 @@ var DashboardServer = class {
|
|
|
7180
7946
|
const sessionId = req.params.id;
|
|
7181
7947
|
this.store.deleteSession(sessionId);
|
|
7182
7948
|
this.store.deleteRuntimeSession(sessionId);
|
|
7183
|
-
const globalDbPath = path13__default.default.join(
|
|
7949
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7184
7950
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7185
7951
|
try {
|
|
7186
7952
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -7194,7 +7960,7 @@ var DashboardServer = class {
|
|
|
7194
7960
|
});
|
|
7195
7961
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
7196
7962
|
const body = req.body;
|
|
7197
|
-
const globalDbPath = path13__default.default.join(
|
|
7963
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7198
7964
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
7199
7965
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7200
7966
|
try {
|
|
@@ -7217,7 +7983,7 @@ var DashboardServer = class {
|
|
|
7217
7983
|
});
|
|
7218
7984
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
7219
7985
|
this.store.deleteAllRuntimeNodes();
|
|
7220
|
-
const globalDbPath = path13__default.default.join(
|
|
7986
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7221
7987
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7222
7988
|
try {
|
|
7223
7989
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -7313,7 +8079,7 @@ var DashboardServer = class {
|
|
|
7313
8079
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
7314
8080
|
const scope = req.query["scope"] ?? "workspace";
|
|
7315
8081
|
if (scope === "global") {
|
|
7316
|
-
const globalDbPath = path13__default.default.join(
|
|
8082
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7317
8083
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7318
8084
|
try {
|
|
7319
8085
|
res.json({
|
|
@@ -7537,8 +8303,10 @@ exports.CASCADE_MD_FILE = CASCADE_MD_FILE;
|
|
|
7537
8303
|
exports.CASCADE_VERSION = CASCADE_VERSION;
|
|
7538
8304
|
exports.COMPLEXITY_T2_COUNT = COMPLEXITY_T2_COUNT;
|
|
7539
8305
|
exports.Cascade = Cascade;
|
|
8306
|
+
exports.CascadeCancelledError = CascadeCancelledError;
|
|
7540
8307
|
exports.CascadeIgnore = CascadeIgnore;
|
|
7541
8308
|
exports.CascadeRouter = CascadeRouter;
|
|
8309
|
+
exports.CascadeToolError = CascadeToolError;
|
|
7542
8310
|
exports.ConfigManager = ConfigManager;
|
|
7543
8311
|
exports.DEFAULT_API_PORT = DEFAULT_API_PORT;
|
|
7544
8312
|
exports.DEFAULT_APPROVAL_REQUIRED = DEFAULT_APPROVAL_REQUIRED;
|