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.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var Database = require('better-sqlite3');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var path26 = require('path');
|
|
5
|
+
var fs25 = require('fs');
|
|
6
6
|
var os6 = require('os');
|
|
7
7
|
var crypto4 = require('crypto');
|
|
8
8
|
var EventEmitter = require('events');
|
|
@@ -61,8 +61,8 @@ function _interopNamespace(e) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
64
|
-
var
|
|
65
|
-
var
|
|
64
|
+
var path26__default = /*#__PURE__*/_interopDefault(path26);
|
|
65
|
+
var fs25__default = /*#__PURE__*/_interopDefault(fs25);
|
|
66
66
|
var os6__default = /*#__PURE__*/_interopDefault(os6);
|
|
67
67
|
var crypto4__default = /*#__PURE__*/_interopDefault(crypto4);
|
|
68
68
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
@@ -107,12 +107,12 @@ var init_audit_logger = __esm({
|
|
|
107
107
|
constructor(workspacePath, debugMode = false) {
|
|
108
108
|
this.workspacePath = workspacePath;
|
|
109
109
|
this.debugMode = debugMode;
|
|
110
|
-
const cascadeDir =
|
|
111
|
-
if (!
|
|
112
|
-
|
|
110
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
111
|
+
if (!fs25__default.default.existsSync(cascadeDir)) {
|
|
112
|
+
fs25__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
113
113
|
}
|
|
114
|
-
this.keyPath =
|
|
115
|
-
this.dbPath =
|
|
114
|
+
this.keyPath = path26__default.default.join(cascadeDir, "audit_log.key");
|
|
115
|
+
this.dbPath = path26__default.default.join(cascadeDir, "audit_log.db");
|
|
116
116
|
this.initEncryptionKey();
|
|
117
117
|
this.db = new Database__default.default(this.dbPath);
|
|
118
118
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -142,11 +142,11 @@ var init_audit_logger = __esm({
|
|
|
142
142
|
return crypto4__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
143
143
|
}
|
|
144
144
|
initEncryptionKey() {
|
|
145
|
-
if (
|
|
146
|
-
this.encryptionKey =
|
|
145
|
+
if (fs25__default.default.existsSync(this.keyPath)) {
|
|
146
|
+
this.encryptionKey = fs25__default.default.readFileSync(this.keyPath);
|
|
147
147
|
} else {
|
|
148
148
|
this.encryptionKey = crypto4__default.default.randomBytes(32);
|
|
149
|
-
|
|
149
|
+
fs25__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
encrypt(text) {
|
|
@@ -224,9 +224,9 @@ var init_audit_logger = __esm({
|
|
|
224
224
|
dumpDebugIfNeeded() {
|
|
225
225
|
if (!this.debugMode) return;
|
|
226
226
|
try {
|
|
227
|
-
const dumpPath =
|
|
227
|
+
const dumpPath = path26__default.default.join(os6__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
228
228
|
const entries = this.getAllLogs();
|
|
229
|
-
|
|
229
|
+
fs25__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
230
230
|
} catch (err) {
|
|
231
231
|
console.error("Failed to dump debug audit logs", err);
|
|
232
232
|
}
|
|
@@ -1908,7 +1908,7 @@ function reconcilePrice(model, live) {
|
|
|
1908
1908
|
}
|
|
1909
1909
|
|
|
1910
1910
|
// src/constants.ts
|
|
1911
|
-
var CASCADE_VERSION = "0.
|
|
1911
|
+
var CASCADE_VERSION = "0.68.0";
|
|
1912
1912
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1913
1913
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1914
1914
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4494,13 +4494,13 @@ var DeadModelStore = class {
|
|
|
4494
4494
|
function fileDeadModelPersistence(filePath) {
|
|
4495
4495
|
return {
|
|
4496
4496
|
load() {
|
|
4497
|
-
if (!
|
|
4498
|
-
const raw = JSON.parse(
|
|
4497
|
+
if (!fs25.existsSync(filePath)) return [];
|
|
4498
|
+
const raw = JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
4499
4499
|
return Array.isArray(raw) ? raw : [];
|
|
4500
4500
|
},
|
|
4501
4501
|
save(records) {
|
|
4502
|
-
|
|
4503
|
-
|
|
4502
|
+
fs25.mkdirSync(path26.dirname(filePath), { recursive: true });
|
|
4503
|
+
fs25.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
4504
4504
|
}
|
|
4505
4505
|
};
|
|
4506
4506
|
}
|
|
@@ -4663,7 +4663,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4663
4663
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4664
4664
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4665
4665
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4666
|
-
var DEFAULT_CACHE_FILE =
|
|
4666
|
+
var DEFAULT_CACHE_FILE = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4667
4667
|
function normalizeModelId2(id) {
|
|
4668
4668
|
let s = id.toLowerCase();
|
|
4669
4669
|
const slash = s.lastIndexOf("/");
|
|
@@ -4810,7 +4810,7 @@ var LiveDataProvider = class {
|
|
|
4810
4810
|
}
|
|
4811
4811
|
async saveCache() {
|
|
4812
4812
|
try {
|
|
4813
|
-
await fs5__default.default.mkdir(
|
|
4813
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
4814
4814
|
const cache = {
|
|
4815
4815
|
fetchedAt: this.fetchedAt,
|
|
4816
4816
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -6602,6 +6602,79 @@ function describeProviderError(c, modelId) {
|
|
|
6602
6602
|
}
|
|
6603
6603
|
}
|
|
6604
6604
|
|
|
6605
|
+
// src/core/verification/acceptance.ts
|
|
6606
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
6607
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
6608
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
6609
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
6610
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
6611
|
+
function targetFile(criterion, ownedFiles) {
|
|
6612
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
6613
|
+
if (mentioned.length > 1) return null;
|
|
6614
|
+
if (mentioned.length === 1) {
|
|
6615
|
+
const candidate = mentioned[0];
|
|
6616
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
6617
|
+
}
|
|
6618
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
6619
|
+
}
|
|
6620
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
6621
|
+
const results = [];
|
|
6622
|
+
for (const criterion of criteria) {
|
|
6623
|
+
const text = criterion.trim();
|
|
6624
|
+
if (!text) {
|
|
6625
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6626
|
+
continue;
|
|
6627
|
+
}
|
|
6628
|
+
if (NEGATION.test(text)) {
|
|
6629
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6630
|
+
continue;
|
|
6631
|
+
}
|
|
6632
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
6633
|
+
const wantsExists = EXISTS.test(text);
|
|
6634
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
6635
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
6636
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6637
|
+
continue;
|
|
6638
|
+
}
|
|
6639
|
+
const file = targetFile(text, ownedFiles);
|
|
6640
|
+
if (!file) {
|
|
6641
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6642
|
+
continue;
|
|
6643
|
+
}
|
|
6644
|
+
const stat = await probe.stat(file);
|
|
6645
|
+
if (!stat) {
|
|
6646
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
6647
|
+
continue;
|
|
6648
|
+
}
|
|
6649
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
6650
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
6651
|
+
continue;
|
|
6652
|
+
}
|
|
6653
|
+
if (needle) {
|
|
6654
|
+
const content = await probe.read(file);
|
|
6655
|
+
if (content === null) {
|
|
6656
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6657
|
+
continue;
|
|
6658
|
+
}
|
|
6659
|
+
const found = content.includes(needle);
|
|
6660
|
+
results.push({
|
|
6661
|
+
criterion,
|
|
6662
|
+
verdict: found ? "passed" : "failed",
|
|
6663
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
6664
|
+
});
|
|
6665
|
+
continue;
|
|
6666
|
+
}
|
|
6667
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
6668
|
+
}
|
|
6669
|
+
return results;
|
|
6670
|
+
}
|
|
6671
|
+
function undecided(results) {
|
|
6672
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
6673
|
+
}
|
|
6674
|
+
function failures(results) {
|
|
6675
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
6676
|
+
}
|
|
6677
|
+
|
|
6605
6678
|
// src/core/tiers/t3-worker.ts
|
|
6606
6679
|
var CriticalToolError = class extends Error {
|
|
6607
6680
|
constructor(message, toolName) {
|
|
@@ -6918,7 +6991,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
6918
6991
|
}
|
|
6919
6992
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
6920
6993
|
if (!artifactCheck.ok) {
|
|
6921
|
-
correctionAttempts
|
|
6994
|
+
correctionAttempts++;
|
|
6922
6995
|
issues.push(...artifactCheck.issues);
|
|
6923
6996
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
6924
6997
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -6936,18 +7009,40 @@ Now execute your subtask using this context where relevant.`
|
|
|
6936
7009
|
this.tools.map((t) => t.name)
|
|
6937
7010
|
);
|
|
6938
7011
|
if (visualIssue) {
|
|
6939
|
-
correctionAttempts
|
|
7012
|
+
correctionAttempts++;
|
|
6940
7013
|
issues.push(visualIssue);
|
|
6941
7014
|
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
6942
7015
|
output = await this.correctOutput(output, [visualIssue]);
|
|
6943
7016
|
}
|
|
7017
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
7018
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
7019
|
+
if (acceptanceFailures.length > 0) {
|
|
7020
|
+
correctionAttempts++;
|
|
7021
|
+
issues.push(...acceptanceFailures);
|
|
7022
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
7023
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
7024
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
7025
|
+
if (recheck.length > 0) {
|
|
7026
|
+
issues.push(...recheck);
|
|
7027
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
7028
|
+
failed.push(...recheck);
|
|
7029
|
+
this.setStatus("FAILED");
|
|
7030
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
7031
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
for (const result2 of acceptanceResults) {
|
|
7035
|
+
if (result2.verdict === "undecidable") continue;
|
|
7036
|
+
checksRun.push(result2.criterion);
|
|
7037
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
7038
|
+
}
|
|
6944
7039
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6945
|
-
const testResult = await this.selfTest(assignment, output);
|
|
7040
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
6946
7041
|
checksRun.push(...testResult.checksRun);
|
|
6947
7042
|
passed.push(...testResult.passed);
|
|
6948
7043
|
failed.push(...testResult.failed);
|
|
6949
7044
|
if (testResult.failed.length > 0) {
|
|
6950
|
-
correctionAttempts
|
|
7045
|
+
correctionAttempts++;
|
|
6951
7046
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
6952
7047
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
6953
7048
|
output = corrected;
|
|
@@ -7255,8 +7350,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7255
7350
|
// Media generation can run for a minute; without this a cancelled run
|
|
7256
7351
|
// still pays for an image nobody will see.
|
|
7257
7352
|
...this.signal ? { signal: this.signal } : {},
|
|
7258
|
-
saveSnapshot: async (
|
|
7259
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7353
|
+
saveSnapshot: async (path28, content) => {
|
|
7354
|
+
this.store?.addFileSnapshot(this.taskId, path28, content);
|
|
7260
7355
|
},
|
|
7261
7356
|
sendPeerSync: (to, syncType, content) => {
|
|
7262
7357
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7443,7 +7538,7 @@ ${assignment.expectedOutput}`;
|
|
|
7443
7538
|
const { promisify: promisify4 } = await import('util');
|
|
7444
7539
|
const execAsync2 = promisify4(exec2);
|
|
7445
7540
|
for (const artifactPath of artifactPaths) {
|
|
7446
|
-
const absolutePath =
|
|
7541
|
+
const absolutePath = path26__default.default.resolve(process.cwd(), artifactPath);
|
|
7447
7542
|
try {
|
|
7448
7543
|
const stat = await fs5__default.default.stat(absolutePath);
|
|
7449
7544
|
if (!stat.isFile()) {
|
|
@@ -7478,7 +7573,7 @@ ${assignment.expectedOutput}`;
|
|
|
7478
7573
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7479
7574
|
continue;
|
|
7480
7575
|
}
|
|
7481
|
-
const ext =
|
|
7576
|
+
const ext = path26__default.default.extname(absolutePath).toLowerCase();
|
|
7482
7577
|
try {
|
|
7483
7578
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7484
7579
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -7564,14 +7659,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7564
7659
|
}
|
|
7565
7660
|
return current;
|
|
7566
7661
|
}
|
|
7567
|
-
|
|
7662
|
+
/**
|
|
7663
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
7664
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
7665
|
+
*/
|
|
7666
|
+
async checkAcceptance(assignment) {
|
|
7667
|
+
const criteria = assignment.acceptance ?? [];
|
|
7668
|
+
if (!criteria.length) return [];
|
|
7669
|
+
const resolve2 = (target) => path26__default.default.resolve(process.cwd(), target);
|
|
7670
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
7671
|
+
stat: async (target) => {
|
|
7672
|
+
try {
|
|
7673
|
+
const stat = await fs5__default.default.stat(resolve2(target));
|
|
7674
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
7675
|
+
} catch {
|
|
7676
|
+
return null;
|
|
7677
|
+
}
|
|
7678
|
+
},
|
|
7679
|
+
read: async (target) => {
|
|
7680
|
+
try {
|
|
7681
|
+
const content = await fs5__default.default.readFile(resolve2(target), "utf-8");
|
|
7682
|
+
return content.includes("\uFFFD") ? null : content;
|
|
7683
|
+
} catch {
|
|
7684
|
+
return null;
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
});
|
|
7688
|
+
}
|
|
7689
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
7690
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
7568
7691
|
const prompt = `Self-test this output against the assignment requirements.
|
|
7569
7692
|
|
|
7570
7693
|
Assignment: ${assignment.description}
|
|
7571
7694
|
Expected output: ${assignment.expectedOutput}
|
|
7572
7695
|
Constraints: ${assignment.constraints.join("; ")}
|
|
7573
|
-
${
|
|
7574
|
-
${
|
|
7696
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
7697
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
7575
7698
|
` : ""}
|
|
7576
7699
|
Output to test:
|
|
7577
7700
|
${output}
|
|
@@ -7638,8 +7761,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
7638
7761
|
} catch {
|
|
7639
7762
|
}
|
|
7640
7763
|
}
|
|
7641
|
-
async correctOutput(originalOutput,
|
|
7642
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
7764
|
+
async correctOutput(originalOutput, failures2) {
|
|
7765
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
7643
7766
|
|
|
7644
7767
|
Original output:
|
|
7645
7768
|
${originalOutput}
|
|
@@ -8300,12 +8423,220 @@ function describeGenerationForPlanner(has) {
|
|
|
8300
8423
|
return lines.join("\n");
|
|
8301
8424
|
}
|
|
8302
8425
|
|
|
8426
|
+
// src/core/orchestration/task-graph.ts
|
|
8427
|
+
var TaskGraphValidationError = class extends Error {
|
|
8428
|
+
constructor(issues) {
|
|
8429
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
8430
|
+
this.issues = issues;
|
|
8431
|
+
this.name = "TaskGraphValidationError";
|
|
8432
|
+
}
|
|
8433
|
+
issues;
|
|
8434
|
+
};
|
|
8435
|
+
function normalizeDependencies(input) {
|
|
8436
|
+
if (!input?.length) return [];
|
|
8437
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
8438
|
+
}
|
|
8439
|
+
function cycleNodes(nodes) {
|
|
8440
|
+
const adjacency = new Map(
|
|
8441
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
8442
|
+
);
|
|
8443
|
+
const index = /* @__PURE__ */ new Map();
|
|
8444
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
8445
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
8446
|
+
const stack = [];
|
|
8447
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
8448
|
+
let counter = 0;
|
|
8449
|
+
const open = (id) => {
|
|
8450
|
+
index.set(id, counter);
|
|
8451
|
+
lowlink.set(id, counter);
|
|
8452
|
+
counter++;
|
|
8453
|
+
stack.push(id);
|
|
8454
|
+
onStack.add(id);
|
|
8455
|
+
};
|
|
8456
|
+
for (const root of nodes) {
|
|
8457
|
+
if (index.has(root.id)) continue;
|
|
8458
|
+
open(root.id);
|
|
8459
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
8460
|
+
while (frames.length > 0) {
|
|
8461
|
+
const frame = frames[frames.length - 1];
|
|
8462
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
8463
|
+
if (frame.edge < neighbours.length) {
|
|
8464
|
+
const next = neighbours[frame.edge];
|
|
8465
|
+
frame.edge++;
|
|
8466
|
+
if (!adjacency.has(next)) continue;
|
|
8467
|
+
if (!index.has(next)) {
|
|
8468
|
+
open(next);
|
|
8469
|
+
frames.push({ id: next, edge: 0 });
|
|
8470
|
+
} else if (onStack.has(next)) {
|
|
8471
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
8472
|
+
}
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
frames.pop();
|
|
8476
|
+
const parent = frames[frames.length - 1];
|
|
8477
|
+
if (parent) {
|
|
8478
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
8479
|
+
}
|
|
8480
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
8481
|
+
const component = [];
|
|
8482
|
+
for (; ; ) {
|
|
8483
|
+
const member = stack.pop();
|
|
8484
|
+
onStack.delete(member);
|
|
8485
|
+
component.push(member);
|
|
8486
|
+
if (member === frame.id) break;
|
|
8487
|
+
}
|
|
8488
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
8489
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
8490
|
+
}
|
|
8491
|
+
}
|
|
8492
|
+
}
|
|
8493
|
+
return inCycle;
|
|
8494
|
+
}
|
|
8495
|
+
function throwIfStrict(mode, issues) {
|
|
8496
|
+
if (mode === "strict" && issues.length > 0) {
|
|
8497
|
+
throw new TaskGraphValidationError(issues);
|
|
8498
|
+
}
|
|
8499
|
+
}
|
|
8500
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
8501
|
+
const mode = options.mode ?? "strict";
|
|
8502
|
+
const issues = [];
|
|
8503
|
+
if (inputNodes.length === 0) {
|
|
8504
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
8505
|
+
throwIfStrict(mode, issues);
|
|
8506
|
+
}
|
|
8507
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8508
|
+
const nodes = [];
|
|
8509
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
8510
|
+
const input = inputNodes[ordinal];
|
|
8511
|
+
const id = input.id?.trim();
|
|
8512
|
+
const title = input.title?.trim();
|
|
8513
|
+
if (!id || !title) {
|
|
8514
|
+
issues.push({
|
|
8515
|
+
code: "INVALID_NODE",
|
|
8516
|
+
nodeId: id || void 0,
|
|
8517
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
8518
|
+
});
|
|
8519
|
+
continue;
|
|
8520
|
+
}
|
|
8521
|
+
if (seen.has(id)) {
|
|
8522
|
+
issues.push({
|
|
8523
|
+
code: "DUPLICATE_NODE_ID",
|
|
8524
|
+
nodeId: id,
|
|
8525
|
+
message: `Duplicate task graph node id: ${id}`
|
|
8526
|
+
});
|
|
8527
|
+
continue;
|
|
8528
|
+
}
|
|
8529
|
+
seen.add(id);
|
|
8530
|
+
nodes.push({
|
|
8531
|
+
id,
|
|
8532
|
+
title,
|
|
8533
|
+
kind: input.kind,
|
|
8534
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
8535
|
+
payload: input.payload,
|
|
8536
|
+
ordinal
|
|
8537
|
+
});
|
|
8538
|
+
}
|
|
8539
|
+
throwIfStrict(mode, issues);
|
|
8540
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
8541
|
+
const sanitized = nodes.map((node) => {
|
|
8542
|
+
const dependsOn = [];
|
|
8543
|
+
for (const dependencyId of node.dependsOn) {
|
|
8544
|
+
if (dependencyId === node.id) {
|
|
8545
|
+
issues.push({
|
|
8546
|
+
code: "SELF_DEPENDENCY",
|
|
8547
|
+
nodeId: node.id,
|
|
8548
|
+
dependencyId,
|
|
8549
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
8550
|
+
});
|
|
8551
|
+
continue;
|
|
8552
|
+
}
|
|
8553
|
+
if (!ids.has(dependencyId)) {
|
|
8554
|
+
issues.push({
|
|
8555
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
8556
|
+
nodeId: node.id,
|
|
8557
|
+
dependencyId,
|
|
8558
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
8559
|
+
});
|
|
8560
|
+
continue;
|
|
8561
|
+
}
|
|
8562
|
+
dependsOn.push(dependencyId);
|
|
8563
|
+
}
|
|
8564
|
+
return { ...node, dependsOn };
|
|
8565
|
+
});
|
|
8566
|
+
throwIfStrict(mode, issues);
|
|
8567
|
+
const cyclic = cycleNodes(sanitized);
|
|
8568
|
+
let repaired = sanitized;
|
|
8569
|
+
if (cyclic.size > 0) {
|
|
8570
|
+
const cycleIssues = [];
|
|
8571
|
+
repaired = sanitized.map((node) => {
|
|
8572
|
+
if (!cyclic.has(node.id)) return node;
|
|
8573
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
8574
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
8575
|
+
cycleIssues.push({
|
|
8576
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
8577
|
+
nodeId: node.id,
|
|
8578
|
+
dependencyId,
|
|
8579
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
8580
|
+
});
|
|
8581
|
+
return false;
|
|
8582
|
+
});
|
|
8583
|
+
return { ...node, dependsOn };
|
|
8584
|
+
});
|
|
8585
|
+
issues.push(...cycleIssues);
|
|
8586
|
+
throwIfStrict(mode, cycleIssues);
|
|
8587
|
+
}
|
|
8588
|
+
const remainingCycle = cycleNodes(repaired);
|
|
8589
|
+
if (remainingCycle.size > 0) {
|
|
8590
|
+
throw new TaskGraphValidationError([
|
|
8591
|
+
{
|
|
8592
|
+
code: "INVALID_NODE",
|
|
8593
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
8594
|
+
}
|
|
8595
|
+
]);
|
|
8596
|
+
}
|
|
8597
|
+
return {
|
|
8598
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
8599
|
+
issues
|
|
8600
|
+
};
|
|
8601
|
+
}
|
|
8602
|
+
|
|
8603
|
+
// src/core/orchestration/adapters.ts
|
|
8604
|
+
function compileSectionGraph(sections) {
|
|
8605
|
+
return compileTaskGraph(
|
|
8606
|
+
sections.map((section) => ({
|
|
8607
|
+
id: section.sectionId,
|
|
8608
|
+
title: section.sectionTitle,
|
|
8609
|
+
kind: "section",
|
|
8610
|
+
dependsOn: section.dependsOn,
|
|
8611
|
+
payload: section
|
|
8612
|
+
})),
|
|
8613
|
+
{ mode: "repair" }
|
|
8614
|
+
);
|
|
8615
|
+
}
|
|
8616
|
+
function compileSubtaskGraph(subtasks) {
|
|
8617
|
+
return compileTaskGraph(
|
|
8618
|
+
subtasks.map((subtask) => ({
|
|
8619
|
+
id: subtask.subtaskId,
|
|
8620
|
+
title: subtask.subtaskTitle,
|
|
8621
|
+
kind: "subtask",
|
|
8622
|
+
dependsOn: subtask.dependsOn,
|
|
8623
|
+
payload: subtask
|
|
8624
|
+
})),
|
|
8625
|
+
{ mode: "repair" }
|
|
8626
|
+
);
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8303
8629
|
// src/core/tiers/t2-manager.ts
|
|
8304
8630
|
function buildT2SystemPrompt(has) {
|
|
8305
8631
|
const generation = describeGenerationForPlanner(has);
|
|
8306
8632
|
return [
|
|
8307
8633
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
8308
|
-
|
|
8634
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
8635
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
8636
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
8637
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
8638
|
+
// real worker call on every small section.
|
|
8639
|
+
"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.",
|
|
8309
8640
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
8310
8641
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
8311
8642
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -8701,21 +9032,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8701
9032
|
* then reduce in-degrees of their dependents and repeat.
|
|
8702
9033
|
*/
|
|
8703
9034
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
9035
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
9036
|
+
for (const issue of compiled.issues) {
|
|
9037
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
9038
|
+
}
|
|
9039
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
9040
|
+
const assignment = node.payload;
|
|
9041
|
+
const original = assignment.dependsOn ?? [];
|
|
9042
|
+
const repaired = node.dependsOn;
|
|
9043
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
9044
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
9045
|
+
});
|
|
8704
9046
|
const adj = /* @__PURE__ */ new Map();
|
|
8705
9047
|
const inDegree = /* @__PURE__ */ new Map();
|
|
8706
9048
|
const resultMap = /* @__PURE__ */ new Map();
|
|
8707
|
-
for (const a of
|
|
9049
|
+
for (const a of sanitizedAssignments) {
|
|
8708
9050
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
8709
9051
|
inDegree.set(a.subtaskId, 0);
|
|
8710
9052
|
}
|
|
8711
|
-
for (const a of
|
|
8712
|
-
const
|
|
8713
|
-
for (const dep of deps) {
|
|
9053
|
+
for (const a of sanitizedAssignments) {
|
|
9054
|
+
for (const dep of a.dependsOn ?? []) {
|
|
8714
9055
|
adj.get(dep).add(a.subtaskId);
|
|
8715
9056
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
8716
9057
|
}
|
|
8717
9058
|
}
|
|
8718
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
8719
9059
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
8720
9060
|
let wave = 0;
|
|
8721
9061
|
let respawnBudget = 1;
|
|
@@ -8922,49 +9262,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8922
9262
|
}
|
|
8923
9263
|
return [...resultMap.values()];
|
|
8924
9264
|
}
|
|
8925
|
-
/**
|
|
8926
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
8927
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
8928
|
-
*
|
|
8929
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
8930
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
8931
|
-
*/
|
|
8932
|
-
breakCycles(assignments, adj, inDegree) {
|
|
8933
|
-
const degree = new Map(inDegree);
|
|
8934
|
-
const queue = [];
|
|
8935
|
-
const visited = /* @__PURE__ */ new Set();
|
|
8936
|
-
for (const [id, d] of degree) {
|
|
8937
|
-
if (d === 0) queue.push(id);
|
|
8938
|
-
}
|
|
8939
|
-
while (queue.length > 0) {
|
|
8940
|
-
const id = queue.shift();
|
|
8941
|
-
visited.add(id);
|
|
8942
|
-
for (const dep of adj.get(id) ?? []) {
|
|
8943
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
8944
|
-
degree.set(dep, newDeg);
|
|
8945
|
-
if (newDeg === 0) queue.push(dep);
|
|
8946
|
-
}
|
|
8947
|
-
}
|
|
8948
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
8949
|
-
if (cycleNodes.length === 0) return assignments;
|
|
8950
|
-
this.log(
|
|
8951
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
8952
|
-
);
|
|
8953
|
-
return assignments.map((a) => {
|
|
8954
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
8955
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
8956
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
8957
|
-
this.log(
|
|
8958
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
8959
|
-
);
|
|
8960
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
8961
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
8962
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
8963
|
-
}
|
|
8964
|
-
}
|
|
8965
|
-
return { ...a, dependsOn: safeDeps };
|
|
8966
|
-
});
|
|
8967
|
-
}
|
|
8968
9265
|
async retryT3(assignment, taskId) {
|
|
8969
9266
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
8970
9267
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -9100,6 +9397,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
9100
9397
|
}
|
|
9101
9398
|
};
|
|
9102
9399
|
|
|
9400
|
+
// src/core/orchestration/scheduler.ts
|
|
9401
|
+
var DependencyScheduler = class {
|
|
9402
|
+
constructor(graph, hooks, options = {}) {
|
|
9403
|
+
this.graph = graph;
|
|
9404
|
+
this.hooks = hooks;
|
|
9405
|
+
this.options = options;
|
|
9406
|
+
}
|
|
9407
|
+
graph;
|
|
9408
|
+
hooks;
|
|
9409
|
+
options;
|
|
9410
|
+
async run() {
|
|
9411
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
9412
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
9413
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
9414
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
9415
|
+
for (const node of nodes) {
|
|
9416
|
+
for (const dependencyId of node.dependsOn) {
|
|
9417
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
9418
|
+
}
|
|
9419
|
+
}
|
|
9420
|
+
const completed = /* @__PURE__ */ new Set();
|
|
9421
|
+
const results = /* @__PURE__ */ new Map();
|
|
9422
|
+
const waves = [];
|
|
9423
|
+
let wave = 0;
|
|
9424
|
+
while (completed.size < nodes.length) {
|
|
9425
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
9426
|
+
if (ready.length === 0) {
|
|
9427
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
9428
|
+
}
|
|
9429
|
+
waves.push(ready.map((node) => node.id));
|
|
9430
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
9431
|
+
const runOne = async (node) => {
|
|
9432
|
+
const result = await this.hooks.execute(node);
|
|
9433
|
+
results.set(node.id, result);
|
|
9434
|
+
completed.add(node.id);
|
|
9435
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
9436
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9437
|
+
}
|
|
9438
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
9439
|
+
};
|
|
9440
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
9441
|
+
for (const node of ready) await runOne(node);
|
|
9442
|
+
} else {
|
|
9443
|
+
await Promise.all(ready.map(runOne));
|
|
9444
|
+
}
|
|
9445
|
+
wave++;
|
|
9446
|
+
}
|
|
9447
|
+
return { results, waves };
|
|
9448
|
+
}
|
|
9449
|
+
};
|
|
9450
|
+
|
|
9103
9451
|
// src/utils/json-extract.ts
|
|
9104
9452
|
function extractFirstJsonObject(input) {
|
|
9105
9453
|
if (!input) return null;
|
|
@@ -9171,7 +9519,7 @@ function buildT1SystemPrompt(has) {
|
|
|
9171
9519
|
Your responsibilities:
|
|
9172
9520
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
9173
9521
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
9174
|
-
3. For each section, define
|
|
9522
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
9175
9523
|
4. Return a structured plan as JSON
|
|
9176
9524
|
|
|
9177
9525
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -9586,7 +9934,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9586
9934
|
}
|
|
9587
9935
|
}
|
|
9588
9936
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
9589
|
-
if (plan.sections.length < min)
|
|
9937
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
9938
|
+
this.log(
|
|
9939
|
+
`\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.`
|
|
9940
|
+
);
|
|
9941
|
+
}
|
|
9590
9942
|
}
|
|
9591
9943
|
async dispatchT2Managers(sections) {
|
|
9592
9944
|
for (const section of sections) {
|
|
@@ -9698,114 +10050,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9698
10050
|
return t2Results;
|
|
9699
10051
|
}
|
|
9700
10052
|
/**
|
|
9701
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
10053
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
10054
|
+
*
|
|
10055
|
+
* Graph construction, validation and cycle repair now live in
|
|
10056
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
10057
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
10058
|
+
* ordered by one implementation rather than two that drifted.
|
|
10059
|
+
*
|
|
10060
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
10061
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
10062
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
10063
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
10064
|
+
* sections and started them immediately — before the sections they consume.
|
|
10065
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
10066
|
+
* members are touched.
|
|
9702
10067
|
*/
|
|
9703
10068
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
9704
|
-
const
|
|
9705
|
-
const
|
|
9706
|
-
|
|
9707
|
-
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
9708
|
-
for (const s of sections) {
|
|
9709
|
-
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
9710
|
-
inDegree.set(s.sectionId, 0);
|
|
9711
|
-
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
10069
|
+
const compiled = compileSectionGraph(sections);
|
|
10070
|
+
for (const issue of compiled.issues) {
|
|
10071
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
9712
10072
|
}
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
}
|
|
9718
|
-
}
|
|
9719
|
-
const queue = [];
|
|
9720
|
-
const degree = new Map(inDegree);
|
|
9721
|
-
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
9722
|
-
const visited = /* @__PURE__ */ new Set();
|
|
9723
|
-
while (queue.length > 0) {
|
|
9724
|
-
const u = queue.shift();
|
|
9725
|
-
visited.add(u);
|
|
9726
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
9727
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
9728
|
-
degree.set(v, newDeg);
|
|
9729
|
-
if (newDeg === 0) queue.push(v);
|
|
9730
|
-
}
|
|
9731
|
-
}
|
|
9732
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
9733
|
-
if (cycleNodes.length > 0) {
|
|
9734
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
9735
|
-
for (const s of sections) {
|
|
9736
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
9737
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
9738
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
9739
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
9740
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
9741
|
-
}
|
|
9742
|
-
s.dependsOn = safeDeps;
|
|
9743
|
-
}
|
|
9744
|
-
}
|
|
10073
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
10074
|
+
for (const section of sections) {
|
|
10075
|
+
const node = byId.get(section.sectionId);
|
|
10076
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
9745
10077
|
}
|
|
9746
|
-
const
|
|
10078
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
10079
|
+
const totalSections = compiled.graph.nodes.length;
|
|
9747
10080
|
let completedSections = 0;
|
|
9748
|
-
const
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
10081
|
+
const scheduler = new DependencyScheduler(
|
|
10082
|
+
compiled.graph,
|
|
10083
|
+
{
|
|
10084
|
+
execute: async (node) => {
|
|
10085
|
+
const section = node.payload;
|
|
10086
|
+
const manager = managers[node.ordinal];
|
|
10087
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
10088
|
+
this.sendStatusUpdate({
|
|
10089
|
+
progressPct,
|
|
10090
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
10091
|
+
status: "IN_PROGRESS"
|
|
10092
|
+
});
|
|
10093
|
+
this.throwIfCancelled();
|
|
10094
|
+
try {
|
|
10095
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
10096
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
10097
|
+
if (result.status === "ESCALATED") {
|
|
10098
|
+
this.escalations.push({
|
|
10099
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
10100
|
+
sectionId: section.sectionId,
|
|
10101
|
+
attempted: result.issues,
|
|
10102
|
+
blocker: result.issues.join("; "),
|
|
10103
|
+
needs: "Human review required"
|
|
10104
|
+
});
|
|
10105
|
+
}
|
|
10106
|
+
return result;
|
|
10107
|
+
} catch (err) {
|
|
10108
|
+
return {
|
|
9775
10109
|
sectionId: section.sectionId,
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
10110
|
+
sectionTitle: section.sectionTitle,
|
|
10111
|
+
status: "FAILED",
|
|
10112
|
+
t3Results: [],
|
|
10113
|
+
sectionSummary: "",
|
|
10114
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
10115
|
+
};
|
|
9780
10116
|
}
|
|
9781
|
-
}
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
resultMap.set(id, result);
|
|
9792
|
-
completedSections++;
|
|
9793
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
9794
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9795
|
-
}
|
|
9796
|
-
};
|
|
9797
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
9798
|
-
for (const id of readyIds) {
|
|
9799
|
-
await runOne(id);
|
|
10117
|
+
},
|
|
10118
|
+
onNodeComplete: (node, result) => {
|
|
10119
|
+
resultMap.set(node.id, result);
|
|
10120
|
+
completedSections++;
|
|
10121
|
+
this.emit("section:complete", {
|
|
10122
|
+
id: node.id,
|
|
10123
|
+
title: result.sectionTitle,
|
|
10124
|
+
status: result.status,
|
|
10125
|
+
output: result.sectionSummary
|
|
10126
|
+
});
|
|
9800
10127
|
}
|
|
9801
|
-
}
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
10128
|
+
},
|
|
10129
|
+
{
|
|
10130
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
10131
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
10132
|
+
// serialized T3 workers within a single section while every section's
|
|
10133
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
10134
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
9806
10135
|
}
|
|
9807
|
-
|
|
9808
|
-
await
|
|
10136
|
+
);
|
|
10137
|
+
await scheduler.run();
|
|
9809
10138
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
9810
10139
|
}
|
|
9811
10140
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -10435,16 +10764,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
10435
10764
|
if (typeof input !== "string" || input.length === 0) {
|
|
10436
10765
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10437
10766
|
}
|
|
10438
|
-
const root =
|
|
10439
|
-
const abs =
|
|
10440
|
-
const rel =
|
|
10441
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
10767
|
+
const root = path26__default.default.resolve(workspaceRoot);
|
|
10768
|
+
const abs = path26__default.default.isAbsolute(input) ? path26__default.default.resolve(input) : path26__default.default.resolve(root, input);
|
|
10769
|
+
const rel = path26__default.default.relative(root, abs);
|
|
10770
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26__default.default.isAbsolute(rel)) {
|
|
10442
10771
|
throw new WorkspaceSandboxError(input, root);
|
|
10443
10772
|
}
|
|
10444
10773
|
try {
|
|
10445
|
-
const real =
|
|
10446
|
-
const realRel =
|
|
10447
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
10774
|
+
const real = fs25__default.default.realpathSync(abs);
|
|
10775
|
+
const realRel = path26__default.default.relative(root, real);
|
|
10776
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26__default.default.isAbsolute(realRel))) {
|
|
10448
10777
|
throw new WorkspaceSandboxError(input, root);
|
|
10449
10778
|
}
|
|
10450
10779
|
} catch (e) {
|
|
@@ -11102,7 +11431,7 @@ var GenerateDocumentTool = class extends BaseTool {
|
|
|
11102
11431
|
}
|
|
11103
11432
|
}
|
|
11104
11433
|
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11105
|
-
await fs5__default.default.mkdir(
|
|
11434
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(absPath), { recursive: true });
|
|
11106
11435
|
await fs5__default.default.writeFile(absPath, bytes);
|
|
11107
11436
|
const charts = countCharts(content);
|
|
11108
11437
|
return [
|
|
@@ -11497,7 +11826,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
11497
11826
|
} catch {
|
|
11498
11827
|
}
|
|
11499
11828
|
}
|
|
11500
|
-
await fs5__default.default.mkdir(
|
|
11829
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(absPath), { recursive: true });
|
|
11501
11830
|
await fs5__default.default.writeFile(absPath, content, "utf-8");
|
|
11502
11831
|
return `Written ${content.length} characters to ${filePath}`;
|
|
11503
11832
|
}
|
|
@@ -11995,7 +12324,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11995
12324
|
};
|
|
11996
12325
|
async function fileToImageAttachment(filePath) {
|
|
11997
12326
|
const data = await fs5__default.default.readFile(filePath);
|
|
11998
|
-
const ext =
|
|
12327
|
+
const ext = path26__default.default.extname(filePath).toLowerCase();
|
|
11999
12328
|
const mimeMap = {
|
|
12000
12329
|
".jpg": "image/jpeg",
|
|
12001
12330
|
".jpeg": "image/jpeg",
|
|
@@ -12029,14 +12358,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
12029
12358
|
const filePath = input["path"];
|
|
12030
12359
|
const content = input["content"];
|
|
12031
12360
|
const title = input["title"];
|
|
12032
|
-
const dir =
|
|
12033
|
-
if (!
|
|
12034
|
-
|
|
12361
|
+
const dir = path26__default.default.dirname(filePath);
|
|
12362
|
+
if (!fs25__default.default.existsSync(dir)) {
|
|
12363
|
+
fs25__default.default.mkdirSync(dir, { recursive: true });
|
|
12035
12364
|
}
|
|
12036
12365
|
return new Promise((resolve2, reject) => {
|
|
12037
12366
|
try {
|
|
12038
12367
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
12039
|
-
const stream =
|
|
12368
|
+
const stream = fs25__default.default.createWriteStream(filePath);
|
|
12040
12369
|
doc.pipe(stream);
|
|
12041
12370
|
if (title) {
|
|
12042
12371
|
doc.info["Title"] = title;
|
|
@@ -12114,22 +12443,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
12114
12443
|
}
|
|
12115
12444
|
cmdPrefix = NODE_CMD;
|
|
12116
12445
|
}
|
|
12117
|
-
const tmpDir =
|
|
12118
|
-
if (!
|
|
12119
|
-
|
|
12446
|
+
const tmpDir = path26__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12447
|
+
if (!fs25__default.default.existsSync(tmpDir)) {
|
|
12448
|
+
fs25__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
12120
12449
|
}
|
|
12121
12450
|
const extension = language === "python" ? "py" : "js";
|
|
12122
12451
|
const fileName = `intp_${crypto4.randomUUID().slice(0, 8)}.${extension}`;
|
|
12123
|
-
const filePath =
|
|
12124
|
-
|
|
12452
|
+
const filePath = path26__default.default.join(tmpDir, fileName);
|
|
12453
|
+
fs25__default.default.writeFileSync(filePath, code, "utf-8");
|
|
12125
12454
|
const execArgs = [filePath, ...args];
|
|
12126
12455
|
return new Promise((resolve2) => {
|
|
12127
12456
|
const startMs = Date.now();
|
|
12128
12457
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
12129
12458
|
const duration = Date.now() - startMs;
|
|
12130
12459
|
try {
|
|
12131
|
-
if (
|
|
12132
|
-
|
|
12460
|
+
if (fs25__default.default.existsSync(filePath)) {
|
|
12461
|
+
fs25__default.default.unlinkSync(filePath);
|
|
12133
12462
|
}
|
|
12134
12463
|
} catch (cleanupErr) {
|
|
12135
12464
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -12441,7 +12770,7 @@ var GlobTool = class extends BaseTool {
|
|
|
12441
12770
|
};
|
|
12442
12771
|
async execute(input, _options) {
|
|
12443
12772
|
const pattern = input["pattern"];
|
|
12444
|
-
const searchPath = input["path"] ?
|
|
12773
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
12445
12774
|
const matches = await glob.glob(pattern, {
|
|
12446
12775
|
cwd: searchPath,
|
|
12447
12776
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -12454,7 +12783,7 @@ var GlobTool = class extends BaseTool {
|
|
|
12454
12783
|
const withMtime = await Promise.all(
|
|
12455
12784
|
matches.map(async (rel) => {
|
|
12456
12785
|
try {
|
|
12457
|
-
const stat = await fs5__default.default.stat(
|
|
12786
|
+
const stat = await fs5__default.default.stat(path26__default.default.join(searchPath, rel));
|
|
12458
12787
|
return { rel, mtime: stat.mtimeMs };
|
|
12459
12788
|
} catch {
|
|
12460
12789
|
return { rel, mtime: 0 };
|
|
@@ -12503,7 +12832,7 @@ var GrepTool = class extends BaseTool {
|
|
|
12503
12832
|
};
|
|
12504
12833
|
async execute(input, _options) {
|
|
12505
12834
|
const pattern = input["pattern"];
|
|
12506
|
-
const searchPath = input["path"] ?
|
|
12835
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
12507
12836
|
const globPattern = input["glob"];
|
|
12508
12837
|
const outputMode = input["output_mode"] ?? "content";
|
|
12509
12838
|
const context = input["context"] ?? 0;
|
|
@@ -12557,12 +12886,12 @@ var GrepTool = class extends BaseTool {
|
|
|
12557
12886
|
nodir: true
|
|
12558
12887
|
});
|
|
12559
12888
|
} catch {
|
|
12560
|
-
files = [
|
|
12889
|
+
files = [path26__default.default.relative(searchPath, searchPath) || "."];
|
|
12561
12890
|
}
|
|
12562
12891
|
const results = [];
|
|
12563
12892
|
let totalCount = 0;
|
|
12564
12893
|
for (const rel of files) {
|
|
12565
|
-
const abs =
|
|
12894
|
+
const abs = path26__default.default.join(searchPath, rel);
|
|
12566
12895
|
let content;
|
|
12567
12896
|
try {
|
|
12568
12897
|
content = await fs5__default.default.readFile(abs, "utf-8");
|
|
@@ -12949,10 +13278,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
12949
13278
|
}
|
|
12950
13279
|
isIgnored(filePath) {
|
|
12951
13280
|
if (!filePath) return false;
|
|
12952
|
-
const abs =
|
|
12953
|
-
const rel =
|
|
12954
|
-
if (!rel || rel.startsWith("..") ||
|
|
12955
|
-
const posixRel = rel.split(
|
|
13281
|
+
const abs = path26__default.default.resolve(this.workspaceRoot, filePath);
|
|
13282
|
+
const rel = path26__default.default.relative(this.workspaceRoot, abs);
|
|
13283
|
+
if (!rel || rel.startsWith("..") || path26__default.default.isAbsolute(rel)) return true;
|
|
13284
|
+
const posixRel = rel.split(path26__default.default.sep).join("/");
|
|
12956
13285
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12957
13286
|
}
|
|
12958
13287
|
};
|
|
@@ -13186,22 +13515,22 @@ var FileMcpOAuthStore = class {
|
|
|
13186
13515
|
filePath;
|
|
13187
13516
|
load() {
|
|
13188
13517
|
try {
|
|
13189
|
-
return JSON.parse(
|
|
13518
|
+
return JSON.parse(fs25__default.default.readFileSync(this.filePath, "utf-8"));
|
|
13190
13519
|
} catch {
|
|
13191
13520
|
return void 0;
|
|
13192
13521
|
}
|
|
13193
13522
|
}
|
|
13194
13523
|
save(state) {
|
|
13195
|
-
|
|
13196
|
-
|
|
13524
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13525
|
+
fs25__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
13197
13526
|
try {
|
|
13198
|
-
|
|
13527
|
+
fs25__default.default.chmodSync(this.filePath, 384);
|
|
13199
13528
|
} catch {
|
|
13200
13529
|
}
|
|
13201
13530
|
}
|
|
13202
13531
|
clear() {
|
|
13203
13532
|
try {
|
|
13204
|
-
|
|
13533
|
+
fs25__default.default.rmSync(this.filePath);
|
|
13205
13534
|
} catch {
|
|
13206
13535
|
}
|
|
13207
13536
|
}
|
|
@@ -14098,6 +14427,14 @@ function heuristicAnalyze(prompt) {
|
|
|
14098
14427
|
const estimatedTokens = wordCount * 5;
|
|
14099
14428
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
14100
14429
|
}
|
|
14430
|
+
function hashPrompt(prompt) {
|
|
14431
|
+
let hash = 2166136261;
|
|
14432
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
14433
|
+
hash ^= prompt.charCodeAt(index);
|
|
14434
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
14435
|
+
}
|
|
14436
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
14437
|
+
}
|
|
14101
14438
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
14102
14439
|
var TASK_TYPE_TAGS = {
|
|
14103
14440
|
code: ["code", "instruction"],
|
|
@@ -14141,7 +14478,7 @@ var TaskAnalyzer = class {
|
|
|
14141
14478
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
14142
14479
|
*/
|
|
14143
14480
|
async analyze(prompt) {
|
|
14144
|
-
const cacheKey = prompt
|
|
14481
|
+
const cacheKey = hashPrompt(prompt);
|
|
14145
14482
|
const cached = analysisCache.get(cacheKey);
|
|
14146
14483
|
if (cached) {
|
|
14147
14484
|
this.lastProfile = cached;
|
|
@@ -14240,7 +14577,7 @@ var TaskAnalyzer = class {
|
|
|
14240
14577
|
analysisCache.clear();
|
|
14241
14578
|
}
|
|
14242
14579
|
};
|
|
14243
|
-
var DEFAULT_STATS_FILE =
|
|
14580
|
+
var DEFAULT_STATS_FILE = path26__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
|
|
14244
14581
|
var ModelPerformanceTracker = class {
|
|
14245
14582
|
stats = /* @__PURE__ */ new Map();
|
|
14246
14583
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -14280,7 +14617,7 @@ var ModelPerformanceTracker = class {
|
|
|
14280
14617
|
async save() {
|
|
14281
14618
|
if (this.readOnly) return;
|
|
14282
14619
|
try {
|
|
14283
|
-
await fs5__default.default.mkdir(
|
|
14620
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(this.statsFile), { recursive: true });
|
|
14284
14621
|
const modelsObj = {};
|
|
14285
14622
|
const featuresObj = {};
|
|
14286
14623
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -14828,7 +15165,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14828
15165
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
14829
15166
|
async loadPersistedTools() {
|
|
14830
15167
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14831
|
-
const file =
|
|
15168
|
+
const file = path26__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
14832
15169
|
try {
|
|
14833
15170
|
const raw = await fs5__default.default.readFile(file, "utf-8");
|
|
14834
15171
|
const specs = JSON.parse(raw);
|
|
@@ -14852,8 +15189,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14852
15189
|
}
|
|
14853
15190
|
async persist() {
|
|
14854
15191
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14855
|
-
const dir =
|
|
14856
|
-
const file =
|
|
15192
|
+
const dir = path26__default.default.join(this.workspacePath, ".cascade");
|
|
15193
|
+
const file = path26__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
14857
15194
|
try {
|
|
14858
15195
|
await fs5__default.default.mkdir(dir, { recursive: true });
|
|
14859
15196
|
await fs5__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -14873,12 +15210,12 @@ var WorldStateDB = class {
|
|
|
14873
15210
|
constructor(workspacePath, debugMode = false) {
|
|
14874
15211
|
this.workspacePath = workspacePath;
|
|
14875
15212
|
this.debugMode = debugMode;
|
|
14876
|
-
const cascadeDir =
|
|
14877
|
-
if (!
|
|
14878
|
-
|
|
15213
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
15214
|
+
if (!fs25__default.default.existsSync(cascadeDir)) {
|
|
15215
|
+
fs25__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
14879
15216
|
}
|
|
14880
|
-
this.keyPath =
|
|
14881
|
-
this.dbPath =
|
|
15217
|
+
this.keyPath = path26__default.default.join(cascadeDir, "world_state.key");
|
|
15218
|
+
this.dbPath = path26__default.default.join(cascadeDir, "world_state.db");
|
|
14882
15219
|
this.initEncryptionKey();
|
|
14883
15220
|
this.db = new Database__default.default(this.dbPath);
|
|
14884
15221
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14935,11 +15272,11 @@ var WorldStateDB = class {
|
|
|
14935
15272
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14936
15273
|
}
|
|
14937
15274
|
initEncryptionKey() {
|
|
14938
|
-
if (
|
|
14939
|
-
this.encryptionKey =
|
|
15275
|
+
if (fs25__default.default.existsSync(this.keyPath)) {
|
|
15276
|
+
this.encryptionKey = fs25__default.default.readFileSync(this.keyPath);
|
|
14940
15277
|
} else {
|
|
14941
15278
|
this.encryptionKey = crypto4__default.default.randomBytes(32);
|
|
14942
|
-
|
|
15279
|
+
fs25__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14943
15280
|
}
|
|
14944
15281
|
}
|
|
14945
15282
|
encrypt(text) {
|
|
@@ -15225,9 +15562,9 @@ var WorldStateDB = class {
|
|
|
15225
15562
|
dumpDebugIfNeeded() {
|
|
15226
15563
|
if (!this.debugMode) return;
|
|
15227
15564
|
try {
|
|
15228
|
-
const dumpPath =
|
|
15565
|
+
const dumpPath = path26__default.default.join(os6__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
15229
15566
|
const entries = this.getAllEntries();
|
|
15230
|
-
|
|
15567
|
+
fs25__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
15231
15568
|
} catch (err) {
|
|
15232
15569
|
console.error("Failed to dump debug world state", err);
|
|
15233
15570
|
}
|
|
@@ -15236,6 +15573,128 @@ var WorldStateDB = class {
|
|
|
15236
15573
|
this.db.close();
|
|
15237
15574
|
}
|
|
15238
15575
|
};
|
|
15576
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
15577
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
15578
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
15579
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
15580
|
+
function safeId(taskId) {
|
|
15581
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
15582
|
+
return cleaned.slice(0, 100) || "run";
|
|
15583
|
+
}
|
|
15584
|
+
function isCheckpoint(value) {
|
|
15585
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15586
|
+
const c = value;
|
|
15587
|
+
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);
|
|
15588
|
+
}
|
|
15589
|
+
var ResumeStore = class {
|
|
15590
|
+
dir;
|
|
15591
|
+
maxCheckpoints;
|
|
15592
|
+
maxAgeMs;
|
|
15593
|
+
now;
|
|
15594
|
+
constructor(options) {
|
|
15595
|
+
this.dir = options.dir;
|
|
15596
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
15597
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
15598
|
+
this.now = options.now ?? Date.now;
|
|
15599
|
+
}
|
|
15600
|
+
/**
|
|
15601
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
15602
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
15603
|
+
* worse outcome than the one this store exists to improve.
|
|
15604
|
+
*/
|
|
15605
|
+
async save(checkpoint) {
|
|
15606
|
+
try {
|
|
15607
|
+
const timestamp = this.now();
|
|
15608
|
+
const record = {
|
|
15609
|
+
...checkpoint,
|
|
15610
|
+
version: RESUME_SCHEMA_VERSION,
|
|
15611
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
15612
|
+
};
|
|
15613
|
+
await fs25.promises.mkdir(this.dir, { recursive: true });
|
|
15614
|
+
const finalPath = path26__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
15615
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
15616
|
+
await fs25.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
15617
|
+
await fs25.promises.rename(tempPath, finalPath);
|
|
15618
|
+
await this.prune();
|
|
15619
|
+
} catch {
|
|
15620
|
+
}
|
|
15621
|
+
}
|
|
15622
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
15623
|
+
async list() {
|
|
15624
|
+
let entries;
|
|
15625
|
+
try {
|
|
15626
|
+
entries = await fs25.promises.readdir(this.dir);
|
|
15627
|
+
} catch {
|
|
15628
|
+
return [];
|
|
15629
|
+
}
|
|
15630
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15631
|
+
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);
|
|
15632
|
+
const checkpoints = [];
|
|
15633
|
+
for (const candidate of candidates) {
|
|
15634
|
+
try {
|
|
15635
|
+
const raw = await fs25.promises.readFile(path26__default.default.join(this.dir, candidate.name), "utf-8");
|
|
15636
|
+
const parsed = JSON.parse(raw);
|
|
15637
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
15638
|
+
} catch {
|
|
15639
|
+
continue;
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15642
|
+
return checkpoints;
|
|
15643
|
+
}
|
|
15644
|
+
/** The most recent resumable run, or null. */
|
|
15645
|
+
async latest() {
|
|
15646
|
+
return (await this.list())[0] ?? null;
|
|
15647
|
+
}
|
|
15648
|
+
/**
|
|
15649
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
15650
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
15651
|
+
*/
|
|
15652
|
+
async consume(taskId) {
|
|
15653
|
+
const all = await this.list();
|
|
15654
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
15655
|
+
if (!target) return null;
|
|
15656
|
+
await this.delete(target.taskId);
|
|
15657
|
+
return target;
|
|
15658
|
+
}
|
|
15659
|
+
/** Remove every checkpoint for a task id. */
|
|
15660
|
+
async delete(taskId) {
|
|
15661
|
+
try {
|
|
15662
|
+
const entries = await fs25.promises.readdir(this.dir);
|
|
15663
|
+
const wanted = safeId(taskId);
|
|
15664
|
+
await Promise.all(entries.map(async (name) => {
|
|
15665
|
+
const match = FILE_PATTERN.exec(name);
|
|
15666
|
+
if (match?.[2] === wanted) {
|
|
15667
|
+
await fs25.promises.rm(path26__default.default.join(this.dir, name), { force: true });
|
|
15668
|
+
}
|
|
15669
|
+
}));
|
|
15670
|
+
} catch {
|
|
15671
|
+
}
|
|
15672
|
+
}
|
|
15673
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
15674
|
+
async prune() {
|
|
15675
|
+
try {
|
|
15676
|
+
const entries = await fs25.promises.readdir(this.dir);
|
|
15677
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15678
|
+
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);
|
|
15679
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
15680
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
15681
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
15682
|
+
(name) => fs25.promises.rm(path26__default.default.join(this.dir, name), { force: true })
|
|
15683
|
+
));
|
|
15684
|
+
} catch {
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
};
|
|
15688
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
15689
|
+
if (!completed.length) return "";
|
|
15690
|
+
const lines = completed.map((node) => {
|
|
15691
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
15692
|
+
return `- [${node.status}] ${node.title}
|
|
15693
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
15694
|
+
});
|
|
15695
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
15696
|
+
${lines.join("\n")}`;
|
|
15697
|
+
}
|
|
15239
15698
|
var ignore2 = ignoreFactory__namespace.default ?? ignoreFactory__namespace;
|
|
15240
15699
|
var PrivacyPaths = class {
|
|
15241
15700
|
localOnly;
|
|
@@ -15282,7 +15741,7 @@ var CascadeIgnore = class {
|
|
|
15282
15741
|
]);
|
|
15283
15742
|
}
|
|
15284
15743
|
async load(workspacePath) {
|
|
15285
|
-
const filePath =
|
|
15744
|
+
const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
15286
15745
|
try {
|
|
15287
15746
|
const content = await fs5__default.default.readFile(filePath, "utf-8");
|
|
15288
15747
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -15293,7 +15752,7 @@ var CascadeIgnore = class {
|
|
|
15293
15752
|
}
|
|
15294
15753
|
isIgnored(filePath, workspacePath) {
|
|
15295
15754
|
try {
|
|
15296
|
-
const relative = workspacePath ?
|
|
15755
|
+
const relative = workspacePath ? path26__default.default.relative(workspacePath, filePath) : filePath;
|
|
15297
15756
|
return this.ig.ignores(relative);
|
|
15298
15757
|
} catch {
|
|
15299
15758
|
return false;
|
|
@@ -15636,7 +16095,7 @@ var WorkspaceIndex = class {
|
|
|
15636
16095
|
isIgnored;
|
|
15637
16096
|
chunker;
|
|
15638
16097
|
constructor(opts) {
|
|
15639
|
-
this.root =
|
|
16098
|
+
this.root = path26__default.default.resolve(opts.root);
|
|
15640
16099
|
this.db = opts.db;
|
|
15641
16100
|
this.namespace = opts.namespace ?? "code";
|
|
15642
16101
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -15668,14 +16127,14 @@ var WorkspaceIndex = class {
|
|
|
15668
16127
|
return;
|
|
15669
16128
|
}
|
|
15670
16129
|
for (const entry of entries) {
|
|
15671
|
-
const abs =
|
|
16130
|
+
const abs = path26__default.default.join(dir, entry.name);
|
|
15672
16131
|
if (entry.isDirectory()) {
|
|
15673
16132
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
15674
16133
|
await walk(abs);
|
|
15675
16134
|
} else if (entry.isFile()) {
|
|
15676
|
-
const ext =
|
|
16135
|
+
const ext = path26__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
15677
16136
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
15678
|
-
out.push(
|
|
16137
|
+
out.push(path26__default.default.relative(this.root, abs));
|
|
15679
16138
|
}
|
|
15680
16139
|
}
|
|
15681
16140
|
};
|
|
@@ -15705,7 +16164,7 @@ var WorkspaceIndex = class {
|
|
|
15705
16164
|
const entries = [];
|
|
15706
16165
|
for (const rel of rels) {
|
|
15707
16166
|
try {
|
|
15708
|
-
const abs =
|
|
16167
|
+
const abs = path26__default.default.join(this.root, rel);
|
|
15709
16168
|
const stat = await fs5__default.default.stat(abs);
|
|
15710
16169
|
if (stat.size > this.maxFileBytes) continue;
|
|
15711
16170
|
const buf = await fs5__default.default.readFile(abs);
|
|
@@ -16243,8 +16702,16 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
16243
16702
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
16244
16703
|
decisionLog = [];
|
|
16245
16704
|
initialized = false;
|
|
16246
|
-
/**
|
|
16705
|
+
/**
|
|
16706
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
16707
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
16708
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
16709
|
+
*/
|
|
16247
16710
|
lastInterruptedRun;
|
|
16711
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
16712
|
+
resumeStore;
|
|
16713
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
16714
|
+
currentRunCompleted = [];
|
|
16248
16715
|
initPromise;
|
|
16249
16716
|
store;
|
|
16250
16717
|
audit;
|
|
@@ -16277,10 +16744,16 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
16277
16744
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
16278
16745
|
try {
|
|
16279
16746
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
16280
|
-
fileDeadModelPersistence(
|
|
16747
|
+
fileDeadModelPersistence(path26__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
16281
16748
|
));
|
|
16282
16749
|
} catch {
|
|
16283
16750
|
}
|
|
16751
|
+
try {
|
|
16752
|
+
this.resumeStore = new ResumeStore({
|
|
16753
|
+
dir: path26__default.default.join(workspacePath, ".cascade", "resume")
|
|
16754
|
+
});
|
|
16755
|
+
} catch {
|
|
16756
|
+
}
|
|
16284
16757
|
this.multimodal = new MultimodalRegistry(
|
|
16285
16758
|
(this.config.providers ?? []).map((p) => p.type)
|
|
16286
16759
|
);
|
|
@@ -16365,8 +16838,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
16365
16838
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
16366
16839
|
return;
|
|
16367
16840
|
}
|
|
16368
|
-
const dbPath = ci.dbPath ||
|
|
16369
|
-
|
|
16841
|
+
const dbPath = ci.dbPath || path26__default.default.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16842
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(dbPath), { recursive: true });
|
|
16370
16843
|
const db = new Database__default.default(dbPath);
|
|
16371
16844
|
db.pragma("journal_mode = WAL");
|
|
16372
16845
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -16746,10 +17219,43 @@ ${input}` }],
|
|
|
16746
17219
|
setFeedbackSource(source) {
|
|
16747
17220
|
this.router.setFeedbackSource(source);
|
|
16748
17221
|
}
|
|
16749
|
-
/**
|
|
17222
|
+
/**
|
|
17223
|
+
* Write a durable checkpoint for an interrupted run.
|
|
17224
|
+
*
|
|
17225
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
17226
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
17227
|
+
* worse. Nothing here is allowed to throw.
|
|
17228
|
+
*/
|
|
17229
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
17230
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
17231
|
+
try {
|
|
17232
|
+
await this.resumeStore?.save({
|
|
17233
|
+
taskId,
|
|
17234
|
+
prompt,
|
|
17235
|
+
reason,
|
|
17236
|
+
...detail ? { detail } : {},
|
|
17237
|
+
partialOutput,
|
|
17238
|
+
completed: [...this.currentRunCompleted]
|
|
17239
|
+
});
|
|
17240
|
+
} catch {
|
|
17241
|
+
}
|
|
17242
|
+
}
|
|
17243
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
16750
17244
|
hasResumableRun() {
|
|
16751
17245
|
return this.lastInterruptedRun != null;
|
|
16752
17246
|
}
|
|
17247
|
+
/**
|
|
17248
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
17249
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
17250
|
+
* answer for callers already on a hot path.
|
|
17251
|
+
*/
|
|
17252
|
+
async hasDurableResume() {
|
|
17253
|
+
return await this.resumeStore?.latest() != null;
|
|
17254
|
+
}
|
|
17255
|
+
/** Interrupted runs available to resume, newest first. */
|
|
17256
|
+
async listResumableRuns() {
|
|
17257
|
+
return await this.resumeStore?.list() ?? [];
|
|
17258
|
+
}
|
|
16753
17259
|
/**
|
|
16754
17260
|
* Raise the per-run token budget for a resume and return the continuation
|
|
16755
17261
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -16760,22 +17266,54 @@ ${input}` }],
|
|
|
16760
17266
|
const last = this.lastInterruptedRun;
|
|
16761
17267
|
if (!last) return null;
|
|
16762
17268
|
this.lastInterruptedRun = void 0;
|
|
16763
|
-
|
|
17269
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17270
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
17271
|
+
}
|
|
17272
|
+
/**
|
|
17273
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
17274
|
+
* process, including one that crashed before it could return. Consumes the
|
|
17275
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
17276
|
+
*/
|
|
17277
|
+
async prepareDurableResume(opts = {}) {
|
|
17278
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
17279
|
+
if (!checkpoint) return null;
|
|
17280
|
+
this.lastInterruptedRun = void 0;
|
|
17281
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17282
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
17283
|
+
}
|
|
17284
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
17285
|
+
raiseBudgetForResume(maxTokens) {
|
|
17286
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
16764
17287
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
16765
17288
|
this.router.setMaxTokensPerRun(raised);
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
17289
|
+
}
|
|
17290
|
+
/**
|
|
17291
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
17292
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
17293
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
17294
|
+
* is something it can act on. That difference is the entire reason node
|
|
17295
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
17296
|
+
* finished work again, and the user pays for it a second time.
|
|
17297
|
+
*/
|
|
17298
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
17299
|
+
const done = summarizeCompleted(completed);
|
|
17300
|
+
return [
|
|
17301
|
+
"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.",
|
|
17302
|
+
"",
|
|
17303
|
+
`Original task: ${prompt}`,
|
|
17304
|
+
done ? `
|
|
17305
|
+
${done}` : "",
|
|
17306
|
+
partialOutput ? `
|
|
16770
17307
|
Partial result so far:
|
|
16771
|
-
${
|
|
17308
|
+
${partialOutput}` : ""
|
|
17309
|
+
].filter(Boolean).join("\n");
|
|
16772
17310
|
}
|
|
16773
17311
|
/**
|
|
16774
17312
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
16775
17313
|
* Returns null when there is nothing to resume.
|
|
16776
17314
|
*/
|
|
16777
17315
|
async resumeRun(opts = {}) {
|
|
16778
|
-
const prompt = this.prepareResume(opts);
|
|
17316
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
16779
17317
|
if (!prompt) return null;
|
|
16780
17318
|
return this.run({ prompt });
|
|
16781
17319
|
}
|
|
@@ -17146,6 +17684,7 @@ ${prompt}` : prompt;
|
|
|
17146
17684
|
const startMs = Date.now();
|
|
17147
17685
|
const taskId = crypto4.randomUUID();
|
|
17148
17686
|
this.decisionLog = [];
|
|
17687
|
+
this.currentRunCompleted = [];
|
|
17149
17688
|
if (options.fastAnswer) {
|
|
17150
17689
|
return this.runFastAnswer(options, startMs, taskId);
|
|
17151
17690
|
}
|
|
@@ -17260,6 +17799,11 @@ ${prompt}` : prompt;
|
|
|
17260
17799
|
});
|
|
17261
17800
|
tier.on("log", (e) => this.emit("log", e));
|
|
17262
17801
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
17802
|
+
tier.on("section:complete", (e) => {
|
|
17803
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
17804
|
+
this.currentRunCompleted.push(e);
|
|
17805
|
+
this.emit("section:complete", e);
|
|
17806
|
+
});
|
|
17263
17807
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
17264
17808
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
17265
17809
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -17406,6 +17950,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
17406
17950
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
17407
17951
|
const trip = runBreaker.reason();
|
|
17408
17952
|
if (trip) {
|
|
17953
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
17409
17954
|
finalOutput = [
|
|
17410
17955
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
17411
17956
|
"",
|
|
@@ -17425,6 +17970,13 @@ ${finalOutput}` : ""
|
|
|
17425
17970
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
17426
17971
|
partialOutput: finalOutput || ""
|
|
17427
17972
|
});
|
|
17973
|
+
await this.checkpointRun(
|
|
17974
|
+
taskId,
|
|
17975
|
+
options.prompt,
|
|
17976
|
+
finalOutput || "",
|
|
17977
|
+
"cancelled",
|
|
17978
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
17979
|
+
);
|
|
17428
17980
|
runError = null;
|
|
17429
17981
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
17430
17982
|
this.emit("run:budget-exceeded", {
|
|
@@ -17433,9 +17985,17 @@ ${finalOutput}` : ""
|
|
|
17433
17985
|
partialOutput: finalOutput || ""
|
|
17434
17986
|
});
|
|
17435
17987
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
17988
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
17436
17989
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
17437
17990
|
runError = null;
|
|
17438
17991
|
} else {
|
|
17992
|
+
await this.checkpointRun(
|
|
17993
|
+
taskId,
|
|
17994
|
+
options.prompt,
|
|
17995
|
+
finalOutput || "",
|
|
17996
|
+
"error",
|
|
17997
|
+
err instanceof Error ? err.message : String(err)
|
|
17998
|
+
);
|
|
17439
17999
|
runError = err;
|
|
17440
18000
|
throw err;
|
|
17441
18001
|
}
|
|
@@ -17683,7 +18243,7 @@ var Keystore = class {
|
|
|
17683
18243
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
17684
18244
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
17685
18245
|
this.backend = "keytar";
|
|
17686
|
-
if (password &&
|
|
18246
|
+
if (password && fs25__default.default.existsSync(this.storePath)) {
|
|
17687
18247
|
try {
|
|
17688
18248
|
const fileEntries = this.decryptFile(password);
|
|
17689
18249
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -17702,7 +18262,7 @@ var Keystore = class {
|
|
|
17702
18262
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
17703
18263
|
);
|
|
17704
18264
|
}
|
|
17705
|
-
if (!
|
|
18265
|
+
if (!fs25__default.default.existsSync(this.storePath)) {
|
|
17706
18266
|
const salt = crypto4__default.default.randomBytes(SALT_LEN);
|
|
17707
18267
|
this.masterKey = this.deriveKey(password, salt);
|
|
17708
18268
|
this.writeWithSalt({}, salt);
|
|
@@ -17716,7 +18276,7 @@ var Keystore = class {
|
|
|
17716
18276
|
}
|
|
17717
18277
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
17718
18278
|
unlockSync(password) {
|
|
17719
|
-
if (!
|
|
18279
|
+
if (!fs25__default.default.existsSync(this.storePath)) {
|
|
17720
18280
|
const salt = crypto4__default.default.randomBytes(SALT_LEN);
|
|
17721
18281
|
this.masterKey = this.deriveKey(password, salt);
|
|
17722
18282
|
this.writeWithSalt({}, salt);
|
|
@@ -17774,7 +18334,7 @@ var Keystore = class {
|
|
|
17774
18334
|
}
|
|
17775
18335
|
}
|
|
17776
18336
|
decryptFile(password, knownSalt) {
|
|
17777
|
-
if (!
|
|
18337
|
+
if (!fs25__default.default.existsSync(this.storePath)) return {};
|
|
17778
18338
|
try {
|
|
17779
18339
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
17780
18340
|
const useSalt = knownSalt ?? salt;
|
|
@@ -17796,8 +18356,8 @@ var Keystore = class {
|
|
|
17796
18356
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17797
18357
|
const tag = cipher.getAuthTag();
|
|
17798
18358
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
17799
|
-
|
|
17800
|
-
|
|
18359
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
18360
|
+
fs25__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
17801
18361
|
}
|
|
17802
18362
|
writeWithSalt(data, salt) {
|
|
17803
18363
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -17807,11 +18367,11 @@ var Keystore = class {
|
|
|
17807
18367
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17808
18368
|
const tag = cipher.getAuthTag();
|
|
17809
18369
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
17810
|
-
|
|
17811
|
-
|
|
18370
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
18371
|
+
fs25__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
17812
18372
|
}
|
|
17813
18373
|
readRaw() {
|
|
17814
|
-
const buf =
|
|
18374
|
+
const buf = fs25__default.default.readFileSync(this.storePath);
|
|
17815
18375
|
let offset = 0;
|
|
17816
18376
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
17817
18377
|
offset += SALT_LEN;
|
|
@@ -17827,7 +18387,7 @@ var Keystore = class {
|
|
|
17827
18387
|
}
|
|
17828
18388
|
};
|
|
17829
18389
|
async function loadCascadeMd(workspacePath) {
|
|
17830
|
-
const filePath =
|
|
18390
|
+
const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
17831
18391
|
try {
|
|
17832
18392
|
const raw = await fs5__default.default.readFile(filePath, "utf-8");
|
|
17833
18393
|
return parseCascadeMd(raw);
|
|
@@ -17858,7 +18418,7 @@ ${raw.trim()}`;
|
|
|
17858
18418
|
var MemoryStore = class _MemoryStore {
|
|
17859
18419
|
db;
|
|
17860
18420
|
constructor(dbPath) {
|
|
17861
|
-
|
|
18421
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(dbPath), { recursive: true });
|
|
17862
18422
|
try {
|
|
17863
18423
|
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
17864
18424
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -18694,7 +19254,7 @@ Original error: ${err.message}`
|
|
|
18694
19254
|
}
|
|
18695
19255
|
};
|
|
18696
19256
|
function credentialsPath(globalDir2) {
|
|
18697
|
-
return
|
|
19257
|
+
return path26__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
18698
19258
|
}
|
|
18699
19259
|
function providerKey(p) {
|
|
18700
19260
|
if (p.type === "azure") {
|
|
@@ -18707,7 +19267,7 @@ function isPersistable(p) {
|
|
|
18707
19267
|
}
|
|
18708
19268
|
function loadGlobalCredentials(globalDir2) {
|
|
18709
19269
|
try {
|
|
18710
|
-
const raw =
|
|
19270
|
+
const raw = fs25__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
18711
19271
|
const parsed = JSON.parse(raw);
|
|
18712
19272
|
if (!Array.isArray(parsed.providers)) return [];
|
|
18713
19273
|
return parsed.providers.filter(
|
|
@@ -18719,13 +19279,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
18719
19279
|
}
|
|
18720
19280
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
18721
19281
|
const filePath = credentialsPath(globalDir2);
|
|
18722
|
-
|
|
19282
|
+
fs25__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
18723
19283
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
18724
19284
|
const tmp = `${filePath}.tmp`;
|
|
18725
|
-
|
|
18726
|
-
|
|
19285
|
+
fs25__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
19286
|
+
fs25__default.default.renameSync(tmp, filePath);
|
|
18727
19287
|
try {
|
|
18728
|
-
|
|
19288
|
+
fs25__default.default.chmodSync(filePath, 384);
|
|
18729
19289
|
} catch {
|
|
18730
19290
|
}
|
|
18731
19291
|
}
|
|
@@ -18760,7 +19320,7 @@ var ConfigManager = class {
|
|
|
18760
19320
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
18761
19321
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
18762
19322
|
this.workspacePath = workspacePath;
|
|
18763
|
-
this.globalDir = globalDirOverride ??
|
|
19323
|
+
this.globalDir = globalDirOverride ?? path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
18764
19324
|
}
|
|
18765
19325
|
async load() {
|
|
18766
19326
|
this.config = await this.loadConfig();
|
|
@@ -18774,8 +19334,8 @@ var ConfigManager = class {
|
|
|
18774
19334
|
this.ignore = new CascadeIgnore();
|
|
18775
19335
|
await this.ignore.load(this.workspacePath);
|
|
18776
19336
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
18777
|
-
this.keystore = new Keystore(
|
|
18778
|
-
this.store = new MemoryStore(
|
|
19337
|
+
this.keystore = new Keystore(path26__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
19338
|
+
this.store = new MemoryStore(path26__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
18779
19339
|
await this.injectEnvKeys();
|
|
18780
19340
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
18781
19341
|
await this.ensureDefaultIdentity();
|
|
@@ -18800,8 +19360,8 @@ var ConfigManager = class {
|
|
|
18800
19360
|
return this.workspacePath;
|
|
18801
19361
|
}
|
|
18802
19362
|
async save() {
|
|
18803
|
-
const configPath =
|
|
18804
|
-
await fs5__default.default.mkdir(
|
|
19363
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19364
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(configPath), { recursive: true });
|
|
18805
19365
|
await fs5__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18806
19366
|
try {
|
|
18807
19367
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
@@ -18837,7 +19397,7 @@ var ConfigManager = class {
|
|
|
18837
19397
|
return configProvider?.apiKey;
|
|
18838
19398
|
}
|
|
18839
19399
|
async loadConfig() {
|
|
18840
|
-
const configPath =
|
|
19400
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18841
19401
|
try {
|
|
18842
19402
|
const raw = await fs5__default.default.readFile(configPath, "utf-8");
|
|
18843
19403
|
return validateConfig(JSON.parse(raw));
|
|
@@ -19369,7 +19929,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
19369
19929
|
}
|
|
19370
19930
|
|
|
19371
19931
|
// src/dashboard/server.ts
|
|
19372
|
-
var __dirname$1 =
|
|
19932
|
+
var __dirname$1 = path26__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
19373
19933
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
19374
19934
|
var DashboardServer = class {
|
|
19375
19935
|
app;
|
|
@@ -19649,14 +20209,14 @@ var DashboardServer = class {
|
|
|
19649
20209
|
*/
|
|
19650
20210
|
persistConfig() {
|
|
19651
20211
|
try {
|
|
19652
|
-
const configPath =
|
|
19653
|
-
|
|
19654
|
-
|
|
20212
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20213
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(configPath), { recursive: true });
|
|
20214
|
+
fs25__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
19655
20215
|
} catch (err) {
|
|
19656
20216
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
19657
20217
|
}
|
|
19658
20218
|
try {
|
|
19659
|
-
saveGlobalCredentials(
|
|
20219
|
+
saveGlobalCredentials(path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
19660
20220
|
} catch (err) {
|
|
19661
20221
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
19662
20222
|
}
|
|
@@ -19671,15 +20231,15 @@ var DashboardServer = class {
|
|
|
19671
20231
|
resolveDashboardSecret() {
|
|
19672
20232
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
19673
20233
|
if (fromConfig) return fromConfig;
|
|
19674
|
-
const secretPath =
|
|
20234
|
+
const secretPath = path26__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
19675
20235
|
try {
|
|
19676
|
-
if (
|
|
19677
|
-
const existing =
|
|
20236
|
+
if (fs25__default.default.existsSync(secretPath)) {
|
|
20237
|
+
const existing = fs25__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
19678
20238
|
if (existing.length >= 16) return existing;
|
|
19679
20239
|
}
|
|
19680
20240
|
const generated = crypto4.randomUUID();
|
|
19681
|
-
|
|
19682
|
-
|
|
20241
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(secretPath), { recursive: true });
|
|
20242
|
+
fs25__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
19683
20243
|
if (this.config.dashboard.auth) {
|
|
19684
20244
|
console.warn(
|
|
19685
20245
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -19706,7 +20266,7 @@ var DashboardServer = class {
|
|
|
19706
20266
|
// ── Setup ─────────────────────────────────────
|
|
19707
20267
|
getGlobalStore() {
|
|
19708
20268
|
if (!this.globalStore) {
|
|
19709
|
-
const globalDbPath =
|
|
20269
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19710
20270
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
19711
20271
|
}
|
|
19712
20272
|
return this.globalStore;
|
|
@@ -20086,12 +20646,12 @@ ${prompt}`;
|
|
|
20086
20646
|
}
|
|
20087
20647
|
}
|
|
20088
20648
|
watchRuntimeChanges() {
|
|
20089
|
-
const workspaceDbPath =
|
|
20090
|
-
const globalDbPath =
|
|
20649
|
+
const workspaceDbPath = path26__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20650
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20091
20651
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
20092
20652
|
for (const watchPath of watchPaths) {
|
|
20093
|
-
if (!
|
|
20094
|
-
|
|
20653
|
+
if (!fs25__default.default.existsSync(watchPath)) continue;
|
|
20654
|
+
fs25__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
20095
20655
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
20096
20656
|
});
|
|
20097
20657
|
}
|
|
@@ -20213,7 +20773,7 @@ ${prompt}`;
|
|
|
20213
20773
|
const sessionId = req.params.id;
|
|
20214
20774
|
this.store.deleteSession(sessionId);
|
|
20215
20775
|
this.store.deleteRuntimeSession(sessionId);
|
|
20216
|
-
const globalDbPath =
|
|
20776
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20217
20777
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20218
20778
|
try {
|
|
20219
20779
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -20306,7 +20866,7 @@ ${prompt}`;
|
|
|
20306
20866
|
});
|
|
20307
20867
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
20308
20868
|
const body = req.body;
|
|
20309
|
-
const globalDbPath =
|
|
20869
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20310
20870
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
20311
20871
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20312
20872
|
try {
|
|
@@ -20329,7 +20889,7 @@ ${prompt}`;
|
|
|
20329
20889
|
});
|
|
20330
20890
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
20331
20891
|
this.store.deleteAllRuntimeNodes();
|
|
20332
|
-
const globalDbPath =
|
|
20892
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20333
20893
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20334
20894
|
try {
|
|
20335
20895
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -20415,12 +20975,12 @@ ${prompt}`;
|
|
|
20415
20975
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
20416
20976
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
20417
20977
|
try {
|
|
20418
|
-
const configPath =
|
|
20419
|
-
const existing =
|
|
20978
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20979
|
+
const existing = fs25__default.default.existsSync(configPath) ? JSON.parse(fs25__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
20420
20980
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
20421
20981
|
const tmp = configPath + ".tmp";
|
|
20422
|
-
|
|
20423
|
-
|
|
20982
|
+
fs25__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20983
|
+
fs25__default.default.renameSync(tmp, configPath);
|
|
20424
20984
|
res.json({ ok: true });
|
|
20425
20985
|
} catch (err) {
|
|
20426
20986
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -20448,7 +21008,7 @@ ${prompt}`;
|
|
|
20448
21008
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
20449
21009
|
const scope = req.query["scope"] ?? "workspace";
|
|
20450
21010
|
if (scope === "global") {
|
|
20451
|
-
const globalDbPath =
|
|
21011
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
20452
21012
|
const globalStore = new MemoryStore(globalDbPath);
|
|
20453
21013
|
try {
|
|
20454
21014
|
res.json({
|
|
@@ -20789,13 +21349,13 @@ ${prompt}`;
|
|
|
20789
21349
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
20790
21350
|
}
|
|
20791
21351
|
});
|
|
20792
|
-
const prodPath =
|
|
20793
|
-
const devPath =
|
|
20794
|
-
const webDistPath =
|
|
20795
|
-
if (
|
|
21352
|
+
const prodPath = path26__default.default.resolve(__dirname$1, "../web/dist");
|
|
21353
|
+
const devPath = path26__default.default.resolve(__dirname$1, "../../web/dist");
|
|
21354
|
+
const webDistPath = fs25__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
21355
|
+
if (fs25__default.default.existsSync(webDistPath)) {
|
|
20796
21356
|
this.app.use(express__default.default.static(webDistPath));
|
|
20797
21357
|
this.app.get("*", (_req, res) => {
|
|
20798
|
-
res.sendFile(
|
|
21358
|
+
res.sendFile(path26__default.default.join(webDistPath, "index.html"));
|
|
20799
21359
|
});
|
|
20800
21360
|
} else {
|
|
20801
21361
|
this.app.get("/", (_req, res) => {
|
|
@@ -20982,14 +21542,14 @@ function planRetrieval(ctx) {
|
|
|
20982
21542
|
}
|
|
20983
21543
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20984
21544
|
function globalDir(homeDir = os6__default.default.homedir()) {
|
|
20985
|
-
return
|
|
21545
|
+
return path26__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20986
21546
|
}
|
|
20987
21547
|
function cloudSessionPath(dir = globalDir()) {
|
|
20988
|
-
return
|
|
21548
|
+
return path26__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
20989
21549
|
}
|
|
20990
21550
|
function loadCloudSession(dir = globalDir()) {
|
|
20991
21551
|
try {
|
|
20992
|
-
const raw =
|
|
21552
|
+
const raw = fs25__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20993
21553
|
const parsed = JSON.parse(raw);
|
|
20994
21554
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20995
21555
|
return parsed;
|
|
@@ -20998,17 +21558,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20998
21558
|
}
|
|
20999
21559
|
}
|
|
21000
21560
|
function saveCloudSession(session, dir = globalDir()) {
|
|
21001
|
-
|
|
21561
|
+
fs25__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
21002
21562
|
const file = cloudSessionPath(dir);
|
|
21003
|
-
|
|
21563
|
+
fs25__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
21004
21564
|
try {
|
|
21005
|
-
|
|
21565
|
+
fs25__default.default.chmodSync(file, 384);
|
|
21006
21566
|
} catch {
|
|
21007
21567
|
}
|
|
21008
21568
|
}
|
|
21009
21569
|
function clearCloudSession(dir = globalDir()) {
|
|
21010
21570
|
try {
|
|
21011
|
-
|
|
21571
|
+
fs25__default.default.rmSync(cloudSessionPath(dir));
|
|
21012
21572
|
} catch {
|
|
21013
21573
|
}
|
|
21014
21574
|
}
|
|
@@ -21180,16 +21740,16 @@ var CloudClient = class _CloudClient {
|
|
|
21180
21740
|
return (await this.refresh()).accessToken;
|
|
21181
21741
|
}
|
|
21182
21742
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
21183
|
-
async authed(
|
|
21184
|
-
const call = (token) => fetch(this.url(
|
|
21743
|
+
async authed(path28, init = {}) {
|
|
21744
|
+
const call = (token) => fetch(this.url(path28), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
21185
21745
|
let res = await call(await this.accessToken());
|
|
21186
21746
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
21187
21747
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
21188
21748
|
const text = await res.text();
|
|
21189
21749
|
return text ? JSON.parse(text) : {};
|
|
21190
21750
|
}
|
|
21191
|
-
authedGet(
|
|
21192
|
-
return this.authed(
|
|
21751
|
+
authedGet(path28) {
|
|
21752
|
+
return this.authed(path28);
|
|
21193
21753
|
}
|
|
21194
21754
|
async fetchMe(token) {
|
|
21195
21755
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -21560,6 +22120,7 @@ exports.DEFAULT_RETENTION_DAYS = DEFAULT_RETENTION_DAYS;
|
|
|
21560
22120
|
exports.DEFAULT_THEME = DEFAULT_THEME;
|
|
21561
22121
|
exports.DOCUMENT_MIME = DOCUMENT_MIME;
|
|
21562
22122
|
exports.DashboardServer = DashboardServer;
|
|
22123
|
+
exports.DependencyScheduler = DependencyScheduler;
|
|
21563
22124
|
exports.FileMcpOAuthStore = FileMcpOAuthStore;
|
|
21564
22125
|
exports.GITHUB_MODELS_API_VERSION = GITHUB_MODELS_API_VERSION;
|
|
21565
22126
|
exports.GITHUB_MODELS_CATALOG_URL = GITHUB_MODELS_CATALOG_URL;
|
|
@@ -21585,6 +22146,8 @@ exports.MemoryStore = MemoryStore;
|
|
|
21585
22146
|
exports.OLLAMA_BASE_URL = OLLAMA_BASE_URL;
|
|
21586
22147
|
exports.OpenAICompatibleEmbedder = OpenAICompatibleEmbedder;
|
|
21587
22148
|
exports.PROVIDER_DISPLAY_NAMES = PROVIDER_DISPLAY_NAMES;
|
|
22149
|
+
exports.RESUME_SCHEMA_VERSION = RESUME_SCHEMA_VERSION;
|
|
22150
|
+
exports.ResumeStore = ResumeStore;
|
|
21588
22151
|
exports.Retriever = Retriever;
|
|
21589
22152
|
exports.SqliteVectorStore = SqliteVectorStore;
|
|
21590
22153
|
exports.T1Administrator = T1Administrator;
|
|
@@ -21595,6 +22158,7 @@ exports.T3Worker = T3Worker;
|
|
|
21595
22158
|
exports.T3_MODEL_PRIORITY = T3_MODEL_PRIORITY;
|
|
21596
22159
|
exports.THEME_NAMES = THEME_NAMES;
|
|
21597
22160
|
exports.TOOL_NAMES = TOOL_NAMES;
|
|
22161
|
+
exports.TaskGraphValidationError = TaskGraphValidationError;
|
|
21598
22162
|
exports.TaskScheduler = TaskScheduler;
|
|
21599
22163
|
exports.Telemetry = Telemetry;
|
|
21600
22164
|
exports.ToolRegistry = ToolRegistry;
|
|
@@ -21613,6 +22177,9 @@ exports.chartToTableRows = chartToTableRows;
|
|
|
21613
22177
|
exports.chatCompleterFromProviders = chatCompleterFromProviders;
|
|
21614
22178
|
exports.chunkCode = chunkCode;
|
|
21615
22179
|
exports.chunkText = chunkText2;
|
|
22180
|
+
exports.compileSectionGraph = compileSectionGraph;
|
|
22181
|
+
exports.compileSubtaskGraph = compileSubtaskGraph;
|
|
22182
|
+
exports.compileTaskGraph = compileTaskGraph;
|
|
21616
22183
|
exports.completeMcpOAuth = completeMcpOAuth;
|
|
21617
22184
|
exports.connectMcpWithLoopbackOAuth = connectMcpWithLoopbackOAuth;
|
|
21618
22185
|
exports.createCascade = createCascade;
|
|
@@ -21624,7 +22191,9 @@ exports.discoverMcpTools = discoverMcpTools;
|
|
|
21624
22191
|
exports.distillSessionFacts = distillSessionFacts;
|
|
21625
22192
|
exports.embedderFromProviders = embedderFromProviders;
|
|
21626
22193
|
exports.encryptSyncBlob = encryptJSON;
|
|
22194
|
+
exports.evaluateAcceptance = evaluateAcceptance;
|
|
21627
22195
|
exports.extractCharts = extractCharts;
|
|
22196
|
+
exports.failures = failures;
|
|
21628
22197
|
exports.fileExt = fileExt;
|
|
21629
22198
|
exports.fileOAuthProvider = fileOAuthProvider;
|
|
21630
22199
|
exports.gatherSyncBundle = gatherSyncBundle;
|
|
@@ -21658,6 +22227,8 @@ exports.sniffImage = sniffImage;
|
|
|
21658
22227
|
exports.splitSlides = splitSlides;
|
|
21659
22228
|
exports.streamCascade = streamCascade;
|
|
21660
22229
|
exports.stripInline = stripInline;
|
|
22230
|
+
exports.summarizeCompleted = summarizeCompleted;
|
|
22231
|
+
exports.undecided = undecided;
|
|
21661
22232
|
exports.uniqueMcpServerName = uniqueMcpServerName;
|
|
21662
22233
|
//# sourceMappingURL=index.cjs.map
|
|
21663
22234
|
//# sourceMappingURL=index.cjs.map
|