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/index.d.cts
CHANGED
|
@@ -188,6 +188,13 @@ interface T3SubtaskSpec {
|
|
|
188
188
|
peerT3Ids: string[];
|
|
189
189
|
dependsOn?: string[];
|
|
190
190
|
executionMode?: 'parallel' | 'sequential';
|
|
191
|
+
/** Spec slice: the exact file paths this subtask creates or edits. */
|
|
192
|
+
files?: string[];
|
|
193
|
+
/** Objectively verifiable acceptance checks for the subtask's output. */
|
|
194
|
+
acceptance?: string[];
|
|
195
|
+
/** The ONLY background the worker needs — workers see nothing else, so the
|
|
196
|
+
* planner must make it self-sufficient (keeps worker context minimal). */
|
|
197
|
+
contextBrief?: string;
|
|
191
198
|
}
|
|
192
199
|
interface T2ToT3Assignment {
|
|
193
200
|
subtaskId: string;
|
|
@@ -200,6 +207,10 @@ interface T2ToT3Assignment {
|
|
|
200
207
|
sectionTitle?: string;
|
|
201
208
|
dependsOn?: string[];
|
|
202
209
|
executionMode?: 'parallel' | 'sequential';
|
|
210
|
+
/** Spec slice fields — see T3SubtaskSpec. */
|
|
211
|
+
files?: string[];
|
|
212
|
+
acceptance?: string[];
|
|
213
|
+
contextBrief?: string;
|
|
203
214
|
}
|
|
204
215
|
interface StatusUpdate {
|
|
205
216
|
progressPct: number;
|
|
@@ -1557,11 +1568,19 @@ declare abstract class BaseTier extends EventEmitter {
|
|
|
1557
1568
|
* which would interleave, are not tagged.
|
|
1558
1569
|
*/
|
|
1559
1570
|
protected isPresenter: boolean;
|
|
1571
|
+
/**
|
|
1572
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
1573
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
1574
|
+
* which node (Cockpit node panel / Why panel).
|
|
1575
|
+
*/
|
|
1576
|
+
protected servingModel?: string;
|
|
1560
1577
|
constructor(role: TierRole, id?: string, parentId?: string);
|
|
1561
1578
|
/** Mark this tier as the run's presenter (root tier). */
|
|
1562
1579
|
setPresenter(on?: boolean): void;
|
|
1563
1580
|
getStatus(): TierStatus;
|
|
1564
1581
|
protected setStatus(status: TierStatus, output?: string): void;
|
|
1582
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
1583
|
+
protected setServingModel(model: string | undefined): void;
|
|
1565
1584
|
protected setLabel(label: string): void;
|
|
1566
1585
|
setSystemPromptOverride(prompt: string): void;
|
|
1567
1586
|
setHierarchyContext(context: string): void;
|
|
@@ -1908,7 +1927,18 @@ declare class Cascade extends EventEmitter {
|
|
|
1908
1927
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
1909
1928
|
* Simple/Moderate) don't get over-escalated.
|
|
1910
1929
|
*/
|
|
1930
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
1931
|
+
private buildSignals;
|
|
1932
|
+
/**
|
|
1933
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
1934
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
1935
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
1936
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
1937
|
+
* × workers for a task one worker handles).
|
|
1938
|
+
*/
|
|
1911
1939
|
private looksClearlyComplex;
|
|
1940
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
1941
|
+
private looksLikeModerateBuild;
|
|
1912
1942
|
private static globCache;
|
|
1913
1943
|
private countWorkspaceFiles;
|
|
1914
1944
|
private determineComplexity;
|
package/dist/index.d.ts
CHANGED
|
@@ -188,6 +188,13 @@ interface T3SubtaskSpec {
|
|
|
188
188
|
peerT3Ids: string[];
|
|
189
189
|
dependsOn?: string[];
|
|
190
190
|
executionMode?: 'parallel' | 'sequential';
|
|
191
|
+
/** Spec slice: the exact file paths this subtask creates or edits. */
|
|
192
|
+
files?: string[];
|
|
193
|
+
/** Objectively verifiable acceptance checks for the subtask's output. */
|
|
194
|
+
acceptance?: string[];
|
|
195
|
+
/** The ONLY background the worker needs — workers see nothing else, so the
|
|
196
|
+
* planner must make it self-sufficient (keeps worker context minimal). */
|
|
197
|
+
contextBrief?: string;
|
|
191
198
|
}
|
|
192
199
|
interface T2ToT3Assignment {
|
|
193
200
|
subtaskId: string;
|
|
@@ -200,6 +207,10 @@ interface T2ToT3Assignment {
|
|
|
200
207
|
sectionTitle?: string;
|
|
201
208
|
dependsOn?: string[];
|
|
202
209
|
executionMode?: 'parallel' | 'sequential';
|
|
210
|
+
/** Spec slice fields — see T3SubtaskSpec. */
|
|
211
|
+
files?: string[];
|
|
212
|
+
acceptance?: string[];
|
|
213
|
+
contextBrief?: string;
|
|
203
214
|
}
|
|
204
215
|
interface StatusUpdate {
|
|
205
216
|
progressPct: number;
|
|
@@ -1557,11 +1568,19 @@ declare abstract class BaseTier extends EventEmitter {
|
|
|
1557
1568
|
* which would interleave, are not tagged.
|
|
1558
1569
|
*/
|
|
1559
1570
|
protected isPresenter: boolean;
|
|
1571
|
+
/**
|
|
1572
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
1573
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
1574
|
+
* which node (Cockpit node panel / Why panel).
|
|
1575
|
+
*/
|
|
1576
|
+
protected servingModel?: string;
|
|
1560
1577
|
constructor(role: TierRole, id?: string, parentId?: string);
|
|
1561
1578
|
/** Mark this tier as the run's presenter (root tier). */
|
|
1562
1579
|
setPresenter(on?: boolean): void;
|
|
1563
1580
|
getStatus(): TierStatus;
|
|
1564
1581
|
protected setStatus(status: TierStatus, output?: string): void;
|
|
1582
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
1583
|
+
protected setServingModel(model: string | undefined): void;
|
|
1565
1584
|
protected setLabel(label: string): void;
|
|
1566
1585
|
setSystemPromptOverride(prompt: string): void;
|
|
1567
1586
|
setHierarchyContext(context: string): void;
|
|
@@ -1908,7 +1927,18 @@ declare class Cascade extends EventEmitter {
|
|
|
1908
1927
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
1909
1928
|
* Simple/Moderate) don't get over-escalated.
|
|
1910
1929
|
*/
|
|
1930
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
1931
|
+
private buildSignals;
|
|
1932
|
+
/**
|
|
1933
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
1934
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
1935
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
1936
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
1937
|
+
* × workers for a task one worker handles).
|
|
1938
|
+
*/
|
|
1911
1939
|
private looksClearlyComplex;
|
|
1940
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
1941
|
+
private looksLikeModerateBuild;
|
|
1912
1942
|
private static globCache;
|
|
1913
1943
|
private countWorkspaceFiles;
|
|
1914
1944
|
private determineComplexity;
|
package/dist/index.js
CHANGED
|
@@ -190,7 +190,7 @@ var init_audit_logger = __esm({
|
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// src/constants.ts
|
|
193
|
-
var CASCADE_VERSION = "0.
|
|
193
|
+
var CASCADE_VERSION = "0.19.0";
|
|
194
194
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
195
195
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
196
196
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -993,6 +993,23 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
993
993
|
};
|
|
994
994
|
|
|
995
995
|
// src/providers/azure.ts
|
|
996
|
+
function azureModelForDeployment(cfg) {
|
|
997
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
998
|
+
const id = cfg.deploymentName.trim();
|
|
999
|
+
return {
|
|
1000
|
+
id,
|
|
1001
|
+
name: cfg.label?.trim() || id,
|
|
1002
|
+
provider: "azure",
|
|
1003
|
+
contextWindow: 128e3,
|
|
1004
|
+
isVisionCapable: false,
|
|
1005
|
+
inputCostPer1kTokens: 25e-4,
|
|
1006
|
+
outputCostPer1kTokens: 0.01,
|
|
1007
|
+
maxOutputTokens: 16e3,
|
|
1008
|
+
supportsStreaming: true,
|
|
1009
|
+
isLocal: false,
|
|
1010
|
+
supportsToolUse: true
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
996
1013
|
var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
997
1014
|
constructor(config, model) {
|
|
998
1015
|
const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -1013,7 +1030,8 @@ var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
|
1013
1030
|
});
|
|
1014
1031
|
}
|
|
1015
1032
|
async listModels() {
|
|
1016
|
-
|
|
1033
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
1034
|
+
return [fromDeployment ?? this.model];
|
|
1017
1035
|
}
|
|
1018
1036
|
async isAvailable() {
|
|
1019
1037
|
try {
|
|
@@ -2522,6 +2540,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
2522
2540
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
2523
2541
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
2524
2542
|
}
|
|
2543
|
+
if (availableProviders.has("azure")) {
|
|
2544
|
+
for (const cfg of config.providers) {
|
|
2545
|
+
const model = azureModelForDeployment(cfg);
|
|
2546
|
+
if (model) this.selector.addDynamicModel(model);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2525
2549
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
2526
2550
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
2527
2551
|
if (!override || override === "auto") continue;
|
|
@@ -3128,7 +3152,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
3128
3152
|
ensureProvider(model, configs) {
|
|
3129
3153
|
const key = `${model.provider}:${model.id}`;
|
|
3130
3154
|
if (this.providers.has(key)) return;
|
|
3131
|
-
const cfg = configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
3155
|
+
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 };
|
|
3132
3156
|
const provider = this.createProvider(cfg, model);
|
|
3133
3157
|
this.providers.set(key, provider);
|
|
3134
3158
|
}
|
|
@@ -3288,6 +3312,12 @@ var BaseTier = class extends EventEmitter {
|
|
|
3288
3312
|
* which would interleave, are not tagged.
|
|
3289
3313
|
*/
|
|
3290
3314
|
isPresenter = false;
|
|
3315
|
+
/**
|
|
3316
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
3317
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
3318
|
+
* which node (Cockpit node panel / Why panel).
|
|
3319
|
+
*/
|
|
3320
|
+
servingModel;
|
|
3291
3321
|
constructor(role, id, parentId) {
|
|
3292
3322
|
super();
|
|
3293
3323
|
this.role = role;
|
|
@@ -3312,11 +3342,16 @@ var BaseTier = class extends EventEmitter {
|
|
|
3312
3342
|
label: this.label,
|
|
3313
3343
|
status,
|
|
3314
3344
|
timestamp,
|
|
3315
|
-
output
|
|
3345
|
+
output,
|
|
3346
|
+
model: this.servingModel
|
|
3316
3347
|
};
|
|
3317
3348
|
this.emit("status", event);
|
|
3318
3349
|
this.emit("tier:status", event);
|
|
3319
3350
|
}
|
|
3351
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
3352
|
+
setServingModel(model) {
|
|
3353
|
+
this.servingModel = model || void 0;
|
|
3354
|
+
}
|
|
3320
3355
|
setLabel(label) {
|
|
3321
3356
|
this.label = label;
|
|
3322
3357
|
}
|
|
@@ -3339,7 +3374,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
3339
3374
|
currentAction: update.currentAction,
|
|
3340
3375
|
progressPct: update.progressPct,
|
|
3341
3376
|
timestamp,
|
|
3342
|
-
output: update.output
|
|
3377
|
+
output: update.output,
|
|
3378
|
+
model: this.servingModel
|
|
3343
3379
|
});
|
|
3344
3380
|
}
|
|
3345
3381
|
buildMessage(type, to, payload) {
|
|
@@ -3705,6 +3741,21 @@ Available tools: ${tools.map((t) => t.name).join(", ")}.
|
|
|
3705
3741
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
3706
3742
|
}
|
|
3707
3743
|
|
|
3744
|
+
// src/utils/truncate.ts
|
|
3745
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
3746
|
+
if (text.length <= maxChars) return text;
|
|
3747
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
3748
|
+
const tailLen = maxChars - headLen;
|
|
3749
|
+
const head = text.slice(0, headLen);
|
|
3750
|
+
const tail = text.slice(-tailLen);
|
|
3751
|
+
const elided = text.length - headLen - tailLen;
|
|
3752
|
+
return `${head}
|
|
3753
|
+
|
|
3754
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
3755
|
+
|
|
3756
|
+
${tail}`;
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3708
3759
|
// src/core/tiers/t3-worker.ts
|
|
3709
3760
|
var CriticalToolError = class extends Error {
|
|
3710
3761
|
constructor(message, toolName) {
|
|
@@ -4012,6 +4063,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
4012
4063
|
} catch {
|
|
4013
4064
|
}
|
|
4014
4065
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
4066
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
4015
4067
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
4016
4068
|
let sentFullTextContract = false;
|
|
4017
4069
|
let textContractSignature = "";
|
|
@@ -4109,7 +4161,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
4109
4161
|
const toolResult = await this.executeTool(tc);
|
|
4110
4162
|
await this.context.addMessage({
|
|
4111
4163
|
role: "tool",
|
|
4112
|
-
content: toolResult,
|
|
4164
|
+
content: truncateForContext(toolResult),
|
|
4113
4165
|
toolCallId: tc.id
|
|
4114
4166
|
});
|
|
4115
4167
|
}
|
|
@@ -4366,15 +4418,17 @@ ${assignment.expectedOutput}`;
|
|
|
4366
4418
|
};
|
|
4367
4419
|
}
|
|
4368
4420
|
requiresArtifact() {
|
|
4421
|
+
if (this.assignment?.files?.length) return true;
|
|
4369
4422
|
const haystack = `${this.assignment?.description ?? ""}
|
|
4370
4423
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
4371
4424
|
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);
|
|
4372
4425
|
}
|
|
4373
4426
|
extractArtifactPaths(assignment) {
|
|
4427
|
+
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
4374
4428
|
const haystack = `${assignment.description}
|
|
4375
4429
|
${assignment.expectedOutput}`;
|
|
4376
4430
|
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) ?? [];
|
|
4377
|
-
return [
|
|
4431
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
4378
4432
|
}
|
|
4379
4433
|
async verifyArtifacts(assignment) {
|
|
4380
4434
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -4497,7 +4551,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4497
4551
|
Assignment: ${assignment.description}
|
|
4498
4552
|
Expected output: ${assignment.expectedOutput}
|
|
4499
4553
|
Constraints: ${assignment.constraints.join("; ")}
|
|
4500
|
-
|
|
4554
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
4555
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
4556
|
+
` : ""}
|
|
4501
4557
|
Output to test:
|
|
4502
4558
|
${output}
|
|
4503
4559
|
|
|
@@ -4586,17 +4642,27 @@ Your subtask:
|
|
|
4586
4642
|
- Title: ${assignment.subtaskTitle}
|
|
4587
4643
|
- Description: ${assignment.description}
|
|
4588
4644
|
- Expected output: ${assignment.expectedOutput}
|
|
4589
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
4645
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
4646
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
4647
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
4590
4648
|
}
|
|
4591
4649
|
buildInitialPrompt(assignment) {
|
|
4592
4650
|
return `Execute the following subtask completely:
|
|
4593
4651
|
|
|
4594
4652
|
**${assignment.subtaskTitle}**
|
|
4595
|
-
|
|
4653
|
+
${assignment.contextBrief ? `
|
|
4654
|
+
Context: ${assignment.contextBrief}
|
|
4655
|
+
` : ""}
|
|
4596
4656
|
${assignment.description}
|
|
4597
4657
|
|
|
4598
4658
|
Expected output: ${assignment.expectedOutput}
|
|
4599
|
-
|
|
4659
|
+
${assignment.files?.length ? `
|
|
4660
|
+
Files you own (create or edit exactly these paths):
|
|
4661
|
+
${assignment.files.map((f) => `- ${f}`).join("\n")}
|
|
4662
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
4663
|
+
Definition of done (your output must satisfy ALL of these):
|
|
4664
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
4665
|
+
` : ""}
|
|
4600
4666
|
Constraints:
|
|
4601
4667
|
${assignment.constraints.map((c) => `- ${c}`).join("\n")}
|
|
4602
4668
|
|
|
@@ -5077,6 +5143,8 @@ var T2Manager = class extends BaseTier {
|
|
|
5077
5143
|
this.assignment = assignment;
|
|
5078
5144
|
this.taskId = taskId;
|
|
5079
5145
|
this.setLabel(assignment.sectionTitle);
|
|
5146
|
+
const m = this.router.getModelForTier("T2");
|
|
5147
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
5080
5148
|
this.setStatus("ACTIVE");
|
|
5081
5149
|
this.sendStatusUpdate({
|
|
5082
5150
|
progressPct: 0,
|
|
@@ -5163,7 +5231,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
5163
5231
|
// ── Private ──────────────────────────────────
|
|
5164
5232
|
async decomposeSection(assignment) {
|
|
5165
5233
|
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");
|
|
5166
|
-
const prompt = `Decompose this section into
|
|
5234
|
+
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).
|
|
5167
5235
|
|
|
5168
5236
|
Section: ${assignment.sectionTitle}
|
|
5169
5237
|
Description: ${assignment.description}
|
|
@@ -5182,6 +5250,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
5182
5250
|
- peerT3Ids: string[] (empty for now)
|
|
5183
5251
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
5184
5252
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
5253
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
5254
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
5255
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
5185
5256
|
|
|
5186
5257
|
Return ONLY the JSON array.`;
|
|
5187
5258
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -5779,6 +5850,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
5779
5850
|
this.signal = signal;
|
|
5780
5851
|
this.taskId = randomUUID();
|
|
5781
5852
|
this.setLabel("Administrator");
|
|
5853
|
+
const m = this.router.getModelForTier("T1");
|
|
5854
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
5782
5855
|
this.setStatus("ACTIVE");
|
|
5783
5856
|
this.taskGoal = userPrompt;
|
|
5784
5857
|
this.sendStatusUpdate({
|
|
@@ -6025,10 +6098,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
6025
6098
|
"description": "Run npm init",
|
|
6026
6099
|
"expectedOutput": "package.json created",
|
|
6027
6100
|
"constraints": [],
|
|
6028
|
-
"dependsOn": []
|
|
6101
|
+
"dependsOn": [],
|
|
6102
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
6103
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
6104
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
6029
6105
|
}]
|
|
6030
6106
|
}, {
|
|
6031
|
-
"sectionId": "s2",
|
|
6107
|
+
"sectionId": "s2",
|
|
6032
6108
|
"sectionTitle": "Write Tests",
|
|
6033
6109
|
"description": "Write tests for the project",
|
|
6034
6110
|
"expectedOutput": "Tests passing",
|
|
@@ -6038,7 +6114,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
6038
6114
|
}]
|
|
6039
6115
|
}
|
|
6040
6116
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
6041
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
6117
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
6118
|
+
|
|
6119
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
6120
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
6121
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
6122
|
+
- "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.
|
|
6123
|
+
- 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.`;
|
|
6042
6124
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
6043
6125
|
const result = await this.router.generate("T1", {
|
|
6044
6126
|
messages,
|
|
@@ -7340,30 +7422,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
7340
7422
|
engine: "tavily"
|
|
7341
7423
|
}));
|
|
7342
7424
|
}
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7425
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
7426
|
+
function unwrapDdgRedirect(href) {
|
|
7427
|
+
try {
|
|
7428
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
7429
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
7430
|
+
const target = url.searchParams.get("uddg");
|
|
7431
|
+
if (target) return decodeURIComponent(target);
|
|
7432
|
+
}
|
|
7433
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
7434
|
+
} catch {
|
|
7435
|
+
return href;
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
function stripTags(html) {
|
|
7439
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
7440
|
+
}
|
|
7441
|
+
function decodeEntities(text) {
|
|
7442
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
7443
|
+
}
|
|
7444
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
7445
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
7446
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
7447
|
+
const results = [];
|
|
7354
7448
|
let m;
|
|
7355
|
-
while ((m =
|
|
7356
|
-
|
|
7449
|
+
while ((m = anchorRe.exec(html)) !== null) {
|
|
7450
|
+
const tag = m[0];
|
|
7451
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7452
|
+
const title = decodeEntities(stripTags(m[1] ?? ""));
|
|
7453
|
+
if (!href || !title) continue;
|
|
7454
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
7455
|
+
}
|
|
7456
|
+
const snippets = [];
|
|
7457
|
+
while ((m = snippetRe.exec(html)) !== null) {
|
|
7458
|
+
snippets.push(decodeEntities(stripTags(m[1] ?? "")));
|
|
7357
7459
|
}
|
|
7358
|
-
|
|
7359
|
-
snippets
|
|
7460
|
+
for (let i = 0; i < results.length; i++) {
|
|
7461
|
+
if (snippets[i]) results[i].snippet = snippets[i];
|
|
7360
7462
|
}
|
|
7361
|
-
return
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7463
|
+
return results;
|
|
7464
|
+
}
|
|
7465
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
7466
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
7467
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
7468
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
7469
|
+
signal: AbortSignal.timeout(1e4)
|
|
7470
|
+
});
|
|
7471
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
7472
|
+
const html = await resp.text();
|
|
7473
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
7474
|
+
return parsed.slice(0, maxResults).map((r) => ({ ...r, engine: `duckduckgo-${variant}` }));
|
|
7367
7475
|
}
|
|
7368
7476
|
var WebSearchTool = class extends BaseTool {
|
|
7369
7477
|
name = "web_search";
|
|
@@ -7422,12 +7530,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
7422
7530
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
7423
7531
|
}
|
|
7424
7532
|
}
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7533
|
+
for (const variant of ["html", "lite"]) {
|
|
7534
|
+
try {
|
|
7535
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
7536
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
7537
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
7538
|
+
} catch (err) {
|
|
7539
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7540
|
+
}
|
|
7431
7541
|
}
|
|
7432
7542
|
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" : "";
|
|
7433
7543
|
return [
|
|
@@ -8371,8 +8481,11 @@ var CascadeConfigSchema = z.object({
|
|
|
8371
8481
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
8372
8482
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
8373
8483
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
8484
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
8485
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
8486
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
8374
8487
|
*/
|
|
8375
|
-
cascadeAuto: z.boolean().default(
|
|
8488
|
+
cascadeAuto: z.boolean().default(true),
|
|
8376
8489
|
/**
|
|
8377
8490
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
8378
8491
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -10085,13 +10198,30 @@ ${last.partialOutput}` : "");
|
|
|
10085
10198
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
10086
10199
|
* Simple/Moderate) don't get over-escalated.
|
|
10087
10200
|
*/
|
|
10088
|
-
|
|
10201
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
10202
|
+
buildSignals(prompt) {
|
|
10089
10203
|
const p = prompt.trim();
|
|
10090
|
-
if (p.length < 24) return false;
|
|
10204
|
+
if (p.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
10091
10205
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
10092
|
-
const
|
|
10206
|
+
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;
|
|
10093
10207
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
10094
|
-
return buildVerb
|
|
10208
|
+
return { buildVerb, scaleCount, multiPart };
|
|
10209
|
+
}
|
|
10210
|
+
/**
|
|
10211
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
10212
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
10213
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
10214
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
10215
|
+
* × workers for a task one worker handles).
|
|
10216
|
+
*/
|
|
10217
|
+
looksClearlyComplex(prompt) {
|
|
10218
|
+
const s = this.buildSignals(prompt);
|
|
10219
|
+
return s.buildVerb && (s.scaleCount >= 2 || s.scaleCount >= 1 && s.multiPart);
|
|
10220
|
+
}
|
|
10221
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
10222
|
+
looksLikeModerateBuild(prompt) {
|
|
10223
|
+
const s = this.buildSignals(prompt);
|
|
10224
|
+
return s.buildVerb && (s.scaleCount >= 1 || s.multiPart);
|
|
10095
10225
|
}
|
|
10096
10226
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
10097
10227
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -10179,6 +10309,9 @@ ${prompt}` : prompt;
|
|
|
10179
10309
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
10180
10310
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
10181
10311
|
verdict = "Complex";
|
|
10312
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
10313
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
10314
|
+
verdict = "Moderate";
|
|
10182
10315
|
} else {
|
|
10183
10316
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
10184
10317
|
}
|