cascade-ai 0.67.0 → 0.68.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 +923 -361
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +917 -355
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +1583 -1012
- package/dist/index.cjs +887 -316
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +295 -12
- package/dist/index.d.ts +295 -12
- package/dist/index.js +871 -311
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path26, { dirname } from 'path';
|
|
3
|
+
import fs25, { promises, mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import os6 from 'os';
|
|
5
5
|
import crypto4, { randomUUID, randomBytes, createHash, timingSafeEqual, webcrypto } from 'crypto';
|
|
6
6
|
import EventEmitter, { setMaxListeners } from 'events';
|
|
@@ -61,12 +61,12 @@ var init_audit_logger = __esm({
|
|
|
61
61
|
constructor(workspacePath, debugMode = false) {
|
|
62
62
|
this.workspacePath = workspacePath;
|
|
63
63
|
this.debugMode = debugMode;
|
|
64
|
-
const cascadeDir =
|
|
65
|
-
if (!
|
|
66
|
-
|
|
64
|
+
const cascadeDir = path26.join(workspacePath, ".cascade");
|
|
65
|
+
if (!fs25.existsSync(cascadeDir)) {
|
|
66
|
+
fs25.mkdirSync(cascadeDir, { recursive: true });
|
|
67
67
|
}
|
|
68
|
-
this.keyPath =
|
|
69
|
-
this.dbPath =
|
|
68
|
+
this.keyPath = path26.join(cascadeDir, "audit_log.key");
|
|
69
|
+
this.dbPath = path26.join(cascadeDir, "audit_log.db");
|
|
70
70
|
this.initEncryptionKey();
|
|
71
71
|
this.db = new Database(this.dbPath);
|
|
72
72
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -96,11 +96,11 @@ var init_audit_logger = __esm({
|
|
|
96
96
|
return crypto4.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
97
97
|
}
|
|
98
98
|
initEncryptionKey() {
|
|
99
|
-
if (
|
|
100
|
-
this.encryptionKey =
|
|
99
|
+
if (fs25.existsSync(this.keyPath)) {
|
|
100
|
+
this.encryptionKey = fs25.readFileSync(this.keyPath);
|
|
101
101
|
} else {
|
|
102
102
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
103
|
-
|
|
103
|
+
fs25.writeFileSync(this.keyPath, this.encryptionKey);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
encrypt(text) {
|
|
@@ -178,9 +178,9 @@ var init_audit_logger = __esm({
|
|
|
178
178
|
dumpDebugIfNeeded() {
|
|
179
179
|
if (!this.debugMode) return;
|
|
180
180
|
try {
|
|
181
|
-
const dumpPath =
|
|
181
|
+
const dumpPath = path26.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
|
|
182
182
|
const entries = this.getAllLogs();
|
|
183
|
-
|
|
183
|
+
fs25.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
184
184
|
} catch (err) {
|
|
185
185
|
console.error("Failed to dump debug audit logs", err);
|
|
186
186
|
}
|
|
@@ -1862,7 +1862,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
|
|
1864
1864
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1865
|
+
var CASCADE_VERSION = "0.68.0";
|
|
1866
1866
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1867
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1868
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4617,7 +4617,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4617
4617
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4618
4618
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4619
4619
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4620
|
-
var DEFAULT_CACHE_FILE =
|
|
4620
|
+
var DEFAULT_CACHE_FILE = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4621
4621
|
function normalizeModelId2(id) {
|
|
4622
4622
|
let s = id.toLowerCase();
|
|
4623
4623
|
const slash = s.lastIndexOf("/");
|
|
@@ -4764,7 +4764,7 @@ var LiveDataProvider = class {
|
|
|
4764
4764
|
}
|
|
4765
4765
|
async saveCache() {
|
|
4766
4766
|
try {
|
|
4767
|
-
await fs5.mkdir(
|
|
4767
|
+
await fs5.mkdir(path26.dirname(this.opts.cacheFile), { recursive: true });
|
|
4768
4768
|
const cache = {
|
|
4769
4769
|
fetchedAt: this.fetchedAt,
|
|
4770
4770
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -6556,6 +6556,79 @@ function describeProviderError(c, modelId) {
|
|
|
6556
6556
|
}
|
|
6557
6557
|
}
|
|
6558
6558
|
|
|
6559
|
+
// src/core/verification/acceptance.ts
|
|
6560
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
6561
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
6562
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
6563
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
6564
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
6565
|
+
function targetFile(criterion, ownedFiles) {
|
|
6566
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
6567
|
+
if (mentioned.length > 1) return null;
|
|
6568
|
+
if (mentioned.length === 1) {
|
|
6569
|
+
const candidate = mentioned[0];
|
|
6570
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
6571
|
+
}
|
|
6572
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
6573
|
+
}
|
|
6574
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
6575
|
+
const results = [];
|
|
6576
|
+
for (const criterion of criteria) {
|
|
6577
|
+
const text = criterion.trim();
|
|
6578
|
+
if (!text) {
|
|
6579
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6580
|
+
continue;
|
|
6581
|
+
}
|
|
6582
|
+
if (NEGATION.test(text)) {
|
|
6583
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6584
|
+
continue;
|
|
6585
|
+
}
|
|
6586
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
6587
|
+
const wantsExists = EXISTS.test(text);
|
|
6588
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
6589
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
6590
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6591
|
+
continue;
|
|
6592
|
+
}
|
|
6593
|
+
const file = targetFile(text, ownedFiles);
|
|
6594
|
+
if (!file) {
|
|
6595
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6596
|
+
continue;
|
|
6597
|
+
}
|
|
6598
|
+
const stat = await probe.stat(file);
|
|
6599
|
+
if (!stat) {
|
|
6600
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
6601
|
+
continue;
|
|
6602
|
+
}
|
|
6603
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
6604
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
6605
|
+
continue;
|
|
6606
|
+
}
|
|
6607
|
+
if (needle) {
|
|
6608
|
+
const content = await probe.read(file);
|
|
6609
|
+
if (content === null) {
|
|
6610
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6611
|
+
continue;
|
|
6612
|
+
}
|
|
6613
|
+
const found = content.includes(needle);
|
|
6614
|
+
results.push({
|
|
6615
|
+
criterion,
|
|
6616
|
+
verdict: found ? "passed" : "failed",
|
|
6617
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
6618
|
+
});
|
|
6619
|
+
continue;
|
|
6620
|
+
}
|
|
6621
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
6622
|
+
}
|
|
6623
|
+
return results;
|
|
6624
|
+
}
|
|
6625
|
+
function undecided(results) {
|
|
6626
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
6627
|
+
}
|
|
6628
|
+
function failures(results) {
|
|
6629
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
6630
|
+
}
|
|
6631
|
+
|
|
6559
6632
|
// src/core/tiers/t3-worker.ts
|
|
6560
6633
|
var CriticalToolError = class extends Error {
|
|
6561
6634
|
constructor(message, toolName) {
|
|
@@ -6872,7 +6945,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
6872
6945
|
}
|
|
6873
6946
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
6874
6947
|
if (!artifactCheck.ok) {
|
|
6875
|
-
correctionAttempts
|
|
6948
|
+
correctionAttempts++;
|
|
6876
6949
|
issues.push(...artifactCheck.issues);
|
|
6877
6950
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
6878
6951
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -6890,18 +6963,40 @@ Now execute your subtask using this context where relevant.`
|
|
|
6890
6963
|
this.tools.map((t) => t.name)
|
|
6891
6964
|
);
|
|
6892
6965
|
if (visualIssue) {
|
|
6893
|
-
correctionAttempts
|
|
6966
|
+
correctionAttempts++;
|
|
6894
6967
|
issues.push(visualIssue);
|
|
6895
6968
|
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
6896
6969
|
output = await this.correctOutput(output, [visualIssue]);
|
|
6897
6970
|
}
|
|
6971
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
6972
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
6973
|
+
if (acceptanceFailures.length > 0) {
|
|
6974
|
+
correctionAttempts++;
|
|
6975
|
+
issues.push(...acceptanceFailures);
|
|
6976
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
6977
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
6978
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
6979
|
+
if (recheck.length > 0) {
|
|
6980
|
+
issues.push(...recheck);
|
|
6981
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
6982
|
+
failed.push(...recheck);
|
|
6983
|
+
this.setStatus("FAILED");
|
|
6984
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
6985
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
for (const result2 of acceptanceResults) {
|
|
6989
|
+
if (result2.verdict === "undecidable") continue;
|
|
6990
|
+
checksRun.push(result2.criterion);
|
|
6991
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
6992
|
+
}
|
|
6898
6993
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6899
|
-
const testResult = await this.selfTest(assignment, output);
|
|
6994
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
6900
6995
|
checksRun.push(...testResult.checksRun);
|
|
6901
6996
|
passed.push(...testResult.passed);
|
|
6902
6997
|
failed.push(...testResult.failed);
|
|
6903
6998
|
if (testResult.failed.length > 0) {
|
|
6904
|
-
correctionAttempts
|
|
6999
|
+
correctionAttempts++;
|
|
6905
7000
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
6906
7001
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
6907
7002
|
output = corrected;
|
|
@@ -7209,8 +7304,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7209
7304
|
// Media generation can run for a minute; without this a cancelled run
|
|
7210
7305
|
// still pays for an image nobody will see.
|
|
7211
7306
|
...this.signal ? { signal: this.signal } : {},
|
|
7212
|
-
saveSnapshot: async (
|
|
7213
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7307
|
+
saveSnapshot: async (path28, content) => {
|
|
7308
|
+
this.store?.addFileSnapshot(this.taskId, path28, content);
|
|
7214
7309
|
},
|
|
7215
7310
|
sendPeerSync: (to, syncType, content) => {
|
|
7216
7311
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7397,7 +7492,7 @@ ${assignment.expectedOutput}`;
|
|
|
7397
7492
|
const { promisify: promisify4 } = await import('util');
|
|
7398
7493
|
const execAsync2 = promisify4(exec2);
|
|
7399
7494
|
for (const artifactPath of artifactPaths) {
|
|
7400
|
-
const absolutePath =
|
|
7495
|
+
const absolutePath = path26.resolve(process.cwd(), artifactPath);
|
|
7401
7496
|
try {
|
|
7402
7497
|
const stat = await fs5.stat(absolutePath);
|
|
7403
7498
|
if (!stat.isFile()) {
|
|
@@ -7432,7 +7527,7 @@ ${assignment.expectedOutput}`;
|
|
|
7432
7527
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7433
7528
|
continue;
|
|
7434
7529
|
}
|
|
7435
|
-
const ext =
|
|
7530
|
+
const ext = path26.extname(absolutePath).toLowerCase();
|
|
7436
7531
|
try {
|
|
7437
7532
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7438
7533
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -7518,14 +7613,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7518
7613
|
}
|
|
7519
7614
|
return current;
|
|
7520
7615
|
}
|
|
7521
|
-
|
|
7616
|
+
/**
|
|
7617
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
7618
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
7619
|
+
*/
|
|
7620
|
+
async checkAcceptance(assignment) {
|
|
7621
|
+
const criteria = assignment.acceptance ?? [];
|
|
7622
|
+
if (!criteria.length) return [];
|
|
7623
|
+
const resolve2 = (target) => path26.resolve(process.cwd(), target);
|
|
7624
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
7625
|
+
stat: async (target) => {
|
|
7626
|
+
try {
|
|
7627
|
+
const stat = await fs5.stat(resolve2(target));
|
|
7628
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
7629
|
+
} catch {
|
|
7630
|
+
return null;
|
|
7631
|
+
}
|
|
7632
|
+
},
|
|
7633
|
+
read: async (target) => {
|
|
7634
|
+
try {
|
|
7635
|
+
const content = await fs5.readFile(resolve2(target), "utf-8");
|
|
7636
|
+
return content.includes("\uFFFD") ? null : content;
|
|
7637
|
+
} catch {
|
|
7638
|
+
return null;
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
});
|
|
7642
|
+
}
|
|
7643
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
7644
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
7522
7645
|
const prompt = `Self-test this output against the assignment requirements.
|
|
7523
7646
|
|
|
7524
7647
|
Assignment: ${assignment.description}
|
|
7525
7648
|
Expected output: ${assignment.expectedOutput}
|
|
7526
7649
|
Constraints: ${assignment.constraints.join("; ")}
|
|
7527
|
-
${
|
|
7528
|
-
${
|
|
7650
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
7651
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
7529
7652
|
` : ""}
|
|
7530
7653
|
Output to test:
|
|
7531
7654
|
${output}
|
|
@@ -7592,8 +7715,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
7592
7715
|
} catch {
|
|
7593
7716
|
}
|
|
7594
7717
|
}
|
|
7595
|
-
async correctOutput(originalOutput,
|
|
7596
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
7718
|
+
async correctOutput(originalOutput, failures2) {
|
|
7719
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
7597
7720
|
|
|
7598
7721
|
Original output:
|
|
7599
7722
|
${originalOutput}
|
|
@@ -8254,12 +8377,220 @@ function describeGenerationForPlanner(has) {
|
|
|
8254
8377
|
return lines.join("\n");
|
|
8255
8378
|
}
|
|
8256
8379
|
|
|
8380
|
+
// src/core/orchestration/task-graph.ts
|
|
8381
|
+
var TaskGraphValidationError = class extends Error {
|
|
8382
|
+
constructor(issues) {
|
|
8383
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
8384
|
+
this.issues = issues;
|
|
8385
|
+
this.name = "TaskGraphValidationError";
|
|
8386
|
+
}
|
|
8387
|
+
issues;
|
|
8388
|
+
};
|
|
8389
|
+
function normalizeDependencies(input) {
|
|
8390
|
+
if (!input?.length) return [];
|
|
8391
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
8392
|
+
}
|
|
8393
|
+
function cycleNodes(nodes) {
|
|
8394
|
+
const adjacency = new Map(
|
|
8395
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
8396
|
+
);
|
|
8397
|
+
const index = /* @__PURE__ */ new Map();
|
|
8398
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
8399
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
8400
|
+
const stack = [];
|
|
8401
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
8402
|
+
let counter = 0;
|
|
8403
|
+
const open = (id) => {
|
|
8404
|
+
index.set(id, counter);
|
|
8405
|
+
lowlink.set(id, counter);
|
|
8406
|
+
counter++;
|
|
8407
|
+
stack.push(id);
|
|
8408
|
+
onStack.add(id);
|
|
8409
|
+
};
|
|
8410
|
+
for (const root of nodes) {
|
|
8411
|
+
if (index.has(root.id)) continue;
|
|
8412
|
+
open(root.id);
|
|
8413
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
8414
|
+
while (frames.length > 0) {
|
|
8415
|
+
const frame = frames[frames.length - 1];
|
|
8416
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
8417
|
+
if (frame.edge < neighbours.length) {
|
|
8418
|
+
const next = neighbours[frame.edge];
|
|
8419
|
+
frame.edge++;
|
|
8420
|
+
if (!adjacency.has(next)) continue;
|
|
8421
|
+
if (!index.has(next)) {
|
|
8422
|
+
open(next);
|
|
8423
|
+
frames.push({ id: next, edge: 0 });
|
|
8424
|
+
} else if (onStack.has(next)) {
|
|
8425
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
8426
|
+
}
|
|
8427
|
+
continue;
|
|
8428
|
+
}
|
|
8429
|
+
frames.pop();
|
|
8430
|
+
const parent = frames[frames.length - 1];
|
|
8431
|
+
if (parent) {
|
|
8432
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
8433
|
+
}
|
|
8434
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
8435
|
+
const component = [];
|
|
8436
|
+
for (; ; ) {
|
|
8437
|
+
const member = stack.pop();
|
|
8438
|
+
onStack.delete(member);
|
|
8439
|
+
component.push(member);
|
|
8440
|
+
if (member === frame.id) break;
|
|
8441
|
+
}
|
|
8442
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
8443
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
8444
|
+
}
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
return inCycle;
|
|
8448
|
+
}
|
|
8449
|
+
function throwIfStrict(mode, issues) {
|
|
8450
|
+
if (mode === "strict" && issues.length > 0) {
|
|
8451
|
+
throw new TaskGraphValidationError(issues);
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
8455
|
+
const mode = options.mode ?? "strict";
|
|
8456
|
+
const issues = [];
|
|
8457
|
+
if (inputNodes.length === 0) {
|
|
8458
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
8459
|
+
throwIfStrict(mode, issues);
|
|
8460
|
+
}
|
|
8461
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8462
|
+
const nodes = [];
|
|
8463
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
8464
|
+
const input = inputNodes[ordinal];
|
|
8465
|
+
const id = input.id?.trim();
|
|
8466
|
+
const title = input.title?.trim();
|
|
8467
|
+
if (!id || !title) {
|
|
8468
|
+
issues.push({
|
|
8469
|
+
code: "INVALID_NODE",
|
|
8470
|
+
nodeId: id || void 0,
|
|
8471
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
8472
|
+
});
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
if (seen.has(id)) {
|
|
8476
|
+
issues.push({
|
|
8477
|
+
code: "DUPLICATE_NODE_ID",
|
|
8478
|
+
nodeId: id,
|
|
8479
|
+
message: `Duplicate task graph node id: ${id}`
|
|
8480
|
+
});
|
|
8481
|
+
continue;
|
|
8482
|
+
}
|
|
8483
|
+
seen.add(id);
|
|
8484
|
+
nodes.push({
|
|
8485
|
+
id,
|
|
8486
|
+
title,
|
|
8487
|
+
kind: input.kind,
|
|
8488
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
8489
|
+
payload: input.payload,
|
|
8490
|
+
ordinal
|
|
8491
|
+
});
|
|
8492
|
+
}
|
|
8493
|
+
throwIfStrict(mode, issues);
|
|
8494
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
8495
|
+
const sanitized = nodes.map((node) => {
|
|
8496
|
+
const dependsOn = [];
|
|
8497
|
+
for (const dependencyId of node.dependsOn) {
|
|
8498
|
+
if (dependencyId === node.id) {
|
|
8499
|
+
issues.push({
|
|
8500
|
+
code: "SELF_DEPENDENCY",
|
|
8501
|
+
nodeId: node.id,
|
|
8502
|
+
dependencyId,
|
|
8503
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
8504
|
+
});
|
|
8505
|
+
continue;
|
|
8506
|
+
}
|
|
8507
|
+
if (!ids.has(dependencyId)) {
|
|
8508
|
+
issues.push({
|
|
8509
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
8510
|
+
nodeId: node.id,
|
|
8511
|
+
dependencyId,
|
|
8512
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
8513
|
+
});
|
|
8514
|
+
continue;
|
|
8515
|
+
}
|
|
8516
|
+
dependsOn.push(dependencyId);
|
|
8517
|
+
}
|
|
8518
|
+
return { ...node, dependsOn };
|
|
8519
|
+
});
|
|
8520
|
+
throwIfStrict(mode, issues);
|
|
8521
|
+
const cyclic = cycleNodes(sanitized);
|
|
8522
|
+
let repaired = sanitized;
|
|
8523
|
+
if (cyclic.size > 0) {
|
|
8524
|
+
const cycleIssues = [];
|
|
8525
|
+
repaired = sanitized.map((node) => {
|
|
8526
|
+
if (!cyclic.has(node.id)) return node;
|
|
8527
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
8528
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
8529
|
+
cycleIssues.push({
|
|
8530
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
8531
|
+
nodeId: node.id,
|
|
8532
|
+
dependencyId,
|
|
8533
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
8534
|
+
});
|
|
8535
|
+
return false;
|
|
8536
|
+
});
|
|
8537
|
+
return { ...node, dependsOn };
|
|
8538
|
+
});
|
|
8539
|
+
issues.push(...cycleIssues);
|
|
8540
|
+
throwIfStrict(mode, cycleIssues);
|
|
8541
|
+
}
|
|
8542
|
+
const remainingCycle = cycleNodes(repaired);
|
|
8543
|
+
if (remainingCycle.size > 0) {
|
|
8544
|
+
throw new TaskGraphValidationError([
|
|
8545
|
+
{
|
|
8546
|
+
code: "INVALID_NODE",
|
|
8547
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
8548
|
+
}
|
|
8549
|
+
]);
|
|
8550
|
+
}
|
|
8551
|
+
return {
|
|
8552
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
8553
|
+
issues
|
|
8554
|
+
};
|
|
8555
|
+
}
|
|
8556
|
+
|
|
8557
|
+
// src/core/orchestration/adapters.ts
|
|
8558
|
+
function compileSectionGraph(sections) {
|
|
8559
|
+
return compileTaskGraph(
|
|
8560
|
+
sections.map((section) => ({
|
|
8561
|
+
id: section.sectionId,
|
|
8562
|
+
title: section.sectionTitle,
|
|
8563
|
+
kind: "section",
|
|
8564
|
+
dependsOn: section.dependsOn,
|
|
8565
|
+
payload: section
|
|
8566
|
+
})),
|
|
8567
|
+
{ mode: "repair" }
|
|
8568
|
+
);
|
|
8569
|
+
}
|
|
8570
|
+
function compileSubtaskGraph(subtasks) {
|
|
8571
|
+
return compileTaskGraph(
|
|
8572
|
+
subtasks.map((subtask) => ({
|
|
8573
|
+
id: subtask.subtaskId,
|
|
8574
|
+
title: subtask.subtaskTitle,
|
|
8575
|
+
kind: "subtask",
|
|
8576
|
+
dependsOn: subtask.dependsOn,
|
|
8577
|
+
payload: subtask
|
|
8578
|
+
})),
|
|
8579
|
+
{ mode: "repair" }
|
|
8580
|
+
);
|
|
8581
|
+
}
|
|
8582
|
+
|
|
8257
8583
|
// src/core/tiers/t2-manager.ts
|
|
8258
8584
|
function buildT2SystemPrompt(has) {
|
|
8259
8585
|
const generation = describeGenerationForPlanner(has);
|
|
8260
8586
|
return [
|
|
8261
8587
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
8262
|
-
|
|
8588
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
8589
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
8590
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
8591
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
8592
|
+
// real worker call on every small section.
|
|
8593
|
+
"Your role is to analyze a section of a task and decompose it into 1-4 discrete subtasks for T3 Workers \u2014 the fewest that fully cover it.",
|
|
8263
8594
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
8264
8595
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
8265
8596
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -8655,21 +8986,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8655
8986
|
* then reduce in-degrees of their dependents and repeat.
|
|
8656
8987
|
*/
|
|
8657
8988
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
8989
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
8990
|
+
for (const issue of compiled.issues) {
|
|
8991
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
8992
|
+
}
|
|
8993
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
8994
|
+
const assignment = node.payload;
|
|
8995
|
+
const original = assignment.dependsOn ?? [];
|
|
8996
|
+
const repaired = node.dependsOn;
|
|
8997
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
8998
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
8999
|
+
});
|
|
8658
9000
|
const adj = /* @__PURE__ */ new Map();
|
|
8659
9001
|
const inDegree = /* @__PURE__ */ new Map();
|
|
8660
9002
|
const resultMap = /* @__PURE__ */ new Map();
|
|
8661
|
-
for (const a of
|
|
9003
|
+
for (const a of sanitizedAssignments) {
|
|
8662
9004
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
8663
9005
|
inDegree.set(a.subtaskId, 0);
|
|
8664
9006
|
}
|
|
8665
|
-
for (const a of
|
|
8666
|
-
const
|
|
8667
|
-
for (const dep of deps) {
|
|
9007
|
+
for (const a of sanitizedAssignments) {
|
|
9008
|
+
for (const dep of a.dependsOn ?? []) {
|
|
8668
9009
|
adj.get(dep).add(a.subtaskId);
|
|
8669
9010
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
8670
9011
|
}
|
|
8671
9012
|
}
|
|
8672
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
8673
9013
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
8674
9014
|
let wave = 0;
|
|
8675
9015
|
let respawnBudget = 1;
|
|
@@ -8876,49 +9216,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8876
9216
|
}
|
|
8877
9217
|
return [...resultMap.values()];
|
|
8878
9218
|
}
|
|
8879
|
-
/**
|
|
8880
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
8881
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
8882
|
-
*
|
|
8883
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
8884
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
8885
|
-
*/
|
|
8886
|
-
breakCycles(assignments, adj, inDegree) {
|
|
8887
|
-
const degree = new Map(inDegree);
|
|
8888
|
-
const queue = [];
|
|
8889
|
-
const visited = /* @__PURE__ */ new Set();
|
|
8890
|
-
for (const [id, d] of degree) {
|
|
8891
|
-
if (d === 0) queue.push(id);
|
|
8892
|
-
}
|
|
8893
|
-
while (queue.length > 0) {
|
|
8894
|
-
const id = queue.shift();
|
|
8895
|
-
visited.add(id);
|
|
8896
|
-
for (const dep of adj.get(id) ?? []) {
|
|
8897
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
8898
|
-
degree.set(dep, newDeg);
|
|
8899
|
-
if (newDeg === 0) queue.push(dep);
|
|
8900
|
-
}
|
|
8901
|
-
}
|
|
8902
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
8903
|
-
if (cycleNodes.length === 0) return assignments;
|
|
8904
|
-
this.log(
|
|
8905
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
8906
|
-
);
|
|
8907
|
-
return assignments.map((a) => {
|
|
8908
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
8909
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
8910
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
8911
|
-
this.log(
|
|
8912
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
8913
|
-
);
|
|
8914
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
8915
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
8916
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
8917
|
-
}
|
|
8918
|
-
}
|
|
8919
|
-
return { ...a, dependsOn: safeDeps };
|
|
8920
|
-
});
|
|
8921
|
-
}
|
|
8922
9219
|
async retryT3(assignment, taskId) {
|
|
8923
9220
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
8924
9221
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -9054,6 +9351,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
9054
9351
|
}
|
|
9055
9352
|
};
|
|
9056
9353
|
|
|
9354
|
+
// src/core/orchestration/scheduler.ts
|
|
9355
|
+
var DependencyScheduler = class {
|
|
9356
|
+
constructor(graph, hooks, options = {}) {
|
|
9357
|
+
this.graph = graph;
|
|
9358
|
+
this.hooks = hooks;
|
|
9359
|
+
this.options = options;
|
|
9360
|
+
}
|
|
9361
|
+
graph;
|
|
9362
|
+
hooks;
|
|
9363
|
+
options;
|
|
9364
|
+
async run() {
|
|
9365
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
9366
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
9367
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
9368
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
9369
|
+
for (const node of nodes) {
|
|
9370
|
+
for (const dependencyId of node.dependsOn) {
|
|
9371
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
const completed = /* @__PURE__ */ new Set();
|
|
9375
|
+
const results = /* @__PURE__ */ new Map();
|
|
9376
|
+
const waves = [];
|
|
9377
|
+
let wave = 0;
|
|
9378
|
+
while (completed.size < nodes.length) {
|
|
9379
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
9380
|
+
if (ready.length === 0) {
|
|
9381
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
9382
|
+
}
|
|
9383
|
+
waves.push(ready.map((node) => node.id));
|
|
9384
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
9385
|
+
const runOne = async (node) => {
|
|
9386
|
+
const result = await this.hooks.execute(node);
|
|
9387
|
+
results.set(node.id, result);
|
|
9388
|
+
completed.add(node.id);
|
|
9389
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
9390
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9391
|
+
}
|
|
9392
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
9393
|
+
};
|
|
9394
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
9395
|
+
for (const node of ready) await runOne(node);
|
|
9396
|
+
} else {
|
|
9397
|
+
await Promise.all(ready.map(runOne));
|
|
9398
|
+
}
|
|
9399
|
+
wave++;
|
|
9400
|
+
}
|
|
9401
|
+
return { results, waves };
|
|
9402
|
+
}
|
|
9403
|
+
};
|
|
9404
|
+
|
|
9057
9405
|
// src/utils/json-extract.ts
|
|
9058
9406
|
function extractFirstJsonObject(input) {
|
|
9059
9407
|
if (!input) return null;
|
|
@@ -9125,7 +9473,7 @@ function buildT1SystemPrompt(has) {
|
|
|
9125
9473
|
Your responsibilities:
|
|
9126
9474
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
9127
9475
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
9128
|
-
3. For each section, define
|
|
9476
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
9129
9477
|
4. Return a structured plan as JSON
|
|
9130
9478
|
|
|
9131
9479
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -9540,7 +9888,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9540
9888
|
}
|
|
9541
9889
|
}
|
|
9542
9890
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
9543
|
-
if (plan.sections.length < min)
|
|
9891
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
9892
|
+
this.log(
|
|
9893
|
+
`\u26A0 Plan has ${plan.sections.length} section(s) but complexity "${plan.complexity}" expects ${min}-${max}. Keeping the plan as written \u2014 a right-sized plan is better than one padded to hit a floor.`
|
|
9894
|
+
);
|
|
9895
|
+
}
|
|
9544
9896
|
}
|
|
9545
9897
|
async dispatchT2Managers(sections) {
|
|
9546
9898
|
for (const section of sections) {
|
|
@@ -9652,114 +10004,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9652
10004
|
return t2Results;
|
|
9653
10005
|
}
|
|
9654
10006
|
/**
|
|
9655
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
10007
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
10008
|
+
*
|
|
10009
|
+
* Graph construction, validation and cycle repair now live in
|
|
10010
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
10011
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
10012
|
+
* ordered by one implementation rather than two that drifted.
|
|
10013
|
+
*
|
|
10014
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
10015
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
10016
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
10017
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
10018
|
+
* sections and started them immediately — before the sections they consume.
|
|
10019
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
10020
|
+
* members are touched.
|
|
9656
10021
|
*/
|
|
9657
10022
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
9658
|
-
const
|
|
9659
|
-
const
|
|
9660
|
-
|
|
9661
|
-
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
9662
|
-
for (const s of sections) {
|
|
9663
|
-
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
9664
|
-
inDegree.set(s.sectionId, 0);
|
|
9665
|
-
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
10023
|
+
const compiled = compileSectionGraph(sections);
|
|
10024
|
+
for (const issue of compiled.issues) {
|
|
10025
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
9666
10026
|
}
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
}
|
|
9672
|
-
}
|
|
9673
|
-
const queue = [];
|
|
9674
|
-
const degree = new Map(inDegree);
|
|
9675
|
-
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
9676
|
-
const visited = /* @__PURE__ */ new Set();
|
|
9677
|
-
while (queue.length > 0) {
|
|
9678
|
-
const u = queue.shift();
|
|
9679
|
-
visited.add(u);
|
|
9680
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
9681
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
9682
|
-
degree.set(v, newDeg);
|
|
9683
|
-
if (newDeg === 0) queue.push(v);
|
|
9684
|
-
}
|
|
9685
|
-
}
|
|
9686
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
9687
|
-
if (cycleNodes.length > 0) {
|
|
9688
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
9689
|
-
for (const s of sections) {
|
|
9690
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
9691
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
9692
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
9693
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
9694
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
9695
|
-
}
|
|
9696
|
-
s.dependsOn = safeDeps;
|
|
9697
|
-
}
|
|
9698
|
-
}
|
|
10027
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
10028
|
+
for (const section of sections) {
|
|
10029
|
+
const node = byId.get(section.sectionId);
|
|
10030
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
9699
10031
|
}
|
|
9700
|
-
const
|
|
10032
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
10033
|
+
const totalSections = compiled.graph.nodes.length;
|
|
9701
10034
|
let completedSections = 0;
|
|
9702
|
-
const
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
10035
|
+
const scheduler = new DependencyScheduler(
|
|
10036
|
+
compiled.graph,
|
|
10037
|
+
{
|
|
10038
|
+
execute: async (node) => {
|
|
10039
|
+
const section = node.payload;
|
|
10040
|
+
const manager = managers[node.ordinal];
|
|
10041
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
10042
|
+
this.sendStatusUpdate({
|
|
10043
|
+
progressPct,
|
|
10044
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
10045
|
+
status: "IN_PROGRESS"
|
|
10046
|
+
});
|
|
10047
|
+
this.throwIfCancelled();
|
|
10048
|
+
try {
|
|
10049
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
10050
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
10051
|
+
if (result.status === "ESCALATED") {
|
|
10052
|
+
this.escalations.push({
|
|
10053
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
10054
|
+
sectionId: section.sectionId,
|
|
10055
|
+
attempted: result.issues,
|
|
10056
|
+
blocker: result.issues.join("; "),
|
|
10057
|
+
needs: "Human review required"
|
|
10058
|
+
});
|
|
10059
|
+
}
|
|
10060
|
+
return result;
|
|
10061
|
+
} catch (err) {
|
|
10062
|
+
return {
|
|
9729
10063
|
sectionId: section.sectionId,
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
10064
|
+
sectionTitle: section.sectionTitle,
|
|
10065
|
+
status: "FAILED",
|
|
10066
|
+
t3Results: [],
|
|
10067
|
+
sectionSummary: "",
|
|
10068
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
10069
|
+
};
|
|
9734
10070
|
}
|
|
9735
|
-
}
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
resultMap.set(id, result);
|
|
9746
|
-
completedSections++;
|
|
9747
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
9748
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9749
|
-
}
|
|
9750
|
-
};
|
|
9751
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
9752
|
-
for (const id of readyIds) {
|
|
9753
|
-
await runOne(id);
|
|
10071
|
+
},
|
|
10072
|
+
onNodeComplete: (node, result) => {
|
|
10073
|
+
resultMap.set(node.id, result);
|
|
10074
|
+
completedSections++;
|
|
10075
|
+
this.emit("section:complete", {
|
|
10076
|
+
id: node.id,
|
|
10077
|
+
title: result.sectionTitle,
|
|
10078
|
+
status: result.status,
|
|
10079
|
+
output: result.sectionSummary
|
|
10080
|
+
});
|
|
9754
10081
|
}
|
|
9755
|
-
}
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
10082
|
+
},
|
|
10083
|
+
{
|
|
10084
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
10085
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
10086
|
+
// serialized T3 workers within a single section while every section's
|
|
10087
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
10088
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
9760
10089
|
}
|
|
9761
|
-
|
|
9762
|
-
await
|
|
10090
|
+
);
|
|
10091
|
+
await scheduler.run();
|
|
9763
10092
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
9764
10093
|
}
|
|
9765
10094
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -10389,16 +10718,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
10389
10718
|
if (typeof input !== "string" || input.length === 0) {
|
|
10390
10719
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10391
10720
|
}
|
|
10392
|
-
const root =
|
|
10393
|
-
const abs =
|
|
10394
|
-
const rel =
|
|
10395
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
10721
|
+
const root = path26.resolve(workspaceRoot);
|
|
10722
|
+
const abs = path26.isAbsolute(input) ? path26.resolve(input) : path26.resolve(root, input);
|
|
10723
|
+
const rel = path26.relative(root, abs);
|
|
10724
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26.isAbsolute(rel)) {
|
|
10396
10725
|
throw new WorkspaceSandboxError(input, root);
|
|
10397
10726
|
}
|
|
10398
10727
|
try {
|
|
10399
|
-
const real =
|
|
10400
|
-
const realRel =
|
|
10401
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
10728
|
+
const real = fs25.realpathSync(abs);
|
|
10729
|
+
const realRel = path26.relative(root, real);
|
|
10730
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26.isAbsolute(realRel))) {
|
|
10402
10731
|
throw new WorkspaceSandboxError(input, root);
|
|
10403
10732
|
}
|
|
10404
10733
|
} catch (e) {
|
|
@@ -11056,7 +11385,7 @@ var GenerateDocumentTool = class extends BaseTool {
|
|
|
11056
11385
|
}
|
|
11057
11386
|
}
|
|
11058
11387
|
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11059
|
-
await fs5.mkdir(
|
|
11388
|
+
await fs5.mkdir(path26.dirname(absPath), { recursive: true });
|
|
11060
11389
|
await fs5.writeFile(absPath, bytes);
|
|
11061
11390
|
const charts = countCharts(content);
|
|
11062
11391
|
return [
|
|
@@ -11451,7 +11780,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
11451
11780
|
} catch {
|
|
11452
11781
|
}
|
|
11453
11782
|
}
|
|
11454
|
-
await fs5.mkdir(
|
|
11783
|
+
await fs5.mkdir(path26.dirname(absPath), { recursive: true });
|
|
11455
11784
|
await fs5.writeFile(absPath, content, "utf-8");
|
|
11456
11785
|
return `Written ${content.length} characters to ${filePath}`;
|
|
11457
11786
|
}
|
|
@@ -11949,7 +12278,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11949
12278
|
};
|
|
11950
12279
|
async function fileToImageAttachment(filePath) {
|
|
11951
12280
|
const data = await fs5.readFile(filePath);
|
|
11952
|
-
const ext =
|
|
12281
|
+
const ext = path26.extname(filePath).toLowerCase();
|
|
11953
12282
|
const mimeMap = {
|
|
11954
12283
|
".jpg": "image/jpeg",
|
|
11955
12284
|
".jpeg": "image/jpeg",
|
|
@@ -11983,14 +12312,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11983
12312
|
const filePath = input["path"];
|
|
11984
12313
|
const content = input["content"];
|
|
11985
12314
|
const title = input["title"];
|
|
11986
|
-
const dir =
|
|
11987
|
-
if (!
|
|
11988
|
-
|
|
12315
|
+
const dir = path26.dirname(filePath);
|
|
12316
|
+
if (!fs25.existsSync(dir)) {
|
|
12317
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
11989
12318
|
}
|
|
11990
12319
|
return new Promise((resolve2, reject) => {
|
|
11991
12320
|
try {
|
|
11992
12321
|
const doc = new PDFDocument({ margin: 50 });
|
|
11993
|
-
const stream =
|
|
12322
|
+
const stream = fs25.createWriteStream(filePath);
|
|
11994
12323
|
doc.pipe(stream);
|
|
11995
12324
|
if (title) {
|
|
11996
12325
|
doc.info["Title"] = title;
|
|
@@ -12068,22 +12397,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
12068
12397
|
}
|
|
12069
12398
|
cmdPrefix = NODE_CMD;
|
|
12070
12399
|
}
|
|
12071
|
-
const tmpDir =
|
|
12072
|
-
if (!
|
|
12073
|
-
|
|
12400
|
+
const tmpDir = path26.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12401
|
+
if (!fs25.existsSync(tmpDir)) {
|
|
12402
|
+
fs25.mkdirSync(tmpDir, { recursive: true });
|
|
12074
12403
|
}
|
|
12075
12404
|
const extension = language === "python" ? "py" : "js";
|
|
12076
12405
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
12077
|
-
const filePath =
|
|
12078
|
-
|
|
12406
|
+
const filePath = path26.join(tmpDir, fileName);
|
|
12407
|
+
fs25.writeFileSync(filePath, code, "utf-8");
|
|
12079
12408
|
const execArgs = [filePath, ...args];
|
|
12080
12409
|
return new Promise((resolve2) => {
|
|
12081
12410
|
const startMs = Date.now();
|
|
12082
12411
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
12083
12412
|
const duration = Date.now() - startMs;
|
|
12084
12413
|
try {
|
|
12085
|
-
if (
|
|
12086
|
-
|
|
12414
|
+
if (fs25.existsSync(filePath)) {
|
|
12415
|
+
fs25.unlinkSync(filePath);
|
|
12087
12416
|
}
|
|
12088
12417
|
} catch (cleanupErr) {
|
|
12089
12418
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -12395,7 +12724,7 @@ var GlobTool = class extends BaseTool {
|
|
|
12395
12724
|
};
|
|
12396
12725
|
async execute(input, _options) {
|
|
12397
12726
|
const pattern = input["pattern"];
|
|
12398
|
-
const searchPath = input["path"] ?
|
|
12727
|
+
const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
12399
12728
|
const matches = await glob(pattern, {
|
|
12400
12729
|
cwd: searchPath,
|
|
12401
12730
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -12408,7 +12737,7 @@ var GlobTool = class extends BaseTool {
|
|
|
12408
12737
|
const withMtime = await Promise.all(
|
|
12409
12738
|
matches.map(async (rel) => {
|
|
12410
12739
|
try {
|
|
12411
|
-
const stat = await fs5.stat(
|
|
12740
|
+
const stat = await fs5.stat(path26.join(searchPath, rel));
|
|
12412
12741
|
return { rel, mtime: stat.mtimeMs };
|
|
12413
12742
|
} catch {
|
|
12414
12743
|
return { rel, mtime: 0 };
|
|
@@ -12457,7 +12786,7 @@ var GrepTool = class extends BaseTool {
|
|
|
12457
12786
|
};
|
|
12458
12787
|
async execute(input, _options) {
|
|
12459
12788
|
const pattern = input["pattern"];
|
|
12460
|
-
const searchPath = input["path"] ?
|
|
12789
|
+
const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
12461
12790
|
const globPattern = input["glob"];
|
|
12462
12791
|
const outputMode = input["output_mode"] ?? "content";
|
|
12463
12792
|
const context = input["context"] ?? 0;
|
|
@@ -12511,12 +12840,12 @@ var GrepTool = class extends BaseTool {
|
|
|
12511
12840
|
nodir: true
|
|
12512
12841
|
});
|
|
12513
12842
|
} catch {
|
|
12514
|
-
files = [
|
|
12843
|
+
files = [path26.relative(searchPath, searchPath) || "."];
|
|
12515
12844
|
}
|
|
12516
12845
|
const results = [];
|
|
12517
12846
|
let totalCount = 0;
|
|
12518
12847
|
for (const rel of files) {
|
|
12519
|
-
const abs =
|
|
12848
|
+
const abs = path26.join(searchPath, rel);
|
|
12520
12849
|
let content;
|
|
12521
12850
|
try {
|
|
12522
12851
|
content = await fs5.readFile(abs, "utf-8");
|
|
@@ -12903,10 +13232,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
12903
13232
|
}
|
|
12904
13233
|
isIgnored(filePath) {
|
|
12905
13234
|
if (!filePath) return false;
|
|
12906
|
-
const abs =
|
|
12907
|
-
const rel =
|
|
12908
|
-
if (!rel || rel.startsWith("..") ||
|
|
12909
|
-
const posixRel = rel.split(
|
|
13235
|
+
const abs = path26.resolve(this.workspaceRoot, filePath);
|
|
13236
|
+
const rel = path26.relative(this.workspaceRoot, abs);
|
|
13237
|
+
if (!rel || rel.startsWith("..") || path26.isAbsolute(rel)) return true;
|
|
13238
|
+
const posixRel = rel.split(path26.sep).join("/");
|
|
12910
13239
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12911
13240
|
}
|
|
12912
13241
|
};
|
|
@@ -13140,22 +13469,22 @@ var FileMcpOAuthStore = class {
|
|
|
13140
13469
|
filePath;
|
|
13141
13470
|
load() {
|
|
13142
13471
|
try {
|
|
13143
|
-
return JSON.parse(
|
|
13472
|
+
return JSON.parse(fs25.readFileSync(this.filePath, "utf-8"));
|
|
13144
13473
|
} catch {
|
|
13145
13474
|
return void 0;
|
|
13146
13475
|
}
|
|
13147
13476
|
}
|
|
13148
13477
|
save(state) {
|
|
13149
|
-
|
|
13150
|
-
|
|
13478
|
+
fs25.mkdirSync(path26.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13479
|
+
fs25.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
13151
13480
|
try {
|
|
13152
|
-
|
|
13481
|
+
fs25.chmodSync(this.filePath, 384);
|
|
13153
13482
|
} catch {
|
|
13154
13483
|
}
|
|
13155
13484
|
}
|
|
13156
13485
|
clear() {
|
|
13157
13486
|
try {
|
|
13158
|
-
|
|
13487
|
+
fs25.rmSync(this.filePath);
|
|
13159
13488
|
} catch {
|
|
13160
13489
|
}
|
|
13161
13490
|
}
|
|
@@ -14052,6 +14381,14 @@ function heuristicAnalyze(prompt) {
|
|
|
14052
14381
|
const estimatedTokens = wordCount * 5;
|
|
14053
14382
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
14054
14383
|
}
|
|
14384
|
+
function hashPrompt(prompt) {
|
|
14385
|
+
let hash = 2166136261;
|
|
14386
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
14387
|
+
hash ^= prompt.charCodeAt(index);
|
|
14388
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
14389
|
+
}
|
|
14390
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
14391
|
+
}
|
|
14055
14392
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
14056
14393
|
var TASK_TYPE_TAGS = {
|
|
14057
14394
|
code: ["code", "instruction"],
|
|
@@ -14095,7 +14432,7 @@ var TaskAnalyzer = class {
|
|
|
14095
14432
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
14096
14433
|
*/
|
|
14097
14434
|
async analyze(prompt) {
|
|
14098
|
-
const cacheKey = prompt
|
|
14435
|
+
const cacheKey = hashPrompt(prompt);
|
|
14099
14436
|
const cached = analysisCache.get(cacheKey);
|
|
14100
14437
|
if (cached) {
|
|
14101
14438
|
this.lastProfile = cached;
|
|
@@ -14194,7 +14531,7 @@ var TaskAnalyzer = class {
|
|
|
14194
14531
|
analysisCache.clear();
|
|
14195
14532
|
}
|
|
14196
14533
|
};
|
|
14197
|
-
var DEFAULT_STATS_FILE =
|
|
14534
|
+
var DEFAULT_STATS_FILE = path26.join(os6.homedir(), ".cascade", "model-perf.json");
|
|
14198
14535
|
var ModelPerformanceTracker = class {
|
|
14199
14536
|
stats = /* @__PURE__ */ new Map();
|
|
14200
14537
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -14234,7 +14571,7 @@ var ModelPerformanceTracker = class {
|
|
|
14234
14571
|
async save() {
|
|
14235
14572
|
if (this.readOnly) return;
|
|
14236
14573
|
try {
|
|
14237
|
-
await fs5.mkdir(
|
|
14574
|
+
await fs5.mkdir(path26.dirname(this.statsFile), { recursive: true });
|
|
14238
14575
|
const modelsObj = {};
|
|
14239
14576
|
const featuresObj = {};
|
|
14240
14577
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -14782,7 +15119,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14782
15119
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
14783
15120
|
async loadPersistedTools() {
|
|
14784
15121
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14785
|
-
const file =
|
|
15122
|
+
const file = path26.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
14786
15123
|
try {
|
|
14787
15124
|
const raw = await fs5.readFile(file, "utf-8");
|
|
14788
15125
|
const specs = JSON.parse(raw);
|
|
@@ -14806,8 +15143,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14806
15143
|
}
|
|
14807
15144
|
async persist() {
|
|
14808
15145
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14809
|
-
const dir =
|
|
14810
|
-
const file =
|
|
15146
|
+
const dir = path26.join(this.workspacePath, ".cascade");
|
|
15147
|
+
const file = path26.join(dir, DYNAMIC_TOOLS_FILE);
|
|
14811
15148
|
try {
|
|
14812
15149
|
await fs5.mkdir(dir, { recursive: true });
|
|
14813
15150
|
await fs5.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -14827,12 +15164,12 @@ var WorldStateDB = class {
|
|
|
14827
15164
|
constructor(workspacePath, debugMode = false) {
|
|
14828
15165
|
this.workspacePath = workspacePath;
|
|
14829
15166
|
this.debugMode = debugMode;
|
|
14830
|
-
const cascadeDir =
|
|
14831
|
-
if (!
|
|
14832
|
-
|
|
15167
|
+
const cascadeDir = path26.join(workspacePath, ".cascade");
|
|
15168
|
+
if (!fs25.existsSync(cascadeDir)) {
|
|
15169
|
+
fs25.mkdirSync(cascadeDir, { recursive: true });
|
|
14833
15170
|
}
|
|
14834
|
-
this.keyPath =
|
|
14835
|
-
this.dbPath =
|
|
15171
|
+
this.keyPath = path26.join(cascadeDir, "world_state.key");
|
|
15172
|
+
this.dbPath = path26.join(cascadeDir, "world_state.db");
|
|
14836
15173
|
this.initEncryptionKey();
|
|
14837
15174
|
this.db = new Database(this.dbPath);
|
|
14838
15175
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14889,11 +15226,11 @@ var WorldStateDB = class {
|
|
|
14889
15226
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14890
15227
|
}
|
|
14891
15228
|
initEncryptionKey() {
|
|
14892
|
-
if (
|
|
14893
|
-
this.encryptionKey =
|
|
15229
|
+
if (fs25.existsSync(this.keyPath)) {
|
|
15230
|
+
this.encryptionKey = fs25.readFileSync(this.keyPath);
|
|
14894
15231
|
} else {
|
|
14895
15232
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
14896
|
-
|
|
15233
|
+
fs25.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14897
15234
|
}
|
|
14898
15235
|
}
|
|
14899
15236
|
encrypt(text) {
|
|
@@ -15179,9 +15516,9 @@ var WorldStateDB = class {
|
|
|
15179
15516
|
dumpDebugIfNeeded() {
|
|
15180
15517
|
if (!this.debugMode) return;
|
|
15181
15518
|
try {
|
|
15182
|
-
const dumpPath =
|
|
15519
|
+
const dumpPath = path26.join(os6.tmpdir(), "cascade_world_state_debug.json");
|
|
15183
15520
|
const entries = this.getAllEntries();
|
|
15184
|
-
|
|
15521
|
+
fs25.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
15185
15522
|
} catch (err) {
|
|
15186
15523
|
console.error("Failed to dump debug world state", err);
|
|
15187
15524
|
}
|
|
@@ -15190,6 +15527,128 @@ var WorldStateDB = class {
|
|
|
15190
15527
|
this.db.close();
|
|
15191
15528
|
}
|
|
15192
15529
|
};
|
|
15530
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
15531
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
15532
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
15533
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
15534
|
+
function safeId(taskId) {
|
|
15535
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
15536
|
+
return cleaned.slice(0, 100) || "run";
|
|
15537
|
+
}
|
|
15538
|
+
function isCheckpoint(value) {
|
|
15539
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15540
|
+
const c = value;
|
|
15541
|
+
return c.version === RESUME_SCHEMA_VERSION && typeof c.taskId === "string" && typeof c.prompt === "string" && typeof c.createdAt === "string" && typeof c.partialOutput === "string" && Array.isArray(c.completed);
|
|
15542
|
+
}
|
|
15543
|
+
var ResumeStore = class {
|
|
15544
|
+
dir;
|
|
15545
|
+
maxCheckpoints;
|
|
15546
|
+
maxAgeMs;
|
|
15547
|
+
now;
|
|
15548
|
+
constructor(options) {
|
|
15549
|
+
this.dir = options.dir;
|
|
15550
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
15551
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
15552
|
+
this.now = options.now ?? Date.now;
|
|
15553
|
+
}
|
|
15554
|
+
/**
|
|
15555
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
15556
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
15557
|
+
* worse outcome than the one this store exists to improve.
|
|
15558
|
+
*/
|
|
15559
|
+
async save(checkpoint) {
|
|
15560
|
+
try {
|
|
15561
|
+
const timestamp = this.now();
|
|
15562
|
+
const record = {
|
|
15563
|
+
...checkpoint,
|
|
15564
|
+
version: RESUME_SCHEMA_VERSION,
|
|
15565
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
15566
|
+
};
|
|
15567
|
+
await promises.mkdir(this.dir, { recursive: true });
|
|
15568
|
+
const finalPath = path26.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
15569
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
15570
|
+
await promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
15571
|
+
await promises.rename(tempPath, finalPath);
|
|
15572
|
+
await this.prune();
|
|
15573
|
+
} catch {
|
|
15574
|
+
}
|
|
15575
|
+
}
|
|
15576
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
15577
|
+
async list() {
|
|
15578
|
+
let entries;
|
|
15579
|
+
try {
|
|
15580
|
+
entries = await promises.readdir(this.dir);
|
|
15581
|
+
} catch {
|
|
15582
|
+
return [];
|
|
15583
|
+
}
|
|
15584
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15585
|
+
const candidates = entries.map((name) => ({ name, match: FILE_PATTERN.exec(name) })).filter((e) => e.match !== null).map((e) => ({ name: e.name, timestamp: Number(e.match[1]) })).filter((e) => e.timestamp >= cutoff).sort((a, b) => b.timestamp - a.timestamp);
|
|
15586
|
+
const checkpoints = [];
|
|
15587
|
+
for (const candidate of candidates) {
|
|
15588
|
+
try {
|
|
15589
|
+
const raw = await promises.readFile(path26.join(this.dir, candidate.name), "utf-8");
|
|
15590
|
+
const parsed = JSON.parse(raw);
|
|
15591
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
15592
|
+
} catch {
|
|
15593
|
+
continue;
|
|
15594
|
+
}
|
|
15595
|
+
}
|
|
15596
|
+
return checkpoints;
|
|
15597
|
+
}
|
|
15598
|
+
/** The most recent resumable run, or null. */
|
|
15599
|
+
async latest() {
|
|
15600
|
+
return (await this.list())[0] ?? null;
|
|
15601
|
+
}
|
|
15602
|
+
/**
|
|
15603
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
15604
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
15605
|
+
*/
|
|
15606
|
+
async consume(taskId) {
|
|
15607
|
+
const all = await this.list();
|
|
15608
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
15609
|
+
if (!target) return null;
|
|
15610
|
+
await this.delete(target.taskId);
|
|
15611
|
+
return target;
|
|
15612
|
+
}
|
|
15613
|
+
/** Remove every checkpoint for a task id. */
|
|
15614
|
+
async delete(taskId) {
|
|
15615
|
+
try {
|
|
15616
|
+
const entries = await promises.readdir(this.dir);
|
|
15617
|
+
const wanted = safeId(taskId);
|
|
15618
|
+
await Promise.all(entries.map(async (name) => {
|
|
15619
|
+
const match = FILE_PATTERN.exec(name);
|
|
15620
|
+
if (match?.[2] === wanted) {
|
|
15621
|
+
await promises.rm(path26.join(this.dir, name), { force: true });
|
|
15622
|
+
}
|
|
15623
|
+
}));
|
|
15624
|
+
} catch {
|
|
15625
|
+
}
|
|
15626
|
+
}
|
|
15627
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
15628
|
+
async prune() {
|
|
15629
|
+
try {
|
|
15630
|
+
const entries = await promises.readdir(this.dir);
|
|
15631
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15632
|
+
const files = entries.map((name) => ({ name, match: FILE_PATTERN.exec(name) })).filter((e) => e.match !== null).map((e) => ({ name: e.name, timestamp: Number(e.match[1]) })).sort((a, b) => b.timestamp - a.timestamp);
|
|
15633
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
15634
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
15635
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
15636
|
+
(name) => promises.rm(path26.join(this.dir, name), { force: true })
|
|
15637
|
+
));
|
|
15638
|
+
} catch {
|
|
15639
|
+
}
|
|
15640
|
+
}
|
|
15641
|
+
};
|
|
15642
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
15643
|
+
if (!completed.length) return "";
|
|
15644
|
+
const lines = completed.map((node) => {
|
|
15645
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
15646
|
+
return `- [${node.status}] ${node.title}
|
|
15647
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
15648
|
+
});
|
|
15649
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
15650
|
+
${lines.join("\n")}`;
|
|
15651
|
+
}
|
|
15193
15652
|
var ignore2 = ignoreFactory.default ?? ignoreFactory;
|
|
15194
15653
|
var PrivacyPaths = class {
|
|
15195
15654
|
localOnly;
|
|
@@ -15236,7 +15695,7 @@ var CascadeIgnore = class {
|
|
|
15236
15695
|
]);
|
|
15237
15696
|
}
|
|
15238
15697
|
async load(workspacePath) {
|
|
15239
|
-
const filePath =
|
|
15698
|
+
const filePath = path26.join(workspacePath, ".cascadeignore");
|
|
15240
15699
|
try {
|
|
15241
15700
|
const content = await fs5.readFile(filePath, "utf-8");
|
|
15242
15701
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -15247,7 +15706,7 @@ var CascadeIgnore = class {
|
|
|
15247
15706
|
}
|
|
15248
15707
|
isIgnored(filePath, workspacePath) {
|
|
15249
15708
|
try {
|
|
15250
|
-
const relative = workspacePath ?
|
|
15709
|
+
const relative = workspacePath ? path26.relative(workspacePath, filePath) : filePath;
|
|
15251
15710
|
return this.ig.ignores(relative);
|
|
15252
15711
|
} catch {
|
|
15253
15712
|
return false;
|
|
@@ -15590,7 +16049,7 @@ var WorkspaceIndex = class {
|
|
|
15590
16049
|
isIgnored;
|
|
15591
16050
|
chunker;
|
|
15592
16051
|
constructor(opts) {
|
|
15593
|
-
this.root =
|
|
16052
|
+
this.root = path26.resolve(opts.root);
|
|
15594
16053
|
this.db = opts.db;
|
|
15595
16054
|
this.namespace = opts.namespace ?? "code";
|
|
15596
16055
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -15622,14 +16081,14 @@ var WorkspaceIndex = class {
|
|
|
15622
16081
|
return;
|
|
15623
16082
|
}
|
|
15624
16083
|
for (const entry of entries) {
|
|
15625
|
-
const abs =
|
|
16084
|
+
const abs = path26.join(dir, entry.name);
|
|
15626
16085
|
if (entry.isDirectory()) {
|
|
15627
16086
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
15628
16087
|
await walk(abs);
|
|
15629
16088
|
} else if (entry.isFile()) {
|
|
15630
|
-
const ext =
|
|
16089
|
+
const ext = path26.extname(entry.name).slice(1).toLowerCase();
|
|
15631
16090
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
15632
|
-
out.push(
|
|
16091
|
+
out.push(path26.relative(this.root, abs));
|
|
15633
16092
|
}
|
|
15634
16093
|
}
|
|
15635
16094
|
};
|
|
@@ -15659,7 +16118,7 @@ var WorkspaceIndex = class {
|
|
|
15659
16118
|
const entries = [];
|
|
15660
16119
|
for (const rel of rels) {
|
|
15661
16120
|
try {
|
|
15662
|
-
const abs =
|
|
16121
|
+
const abs = path26.join(this.root, rel);
|
|
15663
16122
|
const stat = await fs5.stat(abs);
|
|
15664
16123
|
if (stat.size > this.maxFileBytes) continue;
|
|
15665
16124
|
const buf = await fs5.readFile(abs);
|
|
@@ -16197,8 +16656,16 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
16197
16656
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
16198
16657
|
decisionLog = [];
|
|
16199
16658
|
initialized = false;
|
|
16200
|
-
/**
|
|
16659
|
+
/**
|
|
16660
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
16661
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
16662
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
16663
|
+
*/
|
|
16201
16664
|
lastInterruptedRun;
|
|
16665
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
16666
|
+
resumeStore;
|
|
16667
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
16668
|
+
currentRunCompleted = [];
|
|
16202
16669
|
initPromise;
|
|
16203
16670
|
store;
|
|
16204
16671
|
audit;
|
|
@@ -16231,10 +16698,16 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
16231
16698
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
16232
16699
|
try {
|
|
16233
16700
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
16234
|
-
fileDeadModelPersistence(
|
|
16701
|
+
fileDeadModelPersistence(path26.join(workspacePath, ".cascade", "dead-models.json"))
|
|
16235
16702
|
));
|
|
16236
16703
|
} catch {
|
|
16237
16704
|
}
|
|
16705
|
+
try {
|
|
16706
|
+
this.resumeStore = new ResumeStore({
|
|
16707
|
+
dir: path26.join(workspacePath, ".cascade", "resume")
|
|
16708
|
+
});
|
|
16709
|
+
} catch {
|
|
16710
|
+
}
|
|
16238
16711
|
this.multimodal = new MultimodalRegistry(
|
|
16239
16712
|
(this.config.providers ?? []).map((p) => p.type)
|
|
16240
16713
|
);
|
|
@@ -16319,8 +16792,8 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
16319
16792
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
16320
16793
|
return;
|
|
16321
16794
|
}
|
|
16322
|
-
const dbPath = ci.dbPath ||
|
|
16323
|
-
|
|
16795
|
+
const dbPath = ci.dbPath || path26.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16796
|
+
fs25.mkdirSync(path26.dirname(dbPath), { recursive: true });
|
|
16324
16797
|
const db = new Database(dbPath);
|
|
16325
16798
|
db.pragma("journal_mode = WAL");
|
|
16326
16799
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -16700,10 +17173,43 @@ ${input}` }],
|
|
|
16700
17173
|
setFeedbackSource(source) {
|
|
16701
17174
|
this.router.setFeedbackSource(source);
|
|
16702
17175
|
}
|
|
16703
|
-
/**
|
|
17176
|
+
/**
|
|
17177
|
+
* Write a durable checkpoint for an interrupted run.
|
|
17178
|
+
*
|
|
17179
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
17180
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
17181
|
+
* worse. Nothing here is allowed to throw.
|
|
17182
|
+
*/
|
|
17183
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
17184
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
17185
|
+
try {
|
|
17186
|
+
await this.resumeStore?.save({
|
|
17187
|
+
taskId,
|
|
17188
|
+
prompt,
|
|
17189
|
+
reason,
|
|
17190
|
+
...detail ? { detail } : {},
|
|
17191
|
+
partialOutput,
|
|
17192
|
+
completed: [...this.currentRunCompleted]
|
|
17193
|
+
});
|
|
17194
|
+
} catch {
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
17197
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
16704
17198
|
hasResumableRun() {
|
|
16705
17199
|
return this.lastInterruptedRun != null;
|
|
16706
17200
|
}
|
|
17201
|
+
/**
|
|
17202
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
17203
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
17204
|
+
* answer for callers already on a hot path.
|
|
17205
|
+
*/
|
|
17206
|
+
async hasDurableResume() {
|
|
17207
|
+
return await this.resumeStore?.latest() != null;
|
|
17208
|
+
}
|
|
17209
|
+
/** Interrupted runs available to resume, newest first. */
|
|
17210
|
+
async listResumableRuns() {
|
|
17211
|
+
return await this.resumeStore?.list() ?? [];
|
|
17212
|
+
}
|
|
16707
17213
|
/**
|
|
16708
17214
|
* Raise the per-run token budget for a resume and return the continuation
|
|
16709
17215
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -16714,22 +17220,54 @@ ${input}` }],
|
|
|
16714
17220
|
const last = this.lastInterruptedRun;
|
|
16715
17221
|
if (!last) return null;
|
|
16716
17222
|
this.lastInterruptedRun = void 0;
|
|
16717
|
-
|
|
17223
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17224
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
17225
|
+
}
|
|
17226
|
+
/**
|
|
17227
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
17228
|
+
* process, including one that crashed before it could return. Consumes the
|
|
17229
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
17230
|
+
*/
|
|
17231
|
+
async prepareDurableResume(opts = {}) {
|
|
17232
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
17233
|
+
if (!checkpoint) return null;
|
|
17234
|
+
this.lastInterruptedRun = void 0;
|
|
17235
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17236
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
17237
|
+
}
|
|
17238
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
17239
|
+
raiseBudgetForResume(maxTokens) {
|
|
17240
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
16718
17241
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
16719
17242
|
this.router.setMaxTokensPerRun(raised);
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
17243
|
+
}
|
|
17244
|
+
/**
|
|
17245
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
17246
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
17247
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
17248
|
+
* is something it can act on. That difference is the entire reason node
|
|
17249
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
17250
|
+
* finished work again, and the user pays for it a second time.
|
|
17251
|
+
*/
|
|
17252
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
17253
|
+
const done = summarizeCompleted(completed);
|
|
17254
|
+
return [
|
|
17255
|
+
"Continue and FINISH this task. A previous attempt was interrupted before completion; any files already created are on disk \u2014 build on them, do NOT recreate them. Plan ONLY the remaining work.",
|
|
17256
|
+
"",
|
|
17257
|
+
`Original task: ${prompt}`,
|
|
17258
|
+
done ? `
|
|
17259
|
+
${done}` : "",
|
|
17260
|
+
partialOutput ? `
|
|
16724
17261
|
Partial result so far:
|
|
16725
|
-
${
|
|
17262
|
+
${partialOutput}` : ""
|
|
17263
|
+
].filter(Boolean).join("\n");
|
|
16726
17264
|
}
|
|
16727
17265
|
/**
|
|
16728
17266
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
16729
17267
|
* Returns null when there is nothing to resume.
|
|
16730
17268
|
*/
|
|
16731
17269
|
async resumeRun(opts = {}) {
|
|
16732
|
-
const prompt = this.prepareResume(opts);
|
|
17270
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
16733
17271
|
if (!prompt) return null;
|
|
16734
17272
|
return this.run({ prompt });
|
|
16735
17273
|
}
|
|
@@ -17100,6 +17638,7 @@ ${prompt}` : prompt;
|
|
|
17100
17638
|
const startMs = Date.now();
|
|
17101
17639
|
const taskId = randomUUID();
|
|
17102
17640
|
this.decisionLog = [];
|
|
17641
|
+
this.currentRunCompleted = [];
|
|
17103
17642
|
if (options.fastAnswer) {
|
|
17104
17643
|
return this.runFastAnswer(options, startMs, taskId);
|
|
17105
17644
|
}
|
|
@@ -17214,6 +17753,11 @@ ${prompt}` : prompt;
|
|
|
17214
17753
|
});
|
|
17215
17754
|
tier.on("log", (e) => this.emit("log", e));
|
|
17216
17755
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
17756
|
+
tier.on("section:complete", (e) => {
|
|
17757
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
17758
|
+
this.currentRunCompleted.push(e);
|
|
17759
|
+
this.emit("section:complete", e);
|
|
17760
|
+
});
|
|
17217
17761
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
17218
17762
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
17219
17763
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -17360,6 +17904,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
17360
17904
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
17361
17905
|
const trip = runBreaker.reason();
|
|
17362
17906
|
if (trip) {
|
|
17907
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
17363
17908
|
finalOutput = [
|
|
17364
17909
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
17365
17910
|
"",
|
|
@@ -17379,6 +17924,13 @@ ${finalOutput}` : ""
|
|
|
17379
17924
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
17380
17925
|
partialOutput: finalOutput || ""
|
|
17381
17926
|
});
|
|
17927
|
+
await this.checkpointRun(
|
|
17928
|
+
taskId,
|
|
17929
|
+
options.prompt,
|
|
17930
|
+
finalOutput || "",
|
|
17931
|
+
"cancelled",
|
|
17932
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
17933
|
+
);
|
|
17382
17934
|
runError = null;
|
|
17383
17935
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
17384
17936
|
this.emit("run:budget-exceeded", {
|
|
@@ -17387,9 +17939,17 @@ ${finalOutput}` : ""
|
|
|
17387
17939
|
partialOutput: finalOutput || ""
|
|
17388
17940
|
});
|
|
17389
17941
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
17942
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
17390
17943
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
17391
17944
|
runError = null;
|
|
17392
17945
|
} else {
|
|
17946
|
+
await this.checkpointRun(
|
|
17947
|
+
taskId,
|
|
17948
|
+
options.prompt,
|
|
17949
|
+
finalOutput || "",
|
|
17950
|
+
"error",
|
|
17951
|
+
err instanceof Error ? err.message : String(err)
|
|
17952
|
+
);
|
|
17393
17953
|
runError = err;
|
|
17394
17954
|
throw err;
|
|
17395
17955
|
}
|
|
@@ -17637,7 +18197,7 @@ var Keystore = class {
|
|
|
17637
18197
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
17638
18198
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
17639
18199
|
this.backend = "keytar";
|
|
17640
|
-
if (password &&
|
|
18200
|
+
if (password && fs25.existsSync(this.storePath)) {
|
|
17641
18201
|
try {
|
|
17642
18202
|
const fileEntries = this.decryptFile(password);
|
|
17643
18203
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -17656,7 +18216,7 @@ var Keystore = class {
|
|
|
17656
18216
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
17657
18217
|
);
|
|
17658
18218
|
}
|
|
17659
|
-
if (!
|
|
18219
|
+
if (!fs25.existsSync(this.storePath)) {
|
|
17660
18220
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
17661
18221
|
this.masterKey = this.deriveKey(password, salt);
|
|
17662
18222
|
this.writeWithSalt({}, salt);
|
|
@@ -17670,7 +18230,7 @@ var Keystore = class {
|
|
|
17670
18230
|
}
|
|
17671
18231
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
17672
18232
|
unlockSync(password) {
|
|
17673
|
-
if (!
|
|
18233
|
+
if (!fs25.existsSync(this.storePath)) {
|
|
17674
18234
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
17675
18235
|
this.masterKey = this.deriveKey(password, salt);
|
|
17676
18236
|
this.writeWithSalt({}, salt);
|
|
@@ -17728,7 +18288,7 @@ var Keystore = class {
|
|
|
17728
18288
|
}
|
|
17729
18289
|
}
|
|
17730
18290
|
decryptFile(password, knownSalt) {
|
|
17731
|
-
if (!
|
|
18291
|
+
if (!fs25.existsSync(this.storePath)) return {};
|
|
17732
18292
|
try {
|
|
17733
18293
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
17734
18294
|
const useSalt = knownSalt ?? salt;
|
|
@@ -17750,8 +18310,8 @@ var Keystore = class {
|
|
|
17750
18310
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17751
18311
|
const tag = cipher.getAuthTag();
|
|
17752
18312
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
17753
|
-
|
|
17754
|
-
|
|
18313
|
+
fs25.mkdirSync(path26.dirname(this.storePath), { recursive: true });
|
|
18314
|
+
fs25.writeFileSync(this.storePath, out, { mode: 384 });
|
|
17755
18315
|
}
|
|
17756
18316
|
writeWithSalt(data, salt) {
|
|
17757
18317
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -17761,11 +18321,11 @@ var Keystore = class {
|
|
|
17761
18321
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17762
18322
|
const tag = cipher.getAuthTag();
|
|
17763
18323
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
17764
|
-
|
|
17765
|
-
|
|
18324
|
+
fs25.mkdirSync(path26.dirname(this.storePath), { recursive: true });
|
|
18325
|
+
fs25.writeFileSync(this.storePath, out, { mode: 384 });
|
|
17766
18326
|
}
|
|
17767
18327
|
readRaw() {
|
|
17768
|
-
const buf =
|
|
18328
|
+
const buf = fs25.readFileSync(this.storePath);
|
|
17769
18329
|
let offset = 0;
|
|
17770
18330
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
17771
18331
|
offset += SALT_LEN;
|
|
@@ -17781,7 +18341,7 @@ var Keystore = class {
|
|
|
17781
18341
|
}
|
|
17782
18342
|
};
|
|
17783
18343
|
async function loadCascadeMd(workspacePath) {
|
|
17784
|
-
const filePath =
|
|
18344
|
+
const filePath = path26.join(workspacePath, "CASCADE.md");
|
|
17785
18345
|
try {
|
|
17786
18346
|
const raw = await fs5.readFile(filePath, "utf-8");
|
|
17787
18347
|
return parseCascadeMd(raw);
|
|
@@ -17812,7 +18372,7 @@ ${raw.trim()}`;
|
|
|
17812
18372
|
var MemoryStore = class _MemoryStore {
|
|
17813
18373
|
db;
|
|
17814
18374
|
constructor(dbPath) {
|
|
17815
|
-
|
|
18375
|
+
fs25.mkdirSync(path26.dirname(dbPath), { recursive: true });
|
|
17816
18376
|
try {
|
|
17817
18377
|
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
17818
18378
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -18648,7 +19208,7 @@ Original error: ${err.message}`
|
|
|
18648
19208
|
}
|
|
18649
19209
|
};
|
|
18650
19210
|
function credentialsPath(globalDir2) {
|
|
18651
|
-
return
|
|
19211
|
+
return path26.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
18652
19212
|
}
|
|
18653
19213
|
function providerKey(p) {
|
|
18654
19214
|
if (p.type === "azure") {
|
|
@@ -18661,7 +19221,7 @@ function isPersistable(p) {
|
|
|
18661
19221
|
}
|
|
18662
19222
|
function loadGlobalCredentials(globalDir2) {
|
|
18663
19223
|
try {
|
|
18664
|
-
const raw =
|
|
19224
|
+
const raw = fs25.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
18665
19225
|
const parsed = JSON.parse(raw);
|
|
18666
19226
|
if (!Array.isArray(parsed.providers)) return [];
|
|
18667
19227
|
return parsed.providers.filter(
|
|
@@ -18673,13 +19233,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
18673
19233
|
}
|
|
18674
19234
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
18675
19235
|
const filePath = credentialsPath(globalDir2);
|
|
18676
|
-
|
|
19236
|
+
fs25.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
18677
19237
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
18678
19238
|
const tmp = `${filePath}.tmp`;
|
|
18679
|
-
|
|
18680
|
-
|
|
19239
|
+
fs25.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
19240
|
+
fs25.renameSync(tmp, filePath);
|
|
18681
19241
|
try {
|
|
18682
|
-
|
|
19242
|
+
fs25.chmodSync(filePath, 384);
|
|
18683
19243
|
} catch {
|
|
18684
19244
|
}
|
|
18685
19245
|
}
|
|
@@ -18714,7 +19274,7 @@ var ConfigManager = class {
|
|
|
18714
19274
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
18715
19275
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
18716
19276
|
this.workspacePath = workspacePath;
|
|
18717
|
-
this.globalDir = globalDirOverride ??
|
|
19277
|
+
this.globalDir = globalDirOverride ?? path26.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
18718
19278
|
}
|
|
18719
19279
|
async load() {
|
|
18720
19280
|
this.config = await this.loadConfig();
|
|
@@ -18728,8 +19288,8 @@ var ConfigManager = class {
|
|
|
18728
19288
|
this.ignore = new CascadeIgnore();
|
|
18729
19289
|
await this.ignore.load(this.workspacePath);
|
|
18730
19290
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
18731
|
-
this.keystore = new Keystore(
|
|
18732
|
-
this.store = new MemoryStore(
|
|
19291
|
+
this.keystore = new Keystore(path26.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
19292
|
+
this.store = new MemoryStore(path26.join(this.workspacePath, CASCADE_DB_FILE));
|
|
18733
19293
|
await this.injectEnvKeys();
|
|
18734
19294
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
18735
19295
|
await this.ensureDefaultIdentity();
|
|
@@ -18754,8 +19314,8 @@ var ConfigManager = class {
|
|
|
18754
19314
|
return this.workspacePath;
|
|
18755
19315
|
}
|
|
18756
19316
|
async save() {
|
|
18757
|
-
const configPath =
|
|
18758
|
-
await fs5.mkdir(
|
|
19317
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19318
|
+
await fs5.mkdir(path26.dirname(configPath), { recursive: true });
|
|
18759
19319
|
await fs5.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18760
19320
|
try {
|
|
18761
19321
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
@@ -18791,7 +19351,7 @@ var ConfigManager = class {
|
|
|
18791
19351
|
return configProvider?.apiKey;
|
|
18792
19352
|
}
|
|
18793
19353
|
async loadConfig() {
|
|
18794
|
-
const configPath =
|
|
19354
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18795
19355
|
try {
|
|
18796
19356
|
const raw = await fs5.readFile(configPath, "utf-8");
|
|
18797
19357
|
return validateConfig(JSON.parse(raw));
|
|
@@ -19323,7 +19883,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
19323
19883
|
}
|
|
19324
19884
|
|
|
19325
19885
|
// src/dashboard/server.ts
|
|
19326
|
-
var __dirname$1 =
|
|
19886
|
+
var __dirname$1 = path26.dirname(fileURLToPath(import.meta.url));
|
|
19327
19887
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
19328
19888
|
var DashboardServer = class {
|
|
19329
19889
|
app;
|
|
@@ -19603,14 +20163,14 @@ var DashboardServer = class {
|
|
|
19603
20163
|
*/
|
|
19604
20164
|
persistConfig() {
|
|
19605
20165
|
try {
|
|
19606
|
-
const configPath =
|
|
19607
|
-
|
|
19608
|
-
|
|
20166
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20167
|
+
fs25.mkdirSync(path26.dirname(configPath), { recursive: true });
|
|
20168
|
+
fs25.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
19609
20169
|
} catch (err) {
|
|
19610
20170
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
19611
20171
|
}
|
|
19612
20172
|
try {
|
|
19613
|
-
saveGlobalCredentials(
|
|
20173
|
+
saveGlobalCredentials(path26.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
19614
20174
|
} catch (err) {
|
|
19615
20175
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
19616
20176
|
}
|
|
@@ -19625,15 +20185,15 @@ var DashboardServer = class {
|
|
|
19625
20185
|
resolveDashboardSecret() {
|
|
19626
20186
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
19627
20187
|
if (fromConfig) return fromConfig;
|
|
19628
|
-
const secretPath =
|
|
20188
|
+
const secretPath = path26.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
19629
20189
|
try {
|
|
19630
|
-
if (
|
|
19631
|
-
const existing =
|
|
20190
|
+
if (fs25.existsSync(secretPath)) {
|
|
20191
|
+
const existing = fs25.readFileSync(secretPath, "utf-8").trim();
|
|
19632
20192
|
if (existing.length >= 16) return existing;
|
|
19633
20193
|
}
|
|
19634
20194
|
const generated = randomUUID();
|
|
19635
|
-
|
|
19636
|
-
|
|
20195
|
+
fs25.mkdirSync(path26.dirname(secretPath), { recursive: true });
|
|
20196
|
+
fs25.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
19637
20197
|
if (this.config.dashboard.auth) {
|
|
19638
20198
|
console.warn(
|
|
19639
20199
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -19660,7 +20220,7 @@ var DashboardServer = class {
|
|
|
19660
20220
|
// ── Setup ─────────────────────────────────────
|
|
19661
20221
|
getGlobalStore() {
|
|
19662
20222
|
if (!this.globalStore) {
|
|
19663
|
-
const globalDbPath =
|
|
20223
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19664
20224
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
19665
20225
|
}
|
|
19666
20226
|
return this.globalStore;
|
|
@@ -20040,12 +20600,12 @@ ${prompt}`;
|
|
|
20040
20600
|
}
|
|
20041
20601
|
}
|
|
20042
20602
|
watchRuntimeChanges() {
|
|
20043
|
-
const workspaceDbPath =
|
|
20044
|
-
const globalDbPath =
|
|
20603
|
+
const workspaceDbPath = path26.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20604
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20045
20605
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
20046
20606
|
for (const watchPath of watchPaths) {
|
|
20047
|
-
if (!
|
|
20048
|
-
|
|
20607
|
+
if (!fs25.existsSync(watchPath)) continue;
|
|
20608
|
+
fs25.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
20049
20609
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
20050
20610
|
});
|
|
20051
20611
|
}
|
|
@@ -20167,7 +20727,7 @@ ${prompt}`;
|
|
|
20167
20727
|
const sessionId = req.params.id;
|
|
20168
20728
|
this.store.deleteSession(sessionId);
|
|
20169
20729
|
this.store.deleteRuntimeSession(sessionId);
|
|
20170
|
-
const globalDbPath =
|
|
20730
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20171
20731
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20172
20732
|
try {
|
|
20173
20733
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -20260,7 +20820,7 @@ ${prompt}`;
|
|
|
20260
20820
|
});
|
|
20261
20821
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
20262
20822
|
const body = req.body;
|
|
20263
|
-
const globalDbPath =
|
|
20823
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20264
20824
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
20265
20825
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20266
20826
|
try {
|
|
@@ -20283,7 +20843,7 @@ ${prompt}`;
|
|
|
20283
20843
|
});
|
|
20284
20844
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
20285
20845
|
this.store.deleteAllRuntimeNodes();
|
|
20286
|
-
const globalDbPath =
|
|
20846
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20287
20847
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20288
20848
|
try {
|
|
20289
20849
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -20369,12 +20929,12 @@ ${prompt}`;
|
|
|
20369
20929
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
20370
20930
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
20371
20931
|
try {
|
|
20372
|
-
const configPath =
|
|
20373
|
-
const existing =
|
|
20932
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20933
|
+
const existing = fs25.existsSync(configPath) ? JSON.parse(fs25.readFileSync(configPath, "utf-8")) : {};
|
|
20374
20934
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
20375
20935
|
const tmp = configPath + ".tmp";
|
|
20376
|
-
|
|
20377
|
-
|
|
20936
|
+
fs25.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20937
|
+
fs25.renameSync(tmp, configPath);
|
|
20378
20938
|
res.json({ ok: true });
|
|
20379
20939
|
} catch (err) {
|
|
20380
20940
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -20402,7 +20962,7 @@ ${prompt}`;
|
|
|
20402
20962
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
20403
20963
|
const scope = req.query["scope"] ?? "workspace";
|
|
20404
20964
|
if (scope === "global") {
|
|
20405
|
-
const globalDbPath =
|
|
20965
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20406
20966
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20407
20967
|
try {
|
|
20408
20968
|
res.json({
|
|
@@ -20743,13 +21303,13 @@ ${prompt}`;
|
|
|
20743
21303
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
20744
21304
|
}
|
|
20745
21305
|
});
|
|
20746
|
-
const prodPath =
|
|
20747
|
-
const devPath =
|
|
20748
|
-
const webDistPath =
|
|
20749
|
-
if (
|
|
21306
|
+
const prodPath = path26.resolve(__dirname$1, "../web/dist");
|
|
21307
|
+
const devPath = path26.resolve(__dirname$1, "../../web/dist");
|
|
21308
|
+
const webDistPath = fs25.existsSync(prodPath) ? prodPath : devPath;
|
|
21309
|
+
if (fs25.existsSync(webDistPath)) {
|
|
20750
21310
|
this.app.use(express.static(webDistPath));
|
|
20751
21311
|
this.app.get("*", (_req, res) => {
|
|
20752
|
-
res.sendFile(
|
|
21312
|
+
res.sendFile(path26.join(webDistPath, "index.html"));
|
|
20753
21313
|
});
|
|
20754
21314
|
} else {
|
|
20755
21315
|
this.app.get("/", (_req, res) => {
|
|
@@ -20936,14 +21496,14 @@ function planRetrieval(ctx) {
|
|
|
20936
21496
|
}
|
|
20937
21497
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20938
21498
|
function globalDir(homeDir = os6.homedir()) {
|
|
20939
|
-
return
|
|
21499
|
+
return path26.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20940
21500
|
}
|
|
20941
21501
|
function cloudSessionPath(dir = globalDir()) {
|
|
20942
|
-
return
|
|
21502
|
+
return path26.join(dir, CLOUD_SESSION_FILE);
|
|
20943
21503
|
}
|
|
20944
21504
|
function loadCloudSession(dir = globalDir()) {
|
|
20945
21505
|
try {
|
|
20946
|
-
const raw =
|
|
21506
|
+
const raw = fs25.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20947
21507
|
const parsed = JSON.parse(raw);
|
|
20948
21508
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20949
21509
|
return parsed;
|
|
@@ -20952,17 +21512,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20952
21512
|
}
|
|
20953
21513
|
}
|
|
20954
21514
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20955
|
-
|
|
21515
|
+
fs25.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20956
21516
|
const file = cloudSessionPath(dir);
|
|
20957
|
-
|
|
21517
|
+
fs25.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20958
21518
|
try {
|
|
20959
|
-
|
|
21519
|
+
fs25.chmodSync(file, 384);
|
|
20960
21520
|
} catch {
|
|
20961
21521
|
}
|
|
20962
21522
|
}
|
|
20963
21523
|
function clearCloudSession(dir = globalDir()) {
|
|
20964
21524
|
try {
|
|
20965
|
-
|
|
21525
|
+
fs25.rmSync(cloudSessionPath(dir));
|
|
20966
21526
|
} catch {
|
|
20967
21527
|
}
|
|
20968
21528
|
}
|
|
@@ -21134,16 +21694,16 @@ var CloudClient = class _CloudClient {
|
|
|
21134
21694
|
return (await this.refresh()).accessToken;
|
|
21135
21695
|
}
|
|
21136
21696
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
21137
|
-
async authed(
|
|
21138
|
-
const call = (token) => fetch(this.url(
|
|
21697
|
+
async authed(path28, init = {}) {
|
|
21698
|
+
const call = (token) => fetch(this.url(path28), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
21139
21699
|
let res = await call(await this.accessToken());
|
|
21140
21700
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
21141
21701
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
21142
21702
|
const text = await res.text();
|
|
21143
21703
|
return text ? JSON.parse(text) : {};
|
|
21144
21704
|
}
|
|
21145
|
-
authedGet(
|
|
21146
|
-
return this.authed(
|
|
21705
|
+
authedGet(path28) {
|
|
21706
|
+
return this.authed(path28);
|
|
21147
21707
|
}
|
|
21148
21708
|
async fetchMe(token) {
|
|
21149
21709
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -21480,6 +22040,6 @@ function applySyncBundle(bundle, config) {
|
|
|
21480
22040
|
return next;
|
|
21481
22041
|
}
|
|
21482
22042
|
|
|
21483
|
-
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, extractCharts, fileExt, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, uniqueMcpServerName };
|
|
22043
|
+
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, evaluateAcceptance, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
21484
22044
|
//# sourceMappingURL=index.js.map
|
|
21485
22045
|
//# sourceMappingURL=index.js.map
|