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.cjs
CHANGED
|
@@ -9,8 +9,8 @@ var https = require('https');
|
|
|
9
9
|
var zlib = require('zlib');
|
|
10
10
|
var stream = require('stream');
|
|
11
11
|
var Database2 = require('better-sqlite3');
|
|
12
|
-
var
|
|
13
|
-
var
|
|
12
|
+
var path34 = require('path');
|
|
13
|
+
var fs31 = require('fs');
|
|
14
14
|
var os10 = require('os');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var ink = require('ink');
|
|
@@ -77,8 +77,8 @@ var http__default = /*#__PURE__*/_interopDefault(http);
|
|
|
77
77
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
78
78
|
var zlib__default = /*#__PURE__*/_interopDefault(zlib);
|
|
79
79
|
var Database2__default = /*#__PURE__*/_interopDefault(Database2);
|
|
80
|
-
var
|
|
81
|
-
var
|
|
80
|
+
var path34__default = /*#__PURE__*/_interopDefault(path34);
|
|
81
|
+
var fs31__default = /*#__PURE__*/_interopDefault(fs31);
|
|
82
82
|
var os10__default = /*#__PURE__*/_interopDefault(os10);
|
|
83
83
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
84
84
|
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
@@ -1810,7 +1810,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1810
1810
|
var init_constants = __esm({
|
|
1811
1811
|
"src/constants.ts"() {
|
|
1812
1812
|
init_pricing();
|
|
1813
|
-
CASCADE_VERSION = "0.
|
|
1813
|
+
CASCADE_VERSION = "0.68.0";
|
|
1814
1814
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1815
1815
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1816
1816
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -3932,12 +3932,12 @@ var init_audit_logger = __esm({
|
|
|
3932
3932
|
constructor(workspacePath, debugMode = false) {
|
|
3933
3933
|
this.workspacePath = workspacePath;
|
|
3934
3934
|
this.debugMode = debugMode;
|
|
3935
|
-
const cascadeDir =
|
|
3936
|
-
if (!
|
|
3937
|
-
|
|
3935
|
+
const cascadeDir = path34__default.default.join(workspacePath, ".cascade");
|
|
3936
|
+
if (!fs31__default.default.existsSync(cascadeDir)) {
|
|
3937
|
+
fs31__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
3938
3938
|
}
|
|
3939
|
-
this.keyPath =
|
|
3940
|
-
this.dbPath =
|
|
3939
|
+
this.keyPath = path34__default.default.join(cascadeDir, "audit_log.key");
|
|
3940
|
+
this.dbPath = path34__default.default.join(cascadeDir, "audit_log.db");
|
|
3941
3941
|
this.initEncryptionKey();
|
|
3942
3942
|
this.db = new Database2__default.default(this.dbPath);
|
|
3943
3943
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3967,11 +3967,11 @@ var init_audit_logger = __esm({
|
|
|
3967
3967
|
return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
3968
3968
|
}
|
|
3969
3969
|
initEncryptionKey() {
|
|
3970
|
-
if (
|
|
3971
|
-
this.encryptionKey =
|
|
3970
|
+
if (fs31__default.default.existsSync(this.keyPath)) {
|
|
3971
|
+
this.encryptionKey = fs31__default.default.readFileSync(this.keyPath);
|
|
3972
3972
|
} else {
|
|
3973
3973
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
3974
|
-
|
|
3974
|
+
fs31__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
3975
3975
|
}
|
|
3976
3976
|
}
|
|
3977
3977
|
encrypt(text) {
|
|
@@ -4049,9 +4049,9 @@ var init_audit_logger = __esm({
|
|
|
4049
4049
|
dumpDebugIfNeeded() {
|
|
4050
4050
|
if (!this.debugMode) return;
|
|
4051
4051
|
try {
|
|
4052
|
-
const dumpPath =
|
|
4052
|
+
const dumpPath = path34__default.default.join(os10__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
4053
4053
|
const entries = this.getAllLogs();
|
|
4054
|
-
|
|
4054
|
+
fs31__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
4055
4055
|
} catch (err) {
|
|
4056
4056
|
console.error("Failed to dump debug audit logs", err);
|
|
4057
4057
|
}
|
|
@@ -4106,7 +4106,7 @@ var Keystore = class {
|
|
|
4106
4106
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
4107
4107
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
4108
4108
|
this.backend = "keytar";
|
|
4109
|
-
if (password &&
|
|
4109
|
+
if (password && fs31__default.default.existsSync(this.storePath)) {
|
|
4110
4110
|
try {
|
|
4111
4111
|
const fileEntries = this.decryptFile(password);
|
|
4112
4112
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -4125,7 +4125,7 @@ var Keystore = class {
|
|
|
4125
4125
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
4126
4126
|
);
|
|
4127
4127
|
}
|
|
4128
|
-
if (!
|
|
4128
|
+
if (!fs31__default.default.existsSync(this.storePath)) {
|
|
4129
4129
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
4130
4130
|
this.masterKey = this.deriveKey(password, salt);
|
|
4131
4131
|
this.writeWithSalt({}, salt);
|
|
@@ -4139,7 +4139,7 @@ var Keystore = class {
|
|
|
4139
4139
|
}
|
|
4140
4140
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
4141
4141
|
unlockSync(password) {
|
|
4142
|
-
if (!
|
|
4142
|
+
if (!fs31__default.default.existsSync(this.storePath)) {
|
|
4143
4143
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
4144
4144
|
this.masterKey = this.deriveKey(password, salt);
|
|
4145
4145
|
this.writeWithSalt({}, salt);
|
|
@@ -4197,7 +4197,7 @@ var Keystore = class {
|
|
|
4197
4197
|
}
|
|
4198
4198
|
}
|
|
4199
4199
|
decryptFile(password, knownSalt) {
|
|
4200
|
-
if (!
|
|
4200
|
+
if (!fs31__default.default.existsSync(this.storePath)) return {};
|
|
4201
4201
|
try {
|
|
4202
4202
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
4203
4203
|
const useSalt = knownSalt ?? salt;
|
|
@@ -4219,8 +4219,8 @@ var Keystore = class {
|
|
|
4219
4219
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4220
4220
|
const tag = cipher.getAuthTag();
|
|
4221
4221
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
4222
|
-
|
|
4223
|
-
|
|
4222
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(this.storePath), { recursive: true });
|
|
4223
|
+
fs31__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4224
4224
|
}
|
|
4225
4225
|
writeWithSalt(data, salt) {
|
|
4226
4226
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -4230,11 +4230,11 @@ var Keystore = class {
|
|
|
4230
4230
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4231
4231
|
const tag = cipher.getAuthTag();
|
|
4232
4232
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
4233
|
-
|
|
4234
|
-
|
|
4233
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(this.storePath), { recursive: true });
|
|
4234
|
+
fs31__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4235
4235
|
}
|
|
4236
4236
|
readRaw() {
|
|
4237
|
-
const buf =
|
|
4237
|
+
const buf = fs31__default.default.readFileSync(this.storePath);
|
|
4238
4238
|
let offset = 0;
|
|
4239
4239
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
4240
4240
|
offset += SALT_LEN;
|
|
@@ -4267,7 +4267,7 @@ var CascadeIgnore = class {
|
|
|
4267
4267
|
]);
|
|
4268
4268
|
}
|
|
4269
4269
|
async load(workspacePath) {
|
|
4270
|
-
const filePath =
|
|
4270
|
+
const filePath = path34__default.default.join(workspacePath, ".cascadeignore");
|
|
4271
4271
|
try {
|
|
4272
4272
|
const content = await fs11__default.default.readFile(filePath, "utf-8");
|
|
4273
4273
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -4278,7 +4278,7 @@ var CascadeIgnore = class {
|
|
|
4278
4278
|
}
|
|
4279
4279
|
isIgnored(filePath, workspacePath) {
|
|
4280
4280
|
try {
|
|
4281
|
-
const relative = workspacePath ?
|
|
4281
|
+
const relative = workspacePath ? path34__default.default.relative(workspacePath, filePath) : filePath;
|
|
4282
4282
|
return this.ig.ignores(relative);
|
|
4283
4283
|
} catch {
|
|
4284
4284
|
return false;
|
|
@@ -4289,7 +4289,7 @@ var CascadeIgnore = class {
|
|
|
4289
4289
|
}
|
|
4290
4290
|
};
|
|
4291
4291
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
4292
|
-
const filePath =
|
|
4292
|
+
const filePath = path34__default.default.join(workspacePath, ".cascadeignore");
|
|
4293
4293
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
4294
4294
|
# Syntax identical to .gitignore
|
|
4295
4295
|
|
|
@@ -4319,7 +4319,7 @@ Thumbs.db
|
|
|
4319
4319
|
await fs11__default.default.writeFile(filePath, content, "utf-8");
|
|
4320
4320
|
}
|
|
4321
4321
|
async function loadCascadeMd(workspacePath) {
|
|
4322
|
-
const filePath =
|
|
4322
|
+
const filePath = path34__default.default.join(workspacePath, "CASCADE.md");
|
|
4323
4323
|
try {
|
|
4324
4324
|
const raw = await fs11__default.default.readFile(filePath, "utf-8");
|
|
4325
4325
|
return parseCascadeMd(raw);
|
|
@@ -4348,7 +4348,7 @@ ${raw.trim()}`;
|
|
|
4348
4348
|
return { raw, sections, systemPrompt };
|
|
4349
4349
|
}
|
|
4350
4350
|
async function createDefaultCascadeMd(workspacePath) {
|
|
4351
|
-
const filePath =
|
|
4351
|
+
const filePath = path34__default.default.join(workspacePath, "CASCADE.md");
|
|
4352
4352
|
const content = `# Cascade Project Instructions
|
|
4353
4353
|
|
|
4354
4354
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -4383,7 +4383,7 @@ List any areas the agent should not touch.
|
|
|
4383
4383
|
var MemoryStore = class _MemoryStore {
|
|
4384
4384
|
db;
|
|
4385
4385
|
constructor(dbPath) {
|
|
4386
|
-
|
|
4386
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(dbPath), { recursive: true });
|
|
4387
4387
|
try {
|
|
4388
4388
|
this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
|
|
4389
4389
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -5635,7 +5635,7 @@ function validateConfig(raw) {
|
|
|
5635
5635
|
// src/config/global-credentials.ts
|
|
5636
5636
|
init_constants();
|
|
5637
5637
|
function credentialsPath(globalDir2) {
|
|
5638
|
-
return
|
|
5638
|
+
return path34__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
5639
5639
|
}
|
|
5640
5640
|
function providerKey(p) {
|
|
5641
5641
|
if (p.type === "azure") {
|
|
@@ -5648,7 +5648,7 @@ function isPersistable(p) {
|
|
|
5648
5648
|
}
|
|
5649
5649
|
function loadGlobalCredentials(globalDir2) {
|
|
5650
5650
|
try {
|
|
5651
|
-
const raw =
|
|
5651
|
+
const raw = fs31__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
5652
5652
|
const parsed = JSON.parse(raw);
|
|
5653
5653
|
if (!Array.isArray(parsed.providers)) return [];
|
|
5654
5654
|
return parsed.providers.filter(
|
|
@@ -5660,13 +5660,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
5660
5660
|
}
|
|
5661
5661
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
5662
5662
|
const filePath = credentialsPath(globalDir2);
|
|
5663
|
-
|
|
5663
|
+
fs31__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
5664
5664
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
5665
5665
|
const tmp = `${filePath}.tmp`;
|
|
5666
|
-
|
|
5667
|
-
|
|
5666
|
+
fs31__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5667
|
+
fs31__default.default.renameSync(tmp, filePath);
|
|
5668
5668
|
try {
|
|
5669
|
-
|
|
5669
|
+
fs31__default.default.chmodSync(filePath, 384);
|
|
5670
5670
|
} catch {
|
|
5671
5671
|
}
|
|
5672
5672
|
}
|
|
@@ -5884,7 +5884,7 @@ var ConfigManager = class {
|
|
|
5884
5884
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
5885
5885
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
5886
5886
|
this.workspacePath = workspacePath;
|
|
5887
|
-
this.globalDir = globalDirOverride ??
|
|
5887
|
+
this.globalDir = globalDirOverride ?? path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
5888
5888
|
}
|
|
5889
5889
|
async load() {
|
|
5890
5890
|
this.config = await this.loadConfig();
|
|
@@ -5898,8 +5898,8 @@ var ConfigManager = class {
|
|
|
5898
5898
|
this.ignore = new CascadeIgnore();
|
|
5899
5899
|
await this.ignore.load(this.workspacePath);
|
|
5900
5900
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
5901
|
-
this.keystore = new Keystore(
|
|
5902
|
-
this.store = new MemoryStore(
|
|
5901
|
+
this.keystore = new Keystore(path34__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
5902
|
+
this.store = new MemoryStore(path34__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
5903
5903
|
await this.injectEnvKeys();
|
|
5904
5904
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
5905
5905
|
await this.ensureDefaultIdentity();
|
|
@@ -5924,8 +5924,8 @@ var ConfigManager = class {
|
|
|
5924
5924
|
return this.workspacePath;
|
|
5925
5925
|
}
|
|
5926
5926
|
async save() {
|
|
5927
|
-
const configPath =
|
|
5928
|
-
await fs11__default.default.mkdir(
|
|
5927
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5928
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(configPath), { recursive: true });
|
|
5929
5929
|
await fs11__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
5930
5930
|
try {
|
|
5931
5931
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
@@ -5961,7 +5961,7 @@ var ConfigManager = class {
|
|
|
5961
5961
|
return configProvider?.apiKey;
|
|
5962
5962
|
}
|
|
5963
5963
|
async loadConfig() {
|
|
5964
|
-
const configPath =
|
|
5964
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5965
5965
|
try {
|
|
5966
5966
|
const raw = await fs11__default.default.readFile(configPath, "utf-8");
|
|
5967
5967
|
return validateConfig(JSON.parse(raw));
|
|
@@ -6963,13 +6963,13 @@ var DeadModelStore = class {
|
|
|
6963
6963
|
function fileDeadModelPersistence(filePath) {
|
|
6964
6964
|
return {
|
|
6965
6965
|
load() {
|
|
6966
|
-
if (!
|
|
6967
|
-
const raw = JSON.parse(
|
|
6966
|
+
if (!fs31.existsSync(filePath)) return [];
|
|
6967
|
+
const raw = JSON.parse(fs31.readFileSync(filePath, "utf-8"));
|
|
6968
6968
|
return Array.isArray(raw) ? raw : [];
|
|
6969
6969
|
},
|
|
6970
6970
|
save(records) {
|
|
6971
|
-
|
|
6972
|
-
|
|
6971
|
+
fs31.mkdirSync(path34.dirname(filePath), { recursive: true });
|
|
6972
|
+
fs31.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6973
6973
|
}
|
|
6974
6974
|
};
|
|
6975
6975
|
}
|
|
@@ -7128,7 +7128,7 @@ init_pricing();
|
|
|
7128
7128
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
7129
7129
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
7130
7130
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
7131
|
-
var DEFAULT_CACHE_FILE =
|
|
7131
|
+
var DEFAULT_CACHE_FILE = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
7132
7132
|
function normalizeModelId2(id) {
|
|
7133
7133
|
let s = id.toLowerCase();
|
|
7134
7134
|
const slash = s.lastIndexOf("/");
|
|
@@ -7275,7 +7275,7 @@ var LiveDataProvider = class {
|
|
|
7275
7275
|
}
|
|
7276
7276
|
async saveCache() {
|
|
7277
7277
|
try {
|
|
7278
|
-
await fs11__default.default.mkdir(
|
|
7278
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
7279
7279
|
const cache = {
|
|
7280
7280
|
fetchedAt: this.fetchedAt,
|
|
7281
7281
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -9068,6 +9068,79 @@ function describeProviderError(c, modelId) {
|
|
|
9068
9068
|
}
|
|
9069
9069
|
}
|
|
9070
9070
|
|
|
9071
|
+
// src/core/verification/acceptance.ts
|
|
9072
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
9073
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
9074
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
9075
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
9076
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
9077
|
+
function targetFile(criterion, ownedFiles) {
|
|
9078
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
9079
|
+
if (mentioned.length > 1) return null;
|
|
9080
|
+
if (mentioned.length === 1) {
|
|
9081
|
+
const candidate = mentioned[0];
|
|
9082
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
9083
|
+
}
|
|
9084
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
9085
|
+
}
|
|
9086
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
9087
|
+
const results = [];
|
|
9088
|
+
for (const criterion of criteria) {
|
|
9089
|
+
const text = criterion.trim();
|
|
9090
|
+
if (!text) {
|
|
9091
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9092
|
+
continue;
|
|
9093
|
+
}
|
|
9094
|
+
if (NEGATION.test(text)) {
|
|
9095
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9096
|
+
continue;
|
|
9097
|
+
}
|
|
9098
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
9099
|
+
const wantsExists = EXISTS.test(text);
|
|
9100
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
9101
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
9102
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9103
|
+
continue;
|
|
9104
|
+
}
|
|
9105
|
+
const file = targetFile(text, ownedFiles);
|
|
9106
|
+
if (!file) {
|
|
9107
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9108
|
+
continue;
|
|
9109
|
+
}
|
|
9110
|
+
const stat = await probe.stat(file);
|
|
9111
|
+
if (!stat) {
|
|
9112
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
9113
|
+
continue;
|
|
9114
|
+
}
|
|
9115
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
9116
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
9117
|
+
continue;
|
|
9118
|
+
}
|
|
9119
|
+
if (needle) {
|
|
9120
|
+
const content = await probe.read(file);
|
|
9121
|
+
if (content === null) {
|
|
9122
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9123
|
+
continue;
|
|
9124
|
+
}
|
|
9125
|
+
const found = content.includes(needle);
|
|
9126
|
+
results.push({
|
|
9127
|
+
criterion,
|
|
9128
|
+
verdict: found ? "passed" : "failed",
|
|
9129
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
9130
|
+
});
|
|
9131
|
+
continue;
|
|
9132
|
+
}
|
|
9133
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
9134
|
+
}
|
|
9135
|
+
return results;
|
|
9136
|
+
}
|
|
9137
|
+
function undecided(results) {
|
|
9138
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
9139
|
+
}
|
|
9140
|
+
function failures(results) {
|
|
9141
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
9142
|
+
}
|
|
9143
|
+
|
|
9071
9144
|
// src/core/tiers/t3-worker.ts
|
|
9072
9145
|
var CriticalToolError = class extends Error {
|
|
9073
9146
|
constructor(message, toolName) {
|
|
@@ -9384,7 +9457,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
9384
9457
|
}
|
|
9385
9458
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
9386
9459
|
if (!artifactCheck.ok) {
|
|
9387
|
-
correctionAttempts
|
|
9460
|
+
correctionAttempts++;
|
|
9388
9461
|
issues.push(...artifactCheck.issues);
|
|
9389
9462
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
9390
9463
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -9402,18 +9475,40 @@ Now execute your subtask using this context where relevant.`
|
|
|
9402
9475
|
this.tools.map((t) => t.name)
|
|
9403
9476
|
);
|
|
9404
9477
|
if (visualIssue) {
|
|
9405
|
-
correctionAttempts
|
|
9478
|
+
correctionAttempts++;
|
|
9406
9479
|
issues.push(visualIssue);
|
|
9407
9480
|
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
9408
9481
|
output = await this.correctOutput(output, [visualIssue]);
|
|
9409
9482
|
}
|
|
9483
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
9484
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
9485
|
+
if (acceptanceFailures.length > 0) {
|
|
9486
|
+
correctionAttempts++;
|
|
9487
|
+
issues.push(...acceptanceFailures);
|
|
9488
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
9489
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
9490
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
9491
|
+
if (recheck.length > 0) {
|
|
9492
|
+
issues.push(...recheck);
|
|
9493
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
9494
|
+
failed.push(...recheck);
|
|
9495
|
+
this.setStatus("FAILED");
|
|
9496
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
9497
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
for (const result2 of acceptanceResults) {
|
|
9501
|
+
if (result2.verdict === "undecidable") continue;
|
|
9502
|
+
checksRun.push(result2.criterion);
|
|
9503
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
9504
|
+
}
|
|
9410
9505
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
9411
|
-
const testResult = await this.selfTest(assignment, output);
|
|
9506
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
9412
9507
|
checksRun.push(...testResult.checksRun);
|
|
9413
9508
|
passed.push(...testResult.passed);
|
|
9414
9509
|
failed.push(...testResult.failed);
|
|
9415
9510
|
if (testResult.failed.length > 0) {
|
|
9416
|
-
correctionAttempts
|
|
9511
|
+
correctionAttempts++;
|
|
9417
9512
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
9418
9513
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
9419
9514
|
output = corrected;
|
|
@@ -9721,8 +9816,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
9721
9816
|
// Media generation can run for a minute; without this a cancelled run
|
|
9722
9817
|
// still pays for an image nobody will see.
|
|
9723
9818
|
...this.signal ? { signal: this.signal } : {},
|
|
9724
|
-
saveSnapshot: async (
|
|
9725
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
9819
|
+
saveSnapshot: async (path39, content) => {
|
|
9820
|
+
this.store?.addFileSnapshot(this.taskId, path39, content);
|
|
9726
9821
|
},
|
|
9727
9822
|
sendPeerSync: (to, syncType, content) => {
|
|
9728
9823
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -9909,7 +10004,7 @@ ${assignment.expectedOutput}`;
|
|
|
9909
10004
|
const { promisify: promisify4 } = await import('util');
|
|
9910
10005
|
const execAsync3 = promisify4(exec3);
|
|
9911
10006
|
for (const artifactPath of artifactPaths) {
|
|
9912
|
-
const absolutePath =
|
|
10007
|
+
const absolutePath = path34__default.default.resolve(process.cwd(), artifactPath);
|
|
9913
10008
|
try {
|
|
9914
10009
|
const stat = await fs11__default.default.stat(absolutePath);
|
|
9915
10010
|
if (!stat.isFile()) {
|
|
@@ -9944,7 +10039,7 @@ ${assignment.expectedOutput}`;
|
|
|
9944
10039
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
9945
10040
|
continue;
|
|
9946
10041
|
}
|
|
9947
|
-
const ext =
|
|
10042
|
+
const ext = path34__default.default.extname(absolutePath).toLowerCase();
|
|
9948
10043
|
try {
|
|
9949
10044
|
if (ext === ".ts" || ext === ".tsx") {
|
|
9950
10045
|
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -10030,14 +10125,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
10030
10125
|
}
|
|
10031
10126
|
return current;
|
|
10032
10127
|
}
|
|
10033
|
-
|
|
10128
|
+
/**
|
|
10129
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
10130
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
10131
|
+
*/
|
|
10132
|
+
async checkAcceptance(assignment) {
|
|
10133
|
+
const criteria = assignment.acceptance ?? [];
|
|
10134
|
+
if (!criteria.length) return [];
|
|
10135
|
+
const resolve2 = (target) => path34__default.default.resolve(process.cwd(), target);
|
|
10136
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
10137
|
+
stat: async (target) => {
|
|
10138
|
+
try {
|
|
10139
|
+
const stat = await fs11__default.default.stat(resolve2(target));
|
|
10140
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
10141
|
+
} catch {
|
|
10142
|
+
return null;
|
|
10143
|
+
}
|
|
10144
|
+
},
|
|
10145
|
+
read: async (target) => {
|
|
10146
|
+
try {
|
|
10147
|
+
const content = await fs11__default.default.readFile(resolve2(target), "utf-8");
|
|
10148
|
+
return content.includes("\uFFFD") ? null : content;
|
|
10149
|
+
} catch {
|
|
10150
|
+
return null;
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
10153
|
+
});
|
|
10154
|
+
}
|
|
10155
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
10156
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
10034
10157
|
const prompt = `Self-test this output against the assignment requirements.
|
|
10035
10158
|
|
|
10036
10159
|
Assignment: ${assignment.description}
|
|
10037
10160
|
Expected output: ${assignment.expectedOutput}
|
|
10038
10161
|
Constraints: ${assignment.constraints.join("; ")}
|
|
10039
|
-
${
|
|
10040
|
-
${
|
|
10162
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
10163
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
10041
10164
|
` : ""}
|
|
10042
10165
|
Output to test:
|
|
10043
10166
|
${output}
|
|
@@ -10104,8 +10227,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
10104
10227
|
} catch {
|
|
10105
10228
|
}
|
|
10106
10229
|
}
|
|
10107
|
-
async correctOutput(originalOutput,
|
|
10108
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
10230
|
+
async correctOutput(originalOutput, failures2) {
|
|
10231
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
10109
10232
|
|
|
10110
10233
|
Original output:
|
|
10111
10234
|
${originalOutput}
|
|
@@ -10767,12 +10890,220 @@ function describeGenerationForPlanner(has) {
|
|
|
10767
10890
|
return lines.join("\n");
|
|
10768
10891
|
}
|
|
10769
10892
|
|
|
10893
|
+
// src/core/orchestration/task-graph.ts
|
|
10894
|
+
var TaskGraphValidationError = class extends Error {
|
|
10895
|
+
constructor(issues) {
|
|
10896
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
10897
|
+
this.issues = issues;
|
|
10898
|
+
this.name = "TaskGraphValidationError";
|
|
10899
|
+
}
|
|
10900
|
+
issues;
|
|
10901
|
+
};
|
|
10902
|
+
function normalizeDependencies(input) {
|
|
10903
|
+
if (!input?.length) return [];
|
|
10904
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
10905
|
+
}
|
|
10906
|
+
function cycleNodes(nodes) {
|
|
10907
|
+
const adjacency = new Map(
|
|
10908
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
10909
|
+
);
|
|
10910
|
+
const index = /* @__PURE__ */ new Map();
|
|
10911
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
10912
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
10913
|
+
const stack = [];
|
|
10914
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
10915
|
+
let counter = 0;
|
|
10916
|
+
const open = (id) => {
|
|
10917
|
+
index.set(id, counter);
|
|
10918
|
+
lowlink.set(id, counter);
|
|
10919
|
+
counter++;
|
|
10920
|
+
stack.push(id);
|
|
10921
|
+
onStack.add(id);
|
|
10922
|
+
};
|
|
10923
|
+
for (const root of nodes) {
|
|
10924
|
+
if (index.has(root.id)) continue;
|
|
10925
|
+
open(root.id);
|
|
10926
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
10927
|
+
while (frames.length > 0) {
|
|
10928
|
+
const frame = frames[frames.length - 1];
|
|
10929
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
10930
|
+
if (frame.edge < neighbours.length) {
|
|
10931
|
+
const next = neighbours[frame.edge];
|
|
10932
|
+
frame.edge++;
|
|
10933
|
+
if (!adjacency.has(next)) continue;
|
|
10934
|
+
if (!index.has(next)) {
|
|
10935
|
+
open(next);
|
|
10936
|
+
frames.push({ id: next, edge: 0 });
|
|
10937
|
+
} else if (onStack.has(next)) {
|
|
10938
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
10939
|
+
}
|
|
10940
|
+
continue;
|
|
10941
|
+
}
|
|
10942
|
+
frames.pop();
|
|
10943
|
+
const parent = frames[frames.length - 1];
|
|
10944
|
+
if (parent) {
|
|
10945
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
10946
|
+
}
|
|
10947
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
10948
|
+
const component = [];
|
|
10949
|
+
for (; ; ) {
|
|
10950
|
+
const member = stack.pop();
|
|
10951
|
+
onStack.delete(member);
|
|
10952
|
+
component.push(member);
|
|
10953
|
+
if (member === frame.id) break;
|
|
10954
|
+
}
|
|
10955
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
10956
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
return inCycle;
|
|
10961
|
+
}
|
|
10962
|
+
function throwIfStrict(mode, issues) {
|
|
10963
|
+
if (mode === "strict" && issues.length > 0) {
|
|
10964
|
+
throw new TaskGraphValidationError(issues);
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
10968
|
+
const mode = options.mode ?? "strict";
|
|
10969
|
+
const issues = [];
|
|
10970
|
+
if (inputNodes.length === 0) {
|
|
10971
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
10972
|
+
throwIfStrict(mode, issues);
|
|
10973
|
+
}
|
|
10974
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10975
|
+
const nodes = [];
|
|
10976
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
10977
|
+
const input = inputNodes[ordinal];
|
|
10978
|
+
const id = input.id?.trim();
|
|
10979
|
+
const title = input.title?.trim();
|
|
10980
|
+
if (!id || !title) {
|
|
10981
|
+
issues.push({
|
|
10982
|
+
code: "INVALID_NODE",
|
|
10983
|
+
nodeId: id || void 0,
|
|
10984
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
10985
|
+
});
|
|
10986
|
+
continue;
|
|
10987
|
+
}
|
|
10988
|
+
if (seen.has(id)) {
|
|
10989
|
+
issues.push({
|
|
10990
|
+
code: "DUPLICATE_NODE_ID",
|
|
10991
|
+
nodeId: id,
|
|
10992
|
+
message: `Duplicate task graph node id: ${id}`
|
|
10993
|
+
});
|
|
10994
|
+
continue;
|
|
10995
|
+
}
|
|
10996
|
+
seen.add(id);
|
|
10997
|
+
nodes.push({
|
|
10998
|
+
id,
|
|
10999
|
+
title,
|
|
11000
|
+
kind: input.kind,
|
|
11001
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
11002
|
+
payload: input.payload,
|
|
11003
|
+
ordinal
|
|
11004
|
+
});
|
|
11005
|
+
}
|
|
11006
|
+
throwIfStrict(mode, issues);
|
|
11007
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
11008
|
+
const sanitized = nodes.map((node) => {
|
|
11009
|
+
const dependsOn = [];
|
|
11010
|
+
for (const dependencyId of node.dependsOn) {
|
|
11011
|
+
if (dependencyId === node.id) {
|
|
11012
|
+
issues.push({
|
|
11013
|
+
code: "SELF_DEPENDENCY",
|
|
11014
|
+
nodeId: node.id,
|
|
11015
|
+
dependencyId,
|
|
11016
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
11017
|
+
});
|
|
11018
|
+
continue;
|
|
11019
|
+
}
|
|
11020
|
+
if (!ids.has(dependencyId)) {
|
|
11021
|
+
issues.push({
|
|
11022
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
11023
|
+
nodeId: node.id,
|
|
11024
|
+
dependencyId,
|
|
11025
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
11026
|
+
});
|
|
11027
|
+
continue;
|
|
11028
|
+
}
|
|
11029
|
+
dependsOn.push(dependencyId);
|
|
11030
|
+
}
|
|
11031
|
+
return { ...node, dependsOn };
|
|
11032
|
+
});
|
|
11033
|
+
throwIfStrict(mode, issues);
|
|
11034
|
+
const cyclic = cycleNodes(sanitized);
|
|
11035
|
+
let repaired = sanitized;
|
|
11036
|
+
if (cyclic.size > 0) {
|
|
11037
|
+
const cycleIssues = [];
|
|
11038
|
+
repaired = sanitized.map((node) => {
|
|
11039
|
+
if (!cyclic.has(node.id)) return node;
|
|
11040
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
11041
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
11042
|
+
cycleIssues.push({
|
|
11043
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
11044
|
+
nodeId: node.id,
|
|
11045
|
+
dependencyId,
|
|
11046
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
11047
|
+
});
|
|
11048
|
+
return false;
|
|
11049
|
+
});
|
|
11050
|
+
return { ...node, dependsOn };
|
|
11051
|
+
});
|
|
11052
|
+
issues.push(...cycleIssues);
|
|
11053
|
+
throwIfStrict(mode, cycleIssues);
|
|
11054
|
+
}
|
|
11055
|
+
const remainingCycle = cycleNodes(repaired);
|
|
11056
|
+
if (remainingCycle.size > 0) {
|
|
11057
|
+
throw new TaskGraphValidationError([
|
|
11058
|
+
{
|
|
11059
|
+
code: "INVALID_NODE",
|
|
11060
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
11061
|
+
}
|
|
11062
|
+
]);
|
|
11063
|
+
}
|
|
11064
|
+
return {
|
|
11065
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
11066
|
+
issues
|
|
11067
|
+
};
|
|
11068
|
+
}
|
|
11069
|
+
|
|
11070
|
+
// src/core/orchestration/adapters.ts
|
|
11071
|
+
function compileSectionGraph(sections) {
|
|
11072
|
+
return compileTaskGraph(
|
|
11073
|
+
sections.map((section) => ({
|
|
11074
|
+
id: section.sectionId,
|
|
11075
|
+
title: section.sectionTitle,
|
|
11076
|
+
kind: "section",
|
|
11077
|
+
dependsOn: section.dependsOn,
|
|
11078
|
+
payload: section
|
|
11079
|
+
})),
|
|
11080
|
+
{ mode: "repair" }
|
|
11081
|
+
);
|
|
11082
|
+
}
|
|
11083
|
+
function compileSubtaskGraph(subtasks) {
|
|
11084
|
+
return compileTaskGraph(
|
|
11085
|
+
subtasks.map((subtask) => ({
|
|
11086
|
+
id: subtask.subtaskId,
|
|
11087
|
+
title: subtask.subtaskTitle,
|
|
11088
|
+
kind: "subtask",
|
|
11089
|
+
dependsOn: subtask.dependsOn,
|
|
11090
|
+
payload: subtask
|
|
11091
|
+
})),
|
|
11092
|
+
{ mode: "repair" }
|
|
11093
|
+
);
|
|
11094
|
+
}
|
|
11095
|
+
|
|
10770
11096
|
// src/core/tiers/t2-manager.ts
|
|
10771
11097
|
function buildT2SystemPrompt(has) {
|
|
10772
11098
|
const generation = describeGenerationForPlanner(has);
|
|
10773
11099
|
return [
|
|
10774
11100
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
10775
|
-
|
|
11101
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
11102
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
11103
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
11104
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
11105
|
+
// real worker call on every small section.
|
|
11106
|
+
"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.",
|
|
10776
11107
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
10777
11108
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
10778
11109
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -11168,21 +11499,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11168
11499
|
* then reduce in-degrees of their dependents and repeat.
|
|
11169
11500
|
*/
|
|
11170
11501
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
11502
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
11503
|
+
for (const issue of compiled.issues) {
|
|
11504
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
11505
|
+
}
|
|
11506
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
11507
|
+
const assignment = node.payload;
|
|
11508
|
+
const original = assignment.dependsOn ?? [];
|
|
11509
|
+
const repaired = node.dependsOn;
|
|
11510
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
11511
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
11512
|
+
});
|
|
11171
11513
|
const adj = /* @__PURE__ */ new Map();
|
|
11172
11514
|
const inDegree = /* @__PURE__ */ new Map();
|
|
11173
11515
|
const resultMap = /* @__PURE__ */ new Map();
|
|
11174
|
-
for (const a of
|
|
11516
|
+
for (const a of sanitizedAssignments) {
|
|
11175
11517
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
11176
11518
|
inDegree.set(a.subtaskId, 0);
|
|
11177
11519
|
}
|
|
11178
|
-
for (const a of
|
|
11179
|
-
const
|
|
11180
|
-
for (const dep of deps) {
|
|
11520
|
+
for (const a of sanitizedAssignments) {
|
|
11521
|
+
for (const dep of a.dependsOn ?? []) {
|
|
11181
11522
|
adj.get(dep).add(a.subtaskId);
|
|
11182
11523
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
11183
11524
|
}
|
|
11184
11525
|
}
|
|
11185
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
11186
11526
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
11187
11527
|
let wave = 0;
|
|
11188
11528
|
let respawnBudget = 1;
|
|
@@ -11389,49 +11729,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11389
11729
|
}
|
|
11390
11730
|
return [...resultMap.values()];
|
|
11391
11731
|
}
|
|
11392
|
-
/**
|
|
11393
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
11394
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
11395
|
-
*
|
|
11396
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
11397
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
11398
|
-
*/
|
|
11399
|
-
breakCycles(assignments, adj, inDegree) {
|
|
11400
|
-
const degree = new Map(inDegree);
|
|
11401
|
-
const queue = [];
|
|
11402
|
-
const visited = /* @__PURE__ */ new Set();
|
|
11403
|
-
for (const [id, d] of degree) {
|
|
11404
|
-
if (d === 0) queue.push(id);
|
|
11405
|
-
}
|
|
11406
|
-
while (queue.length > 0) {
|
|
11407
|
-
const id = queue.shift();
|
|
11408
|
-
visited.add(id);
|
|
11409
|
-
for (const dep of adj.get(id) ?? []) {
|
|
11410
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
11411
|
-
degree.set(dep, newDeg);
|
|
11412
|
-
if (newDeg === 0) queue.push(dep);
|
|
11413
|
-
}
|
|
11414
|
-
}
|
|
11415
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
11416
|
-
if (cycleNodes.length === 0) return assignments;
|
|
11417
|
-
this.log(
|
|
11418
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
11419
|
-
);
|
|
11420
|
-
return assignments.map((a) => {
|
|
11421
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
11422
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
11423
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
11424
|
-
this.log(
|
|
11425
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
11426
|
-
);
|
|
11427
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
11428
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
11429
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
11430
|
-
}
|
|
11431
|
-
}
|
|
11432
|
-
return { ...a, dependsOn: safeDeps };
|
|
11433
|
-
});
|
|
11434
|
-
}
|
|
11435
11732
|
async retryT3(assignment, taskId) {
|
|
11436
11733
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
11437
11734
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -11570,6 +11867,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11570
11867
|
// src/core/tiers/t1-administrator.ts
|
|
11571
11868
|
init_constants();
|
|
11572
11869
|
|
|
11870
|
+
// src/core/orchestration/scheduler.ts
|
|
11871
|
+
var DependencyScheduler = class {
|
|
11872
|
+
constructor(graph, hooks, options = {}) {
|
|
11873
|
+
this.graph = graph;
|
|
11874
|
+
this.hooks = hooks;
|
|
11875
|
+
this.options = options;
|
|
11876
|
+
}
|
|
11877
|
+
graph;
|
|
11878
|
+
hooks;
|
|
11879
|
+
options;
|
|
11880
|
+
async run() {
|
|
11881
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
11882
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
11883
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
11884
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
11885
|
+
for (const node of nodes) {
|
|
11886
|
+
for (const dependencyId of node.dependsOn) {
|
|
11887
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11890
|
+
const completed = /* @__PURE__ */ new Set();
|
|
11891
|
+
const results = /* @__PURE__ */ new Map();
|
|
11892
|
+
const waves = [];
|
|
11893
|
+
let wave = 0;
|
|
11894
|
+
while (completed.size < nodes.length) {
|
|
11895
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
11896
|
+
if (ready.length === 0) {
|
|
11897
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
11898
|
+
}
|
|
11899
|
+
waves.push(ready.map((node) => node.id));
|
|
11900
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
11901
|
+
const runOne = async (node) => {
|
|
11902
|
+
const result = await this.hooks.execute(node);
|
|
11903
|
+
results.set(node.id, result);
|
|
11904
|
+
completed.add(node.id);
|
|
11905
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
11906
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
11907
|
+
}
|
|
11908
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
11909
|
+
};
|
|
11910
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
11911
|
+
for (const node of ready) await runOne(node);
|
|
11912
|
+
} else {
|
|
11913
|
+
await Promise.all(ready.map(runOne));
|
|
11914
|
+
}
|
|
11915
|
+
wave++;
|
|
11916
|
+
}
|
|
11917
|
+
return { results, waves };
|
|
11918
|
+
}
|
|
11919
|
+
};
|
|
11920
|
+
|
|
11573
11921
|
// src/utils/json-extract.ts
|
|
11574
11922
|
function extractFirstJsonObject(input) {
|
|
11575
11923
|
if (!input) return null;
|
|
@@ -11641,7 +11989,7 @@ function buildT1SystemPrompt(has) {
|
|
|
11641
11989
|
Your responsibilities:
|
|
11642
11990
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
11643
11991
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
11644
|
-
3. For each section, define
|
|
11992
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
11645
11993
|
4. Return a structured plan as JSON
|
|
11646
11994
|
|
|
11647
11995
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -12056,7 +12404,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
12056
12404
|
}
|
|
12057
12405
|
}
|
|
12058
12406
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
12059
|
-
if (plan.sections.length < min)
|
|
12407
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
12408
|
+
this.log(
|
|
12409
|
+
`\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.`
|
|
12410
|
+
);
|
|
12411
|
+
}
|
|
12060
12412
|
}
|
|
12061
12413
|
async dispatchT2Managers(sections) {
|
|
12062
12414
|
for (const section of sections) {
|
|
@@ -12168,114 +12520,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
12168
12520
|
return t2Results;
|
|
12169
12521
|
}
|
|
12170
12522
|
/**
|
|
12171
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
12523
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
12524
|
+
*
|
|
12525
|
+
* Graph construction, validation and cycle repair now live in
|
|
12526
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
12527
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
12528
|
+
* ordered by one implementation rather than two that drifted.
|
|
12529
|
+
*
|
|
12530
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
12531
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
12532
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
12533
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
12534
|
+
* sections and started them immediately — before the sections they consume.
|
|
12535
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
12536
|
+
* members are touched.
|
|
12172
12537
|
*/
|
|
12173
12538
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
12174
|
-
const
|
|
12175
|
-
const
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
}
|
|
12183
|
-
for (const s of sections) {
|
|
12184
|
-
for (const dep of s.dependsOn ?? []) {
|
|
12185
|
-
adj.get(dep).add(s.sectionId);
|
|
12186
|
-
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
12187
|
-
}
|
|
12188
|
-
}
|
|
12189
|
-
const queue = [];
|
|
12190
|
-
const degree = new Map(inDegree);
|
|
12191
|
-
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
12192
|
-
const visited = /* @__PURE__ */ new Set();
|
|
12193
|
-
while (queue.length > 0) {
|
|
12194
|
-
const u = queue.shift();
|
|
12195
|
-
visited.add(u);
|
|
12196
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
12197
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
12198
|
-
degree.set(v, newDeg);
|
|
12199
|
-
if (newDeg === 0) queue.push(v);
|
|
12200
|
-
}
|
|
12201
|
-
}
|
|
12202
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
12203
|
-
if (cycleNodes.length > 0) {
|
|
12204
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
12205
|
-
for (const s of sections) {
|
|
12206
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
12207
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
12208
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
12209
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
12210
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
12211
|
-
}
|
|
12212
|
-
s.dependsOn = safeDeps;
|
|
12213
|
-
}
|
|
12214
|
-
}
|
|
12539
|
+
const compiled = compileSectionGraph(sections);
|
|
12540
|
+
for (const issue of compiled.issues) {
|
|
12541
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
12542
|
+
}
|
|
12543
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
12544
|
+
for (const section of sections) {
|
|
12545
|
+
const node = byId.get(section.sectionId);
|
|
12546
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
12215
12547
|
}
|
|
12216
|
-
const
|
|
12548
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
12549
|
+
const totalSections = compiled.graph.nodes.length;
|
|
12217
12550
|
let completedSections = 0;
|
|
12218
|
-
const
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12551
|
+
const scheduler = new DependencyScheduler(
|
|
12552
|
+
compiled.graph,
|
|
12553
|
+
{
|
|
12554
|
+
execute: async (node) => {
|
|
12555
|
+
const section = node.payload;
|
|
12556
|
+
const manager = managers[node.ordinal];
|
|
12557
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
12558
|
+
this.sendStatusUpdate({
|
|
12559
|
+
progressPct,
|
|
12560
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
12561
|
+
status: "IN_PROGRESS"
|
|
12562
|
+
});
|
|
12563
|
+
this.throwIfCancelled();
|
|
12564
|
+
try {
|
|
12565
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
12566
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
12567
|
+
if (result.status === "ESCALATED") {
|
|
12568
|
+
this.escalations.push({
|
|
12569
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
12570
|
+
sectionId: section.sectionId,
|
|
12571
|
+
attempted: result.issues,
|
|
12572
|
+
blocker: result.issues.join("; "),
|
|
12573
|
+
needs: "Human review required"
|
|
12574
|
+
});
|
|
12575
|
+
}
|
|
12576
|
+
return result;
|
|
12577
|
+
} catch (err) {
|
|
12578
|
+
return {
|
|
12245
12579
|
sectionId: section.sectionId,
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12580
|
+
sectionTitle: section.sectionTitle,
|
|
12581
|
+
status: "FAILED",
|
|
12582
|
+
t3Results: [],
|
|
12583
|
+
sectionSummary: "",
|
|
12584
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
12585
|
+
};
|
|
12250
12586
|
}
|
|
12251
|
-
}
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
resultMap.set(id, result);
|
|
12262
|
-
completedSections++;
|
|
12263
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
12264
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
12265
|
-
}
|
|
12266
|
-
};
|
|
12267
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
12268
|
-
for (const id of readyIds) {
|
|
12269
|
-
await runOne(id);
|
|
12587
|
+
},
|
|
12588
|
+
onNodeComplete: (node, result) => {
|
|
12589
|
+
resultMap.set(node.id, result);
|
|
12590
|
+
completedSections++;
|
|
12591
|
+
this.emit("section:complete", {
|
|
12592
|
+
id: node.id,
|
|
12593
|
+
title: result.sectionTitle,
|
|
12594
|
+
status: result.status,
|
|
12595
|
+
output: result.sectionSummary
|
|
12596
|
+
});
|
|
12270
12597
|
}
|
|
12271
|
-
}
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12598
|
+
},
|
|
12599
|
+
{
|
|
12600
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
12601
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
12602
|
+
// serialized T3 workers within a single section while every section's
|
|
12603
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
12604
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
12276
12605
|
}
|
|
12277
|
-
|
|
12278
|
-
await
|
|
12606
|
+
);
|
|
12607
|
+
await scheduler.run();
|
|
12279
12608
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
12280
12609
|
}
|
|
12281
12610
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -12908,16 +13237,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
12908
13237
|
if (typeof input !== "string" || input.length === 0) {
|
|
12909
13238
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
12910
13239
|
}
|
|
12911
|
-
const root =
|
|
12912
|
-
const abs =
|
|
12913
|
-
const rel =
|
|
12914
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
13240
|
+
const root = path34__default.default.resolve(workspaceRoot);
|
|
13241
|
+
const abs = path34__default.default.isAbsolute(input) ? path34__default.default.resolve(input) : path34__default.default.resolve(root, input);
|
|
13242
|
+
const rel = path34__default.default.relative(root, abs);
|
|
13243
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path34__default.default.isAbsolute(rel)) {
|
|
12915
13244
|
throw new WorkspaceSandboxError(input, root);
|
|
12916
13245
|
}
|
|
12917
13246
|
try {
|
|
12918
|
-
const real =
|
|
12919
|
-
const realRel =
|
|
12920
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
13247
|
+
const real = fs31__default.default.realpathSync(abs);
|
|
13248
|
+
const realRel = path34__default.default.relative(root, real);
|
|
13249
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path34__default.default.isAbsolute(realRel))) {
|
|
12921
13250
|
throw new WorkspaceSandboxError(input, root);
|
|
12922
13251
|
}
|
|
12923
13252
|
} catch (e) {
|
|
@@ -13570,7 +13899,7 @@ var GenerateDocumentTool = class extends BaseTool {
|
|
|
13570
13899
|
}
|
|
13571
13900
|
}
|
|
13572
13901
|
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
13573
|
-
await fs11__default.default.mkdir(
|
|
13902
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(absPath), { recursive: true });
|
|
13574
13903
|
await fs11__default.default.writeFile(absPath, bytes);
|
|
13575
13904
|
const charts = countCharts(content);
|
|
13576
13905
|
return [
|
|
@@ -13791,7 +14120,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
13791
14120
|
} catch {
|
|
13792
14121
|
}
|
|
13793
14122
|
}
|
|
13794
|
-
await fs11__default.default.mkdir(
|
|
14123
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(absPath), { recursive: true });
|
|
13795
14124
|
await fs11__default.default.writeFile(absPath, content, "utf-8");
|
|
13796
14125
|
return `Written ${content.length} characters to ${filePath}`;
|
|
13797
14126
|
}
|
|
@@ -14289,7 +14618,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
14289
14618
|
};
|
|
14290
14619
|
async function fileToImageAttachment(filePath) {
|
|
14291
14620
|
const data = await fs11__default.default.readFile(filePath);
|
|
14292
|
-
const ext =
|
|
14621
|
+
const ext = path34__default.default.extname(filePath).toLowerCase();
|
|
14293
14622
|
const mimeMap = {
|
|
14294
14623
|
".jpg": "image/jpeg",
|
|
14295
14624
|
".jpeg": "image/jpeg",
|
|
@@ -14323,14 +14652,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
14323
14652
|
const filePath = input["path"];
|
|
14324
14653
|
const content = input["content"];
|
|
14325
14654
|
const title = input["title"];
|
|
14326
|
-
const dir =
|
|
14327
|
-
if (!
|
|
14328
|
-
|
|
14655
|
+
const dir = path34__default.default.dirname(filePath);
|
|
14656
|
+
if (!fs31__default.default.existsSync(dir)) {
|
|
14657
|
+
fs31__default.default.mkdirSync(dir, { recursive: true });
|
|
14329
14658
|
}
|
|
14330
14659
|
return new Promise((resolve2, reject) => {
|
|
14331
14660
|
try {
|
|
14332
14661
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
14333
|
-
const stream =
|
|
14662
|
+
const stream = fs31__default.default.createWriteStream(filePath);
|
|
14334
14663
|
doc.pipe(stream);
|
|
14335
14664
|
if (title) {
|
|
14336
14665
|
doc.info["Title"] = title;
|
|
@@ -14408,22 +14737,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
14408
14737
|
}
|
|
14409
14738
|
cmdPrefix = NODE_CMD;
|
|
14410
14739
|
}
|
|
14411
|
-
const tmpDir =
|
|
14412
|
-
if (!
|
|
14413
|
-
|
|
14740
|
+
const tmpDir = path34__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
14741
|
+
if (!fs31__default.default.existsSync(tmpDir)) {
|
|
14742
|
+
fs31__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
14414
14743
|
}
|
|
14415
14744
|
const extension = language === "python" ? "py" : "js";
|
|
14416
14745
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
14417
|
-
const filePath =
|
|
14418
|
-
|
|
14746
|
+
const filePath = path34__default.default.join(tmpDir, fileName);
|
|
14747
|
+
fs31__default.default.writeFileSync(filePath, code, "utf-8");
|
|
14419
14748
|
const execArgs = [filePath, ...args];
|
|
14420
14749
|
return new Promise((resolve2) => {
|
|
14421
14750
|
const startMs = Date.now();
|
|
14422
14751
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
14423
14752
|
const duration = Date.now() - startMs;
|
|
14424
14753
|
try {
|
|
14425
|
-
if (
|
|
14426
|
-
|
|
14754
|
+
if (fs31__default.default.existsSync(filePath)) {
|
|
14755
|
+
fs31__default.default.unlinkSync(filePath);
|
|
14427
14756
|
}
|
|
14428
14757
|
} catch (cleanupErr) {
|
|
14429
14758
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -14735,7 +15064,7 @@ var GlobTool = class extends BaseTool {
|
|
|
14735
15064
|
};
|
|
14736
15065
|
async execute(input, _options) {
|
|
14737
15066
|
const pattern = input["pattern"];
|
|
14738
|
-
const searchPath = input["path"] ?
|
|
15067
|
+
const searchPath = input["path"] ? path34__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
14739
15068
|
const matches = await glob.glob(pattern, {
|
|
14740
15069
|
cwd: searchPath,
|
|
14741
15070
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -14748,7 +15077,7 @@ var GlobTool = class extends BaseTool {
|
|
|
14748
15077
|
const withMtime = await Promise.all(
|
|
14749
15078
|
matches.map(async (rel) => {
|
|
14750
15079
|
try {
|
|
14751
|
-
const stat = await fs11__default.default.stat(
|
|
15080
|
+
const stat = await fs11__default.default.stat(path34__default.default.join(searchPath, rel));
|
|
14752
15081
|
return { rel, mtime: stat.mtimeMs };
|
|
14753
15082
|
} catch {
|
|
14754
15083
|
return { rel, mtime: 0 };
|
|
@@ -14797,7 +15126,7 @@ var GrepTool = class extends BaseTool {
|
|
|
14797
15126
|
};
|
|
14798
15127
|
async execute(input, _options) {
|
|
14799
15128
|
const pattern = input["pattern"];
|
|
14800
|
-
const searchPath = input["path"] ?
|
|
15129
|
+
const searchPath = input["path"] ? path34__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
14801
15130
|
const globPattern = input["glob"];
|
|
14802
15131
|
const outputMode = input["output_mode"] ?? "content";
|
|
14803
15132
|
const context = input["context"] ?? 0;
|
|
@@ -14851,12 +15180,12 @@ var GrepTool = class extends BaseTool {
|
|
|
14851
15180
|
nodir: true
|
|
14852
15181
|
});
|
|
14853
15182
|
} catch {
|
|
14854
|
-
files = [
|
|
15183
|
+
files = [path34__default.default.relative(searchPath, searchPath) || "."];
|
|
14855
15184
|
}
|
|
14856
15185
|
const results = [];
|
|
14857
15186
|
let totalCount = 0;
|
|
14858
15187
|
for (const rel of files) {
|
|
14859
|
-
const abs =
|
|
15188
|
+
const abs = path34__default.default.join(searchPath, rel);
|
|
14860
15189
|
let content;
|
|
14861
15190
|
try {
|
|
14862
15191
|
content = await fs11__default.default.readFile(abs, "utf-8");
|
|
@@ -15243,10 +15572,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
15243
15572
|
}
|
|
15244
15573
|
isIgnored(filePath) {
|
|
15245
15574
|
if (!filePath) return false;
|
|
15246
|
-
const abs =
|
|
15247
|
-
const rel =
|
|
15248
|
-
if (!rel || rel.startsWith("..") ||
|
|
15249
|
-
const posixRel = rel.split(
|
|
15575
|
+
const abs = path34__default.default.resolve(this.workspaceRoot, filePath);
|
|
15576
|
+
const rel = path34__default.default.relative(this.workspaceRoot, abs);
|
|
15577
|
+
if (!rel || rel.startsWith("..") || path34__default.default.isAbsolute(rel)) return true;
|
|
15578
|
+
const posixRel = rel.split(path34__default.default.sep).join("/");
|
|
15250
15579
|
return this.ignoreMatcher.ignores(posixRel);
|
|
15251
15580
|
}
|
|
15252
15581
|
};
|
|
@@ -15480,22 +15809,22 @@ var FileMcpOAuthStore = class {
|
|
|
15480
15809
|
filePath;
|
|
15481
15810
|
load() {
|
|
15482
15811
|
try {
|
|
15483
|
-
return JSON.parse(
|
|
15812
|
+
return JSON.parse(fs31__default.default.readFileSync(this.filePath, "utf-8"));
|
|
15484
15813
|
} catch {
|
|
15485
15814
|
return void 0;
|
|
15486
15815
|
}
|
|
15487
15816
|
}
|
|
15488
15817
|
save(state) {
|
|
15489
|
-
|
|
15490
|
-
|
|
15818
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
15819
|
+
fs31__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
15491
15820
|
try {
|
|
15492
|
-
|
|
15821
|
+
fs31__default.default.chmodSync(this.filePath, 384);
|
|
15493
15822
|
} catch {
|
|
15494
15823
|
}
|
|
15495
15824
|
}
|
|
15496
15825
|
clear() {
|
|
15497
15826
|
try {
|
|
15498
|
-
|
|
15827
|
+
fs31__default.default.rmSync(this.filePath);
|
|
15499
15828
|
} catch {
|
|
15500
15829
|
}
|
|
15501
15830
|
}
|
|
@@ -15958,6 +16287,14 @@ function heuristicAnalyze(prompt) {
|
|
|
15958
16287
|
const estimatedTokens = wordCount * 5;
|
|
15959
16288
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
15960
16289
|
}
|
|
16290
|
+
function hashPrompt(prompt) {
|
|
16291
|
+
let hash = 2166136261;
|
|
16292
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
16293
|
+
hash ^= prompt.charCodeAt(index);
|
|
16294
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
16295
|
+
}
|
|
16296
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
16297
|
+
}
|
|
15961
16298
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
15962
16299
|
var TASK_TYPE_TAGS = {
|
|
15963
16300
|
code: ["code", "instruction"],
|
|
@@ -16001,7 +16338,7 @@ var TaskAnalyzer = class {
|
|
|
16001
16338
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
16002
16339
|
*/
|
|
16003
16340
|
async analyze(prompt) {
|
|
16004
|
-
const cacheKey = prompt
|
|
16341
|
+
const cacheKey = hashPrompt(prompt);
|
|
16005
16342
|
const cached = analysisCache.get(cacheKey);
|
|
16006
16343
|
if (cached) {
|
|
16007
16344
|
this.lastProfile = cached;
|
|
@@ -16103,7 +16440,7 @@ var TaskAnalyzer = class {
|
|
|
16103
16440
|
|
|
16104
16441
|
// src/core/router/model-performance-tracker.ts
|
|
16105
16442
|
init_pricing();
|
|
16106
|
-
var DEFAULT_STATS_FILE =
|
|
16443
|
+
var DEFAULT_STATS_FILE = path34__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
|
|
16107
16444
|
var ModelPerformanceTracker = class {
|
|
16108
16445
|
stats = /* @__PURE__ */ new Map();
|
|
16109
16446
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -16143,7 +16480,7 @@ var ModelPerformanceTracker = class {
|
|
|
16143
16480
|
async save() {
|
|
16144
16481
|
if (this.readOnly) return;
|
|
16145
16482
|
try {
|
|
16146
|
-
await fs11__default.default.mkdir(
|
|
16483
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(this.statsFile), { recursive: true });
|
|
16147
16484
|
const modelsObj = {};
|
|
16148
16485
|
const featuresObj = {};
|
|
16149
16486
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -16691,7 +17028,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
16691
17028
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
16692
17029
|
async loadPersistedTools() {
|
|
16693
17030
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
16694
|
-
const file =
|
|
17031
|
+
const file = path34__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
16695
17032
|
try {
|
|
16696
17033
|
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
16697
17034
|
const specs = JSON.parse(raw);
|
|
@@ -16715,8 +17052,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
16715
17052
|
}
|
|
16716
17053
|
async persist() {
|
|
16717
17054
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
16718
|
-
const dir =
|
|
16719
|
-
const file =
|
|
17055
|
+
const dir = path34__default.default.join(this.workspacePath, ".cascade");
|
|
17056
|
+
const file = path34__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
16720
17057
|
try {
|
|
16721
17058
|
await fs11__default.default.mkdir(dir, { recursive: true });
|
|
16722
17059
|
await fs11__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -16736,12 +17073,12 @@ var WorldStateDB = class {
|
|
|
16736
17073
|
constructor(workspacePath, debugMode = false) {
|
|
16737
17074
|
this.workspacePath = workspacePath;
|
|
16738
17075
|
this.debugMode = debugMode;
|
|
16739
|
-
const cascadeDir =
|
|
16740
|
-
if (!
|
|
16741
|
-
|
|
17076
|
+
const cascadeDir = path34__default.default.join(workspacePath, ".cascade");
|
|
17077
|
+
if (!fs31__default.default.existsSync(cascadeDir)) {
|
|
17078
|
+
fs31__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
16742
17079
|
}
|
|
16743
|
-
this.keyPath =
|
|
16744
|
-
this.dbPath =
|
|
17080
|
+
this.keyPath = path34__default.default.join(cascadeDir, "world_state.key");
|
|
17081
|
+
this.dbPath = path34__default.default.join(cascadeDir, "world_state.db");
|
|
16745
17082
|
this.initEncryptionKey();
|
|
16746
17083
|
this.db = new Database2__default.default(this.dbPath);
|
|
16747
17084
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -16798,11 +17135,11 @@ var WorldStateDB = class {
|
|
|
16798
17135
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
16799
17136
|
}
|
|
16800
17137
|
initEncryptionKey() {
|
|
16801
|
-
if (
|
|
16802
|
-
this.encryptionKey =
|
|
17138
|
+
if (fs31__default.default.existsSync(this.keyPath)) {
|
|
17139
|
+
this.encryptionKey = fs31__default.default.readFileSync(this.keyPath);
|
|
16803
17140
|
} else {
|
|
16804
17141
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
16805
|
-
|
|
17142
|
+
fs31__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
16806
17143
|
}
|
|
16807
17144
|
}
|
|
16808
17145
|
encrypt(text) {
|
|
@@ -17088,9 +17425,9 @@ var WorldStateDB = class {
|
|
|
17088
17425
|
dumpDebugIfNeeded() {
|
|
17089
17426
|
if (!this.debugMode) return;
|
|
17090
17427
|
try {
|
|
17091
|
-
const dumpPath =
|
|
17428
|
+
const dumpPath = path34__default.default.join(os10__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
17092
17429
|
const entries = this.getAllEntries();
|
|
17093
|
-
|
|
17430
|
+
fs31__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
17094
17431
|
} catch (err) {
|
|
17095
17432
|
console.error("Failed to dump debug world state", err);
|
|
17096
17433
|
}
|
|
@@ -17099,6 +17436,128 @@ var WorldStateDB = class {
|
|
|
17099
17436
|
this.db.close();
|
|
17100
17437
|
}
|
|
17101
17438
|
};
|
|
17439
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
17440
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
17441
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
17442
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
17443
|
+
function safeId(taskId) {
|
|
17444
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
17445
|
+
return cleaned.slice(0, 100) || "run";
|
|
17446
|
+
}
|
|
17447
|
+
function isCheckpoint(value) {
|
|
17448
|
+
if (typeof value !== "object" || value === null) return false;
|
|
17449
|
+
const c = value;
|
|
17450
|
+
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);
|
|
17451
|
+
}
|
|
17452
|
+
var ResumeStore = class {
|
|
17453
|
+
dir;
|
|
17454
|
+
maxCheckpoints;
|
|
17455
|
+
maxAgeMs;
|
|
17456
|
+
now;
|
|
17457
|
+
constructor(options) {
|
|
17458
|
+
this.dir = options.dir;
|
|
17459
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
17460
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
17461
|
+
this.now = options.now ?? Date.now;
|
|
17462
|
+
}
|
|
17463
|
+
/**
|
|
17464
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
17465
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
17466
|
+
* worse outcome than the one this store exists to improve.
|
|
17467
|
+
*/
|
|
17468
|
+
async save(checkpoint) {
|
|
17469
|
+
try {
|
|
17470
|
+
const timestamp = this.now();
|
|
17471
|
+
const record = {
|
|
17472
|
+
...checkpoint,
|
|
17473
|
+
version: RESUME_SCHEMA_VERSION,
|
|
17474
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
17475
|
+
};
|
|
17476
|
+
await fs31.promises.mkdir(this.dir, { recursive: true });
|
|
17477
|
+
const finalPath = path34__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
17478
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
17479
|
+
await fs31.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
17480
|
+
await fs31.promises.rename(tempPath, finalPath);
|
|
17481
|
+
await this.prune();
|
|
17482
|
+
} catch {
|
|
17483
|
+
}
|
|
17484
|
+
}
|
|
17485
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
17486
|
+
async list() {
|
|
17487
|
+
let entries;
|
|
17488
|
+
try {
|
|
17489
|
+
entries = await fs31.promises.readdir(this.dir);
|
|
17490
|
+
} catch {
|
|
17491
|
+
return [];
|
|
17492
|
+
}
|
|
17493
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
17494
|
+
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);
|
|
17495
|
+
const checkpoints = [];
|
|
17496
|
+
for (const candidate of candidates2) {
|
|
17497
|
+
try {
|
|
17498
|
+
const raw = await fs31.promises.readFile(path34__default.default.join(this.dir, candidate.name), "utf-8");
|
|
17499
|
+
const parsed = JSON.parse(raw);
|
|
17500
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
17501
|
+
} catch {
|
|
17502
|
+
continue;
|
|
17503
|
+
}
|
|
17504
|
+
}
|
|
17505
|
+
return checkpoints;
|
|
17506
|
+
}
|
|
17507
|
+
/** The most recent resumable run, or null. */
|
|
17508
|
+
async latest() {
|
|
17509
|
+
return (await this.list())[0] ?? null;
|
|
17510
|
+
}
|
|
17511
|
+
/**
|
|
17512
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
17513
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
17514
|
+
*/
|
|
17515
|
+
async consume(taskId) {
|
|
17516
|
+
const all = await this.list();
|
|
17517
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
17518
|
+
if (!target) return null;
|
|
17519
|
+
await this.delete(target.taskId);
|
|
17520
|
+
return target;
|
|
17521
|
+
}
|
|
17522
|
+
/** Remove every checkpoint for a task id. */
|
|
17523
|
+
async delete(taskId) {
|
|
17524
|
+
try {
|
|
17525
|
+
const entries = await fs31.promises.readdir(this.dir);
|
|
17526
|
+
const wanted = safeId(taskId);
|
|
17527
|
+
await Promise.all(entries.map(async (name) => {
|
|
17528
|
+
const match = FILE_PATTERN.exec(name);
|
|
17529
|
+
if (match?.[2] === wanted) {
|
|
17530
|
+
await fs31.promises.rm(path34__default.default.join(this.dir, name), { force: true });
|
|
17531
|
+
}
|
|
17532
|
+
}));
|
|
17533
|
+
} catch {
|
|
17534
|
+
}
|
|
17535
|
+
}
|
|
17536
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
17537
|
+
async prune() {
|
|
17538
|
+
try {
|
|
17539
|
+
const entries = await fs31.promises.readdir(this.dir);
|
|
17540
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
17541
|
+
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);
|
|
17542
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
17543
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
17544
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
17545
|
+
(name) => fs31.promises.rm(path34__default.default.join(this.dir, name), { force: true })
|
|
17546
|
+
));
|
|
17547
|
+
} catch {
|
|
17548
|
+
}
|
|
17549
|
+
}
|
|
17550
|
+
};
|
|
17551
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
17552
|
+
if (!completed.length) return "";
|
|
17553
|
+
const lines = completed.map((node) => {
|
|
17554
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
17555
|
+
return `- [${node.status}] ${node.title}
|
|
17556
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
17557
|
+
});
|
|
17558
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
17559
|
+
${lines.join("\n")}`;
|
|
17560
|
+
}
|
|
17102
17561
|
var ignore3 = _ignoreModule__namespace.default ?? _ignoreModule__namespace;
|
|
17103
17562
|
var PrivacyPaths = class {
|
|
17104
17563
|
localOnly;
|
|
@@ -17460,7 +17919,7 @@ var WorkspaceIndex = class {
|
|
|
17460
17919
|
isIgnored;
|
|
17461
17920
|
chunker;
|
|
17462
17921
|
constructor(opts) {
|
|
17463
|
-
this.root =
|
|
17922
|
+
this.root = path34__default.default.resolve(opts.root);
|
|
17464
17923
|
this.db = opts.db;
|
|
17465
17924
|
this.namespace = opts.namespace ?? "code";
|
|
17466
17925
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -17492,14 +17951,14 @@ var WorkspaceIndex = class {
|
|
|
17492
17951
|
return;
|
|
17493
17952
|
}
|
|
17494
17953
|
for (const entry of entries) {
|
|
17495
|
-
const abs =
|
|
17954
|
+
const abs = path34__default.default.join(dir, entry.name);
|
|
17496
17955
|
if (entry.isDirectory()) {
|
|
17497
17956
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
17498
17957
|
await walk(abs);
|
|
17499
17958
|
} else if (entry.isFile()) {
|
|
17500
|
-
const ext =
|
|
17959
|
+
const ext = path34__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
17501
17960
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
17502
|
-
out.push(
|
|
17961
|
+
out.push(path34__default.default.relative(this.root, abs));
|
|
17503
17962
|
}
|
|
17504
17963
|
}
|
|
17505
17964
|
};
|
|
@@ -17529,7 +17988,7 @@ var WorkspaceIndex = class {
|
|
|
17529
17988
|
const entries = [];
|
|
17530
17989
|
for (const rel of rels) {
|
|
17531
17990
|
try {
|
|
17532
|
-
const abs =
|
|
17991
|
+
const abs = path34__default.default.join(this.root, rel);
|
|
17533
17992
|
const stat = await fs11__default.default.stat(abs);
|
|
17534
17993
|
if (stat.size > this.maxFileBytes) continue;
|
|
17535
17994
|
const buf = await fs11__default.default.readFile(abs);
|
|
@@ -18067,8 +18526,16 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
18067
18526
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
18068
18527
|
decisionLog = [];
|
|
18069
18528
|
initialized = false;
|
|
18070
|
-
/**
|
|
18529
|
+
/**
|
|
18530
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
18531
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
18532
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
18533
|
+
*/
|
|
18071
18534
|
lastInterruptedRun;
|
|
18535
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
18536
|
+
resumeStore;
|
|
18537
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
18538
|
+
currentRunCompleted = [];
|
|
18072
18539
|
initPromise;
|
|
18073
18540
|
store;
|
|
18074
18541
|
audit;
|
|
@@ -18101,10 +18568,16 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
18101
18568
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
18102
18569
|
try {
|
|
18103
18570
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
18104
|
-
fileDeadModelPersistence(
|
|
18571
|
+
fileDeadModelPersistence(path34__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
18105
18572
|
));
|
|
18106
18573
|
} catch {
|
|
18107
18574
|
}
|
|
18575
|
+
try {
|
|
18576
|
+
this.resumeStore = new ResumeStore({
|
|
18577
|
+
dir: path34__default.default.join(workspacePath, ".cascade", "resume")
|
|
18578
|
+
});
|
|
18579
|
+
} catch {
|
|
18580
|
+
}
|
|
18108
18581
|
this.multimodal = new MultimodalRegistry(
|
|
18109
18582
|
(this.config.providers ?? []).map((p) => p.type)
|
|
18110
18583
|
);
|
|
@@ -18189,8 +18662,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
18189
18662
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
18190
18663
|
return;
|
|
18191
18664
|
}
|
|
18192
|
-
const dbPath = ci.dbPath ||
|
|
18193
|
-
|
|
18665
|
+
const dbPath = ci.dbPath || path34__default.default.join(this.workspacePath, ".cascade", "code-index.db");
|
|
18666
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(dbPath), { recursive: true });
|
|
18194
18667
|
const db = new Database2__default.default(dbPath);
|
|
18195
18668
|
db.pragma("journal_mode = WAL");
|
|
18196
18669
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -18570,10 +19043,43 @@ ${input}` }],
|
|
|
18570
19043
|
setFeedbackSource(source) {
|
|
18571
19044
|
this.router.setFeedbackSource(source);
|
|
18572
19045
|
}
|
|
18573
|
-
/**
|
|
19046
|
+
/**
|
|
19047
|
+
* Write a durable checkpoint for an interrupted run.
|
|
19048
|
+
*
|
|
19049
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
19050
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
19051
|
+
* worse. Nothing here is allowed to throw.
|
|
19052
|
+
*/
|
|
19053
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
19054
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
19055
|
+
try {
|
|
19056
|
+
await this.resumeStore?.save({
|
|
19057
|
+
taskId,
|
|
19058
|
+
prompt,
|
|
19059
|
+
reason,
|
|
19060
|
+
...detail ? { detail } : {},
|
|
19061
|
+
partialOutput,
|
|
19062
|
+
completed: [...this.currentRunCompleted]
|
|
19063
|
+
});
|
|
19064
|
+
} catch {
|
|
19065
|
+
}
|
|
19066
|
+
}
|
|
19067
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
18574
19068
|
hasResumableRun() {
|
|
18575
19069
|
return this.lastInterruptedRun != null;
|
|
18576
19070
|
}
|
|
19071
|
+
/**
|
|
19072
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
19073
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
19074
|
+
* answer for callers already on a hot path.
|
|
19075
|
+
*/
|
|
19076
|
+
async hasDurableResume() {
|
|
19077
|
+
return await this.resumeStore?.latest() != null;
|
|
19078
|
+
}
|
|
19079
|
+
/** Interrupted runs available to resume, newest first. */
|
|
19080
|
+
async listResumableRuns() {
|
|
19081
|
+
return await this.resumeStore?.list() ?? [];
|
|
19082
|
+
}
|
|
18577
19083
|
/**
|
|
18578
19084
|
* Raise the per-run token budget for a resume and return the continuation
|
|
18579
19085
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -18584,22 +19090,54 @@ ${input}` }],
|
|
|
18584
19090
|
const last = this.lastInterruptedRun;
|
|
18585
19091
|
if (!last) return null;
|
|
18586
19092
|
this.lastInterruptedRun = void 0;
|
|
18587
|
-
|
|
19093
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
19094
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
19095
|
+
}
|
|
19096
|
+
/**
|
|
19097
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
19098
|
+
* process, including one that crashed before it could return. Consumes the
|
|
19099
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
19100
|
+
*/
|
|
19101
|
+
async prepareDurableResume(opts = {}) {
|
|
19102
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
19103
|
+
if (!checkpoint) return null;
|
|
19104
|
+
this.lastInterruptedRun = void 0;
|
|
19105
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
19106
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
19107
|
+
}
|
|
19108
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
19109
|
+
raiseBudgetForResume(maxTokens) {
|
|
19110
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
18588
19111
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
18589
19112
|
this.router.setMaxTokensPerRun(raised);
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
19113
|
+
}
|
|
19114
|
+
/**
|
|
19115
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
19116
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
19117
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
19118
|
+
* is something it can act on. That difference is the entire reason node
|
|
19119
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
19120
|
+
* finished work again, and the user pays for it a second time.
|
|
19121
|
+
*/
|
|
19122
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
19123
|
+
const done = summarizeCompleted(completed);
|
|
19124
|
+
return [
|
|
19125
|
+
"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.",
|
|
19126
|
+
"",
|
|
19127
|
+
`Original task: ${prompt}`,
|
|
19128
|
+
done ? `
|
|
19129
|
+
${done}` : "",
|
|
19130
|
+
partialOutput ? `
|
|
18594
19131
|
Partial result so far:
|
|
18595
|
-
${
|
|
19132
|
+
${partialOutput}` : ""
|
|
19133
|
+
].filter(Boolean).join("\n");
|
|
18596
19134
|
}
|
|
18597
19135
|
/**
|
|
18598
19136
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
18599
19137
|
* Returns null when there is nothing to resume.
|
|
18600
19138
|
*/
|
|
18601
19139
|
async resumeRun(opts = {}) {
|
|
18602
|
-
const prompt = this.prepareResume(opts);
|
|
19140
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
18603
19141
|
if (!prompt) return null;
|
|
18604
19142
|
return this.run({ prompt });
|
|
18605
19143
|
}
|
|
@@ -18970,6 +19508,7 @@ ${prompt}` : prompt;
|
|
|
18970
19508
|
const startMs = Date.now();
|
|
18971
19509
|
const taskId = crypto.randomUUID();
|
|
18972
19510
|
this.decisionLog = [];
|
|
19511
|
+
this.currentRunCompleted = [];
|
|
18973
19512
|
if (options.fastAnswer) {
|
|
18974
19513
|
return this.runFastAnswer(options, startMs, taskId);
|
|
18975
19514
|
}
|
|
@@ -19084,6 +19623,11 @@ ${prompt}` : prompt;
|
|
|
19084
19623
|
});
|
|
19085
19624
|
tier.on("log", (e) => this.emit("log", e));
|
|
19086
19625
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
19626
|
+
tier.on("section:complete", (e) => {
|
|
19627
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
19628
|
+
this.currentRunCompleted.push(e);
|
|
19629
|
+
this.emit("section:complete", e);
|
|
19630
|
+
});
|
|
19087
19631
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
19088
19632
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
19089
19633
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -19230,6 +19774,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
19230
19774
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
19231
19775
|
const trip = runBreaker.reason();
|
|
19232
19776
|
if (trip) {
|
|
19777
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
19233
19778
|
finalOutput = [
|
|
19234
19779
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
19235
19780
|
"",
|
|
@@ -19249,6 +19794,13 @@ ${finalOutput}` : ""
|
|
|
19249
19794
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
19250
19795
|
partialOutput: finalOutput || ""
|
|
19251
19796
|
});
|
|
19797
|
+
await this.checkpointRun(
|
|
19798
|
+
taskId,
|
|
19799
|
+
options.prompt,
|
|
19800
|
+
finalOutput || "",
|
|
19801
|
+
"cancelled",
|
|
19802
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
19803
|
+
);
|
|
19252
19804
|
runError = null;
|
|
19253
19805
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
19254
19806
|
this.emit("run:budget-exceeded", {
|
|
@@ -19257,9 +19809,17 @@ ${finalOutput}` : ""
|
|
|
19257
19809
|
partialOutput: finalOutput || ""
|
|
19258
19810
|
});
|
|
19259
19811
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
19812
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
19260
19813
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
19261
19814
|
runError = null;
|
|
19262
19815
|
} else {
|
|
19816
|
+
await this.checkpointRun(
|
|
19817
|
+
taskId,
|
|
19818
|
+
options.prompt,
|
|
19819
|
+
finalOutput || "",
|
|
19820
|
+
"error",
|
|
19821
|
+
err instanceof Error ? err.message : String(err)
|
|
19822
|
+
);
|
|
19263
19823
|
runError = err;
|
|
19264
19824
|
throw err;
|
|
19265
19825
|
}
|
|
@@ -19471,14 +20031,14 @@ ${finalOutput}` : ""
|
|
|
19471
20031
|
init_constants();
|
|
19472
20032
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
19473
20033
|
function globalDir(homeDir = os10__default.default.homedir()) {
|
|
19474
|
-
return
|
|
20034
|
+
return path34__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
19475
20035
|
}
|
|
19476
20036
|
function cloudSessionPath(dir = globalDir()) {
|
|
19477
|
-
return
|
|
20037
|
+
return path34__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
19478
20038
|
}
|
|
19479
20039
|
function loadCloudSession(dir = globalDir()) {
|
|
19480
20040
|
try {
|
|
19481
|
-
const raw =
|
|
20041
|
+
const raw = fs31__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
19482
20042
|
const parsed = JSON.parse(raw);
|
|
19483
20043
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
19484
20044
|
return parsed;
|
|
@@ -19487,17 +20047,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
19487
20047
|
}
|
|
19488
20048
|
}
|
|
19489
20049
|
function saveCloudSession(session, dir = globalDir()) {
|
|
19490
|
-
|
|
20050
|
+
fs31__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
19491
20051
|
const file = cloudSessionPath(dir);
|
|
19492
|
-
|
|
20052
|
+
fs31__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
19493
20053
|
try {
|
|
19494
|
-
|
|
20054
|
+
fs31__default.default.chmodSync(file, 384);
|
|
19495
20055
|
} catch {
|
|
19496
20056
|
}
|
|
19497
20057
|
}
|
|
19498
20058
|
function clearCloudSession(dir = globalDir()) {
|
|
19499
20059
|
try {
|
|
19500
|
-
|
|
20060
|
+
fs31__default.default.rmSync(cloudSessionPath(dir));
|
|
19501
20061
|
} catch {
|
|
19502
20062
|
}
|
|
19503
20063
|
}
|
|
@@ -19669,16 +20229,16 @@ var CloudClient = class _CloudClient {
|
|
|
19669
20229
|
return (await this.refresh()).accessToken;
|
|
19670
20230
|
}
|
|
19671
20231
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
19672
|
-
async authed(
|
|
19673
|
-
const call = (token) => fetch(this.url(
|
|
20232
|
+
async authed(path39, init = {}) {
|
|
20233
|
+
const call = (token) => fetch(this.url(path39), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
19674
20234
|
let res = await call(await this.accessToken());
|
|
19675
20235
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
19676
20236
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
19677
20237
|
const text = await res.text();
|
|
19678
20238
|
return text ? JSON.parse(text) : {};
|
|
19679
20239
|
}
|
|
19680
|
-
authedGet(
|
|
19681
|
-
return this.authed(
|
|
20240
|
+
authedGet(path39) {
|
|
20241
|
+
return this.authed(path39);
|
|
19682
20242
|
}
|
|
19683
20243
|
async fetchMe(token) {
|
|
19684
20244
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -21453,9 +22013,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
21453
22013
|
}
|
|
21454
22014
|
} catch {
|
|
21455
22015
|
}
|
|
21456
|
-
const configPath =
|
|
22016
|
+
const configPath = path34__default.default.join(workspacePath, ".cascade", "config.json");
|
|
21457
22017
|
try {
|
|
21458
|
-
await fs11__default.default.mkdir(
|
|
22018
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(configPath), { recursive: true });
|
|
21459
22019
|
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
21460
22020
|
} catch (err) {
|
|
21461
22021
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -21507,9 +22067,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
21507
22067
|
if (msg.includes("non-text parts")) return;
|
|
21508
22068
|
originalLog(...args);
|
|
21509
22069
|
};
|
|
21510
|
-
const store = new MemoryStore(
|
|
22070
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
21511
22071
|
storeRef.current = store;
|
|
21512
|
-
globalStoreRef.current = new MemoryStore(
|
|
22072
|
+
globalStoreRef.current = new MemoryStore(path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
21513
22073
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
21514
22074
|
setIdentities(identityRows);
|
|
21515
22075
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -21577,7 +22137,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
21577
22137
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
21578
22138
|
onExport: async (fmt) => {
|
|
21579
22139
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
21580
|
-
const exportPath =
|
|
22140
|
+
const exportPath = path34__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
|
|
21581
22141
|
if (fmt === "json") {
|
|
21582
22142
|
await fs11__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
21583
22143
|
} else {
|
|
@@ -21819,10 +22379,12 @@ ${lastUser.content}`;
|
|
|
21819
22379
|
onContinue: async (args) => {
|
|
21820
22380
|
const cascade = cascadeRef.current;
|
|
21821
22381
|
if (!cascade) return "Not ready yet.";
|
|
21822
|
-
if (!cascade.hasResumableRun()) return "Nothing to continue \u2014 no task was stopped by the budget cap.";
|
|
21823
22382
|
const n = args[0] ? parseInt(args[0].replace(/[^0-9]/g, ""), 10) : NaN;
|
|
21824
|
-
const
|
|
21825
|
-
|
|
22383
|
+
const opts = Number.isFinite(n) && n > 0 ? { maxTokens: n } : {};
|
|
22384
|
+
const prompt = await cascade.prepareDurableResume(opts) ?? cascade.prepareResume(opts);
|
|
22385
|
+
if (!prompt) {
|
|
22386
|
+
return "Nothing to continue \u2014 no interrupted task was found for this workspace.";
|
|
22387
|
+
}
|
|
21826
22388
|
await handleSubmit(prompt);
|
|
21827
22389
|
return "";
|
|
21828
22390
|
}
|
|
@@ -22475,7 +23037,7 @@ function stringifySlashOutput(val) {
|
|
|
22475
23037
|
}
|
|
22476
23038
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
22477
23039
|
if (!query) return "Usage: /search <query>";
|
|
22478
|
-
const dbPath =
|
|
23040
|
+
const dbPath = path34__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
22479
23041
|
try {
|
|
22480
23042
|
await fs11__default.default.access(dbPath);
|
|
22481
23043
|
} catch {
|
|
@@ -22509,7 +23071,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
22509
23071
|
async function diagnoseRuntime(config, workspacePath) {
|
|
22510
23072
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
22511
23073
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
22512
|
-
const store = new MemoryStore(
|
|
23074
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
22513
23075
|
try {
|
|
22514
23076
|
const sessions2 = store.listSessions(void 0, 3);
|
|
22515
23077
|
return [
|
|
@@ -22528,7 +23090,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
22528
23090
|
}
|
|
22529
23091
|
async function showRecentLogs(args, workspacePath) {
|
|
22530
23092
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
22531
|
-
const store = new MemoryStore(
|
|
23093
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
22532
23094
|
try {
|
|
22533
23095
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
22534
23096
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -22540,7 +23102,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
22540
23102
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
22541
23103
|
const sessionId = args[0];
|
|
22542
23104
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
22543
|
-
const store = new MemoryStore(
|
|
23105
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
22544
23106
|
try {
|
|
22545
23107
|
const session = store.getSession(sessionId);
|
|
22546
23108
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -22968,9 +23530,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
22968
23530
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
22969
23531
|
};
|
|
22970
23532
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
22971
|
-
const configDir =
|
|
23533
|
+
const configDir = path34__default.default.join(workspacePath, ".cascade");
|
|
22972
23534
|
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
22973
|
-
const configPath =
|
|
23535
|
+
const configPath = path34__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
22974
23536
|
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
22975
23537
|
savedConfigRef.current = config;
|
|
22976
23538
|
dispatchRef.current({ type: "GO_DONE" });
|
|
@@ -23312,14 +23874,14 @@ function printTelemetryBanner() {
|
|
|
23312
23874
|
async function initCommand(workspacePath = process.cwd()) {
|
|
23313
23875
|
const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
23314
23876
|
try {
|
|
23315
|
-
const configDir =
|
|
23877
|
+
const configDir = path34__default.default.join(workspacePath, ".cascade");
|
|
23316
23878
|
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
23317
|
-
const mdPath =
|
|
23879
|
+
const mdPath = path34__default.default.join(workspacePath, "CASCADE.md");
|
|
23318
23880
|
if (!await fileExists(mdPath)) {
|
|
23319
23881
|
await createDefaultCascadeMd(workspacePath);
|
|
23320
23882
|
spin.succeed(chalk7__default.default.green("Created CASCADE.md"));
|
|
23321
23883
|
}
|
|
23322
|
-
const ignorePath =
|
|
23884
|
+
const ignorePath = path34__default.default.join(workspacePath, ".cascadeignore");
|
|
23323
23885
|
if (!await fileExists(ignorePath)) {
|
|
23324
23886
|
await createDefaultIgnoreFile(workspacePath);
|
|
23325
23887
|
spin.succeed(chalk7__default.default.green("Created .cascadeignore"));
|
|
@@ -23328,7 +23890,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
23328
23890
|
console.log();
|
|
23329
23891
|
console.log(chalk7__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
23330
23892
|
console.log();
|
|
23331
|
-
const configPath =
|
|
23893
|
+
const configPath = path34__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
23332
23894
|
if (await fileExists(configPath)) {
|
|
23333
23895
|
console.log(chalk7__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
23334
23896
|
console.log();
|
|
@@ -23386,7 +23948,7 @@ function fromEnv(env) {
|
|
|
23386
23948
|
return out;
|
|
23387
23949
|
}
|
|
23388
23950
|
async function fromClaudeCode(home) {
|
|
23389
|
-
const file =
|
|
23951
|
+
const file = path34__default.default.join(home, ".claude", ".credentials.json");
|
|
23390
23952
|
const data = await readJson(file);
|
|
23391
23953
|
if (!data) return [];
|
|
23392
23954
|
const oauth = data["claudeAiOauth"];
|
|
@@ -23409,7 +23971,7 @@ async function fromClaudeCode(home) {
|
|
|
23409
23971
|
return [];
|
|
23410
23972
|
}
|
|
23411
23973
|
async function fromCodex(home) {
|
|
23412
|
-
const file =
|
|
23974
|
+
const file = path34__default.default.join(home, ".codex", "auth.json");
|
|
23413
23975
|
const data = await readJson(file);
|
|
23414
23976
|
if (!data) return [];
|
|
23415
23977
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -23432,7 +23994,7 @@ async function fromCodex(home) {
|
|
|
23432
23994
|
return [];
|
|
23433
23995
|
}
|
|
23434
23996
|
async function fromGemini(home) {
|
|
23435
|
-
const file =
|
|
23997
|
+
const file = path34__default.default.join(home, ".gemini", "oauth_creds.json");
|
|
23436
23998
|
const data = await readJson(file);
|
|
23437
23999
|
const accessToken = str(data?.["access_token"]);
|
|
23438
24000
|
if (!accessToken) return [];
|
|
@@ -23448,7 +24010,7 @@ async function fromGemini(home) {
|
|
|
23448
24010
|
}
|
|
23449
24011
|
async function fromCopilot(home) {
|
|
23450
24012
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
23451
|
-
const file =
|
|
24013
|
+
const file = path34__default.default.join(home, ".config", "github-copilot", name);
|
|
23452
24014
|
const data = await readJson(file);
|
|
23453
24015
|
if (!data) continue;
|
|
23454
24016
|
for (const value of Object.values(data)) {
|
|
@@ -23502,7 +24064,7 @@ async function doctorCommand() {
|
|
|
23502
24064
|
checks.push({
|
|
23503
24065
|
label: "Cascade config",
|
|
23504
24066
|
ok: true,
|
|
23505
|
-
detail: `Loaded ${
|
|
24067
|
+
detail: `Loaded ${path34__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
23506
24068
|
});
|
|
23507
24069
|
const providers = [
|
|
23508
24070
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -23585,17 +24147,17 @@ async function doctorCommand() {
|
|
|
23585
24147
|
const detail = c.detail ? chalk7__default.default.gray(` \u2014 ${c.detail}`) : "";
|
|
23586
24148
|
console.log(`${icon} ${label}${detail}`);
|
|
23587
24149
|
}
|
|
23588
|
-
const
|
|
24150
|
+
const failures2 = checks.filter((c) => !c.ok);
|
|
23589
24151
|
console.log();
|
|
23590
|
-
if (
|
|
24152
|
+
if (failures2.length === 0) {
|
|
23591
24153
|
console.log(chalk7__default.default.green(" All checks passed!\n"));
|
|
23592
24154
|
} else {
|
|
23593
|
-
const critical =
|
|
24155
|
+
const critical = failures2.filter((c) => c.label.includes("Node") || c.label.includes("API key"));
|
|
23594
24156
|
if (critical.length) {
|
|
23595
24157
|
console.log(chalk7__default.default.yellow(` ${critical.length} issue(s) need attention.
|
|
23596
24158
|
`));
|
|
23597
24159
|
} else {
|
|
23598
|
-
console.log(chalk7__default.default.gray(` ${
|
|
24160
|
+
console.log(chalk7__default.default.gray(` ${failures2.length} optional item(s) not configured.
|
|
23599
24161
|
`));
|
|
23600
24162
|
}
|
|
23601
24163
|
}
|
|
@@ -23613,7 +24175,7 @@ async function checkEndpoint(url) {
|
|
|
23613
24175
|
}
|
|
23614
24176
|
}
|
|
23615
24177
|
async function indexCommand(dirPath) {
|
|
23616
|
-
const workspace =
|
|
24178
|
+
const workspace = path34__default.default.resolve(dirPath || process.cwd());
|
|
23617
24179
|
const cm = new ConfigManager(workspace);
|
|
23618
24180
|
await cm.load();
|
|
23619
24181
|
const config = cm.getConfig();
|
|
@@ -23624,8 +24186,8 @@ async function indexCommand(dirPath) {
|
|
|
23624
24186
|
process.exitCode = 1;
|
|
23625
24187
|
return;
|
|
23626
24188
|
}
|
|
23627
|
-
const dbPath = config.codeIndex?.dbPath ||
|
|
23628
|
-
|
|
24189
|
+
const dbPath = config.codeIndex?.dbPath || path34__default.default.join(workspace, ".cascade", "code-index.db");
|
|
24190
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(dbPath), { recursive: true });
|
|
23629
24191
|
const db = new Database2__default.default(dbPath);
|
|
23630
24192
|
db.pragma("journal_mode = WAL");
|
|
23631
24193
|
const complete = chatCompleterFromProviders(config.providers);
|
|
@@ -24114,7 +24676,7 @@ function openBrowser2(url) {
|
|
|
24114
24676
|
}
|
|
24115
24677
|
function storePathFor(name) {
|
|
24116
24678
|
const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
|
|
24117
|
-
return
|
|
24679
|
+
return path34__default.default.join(os10__default.default.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
|
|
24118
24680
|
}
|
|
24119
24681
|
function defaultName(url) {
|
|
24120
24682
|
try {
|
|
@@ -24616,7 +25178,7 @@ function aggregateCostStats(sessions2, opts = {}) {
|
|
|
24616
25178
|
}
|
|
24617
25179
|
|
|
24618
25180
|
// src/dashboard/server.ts
|
|
24619
|
-
var __dirname$1 =
|
|
25181
|
+
var __dirname$1 = path34__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
|
|
24620
25182
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
24621
25183
|
var DashboardServer = class {
|
|
24622
25184
|
app;
|
|
@@ -24896,14 +25458,14 @@ var DashboardServer = class {
|
|
|
24896
25458
|
*/
|
|
24897
25459
|
persistConfig() {
|
|
24898
25460
|
try {
|
|
24899
|
-
const configPath =
|
|
24900
|
-
|
|
24901
|
-
|
|
25461
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
25462
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(configPath), { recursive: true });
|
|
25463
|
+
fs31__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
24902
25464
|
} catch (err) {
|
|
24903
25465
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
24904
25466
|
}
|
|
24905
25467
|
try {
|
|
24906
|
-
saveGlobalCredentials(
|
|
25468
|
+
saveGlobalCredentials(path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
24907
25469
|
} catch (err) {
|
|
24908
25470
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
24909
25471
|
}
|
|
@@ -24918,15 +25480,15 @@ var DashboardServer = class {
|
|
|
24918
25480
|
resolveDashboardSecret() {
|
|
24919
25481
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
24920
25482
|
if (fromConfig) return fromConfig;
|
|
24921
|
-
const secretPath =
|
|
25483
|
+
const secretPath = path34__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
24922
25484
|
try {
|
|
24923
|
-
if (
|
|
24924
|
-
const existing =
|
|
25485
|
+
if (fs31__default.default.existsSync(secretPath)) {
|
|
25486
|
+
const existing = fs31__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
24925
25487
|
if (existing.length >= 16) return existing;
|
|
24926
25488
|
}
|
|
24927
25489
|
const generated = crypto.randomUUID();
|
|
24928
|
-
|
|
24929
|
-
|
|
25490
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(secretPath), { recursive: true });
|
|
25491
|
+
fs31__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
24930
25492
|
if (this.config.dashboard.auth) {
|
|
24931
25493
|
console.warn(
|
|
24932
25494
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -24953,7 +25515,7 @@ var DashboardServer = class {
|
|
|
24953
25515
|
// ── Setup ─────────────────────────────────────
|
|
24954
25516
|
getGlobalStore() {
|
|
24955
25517
|
if (!this.globalStore) {
|
|
24956
|
-
const globalDbPath =
|
|
25518
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24957
25519
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
24958
25520
|
}
|
|
24959
25521
|
return this.globalStore;
|
|
@@ -25333,12 +25895,12 @@ ${prompt}`;
|
|
|
25333
25895
|
}
|
|
25334
25896
|
}
|
|
25335
25897
|
watchRuntimeChanges() {
|
|
25336
|
-
const workspaceDbPath =
|
|
25337
|
-
const globalDbPath =
|
|
25898
|
+
const workspaceDbPath = path34__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
25899
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25338
25900
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
25339
25901
|
for (const watchPath of watchPaths) {
|
|
25340
|
-
if (!
|
|
25341
|
-
|
|
25902
|
+
if (!fs31__default.default.existsSync(watchPath)) continue;
|
|
25903
|
+
fs31__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
25342
25904
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
25343
25905
|
});
|
|
25344
25906
|
}
|
|
@@ -25460,7 +26022,7 @@ ${prompt}`;
|
|
|
25460
26022
|
const sessionId = req.params.id;
|
|
25461
26023
|
this.store.deleteSession(sessionId);
|
|
25462
26024
|
this.store.deleteRuntimeSession(sessionId);
|
|
25463
|
-
const globalDbPath =
|
|
26025
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25464
26026
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25465
26027
|
try {
|
|
25466
26028
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -25553,7 +26115,7 @@ ${prompt}`;
|
|
|
25553
26115
|
});
|
|
25554
26116
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
25555
26117
|
const body = req.body;
|
|
25556
|
-
const globalDbPath =
|
|
26118
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25557
26119
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
25558
26120
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25559
26121
|
try {
|
|
@@ -25576,7 +26138,7 @@ ${prompt}`;
|
|
|
25576
26138
|
});
|
|
25577
26139
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
25578
26140
|
this.store.deleteAllRuntimeNodes();
|
|
25579
|
-
const globalDbPath =
|
|
26141
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25580
26142
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25581
26143
|
try {
|
|
25582
26144
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -25662,12 +26224,12 @@ ${prompt}`;
|
|
|
25662
26224
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
25663
26225
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
25664
26226
|
try {
|
|
25665
|
-
const configPath =
|
|
25666
|
-
const existing =
|
|
26227
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
26228
|
+
const existing = fs31__default.default.existsSync(configPath) ? JSON.parse(fs31__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
25667
26229
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
25668
26230
|
const tmp = configPath + ".tmp";
|
|
25669
|
-
|
|
25670
|
-
|
|
26231
|
+
fs31__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
26232
|
+
fs31__default.default.renameSync(tmp, configPath);
|
|
25671
26233
|
res.json({ ok: true });
|
|
25672
26234
|
} catch (err) {
|
|
25673
26235
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -25695,7 +26257,7 @@ ${prompt}`;
|
|
|
25695
26257
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
25696
26258
|
const scope = req.query["scope"] ?? "workspace";
|
|
25697
26259
|
if (scope === "global") {
|
|
25698
|
-
const globalDbPath =
|
|
26260
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
25699
26261
|
const globalStore = new MemoryStore(globalDbPath);
|
|
25700
26262
|
try {
|
|
25701
26263
|
res.json({
|
|
@@ -26036,13 +26598,13 @@ ${prompt}`;
|
|
|
26036
26598
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
26037
26599
|
}
|
|
26038
26600
|
});
|
|
26039
|
-
const prodPath =
|
|
26040
|
-
const devPath =
|
|
26041
|
-
const webDistPath =
|
|
26042
|
-
if (
|
|
26601
|
+
const prodPath = path34__default.default.resolve(__dirname$1, "../web/dist");
|
|
26602
|
+
const devPath = path34__default.default.resolve(__dirname$1, "../../web/dist");
|
|
26603
|
+
const webDistPath = fs31__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
26604
|
+
if (fs31__default.default.existsSync(webDistPath)) {
|
|
26043
26605
|
this.app.use(express__default.default.static(webDistPath));
|
|
26044
26606
|
this.app.get("*", (_req, res) => {
|
|
26045
|
-
res.sendFile(
|
|
26607
|
+
res.sendFile(path34__default.default.join(webDistPath, "index.html"));
|
|
26046
26608
|
});
|
|
26047
26609
|
} else {
|
|
26048
26610
|
this.app.get("/", (_req, res) => {
|
|
@@ -26063,7 +26625,7 @@ init_constants();
|
|
|
26063
26625
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
26064
26626
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
26065
26627
|
const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
26066
|
-
const store = new MemoryStore(
|
|
26628
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
26067
26629
|
const server = new DashboardServer(config, store, workspacePath);
|
|
26068
26630
|
server.watchRuntimeChanges();
|
|
26069
26631
|
const gThis = globalThis;
|
|
@@ -26095,7 +26657,7 @@ init_constants();
|
|
|
26095
26657
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
26096
26658
|
const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
|
|
26097
26659
|
identity.command("list").description("List all available identities").action(() => {
|
|
26098
|
-
const store = new MemoryStore(
|
|
26660
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
26099
26661
|
try {
|
|
26100
26662
|
const identities = store.listIdentities();
|
|
26101
26663
|
console.log(chalk7__default.default.bold("\n Identities:"));
|
|
@@ -26115,7 +26677,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
26115
26677
|
}
|
|
26116
26678
|
});
|
|
26117
26679
|
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) => {
|
|
26118
|
-
const store = new MemoryStore(
|
|
26680
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
26119
26681
|
try {
|
|
26120
26682
|
if (options.default) {
|
|
26121
26683
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -26141,7 +26703,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
26141
26703
|
}
|
|
26142
26704
|
});
|
|
26143
26705
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
26144
|
-
const store = new MemoryStore(
|
|
26706
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
26145
26707
|
try {
|
|
26146
26708
|
const identities = store.listIdentities();
|
|
26147
26709
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
@@ -26290,8 +26852,8 @@ async function exportCommand(options = {}) {
|
|
|
26290
26852
|
let store;
|
|
26291
26853
|
try {
|
|
26292
26854
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
26293
|
-
const workspaceDbPath =
|
|
26294
|
-
const globalDbPath =
|
|
26855
|
+
const workspaceDbPath = path34__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
26856
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
26295
26857
|
let dbPath = globalDbPath;
|
|
26296
26858
|
try {
|
|
26297
26859
|
await fs11__default.default.access(workspaceDbPath);
|
|
@@ -26333,7 +26895,7 @@ async function exportCommand(options = {}) {
|
|
|
26333
26895
|
const ext = format === "json" ? ".json" : ".md";
|
|
26334
26896
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
26335
26897
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
26336
|
-
const outPath = options.output ?
|
|
26898
|
+
const outPath = options.output ? path34__default.default.resolve(options.output) : path34__default.default.join(process.cwd(), defaultFile);
|
|
26337
26899
|
await fs11__default.default.writeFile(outPath, content, "utf-8");
|
|
26338
26900
|
spin.succeed(chalk7__default.default.green(`Exported to ${chalk7__default.default.white(outPath)}`));
|
|
26339
26901
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
@@ -26561,11 +27123,11 @@ async function statsCommand() {
|
|
|
26561
27123
|
dotenv__default.default.config();
|
|
26562
27124
|
function warnIfBuildIsStale() {
|
|
26563
27125
|
try {
|
|
26564
|
-
const here =
|
|
27126
|
+
const here = path34__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
|
|
26565
27127
|
for (const rel of ["..", "../.."]) {
|
|
26566
|
-
const pkgPath =
|
|
26567
|
-
if (!
|
|
26568
|
-
const pkg = JSON.parse(
|
|
27128
|
+
const pkgPath = path34__default.default.join(here, rel, "package.json");
|
|
27129
|
+
if (!fs31__default.default.existsSync(pkgPath)) continue;
|
|
27130
|
+
const pkg = JSON.parse(fs31__default.default.readFileSync(pkgPath, "utf-8"));
|
|
26569
27131
|
if (pkg.name !== "cascade-ai") continue;
|
|
26570
27132
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
26571
27133
|
console.error(
|