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.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);
|
|
@@ -2266,14 +2395,13 @@ Now execute your subtask using this context where relevant.`
|
|
|
2266
2395
|
await this.peerBus.barrier(this.id, barrierName, total);
|
|
2267
2396
|
}
|
|
2268
2397
|
receivePeerSync(fromId, content) {
|
|
2269
|
-
|
|
2270
|
-
if (existing) {
|
|
2271
|
-
existing.content = content;
|
|
2272
|
-
existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2273
|
-
} else {
|
|
2274
|
-
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2275
|
-
}
|
|
2398
|
+
this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2276
2399
|
this.emit("peer-sync-received", { fromId, content });
|
|
2400
|
+
this.context.addMessage({
|
|
2401
|
+
role: "user",
|
|
2402
|
+
content: `[SYSTEM_NOTIFICATION]: You received a new peer message from ${fromId}. Use the "peer_message" tool with action="receive" to read it.`
|
|
2403
|
+
}).catch(() => {
|
|
2404
|
+
});
|
|
2277
2405
|
}
|
|
2278
2406
|
// ── Private ──────────────────────────────────
|
|
2279
2407
|
async runAgentLoop(systemPrompt, tools) {
|
|
@@ -2285,6 +2413,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
2285
2413
|
tools = [...tools];
|
|
2286
2414
|
while (iterations < MAX_ITERATIONS) {
|
|
2287
2415
|
iterations++;
|
|
2416
|
+
this.throwIfCancelled();
|
|
2288
2417
|
const options = {
|
|
2289
2418
|
messages: this.context.getMessages(),
|
|
2290
2419
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -2305,21 +2434,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2305
2434
|
if (requiresArtifact) {
|
|
2306
2435
|
stalledArtifactIterations += 1;
|
|
2307
2436
|
if (stalledArtifactIterations >= 2) {
|
|
2308
|
-
if (
|
|
2309
|
-
|
|
2310
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
2311
|
-
this.assignment?.description ?? ""
|
|
2312
|
-
);
|
|
2313
|
-
if (toolName) {
|
|
2314
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
2315
|
-
this.sendStatusUpdate({
|
|
2316
|
-
progressPct: 50,
|
|
2317
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
2318
|
-
status: "IN_PROGRESS"
|
|
2319
|
-
});
|
|
2320
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
2321
|
-
continue;
|
|
2322
|
-
}
|
|
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"}`);
|
|
2323
2439
|
}
|
|
2324
2440
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
2325
2441
|
}
|
|
@@ -2404,7 +2520,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
2404
2520
|
sendPeerSync: (to, syncType, content) => {
|
|
2405
2521
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
2406
2522
|
},
|
|
2407
|
-
getPeerMessages: () =>
|
|
2523
|
+
getPeerMessages: () => {
|
|
2524
|
+
const msgs = [...this.peerSyncBuffer];
|
|
2525
|
+
this.peerSyncBuffer = [];
|
|
2526
|
+
return msgs;
|
|
2527
|
+
}
|
|
2408
2528
|
});
|
|
2409
2529
|
if (this.audit) {
|
|
2410
2530
|
this.audit.toolCall(this.id, tc.name, tc.input);
|
|
@@ -2475,6 +2595,9 @@ ${assignment.expectedOutput}`;
|
|
|
2475
2595
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
2476
2596
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
2477
2597
|
const issues = [];
|
|
2598
|
+
const { exec: exec3 } = await import('child_process');
|
|
2599
|
+
const { promisify: promisify3 } = await import('util');
|
|
2600
|
+
const execAsync2 = promisify3(exec3);
|
|
2478
2601
|
for (const artifactPath of artifactPaths) {
|
|
2479
2602
|
const absolutePath = path13.resolve(process.cwd(), artifactPath);
|
|
2480
2603
|
try {
|
|
@@ -2491,9 +2614,27 @@ ${assignment.expectedOutput}`;
|
|
|
2491
2614
|
const content = await fs2.readFile(absolutePath, "utf-8");
|
|
2492
2615
|
if (!content.trim()) {
|
|
2493
2616
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
2617
|
+
continue;
|
|
2494
2618
|
}
|
|
2495
2619
|
} else if (stat.size < 100) {
|
|
2496
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}`);
|
|
2497
2638
|
}
|
|
2498
2639
|
} catch {
|
|
2499
2640
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -2890,7 +3031,8 @@ var T2Manager = class extends BaseTier {
|
|
|
2890
3031
|
});
|
|
2891
3032
|
this.emit("peer-sync-received", { fromId, content });
|
|
2892
3033
|
}
|
|
2893
|
-
async execute(assignment, taskId) {
|
|
3034
|
+
async execute(assignment, taskId, signal) {
|
|
3035
|
+
this.signal = signal;
|
|
2894
3036
|
this.assignment = assignment;
|
|
2895
3037
|
this.taskId = taskId;
|
|
2896
3038
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -2902,12 +3044,14 @@ var T2Manager = class extends BaseTier {
|
|
|
2902
3044
|
});
|
|
2903
3045
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
2904
3046
|
try {
|
|
3047
|
+
this.throwIfCancelled();
|
|
2905
3048
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
2906
3049
|
this.sendStatusUpdate({
|
|
2907
3050
|
progressPct: 20,
|
|
2908
3051
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
2909
3052
|
status: "IN_PROGRESS"
|
|
2910
3053
|
});
|
|
3054
|
+
this.throwIfCancelled();
|
|
2911
3055
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
2912
3056
|
this.sendStatusUpdate({
|
|
2913
3057
|
progressPct: 90,
|
|
@@ -2946,13 +3090,17 @@ var T2Manager = class extends BaseTier {
|
|
|
2946
3090
|
}
|
|
2947
3091
|
// ── Private ──────────────────────────────────
|
|
2948
3092
|
async decomposeSection(assignment) {
|
|
3093
|
+
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");
|
|
2949
3094
|
const prompt = `Decompose this section into 2-5 concrete subtasks for T3 workers.
|
|
2950
3095
|
|
|
2951
3096
|
Section: ${assignment.sectionTitle}
|
|
2952
3097
|
Description: ${assignment.description}
|
|
2953
3098
|
Expected output: ${assignment.expectedOutput}
|
|
2954
3099
|
Constraints: ${assignment.constraints.join("; ")}
|
|
2955
|
-
|
|
3100
|
+
${peerPlans ? `
|
|
3101
|
+
Context from sibling T2 plans (use this to align execution and avoid overlaps):
|
|
3102
|
+
${peerPlans}
|
|
3103
|
+
` : ""}
|
|
2956
3104
|
Return a JSON array of subtask objects, each with:
|
|
2957
3105
|
- subtaskId: string (unique)
|
|
2958
3106
|
- subtaskTitle: string
|
|
@@ -3070,11 +3218,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3070
3218
|
).join(", ")}`,
|
|
3071
3219
|
status: "IN_PROGRESS"
|
|
3072
3220
|
});
|
|
3221
|
+
this.throwIfCancelled();
|
|
3073
3222
|
const waveResults = await Promise.allSettled(
|
|
3074
3223
|
runnableIds.map(async (id) => {
|
|
3075
3224
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3076
3225
|
const worker = workerMap.get(id);
|
|
3077
|
-
const result = await worker.execute(assignment, taskId);
|
|
3226
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
3078
3227
|
resultMap.set(id, result);
|
|
3079
3228
|
return result;
|
|
3080
3229
|
})
|
|
@@ -3088,6 +3237,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3088
3237
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
3089
3238
|
const retried = await this.retryT3(assignment, taskId);
|
|
3090
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
|
+
}
|
|
3091
3294
|
}
|
|
3092
3295
|
for (const dependent of adj.get(id) ?? []) {
|
|
3093
3296
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -3151,7 +3354,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3151
3354
|
}));
|
|
3152
3355
|
return worker.execute(
|
|
3153
3356
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
3154
|
-
taskId
|
|
3357
|
+
taskId,
|
|
3358
|
+
this.signal
|
|
3155
3359
|
);
|
|
3156
3360
|
}
|
|
3157
3361
|
publishSectionOutput(result) {
|
|
@@ -3165,24 +3369,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3165
3369
|
async aggregateResults(assignment, results) {
|
|
3166
3370
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
3167
3371
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
3168
|
-
const
|
|
3169
|
-
const
|
|
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");
|
|
3373
|
+
const peerContext = peerOutputs ? `
|
|
3170
3374
|
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3375
|
+
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
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 ? `
|
|
3177
3406
|
|
|
3178
3407
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
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
|
+
}
|
|
3185
3415
|
}
|
|
3416
|
+
return currentSummary;
|
|
3186
3417
|
}
|
|
3187
3418
|
determineStatus(results) {
|
|
3188
3419
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -3307,10 +3538,10 @@ Rules:
|
|
|
3307
3538
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
3308
3539
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
3309
3540
|
|
|
3310
|
-
|
|
3311
|
-
-
|
|
3312
|
-
-
|
|
3313
|
-
- 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.
|
|
3314
3545
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
3315
3546
|
|
|
3316
3547
|
QUALITY RULES:
|
|
@@ -3349,7 +3580,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
3349
3580
|
setToolCreator(creator) {
|
|
3350
3581
|
this.toolCreator = creator;
|
|
3351
3582
|
}
|
|
3352
|
-
async execute(userPrompt, images, systemContext) {
|
|
3583
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
3584
|
+
this.signal = signal;
|
|
3353
3585
|
this.taskId = randomUUID();
|
|
3354
3586
|
this.setLabel("Administrator");
|
|
3355
3587
|
this.setStatus("ACTIVE");
|
|
@@ -3360,10 +3592,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
3360
3592
|
status: "IN_PROGRESS"
|
|
3361
3593
|
});
|
|
3362
3594
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
3595
|
+
this.throwIfCancelled();
|
|
3363
3596
|
let enrichedPrompt = userPrompt;
|
|
3364
3597
|
if (images?.length) {
|
|
3365
3598
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
3366
3599
|
}
|
|
3600
|
+
this.throwIfCancelled();
|
|
3367
3601
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
3368
3602
|
this.sendStatusUpdate({
|
|
3369
3603
|
progressPct: 10,
|
|
@@ -3371,21 +3605,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
3371
3605
|
status: "IN_PROGRESS"
|
|
3372
3606
|
});
|
|
3373
3607
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
3374
|
-
|
|
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
|
+
}
|
|
3375
3634
|
this.sendStatusUpdate({
|
|
3376
3635
|
progressPct: 95,
|
|
3377
3636
|
currentAction: "Compiling final output",
|
|
3378
3637
|
status: "IN_PROGRESS"
|
|
3379
3638
|
});
|
|
3380
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
3639
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
3381
3640
|
this.setStatus("COMPLETED");
|
|
3382
3641
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
3383
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3642
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
3384
3643
|
}
|
|
3385
3644
|
getEscalations() {
|
|
3386
3645
|
return [...this.escalations];
|
|
3387
3646
|
}
|
|
3388
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
|
+
}
|
|
3389
3685
|
async analyzeImages(prompt, images) {
|
|
3390
3686
|
const visionModel = this.router.getModelForTier("T1");
|
|
3391
3687
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -3414,29 +3710,35 @@ ${systemContext}` : "";
|
|
|
3414
3710
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
3415
3711
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
3416
3712
|
|
|
3417
|
-
Return JSON where
|
|
3713
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
3418
3714
|
{
|
|
3419
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
|
|
3420
3722
|
"t3Subtasks": [{
|
|
3421
3723
|
"subtaskId": "t1",
|
|
3422
|
-
"subtaskTitle": "
|
|
3423
|
-
"
|
|
3424
|
-
"
|
|
3425
|
-
|
|
3426
|
-
"
|
|
3427
|
-
"subtaskTitle": "Save Code to File",
|
|
3428
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
3429
|
-
"executionMode": "parallel"
|
|
3430
|
-
}, {
|
|
3431
|
-
"subtaskId": "t3",
|
|
3432
|
-
"subtaskTitle": "Execute and Verify",
|
|
3433
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
3434
|
-
"executionMode": "parallel"
|
|
3724
|
+
"subtaskTitle": "Init NPM",
|
|
3725
|
+
"description": "Run npm init",
|
|
3726
|
+
"expectedOutput": "package.json created",
|
|
3727
|
+
"constraints": [],
|
|
3728
|
+
"dependsOn": []
|
|
3435
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": [...]
|
|
3436
3738
|
}]
|
|
3437
3739
|
}
|
|
3438
|
-
Use dependsOn when a
|
|
3439
|
-
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.`;
|
|
3440
3742
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
3441
3743
|
const result = await this.router.generate("T1", {
|
|
3442
3744
|
messages,
|
|
@@ -3564,92 +3866,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
3564
3866
|
].filter(Boolean).join(" ");
|
|
3565
3867
|
m.setHierarchyContext(context);
|
|
3566
3868
|
});
|
|
3567
|
-
if (overlapSections.size > 0
|
|
3568
|
-
this.log("Overlap detected \u2014
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
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]];
|
|
3572
3876
|
}
|
|
3573
3877
|
}
|
|
3574
3878
|
}
|
|
3575
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
3576
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
3577
3879
|
const t2Results = [];
|
|
3578
3880
|
try {
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
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);
|
|
3610
3928
|
}
|
|
3929
|
+
s.dependsOn = safeDeps;
|
|
3611
3930
|
}
|
|
3612
|
-
}
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
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
|
-
|
|
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"
|
|
3645
3966
|
});
|
|
3646
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));
|
|
3647
3982
|
}
|
|
3983
|
+
}));
|
|
3984
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
3985
|
+
await executeWave();
|
|
3648
3986
|
}
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
return t2Results;
|
|
3987
|
+
};
|
|
3988
|
+
await executeWave();
|
|
3989
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
3653
3990
|
}
|
|
3654
3991
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
3655
3992
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -4096,13 +4433,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
4096
4433
|
}
|
|
4097
4434
|
async execute(input, _options) {
|
|
4098
4435
|
const platform = input["platform"] ?? "github";
|
|
4099
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
4100
4436
|
const operation = input["operation"];
|
|
4101
4437
|
const repo = input["repo"];
|
|
4102
|
-
|
|
4103
|
-
|
|
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)}`;
|
|
4104
4476
|
}
|
|
4105
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
4106
4477
|
}
|
|
4107
4478
|
async executeGitHub(operation, repo, token, input) {
|
|
4108
4479
|
const headers = {
|
|
@@ -4189,6 +4560,7 @@ ${response.data.description}`;
|
|
|
4189
4560
|
};
|
|
4190
4561
|
|
|
4191
4562
|
// src/tools/browser.ts
|
|
4563
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
4192
4564
|
var BrowserTool = class extends BaseTool {
|
|
4193
4565
|
name = "browser";
|
|
4194
4566
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -4197,7 +4569,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
4197
4569
|
properties: {
|
|
4198
4570
|
action: {
|
|
4199
4571
|
type: "string",
|
|
4200
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
4572
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
4201
4573
|
},
|
|
4202
4574
|
url: { type: "string", description: "URL to navigate to" },
|
|
4203
4575
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -4217,53 +4589,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
4217
4589
|
try {
|
|
4218
4590
|
playwright = await import('playwright');
|
|
4219
4591
|
} catch {
|
|
4220
|
-
|
|
4221
|
-
}
|
|
4222
|
-
if (!this.browser) {
|
|
4223
|
-
const pw = playwright;
|
|
4224
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
4225
|
-
const b = this.browser;
|
|
4226
|
-
this.page = await b.newPage();
|
|
4592
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
4227
4593
|
}
|
|
4228
|
-
const page = this.page;
|
|
4229
4594
|
const action = input["action"];
|
|
4230
4595
|
const timeout = input["timeout"] ?? 1e4;
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
case "evaluate": {
|
|
4249
|
-
const result = await page.evaluate(input["script"]);
|
|
4250
|
-
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)}`;
|
|
4251
4613
|
}
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
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`;
|
|
4255
4649
|
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
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}`;
|
|
4259
4655
|
}
|
|
4260
|
-
|
|
4261
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
4656
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
4262
4657
|
}
|
|
4263
4658
|
}
|
|
4264
4659
|
async close() {
|
|
4265
|
-
|
|
4266
|
-
|
|
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 {
|
|
4267
4672
|
this.browser = null;
|
|
4268
4673
|
this.page = null;
|
|
4269
4674
|
}
|
|
@@ -4360,6 +4765,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
4360
4765
|
});
|
|
4361
4766
|
}
|
|
4362
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"]);
|
|
4363
4781
|
var CodeInterpreterTool = class extends BaseTool {
|
|
4364
4782
|
name = "run_code";
|
|
4365
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.";
|
|
@@ -4375,10 +4793,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4375
4793
|
isDangerous() {
|
|
4376
4794
|
return true;
|
|
4377
4795
|
}
|
|
4378
|
-
async execute(input,
|
|
4796
|
+
async execute(input, _options) {
|
|
4379
4797
|
const language = input["language"];
|
|
4380
4798
|
const code = input["code"];
|
|
4381
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
|
+
}
|
|
4382
4820
|
const tmpDir = path13.join(process.cwd(), ".cascade", "tmp");
|
|
4383
4821
|
if (!fs11.existsSync(tmpDir)) {
|
|
4384
4822
|
fs11.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -4387,8 +4825,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4387
4825
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
4388
4826
|
const filePath = path13.join(tmpDir, fileName);
|
|
4389
4827
|
fs11.writeFileSync(filePath, code, "utf-8");
|
|
4390
|
-
const
|
|
4391
|
-
const
|
|
4828
|
+
const quotedPath = `"${filePath}"`;
|
|
4829
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
4830
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
4392
4831
|
return new Promise((resolve) => {
|
|
4393
4832
|
const startMs = Date.now();
|
|
4394
4833
|
exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -4401,10 +4840,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
4401
4840
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
4402
4841
|
}
|
|
4403
4842
|
if (error) {
|
|
4404
|
-
|
|
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):
|
|
4405
4850
|
Error: ${error.message}
|
|
4406
4851
|
Stderr: ${stderr}
|
|
4407
4852
|
Stdout: ${stdout}`);
|
|
4853
|
+
}
|
|
4408
4854
|
} else {
|
|
4409
4855
|
resolve(`Execution successful (${duration}ms):
|
|
4410
4856
|
Stdout: ${stdout}
|
|
@@ -4469,6 +4915,186 @@ ${formatted}`;
|
|
|
4469
4915
|
}
|
|
4470
4916
|
};
|
|
4471
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
|
+
|
|
4472
5098
|
// src/tools/mcp.ts
|
|
4473
5099
|
var McpToolWrapper = class extends BaseTool {
|
|
4474
5100
|
name;
|
|
@@ -4590,7 +5216,8 @@ var ToolRegistry = class {
|
|
|
4590
5216
|
new ImageAnalyzeTool(),
|
|
4591
5217
|
new PDFCreateTool(),
|
|
4592
5218
|
new CodeInterpreterTool(),
|
|
4593
|
-
new PeerCommunicationTool()
|
|
5219
|
+
new PeerCommunicationTool(),
|
|
5220
|
+
new WebSearchTool(this.config.webSearch)
|
|
4594
5221
|
];
|
|
4595
5222
|
for (const tool of tools) {
|
|
4596
5223
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -4614,8 +5241,23 @@ var ToolRegistry = class {
|
|
|
4614
5241
|
return this.ignoreMatcher.ignores(posixRel);
|
|
4615
5242
|
}
|
|
4616
5243
|
};
|
|
4617
|
-
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
|
+
}
|
|
4618
5259
|
clients = /* @__PURE__ */ new Map();
|
|
5260
|
+
transports = /* @__PURE__ */ new Map();
|
|
4619
5261
|
tools = /* @__PURE__ */ new Map();
|
|
4620
5262
|
trustedServers;
|
|
4621
5263
|
approvalCallback;
|
|
@@ -4644,6 +5286,8 @@ var McpClient = class {
|
|
|
4644
5286
|
);
|
|
4645
5287
|
await client.connect(transport);
|
|
4646
5288
|
this.clients.set(server.name, client);
|
|
5289
|
+
this.transports.set(server.name, transport);
|
|
5290
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
4647
5291
|
const toolsResult = await client.listTools();
|
|
4648
5292
|
for (const tool of toolsResult.tools) {
|
|
4649
5293
|
for (const existing of this.tools.values()) {
|
|
@@ -4665,8 +5309,11 @@ var McpClient = class {
|
|
|
4665
5309
|
async disconnect(serverName) {
|
|
4666
5310
|
const client = this.clients.get(serverName);
|
|
4667
5311
|
if (client) {
|
|
5312
|
+
const transport = this.transports.get(serverName);
|
|
5313
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
4668
5314
|
await client.close();
|
|
4669
5315
|
this.clients.delete(serverName);
|
|
5316
|
+
this.transports.delete(serverName);
|
|
4670
5317
|
for (const key of this.tools.keys()) {
|
|
4671
5318
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
4672
5319
|
}
|
|
@@ -4694,6 +5341,13 @@ var McpClient = class {
|
|
|
4694
5341
|
getConnectedServers() {
|
|
4695
5342
|
return Array.from(this.clients.keys());
|
|
4696
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
|
+
}
|
|
4697
5351
|
isConnected(serverName) {
|
|
4698
5352
|
return this.clients.has(serverName);
|
|
4699
5353
|
}
|
|
@@ -4832,12 +5486,24 @@ var McpServerConfigSchema = z.object({
|
|
|
4832
5486
|
args: z.array(z.string()).optional(),
|
|
4833
5487
|
env: z.record(z.string()).optional()
|
|
4834
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
|
+
});
|
|
4835
5499
|
var ToolsConfigSchema = z.object({
|
|
4836
5500
|
shellAllowlist: z.array(z.string()).default([]),
|
|
4837
5501
|
shellBlocklist: z.array(z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
4838
5502
|
requireApprovalFor: z.array(z.string()).default([]),
|
|
4839
5503
|
browserEnabled: z.boolean().default(false),
|
|
4840
|
-
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()
|
|
4841
5507
|
});
|
|
4842
5508
|
var HookDefinitionSchema = z.object({
|
|
4843
5509
|
command: z.string(),
|
|
@@ -5379,12 +6045,25 @@ var Cascade = class extends EventEmitter {
|
|
|
5379
6045
|
looksLikeSimpleArtifactTask(prompt) {
|
|
5380
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);
|
|
5381
6047
|
}
|
|
5382
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
6048
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
5383
6049
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
5384
6050
|
return "Simple";
|
|
5385
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
|
+
}
|
|
5386
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.
|
|
5387
6064
|
|
|
6065
|
+
${workspaceContext}
|
|
6066
|
+
|
|
5388
6067
|
Classification:
|
|
5389
6068
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
5390
6069
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -5459,7 +6138,7 @@ ${prompt}` : prompt;
|
|
|
5459
6138
|
}
|
|
5460
6139
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
5461
6140
|
});
|
|
5462
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
6141
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
5463
6142
|
this.telemetry.capture("cascade:session_start", {
|
|
5464
6143
|
complexity,
|
|
5465
6144
|
providerCount: this.config.providers.length,
|
|
@@ -5539,7 +6218,7 @@ ${prompt}` : prompt;
|
|
|
5539
6218
|
peerT3Ids: [],
|
|
5540
6219
|
parentT2: "root"
|
|
5541
6220
|
};
|
|
5542
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
6221
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
5543
6222
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
5544
6223
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
5545
6224
|
} else if (complexity === "Moderate") {
|
|
@@ -5562,7 +6241,7 @@ ${prompt}` : prompt;
|
|
|
5562
6241
|
constraints: [],
|
|
5563
6242
|
t3Subtasks: []
|
|
5564
6243
|
};
|
|
5565
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
6244
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
5566
6245
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
5567
6246
|
t2Results = [t2Result];
|
|
5568
6247
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -5584,13 +6263,22 @@ ${prompt}` : prompt;
|
|
|
5584
6263
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
5585
6264
|
bindTierEvents(t1);
|
|
5586
6265
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
5587
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
6266
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
5588
6267
|
finalOutput = result.output;
|
|
5589
6268
|
t2Results = result.t2Results;
|
|
5590
6269
|
}
|
|
5591
6270
|
} catch (err) {
|
|
5592
|
-
|
|
5593
|
-
|
|
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
|
+
}
|
|
5594
6282
|
} finally {
|
|
5595
6283
|
try {
|
|
5596
6284
|
escalator.cancelAllPending();
|
|
@@ -5912,9 +6600,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
5912
6600
|
constructor(dbPath) {
|
|
5913
6601
|
fs11.mkdirSync(path13.dirname(dbPath), { recursive: true });
|
|
5914
6602
|
try {
|
|
5915
|
-
this.db = new Database(dbPath);
|
|
6603
|
+
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
5916
6604
|
this.db.pragma("journal_mode = WAL");
|
|
5917
6605
|
this.db.pragma("foreign_keys = ON");
|
|
6606
|
+
this.db.pragma("synchronous = NORMAL");
|
|
5918
6607
|
this.migrate();
|
|
5919
6608
|
} catch (err) {
|
|
5920
6609
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -5928,6 +6617,38 @@ Original error: ${err.message}`
|
|
|
5928
6617
|
throw err;
|
|
5929
6618
|
}
|
|
5930
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
|
+
}
|
|
5931
6652
|
// ── Sessions ──────────────────────────────────
|
|
5932
6653
|
createSession(session) {
|
|
5933
6654
|
this.db.prepare(`
|
|
@@ -6014,26 +6735,28 @@ Original error: ${err.message}`
|
|
|
6014
6735
|
}
|
|
6015
6736
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
6016
6737
|
upsertRuntimeSession(session) {
|
|
6017
|
-
this.
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
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
|
+
});
|
|
6037
6760
|
}
|
|
6038
6761
|
listRuntimeSessions(limit = 100) {
|
|
6039
6762
|
const rows = this.db.prepare(`
|
|
@@ -6051,33 +6774,35 @@ Original error: ${err.message}`
|
|
|
6051
6774
|
}));
|
|
6052
6775
|
}
|
|
6053
6776
|
upsertRuntimeNode(node) {
|
|
6054
|
-
this.
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
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
|
+
});
|
|
6081
6806
|
}
|
|
6082
6807
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
6083
6808
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -6100,30 +6825,32 @@ Original error: ${err.message}`
|
|
|
6100
6825
|
}));
|
|
6101
6826
|
}
|
|
6102
6827
|
addRuntimeNodeLog(log) {
|
|
6103
|
-
this.
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
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
|
+
});
|
|
6127
6854
|
}
|
|
6128
6855
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
6129
6856
|
let rows;
|
|
@@ -6161,19 +6888,21 @@ Original error: ${err.message}`
|
|
|
6161
6888
|
}
|
|
6162
6889
|
// ── Messages ──────────────────────────────────
|
|
6163
6890
|
addMessage(message) {
|
|
6164
|
-
this.
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
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
|
+
});
|
|
6177
6906
|
}
|
|
6178
6907
|
getSessionMessages(sessionId) {
|
|
6179
6908
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -6270,10 +6999,12 @@ Original error: ${err.message}`
|
|
|
6270
6999
|
}
|
|
6271
7000
|
// ── Audit Log ─────────────────────────────────
|
|
6272
7001
|
addAuditEntry(entry) {
|
|
6273
|
-
this.
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
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
|
+
});
|
|
6277
7008
|
}
|
|
6278
7009
|
getAuditLog(sessionId, limit = 100) {
|
|
6279
7010
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -6288,10 +7019,12 @@ Original error: ${err.message}`
|
|
|
6288
7019
|
}
|
|
6289
7020
|
// ── File Snapshots ────────────────────────────
|
|
6290
7021
|
addFileSnapshot(sessionId, filePath, content) {
|
|
6291
|
-
this.
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
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
|
+
});
|
|
6295
7028
|
}
|
|
6296
7029
|
getLatestFileSnapshots(sessionId) {
|
|
6297
7030
|
const rows = this.db.prepare(`
|
|
@@ -6587,7 +7320,7 @@ var ConfigManager = class {
|
|
|
6587
7320
|
globalDir;
|
|
6588
7321
|
constructor(workspacePath = process.cwd()) {
|
|
6589
7322
|
this.workspacePath = workspacePath;
|
|
6590
|
-
this.globalDir = path13.join(
|
|
7323
|
+
this.globalDir = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR);
|
|
6591
7324
|
}
|
|
6592
7325
|
async load() {
|
|
6593
7326
|
this.config = await this.loadConfig();
|
|
@@ -6957,7 +7690,7 @@ var DashboardServer = class {
|
|
|
6957
7690
|
// ── Setup ─────────────────────────────────────
|
|
6958
7691
|
getGlobalStore() {
|
|
6959
7692
|
if (!this.globalStore) {
|
|
6960
|
-
const globalDbPath = path13.join(
|
|
7693
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
6961
7694
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
6962
7695
|
}
|
|
6963
7696
|
return this.globalStore;
|
|
@@ -7019,7 +7752,7 @@ var DashboardServer = class {
|
|
|
7019
7752
|
}
|
|
7020
7753
|
watchRuntimeChanges() {
|
|
7021
7754
|
const workspaceDbPath = path13.join(this.workspacePath, CASCADE_DB_FILE);
|
|
7022
|
-
const globalDbPath = path13.join(
|
|
7755
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7023
7756
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
7024
7757
|
for (const watchPath of watchPaths) {
|
|
7025
7758
|
if (!fs11.existsSync(watchPath)) continue;
|
|
@@ -7127,7 +7860,7 @@ var DashboardServer = class {
|
|
|
7127
7860
|
const sessionId = req.params.id;
|
|
7128
7861
|
this.store.deleteSession(sessionId);
|
|
7129
7862
|
this.store.deleteRuntimeSession(sessionId);
|
|
7130
|
-
const globalDbPath = path13.join(
|
|
7863
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7131
7864
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7132
7865
|
try {
|
|
7133
7866
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -7141,7 +7874,7 @@ var DashboardServer = class {
|
|
|
7141
7874
|
});
|
|
7142
7875
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
7143
7876
|
const body = req.body;
|
|
7144
|
-
const globalDbPath = path13.join(
|
|
7877
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7145
7878
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
7146
7879
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7147
7880
|
try {
|
|
@@ -7164,7 +7897,7 @@ var DashboardServer = class {
|
|
|
7164
7897
|
});
|
|
7165
7898
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
7166
7899
|
this.store.deleteAllRuntimeNodes();
|
|
7167
|
-
const globalDbPath = path13.join(
|
|
7900
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7168
7901
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7169
7902
|
try {
|
|
7170
7903
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -7260,7 +7993,7 @@ var DashboardServer = class {
|
|
|
7260
7993
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
7261
7994
|
const scope = req.query["scope"] ?? "workspace";
|
|
7262
7995
|
if (scope === "global") {
|
|
7263
|
-
const globalDbPath = path13.join(
|
|
7996
|
+
const globalDbPath = path13.join(os2.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
7264
7997
|
const globalStore = new MemoryStore(globalDbPath);
|
|
7265
7998
|
try {
|
|
7266
7999
|
res.json({
|
|
@@ -7471,6 +8204,6 @@ var HooksRunner = class {
|
|
|
7471
8204
|
}
|
|
7472
8205
|
};
|
|
7473
8206
|
|
|
7474
|
-
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 };
|
|
7475
8208
|
//# sourceMappingURL=index.js.map
|
|
7476
8209
|
//# sourceMappingURL=index.js.map
|