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.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import crypto, { randomUUID, timingSafeEqual } from 'crypto';
|
|
3
|
+
import { glob } from 'glob';
|
|
3
4
|
import Anthropic from '@anthropic-ai/sdk';
|
|
4
5
|
import OpenAI from 'openai';
|
|
5
6
|
import { GoogleGenAI, HarmBlockThreshold, HarmCategory } from '@google/genai';
|
|
@@ -8,7 +9,7 @@ import fs2 from 'fs/promises';
|
|
|
8
9
|
import path13 from 'path';
|
|
9
10
|
import * as ignoreFactory from 'ignore';
|
|
10
11
|
import ignoreFactory__default from 'ignore';
|
|
11
|
-
import { exec, execFile } from 'child_process';
|
|
12
|
+
import { exec, execFile, execSync } from 'child_process';
|
|
12
13
|
import { promisify } from 'util';
|
|
13
14
|
import { simpleGit } from 'simple-git';
|
|
14
15
|
import fs11 from 'fs';
|
|
@@ -17,7 +18,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
17
18
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
18
19
|
import { z } from 'zod';
|
|
19
20
|
import { createContext, runInContext } from 'vm';
|
|
20
|
-
import
|
|
21
|
+
import os2 from 'os';
|
|
21
22
|
import Database from 'better-sqlite3';
|
|
22
23
|
import { createServer } from 'http';
|
|
23
24
|
import { fileURLToPath } from 'url';
|
|
@@ -123,7 +124,7 @@ var require_keytar2 = __commonJS({
|
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
// src/constants.ts
|
|
126
|
-
var CASCADE_VERSION = "0.2.
|
|
127
|
+
var CASCADE_VERSION = "0.2.12";
|
|
127
128
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
128
129
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
129
130
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -426,7 +427,8 @@ var TOOL_NAMES = {
|
|
|
426
427
|
IMAGE_ANALYZE: "image_analyze",
|
|
427
428
|
PDF_CREATE: "pdf_create",
|
|
428
429
|
RUN_CODE: "run_code",
|
|
429
|
-
PEER_MESSAGE: "peer_message"
|
|
430
|
+
PEER_MESSAGE: "peer_message",
|
|
431
|
+
WEB_SEARCH: "web_search"
|
|
430
432
|
};
|
|
431
433
|
var DEFAULT_APPROVAL_REQUIRED = [
|
|
432
434
|
TOOL_NAMES.SHELL,
|
|
@@ -504,17 +506,38 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
504
506
|
messages,
|
|
505
507
|
tools: tools?.length ? tools : void 0
|
|
506
508
|
});
|
|
509
|
+
let isThinking = false;
|
|
507
510
|
for await (const event of stream) {
|
|
508
|
-
if (event.type === "content_block_delta"
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
511
|
+
if (event.type === "content_block_delta") {
|
|
512
|
+
if (event.delta.type === "thinking_delta") {
|
|
513
|
+
if (!isThinking) {
|
|
514
|
+
isThinking = true;
|
|
515
|
+
fullContent += "<think>\n";
|
|
516
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
517
|
+
}
|
|
518
|
+
const text = event.delta.thinking;
|
|
519
|
+
fullContent += text;
|
|
520
|
+
onChunk({ text, finishReason: null });
|
|
521
|
+
} else if (event.delta.type === "text_delta") {
|
|
522
|
+
if (isThinking) {
|
|
523
|
+
isThinking = false;
|
|
524
|
+
fullContent += "\n</think>\n\n";
|
|
525
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
526
|
+
}
|
|
527
|
+
const text = event.delta.text;
|
|
528
|
+
fullContent += text;
|
|
529
|
+
onChunk({ text, finishReason: null });
|
|
530
|
+
}
|
|
512
531
|
} else if (event.type === "message_delta" && event.usage) {
|
|
513
532
|
outputTokens = event.usage.output_tokens;
|
|
514
533
|
} else if (event.type === "message_start" && event.message.usage) {
|
|
515
534
|
inputTokens = event.message.usage.input_tokens;
|
|
516
535
|
}
|
|
517
536
|
}
|
|
537
|
+
if (isThinking) {
|
|
538
|
+
fullContent += "\n</think>\n\n";
|
|
539
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
540
|
+
}
|
|
518
541
|
const finalMessage = await stream.finalMessage();
|
|
519
542
|
const toolCalls = finalMessage.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
520
543
|
id: b.id,
|
|
@@ -575,33 +598,61 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
575
598
|
}
|
|
576
599
|
}
|
|
577
600
|
convertMessages(messages) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
601
|
+
const result = [];
|
|
602
|
+
for (const m of messages) {
|
|
603
|
+
if (m.role === "system") continue;
|
|
604
|
+
if (m.role === "tool") {
|
|
605
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
606
|
+
result.push({
|
|
607
|
+
role: "user",
|
|
608
|
+
content: [{
|
|
609
|
+
type: "tool_result",
|
|
610
|
+
tool_use_id: m.toolCallId ?? "",
|
|
611
|
+
content: toolContent
|
|
612
|
+
}]
|
|
613
|
+
});
|
|
614
|
+
continue;
|
|
581
615
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
616
|
+
if (m.role === "assistant") {
|
|
617
|
+
const content = [];
|
|
618
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
619
|
+
if (text) content.push({ type: "text", text });
|
|
620
|
+
for (const tc of m.toolCalls ?? []) {
|
|
621
|
+
content.push({
|
|
622
|
+
type: "tool_use",
|
|
623
|
+
id: tc.id,
|
|
624
|
+
name: tc.name,
|
|
625
|
+
input: tc.input
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (content.length > 0) {
|
|
629
|
+
result.push({ role: "assistant", content });
|
|
630
|
+
}
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (m.role === "user") {
|
|
634
|
+
if (typeof m.content === "string") {
|
|
635
|
+
result.push({ role: "user", content: m.content });
|
|
636
|
+
} else {
|
|
637
|
+
const content = m.content.map((block) => {
|
|
638
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
639
|
+
if (block.type === "image") {
|
|
640
|
+
const img = block.image;
|
|
641
|
+
if (img.type === "base64") {
|
|
642
|
+
return {
|
|
643
|
+
type: "image",
|
|
644
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
645
|
+
};
|
|
593
646
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
};
|
|
647
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
648
|
+
}
|
|
649
|
+
return { type: "text", text: "" };
|
|
650
|
+
});
|
|
651
|
+
result.push({ role: "user", content });
|
|
600
652
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
605
656
|
}
|
|
606
657
|
};
|
|
607
658
|
var OpenAIProvider = class extends BaseProvider {
|
|
@@ -657,9 +708,25 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
657
708
|
}
|
|
658
709
|
}
|
|
659
710
|
const toolCallsMap = {};
|
|
711
|
+
let isThinking = false;
|
|
660
712
|
for await (const chunk of stream) {
|
|
661
713
|
const delta = chunk.choices[0]?.delta;
|
|
714
|
+
const reasoningContent = delta?.reasoning_content;
|
|
715
|
+
if (reasoningContent) {
|
|
716
|
+
if (!isThinking) {
|
|
717
|
+
isThinking = true;
|
|
718
|
+
fullContent += "<think>\n";
|
|
719
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
720
|
+
}
|
|
721
|
+
fullContent += reasoningContent;
|
|
722
|
+
onChunk({ text: reasoningContent, finishReason: null });
|
|
723
|
+
}
|
|
662
724
|
if (delta?.content) {
|
|
725
|
+
if (isThinking) {
|
|
726
|
+
isThinking = false;
|
|
727
|
+
fullContent += "\n</think>\n\n";
|
|
728
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
729
|
+
}
|
|
663
730
|
fullContent += delta.content;
|
|
664
731
|
onChunk({ text: delta.content, finishReason: null });
|
|
665
732
|
}
|
|
@@ -682,6 +749,10 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
682
749
|
outputTokens = chunk.usage.completion_tokens;
|
|
683
750
|
}
|
|
684
751
|
}
|
|
752
|
+
if (isThinking) {
|
|
753
|
+
fullContent += "\n</think>\n\n";
|
|
754
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
755
|
+
}
|
|
685
756
|
const toolCalls = Object.values(toolCallsMap).map((tc) => {
|
|
686
757
|
let input = {};
|
|
687
758
|
try {
|
|
@@ -862,7 +933,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
862
933
|
for (const part of candidate?.content?.parts ?? []) {
|
|
863
934
|
if (part.functionCall) {
|
|
864
935
|
toolCalls.push({
|
|
865
|
-
id:
|
|
936
|
+
id: part.functionCall.name,
|
|
866
937
|
name: part.functionCall.name,
|
|
867
938
|
input: part.functionCall.args ?? {}
|
|
868
939
|
});
|
|
@@ -950,10 +1021,70 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
950
1021
|
}
|
|
951
1022
|
// ── Private ──────────────────────────────────
|
|
952
1023
|
buildContents(messages, extraImages) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1024
|
+
const contents = [];
|
|
1025
|
+
for (const m of messages) {
|
|
1026
|
+
if (m.role === "system") {
|
|
1027
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
1028
|
+
if (!text.trim()) continue;
|
|
1029
|
+
const prev = contents[contents.length - 1];
|
|
1030
|
+
if (prev?.role === "user") {
|
|
1031
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
1032
|
+
|
|
1033
|
+
` });
|
|
1034
|
+
} else {
|
|
1035
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
1036
|
+
}
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
if (m.role === "tool") {
|
|
1040
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1041
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1042
|
+
contents.push({
|
|
1043
|
+
role: "user",
|
|
1044
|
+
parts: [{
|
|
1045
|
+
functionResponse: {
|
|
1046
|
+
name: functionName,
|
|
1047
|
+
response: { output: toolContent }
|
|
1048
|
+
}
|
|
1049
|
+
}]
|
|
1050
|
+
});
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (m.role === "assistant") {
|
|
1054
|
+
const parts = [];
|
|
1055
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1056
|
+
if (textContent) parts.push({ text: textContent });
|
|
1057
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1058
|
+
parts.push({
|
|
1059
|
+
functionCall: {
|
|
1060
|
+
name: tc.name,
|
|
1061
|
+
args: tc.input
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
if (parts.length > 0) {
|
|
1066
|
+
contents.push({ role: "model", parts });
|
|
1067
|
+
}
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
if (m.role === "user") {
|
|
1071
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1072
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1073
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1074
|
+
if (isLastUser) {
|
|
1075
|
+
for (const img of extraImages) {
|
|
1076
|
+
if (img.type === "base64") {
|
|
1077
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (parts.length > 0) {
|
|
1083
|
+
contents.push({ role: "user", parts });
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return contents;
|
|
957
1088
|
}
|
|
958
1089
|
convertMessageContent(msg, extraImages) {
|
|
959
1090
|
const parts = [];
|
|
@@ -1820,6 +1951,29 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
1820
1951
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
1821
1952
|
}
|
|
1822
1953
|
};
|
|
1954
|
+
|
|
1955
|
+
// src/utils/retry.ts
|
|
1956
|
+
var CascadeCancelledError = class extends Error {
|
|
1957
|
+
constructor(reason) {
|
|
1958
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
1959
|
+
this.name = "CascadeCancelledError";
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1962
|
+
var CascadeToolError = class extends Error {
|
|
1963
|
+
/** A friendly message to show the user / T3 */
|
|
1964
|
+
userMessage;
|
|
1965
|
+
/** Whether this error class is retryable by default */
|
|
1966
|
+
retryable;
|
|
1967
|
+
constructor(userMessage, cause, retryable = false) {
|
|
1968
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause);
|
|
1969
|
+
super(`${userMessage}: ${causeMsg}`);
|
|
1970
|
+
this.name = "CascadeToolError";
|
|
1971
|
+
this.userMessage = userMessage;
|
|
1972
|
+
this.retryable = retryable;
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
|
|
1976
|
+
// src/core/tiers/base.ts
|
|
1823
1977
|
var BaseTier = class extends EventEmitter {
|
|
1824
1978
|
id;
|
|
1825
1979
|
role;
|
|
@@ -1829,6 +1983,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
1829
1983
|
label;
|
|
1830
1984
|
systemPromptOverride = "";
|
|
1831
1985
|
hierarchyContext = "";
|
|
1986
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
1987
|
+
signal;
|
|
1832
1988
|
constructor(role, id, parentId) {
|
|
1833
1989
|
super();
|
|
1834
1990
|
this.role = role;
|
|
@@ -1891,6 +2047,18 @@ var BaseTier = class extends EventEmitter {
|
|
|
1891
2047
|
log(message, data) {
|
|
1892
2048
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1893
2049
|
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
2052
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
2053
|
+
* to provide a fast, clean cancellation path.
|
|
2054
|
+
*/
|
|
2055
|
+
throwIfCancelled() {
|
|
2056
|
+
if (this.signal?.aborted) {
|
|
2057
|
+
throw new CascadeCancelledError(
|
|
2058
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1894
2062
|
};
|
|
1895
2063
|
|
|
1896
2064
|
// src/core/context/manager.ts
|
|
@@ -2091,6 +2259,7 @@ Rules:
|
|
|
2091
2259
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
2092
2260
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
2093
2261
|
- 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.
|
|
2262
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
2094
2263
|
- Use the "pdf_create" tool for PDF requests.
|
|
2095
2264
|
- 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.
|
|
2096
2265
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -2134,7 +2303,8 @@ var T3Worker = class extends BaseTier {
|
|
|
2134
2303
|
this.store = store;
|
|
2135
2304
|
this.audit = new AuditLogger(store, sessionId);
|
|
2136
2305
|
}
|
|
2137
|
-
async execute(assignment, taskId) {
|
|
2306
|
+
async execute(assignment, taskId, signal) {
|
|
2307
|
+
this.signal = signal;
|
|
2138
2308
|
this.assignment = assignment;
|
|
2139
2309
|
this.taskId = taskId;
|
|
2140
2310
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -2284,6 +2454,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
2284
2454
|
tools = [...tools];
|
|
2285
2455
|
while (iterations < MAX_ITERATIONS) {
|
|
2286
2456
|
iterations++;
|
|
2457
|
+
this.throwIfCancelled();
|
|
2287
2458
|
const options = {
|
|
2288
2459
|
messages: this.context.getMessages(),
|
|
2289
2460
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -2304,21 +2475,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2304
2475
|
if (requiresArtifact) {
|
|
2305
2476
|
stalledArtifactIterations += 1;
|
|
2306
2477
|
if (stalledArtifactIterations >= 2) {
|
|
2307
|
-
if (
|
|
2308
|
-
|
|
2309
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
2310
|
-
this.assignment?.description ?? ""
|
|
2311
|
-
);
|
|
2312
|
-
if (toolName) {
|
|
2313
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
2314
|
-
this.sendStatusUpdate({
|
|
2315
|
-
progressPct: 50,
|
|
2316
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
2317
|
-
status: "IN_PROGRESS"
|
|
2318
|
-
});
|
|
2319
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
2320
|
-
continue;
|
|
2321
|
-
}
|
|
2478
|
+
if (stalledArtifactIterations === 2) {
|
|
2479
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
2322
2480
|
}
|
|
2323
2481
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
2324
2482
|
}
|
|
@@ -2478,6 +2636,9 @@ ${assignment.expectedOutput}`;
|
|
|
2478
2636
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
2479
2637
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
2480
2638
|
const issues = [];
|
|
2639
|
+
const { exec: exec3 } = await import('child_process');
|
|
2640
|
+
const { promisify: promisify3 } = await import('util');
|
|
2641
|
+
const execAsync2 = promisify3(exec3);
|
|
2481
2642
|
for (const artifactPath of artifactPaths) {
|
|
2482
2643
|
const absolutePath = path13.resolve(process.cwd(), artifactPath);
|
|
2483
2644
|
try {
|
|
@@ -2494,9 +2655,27 @@ ${assignment.expectedOutput}`;
|
|
|
2494
2655
|
const content = await fs2.readFile(absolutePath, "utf-8");
|
|
2495
2656
|
if (!content.trim()) {
|
|
2496
2657
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
2658
|
+
continue;
|
|
2497
2659
|
}
|
|
2498
2660
|
} else if (stat.size < 100) {
|
|
2499
2661
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
const ext = path13.extname(absolutePath).toLowerCase();
|
|
2665
|
+
try {
|
|
2666
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
2667
|
+
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
2668
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
2669
|
+
await execAsync2(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
2670
|
+
} else if (ext === ".py") {
|
|
2671
|
+
await execAsync2(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
2672
|
+
}
|
|
2673
|
+
} catch (err) {
|
|
2674
|
+
const stderr = err?.stderr || String(err);
|
|
2675
|
+
const stdout = err?.stdout || "";
|
|
2676
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
2677
|
+
${stderr}
|
|
2678
|
+
${stdout}`);
|
|
2500
2679
|
}
|
|
2501
2680
|
} catch {
|
|
2502
2681
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -2893,7 +3072,8 @@ var T2Manager = class extends BaseTier {
|
|
|
2893
3072
|
});
|
|
2894
3073
|
this.emit("peer-sync-received", { fromId, content });
|
|
2895
3074
|
}
|
|
2896
|
-
async execute(assignment, taskId) {
|
|
3075
|
+
async execute(assignment, taskId, signal) {
|
|
3076
|
+
this.signal = signal;
|
|
2897
3077
|
this.assignment = assignment;
|
|
2898
3078
|
this.taskId = taskId;
|
|
2899
3079
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -2905,12 +3085,14 @@ var T2Manager = class extends BaseTier {
|
|
|
2905
3085
|
});
|
|
2906
3086
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
2907
3087
|
try {
|
|
3088
|
+
this.throwIfCancelled();
|
|
2908
3089
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
2909
3090
|
this.sendStatusUpdate({
|
|
2910
3091
|
progressPct: 20,
|
|
2911
3092
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
2912
3093
|
status: "IN_PROGRESS"
|
|
2913
3094
|
});
|
|
3095
|
+
this.throwIfCancelled();
|
|
2914
3096
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
2915
3097
|
this.sendStatusUpdate({
|
|
2916
3098
|
progressPct: 90,
|
|
@@ -3077,11 +3259,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3077
3259
|
).join(", ")}`,
|
|
3078
3260
|
status: "IN_PROGRESS"
|
|
3079
3261
|
});
|
|
3262
|
+
this.throwIfCancelled();
|
|
3080
3263
|
const waveResults = await Promise.allSettled(
|
|
3081
3264
|
runnableIds.map(async (id) => {
|
|
3082
3265
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3083
3266
|
const worker = workerMap.get(id);
|
|
3084
|
-
const result = await worker.execute(assignment, taskId);
|
|
3267
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
3085
3268
|
resultMap.set(id, result);
|
|
3086
3269
|
return result;
|
|
3087
3270
|
})
|
|
@@ -3095,6 +3278,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3095
3278
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3096
3279
|
const retried = await this.retryT3(assignment, taskId);
|
|
3097
3280
|
resultMap.set(id, retried);
|
|
3281
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
3282
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3283
|
+
if (this.toolCreator) {
|
|
3284
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
3285
|
+
this.sendStatusUpdate({
|
|
3286
|
+
progressPct: 50,
|
|
3287
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
3288
|
+
status: "IN_PROGRESS"
|
|
3289
|
+
});
|
|
3290
|
+
const toolName = await this.toolCreator.createTool(
|
|
3291
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
3292
|
+
assignment.description
|
|
3293
|
+
);
|
|
3294
|
+
if (toolName) {
|
|
3295
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
3296
|
+
this.sendStatusUpdate({
|
|
3297
|
+
progressPct: 60,
|
|
3298
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
3299
|
+
status: "IN_PROGRESS"
|
|
3300
|
+
});
|
|
3301
|
+
try {
|
|
3302
|
+
const verifyResult = await this.router.generate("T2", {
|
|
3303
|
+
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".` }],
|
|
3304
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
3305
|
+
maxTokens: 50
|
|
3306
|
+
});
|
|
3307
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
3308
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
3309
|
+
const retried = await this.retryT3({
|
|
3310
|
+
...assignment,
|
|
3311
|
+
description: `${assignment.description}
|
|
3312
|
+
|
|
3313
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
3314
|
+
}, taskId);
|
|
3315
|
+
resultMap.set(id, retried);
|
|
3316
|
+
} else {
|
|
3317
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
3318
|
+
resultMap.set(id, r.value);
|
|
3319
|
+
}
|
|
3320
|
+
} catch {
|
|
3321
|
+
const retried = await this.retryT3({
|
|
3322
|
+
...assignment,
|
|
3323
|
+
description: `${assignment.description}
|
|
3324
|
+
|
|
3325
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
3326
|
+
}, taskId);
|
|
3327
|
+
resultMap.set(id, retried);
|
|
3328
|
+
}
|
|
3329
|
+
} else {
|
|
3330
|
+
resultMap.set(id, r.value);
|
|
3331
|
+
}
|
|
3332
|
+
} else {
|
|
3333
|
+
resultMap.set(id, r.value);
|
|
3334
|
+
}
|
|
3098
3335
|
}
|
|
3099
3336
|
for (const dependent of adj.get(id) ?? []) {
|
|
3100
3337
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -3158,7 +3395,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3158
3395
|
}));
|
|
3159
3396
|
return worker.execute(
|
|
3160
3397
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
3161
|
-
taskId
|
|
3398
|
+
taskId,
|
|
3399
|
+
this.signal
|
|
3162
3400
|
);
|
|
3163
3401
|
}
|
|
3164
3402
|
publishSectionOutput(result) {
|
|
@@ -3172,29 +3410,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3172
3410
|
async aggregateResults(assignment, results) {
|
|
3173
3411
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
3174
3412
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
3175
|
-
const outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
|
|
3176
3413
|
const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
|
|
3177
|
-
const
|
|
3178
|
-
|
|
3179
|
-
${outputs}
|
|
3180
|
-
${peerOutputs ? `
|
|
3414
|
+
const peerContext = peerOutputs ? `
|
|
3181
3415
|
|
|
3182
3416
|
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
3183
|
-
${peerOutputs}` : ""
|
|
3184
|
-
const
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3417
|
+
${peerOutputs}` : "";
|
|
3418
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
3419
|
+
let currentSummary = "";
|
|
3420
|
+
let i = 0;
|
|
3421
|
+
while (i < completed.length) {
|
|
3422
|
+
let chunkText = "";
|
|
3423
|
+
let chunkEnd = i;
|
|
3424
|
+
while (chunkEnd < completed.length) {
|
|
3425
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
3426
|
+
|
|
3427
|
+
`;
|
|
3428
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
3429
|
+
break;
|
|
3430
|
+
}
|
|
3431
|
+
chunkText += nextOutput;
|
|
3432
|
+
chunkEnd++;
|
|
3433
|
+
}
|
|
3434
|
+
i = chunkEnd;
|
|
3435
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
3436
|
+
${currentSummary ? `
|
|
3437
|
+
PREVIOUS SUMMARY SO FAR:
|
|
3438
|
+
${currentSummary}
|
|
3439
|
+
|
|
3440
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
3441
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
3442
|
+
const messages = [{ role: "user", content: prompt }];
|
|
3443
|
+
try {
|
|
3444
|
+
const result = await this.router.generate("T2", {
|
|
3445
|
+
messages,
|
|
3446
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
3189
3447
|
|
|
3190
3448
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3449
|
+
maxTokens: 500
|
|
3450
|
+
});
|
|
3451
|
+
currentSummary = result.content;
|
|
3452
|
+
} catch (err) {
|
|
3453
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3454
|
+
return currentSummary + "\n\n" + chunkText;
|
|
3455
|
+
}
|
|
3197
3456
|
}
|
|
3457
|
+
return currentSummary;
|
|
3198
3458
|
}
|
|
3199
3459
|
determineStatus(results) {
|
|
3200
3460
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -3319,10 +3579,10 @@ Rules:
|
|
|
3319
3579
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
3320
3580
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
3321
3581
|
|
|
3322
|
-
|
|
3323
|
-
-
|
|
3324
|
-
-
|
|
3325
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
3582
|
+
DEPENDENCY GUIDANCE:
|
|
3583
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
3584
|
+
- 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).
|
|
3585
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
3326
3586
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
3327
3587
|
|
|
3328
3588
|
QUALITY RULES:
|
|
@@ -3361,7 +3621,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
3361
3621
|
setToolCreator(creator) {
|
|
3362
3622
|
this.toolCreator = creator;
|
|
3363
3623
|
}
|
|
3364
|
-
async execute(userPrompt, images, systemContext) {
|
|
3624
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
3625
|
+
this.signal = signal;
|
|
3365
3626
|
this.taskId = randomUUID();
|
|
3366
3627
|
this.setLabel("Administrator");
|
|
3367
3628
|
this.setStatus("ACTIVE");
|
|
@@ -3372,10 +3633,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
3372
3633
|
status: "IN_PROGRESS"
|
|
3373
3634
|
});
|
|
3374
3635
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
3636
|
+
this.throwIfCancelled();
|
|
3375
3637
|
let enrichedPrompt = userPrompt;
|
|
3376
3638
|
if (images?.length) {
|
|
3377
3639
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
3378
3640
|
}
|
|
3641
|
+
this.throwIfCancelled();
|
|
3379
3642
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
3380
3643
|
this.sendStatusUpdate({
|
|
3381
3644
|
progressPct: 10,
|
|
@@ -3383,21 +3646,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
3383
3646
|
status: "IN_PROGRESS"
|
|
3384
3647
|
});
|
|
3385
3648
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
3386
|
-
|
|
3649
|
+
this.throwIfCancelled();
|
|
3650
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
3651
|
+
let pass = 1;
|
|
3652
|
+
const MAX_REPLAN_PASSES = 2;
|
|
3653
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
3654
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
3655
|
+
if (reviewResult.approved) {
|
|
3656
|
+
this.log("T1 Review passed.");
|
|
3657
|
+
break;
|
|
3658
|
+
}
|
|
3659
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
3660
|
+
this.sendStatusUpdate({
|
|
3661
|
+
progressPct: 80 + pass * 5,
|
|
3662
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
3663
|
+
status: "IN_PROGRESS"
|
|
3664
|
+
});
|
|
3665
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
3666
|
+
Review reason: ${reviewResult.reason}
|
|
3667
|
+
|
|
3668
|
+
Original goal: ${enrichedPrompt}
|
|
3669
|
+
|
|
3670
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
3671
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
3672
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
3673
|
+
pass++;
|
|
3674
|
+
}
|
|
3387
3675
|
this.sendStatusUpdate({
|
|
3388
3676
|
progressPct: 95,
|
|
3389
3677
|
currentAction: "Compiling final output",
|
|
3390
3678
|
status: "IN_PROGRESS"
|
|
3391
3679
|
});
|
|
3392
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
3680
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
3393
3681
|
this.setStatus("COMPLETED");
|
|
3394
3682
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
3395
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3683
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3396
3684
|
}
|
|
3397
3685
|
getEscalations() {
|
|
3398
3686
|
return [...this.escalations];
|
|
3399
3687
|
}
|
|
3400
3688
|
// ── Private ──────────────────────────────────
|
|
3689
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
3690
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
3691
|
+
if (failedSections.length > 0) {
|
|
3692
|
+
return {
|
|
3693
|
+
approved: false,
|
|
3694
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
3698
|
+
${r.sectionSummary}`).join("\n\n");
|
|
3699
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
3700
|
+
Review the following execution outputs against the original user prompt.
|
|
3701
|
+
|
|
3702
|
+
Original Request: ${originalPrompt}
|
|
3703
|
+
|
|
3704
|
+
T2 Manager Summaries:
|
|
3705
|
+
${sectionsText}
|
|
3706
|
+
|
|
3707
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
3708
|
+
If yes, reply with exactly: "APPROVED".
|
|
3709
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
3710
|
+
try {
|
|
3711
|
+
const result = await this.router.generate("T1", {
|
|
3712
|
+
messages: [{ role: "user", content: prompt }],
|
|
3713
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
3714
|
+
maxTokens: 500,
|
|
3715
|
+
temperature: 0
|
|
3716
|
+
});
|
|
3717
|
+
const response = result.content.trim();
|
|
3718
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
3719
|
+
return { approved: true };
|
|
3720
|
+
}
|
|
3721
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
3722
|
+
} catch {
|
|
3723
|
+
return { approved: true };
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3401
3726
|
async analyzeImages(prompt, images) {
|
|
3402
3727
|
const visionModel = this.router.getModelForTier("T1");
|
|
3403
3728
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -3426,29 +3751,35 @@ ${systemContext}` : "";
|
|
|
3426
3751
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
3427
3752
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
3428
3753
|
|
|
3429
|
-
Return JSON where
|
|
3754
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
3430
3755
|
{
|
|
3431
3756
|
"sections": [{
|
|
3757
|
+
"sectionId": "s1",
|
|
3758
|
+
"sectionTitle": "Setup Project",
|
|
3759
|
+
"description": "Initialize the project",
|
|
3760
|
+
"expectedOutput": "Basic structure created",
|
|
3761
|
+
"constraints": [],
|
|
3762
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
3432
3763
|
"t3Subtasks": [{
|
|
3433
3764
|
"subtaskId": "t1",
|
|
3434
|
-
"subtaskTitle": "
|
|
3435
|
-
"
|
|
3436
|
-
"
|
|
3437
|
-
|
|
3438
|
-
"
|
|
3439
|
-
"subtaskTitle": "Save Code to File",
|
|
3440
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
3441
|
-
"executionMode": "parallel"
|
|
3442
|
-
}, {
|
|
3443
|
-
"subtaskId": "t3",
|
|
3444
|
-
"subtaskTitle": "Execute and Verify",
|
|
3445
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
3446
|
-
"executionMode": "parallel"
|
|
3765
|
+
"subtaskTitle": "Init NPM",
|
|
3766
|
+
"description": "Run npm init",
|
|
3767
|
+
"expectedOutput": "package.json created",
|
|
3768
|
+
"constraints": [],
|
|
3769
|
+
"dependsOn": []
|
|
3447
3770
|
}]
|
|
3771
|
+
}, {
|
|
3772
|
+
"sectionId": "s2",
|
|
3773
|
+
"sectionTitle": "Write Tests",
|
|
3774
|
+
"description": "Write tests for the project",
|
|
3775
|
+
"expectedOutput": "Tests passing",
|
|
3776
|
+
"constraints": [],
|
|
3777
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
3778
|
+
"t3Subtasks": [...]
|
|
3448
3779
|
}]
|
|
3449
3780
|
}
|
|
3450
|
-
Use dependsOn when a
|
|
3451
|
-
Leave dependsOn empty for
|
|
3781
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
3782
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
3452
3783
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
3453
3784
|
const result = await this.router.generate("T1", {
|
|
3454
3785
|
messages,
|
|
@@ -3576,92 +3907,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
3576
3907
|
].filter(Boolean).join(" ");
|
|
3577
3908
|
m.setHierarchyContext(context);
|
|
3578
3909
|
});
|
|
3579
|
-
if (overlapSections.size > 0
|
|
3580
|
-
this.log("Overlap detected \u2014
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3910
|
+
if (overlapSections.size > 0) {
|
|
3911
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
3912
|
+
const overlapArray = Array.from(overlapSections);
|
|
3913
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
3914
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
3915
|
+
if (section) {
|
|
3916
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
3584
3917
|
}
|
|
3585
3918
|
}
|
|
3586
3919
|
}
|
|
3587
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
3588
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
3589
3920
|
const t2Results = [];
|
|
3590
3921
|
try {
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3922
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
3923
|
+
} finally {
|
|
3924
|
+
cleanup();
|
|
3925
|
+
}
|
|
3926
|
+
return t2Results;
|
|
3927
|
+
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
3930
|
+
*/
|
|
3931
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
3932
|
+
const adj = /* @__PURE__ */ new Map();
|
|
3933
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
3934
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
3935
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
3936
|
+
for (const s of sections) {
|
|
3937
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
3938
|
+
inDegree.set(s.sectionId, 0);
|
|
3939
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
3940
|
+
}
|
|
3941
|
+
for (const s of sections) {
|
|
3942
|
+
for (const dep of s.dependsOn ?? []) {
|
|
3943
|
+
adj.get(dep).add(s.sectionId);
|
|
3944
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
const queue = [];
|
|
3948
|
+
const degree = new Map(inDegree);
|
|
3949
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
3950
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3951
|
+
while (queue.length > 0) {
|
|
3952
|
+
const u = queue.shift();
|
|
3953
|
+
visited.add(u);
|
|
3954
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
3955
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
3956
|
+
degree.set(v, newDeg);
|
|
3957
|
+
if (newDeg === 0) queue.push(v);
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
3961
|
+
if (cycleNodes.length > 0) {
|
|
3962
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
3963
|
+
for (const s of sections) {
|
|
3964
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
3965
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
3966
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
3967
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
3968
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
3622
3969
|
}
|
|
3970
|
+
s.dependsOn = safeDeps;
|
|
3623
3971
|
}
|
|
3624
|
-
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
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
|
-
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
const totalSections = sections.length;
|
|
3975
|
+
let completedSections = 0;
|
|
3976
|
+
const executeWave = async () => {
|
|
3977
|
+
const readyIds = [];
|
|
3978
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
3979
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
3980
|
+
readyIds.push(id);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
if (readyIds.length === 0) return;
|
|
3984
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
3985
|
+
resultMap.set(id, null);
|
|
3986
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
3987
|
+
const section = sections[index];
|
|
3988
|
+
const manager = managers[index];
|
|
3989
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
3990
|
+
this.sendStatusUpdate({
|
|
3991
|
+
progressPct,
|
|
3992
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
3993
|
+
status: "IN_PROGRESS"
|
|
3994
|
+
});
|
|
3995
|
+
this.throwIfCancelled();
|
|
3996
|
+
let result;
|
|
3997
|
+
try {
|
|
3998
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
3999
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
4000
|
+
if (result.status === "ESCALATED") {
|
|
4001
|
+
this.escalations.push({
|
|
4002
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
4003
|
+
sectionId: section.sectionId,
|
|
4004
|
+
attempted: result.issues,
|
|
4005
|
+
blocker: result.issues.join("; "),
|
|
4006
|
+
needs: "Human review required"
|
|
3657
4007
|
});
|
|
3658
4008
|
}
|
|
4009
|
+
} catch (err) {
|
|
4010
|
+
result = {
|
|
4011
|
+
sectionId: section.sectionId,
|
|
4012
|
+
sectionTitle: section.sectionTitle,
|
|
4013
|
+
status: "FAILED",
|
|
4014
|
+
t3Results: [],
|
|
4015
|
+
sectionSummary: "",
|
|
4016
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
4017
|
+
};
|
|
3659
4018
|
}
|
|
4019
|
+
resultMap.set(id, result);
|
|
4020
|
+
completedSections++;
|
|
4021
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
4022
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
4023
|
+
}
|
|
4024
|
+
}));
|
|
4025
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
4026
|
+
await executeWave();
|
|
3660
4027
|
}
|
|
3661
|
-
}
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
return t2Results;
|
|
4028
|
+
};
|
|
4029
|
+
await executeWave();
|
|
4030
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
3665
4031
|
}
|
|
3666
4032
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
3667
4033
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -4108,13 +4474,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
4108
4474
|
}
|
|
4109
4475
|
async execute(input, _options) {
|
|
4110
4476
|
const platform = input["platform"] ?? "github";
|
|
4111
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
4112
4477
|
const operation = input["operation"];
|
|
4113
4478
|
const repo = input["repo"];
|
|
4114
|
-
|
|
4115
|
-
|
|
4479
|
+
let token = input["token"];
|
|
4480
|
+
if (!token) {
|
|
4481
|
+
if (platform === "github") {
|
|
4482
|
+
token = process.env["GITHUB_TOKEN"];
|
|
4483
|
+
} else {
|
|
4484
|
+
token = process.env["GITLAB_TOKEN"];
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
if (!token) {
|
|
4488
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
4489
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
4490
|
+
}
|
|
4491
|
+
try {
|
|
4492
|
+
if (platform === "github") {
|
|
4493
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
4494
|
+
}
|
|
4495
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
4496
|
+
} catch (err) {
|
|
4497
|
+
const axiosErr = err;
|
|
4498
|
+
if (axiosErr?.response?.status) {
|
|
4499
|
+
const status = axiosErr.response.status;
|
|
4500
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
4501
|
+
switch (status) {
|
|
4502
|
+
case 401:
|
|
4503
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
4504
|
+
case 403:
|
|
4505
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
4506
|
+
case 404:
|
|
4507
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
4508
|
+
case 422:
|
|
4509
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
4510
|
+
case 429:
|
|
4511
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
4512
|
+
default:
|
|
4513
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
4116
4517
|
}
|
|
4117
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
4118
4518
|
}
|
|
4119
4519
|
async executeGitHub(operation, repo, token, input) {
|
|
4120
4520
|
const headers = {
|
|
@@ -4201,6 +4601,7 @@ ${response.data.description}`;
|
|
|
4201
4601
|
};
|
|
4202
4602
|
|
|
4203
4603
|
// src/tools/browser.ts
|
|
4604
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
4204
4605
|
var BrowserTool = class extends BaseTool {
|
|
4205
4606
|
name = "browser";
|
|
4206
4607
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -4209,7 +4610,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
4209
4610
|
properties: {
|
|
4210
4611
|
action: {
|
|
4211
4612
|
type: "string",
|
|
4212
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
4613
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
4213
4614
|
},
|
|
4214
4615
|
url: { type: "string", description: "URL to navigate to" },
|
|
4215
4616
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -4229,53 +4630,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
4229
4630
|
try {
|
|
4230
4631
|
playwright = await import('playwright');
|
|
4231
4632
|
} catch {
|
|
4232
|
-
|
|
4233
|
-
}
|
|
4234
|
-
if (!this.browser) {
|
|
4235
|
-
const pw = playwright;
|
|
4236
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
4237
|
-
const b = this.browser;
|
|
4238
|
-
this.page = await b.newPage();
|
|
4633
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
4239
4634
|
}
|
|
4240
|
-
const page = this.page;
|
|
4241
4635
|
const action = input["action"];
|
|
4242
4636
|
const timeout = input["timeout"] ?? 1e4;
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
case "evaluate": {
|
|
4261
|
-
const result = await page.evaluate(input["script"]);
|
|
4262
|
-
return JSON.stringify(result);
|
|
4637
|
+
if (action === "close") {
|
|
4638
|
+
await this.close();
|
|
4639
|
+
return "Browser closed.";
|
|
4640
|
+
}
|
|
4641
|
+
if (!this.browser || !this.page) {
|
|
4642
|
+
await this.close();
|
|
4643
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
4644
|
+
const timeoutPromise = new Promise(
|
|
4645
|
+
(_, 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)
|
|
4646
|
+
);
|
|
4647
|
+
try {
|
|
4648
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
4649
|
+
this.page = await this.browser.newPage();
|
|
4650
|
+
} catch (err) {
|
|
4651
|
+
this.browser = null;
|
|
4652
|
+
this.page = null;
|
|
4653
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
4263
4654
|
}
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4655
|
+
}
|
|
4656
|
+
const page = this.page;
|
|
4657
|
+
try {
|
|
4658
|
+
switch (action) {
|
|
4659
|
+
case "navigate": {
|
|
4660
|
+
await page.goto(input["url"], { timeout });
|
|
4661
|
+
const title = await page.title();
|
|
4662
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
4663
|
+
}
|
|
4664
|
+
case "click": {
|
|
4665
|
+
await page.click(input["selector"], { timeout });
|
|
4666
|
+
return `Clicked ${input["selector"]}`;
|
|
4667
|
+
}
|
|
4668
|
+
case "fill": {
|
|
4669
|
+
await page.fill(input["selector"], input["value"]);
|
|
4670
|
+
return `Filled ${input["selector"]} with value`;
|
|
4671
|
+
}
|
|
4672
|
+
case "screenshot": {
|
|
4673
|
+
const buf = await page.screenshot({ type: "png" });
|
|
4674
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
4675
|
+
}
|
|
4676
|
+
case "evaluate": {
|
|
4677
|
+
const result = await page.evaluate(input["script"]);
|
|
4678
|
+
return JSON.stringify(result);
|
|
4679
|
+
}
|
|
4680
|
+
case "extract_text": {
|
|
4681
|
+
const text = await page.locator("body").innerText();
|
|
4682
|
+
return text.slice(0, 1e4);
|
|
4683
|
+
}
|
|
4684
|
+
case "wait": {
|
|
4685
|
+
await page.waitForTimeout(timeout);
|
|
4686
|
+
return `Waited ${timeout}ms`;
|
|
4687
|
+
}
|
|
4688
|
+
default:
|
|
4689
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
4267
4690
|
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4691
|
+
} catch (err) {
|
|
4692
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4693
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
4694
|
+
await this.close();
|
|
4695
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
4271
4696
|
}
|
|
4272
|
-
|
|
4273
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
4697
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
4274
4698
|
}
|
|
4275
4699
|
}
|
|
4276
4700
|
async close() {
|
|
4277
|
-
|
|
4278
|
-
|
|
4701
|
+
try {
|
|
4702
|
+
if (this.page) {
|
|
4703
|
+
await this.page.close().catch(() => {
|
|
4704
|
+
});
|
|
4705
|
+
this.page = null;
|
|
4706
|
+
}
|
|
4707
|
+
if (this.browser) {
|
|
4708
|
+
await this.browser.close().catch(() => {
|
|
4709
|
+
});
|
|
4710
|
+
this.browser = null;
|
|
4711
|
+
}
|
|
4712
|
+
} catch {
|
|
4279
4713
|
this.browser = null;
|
|
4280
4714
|
this.page = null;
|
|
4281
4715
|
}
|
|
@@ -4372,6 +4806,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
4372
4806
|
});
|
|
4373
4807
|
}
|
|
4374
4808
|
};
|
|
4809
|
+
function detectCommand(candidates) {
|
|
4810
|
+
for (const cmd of candidates) {
|
|
4811
|
+
try {
|
|
4812
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
4813
|
+
execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
4814
|
+
return cmd;
|
|
4815
|
+
} catch {
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
return null;
|
|
4819
|
+
}
|
|
4820
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
4821
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
4375
4822
|
var CodeInterpreterTool = class extends BaseTool {
|
|
4376
4823
|
name = "run_code";
|
|
4377
4824
|
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.";
|
|
@@ -4387,10 +4834,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4387
4834
|
isDangerous() {
|
|
4388
4835
|
return true;
|
|
4389
4836
|
}
|
|
4390
|
-
async execute(input,
|
|
4837
|
+
async execute(input, _options) {
|
|
4391
4838
|
const language = input["language"];
|
|
4392
4839
|
const code = input["code"];
|
|
4393
4840
|
const args = input["args"] ?? [];
|
|
4841
|
+
let cmdPrefix;
|
|
4842
|
+
if (language === "python") {
|
|
4843
|
+
if (!PYTHON_CMD) {
|
|
4844
|
+
return [
|
|
4845
|
+
"Error: Python interpreter not found.",
|
|
4846
|
+
"Please install Python and ensure it is in your PATH.",
|
|
4847
|
+
"Tried: python3, python"
|
|
4848
|
+
].join("\n");
|
|
4849
|
+
}
|
|
4850
|
+
cmdPrefix = PYTHON_CMD;
|
|
4851
|
+
} else {
|
|
4852
|
+
if (!NODE_CMD) {
|
|
4853
|
+
return [
|
|
4854
|
+
"Error: Node.js interpreter not found.",
|
|
4855
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
4856
|
+
"Tried: node"
|
|
4857
|
+
].join("\n");
|
|
4858
|
+
}
|
|
4859
|
+
cmdPrefix = NODE_CMD;
|
|
4860
|
+
}
|
|
4394
4861
|
const tmpDir = path13.join(process.cwd(), ".cascade", "tmp");
|
|
4395
4862
|
if (!fs11.existsSync(tmpDir)) {
|
|
4396
4863
|
fs11.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -4399,8 +4866,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4399
4866
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
4400
4867
|
const filePath = path13.join(tmpDir, fileName);
|
|
4401
4868
|
fs11.writeFileSync(filePath, code, "utf-8");
|
|
4402
|
-
const
|
|
4403
|
-
const
|
|
4869
|
+
const quotedPath = `"${filePath}"`;
|
|
4870
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
4871
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
4404
4872
|
return new Promise((resolve) => {
|
|
4405
4873
|
const startMs = Date.now();
|
|
4406
4874
|
exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -4413,10 +4881,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4413
4881
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
4414
4882
|
}
|
|
4415
4883
|
if (error) {
|
|
4416
|
-
|
|
4884
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
4885
|
+
if (timedOut) {
|
|
4886
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
4887
|
+
Partial stdout: ${stdout}
|
|
4888
|
+
Stderr: ${stderr}`);
|
|
4889
|
+
} else {
|
|
4890
|
+
resolve(`Execution failed (${duration}ms):
|
|
4417
4891
|
Error: ${error.message}
|
|
4418
4892
|
Stderr: ${stderr}
|
|
4419
4893
|
Stdout: ${stdout}`);
|
|
4894
|
+
}
|
|
4420
4895
|
} else {
|
|
4421
4896
|
resolve(`Execution successful (${duration}ms):
|
|
4422
4897
|
Stdout: ${stdout}
|
|
@@ -4481,6 +4956,186 @@ ${formatted}`;
|
|
|
4481
4956
|
}
|
|
4482
4957
|
};
|
|
4483
4958
|
|
|
4959
|
+
// src/tools/web-search.ts
|
|
4960
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
4961
|
+
const url = new URL("/search", baseUrl);
|
|
4962
|
+
url.searchParams.set("q", query);
|
|
4963
|
+
url.searchParams.set("format", "json");
|
|
4964
|
+
url.searchParams.set("categories", "general");
|
|
4965
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
4966
|
+
const resp = await fetch(url.toString(), {
|
|
4967
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
4968
|
+
signal: AbortSignal.timeout(1e4)
|
|
4969
|
+
});
|
|
4970
|
+
if (!resp.ok) {
|
|
4971
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
4972
|
+
}
|
|
4973
|
+
const data = await resp.json();
|
|
4974
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
4975
|
+
title: r.title ?? "",
|
|
4976
|
+
url: r.url ?? "",
|
|
4977
|
+
snippet: r.content ?? "",
|
|
4978
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
4979
|
+
}));
|
|
4980
|
+
}
|
|
4981
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
4982
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
4983
|
+
const resp = await fetch(url, {
|
|
4984
|
+
headers: {
|
|
4985
|
+
"Accept": "application/json",
|
|
4986
|
+
"Accept-Encoding": "gzip",
|
|
4987
|
+
"X-Subscription-Token": apiKey
|
|
4988
|
+
},
|
|
4989
|
+
signal: AbortSignal.timeout(1e4)
|
|
4990
|
+
});
|
|
4991
|
+
if (!resp.ok) {
|
|
4992
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
4993
|
+
}
|
|
4994
|
+
const data = await resp.json();
|
|
4995
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
4996
|
+
title: r.title ?? "",
|
|
4997
|
+
url: r.url ?? "",
|
|
4998
|
+
snippet: r.description ?? "",
|
|
4999
|
+
engine: "brave"
|
|
5000
|
+
}));
|
|
5001
|
+
}
|
|
5002
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
5003
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
5004
|
+
method: "POST",
|
|
5005
|
+
headers: {
|
|
5006
|
+
"Content-Type": "application/json",
|
|
5007
|
+
"Authorization": `Bearer ${apiKey}`
|
|
5008
|
+
},
|
|
5009
|
+
body: JSON.stringify({
|
|
5010
|
+
query,
|
|
5011
|
+
max_results: maxResults,
|
|
5012
|
+
search_depth: "basic",
|
|
5013
|
+
include_answer: false,
|
|
5014
|
+
include_raw_content: false
|
|
5015
|
+
}),
|
|
5016
|
+
signal: AbortSignal.timeout(15e3)
|
|
5017
|
+
});
|
|
5018
|
+
if (!resp.ok) {
|
|
5019
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
5020
|
+
}
|
|
5021
|
+
const data = await resp.json();
|
|
5022
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
5023
|
+
title: r.title ?? "",
|
|
5024
|
+
url: r.url ?? "",
|
|
5025
|
+
snippet: r.content ?? "",
|
|
5026
|
+
engine: "tavily"
|
|
5027
|
+
}));
|
|
5028
|
+
}
|
|
5029
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
5030
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
5031
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
5032
|
+
signal: AbortSignal.timeout(1e4)
|
|
5033
|
+
});
|
|
5034
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
5035
|
+
const html = await resp.text();
|
|
5036
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
5037
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
5038
|
+
const links = [];
|
|
5039
|
+
const snippets = [];
|
|
5040
|
+
let m;
|
|
5041
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
5042
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
5043
|
+
}
|
|
5044
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
5045
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
5046
|
+
}
|
|
5047
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
5048
|
+
title: link.title,
|
|
5049
|
+
url: link.url,
|
|
5050
|
+
snippet: snippets[i] ?? "",
|
|
5051
|
+
engine: "duckduckgo-lite"
|
|
5052
|
+
}));
|
|
5053
|
+
}
|
|
5054
|
+
var WebSearchTool = class extends BaseTool {
|
|
5055
|
+
name = "web_search";
|
|
5056
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
5057
|
+
inputSchema = {
|
|
5058
|
+
type: "object",
|
|
5059
|
+
properties: {
|
|
5060
|
+
query: { type: "string", description: "The search query" },
|
|
5061
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
5062
|
+
},
|
|
5063
|
+
required: ["query"]
|
|
5064
|
+
};
|
|
5065
|
+
config;
|
|
5066
|
+
constructor(config = {}) {
|
|
5067
|
+
super();
|
|
5068
|
+
this.config = {
|
|
5069
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
5070
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
5071
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
5072
|
+
maxResults: config.maxResults ?? 5
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
async execute(input, _options) {
|
|
5076
|
+
const query = input["query"];
|
|
5077
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
5078
|
+
const maxResults = Math.min(
|
|
5079
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
5080
|
+
10
|
|
5081
|
+
);
|
|
5082
|
+
const errors = [];
|
|
5083
|
+
let results = [];
|
|
5084
|
+
if (this.config.searxngUrl) {
|
|
5085
|
+
try {
|
|
5086
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
5087
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5088
|
+
errors.push("SearXNG: returned 0 results");
|
|
5089
|
+
} catch (err) {
|
|
5090
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
if (this.config.braveApiKey) {
|
|
5094
|
+
try {
|
|
5095
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
5096
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5097
|
+
errors.push("Brave: returned 0 results");
|
|
5098
|
+
} catch (err) {
|
|
5099
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
if (this.config.tavilyApiKey) {
|
|
5103
|
+
try {
|
|
5104
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
5105
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5106
|
+
errors.push("Tavily: returned 0 results");
|
|
5107
|
+
} catch (err) {
|
|
5108
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
try {
|
|
5112
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
5113
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5114
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
5115
|
+
} catch (err) {
|
|
5116
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
5117
|
+
}
|
|
5118
|
+
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" : "";
|
|
5119
|
+
return [
|
|
5120
|
+
`Web search for "${query}" failed across all backends:`,
|
|
5121
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
5122
|
+
configHint
|
|
5123
|
+
].join("\n");
|
|
5124
|
+
}
|
|
5125
|
+
formatResults(query, results) {
|
|
5126
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
5127
|
+
for (let i = 0; i < results.length; i++) {
|
|
5128
|
+
const r = results[i];
|
|
5129
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
5130
|
+
lines.push(` URL: ${r.url}`);
|
|
5131
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
5132
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
5133
|
+
lines.push("");
|
|
5134
|
+
}
|
|
5135
|
+
return lines.join("\n");
|
|
5136
|
+
}
|
|
5137
|
+
};
|
|
5138
|
+
|
|
4484
5139
|
// src/tools/mcp.ts
|
|
4485
5140
|
var McpToolWrapper = class extends BaseTool {
|
|
4486
5141
|
name;
|
|
@@ -4602,7 +5257,8 @@ var ToolRegistry = class {
|
|
|
4602
5257
|
new ImageAnalyzeTool(),
|
|
4603
5258
|
new PDFCreateTool(),
|
|
4604
5259
|
new CodeInterpreterTool(),
|
|
4605
|
-
new PeerCommunicationTool()
|
|
5260
|
+
new PeerCommunicationTool(),
|
|
5261
|
+
new WebSearchTool(this.config.webSearch)
|
|
4606
5262
|
];
|
|
4607
5263
|
for (const tool of tools) {
|
|
4608
5264
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -4626,8 +5282,23 @@ var ToolRegistry = class {
|
|
|
4626
5282
|
return this.ignoreMatcher.ignores(posixRel);
|
|
4627
5283
|
}
|
|
4628
5284
|
};
|
|
4629
|
-
var McpClient = class {
|
|
5285
|
+
var McpClient = class _McpClient {
|
|
5286
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
5287
|
+
/**
|
|
5288
|
+
* Forcefully kills all known MCP child processes.
|
|
5289
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
5290
|
+
*/
|
|
5291
|
+
static killAllProcesses() {
|
|
5292
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
5293
|
+
try {
|
|
5294
|
+
process.kill(pid, "SIGKILL");
|
|
5295
|
+
} catch {
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
_McpClient.activeProcessPids.clear();
|
|
5299
|
+
}
|
|
4630
5300
|
clients = /* @__PURE__ */ new Map();
|
|
5301
|
+
transports = /* @__PURE__ */ new Map();
|
|
4631
5302
|
tools = /* @__PURE__ */ new Map();
|
|
4632
5303
|
trustedServers;
|
|
4633
5304
|
approvalCallback;
|
|
@@ -4656,6 +5327,8 @@ var McpClient = class {
|
|
|
4656
5327
|
);
|
|
4657
5328
|
await client.connect(transport);
|
|
4658
5329
|
this.clients.set(server.name, client);
|
|
5330
|
+
this.transports.set(server.name, transport);
|
|
5331
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
4659
5332
|
const toolsResult = await client.listTools();
|
|
4660
5333
|
for (const tool of toolsResult.tools) {
|
|
4661
5334
|
for (const existing of this.tools.values()) {
|
|
@@ -4677,8 +5350,11 @@ var McpClient = class {
|
|
|
4677
5350
|
async disconnect(serverName) {
|
|
4678
5351
|
const client = this.clients.get(serverName);
|
|
4679
5352
|
if (client) {
|
|
5353
|
+
const transport = this.transports.get(serverName);
|
|
5354
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
4680
5355
|
await client.close();
|
|
4681
5356
|
this.clients.delete(serverName);
|
|
5357
|
+
this.transports.delete(serverName);
|
|
4682
5358
|
for (const key of this.tools.keys()) {
|
|
4683
5359
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
4684
5360
|
}
|
|
@@ -4706,6 +5382,13 @@ var McpClient = class {
|
|
|
4706
5382
|
getConnectedServers() {
|
|
4707
5383
|
return Array.from(this.clients.keys());
|
|
4708
5384
|
}
|
|
5385
|
+
getActivePids() {
|
|
5386
|
+
const pids = [];
|
|
5387
|
+
for (const transport of this.transports.values()) {
|
|
5388
|
+
if (transport.pid) pids.push(transport.pid);
|
|
5389
|
+
}
|
|
5390
|
+
return pids;
|
|
5391
|
+
}
|
|
4709
5392
|
isConnected(serverName) {
|
|
4710
5393
|
return this.clients.has(serverName);
|
|
4711
5394
|
}
|
|
@@ -4844,12 +5527,24 @@ var McpServerConfigSchema = z.object({
|
|
|
4844
5527
|
args: z.array(z.string()).optional(),
|
|
4845
5528
|
env: z.record(z.string()).optional()
|
|
4846
5529
|
});
|
|
5530
|
+
var WebSearchConfigSchema = z.object({
|
|
5531
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
5532
|
+
searxngUrl: z.string().optional(),
|
|
5533
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
5534
|
+
braveApiKey: z.string().optional(),
|
|
5535
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
5536
|
+
tavilyApiKey: z.string().optional(),
|
|
5537
|
+
/** Max results per search (default 5) */
|
|
5538
|
+
maxResults: z.number().default(5)
|
|
5539
|
+
});
|
|
4847
5540
|
var ToolsConfigSchema = z.object({
|
|
4848
5541
|
shellAllowlist: z.array(z.string()).default([]),
|
|
4849
5542
|
shellBlocklist: z.array(z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
4850
5543
|
requireApprovalFor: z.array(z.string()).default([]),
|
|
4851
5544
|
browserEnabled: z.boolean().default(false),
|
|
4852
|
-
mcpServers: z.array(McpServerConfigSchema).optional()
|
|
5545
|
+
mcpServers: z.array(McpServerConfigSchema).optional(),
|
|
5546
|
+
/** Web search backends — at least one should be configured for best results */
|
|
5547
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
4853
5548
|
});
|
|
4854
5549
|
var HookDefinitionSchema = z.object({
|
|
4855
5550
|
command: z.string(),
|
|
@@ -5391,12 +6086,25 @@ var Cascade = class extends EventEmitter {
|
|
|
5391
6086
|
looksLikeSimpleArtifactTask(prompt) {
|
|
5392
6087
|
return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
|
|
5393
6088
|
}
|
|
5394
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
6089
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
5395
6090
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
5396
6091
|
return "Simple";
|
|
5397
6092
|
}
|
|
6093
|
+
let workspaceContext = "";
|
|
6094
|
+
try {
|
|
6095
|
+
const files = await glob("**/*.*", {
|
|
6096
|
+
cwd: workspacePath,
|
|
6097
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
6098
|
+
nodir: true
|
|
6099
|
+
});
|
|
6100
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
6101
|
+
} catch {
|
|
6102
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
6103
|
+
}
|
|
5398
6104
|
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.
|
|
5399
6105
|
|
|
6106
|
+
${workspaceContext}
|
|
6107
|
+
|
|
5400
6108
|
Classification:
|
|
5401
6109
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
5402
6110
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -5471,7 +6179,7 @@ ${prompt}` : prompt;
|
|
|
5471
6179
|
}
|
|
5472
6180
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
5473
6181
|
});
|
|
5474
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
6182
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
5475
6183
|
this.telemetry.capture("cascade:session_start", {
|
|
5476
6184
|
complexity,
|
|
5477
6185
|
providerCount: this.config.providers.length,
|
|
@@ -5551,7 +6259,7 @@ ${prompt}` : prompt;
|
|
|
5551
6259
|
peerT3Ids: [],
|
|
5552
6260
|
parentT2: "root"
|
|
5553
6261
|
};
|
|
5554
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
6262
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
5555
6263
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
5556
6264
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
5557
6265
|
} else if (complexity === "Moderate") {
|
|
@@ -5574,7 +6282,7 @@ ${prompt}` : prompt;
|
|
|
5574
6282
|
constraints: [],
|
|
5575
6283
|
t3Subtasks: []
|
|
5576
6284
|
};
|
|
5577
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
6285
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
5578
6286
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
5579
6287
|
t2Results = [t2Result];
|
|
5580
6288
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -5596,13 +6304,22 @@ ${prompt}` : prompt;
|
|
|
5596
6304
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
5597
6305
|
bindTierEvents(t1);
|
|
5598
6306
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
5599
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
6307
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
5600
6308
|
finalOutput = result.output;
|
|
5601
6309
|
t2Results = result.t2Results;
|
|
5602
6310
|
}
|
|
5603
6311
|
} catch (err) {
|
|
5604
|
-
|
|
5605
|
-
|
|
6312
|
+
if (err instanceof CascadeCancelledError) {
|
|
6313
|
+
this.emit("run:cancelled", {
|
|
6314
|
+
taskId,
|
|
6315
|
+
reason: err.message,
|
|
6316
|
+
partialOutput: finalOutput || ""
|
|
6317
|
+
});
|
|
6318
|
+
runError = null;
|
|
6319
|
+
} else {
|
|
6320
|
+
runError = err;
|
|
6321
|
+
throw err;
|
|
6322
|
+
}
|
|
5606
6323
|
} finally {
|
|
5607
6324
|
try {
|
|
5608
6325
|
escalator.cancelAllPending();
|
|
@@ -5924,9 +6641,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
5924
6641
|
constructor(dbPath) {
|
|
5925
6642
|
fs11.mkdirSync(path13.dirname(dbPath), { recursive: true });
|
|
5926
6643
|
try {
|
|
5927
|
-
this.db = new Database(dbPath);
|
|
6644
|
+
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
5928
6645
|
this.db.pragma("journal_mode = WAL");
|
|
5929
6646
|
this.db.pragma("foreign_keys = ON");
|
|
6647
|
+
this.db.pragma("synchronous = NORMAL");
|
|
5930
6648
|
this.migrate();
|
|
5931
6649
|
} catch (err) {
|
|
5932
6650
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -5940,6 +6658,38 @@ Original error: ${err.message}`
|
|
|
5940
6658
|
throw err;
|
|
5941
6659
|
}
|
|
5942
6660
|
}
|
|
6661
|
+
// ── Async Write Queue ─────────────────────────
|
|
6662
|
+
writeQueue = [];
|
|
6663
|
+
isProcessingQueue = false;
|
|
6664
|
+
async processQueue() {
|
|
6665
|
+
if (this.isProcessingQueue) return;
|
|
6666
|
+
this.isProcessingQueue = true;
|
|
6667
|
+
while (this.writeQueue.length > 0) {
|
|
6668
|
+
const op = this.writeQueue.shift();
|
|
6669
|
+
if (op) {
|
|
6670
|
+
let attempts = 0;
|
|
6671
|
+
while (attempts < 5) {
|
|
6672
|
+
try {
|
|
6673
|
+
op();
|
|
6674
|
+
break;
|
|
6675
|
+
} catch (err) {
|
|
6676
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
6677
|
+
attempts++;
|
|
6678
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
6679
|
+
} else {
|
|
6680
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
6681
|
+
break;
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6687
|
+
this.isProcessingQueue = false;
|
|
6688
|
+
}
|
|
6689
|
+
enqueueWrite(op) {
|
|
6690
|
+
this.writeQueue.push(op);
|
|
6691
|
+
this.processQueue().catch(console.error);
|
|
6692
|
+
}
|
|
5943
6693
|
// ── Sessions ──────────────────────────────────
|
|
5944
6694
|
createSession(session) {
|
|
5945
6695
|
this.db.prepare(`
|
|
@@ -6026,26 +6776,28 @@ Original error: ${err.message}`
|
|
|
6026
6776
|
}
|
|
6027
6777
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
6028
6778
|
upsertRuntimeSession(session) {
|
|
6029
|
-
this.
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6779
|
+
this.enqueueWrite(() => {
|
|
6780
|
+
this.db.prepare(`
|
|
6781
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
6782
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6783
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
6784
|
+
title = excluded.title,
|
|
6785
|
+
workspace_path = excluded.workspace_path,
|
|
6786
|
+
status = excluded.status,
|
|
6787
|
+
updated_at = excluded.updated_at,
|
|
6788
|
+
latest_prompt = excluded.latest_prompt,
|
|
6789
|
+
is_global = excluded.is_global
|
|
6790
|
+
`).run(
|
|
6791
|
+
session.sessionId,
|
|
6792
|
+
session.title,
|
|
6793
|
+
session.workspacePath,
|
|
6794
|
+
session.status,
|
|
6795
|
+
session.startedAt,
|
|
6796
|
+
session.updatedAt,
|
|
6797
|
+
session.latestPrompt ?? null,
|
|
6798
|
+
session.isGlobal ? 1 : 0
|
|
6799
|
+
);
|
|
6800
|
+
});
|
|
6049
6801
|
}
|
|
6050
6802
|
listRuntimeSessions(limit = 100) {
|
|
6051
6803
|
const rows = this.db.prepare(`
|
|
@@ -6063,33 +6815,35 @@ Original error: ${err.message}`
|
|
|
6063
6815
|
}));
|
|
6064
6816
|
}
|
|
6065
6817
|
upsertRuntimeNode(node) {
|
|
6066
|
-
this.
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6818
|
+
this.enqueueWrite(() => {
|
|
6819
|
+
this.db.prepare(`
|
|
6820
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
6821
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6822
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
6823
|
+
session_id = excluded.session_id,
|
|
6824
|
+
parent_id = excluded.parent_id,
|
|
6825
|
+
role = excluded.role,
|
|
6826
|
+
label = excluded.label,
|
|
6827
|
+
status = excluded.status,
|
|
6828
|
+
current_action = excluded.current_action,
|
|
6829
|
+
progress_pct = excluded.progress_pct,
|
|
6830
|
+
updated_at = excluded.updated_at,
|
|
6831
|
+
workspace_path = excluded.workspace_path,
|
|
6832
|
+
is_global = excluded.is_global
|
|
6833
|
+
`).run(
|
|
6834
|
+
node.tierId,
|
|
6835
|
+
node.sessionId,
|
|
6836
|
+
node.parentId ?? null,
|
|
6837
|
+
node.role,
|
|
6838
|
+
node.label,
|
|
6839
|
+
node.status,
|
|
6840
|
+
node.currentAction ?? null,
|
|
6841
|
+
node.progressPct ?? null,
|
|
6842
|
+
node.updatedAt,
|
|
6843
|
+
node.workspacePath ?? null,
|
|
6844
|
+
node.isGlobal ? 1 : 0
|
|
6845
|
+
);
|
|
6846
|
+
});
|
|
6093
6847
|
}
|
|
6094
6848
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
6095
6849
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -6112,30 +6866,32 @@ Original error: ${err.message}`
|
|
|
6112
6866
|
}));
|
|
6113
6867
|
}
|
|
6114
6868
|
addRuntimeNodeLog(log) {
|
|
6115
|
-
this.
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6869
|
+
this.enqueueWrite(() => {
|
|
6870
|
+
this.db.prepare(`
|
|
6871
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
6872
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6873
|
+
`).run(
|
|
6874
|
+
log.id,
|
|
6875
|
+
log.sessionId,
|
|
6876
|
+
log.tierId,
|
|
6877
|
+
log.role,
|
|
6878
|
+
log.label,
|
|
6879
|
+
log.status,
|
|
6880
|
+
log.currentAction ?? null,
|
|
6881
|
+
log.progressPct ?? null,
|
|
6882
|
+
log.timestamp,
|
|
6883
|
+
log.workspacePath ?? null,
|
|
6884
|
+
log.isGlobal ? 1 : 0
|
|
6885
|
+
);
|
|
6886
|
+
this.db.prepare(`
|
|
6887
|
+
DELETE FROM runtime_node_logs
|
|
6888
|
+
WHERE id NOT IN (
|
|
6889
|
+
SELECT id FROM runtime_node_logs
|
|
6890
|
+
ORDER BY timestamp DESC
|
|
6891
|
+
LIMIT 2000
|
|
6892
|
+
)
|
|
6893
|
+
`).run();
|
|
6894
|
+
});
|
|
6139
6895
|
}
|
|
6140
6896
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
6141
6897
|
let rows;
|
|
@@ -6173,19 +6929,21 @@ Original error: ${err.message}`
|
|
|
6173
6929
|
}
|
|
6174
6930
|
// ── Messages ──────────────────────────────────
|
|
6175
6931
|
addMessage(message) {
|
|
6176
|
-
this.
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6932
|
+
this.enqueueWrite(() => {
|
|
6933
|
+
this.db.prepare(`
|
|
6934
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
6935
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6936
|
+
`).run(
|
|
6937
|
+
message.id,
|
|
6938
|
+
message.sessionId,
|
|
6939
|
+
message.role,
|
|
6940
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
6941
|
+
message.timestamp,
|
|
6942
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
6943
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
6944
|
+
);
|
|
6945
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
6946
|
+
});
|
|
6189
6947
|
}
|
|
6190
6948
|
getSessionMessages(sessionId) {
|
|
6191
6949
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -6282,10 +7040,12 @@ Original error: ${err.message}`
|
|
|
6282
7040
|
}
|
|
6283
7041
|
// ── Audit Log ─────────────────────────────────
|
|
6284
7042
|
addAuditEntry(entry) {
|
|
6285
|
-
this.
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
7043
|
+
this.enqueueWrite(() => {
|
|
7044
|
+
this.db.prepare(`
|
|
7045
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
7046
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7047
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
7048
|
+
});
|
|
6289
7049
|
}
|
|
6290
7050
|
getAuditLog(sessionId, limit = 100) {
|
|
6291
7051
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -6300,10 +7060,12 @@ Original error: ${err.message}`
|
|
|
6300
7060
|
}
|
|
6301
7061
|
// ── File Snapshots ────────────────────────────
|
|
6302
7062
|
addFileSnapshot(sessionId, filePath, content) {
|
|
6303
|
-
this.
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
7063
|
+
this.enqueueWrite(() => {
|
|
7064
|
+
this.db.prepare(`
|
|
7065
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
7066
|
+
VALUES (?, ?, ?, ?, ?)
|
|
7067
|
+
`).run(randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
7068
|
+
});
|
|
6307
7069
|
}
|
|
6308
7070
|
getLatestFileSnapshots(sessionId) {
|
|
6309
7071
|
const rows = this.db.prepare(`
|
|
@@ -6599,7 +7361,7 @@ var ConfigManager = class {
|
|
|
6599
7361
|
globalDir;
|
|
6600
7362
|
constructor(workspacePath = process.cwd()) {
|
|
6601
7363
|
this.workspacePath = workspacePath;
|
|
6602
|
-
this.globalDir = path13.join(
|
|
7364
|
+
this.globalDir = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR);
|
|
6603
7365
|
}
|
|
6604
7366
|
async load() {
|
|
6605
7367
|
this.config = await this.loadConfig();
|
|
@@ -6667,6 +7429,7 @@ var ConfigManager = class {
|
|
|
6667
7429
|
}
|
|
6668
7430
|
}
|
|
6669
7431
|
async injectEnvKeys() {
|
|
7432
|
+
const isFirstRun = this.config.providers.length === 0;
|
|
6670
7433
|
const envProviders = [
|
|
6671
7434
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
6672
7435
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
@@ -6677,10 +7440,13 @@ var ConfigManager = class {
|
|
|
6677
7440
|
const key = process.env[env];
|
|
6678
7441
|
if (!key) continue;
|
|
6679
7442
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
6680
|
-
if (!existing)
|
|
6681
|
-
|
|
7443
|
+
if (!existing && isFirstRun) {
|
|
7444
|
+
this.config.providers.push({ type, apiKey: key });
|
|
7445
|
+
} else if (existing && !existing.apiKey) {
|
|
7446
|
+
existing.apiKey = key;
|
|
7447
|
+
}
|
|
6682
7448
|
}
|
|
6683
|
-
if (!this.config.providers.find((p) => p.type === "ollama")) {
|
|
7449
|
+
if (isFirstRun && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
6684
7450
|
this.config.providers.push({ type: "ollama" });
|
|
6685
7451
|
}
|
|
6686
7452
|
}
|
|
@@ -6969,7 +7735,7 @@ var DashboardServer = class {
|
|
|
6969
7735
|
// ── Setup ─────────────────────────────────────
|
|
6970
7736
|
getGlobalStore() {
|
|
6971
7737
|
if (!this.globalStore) {
|
|
6972
|
-
const globalDbPath = path13.join(
|
|
7738
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
6973
7739
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
6974
7740
|
}
|
|
6975
7741
|
return this.globalStore;
|
|
@@ -7031,7 +7797,7 @@ var DashboardServer = class {
|
|
|
7031
7797
|
}
|
|
7032
7798
|
watchRuntimeChanges() {
|
|
7033
7799
|
const workspaceDbPath = path13.join(this.workspacePath, CASCADE_DB_FILE);
|
|
7034
|
-
const globalDbPath = path13.join(
|
|
7800
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7035
7801
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
7036
7802
|
for (const watchPath of watchPaths) {
|
|
7037
7803
|
if (!fs11.existsSync(watchPath)) continue;
|
|
@@ -7139,7 +7905,7 @@ var DashboardServer = class {
|
|
|
7139
7905
|
const sessionId = req.params.id;
|
|
7140
7906
|
this.store.deleteSession(sessionId);
|
|
7141
7907
|
this.store.deleteRuntimeSession(sessionId);
|
|
7142
|
-
const globalDbPath = path13.join(
|
|
7908
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7143
7909
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7144
7910
|
try {
|
|
7145
7911
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -7153,7 +7919,7 @@ var DashboardServer = class {
|
|
|
7153
7919
|
});
|
|
7154
7920
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
7155
7921
|
const body = req.body;
|
|
7156
|
-
const globalDbPath = path13.join(
|
|
7922
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7157
7923
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
7158
7924
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7159
7925
|
try {
|
|
@@ -7176,7 +7942,7 @@ var DashboardServer = class {
|
|
|
7176
7942
|
});
|
|
7177
7943
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
7178
7944
|
this.store.deleteAllRuntimeNodes();
|
|
7179
|
-
const globalDbPath = path13.join(
|
|
7945
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7180
7946
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7181
7947
|
try {
|
|
7182
7948
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -7272,7 +8038,7 @@ var DashboardServer = class {
|
|
|
7272
8038
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
7273
8039
|
const scope = req.query["scope"] ?? "workspace";
|
|
7274
8040
|
if (scope === "global") {
|
|
7275
|
-
const globalDbPath = path13.join(
|
|
8041
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7276
8042
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7277
8043
|
try {
|
|
7278
8044
|
res.json({
|
|
@@ -7483,6 +8249,6 @@ var HooksRunner = class {
|
|
|
7483
8249
|
}
|
|
7484
8250
|
};
|
|
7485
8251
|
|
|
7486
|
-
export { AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, COMPLEXITY_T2_COUNT, Cascade, CascadeIgnore, CascadeRouter, ConfigManager, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, HooksRunner, Keystore, LM_STUDIO_BASE_URL, MODELS, McpClient, MemoryStore, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, createCascade, runCascade, streamCascade };
|
|
8252
|
+
export { AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, ConfigManager, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, HooksRunner, Keystore, LM_STUDIO_BASE_URL, MODELS, McpClient, MemoryStore, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, createCascade, runCascade, streamCascade };
|
|
7487
8253
|
//# sourceMappingURL=index.js.map
|
|
7488
8254
|
//# sourceMappingURL=index.js.map
|