cascade-ai 0.18.0 → 0.19.1
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 +205 -54
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +205 -54
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +203 -53
- package/dist/index.cjs +203 -53
- 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 +203 -53
- 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.1";
|
|
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,17 +231308,31 @@ 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() {
|
|
231315
|
+
const params = {
|
|
231316
|
+
model: this.model.id,
|
|
231317
|
+
messages: [{ role: "user", content: "ping" }],
|
|
231318
|
+
max_tokens: 1
|
|
231319
|
+
};
|
|
231297
231320
|
try {
|
|
231298
|
-
await this.client.chat.completions.create(
|
|
231299
|
-
model: this.model.id,
|
|
231300
|
-
messages: [{ role: "user", content: "ping" }],
|
|
231301
|
-
max_tokens: 1
|
|
231302
|
-
});
|
|
231321
|
+
await this.client.chat.completions.create(params);
|
|
231303
231322
|
return true;
|
|
231304
|
-
} catch {
|
|
231323
|
+
} catch (err) {
|
|
231324
|
+
if (err?.message && String(err.message).includes("max_completion_tokens")) {
|
|
231325
|
+
try {
|
|
231326
|
+
await this.client.chat.completions.create({
|
|
231327
|
+
model: this.model.id,
|
|
231328
|
+
messages: [{ role: "user", content: "ping" }],
|
|
231329
|
+
max_completion_tokens: 1
|
|
231330
|
+
});
|
|
231331
|
+
return true;
|
|
231332
|
+
} catch {
|
|
231333
|
+
return false;
|
|
231334
|
+
}
|
|
231335
|
+
}
|
|
231305
231336
|
return false;
|
|
231306
231337
|
}
|
|
231307
231338
|
}
|
|
@@ -250075,6 +250106,10 @@ var ModelSelector = class {
|
|
|
250075
250106
|
actualId = parts.slice(1).join(":");
|
|
250076
250107
|
}
|
|
250077
250108
|
}
|
|
250109
|
+
const registered = this.availableModels.get(actualId);
|
|
250110
|
+
if (registered && (!providerStr || registered.provider === providerStr)) {
|
|
250111
|
+
return registered;
|
|
250112
|
+
}
|
|
250078
250113
|
if (!providerStr) {
|
|
250079
250114
|
const lower2 = actualId.toLowerCase();
|
|
250080
250115
|
if (lower2.includes("claude")) providerStr = "anthropic";
|
|
@@ -250832,6 +250867,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
250832
250867
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
250833
250868
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
250834
250869
|
}
|
|
250870
|
+
if (availableProviders.has("azure")) {
|
|
250871
|
+
for (const cfg of config2.providers) {
|
|
250872
|
+
const model = azureModelForDeployment(cfg);
|
|
250873
|
+
if (model) this.selector.addDynamicModel(model);
|
|
250874
|
+
}
|
|
250875
|
+
}
|
|
250835
250876
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
250836
250877
|
const override = tier === "T1" ? config2.models.t1 : tier === "T2" ? config2.models.t2 : config2.models.t3;
|
|
250837
250878
|
if (!override || override === "auto") continue;
|
|
@@ -251438,7 +251479,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
251438
251479
|
ensureProvider(model, configs) {
|
|
251439
251480
|
const key = `${model.provider}:${model.id}`;
|
|
251440
251481
|
if (this.providers.has(key)) return;
|
|
251441
|
-
const cfg = configs.find((c4) => c4.type === model.provider) ?? { type: model.provider };
|
|
251482
|
+
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
251483
|
const provider = this.createProvider(cfg, model);
|
|
251443
251484
|
this.providers.set(key, provider);
|
|
251444
251485
|
}
|
|
@@ -251598,6 +251639,12 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
251598
251639
|
* which would interleave, are not tagged.
|
|
251599
251640
|
*/
|
|
251600
251641
|
isPresenter = false;
|
|
251642
|
+
/**
|
|
251643
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
251644
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
251645
|
+
* which node (Cockpit node panel / Why panel).
|
|
251646
|
+
*/
|
|
251647
|
+
servingModel;
|
|
251601
251648
|
constructor(role, id, parentId) {
|
|
251602
251649
|
super();
|
|
251603
251650
|
this.role = role;
|
|
@@ -251622,11 +251669,16 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
251622
251669
|
label: this.label,
|
|
251623
251670
|
status,
|
|
251624
251671
|
timestamp,
|
|
251625
|
-
output
|
|
251672
|
+
output,
|
|
251673
|
+
model: this.servingModel
|
|
251626
251674
|
};
|
|
251627
251675
|
this.emit("status", event);
|
|
251628
251676
|
this.emit("tier:status", event);
|
|
251629
251677
|
}
|
|
251678
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
251679
|
+
setServingModel(model) {
|
|
251680
|
+
this.servingModel = model || void 0;
|
|
251681
|
+
}
|
|
251630
251682
|
setLabel(label) {
|
|
251631
251683
|
this.label = label;
|
|
251632
251684
|
}
|
|
@@ -251649,7 +251701,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
251649
251701
|
currentAction: update.currentAction,
|
|
251650
251702
|
progressPct: update.progressPct,
|
|
251651
251703
|
timestamp,
|
|
251652
|
-
output: update.output
|
|
251704
|
+
output: update.output,
|
|
251705
|
+
model: this.servingModel
|
|
251653
251706
|
});
|
|
251654
251707
|
}
|
|
251655
251708
|
buildMessage(type, to, payload) {
|
|
@@ -252015,6 +252068,21 @@ Available tools: ${tools.map((t4) => t4.name).join(", ")}.
|
|
|
252015
252068
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
252016
252069
|
}
|
|
252017
252070
|
|
|
252071
|
+
// src/utils/truncate.ts
|
|
252072
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
252073
|
+
if (text.length <= maxChars) return text;
|
|
252074
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
252075
|
+
const tailLen = maxChars - headLen;
|
|
252076
|
+
const head = text.slice(0, headLen);
|
|
252077
|
+
const tail = text.slice(-tailLen);
|
|
252078
|
+
const elided = text.length - headLen - tailLen;
|
|
252079
|
+
return `${head}
|
|
252080
|
+
|
|
252081
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
252082
|
+
|
|
252083
|
+
${tail}`;
|
|
252084
|
+
}
|
|
252085
|
+
|
|
252018
252086
|
// src/core/tiers/t3-worker.ts
|
|
252019
252087
|
var CriticalToolError = class extends Error {
|
|
252020
252088
|
constructor(message, toolName) {
|
|
@@ -252322,6 +252390,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
252322
252390
|
} catch {
|
|
252323
252391
|
}
|
|
252324
252392
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
252393
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
252325
252394
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
252326
252395
|
let sentFullTextContract = false;
|
|
252327
252396
|
let textContractSignature = "";
|
|
@@ -252419,7 +252488,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
252419
252488
|
const toolResult = await this.executeTool(tc);
|
|
252420
252489
|
await this.context.addMessage({
|
|
252421
252490
|
role: "tool",
|
|
252422
|
-
content: toolResult,
|
|
252491
|
+
content: truncateForContext(toolResult),
|
|
252423
252492
|
toolCallId: tc.id
|
|
252424
252493
|
});
|
|
252425
252494
|
}
|
|
@@ -252676,15 +252745,17 @@ ${assignment.expectedOutput}`;
|
|
|
252676
252745
|
};
|
|
252677
252746
|
}
|
|
252678
252747
|
requiresArtifact() {
|
|
252748
|
+
if (this.assignment?.files?.length) return true;
|
|
252679
252749
|
const haystack = `${this.assignment?.description ?? ""}
|
|
252680
252750
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
252681
252751
|
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
252752
|
}
|
|
252683
252753
|
extractArtifactPaths(assignment) {
|
|
252754
|
+
const declared = (assignment.files ?? []).map((f4) => f4.trim()).filter((f4) => f4.includes("."));
|
|
252684
252755
|
const haystack = `${assignment.description}
|
|
252685
252756
|
${assignment.expectedOutput}`;
|
|
252686
252757
|
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 [
|
|
252758
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m3) => m3.trim())])];
|
|
252688
252759
|
}
|
|
252689
252760
|
async verifyArtifacts(assignment) {
|
|
252690
252761
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -252807,7 +252878,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
252807
252878
|
Assignment: ${assignment.description}
|
|
252808
252879
|
Expected output: ${assignment.expectedOutput}
|
|
252809
252880
|
Constraints: ${assignment.constraints.join("; ")}
|
|
252810
|
-
|
|
252881
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
252882
|
+
${assignment.acceptance.map((a2) => `- ${a2}`).join("\n")}
|
|
252883
|
+
` : ""}
|
|
252811
252884
|
Output to test:
|
|
252812
252885
|
${output}
|
|
252813
252886
|
|
|
@@ -252896,17 +252969,27 @@ Your subtask:
|
|
|
252896
252969
|
- Title: ${assignment.subtaskTitle}
|
|
252897
252970
|
- Description: ${assignment.description}
|
|
252898
252971
|
- Expected output: ${assignment.expectedOutput}
|
|
252899
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
252972
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
252973
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
252974
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
252900
252975
|
}
|
|
252901
252976
|
buildInitialPrompt(assignment) {
|
|
252902
252977
|
return `Execute the following subtask completely:
|
|
252903
252978
|
|
|
252904
252979
|
**${assignment.subtaskTitle}**
|
|
252905
|
-
|
|
252980
|
+
${assignment.contextBrief ? `
|
|
252981
|
+
Context: ${assignment.contextBrief}
|
|
252982
|
+
` : ""}
|
|
252906
252983
|
${assignment.description}
|
|
252907
252984
|
|
|
252908
252985
|
Expected output: ${assignment.expectedOutput}
|
|
252909
|
-
|
|
252986
|
+
${assignment.files?.length ? `
|
|
252987
|
+
Files you own (create or edit exactly these paths):
|
|
252988
|
+
${assignment.files.map((f4) => `- ${f4}`).join("\n")}
|
|
252989
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
252990
|
+
Definition of done (your output must satisfy ALL of these):
|
|
252991
|
+
${assignment.acceptance.map((a2) => `- ${a2}`).join("\n")}
|
|
252992
|
+
` : ""}
|
|
252910
252993
|
Constraints:
|
|
252911
252994
|
${assignment.constraints.map((c4) => `- ${c4}`).join("\n")}
|
|
252912
252995
|
|
|
@@ -253387,6 +253470,8 @@ var T2Manager = class extends BaseTier {
|
|
|
253387
253470
|
this.assignment = assignment;
|
|
253388
253471
|
this.taskId = taskId;
|
|
253389
253472
|
this.setLabel(assignment.sectionTitle);
|
|
253473
|
+
const m3 = this.router.getModelForTier("T2");
|
|
253474
|
+
if (m3) this.setServingModel(`${m3.provider}:${m3.id}`);
|
|
253390
253475
|
this.setStatus("ACTIVE");
|
|
253391
253476
|
this.sendStatusUpdate({
|
|
253392
253477
|
progressPct: 0,
|
|
@@ -253473,7 +253558,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
253473
253558
|
// ── Private ──────────────────────────────────
|
|
253474
253559
|
async decomposeSection(assignment) {
|
|
253475
253560
|
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
|
|
253561
|
+
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
253562
|
|
|
253478
253563
|
Section: ${assignment.sectionTitle}
|
|
253479
253564
|
Description: ${assignment.description}
|
|
@@ -253492,6 +253577,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
253492
253577
|
- peerT3Ids: string[] (empty for now)
|
|
253493
253578
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
253494
253579
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
253580
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
253581
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
253582
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
253495
253583
|
|
|
253496
253584
|
Return ONLY the JSON array.`;
|
|
253497
253585
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -254089,6 +254177,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
254089
254177
|
this.signal = signal;
|
|
254090
254178
|
this.taskId = crypto4.randomUUID();
|
|
254091
254179
|
this.setLabel("Administrator");
|
|
254180
|
+
const m3 = this.router.getModelForTier("T1");
|
|
254181
|
+
if (m3) this.setServingModel(`${m3.provider}:${m3.id}`);
|
|
254092
254182
|
this.setStatus("ACTIVE");
|
|
254093
254183
|
this.taskGoal = userPrompt;
|
|
254094
254184
|
this.sendStatusUpdate({
|
|
@@ -254335,10 +254425,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
254335
254425
|
"description": "Run npm init",
|
|
254336
254426
|
"expectedOutput": "package.json created",
|
|
254337
254427
|
"constraints": [],
|
|
254338
|
-
"dependsOn": []
|
|
254428
|
+
"dependsOn": [],
|
|
254429
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
254430
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
254431
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
254339
254432
|
}]
|
|
254340
254433
|
}, {
|
|
254341
|
-
"sectionId": "s2",
|
|
254434
|
+
"sectionId": "s2",
|
|
254342
254435
|
"sectionTitle": "Write Tests",
|
|
254343
254436
|
"description": "Write tests for the project",
|
|
254344
254437
|
"expectedOutput": "Tests passing",
|
|
@@ -254348,7 +254441,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
254348
254441
|
}]
|
|
254349
254442
|
}
|
|
254350
254443
|
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
|
|
254444
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
254445
|
+
|
|
254446
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
254447
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
254448
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
254449
|
+
- "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.
|
|
254450
|
+
- 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
254451
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
254353
254452
|
const result = await this.router.generate("T1", {
|
|
254354
254453
|
messages,
|
|
@@ -282539,30 +282638,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
282539
282638
|
engine: "tavily"
|
|
282540
282639
|
}));
|
|
282541
282640
|
}
|
|
282542
|
-
|
|
282543
|
-
|
|
282544
|
-
|
|
282545
|
-
|
|
282546
|
-
|
|
282547
|
-
|
|
282548
|
-
|
|
282549
|
-
|
|
282550
|
-
|
|
282551
|
-
|
|
282552
|
-
|
|
282641
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
282642
|
+
function unwrapDdgRedirect(href) {
|
|
282643
|
+
try {
|
|
282644
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
282645
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
282646
|
+
const target = url.searchParams.get("uddg");
|
|
282647
|
+
if (target) return decodeURIComponent(target);
|
|
282648
|
+
}
|
|
282649
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
282650
|
+
} catch {
|
|
282651
|
+
return href;
|
|
282652
|
+
}
|
|
282653
|
+
}
|
|
282654
|
+
function stripTags(html) {
|
|
282655
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
282656
|
+
}
|
|
282657
|
+
function decodeEntities(text) {
|
|
282658
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
282659
|
+
}
|
|
282660
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
282661
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
282662
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
282663
|
+
const results = [];
|
|
282553
282664
|
let m3;
|
|
282554
|
-
while ((m3 =
|
|
282555
|
-
|
|
282665
|
+
while ((m3 = anchorRe.exec(html)) !== null) {
|
|
282666
|
+
const tag = m3[0];
|
|
282667
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
282668
|
+
const title = decodeEntities(stripTags(m3[1] ?? ""));
|
|
282669
|
+
if (!href || !title) continue;
|
|
282670
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
282556
282671
|
}
|
|
282557
|
-
|
|
282558
|
-
|
|
282672
|
+
const snippets = [];
|
|
282673
|
+
while ((m3 = snippetRe.exec(html)) !== null) {
|
|
282674
|
+
snippets.push(decodeEntities(stripTags(m3[1] ?? "")));
|
|
282559
282675
|
}
|
|
282560
|
-
|
|
282561
|
-
|
|
282562
|
-
|
|
282563
|
-
|
|
282564
|
-
|
|
282565
|
-
|
|
282676
|
+
for (let i4 = 0; i4 < results.length; i4++) {
|
|
282677
|
+
if (snippets[i4]) results[i4].snippet = snippets[i4];
|
|
282678
|
+
}
|
|
282679
|
+
return results;
|
|
282680
|
+
}
|
|
282681
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
282682
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
282683
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
282684
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
282685
|
+
signal: AbortSignal.timeout(1e4)
|
|
282686
|
+
});
|
|
282687
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
282688
|
+
const html = await resp.text();
|
|
282689
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
282690
|
+
return parsed.slice(0, maxResults).map((r4) => ({ ...r4, engine: `duckduckgo-${variant}` }));
|
|
282566
282691
|
}
|
|
282567
282692
|
var WebSearchTool = class extends BaseTool {
|
|
282568
282693
|
name = "web_search";
|
|
@@ -282621,12 +282746,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
282621
282746
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
282622
282747
|
}
|
|
282623
282748
|
}
|
|
282624
|
-
|
|
282625
|
-
|
|
282626
|
-
|
|
282627
|
-
|
|
282628
|
-
|
|
282629
|
-
|
|
282749
|
+
for (const variant of ["html", "lite"]) {
|
|
282750
|
+
try {
|
|
282751
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
282752
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
282753
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
282754
|
+
} catch (err) {
|
|
282755
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
282756
|
+
}
|
|
282630
282757
|
}
|
|
282631
282758
|
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
282759
|
return [
|
|
@@ -294855,8 +294982,11 @@ var CascadeConfigSchema = external_exports.object({
|
|
|
294855
294982
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
294856
294983
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
294857
294984
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
294985
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
294986
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
294987
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
294858
294988
|
*/
|
|
294859
|
-
cascadeAuto: external_exports.boolean().default(
|
|
294989
|
+
cascadeAuto: external_exports.boolean().default(true),
|
|
294860
294990
|
/**
|
|
294861
294991
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
294862
294992
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -296572,13 +296702,30 @@ ${last3.partialOutput}` : "");
|
|
|
296572
296702
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
296573
296703
|
* Simple/Moderate) don't get over-escalated.
|
|
296574
296704
|
*/
|
|
296575
|
-
|
|
296705
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
296706
|
+
buildSignals(prompt) {
|
|
296576
296707
|
const p3 = prompt.trim();
|
|
296577
|
-
if (p3.length < 24) return false;
|
|
296708
|
+
if (p3.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
296578
296709
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p3);
|
|
296579
|
-
const
|
|
296710
|
+
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
296711
|
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
|
|
296712
|
+
return { buildVerb, scaleCount, multiPart };
|
|
296713
|
+
}
|
|
296714
|
+
/**
|
|
296715
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
296716
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
296717
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
296718
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
296719
|
+
* × workers for a task one worker handles).
|
|
296720
|
+
*/
|
|
296721
|
+
looksClearlyComplex(prompt) {
|
|
296722
|
+
const s3 = this.buildSignals(prompt);
|
|
296723
|
+
return s3.buildVerb && (s3.scaleCount >= 2 || s3.scaleCount >= 1 && s3.multiPart);
|
|
296724
|
+
}
|
|
296725
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
296726
|
+
looksLikeModerateBuild(prompt) {
|
|
296727
|
+
const s3 = this.buildSignals(prompt);
|
|
296728
|
+
return s3.buildVerb && (s3.scaleCount >= 1 || s3.multiPart);
|
|
296582
296729
|
}
|
|
296583
296730
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
296584
296731
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -296666,6 +296813,9 @@ ${prompt}` : prompt;
|
|
|
296666
296813
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
296667
296814
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
296668
296815
|
verdict = "Complex";
|
|
296816
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
296817
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
296818
|
+
verdict = "Moderate";
|
|
296669
296819
|
} else {
|
|
296670
296820
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
296671
296821
|
}
|