cascade-ai 0.16.0 → 0.18.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 +746 -188
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +742 -185
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +1316 -824
- package/dist/index.cjs +669 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +667 -176
- 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 path26 = require('path');
|
|
13
|
+
var fs24 = require('fs');
|
|
14
14
|
var os8 = require('os');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var ink = require('ink');
|
|
@@ -19,7 +19,7 @@ var React2 = require('react');
|
|
|
19
19
|
var chalk9 = require('chalk');
|
|
20
20
|
var dotenv = require('dotenv');
|
|
21
21
|
var url = require('url');
|
|
22
|
-
var
|
|
22
|
+
var fs10 = require('fs/promises');
|
|
23
23
|
var _ignoreModule = require('ignore');
|
|
24
24
|
var zod = require('zod');
|
|
25
25
|
var child_process = require('child_process');
|
|
@@ -43,6 +43,7 @@ var bcrypt = require('bcryptjs');
|
|
|
43
43
|
var socket_io = require('socket.io');
|
|
44
44
|
var parser = require('socket.io-msgpack-parser');
|
|
45
45
|
var jwt = require('jsonwebtoken');
|
|
46
|
+
var cron = require('node-cron');
|
|
46
47
|
|
|
47
48
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
48
49
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -72,14 +73,14 @@ var http__default = /*#__PURE__*/_interopDefault(http);
|
|
|
72
73
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
73
74
|
var zlib__default = /*#__PURE__*/_interopDefault(zlib);
|
|
74
75
|
var Database2__default = /*#__PURE__*/_interopDefault(Database2);
|
|
75
|
-
var
|
|
76
|
-
var
|
|
76
|
+
var path26__default = /*#__PURE__*/_interopDefault(path26);
|
|
77
|
+
var fs24__default = /*#__PURE__*/_interopDefault(fs24);
|
|
77
78
|
var os8__default = /*#__PURE__*/_interopDefault(os8);
|
|
78
79
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
79
80
|
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
80
81
|
var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
|
|
81
82
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
82
|
-
var
|
|
83
|
+
var fs10__default = /*#__PURE__*/_interopDefault(fs10);
|
|
83
84
|
var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
|
|
84
85
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
85
86
|
var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
|
|
@@ -93,6 +94,7 @@ var rateLimit__default = /*#__PURE__*/_interopDefault(rateLimit);
|
|
|
93
94
|
var bcrypt__default = /*#__PURE__*/_interopDefault(bcrypt);
|
|
94
95
|
var parser__default = /*#__PURE__*/_interopDefault(parser);
|
|
95
96
|
var jwt__default = /*#__PURE__*/_interopDefault(jwt);
|
|
97
|
+
var cron__default = /*#__PURE__*/_interopDefault(cron);
|
|
96
98
|
|
|
97
99
|
// Cascade AI — Multi-tier AI Orchestration System
|
|
98
100
|
var __defProp = Object.defineProperty;
|
|
@@ -106,16 +108,17 @@ var __export = (target, all) => {
|
|
|
106
108
|
};
|
|
107
109
|
|
|
108
110
|
// src/constants.ts
|
|
109
|
-
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
111
|
+
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
110
112
|
var init_constants = __esm({
|
|
111
113
|
"src/constants.ts"() {
|
|
112
|
-
CASCADE_VERSION = "0.
|
|
114
|
+
CASCADE_VERSION = "0.18.0";
|
|
113
115
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
114
116
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
115
117
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
116
118
|
GLOBAL_CONFIG_DIR = ".cascade-ai";
|
|
117
119
|
GLOBAL_DB_FILE = "memory.db";
|
|
118
120
|
GLOBAL_KEYSTORE_FILE = "keystore.enc";
|
|
121
|
+
GLOBAL_CREDENTIALS_FILE = "credentials.json";
|
|
119
122
|
GLOBAL_RUNTIME_DB_FILE = "runtime.db";
|
|
120
123
|
DEFAULT_DASHBOARD_PORT = 4891;
|
|
121
124
|
DEFAULT_CONTEXT_LIMIT = 2e5;
|
|
@@ -1630,12 +1633,12 @@ var init_audit_logger = __esm({
|
|
|
1630
1633
|
constructor(workspacePath, debugMode = false) {
|
|
1631
1634
|
this.workspacePath = workspacePath;
|
|
1632
1635
|
this.debugMode = debugMode;
|
|
1633
|
-
const cascadeDir =
|
|
1634
|
-
if (!
|
|
1635
|
-
|
|
1636
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
1637
|
+
if (!fs24__default.default.existsSync(cascadeDir)) {
|
|
1638
|
+
fs24__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
1636
1639
|
}
|
|
1637
|
-
this.keyPath =
|
|
1638
|
-
this.dbPath =
|
|
1640
|
+
this.keyPath = path26__default.default.join(cascadeDir, "audit_log.key");
|
|
1641
|
+
this.dbPath = path26__default.default.join(cascadeDir, "audit_log.db");
|
|
1639
1642
|
this.initEncryptionKey();
|
|
1640
1643
|
this.db = new Database2__default.default(this.dbPath);
|
|
1641
1644
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -1665,11 +1668,11 @@ var init_audit_logger = __esm({
|
|
|
1665
1668
|
return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
1666
1669
|
}
|
|
1667
1670
|
initEncryptionKey() {
|
|
1668
|
-
if (
|
|
1669
|
-
this.encryptionKey =
|
|
1671
|
+
if (fs24__default.default.existsSync(this.keyPath)) {
|
|
1672
|
+
this.encryptionKey = fs24__default.default.readFileSync(this.keyPath);
|
|
1670
1673
|
} else {
|
|
1671
1674
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
1672
|
-
|
|
1675
|
+
fs24__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
1673
1676
|
}
|
|
1674
1677
|
}
|
|
1675
1678
|
encrypt(text) {
|
|
@@ -1747,9 +1750,9 @@ var init_audit_logger = __esm({
|
|
|
1747
1750
|
dumpDebugIfNeeded() {
|
|
1748
1751
|
if (!this.debugMode) return;
|
|
1749
1752
|
try {
|
|
1750
|
-
const dumpPath =
|
|
1753
|
+
const dumpPath = path26__default.default.join(os8__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
1751
1754
|
const entries = this.getAllLogs();
|
|
1752
|
-
|
|
1755
|
+
fs24__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
1753
1756
|
} catch (err) {
|
|
1754
1757
|
console.error("Failed to dump debug audit logs", err);
|
|
1755
1758
|
}
|
|
@@ -1804,7 +1807,7 @@ var Keystore = class {
|
|
|
1804
1807
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
1805
1808
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
1806
1809
|
this.backend = "keytar";
|
|
1807
|
-
if (password &&
|
|
1810
|
+
if (password && fs24__default.default.existsSync(this.storePath)) {
|
|
1808
1811
|
try {
|
|
1809
1812
|
const fileEntries = this.decryptFile(password);
|
|
1810
1813
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -1823,7 +1826,7 @@ var Keystore = class {
|
|
|
1823
1826
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
1824
1827
|
);
|
|
1825
1828
|
}
|
|
1826
|
-
if (!
|
|
1829
|
+
if (!fs24__default.default.existsSync(this.storePath)) {
|
|
1827
1830
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
1828
1831
|
this.masterKey = this.deriveKey(password, salt);
|
|
1829
1832
|
this.writeWithSalt({}, salt);
|
|
@@ -1837,7 +1840,7 @@ var Keystore = class {
|
|
|
1837
1840
|
}
|
|
1838
1841
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
1839
1842
|
unlockSync(password) {
|
|
1840
|
-
if (!
|
|
1843
|
+
if (!fs24__default.default.existsSync(this.storePath)) {
|
|
1841
1844
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
1842
1845
|
this.masterKey = this.deriveKey(password, salt);
|
|
1843
1846
|
this.writeWithSalt({}, salt);
|
|
@@ -1895,7 +1898,7 @@ var Keystore = class {
|
|
|
1895
1898
|
}
|
|
1896
1899
|
}
|
|
1897
1900
|
decryptFile(password, knownSalt) {
|
|
1898
|
-
if (!
|
|
1901
|
+
if (!fs24__default.default.existsSync(this.storePath)) return {};
|
|
1899
1902
|
try {
|
|
1900
1903
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
1901
1904
|
const useSalt = knownSalt ?? salt;
|
|
@@ -1917,8 +1920,8 @@ var Keystore = class {
|
|
|
1917
1920
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1918
1921
|
const tag = cipher.getAuthTag();
|
|
1919
1922
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
1920
|
-
|
|
1921
|
-
|
|
1923
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
1924
|
+
fs24__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
1922
1925
|
}
|
|
1923
1926
|
writeWithSalt(data, salt) {
|
|
1924
1927
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -1928,11 +1931,11 @@ var Keystore = class {
|
|
|
1928
1931
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1929
1932
|
const tag = cipher.getAuthTag();
|
|
1930
1933
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
1931
|
-
|
|
1932
|
-
|
|
1934
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
1935
|
+
fs24__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
1933
1936
|
}
|
|
1934
1937
|
readRaw() {
|
|
1935
|
-
const buf =
|
|
1938
|
+
const buf = fs24__default.default.readFileSync(this.storePath);
|
|
1936
1939
|
let offset = 0;
|
|
1937
1940
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
1938
1941
|
offset += SALT_LEN;
|
|
@@ -1965,9 +1968,9 @@ var CascadeIgnore = class {
|
|
|
1965
1968
|
]);
|
|
1966
1969
|
}
|
|
1967
1970
|
async load(workspacePath) {
|
|
1968
|
-
const filePath =
|
|
1971
|
+
const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
1969
1972
|
try {
|
|
1970
|
-
const content = await
|
|
1973
|
+
const content = await fs10__default.default.readFile(filePath, "utf-8");
|
|
1971
1974
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
1972
1975
|
this.ig.add(lines);
|
|
1973
1976
|
this.loaded = true;
|
|
@@ -1976,7 +1979,7 @@ var CascadeIgnore = class {
|
|
|
1976
1979
|
}
|
|
1977
1980
|
isIgnored(filePath, workspacePath) {
|
|
1978
1981
|
try {
|
|
1979
|
-
const relative = workspacePath ?
|
|
1982
|
+
const relative = workspacePath ? path26__default.default.relative(workspacePath, filePath) : filePath;
|
|
1980
1983
|
return this.ig.ignores(relative);
|
|
1981
1984
|
} catch {
|
|
1982
1985
|
return false;
|
|
@@ -1987,7 +1990,7 @@ var CascadeIgnore = class {
|
|
|
1987
1990
|
}
|
|
1988
1991
|
};
|
|
1989
1992
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
1990
|
-
const filePath =
|
|
1993
|
+
const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
1991
1994
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
1992
1995
|
# Syntax identical to .gitignore
|
|
1993
1996
|
|
|
@@ -2014,12 +2017,12 @@ build/
|
|
|
2014
2017
|
.DS_Store
|
|
2015
2018
|
Thumbs.db
|
|
2016
2019
|
`;
|
|
2017
|
-
await
|
|
2020
|
+
await fs10__default.default.writeFile(filePath, content, "utf-8");
|
|
2018
2021
|
}
|
|
2019
2022
|
async function loadCascadeMd(workspacePath) {
|
|
2020
|
-
const filePath =
|
|
2023
|
+
const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
2021
2024
|
try {
|
|
2022
|
-
const raw = await
|
|
2025
|
+
const raw = await fs10__default.default.readFile(filePath, "utf-8");
|
|
2023
2026
|
return parseCascadeMd(raw);
|
|
2024
2027
|
} catch {
|
|
2025
2028
|
return null;
|
|
@@ -2046,7 +2049,7 @@ ${raw.trim()}`;
|
|
|
2046
2049
|
return { raw, sections, systemPrompt };
|
|
2047
2050
|
}
|
|
2048
2051
|
async function createDefaultCascadeMd(workspacePath) {
|
|
2049
|
-
const filePath =
|
|
2052
|
+
const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
2050
2053
|
const content = `# Cascade Project Instructions
|
|
2051
2054
|
|
|
2052
2055
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -2076,12 +2079,12 @@ All tools are allowed unless specified otherwise.
|
|
|
2076
2079
|
|
|
2077
2080
|
List any areas the agent should not touch.
|
|
2078
2081
|
`;
|
|
2079
|
-
await
|
|
2082
|
+
await fs10__default.default.writeFile(filePath, content, "utf-8");
|
|
2080
2083
|
}
|
|
2081
2084
|
var MemoryStore = class _MemoryStore {
|
|
2082
2085
|
db;
|
|
2083
2086
|
constructor(dbPath) {
|
|
2084
|
-
|
|
2087
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(dbPath), { recursive: true });
|
|
2085
2088
|
try {
|
|
2086
2089
|
this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
|
|
2087
2090
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3228,6 +3231,63 @@ function validateConfig(raw) {
|
|
|
3228
3231
|
return result.data;
|
|
3229
3232
|
}
|
|
3230
3233
|
|
|
3234
|
+
// src/config/global-credentials.ts
|
|
3235
|
+
init_constants();
|
|
3236
|
+
function credentialsPath(globalDir) {
|
|
3237
|
+
return path26__default.default.join(globalDir, GLOBAL_CREDENTIALS_FILE);
|
|
3238
|
+
}
|
|
3239
|
+
function providerKey(p) {
|
|
3240
|
+
if (p.type === "azure") {
|
|
3241
|
+
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
3242
|
+
}
|
|
3243
|
+
return p.type;
|
|
3244
|
+
}
|
|
3245
|
+
function isPersistable(p) {
|
|
3246
|
+
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
3247
|
+
}
|
|
3248
|
+
function loadGlobalCredentials(globalDir) {
|
|
3249
|
+
try {
|
|
3250
|
+
const raw = fs24__default.default.readFileSync(credentialsPath(globalDir), "utf-8");
|
|
3251
|
+
const parsed = JSON.parse(raw);
|
|
3252
|
+
if (!Array.isArray(parsed.providers)) return [];
|
|
3253
|
+
return parsed.providers.filter(
|
|
3254
|
+
(p) => Boolean(p) && typeof p.type === "string"
|
|
3255
|
+
);
|
|
3256
|
+
} catch {
|
|
3257
|
+
return [];
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
function saveGlobalCredentials(globalDir, providers) {
|
|
3261
|
+
const filePath = credentialsPath(globalDir);
|
|
3262
|
+
fs24__default.default.mkdirSync(globalDir, { recursive: true, mode: 448 });
|
|
3263
|
+
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
3264
|
+
const tmp = `${filePath}.tmp`;
|
|
3265
|
+
fs24__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3266
|
+
fs24__default.default.renameSync(tmp, filePath);
|
|
3267
|
+
try {
|
|
3268
|
+
fs24__default.default.chmodSync(filePath, 384);
|
|
3269
|
+
} catch {
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
3273
|
+
const merged = [...workspaceProviders];
|
|
3274
|
+
const byKey = new Map(merged.map((p) => [providerKey(p), p]));
|
|
3275
|
+
for (const g of globalProviders) {
|
|
3276
|
+
const existing = byKey.get(providerKey(g));
|
|
3277
|
+
if (!existing) {
|
|
3278
|
+
merged.push({ ...g });
|
|
3279
|
+
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
3280
|
+
continue;
|
|
3281
|
+
}
|
|
3282
|
+
if (!existing.apiKey && g.apiKey) existing.apiKey = g.apiKey;
|
|
3283
|
+
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
3284
|
+
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
3285
|
+
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
3286
|
+
if (!existing.label && g.label) existing.label = g.label;
|
|
3287
|
+
}
|
|
3288
|
+
return merged;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3231
3291
|
// src/config/index.ts
|
|
3232
3292
|
init_constants();
|
|
3233
3293
|
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
@@ -3243,18 +3303,20 @@ var ConfigManager = class {
|
|
|
3243
3303
|
cascadeMd = null;
|
|
3244
3304
|
workspacePath;
|
|
3245
3305
|
globalDir;
|
|
3246
|
-
|
|
3306
|
+
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
3307
|
+
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
3247
3308
|
this.workspacePath = workspacePath;
|
|
3248
|
-
this.globalDir =
|
|
3309
|
+
this.globalDir = globalDirOverride ?? path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
3249
3310
|
}
|
|
3250
3311
|
async load() {
|
|
3251
3312
|
this.config = await this.loadConfig();
|
|
3252
3313
|
this.ignore = new CascadeIgnore();
|
|
3253
3314
|
await this.ignore.load(this.workspacePath);
|
|
3254
3315
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
3255
|
-
this.keystore = new Keystore(
|
|
3256
|
-
this.store = new MemoryStore(
|
|
3316
|
+
this.keystore = new Keystore(path26__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
3317
|
+
this.store = new MemoryStore(path26__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
3257
3318
|
await this.injectEnvKeys();
|
|
3319
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
3258
3320
|
await this.ensureDefaultIdentity();
|
|
3259
3321
|
}
|
|
3260
3322
|
getConfig() {
|
|
@@ -3276,9 +3338,14 @@ var ConfigManager = class {
|
|
|
3276
3338
|
return this.workspacePath;
|
|
3277
3339
|
}
|
|
3278
3340
|
async save() {
|
|
3279
|
-
const configPath =
|
|
3280
|
-
await
|
|
3281
|
-
await
|
|
3341
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
3342
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(configPath), { recursive: true });
|
|
3343
|
+
await fs10__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
3344
|
+
try {
|
|
3345
|
+
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
3346
|
+
} catch (err) {
|
|
3347
|
+
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
3348
|
+
}
|
|
3282
3349
|
}
|
|
3283
3350
|
async updateConfig(updates) {
|
|
3284
3351
|
this.config = validateConfig({ ...this.config, ...updates });
|
|
@@ -3301,9 +3368,9 @@ var ConfigManager = class {
|
|
|
3301
3368
|
return configProvider?.apiKey;
|
|
3302
3369
|
}
|
|
3303
3370
|
async loadConfig() {
|
|
3304
|
-
const configPath =
|
|
3371
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
3305
3372
|
try {
|
|
3306
|
-
const raw = await
|
|
3373
|
+
const raw = await fs10__default.default.readFile(configPath, "utf-8");
|
|
3307
3374
|
return validateConfig(JSON.parse(raw));
|
|
3308
3375
|
} catch (err) {
|
|
3309
3376
|
if (err.code === "ENOENT") {
|
|
@@ -4180,7 +4247,7 @@ init_constants();
|
|
|
4180
4247
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4181
4248
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4182
4249
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4183
|
-
var DEFAULT_CACHE_FILE =
|
|
4250
|
+
var DEFAULT_CACHE_FILE = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4184
4251
|
function normalizeModelId(id) {
|
|
4185
4252
|
let s = id.toLowerCase();
|
|
4186
4253
|
const slash = s.lastIndexOf("/");
|
|
@@ -4213,7 +4280,7 @@ var LiveDataProvider = class {
|
|
|
4213
4280
|
if (this.loaded) return;
|
|
4214
4281
|
this.loaded = true;
|
|
4215
4282
|
try {
|
|
4216
|
-
const raw = await
|
|
4283
|
+
const raw = await fs10__default.default.readFile(this.opts.cacheFile, "utf-8");
|
|
4217
4284
|
const cache = JSON.parse(raw);
|
|
4218
4285
|
if (cache.snapshot?.families) {
|
|
4219
4286
|
this.snapshot = cache.snapshot;
|
|
@@ -4325,14 +4392,14 @@ var LiveDataProvider = class {
|
|
|
4325
4392
|
}
|
|
4326
4393
|
async saveCache() {
|
|
4327
4394
|
try {
|
|
4328
|
-
await
|
|
4395
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
4329
4396
|
const cache = {
|
|
4330
4397
|
fetchedAt: this.fetchedAt,
|
|
4331
4398
|
snapshot: this.snapshot ?? void 0,
|
|
4332
4399
|
prices: Object.fromEntries(this.prices),
|
|
4333
4400
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4334
4401
|
};
|
|
4335
|
-
await
|
|
4402
|
+
await fs10__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4336
4403
|
} catch {
|
|
4337
4404
|
}
|
|
4338
4405
|
}
|
|
@@ -6229,8 +6296,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6229
6296
|
tierId: this.id,
|
|
6230
6297
|
sessionId: this.taskId,
|
|
6231
6298
|
requireApproval: false,
|
|
6232
|
-
saveSnapshot: async (
|
|
6233
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
6299
|
+
saveSnapshot: async (path31, content) => {
|
|
6300
|
+
this.store?.addFileSnapshot(this.taskId, path31, content);
|
|
6234
6301
|
},
|
|
6235
6302
|
sendPeerSync: (to, syncType, content) => {
|
|
6236
6303
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -6400,9 +6467,9 @@ ${assignment.expectedOutput}`;
|
|
|
6400
6467
|
const { promisify: promisify4 } = await import('util');
|
|
6401
6468
|
const execAsync3 = promisify4(exec3);
|
|
6402
6469
|
for (const artifactPath of artifactPaths) {
|
|
6403
|
-
const absolutePath =
|
|
6470
|
+
const absolutePath = path26__default.default.resolve(process.cwd(), artifactPath);
|
|
6404
6471
|
try {
|
|
6405
|
-
const stat = await
|
|
6472
|
+
const stat = await fs10__default.default.stat(absolutePath);
|
|
6406
6473
|
if (!stat.isFile()) {
|
|
6407
6474
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
6408
6475
|
continue;
|
|
@@ -6412,7 +6479,7 @@ ${assignment.expectedOutput}`;
|
|
|
6412
6479
|
continue;
|
|
6413
6480
|
}
|
|
6414
6481
|
if (!/\.pdf$/i.test(artifactPath)) {
|
|
6415
|
-
const content = await
|
|
6482
|
+
const content = await fs10__default.default.readFile(absolutePath, "utf-8");
|
|
6416
6483
|
if (!content.trim()) {
|
|
6417
6484
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
6418
6485
|
continue;
|
|
@@ -6421,7 +6488,7 @@ ${assignment.expectedOutput}`;
|
|
|
6421
6488
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
6422
6489
|
continue;
|
|
6423
6490
|
}
|
|
6424
|
-
const ext =
|
|
6491
|
+
const ext = path26__default.default.extname(absolutePath).toLowerCase();
|
|
6425
6492
|
try {
|
|
6426
6493
|
if (ext === ".ts" || ext === ".tsx") {
|
|
6427
6494
|
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -8512,16 +8579,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
8512
8579
|
if (typeof input !== "string" || input.length === 0) {
|
|
8513
8580
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
8514
8581
|
}
|
|
8515
|
-
const root =
|
|
8516
|
-
const abs =
|
|
8517
|
-
const rel =
|
|
8518
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
8582
|
+
const root = path26__default.default.resolve(workspaceRoot);
|
|
8583
|
+
const abs = path26__default.default.isAbsolute(input) ? path26__default.default.resolve(input) : path26__default.default.resolve(root, input);
|
|
8584
|
+
const rel = path26__default.default.relative(root, abs);
|
|
8585
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26__default.default.isAbsolute(rel)) {
|
|
8519
8586
|
throw new WorkspaceSandboxError(input, root);
|
|
8520
8587
|
}
|
|
8521
8588
|
try {
|
|
8522
|
-
const real =
|
|
8523
|
-
const realRel =
|
|
8524
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
8589
|
+
const real = fs24__default.default.realpathSync(abs);
|
|
8590
|
+
const realRel = path26__default.default.relative(root, real);
|
|
8591
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26__default.default.isAbsolute(realRel))) {
|
|
8525
8592
|
throw new WorkspaceSandboxError(input, root);
|
|
8526
8593
|
}
|
|
8527
8594
|
} catch (e) {
|
|
@@ -8548,7 +8615,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
8548
8615
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
8549
8616
|
const offset = input["offset"] ?? 1;
|
|
8550
8617
|
const limit = input["limit"];
|
|
8551
|
-
const content = await
|
|
8618
|
+
const content = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8552
8619
|
const lines = content.split("\n");
|
|
8553
8620
|
const start = Math.max(0, offset - 1);
|
|
8554
8621
|
const end = limit ? start + limit : lines.length;
|
|
@@ -8577,13 +8644,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
8577
8644
|
const content = input["content"];
|
|
8578
8645
|
if (options.saveSnapshot) {
|
|
8579
8646
|
try {
|
|
8580
|
-
const oldContent = await
|
|
8647
|
+
const oldContent = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8581
8648
|
await options.saveSnapshot(absPath, oldContent);
|
|
8582
8649
|
} catch {
|
|
8583
8650
|
}
|
|
8584
8651
|
}
|
|
8585
|
-
await
|
|
8586
|
-
await
|
|
8652
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(absPath), { recursive: true });
|
|
8653
|
+
await fs10__default.default.writeFile(absPath, content, "utf-8");
|
|
8587
8654
|
return `Written ${content.length} characters to ${filePath}`;
|
|
8588
8655
|
}
|
|
8589
8656
|
};
|
|
@@ -8609,7 +8676,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
8609
8676
|
const oldString = input["old_string"];
|
|
8610
8677
|
const newString = input["new_string"];
|
|
8611
8678
|
const replaceAll = input["replace_all"] ?? false;
|
|
8612
|
-
const rawContent = await
|
|
8679
|
+
const rawContent = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8613
8680
|
if (options.saveSnapshot) {
|
|
8614
8681
|
await options.saveSnapshot(absPath, rawContent);
|
|
8615
8682
|
}
|
|
@@ -8621,7 +8688,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
8621
8688
|
);
|
|
8622
8689
|
}
|
|
8623
8690
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
8624
|
-
await
|
|
8691
|
+
await fs10__default.default.writeFile(absPath, updated, "utf-8");
|
|
8625
8692
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
8626
8693
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
8627
8694
|
}
|
|
@@ -8644,12 +8711,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
8644
8711
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
8645
8712
|
if (options.saveSnapshot) {
|
|
8646
8713
|
try {
|
|
8647
|
-
const oldContent = await
|
|
8714
|
+
const oldContent = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8648
8715
|
await options.saveSnapshot(absPath, oldContent);
|
|
8649
8716
|
} catch {
|
|
8650
8717
|
}
|
|
8651
8718
|
}
|
|
8652
|
-
await
|
|
8719
|
+
await fs10__default.default.rm(absPath, { recursive: false });
|
|
8653
8720
|
return `Deleted ${filePath}`;
|
|
8654
8721
|
}
|
|
8655
8722
|
};
|
|
@@ -8666,7 +8733,7 @@ var FileListTool = class extends BaseTool {
|
|
|
8666
8733
|
async execute(input, _options) {
|
|
8667
8734
|
const inputPath = input["path"] || ".";
|
|
8668
8735
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
8669
|
-
const entries = await
|
|
8736
|
+
const entries = await fs10__default.default.readdir(absPath, { withFileTypes: true });
|
|
8670
8737
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
8671
8738
|
}
|
|
8672
8739
|
};
|
|
@@ -9079,8 +9146,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
9079
9146
|
}
|
|
9080
9147
|
};
|
|
9081
9148
|
async function fileToImageAttachment(filePath) {
|
|
9082
|
-
const data = await
|
|
9083
|
-
const ext =
|
|
9149
|
+
const data = await fs10__default.default.readFile(filePath);
|
|
9150
|
+
const ext = path26__default.default.extname(filePath).toLowerCase();
|
|
9084
9151
|
const mimeMap = {
|
|
9085
9152
|
".jpg": "image/jpeg",
|
|
9086
9153
|
".jpeg": "image/jpeg",
|
|
@@ -9114,14 +9181,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
9114
9181
|
const filePath = input["path"];
|
|
9115
9182
|
const content = input["content"];
|
|
9116
9183
|
const title = input["title"];
|
|
9117
|
-
const dir =
|
|
9118
|
-
if (!
|
|
9119
|
-
|
|
9184
|
+
const dir = path26__default.default.dirname(filePath);
|
|
9185
|
+
if (!fs24__default.default.existsSync(dir)) {
|
|
9186
|
+
fs24__default.default.mkdirSync(dir, { recursive: true });
|
|
9120
9187
|
}
|
|
9121
9188
|
return new Promise((resolve, reject) => {
|
|
9122
9189
|
try {
|
|
9123
9190
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
9124
|
-
const stream =
|
|
9191
|
+
const stream = fs24__default.default.createWriteStream(filePath);
|
|
9125
9192
|
doc.pipe(stream);
|
|
9126
9193
|
if (title) {
|
|
9127
9194
|
doc.info["Title"] = title;
|
|
@@ -9199,22 +9266,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
9199
9266
|
}
|
|
9200
9267
|
cmdPrefix = NODE_CMD;
|
|
9201
9268
|
}
|
|
9202
|
-
const tmpDir =
|
|
9203
|
-
if (!
|
|
9204
|
-
|
|
9269
|
+
const tmpDir = path26__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
9270
|
+
if (!fs24__default.default.existsSync(tmpDir)) {
|
|
9271
|
+
fs24__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
9205
9272
|
}
|
|
9206
9273
|
const extension = language === "python" ? "py" : "js";
|
|
9207
9274
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
9208
|
-
const filePath =
|
|
9209
|
-
|
|
9275
|
+
const filePath = path26__default.default.join(tmpDir, fileName);
|
|
9276
|
+
fs24__default.default.writeFileSync(filePath, code, "utf-8");
|
|
9210
9277
|
const execArgs = [filePath, ...args];
|
|
9211
9278
|
return new Promise((resolve) => {
|
|
9212
9279
|
const startMs = Date.now();
|
|
9213
9280
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
9214
9281
|
const duration = Date.now() - startMs;
|
|
9215
9282
|
try {
|
|
9216
|
-
if (
|
|
9217
|
-
|
|
9283
|
+
if (fs24__default.default.existsSync(filePath)) {
|
|
9284
|
+
fs24__default.default.unlinkSync(filePath);
|
|
9218
9285
|
}
|
|
9219
9286
|
} catch (cleanupErr) {
|
|
9220
9287
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -9493,7 +9560,7 @@ var GlobTool = class extends BaseTool {
|
|
|
9493
9560
|
};
|
|
9494
9561
|
async execute(input, _options) {
|
|
9495
9562
|
const pattern = input["pattern"];
|
|
9496
|
-
const searchPath = input["path"] ?
|
|
9563
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
9497
9564
|
const matches = await glob.glob(pattern, {
|
|
9498
9565
|
cwd: searchPath,
|
|
9499
9566
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -9506,7 +9573,7 @@ var GlobTool = class extends BaseTool {
|
|
|
9506
9573
|
const withMtime = await Promise.all(
|
|
9507
9574
|
matches.map(async (rel) => {
|
|
9508
9575
|
try {
|
|
9509
|
-
const stat = await
|
|
9576
|
+
const stat = await fs10__default.default.stat(path26__default.default.join(searchPath, rel));
|
|
9510
9577
|
return { rel, mtime: stat.mtimeMs };
|
|
9511
9578
|
} catch {
|
|
9512
9579
|
return { rel, mtime: 0 };
|
|
@@ -9555,7 +9622,7 @@ var GrepTool = class extends BaseTool {
|
|
|
9555
9622
|
};
|
|
9556
9623
|
async execute(input, _options) {
|
|
9557
9624
|
const pattern = input["pattern"];
|
|
9558
|
-
const searchPath = input["path"] ?
|
|
9625
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
9559
9626
|
const globPattern = input["glob"];
|
|
9560
9627
|
const outputMode = input["output_mode"] ?? "content";
|
|
9561
9628
|
const context = input["context"] ?? 0;
|
|
@@ -9609,15 +9676,15 @@ var GrepTool = class extends BaseTool {
|
|
|
9609
9676
|
nodir: true
|
|
9610
9677
|
});
|
|
9611
9678
|
} catch {
|
|
9612
|
-
files = [
|
|
9679
|
+
files = [path26__default.default.relative(searchPath, searchPath) || "."];
|
|
9613
9680
|
}
|
|
9614
9681
|
const results = [];
|
|
9615
9682
|
let totalCount = 0;
|
|
9616
9683
|
for (const rel of files) {
|
|
9617
|
-
const abs =
|
|
9684
|
+
const abs = path26__default.default.join(searchPath, rel);
|
|
9618
9685
|
let content;
|
|
9619
9686
|
try {
|
|
9620
|
-
content = await
|
|
9687
|
+
content = await fs10__default.default.readFile(abs, "utf-8");
|
|
9621
9688
|
} catch {
|
|
9622
9689
|
continue;
|
|
9623
9690
|
}
|
|
@@ -9976,10 +10043,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
9976
10043
|
}
|
|
9977
10044
|
isIgnored(filePath) {
|
|
9978
10045
|
if (!filePath) return false;
|
|
9979
|
-
const abs =
|
|
9980
|
-
const rel =
|
|
9981
|
-
if (!rel || rel.startsWith("..") ||
|
|
9982
|
-
const posixRel = rel.split(
|
|
10046
|
+
const abs = path26__default.default.resolve(this.workspaceRoot, filePath);
|
|
10047
|
+
const rel = path26__default.default.relative(this.workspaceRoot, abs);
|
|
10048
|
+
if (!rel || rel.startsWith("..") || path26__default.default.isAbsolute(rel)) return true;
|
|
10049
|
+
const posixRel = rel.split(path26__default.default.sep).join("/");
|
|
9983
10050
|
return this.ignoreMatcher.ignores(posixRel);
|
|
9984
10051
|
}
|
|
9985
10052
|
};
|
|
@@ -10502,7 +10569,7 @@ var TaskAnalyzer = class {
|
|
|
10502
10569
|
analysisCache.clear();
|
|
10503
10570
|
}
|
|
10504
10571
|
};
|
|
10505
|
-
var DEFAULT_STATS_FILE =
|
|
10572
|
+
var DEFAULT_STATS_FILE = path26__default.default.join(os8__default.default.homedir(), ".cascade", "model-perf.json");
|
|
10506
10573
|
var ModelPerformanceTracker = class {
|
|
10507
10574
|
stats = /* @__PURE__ */ new Map();
|
|
10508
10575
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -10515,7 +10582,7 @@ var ModelPerformanceTracker = class {
|
|
|
10515
10582
|
if (this.loaded) return;
|
|
10516
10583
|
this.loaded = true;
|
|
10517
10584
|
try {
|
|
10518
|
-
const raw = await
|
|
10585
|
+
const raw = await fs10__default.default.readFile(this.statsFile, "utf-8");
|
|
10519
10586
|
const parsed = JSON.parse(raw);
|
|
10520
10587
|
if (parsed.models) {
|
|
10521
10588
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -10534,12 +10601,12 @@ var ModelPerformanceTracker = class {
|
|
|
10534
10601
|
}
|
|
10535
10602
|
async save() {
|
|
10536
10603
|
try {
|
|
10537
|
-
await
|
|
10604
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(this.statsFile), { recursive: true });
|
|
10538
10605
|
const modelsObj = {};
|
|
10539
10606
|
const featuresObj = {};
|
|
10540
10607
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
10541
10608
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
10542
|
-
await
|
|
10609
|
+
await fs10__default.default.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
|
|
10543
10610
|
} catch {
|
|
10544
10611
|
}
|
|
10545
10612
|
}
|
|
@@ -11073,9 +11140,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
11073
11140
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
11074
11141
|
async loadPersistedTools() {
|
|
11075
11142
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
11076
|
-
const file =
|
|
11143
|
+
const file = path26__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
11077
11144
|
try {
|
|
11078
|
-
const raw = await
|
|
11145
|
+
const raw = await fs10__default.default.readFile(file, "utf-8");
|
|
11079
11146
|
const specs = JSON.parse(raw);
|
|
11080
11147
|
if (!Array.isArray(specs)) return;
|
|
11081
11148
|
let loaded = 0;
|
|
@@ -11097,11 +11164,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
11097
11164
|
}
|
|
11098
11165
|
async persist() {
|
|
11099
11166
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
11100
|
-
const dir =
|
|
11101
|
-
const file =
|
|
11167
|
+
const dir = path26__default.default.join(this.workspacePath, ".cascade");
|
|
11168
|
+
const file = path26__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
11102
11169
|
try {
|
|
11103
|
-
await
|
|
11104
|
-
await
|
|
11170
|
+
await fs10__default.default.mkdir(dir, { recursive: true });
|
|
11171
|
+
await fs10__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
11105
11172
|
} catch (err) {
|
|
11106
11173
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
11107
11174
|
}
|
|
@@ -11118,12 +11185,12 @@ var WorldStateDB = class {
|
|
|
11118
11185
|
constructor(workspacePath, debugMode = false) {
|
|
11119
11186
|
this.workspacePath = workspacePath;
|
|
11120
11187
|
this.debugMode = debugMode;
|
|
11121
|
-
const cascadeDir =
|
|
11122
|
-
if (!
|
|
11123
|
-
|
|
11188
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
11189
|
+
if (!fs24__default.default.existsSync(cascadeDir)) {
|
|
11190
|
+
fs24__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
11124
11191
|
}
|
|
11125
|
-
this.keyPath =
|
|
11126
|
-
this.dbPath =
|
|
11192
|
+
this.keyPath = path26__default.default.join(cascadeDir, "world_state.key");
|
|
11193
|
+
this.dbPath = path26__default.default.join(cascadeDir, "world_state.db");
|
|
11127
11194
|
this.initEncryptionKey();
|
|
11128
11195
|
this.db = new Database2__default.default(this.dbPath);
|
|
11129
11196
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -11153,11 +11220,11 @@ var WorldStateDB = class {
|
|
|
11153
11220
|
dbPath;
|
|
11154
11221
|
encryptionKey;
|
|
11155
11222
|
initEncryptionKey() {
|
|
11156
|
-
if (
|
|
11157
|
-
this.encryptionKey =
|
|
11223
|
+
if (fs24__default.default.existsSync(this.keyPath)) {
|
|
11224
|
+
this.encryptionKey = fs24__default.default.readFileSync(this.keyPath);
|
|
11158
11225
|
} else {
|
|
11159
11226
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
11160
|
-
|
|
11227
|
+
fs24__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
11161
11228
|
}
|
|
11162
11229
|
}
|
|
11163
11230
|
encrypt(text) {
|
|
@@ -11298,6 +11365,22 @@ var WorldStateDB = class {
|
|
|
11298
11365
|
}
|
|
11299
11366
|
return selected.slice(0, limit).map((f) => `- ${f.entity} ${f.relation} ${f.value}`).join("\n");
|
|
11300
11367
|
}
|
|
11368
|
+
/**
|
|
11369
|
+
* Delete one fact by its (normalized) entity + relation key. Returns whether
|
|
11370
|
+
* a row was actually removed. Powers the desktop Knowledge tab's per-fact
|
|
11371
|
+
* delete — users can prune what the planner remembers about their project.
|
|
11372
|
+
*/
|
|
11373
|
+
deleteFact(entity, relation) {
|
|
11374
|
+
const e = normalizeKey(entity);
|
|
11375
|
+
const r = normalizeKey(relation);
|
|
11376
|
+
if (!e || !r) return false;
|
|
11377
|
+
const result = this.db.prepare("DELETE FROM facts WHERE entity = ? AND relation = ?").run(e, r);
|
|
11378
|
+
return result.changes > 0;
|
|
11379
|
+
}
|
|
11380
|
+
/** Delete every fact. Returns how many were removed. */
|
|
11381
|
+
clearFacts() {
|
|
11382
|
+
return this.db.prepare("DELETE FROM facts").run().changes;
|
|
11383
|
+
}
|
|
11301
11384
|
// ── Export / Import ──────────────────────────
|
|
11302
11385
|
//
|
|
11303
11386
|
// Knowledge travels DECRYPTED in the export bundle (a portable plaintext
|
|
@@ -11346,9 +11429,9 @@ var WorldStateDB = class {
|
|
|
11346
11429
|
dumpDebugIfNeeded() {
|
|
11347
11430
|
if (!this.debugMode) return;
|
|
11348
11431
|
try {
|
|
11349
|
-
const dumpPath =
|
|
11432
|
+
const dumpPath = path26__default.default.join(os8__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
11350
11433
|
const entries = this.getAllEntries();
|
|
11351
|
-
|
|
11434
|
+
fs24__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
11352
11435
|
} catch (err) {
|
|
11353
11436
|
console.error("Failed to dump debug world state", err);
|
|
11354
11437
|
}
|
|
@@ -13766,10 +13849,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13766
13849
|
}
|
|
13767
13850
|
} catch {
|
|
13768
13851
|
}
|
|
13769
|
-
const configPath =
|
|
13852
|
+
const configPath = path26__default.default.join(workspacePath, ".cascade", "config.json");
|
|
13770
13853
|
try {
|
|
13771
|
-
await
|
|
13772
|
-
await
|
|
13854
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(configPath), { recursive: true });
|
|
13855
|
+
await fs10__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
13773
13856
|
} catch (err) {
|
|
13774
13857
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13775
13858
|
dispatch({
|
|
@@ -13820,9 +13903,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13820
13903
|
if (msg.includes("non-text parts")) return;
|
|
13821
13904
|
originalLog(...args);
|
|
13822
13905
|
};
|
|
13823
|
-
const store = new MemoryStore(
|
|
13906
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
13824
13907
|
storeRef.current = store;
|
|
13825
|
-
globalStoreRef.current = new MemoryStore(
|
|
13908
|
+
globalStoreRef.current = new MemoryStore(path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
13826
13909
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
13827
13910
|
setIdentities(identityRows);
|
|
13828
13911
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -13885,14 +13968,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13885
13968
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
13886
13969
|
onExport: async (fmt) => {
|
|
13887
13970
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13888
|
-
const exportPath =
|
|
13971
|
+
const exportPath = path26__default.default.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
|
|
13889
13972
|
if (fmt === "json") {
|
|
13890
|
-
await
|
|
13973
|
+
await fs10__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
13891
13974
|
} else {
|
|
13892
13975
|
const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
|
|
13893
13976
|
|
|
13894
13977
|
${msg.content}`).join("\n\n");
|
|
13895
|
-
await
|
|
13978
|
+
await fs10__default.default.writeFile(exportPath, markdown, "utf-8");
|
|
13896
13979
|
}
|
|
13897
13980
|
},
|
|
13898
13981
|
onRollback: async () => {
|
|
@@ -13902,7 +13985,7 @@ ${msg.content}`).join("\n\n");
|
|
|
13902
13985
|
if (!snapshots.length) return "No file snapshots found for this session.";
|
|
13903
13986
|
for (const { filePath, content } of snapshots) {
|
|
13904
13987
|
try {
|
|
13905
|
-
await
|
|
13988
|
+
await fs10__default.default.writeFile(filePath, content, "utf-8");
|
|
13906
13989
|
} catch (err) {
|
|
13907
13990
|
console.error(`Restore failed: ${filePath}`, err);
|
|
13908
13991
|
}
|
|
@@ -14781,9 +14864,9 @@ function stringifySlashOutput(val) {
|
|
|
14781
14864
|
}
|
|
14782
14865
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
14783
14866
|
if (!query) return "Usage: /search <query>";
|
|
14784
|
-
const dbPath =
|
|
14867
|
+
const dbPath = path26__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
14785
14868
|
try {
|
|
14786
|
-
await
|
|
14869
|
+
await fs10__default.default.access(dbPath);
|
|
14787
14870
|
} catch {
|
|
14788
14871
|
return "No database found. Start a conversation first.";
|
|
14789
14872
|
}
|
|
@@ -14815,7 +14898,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
14815
14898
|
async function diagnoseRuntime(config, workspacePath) {
|
|
14816
14899
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
14817
14900
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
14818
|
-
const store = new MemoryStore(
|
|
14901
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
14819
14902
|
try {
|
|
14820
14903
|
const sessions = store.listSessions(void 0, 3);
|
|
14821
14904
|
return [
|
|
@@ -14834,7 +14917,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
14834
14917
|
}
|
|
14835
14918
|
async function showRecentLogs(args, workspacePath) {
|
|
14836
14919
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
14837
|
-
const store = new MemoryStore(
|
|
14920
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
14838
14921
|
try {
|
|
14839
14922
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
14840
14923
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -14846,7 +14929,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
14846
14929
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
14847
14930
|
const sessionId = args[0];
|
|
14848
14931
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
14849
|
-
const store = new MemoryStore(
|
|
14932
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
14850
14933
|
try {
|
|
14851
14934
|
const session = store.getSession(sessionId);
|
|
14852
14935
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -15266,10 +15349,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
15266
15349
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
15267
15350
|
};
|
|
15268
15351
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
15269
|
-
const configDir =
|
|
15270
|
-
await
|
|
15271
|
-
const configPath =
|
|
15272
|
-
await
|
|
15352
|
+
const configDir = path26__default.default.join(workspacePath, ".cascade");
|
|
15353
|
+
await fs10__default.default.mkdir(configDir, { recursive: true });
|
|
15354
|
+
const configPath = path26__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
15355
|
+
await fs10__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
15273
15356
|
savedConfigRef.current = config;
|
|
15274
15357
|
dispatchRef.current({ type: "GO_DONE" });
|
|
15275
15358
|
} catch (err) {
|
|
@@ -15609,14 +15692,14 @@ function printTelemetryBanner() {
|
|
|
15609
15692
|
async function initCommand(workspacePath = process.cwd()) {
|
|
15610
15693
|
const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
15611
15694
|
try {
|
|
15612
|
-
const configDir =
|
|
15613
|
-
await
|
|
15614
|
-
const mdPath =
|
|
15695
|
+
const configDir = path26__default.default.join(workspacePath, ".cascade");
|
|
15696
|
+
await fs10__default.default.mkdir(configDir, { recursive: true });
|
|
15697
|
+
const mdPath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
15615
15698
|
if (!await fileExists(mdPath)) {
|
|
15616
15699
|
await createDefaultCascadeMd(workspacePath);
|
|
15617
15700
|
spin.succeed(chalk9__default.default.green("Created CASCADE.md"));
|
|
15618
15701
|
}
|
|
15619
|
-
const ignorePath =
|
|
15702
|
+
const ignorePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
15620
15703
|
if (!await fileExists(ignorePath)) {
|
|
15621
15704
|
await createDefaultIgnoreFile(workspacePath);
|
|
15622
15705
|
spin.succeed(chalk9__default.default.green("Created .cascadeignore"));
|
|
@@ -15625,7 +15708,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
15625
15708
|
console.log();
|
|
15626
15709
|
console.log(chalk9__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
15627
15710
|
console.log();
|
|
15628
|
-
const configPath =
|
|
15711
|
+
const configPath = path26__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
15629
15712
|
if (await fileExists(configPath)) {
|
|
15630
15713
|
console.log(chalk9__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
15631
15714
|
console.log();
|
|
@@ -15644,7 +15727,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
15644
15727
|
}
|
|
15645
15728
|
async function fileExists(p) {
|
|
15646
15729
|
try {
|
|
15647
|
-
await
|
|
15730
|
+
await fs10__default.default.access(p);
|
|
15648
15731
|
return true;
|
|
15649
15732
|
} catch {
|
|
15650
15733
|
return false;
|
|
@@ -15656,7 +15739,7 @@ init_constants();
|
|
|
15656
15739
|
var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
|
|
15657
15740
|
async function readJson(file) {
|
|
15658
15741
|
try {
|
|
15659
|
-
const raw = await
|
|
15742
|
+
const raw = await fs10__default.default.readFile(file, "utf-8");
|
|
15660
15743
|
return JSON.parse(raw);
|
|
15661
15744
|
} catch {
|
|
15662
15745
|
return null;
|
|
@@ -15683,7 +15766,7 @@ function fromEnv(env) {
|
|
|
15683
15766
|
return out;
|
|
15684
15767
|
}
|
|
15685
15768
|
async function fromClaudeCode(home) {
|
|
15686
|
-
const file =
|
|
15769
|
+
const file = path26__default.default.join(home, ".claude", ".credentials.json");
|
|
15687
15770
|
const data = await readJson(file);
|
|
15688
15771
|
if (!data) return [];
|
|
15689
15772
|
const oauth = data["claudeAiOauth"];
|
|
@@ -15706,7 +15789,7 @@ async function fromClaudeCode(home) {
|
|
|
15706
15789
|
return [];
|
|
15707
15790
|
}
|
|
15708
15791
|
async function fromCodex(home) {
|
|
15709
|
-
const file =
|
|
15792
|
+
const file = path26__default.default.join(home, ".codex", "auth.json");
|
|
15710
15793
|
const data = await readJson(file);
|
|
15711
15794
|
if (!data) return [];
|
|
15712
15795
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -15729,7 +15812,7 @@ async function fromCodex(home) {
|
|
|
15729
15812
|
return [];
|
|
15730
15813
|
}
|
|
15731
15814
|
async function fromGemini(home) {
|
|
15732
|
-
const file =
|
|
15815
|
+
const file = path26__default.default.join(home, ".gemini", "oauth_creds.json");
|
|
15733
15816
|
const data = await readJson(file);
|
|
15734
15817
|
const accessToken = str(data?.["access_token"]);
|
|
15735
15818
|
if (!accessToken) return [];
|
|
@@ -15745,7 +15828,7 @@ async function fromGemini(home) {
|
|
|
15745
15828
|
}
|
|
15746
15829
|
async function fromCopilot(home) {
|
|
15747
15830
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
15748
|
-
const file =
|
|
15831
|
+
const file = path26__default.default.join(home, ".config", "github-copilot", name);
|
|
15749
15832
|
const data = await readJson(file);
|
|
15750
15833
|
if (!data) continue;
|
|
15751
15834
|
for (const value of Object.values(data)) {
|
|
@@ -15799,7 +15882,7 @@ async function doctorCommand() {
|
|
|
15799
15882
|
checks.push({
|
|
15800
15883
|
label: "Cascade config",
|
|
15801
15884
|
ok: true,
|
|
15802
|
-
detail: `Loaded ${
|
|
15885
|
+
detail: `Loaded ${path26__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
15803
15886
|
});
|
|
15804
15887
|
const providers = [
|
|
15805
15888
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -15955,6 +16038,11 @@ function authMiddleware(secret, required = true) {
|
|
|
15955
16038
|
}
|
|
15956
16039
|
const user = verifyToken(token, secret);
|
|
15957
16040
|
if (!user) {
|
|
16041
|
+
if (!required) {
|
|
16042
|
+
req.user = void 0;
|
|
16043
|
+
next();
|
|
16044
|
+
return;
|
|
16045
|
+
}
|
|
15958
16046
|
res.status(401).json({ error: "Invalid or expired token" });
|
|
15959
16047
|
return;
|
|
15960
16048
|
}
|
|
@@ -16087,6 +16175,25 @@ var DashboardSocket = class {
|
|
|
16087
16175
|
});
|
|
16088
16176
|
});
|
|
16089
16177
|
}
|
|
16178
|
+
/**
|
|
16179
|
+
* Boardroom plan decisions from a connected client. The desktop shows a
|
|
16180
|
+
* plan-review modal on `plan:approval-required` and answers here; the
|
|
16181
|
+
* server routes the decision into the paused run via resolvePlanApproval.
|
|
16182
|
+
*/
|
|
16183
|
+
onPlanDecision(callback) {
|
|
16184
|
+
this.io.on("connection", (socket) => {
|
|
16185
|
+
socket.on("plan:decision", (payload) => {
|
|
16186
|
+
if (typeof payload?.sessionId === "string" && typeof payload?.approved === "boolean") {
|
|
16187
|
+
callback({
|
|
16188
|
+
sessionId: payload.sessionId,
|
|
16189
|
+
approved: payload.approved,
|
|
16190
|
+
note: typeof payload.note === "string" && payload.note.trim() ? payload.note.trim() : void 0,
|
|
16191
|
+
editedPlan: payload.editedPlan
|
|
16192
|
+
});
|
|
16193
|
+
}
|
|
16194
|
+
});
|
|
16195
|
+
});
|
|
16196
|
+
}
|
|
16090
16197
|
onSessionSteer(callback) {
|
|
16091
16198
|
this.io.on("connection", (socket) => {
|
|
16092
16199
|
socket.on("session:steer", (payload) => {
|
|
@@ -16131,7 +16238,123 @@ var DashboardSocket = class {
|
|
|
16131
16238
|
|
|
16132
16239
|
// src/dashboard/server.ts
|
|
16133
16240
|
init_constants();
|
|
16134
|
-
var
|
|
16241
|
+
var TaskScheduler = class {
|
|
16242
|
+
cronJobs = /* @__PURE__ */ new Map();
|
|
16243
|
+
store;
|
|
16244
|
+
runner;
|
|
16245
|
+
constructor(store, runner) {
|
|
16246
|
+
this.store = store;
|
|
16247
|
+
this.runner = runner;
|
|
16248
|
+
}
|
|
16249
|
+
start() {
|
|
16250
|
+
const tasks = this.store.listScheduledTasks();
|
|
16251
|
+
for (const task of tasks) {
|
|
16252
|
+
if (task.enabled) this.schedule(task);
|
|
16253
|
+
}
|
|
16254
|
+
}
|
|
16255
|
+
stop() {
|
|
16256
|
+
for (const job of this.cronJobs.values()) job.stop();
|
|
16257
|
+
this.cronJobs.clear();
|
|
16258
|
+
}
|
|
16259
|
+
schedule(task) {
|
|
16260
|
+
if (!cron__default.default.validate(task.cronExpression)) {
|
|
16261
|
+
throw new Error(`Invalid cron expression: ${task.cronExpression}`);
|
|
16262
|
+
}
|
|
16263
|
+
const existing = this.cronJobs.get(task.id);
|
|
16264
|
+
if (existing) {
|
|
16265
|
+
try {
|
|
16266
|
+
existing.stop();
|
|
16267
|
+
} catch {
|
|
16268
|
+
}
|
|
16269
|
+
}
|
|
16270
|
+
const job = cron__default.default.schedule(task.cronExpression, async () => {
|
|
16271
|
+
try {
|
|
16272
|
+
task.lastRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
16273
|
+
this.store.saveScheduledTask(task);
|
|
16274
|
+
await this.runner(task);
|
|
16275
|
+
} catch (err) {
|
|
16276
|
+
console.error(`[scheduler] Task "${task.id}" (${task.cronExpression}) failed:`, err);
|
|
16277
|
+
}
|
|
16278
|
+
}, { timezone: "UTC" });
|
|
16279
|
+
this.cronJobs.set(task.id, job);
|
|
16280
|
+
}
|
|
16281
|
+
unschedule(taskId) {
|
|
16282
|
+
this.cronJobs.get(taskId)?.stop();
|
|
16283
|
+
this.cronJobs.delete(taskId);
|
|
16284
|
+
}
|
|
16285
|
+
add(task) {
|
|
16286
|
+
this.store.saveScheduledTask(task);
|
|
16287
|
+
if (task.enabled) this.schedule(task);
|
|
16288
|
+
}
|
|
16289
|
+
remove(taskId) {
|
|
16290
|
+
this.unschedule(taskId);
|
|
16291
|
+
this.store.deleteScheduledTask(taskId);
|
|
16292
|
+
}
|
|
16293
|
+
list() {
|
|
16294
|
+
return this.store.listScheduledTasks();
|
|
16295
|
+
}
|
|
16296
|
+
isRunning(taskId) {
|
|
16297
|
+
return this.cronJobs.has(taskId);
|
|
16298
|
+
}
|
|
16299
|
+
static validateCron(expression) {
|
|
16300
|
+
return cron__default.default.validate(expression);
|
|
16301
|
+
}
|
|
16302
|
+
};
|
|
16303
|
+
|
|
16304
|
+
// src/dashboard/cost-stats.ts
|
|
16305
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
16306
|
+
function utcDateKey(d) {
|
|
16307
|
+
return d.toISOString().slice(0, 10);
|
|
16308
|
+
}
|
|
16309
|
+
function aggregateCostStats(sessions, opts = {}) {
|
|
16310
|
+
const days = Math.max(1, opts.days ?? 30);
|
|
16311
|
+
const topN = Math.max(1, opts.topN ?? 8);
|
|
16312
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
16313
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
16314
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
16315
|
+
const key = utcDateKey(new Date(now.getTime() - i * DAY_MS));
|
|
16316
|
+
buckets.set(key, { date: key, costUsd: 0, tokens: 0, runs: 0 });
|
|
16317
|
+
}
|
|
16318
|
+
let totalCostUsd = 0;
|
|
16319
|
+
let totalTokens = 0;
|
|
16320
|
+
let totalRuns = 0;
|
|
16321
|
+
for (const s of sessions) {
|
|
16322
|
+
const cost = s.metadata?.totalCostUsd ?? 0;
|
|
16323
|
+
const tokens = s.metadata?.totalTokens ?? 0;
|
|
16324
|
+
const runs = s.metadata?.taskCount ?? 0;
|
|
16325
|
+
totalCostUsd += cost;
|
|
16326
|
+
totalTokens += tokens;
|
|
16327
|
+
totalRuns += runs;
|
|
16328
|
+
const when = new Date(s.updatedAt || s.createdAt);
|
|
16329
|
+
if (!Number.isNaN(when.getTime())) {
|
|
16330
|
+
const bucket = buckets.get(utcDateKey(when));
|
|
16331
|
+
if (bucket) {
|
|
16332
|
+
bucket.costUsd += cost;
|
|
16333
|
+
bucket.tokens += tokens;
|
|
16334
|
+
bucket.runs += runs;
|
|
16335
|
+
}
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
const topSessions = [...sessions].filter((s) => (s.metadata?.totalCostUsd ?? 0) > 0).sort((a, b) => (b.metadata?.totalCostUsd ?? 0) - (a.metadata?.totalCostUsd ?? 0)).slice(0, topN).map((s) => ({
|
|
16339
|
+
sessionId: s.id,
|
|
16340
|
+
title: s.title,
|
|
16341
|
+
costUsd: s.metadata?.totalCostUsd ?? 0,
|
|
16342
|
+
tokens: s.metadata?.totalTokens ?? 0,
|
|
16343
|
+
runs: s.metadata?.taskCount ?? 0,
|
|
16344
|
+
updatedAt: s.updatedAt
|
|
16345
|
+
}));
|
|
16346
|
+
return {
|
|
16347
|
+
totalCostUsd,
|
|
16348
|
+
totalTokens,
|
|
16349
|
+
totalSessions: sessions.length,
|
|
16350
|
+
totalRuns,
|
|
16351
|
+
perDay: [...buckets.values()],
|
|
16352
|
+
topSessions
|
|
16353
|
+
};
|
|
16354
|
+
}
|
|
16355
|
+
|
|
16356
|
+
// src/dashboard/server.ts
|
|
16357
|
+
var __dirname$1 = path26__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
|
|
16135
16358
|
var DashboardServer = class {
|
|
16136
16359
|
app;
|
|
16137
16360
|
httpServer;
|
|
@@ -16156,6 +16379,14 @@ var DashboardServer = class {
|
|
|
16156
16379
|
* map is how that answer reaches the run that's blocked on it.
|
|
16157
16380
|
*/
|
|
16158
16381
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
16382
|
+
/**
|
|
16383
|
+
* The orchestration decision trail ("why") of each session's most recent
|
|
16384
|
+
* run — captured when the run ends so the desktop's Why panel can show it
|
|
16385
|
+
* after the fact. Bounded: oldest entry evicted past 50 sessions.
|
|
16386
|
+
*/
|
|
16387
|
+
whyBySession = /* @__PURE__ */ new Map();
|
|
16388
|
+
/** Cron scheduler for the Schedules UI — runs tasks while this server is up. */
|
|
16389
|
+
scheduler;
|
|
16159
16390
|
port;
|
|
16160
16391
|
host;
|
|
16161
16392
|
workspacePath;
|
|
@@ -16173,6 +16404,7 @@ var DashboardServer = class {
|
|
|
16173
16404
|
secret: this.dashboardSecret,
|
|
16174
16405
|
corsOrigin: config.dashboard.auth ? [`http://localhost:${this.port}`, `http://127.0.0.1:${this.port}`] : "*"
|
|
16175
16406
|
});
|
|
16407
|
+
this.scheduler = new TaskScheduler(store, (task) => this.runScheduledTask(task));
|
|
16176
16408
|
this.setupMiddleware();
|
|
16177
16409
|
this.setupRoutes();
|
|
16178
16410
|
this.socket.onSessionRate((sessionId, rating) => {
|
|
@@ -16236,6 +16468,9 @@ var DashboardServer = class {
|
|
|
16236
16468
|
cascade.on("peer:message", (e) => {
|
|
16237
16469
|
this.socket.emitPeerMessage(e);
|
|
16238
16470
|
});
|
|
16471
|
+
cascade.on("plan:approval-required", (e) => {
|
|
16472
|
+
this.socket.emitToSocket(socketId, "plan:approval-required", { sessionId, ...e });
|
|
16473
|
+
});
|
|
16239
16474
|
try {
|
|
16240
16475
|
const result = await cascade.run({
|
|
16241
16476
|
prompt: runPrompt,
|
|
@@ -16243,7 +16478,8 @@ var DashboardServer = class {
|
|
|
16243
16478
|
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
16244
16479
|
});
|
|
16245
16480
|
this.recordSessionTask(sessionId, result.taskId);
|
|
16246
|
-
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
|
|
16481
|
+
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
16482
|
+
this.captureWhy(sessionId, cascade, result);
|
|
16247
16483
|
this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
|
|
16248
16484
|
this.socket.broadcast("cost:update", {
|
|
16249
16485
|
sessionId,
|
|
@@ -16253,6 +16489,7 @@ var DashboardServer = class {
|
|
|
16253
16489
|
this.throttledBroadcast("workspace");
|
|
16254
16490
|
} catch (err) {
|
|
16255
16491
|
this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
|
|
16492
|
+
this.captureWhy(sessionId, cascade);
|
|
16256
16493
|
this.socket.emitToSocket(socketId, "session:error", {
|
|
16257
16494
|
sessionId,
|
|
16258
16495
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -16263,9 +16500,17 @@ var DashboardServer = class {
|
|
|
16263
16500
|
this.denyPendingApprovals(sessionId);
|
|
16264
16501
|
}
|
|
16265
16502
|
});
|
|
16503
|
+
this.socket.onPlanDecision(({ sessionId, approved, note, editedPlan }) => {
|
|
16504
|
+
this.activeSessions.get(sessionId)?.resolvePlanApproval(
|
|
16505
|
+
approved,
|
|
16506
|
+
note,
|
|
16507
|
+
editedPlan
|
|
16508
|
+
);
|
|
16509
|
+
});
|
|
16266
16510
|
this.socket.onSessionHalt((sessionId) => {
|
|
16267
16511
|
this.activeControllers.get(sessionId)?.abort();
|
|
16268
16512
|
this.denyPendingApprovals(sessionId);
|
|
16513
|
+
this.activeSessions.get(sessionId)?.resolvePlanApproval(false);
|
|
16269
16514
|
});
|
|
16270
16515
|
this.socket.onApprovalResponse(({ requestId, approved, always }) => {
|
|
16271
16516
|
const pending = this.pendingApprovals.get(requestId);
|
|
@@ -16298,12 +16543,21 @@ var DashboardServer = class {
|
|
|
16298
16543
|
resolve();
|
|
16299
16544
|
});
|
|
16300
16545
|
});
|
|
16546
|
+
try {
|
|
16547
|
+
this.scheduler.start();
|
|
16548
|
+
} catch (err) {
|
|
16549
|
+
console.warn("[dashboard] failed to start task scheduler:", err);
|
|
16550
|
+
}
|
|
16301
16551
|
}
|
|
16302
16552
|
async stop() {
|
|
16303
16553
|
if (this.broadcastTimer) {
|
|
16304
16554
|
clearTimeout(this.broadcastTimer);
|
|
16305
16555
|
this.broadcastTimer = null;
|
|
16306
16556
|
}
|
|
16557
|
+
try {
|
|
16558
|
+
this.scheduler.stop();
|
|
16559
|
+
} catch {
|
|
16560
|
+
}
|
|
16307
16561
|
this.socket.close();
|
|
16308
16562
|
try {
|
|
16309
16563
|
this.globalStore?.close();
|
|
@@ -16334,12 +16588,17 @@ var DashboardServer = class {
|
|
|
16334
16588
|
*/
|
|
16335
16589
|
persistConfig() {
|
|
16336
16590
|
try {
|
|
16337
|
-
const configPath =
|
|
16338
|
-
|
|
16339
|
-
|
|
16591
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
16592
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(configPath), { recursive: true });
|
|
16593
|
+
fs24__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
16340
16594
|
} catch (err) {
|
|
16341
16595
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
16342
16596
|
}
|
|
16597
|
+
try {
|
|
16598
|
+
saveGlobalCredentials(path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
16599
|
+
} catch (err) {
|
|
16600
|
+
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
16601
|
+
}
|
|
16343
16602
|
}
|
|
16344
16603
|
/**
|
|
16345
16604
|
* Produce a stable dashboard JWT signing secret.
|
|
@@ -16351,15 +16610,15 @@ var DashboardServer = class {
|
|
|
16351
16610
|
resolveDashboardSecret() {
|
|
16352
16611
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
16353
16612
|
if (fromConfig) return fromConfig;
|
|
16354
|
-
const secretPath =
|
|
16613
|
+
const secretPath = path26__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
16355
16614
|
try {
|
|
16356
|
-
if (
|
|
16357
|
-
const existing =
|
|
16615
|
+
if (fs24__default.default.existsSync(secretPath)) {
|
|
16616
|
+
const existing = fs24__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
16358
16617
|
if (existing.length >= 16) return existing;
|
|
16359
16618
|
}
|
|
16360
16619
|
const generated = crypto.randomUUID();
|
|
16361
|
-
|
|
16362
|
-
|
|
16620
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(secretPath), { recursive: true });
|
|
16621
|
+
fs24__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
16363
16622
|
if (this.config.dashboard.auth) {
|
|
16364
16623
|
console.warn(
|
|
16365
16624
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -16386,7 +16645,7 @@ var DashboardServer = class {
|
|
|
16386
16645
|
// ── Setup ─────────────────────────────────────
|
|
16387
16646
|
getGlobalStore() {
|
|
16388
16647
|
if (!this.globalStore) {
|
|
16389
|
-
const globalDbPath =
|
|
16648
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16390
16649
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
16391
16650
|
}
|
|
16392
16651
|
return this.globalStore;
|
|
@@ -16424,16 +16683,60 @@ var DashboardServer = class {
|
|
|
16424
16683
|
return title;
|
|
16425
16684
|
}
|
|
16426
16685
|
/** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
|
|
16427
|
-
persistRunEnd(sessionId, title, latestPrompt, reply, status) {
|
|
16686
|
+
persistRunEnd(sessionId, title, latestPrompt, reply, status, result) {
|
|
16428
16687
|
try {
|
|
16429
16688
|
if (reply && reply.trim()) {
|
|
16430
16689
|
this.store.addMessage({ id: crypto.randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
16431
16690
|
}
|
|
16691
|
+
if (result) {
|
|
16692
|
+
const session = this.store.getSession(sessionId);
|
|
16693
|
+
if (session) {
|
|
16694
|
+
this.store.updateSession(sessionId, {
|
|
16695
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16696
|
+
metadata: {
|
|
16697
|
+
...session.metadata,
|
|
16698
|
+
totalTokens: session.metadata.totalTokens + result.usage.totalTokens,
|
|
16699
|
+
totalCostUsd: session.metadata.totalCostUsd + result.usage.estimatedCostUsd,
|
|
16700
|
+
taskCount: session.metadata.taskCount + 1
|
|
16701
|
+
}
|
|
16702
|
+
});
|
|
16703
|
+
}
|
|
16704
|
+
}
|
|
16432
16705
|
} catch (err) {
|
|
16433
16706
|
console.warn("[dashboard] failed to persist run end:", err);
|
|
16434
16707
|
}
|
|
16435
16708
|
this.persistRuntimeRow(sessionId, title, status, latestPrompt);
|
|
16436
16709
|
}
|
|
16710
|
+
/**
|
|
16711
|
+
* Capture the run's decision trail + router economics ("why") and broadcast
|
|
16712
|
+
* it so the desktop's Why panel updates live; kept per-session for the
|
|
16713
|
+
* GET /api/sessions/:id/why fallback (panel opened after the run).
|
|
16714
|
+
*/
|
|
16715
|
+
captureWhy(sessionId, cascade, result) {
|
|
16716
|
+
try {
|
|
16717
|
+
const stats = cascade.getRouter().getStats();
|
|
16718
|
+
const savings = cascade.getRouter().getDelegationSavings();
|
|
16719
|
+
const report = {
|
|
16720
|
+
sessionId,
|
|
16721
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16722
|
+
decisions: cascade.getDecisionLog(),
|
|
16723
|
+
savedUsd: savings.savedUsd,
|
|
16724
|
+
savedPct: savings.savedPct,
|
|
16725
|
+
totalCostUsd: stats.totalCostUsd,
|
|
16726
|
+
totalTokens: stats.totalTokens,
|
|
16727
|
+
costByTier: stats.costByTier,
|
|
16728
|
+
durationMs: result?.durationMs
|
|
16729
|
+
};
|
|
16730
|
+
this.whyBySession.set(sessionId, report);
|
|
16731
|
+
if (this.whyBySession.size > 50) {
|
|
16732
|
+
const oldest = this.whyBySession.keys().next().value;
|
|
16733
|
+
if (oldest) this.whyBySession.delete(oldest);
|
|
16734
|
+
}
|
|
16735
|
+
this.socket.broadcast("run:why", report);
|
|
16736
|
+
} catch (err) {
|
|
16737
|
+
console.warn("[dashboard] failed to capture decision trail:", err);
|
|
16738
|
+
}
|
|
16739
|
+
}
|
|
16437
16740
|
/**
|
|
16438
16741
|
* Route steering text into running Cascade instances. Targets the given
|
|
16439
16742
|
* session, or every active session when none is specified (the desktop
|
|
@@ -16463,6 +16766,52 @@ var DashboardServer = class {
|
|
|
16463
16766
|
this.pendingApprovals.set(request.id, { resolve, sessionId });
|
|
16464
16767
|
});
|
|
16465
16768
|
}
|
|
16769
|
+
/**
|
|
16770
|
+
* Execute one scheduled task firing. Runs headless (like `cascade run -p`):
|
|
16771
|
+
* tool approvals are auto-granted since nobody may be watching when the cron
|
|
16772
|
+
* fires — the Schedules UI states this. Events broadcast to every connected
|
|
16773
|
+
* client so an open desktop sees the run appear live.
|
|
16774
|
+
*/
|
|
16775
|
+
async runScheduledTask(task) {
|
|
16776
|
+
const sessionId = crypto.randomUUID();
|
|
16777
|
+
const prompt = task.prompt;
|
|
16778
|
+
const title = this.persistRunStart(sessionId, `[${task.name}] ${prompt}`);
|
|
16779
|
+
const cascade = new Cascade(this.config, task.workspacePath ?? this.workspacePath, this.store);
|
|
16780
|
+
this.activeSessions.set(sessionId, cascade);
|
|
16781
|
+
cascade.on("tier:status", (e) => {
|
|
16782
|
+
this.socket.broadcast("tier:status", { sessionId, ...e });
|
|
16783
|
+
});
|
|
16784
|
+
cascade.on("peer:message", (e) => {
|
|
16785
|
+
this.socket.emitPeerMessage(e);
|
|
16786
|
+
});
|
|
16787
|
+
try {
|
|
16788
|
+
const result = await cascade.run({
|
|
16789
|
+
prompt,
|
|
16790
|
+
identityId: task.identityId,
|
|
16791
|
+
approvalCallback: async () => ({ approved: true, always: false })
|
|
16792
|
+
});
|
|
16793
|
+
this.recordSessionTask(sessionId, result.taskId);
|
|
16794
|
+
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
16795
|
+
this.captureWhy(sessionId, cascade, result);
|
|
16796
|
+
this.socket.broadcast("session:complete", { sessionId, result });
|
|
16797
|
+
this.socket.broadcast("cost:update", {
|
|
16798
|
+
sessionId,
|
|
16799
|
+
totalTokens: result.usage.totalTokens,
|
|
16800
|
+
totalCostUsd: result.usage.estimatedCostUsd
|
|
16801
|
+
});
|
|
16802
|
+
this.throttledBroadcast("workspace");
|
|
16803
|
+
} catch (err) {
|
|
16804
|
+
this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
|
|
16805
|
+
this.captureWhy(sessionId, cascade);
|
|
16806
|
+
this.socket.broadcast("session:error", {
|
|
16807
|
+
sessionId,
|
|
16808
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16809
|
+
});
|
|
16810
|
+
throw err;
|
|
16811
|
+
} finally {
|
|
16812
|
+
this.activeSessions.delete(sessionId);
|
|
16813
|
+
}
|
|
16814
|
+
}
|
|
16466
16815
|
/** Deny + clear any approvals still pending for a session (run end / abort). */
|
|
16467
16816
|
denyPendingApprovals(sessionId) {
|
|
16468
16817
|
for (const [id, pending] of this.pendingApprovals) {
|
|
@@ -16565,12 +16914,12 @@ ${prompt}`;
|
|
|
16565
16914
|
}
|
|
16566
16915
|
}
|
|
16567
16916
|
watchRuntimeChanges() {
|
|
16568
|
-
const workspaceDbPath =
|
|
16569
|
-
const globalDbPath =
|
|
16917
|
+
const workspaceDbPath = path26__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
16918
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16570
16919
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
16571
16920
|
for (const watchPath of watchPaths) {
|
|
16572
|
-
if (!
|
|
16573
|
-
|
|
16921
|
+
if (!fs24__default.default.existsSync(watchPath)) continue;
|
|
16922
|
+
fs24__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
16574
16923
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
16575
16924
|
});
|
|
16576
16925
|
}
|
|
@@ -16692,7 +17041,7 @@ ${prompt}`;
|
|
|
16692
17041
|
const sessionId = req.params.id;
|
|
16693
17042
|
this.store.deleteSession(sessionId);
|
|
16694
17043
|
this.store.deleteRuntimeSession(sessionId);
|
|
16695
|
-
const globalDbPath =
|
|
17044
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16696
17045
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16697
17046
|
try {
|
|
16698
17047
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -16785,7 +17134,7 @@ ${prompt}`;
|
|
|
16785
17134
|
});
|
|
16786
17135
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
16787
17136
|
const body = req.body;
|
|
16788
|
-
const globalDbPath =
|
|
17137
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16789
17138
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
16790
17139
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16791
17140
|
try {
|
|
@@ -16808,7 +17157,7 @@ ${prompt}`;
|
|
|
16808
17157
|
});
|
|
16809
17158
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
16810
17159
|
this.store.deleteAllRuntimeNodes();
|
|
16811
|
-
const globalDbPath =
|
|
17160
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16812
17161
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16813
17162
|
try {
|
|
16814
17163
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -16894,12 +17243,12 @@ ${prompt}`;
|
|
|
16894
17243
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
16895
17244
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
16896
17245
|
try {
|
|
16897
|
-
const configPath =
|
|
16898
|
-
const existing =
|
|
17246
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
17247
|
+
const existing = fs24__default.default.existsSync(configPath) ? JSON.parse(fs24__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
16899
17248
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
16900
17249
|
const tmp = configPath + ".tmp";
|
|
16901
|
-
|
|
16902
|
-
|
|
17250
|
+
fs24__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
17251
|
+
fs24__default.default.renameSync(tmp, configPath);
|
|
16903
17252
|
res.json({ ok: true });
|
|
16904
17253
|
} catch (err) {
|
|
16905
17254
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -16927,7 +17276,7 @@ ${prompt}`;
|
|
|
16927
17276
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
16928
17277
|
const scope = req.query["scope"] ?? "workspace";
|
|
16929
17278
|
if (scope === "global") {
|
|
16930
|
-
const globalDbPath =
|
|
17279
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16931
17280
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16932
17281
|
try {
|
|
16933
17282
|
res.json({
|
|
@@ -16975,6 +17324,9 @@ ${prompt}`;
|
|
|
16975
17324
|
cascade.on("peer:message", (e) => {
|
|
16976
17325
|
this.socket.emitPeerMessage(e);
|
|
16977
17326
|
});
|
|
17327
|
+
cascade.on("plan:approval-required", (e) => {
|
|
17328
|
+
this.socket.broadcastToRoom(`session:${sessionId}`, "plan:approval-required", { sessionId, ...e });
|
|
17329
|
+
});
|
|
16978
17330
|
try {
|
|
16979
17331
|
const result = await cascade.run({
|
|
16980
17332
|
prompt: runPrompt,
|
|
@@ -16982,7 +17334,8 @@ ${prompt}`;
|
|
|
16982
17334
|
approvalCallback: this.makeApprovalCallback(sessionId)
|
|
16983
17335
|
});
|
|
16984
17336
|
this.recordSessionTask(sessionId, result.taskId);
|
|
16985
|
-
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
|
|
17337
|
+
this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
|
|
17338
|
+
this.captureWhy(sessionId, cascade, result);
|
|
16986
17339
|
this.socket.broadcast("cost:update", {
|
|
16987
17340
|
sessionId,
|
|
16988
17341
|
totalTokens: result.usage.totalTokens,
|
|
@@ -16992,6 +17345,7 @@ ${prompt}`;
|
|
|
16992
17345
|
this.throttledBroadcast("workspace");
|
|
16993
17346
|
} catch (err) {
|
|
16994
17347
|
this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
|
|
17348
|
+
this.captureWhy(sessionId, cascade);
|
|
16995
17349
|
this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
|
|
16996
17350
|
sessionId,
|
|
16997
17351
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -17013,13 +17367,217 @@ ${prompt}`;
|
|
|
17013
17367
|
}))
|
|
17014
17368
|
});
|
|
17015
17369
|
});
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17370
|
+
this.app.get("/api/sessions/:id/why", auth, (req, res) => {
|
|
17371
|
+
const report = this.whyBySession.get(req.params.id);
|
|
17372
|
+
if (!report) {
|
|
17373
|
+
res.status(404).json({ error: "No decision trail recorded for this session in the current app run." });
|
|
17374
|
+
return;
|
|
17375
|
+
}
|
|
17376
|
+
res.json(report);
|
|
17377
|
+
});
|
|
17378
|
+
this.app.get("/api/sessions/:id/changes", auth, async (req, res) => {
|
|
17379
|
+
const sessionId = req.params.id;
|
|
17380
|
+
const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
|
|
17381
|
+
const before = /* @__PURE__ */ new Map();
|
|
17382
|
+
for (const taskId of taskIds) {
|
|
17383
|
+
for (const { filePath, content } of this.store.getLatestFileSnapshots(taskId)) {
|
|
17384
|
+
if (!before.has(filePath)) before.set(filePath, content);
|
|
17385
|
+
}
|
|
17386
|
+
}
|
|
17387
|
+
const { readFile } = await import('fs/promises');
|
|
17388
|
+
const MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
17389
|
+
const changes = await Promise.all([...before.entries()].map(async ([filePath, beforeContent]) => {
|
|
17390
|
+
let after = "";
|
|
17391
|
+
let missing = false;
|
|
17392
|
+
try {
|
|
17393
|
+
const buf = await readFile(filePath);
|
|
17394
|
+
after = buf.length > MAX_DIFF_BYTES ? `[file too large to diff \u2014 ${buf.length} bytes]` : buf.toString("utf-8");
|
|
17395
|
+
} catch {
|
|
17396
|
+
missing = true;
|
|
17397
|
+
}
|
|
17398
|
+
return { filePath, before: beforeContent, after, missing, changed: missing || after !== beforeContent };
|
|
17399
|
+
}));
|
|
17400
|
+
res.json({ sessionId, changes: changes.filter((c) => c.changed) });
|
|
17401
|
+
});
|
|
17402
|
+
this.app.post("/api/sessions/:id/revert-file", auth, mutationLimiter, async (req, res) => {
|
|
17403
|
+
const sessionId = req.params.id;
|
|
17404
|
+
const body = req.body;
|
|
17405
|
+
if (!body.filePath || typeof body.filePath !== "string") {
|
|
17406
|
+
res.status(400).json({ error: "filePath is required" });
|
|
17407
|
+
return;
|
|
17408
|
+
}
|
|
17409
|
+
const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
|
|
17410
|
+
let content;
|
|
17411
|
+
for (const taskId of taskIds) {
|
|
17412
|
+
const snap = this.store.getLatestFileSnapshots(taskId).find((s) => s.filePath === body.filePath);
|
|
17413
|
+
if (snap) {
|
|
17414
|
+
content = snap.content;
|
|
17415
|
+
break;
|
|
17416
|
+
}
|
|
17417
|
+
}
|
|
17418
|
+
if (content === void 0) {
|
|
17419
|
+
res.status(404).json({ error: "No snapshot recorded for that file in this session." });
|
|
17420
|
+
return;
|
|
17421
|
+
}
|
|
17422
|
+
try {
|
|
17423
|
+
const { writeFile } = await import('fs/promises');
|
|
17424
|
+
await writeFile(body.filePath, content, "utf-8");
|
|
17425
|
+
res.json({ ok: true, filePath: body.filePath });
|
|
17426
|
+
} catch (err) {
|
|
17427
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17428
|
+
}
|
|
17429
|
+
});
|
|
17430
|
+
this.app.get("/api/costs", auth, (_req, res) => {
|
|
17431
|
+
try {
|
|
17432
|
+
const sessions = this.store.listSessions(void 0, 1e3);
|
|
17433
|
+
const budget = this.config.budget ?? { warnAtPct: 80 };
|
|
17434
|
+
res.json({
|
|
17435
|
+
...aggregateCostStats(sessions, { days: 30, topN: 8 }),
|
|
17436
|
+
budget: {
|
|
17437
|
+
dailyBudgetUsd: budget.dailyBudgetUsd,
|
|
17438
|
+
sessionBudgetUsd: budget.sessionBudgetUsd,
|
|
17439
|
+
maxCostPerRunUsd: budget.maxCostPerRunUsd
|
|
17440
|
+
}
|
|
17441
|
+
});
|
|
17442
|
+
} catch (err) {
|
|
17443
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17444
|
+
}
|
|
17445
|
+
});
|
|
17446
|
+
this.app.get("/api/schedules", auth, (_req, res) => {
|
|
17447
|
+
try {
|
|
17448
|
+
res.json(this.scheduler.list().map((t) => ({ ...t, armed: this.scheduler.isRunning(t.id) })));
|
|
17449
|
+
} catch (err) {
|
|
17450
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17451
|
+
}
|
|
17452
|
+
});
|
|
17453
|
+
this.app.post("/api/schedules", auth, mutationLimiter, (req, res) => {
|
|
17454
|
+
const body = req.body;
|
|
17455
|
+
if (!body.name?.trim() || !body.cronExpression?.trim() || !body.prompt?.trim()) {
|
|
17456
|
+
res.status(400).json({ error: "name, cronExpression, and prompt are required" });
|
|
17457
|
+
return;
|
|
17458
|
+
}
|
|
17459
|
+
if (!TaskScheduler.validateCron(body.cronExpression.trim())) {
|
|
17460
|
+
res.status(400).json({ error: `Invalid cron expression: ${body.cronExpression}` });
|
|
17461
|
+
return;
|
|
17462
|
+
}
|
|
17463
|
+
const task = {
|
|
17464
|
+
id: crypto.randomUUID(),
|
|
17465
|
+
name: body.name.trim(),
|
|
17466
|
+
cronExpression: body.cronExpression.trim(),
|
|
17467
|
+
prompt: body.prompt.trim(),
|
|
17468
|
+
workspacePath: this.workspacePath,
|
|
17469
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17470
|
+
enabled: body.enabled !== false
|
|
17471
|
+
};
|
|
17472
|
+
try {
|
|
17473
|
+
this.scheduler.add(task);
|
|
17474
|
+
res.json(task);
|
|
17475
|
+
} catch (err) {
|
|
17476
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17477
|
+
}
|
|
17478
|
+
});
|
|
17479
|
+
this.app.put("/api/schedules/:id", auth, mutationLimiter, (req, res) => {
|
|
17480
|
+
const id = req.params.id;
|
|
17481
|
+
const existing = this.scheduler.list().find((t) => t.id === id);
|
|
17482
|
+
if (!existing) {
|
|
17483
|
+
res.status(404).json({ error: "Not found" });
|
|
17484
|
+
return;
|
|
17485
|
+
}
|
|
17486
|
+
const body = req.body;
|
|
17487
|
+
if (body.cronExpression !== void 0 && !TaskScheduler.validateCron(body.cronExpression)) {
|
|
17488
|
+
res.status(400).json({ error: `Invalid cron expression: ${body.cronExpression}` });
|
|
17489
|
+
return;
|
|
17490
|
+
}
|
|
17491
|
+
const updated = {
|
|
17492
|
+
...existing,
|
|
17493
|
+
name: body.name?.trim() || existing.name,
|
|
17494
|
+
cronExpression: body.cronExpression?.trim() || existing.cronExpression,
|
|
17495
|
+
prompt: body.prompt?.trim() || existing.prompt,
|
|
17496
|
+
enabled: body.enabled ?? existing.enabled
|
|
17497
|
+
};
|
|
17498
|
+
try {
|
|
17499
|
+
this.scheduler.add(updated);
|
|
17500
|
+
if (!updated.enabled) this.scheduler.unschedule(id);
|
|
17501
|
+
res.json(updated);
|
|
17502
|
+
} catch (err) {
|
|
17503
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17504
|
+
}
|
|
17505
|
+
});
|
|
17506
|
+
this.app.delete("/api/schedules/:id", auth, mutationLimiter, (req, res) => {
|
|
17507
|
+
try {
|
|
17508
|
+
this.scheduler.remove(req.params.id);
|
|
17509
|
+
res.json({ ok: true });
|
|
17510
|
+
} catch (err) {
|
|
17511
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17512
|
+
}
|
|
17513
|
+
});
|
|
17514
|
+
this.app.get("/api/knowledge", auth, (_req, res) => {
|
|
17515
|
+
try {
|
|
17516
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17517
|
+
try {
|
|
17518
|
+
const facts = ws.getAllFacts();
|
|
17519
|
+
res.json({ total: facts.length, facts });
|
|
17520
|
+
} finally {
|
|
17521
|
+
ws.close();
|
|
17522
|
+
}
|
|
17523
|
+
} catch (err) {
|
|
17524
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17525
|
+
}
|
|
17526
|
+
});
|
|
17527
|
+
this.app.delete("/api/knowledge/fact", auth, mutationLimiter, (req, res) => {
|
|
17528
|
+
const body = req.body;
|
|
17529
|
+
if (!body.entity || !body.relation) {
|
|
17530
|
+
res.status(400).json({ error: "entity and relation are required" });
|
|
17531
|
+
return;
|
|
17532
|
+
}
|
|
17533
|
+
try {
|
|
17534
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17535
|
+
try {
|
|
17536
|
+
res.json({ ok: true, deleted: ws.deleteFact(body.entity, body.relation) });
|
|
17537
|
+
} finally {
|
|
17538
|
+
ws.close();
|
|
17539
|
+
}
|
|
17540
|
+
} catch (err) {
|
|
17541
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17542
|
+
}
|
|
17543
|
+
});
|
|
17544
|
+
this.app.delete("/api/knowledge", auth, mutationLimiter, (_req, res) => {
|
|
17545
|
+
try {
|
|
17546
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17547
|
+
try {
|
|
17548
|
+
res.json({ ok: true, deleted: ws.clearFacts() });
|
|
17549
|
+
} finally {
|
|
17550
|
+
ws.close();
|
|
17551
|
+
}
|
|
17552
|
+
} catch (err) {
|
|
17553
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17554
|
+
}
|
|
17555
|
+
});
|
|
17556
|
+
this.app.get("/api/audit-chain", auth, async (req, res) => {
|
|
17557
|
+
try {
|
|
17558
|
+
const limit = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
|
|
17559
|
+
const offset = Math.max(0, parseInt(req.query["offset"] || "0", 10) || 0);
|
|
17560
|
+
const { AuditLogger: AuditLogger3 } = await Promise.resolve().then(() => (init_audit_logger(), audit_logger_exports));
|
|
17561
|
+
const logger = new AuditLogger3(this.workspacePath);
|
|
17562
|
+
try {
|
|
17563
|
+
const all = logger.getAllLogs();
|
|
17564
|
+
const total = all.length;
|
|
17565
|
+
const entries = all.reverse().slice(offset, offset + limit);
|
|
17566
|
+
res.json({ total, offset, entries });
|
|
17567
|
+
} finally {
|
|
17568
|
+
logger.close();
|
|
17569
|
+
}
|
|
17570
|
+
} catch (err) {
|
|
17571
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17572
|
+
}
|
|
17573
|
+
});
|
|
17574
|
+
const prodPath = path26__default.default.resolve(__dirname$1, "../web/dist");
|
|
17575
|
+
const devPath = path26__default.default.resolve(__dirname$1, "../../web/dist");
|
|
17576
|
+
const webDistPath = fs24__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
17577
|
+
if (fs24__default.default.existsSync(webDistPath)) {
|
|
17020
17578
|
this.app.use(express__default.default.static(webDistPath));
|
|
17021
17579
|
this.app.get("*", (_req, res) => {
|
|
17022
|
-
res.sendFile(
|
|
17580
|
+
res.sendFile(path26__default.default.join(webDistPath, "index.html"));
|
|
17023
17581
|
});
|
|
17024
17582
|
} else {
|
|
17025
17583
|
this.app.get("/", (_req, res) => {
|
|
@@ -17040,7 +17598,7 @@ init_constants();
|
|
|
17040
17598
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
17041
17599
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
17042
17600
|
const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
17043
|
-
const store = new MemoryStore(
|
|
17601
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17044
17602
|
const server = new DashboardServer(config, store, workspacePath);
|
|
17045
17603
|
server.watchRuntimeChanges();
|
|
17046
17604
|
const gThis = globalThis;
|
|
@@ -17072,7 +17630,7 @@ init_constants();
|
|
|
17072
17630
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
17073
17631
|
const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
|
|
17074
17632
|
identity.command("list").description("List all available identities").action(() => {
|
|
17075
|
-
const store = new MemoryStore(
|
|
17633
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17076
17634
|
try {
|
|
17077
17635
|
const identities = store.listIdentities();
|
|
17078
17636
|
console.log(chalk9__default.default.bold("\n Identities:"));
|
|
@@ -17092,7 +17650,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
17092
17650
|
}
|
|
17093
17651
|
});
|
|
17094
17652
|
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) => {
|
|
17095
|
-
const store = new MemoryStore(
|
|
17653
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17096
17654
|
try {
|
|
17097
17655
|
if (options.default) {
|
|
17098
17656
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -17118,7 +17676,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
17118
17676
|
}
|
|
17119
17677
|
});
|
|
17120
17678
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
17121
|
-
const store = new MemoryStore(
|
|
17679
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17122
17680
|
try {
|
|
17123
17681
|
const identities = store.listIdentities();
|
|
17124
17682
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
@@ -17246,11 +17804,11 @@ async function exportCommand(options = {}) {
|
|
|
17246
17804
|
let store;
|
|
17247
17805
|
try {
|
|
17248
17806
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
17249
|
-
const workspaceDbPath =
|
|
17250
|
-
const globalDbPath =
|
|
17807
|
+
const workspaceDbPath = path26__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
17808
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
17251
17809
|
let dbPath = globalDbPath;
|
|
17252
17810
|
try {
|
|
17253
|
-
await
|
|
17811
|
+
await fs10__default.default.access(workspaceDbPath);
|
|
17254
17812
|
dbPath = workspaceDbPath;
|
|
17255
17813
|
} catch {
|
|
17256
17814
|
}
|
|
@@ -17289,8 +17847,8 @@ async function exportCommand(options = {}) {
|
|
|
17289
17847
|
const ext = format === "json" ? ".json" : ".md";
|
|
17290
17848
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
17291
17849
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
17292
|
-
const outPath = options.output ?
|
|
17293
|
-
await
|
|
17850
|
+
const outPath = options.output ? path26__default.default.resolve(options.output) : path26__default.default.join(process.cwd(), defaultFile);
|
|
17851
|
+
await fs10__default.default.writeFile(outPath, content, "utf-8");
|
|
17294
17852
|
spin.succeed(chalk9__default.default.green(`Exported to ${chalk9__default.default.white(outPath)}`));
|
|
17295
17853
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
17296
17854
|
console.log();
|
|
@@ -17517,11 +18075,11 @@ async function statsCommand() {
|
|
|
17517
18075
|
dotenv__default.default.config();
|
|
17518
18076
|
function warnIfBuildIsStale() {
|
|
17519
18077
|
try {
|
|
17520
|
-
const here =
|
|
18078
|
+
const here = path26__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))));
|
|
17521
18079
|
for (const rel of ["..", "../.."]) {
|
|
17522
|
-
const pkgPath =
|
|
17523
|
-
if (!
|
|
17524
|
-
const pkg = JSON.parse(
|
|
18080
|
+
const pkgPath = path26__default.default.join(here, rel, "package.json");
|
|
18081
|
+
if (!fs24__default.default.existsSync(pkgPath)) continue;
|
|
18082
|
+
const pkg = JSON.parse(fs24__default.default.readFileSync(pkgPath, "utf-8"));
|
|
17525
18083
|
if (pkg.name !== "cascade-ai") continue;
|
|
17526
18084
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
17527
18085
|
console.error(
|