cascade-ai 0.2.2 → 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 +1146 -353
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1146 -353
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1048 -325
- 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 +1047 -326
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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.11";
|
|
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,
|
|
@@ -575,33 +577,61 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
575
577
|
}
|
|
576
578
|
}
|
|
577
579
|
convertMessages(messages) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
580
|
+
const result = [];
|
|
581
|
+
for (const m of messages) {
|
|
582
|
+
if (m.role === "system") continue;
|
|
583
|
+
if (m.role === "tool") {
|
|
584
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
585
|
+
result.push({
|
|
586
|
+
role: "user",
|
|
587
|
+
content: [{
|
|
588
|
+
type: "tool_result",
|
|
589
|
+
tool_use_id: m.toolCallId ?? "",
|
|
590
|
+
content: toolContent
|
|
591
|
+
}]
|
|
592
|
+
});
|
|
593
|
+
continue;
|
|
581
594
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
595
|
+
if (m.role === "assistant") {
|
|
596
|
+
const content = [];
|
|
597
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
598
|
+
if (text) content.push({ type: "text", text });
|
|
599
|
+
for (const tc of m.toolCalls ?? []) {
|
|
600
|
+
content.push({
|
|
601
|
+
type: "tool_use",
|
|
602
|
+
id: tc.id,
|
|
603
|
+
name: tc.name,
|
|
604
|
+
input: tc.input
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (content.length > 0) {
|
|
608
|
+
result.push({ role: "assistant", content });
|
|
609
|
+
}
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (m.role === "user") {
|
|
613
|
+
if (typeof m.content === "string") {
|
|
614
|
+
result.push({ role: "user", content: m.content });
|
|
615
|
+
} else {
|
|
616
|
+
const content = m.content.map((block) => {
|
|
617
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
618
|
+
if (block.type === "image") {
|
|
619
|
+
const img = block.image;
|
|
620
|
+
if (img.type === "base64") {
|
|
621
|
+
return {
|
|
622
|
+
type: "image",
|
|
623
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
624
|
+
};
|
|
593
625
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
};
|
|
626
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
627
|
+
}
|
|
628
|
+
return { type: "text", text: "" };
|
|
629
|
+
});
|
|
630
|
+
result.push({ role: "user", content });
|
|
600
631
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return result;
|
|
605
635
|
}
|
|
606
636
|
};
|
|
607
637
|
var OpenAIProvider = class extends BaseProvider {
|
|
@@ -862,7 +892,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
862
892
|
for (const part of candidate?.content?.parts ?? []) {
|
|
863
893
|
if (part.functionCall) {
|
|
864
894
|
toolCalls.push({
|
|
865
|
-
id:
|
|
895
|
+
id: part.functionCall.name,
|
|
866
896
|
name: part.functionCall.name,
|
|
867
897
|
input: part.functionCall.args ?? {}
|
|
868
898
|
});
|
|
@@ -950,10 +980,70 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
950
980
|
}
|
|
951
981
|
// ── Private ──────────────────────────────────
|
|
952
982
|
buildContents(messages, extraImages) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
983
|
+
const contents = [];
|
|
984
|
+
for (const m of messages) {
|
|
985
|
+
if (m.role === "system") {
|
|
986
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
987
|
+
if (!text.trim()) continue;
|
|
988
|
+
const prev = contents[contents.length - 1];
|
|
989
|
+
if (prev?.role === "user") {
|
|
990
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
991
|
+
|
|
992
|
+
` });
|
|
993
|
+
} else {
|
|
994
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
995
|
+
}
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (m.role === "tool") {
|
|
999
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1000
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1001
|
+
contents.push({
|
|
1002
|
+
role: "user",
|
|
1003
|
+
parts: [{
|
|
1004
|
+
functionResponse: {
|
|
1005
|
+
name: functionName,
|
|
1006
|
+
response: { output: toolContent }
|
|
1007
|
+
}
|
|
1008
|
+
}]
|
|
1009
|
+
});
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (m.role === "assistant") {
|
|
1013
|
+
const parts = [];
|
|
1014
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1015
|
+
if (textContent) parts.push({ text: textContent });
|
|
1016
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1017
|
+
parts.push({
|
|
1018
|
+
functionCall: {
|
|
1019
|
+
name: tc.name,
|
|
1020
|
+
args: tc.input
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
if (parts.length > 0) {
|
|
1025
|
+
contents.push({ role: "model", parts });
|
|
1026
|
+
}
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
if (m.role === "user") {
|
|
1030
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1031
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1032
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1033
|
+
if (isLastUser) {
|
|
1034
|
+
for (const img of extraImages) {
|
|
1035
|
+
if (img.type === "base64") {
|
|
1036
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (parts.length > 0) {
|
|
1042
|
+
contents.push({ role: "user", parts });
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return contents;
|
|
957
1047
|
}
|
|
958
1048
|
convertMessageContent(msg, extraImages) {
|
|
959
1049
|
const parts = [];
|
|
@@ -1820,6 +1910,29 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
1820
1910
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
1821
1911
|
}
|
|
1822
1912
|
};
|
|
1913
|
+
|
|
1914
|
+
// src/utils/retry.ts
|
|
1915
|
+
var CascadeCancelledError = class extends Error {
|
|
1916
|
+
constructor(reason) {
|
|
1917
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
1918
|
+
this.name = "CascadeCancelledError";
|
|
1919
|
+
}
|
|
1920
|
+
};
|
|
1921
|
+
var CascadeToolError = class extends Error {
|
|
1922
|
+
/** A friendly message to show the user / T3 */
|
|
1923
|
+
userMessage;
|
|
1924
|
+
/** Whether this error class is retryable by default */
|
|
1925
|
+
retryable;
|
|
1926
|
+
constructor(userMessage, cause, retryable = false) {
|
|
1927
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause);
|
|
1928
|
+
super(`${userMessage}: ${causeMsg}`);
|
|
1929
|
+
this.name = "CascadeToolError";
|
|
1930
|
+
this.userMessage = userMessage;
|
|
1931
|
+
this.retryable = retryable;
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
|
|
1935
|
+
// src/core/tiers/base.ts
|
|
1823
1936
|
var BaseTier = class extends EventEmitter {
|
|
1824
1937
|
id;
|
|
1825
1938
|
role;
|
|
@@ -1829,6 +1942,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
1829
1942
|
label;
|
|
1830
1943
|
systemPromptOverride = "";
|
|
1831
1944
|
hierarchyContext = "";
|
|
1945
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
1946
|
+
signal;
|
|
1832
1947
|
constructor(role, id, parentId) {
|
|
1833
1948
|
super();
|
|
1834
1949
|
this.role = role;
|
|
@@ -1891,6 +2006,18 @@ var BaseTier = class extends EventEmitter {
|
|
|
1891
2006
|
log(message, data) {
|
|
1892
2007
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1893
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
2011
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
2012
|
+
* to provide a fast, clean cancellation path.
|
|
2013
|
+
*/
|
|
2014
|
+
throwIfCancelled() {
|
|
2015
|
+
if (this.signal?.aborted) {
|
|
2016
|
+
throw new CascadeCancelledError(
|
|
2017
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
1894
2021
|
};
|
|
1895
2022
|
|
|
1896
2023
|
// src/core/context/manager.ts
|
|
@@ -2091,6 +2218,7 @@ Rules:
|
|
|
2091
2218
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
2092
2219
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
2093
2220
|
- 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.
|
|
2221
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
2094
2222
|
- Use the "pdf_create" tool for PDF requests.
|
|
2095
2223
|
- 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
2224
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -2134,7 +2262,8 @@ var T3Worker = class extends BaseTier {
|
|
|
2134
2262
|
this.store = store;
|
|
2135
2263
|
this.audit = new AuditLogger(store, sessionId);
|
|
2136
2264
|
}
|
|
2137
|
-
async execute(assignment, taskId) {
|
|
2265
|
+
async execute(assignment, taskId, signal) {
|
|
2266
|
+
this.signal = signal;
|
|
2138
2267
|
this.assignment = assignment;
|
|
2139
2268
|
this.taskId = taskId;
|
|
2140
2269
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -2284,6 +2413,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
2284
2413
|
tools = [...tools];
|
|
2285
2414
|
while (iterations < MAX_ITERATIONS) {
|
|
2286
2415
|
iterations++;
|
|
2416
|
+
this.throwIfCancelled();
|
|
2287
2417
|
const options = {
|
|
2288
2418
|
messages: this.context.getMessages(),
|
|
2289
2419
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -2304,21 +2434,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2304
2434
|
if (requiresArtifact) {
|
|
2305
2435
|
stalledArtifactIterations += 1;
|
|
2306
2436
|
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
|
-
}
|
|
2437
|
+
if (stalledArtifactIterations === 2) {
|
|
2438
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
2322
2439
|
}
|
|
2323
2440
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
2324
2441
|
}
|
|
@@ -2478,6 +2595,9 @@ ${assignment.expectedOutput}`;
|
|
|
2478
2595
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
2479
2596
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
2480
2597
|
const issues = [];
|
|
2598
|
+
const { exec: exec3 } = await import('child_process');
|
|
2599
|
+
const { promisify: promisify3 } = await import('util');
|
|
2600
|
+
const execAsync2 = promisify3(exec3);
|
|
2481
2601
|
for (const artifactPath of artifactPaths) {
|
|
2482
2602
|
const absolutePath = path13.resolve(process.cwd(), artifactPath);
|
|
2483
2603
|
try {
|
|
@@ -2494,9 +2614,27 @@ ${assignment.expectedOutput}`;
|
|
|
2494
2614
|
const content = await fs2.readFile(absolutePath, "utf-8");
|
|
2495
2615
|
if (!content.trim()) {
|
|
2496
2616
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
2617
|
+
continue;
|
|
2497
2618
|
}
|
|
2498
2619
|
} else if (stat.size < 100) {
|
|
2499
2620
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
const ext = path13.extname(absolutePath).toLowerCase();
|
|
2624
|
+
try {
|
|
2625
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
2626
|
+
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
2627
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
2628
|
+
await execAsync2(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
2629
|
+
} else if (ext === ".py") {
|
|
2630
|
+
await execAsync2(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
2631
|
+
}
|
|
2632
|
+
} catch (err) {
|
|
2633
|
+
const stderr = err?.stderr || String(err);
|
|
2634
|
+
const stdout = err?.stdout || "";
|
|
2635
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
2636
|
+
${stderr}
|
|
2637
|
+
${stdout}`);
|
|
2500
2638
|
}
|
|
2501
2639
|
} catch {
|
|
2502
2640
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -2893,7 +3031,8 @@ var T2Manager = class extends BaseTier {
|
|
|
2893
3031
|
});
|
|
2894
3032
|
this.emit("peer-sync-received", { fromId, content });
|
|
2895
3033
|
}
|
|
2896
|
-
async execute(assignment, taskId) {
|
|
3034
|
+
async execute(assignment, taskId, signal) {
|
|
3035
|
+
this.signal = signal;
|
|
2897
3036
|
this.assignment = assignment;
|
|
2898
3037
|
this.taskId = taskId;
|
|
2899
3038
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -2905,12 +3044,14 @@ var T2Manager = class extends BaseTier {
|
|
|
2905
3044
|
});
|
|
2906
3045
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
2907
3046
|
try {
|
|
3047
|
+
this.throwIfCancelled();
|
|
2908
3048
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
2909
3049
|
this.sendStatusUpdate({
|
|
2910
3050
|
progressPct: 20,
|
|
2911
3051
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
2912
3052
|
status: "IN_PROGRESS"
|
|
2913
3053
|
});
|
|
3054
|
+
this.throwIfCancelled();
|
|
2914
3055
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
2915
3056
|
this.sendStatusUpdate({
|
|
2916
3057
|
progressPct: 90,
|
|
@@ -3077,11 +3218,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3077
3218
|
).join(", ")}`,
|
|
3078
3219
|
status: "IN_PROGRESS"
|
|
3079
3220
|
});
|
|
3221
|
+
this.throwIfCancelled();
|
|
3080
3222
|
const waveResults = await Promise.allSettled(
|
|
3081
3223
|
runnableIds.map(async (id) => {
|
|
3082
3224
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3083
3225
|
const worker = workerMap.get(id);
|
|
3084
|
-
const result = await worker.execute(assignment, taskId);
|
|
3226
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
3085
3227
|
resultMap.set(id, result);
|
|
3086
3228
|
return result;
|
|
3087
3229
|
})
|
|
@@ -3095,6 +3237,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3095
3237
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3096
3238
|
const retried = await this.retryT3(assignment, taskId);
|
|
3097
3239
|
resultMap.set(id, retried);
|
|
3240
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
3241
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3242
|
+
if (this.toolCreator) {
|
|
3243
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
3244
|
+
this.sendStatusUpdate({
|
|
3245
|
+
progressPct: 50,
|
|
3246
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
3247
|
+
status: "IN_PROGRESS"
|
|
3248
|
+
});
|
|
3249
|
+
const toolName = await this.toolCreator.createTool(
|
|
3250
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
3251
|
+
assignment.description
|
|
3252
|
+
);
|
|
3253
|
+
if (toolName) {
|
|
3254
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
3255
|
+
this.sendStatusUpdate({
|
|
3256
|
+
progressPct: 60,
|
|
3257
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
3258
|
+
status: "IN_PROGRESS"
|
|
3259
|
+
});
|
|
3260
|
+
try {
|
|
3261
|
+
const verifyResult = await this.router.generate("T2", {
|
|
3262
|
+
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".` }],
|
|
3263
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
3264
|
+
maxTokens: 50
|
|
3265
|
+
});
|
|
3266
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
3267
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
3268
|
+
const retried = await this.retryT3({
|
|
3269
|
+
...assignment,
|
|
3270
|
+
description: `${assignment.description}
|
|
3271
|
+
|
|
3272
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
3273
|
+
}, taskId);
|
|
3274
|
+
resultMap.set(id, retried);
|
|
3275
|
+
} else {
|
|
3276
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
3277
|
+
resultMap.set(id, r.value);
|
|
3278
|
+
}
|
|
3279
|
+
} catch {
|
|
3280
|
+
const retried = await this.retryT3({
|
|
3281
|
+
...assignment,
|
|
3282
|
+
description: `${assignment.description}
|
|
3283
|
+
|
|
3284
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
3285
|
+
}, taskId);
|
|
3286
|
+
resultMap.set(id, retried);
|
|
3287
|
+
}
|
|
3288
|
+
} else {
|
|
3289
|
+
resultMap.set(id, r.value);
|
|
3290
|
+
}
|
|
3291
|
+
} else {
|
|
3292
|
+
resultMap.set(id, r.value);
|
|
3293
|
+
}
|
|
3098
3294
|
}
|
|
3099
3295
|
for (const dependent of adj.get(id) ?? []) {
|
|
3100
3296
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -3158,7 +3354,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3158
3354
|
}));
|
|
3159
3355
|
return worker.execute(
|
|
3160
3356
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
3161
|
-
taskId
|
|
3357
|
+
taskId,
|
|
3358
|
+
this.signal
|
|
3162
3359
|
);
|
|
3163
3360
|
}
|
|
3164
3361
|
publishSectionOutput(result) {
|
|
@@ -3172,29 +3369,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3172
3369
|
async aggregateResults(assignment, results) {
|
|
3173
3370
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
3174
3371
|
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
3372
|
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 ? `
|
|
3373
|
+
const peerContext = peerOutputs ? `
|
|
3181
3374
|
|
|
3182
3375
|
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
|
-
|
|
3376
|
+
${peerOutputs}` : "";
|
|
3377
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
3378
|
+
let currentSummary = "";
|
|
3379
|
+
let i = 0;
|
|
3380
|
+
while (i < completed.length) {
|
|
3381
|
+
let chunkText = "";
|
|
3382
|
+
let chunkEnd = i;
|
|
3383
|
+
while (chunkEnd < completed.length) {
|
|
3384
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
3385
|
+
|
|
3386
|
+
`;
|
|
3387
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
3388
|
+
break;
|
|
3389
|
+
}
|
|
3390
|
+
chunkText += nextOutput;
|
|
3391
|
+
chunkEnd++;
|
|
3392
|
+
}
|
|
3393
|
+
i = chunkEnd;
|
|
3394
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
3395
|
+
${currentSummary ? `
|
|
3396
|
+
PREVIOUS SUMMARY SO FAR:
|
|
3397
|
+
${currentSummary}
|
|
3398
|
+
|
|
3399
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
3400
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
3401
|
+
const messages = [{ role: "user", content: prompt }];
|
|
3402
|
+
try {
|
|
3403
|
+
const result = await this.router.generate("T2", {
|
|
3404
|
+
messages,
|
|
3405
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
3189
3406
|
|
|
3190
3407
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3408
|
+
maxTokens: 500
|
|
3409
|
+
});
|
|
3410
|
+
currentSummary = result.content;
|
|
3411
|
+
} catch (err) {
|
|
3412
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3413
|
+
return currentSummary + "\n\n" + chunkText;
|
|
3414
|
+
}
|
|
3197
3415
|
}
|
|
3416
|
+
return currentSummary;
|
|
3198
3417
|
}
|
|
3199
3418
|
determineStatus(results) {
|
|
3200
3419
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -3319,10 +3538,10 @@ Rules:
|
|
|
3319
3538
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
3320
3539
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
3321
3540
|
|
|
3322
|
-
|
|
3323
|
-
-
|
|
3324
|
-
-
|
|
3325
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
3541
|
+
DEPENDENCY GUIDANCE:
|
|
3542
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
3543
|
+
- 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).
|
|
3544
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
3326
3545
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
3327
3546
|
|
|
3328
3547
|
QUALITY RULES:
|
|
@@ -3361,7 +3580,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
3361
3580
|
setToolCreator(creator) {
|
|
3362
3581
|
this.toolCreator = creator;
|
|
3363
3582
|
}
|
|
3364
|
-
async execute(userPrompt, images, systemContext) {
|
|
3583
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
3584
|
+
this.signal = signal;
|
|
3365
3585
|
this.taskId = randomUUID();
|
|
3366
3586
|
this.setLabel("Administrator");
|
|
3367
3587
|
this.setStatus("ACTIVE");
|
|
@@ -3372,10 +3592,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
3372
3592
|
status: "IN_PROGRESS"
|
|
3373
3593
|
});
|
|
3374
3594
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
3595
|
+
this.throwIfCancelled();
|
|
3375
3596
|
let enrichedPrompt = userPrompt;
|
|
3376
3597
|
if (images?.length) {
|
|
3377
3598
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
3378
3599
|
}
|
|
3600
|
+
this.throwIfCancelled();
|
|
3379
3601
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
3380
3602
|
this.sendStatusUpdate({
|
|
3381
3603
|
progressPct: 10,
|
|
@@ -3383,21 +3605,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
3383
3605
|
status: "IN_PROGRESS"
|
|
3384
3606
|
});
|
|
3385
3607
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
3386
|
-
|
|
3608
|
+
this.throwIfCancelled();
|
|
3609
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
3610
|
+
let pass = 1;
|
|
3611
|
+
const MAX_REPLAN_PASSES = 2;
|
|
3612
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
3613
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
3614
|
+
if (reviewResult.approved) {
|
|
3615
|
+
this.log("T1 Review passed.");
|
|
3616
|
+
break;
|
|
3617
|
+
}
|
|
3618
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
3619
|
+
this.sendStatusUpdate({
|
|
3620
|
+
progressPct: 80 + pass * 5,
|
|
3621
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
3622
|
+
status: "IN_PROGRESS"
|
|
3623
|
+
});
|
|
3624
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
3625
|
+
Review reason: ${reviewResult.reason}
|
|
3626
|
+
|
|
3627
|
+
Original goal: ${enrichedPrompt}
|
|
3628
|
+
|
|
3629
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
3630
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
3631
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
3632
|
+
pass++;
|
|
3633
|
+
}
|
|
3387
3634
|
this.sendStatusUpdate({
|
|
3388
3635
|
progressPct: 95,
|
|
3389
3636
|
currentAction: "Compiling final output",
|
|
3390
3637
|
status: "IN_PROGRESS"
|
|
3391
3638
|
});
|
|
3392
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
3639
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
3393
3640
|
this.setStatus("COMPLETED");
|
|
3394
3641
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
3395
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3642
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3396
3643
|
}
|
|
3397
3644
|
getEscalations() {
|
|
3398
3645
|
return [...this.escalations];
|
|
3399
3646
|
}
|
|
3400
3647
|
// ── Private ──────────────────────────────────
|
|
3648
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
3649
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
3650
|
+
if (failedSections.length > 0) {
|
|
3651
|
+
return {
|
|
3652
|
+
approved: false,
|
|
3653
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
3657
|
+
${r.sectionSummary}`).join("\n\n");
|
|
3658
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
3659
|
+
Review the following execution outputs against the original user prompt.
|
|
3660
|
+
|
|
3661
|
+
Original Request: ${originalPrompt}
|
|
3662
|
+
|
|
3663
|
+
T2 Manager Summaries:
|
|
3664
|
+
${sectionsText}
|
|
3665
|
+
|
|
3666
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
3667
|
+
If yes, reply with exactly: "APPROVED".
|
|
3668
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
3669
|
+
try {
|
|
3670
|
+
const result = await this.router.generate("T1", {
|
|
3671
|
+
messages: [{ role: "user", content: prompt }],
|
|
3672
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
3673
|
+
maxTokens: 500,
|
|
3674
|
+
temperature: 0
|
|
3675
|
+
});
|
|
3676
|
+
const response = result.content.trim();
|
|
3677
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
3678
|
+
return { approved: true };
|
|
3679
|
+
}
|
|
3680
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
3681
|
+
} catch {
|
|
3682
|
+
return { approved: true };
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3401
3685
|
async analyzeImages(prompt, images) {
|
|
3402
3686
|
const visionModel = this.router.getModelForTier("T1");
|
|
3403
3687
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -3426,29 +3710,35 @@ ${systemContext}` : "";
|
|
|
3426
3710
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
3427
3711
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
3428
3712
|
|
|
3429
|
-
Return JSON where
|
|
3713
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
3430
3714
|
{
|
|
3431
3715
|
"sections": [{
|
|
3716
|
+
"sectionId": "s1",
|
|
3717
|
+
"sectionTitle": "Setup Project",
|
|
3718
|
+
"description": "Initialize the project",
|
|
3719
|
+
"expectedOutput": "Basic structure created",
|
|
3720
|
+
"constraints": [],
|
|
3721
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
3432
3722
|
"t3Subtasks": [{
|
|
3433
3723
|
"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"
|
|
3724
|
+
"subtaskTitle": "Init NPM",
|
|
3725
|
+
"description": "Run npm init",
|
|
3726
|
+
"expectedOutput": "package.json created",
|
|
3727
|
+
"constraints": [],
|
|
3728
|
+
"dependsOn": []
|
|
3447
3729
|
}]
|
|
3730
|
+
}, {
|
|
3731
|
+
"sectionId": "s2",
|
|
3732
|
+
"sectionTitle": "Write Tests",
|
|
3733
|
+
"description": "Write tests for the project",
|
|
3734
|
+
"expectedOutput": "Tests passing",
|
|
3735
|
+
"constraints": [],
|
|
3736
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
3737
|
+
"t3Subtasks": [...]
|
|
3448
3738
|
}]
|
|
3449
3739
|
}
|
|
3450
|
-
Use dependsOn when a
|
|
3451
|
-
Leave dependsOn empty for
|
|
3740
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
3741
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
3452
3742
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
3453
3743
|
const result = await this.router.generate("T1", {
|
|
3454
3744
|
messages,
|
|
@@ -3576,92 +3866,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
3576
3866
|
].filter(Boolean).join(" ");
|
|
3577
3867
|
m.setHierarchyContext(context);
|
|
3578
3868
|
});
|
|
3579
|
-
if (overlapSections.size > 0
|
|
3580
|
-
this.log("Overlap detected \u2014
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3869
|
+
if (overlapSections.size > 0) {
|
|
3870
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
3871
|
+
const overlapArray = Array.from(overlapSections);
|
|
3872
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
3873
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
3874
|
+
if (section) {
|
|
3875
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
3584
3876
|
}
|
|
3585
3877
|
}
|
|
3586
3878
|
}
|
|
3587
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
3588
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
3589
3879
|
const t2Results = [];
|
|
3590
3880
|
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
|
-
|
|
3881
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
3882
|
+
} finally {
|
|
3883
|
+
cleanup();
|
|
3884
|
+
}
|
|
3885
|
+
return t2Results;
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
3889
|
+
*/
|
|
3890
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
3891
|
+
const adj = /* @__PURE__ */ new Map();
|
|
3892
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
3893
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
3894
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
3895
|
+
for (const s of sections) {
|
|
3896
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
3897
|
+
inDegree.set(s.sectionId, 0);
|
|
3898
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
3899
|
+
}
|
|
3900
|
+
for (const s of sections) {
|
|
3901
|
+
for (const dep of s.dependsOn ?? []) {
|
|
3902
|
+
adj.get(dep).add(s.sectionId);
|
|
3903
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
const queue = [];
|
|
3907
|
+
const degree = new Map(inDegree);
|
|
3908
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
3909
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3910
|
+
while (queue.length > 0) {
|
|
3911
|
+
const u = queue.shift();
|
|
3912
|
+
visited.add(u);
|
|
3913
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
3914
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
3915
|
+
degree.set(v, newDeg);
|
|
3916
|
+
if (newDeg === 0) queue.push(v);
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
3920
|
+
if (cycleNodes.length > 0) {
|
|
3921
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
3922
|
+
for (const s of sections) {
|
|
3923
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
3924
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
3925
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
3926
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
3927
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
3622
3928
|
}
|
|
3929
|
+
s.dependsOn = safeDeps;
|
|
3623
3930
|
}
|
|
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
|
-
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
const totalSections = sections.length;
|
|
3934
|
+
let completedSections = 0;
|
|
3935
|
+
const executeWave = async () => {
|
|
3936
|
+
const readyIds = [];
|
|
3937
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
3938
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
3939
|
+
readyIds.push(id);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
if (readyIds.length === 0) return;
|
|
3943
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
3944
|
+
resultMap.set(id, null);
|
|
3945
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
3946
|
+
const section = sections[index];
|
|
3947
|
+
const manager = managers[index];
|
|
3948
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
3949
|
+
this.sendStatusUpdate({
|
|
3950
|
+
progressPct,
|
|
3951
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
3952
|
+
status: "IN_PROGRESS"
|
|
3953
|
+
});
|
|
3954
|
+
this.throwIfCancelled();
|
|
3955
|
+
let result;
|
|
3956
|
+
try {
|
|
3957
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
3958
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
3959
|
+
if (result.status === "ESCALATED") {
|
|
3960
|
+
this.escalations.push({
|
|
3961
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
3962
|
+
sectionId: section.sectionId,
|
|
3963
|
+
attempted: result.issues,
|
|
3964
|
+
blocker: result.issues.join("; "),
|
|
3965
|
+
needs: "Human review required"
|
|
3657
3966
|
});
|
|
3658
3967
|
}
|
|
3968
|
+
} catch (err) {
|
|
3969
|
+
result = {
|
|
3970
|
+
sectionId: section.sectionId,
|
|
3971
|
+
sectionTitle: section.sectionTitle,
|
|
3972
|
+
status: "FAILED",
|
|
3973
|
+
t3Results: [],
|
|
3974
|
+
sectionSummary: "",
|
|
3975
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
resultMap.set(id, result);
|
|
3979
|
+
completedSections++;
|
|
3980
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
3981
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
3659
3982
|
}
|
|
3983
|
+
}));
|
|
3984
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
3985
|
+
await executeWave();
|
|
3660
3986
|
}
|
|
3661
|
-
}
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
return t2Results;
|
|
3987
|
+
};
|
|
3988
|
+
await executeWave();
|
|
3989
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
3665
3990
|
}
|
|
3666
3991
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
3667
3992
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -4108,13 +4433,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
4108
4433
|
}
|
|
4109
4434
|
async execute(input, _options) {
|
|
4110
4435
|
const platform = input["platform"] ?? "github";
|
|
4111
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
4112
4436
|
const operation = input["operation"];
|
|
4113
4437
|
const repo = input["repo"];
|
|
4114
|
-
|
|
4115
|
-
|
|
4438
|
+
let token = input["token"];
|
|
4439
|
+
if (!token) {
|
|
4440
|
+
if (platform === "github") {
|
|
4441
|
+
token = process.env["GITHUB_TOKEN"];
|
|
4442
|
+
} else {
|
|
4443
|
+
token = process.env["GITLAB_TOKEN"];
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
if (!token) {
|
|
4447
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
4448
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
4449
|
+
}
|
|
4450
|
+
try {
|
|
4451
|
+
if (platform === "github") {
|
|
4452
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
4453
|
+
}
|
|
4454
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
4455
|
+
} catch (err) {
|
|
4456
|
+
const axiosErr = err;
|
|
4457
|
+
if (axiosErr?.response?.status) {
|
|
4458
|
+
const status = axiosErr.response.status;
|
|
4459
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
4460
|
+
switch (status) {
|
|
4461
|
+
case 401:
|
|
4462
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
4463
|
+
case 403:
|
|
4464
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
4465
|
+
case 404:
|
|
4466
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
4467
|
+
case 422:
|
|
4468
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
4469
|
+
case 429:
|
|
4470
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
4471
|
+
default:
|
|
4472
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
4116
4476
|
}
|
|
4117
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
4118
4477
|
}
|
|
4119
4478
|
async executeGitHub(operation, repo, token, input) {
|
|
4120
4479
|
const headers = {
|
|
@@ -4201,6 +4560,7 @@ ${response.data.description}`;
|
|
|
4201
4560
|
};
|
|
4202
4561
|
|
|
4203
4562
|
// src/tools/browser.ts
|
|
4563
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
4204
4564
|
var BrowserTool = class extends BaseTool {
|
|
4205
4565
|
name = "browser";
|
|
4206
4566
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -4209,7 +4569,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
4209
4569
|
properties: {
|
|
4210
4570
|
action: {
|
|
4211
4571
|
type: "string",
|
|
4212
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
4572
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
4213
4573
|
},
|
|
4214
4574
|
url: { type: "string", description: "URL to navigate to" },
|
|
4215
4575
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -4229,53 +4589,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
4229
4589
|
try {
|
|
4230
4590
|
playwright = await import('playwright');
|
|
4231
4591
|
} 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();
|
|
4592
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
4239
4593
|
}
|
|
4240
|
-
const page = this.page;
|
|
4241
4594
|
const action = input["action"];
|
|
4242
4595
|
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);
|
|
4596
|
+
if (action === "close") {
|
|
4597
|
+
await this.close();
|
|
4598
|
+
return "Browser closed.";
|
|
4599
|
+
}
|
|
4600
|
+
if (!this.browser || !this.page) {
|
|
4601
|
+
await this.close();
|
|
4602
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
4603
|
+
const timeoutPromise = new Promise(
|
|
4604
|
+
(_, 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)
|
|
4605
|
+
);
|
|
4606
|
+
try {
|
|
4607
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
4608
|
+
this.page = await this.browser.newPage();
|
|
4609
|
+
} catch (err) {
|
|
4610
|
+
this.browser = null;
|
|
4611
|
+
this.page = null;
|
|
4612
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
4263
4613
|
}
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4614
|
+
}
|
|
4615
|
+
const page = this.page;
|
|
4616
|
+
try {
|
|
4617
|
+
switch (action) {
|
|
4618
|
+
case "navigate": {
|
|
4619
|
+
await page.goto(input["url"], { timeout });
|
|
4620
|
+
const title = await page.title();
|
|
4621
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
4622
|
+
}
|
|
4623
|
+
case "click": {
|
|
4624
|
+
await page.click(input["selector"], { timeout });
|
|
4625
|
+
return `Clicked ${input["selector"]}`;
|
|
4626
|
+
}
|
|
4627
|
+
case "fill": {
|
|
4628
|
+
await page.fill(input["selector"], input["value"]);
|
|
4629
|
+
return `Filled ${input["selector"]} with value`;
|
|
4630
|
+
}
|
|
4631
|
+
case "screenshot": {
|
|
4632
|
+
const buf = await page.screenshot({ type: "png" });
|
|
4633
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
4634
|
+
}
|
|
4635
|
+
case "evaluate": {
|
|
4636
|
+
const result = await page.evaluate(input["script"]);
|
|
4637
|
+
return JSON.stringify(result);
|
|
4638
|
+
}
|
|
4639
|
+
case "extract_text": {
|
|
4640
|
+
const text = await page.locator("body").innerText();
|
|
4641
|
+
return text.slice(0, 1e4);
|
|
4642
|
+
}
|
|
4643
|
+
case "wait": {
|
|
4644
|
+
await page.waitForTimeout(timeout);
|
|
4645
|
+
return `Waited ${timeout}ms`;
|
|
4646
|
+
}
|
|
4647
|
+
default:
|
|
4648
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
4267
4649
|
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4650
|
+
} catch (err) {
|
|
4651
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4652
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
4653
|
+
await this.close();
|
|
4654
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
4271
4655
|
}
|
|
4272
|
-
|
|
4273
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
4656
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
4274
4657
|
}
|
|
4275
4658
|
}
|
|
4276
4659
|
async close() {
|
|
4277
|
-
|
|
4278
|
-
|
|
4660
|
+
try {
|
|
4661
|
+
if (this.page) {
|
|
4662
|
+
await this.page.close().catch(() => {
|
|
4663
|
+
});
|
|
4664
|
+
this.page = null;
|
|
4665
|
+
}
|
|
4666
|
+
if (this.browser) {
|
|
4667
|
+
await this.browser.close().catch(() => {
|
|
4668
|
+
});
|
|
4669
|
+
this.browser = null;
|
|
4670
|
+
}
|
|
4671
|
+
} catch {
|
|
4279
4672
|
this.browser = null;
|
|
4280
4673
|
this.page = null;
|
|
4281
4674
|
}
|
|
@@ -4372,6 +4765,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
4372
4765
|
});
|
|
4373
4766
|
}
|
|
4374
4767
|
};
|
|
4768
|
+
function detectCommand(candidates) {
|
|
4769
|
+
for (const cmd of candidates) {
|
|
4770
|
+
try {
|
|
4771
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
4772
|
+
execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
4773
|
+
return cmd;
|
|
4774
|
+
} catch {
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
return null;
|
|
4778
|
+
}
|
|
4779
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
4780
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
4375
4781
|
var CodeInterpreterTool = class extends BaseTool {
|
|
4376
4782
|
name = "run_code";
|
|
4377
4783
|
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 +4793,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4387
4793
|
isDangerous() {
|
|
4388
4794
|
return true;
|
|
4389
4795
|
}
|
|
4390
|
-
async execute(input,
|
|
4796
|
+
async execute(input, _options) {
|
|
4391
4797
|
const language = input["language"];
|
|
4392
4798
|
const code = input["code"];
|
|
4393
4799
|
const args = input["args"] ?? [];
|
|
4800
|
+
let cmdPrefix;
|
|
4801
|
+
if (language === "python") {
|
|
4802
|
+
if (!PYTHON_CMD) {
|
|
4803
|
+
return [
|
|
4804
|
+
"Error: Python interpreter not found.",
|
|
4805
|
+
"Please install Python and ensure it is in your PATH.",
|
|
4806
|
+
"Tried: python3, python"
|
|
4807
|
+
].join("\n");
|
|
4808
|
+
}
|
|
4809
|
+
cmdPrefix = PYTHON_CMD;
|
|
4810
|
+
} else {
|
|
4811
|
+
if (!NODE_CMD) {
|
|
4812
|
+
return [
|
|
4813
|
+
"Error: Node.js interpreter not found.",
|
|
4814
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
4815
|
+
"Tried: node"
|
|
4816
|
+
].join("\n");
|
|
4817
|
+
}
|
|
4818
|
+
cmdPrefix = NODE_CMD;
|
|
4819
|
+
}
|
|
4394
4820
|
const tmpDir = path13.join(process.cwd(), ".cascade", "tmp");
|
|
4395
4821
|
if (!fs11.existsSync(tmpDir)) {
|
|
4396
4822
|
fs11.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -4399,8 +4825,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4399
4825
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
4400
4826
|
const filePath = path13.join(tmpDir, fileName);
|
|
4401
4827
|
fs11.writeFileSync(filePath, code, "utf-8");
|
|
4402
|
-
const
|
|
4403
|
-
const
|
|
4828
|
+
const quotedPath = `"${filePath}"`;
|
|
4829
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
4830
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
4404
4831
|
return new Promise((resolve) => {
|
|
4405
4832
|
const startMs = Date.now();
|
|
4406
4833
|
exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -4413,10 +4840,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4413
4840
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
4414
4841
|
}
|
|
4415
4842
|
if (error) {
|
|
4416
|
-
|
|
4843
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
4844
|
+
if (timedOut) {
|
|
4845
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
4846
|
+
Partial stdout: ${stdout}
|
|
4847
|
+
Stderr: ${stderr}`);
|
|
4848
|
+
} else {
|
|
4849
|
+
resolve(`Execution failed (${duration}ms):
|
|
4417
4850
|
Error: ${error.message}
|
|
4418
4851
|
Stderr: ${stderr}
|
|
4419
4852
|
Stdout: ${stdout}`);
|
|
4853
|
+
}
|
|
4420
4854
|
} else {
|
|
4421
4855
|
resolve(`Execution successful (${duration}ms):
|
|
4422
4856
|
Stdout: ${stdout}
|
|
@@ -4481,6 +4915,186 @@ ${formatted}`;
|
|
|
4481
4915
|
}
|
|
4482
4916
|
};
|
|
4483
4917
|
|
|
4918
|
+
// src/tools/web-search.ts
|
|
4919
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
4920
|
+
const url = new URL("/search", baseUrl);
|
|
4921
|
+
url.searchParams.set("q", query);
|
|
4922
|
+
url.searchParams.set("format", "json");
|
|
4923
|
+
url.searchParams.set("categories", "general");
|
|
4924
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
4925
|
+
const resp = await fetch(url.toString(), {
|
|
4926
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
4927
|
+
signal: AbortSignal.timeout(1e4)
|
|
4928
|
+
});
|
|
4929
|
+
if (!resp.ok) {
|
|
4930
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
4931
|
+
}
|
|
4932
|
+
const data = await resp.json();
|
|
4933
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
4934
|
+
title: r.title ?? "",
|
|
4935
|
+
url: r.url ?? "",
|
|
4936
|
+
snippet: r.content ?? "",
|
|
4937
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
4938
|
+
}));
|
|
4939
|
+
}
|
|
4940
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
4941
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
4942
|
+
const resp = await fetch(url, {
|
|
4943
|
+
headers: {
|
|
4944
|
+
"Accept": "application/json",
|
|
4945
|
+
"Accept-Encoding": "gzip",
|
|
4946
|
+
"X-Subscription-Token": apiKey
|
|
4947
|
+
},
|
|
4948
|
+
signal: AbortSignal.timeout(1e4)
|
|
4949
|
+
});
|
|
4950
|
+
if (!resp.ok) {
|
|
4951
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
4952
|
+
}
|
|
4953
|
+
const data = await resp.json();
|
|
4954
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
4955
|
+
title: r.title ?? "",
|
|
4956
|
+
url: r.url ?? "",
|
|
4957
|
+
snippet: r.description ?? "",
|
|
4958
|
+
engine: "brave"
|
|
4959
|
+
}));
|
|
4960
|
+
}
|
|
4961
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
4962
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
4963
|
+
method: "POST",
|
|
4964
|
+
headers: {
|
|
4965
|
+
"Content-Type": "application/json",
|
|
4966
|
+
"Authorization": `Bearer ${apiKey}`
|
|
4967
|
+
},
|
|
4968
|
+
body: JSON.stringify({
|
|
4969
|
+
query,
|
|
4970
|
+
max_results: maxResults,
|
|
4971
|
+
search_depth: "basic",
|
|
4972
|
+
include_answer: false,
|
|
4973
|
+
include_raw_content: false
|
|
4974
|
+
}),
|
|
4975
|
+
signal: AbortSignal.timeout(15e3)
|
|
4976
|
+
});
|
|
4977
|
+
if (!resp.ok) {
|
|
4978
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
4979
|
+
}
|
|
4980
|
+
const data = await resp.json();
|
|
4981
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
4982
|
+
title: r.title ?? "",
|
|
4983
|
+
url: r.url ?? "",
|
|
4984
|
+
snippet: r.content ?? "",
|
|
4985
|
+
engine: "tavily"
|
|
4986
|
+
}));
|
|
4987
|
+
}
|
|
4988
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
4989
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
4990
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
4991
|
+
signal: AbortSignal.timeout(1e4)
|
|
4992
|
+
});
|
|
4993
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
4994
|
+
const html = await resp.text();
|
|
4995
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
4996
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
4997
|
+
const links = [];
|
|
4998
|
+
const snippets = [];
|
|
4999
|
+
let m;
|
|
5000
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
5001
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
5002
|
+
}
|
|
5003
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
5004
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
5005
|
+
}
|
|
5006
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
5007
|
+
title: link.title,
|
|
5008
|
+
url: link.url,
|
|
5009
|
+
snippet: snippets[i] ?? "",
|
|
5010
|
+
engine: "duckduckgo-lite"
|
|
5011
|
+
}));
|
|
5012
|
+
}
|
|
5013
|
+
var WebSearchTool = class extends BaseTool {
|
|
5014
|
+
name = "web_search";
|
|
5015
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
5016
|
+
inputSchema = {
|
|
5017
|
+
type: "object",
|
|
5018
|
+
properties: {
|
|
5019
|
+
query: { type: "string", description: "The search query" },
|
|
5020
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
5021
|
+
},
|
|
5022
|
+
required: ["query"]
|
|
5023
|
+
};
|
|
5024
|
+
config;
|
|
5025
|
+
constructor(config = {}) {
|
|
5026
|
+
super();
|
|
5027
|
+
this.config = {
|
|
5028
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
5029
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
5030
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
5031
|
+
maxResults: config.maxResults ?? 5
|
|
5032
|
+
};
|
|
5033
|
+
}
|
|
5034
|
+
async execute(input, _options) {
|
|
5035
|
+
const query = input["query"];
|
|
5036
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
5037
|
+
const maxResults = Math.min(
|
|
5038
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
5039
|
+
10
|
|
5040
|
+
);
|
|
5041
|
+
const errors = [];
|
|
5042
|
+
let results = [];
|
|
5043
|
+
if (this.config.searxngUrl) {
|
|
5044
|
+
try {
|
|
5045
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
5046
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5047
|
+
errors.push("SearXNG: returned 0 results");
|
|
5048
|
+
} catch (err) {
|
|
5049
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
if (this.config.braveApiKey) {
|
|
5053
|
+
try {
|
|
5054
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
5055
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5056
|
+
errors.push("Brave: returned 0 results");
|
|
5057
|
+
} catch (err) {
|
|
5058
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
if (this.config.tavilyApiKey) {
|
|
5062
|
+
try {
|
|
5063
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
5064
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5065
|
+
errors.push("Tavily: returned 0 results");
|
|
5066
|
+
} catch (err) {
|
|
5067
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
try {
|
|
5071
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
5072
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
5073
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
5074
|
+
} catch (err) {
|
|
5075
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
5076
|
+
}
|
|
5077
|
+
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" : "";
|
|
5078
|
+
return [
|
|
5079
|
+
`Web search for "${query}" failed across all backends:`,
|
|
5080
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
5081
|
+
configHint
|
|
5082
|
+
].join("\n");
|
|
5083
|
+
}
|
|
5084
|
+
formatResults(query, results) {
|
|
5085
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
5086
|
+
for (let i = 0; i < results.length; i++) {
|
|
5087
|
+
const r = results[i];
|
|
5088
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
5089
|
+
lines.push(` URL: ${r.url}`);
|
|
5090
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
5091
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
5092
|
+
lines.push("");
|
|
5093
|
+
}
|
|
5094
|
+
return lines.join("\n");
|
|
5095
|
+
}
|
|
5096
|
+
};
|
|
5097
|
+
|
|
4484
5098
|
// src/tools/mcp.ts
|
|
4485
5099
|
var McpToolWrapper = class extends BaseTool {
|
|
4486
5100
|
name;
|
|
@@ -4602,7 +5216,8 @@ var ToolRegistry = class {
|
|
|
4602
5216
|
new ImageAnalyzeTool(),
|
|
4603
5217
|
new PDFCreateTool(),
|
|
4604
5218
|
new CodeInterpreterTool(),
|
|
4605
|
-
new PeerCommunicationTool()
|
|
5219
|
+
new PeerCommunicationTool(),
|
|
5220
|
+
new WebSearchTool(this.config.webSearch)
|
|
4606
5221
|
];
|
|
4607
5222
|
for (const tool of tools) {
|
|
4608
5223
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -4626,8 +5241,23 @@ var ToolRegistry = class {
|
|
|
4626
5241
|
return this.ignoreMatcher.ignores(posixRel);
|
|
4627
5242
|
}
|
|
4628
5243
|
};
|
|
4629
|
-
var McpClient = class {
|
|
5244
|
+
var McpClient = class _McpClient {
|
|
5245
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
5246
|
+
/**
|
|
5247
|
+
* Forcefully kills all known MCP child processes.
|
|
5248
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
5249
|
+
*/
|
|
5250
|
+
static killAllProcesses() {
|
|
5251
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
5252
|
+
try {
|
|
5253
|
+
process.kill(pid, "SIGKILL");
|
|
5254
|
+
} catch {
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
_McpClient.activeProcessPids.clear();
|
|
5258
|
+
}
|
|
4630
5259
|
clients = /* @__PURE__ */ new Map();
|
|
5260
|
+
transports = /* @__PURE__ */ new Map();
|
|
4631
5261
|
tools = /* @__PURE__ */ new Map();
|
|
4632
5262
|
trustedServers;
|
|
4633
5263
|
approvalCallback;
|
|
@@ -4656,6 +5286,8 @@ var McpClient = class {
|
|
|
4656
5286
|
);
|
|
4657
5287
|
await client.connect(transport);
|
|
4658
5288
|
this.clients.set(server.name, client);
|
|
5289
|
+
this.transports.set(server.name, transport);
|
|
5290
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
4659
5291
|
const toolsResult = await client.listTools();
|
|
4660
5292
|
for (const tool of toolsResult.tools) {
|
|
4661
5293
|
for (const existing of this.tools.values()) {
|
|
@@ -4677,8 +5309,11 @@ var McpClient = class {
|
|
|
4677
5309
|
async disconnect(serverName) {
|
|
4678
5310
|
const client = this.clients.get(serverName);
|
|
4679
5311
|
if (client) {
|
|
5312
|
+
const transport = this.transports.get(serverName);
|
|
5313
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
4680
5314
|
await client.close();
|
|
4681
5315
|
this.clients.delete(serverName);
|
|
5316
|
+
this.transports.delete(serverName);
|
|
4682
5317
|
for (const key of this.tools.keys()) {
|
|
4683
5318
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
4684
5319
|
}
|
|
@@ -4706,6 +5341,13 @@ var McpClient = class {
|
|
|
4706
5341
|
getConnectedServers() {
|
|
4707
5342
|
return Array.from(this.clients.keys());
|
|
4708
5343
|
}
|
|
5344
|
+
getActivePids() {
|
|
5345
|
+
const pids = [];
|
|
5346
|
+
for (const transport of this.transports.values()) {
|
|
5347
|
+
if (transport.pid) pids.push(transport.pid);
|
|
5348
|
+
}
|
|
5349
|
+
return pids;
|
|
5350
|
+
}
|
|
4709
5351
|
isConnected(serverName) {
|
|
4710
5352
|
return this.clients.has(serverName);
|
|
4711
5353
|
}
|
|
@@ -4844,12 +5486,24 @@ var McpServerConfigSchema = z.object({
|
|
|
4844
5486
|
args: z.array(z.string()).optional(),
|
|
4845
5487
|
env: z.record(z.string()).optional()
|
|
4846
5488
|
});
|
|
5489
|
+
var WebSearchConfigSchema = z.object({
|
|
5490
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
5491
|
+
searxngUrl: z.string().optional(),
|
|
5492
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
5493
|
+
braveApiKey: z.string().optional(),
|
|
5494
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
5495
|
+
tavilyApiKey: z.string().optional(),
|
|
5496
|
+
/** Max results per search (default 5) */
|
|
5497
|
+
maxResults: z.number().default(5)
|
|
5498
|
+
});
|
|
4847
5499
|
var ToolsConfigSchema = z.object({
|
|
4848
5500
|
shellAllowlist: z.array(z.string()).default([]),
|
|
4849
5501
|
shellBlocklist: z.array(z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
4850
5502
|
requireApprovalFor: z.array(z.string()).default([]),
|
|
4851
5503
|
browserEnabled: z.boolean().default(false),
|
|
4852
|
-
mcpServers: z.array(McpServerConfigSchema).optional()
|
|
5504
|
+
mcpServers: z.array(McpServerConfigSchema).optional(),
|
|
5505
|
+
/** Web search backends — at least one should be configured for best results */
|
|
5506
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
4853
5507
|
});
|
|
4854
5508
|
var HookDefinitionSchema = z.object({
|
|
4855
5509
|
command: z.string(),
|
|
@@ -5391,12 +6045,25 @@ var Cascade = class extends EventEmitter {
|
|
|
5391
6045
|
looksLikeSimpleArtifactTask(prompt) {
|
|
5392
6046
|
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
6047
|
}
|
|
5394
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
6048
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
5395
6049
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
5396
6050
|
return "Simple";
|
|
5397
6051
|
}
|
|
6052
|
+
let workspaceContext = "";
|
|
6053
|
+
try {
|
|
6054
|
+
const files = await glob("**/*.*", {
|
|
6055
|
+
cwd: workspacePath,
|
|
6056
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
6057
|
+
nodir: true
|
|
6058
|
+
});
|
|
6059
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
6060
|
+
} catch {
|
|
6061
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
6062
|
+
}
|
|
5398
6063
|
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
6064
|
|
|
6065
|
+
${workspaceContext}
|
|
6066
|
+
|
|
5400
6067
|
Classification:
|
|
5401
6068
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
5402
6069
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -5471,7 +6138,7 @@ ${prompt}` : prompt;
|
|
|
5471
6138
|
}
|
|
5472
6139
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
5473
6140
|
});
|
|
5474
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
6141
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
5475
6142
|
this.telemetry.capture("cascade:session_start", {
|
|
5476
6143
|
complexity,
|
|
5477
6144
|
providerCount: this.config.providers.length,
|
|
@@ -5551,7 +6218,7 @@ ${prompt}` : prompt;
|
|
|
5551
6218
|
peerT3Ids: [],
|
|
5552
6219
|
parentT2: "root"
|
|
5553
6220
|
};
|
|
5554
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
6221
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
5555
6222
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
5556
6223
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
5557
6224
|
} else if (complexity === "Moderate") {
|
|
@@ -5574,7 +6241,7 @@ ${prompt}` : prompt;
|
|
|
5574
6241
|
constraints: [],
|
|
5575
6242
|
t3Subtasks: []
|
|
5576
6243
|
};
|
|
5577
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
6244
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
5578
6245
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
5579
6246
|
t2Results = [t2Result];
|
|
5580
6247
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -5596,13 +6263,22 @@ ${prompt}` : prompt;
|
|
|
5596
6263
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
5597
6264
|
bindTierEvents(t1);
|
|
5598
6265
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
5599
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
6266
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
5600
6267
|
finalOutput = result.output;
|
|
5601
6268
|
t2Results = result.t2Results;
|
|
5602
6269
|
}
|
|
5603
6270
|
} catch (err) {
|
|
5604
|
-
|
|
5605
|
-
|
|
6271
|
+
if (err instanceof CascadeCancelledError) {
|
|
6272
|
+
this.emit("run:cancelled", {
|
|
6273
|
+
taskId,
|
|
6274
|
+
reason: err.message,
|
|
6275
|
+
partialOutput: finalOutput || ""
|
|
6276
|
+
});
|
|
6277
|
+
runError = null;
|
|
6278
|
+
} else {
|
|
6279
|
+
runError = err;
|
|
6280
|
+
throw err;
|
|
6281
|
+
}
|
|
5606
6282
|
} finally {
|
|
5607
6283
|
try {
|
|
5608
6284
|
escalator.cancelAllPending();
|
|
@@ -5924,9 +6600,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
5924
6600
|
constructor(dbPath) {
|
|
5925
6601
|
fs11.mkdirSync(path13.dirname(dbPath), { recursive: true });
|
|
5926
6602
|
try {
|
|
5927
|
-
this.db = new Database(dbPath);
|
|
6603
|
+
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
5928
6604
|
this.db.pragma("journal_mode = WAL");
|
|
5929
6605
|
this.db.pragma("foreign_keys = ON");
|
|
6606
|
+
this.db.pragma("synchronous = NORMAL");
|
|
5930
6607
|
this.migrate();
|
|
5931
6608
|
} catch (err) {
|
|
5932
6609
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -5940,6 +6617,38 @@ Original error: ${err.message}`
|
|
|
5940
6617
|
throw err;
|
|
5941
6618
|
}
|
|
5942
6619
|
}
|
|
6620
|
+
// ── Async Write Queue ─────────────────────────
|
|
6621
|
+
writeQueue = [];
|
|
6622
|
+
isProcessingQueue = false;
|
|
6623
|
+
async processQueue() {
|
|
6624
|
+
if (this.isProcessingQueue) return;
|
|
6625
|
+
this.isProcessingQueue = true;
|
|
6626
|
+
while (this.writeQueue.length > 0) {
|
|
6627
|
+
const op = this.writeQueue.shift();
|
|
6628
|
+
if (op) {
|
|
6629
|
+
let attempts = 0;
|
|
6630
|
+
while (attempts < 5) {
|
|
6631
|
+
try {
|
|
6632
|
+
op();
|
|
6633
|
+
break;
|
|
6634
|
+
} catch (err) {
|
|
6635
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
6636
|
+
attempts++;
|
|
6637
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
6638
|
+
} else {
|
|
6639
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
6640
|
+
break;
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
this.isProcessingQueue = false;
|
|
6647
|
+
}
|
|
6648
|
+
enqueueWrite(op) {
|
|
6649
|
+
this.writeQueue.push(op);
|
|
6650
|
+
this.processQueue().catch(console.error);
|
|
6651
|
+
}
|
|
5943
6652
|
// ── Sessions ──────────────────────────────────
|
|
5944
6653
|
createSession(session) {
|
|
5945
6654
|
this.db.prepare(`
|
|
@@ -6026,26 +6735,28 @@ Original error: ${err.message}`
|
|
|
6026
6735
|
}
|
|
6027
6736
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
6028
6737
|
upsertRuntimeSession(session) {
|
|
6029
|
-
this.
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6738
|
+
this.enqueueWrite(() => {
|
|
6739
|
+
this.db.prepare(`
|
|
6740
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
6741
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6742
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
6743
|
+
title = excluded.title,
|
|
6744
|
+
workspace_path = excluded.workspace_path,
|
|
6745
|
+
status = excluded.status,
|
|
6746
|
+
updated_at = excluded.updated_at,
|
|
6747
|
+
latest_prompt = excluded.latest_prompt,
|
|
6748
|
+
is_global = excluded.is_global
|
|
6749
|
+
`).run(
|
|
6750
|
+
session.sessionId,
|
|
6751
|
+
session.title,
|
|
6752
|
+
session.workspacePath,
|
|
6753
|
+
session.status,
|
|
6754
|
+
session.startedAt,
|
|
6755
|
+
session.updatedAt,
|
|
6756
|
+
session.latestPrompt ?? null,
|
|
6757
|
+
session.isGlobal ? 1 : 0
|
|
6758
|
+
);
|
|
6759
|
+
});
|
|
6049
6760
|
}
|
|
6050
6761
|
listRuntimeSessions(limit = 100) {
|
|
6051
6762
|
const rows = this.db.prepare(`
|
|
@@ -6063,33 +6774,35 @@ Original error: ${err.message}`
|
|
|
6063
6774
|
}));
|
|
6064
6775
|
}
|
|
6065
6776
|
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
|
-
|
|
6777
|
+
this.enqueueWrite(() => {
|
|
6778
|
+
this.db.prepare(`
|
|
6779
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
6780
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6781
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
6782
|
+
session_id = excluded.session_id,
|
|
6783
|
+
parent_id = excluded.parent_id,
|
|
6784
|
+
role = excluded.role,
|
|
6785
|
+
label = excluded.label,
|
|
6786
|
+
status = excluded.status,
|
|
6787
|
+
current_action = excluded.current_action,
|
|
6788
|
+
progress_pct = excluded.progress_pct,
|
|
6789
|
+
updated_at = excluded.updated_at,
|
|
6790
|
+
workspace_path = excluded.workspace_path,
|
|
6791
|
+
is_global = excluded.is_global
|
|
6792
|
+
`).run(
|
|
6793
|
+
node.tierId,
|
|
6794
|
+
node.sessionId,
|
|
6795
|
+
node.parentId ?? null,
|
|
6796
|
+
node.role,
|
|
6797
|
+
node.label,
|
|
6798
|
+
node.status,
|
|
6799
|
+
node.currentAction ?? null,
|
|
6800
|
+
node.progressPct ?? null,
|
|
6801
|
+
node.updatedAt,
|
|
6802
|
+
node.workspacePath ?? null,
|
|
6803
|
+
node.isGlobal ? 1 : 0
|
|
6804
|
+
);
|
|
6805
|
+
});
|
|
6093
6806
|
}
|
|
6094
6807
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
6095
6808
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -6112,30 +6825,32 @@ Original error: ${err.message}`
|
|
|
6112
6825
|
}));
|
|
6113
6826
|
}
|
|
6114
6827
|
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
|
-
|
|
6828
|
+
this.enqueueWrite(() => {
|
|
6829
|
+
this.db.prepare(`
|
|
6830
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
6831
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6832
|
+
`).run(
|
|
6833
|
+
log.id,
|
|
6834
|
+
log.sessionId,
|
|
6835
|
+
log.tierId,
|
|
6836
|
+
log.role,
|
|
6837
|
+
log.label,
|
|
6838
|
+
log.status,
|
|
6839
|
+
log.currentAction ?? null,
|
|
6840
|
+
log.progressPct ?? null,
|
|
6841
|
+
log.timestamp,
|
|
6842
|
+
log.workspacePath ?? null,
|
|
6843
|
+
log.isGlobal ? 1 : 0
|
|
6844
|
+
);
|
|
6845
|
+
this.db.prepare(`
|
|
6846
|
+
DELETE FROM runtime_node_logs
|
|
6847
|
+
WHERE id NOT IN (
|
|
6848
|
+
SELECT id FROM runtime_node_logs
|
|
6849
|
+
ORDER BY timestamp DESC
|
|
6850
|
+
LIMIT 2000
|
|
6851
|
+
)
|
|
6852
|
+
`).run();
|
|
6853
|
+
});
|
|
6139
6854
|
}
|
|
6140
6855
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
6141
6856
|
let rows;
|
|
@@ -6173,19 +6888,21 @@ Original error: ${err.message}`
|
|
|
6173
6888
|
}
|
|
6174
6889
|
// ── Messages ──────────────────────────────────
|
|
6175
6890
|
addMessage(message) {
|
|
6176
|
-
this.
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6891
|
+
this.enqueueWrite(() => {
|
|
6892
|
+
this.db.prepare(`
|
|
6893
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
6894
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6895
|
+
`).run(
|
|
6896
|
+
message.id,
|
|
6897
|
+
message.sessionId,
|
|
6898
|
+
message.role,
|
|
6899
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
6900
|
+
message.timestamp,
|
|
6901
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
6902
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
6903
|
+
);
|
|
6904
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
6905
|
+
});
|
|
6189
6906
|
}
|
|
6190
6907
|
getSessionMessages(sessionId) {
|
|
6191
6908
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -6282,10 +6999,12 @@ Original error: ${err.message}`
|
|
|
6282
6999
|
}
|
|
6283
7000
|
// ── Audit Log ─────────────────────────────────
|
|
6284
7001
|
addAuditEntry(entry) {
|
|
6285
|
-
this.
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
7002
|
+
this.enqueueWrite(() => {
|
|
7003
|
+
this.db.prepare(`
|
|
7004
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
7005
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7006
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
7007
|
+
});
|
|
6289
7008
|
}
|
|
6290
7009
|
getAuditLog(sessionId, limit = 100) {
|
|
6291
7010
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -6300,10 +7019,12 @@ Original error: ${err.message}`
|
|
|
6300
7019
|
}
|
|
6301
7020
|
// ── File Snapshots ────────────────────────────
|
|
6302
7021
|
addFileSnapshot(sessionId, filePath, content) {
|
|
6303
|
-
this.
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
7022
|
+
this.enqueueWrite(() => {
|
|
7023
|
+
this.db.prepare(`
|
|
7024
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
7025
|
+
VALUES (?, ?, ?, ?, ?)
|
|
7026
|
+
`).run(randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
7027
|
+
});
|
|
6307
7028
|
}
|
|
6308
7029
|
getLatestFileSnapshots(sessionId) {
|
|
6309
7030
|
const rows = this.db.prepare(`
|
|
@@ -6599,7 +7320,7 @@ var ConfigManager = class {
|
|
|
6599
7320
|
globalDir;
|
|
6600
7321
|
constructor(workspacePath = process.cwd()) {
|
|
6601
7322
|
this.workspacePath = workspacePath;
|
|
6602
|
-
this.globalDir = path13.join(
|
|
7323
|
+
this.globalDir = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR);
|
|
6603
7324
|
}
|
|
6604
7325
|
async load() {
|
|
6605
7326
|
this.config = await this.loadConfig();
|
|
@@ -6969,7 +7690,7 @@ var DashboardServer = class {
|
|
|
6969
7690
|
// ── Setup ─────────────────────────────────────
|
|
6970
7691
|
getGlobalStore() {
|
|
6971
7692
|
if (!this.globalStore) {
|
|
6972
|
-
const globalDbPath = path13.join(
|
|
7693
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
6973
7694
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
6974
7695
|
}
|
|
6975
7696
|
return this.globalStore;
|
|
@@ -7031,7 +7752,7 @@ var DashboardServer = class {
|
|
|
7031
7752
|
}
|
|
7032
7753
|
watchRuntimeChanges() {
|
|
7033
7754
|
const workspaceDbPath = path13.join(this.workspacePath, CASCADE_DB_FILE);
|
|
7034
|
-
const globalDbPath = path13.join(
|
|
7755
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7035
7756
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
7036
7757
|
for (const watchPath of watchPaths) {
|
|
7037
7758
|
if (!fs11.existsSync(watchPath)) continue;
|
|
@@ -7139,7 +7860,7 @@ var DashboardServer = class {
|
|
|
7139
7860
|
const sessionId = req.params.id;
|
|
7140
7861
|
this.store.deleteSession(sessionId);
|
|
7141
7862
|
this.store.deleteRuntimeSession(sessionId);
|
|
7142
|
-
const globalDbPath = path13.join(
|
|
7863
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7143
7864
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7144
7865
|
try {
|
|
7145
7866
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -7153,7 +7874,7 @@ var DashboardServer = class {
|
|
|
7153
7874
|
});
|
|
7154
7875
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
7155
7876
|
const body = req.body;
|
|
7156
|
-
const globalDbPath = path13.join(
|
|
7877
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7157
7878
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
7158
7879
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7159
7880
|
try {
|
|
@@ -7176,7 +7897,7 @@ var DashboardServer = class {
|
|
|
7176
7897
|
});
|
|
7177
7898
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
7178
7899
|
this.store.deleteAllRuntimeNodes();
|
|
7179
|
-
const globalDbPath = path13.join(
|
|
7900
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7180
7901
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7181
7902
|
try {
|
|
7182
7903
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -7272,7 +7993,7 @@ var DashboardServer = class {
|
|
|
7272
7993
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
7273
7994
|
const scope = req.query["scope"] ?? "workspace";
|
|
7274
7995
|
if (scope === "global") {
|
|
7275
|
-
const globalDbPath = path13.join(
|
|
7996
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7276
7997
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7277
7998
|
try {
|
|
7278
7999
|
res.json({
|
|
@@ -7483,6 +8204,6 @@ var HooksRunner = class {
|
|
|
7483
8204
|
}
|
|
7484
8205
|
};
|
|
7485
8206
|
|
|
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 };
|
|
8207
|
+
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
8208
|
//# sourceMappingURL=index.js.map
|
|
7488
8209
|
//# sourceMappingURL=index.js.map
|