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/cli.js
CHANGED
|
@@ -7,8 +7,8 @@ import https from 'https';
|
|
|
7
7
|
import zlib from 'zlib';
|
|
8
8
|
import { Readable } from 'stream';
|
|
9
9
|
import Database2 from 'better-sqlite3';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import path34, { dirname } from 'path';
|
|
11
|
+
import fs31, { mkdirSync, writeFileSync, existsSync, readFileSync, promises } from 'fs';
|
|
12
12
|
import os10 from 'os';
|
|
13
13
|
import crypto, { randomUUID, randomBytes, webcrypto, timingSafeEqual, createHash } from 'crypto';
|
|
14
14
|
import { render, Box, Text, useStdout, useApp, useInput, Static } from 'ink';
|
|
@@ -1757,7 +1757,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1757
1757
|
var init_constants = __esm({
|
|
1758
1758
|
"src/constants.ts"() {
|
|
1759
1759
|
init_pricing();
|
|
1760
|
-
CASCADE_VERSION = "0.
|
|
1760
|
+
CASCADE_VERSION = "0.68.0";
|
|
1761
1761
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1762
1762
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1763
1763
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -3879,12 +3879,12 @@ var init_audit_logger = __esm({
|
|
|
3879
3879
|
constructor(workspacePath, debugMode = false) {
|
|
3880
3880
|
this.workspacePath = workspacePath;
|
|
3881
3881
|
this.debugMode = debugMode;
|
|
3882
|
-
const cascadeDir =
|
|
3883
|
-
if (!
|
|
3884
|
-
|
|
3882
|
+
const cascadeDir = path34.join(workspacePath, ".cascade");
|
|
3883
|
+
if (!fs31.existsSync(cascadeDir)) {
|
|
3884
|
+
fs31.mkdirSync(cascadeDir, { recursive: true });
|
|
3885
3885
|
}
|
|
3886
|
-
this.keyPath =
|
|
3887
|
-
this.dbPath =
|
|
3886
|
+
this.keyPath = path34.join(cascadeDir, "audit_log.key");
|
|
3887
|
+
this.dbPath = path34.join(cascadeDir, "audit_log.db");
|
|
3888
3888
|
this.initEncryptionKey();
|
|
3889
3889
|
this.db = new Database2(this.dbPath);
|
|
3890
3890
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3914,11 +3914,11 @@ var init_audit_logger = __esm({
|
|
|
3914
3914
|
return crypto.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
3915
3915
|
}
|
|
3916
3916
|
initEncryptionKey() {
|
|
3917
|
-
if (
|
|
3918
|
-
this.encryptionKey =
|
|
3917
|
+
if (fs31.existsSync(this.keyPath)) {
|
|
3918
|
+
this.encryptionKey = fs31.readFileSync(this.keyPath);
|
|
3919
3919
|
} else {
|
|
3920
3920
|
this.encryptionKey = crypto.randomBytes(32);
|
|
3921
|
-
|
|
3921
|
+
fs31.writeFileSync(this.keyPath, this.encryptionKey);
|
|
3922
3922
|
}
|
|
3923
3923
|
}
|
|
3924
3924
|
encrypt(text) {
|
|
@@ -3996,9 +3996,9 @@ var init_audit_logger = __esm({
|
|
|
3996
3996
|
dumpDebugIfNeeded() {
|
|
3997
3997
|
if (!this.debugMode) return;
|
|
3998
3998
|
try {
|
|
3999
|
-
const dumpPath =
|
|
3999
|
+
const dumpPath = path34.join(os10.tmpdir(), "cascade_audit_logs_debug.json");
|
|
4000
4000
|
const entries = this.getAllLogs();
|
|
4001
|
-
|
|
4001
|
+
fs31.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
4002
4002
|
} catch (err) {
|
|
4003
4003
|
console.error("Failed to dump debug audit logs", err);
|
|
4004
4004
|
}
|
|
@@ -4053,7 +4053,7 @@ var Keystore = class {
|
|
|
4053
4053
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
4054
4054
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
4055
4055
|
this.backend = "keytar";
|
|
4056
|
-
if (password &&
|
|
4056
|
+
if (password && fs31.existsSync(this.storePath)) {
|
|
4057
4057
|
try {
|
|
4058
4058
|
const fileEntries = this.decryptFile(password);
|
|
4059
4059
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -4072,7 +4072,7 @@ var Keystore = class {
|
|
|
4072
4072
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
4073
4073
|
);
|
|
4074
4074
|
}
|
|
4075
|
-
if (!
|
|
4075
|
+
if (!fs31.existsSync(this.storePath)) {
|
|
4076
4076
|
const salt = crypto.randomBytes(SALT_LEN);
|
|
4077
4077
|
this.masterKey = this.deriveKey(password, salt);
|
|
4078
4078
|
this.writeWithSalt({}, salt);
|
|
@@ -4086,7 +4086,7 @@ var Keystore = class {
|
|
|
4086
4086
|
}
|
|
4087
4087
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
4088
4088
|
unlockSync(password) {
|
|
4089
|
-
if (!
|
|
4089
|
+
if (!fs31.existsSync(this.storePath)) {
|
|
4090
4090
|
const salt = crypto.randomBytes(SALT_LEN);
|
|
4091
4091
|
this.masterKey = this.deriveKey(password, salt);
|
|
4092
4092
|
this.writeWithSalt({}, salt);
|
|
@@ -4144,7 +4144,7 @@ var Keystore = class {
|
|
|
4144
4144
|
}
|
|
4145
4145
|
}
|
|
4146
4146
|
decryptFile(password, knownSalt) {
|
|
4147
|
-
if (!
|
|
4147
|
+
if (!fs31.existsSync(this.storePath)) return {};
|
|
4148
4148
|
try {
|
|
4149
4149
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
4150
4150
|
const useSalt = knownSalt ?? salt;
|
|
@@ -4166,8 +4166,8 @@ var Keystore = class {
|
|
|
4166
4166
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4167
4167
|
const tag = cipher.getAuthTag();
|
|
4168
4168
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
4169
|
-
|
|
4170
|
-
|
|
4169
|
+
fs31.mkdirSync(path34.dirname(this.storePath), { recursive: true });
|
|
4170
|
+
fs31.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4171
4171
|
}
|
|
4172
4172
|
writeWithSalt(data, salt) {
|
|
4173
4173
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -4177,11 +4177,11 @@ var Keystore = class {
|
|
|
4177
4177
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4178
4178
|
const tag = cipher.getAuthTag();
|
|
4179
4179
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
4180
|
-
|
|
4181
|
-
|
|
4180
|
+
fs31.mkdirSync(path34.dirname(this.storePath), { recursive: true });
|
|
4181
|
+
fs31.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4182
4182
|
}
|
|
4183
4183
|
readRaw() {
|
|
4184
|
-
const buf =
|
|
4184
|
+
const buf = fs31.readFileSync(this.storePath);
|
|
4185
4185
|
let offset = 0;
|
|
4186
4186
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
4187
4187
|
offset += SALT_LEN;
|
|
@@ -4214,7 +4214,7 @@ var CascadeIgnore = class {
|
|
|
4214
4214
|
]);
|
|
4215
4215
|
}
|
|
4216
4216
|
async load(workspacePath) {
|
|
4217
|
-
const filePath =
|
|
4217
|
+
const filePath = path34.join(workspacePath, ".cascadeignore");
|
|
4218
4218
|
try {
|
|
4219
4219
|
const content = await fs11.readFile(filePath, "utf-8");
|
|
4220
4220
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -4225,7 +4225,7 @@ var CascadeIgnore = class {
|
|
|
4225
4225
|
}
|
|
4226
4226
|
isIgnored(filePath, workspacePath) {
|
|
4227
4227
|
try {
|
|
4228
|
-
const relative = workspacePath ?
|
|
4228
|
+
const relative = workspacePath ? path34.relative(workspacePath, filePath) : filePath;
|
|
4229
4229
|
return this.ig.ignores(relative);
|
|
4230
4230
|
} catch {
|
|
4231
4231
|
return false;
|
|
@@ -4236,7 +4236,7 @@ var CascadeIgnore = class {
|
|
|
4236
4236
|
}
|
|
4237
4237
|
};
|
|
4238
4238
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
4239
|
-
const filePath =
|
|
4239
|
+
const filePath = path34.join(workspacePath, ".cascadeignore");
|
|
4240
4240
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
4241
4241
|
# Syntax identical to .gitignore
|
|
4242
4242
|
|
|
@@ -4266,7 +4266,7 @@ Thumbs.db
|
|
|
4266
4266
|
await fs11.writeFile(filePath, content, "utf-8");
|
|
4267
4267
|
}
|
|
4268
4268
|
async function loadCascadeMd(workspacePath) {
|
|
4269
|
-
const filePath =
|
|
4269
|
+
const filePath = path34.join(workspacePath, "CASCADE.md");
|
|
4270
4270
|
try {
|
|
4271
4271
|
const raw = await fs11.readFile(filePath, "utf-8");
|
|
4272
4272
|
return parseCascadeMd(raw);
|
|
@@ -4295,7 +4295,7 @@ ${raw.trim()}`;
|
|
|
4295
4295
|
return { raw, sections, systemPrompt };
|
|
4296
4296
|
}
|
|
4297
4297
|
async function createDefaultCascadeMd(workspacePath) {
|
|
4298
|
-
const filePath =
|
|
4298
|
+
const filePath = path34.join(workspacePath, "CASCADE.md");
|
|
4299
4299
|
const content = `# Cascade Project Instructions
|
|
4300
4300
|
|
|
4301
4301
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -4330,7 +4330,7 @@ List any areas the agent should not touch.
|
|
|
4330
4330
|
var MemoryStore = class _MemoryStore {
|
|
4331
4331
|
db;
|
|
4332
4332
|
constructor(dbPath) {
|
|
4333
|
-
|
|
4333
|
+
fs31.mkdirSync(path34.dirname(dbPath), { recursive: true });
|
|
4334
4334
|
try {
|
|
4335
4335
|
this.db = new Database2(dbPath, { timeout: 5e3 });
|
|
4336
4336
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -5582,7 +5582,7 @@ function validateConfig(raw) {
|
|
|
5582
5582
|
// src/config/global-credentials.ts
|
|
5583
5583
|
init_constants();
|
|
5584
5584
|
function credentialsPath(globalDir2) {
|
|
5585
|
-
return
|
|
5585
|
+
return path34.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
5586
5586
|
}
|
|
5587
5587
|
function providerKey(p) {
|
|
5588
5588
|
if (p.type === "azure") {
|
|
@@ -5595,7 +5595,7 @@ function isPersistable(p) {
|
|
|
5595
5595
|
}
|
|
5596
5596
|
function loadGlobalCredentials(globalDir2) {
|
|
5597
5597
|
try {
|
|
5598
|
-
const raw =
|
|
5598
|
+
const raw = fs31.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
5599
5599
|
const parsed = JSON.parse(raw);
|
|
5600
5600
|
if (!Array.isArray(parsed.providers)) return [];
|
|
5601
5601
|
return parsed.providers.filter(
|
|
@@ -5607,13 +5607,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
5607
5607
|
}
|
|
5608
5608
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
5609
5609
|
const filePath = credentialsPath(globalDir2);
|
|
5610
|
-
|
|
5610
|
+
fs31.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
5611
5611
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
5612
5612
|
const tmp = `${filePath}.tmp`;
|
|
5613
|
-
|
|
5614
|
-
|
|
5613
|
+
fs31.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5614
|
+
fs31.renameSync(tmp, filePath);
|
|
5615
5615
|
try {
|
|
5616
|
-
|
|
5616
|
+
fs31.chmodSync(filePath, 384);
|
|
5617
5617
|
} catch {
|
|
5618
5618
|
}
|
|
5619
5619
|
}
|
|
@@ -5831,7 +5831,7 @@ var ConfigManager = class {
|
|
|
5831
5831
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
5832
5832
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
5833
5833
|
this.workspacePath = workspacePath;
|
|
5834
|
-
this.globalDir = globalDirOverride ??
|
|
5834
|
+
this.globalDir = globalDirOverride ?? path34.join(os10.homedir(), GLOBAL_CONFIG_DIR);
|
|
5835
5835
|
}
|
|
5836
5836
|
async load() {
|
|
5837
5837
|
this.config = await this.loadConfig();
|
|
@@ -5845,8 +5845,8 @@ var ConfigManager = class {
|
|
|
5845
5845
|
this.ignore = new CascadeIgnore();
|
|
5846
5846
|
await this.ignore.load(this.workspacePath);
|
|
5847
5847
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
5848
|
-
this.keystore = new Keystore(
|
|
5849
|
-
this.store = new MemoryStore(
|
|
5848
|
+
this.keystore = new Keystore(path34.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
5849
|
+
this.store = new MemoryStore(path34.join(this.workspacePath, CASCADE_DB_FILE));
|
|
5850
5850
|
await this.injectEnvKeys();
|
|
5851
5851
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
5852
5852
|
await this.ensureDefaultIdentity();
|
|
@@ -5871,8 +5871,8 @@ var ConfigManager = class {
|
|
|
5871
5871
|
return this.workspacePath;
|
|
5872
5872
|
}
|
|
5873
5873
|
async save() {
|
|
5874
|
-
const configPath =
|
|
5875
|
-
await fs11.mkdir(
|
|
5874
|
+
const configPath = path34.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5875
|
+
await fs11.mkdir(path34.dirname(configPath), { recursive: true });
|
|
5876
5876
|
await fs11.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
5877
5877
|
try {
|
|
5878
5878
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
@@ -5908,7 +5908,7 @@ var ConfigManager = class {
|
|
|
5908
5908
|
return configProvider?.apiKey;
|
|
5909
5909
|
}
|
|
5910
5910
|
async loadConfig() {
|
|
5911
|
-
const configPath =
|
|
5911
|
+
const configPath = path34.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5912
5912
|
try {
|
|
5913
5913
|
const raw = await fs11.readFile(configPath, "utf-8");
|
|
5914
5914
|
return validateConfig(JSON.parse(raw));
|
|
@@ -7075,7 +7075,7 @@ init_pricing();
|
|
|
7075
7075
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
7076
7076
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
7077
7077
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
7078
|
-
var DEFAULT_CACHE_FILE =
|
|
7078
|
+
var DEFAULT_CACHE_FILE = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
7079
7079
|
function normalizeModelId2(id) {
|
|
7080
7080
|
let s = id.toLowerCase();
|
|
7081
7081
|
const slash = s.lastIndexOf("/");
|
|
@@ -7222,7 +7222,7 @@ var LiveDataProvider = class {
|
|
|
7222
7222
|
}
|
|
7223
7223
|
async saveCache() {
|
|
7224
7224
|
try {
|
|
7225
|
-
await fs11.mkdir(
|
|
7225
|
+
await fs11.mkdir(path34.dirname(this.opts.cacheFile), { recursive: true });
|
|
7226
7226
|
const cache = {
|
|
7227
7227
|
fetchedAt: this.fetchedAt,
|
|
7228
7228
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -9015,6 +9015,79 @@ function describeProviderError(c, modelId) {
|
|
|
9015
9015
|
}
|
|
9016
9016
|
}
|
|
9017
9017
|
|
|
9018
|
+
// src/core/verification/acceptance.ts
|
|
9019
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
9020
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
9021
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
9022
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
9023
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
9024
|
+
function targetFile(criterion, ownedFiles) {
|
|
9025
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
9026
|
+
if (mentioned.length > 1) return null;
|
|
9027
|
+
if (mentioned.length === 1) {
|
|
9028
|
+
const candidate = mentioned[0];
|
|
9029
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
9030
|
+
}
|
|
9031
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
9032
|
+
}
|
|
9033
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
9034
|
+
const results = [];
|
|
9035
|
+
for (const criterion of criteria) {
|
|
9036
|
+
const text = criterion.trim();
|
|
9037
|
+
if (!text) {
|
|
9038
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9039
|
+
continue;
|
|
9040
|
+
}
|
|
9041
|
+
if (NEGATION.test(text)) {
|
|
9042
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9043
|
+
continue;
|
|
9044
|
+
}
|
|
9045
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
9046
|
+
const wantsExists = EXISTS.test(text);
|
|
9047
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
9048
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
9049
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9050
|
+
continue;
|
|
9051
|
+
}
|
|
9052
|
+
const file = targetFile(text, ownedFiles);
|
|
9053
|
+
if (!file) {
|
|
9054
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9055
|
+
continue;
|
|
9056
|
+
}
|
|
9057
|
+
const stat = await probe.stat(file);
|
|
9058
|
+
if (!stat) {
|
|
9059
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
9060
|
+
continue;
|
|
9061
|
+
}
|
|
9062
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
9063
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
9064
|
+
continue;
|
|
9065
|
+
}
|
|
9066
|
+
if (needle) {
|
|
9067
|
+
const content = await probe.read(file);
|
|
9068
|
+
if (content === null) {
|
|
9069
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9070
|
+
continue;
|
|
9071
|
+
}
|
|
9072
|
+
const found = content.includes(needle);
|
|
9073
|
+
results.push({
|
|
9074
|
+
criterion,
|
|
9075
|
+
verdict: found ? "passed" : "failed",
|
|
9076
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
9077
|
+
});
|
|
9078
|
+
continue;
|
|
9079
|
+
}
|
|
9080
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
9081
|
+
}
|
|
9082
|
+
return results;
|
|
9083
|
+
}
|
|
9084
|
+
function undecided(results) {
|
|
9085
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
9086
|
+
}
|
|
9087
|
+
function failures(results) {
|
|
9088
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
9089
|
+
}
|
|
9090
|
+
|
|
9018
9091
|
// src/core/tiers/t3-worker.ts
|
|
9019
9092
|
var CriticalToolError = class extends Error {
|
|
9020
9093
|
constructor(message, toolName) {
|
|
@@ -9331,7 +9404,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
9331
9404
|
}
|
|
9332
9405
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
9333
9406
|
if (!artifactCheck.ok) {
|
|
9334
|
-
correctionAttempts
|
|
9407
|
+
correctionAttempts++;
|
|
9335
9408
|
issues.push(...artifactCheck.issues);
|
|
9336
9409
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
9337
9410
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -9349,18 +9422,40 @@ Now execute your subtask using this context where relevant.`
|
|
|
9349
9422
|
this.tools.map((t) => t.name)
|
|
9350
9423
|
);
|
|
9351
9424
|
if (visualIssue) {
|
|
9352
|
-
correctionAttempts
|
|
9425
|
+
correctionAttempts++;
|
|
9353
9426
|
issues.push(visualIssue);
|
|
9354
9427
|
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
9355
9428
|
output = await this.correctOutput(output, [visualIssue]);
|
|
9356
9429
|
}
|
|
9430
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
9431
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
9432
|
+
if (acceptanceFailures.length > 0) {
|
|
9433
|
+
correctionAttempts++;
|
|
9434
|
+
issues.push(...acceptanceFailures);
|
|
9435
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
9436
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
9437
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
9438
|
+
if (recheck.length > 0) {
|
|
9439
|
+
issues.push(...recheck);
|
|
9440
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
9441
|
+
failed.push(...recheck);
|
|
9442
|
+
this.setStatus("FAILED");
|
|
9443
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
9444
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
9445
|
+
}
|
|
9446
|
+
}
|
|
9447
|
+
for (const result2 of acceptanceResults) {
|
|
9448
|
+
if (result2.verdict === "undecidable") continue;
|
|
9449
|
+
checksRun.push(result2.criterion);
|
|
9450
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
9451
|
+
}
|
|
9357
9452
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
9358
|
-
const testResult = await this.selfTest(assignment, output);
|
|
9453
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
9359
9454
|
checksRun.push(...testResult.checksRun);
|
|
9360
9455
|
passed.push(...testResult.passed);
|
|
9361
9456
|
failed.push(...testResult.failed);
|
|
9362
9457
|
if (testResult.failed.length > 0) {
|
|
9363
|
-
correctionAttempts
|
|
9458
|
+
correctionAttempts++;
|
|
9364
9459
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
9365
9460
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
9366
9461
|
output = corrected;
|
|
@@ -9668,8 +9763,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
9668
9763
|
// Media generation can run for a minute; without this a cancelled run
|
|
9669
9764
|
// still pays for an image nobody will see.
|
|
9670
9765
|
...this.signal ? { signal: this.signal } : {},
|
|
9671
|
-
saveSnapshot: async (
|
|
9672
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
9766
|
+
saveSnapshot: async (path39, content) => {
|
|
9767
|
+
this.store?.addFileSnapshot(this.taskId, path39, content);
|
|
9673
9768
|
},
|
|
9674
9769
|
sendPeerSync: (to, syncType, content) => {
|
|
9675
9770
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -9856,7 +9951,7 @@ ${assignment.expectedOutput}`;
|
|
|
9856
9951
|
const { promisify: promisify4 } = await import('util');
|
|
9857
9952
|
const execAsync3 = promisify4(exec3);
|
|
9858
9953
|
for (const artifactPath of artifactPaths) {
|
|
9859
|
-
const absolutePath =
|
|
9954
|
+
const absolutePath = path34.resolve(process.cwd(), artifactPath);
|
|
9860
9955
|
try {
|
|
9861
9956
|
const stat = await fs11.stat(absolutePath);
|
|
9862
9957
|
if (!stat.isFile()) {
|
|
@@ -9891,7 +9986,7 @@ ${assignment.expectedOutput}`;
|
|
|
9891
9986
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
9892
9987
|
continue;
|
|
9893
9988
|
}
|
|
9894
|
-
const ext =
|
|
9989
|
+
const ext = path34.extname(absolutePath).toLowerCase();
|
|
9895
9990
|
try {
|
|
9896
9991
|
if (ext === ".ts" || ext === ".tsx") {
|
|
9897
9992
|
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -9977,14 +10072,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
9977
10072
|
}
|
|
9978
10073
|
return current;
|
|
9979
10074
|
}
|
|
9980
|
-
|
|
10075
|
+
/**
|
|
10076
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
10077
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
10078
|
+
*/
|
|
10079
|
+
async checkAcceptance(assignment) {
|
|
10080
|
+
const criteria = assignment.acceptance ?? [];
|
|
10081
|
+
if (!criteria.length) return [];
|
|
10082
|
+
const resolve2 = (target) => path34.resolve(process.cwd(), target);
|
|
10083
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
10084
|
+
stat: async (target) => {
|
|
10085
|
+
try {
|
|
10086
|
+
const stat = await fs11.stat(resolve2(target));
|
|
10087
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
10088
|
+
} catch {
|
|
10089
|
+
return null;
|
|
10090
|
+
}
|
|
10091
|
+
},
|
|
10092
|
+
read: async (target) => {
|
|
10093
|
+
try {
|
|
10094
|
+
const content = await fs11.readFile(resolve2(target), "utf-8");
|
|
10095
|
+
return content.includes("\uFFFD") ? null : content;
|
|
10096
|
+
} catch {
|
|
10097
|
+
return null;
|
|
10098
|
+
}
|
|
10099
|
+
}
|
|
10100
|
+
});
|
|
10101
|
+
}
|
|
10102
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
10103
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
9981
10104
|
const prompt = `Self-test this output against the assignment requirements.
|
|
9982
10105
|
|
|
9983
10106
|
Assignment: ${assignment.description}
|
|
9984
10107
|
Expected output: ${assignment.expectedOutput}
|
|
9985
10108
|
Constraints: ${assignment.constraints.join("; ")}
|
|
9986
|
-
${
|
|
9987
|
-
${
|
|
10109
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
10110
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
9988
10111
|
` : ""}
|
|
9989
10112
|
Output to test:
|
|
9990
10113
|
${output}
|
|
@@ -10051,8 +10174,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
10051
10174
|
} catch {
|
|
10052
10175
|
}
|
|
10053
10176
|
}
|
|
10054
|
-
async correctOutput(originalOutput,
|
|
10055
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
10177
|
+
async correctOutput(originalOutput, failures2) {
|
|
10178
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
10056
10179
|
|
|
10057
10180
|
Original output:
|
|
10058
10181
|
${originalOutput}
|
|
@@ -10714,12 +10837,220 @@ function describeGenerationForPlanner(has) {
|
|
|
10714
10837
|
return lines.join("\n");
|
|
10715
10838
|
}
|
|
10716
10839
|
|
|
10840
|
+
// src/core/orchestration/task-graph.ts
|
|
10841
|
+
var TaskGraphValidationError = class extends Error {
|
|
10842
|
+
constructor(issues) {
|
|
10843
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
10844
|
+
this.issues = issues;
|
|
10845
|
+
this.name = "TaskGraphValidationError";
|
|
10846
|
+
}
|
|
10847
|
+
issues;
|
|
10848
|
+
};
|
|
10849
|
+
function normalizeDependencies(input) {
|
|
10850
|
+
if (!input?.length) return [];
|
|
10851
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
10852
|
+
}
|
|
10853
|
+
function cycleNodes(nodes) {
|
|
10854
|
+
const adjacency = new Map(
|
|
10855
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
10856
|
+
);
|
|
10857
|
+
const index = /* @__PURE__ */ new Map();
|
|
10858
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
10859
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
10860
|
+
const stack = [];
|
|
10861
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
10862
|
+
let counter = 0;
|
|
10863
|
+
const open = (id) => {
|
|
10864
|
+
index.set(id, counter);
|
|
10865
|
+
lowlink.set(id, counter);
|
|
10866
|
+
counter++;
|
|
10867
|
+
stack.push(id);
|
|
10868
|
+
onStack.add(id);
|
|
10869
|
+
};
|
|
10870
|
+
for (const root of nodes) {
|
|
10871
|
+
if (index.has(root.id)) continue;
|
|
10872
|
+
open(root.id);
|
|
10873
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
10874
|
+
while (frames.length > 0) {
|
|
10875
|
+
const frame = frames[frames.length - 1];
|
|
10876
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
10877
|
+
if (frame.edge < neighbours.length) {
|
|
10878
|
+
const next = neighbours[frame.edge];
|
|
10879
|
+
frame.edge++;
|
|
10880
|
+
if (!adjacency.has(next)) continue;
|
|
10881
|
+
if (!index.has(next)) {
|
|
10882
|
+
open(next);
|
|
10883
|
+
frames.push({ id: next, edge: 0 });
|
|
10884
|
+
} else if (onStack.has(next)) {
|
|
10885
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
10886
|
+
}
|
|
10887
|
+
continue;
|
|
10888
|
+
}
|
|
10889
|
+
frames.pop();
|
|
10890
|
+
const parent = frames[frames.length - 1];
|
|
10891
|
+
if (parent) {
|
|
10892
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
10893
|
+
}
|
|
10894
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
10895
|
+
const component = [];
|
|
10896
|
+
for (; ; ) {
|
|
10897
|
+
const member = stack.pop();
|
|
10898
|
+
onStack.delete(member);
|
|
10899
|
+
component.push(member);
|
|
10900
|
+
if (member === frame.id) break;
|
|
10901
|
+
}
|
|
10902
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
10903
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10907
|
+
return inCycle;
|
|
10908
|
+
}
|
|
10909
|
+
function throwIfStrict(mode, issues) {
|
|
10910
|
+
if (mode === "strict" && issues.length > 0) {
|
|
10911
|
+
throw new TaskGraphValidationError(issues);
|
|
10912
|
+
}
|
|
10913
|
+
}
|
|
10914
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
10915
|
+
const mode = options.mode ?? "strict";
|
|
10916
|
+
const issues = [];
|
|
10917
|
+
if (inputNodes.length === 0) {
|
|
10918
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
10919
|
+
throwIfStrict(mode, issues);
|
|
10920
|
+
}
|
|
10921
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10922
|
+
const nodes = [];
|
|
10923
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
10924
|
+
const input = inputNodes[ordinal];
|
|
10925
|
+
const id = input.id?.trim();
|
|
10926
|
+
const title = input.title?.trim();
|
|
10927
|
+
if (!id || !title) {
|
|
10928
|
+
issues.push({
|
|
10929
|
+
code: "INVALID_NODE",
|
|
10930
|
+
nodeId: id || void 0,
|
|
10931
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
10932
|
+
});
|
|
10933
|
+
continue;
|
|
10934
|
+
}
|
|
10935
|
+
if (seen.has(id)) {
|
|
10936
|
+
issues.push({
|
|
10937
|
+
code: "DUPLICATE_NODE_ID",
|
|
10938
|
+
nodeId: id,
|
|
10939
|
+
message: `Duplicate task graph node id: ${id}`
|
|
10940
|
+
});
|
|
10941
|
+
continue;
|
|
10942
|
+
}
|
|
10943
|
+
seen.add(id);
|
|
10944
|
+
nodes.push({
|
|
10945
|
+
id,
|
|
10946
|
+
title,
|
|
10947
|
+
kind: input.kind,
|
|
10948
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
10949
|
+
payload: input.payload,
|
|
10950
|
+
ordinal
|
|
10951
|
+
});
|
|
10952
|
+
}
|
|
10953
|
+
throwIfStrict(mode, issues);
|
|
10954
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
10955
|
+
const sanitized = nodes.map((node) => {
|
|
10956
|
+
const dependsOn = [];
|
|
10957
|
+
for (const dependencyId of node.dependsOn) {
|
|
10958
|
+
if (dependencyId === node.id) {
|
|
10959
|
+
issues.push({
|
|
10960
|
+
code: "SELF_DEPENDENCY",
|
|
10961
|
+
nodeId: node.id,
|
|
10962
|
+
dependencyId,
|
|
10963
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
10964
|
+
});
|
|
10965
|
+
continue;
|
|
10966
|
+
}
|
|
10967
|
+
if (!ids.has(dependencyId)) {
|
|
10968
|
+
issues.push({
|
|
10969
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
10970
|
+
nodeId: node.id,
|
|
10971
|
+
dependencyId,
|
|
10972
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
10973
|
+
});
|
|
10974
|
+
continue;
|
|
10975
|
+
}
|
|
10976
|
+
dependsOn.push(dependencyId);
|
|
10977
|
+
}
|
|
10978
|
+
return { ...node, dependsOn };
|
|
10979
|
+
});
|
|
10980
|
+
throwIfStrict(mode, issues);
|
|
10981
|
+
const cyclic = cycleNodes(sanitized);
|
|
10982
|
+
let repaired = sanitized;
|
|
10983
|
+
if (cyclic.size > 0) {
|
|
10984
|
+
const cycleIssues = [];
|
|
10985
|
+
repaired = sanitized.map((node) => {
|
|
10986
|
+
if (!cyclic.has(node.id)) return node;
|
|
10987
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
10988
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
10989
|
+
cycleIssues.push({
|
|
10990
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
10991
|
+
nodeId: node.id,
|
|
10992
|
+
dependencyId,
|
|
10993
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
10994
|
+
});
|
|
10995
|
+
return false;
|
|
10996
|
+
});
|
|
10997
|
+
return { ...node, dependsOn };
|
|
10998
|
+
});
|
|
10999
|
+
issues.push(...cycleIssues);
|
|
11000
|
+
throwIfStrict(mode, cycleIssues);
|
|
11001
|
+
}
|
|
11002
|
+
const remainingCycle = cycleNodes(repaired);
|
|
11003
|
+
if (remainingCycle.size > 0) {
|
|
11004
|
+
throw new TaskGraphValidationError([
|
|
11005
|
+
{
|
|
11006
|
+
code: "INVALID_NODE",
|
|
11007
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
11008
|
+
}
|
|
11009
|
+
]);
|
|
11010
|
+
}
|
|
11011
|
+
return {
|
|
11012
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
11013
|
+
issues
|
|
11014
|
+
};
|
|
11015
|
+
}
|
|
11016
|
+
|
|
11017
|
+
// src/core/orchestration/adapters.ts
|
|
11018
|
+
function compileSectionGraph(sections) {
|
|
11019
|
+
return compileTaskGraph(
|
|
11020
|
+
sections.map((section) => ({
|
|
11021
|
+
id: section.sectionId,
|
|
11022
|
+
title: section.sectionTitle,
|
|
11023
|
+
kind: "section",
|
|
11024
|
+
dependsOn: section.dependsOn,
|
|
11025
|
+
payload: section
|
|
11026
|
+
})),
|
|
11027
|
+
{ mode: "repair" }
|
|
11028
|
+
);
|
|
11029
|
+
}
|
|
11030
|
+
function compileSubtaskGraph(subtasks) {
|
|
11031
|
+
return compileTaskGraph(
|
|
11032
|
+
subtasks.map((subtask) => ({
|
|
11033
|
+
id: subtask.subtaskId,
|
|
11034
|
+
title: subtask.subtaskTitle,
|
|
11035
|
+
kind: "subtask",
|
|
11036
|
+
dependsOn: subtask.dependsOn,
|
|
11037
|
+
payload: subtask
|
|
11038
|
+
})),
|
|
11039
|
+
{ mode: "repair" }
|
|
11040
|
+
);
|
|
11041
|
+
}
|
|
11042
|
+
|
|
10717
11043
|
// src/core/tiers/t2-manager.ts
|
|
10718
11044
|
function buildT2SystemPrompt(has) {
|
|
10719
11045
|
const generation = describeGenerationForPlanner(has);
|
|
10720
11046
|
return [
|
|
10721
11047
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
10722
|
-
|
|
11048
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
11049
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
11050
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
11051
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
11052
|
+
// real worker call on every small section.
|
|
11053
|
+
"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.",
|
|
10723
11054
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
10724
11055
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
10725
11056
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -11115,21 +11446,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11115
11446
|
* then reduce in-degrees of their dependents and repeat.
|
|
11116
11447
|
*/
|
|
11117
11448
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
11449
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
11450
|
+
for (const issue of compiled.issues) {
|
|
11451
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
11452
|
+
}
|
|
11453
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
11454
|
+
const assignment = node.payload;
|
|
11455
|
+
const original = assignment.dependsOn ?? [];
|
|
11456
|
+
const repaired = node.dependsOn;
|
|
11457
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
11458
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
11459
|
+
});
|
|
11118
11460
|
const adj = /* @__PURE__ */ new Map();
|
|
11119
11461
|
const inDegree = /* @__PURE__ */ new Map();
|
|
11120
11462
|
const resultMap = /* @__PURE__ */ new Map();
|
|
11121
|
-
for (const a of
|
|
11463
|
+
for (const a of sanitizedAssignments) {
|
|
11122
11464
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
11123
11465
|
inDegree.set(a.subtaskId, 0);
|
|
11124
11466
|
}
|
|
11125
|
-
for (const a of
|
|
11126
|
-
const
|
|
11127
|
-
for (const dep of deps) {
|
|
11467
|
+
for (const a of sanitizedAssignments) {
|
|
11468
|
+
for (const dep of a.dependsOn ?? []) {
|
|
11128
11469
|
adj.get(dep).add(a.subtaskId);
|
|
11129
11470
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
11130
11471
|
}
|
|
11131
11472
|
}
|
|
11132
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
11133
11473
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
11134
11474
|
let wave = 0;
|
|
11135
11475
|
let respawnBudget = 1;
|
|
@@ -11336,49 +11676,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11336
11676
|
}
|
|
11337
11677
|
return [...resultMap.values()];
|
|
11338
11678
|
}
|
|
11339
|
-
/**
|
|
11340
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
11341
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
11342
|
-
*
|
|
11343
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
11344
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
11345
|
-
*/
|
|
11346
|
-
breakCycles(assignments, adj, inDegree) {
|
|
11347
|
-
const degree = new Map(inDegree);
|
|
11348
|
-
const queue = [];
|
|
11349
|
-
const visited = /* @__PURE__ */ new Set();
|
|
11350
|
-
for (const [id, d] of degree) {
|
|
11351
|
-
if (d === 0) queue.push(id);
|
|
11352
|
-
}
|
|
11353
|
-
while (queue.length > 0) {
|
|
11354
|
-
const id = queue.shift();
|
|
11355
|
-
visited.add(id);
|
|
11356
|
-
for (const dep of adj.get(id) ?? []) {
|
|
11357
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
11358
|
-
degree.set(dep, newDeg);
|
|
11359
|
-
if (newDeg === 0) queue.push(dep);
|
|
11360
|
-
}
|
|
11361
|
-
}
|
|
11362
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
11363
|
-
if (cycleNodes.length === 0) return assignments;
|
|
11364
|
-
this.log(
|
|
11365
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
11366
|
-
);
|
|
11367
|
-
return assignments.map((a) => {
|
|
11368
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
11369
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
11370
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
11371
|
-
this.log(
|
|
11372
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
11373
|
-
);
|
|
11374
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
11375
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
11376
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
11377
|
-
}
|
|
11378
|
-
}
|
|
11379
|
-
return { ...a, dependsOn: safeDeps };
|
|
11380
|
-
});
|
|
11381
|
-
}
|
|
11382
11679
|
async retryT3(assignment, taskId) {
|
|
11383
11680
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
11384
11681
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -11517,6 +11814,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11517
11814
|
// src/core/tiers/t1-administrator.ts
|
|
11518
11815
|
init_constants();
|
|
11519
11816
|
|
|
11817
|
+
// src/core/orchestration/scheduler.ts
|
|
11818
|
+
var DependencyScheduler = class {
|
|
11819
|
+
constructor(graph, hooks, options = {}) {
|
|
11820
|
+
this.graph = graph;
|
|
11821
|
+
this.hooks = hooks;
|
|
11822
|
+
this.options = options;
|
|
11823
|
+
}
|
|
11824
|
+
graph;
|
|
11825
|
+
hooks;
|
|
11826
|
+
options;
|
|
11827
|
+
async run() {
|
|
11828
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
11829
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
11830
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
11831
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
11832
|
+
for (const node of nodes) {
|
|
11833
|
+
for (const dependencyId of node.dependsOn) {
|
|
11834
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
11835
|
+
}
|
|
11836
|
+
}
|
|
11837
|
+
const completed = /* @__PURE__ */ new Set();
|
|
11838
|
+
const results = /* @__PURE__ */ new Map();
|
|
11839
|
+
const waves = [];
|
|
11840
|
+
let wave = 0;
|
|
11841
|
+
while (completed.size < nodes.length) {
|
|
11842
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
11843
|
+
if (ready.length === 0) {
|
|
11844
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
11845
|
+
}
|
|
11846
|
+
waves.push(ready.map((node) => node.id));
|
|
11847
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
11848
|
+
const runOne = async (node) => {
|
|
11849
|
+
const result = await this.hooks.execute(node);
|
|
11850
|
+
results.set(node.id, result);
|
|
11851
|
+
completed.add(node.id);
|
|
11852
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
11853
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
11854
|
+
}
|
|
11855
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
11856
|
+
};
|
|
11857
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
11858
|
+
for (const node of ready) await runOne(node);
|
|
11859
|
+
} else {
|
|
11860
|
+
await Promise.all(ready.map(runOne));
|
|
11861
|
+
}
|
|
11862
|
+
wave++;
|
|
11863
|
+
}
|
|
11864
|
+
return { results, waves };
|
|
11865
|
+
}
|
|
11866
|
+
};
|
|
11867
|
+
|
|
11520
11868
|
// src/utils/json-extract.ts
|
|
11521
11869
|
function extractFirstJsonObject(input) {
|
|
11522
11870
|
if (!input) return null;
|
|
@@ -11588,7 +11936,7 @@ function buildT1SystemPrompt(has) {
|
|
|
11588
11936
|
Your responsibilities:
|
|
11589
11937
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
11590
11938
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
11591
|
-
3. For each section, define
|
|
11939
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
11592
11940
|
4. Return a structured plan as JSON
|
|
11593
11941
|
|
|
11594
11942
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -12003,7 +12351,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
12003
12351
|
}
|
|
12004
12352
|
}
|
|
12005
12353
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
12006
|
-
if (plan.sections.length < min)
|
|
12354
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
12355
|
+
this.log(
|
|
12356
|
+
`\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.`
|
|
12357
|
+
);
|
|
12358
|
+
}
|
|
12007
12359
|
}
|
|
12008
12360
|
async dispatchT2Managers(sections) {
|
|
12009
12361
|
for (const section of sections) {
|
|
@@ -12115,114 +12467,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
12115
12467
|
return t2Results;
|
|
12116
12468
|
}
|
|
12117
12469
|
/**
|
|
12118
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
12470
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
12471
|
+
*
|
|
12472
|
+
* Graph construction, validation and cycle repair now live in
|
|
12473
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
12474
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
12475
|
+
* ordered by one implementation rather than two that drifted.
|
|
12476
|
+
*
|
|
12477
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
12478
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
12479
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
12480
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
12481
|
+
* sections and started them immediately — before the sections they consume.
|
|
12482
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
12483
|
+
* members are touched.
|
|
12119
12484
|
*/
|
|
12120
12485
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
12121
|
-
const
|
|
12122
|
-
const
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
}
|
|
12130
|
-
for (const s of sections) {
|
|
12131
|
-
for (const dep of s.dependsOn ?? []) {
|
|
12132
|
-
adj.get(dep).add(s.sectionId);
|
|
12133
|
-
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
12134
|
-
}
|
|
12135
|
-
}
|
|
12136
|
-
const queue = [];
|
|
12137
|
-
const degree = new Map(inDegree);
|
|
12138
|
-
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
12139
|
-
const visited = /* @__PURE__ */ new Set();
|
|
12140
|
-
while (queue.length > 0) {
|
|
12141
|
-
const u = queue.shift();
|
|
12142
|
-
visited.add(u);
|
|
12143
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
12144
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
12145
|
-
degree.set(v, newDeg);
|
|
12146
|
-
if (newDeg === 0) queue.push(v);
|
|
12147
|
-
}
|
|
12148
|
-
}
|
|
12149
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
12150
|
-
if (cycleNodes.length > 0) {
|
|
12151
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
12152
|
-
for (const s of sections) {
|
|
12153
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
12154
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
12155
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
12156
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
12157
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
12158
|
-
}
|
|
12159
|
-
s.dependsOn = safeDeps;
|
|
12160
|
-
}
|
|
12161
|
-
}
|
|
12486
|
+
const compiled = compileSectionGraph(sections);
|
|
12487
|
+
for (const issue of compiled.issues) {
|
|
12488
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
12489
|
+
}
|
|
12490
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
12491
|
+
for (const section of sections) {
|
|
12492
|
+
const node = byId.get(section.sectionId);
|
|
12493
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
12162
12494
|
}
|
|
12163
|
-
const
|
|
12495
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
12496
|
+
const totalSections = compiled.graph.nodes.length;
|
|
12164
12497
|
let completedSections = 0;
|
|
12165
|
-
const
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12498
|
+
const scheduler = new DependencyScheduler(
|
|
12499
|
+
compiled.graph,
|
|
12500
|
+
{
|
|
12501
|
+
execute: async (node) => {
|
|
12502
|
+
const section = node.payload;
|
|
12503
|
+
const manager = managers[node.ordinal];
|
|
12504
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
12505
|
+
this.sendStatusUpdate({
|
|
12506
|
+
progressPct,
|
|
12507
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
12508
|
+
status: "IN_PROGRESS"
|
|
12509
|
+
});
|
|
12510
|
+
this.throwIfCancelled();
|
|
12511
|
+
try {
|
|
12512
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
12513
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
12514
|
+
if (result.status === "ESCALATED") {
|
|
12515
|
+
this.escalations.push({
|
|
12516
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
12517
|
+
sectionId: section.sectionId,
|
|
12518
|
+
attempted: result.issues,
|
|
12519
|
+
blocker: result.issues.join("; "),
|
|
12520
|
+
needs: "Human review required"
|
|
12521
|
+
});
|
|
12522
|
+
}
|
|
12523
|
+
return result;
|
|
12524
|
+
} catch (err) {
|
|
12525
|
+
return {
|
|
12192
12526
|
sectionId: section.sectionId,
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12527
|
+
sectionTitle: section.sectionTitle,
|
|
12528
|
+
status: "FAILED",
|
|
12529
|
+
t3Results: [],
|
|
12530
|
+
sectionSummary: "",
|
|
12531
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
12532
|
+
};
|
|
12197
12533
|
}
|
|
12198
|
-
}
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
resultMap.set(id, result);
|
|
12209
|
-
completedSections++;
|
|
12210
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
12211
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
12212
|
-
}
|
|
12213
|
-
};
|
|
12214
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
12215
|
-
for (const id of readyIds) {
|
|
12216
|
-
await runOne(id);
|
|
12534
|
+
},
|
|
12535
|
+
onNodeComplete: (node, result) => {
|
|
12536
|
+
resultMap.set(node.id, result);
|
|
12537
|
+
completedSections++;
|
|
12538
|
+
this.emit("section:complete", {
|
|
12539
|
+
id: node.id,
|
|
12540
|
+
title: result.sectionTitle,
|
|
12541
|
+
status: result.status,
|
|
12542
|
+
output: result.sectionSummary
|
|
12543
|
+
});
|
|
12217
12544
|
}
|
|
12218
|
-
}
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12545
|
+
},
|
|
12546
|
+
{
|
|
12547
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
12548
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
12549
|
+
// serialized T3 workers within a single section while every section's
|
|
12550
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
12551
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
12223
12552
|
}
|
|
12224
|
-
|
|
12225
|
-
await
|
|
12553
|
+
);
|
|
12554
|
+
await scheduler.run();
|
|
12226
12555
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
12227
12556
|
}
|
|
12228
12557
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -12855,16 +13184,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
12855
13184
|
if (typeof input !== "string" || input.length === 0) {
|
|
12856
13185
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
12857
13186
|
}
|
|
12858
|
-
const root =
|
|
12859
|
-
const abs =
|
|
12860
|
-
const rel =
|
|
12861
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
13187
|
+
const root = path34.resolve(workspaceRoot);
|
|
13188
|
+
const abs = path34.isAbsolute(input) ? path34.resolve(input) : path34.resolve(root, input);
|
|
13189
|
+
const rel = path34.relative(root, abs);
|
|
13190
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path34.isAbsolute(rel)) {
|
|
12862
13191
|
throw new WorkspaceSandboxError(input, root);
|
|
12863
13192
|
}
|
|
12864
13193
|
try {
|
|
12865
|
-
const real =
|
|
12866
|
-
const realRel =
|
|
12867
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
13194
|
+
const real = fs31.realpathSync(abs);
|
|
13195
|
+
const realRel = path34.relative(root, real);
|
|
13196
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path34.isAbsolute(realRel))) {
|
|
12868
13197
|
throw new WorkspaceSandboxError(input, root);
|
|
12869
13198
|
}
|
|
12870
13199
|
} catch (e) {
|
|
@@ -13517,7 +13846,7 @@ var GenerateDocumentTool = class extends BaseTool {
|
|
|
13517
13846
|
}
|
|
13518
13847
|
}
|
|
13519
13848
|
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
13520
|
-
await fs11.mkdir(
|
|
13849
|
+
await fs11.mkdir(path34.dirname(absPath), { recursive: true });
|
|
13521
13850
|
await fs11.writeFile(absPath, bytes);
|
|
13522
13851
|
const charts = countCharts(content);
|
|
13523
13852
|
return [
|
|
@@ -13738,7 +14067,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
13738
14067
|
} catch {
|
|
13739
14068
|
}
|
|
13740
14069
|
}
|
|
13741
|
-
await fs11.mkdir(
|
|
14070
|
+
await fs11.mkdir(path34.dirname(absPath), { recursive: true });
|
|
13742
14071
|
await fs11.writeFile(absPath, content, "utf-8");
|
|
13743
14072
|
return `Written ${content.length} characters to ${filePath}`;
|
|
13744
14073
|
}
|
|
@@ -14236,7 +14565,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
14236
14565
|
};
|
|
14237
14566
|
async function fileToImageAttachment(filePath) {
|
|
14238
14567
|
const data = await fs11.readFile(filePath);
|
|
14239
|
-
const ext =
|
|
14568
|
+
const ext = path34.extname(filePath).toLowerCase();
|
|
14240
14569
|
const mimeMap = {
|
|
14241
14570
|
".jpg": "image/jpeg",
|
|
14242
14571
|
".jpeg": "image/jpeg",
|
|
@@ -14270,14 +14599,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
14270
14599
|
const filePath = input["path"];
|
|
14271
14600
|
const content = input["content"];
|
|
14272
14601
|
const title = input["title"];
|
|
14273
|
-
const dir =
|
|
14274
|
-
if (!
|
|
14275
|
-
|
|
14602
|
+
const dir = path34.dirname(filePath);
|
|
14603
|
+
if (!fs31.existsSync(dir)) {
|
|
14604
|
+
fs31.mkdirSync(dir, { recursive: true });
|
|
14276
14605
|
}
|
|
14277
14606
|
return new Promise((resolve2, reject) => {
|
|
14278
14607
|
try {
|
|
14279
14608
|
const doc = new PDFDocument({ margin: 50 });
|
|
14280
|
-
const stream =
|
|
14609
|
+
const stream = fs31.createWriteStream(filePath);
|
|
14281
14610
|
doc.pipe(stream);
|
|
14282
14611
|
if (title) {
|
|
14283
14612
|
doc.info["Title"] = title;
|
|
@@ -14355,22 +14684,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
14355
14684
|
}
|
|
14356
14685
|
cmdPrefix = NODE_CMD;
|
|
14357
14686
|
}
|
|
14358
|
-
const tmpDir =
|
|
14359
|
-
if (!
|
|
14360
|
-
|
|
14687
|
+
const tmpDir = path34.join(this.workspaceRoot, ".cascade", "tmp");
|
|
14688
|
+
if (!fs31.existsSync(tmpDir)) {
|
|
14689
|
+
fs31.mkdirSync(tmpDir, { recursive: true });
|
|
14361
14690
|
}
|
|
14362
14691
|
const extension = language === "python" ? "py" : "js";
|
|
14363
14692
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
14364
|
-
const filePath =
|
|
14365
|
-
|
|
14693
|
+
const filePath = path34.join(tmpDir, fileName);
|
|
14694
|
+
fs31.writeFileSync(filePath, code, "utf-8");
|
|
14366
14695
|
const execArgs = [filePath, ...args];
|
|
14367
14696
|
return new Promise((resolve2) => {
|
|
14368
14697
|
const startMs = Date.now();
|
|
14369
14698
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
14370
14699
|
const duration = Date.now() - startMs;
|
|
14371
14700
|
try {
|
|
14372
|
-
if (
|
|
14373
|
-
|
|
14701
|
+
if (fs31.existsSync(filePath)) {
|
|
14702
|
+
fs31.unlinkSync(filePath);
|
|
14374
14703
|
}
|
|
14375
14704
|
} catch (cleanupErr) {
|
|
14376
14705
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -14682,7 +15011,7 @@ var GlobTool = class extends BaseTool {
|
|
|
14682
15011
|
};
|
|
14683
15012
|
async execute(input, _options) {
|
|
14684
15013
|
const pattern = input["pattern"];
|
|
14685
|
-
const searchPath = input["path"] ?
|
|
15014
|
+
const searchPath = input["path"] ? path34.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
14686
15015
|
const matches = await glob(pattern, {
|
|
14687
15016
|
cwd: searchPath,
|
|
14688
15017
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -14695,7 +15024,7 @@ var GlobTool = class extends BaseTool {
|
|
|
14695
15024
|
const withMtime = await Promise.all(
|
|
14696
15025
|
matches.map(async (rel) => {
|
|
14697
15026
|
try {
|
|
14698
|
-
const stat = await fs11.stat(
|
|
15027
|
+
const stat = await fs11.stat(path34.join(searchPath, rel));
|
|
14699
15028
|
return { rel, mtime: stat.mtimeMs };
|
|
14700
15029
|
} catch {
|
|
14701
15030
|
return { rel, mtime: 0 };
|
|
@@ -14744,7 +15073,7 @@ var GrepTool = class extends BaseTool {
|
|
|
14744
15073
|
};
|
|
14745
15074
|
async execute(input, _options) {
|
|
14746
15075
|
const pattern = input["pattern"];
|
|
14747
|
-
const searchPath = input["path"] ?
|
|
15076
|
+
const searchPath = input["path"] ? path34.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
14748
15077
|
const globPattern = input["glob"];
|
|
14749
15078
|
const outputMode = input["output_mode"] ?? "content";
|
|
14750
15079
|
const context = input["context"] ?? 0;
|
|
@@ -14798,12 +15127,12 @@ var GrepTool = class extends BaseTool {
|
|
|
14798
15127
|
nodir: true
|
|
14799
15128
|
});
|
|
14800
15129
|
} catch {
|
|
14801
|
-
files = [
|
|
15130
|
+
files = [path34.relative(searchPath, searchPath) || "."];
|
|
14802
15131
|
}
|
|
14803
15132
|
const results = [];
|
|
14804
15133
|
let totalCount = 0;
|
|
14805
15134
|
for (const rel of files) {
|
|
14806
|
-
const abs =
|
|
15135
|
+
const abs = path34.join(searchPath, rel);
|
|
14807
15136
|
let content;
|
|
14808
15137
|
try {
|
|
14809
15138
|
content = await fs11.readFile(abs, "utf-8");
|
|
@@ -15190,10 +15519,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
15190
15519
|
}
|
|
15191
15520
|
isIgnored(filePath) {
|
|
15192
15521
|
if (!filePath) return false;
|
|
15193
|
-
const abs =
|
|
15194
|
-
const rel =
|
|
15195
|
-
if (!rel || rel.startsWith("..") ||
|
|
15196
|
-
const posixRel = rel.split(
|
|
15522
|
+
const abs = path34.resolve(this.workspaceRoot, filePath);
|
|
15523
|
+
const rel = path34.relative(this.workspaceRoot, abs);
|
|
15524
|
+
if (!rel || rel.startsWith("..") || path34.isAbsolute(rel)) return true;
|
|
15525
|
+
const posixRel = rel.split(path34.sep).join("/");
|
|
15197
15526
|
return this.ignoreMatcher.ignores(posixRel);
|
|
15198
15527
|
}
|
|
15199
15528
|
};
|
|
@@ -15427,22 +15756,22 @@ var FileMcpOAuthStore = class {
|
|
|
15427
15756
|
filePath;
|
|
15428
15757
|
load() {
|
|
15429
15758
|
try {
|
|
15430
|
-
return JSON.parse(
|
|
15759
|
+
return JSON.parse(fs31.readFileSync(this.filePath, "utf-8"));
|
|
15431
15760
|
} catch {
|
|
15432
15761
|
return void 0;
|
|
15433
15762
|
}
|
|
15434
15763
|
}
|
|
15435
15764
|
save(state) {
|
|
15436
|
-
|
|
15437
|
-
|
|
15765
|
+
fs31.mkdirSync(path34.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
15766
|
+
fs31.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
15438
15767
|
try {
|
|
15439
|
-
|
|
15768
|
+
fs31.chmodSync(this.filePath, 384);
|
|
15440
15769
|
} catch {
|
|
15441
15770
|
}
|
|
15442
15771
|
}
|
|
15443
15772
|
clear() {
|
|
15444
15773
|
try {
|
|
15445
|
-
|
|
15774
|
+
fs31.rmSync(this.filePath);
|
|
15446
15775
|
} catch {
|
|
15447
15776
|
}
|
|
15448
15777
|
}
|
|
@@ -15905,6 +16234,14 @@ function heuristicAnalyze(prompt) {
|
|
|
15905
16234
|
const estimatedTokens = wordCount * 5;
|
|
15906
16235
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
15907
16236
|
}
|
|
16237
|
+
function hashPrompt(prompt) {
|
|
16238
|
+
let hash = 2166136261;
|
|
16239
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
16240
|
+
hash ^= prompt.charCodeAt(index);
|
|
16241
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
16242
|
+
}
|
|
16243
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
16244
|
+
}
|
|
15908
16245
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
15909
16246
|
var TASK_TYPE_TAGS = {
|
|
15910
16247
|
code: ["code", "instruction"],
|
|
@@ -15948,7 +16285,7 @@ var TaskAnalyzer = class {
|
|
|
15948
16285
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
15949
16286
|
*/
|
|
15950
16287
|
async analyze(prompt) {
|
|
15951
|
-
const cacheKey = prompt
|
|
16288
|
+
const cacheKey = hashPrompt(prompt);
|
|
15952
16289
|
const cached = analysisCache.get(cacheKey);
|
|
15953
16290
|
if (cached) {
|
|
15954
16291
|
this.lastProfile = cached;
|
|
@@ -16050,7 +16387,7 @@ var TaskAnalyzer = class {
|
|
|
16050
16387
|
|
|
16051
16388
|
// src/core/router/model-performance-tracker.ts
|
|
16052
16389
|
init_pricing();
|
|
16053
|
-
var DEFAULT_STATS_FILE =
|
|
16390
|
+
var DEFAULT_STATS_FILE = path34.join(os10.homedir(), ".cascade", "model-perf.json");
|
|
16054
16391
|
var ModelPerformanceTracker = class {
|
|
16055
16392
|
stats = /* @__PURE__ */ new Map();
|
|
16056
16393
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -16090,7 +16427,7 @@ var ModelPerformanceTracker = class {
|
|
|
16090
16427
|
async save() {
|
|
16091
16428
|
if (this.readOnly) return;
|
|
16092
16429
|
try {
|
|
16093
|
-
await fs11.mkdir(
|
|
16430
|
+
await fs11.mkdir(path34.dirname(this.statsFile), { recursive: true });
|
|
16094
16431
|
const modelsObj = {};
|
|
16095
16432
|
const featuresObj = {};
|
|
16096
16433
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -16638,7 +16975,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
16638
16975
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
16639
16976
|
async loadPersistedTools() {
|
|
16640
16977
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
16641
|
-
const file =
|
|
16978
|
+
const file = path34.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
16642
16979
|
try {
|
|
16643
16980
|
const raw = await fs11.readFile(file, "utf-8");
|
|
16644
16981
|
const specs = JSON.parse(raw);
|
|
@@ -16662,8 +16999,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
16662
16999
|
}
|
|
16663
17000
|
async persist() {
|
|
16664
17001
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
16665
|
-
const dir =
|
|
16666
|
-
const file =
|
|
17002
|
+
const dir = path34.join(this.workspacePath, ".cascade");
|
|
17003
|
+
const file = path34.join(dir, DYNAMIC_TOOLS_FILE);
|
|
16667
17004
|
try {
|
|
16668
17005
|
await fs11.mkdir(dir, { recursive: true });
|
|
16669
17006
|
await fs11.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -16683,12 +17020,12 @@ var WorldStateDB = class {
|
|
|
16683
17020
|
constructor(workspacePath, debugMode = false) {
|
|
16684
17021
|
this.workspacePath = workspacePath;
|
|
16685
17022
|
this.debugMode = debugMode;
|
|
16686
|
-
const cascadeDir =
|
|
16687
|
-
if (!
|
|
16688
|
-
|
|
17023
|
+
const cascadeDir = path34.join(workspacePath, ".cascade");
|
|
17024
|
+
if (!fs31.existsSync(cascadeDir)) {
|
|
17025
|
+
fs31.mkdirSync(cascadeDir, { recursive: true });
|
|
16689
17026
|
}
|
|
16690
|
-
this.keyPath =
|
|
16691
|
-
this.dbPath =
|
|
17027
|
+
this.keyPath = path34.join(cascadeDir, "world_state.key");
|
|
17028
|
+
this.dbPath = path34.join(cascadeDir, "world_state.db");
|
|
16692
17029
|
this.initEncryptionKey();
|
|
16693
17030
|
this.db = new Database2(this.dbPath);
|
|
16694
17031
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -16745,11 +17082,11 @@ var WorldStateDB = class {
|
|
|
16745
17082
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
16746
17083
|
}
|
|
16747
17084
|
initEncryptionKey() {
|
|
16748
|
-
if (
|
|
16749
|
-
this.encryptionKey =
|
|
17085
|
+
if (fs31.existsSync(this.keyPath)) {
|
|
17086
|
+
this.encryptionKey = fs31.readFileSync(this.keyPath);
|
|
16750
17087
|
} else {
|
|
16751
17088
|
this.encryptionKey = crypto.randomBytes(32);
|
|
16752
|
-
|
|
17089
|
+
fs31.writeFileSync(this.keyPath, this.encryptionKey);
|
|
16753
17090
|
}
|
|
16754
17091
|
}
|
|
16755
17092
|
encrypt(text) {
|
|
@@ -17035,9 +17372,9 @@ var WorldStateDB = class {
|
|
|
17035
17372
|
dumpDebugIfNeeded() {
|
|
17036
17373
|
if (!this.debugMode) return;
|
|
17037
17374
|
try {
|
|
17038
|
-
const dumpPath =
|
|
17375
|
+
const dumpPath = path34.join(os10.tmpdir(), "cascade_world_state_debug.json");
|
|
17039
17376
|
const entries = this.getAllEntries();
|
|
17040
|
-
|
|
17377
|
+
fs31.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
17041
17378
|
} catch (err) {
|
|
17042
17379
|
console.error("Failed to dump debug world state", err);
|
|
17043
17380
|
}
|
|
@@ -17046,6 +17383,128 @@ var WorldStateDB = class {
|
|
|
17046
17383
|
this.db.close();
|
|
17047
17384
|
}
|
|
17048
17385
|
};
|
|
17386
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
17387
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
17388
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
17389
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
17390
|
+
function safeId(taskId) {
|
|
17391
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
17392
|
+
return cleaned.slice(0, 100) || "run";
|
|
17393
|
+
}
|
|
17394
|
+
function isCheckpoint(value) {
|
|
17395
|
+
if (typeof value !== "object" || value === null) return false;
|
|
17396
|
+
const c = value;
|
|
17397
|
+
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);
|
|
17398
|
+
}
|
|
17399
|
+
var ResumeStore = class {
|
|
17400
|
+
dir;
|
|
17401
|
+
maxCheckpoints;
|
|
17402
|
+
maxAgeMs;
|
|
17403
|
+
now;
|
|
17404
|
+
constructor(options) {
|
|
17405
|
+
this.dir = options.dir;
|
|
17406
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
17407
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
17408
|
+
this.now = options.now ?? Date.now;
|
|
17409
|
+
}
|
|
17410
|
+
/**
|
|
17411
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
17412
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
17413
|
+
* worse outcome than the one this store exists to improve.
|
|
17414
|
+
*/
|
|
17415
|
+
async save(checkpoint) {
|
|
17416
|
+
try {
|
|
17417
|
+
const timestamp = this.now();
|
|
17418
|
+
const record = {
|
|
17419
|
+
...checkpoint,
|
|
17420
|
+
version: RESUME_SCHEMA_VERSION,
|
|
17421
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
17422
|
+
};
|
|
17423
|
+
await promises.mkdir(this.dir, { recursive: true });
|
|
17424
|
+
const finalPath = path34.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
17425
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
17426
|
+
await promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
17427
|
+
await promises.rename(tempPath, finalPath);
|
|
17428
|
+
await this.prune();
|
|
17429
|
+
} catch {
|
|
17430
|
+
}
|
|
17431
|
+
}
|
|
17432
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
17433
|
+
async list() {
|
|
17434
|
+
let entries;
|
|
17435
|
+
try {
|
|
17436
|
+
entries = await promises.readdir(this.dir);
|
|
17437
|
+
} catch {
|
|
17438
|
+
return [];
|
|
17439
|
+
}
|
|
17440
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
17441
|
+
const candidates2 = 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);
|
|
17442
|
+
const checkpoints = [];
|
|
17443
|
+
for (const candidate of candidates2) {
|
|
17444
|
+
try {
|
|
17445
|
+
const raw = await promises.readFile(path34.join(this.dir, candidate.name), "utf-8");
|
|
17446
|
+
const parsed = JSON.parse(raw);
|
|
17447
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
17448
|
+
} catch {
|
|
17449
|
+
continue;
|
|
17450
|
+
}
|
|
17451
|
+
}
|
|
17452
|
+
return checkpoints;
|
|
17453
|
+
}
|
|
17454
|
+
/** The most recent resumable run, or null. */
|
|
17455
|
+
async latest() {
|
|
17456
|
+
return (await this.list())[0] ?? null;
|
|
17457
|
+
}
|
|
17458
|
+
/**
|
|
17459
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
17460
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
17461
|
+
*/
|
|
17462
|
+
async consume(taskId) {
|
|
17463
|
+
const all = await this.list();
|
|
17464
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
17465
|
+
if (!target) return null;
|
|
17466
|
+
await this.delete(target.taskId);
|
|
17467
|
+
return target;
|
|
17468
|
+
}
|
|
17469
|
+
/** Remove every checkpoint for a task id. */
|
|
17470
|
+
async delete(taskId) {
|
|
17471
|
+
try {
|
|
17472
|
+
const entries = await promises.readdir(this.dir);
|
|
17473
|
+
const wanted = safeId(taskId);
|
|
17474
|
+
await Promise.all(entries.map(async (name) => {
|
|
17475
|
+
const match = FILE_PATTERN.exec(name);
|
|
17476
|
+
if (match?.[2] === wanted) {
|
|
17477
|
+
await promises.rm(path34.join(this.dir, name), { force: true });
|
|
17478
|
+
}
|
|
17479
|
+
}));
|
|
17480
|
+
} catch {
|
|
17481
|
+
}
|
|
17482
|
+
}
|
|
17483
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
17484
|
+
async prune() {
|
|
17485
|
+
try {
|
|
17486
|
+
const entries = await promises.readdir(this.dir);
|
|
17487
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
17488
|
+
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);
|
|
17489
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
17490
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
17491
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
17492
|
+
(name) => promises.rm(path34.join(this.dir, name), { force: true })
|
|
17493
|
+
));
|
|
17494
|
+
} catch {
|
|
17495
|
+
}
|
|
17496
|
+
}
|
|
17497
|
+
};
|
|
17498
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
17499
|
+
if (!completed.length) return "";
|
|
17500
|
+
const lines = completed.map((node) => {
|
|
17501
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
17502
|
+
return `- [${node.status}] ${node.title}
|
|
17503
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
17504
|
+
});
|
|
17505
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
17506
|
+
${lines.join("\n")}`;
|
|
17507
|
+
}
|
|
17049
17508
|
var ignore3 = _ignoreModule.default ?? _ignoreModule;
|
|
17050
17509
|
var PrivacyPaths = class {
|
|
17051
17510
|
localOnly;
|
|
@@ -17407,7 +17866,7 @@ var WorkspaceIndex = class {
|
|
|
17407
17866
|
isIgnored;
|
|
17408
17867
|
chunker;
|
|
17409
17868
|
constructor(opts) {
|
|
17410
|
-
this.root =
|
|
17869
|
+
this.root = path34.resolve(opts.root);
|
|
17411
17870
|
this.db = opts.db;
|
|
17412
17871
|
this.namespace = opts.namespace ?? "code";
|
|
17413
17872
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -17439,14 +17898,14 @@ var WorkspaceIndex = class {
|
|
|
17439
17898
|
return;
|
|
17440
17899
|
}
|
|
17441
17900
|
for (const entry of entries) {
|
|
17442
|
-
const abs =
|
|
17901
|
+
const abs = path34.join(dir, entry.name);
|
|
17443
17902
|
if (entry.isDirectory()) {
|
|
17444
17903
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
17445
17904
|
await walk(abs);
|
|
17446
17905
|
} else if (entry.isFile()) {
|
|
17447
|
-
const ext =
|
|
17906
|
+
const ext = path34.extname(entry.name).slice(1).toLowerCase();
|
|
17448
17907
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
17449
|
-
out.push(
|
|
17908
|
+
out.push(path34.relative(this.root, abs));
|
|
17450
17909
|
}
|
|
17451
17910
|
}
|
|
17452
17911
|
};
|
|
@@ -17476,7 +17935,7 @@ var WorkspaceIndex = class {
|
|
|
17476
17935
|
const entries = [];
|
|
17477
17936
|
for (const rel of rels) {
|
|
17478
17937
|
try {
|
|
17479
|
-
const abs =
|
|
17938
|
+
const abs = path34.join(this.root, rel);
|
|
17480
17939
|
const stat = await fs11.stat(abs);
|
|
17481
17940
|
if (stat.size > this.maxFileBytes) continue;
|
|
17482
17941
|
const buf = await fs11.readFile(abs);
|
|
@@ -18014,8 +18473,16 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
18014
18473
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
18015
18474
|
decisionLog = [];
|
|
18016
18475
|
initialized = false;
|
|
18017
|
-
/**
|
|
18476
|
+
/**
|
|
18477
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
18478
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
18479
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
18480
|
+
*/
|
|
18018
18481
|
lastInterruptedRun;
|
|
18482
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
18483
|
+
resumeStore;
|
|
18484
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
18485
|
+
currentRunCompleted = [];
|
|
18019
18486
|
initPromise;
|
|
18020
18487
|
store;
|
|
18021
18488
|
audit;
|
|
@@ -18048,10 +18515,16 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
18048
18515
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
18049
18516
|
try {
|
|
18050
18517
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
18051
|
-
fileDeadModelPersistence(
|
|
18518
|
+
fileDeadModelPersistence(path34.join(workspacePath, ".cascade", "dead-models.json"))
|
|
18052
18519
|
));
|
|
18053
18520
|
} catch {
|
|
18054
18521
|
}
|
|
18522
|
+
try {
|
|
18523
|
+
this.resumeStore = new ResumeStore({
|
|
18524
|
+
dir: path34.join(workspacePath, ".cascade", "resume")
|
|
18525
|
+
});
|
|
18526
|
+
} catch {
|
|
18527
|
+
}
|
|
18055
18528
|
this.multimodal = new MultimodalRegistry(
|
|
18056
18529
|
(this.config.providers ?? []).map((p) => p.type)
|
|
18057
18530
|
);
|
|
@@ -18136,8 +18609,8 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
18136
18609
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
18137
18610
|
return;
|
|
18138
18611
|
}
|
|
18139
|
-
const dbPath = ci.dbPath ||
|
|
18140
|
-
|
|
18612
|
+
const dbPath = ci.dbPath || path34.join(this.workspacePath, ".cascade", "code-index.db");
|
|
18613
|
+
fs31.mkdirSync(path34.dirname(dbPath), { recursive: true });
|
|
18141
18614
|
const db = new Database2(dbPath);
|
|
18142
18615
|
db.pragma("journal_mode = WAL");
|
|
18143
18616
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -18517,10 +18990,43 @@ ${input}` }],
|
|
|
18517
18990
|
setFeedbackSource(source) {
|
|
18518
18991
|
this.router.setFeedbackSource(source);
|
|
18519
18992
|
}
|
|
18520
|
-
/**
|
|
18993
|
+
/**
|
|
18994
|
+
* Write a durable checkpoint for an interrupted run.
|
|
18995
|
+
*
|
|
18996
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
18997
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
18998
|
+
* worse. Nothing here is allowed to throw.
|
|
18999
|
+
*/
|
|
19000
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
19001
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
19002
|
+
try {
|
|
19003
|
+
await this.resumeStore?.save({
|
|
19004
|
+
taskId,
|
|
19005
|
+
prompt,
|
|
19006
|
+
reason,
|
|
19007
|
+
...detail ? { detail } : {},
|
|
19008
|
+
partialOutput,
|
|
19009
|
+
completed: [...this.currentRunCompleted]
|
|
19010
|
+
});
|
|
19011
|
+
} catch {
|
|
19012
|
+
}
|
|
19013
|
+
}
|
|
19014
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
18521
19015
|
hasResumableRun() {
|
|
18522
19016
|
return this.lastInterruptedRun != null;
|
|
18523
19017
|
}
|
|
19018
|
+
/**
|
|
19019
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
19020
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
19021
|
+
* answer for callers already on a hot path.
|
|
19022
|
+
*/
|
|
19023
|
+
async hasDurableResume() {
|
|
19024
|
+
return await this.resumeStore?.latest() != null;
|
|
19025
|
+
}
|
|
19026
|
+
/** Interrupted runs available to resume, newest first. */
|
|
19027
|
+
async listResumableRuns() {
|
|
19028
|
+
return await this.resumeStore?.list() ?? [];
|
|
19029
|
+
}
|
|
18524
19030
|
/**
|
|
18525
19031
|
* Raise the per-run token budget for a resume and return the continuation
|
|
18526
19032
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -18531,22 +19037,54 @@ ${input}` }],
|
|
|
18531
19037
|
const last = this.lastInterruptedRun;
|
|
18532
19038
|
if (!last) return null;
|
|
18533
19039
|
this.lastInterruptedRun = void 0;
|
|
18534
|
-
|
|
19040
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
19041
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
19042
|
+
}
|
|
19043
|
+
/**
|
|
19044
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
19045
|
+
* process, including one that crashed before it could return. Consumes the
|
|
19046
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
19047
|
+
*/
|
|
19048
|
+
async prepareDurableResume(opts = {}) {
|
|
19049
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
19050
|
+
if (!checkpoint) return null;
|
|
19051
|
+
this.lastInterruptedRun = void 0;
|
|
19052
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
19053
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
19054
|
+
}
|
|
19055
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
19056
|
+
raiseBudgetForResume(maxTokens) {
|
|
19057
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
18535
19058
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
18536
19059
|
this.router.setMaxTokensPerRun(raised);
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
19060
|
+
}
|
|
19061
|
+
/**
|
|
19062
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
19063
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
19064
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
19065
|
+
* is something it can act on. That difference is the entire reason node
|
|
19066
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
19067
|
+
* finished work again, and the user pays for it a second time.
|
|
19068
|
+
*/
|
|
19069
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
19070
|
+
const done = summarizeCompleted(completed);
|
|
19071
|
+
return [
|
|
19072
|
+
"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.",
|
|
19073
|
+
"",
|
|
19074
|
+
`Original task: ${prompt}`,
|
|
19075
|
+
done ? `
|
|
19076
|
+
${done}` : "",
|
|
19077
|
+
partialOutput ? `
|
|
18541
19078
|
Partial result so far:
|
|
18542
|
-
${
|
|
19079
|
+
${partialOutput}` : ""
|
|
19080
|
+
].filter(Boolean).join("\n");
|
|
18543
19081
|
}
|
|
18544
19082
|
/**
|
|
18545
19083
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
18546
19084
|
* Returns null when there is nothing to resume.
|
|
18547
19085
|
*/
|
|
18548
19086
|
async resumeRun(opts = {}) {
|
|
18549
|
-
const prompt = this.prepareResume(opts);
|
|
19087
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
18550
19088
|
if (!prompt) return null;
|
|
18551
19089
|
return this.run({ prompt });
|
|
18552
19090
|
}
|
|
@@ -18917,6 +19455,7 @@ ${prompt}` : prompt;
|
|
|
18917
19455
|
const startMs = Date.now();
|
|
18918
19456
|
const taskId = randomUUID();
|
|
18919
19457
|
this.decisionLog = [];
|
|
19458
|
+
this.currentRunCompleted = [];
|
|
18920
19459
|
if (options.fastAnswer) {
|
|
18921
19460
|
return this.runFastAnswer(options, startMs, taskId);
|
|
18922
19461
|
}
|
|
@@ -19031,6 +19570,11 @@ ${prompt}` : prompt;
|
|
|
19031
19570
|
});
|
|
19032
19571
|
tier.on("log", (e) => this.emit("log", e));
|
|
19033
19572
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
19573
|
+
tier.on("section:complete", (e) => {
|
|
19574
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
19575
|
+
this.currentRunCompleted.push(e);
|
|
19576
|
+
this.emit("section:complete", e);
|
|
19577
|
+
});
|
|
19034
19578
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
19035
19579
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
19036
19580
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -19177,6 +19721,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
19177
19721
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
19178
19722
|
const trip = runBreaker.reason();
|
|
19179
19723
|
if (trip) {
|
|
19724
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
19180
19725
|
finalOutput = [
|
|
19181
19726
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
19182
19727
|
"",
|
|
@@ -19196,6 +19741,13 @@ ${finalOutput}` : ""
|
|
|
19196
19741
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
19197
19742
|
partialOutput: finalOutput || ""
|
|
19198
19743
|
});
|
|
19744
|
+
await this.checkpointRun(
|
|
19745
|
+
taskId,
|
|
19746
|
+
options.prompt,
|
|
19747
|
+
finalOutput || "",
|
|
19748
|
+
"cancelled",
|
|
19749
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
19750
|
+
);
|
|
19199
19751
|
runError = null;
|
|
19200
19752
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
19201
19753
|
this.emit("run:budget-exceeded", {
|
|
@@ -19204,9 +19756,17 @@ ${finalOutput}` : ""
|
|
|
19204
19756
|
partialOutput: finalOutput || ""
|
|
19205
19757
|
});
|
|
19206
19758
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
19759
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
19207
19760
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
19208
19761
|
runError = null;
|
|
19209
19762
|
} else {
|
|
19763
|
+
await this.checkpointRun(
|
|
19764
|
+
taskId,
|
|
19765
|
+
options.prompt,
|
|
19766
|
+
finalOutput || "",
|
|
19767
|
+
"error",
|
|
19768
|
+
err instanceof Error ? err.message : String(err)
|
|
19769
|
+
);
|
|
19210
19770
|
runError = err;
|
|
19211
19771
|
throw err;
|
|
19212
19772
|
}
|
|
@@ -19418,14 +19978,14 @@ ${finalOutput}` : ""
|
|
|
19418
19978
|
init_constants();
|
|
19419
19979
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
19420
19980
|
function globalDir(homeDir = os10.homedir()) {
|
|
19421
|
-
return
|
|
19981
|
+
return path34.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
19422
19982
|
}
|
|
19423
19983
|
function cloudSessionPath(dir = globalDir()) {
|
|
19424
|
-
return
|
|
19984
|
+
return path34.join(dir, CLOUD_SESSION_FILE);
|
|
19425
19985
|
}
|
|
19426
19986
|
function loadCloudSession(dir = globalDir()) {
|
|
19427
19987
|
try {
|
|
19428
|
-
const raw =
|
|
19988
|
+
const raw = fs31.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
19429
19989
|
const parsed = JSON.parse(raw);
|
|
19430
19990
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
19431
19991
|
return parsed;
|
|
@@ -19434,17 +19994,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
19434
19994
|
}
|
|
19435
19995
|
}
|
|
19436
19996
|
function saveCloudSession(session, dir = globalDir()) {
|
|
19437
|
-
|
|
19997
|
+
fs31.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
19438
19998
|
const file = cloudSessionPath(dir);
|
|
19439
|
-
|
|
19999
|
+
fs31.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
19440
20000
|
try {
|
|
19441
|
-
|
|
20001
|
+
fs31.chmodSync(file, 384);
|
|
19442
20002
|
} catch {
|
|
19443
20003
|
}
|
|
19444
20004
|
}
|
|
19445
20005
|
function clearCloudSession(dir = globalDir()) {
|
|
19446
20006
|
try {
|
|
19447
|
-
|
|
20007
|
+
fs31.rmSync(cloudSessionPath(dir));
|
|
19448
20008
|
} catch {
|
|
19449
20009
|
}
|
|
19450
20010
|
}
|
|
@@ -19616,16 +20176,16 @@ var CloudClient = class _CloudClient {
|
|
|
19616
20176
|
return (await this.refresh()).accessToken;
|
|
19617
20177
|
}
|
|
19618
20178
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
19619
|
-
async authed(
|
|
19620
|
-
const call = (token) => fetch(this.url(
|
|
20179
|
+
async authed(path39, init = {}) {
|
|
20180
|
+
const call = (token) => fetch(this.url(path39), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
19621
20181
|
let res = await call(await this.accessToken());
|
|
19622
20182
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
19623
20183
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
19624
20184
|
const text = await res.text();
|
|
19625
20185
|
return text ? JSON.parse(text) : {};
|
|
19626
20186
|
}
|
|
19627
|
-
authedGet(
|
|
19628
|
-
return this.authed(
|
|
20187
|
+
authedGet(path39) {
|
|
20188
|
+
return this.authed(path39);
|
|
19629
20189
|
}
|
|
19630
20190
|
async fetchMe(token) {
|
|
19631
20191
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -21400,9 +21960,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
21400
21960
|
}
|
|
21401
21961
|
} catch {
|
|
21402
21962
|
}
|
|
21403
|
-
const configPath =
|
|
21963
|
+
const configPath = path34.join(workspacePath, ".cascade", "config.json");
|
|
21404
21964
|
try {
|
|
21405
|
-
await fs11.mkdir(
|
|
21965
|
+
await fs11.mkdir(path34.dirname(configPath), { recursive: true });
|
|
21406
21966
|
await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
21407
21967
|
} catch (err) {
|
|
21408
21968
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -21454,9 +22014,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
21454
22014
|
if (msg.includes("non-text parts")) return;
|
|
21455
22015
|
originalLog(...args);
|
|
21456
22016
|
};
|
|
21457
|
-
const store = new MemoryStore(
|
|
22017
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
21458
22018
|
storeRef.current = store;
|
|
21459
|
-
globalStoreRef.current = new MemoryStore(
|
|
22019
|
+
globalStoreRef.current = new MemoryStore(path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
21460
22020
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
21461
22021
|
setIdentities(identityRows);
|
|
21462
22022
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -21524,7 +22084,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
21524
22084
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
21525
22085
|
onExport: async (fmt) => {
|
|
21526
22086
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
21527
|
-
const exportPath =
|
|
22087
|
+
const exportPath = path34.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
|
|
21528
22088
|
if (fmt === "json") {
|
|
21529
22089
|
await fs11.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
21530
22090
|
} else {
|
|
@@ -21766,10 +22326,12 @@ ${lastUser.content}`;
|
|
|
21766
22326
|
onContinue: async (args) => {
|
|
21767
22327
|
const cascade = cascadeRef.current;
|
|
21768
22328
|
if (!cascade) return "Not ready yet.";
|
|
21769
|
-
if (!cascade.hasResumableRun()) return "Nothing to continue \u2014 no task was stopped by the budget cap.";
|
|
21770
22329
|
const n = args[0] ? parseInt(args[0].replace(/[^0-9]/g, ""), 10) : NaN;
|
|
21771
|
-
const
|
|
21772
|
-
|
|
22330
|
+
const opts = Number.isFinite(n) && n > 0 ? { maxTokens: n } : {};
|
|
22331
|
+
const prompt = await cascade.prepareDurableResume(opts) ?? cascade.prepareResume(opts);
|
|
22332
|
+
if (!prompt) {
|
|
22333
|
+
return "Nothing to continue \u2014 no interrupted task was found for this workspace.";
|
|
22334
|
+
}
|
|
21773
22335
|
await handleSubmit(prompt);
|
|
21774
22336
|
return "";
|
|
21775
22337
|
}
|
|
@@ -22422,7 +22984,7 @@ function stringifySlashOutput(val) {
|
|
|
22422
22984
|
}
|
|
22423
22985
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
22424
22986
|
if (!query) return "Usage: /search <query>";
|
|
22425
|
-
const dbPath =
|
|
22987
|
+
const dbPath = path34.join(workspacePath, CASCADE_DB_FILE);
|
|
22426
22988
|
try {
|
|
22427
22989
|
await fs11.access(dbPath);
|
|
22428
22990
|
} catch {
|
|
@@ -22456,7 +23018,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
22456
23018
|
async function diagnoseRuntime(config, workspacePath) {
|
|
22457
23019
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
22458
23020
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
22459
|
-
const store = new MemoryStore(
|
|
23021
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
22460
23022
|
try {
|
|
22461
23023
|
const sessions2 = store.listSessions(void 0, 3);
|
|
22462
23024
|
return [
|
|
@@ -22475,7 +23037,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
22475
23037
|
}
|
|
22476
23038
|
async function showRecentLogs(args, workspacePath) {
|
|
22477
23039
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
22478
|
-
const store = new MemoryStore(
|
|
23040
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
22479
23041
|
try {
|
|
22480
23042
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
22481
23043
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -22487,7 +23049,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
22487
23049
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
22488
23050
|
const sessionId = args[0];
|
|
22489
23051
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
22490
|
-
const store = new MemoryStore(
|
|
23052
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
22491
23053
|
try {
|
|
22492
23054
|
const session = store.getSession(sessionId);
|
|
22493
23055
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -22915,9 +23477,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
22915
23477
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
22916
23478
|
};
|
|
22917
23479
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
22918
|
-
const configDir =
|
|
23480
|
+
const configDir = path34.join(workspacePath, ".cascade");
|
|
22919
23481
|
await fs11.mkdir(configDir, { recursive: true });
|
|
22920
|
-
const configPath =
|
|
23482
|
+
const configPath = path34.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
22921
23483
|
await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
22922
23484
|
savedConfigRef.current = config;
|
|
22923
23485
|
dispatchRef.current({ type: "GO_DONE" });
|
|
@@ -23259,14 +23821,14 @@ function printTelemetryBanner() {
|
|
|
23259
23821
|
async function initCommand(workspacePath = process.cwd()) {
|
|
23260
23822
|
const spin = ora({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
23261
23823
|
try {
|
|
23262
|
-
const configDir =
|
|
23824
|
+
const configDir = path34.join(workspacePath, ".cascade");
|
|
23263
23825
|
await fs11.mkdir(configDir, { recursive: true });
|
|
23264
|
-
const mdPath =
|
|
23826
|
+
const mdPath = path34.join(workspacePath, "CASCADE.md");
|
|
23265
23827
|
if (!await fileExists(mdPath)) {
|
|
23266
23828
|
await createDefaultCascadeMd(workspacePath);
|
|
23267
23829
|
spin.succeed(chalk7.green("Created CASCADE.md"));
|
|
23268
23830
|
}
|
|
23269
|
-
const ignorePath =
|
|
23831
|
+
const ignorePath = path34.join(workspacePath, ".cascadeignore");
|
|
23270
23832
|
if (!await fileExists(ignorePath)) {
|
|
23271
23833
|
await createDefaultIgnoreFile(workspacePath);
|
|
23272
23834
|
spin.succeed(chalk7.green("Created .cascadeignore"));
|
|
@@ -23275,7 +23837,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
23275
23837
|
console.log();
|
|
23276
23838
|
console.log(chalk7.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
23277
23839
|
console.log();
|
|
23278
|
-
const configPath =
|
|
23840
|
+
const configPath = path34.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
23279
23841
|
if (await fileExists(configPath)) {
|
|
23280
23842
|
console.log(chalk7.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
23281
23843
|
console.log();
|
|
@@ -23333,7 +23895,7 @@ function fromEnv(env) {
|
|
|
23333
23895
|
return out;
|
|
23334
23896
|
}
|
|
23335
23897
|
async function fromClaudeCode(home) {
|
|
23336
|
-
const file =
|
|
23898
|
+
const file = path34.join(home, ".claude", ".credentials.json");
|
|
23337
23899
|
const data = await readJson(file);
|
|
23338
23900
|
if (!data) return [];
|
|
23339
23901
|
const oauth = data["claudeAiOauth"];
|
|
@@ -23356,7 +23918,7 @@ async function fromClaudeCode(home) {
|
|
|
23356
23918
|
return [];
|
|
23357
23919
|
}
|
|
23358
23920
|
async function fromCodex(home) {
|
|
23359
|
-
const file =
|
|
23921
|
+
const file = path34.join(home, ".codex", "auth.json");
|
|
23360
23922
|
const data = await readJson(file);
|
|
23361
23923
|
if (!data) return [];
|
|
23362
23924
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -23379,7 +23941,7 @@ async function fromCodex(home) {
|
|
|
23379
23941
|
return [];
|
|
23380
23942
|
}
|
|
23381
23943
|
async function fromGemini(home) {
|
|
23382
|
-
const file =
|
|
23944
|
+
const file = path34.join(home, ".gemini", "oauth_creds.json");
|
|
23383
23945
|
const data = await readJson(file);
|
|
23384
23946
|
const accessToken = str(data?.["access_token"]);
|
|
23385
23947
|
if (!accessToken) return [];
|
|
@@ -23395,7 +23957,7 @@ async function fromGemini(home) {
|
|
|
23395
23957
|
}
|
|
23396
23958
|
async function fromCopilot(home) {
|
|
23397
23959
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
23398
|
-
const file =
|
|
23960
|
+
const file = path34.join(home, ".config", "github-copilot", name);
|
|
23399
23961
|
const data = await readJson(file);
|
|
23400
23962
|
if (!data) continue;
|
|
23401
23963
|
for (const value of Object.values(data)) {
|
|
@@ -23449,7 +24011,7 @@ async function doctorCommand() {
|
|
|
23449
24011
|
checks.push({
|
|
23450
24012
|
label: "Cascade config",
|
|
23451
24013
|
ok: true,
|
|
23452
|
-
detail: `Loaded ${
|
|
24014
|
+
detail: `Loaded ${path34.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
23453
24015
|
});
|
|
23454
24016
|
const providers = [
|
|
23455
24017
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -23532,17 +24094,17 @@ async function doctorCommand() {
|
|
|
23532
24094
|
const detail = c.detail ? chalk7.gray(` \u2014 ${c.detail}`) : "";
|
|
23533
24095
|
console.log(`${icon} ${label}${detail}`);
|
|
23534
24096
|
}
|
|
23535
|
-
const
|
|
24097
|
+
const failures2 = checks.filter((c) => !c.ok);
|
|
23536
24098
|
console.log();
|
|
23537
|
-
if (
|
|
24099
|
+
if (failures2.length === 0) {
|
|
23538
24100
|
console.log(chalk7.green(" All checks passed!\n"));
|
|
23539
24101
|
} else {
|
|
23540
|
-
const critical =
|
|
24102
|
+
const critical = failures2.filter((c) => c.label.includes("Node") || c.label.includes("API key"));
|
|
23541
24103
|
if (critical.length) {
|
|
23542
24104
|
console.log(chalk7.yellow(` ${critical.length} issue(s) need attention.
|
|
23543
24105
|
`));
|
|
23544
24106
|
} else {
|
|
23545
|
-
console.log(chalk7.gray(` ${
|
|
24107
|
+
console.log(chalk7.gray(` ${failures2.length} optional item(s) not configured.
|
|
23546
24108
|
`));
|
|
23547
24109
|
}
|
|
23548
24110
|
}
|
|
@@ -23560,7 +24122,7 @@ async function checkEndpoint(url) {
|
|
|
23560
24122
|
}
|
|
23561
24123
|
}
|
|
23562
24124
|
async function indexCommand(dirPath) {
|
|
23563
|
-
const workspace =
|
|
24125
|
+
const workspace = path34.resolve(dirPath || process.cwd());
|
|
23564
24126
|
const cm = new ConfigManager(workspace);
|
|
23565
24127
|
await cm.load();
|
|
23566
24128
|
const config = cm.getConfig();
|
|
@@ -23571,8 +24133,8 @@ async function indexCommand(dirPath) {
|
|
|
23571
24133
|
process.exitCode = 1;
|
|
23572
24134
|
return;
|
|
23573
24135
|
}
|
|
23574
|
-
const dbPath = config.codeIndex?.dbPath ||
|
|
23575
|
-
|
|
24136
|
+
const dbPath = config.codeIndex?.dbPath || path34.join(workspace, ".cascade", "code-index.db");
|
|
24137
|
+
fs31.mkdirSync(path34.dirname(dbPath), { recursive: true });
|
|
23576
24138
|
const db = new Database2(dbPath);
|
|
23577
24139
|
db.pragma("journal_mode = WAL");
|
|
23578
24140
|
const complete = chatCompleterFromProviders(config.providers);
|
|
@@ -24061,7 +24623,7 @@ function openBrowser2(url) {
|
|
|
24061
24623
|
}
|
|
24062
24624
|
function storePathFor(name) {
|
|
24063
24625
|
const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
|
|
24064
|
-
return
|
|
24626
|
+
return path34.join(os10.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
|
|
24065
24627
|
}
|
|
24066
24628
|
function defaultName(url) {
|
|
24067
24629
|
try {
|
|
@@ -24563,7 +25125,7 @@ function aggregateCostStats(sessions2, opts = {}) {
|
|
|
24563
25125
|
}
|
|
24564
25126
|
|
|
24565
25127
|
// src/dashboard/server.ts
|
|
24566
|
-
var __dirname$1 =
|
|
25128
|
+
var __dirname$1 = path34.dirname(fileURLToPath(import.meta.url));
|
|
24567
25129
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
24568
25130
|
var DashboardServer = class {
|
|
24569
25131
|
app;
|
|
@@ -24843,14 +25405,14 @@ var DashboardServer = class {
|
|
|
24843
25405
|
*/
|
|
24844
25406
|
persistConfig() {
|
|
24845
25407
|
try {
|
|
24846
|
-
const configPath =
|
|
24847
|
-
|
|
24848
|
-
|
|
25408
|
+
const configPath = path34.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
25409
|
+
fs31.mkdirSync(path34.dirname(configPath), { recursive: true });
|
|
25410
|
+
fs31.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
24849
25411
|
} catch (err) {
|
|
24850
25412
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
24851
25413
|
}
|
|
24852
25414
|
try {
|
|
24853
|
-
saveGlobalCredentials(
|
|
25415
|
+
saveGlobalCredentials(path34.join(os10.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
24854
25416
|
} catch (err) {
|
|
24855
25417
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
24856
25418
|
}
|
|
@@ -24865,15 +25427,15 @@ var DashboardServer = class {
|
|
|
24865
25427
|
resolveDashboardSecret() {
|
|
24866
25428
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
24867
25429
|
if (fromConfig) return fromConfig;
|
|
24868
|
-
const secretPath =
|
|
25430
|
+
const secretPath = path34.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
24869
25431
|
try {
|
|
24870
|
-
if (
|
|
24871
|
-
const existing =
|
|
25432
|
+
if (fs31.existsSync(secretPath)) {
|
|
25433
|
+
const existing = fs31.readFileSync(secretPath, "utf-8").trim();
|
|
24872
25434
|
if (existing.length >= 16) return existing;
|
|
24873
25435
|
}
|
|
24874
25436
|
const generated = randomUUID();
|
|
24875
|
-
|
|
24876
|
-
|
|
25437
|
+
fs31.mkdirSync(path34.dirname(secretPath), { recursive: true });
|
|
25438
|
+
fs31.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
24877
25439
|
if (this.config.dashboard.auth) {
|
|
24878
25440
|
console.warn(
|
|
24879
25441
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -24900,7 +25462,7 @@ var DashboardServer = class {
|
|
|
24900
25462
|
// ── Setup ─────────────────────────────────────
|
|
24901
25463
|
getGlobalStore() {
|
|
24902
25464
|
if (!this.globalStore) {
|
|
24903
|
-
const globalDbPath =
|
|
25465
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24904
25466
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
24905
25467
|
}
|
|
24906
25468
|
return this.globalStore;
|
|
@@ -25280,12 +25842,12 @@ ${prompt}`;
|
|
|
25280
25842
|
}
|
|
25281
25843
|
}
|
|
25282
25844
|
watchRuntimeChanges() {
|
|
25283
|
-
const workspaceDbPath =
|
|
25284
|
-
const globalDbPath =
|
|
25845
|
+
const workspaceDbPath = path34.join(this.workspacePath, CASCADE_DB_FILE);
|
|
25846
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25285
25847
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
25286
25848
|
for (const watchPath of watchPaths) {
|
|
25287
|
-
if (!
|
|
25288
|
-
|
|
25849
|
+
if (!fs31.existsSync(watchPath)) continue;
|
|
25850
|
+
fs31.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
25289
25851
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
25290
25852
|
});
|
|
25291
25853
|
}
|
|
@@ -25407,7 +25969,7 @@ ${prompt}`;
|
|
|
25407
25969
|
const sessionId = req.params.id;
|
|
25408
25970
|
this.store.deleteSession(sessionId);
|
|
25409
25971
|
this.store.deleteRuntimeSession(sessionId);
|
|
25410
|
-
const globalDbPath =
|
|
25972
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25411
25973
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25412
25974
|
try {
|
|
25413
25975
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -25500,7 +26062,7 @@ ${prompt}`;
|
|
|
25500
26062
|
});
|
|
25501
26063
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
25502
26064
|
const body = req.body;
|
|
25503
|
-
const globalDbPath =
|
|
26065
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25504
26066
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
25505
26067
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25506
26068
|
try {
|
|
@@ -25523,7 +26085,7 @@ ${prompt}`;
|
|
|
25523
26085
|
});
|
|
25524
26086
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
25525
26087
|
this.store.deleteAllRuntimeNodes();
|
|
25526
|
-
const globalDbPath =
|
|
26088
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25527
26089
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25528
26090
|
try {
|
|
25529
26091
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -25609,12 +26171,12 @@ ${prompt}`;
|
|
|
25609
26171
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
25610
26172
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
25611
26173
|
try {
|
|
25612
|
-
const configPath =
|
|
25613
|
-
const existing =
|
|
26174
|
+
const configPath = path34.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
26175
|
+
const existing = fs31.existsSync(configPath) ? JSON.parse(fs31.readFileSync(configPath, "utf-8")) : {};
|
|
25614
26176
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
25615
26177
|
const tmp = configPath + ".tmp";
|
|
25616
|
-
|
|
25617
|
-
|
|
26178
|
+
fs31.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
26179
|
+
fs31.renameSync(tmp, configPath);
|
|
25618
26180
|
res.json({ ok: true });
|
|
25619
26181
|
} catch (err) {
|
|
25620
26182
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -25642,7 +26204,7 @@ ${prompt}`;
|
|
|
25642
26204
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
25643
26205
|
const scope = req.query["scope"] ?? "workspace";
|
|
25644
26206
|
if (scope === "global") {
|
|
25645
|
-
const globalDbPath =
|
|
26207
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25646
26208
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25647
26209
|
try {
|
|
25648
26210
|
res.json({
|
|
@@ -25983,13 +26545,13 @@ ${prompt}`;
|
|
|
25983
26545
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
25984
26546
|
}
|
|
25985
26547
|
});
|
|
25986
|
-
const prodPath =
|
|
25987
|
-
const devPath =
|
|
25988
|
-
const webDistPath =
|
|
25989
|
-
if (
|
|
26548
|
+
const prodPath = path34.resolve(__dirname$1, "../web/dist");
|
|
26549
|
+
const devPath = path34.resolve(__dirname$1, "../../web/dist");
|
|
26550
|
+
const webDistPath = fs31.existsSync(prodPath) ? prodPath : devPath;
|
|
26551
|
+
if (fs31.existsSync(webDistPath)) {
|
|
25990
26552
|
this.app.use(express.static(webDistPath));
|
|
25991
26553
|
this.app.get("*", (_req, res) => {
|
|
25992
|
-
res.sendFile(
|
|
26554
|
+
res.sendFile(path34.join(webDistPath, "index.html"));
|
|
25993
26555
|
});
|
|
25994
26556
|
} else {
|
|
25995
26557
|
this.app.get("/", (_req, res) => {
|
|
@@ -26010,7 +26572,7 @@ init_constants();
|
|
|
26010
26572
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
26011
26573
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
26012
26574
|
const spin = ora({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
26013
|
-
const store = new MemoryStore(
|
|
26575
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
26014
26576
|
const server = new DashboardServer(config, store, workspacePath);
|
|
26015
26577
|
server.watchRuntimeChanges();
|
|
26016
26578
|
const gThis = globalThis;
|
|
@@ -26042,7 +26604,7 @@ init_constants();
|
|
|
26042
26604
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
26043
26605
|
const identity = new Command("identity").alias("id").description("Manage Cascade identities");
|
|
26044
26606
|
identity.command("list").description("List all available identities").action(() => {
|
|
26045
|
-
const store = new MemoryStore(
|
|
26607
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
26046
26608
|
try {
|
|
26047
26609
|
const identities = store.listIdentities();
|
|
26048
26610
|
console.log(chalk7.bold("\n Identities:"));
|
|
@@ -26062,7 +26624,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
26062
26624
|
}
|
|
26063
26625
|
});
|
|
26064
26626
|
identity.command("create <name>").description("Create a new identity").option("-d, --desc <text>", "Description of the identity").option("-s, --system <text>", "System prompt").option("--default", "Set as default identity").action((name, options) => {
|
|
26065
|
-
const store = new MemoryStore(
|
|
26627
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
26066
26628
|
try {
|
|
26067
26629
|
if (options.default) {
|
|
26068
26630
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -26088,7 +26650,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
26088
26650
|
}
|
|
26089
26651
|
});
|
|
26090
26652
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
26091
|
-
const store = new MemoryStore(
|
|
26653
|
+
const store = new MemoryStore(path34.join(workspacePath, CASCADE_DB_FILE));
|
|
26092
26654
|
try {
|
|
26093
26655
|
const identities = store.listIdentities();
|
|
26094
26656
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
@@ -26237,8 +26799,8 @@ async function exportCommand(options = {}) {
|
|
|
26237
26799
|
let store;
|
|
26238
26800
|
try {
|
|
26239
26801
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
26240
|
-
const workspaceDbPath =
|
|
26241
|
-
const globalDbPath =
|
|
26802
|
+
const workspaceDbPath = path34.join(workspacePath, CASCADE_DB_FILE);
|
|
26803
|
+
const globalDbPath = path34.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
26242
26804
|
let dbPath = globalDbPath;
|
|
26243
26805
|
try {
|
|
26244
26806
|
await fs11.access(workspaceDbPath);
|
|
@@ -26280,7 +26842,7 @@ async function exportCommand(options = {}) {
|
|
|
26280
26842
|
const ext = format === "json" ? ".json" : ".md";
|
|
26281
26843
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
26282
26844
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
26283
|
-
const outPath = options.output ?
|
|
26845
|
+
const outPath = options.output ? path34.resolve(options.output) : path34.join(process.cwd(), defaultFile);
|
|
26284
26846
|
await fs11.writeFile(outPath, content, "utf-8");
|
|
26285
26847
|
spin.succeed(chalk7.green(`Exported to ${chalk7.white(outPath)}`));
|
|
26286
26848
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
@@ -26508,11 +27070,11 @@ async function statsCommand() {
|
|
|
26508
27070
|
dotenv.config();
|
|
26509
27071
|
function warnIfBuildIsStale() {
|
|
26510
27072
|
try {
|
|
26511
|
-
const here =
|
|
27073
|
+
const here = path34.dirname(fileURLToPath(import.meta.url));
|
|
26512
27074
|
for (const rel of ["..", "../.."]) {
|
|
26513
|
-
const pkgPath =
|
|
26514
|
-
if (!
|
|
26515
|
-
const pkg = JSON.parse(
|
|
27075
|
+
const pkgPath = path34.join(here, rel, "package.json");
|
|
27076
|
+
if (!fs31.existsSync(pkgPath)) continue;
|
|
27077
|
+
const pkg = JSON.parse(fs31.readFileSync(pkgPath, "utf-8"));
|
|
26516
27078
|
if (pkg.name !== "cascade-ai") continue;
|
|
26517
27079
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
26518
27080
|
console.error(
|