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/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 os from 'os';
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.0";
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
- return messages.filter((m) => m.role !== "system").map((m) => {
579
- if (typeof m.content === "string") {
580
- return { role: m.role, content: m.content };
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
- const content = m.content.map((block) => {
583
- if (block.type === "text") return { type: "text", text: block.text };
584
- if (block.type === "image") {
585
- const img = block.image;
586
- if (img.type === "base64") {
587
- return {
588
- type: "image",
589
- source: {
590
- type: "base64",
591
- media_type: img.mimeType,
592
- data: img.data
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
- return {
597
- type: "image",
598
- source: { type: "url", url: img.data }
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
- return { type: "text", text: "" };
602
- });
603
- return { role: m.role, content };
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: `gemini-tool-${Date.now()}-${toolCalls.length}`,
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
- return messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({
954
- role: m.role === "assistant" ? "model" : "user",
955
- parts: typeof m.content === "string" ? [{ text: m.content }] : this.convertMessageContent(m, extraImages)
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
- const existing = this.peerSyncBuffer.find((p) => p.fromId === fromId);
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 (this.toolCreator && stalledArtifactIterations === 2) {
2309
- const toolName = await this.toolCreator.createTool(
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: () => [...this.peerSyncBuffer]
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 outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
3169
- const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences:
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
- ${outputs}`;
3172
- const messages = [{ role: "user", content: prompt }];
3173
- try {
3174
- const result = await this.router.generate("T2", {
3175
- messages,
3176
- systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
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
- maxTokens: 300
3180
- });
3181
- return result.content;
3182
- } catch (err) {
3183
- this.log(`aggregateResults: LLM summarization failed \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
3184
- return outputs;
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
- EXECUTION MODE GUIDANCE:
3311
- - Use "parallel" for sections that are independent (e.g. writing different files, researching different topics).
3312
- - Use "sequential" ONLY when a later section strictly depends on the output of an earlier one (e.g. write code \u2192 then test it).
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
- const t2Results = await this.dispatchT2Managers(plan.sections);
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, t2Results);
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 subtasks can declare dependencies:
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": "Generate Source Code",
3423
- "dependsOn": [], // \u2190 empty = runs immediately
3424
- "executionMode": "parallel"
3425
- }, {
3426
- "subtaskId": "t2",
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 subtask needs the output of a previous one.
3439
- Leave dependsOn empty for subtasks that can run immediately in parallel.`;
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 && !sections.some((s) => s.executionMode === "sequential")) {
3568
- this.log("Overlap detected \u2014 switching to sequential execution for conflicting sections");
3569
- for (const section of sections) {
3570
- if (overlapSections.has(section.sectionId)) {
3571
- section.executionMode = "sequential";
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
- if (isSequential) {
3580
- this.log("Dispatching T2 managers sequentially");
3581
- for (let i = 0; i < managers.length; i++) {
3582
- const m = managers[i];
3583
- this.sendStatusUpdate({
3584
- progressPct: pct(i),
3585
- currentAction: `T2 working on: ${sections[i].sectionTitle} (Sequential)`,
3586
- status: "IN_PROGRESS"
3587
- });
3588
- try {
3589
- const result = await m.execute(sections[i], this.taskId);
3590
- t2Results.push(result);
3591
- m.shareCompletedOutput(sections[i].sectionId, result.sectionSummary);
3592
- if (result.status === "ESCALATED") {
3593
- this.escalations.push({
3594
- raisedBy: `T2_${sections[i].sectionId}`,
3595
- sectionId: sections[i].sectionId,
3596
- attempted: result.issues,
3597
- blocker: result.issues.join("; "),
3598
- needs: "Human review required"
3599
- });
3600
- }
3601
- } catch (err) {
3602
- t2Results.push({
3603
- sectionId: sections[i].sectionId,
3604
- sectionTitle: sections[i].sectionTitle,
3605
- status: "FAILED",
3606
- t3Results: [],
3607
- sectionSummary: "",
3608
- issues: [err instanceof Error ? err.message : String(err)]
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
- } else {
3613
- const results = await Promise.allSettled(
3614
- managers.map((m, i) => {
3615
- this.sendStatusUpdate({
3616
- progressPct: pct(i),
3617
- currentAction: `T2 working on: ${sections[i].sectionTitle}`,
3618
- status: "IN_PROGRESS"
3619
- });
3620
- return m.execute(sections[i], this.taskId);
3621
- })
3622
- );
3623
- for (let i = 0; i < results.length; i++) {
3624
- const r = results[i];
3625
- if (r.status === "fulfilled") {
3626
- t2Results.push(r.value);
3627
- managers[i].shareCompletedOutput(sections[i].sectionId, r.value.sectionSummary);
3628
- if (r.value.status === "ESCALATED") {
3629
- this.escalations.push({
3630
- raisedBy: `T2_${sections[i].sectionId}`,
3631
- sectionId: sections[i].sectionId,
3632
- attempted: r.value.issues,
3633
- blocker: r.value.issues.join("; "),
3634
- needs: "Human review required"
3635
- });
3636
- }
3637
- } else {
3638
- t2Results.push({
3639
- sectionId: sections[i].sectionId,
3640
- sectionTitle: sections[i].sectionTitle,
3641
- status: "FAILED",
3642
- t3Results: [],
3643
- sectionSummary: "",
3644
- issues: [r.reason instanceof Error ? r.reason.message : String(r.reason)]
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
- } finally {
3650
- cleanup();
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
- if (platform === "github") {
4103
- return this.executeGitHub(operation, repo, token, input);
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
- throw new Error("Playwright is not installed. Run: npm install playwright && npx playwright install chromium");
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
- switch (action) {
4232
- case "navigate": {
4233
- await page.goto(input["url"], { timeout });
4234
- return `Navigated to ${input["url"]}`;
4235
- }
4236
- case "click": {
4237
- await page.click(input["selector"], { timeout });
4238
- return `Clicked ${input["selector"]}`;
4239
- }
4240
- case "fill": {
4241
- await page.fill(input["selector"], input["value"]);
4242
- return `Filled ${input["selector"]} with value`;
4243
- }
4244
- case "screenshot": {
4245
- const buf = await page.screenshot({ type: "png" });
4246
- return `data:image/png;base64,${buf.toString("base64")}`;
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
- case "extract_text": {
4253
- const text = await page.locator("body").innerText();
4254
- return text.slice(0, 1e4);
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
- case "wait": {
4257
- await page.waitForTimeout(timeout);
4258
- return `Waited ${timeout}ms`;
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
- default:
4261
- throw new Error(`Unknown browser action: ${action}`);
4656
+ return `Browser action "${action}" failed: ${errMsg}`;
4262
4657
  }
4263
4658
  }
4264
4659
  async close() {
4265
- if (this.browser) {
4266
- await this.browser.close();
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, options) {
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 cmdPrefix = language === "python" ? "python3" : "node";
4391
- const fullCmd = `${cmdPrefix} "${filePath}" ${args.map((a) => `"${a}"`).join(" ")}`;
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
- resolve(`Execution failed (${duration}ms):
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
- runError = err;
5593
- throw err;
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.db.prepare(`
6018
- INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
6019
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
6020
- ON CONFLICT(session_id) DO UPDATE SET
6021
- title = excluded.title,
6022
- workspace_path = excluded.workspace_path,
6023
- status = excluded.status,
6024
- updated_at = excluded.updated_at,
6025
- latest_prompt = excluded.latest_prompt,
6026
- is_global = excluded.is_global
6027
- `).run(
6028
- session.sessionId,
6029
- session.title,
6030
- session.workspacePath,
6031
- session.status,
6032
- session.startedAt,
6033
- session.updatedAt,
6034
- session.latestPrompt ?? null,
6035
- session.isGlobal ? 1 : 0
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.db.prepare(`
6055
- INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
6056
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6057
- ON CONFLICT(tier_id) DO UPDATE SET
6058
- session_id = excluded.session_id,
6059
- parent_id = excluded.parent_id,
6060
- role = excluded.role,
6061
- label = excluded.label,
6062
- status = excluded.status,
6063
- current_action = excluded.current_action,
6064
- progress_pct = excluded.progress_pct,
6065
- updated_at = excluded.updated_at,
6066
- workspace_path = excluded.workspace_path,
6067
- is_global = excluded.is_global
6068
- `).run(
6069
- node.tierId,
6070
- node.sessionId,
6071
- node.parentId ?? null,
6072
- node.role,
6073
- node.label,
6074
- node.status,
6075
- node.currentAction ?? null,
6076
- node.progressPct ?? null,
6077
- node.updatedAt,
6078
- node.workspacePath ?? null,
6079
- node.isGlobal ? 1 : 0
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.db.prepare(`
6104
- INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
6105
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6106
- `).run(
6107
- log.id,
6108
- log.sessionId,
6109
- log.tierId,
6110
- log.role,
6111
- log.label,
6112
- log.status,
6113
- log.currentAction ?? null,
6114
- log.progressPct ?? null,
6115
- log.timestamp,
6116
- log.workspacePath ?? null,
6117
- log.isGlobal ? 1 : 0
6118
- );
6119
- this.db.prepare(`
6120
- DELETE FROM runtime_node_logs
6121
- WHERE id NOT IN (
6122
- SELECT id FROM runtime_node_logs
6123
- ORDER BY timestamp DESC
6124
- LIMIT 2000
6125
- )
6126
- `).run();
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.db.prepare(`
6165
- INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
6166
- VALUES (?, ?, ?, ?, ?, ?, ?)
6167
- `).run(
6168
- message.id,
6169
- message.sessionId,
6170
- message.role,
6171
- typeof message.content === "string" ? message.content : JSON.stringify(message.content),
6172
- message.timestamp,
6173
- message.tokens ? JSON.stringify(message.tokens) : null,
6174
- message.agentMessages ? JSON.stringify(message.agentMessages) : null
6175
- );
6176
- this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
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.db.prepare(`
6274
- INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
6275
- VALUES (?, ?, ?, ?, ?, ?)
6276
- `).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
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.db.prepare(`
6292
- INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
6293
- VALUES (?, ?, ?, ?, ?)
6294
- `).run(randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
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(os.homedir(), GLOBAL_CONFIG_DIR);
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(process.env["HOME"] ?? process.cwd(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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(process.env["HOME"] ?? process.cwd(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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(process.env["HOME"] ?? process.cwd(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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(process.env["HOME"] ?? process.cwd(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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(process.env["HOME"] ?? process.cwd(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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(process.env["HOME"] ?? process.cwd(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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