cascade-ai 0.18.0 → 0.19.0
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/cli.cjs +182 -48
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +182 -48
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +180 -47
- package/dist/index.cjs +180 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +180 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/desktop-core.cjs
CHANGED
|
@@ -222595,7 +222595,7 @@ Anthropic.Models = Models2;
|
|
|
222595
222595
|
Anthropic.Beta = Beta;
|
|
222596
222596
|
|
|
222597
222597
|
// src/constants.ts
|
|
222598
|
-
var CASCADE_VERSION = "0.
|
|
222598
|
+
var CASCADE_VERSION = "0.19.0";
|
|
222599
222599
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
222600
222600
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
222601
222601
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -231271,6 +231271,23 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
231271
231271
|
};
|
|
231272
231272
|
|
|
231273
231273
|
// src/providers/azure.ts
|
|
231274
|
+
function azureModelForDeployment(cfg) {
|
|
231275
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
231276
|
+
const id = cfg.deploymentName.trim();
|
|
231277
|
+
return {
|
|
231278
|
+
id,
|
|
231279
|
+
name: cfg.label?.trim() || id,
|
|
231280
|
+
provider: "azure",
|
|
231281
|
+
contextWindow: 128e3,
|
|
231282
|
+
isVisionCapable: false,
|
|
231283
|
+
inputCostPer1kTokens: 25e-4,
|
|
231284
|
+
outputCostPer1kTokens: 0.01,
|
|
231285
|
+
maxOutputTokens: 16e3,
|
|
231286
|
+
supportsStreaming: true,
|
|
231287
|
+
isLocal: false,
|
|
231288
|
+
supportsToolUse: true
|
|
231289
|
+
};
|
|
231290
|
+
}
|
|
231274
231291
|
var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
231275
231292
|
constructor(config2, model) {
|
|
231276
231293
|
const rawUrl = config2.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -231291,7 +231308,8 @@ var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
|
231291
231308
|
});
|
|
231292
231309
|
}
|
|
231293
231310
|
async listModels() {
|
|
231294
|
-
|
|
231311
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
231312
|
+
return [fromDeployment ?? this.model];
|
|
231295
231313
|
}
|
|
231296
231314
|
async isAvailable() {
|
|
231297
231315
|
try {
|
|
@@ -250832,6 +250850,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
250832
250850
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
250833
250851
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
250834
250852
|
}
|
|
250853
|
+
if (availableProviders.has("azure")) {
|
|
250854
|
+
for (const cfg of config2.providers) {
|
|
250855
|
+
const model = azureModelForDeployment(cfg);
|
|
250856
|
+
if (model) this.selector.addDynamicModel(model);
|
|
250857
|
+
}
|
|
250858
|
+
}
|
|
250835
250859
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
250836
250860
|
const override = tier === "T1" ? config2.models.t1 : tier === "T2" ? config2.models.t2 : config2.models.t3;
|
|
250837
250861
|
if (!override || override === "auto") continue;
|
|
@@ -251438,7 +251462,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
251438
251462
|
ensureProvider(model, configs) {
|
|
251439
251463
|
const key = `${model.provider}:${model.id}`;
|
|
251440
251464
|
if (this.providers.has(key)) return;
|
|
251441
|
-
const cfg = configs.find((c4) => c4.type === model.provider) ?? { type: model.provider };
|
|
251465
|
+
const cfg = (model.provider === "azure" ? configs.find((c4) => c4.type === "azure" && c4.deploymentName === model.id) : void 0) ?? configs.find((c4) => c4.type === model.provider) ?? { type: model.provider };
|
|
251442
251466
|
const provider = this.createProvider(cfg, model);
|
|
251443
251467
|
this.providers.set(key, provider);
|
|
251444
251468
|
}
|
|
@@ -251598,6 +251622,12 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
251598
251622
|
* which would interleave, are not tagged.
|
|
251599
251623
|
*/
|
|
251600
251624
|
isPresenter = false;
|
|
251625
|
+
/**
|
|
251626
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
251627
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
251628
|
+
* which node (Cockpit node panel / Why panel).
|
|
251629
|
+
*/
|
|
251630
|
+
servingModel;
|
|
251601
251631
|
constructor(role, id, parentId) {
|
|
251602
251632
|
super();
|
|
251603
251633
|
this.role = role;
|
|
@@ -251622,11 +251652,16 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
251622
251652
|
label: this.label,
|
|
251623
251653
|
status,
|
|
251624
251654
|
timestamp,
|
|
251625
|
-
output
|
|
251655
|
+
output,
|
|
251656
|
+
model: this.servingModel
|
|
251626
251657
|
};
|
|
251627
251658
|
this.emit("status", event);
|
|
251628
251659
|
this.emit("tier:status", event);
|
|
251629
251660
|
}
|
|
251661
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
251662
|
+
setServingModel(model) {
|
|
251663
|
+
this.servingModel = model || void 0;
|
|
251664
|
+
}
|
|
251630
251665
|
setLabel(label) {
|
|
251631
251666
|
this.label = label;
|
|
251632
251667
|
}
|
|
@@ -251649,7 +251684,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
251649
251684
|
currentAction: update.currentAction,
|
|
251650
251685
|
progressPct: update.progressPct,
|
|
251651
251686
|
timestamp,
|
|
251652
|
-
output: update.output
|
|
251687
|
+
output: update.output,
|
|
251688
|
+
model: this.servingModel
|
|
251653
251689
|
});
|
|
251654
251690
|
}
|
|
251655
251691
|
buildMessage(type, to, payload) {
|
|
@@ -252015,6 +252051,21 @@ Available tools: ${tools.map((t4) => t4.name).join(", ")}.
|
|
|
252015
252051
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
252016
252052
|
}
|
|
252017
252053
|
|
|
252054
|
+
// src/utils/truncate.ts
|
|
252055
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
252056
|
+
if (text.length <= maxChars) return text;
|
|
252057
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
252058
|
+
const tailLen = maxChars - headLen;
|
|
252059
|
+
const head = text.slice(0, headLen);
|
|
252060
|
+
const tail = text.slice(-tailLen);
|
|
252061
|
+
const elided = text.length - headLen - tailLen;
|
|
252062
|
+
return `${head}
|
|
252063
|
+
|
|
252064
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
252065
|
+
|
|
252066
|
+
${tail}`;
|
|
252067
|
+
}
|
|
252068
|
+
|
|
252018
252069
|
// src/core/tiers/t3-worker.ts
|
|
252019
252070
|
var CriticalToolError = class extends Error {
|
|
252020
252071
|
constructor(message, toolName) {
|
|
@@ -252322,6 +252373,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
252322
252373
|
} catch {
|
|
252323
252374
|
}
|
|
252324
252375
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
252376
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
252325
252377
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
252326
252378
|
let sentFullTextContract = false;
|
|
252327
252379
|
let textContractSignature = "";
|
|
@@ -252419,7 +252471,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
252419
252471
|
const toolResult = await this.executeTool(tc);
|
|
252420
252472
|
await this.context.addMessage({
|
|
252421
252473
|
role: "tool",
|
|
252422
|
-
content: toolResult,
|
|
252474
|
+
content: truncateForContext(toolResult),
|
|
252423
252475
|
toolCallId: tc.id
|
|
252424
252476
|
});
|
|
252425
252477
|
}
|
|
@@ -252676,15 +252728,17 @@ ${assignment.expectedOutput}`;
|
|
|
252676
252728
|
};
|
|
252677
252729
|
}
|
|
252678
252730
|
requiresArtifact() {
|
|
252731
|
+
if (this.assignment?.files?.length) return true;
|
|
252679
252732
|
const haystack = `${this.assignment?.description ?? ""}
|
|
252680
252733
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
252681
252734
|
return /\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/i.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
252682
252735
|
}
|
|
252683
252736
|
extractArtifactPaths(assignment) {
|
|
252737
|
+
const declared = (assignment.files ?? []).map((f4) => f4.trim()).filter((f4) => f4.includes("."));
|
|
252684
252738
|
const haystack = `${assignment.description}
|
|
252685
252739
|
${assignment.expectedOutput}`;
|
|
252686
252740
|
const matches = haystack.match(/\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/gi) ?? [];
|
|
252687
|
-
return [
|
|
252741
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m3) => m3.trim())])];
|
|
252688
252742
|
}
|
|
252689
252743
|
async verifyArtifacts(assignment) {
|
|
252690
252744
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -252807,7 +252861,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
252807
252861
|
Assignment: ${assignment.description}
|
|
252808
252862
|
Expected output: ${assignment.expectedOutput}
|
|
252809
252863
|
Constraints: ${assignment.constraints.join("; ")}
|
|
252810
|
-
|
|
252864
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
252865
|
+
${assignment.acceptance.map((a2) => `- ${a2}`).join("\n")}
|
|
252866
|
+
` : ""}
|
|
252811
252867
|
Output to test:
|
|
252812
252868
|
${output}
|
|
252813
252869
|
|
|
@@ -252896,17 +252952,27 @@ Your subtask:
|
|
|
252896
252952
|
- Title: ${assignment.subtaskTitle}
|
|
252897
252953
|
- Description: ${assignment.description}
|
|
252898
252954
|
- Expected output: ${assignment.expectedOutput}
|
|
252899
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
252955
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
252956
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
252957
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
252900
252958
|
}
|
|
252901
252959
|
buildInitialPrompt(assignment) {
|
|
252902
252960
|
return `Execute the following subtask completely:
|
|
252903
252961
|
|
|
252904
252962
|
**${assignment.subtaskTitle}**
|
|
252905
|
-
|
|
252963
|
+
${assignment.contextBrief ? `
|
|
252964
|
+
Context: ${assignment.contextBrief}
|
|
252965
|
+
` : ""}
|
|
252906
252966
|
${assignment.description}
|
|
252907
252967
|
|
|
252908
252968
|
Expected output: ${assignment.expectedOutput}
|
|
252909
|
-
|
|
252969
|
+
${assignment.files?.length ? `
|
|
252970
|
+
Files you own (create or edit exactly these paths):
|
|
252971
|
+
${assignment.files.map((f4) => `- ${f4}`).join("\n")}
|
|
252972
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
252973
|
+
Definition of done (your output must satisfy ALL of these):
|
|
252974
|
+
${assignment.acceptance.map((a2) => `- ${a2}`).join("\n")}
|
|
252975
|
+
` : ""}
|
|
252910
252976
|
Constraints:
|
|
252911
252977
|
${assignment.constraints.map((c4) => `- ${c4}`).join("\n")}
|
|
252912
252978
|
|
|
@@ -253387,6 +253453,8 @@ var T2Manager = class extends BaseTier {
|
|
|
253387
253453
|
this.assignment = assignment;
|
|
253388
253454
|
this.taskId = taskId;
|
|
253389
253455
|
this.setLabel(assignment.sectionTitle);
|
|
253456
|
+
const m3 = this.router.getModelForTier("T2");
|
|
253457
|
+
if (m3) this.setServingModel(`${m3.provider}:${m3.id}`);
|
|
253390
253458
|
this.setStatus("ACTIVE");
|
|
253391
253459
|
this.sendStatusUpdate({
|
|
253392
253460
|
progressPct: 0,
|
|
@@ -253473,7 +253541,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
253473
253541
|
// ── Private ──────────────────────────────────
|
|
253474
253542
|
async decomposeSection(assignment) {
|
|
253475
253543
|
const peerPlans = this.peerSyncBuffer.filter((p3) => p3.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p3) => `[Peer ${p3.fromId} Plan]: ${p3.content.sectionTitle} - ${p3.content.subtaskTitles?.join(", ")}`).join("\n");
|
|
253476
|
-
const prompt = `Decompose this section into
|
|
253544
|
+
const prompt = `Decompose this section into 1-4 concrete subtasks for T3 workers \u2014 the FEWEST that fully cover it (one subtask is the correct answer for a small section).
|
|
253477
253545
|
|
|
253478
253546
|
Section: ${assignment.sectionTitle}
|
|
253479
253547
|
Description: ${assignment.description}
|
|
@@ -253492,6 +253560,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
253492
253560
|
- peerT3Ids: string[] (empty for now)
|
|
253493
253561
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
253494
253562
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
253563
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
253564
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
253565
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
253495
253566
|
|
|
253496
253567
|
Return ONLY the JSON array.`;
|
|
253497
253568
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -254089,6 +254160,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
254089
254160
|
this.signal = signal;
|
|
254090
254161
|
this.taskId = crypto4.randomUUID();
|
|
254091
254162
|
this.setLabel("Administrator");
|
|
254163
|
+
const m3 = this.router.getModelForTier("T1");
|
|
254164
|
+
if (m3) this.setServingModel(`${m3.provider}:${m3.id}`);
|
|
254092
254165
|
this.setStatus("ACTIVE");
|
|
254093
254166
|
this.taskGoal = userPrompt;
|
|
254094
254167
|
this.sendStatusUpdate({
|
|
@@ -254335,10 +254408,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
254335
254408
|
"description": "Run npm init",
|
|
254336
254409
|
"expectedOutput": "package.json created",
|
|
254337
254410
|
"constraints": [],
|
|
254338
|
-
"dependsOn": []
|
|
254411
|
+
"dependsOn": [],
|
|
254412
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
254413
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
254414
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
254339
254415
|
}]
|
|
254340
254416
|
}, {
|
|
254341
|
-
"sectionId": "s2",
|
|
254417
|
+
"sectionId": "s2",
|
|
254342
254418
|
"sectionTitle": "Write Tests",
|
|
254343
254419
|
"description": "Write tests for the project",
|
|
254344
254420
|
"expectedOutput": "Tests passing",
|
|
@@ -254348,7 +254424,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
254348
254424
|
}]
|
|
254349
254425
|
}
|
|
254350
254426
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
254351
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
254427
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
254428
|
+
|
|
254429
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
254430
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
254431
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
254432
|
+
- "contextBrief": 1-3 short sentences with the ONLY background the worker needs. It sees nothing else about the task, so make the brief self-sufficient \u2014 but never pad it.
|
|
254433
|
+
- RIGHT-SIZE the plan: use the FEWEST sections and workers that fully cover the task. One section with 1-2 subtasks is the CORRECT plan for a small task; padding a plan with filler sections wastes the user's money.`;
|
|
254352
254434
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
254353
254435
|
const result = await this.router.generate("T1", {
|
|
254354
254436
|
messages,
|
|
@@ -282539,30 +282621,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
282539
282621
|
engine: "tavily"
|
|
282540
282622
|
}));
|
|
282541
282623
|
}
|
|
282542
|
-
|
|
282543
|
-
|
|
282544
|
-
|
|
282545
|
-
|
|
282546
|
-
|
|
282547
|
-
|
|
282548
|
-
|
|
282549
|
-
|
|
282550
|
-
|
|
282551
|
-
|
|
282552
|
-
|
|
282624
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
282625
|
+
function unwrapDdgRedirect(href) {
|
|
282626
|
+
try {
|
|
282627
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
282628
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
282629
|
+
const target = url.searchParams.get("uddg");
|
|
282630
|
+
if (target) return decodeURIComponent(target);
|
|
282631
|
+
}
|
|
282632
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
282633
|
+
} catch {
|
|
282634
|
+
return href;
|
|
282635
|
+
}
|
|
282636
|
+
}
|
|
282637
|
+
function stripTags(html) {
|
|
282638
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
282639
|
+
}
|
|
282640
|
+
function decodeEntities(text) {
|
|
282641
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
282642
|
+
}
|
|
282643
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
282644
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
282645
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
282646
|
+
const results = [];
|
|
282553
282647
|
let m3;
|
|
282554
|
-
while ((m3 =
|
|
282555
|
-
|
|
282648
|
+
while ((m3 = anchorRe.exec(html)) !== null) {
|
|
282649
|
+
const tag = m3[0];
|
|
282650
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
282651
|
+
const title = decodeEntities(stripTags(m3[1] ?? ""));
|
|
282652
|
+
if (!href || !title) continue;
|
|
282653
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
282654
|
+
}
|
|
282655
|
+
const snippets = [];
|
|
282656
|
+
while ((m3 = snippetRe.exec(html)) !== null) {
|
|
282657
|
+
snippets.push(decodeEntities(stripTags(m3[1] ?? "")));
|
|
282556
282658
|
}
|
|
282557
|
-
|
|
282558
|
-
snippets
|
|
282659
|
+
for (let i4 = 0; i4 < results.length; i4++) {
|
|
282660
|
+
if (snippets[i4]) results[i4].snippet = snippets[i4];
|
|
282559
282661
|
}
|
|
282560
|
-
return
|
|
282561
|
-
|
|
282562
|
-
|
|
282563
|
-
|
|
282564
|
-
|
|
282565
|
-
|
|
282662
|
+
return results;
|
|
282663
|
+
}
|
|
282664
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
282665
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
282666
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
282667
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
282668
|
+
signal: AbortSignal.timeout(1e4)
|
|
282669
|
+
});
|
|
282670
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
282671
|
+
const html = await resp.text();
|
|
282672
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
282673
|
+
return parsed.slice(0, maxResults).map((r4) => ({ ...r4, engine: `duckduckgo-${variant}` }));
|
|
282566
282674
|
}
|
|
282567
282675
|
var WebSearchTool = class extends BaseTool {
|
|
282568
282676
|
name = "web_search";
|
|
@@ -282621,12 +282729,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
282621
282729
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
282622
282730
|
}
|
|
282623
282731
|
}
|
|
282624
|
-
|
|
282625
|
-
|
|
282626
|
-
|
|
282627
|
-
|
|
282628
|
-
|
|
282629
|
-
|
|
282732
|
+
for (const variant of ["html", "lite"]) {
|
|
282733
|
+
try {
|
|
282734
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
282735
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
282736
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
282737
|
+
} catch (err) {
|
|
282738
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
282739
|
+
}
|
|
282630
282740
|
}
|
|
282631
282741
|
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" : "";
|
|
282632
282742
|
return [
|
|
@@ -294855,8 +294965,11 @@ var CascadeConfigSchema = external_exports.object({
|
|
|
294855
294965
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
294856
294966
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
294857
294967
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
294968
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
294969
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
294970
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
294858
294971
|
*/
|
|
294859
|
-
cascadeAuto: external_exports.boolean().default(
|
|
294972
|
+
cascadeAuto: external_exports.boolean().default(true),
|
|
294860
294973
|
/**
|
|
294861
294974
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
294862
294975
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -296572,13 +296685,30 @@ ${last3.partialOutput}` : "");
|
|
|
296572
296685
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
296573
296686
|
* Simple/Moderate) don't get over-escalated.
|
|
296574
296687
|
*/
|
|
296575
|
-
|
|
296688
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
296689
|
+
buildSignals(prompt) {
|
|
296576
296690
|
const p3 = prompt.trim();
|
|
296577
|
-
if (p3.length < 24) return false;
|
|
296691
|
+
if (p3.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
296578
296692
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p3);
|
|
296579
|
-
const
|
|
296693
|
+
const scaleCount = (p3.match(/\b(?:app(?:lication)?|system|platform|service|api|backend|frontend|full[- ]?stack|website|dashboard|pipeline|microservices?|database schema|authentication|end[- ]to[- ]end|codebase|project|multiple files|several (?:files|modules|components)|test suite)\b/gi) ?? []).length;
|
|
296580
296694
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p3);
|
|
296581
|
-
return buildVerb
|
|
296695
|
+
return { buildVerb, scaleCount, multiPart };
|
|
296696
|
+
}
|
|
296697
|
+
/**
|
|
296698
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
296699
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
296700
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
296701
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
296702
|
+
* × workers for a task one worker handles).
|
|
296703
|
+
*/
|
|
296704
|
+
looksClearlyComplex(prompt) {
|
|
296705
|
+
const s3 = this.buildSignals(prompt);
|
|
296706
|
+
return s3.buildVerb && (s3.scaleCount >= 2 || s3.scaleCount >= 1 && s3.multiPart);
|
|
296707
|
+
}
|
|
296708
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
296709
|
+
looksLikeModerateBuild(prompt) {
|
|
296710
|
+
const s3 = this.buildSignals(prompt);
|
|
296711
|
+
return s3.buildVerb && (s3.scaleCount >= 1 || s3.multiPart);
|
|
296582
296712
|
}
|
|
296583
296713
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
296584
296714
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -296666,6 +296796,9 @@ ${prompt}` : prompt;
|
|
|
296666
296796
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
296667
296797
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
296668
296798
|
verdict = "Complex";
|
|
296799
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
296800
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
296801
|
+
verdict = "Moderate";
|
|
296669
296802
|
} else {
|
|
296670
296803
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
296671
296804
|
}
|