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/cli.js
CHANGED
|
@@ -59,7 +59,7 @@ var __export = (target, all) => {
|
|
|
59
59
|
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
60
60
|
var init_constants = __esm({
|
|
61
61
|
"src/constants.ts"() {
|
|
62
|
-
CASCADE_VERSION = "0.
|
|
62
|
+
CASCADE_VERSION = "0.19.1";
|
|
63
63
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
64
64
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
65
65
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -885,8 +885,26 @@ var init_openai = __esm({
|
|
|
885
885
|
// src/providers/azure.ts
|
|
886
886
|
var azure_exports = {};
|
|
887
887
|
__export(azure_exports, {
|
|
888
|
-
AzureOpenAIProvider: () => AzureOpenAIProvider
|
|
888
|
+
AzureOpenAIProvider: () => AzureOpenAIProvider,
|
|
889
|
+
azureModelForDeployment: () => azureModelForDeployment
|
|
889
890
|
});
|
|
891
|
+
function azureModelForDeployment(cfg) {
|
|
892
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
893
|
+
const id = cfg.deploymentName.trim();
|
|
894
|
+
return {
|
|
895
|
+
id,
|
|
896
|
+
name: cfg.label?.trim() || id,
|
|
897
|
+
provider: "azure",
|
|
898
|
+
contextWindow: 128e3,
|
|
899
|
+
isVisionCapable: false,
|
|
900
|
+
inputCostPer1kTokens: 25e-4,
|
|
901
|
+
outputCostPer1kTokens: 0.01,
|
|
902
|
+
maxOutputTokens: 16e3,
|
|
903
|
+
supportsStreaming: true,
|
|
904
|
+
isLocal: false,
|
|
905
|
+
supportsToolUse: true
|
|
906
|
+
};
|
|
907
|
+
}
|
|
890
908
|
var AzureOpenAIProvider;
|
|
891
909
|
var init_azure = __esm({
|
|
892
910
|
"src/providers/azure.ts"() {
|
|
@@ -912,17 +930,31 @@ var init_azure = __esm({
|
|
|
912
930
|
});
|
|
913
931
|
}
|
|
914
932
|
async listModels() {
|
|
915
|
-
|
|
933
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
934
|
+
return [fromDeployment ?? this.model];
|
|
916
935
|
}
|
|
917
936
|
async isAvailable() {
|
|
937
|
+
const params = {
|
|
938
|
+
model: this.model.id,
|
|
939
|
+
messages: [{ role: "user", content: "ping" }],
|
|
940
|
+
max_tokens: 1
|
|
941
|
+
};
|
|
918
942
|
try {
|
|
919
|
-
await this.client.chat.completions.create(
|
|
920
|
-
model: this.model.id,
|
|
921
|
-
messages: [{ role: "user", content: "ping" }],
|
|
922
|
-
max_tokens: 1
|
|
923
|
-
});
|
|
943
|
+
await this.client.chat.completions.create(params);
|
|
924
944
|
return true;
|
|
925
|
-
} catch {
|
|
945
|
+
} catch (err) {
|
|
946
|
+
if (err?.message && String(err.message).includes("max_completion_tokens")) {
|
|
947
|
+
try {
|
|
948
|
+
await this.client.chat.completions.create({
|
|
949
|
+
model: this.model.id,
|
|
950
|
+
messages: [{ role: "user", content: "ping" }],
|
|
951
|
+
max_completion_tokens: 1
|
|
952
|
+
});
|
|
953
|
+
return true;
|
|
954
|
+
} catch {
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
926
958
|
return false;
|
|
927
959
|
}
|
|
928
960
|
}
|
|
@@ -2997,8 +3029,11 @@ var CascadeConfigSchema = z.object({
|
|
|
2997
3029
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
2998
3030
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
2999
3031
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
3032
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
3033
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
3034
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
3000
3035
|
*/
|
|
3001
|
-
cascadeAuto: z.boolean().default(
|
|
3036
|
+
cascadeAuto: z.boolean().default(true),
|
|
3002
3037
|
/**
|
|
3003
3038
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
3004
3039
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -3800,6 +3835,10 @@ var ModelSelector = class {
|
|
|
3800
3835
|
actualId = parts.slice(1).join(":");
|
|
3801
3836
|
}
|
|
3802
3837
|
}
|
|
3838
|
+
const registered = this.availableModels.get(actualId);
|
|
3839
|
+
if (registered && (!providerStr || registered.provider === providerStr)) {
|
|
3840
|
+
return registered;
|
|
3841
|
+
}
|
|
3803
3842
|
if (!providerStr) {
|
|
3804
3843
|
const lower = actualId.toLowerCase();
|
|
3805
3844
|
if (lower.includes("claude")) providerStr = "anthropic";
|
|
@@ -4552,6 +4591,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4552
4591
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
4553
4592
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
4554
4593
|
}
|
|
4594
|
+
if (availableProviders.has("azure")) {
|
|
4595
|
+
for (const cfg of config.providers) {
|
|
4596
|
+
const model = azureModelForDeployment(cfg);
|
|
4597
|
+
if (model) this.selector.addDynamicModel(model);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4555
4600
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
4556
4601
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
4557
4602
|
if (!override || override === "auto") continue;
|
|
@@ -5158,7 +5203,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5158
5203
|
ensureProvider(model, configs) {
|
|
5159
5204
|
const key = `${model.provider}:${model.id}`;
|
|
5160
5205
|
if (this.providers.has(key)) return;
|
|
5161
|
-
const cfg = configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
5206
|
+
const cfg = (model.provider === "azure" ? configs.find((c) => c.type === "azure" && c.deploymentName === model.id) : void 0) ?? configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
5162
5207
|
const provider = this.createProvider(cfg, model);
|
|
5163
5208
|
this.providers.set(key, provider);
|
|
5164
5209
|
}
|
|
@@ -5318,6 +5363,12 @@ var BaseTier = class extends EventEmitter {
|
|
|
5318
5363
|
* which would interleave, are not tagged.
|
|
5319
5364
|
*/
|
|
5320
5365
|
isPresenter = false;
|
|
5366
|
+
/**
|
|
5367
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
5368
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
5369
|
+
* which node (Cockpit node panel / Why panel).
|
|
5370
|
+
*/
|
|
5371
|
+
servingModel;
|
|
5321
5372
|
constructor(role, id, parentId) {
|
|
5322
5373
|
super();
|
|
5323
5374
|
this.role = role;
|
|
@@ -5342,11 +5393,16 @@ var BaseTier = class extends EventEmitter {
|
|
|
5342
5393
|
label: this.label,
|
|
5343
5394
|
status,
|
|
5344
5395
|
timestamp,
|
|
5345
|
-
output
|
|
5396
|
+
output,
|
|
5397
|
+
model: this.servingModel
|
|
5346
5398
|
};
|
|
5347
5399
|
this.emit("status", event);
|
|
5348
5400
|
this.emit("tier:status", event);
|
|
5349
5401
|
}
|
|
5402
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
5403
|
+
setServingModel(model) {
|
|
5404
|
+
this.servingModel = model || void 0;
|
|
5405
|
+
}
|
|
5350
5406
|
setLabel(label) {
|
|
5351
5407
|
this.label = label;
|
|
5352
5408
|
}
|
|
@@ -5369,7 +5425,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
5369
5425
|
currentAction: update.currentAction,
|
|
5370
5426
|
progressPct: update.progressPct,
|
|
5371
5427
|
timestamp,
|
|
5372
|
-
output: update.output
|
|
5428
|
+
output: update.output,
|
|
5429
|
+
model: this.servingModel
|
|
5373
5430
|
});
|
|
5374
5431
|
}
|
|
5375
5432
|
buildMessage(type, to, payload) {
|
|
@@ -5736,6 +5793,21 @@ Available tools: ${tools.map((t) => t.name).join(", ")}.
|
|
|
5736
5793
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
5737
5794
|
}
|
|
5738
5795
|
|
|
5796
|
+
// src/utils/truncate.ts
|
|
5797
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
5798
|
+
if (text.length <= maxChars) return text;
|
|
5799
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
5800
|
+
const tailLen = maxChars - headLen;
|
|
5801
|
+
const head = text.slice(0, headLen);
|
|
5802
|
+
const tail = text.slice(-tailLen);
|
|
5803
|
+
const elided = text.length - headLen - tailLen;
|
|
5804
|
+
return `${head}
|
|
5805
|
+
|
|
5806
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
5807
|
+
|
|
5808
|
+
${tail}`;
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5739
5811
|
// src/core/tiers/t3-worker.ts
|
|
5740
5812
|
var CriticalToolError = class extends Error {
|
|
5741
5813
|
constructor(message, toolName) {
|
|
@@ -6043,6 +6115,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
6043
6115
|
} catch {
|
|
6044
6116
|
}
|
|
6045
6117
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
6118
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
6046
6119
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
6047
6120
|
let sentFullTextContract = false;
|
|
6048
6121
|
let textContractSignature = "";
|
|
@@ -6140,7 +6213,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6140
6213
|
const toolResult = await this.executeTool(tc);
|
|
6141
6214
|
await this.context.addMessage({
|
|
6142
6215
|
role: "tool",
|
|
6143
|
-
content: toolResult,
|
|
6216
|
+
content: truncateForContext(toolResult),
|
|
6144
6217
|
toolCallId: tc.id
|
|
6145
6218
|
});
|
|
6146
6219
|
}
|
|
@@ -6397,15 +6470,17 @@ ${assignment.expectedOutput}`;
|
|
|
6397
6470
|
};
|
|
6398
6471
|
}
|
|
6399
6472
|
requiresArtifact() {
|
|
6473
|
+
if (this.assignment?.files?.length) return true;
|
|
6400
6474
|
const haystack = `${this.assignment?.description ?? ""}
|
|
6401
6475
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
6402
6476
|
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);
|
|
6403
6477
|
}
|
|
6404
6478
|
extractArtifactPaths(assignment) {
|
|
6479
|
+
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
6405
6480
|
const haystack = `${assignment.description}
|
|
6406
6481
|
${assignment.expectedOutput}`;
|
|
6407
6482
|
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) ?? [];
|
|
6408
|
-
return [
|
|
6483
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
6409
6484
|
}
|
|
6410
6485
|
async verifyArtifacts(assignment) {
|
|
6411
6486
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -6528,7 +6603,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
6528
6603
|
Assignment: ${assignment.description}
|
|
6529
6604
|
Expected output: ${assignment.expectedOutput}
|
|
6530
6605
|
Constraints: ${assignment.constraints.join("; ")}
|
|
6531
|
-
|
|
6606
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
6607
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
6608
|
+
` : ""}
|
|
6532
6609
|
Output to test:
|
|
6533
6610
|
${output}
|
|
6534
6611
|
|
|
@@ -6617,17 +6694,27 @@ Your subtask:
|
|
|
6617
6694
|
- Title: ${assignment.subtaskTitle}
|
|
6618
6695
|
- Description: ${assignment.description}
|
|
6619
6696
|
- Expected output: ${assignment.expectedOutput}
|
|
6620
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
6697
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
6698
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
6699
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
6621
6700
|
}
|
|
6622
6701
|
buildInitialPrompt(assignment) {
|
|
6623
6702
|
return `Execute the following subtask completely:
|
|
6624
6703
|
|
|
6625
6704
|
**${assignment.subtaskTitle}**
|
|
6626
|
-
|
|
6705
|
+
${assignment.contextBrief ? `
|
|
6706
|
+
Context: ${assignment.contextBrief}
|
|
6707
|
+
` : ""}
|
|
6627
6708
|
${assignment.description}
|
|
6628
6709
|
|
|
6629
6710
|
Expected output: ${assignment.expectedOutput}
|
|
6630
|
-
|
|
6711
|
+
${assignment.files?.length ? `
|
|
6712
|
+
Files you own (create or edit exactly these paths):
|
|
6713
|
+
${assignment.files.map((f) => `- ${f}`).join("\n")}
|
|
6714
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
6715
|
+
Definition of done (your output must satisfy ALL of these):
|
|
6716
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
6717
|
+
` : ""}
|
|
6631
6718
|
Constraints:
|
|
6632
6719
|
${assignment.constraints.map((c) => `- ${c}`).join("\n")}
|
|
6633
6720
|
|
|
@@ -7108,6 +7195,8 @@ var T2Manager = class extends BaseTier {
|
|
|
7108
7195
|
this.assignment = assignment;
|
|
7109
7196
|
this.taskId = taskId;
|
|
7110
7197
|
this.setLabel(assignment.sectionTitle);
|
|
7198
|
+
const m = this.router.getModelForTier("T2");
|
|
7199
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
7111
7200
|
this.setStatus("ACTIVE");
|
|
7112
7201
|
this.sendStatusUpdate({
|
|
7113
7202
|
progressPct: 0,
|
|
@@ -7194,7 +7283,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
7194
7283
|
// ── Private ──────────────────────────────────
|
|
7195
7284
|
async decomposeSection(assignment) {
|
|
7196
7285
|
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");
|
|
7197
|
-
const prompt = `Decompose this section into
|
|
7286
|
+
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).
|
|
7198
7287
|
|
|
7199
7288
|
Section: ${assignment.sectionTitle}
|
|
7200
7289
|
Description: ${assignment.description}
|
|
@@ -7213,6 +7302,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
7213
7302
|
- peerT3Ids: string[] (empty for now)
|
|
7214
7303
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
7215
7304
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
7305
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
7306
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
7307
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
7216
7308
|
|
|
7217
7309
|
Return ONLY the JSON array.`;
|
|
7218
7310
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -7813,6 +7905,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
7813
7905
|
this.signal = signal;
|
|
7814
7906
|
this.taskId = randomUUID();
|
|
7815
7907
|
this.setLabel("Administrator");
|
|
7908
|
+
const m = this.router.getModelForTier("T1");
|
|
7909
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
7816
7910
|
this.setStatus("ACTIVE");
|
|
7817
7911
|
this.taskGoal = userPrompt;
|
|
7818
7912
|
this.sendStatusUpdate({
|
|
@@ -8059,10 +8153,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
8059
8153
|
"description": "Run npm init",
|
|
8060
8154
|
"expectedOutput": "package.json created",
|
|
8061
8155
|
"constraints": [],
|
|
8062
|
-
"dependsOn": []
|
|
8156
|
+
"dependsOn": [],
|
|
8157
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
8158
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
8159
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
8063
8160
|
}]
|
|
8064
8161
|
}, {
|
|
8065
|
-
"sectionId": "s2",
|
|
8162
|
+
"sectionId": "s2",
|
|
8066
8163
|
"sectionTitle": "Write Tests",
|
|
8067
8164
|
"description": "Write tests for the project",
|
|
8068
8165
|
"expectedOutput": "Tests passing",
|
|
@@ -8072,7 +8169,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
8072
8169
|
}]
|
|
8073
8170
|
}
|
|
8074
8171
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
8075
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
8172
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
8173
|
+
|
|
8174
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
8175
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
8176
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
8177
|
+
- "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.
|
|
8178
|
+
- 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.`;
|
|
8076
8179
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
8077
8180
|
const result = await this.router.generate("T1", {
|
|
8078
8181
|
messages,
|
|
@@ -9380,30 +9483,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
9380
9483
|
engine: "tavily"
|
|
9381
9484
|
}));
|
|
9382
9485
|
}
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9486
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
9487
|
+
function unwrapDdgRedirect(href) {
|
|
9488
|
+
try {
|
|
9489
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
9490
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
9491
|
+
const target = url.searchParams.get("uddg");
|
|
9492
|
+
if (target) return decodeURIComponent(target);
|
|
9493
|
+
}
|
|
9494
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
9495
|
+
} catch {
|
|
9496
|
+
return href;
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
function stripTags(html) {
|
|
9500
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
9501
|
+
}
|
|
9502
|
+
function decodeEntities(text) {
|
|
9503
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
9504
|
+
}
|
|
9505
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
9506
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
9507
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
9508
|
+
const results = [];
|
|
9394
9509
|
let m;
|
|
9395
|
-
while ((m =
|
|
9396
|
-
|
|
9510
|
+
while ((m = anchorRe.exec(html)) !== null) {
|
|
9511
|
+
const tag = m[0];
|
|
9512
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
9513
|
+
const title = decodeEntities(stripTags(m[1] ?? ""));
|
|
9514
|
+
if (!href || !title) continue;
|
|
9515
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
9397
9516
|
}
|
|
9398
|
-
|
|
9399
|
-
|
|
9517
|
+
const snippets = [];
|
|
9518
|
+
while ((m = snippetRe.exec(html)) !== null) {
|
|
9519
|
+
snippets.push(decodeEntities(stripTags(m[1] ?? "")));
|
|
9400
9520
|
}
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9521
|
+
for (let i = 0; i < results.length; i++) {
|
|
9522
|
+
if (snippets[i]) results[i].snippet = snippets[i];
|
|
9523
|
+
}
|
|
9524
|
+
return results;
|
|
9525
|
+
}
|
|
9526
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
9527
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
9528
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
9529
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
9530
|
+
signal: AbortSignal.timeout(1e4)
|
|
9531
|
+
});
|
|
9532
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
9533
|
+
const html = await resp.text();
|
|
9534
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
9535
|
+
return parsed.slice(0, maxResults).map((r) => ({ ...r, engine: `duckduckgo-${variant}` }));
|
|
9407
9536
|
}
|
|
9408
9537
|
var WebSearchTool = class extends BaseTool {
|
|
9409
9538
|
name = "web_search";
|
|
@@ -9462,12 +9591,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
9462
9591
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
9463
9592
|
}
|
|
9464
9593
|
}
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9594
|
+
for (const variant of ["html", "lite"]) {
|
|
9595
|
+
try {
|
|
9596
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
9597
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
9598
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
9599
|
+
} catch (err) {
|
|
9600
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9601
|
+
}
|
|
9471
9602
|
}
|
|
9472
9603
|
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" : "";
|
|
9473
9604
|
return [
|
|
@@ -11814,13 +11945,30 @@ ${last.partialOutput}` : "");
|
|
|
11814
11945
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
11815
11946
|
* Simple/Moderate) don't get over-escalated.
|
|
11816
11947
|
*/
|
|
11817
|
-
|
|
11948
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
11949
|
+
buildSignals(prompt) {
|
|
11818
11950
|
const p = prompt.trim();
|
|
11819
|
-
if (p.length < 24) return false;
|
|
11951
|
+
if (p.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
11820
11952
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
11821
|
-
const
|
|
11953
|
+
const scaleCount = (p.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;
|
|
11822
11954
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
11823
|
-
return buildVerb
|
|
11955
|
+
return { buildVerb, scaleCount, multiPart };
|
|
11956
|
+
}
|
|
11957
|
+
/**
|
|
11958
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
11959
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
11960
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
11961
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
11962
|
+
* × workers for a task one worker handles).
|
|
11963
|
+
*/
|
|
11964
|
+
looksClearlyComplex(prompt) {
|
|
11965
|
+
const s = this.buildSignals(prompt);
|
|
11966
|
+
return s.buildVerb && (s.scaleCount >= 2 || s.scaleCount >= 1 && s.multiPart);
|
|
11967
|
+
}
|
|
11968
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
11969
|
+
looksLikeModerateBuild(prompt) {
|
|
11970
|
+
const s = this.buildSignals(prompt);
|
|
11971
|
+
return s.buildVerb && (s.scaleCount >= 1 || s.multiPart);
|
|
11824
11972
|
}
|
|
11825
11973
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
11826
11974
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -11908,6 +12056,9 @@ ${prompt}` : prompt;
|
|
|
11908
12056
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
11909
12057
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
11910
12058
|
verdict = "Complex";
|
|
12059
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
12060
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
12061
|
+
verdict = "Moderate";
|
|
11911
12062
|
} else {
|
|
11912
12063
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
11913
12064
|
}
|