cascade-ai 0.2.1 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/cli.cjs +1164 -359
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1164 -359
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1066 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -5
- package/dist/index.d.ts +72 -5
- package/dist/index.js +1065 -332
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/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.11";
|
|
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,
|
|
@@ -616,33 +618,61 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
616
618
|
}
|
|
617
619
|
}
|
|
618
620
|
convertMessages(messages) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
621
|
+
const result = [];
|
|
622
|
+
for (const m of messages) {
|
|
623
|
+
if (m.role === "system") continue;
|
|
624
|
+
if (m.role === "tool") {
|
|
625
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
626
|
+
result.push({
|
|
627
|
+
role: "user",
|
|
628
|
+
content: [{
|
|
629
|
+
type: "tool_result",
|
|
630
|
+
tool_use_id: m.toolCallId ?? "",
|
|
631
|
+
content: toolContent
|
|
632
|
+
}]
|
|
633
|
+
});
|
|
634
|
+
continue;
|
|
622
635
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
636
|
+
if (m.role === "assistant") {
|
|
637
|
+
const content = [];
|
|
638
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
639
|
+
if (text) content.push({ type: "text", text });
|
|
640
|
+
for (const tc of m.toolCalls ?? []) {
|
|
641
|
+
content.push({
|
|
642
|
+
type: "tool_use",
|
|
643
|
+
id: tc.id,
|
|
644
|
+
name: tc.name,
|
|
645
|
+
input: tc.input
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (content.length > 0) {
|
|
649
|
+
result.push({ role: "assistant", content });
|
|
650
|
+
}
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (m.role === "user") {
|
|
654
|
+
if (typeof m.content === "string") {
|
|
655
|
+
result.push({ role: "user", content: m.content });
|
|
656
|
+
} else {
|
|
657
|
+
const content = m.content.map((block) => {
|
|
658
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
659
|
+
if (block.type === "image") {
|
|
660
|
+
const img = block.image;
|
|
661
|
+
if (img.type === "base64") {
|
|
662
|
+
return {
|
|
663
|
+
type: "image",
|
|
664
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
665
|
+
};
|
|
634
666
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
};
|
|
667
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
668
|
+
}
|
|
669
|
+
return { type: "text", text: "" };
|
|
670
|
+
});
|
|
671
|
+
result.push({ role: "user", content });
|
|
641
672
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return result;
|
|
646
676
|
}
|
|
647
677
|
};
|
|
648
678
|
var OpenAIProvider = class extends BaseProvider {
|
|
@@ -903,7 +933,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
903
933
|
for (const part of candidate?.content?.parts ?? []) {
|
|
904
934
|
if (part.functionCall) {
|
|
905
935
|
toolCalls.push({
|
|
906
|
-
id:
|
|
936
|
+
id: part.functionCall.name,
|
|
907
937
|
name: part.functionCall.name,
|
|
908
938
|
input: part.functionCall.args ?? {}
|
|
909
939
|
});
|
|
@@ -991,10 +1021,70 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
991
1021
|
}
|
|
992
1022
|
// ── Private ──────────────────────────────────
|
|
993
1023
|
buildContents(messages, extraImages) {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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;
|
|
998
1088
|
}
|
|
999
1089
|
convertMessageContent(msg, extraImages) {
|
|
1000
1090
|
const parts = [];
|
|
@@ -1861,6 +1951,29 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
1861
1951
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
1862
1952
|
}
|
|
1863
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
|
|
1864
1977
|
var BaseTier = class extends EventEmitter__default.default {
|
|
1865
1978
|
id;
|
|
1866
1979
|
role;
|
|
@@ -1870,6 +1983,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
1870
1983
|
label;
|
|
1871
1984
|
systemPromptOverride = "";
|
|
1872
1985
|
hierarchyContext = "";
|
|
1986
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
1987
|
+
signal;
|
|
1873
1988
|
constructor(role, id, parentId) {
|
|
1874
1989
|
super();
|
|
1875
1990
|
this.role = role;
|
|
@@ -1932,6 +2047,18 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
1932
2047
|
log(message, data) {
|
|
1933
2048
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1934
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
|
+
}
|
|
1935
2062
|
};
|
|
1936
2063
|
|
|
1937
2064
|
// src/core/context/manager.ts
|
|
@@ -2132,6 +2259,7 @@ Rules:
|
|
|
2132
2259
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
2133
2260
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
2134
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.
|
|
2135
2263
|
- Use the "pdf_create" tool for PDF requests.
|
|
2136
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.
|
|
2137
2265
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -2175,7 +2303,8 @@ var T3Worker = class extends BaseTier {
|
|
|
2175
2303
|
this.store = store;
|
|
2176
2304
|
this.audit = new AuditLogger(store, sessionId);
|
|
2177
2305
|
}
|
|
2178
|
-
async execute(assignment, taskId) {
|
|
2306
|
+
async execute(assignment, taskId, signal) {
|
|
2307
|
+
this.signal = signal;
|
|
2179
2308
|
this.assignment = assignment;
|
|
2180
2309
|
this.taskId = taskId;
|
|
2181
2310
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -2307,14 +2436,13 @@ Now execute your subtask using this context where relevant.`
|
|
|
2307
2436
|
await this.peerBus.barrier(this.id, barrierName, total);
|
|
2308
2437
|
}
|
|
2309
2438
|
receivePeerSync(fromId, content) {
|
|
2310
|
-
|
|
2311
|
-
if (existing) {
|
|
2312
|
-
existing.content = content;
|
|
2313
|
-
existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2314
|
-
} else {
|
|
2315
|
-
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2316
|
-
}
|
|
2439
|
+
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2317
2440
|
this.emit("peer-sync-received", { fromId, content });
|
|
2441
|
+
this.context.addMessage({
|
|
2442
|
+
role: "user",
|
|
2443
|
+
content: `[SYSTEM_NOTIFICATION]: You received a new peer message from ${fromId}. Use the "peer_message" tool with action="receive" to read it.`
|
|
2444
|
+
}).catch(() => {
|
|
2445
|
+
});
|
|
2318
2446
|
}
|
|
2319
2447
|
// ── Private ──────────────────────────────────
|
|
2320
2448
|
async runAgentLoop(systemPrompt, tools) {
|
|
@@ -2326,6 +2454,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
2326
2454
|
tools = [...tools];
|
|
2327
2455
|
while (iterations < MAX_ITERATIONS) {
|
|
2328
2456
|
iterations++;
|
|
2457
|
+
this.throwIfCancelled();
|
|
2329
2458
|
const options = {
|
|
2330
2459
|
messages: this.context.getMessages(),
|
|
2331
2460
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -2346,21 +2475,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2346
2475
|
if (requiresArtifact) {
|
|
2347
2476
|
stalledArtifactIterations += 1;
|
|
2348
2477
|
if (stalledArtifactIterations >= 2) {
|
|
2349
|
-
if (
|
|
2350
|
-
|
|
2351
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
2352
|
-
this.assignment?.description ?? ""
|
|
2353
|
-
);
|
|
2354
|
-
if (toolName) {
|
|
2355
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
2356
|
-
this.sendStatusUpdate({
|
|
2357
|
-
progressPct: 50,
|
|
2358
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
2359
|
-
status: "IN_PROGRESS"
|
|
2360
|
-
});
|
|
2361
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
2362
|
-
continue;
|
|
2363
|
-
}
|
|
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"}`);
|
|
2364
2480
|
}
|
|
2365
2481
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
2366
2482
|
}
|
|
@@ -2445,7 +2561,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2445
2561
|
sendPeerSync: (to, syncType, content) => {
|
|
2446
2562
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
2447
2563
|
},
|
|
2448
|
-
getPeerMessages: () =>
|
|
2564
|
+
getPeerMessages: () => {
|
|
2565
|
+
const msgs = [...this.peerSyncBuffer];
|
|
2566
|
+
this.peerSyncBuffer = [];
|
|
2567
|
+
return msgs;
|
|
2568
|
+
}
|
|
2449
2569
|
});
|
|
2450
2570
|
if (this.audit) {
|
|
2451
2571
|
this.audit.toolCall(this.id, tc.name, tc.input);
|
|
@@ -2516,6 +2636,9 @@ ${assignment.expectedOutput}`;
|
|
|
2516
2636
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
2517
2637
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
2518
2638
|
const issues = [];
|
|
2639
|
+
const { exec: exec3 } = await import('child_process');
|
|
2640
|
+
const { promisify: promisify3 } = await import('util');
|
|
2641
|
+
const execAsync2 = promisify3(exec3);
|
|
2519
2642
|
for (const artifactPath of artifactPaths) {
|
|
2520
2643
|
const absolutePath = path13__default.default.resolve(process.cwd(), artifactPath);
|
|
2521
2644
|
try {
|
|
@@ -2532,9 +2655,27 @@ ${assignment.expectedOutput}`;
|
|
|
2532
2655
|
const content = await fs2__default.default.readFile(absolutePath, "utf-8");
|
|
2533
2656
|
if (!content.trim()) {
|
|
2534
2657
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
2658
|
+
continue;
|
|
2535
2659
|
}
|
|
2536
2660
|
} else if (stat.size < 100) {
|
|
2537
2661
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
const ext = path13__default.default.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}`);
|
|
2538
2679
|
}
|
|
2539
2680
|
} catch {
|
|
2540
2681
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -2931,7 +3072,8 @@ var T2Manager = class extends BaseTier {
|
|
|
2931
3072
|
});
|
|
2932
3073
|
this.emit("peer-sync-received", { fromId, content });
|
|
2933
3074
|
}
|
|
2934
|
-
async execute(assignment, taskId) {
|
|
3075
|
+
async execute(assignment, taskId, signal) {
|
|
3076
|
+
this.signal = signal;
|
|
2935
3077
|
this.assignment = assignment;
|
|
2936
3078
|
this.taskId = taskId;
|
|
2937
3079
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -2943,12 +3085,14 @@ var T2Manager = class extends BaseTier {
|
|
|
2943
3085
|
});
|
|
2944
3086
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
2945
3087
|
try {
|
|
3088
|
+
this.throwIfCancelled();
|
|
2946
3089
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
2947
3090
|
this.sendStatusUpdate({
|
|
2948
3091
|
progressPct: 20,
|
|
2949
3092
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
2950
3093
|
status: "IN_PROGRESS"
|
|
2951
3094
|
});
|
|
3095
|
+
this.throwIfCancelled();
|
|
2952
3096
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
2953
3097
|
this.sendStatusUpdate({
|
|
2954
3098
|
progressPct: 90,
|
|
@@ -2987,13 +3131,17 @@ var T2Manager = class extends BaseTier {
|
|
|
2987
3131
|
}
|
|
2988
3132
|
// ── Private ──────────────────────────────────
|
|
2989
3133
|
async decomposeSection(assignment) {
|
|
3134
|
+
const peerPlans = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p) => `[Peer ${p.fromId} Plan]: ${p.content.sectionTitle} - ${p.content.subtaskTitles?.join(", ")}`).join("\n");
|
|
2990
3135
|
const prompt = `Decompose this section into 2-5 concrete subtasks for T3 workers.
|
|
2991
3136
|
|
|
2992
3137
|
Section: ${assignment.sectionTitle}
|
|
2993
3138
|
Description: ${assignment.description}
|
|
2994
3139
|
Expected output: ${assignment.expectedOutput}
|
|
2995
3140
|
Constraints: ${assignment.constraints.join("; ")}
|
|
2996
|
-
|
|
3141
|
+
${peerPlans ? `
|
|
3142
|
+
Context from sibling T2 plans (use this to align execution and avoid overlaps):
|
|
3143
|
+
${peerPlans}
|
|
3144
|
+
` : ""}
|
|
2997
3145
|
Return a JSON array of subtask objects, each with:
|
|
2998
3146
|
- subtaskId: string (unique)
|
|
2999
3147
|
- subtaskTitle: string
|
|
@@ -3111,11 +3259,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3111
3259
|
).join(", ")}`,
|
|
3112
3260
|
status: "IN_PROGRESS"
|
|
3113
3261
|
});
|
|
3262
|
+
this.throwIfCancelled();
|
|
3114
3263
|
const waveResults = await Promise.allSettled(
|
|
3115
3264
|
runnableIds.map(async (id) => {
|
|
3116
3265
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3117
3266
|
const worker = workerMap.get(id);
|
|
3118
|
-
const result = await worker.execute(assignment, taskId);
|
|
3267
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
3119
3268
|
resultMap.set(id, result);
|
|
3120
3269
|
return result;
|
|
3121
3270
|
})
|
|
@@ -3129,6 +3278,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3129
3278
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3130
3279
|
const retried = await this.retryT3(assignment, taskId);
|
|
3131
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
|
+
}
|
|
3132
3335
|
}
|
|
3133
3336
|
for (const dependent of adj.get(id) ?? []) {
|
|
3134
3337
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -3192,7 +3395,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3192
3395
|
}));
|
|
3193
3396
|
return worker.execute(
|
|
3194
3397
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
3195
|
-
taskId
|
|
3398
|
+
taskId,
|
|
3399
|
+
this.signal
|
|
3196
3400
|
);
|
|
3197
3401
|
}
|
|
3198
3402
|
publishSectionOutput(result) {
|
|
@@ -3206,24 +3410,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3206
3410
|
async aggregateResults(assignment, results) {
|
|
3207
3411
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
3208
3412
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
3209
|
-
const
|
|
3210
|
-
const
|
|
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");
|
|
3414
|
+
const peerContext = peerOutputs ? `
|
|
3211
3415
|
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3416
|
+
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
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 ? `
|
|
3218
3447
|
|
|
3219
3448
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
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
|
+
}
|
|
3226
3456
|
}
|
|
3457
|
+
return currentSummary;
|
|
3227
3458
|
}
|
|
3228
3459
|
determineStatus(results) {
|
|
3229
3460
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -3348,10 +3579,10 @@ Rules:
|
|
|
3348
3579
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
3349
3580
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
3350
3581
|
|
|
3351
|
-
|
|
3352
|
-
-
|
|
3353
|
-
-
|
|
3354
|
-
- 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.
|
|
3355
3586
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
3356
3587
|
|
|
3357
3588
|
QUALITY RULES:
|
|
@@ -3390,7 +3621,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
3390
3621
|
setToolCreator(creator) {
|
|
3391
3622
|
this.toolCreator = creator;
|
|
3392
3623
|
}
|
|
3393
|
-
async execute(userPrompt, images, systemContext) {
|
|
3624
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
3625
|
+
this.signal = signal;
|
|
3394
3626
|
this.taskId = crypto.randomUUID();
|
|
3395
3627
|
this.setLabel("Administrator");
|
|
3396
3628
|
this.setStatus("ACTIVE");
|
|
@@ -3401,10 +3633,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
3401
3633
|
status: "IN_PROGRESS"
|
|
3402
3634
|
});
|
|
3403
3635
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
3636
|
+
this.throwIfCancelled();
|
|
3404
3637
|
let enrichedPrompt = userPrompt;
|
|
3405
3638
|
if (images?.length) {
|
|
3406
3639
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
3407
3640
|
}
|
|
3641
|
+
this.throwIfCancelled();
|
|
3408
3642
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
3409
3643
|
this.sendStatusUpdate({
|
|
3410
3644
|
progressPct: 10,
|
|
@@ -3412,21 +3646,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
3412
3646
|
status: "IN_PROGRESS"
|
|
3413
3647
|
});
|
|
3414
3648
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
3415
|
-
|
|
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
|
+
}
|
|
3416
3675
|
this.sendStatusUpdate({
|
|
3417
3676
|
progressPct: 95,
|
|
3418
3677
|
currentAction: "Compiling final output",
|
|
3419
3678
|
status: "IN_PROGRESS"
|
|
3420
3679
|
});
|
|
3421
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
3680
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
3422
3681
|
this.setStatus("COMPLETED");
|
|
3423
3682
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
3424
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3683
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3425
3684
|
}
|
|
3426
3685
|
getEscalations() {
|
|
3427
3686
|
return [...this.escalations];
|
|
3428
3687
|
}
|
|
3429
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
|
+
}
|
|
3430
3726
|
async analyzeImages(prompt, images) {
|
|
3431
3727
|
const visionModel = this.router.getModelForTier("T1");
|
|
3432
3728
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -3455,29 +3751,35 @@ ${systemContext}` : "";
|
|
|
3455
3751
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
3456
3752
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
3457
3753
|
|
|
3458
|
-
Return JSON where
|
|
3754
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
3459
3755
|
{
|
|
3460
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
|
|
3461
3763
|
"t3Subtasks": [{
|
|
3462
3764
|
"subtaskId": "t1",
|
|
3463
|
-
"subtaskTitle": "
|
|
3464
|
-
"
|
|
3465
|
-
"
|
|
3466
|
-
|
|
3467
|
-
"
|
|
3468
|
-
"subtaskTitle": "Save Code to File",
|
|
3469
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
3470
|
-
"executionMode": "parallel"
|
|
3471
|
-
}, {
|
|
3472
|
-
"subtaskId": "t3",
|
|
3473
|
-
"subtaskTitle": "Execute and Verify",
|
|
3474
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
3475
|
-
"executionMode": "parallel"
|
|
3765
|
+
"subtaskTitle": "Init NPM",
|
|
3766
|
+
"description": "Run npm init",
|
|
3767
|
+
"expectedOutput": "package.json created",
|
|
3768
|
+
"constraints": [],
|
|
3769
|
+
"dependsOn": []
|
|
3476
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": [...]
|
|
3477
3779
|
}]
|
|
3478
3780
|
}
|
|
3479
|
-
Use dependsOn when a
|
|
3480
|
-
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.`;
|
|
3481
3783
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
3482
3784
|
const result = await this.router.generate("T1", {
|
|
3483
3785
|
messages,
|
|
@@ -3605,92 +3907,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
3605
3907
|
].filter(Boolean).join(" ");
|
|
3606
3908
|
m.setHierarchyContext(context);
|
|
3607
3909
|
});
|
|
3608
|
-
if (overlapSections.size > 0
|
|
3609
|
-
this.log("Overlap detected \u2014
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
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]];
|
|
3613
3917
|
}
|
|
3614
3918
|
}
|
|
3615
3919
|
}
|
|
3616
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
3617
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
3618
3920
|
const t2Results = [];
|
|
3619
3921
|
try {
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
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
|
-
|
|
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);
|
|
3651
3969
|
}
|
|
3970
|
+
s.dependsOn = safeDeps;
|
|
3652
3971
|
}
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
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"
|
|
3686
4007
|
});
|
|
3687
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
|
+
};
|
|
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));
|
|
3688
4023
|
}
|
|
4024
|
+
}));
|
|
4025
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
4026
|
+
await executeWave();
|
|
3689
4027
|
}
|
|
3690
|
-
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
return t2Results;
|
|
4028
|
+
};
|
|
4029
|
+
await executeWave();
|
|
4030
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
3694
4031
|
}
|
|
3695
4032
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
3696
4033
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -4137,13 +4474,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
4137
4474
|
}
|
|
4138
4475
|
async execute(input, _options) {
|
|
4139
4476
|
const platform = input["platform"] ?? "github";
|
|
4140
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
4141
4477
|
const operation = input["operation"];
|
|
4142
4478
|
const repo = input["repo"];
|
|
4143
|
-
|
|
4144
|
-
|
|
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)}`;
|
|
4145
4517
|
}
|
|
4146
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
4147
4518
|
}
|
|
4148
4519
|
async executeGitHub(operation, repo, token, input) {
|
|
4149
4520
|
const headers = {
|
|
@@ -4230,6 +4601,7 @@ ${response.data.description}`;
|
|
|
4230
4601
|
};
|
|
4231
4602
|
|
|
4232
4603
|
// src/tools/browser.ts
|
|
4604
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
4233
4605
|
var BrowserTool = class extends BaseTool {
|
|
4234
4606
|
name = "browser";
|
|
4235
4607
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -4238,7 +4610,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
4238
4610
|
properties: {
|
|
4239
4611
|
action: {
|
|
4240
4612
|
type: "string",
|
|
4241
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
4613
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
4242
4614
|
},
|
|
4243
4615
|
url: { type: "string", description: "URL to navigate to" },
|
|
4244
4616
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -4258,53 +4630,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
4258
4630
|
try {
|
|
4259
4631
|
playwright = await import('playwright');
|
|
4260
4632
|
} catch {
|
|
4261
|
-
|
|
4262
|
-
}
|
|
4263
|
-
if (!this.browser) {
|
|
4264
|
-
const pw = playwright;
|
|
4265
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
4266
|
-
const b = this.browser;
|
|
4267
|
-
this.page = await b.newPage();
|
|
4633
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
4268
4634
|
}
|
|
4269
|
-
const page = this.page;
|
|
4270
4635
|
const action = input["action"];
|
|
4271
4636
|
const timeout = input["timeout"] ?? 1e4;
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
case "evaluate": {
|
|
4290
|
-
const result = await page.evaluate(input["script"]);
|
|
4291
|
-
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)}`;
|
|
4292
4654
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
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`;
|
|
4296
4690
|
}
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
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}`;
|
|
4300
4696
|
}
|
|
4301
|
-
|
|
4302
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
4697
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
4303
4698
|
}
|
|
4304
4699
|
}
|
|
4305
4700
|
async close() {
|
|
4306
|
-
|
|
4307
|
-
|
|
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 {
|
|
4308
4713
|
this.browser = null;
|
|
4309
4714
|
this.page = null;
|
|
4310
4715
|
}
|
|
@@ -4401,6 +4806,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
4401
4806
|
});
|
|
4402
4807
|
}
|
|
4403
4808
|
};
|
|
4809
|
+
function detectCommand(candidates) {
|
|
4810
|
+
for (const cmd of candidates) {
|
|
4811
|
+
try {
|
|
4812
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
4813
|
+
child_process.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"]);
|
|
4404
4822
|
var CodeInterpreterTool = class extends BaseTool {
|
|
4405
4823
|
name = "run_code";
|
|
4406
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.";
|
|
@@ -4416,10 +4834,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4416
4834
|
isDangerous() {
|
|
4417
4835
|
return true;
|
|
4418
4836
|
}
|
|
4419
|
-
async execute(input,
|
|
4837
|
+
async execute(input, _options) {
|
|
4420
4838
|
const language = input["language"];
|
|
4421
4839
|
const code = input["code"];
|
|
4422
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
|
+
}
|
|
4423
4861
|
const tmpDir = path13__default.default.join(process.cwd(), ".cascade", "tmp");
|
|
4424
4862
|
if (!fs11__default.default.existsSync(tmpDir)) {
|
|
4425
4863
|
fs11__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -4428,8 +4866,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4428
4866
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
4429
4867
|
const filePath = path13__default.default.join(tmpDir, fileName);
|
|
4430
4868
|
fs11__default.default.writeFileSync(filePath, code, "utf-8");
|
|
4431
|
-
const
|
|
4432
|
-
const
|
|
4869
|
+
const quotedPath = `"${filePath}"`;
|
|
4870
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
4871
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
4433
4872
|
return new Promise((resolve) => {
|
|
4434
4873
|
const startMs = Date.now();
|
|
4435
4874
|
child_process.exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -4442,10 +4881,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4442
4881
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
4443
4882
|
}
|
|
4444
4883
|
if (error) {
|
|
4445
|
-
|
|
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):
|
|
4446
4891
|
Error: ${error.message}
|
|
4447
4892
|
Stderr: ${stderr}
|
|
4448
4893
|
Stdout: ${stdout}`);
|
|
4894
|
+
}
|
|
4449
4895
|
} else {
|
|
4450
4896
|
resolve(`Execution successful (${duration}ms):
|
|
4451
4897
|
Stdout: ${stdout}
|
|
@@ -4510,6 +4956,186 @@ ${formatted}`;
|
|
|
4510
4956
|
}
|
|
4511
4957
|
};
|
|
4512
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
|
+
|
|
4513
5139
|
// src/tools/mcp.ts
|
|
4514
5140
|
var McpToolWrapper = class extends BaseTool {
|
|
4515
5141
|
name;
|
|
@@ -4631,7 +5257,8 @@ var ToolRegistry = class {
|
|
|
4631
5257
|
new ImageAnalyzeTool(),
|
|
4632
5258
|
new PDFCreateTool(),
|
|
4633
5259
|
new CodeInterpreterTool(),
|
|
4634
|
-
new PeerCommunicationTool()
|
|
5260
|
+
new PeerCommunicationTool(),
|
|
5261
|
+
new WebSearchTool(this.config.webSearch)
|
|
4635
5262
|
];
|
|
4636
5263
|
for (const tool of tools) {
|
|
4637
5264
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -4655,8 +5282,23 @@ var ToolRegistry = class {
|
|
|
4655
5282
|
return this.ignoreMatcher.ignores(posixRel);
|
|
4656
5283
|
}
|
|
4657
5284
|
};
|
|
4658
|
-
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
|
+
}
|
|
4659
5300
|
clients = /* @__PURE__ */ new Map();
|
|
5301
|
+
transports = /* @__PURE__ */ new Map();
|
|
4660
5302
|
tools = /* @__PURE__ */ new Map();
|
|
4661
5303
|
trustedServers;
|
|
4662
5304
|
approvalCallback;
|
|
@@ -4685,6 +5327,8 @@ var McpClient = class {
|
|
|
4685
5327
|
);
|
|
4686
5328
|
await client.connect(transport);
|
|
4687
5329
|
this.clients.set(server.name, client);
|
|
5330
|
+
this.transports.set(server.name, transport);
|
|
5331
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
4688
5332
|
const toolsResult = await client.listTools();
|
|
4689
5333
|
for (const tool of toolsResult.tools) {
|
|
4690
5334
|
for (const existing of this.tools.values()) {
|
|
@@ -4706,8 +5350,11 @@ var McpClient = class {
|
|
|
4706
5350
|
async disconnect(serverName) {
|
|
4707
5351
|
const client = this.clients.get(serverName);
|
|
4708
5352
|
if (client) {
|
|
5353
|
+
const transport = this.transports.get(serverName);
|
|
5354
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
4709
5355
|
await client.close();
|
|
4710
5356
|
this.clients.delete(serverName);
|
|
5357
|
+
this.transports.delete(serverName);
|
|
4711
5358
|
for (const key of this.tools.keys()) {
|
|
4712
5359
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
4713
5360
|
}
|
|
@@ -4735,6 +5382,13 @@ var McpClient = class {
|
|
|
4735
5382
|
getConnectedServers() {
|
|
4736
5383
|
return Array.from(this.clients.keys());
|
|
4737
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
|
+
}
|
|
4738
5392
|
isConnected(serverName) {
|
|
4739
5393
|
return this.clients.has(serverName);
|
|
4740
5394
|
}
|
|
@@ -4873,12 +5527,24 @@ var McpServerConfigSchema = zod.z.object({
|
|
|
4873
5527
|
args: zod.z.array(zod.z.string()).optional(),
|
|
4874
5528
|
env: zod.z.record(zod.z.string()).optional()
|
|
4875
5529
|
});
|
|
5530
|
+
var WebSearchConfigSchema = zod.z.object({
|
|
5531
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
5532
|
+
searxngUrl: zod.z.string().optional(),
|
|
5533
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
5534
|
+
braveApiKey: zod.z.string().optional(),
|
|
5535
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
5536
|
+
tavilyApiKey: zod.z.string().optional(),
|
|
5537
|
+
/** Max results per search (default 5) */
|
|
5538
|
+
maxResults: zod.z.number().default(5)
|
|
5539
|
+
});
|
|
4876
5540
|
var ToolsConfigSchema = zod.z.object({
|
|
4877
5541
|
shellAllowlist: zod.z.array(zod.z.string()).default([]),
|
|
4878
5542
|
shellBlocklist: zod.z.array(zod.z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
4879
5543
|
requireApprovalFor: zod.z.array(zod.z.string()).default([]),
|
|
4880
5544
|
browserEnabled: zod.z.boolean().default(false),
|
|
4881
|
-
mcpServers: zod.z.array(McpServerConfigSchema).optional()
|
|
5545
|
+
mcpServers: zod.z.array(McpServerConfigSchema).optional(),
|
|
5546
|
+
/** Web search backends — at least one should be configured for best results */
|
|
5547
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
4882
5548
|
});
|
|
4883
5549
|
var HookDefinitionSchema = zod.z.object({
|
|
4884
5550
|
command: zod.z.string(),
|
|
@@ -5420,12 +6086,25 @@ var Cascade = class extends EventEmitter__default.default {
|
|
|
5420
6086
|
looksLikeSimpleArtifactTask(prompt) {
|
|
5421
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);
|
|
5422
6088
|
}
|
|
5423
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
6089
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
5424
6090
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
5425
6091
|
return "Simple";
|
|
5426
6092
|
}
|
|
6093
|
+
let workspaceContext = "";
|
|
6094
|
+
try {
|
|
6095
|
+
const files = await glob.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
|
+
}
|
|
5427
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.
|
|
5428
6105
|
|
|
6106
|
+
${workspaceContext}
|
|
6107
|
+
|
|
5429
6108
|
Classification:
|
|
5430
6109
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
5431
6110
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -5500,7 +6179,7 @@ ${prompt}` : prompt;
|
|
|
5500
6179
|
}
|
|
5501
6180
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
5502
6181
|
});
|
|
5503
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
6182
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
5504
6183
|
this.telemetry.capture("cascade:session_start", {
|
|
5505
6184
|
complexity,
|
|
5506
6185
|
providerCount: this.config.providers.length,
|
|
@@ -5580,7 +6259,7 @@ ${prompt}` : prompt;
|
|
|
5580
6259
|
peerT3Ids: [],
|
|
5581
6260
|
parentT2: "root"
|
|
5582
6261
|
};
|
|
5583
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
6262
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
5584
6263
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
5585
6264
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
5586
6265
|
} else if (complexity === "Moderate") {
|
|
@@ -5603,7 +6282,7 @@ ${prompt}` : prompt;
|
|
|
5603
6282
|
constraints: [],
|
|
5604
6283
|
t3Subtasks: []
|
|
5605
6284
|
};
|
|
5606
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
6285
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
5607
6286
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
5608
6287
|
t2Results = [t2Result];
|
|
5609
6288
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -5625,13 +6304,22 @@ ${prompt}` : prompt;
|
|
|
5625
6304
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
5626
6305
|
bindTierEvents(t1);
|
|
5627
6306
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
5628
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
6307
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
5629
6308
|
finalOutput = result.output;
|
|
5630
6309
|
t2Results = result.t2Results;
|
|
5631
6310
|
}
|
|
5632
6311
|
} catch (err) {
|
|
5633
|
-
|
|
5634
|
-
|
|
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
|
+
}
|
|
5635
6323
|
} finally {
|
|
5636
6324
|
try {
|
|
5637
6325
|
escalator.cancelAllPending();
|
|
@@ -5953,9 +6641,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
5953
6641
|
constructor(dbPath) {
|
|
5954
6642
|
fs11__default.default.mkdirSync(path13__default.default.dirname(dbPath), { recursive: true });
|
|
5955
6643
|
try {
|
|
5956
|
-
this.db = new Database__default.default(dbPath);
|
|
6644
|
+
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
5957
6645
|
this.db.pragma("journal_mode = WAL");
|
|
5958
6646
|
this.db.pragma("foreign_keys = ON");
|
|
6647
|
+
this.db.pragma("synchronous = NORMAL");
|
|
5959
6648
|
this.migrate();
|
|
5960
6649
|
} catch (err) {
|
|
5961
6650
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -5969,6 +6658,38 @@ Original error: ${err.message}`
|
|
|
5969
6658
|
throw err;
|
|
5970
6659
|
}
|
|
5971
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
|
+
}
|
|
5972
6693
|
// ── Sessions ──────────────────────────────────
|
|
5973
6694
|
createSession(session) {
|
|
5974
6695
|
this.db.prepare(`
|
|
@@ -6055,26 +6776,28 @@ Original error: ${err.message}`
|
|
|
6055
6776
|
}
|
|
6056
6777
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
6057
6778
|
upsertRuntimeSession(session) {
|
|
6058
|
-
this.
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
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
|
+
});
|
|
6078
6801
|
}
|
|
6079
6802
|
listRuntimeSessions(limit = 100) {
|
|
6080
6803
|
const rows = this.db.prepare(`
|
|
@@ -6092,33 +6815,35 @@ Original error: ${err.message}`
|
|
|
6092
6815
|
}));
|
|
6093
6816
|
}
|
|
6094
6817
|
upsertRuntimeNode(node) {
|
|
6095
|
-
this.
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
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
|
+
});
|
|
6122
6847
|
}
|
|
6123
6848
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
6124
6849
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -6141,30 +6866,32 @@ Original error: ${err.message}`
|
|
|
6141
6866
|
}));
|
|
6142
6867
|
}
|
|
6143
6868
|
addRuntimeNodeLog(log) {
|
|
6144
|
-
this.
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
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
|
+
});
|
|
6168
6895
|
}
|
|
6169
6896
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
6170
6897
|
let rows;
|
|
@@ -6202,19 +6929,21 @@ Original error: ${err.message}`
|
|
|
6202
6929
|
}
|
|
6203
6930
|
// ── Messages ──────────────────────────────────
|
|
6204
6931
|
addMessage(message) {
|
|
6205
|
-
this.
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
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
|
+
});
|
|
6218
6947
|
}
|
|
6219
6948
|
getSessionMessages(sessionId) {
|
|
6220
6949
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -6311,10 +7040,12 @@ Original error: ${err.message}`
|
|
|
6311
7040
|
}
|
|
6312
7041
|
// ── Audit Log ─────────────────────────────────
|
|
6313
7042
|
addAuditEntry(entry) {
|
|
6314
|
-
this.
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
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
|
+
});
|
|
6318
7049
|
}
|
|
6319
7050
|
getAuditLog(sessionId, limit = 100) {
|
|
6320
7051
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -6329,10 +7060,12 @@ Original error: ${err.message}`
|
|
|
6329
7060
|
}
|
|
6330
7061
|
// ── File Snapshots ────────────────────────────
|
|
6331
7062
|
addFileSnapshot(sessionId, filePath, content) {
|
|
6332
|
-
this.
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
7063
|
+
this.enqueueWrite(() => {
|
|
7064
|
+
this.db.prepare(`
|
|
7065
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
7066
|
+
VALUES (?, ?, ?, ?, ?)
|
|
7067
|
+
`).run(crypto.randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
7068
|
+
});
|
|
6336
7069
|
}
|
|
6337
7070
|
getLatestFileSnapshots(sessionId) {
|
|
6338
7071
|
const rows = this.db.prepare(`
|
|
@@ -6628,7 +7361,7 @@ var ConfigManager = class {
|
|
|
6628
7361
|
globalDir;
|
|
6629
7362
|
constructor(workspacePath = process.cwd()) {
|
|
6630
7363
|
this.workspacePath = workspacePath;
|
|
6631
|
-
this.globalDir = path13__default.default.join(
|
|
7364
|
+
this.globalDir = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
6632
7365
|
}
|
|
6633
7366
|
async load() {
|
|
6634
7367
|
this.config = await this.loadConfig();
|
|
@@ -6998,7 +7731,7 @@ var DashboardServer = class {
|
|
|
6998
7731
|
// ── Setup ─────────────────────────────────────
|
|
6999
7732
|
getGlobalStore() {
|
|
7000
7733
|
if (!this.globalStore) {
|
|
7001
|
-
const globalDbPath = path13__default.default.join(
|
|
7734
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7002
7735
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
7003
7736
|
}
|
|
7004
7737
|
return this.globalStore;
|
|
@@ -7060,7 +7793,7 @@ var DashboardServer = class {
|
|
|
7060
7793
|
}
|
|
7061
7794
|
watchRuntimeChanges() {
|
|
7062
7795
|
const workspaceDbPath = path13__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
7063
|
-
const globalDbPath = path13__default.default.join(
|
|
7796
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7064
7797
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
7065
7798
|
for (const watchPath of watchPaths) {
|
|
7066
7799
|
if (!fs11__default.default.existsSync(watchPath)) continue;
|
|
@@ -7168,7 +7901,7 @@ var DashboardServer = class {
|
|
|
7168
7901
|
const sessionId = req.params.id;
|
|
7169
7902
|
this.store.deleteSession(sessionId);
|
|
7170
7903
|
this.store.deleteRuntimeSession(sessionId);
|
|
7171
|
-
const globalDbPath = path13__default.default.join(
|
|
7904
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7172
7905
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7173
7906
|
try {
|
|
7174
7907
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -7182,7 +7915,7 @@ var DashboardServer = class {
|
|
|
7182
7915
|
});
|
|
7183
7916
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
7184
7917
|
const body = req.body;
|
|
7185
|
-
const globalDbPath = path13__default.default.join(
|
|
7918
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7186
7919
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
7187
7920
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7188
7921
|
try {
|
|
@@ -7205,7 +7938,7 @@ var DashboardServer = class {
|
|
|
7205
7938
|
});
|
|
7206
7939
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
7207
7940
|
this.store.deleteAllRuntimeNodes();
|
|
7208
|
-
const globalDbPath = path13__default.default.join(
|
|
7941
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7209
7942
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7210
7943
|
try {
|
|
7211
7944
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -7301,7 +8034,7 @@ var DashboardServer = class {
|
|
|
7301
8034
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
7302
8035
|
const scope = req.query["scope"] ?? "workspace";
|
|
7303
8036
|
if (scope === "global") {
|
|
7304
|
-
const globalDbPath = path13__default.default.join(
|
|
8037
|
+
const globalDbPath = path13__default.default.join(os2__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7305
8038
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7306
8039
|
try {
|
|
7307
8040
|
res.json({
|
|
@@ -7525,8 +8258,10 @@ exports.CASCADE_MD_FILE = CASCADE_MD_FILE;
|
|
|
7525
8258
|
exports.CASCADE_VERSION = CASCADE_VERSION;
|
|
7526
8259
|
exports.COMPLEXITY_T2_COUNT = COMPLEXITY_T2_COUNT;
|
|
7527
8260
|
exports.Cascade = Cascade;
|
|
8261
|
+
exports.CascadeCancelledError = CascadeCancelledError;
|
|
7528
8262
|
exports.CascadeIgnore = CascadeIgnore;
|
|
7529
8263
|
exports.CascadeRouter = CascadeRouter;
|
|
8264
|
+
exports.CascadeToolError = CascadeToolError;
|
|
7530
8265
|
exports.ConfigManager = ConfigManager;
|
|
7531
8266
|
exports.DEFAULT_API_PORT = DEFAULT_API_PORT;
|
|
7532
8267
|
exports.DEFAULT_APPROVAL_REQUIRED = DEFAULT_APPROVAL_REQUIRED;
|