cascade-ai 0.17.0 → 0.19.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 +499 -232
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +496 -229
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +1066 -802
- package/dist/index.cjs +422 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +420 -157
- 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');
|
|
@@ -73,14 +73,14 @@ var http__default = /*#__PURE__*/_interopDefault(http);
|
|
|
73
73
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
74
74
|
var zlib__default = /*#__PURE__*/_interopDefault(zlib);
|
|
75
75
|
var Database2__default = /*#__PURE__*/_interopDefault(Database2);
|
|
76
|
-
var
|
|
77
|
-
var
|
|
76
|
+
var path26__default = /*#__PURE__*/_interopDefault(path26);
|
|
77
|
+
var fs24__default = /*#__PURE__*/_interopDefault(fs24);
|
|
78
78
|
var os8__default = /*#__PURE__*/_interopDefault(os8);
|
|
79
79
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
80
80
|
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
81
81
|
var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
|
|
82
82
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
83
|
-
var
|
|
83
|
+
var fs10__default = /*#__PURE__*/_interopDefault(fs10);
|
|
84
84
|
var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
|
|
85
85
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
86
86
|
var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
|
|
@@ -108,16 +108,17 @@ var __export = (target, all) => {
|
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
// src/constants.ts
|
|
111
|
-
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;
|
|
112
112
|
var init_constants = __esm({
|
|
113
113
|
"src/constants.ts"() {
|
|
114
|
-
CASCADE_VERSION = "0.
|
|
114
|
+
CASCADE_VERSION = "0.19.0";
|
|
115
115
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
116
116
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
117
117
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
118
118
|
GLOBAL_CONFIG_DIR = ".cascade-ai";
|
|
119
119
|
GLOBAL_DB_FILE = "memory.db";
|
|
120
120
|
GLOBAL_KEYSTORE_FILE = "keystore.enc";
|
|
121
|
+
GLOBAL_CREDENTIALS_FILE = "credentials.json";
|
|
121
122
|
GLOBAL_RUNTIME_DB_FILE = "runtime.db";
|
|
122
123
|
DEFAULT_DASHBOARD_PORT = 4891;
|
|
123
124
|
DEFAULT_CONTEXT_LIMIT = 2e5;
|
|
@@ -936,8 +937,26 @@ var init_openai = __esm({
|
|
|
936
937
|
// src/providers/azure.ts
|
|
937
938
|
var azure_exports = {};
|
|
938
939
|
__export(azure_exports, {
|
|
939
|
-
AzureOpenAIProvider: () => AzureOpenAIProvider
|
|
940
|
+
AzureOpenAIProvider: () => AzureOpenAIProvider,
|
|
941
|
+
azureModelForDeployment: () => azureModelForDeployment
|
|
940
942
|
});
|
|
943
|
+
function azureModelForDeployment(cfg) {
|
|
944
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
945
|
+
const id = cfg.deploymentName.trim();
|
|
946
|
+
return {
|
|
947
|
+
id,
|
|
948
|
+
name: cfg.label?.trim() || id,
|
|
949
|
+
provider: "azure",
|
|
950
|
+
contextWindow: 128e3,
|
|
951
|
+
isVisionCapable: false,
|
|
952
|
+
inputCostPer1kTokens: 25e-4,
|
|
953
|
+
outputCostPer1kTokens: 0.01,
|
|
954
|
+
maxOutputTokens: 16e3,
|
|
955
|
+
supportsStreaming: true,
|
|
956
|
+
isLocal: false,
|
|
957
|
+
supportsToolUse: true
|
|
958
|
+
};
|
|
959
|
+
}
|
|
941
960
|
var AzureOpenAIProvider;
|
|
942
961
|
var init_azure = __esm({
|
|
943
962
|
"src/providers/azure.ts"() {
|
|
@@ -963,7 +982,8 @@ var init_azure = __esm({
|
|
|
963
982
|
});
|
|
964
983
|
}
|
|
965
984
|
async listModels() {
|
|
966
|
-
|
|
985
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
986
|
+
return [fromDeployment ?? this.model];
|
|
967
987
|
}
|
|
968
988
|
async isAvailable() {
|
|
969
989
|
try {
|
|
@@ -1632,12 +1652,12 @@ var init_audit_logger = __esm({
|
|
|
1632
1652
|
constructor(workspacePath, debugMode = false) {
|
|
1633
1653
|
this.workspacePath = workspacePath;
|
|
1634
1654
|
this.debugMode = debugMode;
|
|
1635
|
-
const cascadeDir =
|
|
1636
|
-
if (!
|
|
1637
|
-
|
|
1655
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
1656
|
+
if (!fs24__default.default.existsSync(cascadeDir)) {
|
|
1657
|
+
fs24__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
1638
1658
|
}
|
|
1639
|
-
this.keyPath =
|
|
1640
|
-
this.dbPath =
|
|
1659
|
+
this.keyPath = path26__default.default.join(cascadeDir, "audit_log.key");
|
|
1660
|
+
this.dbPath = path26__default.default.join(cascadeDir, "audit_log.db");
|
|
1641
1661
|
this.initEncryptionKey();
|
|
1642
1662
|
this.db = new Database2__default.default(this.dbPath);
|
|
1643
1663
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -1667,11 +1687,11 @@ var init_audit_logger = __esm({
|
|
|
1667
1687
|
return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
1668
1688
|
}
|
|
1669
1689
|
initEncryptionKey() {
|
|
1670
|
-
if (
|
|
1671
|
-
this.encryptionKey =
|
|
1690
|
+
if (fs24__default.default.existsSync(this.keyPath)) {
|
|
1691
|
+
this.encryptionKey = fs24__default.default.readFileSync(this.keyPath);
|
|
1672
1692
|
} else {
|
|
1673
1693
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
1674
|
-
|
|
1694
|
+
fs24__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
1675
1695
|
}
|
|
1676
1696
|
}
|
|
1677
1697
|
encrypt(text) {
|
|
@@ -1749,9 +1769,9 @@ var init_audit_logger = __esm({
|
|
|
1749
1769
|
dumpDebugIfNeeded() {
|
|
1750
1770
|
if (!this.debugMode) return;
|
|
1751
1771
|
try {
|
|
1752
|
-
const dumpPath =
|
|
1772
|
+
const dumpPath = path26__default.default.join(os8__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
1753
1773
|
const entries = this.getAllLogs();
|
|
1754
|
-
|
|
1774
|
+
fs24__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
1755
1775
|
} catch (err) {
|
|
1756
1776
|
console.error("Failed to dump debug audit logs", err);
|
|
1757
1777
|
}
|
|
@@ -1806,7 +1826,7 @@ var Keystore = class {
|
|
|
1806
1826
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
1807
1827
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
1808
1828
|
this.backend = "keytar";
|
|
1809
|
-
if (password &&
|
|
1829
|
+
if (password && fs24__default.default.existsSync(this.storePath)) {
|
|
1810
1830
|
try {
|
|
1811
1831
|
const fileEntries = this.decryptFile(password);
|
|
1812
1832
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -1825,7 +1845,7 @@ var Keystore = class {
|
|
|
1825
1845
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
1826
1846
|
);
|
|
1827
1847
|
}
|
|
1828
|
-
if (!
|
|
1848
|
+
if (!fs24__default.default.existsSync(this.storePath)) {
|
|
1829
1849
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
1830
1850
|
this.masterKey = this.deriveKey(password, salt);
|
|
1831
1851
|
this.writeWithSalt({}, salt);
|
|
@@ -1839,7 +1859,7 @@ var Keystore = class {
|
|
|
1839
1859
|
}
|
|
1840
1860
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
1841
1861
|
unlockSync(password) {
|
|
1842
|
-
if (!
|
|
1862
|
+
if (!fs24__default.default.existsSync(this.storePath)) {
|
|
1843
1863
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
1844
1864
|
this.masterKey = this.deriveKey(password, salt);
|
|
1845
1865
|
this.writeWithSalt({}, salt);
|
|
@@ -1897,7 +1917,7 @@ var Keystore = class {
|
|
|
1897
1917
|
}
|
|
1898
1918
|
}
|
|
1899
1919
|
decryptFile(password, knownSalt) {
|
|
1900
|
-
if (!
|
|
1920
|
+
if (!fs24__default.default.existsSync(this.storePath)) return {};
|
|
1901
1921
|
try {
|
|
1902
1922
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
1903
1923
|
const useSalt = knownSalt ?? salt;
|
|
@@ -1919,8 +1939,8 @@ var Keystore = class {
|
|
|
1919
1939
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1920
1940
|
const tag = cipher.getAuthTag();
|
|
1921
1941
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
1922
|
-
|
|
1923
|
-
|
|
1942
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
1943
|
+
fs24__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
1924
1944
|
}
|
|
1925
1945
|
writeWithSalt(data, salt) {
|
|
1926
1946
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -1930,11 +1950,11 @@ var Keystore = class {
|
|
|
1930
1950
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1931
1951
|
const tag = cipher.getAuthTag();
|
|
1932
1952
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
1933
|
-
|
|
1934
|
-
|
|
1953
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
1954
|
+
fs24__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
1935
1955
|
}
|
|
1936
1956
|
readRaw() {
|
|
1937
|
-
const buf =
|
|
1957
|
+
const buf = fs24__default.default.readFileSync(this.storePath);
|
|
1938
1958
|
let offset = 0;
|
|
1939
1959
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
1940
1960
|
offset += SALT_LEN;
|
|
@@ -1967,9 +1987,9 @@ var CascadeIgnore = class {
|
|
|
1967
1987
|
]);
|
|
1968
1988
|
}
|
|
1969
1989
|
async load(workspacePath) {
|
|
1970
|
-
const filePath =
|
|
1990
|
+
const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
1971
1991
|
try {
|
|
1972
|
-
const content = await
|
|
1992
|
+
const content = await fs10__default.default.readFile(filePath, "utf-8");
|
|
1973
1993
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
1974
1994
|
this.ig.add(lines);
|
|
1975
1995
|
this.loaded = true;
|
|
@@ -1978,7 +1998,7 @@ var CascadeIgnore = class {
|
|
|
1978
1998
|
}
|
|
1979
1999
|
isIgnored(filePath, workspacePath) {
|
|
1980
2000
|
try {
|
|
1981
|
-
const relative = workspacePath ?
|
|
2001
|
+
const relative = workspacePath ? path26__default.default.relative(workspacePath, filePath) : filePath;
|
|
1982
2002
|
return this.ig.ignores(relative);
|
|
1983
2003
|
} catch {
|
|
1984
2004
|
return false;
|
|
@@ -1989,7 +2009,7 @@ var CascadeIgnore = class {
|
|
|
1989
2009
|
}
|
|
1990
2010
|
};
|
|
1991
2011
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
1992
|
-
const filePath =
|
|
2012
|
+
const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
1993
2013
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
1994
2014
|
# Syntax identical to .gitignore
|
|
1995
2015
|
|
|
@@ -2016,12 +2036,12 @@ build/
|
|
|
2016
2036
|
.DS_Store
|
|
2017
2037
|
Thumbs.db
|
|
2018
2038
|
`;
|
|
2019
|
-
await
|
|
2039
|
+
await fs10__default.default.writeFile(filePath, content, "utf-8");
|
|
2020
2040
|
}
|
|
2021
2041
|
async function loadCascadeMd(workspacePath) {
|
|
2022
|
-
const filePath =
|
|
2042
|
+
const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
2023
2043
|
try {
|
|
2024
|
-
const raw = await
|
|
2044
|
+
const raw = await fs10__default.default.readFile(filePath, "utf-8");
|
|
2025
2045
|
return parseCascadeMd(raw);
|
|
2026
2046
|
} catch {
|
|
2027
2047
|
return null;
|
|
@@ -2048,7 +2068,7 @@ ${raw.trim()}`;
|
|
|
2048
2068
|
return { raw, sections, systemPrompt };
|
|
2049
2069
|
}
|
|
2050
2070
|
async function createDefaultCascadeMd(workspacePath) {
|
|
2051
|
-
const filePath =
|
|
2071
|
+
const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
2052
2072
|
const content = `# Cascade Project Instructions
|
|
2053
2073
|
|
|
2054
2074
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -2078,12 +2098,12 @@ All tools are allowed unless specified otherwise.
|
|
|
2078
2098
|
|
|
2079
2099
|
List any areas the agent should not touch.
|
|
2080
2100
|
`;
|
|
2081
|
-
await
|
|
2101
|
+
await fs10__default.default.writeFile(filePath, content, "utf-8");
|
|
2082
2102
|
}
|
|
2083
2103
|
var MemoryStore = class _MemoryStore {
|
|
2084
2104
|
db;
|
|
2085
2105
|
constructor(dbPath) {
|
|
2086
|
-
|
|
2106
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(dbPath), { recursive: true });
|
|
2087
2107
|
try {
|
|
2088
2108
|
this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
|
|
2089
2109
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3048,8 +3068,11 @@ var CascadeConfigSchema = zod.z.object({
|
|
|
3048
3068
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
3049
3069
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
3050
3070
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
3071
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
3072
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
3073
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
3051
3074
|
*/
|
|
3052
|
-
cascadeAuto: zod.z.boolean().default(
|
|
3075
|
+
cascadeAuto: zod.z.boolean().default(true),
|
|
3053
3076
|
/**
|
|
3054
3077
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
3055
3078
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -3230,6 +3253,63 @@ function validateConfig(raw) {
|
|
|
3230
3253
|
return result.data;
|
|
3231
3254
|
}
|
|
3232
3255
|
|
|
3256
|
+
// src/config/global-credentials.ts
|
|
3257
|
+
init_constants();
|
|
3258
|
+
function credentialsPath(globalDir) {
|
|
3259
|
+
return path26__default.default.join(globalDir, GLOBAL_CREDENTIALS_FILE);
|
|
3260
|
+
}
|
|
3261
|
+
function providerKey(p) {
|
|
3262
|
+
if (p.type === "azure") {
|
|
3263
|
+
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
3264
|
+
}
|
|
3265
|
+
return p.type;
|
|
3266
|
+
}
|
|
3267
|
+
function isPersistable(p) {
|
|
3268
|
+
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
3269
|
+
}
|
|
3270
|
+
function loadGlobalCredentials(globalDir) {
|
|
3271
|
+
try {
|
|
3272
|
+
const raw = fs24__default.default.readFileSync(credentialsPath(globalDir), "utf-8");
|
|
3273
|
+
const parsed = JSON.parse(raw);
|
|
3274
|
+
if (!Array.isArray(parsed.providers)) return [];
|
|
3275
|
+
return parsed.providers.filter(
|
|
3276
|
+
(p) => Boolean(p) && typeof p.type === "string"
|
|
3277
|
+
);
|
|
3278
|
+
} catch {
|
|
3279
|
+
return [];
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
function saveGlobalCredentials(globalDir, providers) {
|
|
3283
|
+
const filePath = credentialsPath(globalDir);
|
|
3284
|
+
fs24__default.default.mkdirSync(globalDir, { recursive: true, mode: 448 });
|
|
3285
|
+
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
3286
|
+
const tmp = `${filePath}.tmp`;
|
|
3287
|
+
fs24__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3288
|
+
fs24__default.default.renameSync(tmp, filePath);
|
|
3289
|
+
try {
|
|
3290
|
+
fs24__default.default.chmodSync(filePath, 384);
|
|
3291
|
+
} catch {
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
3295
|
+
const merged = [...workspaceProviders];
|
|
3296
|
+
const byKey = new Map(merged.map((p) => [providerKey(p), p]));
|
|
3297
|
+
for (const g of globalProviders) {
|
|
3298
|
+
const existing = byKey.get(providerKey(g));
|
|
3299
|
+
if (!existing) {
|
|
3300
|
+
merged.push({ ...g });
|
|
3301
|
+
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
3302
|
+
continue;
|
|
3303
|
+
}
|
|
3304
|
+
if (!existing.apiKey && g.apiKey) existing.apiKey = g.apiKey;
|
|
3305
|
+
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
3306
|
+
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
3307
|
+
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
3308
|
+
if (!existing.label && g.label) existing.label = g.label;
|
|
3309
|
+
}
|
|
3310
|
+
return merged;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3233
3313
|
// src/config/index.ts
|
|
3234
3314
|
init_constants();
|
|
3235
3315
|
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
@@ -3245,18 +3325,20 @@ var ConfigManager = class {
|
|
|
3245
3325
|
cascadeMd = null;
|
|
3246
3326
|
workspacePath;
|
|
3247
3327
|
globalDir;
|
|
3248
|
-
|
|
3328
|
+
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
3329
|
+
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
3249
3330
|
this.workspacePath = workspacePath;
|
|
3250
|
-
this.globalDir =
|
|
3331
|
+
this.globalDir = globalDirOverride ?? path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
3251
3332
|
}
|
|
3252
3333
|
async load() {
|
|
3253
3334
|
this.config = await this.loadConfig();
|
|
3254
3335
|
this.ignore = new CascadeIgnore();
|
|
3255
3336
|
await this.ignore.load(this.workspacePath);
|
|
3256
3337
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
3257
|
-
this.keystore = new Keystore(
|
|
3258
|
-
this.store = new MemoryStore(
|
|
3338
|
+
this.keystore = new Keystore(path26__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
3339
|
+
this.store = new MemoryStore(path26__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
3259
3340
|
await this.injectEnvKeys();
|
|
3341
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
3260
3342
|
await this.ensureDefaultIdentity();
|
|
3261
3343
|
}
|
|
3262
3344
|
getConfig() {
|
|
@@ -3278,9 +3360,14 @@ var ConfigManager = class {
|
|
|
3278
3360
|
return this.workspacePath;
|
|
3279
3361
|
}
|
|
3280
3362
|
async save() {
|
|
3281
|
-
const configPath =
|
|
3282
|
-
await
|
|
3283
|
-
await
|
|
3363
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
3364
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(configPath), { recursive: true });
|
|
3365
|
+
await fs10__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
3366
|
+
try {
|
|
3367
|
+
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
3368
|
+
} catch (err) {
|
|
3369
|
+
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
3370
|
+
}
|
|
3284
3371
|
}
|
|
3285
3372
|
async updateConfig(updates) {
|
|
3286
3373
|
this.config = validateConfig({ ...this.config, ...updates });
|
|
@@ -3303,9 +3390,9 @@ var ConfigManager = class {
|
|
|
3303
3390
|
return configProvider?.apiKey;
|
|
3304
3391
|
}
|
|
3305
3392
|
async loadConfig() {
|
|
3306
|
-
const configPath =
|
|
3393
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
3307
3394
|
try {
|
|
3308
|
-
const raw = await
|
|
3395
|
+
const raw = await fs10__default.default.readFile(configPath, "utf-8");
|
|
3309
3396
|
return validateConfig(JSON.parse(raw));
|
|
3310
3397
|
} catch (err) {
|
|
3311
3398
|
if (err.code === "ENOENT") {
|
|
@@ -4182,7 +4269,7 @@ init_constants();
|
|
|
4182
4269
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4183
4270
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4184
4271
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4185
|
-
var DEFAULT_CACHE_FILE =
|
|
4272
|
+
var DEFAULT_CACHE_FILE = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4186
4273
|
function normalizeModelId(id) {
|
|
4187
4274
|
let s = id.toLowerCase();
|
|
4188
4275
|
const slash = s.lastIndexOf("/");
|
|
@@ -4215,7 +4302,7 @@ var LiveDataProvider = class {
|
|
|
4215
4302
|
if (this.loaded) return;
|
|
4216
4303
|
this.loaded = true;
|
|
4217
4304
|
try {
|
|
4218
|
-
const raw = await
|
|
4305
|
+
const raw = await fs10__default.default.readFile(this.opts.cacheFile, "utf-8");
|
|
4219
4306
|
const cache = JSON.parse(raw);
|
|
4220
4307
|
if (cache.snapshot?.families) {
|
|
4221
4308
|
this.snapshot = cache.snapshot;
|
|
@@ -4327,14 +4414,14 @@ var LiveDataProvider = class {
|
|
|
4327
4414
|
}
|
|
4328
4415
|
async saveCache() {
|
|
4329
4416
|
try {
|
|
4330
|
-
await
|
|
4417
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
4331
4418
|
const cache = {
|
|
4332
4419
|
fetchedAt: this.fetchedAt,
|
|
4333
4420
|
snapshot: this.snapshot ?? void 0,
|
|
4334
4421
|
prices: Object.fromEntries(this.prices),
|
|
4335
4422
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4336
4423
|
};
|
|
4337
|
-
await
|
|
4424
|
+
await fs10__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4338
4425
|
} catch {
|
|
4339
4426
|
}
|
|
4340
4427
|
}
|
|
@@ -4539,6 +4626,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
4539
4626
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
4540
4627
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
4541
4628
|
}
|
|
4629
|
+
if (availableProviders.has("azure")) {
|
|
4630
|
+
for (const cfg of config.providers) {
|
|
4631
|
+
const model = azureModelForDeployment(cfg);
|
|
4632
|
+
if (model) this.selector.addDynamicModel(model);
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4542
4635
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
4543
4636
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
4544
4637
|
if (!override || override === "auto") continue;
|
|
@@ -5145,7 +5238,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5145
5238
|
ensureProvider(model, configs) {
|
|
5146
5239
|
const key = `${model.provider}:${model.id}`;
|
|
5147
5240
|
if (this.providers.has(key)) return;
|
|
5148
|
-
const cfg = configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
5241
|
+
const cfg = (model.provider === "azure" ? configs.find((c) => c.type === "azure" && c.deploymentName === model.id) : void 0) ?? configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
5149
5242
|
const provider = this.createProvider(cfg, model);
|
|
5150
5243
|
this.providers.set(key, provider);
|
|
5151
5244
|
}
|
|
@@ -5305,6 +5398,12 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
5305
5398
|
* which would interleave, are not tagged.
|
|
5306
5399
|
*/
|
|
5307
5400
|
isPresenter = false;
|
|
5401
|
+
/**
|
|
5402
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
5403
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
5404
|
+
* which node (Cockpit node panel / Why panel).
|
|
5405
|
+
*/
|
|
5406
|
+
servingModel;
|
|
5308
5407
|
constructor(role, id, parentId) {
|
|
5309
5408
|
super();
|
|
5310
5409
|
this.role = role;
|
|
@@ -5329,11 +5428,16 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
5329
5428
|
label: this.label,
|
|
5330
5429
|
status,
|
|
5331
5430
|
timestamp,
|
|
5332
|
-
output
|
|
5431
|
+
output,
|
|
5432
|
+
model: this.servingModel
|
|
5333
5433
|
};
|
|
5334
5434
|
this.emit("status", event);
|
|
5335
5435
|
this.emit("tier:status", event);
|
|
5336
5436
|
}
|
|
5437
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
5438
|
+
setServingModel(model) {
|
|
5439
|
+
this.servingModel = model || void 0;
|
|
5440
|
+
}
|
|
5337
5441
|
setLabel(label) {
|
|
5338
5442
|
this.label = label;
|
|
5339
5443
|
}
|
|
@@ -5356,7 +5460,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
5356
5460
|
currentAction: update.currentAction,
|
|
5357
5461
|
progressPct: update.progressPct,
|
|
5358
5462
|
timestamp,
|
|
5359
|
-
output: update.output
|
|
5463
|
+
output: update.output,
|
|
5464
|
+
model: this.servingModel
|
|
5360
5465
|
});
|
|
5361
5466
|
}
|
|
5362
5467
|
buildMessage(type, to, payload) {
|
|
@@ -5723,6 +5828,21 @@ Available tools: ${tools.map((t) => t.name).join(", ")}.
|
|
|
5723
5828
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
5724
5829
|
}
|
|
5725
5830
|
|
|
5831
|
+
// src/utils/truncate.ts
|
|
5832
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
5833
|
+
if (text.length <= maxChars) return text;
|
|
5834
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
5835
|
+
const tailLen = maxChars - headLen;
|
|
5836
|
+
const head = text.slice(0, headLen);
|
|
5837
|
+
const tail = text.slice(-tailLen);
|
|
5838
|
+
const elided = text.length - headLen - tailLen;
|
|
5839
|
+
return `${head}
|
|
5840
|
+
|
|
5841
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
5842
|
+
|
|
5843
|
+
${tail}`;
|
|
5844
|
+
}
|
|
5845
|
+
|
|
5726
5846
|
// src/core/tiers/t3-worker.ts
|
|
5727
5847
|
var CriticalToolError = class extends Error {
|
|
5728
5848
|
constructor(message, toolName) {
|
|
@@ -6030,6 +6150,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
6030
6150
|
} catch {
|
|
6031
6151
|
}
|
|
6032
6152
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
6153
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
6033
6154
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
6034
6155
|
let sentFullTextContract = false;
|
|
6035
6156
|
let textContractSignature = "";
|
|
@@ -6127,7 +6248,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6127
6248
|
const toolResult = await this.executeTool(tc);
|
|
6128
6249
|
await this.context.addMessage({
|
|
6129
6250
|
role: "tool",
|
|
6130
|
-
content: toolResult,
|
|
6251
|
+
content: truncateForContext(toolResult),
|
|
6131
6252
|
toolCallId: tc.id
|
|
6132
6253
|
});
|
|
6133
6254
|
}
|
|
@@ -6231,8 +6352,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6231
6352
|
tierId: this.id,
|
|
6232
6353
|
sessionId: this.taskId,
|
|
6233
6354
|
requireApproval: false,
|
|
6234
|
-
saveSnapshot: async (
|
|
6235
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
6355
|
+
saveSnapshot: async (path31, content) => {
|
|
6356
|
+
this.store?.addFileSnapshot(this.taskId, path31, content);
|
|
6236
6357
|
},
|
|
6237
6358
|
sendPeerSync: (to, syncType, content) => {
|
|
6238
6359
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -6384,15 +6505,17 @@ ${assignment.expectedOutput}`;
|
|
|
6384
6505
|
};
|
|
6385
6506
|
}
|
|
6386
6507
|
requiresArtifact() {
|
|
6508
|
+
if (this.assignment?.files?.length) return true;
|
|
6387
6509
|
const haystack = `${this.assignment?.description ?? ""}
|
|
6388
6510
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
6389
6511
|
return /\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/i.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
6390
6512
|
}
|
|
6391
6513
|
extractArtifactPaths(assignment) {
|
|
6514
|
+
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
6392
6515
|
const haystack = `${assignment.description}
|
|
6393
6516
|
${assignment.expectedOutput}`;
|
|
6394
6517
|
const matches = haystack.match(/\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/gi) ?? [];
|
|
6395
|
-
return [
|
|
6518
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
6396
6519
|
}
|
|
6397
6520
|
async verifyArtifacts(assignment) {
|
|
6398
6521
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -6402,9 +6525,9 @@ ${assignment.expectedOutput}`;
|
|
|
6402
6525
|
const { promisify: promisify4 } = await import('util');
|
|
6403
6526
|
const execAsync3 = promisify4(exec3);
|
|
6404
6527
|
for (const artifactPath of artifactPaths) {
|
|
6405
|
-
const absolutePath =
|
|
6528
|
+
const absolutePath = path26__default.default.resolve(process.cwd(), artifactPath);
|
|
6406
6529
|
try {
|
|
6407
|
-
const stat = await
|
|
6530
|
+
const stat = await fs10__default.default.stat(absolutePath);
|
|
6408
6531
|
if (!stat.isFile()) {
|
|
6409
6532
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
6410
6533
|
continue;
|
|
@@ -6414,7 +6537,7 @@ ${assignment.expectedOutput}`;
|
|
|
6414
6537
|
continue;
|
|
6415
6538
|
}
|
|
6416
6539
|
if (!/\.pdf$/i.test(artifactPath)) {
|
|
6417
|
-
const content = await
|
|
6540
|
+
const content = await fs10__default.default.readFile(absolutePath, "utf-8");
|
|
6418
6541
|
if (!content.trim()) {
|
|
6419
6542
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
6420
6543
|
continue;
|
|
@@ -6423,7 +6546,7 @@ ${assignment.expectedOutput}`;
|
|
|
6423
6546
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
6424
6547
|
continue;
|
|
6425
6548
|
}
|
|
6426
|
-
const ext =
|
|
6549
|
+
const ext = path26__default.default.extname(absolutePath).toLowerCase();
|
|
6427
6550
|
try {
|
|
6428
6551
|
if (ext === ".ts" || ext === ".tsx") {
|
|
6429
6552
|
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -6515,7 +6638,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
6515
6638
|
Assignment: ${assignment.description}
|
|
6516
6639
|
Expected output: ${assignment.expectedOutput}
|
|
6517
6640
|
Constraints: ${assignment.constraints.join("; ")}
|
|
6518
|
-
|
|
6641
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
6642
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
6643
|
+
` : ""}
|
|
6519
6644
|
Output to test:
|
|
6520
6645
|
${output}
|
|
6521
6646
|
|
|
@@ -6604,17 +6729,27 @@ Your subtask:
|
|
|
6604
6729
|
- Title: ${assignment.subtaskTitle}
|
|
6605
6730
|
- Description: ${assignment.description}
|
|
6606
6731
|
- Expected output: ${assignment.expectedOutput}
|
|
6607
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
6732
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
6733
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
6734
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
6608
6735
|
}
|
|
6609
6736
|
buildInitialPrompt(assignment) {
|
|
6610
6737
|
return `Execute the following subtask completely:
|
|
6611
6738
|
|
|
6612
6739
|
**${assignment.subtaskTitle}**
|
|
6613
|
-
|
|
6740
|
+
${assignment.contextBrief ? `
|
|
6741
|
+
Context: ${assignment.contextBrief}
|
|
6742
|
+
` : ""}
|
|
6614
6743
|
${assignment.description}
|
|
6615
6744
|
|
|
6616
6745
|
Expected output: ${assignment.expectedOutput}
|
|
6617
|
-
|
|
6746
|
+
${assignment.files?.length ? `
|
|
6747
|
+
Files you own (create or edit exactly these paths):
|
|
6748
|
+
${assignment.files.map((f) => `- ${f}`).join("\n")}
|
|
6749
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
6750
|
+
Definition of done (your output must satisfy ALL of these):
|
|
6751
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
6752
|
+
` : ""}
|
|
6618
6753
|
Constraints:
|
|
6619
6754
|
${assignment.constraints.map((c) => `- ${c}`).join("\n")}
|
|
6620
6755
|
|
|
@@ -7095,6 +7230,8 @@ var T2Manager = class extends BaseTier {
|
|
|
7095
7230
|
this.assignment = assignment;
|
|
7096
7231
|
this.taskId = taskId;
|
|
7097
7232
|
this.setLabel(assignment.sectionTitle);
|
|
7233
|
+
const m = this.router.getModelForTier("T2");
|
|
7234
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
7098
7235
|
this.setStatus("ACTIVE");
|
|
7099
7236
|
this.sendStatusUpdate({
|
|
7100
7237
|
progressPct: 0,
|
|
@@ -7181,7 +7318,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
7181
7318
|
// ── Private ──────────────────────────────────
|
|
7182
7319
|
async decomposeSection(assignment) {
|
|
7183
7320
|
const peerPlans = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p) => `[Peer ${p.fromId} Plan]: ${p.content.sectionTitle} - ${p.content.subtaskTitles?.join(", ")}`).join("\n");
|
|
7184
|
-
const prompt = `Decompose this section into
|
|
7321
|
+
const prompt = `Decompose this section into 1-4 concrete subtasks for T3 workers \u2014 the FEWEST that fully cover it (one subtask is the correct answer for a small section).
|
|
7185
7322
|
|
|
7186
7323
|
Section: ${assignment.sectionTitle}
|
|
7187
7324
|
Description: ${assignment.description}
|
|
@@ -7200,6 +7337,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
7200
7337
|
- peerT3Ids: string[] (empty for now)
|
|
7201
7338
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
7202
7339
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
7340
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
7341
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
7342
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
7203
7343
|
|
|
7204
7344
|
Return ONLY the JSON array.`;
|
|
7205
7345
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -7800,6 +7940,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
7800
7940
|
this.signal = signal;
|
|
7801
7941
|
this.taskId = crypto.randomUUID();
|
|
7802
7942
|
this.setLabel("Administrator");
|
|
7943
|
+
const m = this.router.getModelForTier("T1");
|
|
7944
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
7803
7945
|
this.setStatus("ACTIVE");
|
|
7804
7946
|
this.taskGoal = userPrompt;
|
|
7805
7947
|
this.sendStatusUpdate({
|
|
@@ -8046,10 +8188,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
8046
8188
|
"description": "Run npm init",
|
|
8047
8189
|
"expectedOutput": "package.json created",
|
|
8048
8190
|
"constraints": [],
|
|
8049
|
-
"dependsOn": []
|
|
8191
|
+
"dependsOn": [],
|
|
8192
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
8193
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
8194
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
8050
8195
|
}]
|
|
8051
8196
|
}, {
|
|
8052
|
-
"sectionId": "s2",
|
|
8197
|
+
"sectionId": "s2",
|
|
8053
8198
|
"sectionTitle": "Write Tests",
|
|
8054
8199
|
"description": "Write tests for the project",
|
|
8055
8200
|
"expectedOutput": "Tests passing",
|
|
@@ -8059,7 +8204,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
8059
8204
|
}]
|
|
8060
8205
|
}
|
|
8061
8206
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
8062
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
8207
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
8208
|
+
|
|
8209
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
8210
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
8211
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
8212
|
+
- "contextBrief": 1-3 short sentences with the ONLY background the worker needs. It sees nothing else about the task, so make the brief self-sufficient \u2014 but never pad it.
|
|
8213
|
+
- RIGHT-SIZE the plan: use the FEWEST sections and workers that fully cover the task. One section with 1-2 subtasks is the CORRECT plan for a small task; padding a plan with filler sections wastes the user's money.`;
|
|
8063
8214
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
8064
8215
|
const result = await this.router.generate("T1", {
|
|
8065
8216
|
messages,
|
|
@@ -8514,16 +8665,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
8514
8665
|
if (typeof input !== "string" || input.length === 0) {
|
|
8515
8666
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
8516
8667
|
}
|
|
8517
|
-
const root =
|
|
8518
|
-
const abs =
|
|
8519
|
-
const rel =
|
|
8520
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
8668
|
+
const root = path26__default.default.resolve(workspaceRoot);
|
|
8669
|
+
const abs = path26__default.default.isAbsolute(input) ? path26__default.default.resolve(input) : path26__default.default.resolve(root, input);
|
|
8670
|
+
const rel = path26__default.default.relative(root, abs);
|
|
8671
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26__default.default.isAbsolute(rel)) {
|
|
8521
8672
|
throw new WorkspaceSandboxError(input, root);
|
|
8522
8673
|
}
|
|
8523
8674
|
try {
|
|
8524
|
-
const real =
|
|
8525
|
-
const realRel =
|
|
8526
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
8675
|
+
const real = fs24__default.default.realpathSync(abs);
|
|
8676
|
+
const realRel = path26__default.default.relative(root, real);
|
|
8677
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26__default.default.isAbsolute(realRel))) {
|
|
8527
8678
|
throw new WorkspaceSandboxError(input, root);
|
|
8528
8679
|
}
|
|
8529
8680
|
} catch (e) {
|
|
@@ -8550,7 +8701,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
8550
8701
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
8551
8702
|
const offset = input["offset"] ?? 1;
|
|
8552
8703
|
const limit = input["limit"];
|
|
8553
|
-
const content = await
|
|
8704
|
+
const content = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8554
8705
|
const lines = content.split("\n");
|
|
8555
8706
|
const start = Math.max(0, offset - 1);
|
|
8556
8707
|
const end = limit ? start + limit : lines.length;
|
|
@@ -8579,13 +8730,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
8579
8730
|
const content = input["content"];
|
|
8580
8731
|
if (options.saveSnapshot) {
|
|
8581
8732
|
try {
|
|
8582
|
-
const oldContent = await
|
|
8733
|
+
const oldContent = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8583
8734
|
await options.saveSnapshot(absPath, oldContent);
|
|
8584
8735
|
} catch {
|
|
8585
8736
|
}
|
|
8586
8737
|
}
|
|
8587
|
-
await
|
|
8588
|
-
await
|
|
8738
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(absPath), { recursive: true });
|
|
8739
|
+
await fs10__default.default.writeFile(absPath, content, "utf-8");
|
|
8589
8740
|
return `Written ${content.length} characters to ${filePath}`;
|
|
8590
8741
|
}
|
|
8591
8742
|
};
|
|
@@ -8611,7 +8762,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
8611
8762
|
const oldString = input["old_string"];
|
|
8612
8763
|
const newString = input["new_string"];
|
|
8613
8764
|
const replaceAll = input["replace_all"] ?? false;
|
|
8614
|
-
const rawContent = await
|
|
8765
|
+
const rawContent = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8615
8766
|
if (options.saveSnapshot) {
|
|
8616
8767
|
await options.saveSnapshot(absPath, rawContent);
|
|
8617
8768
|
}
|
|
@@ -8623,7 +8774,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
8623
8774
|
);
|
|
8624
8775
|
}
|
|
8625
8776
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
8626
|
-
await
|
|
8777
|
+
await fs10__default.default.writeFile(absPath, updated, "utf-8");
|
|
8627
8778
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
8628
8779
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
8629
8780
|
}
|
|
@@ -8646,12 +8797,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
8646
8797
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
8647
8798
|
if (options.saveSnapshot) {
|
|
8648
8799
|
try {
|
|
8649
|
-
const oldContent = await
|
|
8800
|
+
const oldContent = await fs10__default.default.readFile(absPath, "utf-8");
|
|
8650
8801
|
await options.saveSnapshot(absPath, oldContent);
|
|
8651
8802
|
} catch {
|
|
8652
8803
|
}
|
|
8653
8804
|
}
|
|
8654
|
-
await
|
|
8805
|
+
await fs10__default.default.rm(absPath, { recursive: false });
|
|
8655
8806
|
return `Deleted ${filePath}`;
|
|
8656
8807
|
}
|
|
8657
8808
|
};
|
|
@@ -8668,7 +8819,7 @@ var FileListTool = class extends BaseTool {
|
|
|
8668
8819
|
async execute(input, _options) {
|
|
8669
8820
|
const inputPath = input["path"] || ".";
|
|
8670
8821
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
8671
|
-
const entries = await
|
|
8822
|
+
const entries = await fs10__default.default.readdir(absPath, { withFileTypes: true });
|
|
8672
8823
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
8673
8824
|
}
|
|
8674
8825
|
};
|
|
@@ -9081,8 +9232,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
9081
9232
|
}
|
|
9082
9233
|
};
|
|
9083
9234
|
async function fileToImageAttachment(filePath) {
|
|
9084
|
-
const data = await
|
|
9085
|
-
const ext =
|
|
9235
|
+
const data = await fs10__default.default.readFile(filePath);
|
|
9236
|
+
const ext = path26__default.default.extname(filePath).toLowerCase();
|
|
9086
9237
|
const mimeMap = {
|
|
9087
9238
|
".jpg": "image/jpeg",
|
|
9088
9239
|
".jpeg": "image/jpeg",
|
|
@@ -9116,14 +9267,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
9116
9267
|
const filePath = input["path"];
|
|
9117
9268
|
const content = input["content"];
|
|
9118
9269
|
const title = input["title"];
|
|
9119
|
-
const dir =
|
|
9120
|
-
if (!
|
|
9121
|
-
|
|
9270
|
+
const dir = path26__default.default.dirname(filePath);
|
|
9271
|
+
if (!fs24__default.default.existsSync(dir)) {
|
|
9272
|
+
fs24__default.default.mkdirSync(dir, { recursive: true });
|
|
9122
9273
|
}
|
|
9123
9274
|
return new Promise((resolve, reject) => {
|
|
9124
9275
|
try {
|
|
9125
9276
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
9126
|
-
const stream =
|
|
9277
|
+
const stream = fs24__default.default.createWriteStream(filePath);
|
|
9127
9278
|
doc.pipe(stream);
|
|
9128
9279
|
if (title) {
|
|
9129
9280
|
doc.info["Title"] = title;
|
|
@@ -9201,22 +9352,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
9201
9352
|
}
|
|
9202
9353
|
cmdPrefix = NODE_CMD;
|
|
9203
9354
|
}
|
|
9204
|
-
const tmpDir =
|
|
9205
|
-
if (!
|
|
9206
|
-
|
|
9355
|
+
const tmpDir = path26__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
9356
|
+
if (!fs24__default.default.existsSync(tmpDir)) {
|
|
9357
|
+
fs24__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
9207
9358
|
}
|
|
9208
9359
|
const extension = language === "python" ? "py" : "js";
|
|
9209
9360
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
9210
|
-
const filePath =
|
|
9211
|
-
|
|
9361
|
+
const filePath = path26__default.default.join(tmpDir, fileName);
|
|
9362
|
+
fs24__default.default.writeFileSync(filePath, code, "utf-8");
|
|
9212
9363
|
const execArgs = [filePath, ...args];
|
|
9213
9364
|
return new Promise((resolve) => {
|
|
9214
9365
|
const startMs = Date.now();
|
|
9215
9366
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
9216
9367
|
const duration = Date.now() - startMs;
|
|
9217
9368
|
try {
|
|
9218
|
-
if (
|
|
9219
|
-
|
|
9369
|
+
if (fs24__default.default.existsSync(filePath)) {
|
|
9370
|
+
fs24__default.default.unlinkSync(filePath);
|
|
9220
9371
|
}
|
|
9221
9372
|
} catch (cleanupErr) {
|
|
9222
9373
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -9367,30 +9518,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
9367
9518
|
engine: "tavily"
|
|
9368
9519
|
}));
|
|
9369
9520
|
}
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9521
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
9522
|
+
function unwrapDdgRedirect(href) {
|
|
9523
|
+
try {
|
|
9524
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
9525
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
9526
|
+
const target = url.searchParams.get("uddg");
|
|
9527
|
+
if (target) return decodeURIComponent(target);
|
|
9528
|
+
}
|
|
9529
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
9530
|
+
} catch {
|
|
9531
|
+
return href;
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9534
|
+
function stripTags(html) {
|
|
9535
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
9536
|
+
}
|
|
9537
|
+
function decodeEntities(text) {
|
|
9538
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
9539
|
+
}
|
|
9540
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
9541
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
9542
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
9543
|
+
const results = [];
|
|
9381
9544
|
let m;
|
|
9382
|
-
while ((m =
|
|
9383
|
-
|
|
9545
|
+
while ((m = anchorRe.exec(html)) !== null) {
|
|
9546
|
+
const tag = m[0];
|
|
9547
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
9548
|
+
const title = decodeEntities(stripTags(m[1] ?? ""));
|
|
9549
|
+
if (!href || !title) continue;
|
|
9550
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
9384
9551
|
}
|
|
9385
|
-
|
|
9386
|
-
|
|
9552
|
+
const snippets = [];
|
|
9553
|
+
while ((m = snippetRe.exec(html)) !== null) {
|
|
9554
|
+
snippets.push(decodeEntities(stripTags(m[1] ?? "")));
|
|
9387
9555
|
}
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9556
|
+
for (let i = 0; i < results.length; i++) {
|
|
9557
|
+
if (snippets[i]) results[i].snippet = snippets[i];
|
|
9558
|
+
}
|
|
9559
|
+
return results;
|
|
9560
|
+
}
|
|
9561
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
9562
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
9563
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
9564
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
9565
|
+
signal: AbortSignal.timeout(1e4)
|
|
9566
|
+
});
|
|
9567
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
9568
|
+
const html = await resp.text();
|
|
9569
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
9570
|
+
return parsed.slice(0, maxResults).map((r) => ({ ...r, engine: `duckduckgo-${variant}` }));
|
|
9394
9571
|
}
|
|
9395
9572
|
var WebSearchTool = class extends BaseTool {
|
|
9396
9573
|
name = "web_search";
|
|
@@ -9449,12 +9626,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
9449
9626
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
9450
9627
|
}
|
|
9451
9628
|
}
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9629
|
+
for (const variant of ["html", "lite"]) {
|
|
9630
|
+
try {
|
|
9631
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
9632
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
9633
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
9634
|
+
} catch (err) {
|
|
9635
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9636
|
+
}
|
|
9458
9637
|
}
|
|
9459
9638
|
const configHint = !this.config.searxngUrl && !this.config.braveApiKey && !this.config.tavilyApiKey ? "\nTip: Configure a search backend for better results:\n \u2022 Self-hosted: set SEARXNG_URL in your environment\n \u2022 Brave Search API: set BRAVE_SEARCH_API_KEY\n \u2022 Tavily API: set TAVILY_API_KEY" : "";
|
|
9460
9639
|
return [
|
|
@@ -9495,7 +9674,7 @@ var GlobTool = class extends BaseTool {
|
|
|
9495
9674
|
};
|
|
9496
9675
|
async execute(input, _options) {
|
|
9497
9676
|
const pattern = input["pattern"];
|
|
9498
|
-
const searchPath = input["path"] ?
|
|
9677
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
9499
9678
|
const matches = await glob.glob(pattern, {
|
|
9500
9679
|
cwd: searchPath,
|
|
9501
9680
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -9508,7 +9687,7 @@ var GlobTool = class extends BaseTool {
|
|
|
9508
9687
|
const withMtime = await Promise.all(
|
|
9509
9688
|
matches.map(async (rel) => {
|
|
9510
9689
|
try {
|
|
9511
|
-
const stat = await
|
|
9690
|
+
const stat = await fs10__default.default.stat(path26__default.default.join(searchPath, rel));
|
|
9512
9691
|
return { rel, mtime: stat.mtimeMs };
|
|
9513
9692
|
} catch {
|
|
9514
9693
|
return { rel, mtime: 0 };
|
|
@@ -9557,7 +9736,7 @@ var GrepTool = class extends BaseTool {
|
|
|
9557
9736
|
};
|
|
9558
9737
|
async execute(input, _options) {
|
|
9559
9738
|
const pattern = input["pattern"];
|
|
9560
|
-
const searchPath = input["path"] ?
|
|
9739
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
9561
9740
|
const globPattern = input["glob"];
|
|
9562
9741
|
const outputMode = input["output_mode"] ?? "content";
|
|
9563
9742
|
const context = input["context"] ?? 0;
|
|
@@ -9611,15 +9790,15 @@ var GrepTool = class extends BaseTool {
|
|
|
9611
9790
|
nodir: true
|
|
9612
9791
|
});
|
|
9613
9792
|
} catch {
|
|
9614
|
-
files = [
|
|
9793
|
+
files = [path26__default.default.relative(searchPath, searchPath) || "."];
|
|
9615
9794
|
}
|
|
9616
9795
|
const results = [];
|
|
9617
9796
|
let totalCount = 0;
|
|
9618
9797
|
for (const rel of files) {
|
|
9619
|
-
const abs =
|
|
9798
|
+
const abs = path26__default.default.join(searchPath, rel);
|
|
9620
9799
|
let content;
|
|
9621
9800
|
try {
|
|
9622
|
-
content = await
|
|
9801
|
+
content = await fs10__default.default.readFile(abs, "utf-8");
|
|
9623
9802
|
} catch {
|
|
9624
9803
|
continue;
|
|
9625
9804
|
}
|
|
@@ -9978,10 +10157,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
9978
10157
|
}
|
|
9979
10158
|
isIgnored(filePath) {
|
|
9980
10159
|
if (!filePath) return false;
|
|
9981
|
-
const abs =
|
|
9982
|
-
const rel =
|
|
9983
|
-
if (!rel || rel.startsWith("..") ||
|
|
9984
|
-
const posixRel = rel.split(
|
|
10160
|
+
const abs = path26__default.default.resolve(this.workspaceRoot, filePath);
|
|
10161
|
+
const rel = path26__default.default.relative(this.workspaceRoot, abs);
|
|
10162
|
+
if (!rel || rel.startsWith("..") || path26__default.default.isAbsolute(rel)) return true;
|
|
10163
|
+
const posixRel = rel.split(path26__default.default.sep).join("/");
|
|
9985
10164
|
return this.ignoreMatcher.ignores(posixRel);
|
|
9986
10165
|
}
|
|
9987
10166
|
};
|
|
@@ -10504,7 +10683,7 @@ var TaskAnalyzer = class {
|
|
|
10504
10683
|
analysisCache.clear();
|
|
10505
10684
|
}
|
|
10506
10685
|
};
|
|
10507
|
-
var DEFAULT_STATS_FILE =
|
|
10686
|
+
var DEFAULT_STATS_FILE = path26__default.default.join(os8__default.default.homedir(), ".cascade", "model-perf.json");
|
|
10508
10687
|
var ModelPerformanceTracker = class {
|
|
10509
10688
|
stats = /* @__PURE__ */ new Map();
|
|
10510
10689
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -10517,7 +10696,7 @@ var ModelPerformanceTracker = class {
|
|
|
10517
10696
|
if (this.loaded) return;
|
|
10518
10697
|
this.loaded = true;
|
|
10519
10698
|
try {
|
|
10520
|
-
const raw = await
|
|
10699
|
+
const raw = await fs10__default.default.readFile(this.statsFile, "utf-8");
|
|
10521
10700
|
const parsed = JSON.parse(raw);
|
|
10522
10701
|
if (parsed.models) {
|
|
10523
10702
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -10536,12 +10715,12 @@ var ModelPerformanceTracker = class {
|
|
|
10536
10715
|
}
|
|
10537
10716
|
async save() {
|
|
10538
10717
|
try {
|
|
10539
|
-
await
|
|
10718
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(this.statsFile), { recursive: true });
|
|
10540
10719
|
const modelsObj = {};
|
|
10541
10720
|
const featuresObj = {};
|
|
10542
10721
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
10543
10722
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
10544
|
-
await
|
|
10723
|
+
await fs10__default.default.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
|
|
10545
10724
|
} catch {
|
|
10546
10725
|
}
|
|
10547
10726
|
}
|
|
@@ -11075,9 +11254,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
11075
11254
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
11076
11255
|
async loadPersistedTools() {
|
|
11077
11256
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
11078
|
-
const file =
|
|
11257
|
+
const file = path26__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
11079
11258
|
try {
|
|
11080
|
-
const raw = await
|
|
11259
|
+
const raw = await fs10__default.default.readFile(file, "utf-8");
|
|
11081
11260
|
const specs = JSON.parse(raw);
|
|
11082
11261
|
if (!Array.isArray(specs)) return;
|
|
11083
11262
|
let loaded = 0;
|
|
@@ -11099,11 +11278,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
11099
11278
|
}
|
|
11100
11279
|
async persist() {
|
|
11101
11280
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
11102
|
-
const dir =
|
|
11103
|
-
const file =
|
|
11281
|
+
const dir = path26__default.default.join(this.workspacePath, ".cascade");
|
|
11282
|
+
const file = path26__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
11104
11283
|
try {
|
|
11105
|
-
await
|
|
11106
|
-
await
|
|
11284
|
+
await fs10__default.default.mkdir(dir, { recursive: true });
|
|
11285
|
+
await fs10__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
11107
11286
|
} catch (err) {
|
|
11108
11287
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
11109
11288
|
}
|
|
@@ -11120,12 +11299,12 @@ var WorldStateDB = class {
|
|
|
11120
11299
|
constructor(workspacePath, debugMode = false) {
|
|
11121
11300
|
this.workspacePath = workspacePath;
|
|
11122
11301
|
this.debugMode = debugMode;
|
|
11123
|
-
const cascadeDir =
|
|
11124
|
-
if (!
|
|
11125
|
-
|
|
11302
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
11303
|
+
if (!fs24__default.default.existsSync(cascadeDir)) {
|
|
11304
|
+
fs24__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
11126
11305
|
}
|
|
11127
|
-
this.keyPath =
|
|
11128
|
-
this.dbPath =
|
|
11306
|
+
this.keyPath = path26__default.default.join(cascadeDir, "world_state.key");
|
|
11307
|
+
this.dbPath = path26__default.default.join(cascadeDir, "world_state.db");
|
|
11129
11308
|
this.initEncryptionKey();
|
|
11130
11309
|
this.db = new Database2__default.default(this.dbPath);
|
|
11131
11310
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -11155,11 +11334,11 @@ var WorldStateDB = class {
|
|
|
11155
11334
|
dbPath;
|
|
11156
11335
|
encryptionKey;
|
|
11157
11336
|
initEncryptionKey() {
|
|
11158
|
-
if (
|
|
11159
|
-
this.encryptionKey =
|
|
11337
|
+
if (fs24__default.default.existsSync(this.keyPath)) {
|
|
11338
|
+
this.encryptionKey = fs24__default.default.readFileSync(this.keyPath);
|
|
11160
11339
|
} else {
|
|
11161
11340
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
11162
|
-
|
|
11341
|
+
fs24__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
11163
11342
|
}
|
|
11164
11343
|
}
|
|
11165
11344
|
encrypt(text) {
|
|
@@ -11300,6 +11479,22 @@ var WorldStateDB = class {
|
|
|
11300
11479
|
}
|
|
11301
11480
|
return selected.slice(0, limit).map((f) => `- ${f.entity} ${f.relation} ${f.value}`).join("\n");
|
|
11302
11481
|
}
|
|
11482
|
+
/**
|
|
11483
|
+
* Delete one fact by its (normalized) entity + relation key. Returns whether
|
|
11484
|
+
* a row was actually removed. Powers the desktop Knowledge tab's per-fact
|
|
11485
|
+
* delete — users can prune what the planner remembers about their project.
|
|
11486
|
+
*/
|
|
11487
|
+
deleteFact(entity, relation) {
|
|
11488
|
+
const e = normalizeKey(entity);
|
|
11489
|
+
const r = normalizeKey(relation);
|
|
11490
|
+
if (!e || !r) return false;
|
|
11491
|
+
const result = this.db.prepare("DELETE FROM facts WHERE entity = ? AND relation = ?").run(e, r);
|
|
11492
|
+
return result.changes > 0;
|
|
11493
|
+
}
|
|
11494
|
+
/** Delete every fact. Returns how many were removed. */
|
|
11495
|
+
clearFacts() {
|
|
11496
|
+
return this.db.prepare("DELETE FROM facts").run().changes;
|
|
11497
|
+
}
|
|
11303
11498
|
// ── Export / Import ──────────────────────────
|
|
11304
11499
|
//
|
|
11305
11500
|
// Knowledge travels DECRYPTED in the export bundle (a portable plaintext
|
|
@@ -11348,9 +11543,9 @@ var WorldStateDB = class {
|
|
|
11348
11543
|
dumpDebugIfNeeded() {
|
|
11349
11544
|
if (!this.debugMode) return;
|
|
11350
11545
|
try {
|
|
11351
|
-
const dumpPath =
|
|
11546
|
+
const dumpPath = path26__default.default.join(os8__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
11352
11547
|
const entries = this.getAllEntries();
|
|
11353
|
-
|
|
11548
|
+
fs24__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
11354
11549
|
} catch (err) {
|
|
11355
11550
|
console.error("Failed to dump debug world state", err);
|
|
11356
11551
|
}
|
|
@@ -11785,13 +11980,30 @@ ${last.partialOutput}` : "");
|
|
|
11785
11980
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
11786
11981
|
* Simple/Moderate) don't get over-escalated.
|
|
11787
11982
|
*/
|
|
11788
|
-
|
|
11983
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
11984
|
+
buildSignals(prompt) {
|
|
11789
11985
|
const p = prompt.trim();
|
|
11790
|
-
if (p.length < 24) return false;
|
|
11986
|
+
if (p.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
11791
11987
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
11792
|
-
const
|
|
11988
|
+
const scaleCount = (p.match(/\b(?:app(?:lication)?|system|platform|service|api|backend|frontend|full[- ]?stack|website|dashboard|pipeline|microservices?|database schema|authentication|end[- ]to[- ]end|codebase|project|multiple files|several (?:files|modules|components)|test suite)\b/gi) ?? []).length;
|
|
11793
11989
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
11794
|
-
return buildVerb
|
|
11990
|
+
return { buildVerb, scaleCount, multiPart };
|
|
11991
|
+
}
|
|
11992
|
+
/**
|
|
11993
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
11994
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
11995
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
11996
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
11997
|
+
* × workers for a task one worker handles).
|
|
11998
|
+
*/
|
|
11999
|
+
looksClearlyComplex(prompt) {
|
|
12000
|
+
const s = this.buildSignals(prompt);
|
|
12001
|
+
return s.buildVerb && (s.scaleCount >= 2 || s.scaleCount >= 1 && s.multiPart);
|
|
12002
|
+
}
|
|
12003
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
12004
|
+
looksLikeModerateBuild(prompt) {
|
|
12005
|
+
const s = this.buildSignals(prompt);
|
|
12006
|
+
return s.buildVerb && (s.scaleCount >= 1 || s.multiPart);
|
|
11795
12007
|
}
|
|
11796
12008
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
11797
12009
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -11879,6 +12091,9 @@ ${prompt}` : prompt;
|
|
|
11879
12091
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
11880
12092
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
11881
12093
|
verdict = "Complex";
|
|
12094
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
12095
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
12096
|
+
verdict = "Moderate";
|
|
11882
12097
|
} else {
|
|
11883
12098
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
11884
12099
|
}
|
|
@@ -13768,10 +13983,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13768
13983
|
}
|
|
13769
13984
|
} catch {
|
|
13770
13985
|
}
|
|
13771
|
-
const configPath =
|
|
13986
|
+
const configPath = path26__default.default.join(workspacePath, ".cascade", "config.json");
|
|
13772
13987
|
try {
|
|
13773
|
-
await
|
|
13774
|
-
await
|
|
13988
|
+
await fs10__default.default.mkdir(path26__default.default.dirname(configPath), { recursive: true });
|
|
13989
|
+
await fs10__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
13775
13990
|
} catch (err) {
|
|
13776
13991
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13777
13992
|
dispatch({
|
|
@@ -13822,9 +14037,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13822
14037
|
if (msg.includes("non-text parts")) return;
|
|
13823
14038
|
originalLog(...args);
|
|
13824
14039
|
};
|
|
13825
|
-
const store = new MemoryStore(
|
|
14040
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
13826
14041
|
storeRef.current = store;
|
|
13827
|
-
globalStoreRef.current = new MemoryStore(
|
|
14042
|
+
globalStoreRef.current = new MemoryStore(path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
13828
14043
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
13829
14044
|
setIdentities(identityRows);
|
|
13830
14045
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -13887,14 +14102,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13887
14102
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
13888
14103
|
onExport: async (fmt) => {
|
|
13889
14104
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13890
|
-
const exportPath =
|
|
14105
|
+
const exportPath = path26__default.default.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
|
|
13891
14106
|
if (fmt === "json") {
|
|
13892
|
-
await
|
|
14107
|
+
await fs10__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
13893
14108
|
} else {
|
|
13894
14109
|
const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
|
|
13895
14110
|
|
|
13896
14111
|
${msg.content}`).join("\n\n");
|
|
13897
|
-
await
|
|
14112
|
+
await fs10__default.default.writeFile(exportPath, markdown, "utf-8");
|
|
13898
14113
|
}
|
|
13899
14114
|
},
|
|
13900
14115
|
onRollback: async () => {
|
|
@@ -13904,7 +14119,7 @@ ${msg.content}`).join("\n\n");
|
|
|
13904
14119
|
if (!snapshots.length) return "No file snapshots found for this session.";
|
|
13905
14120
|
for (const { filePath, content } of snapshots) {
|
|
13906
14121
|
try {
|
|
13907
|
-
await
|
|
14122
|
+
await fs10__default.default.writeFile(filePath, content, "utf-8");
|
|
13908
14123
|
} catch (err) {
|
|
13909
14124
|
console.error(`Restore failed: ${filePath}`, err);
|
|
13910
14125
|
}
|
|
@@ -14783,9 +14998,9 @@ function stringifySlashOutput(val) {
|
|
|
14783
14998
|
}
|
|
14784
14999
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
14785
15000
|
if (!query) return "Usage: /search <query>";
|
|
14786
|
-
const dbPath =
|
|
15001
|
+
const dbPath = path26__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
14787
15002
|
try {
|
|
14788
|
-
await
|
|
15003
|
+
await fs10__default.default.access(dbPath);
|
|
14789
15004
|
} catch {
|
|
14790
15005
|
return "No database found. Start a conversation first.";
|
|
14791
15006
|
}
|
|
@@ -14817,7 +15032,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
14817
15032
|
async function diagnoseRuntime(config, workspacePath) {
|
|
14818
15033
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
14819
15034
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
14820
|
-
const store = new MemoryStore(
|
|
15035
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
14821
15036
|
try {
|
|
14822
15037
|
const sessions = store.listSessions(void 0, 3);
|
|
14823
15038
|
return [
|
|
@@ -14836,7 +15051,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
14836
15051
|
}
|
|
14837
15052
|
async function showRecentLogs(args, workspacePath) {
|
|
14838
15053
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
14839
|
-
const store = new MemoryStore(
|
|
15054
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
14840
15055
|
try {
|
|
14841
15056
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
14842
15057
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -14848,7 +15063,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
14848
15063
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
14849
15064
|
const sessionId = args[0];
|
|
14850
15065
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
14851
|
-
const store = new MemoryStore(
|
|
15066
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
14852
15067
|
try {
|
|
14853
15068
|
const session = store.getSession(sessionId);
|
|
14854
15069
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -15268,10 +15483,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
15268
15483
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
15269
15484
|
};
|
|
15270
15485
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
15271
|
-
const configDir =
|
|
15272
|
-
await
|
|
15273
|
-
const configPath =
|
|
15274
|
-
await
|
|
15486
|
+
const configDir = path26__default.default.join(workspacePath, ".cascade");
|
|
15487
|
+
await fs10__default.default.mkdir(configDir, { recursive: true });
|
|
15488
|
+
const configPath = path26__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
15489
|
+
await fs10__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
15275
15490
|
savedConfigRef.current = config;
|
|
15276
15491
|
dispatchRef.current({ type: "GO_DONE" });
|
|
15277
15492
|
} catch (err) {
|
|
@@ -15611,14 +15826,14 @@ function printTelemetryBanner() {
|
|
|
15611
15826
|
async function initCommand(workspacePath = process.cwd()) {
|
|
15612
15827
|
const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
15613
15828
|
try {
|
|
15614
|
-
const configDir =
|
|
15615
|
-
await
|
|
15616
|
-
const mdPath =
|
|
15829
|
+
const configDir = path26__default.default.join(workspacePath, ".cascade");
|
|
15830
|
+
await fs10__default.default.mkdir(configDir, { recursive: true });
|
|
15831
|
+
const mdPath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
15617
15832
|
if (!await fileExists(mdPath)) {
|
|
15618
15833
|
await createDefaultCascadeMd(workspacePath);
|
|
15619
15834
|
spin.succeed(chalk9__default.default.green("Created CASCADE.md"));
|
|
15620
15835
|
}
|
|
15621
|
-
const ignorePath =
|
|
15836
|
+
const ignorePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
15622
15837
|
if (!await fileExists(ignorePath)) {
|
|
15623
15838
|
await createDefaultIgnoreFile(workspacePath);
|
|
15624
15839
|
spin.succeed(chalk9__default.default.green("Created .cascadeignore"));
|
|
@@ -15627,7 +15842,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
15627
15842
|
console.log();
|
|
15628
15843
|
console.log(chalk9__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
15629
15844
|
console.log();
|
|
15630
|
-
const configPath =
|
|
15845
|
+
const configPath = path26__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
15631
15846
|
if (await fileExists(configPath)) {
|
|
15632
15847
|
console.log(chalk9__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
15633
15848
|
console.log();
|
|
@@ -15646,7 +15861,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
15646
15861
|
}
|
|
15647
15862
|
async function fileExists(p) {
|
|
15648
15863
|
try {
|
|
15649
|
-
await
|
|
15864
|
+
await fs10__default.default.access(p);
|
|
15650
15865
|
return true;
|
|
15651
15866
|
} catch {
|
|
15652
15867
|
return false;
|
|
@@ -15658,7 +15873,7 @@ init_constants();
|
|
|
15658
15873
|
var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
|
|
15659
15874
|
async function readJson(file) {
|
|
15660
15875
|
try {
|
|
15661
|
-
const raw = await
|
|
15876
|
+
const raw = await fs10__default.default.readFile(file, "utf-8");
|
|
15662
15877
|
return JSON.parse(raw);
|
|
15663
15878
|
} catch {
|
|
15664
15879
|
return null;
|
|
@@ -15685,7 +15900,7 @@ function fromEnv(env) {
|
|
|
15685
15900
|
return out;
|
|
15686
15901
|
}
|
|
15687
15902
|
async function fromClaudeCode(home) {
|
|
15688
|
-
const file =
|
|
15903
|
+
const file = path26__default.default.join(home, ".claude", ".credentials.json");
|
|
15689
15904
|
const data = await readJson(file);
|
|
15690
15905
|
if (!data) return [];
|
|
15691
15906
|
const oauth = data["claudeAiOauth"];
|
|
@@ -15708,7 +15923,7 @@ async function fromClaudeCode(home) {
|
|
|
15708
15923
|
return [];
|
|
15709
15924
|
}
|
|
15710
15925
|
async function fromCodex(home) {
|
|
15711
|
-
const file =
|
|
15926
|
+
const file = path26__default.default.join(home, ".codex", "auth.json");
|
|
15712
15927
|
const data = await readJson(file);
|
|
15713
15928
|
if (!data) return [];
|
|
15714
15929
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -15731,7 +15946,7 @@ async function fromCodex(home) {
|
|
|
15731
15946
|
return [];
|
|
15732
15947
|
}
|
|
15733
15948
|
async function fromGemini(home) {
|
|
15734
|
-
const file =
|
|
15949
|
+
const file = path26__default.default.join(home, ".gemini", "oauth_creds.json");
|
|
15735
15950
|
const data = await readJson(file);
|
|
15736
15951
|
const accessToken = str(data?.["access_token"]);
|
|
15737
15952
|
if (!accessToken) return [];
|
|
@@ -15747,7 +15962,7 @@ async function fromGemini(home) {
|
|
|
15747
15962
|
}
|
|
15748
15963
|
async function fromCopilot(home) {
|
|
15749
15964
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
15750
|
-
const file =
|
|
15965
|
+
const file = path26__default.default.join(home, ".config", "github-copilot", name);
|
|
15751
15966
|
const data = await readJson(file);
|
|
15752
15967
|
if (!data) continue;
|
|
15753
15968
|
for (const value of Object.values(data)) {
|
|
@@ -15801,7 +16016,7 @@ async function doctorCommand() {
|
|
|
15801
16016
|
checks.push({
|
|
15802
16017
|
label: "Cascade config",
|
|
15803
16018
|
ok: true,
|
|
15804
|
-
detail: `Loaded ${
|
|
16019
|
+
detail: `Loaded ${path26__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
15805
16020
|
});
|
|
15806
16021
|
const providers = [
|
|
15807
16022
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -15957,6 +16172,11 @@ function authMiddleware(secret, required = true) {
|
|
|
15957
16172
|
}
|
|
15958
16173
|
const user = verifyToken(token, secret);
|
|
15959
16174
|
if (!user) {
|
|
16175
|
+
if (!required) {
|
|
16176
|
+
req.user = void 0;
|
|
16177
|
+
next();
|
|
16178
|
+
return;
|
|
16179
|
+
}
|
|
15960
16180
|
res.status(401).json({ error: "Invalid or expired token" });
|
|
15961
16181
|
return;
|
|
15962
16182
|
}
|
|
@@ -16268,7 +16488,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
16268
16488
|
}
|
|
16269
16489
|
|
|
16270
16490
|
// src/dashboard/server.ts
|
|
16271
|
-
var __dirname$1 =
|
|
16491
|
+
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))));
|
|
16272
16492
|
var DashboardServer = class {
|
|
16273
16493
|
app;
|
|
16274
16494
|
httpServer;
|
|
@@ -16502,12 +16722,17 @@ var DashboardServer = class {
|
|
|
16502
16722
|
*/
|
|
16503
16723
|
persistConfig() {
|
|
16504
16724
|
try {
|
|
16505
|
-
const configPath =
|
|
16506
|
-
|
|
16507
|
-
|
|
16725
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
16726
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(configPath), { recursive: true });
|
|
16727
|
+
fs24__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
16508
16728
|
} catch (err) {
|
|
16509
16729
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
16510
16730
|
}
|
|
16731
|
+
try {
|
|
16732
|
+
saveGlobalCredentials(path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
16733
|
+
} catch (err) {
|
|
16734
|
+
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
16735
|
+
}
|
|
16511
16736
|
}
|
|
16512
16737
|
/**
|
|
16513
16738
|
* Produce a stable dashboard JWT signing secret.
|
|
@@ -16519,15 +16744,15 @@ var DashboardServer = class {
|
|
|
16519
16744
|
resolveDashboardSecret() {
|
|
16520
16745
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
16521
16746
|
if (fromConfig) return fromConfig;
|
|
16522
|
-
const secretPath =
|
|
16747
|
+
const secretPath = path26__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
16523
16748
|
try {
|
|
16524
|
-
if (
|
|
16525
|
-
const existing =
|
|
16749
|
+
if (fs24__default.default.existsSync(secretPath)) {
|
|
16750
|
+
const existing = fs24__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
16526
16751
|
if (existing.length >= 16) return existing;
|
|
16527
16752
|
}
|
|
16528
16753
|
const generated = crypto.randomUUID();
|
|
16529
|
-
|
|
16530
|
-
|
|
16754
|
+
fs24__default.default.mkdirSync(path26__default.default.dirname(secretPath), { recursive: true });
|
|
16755
|
+
fs24__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
16531
16756
|
if (this.config.dashboard.auth) {
|
|
16532
16757
|
console.warn(
|
|
16533
16758
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -16554,7 +16779,7 @@ var DashboardServer = class {
|
|
|
16554
16779
|
// ── Setup ─────────────────────────────────────
|
|
16555
16780
|
getGlobalStore() {
|
|
16556
16781
|
if (!this.globalStore) {
|
|
16557
|
-
const globalDbPath =
|
|
16782
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16558
16783
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
16559
16784
|
}
|
|
16560
16785
|
return this.globalStore;
|
|
@@ -16823,12 +17048,12 @@ ${prompt}`;
|
|
|
16823
17048
|
}
|
|
16824
17049
|
}
|
|
16825
17050
|
watchRuntimeChanges() {
|
|
16826
|
-
const workspaceDbPath =
|
|
16827
|
-
const globalDbPath =
|
|
17051
|
+
const workspaceDbPath = path26__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
17052
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16828
17053
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
16829
17054
|
for (const watchPath of watchPaths) {
|
|
16830
|
-
if (!
|
|
16831
|
-
|
|
17055
|
+
if (!fs24__default.default.existsSync(watchPath)) continue;
|
|
17056
|
+
fs24__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
16832
17057
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
16833
17058
|
});
|
|
16834
17059
|
}
|
|
@@ -16950,7 +17175,7 @@ ${prompt}`;
|
|
|
16950
17175
|
const sessionId = req.params.id;
|
|
16951
17176
|
this.store.deleteSession(sessionId);
|
|
16952
17177
|
this.store.deleteRuntimeSession(sessionId);
|
|
16953
|
-
const globalDbPath =
|
|
17178
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16954
17179
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16955
17180
|
try {
|
|
16956
17181
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -17043,7 +17268,7 @@ ${prompt}`;
|
|
|
17043
17268
|
});
|
|
17044
17269
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
17045
17270
|
const body = req.body;
|
|
17046
|
-
const globalDbPath =
|
|
17271
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
17047
17272
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
17048
17273
|
const globalStore = new MemoryStore(globalDbPath);
|
|
17049
17274
|
try {
|
|
@@ -17066,7 +17291,7 @@ ${prompt}`;
|
|
|
17066
17291
|
});
|
|
17067
17292
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
17068
17293
|
this.store.deleteAllRuntimeNodes();
|
|
17069
|
-
const globalDbPath =
|
|
17294
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
17070
17295
|
const globalStore = new MemoryStore(globalDbPath);
|
|
17071
17296
|
try {
|
|
17072
17297
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -17152,12 +17377,12 @@ ${prompt}`;
|
|
|
17152
17377
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
17153
17378
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
17154
17379
|
try {
|
|
17155
|
-
const configPath =
|
|
17156
|
-
const existing =
|
|
17380
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
17381
|
+
const existing = fs24__default.default.existsSync(configPath) ? JSON.parse(fs24__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
17157
17382
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
17158
17383
|
const tmp = configPath + ".tmp";
|
|
17159
|
-
|
|
17160
|
-
|
|
17384
|
+
fs24__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
17385
|
+
fs24__default.default.renameSync(tmp, configPath);
|
|
17161
17386
|
res.json({ ok: true });
|
|
17162
17387
|
} catch (err) {
|
|
17163
17388
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -17185,7 +17410,7 @@ ${prompt}`;
|
|
|
17185
17410
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
17186
17411
|
const scope = req.query["scope"] ?? "workspace";
|
|
17187
17412
|
if (scope === "global") {
|
|
17188
|
-
const globalDbPath =
|
|
17413
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
17189
17414
|
const globalStore = new MemoryStore(globalDbPath);
|
|
17190
17415
|
try {
|
|
17191
17416
|
res.json({
|
|
@@ -17420,6 +17645,48 @@ ${prompt}`;
|
|
|
17420
17645
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17421
17646
|
}
|
|
17422
17647
|
});
|
|
17648
|
+
this.app.get("/api/knowledge", auth, (_req, res) => {
|
|
17649
|
+
try {
|
|
17650
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17651
|
+
try {
|
|
17652
|
+
const facts = ws.getAllFacts();
|
|
17653
|
+
res.json({ total: facts.length, facts });
|
|
17654
|
+
} finally {
|
|
17655
|
+
ws.close();
|
|
17656
|
+
}
|
|
17657
|
+
} catch (err) {
|
|
17658
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17659
|
+
}
|
|
17660
|
+
});
|
|
17661
|
+
this.app.delete("/api/knowledge/fact", auth, mutationLimiter, (req, res) => {
|
|
17662
|
+
const body = req.body;
|
|
17663
|
+
if (!body.entity || !body.relation) {
|
|
17664
|
+
res.status(400).json({ error: "entity and relation are required" });
|
|
17665
|
+
return;
|
|
17666
|
+
}
|
|
17667
|
+
try {
|
|
17668
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17669
|
+
try {
|
|
17670
|
+
res.json({ ok: true, deleted: ws.deleteFact(body.entity, body.relation) });
|
|
17671
|
+
} finally {
|
|
17672
|
+
ws.close();
|
|
17673
|
+
}
|
|
17674
|
+
} catch (err) {
|
|
17675
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17676
|
+
}
|
|
17677
|
+
});
|
|
17678
|
+
this.app.delete("/api/knowledge", auth, mutationLimiter, (_req, res) => {
|
|
17679
|
+
try {
|
|
17680
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17681
|
+
try {
|
|
17682
|
+
res.json({ ok: true, deleted: ws.clearFacts() });
|
|
17683
|
+
} finally {
|
|
17684
|
+
ws.close();
|
|
17685
|
+
}
|
|
17686
|
+
} catch (err) {
|
|
17687
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17688
|
+
}
|
|
17689
|
+
});
|
|
17423
17690
|
this.app.get("/api/audit-chain", auth, async (req, res) => {
|
|
17424
17691
|
try {
|
|
17425
17692
|
const limit = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
|
|
@@ -17438,13 +17705,13 @@ ${prompt}`;
|
|
|
17438
17705
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17439
17706
|
}
|
|
17440
17707
|
});
|
|
17441
|
-
const prodPath =
|
|
17442
|
-
const devPath =
|
|
17443
|
-
const webDistPath =
|
|
17444
|
-
if (
|
|
17708
|
+
const prodPath = path26__default.default.resolve(__dirname$1, "../web/dist");
|
|
17709
|
+
const devPath = path26__default.default.resolve(__dirname$1, "../../web/dist");
|
|
17710
|
+
const webDistPath = fs24__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
17711
|
+
if (fs24__default.default.existsSync(webDistPath)) {
|
|
17445
17712
|
this.app.use(express__default.default.static(webDistPath));
|
|
17446
17713
|
this.app.get("*", (_req, res) => {
|
|
17447
|
-
res.sendFile(
|
|
17714
|
+
res.sendFile(path26__default.default.join(webDistPath, "index.html"));
|
|
17448
17715
|
});
|
|
17449
17716
|
} else {
|
|
17450
17717
|
this.app.get("/", (_req, res) => {
|
|
@@ -17465,7 +17732,7 @@ init_constants();
|
|
|
17465
17732
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
17466
17733
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
17467
17734
|
const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
17468
|
-
const store = new MemoryStore(
|
|
17735
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17469
17736
|
const server = new DashboardServer(config, store, workspacePath);
|
|
17470
17737
|
server.watchRuntimeChanges();
|
|
17471
17738
|
const gThis = globalThis;
|
|
@@ -17497,7 +17764,7 @@ init_constants();
|
|
|
17497
17764
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
17498
17765
|
const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
|
|
17499
17766
|
identity.command("list").description("List all available identities").action(() => {
|
|
17500
|
-
const store = new MemoryStore(
|
|
17767
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17501
17768
|
try {
|
|
17502
17769
|
const identities = store.listIdentities();
|
|
17503
17770
|
console.log(chalk9__default.default.bold("\n Identities:"));
|
|
@@ -17517,7 +17784,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
17517
17784
|
}
|
|
17518
17785
|
});
|
|
17519
17786
|
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) => {
|
|
17520
|
-
const store = new MemoryStore(
|
|
17787
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17521
17788
|
try {
|
|
17522
17789
|
if (options.default) {
|
|
17523
17790
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -17543,7 +17810,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
17543
17810
|
}
|
|
17544
17811
|
});
|
|
17545
17812
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
17546
|
-
const store = new MemoryStore(
|
|
17813
|
+
const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
17547
17814
|
try {
|
|
17548
17815
|
const identities = store.listIdentities();
|
|
17549
17816
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
@@ -17671,11 +17938,11 @@ async function exportCommand(options = {}) {
|
|
|
17671
17938
|
let store;
|
|
17672
17939
|
try {
|
|
17673
17940
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
17674
|
-
const workspaceDbPath =
|
|
17675
|
-
const globalDbPath =
|
|
17941
|
+
const workspaceDbPath = path26__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
17942
|
+
const globalDbPath = path26__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
17676
17943
|
let dbPath = globalDbPath;
|
|
17677
17944
|
try {
|
|
17678
|
-
await
|
|
17945
|
+
await fs10__default.default.access(workspaceDbPath);
|
|
17679
17946
|
dbPath = workspaceDbPath;
|
|
17680
17947
|
} catch {
|
|
17681
17948
|
}
|
|
@@ -17714,8 +17981,8 @@ async function exportCommand(options = {}) {
|
|
|
17714
17981
|
const ext = format === "json" ? ".json" : ".md";
|
|
17715
17982
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
17716
17983
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
17717
|
-
const outPath = options.output ?
|
|
17718
|
-
await
|
|
17984
|
+
const outPath = options.output ? path26__default.default.resolve(options.output) : path26__default.default.join(process.cwd(), defaultFile);
|
|
17985
|
+
await fs10__default.default.writeFile(outPath, content, "utf-8");
|
|
17719
17986
|
spin.succeed(chalk9__default.default.green(`Exported to ${chalk9__default.default.white(outPath)}`));
|
|
17720
17987
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
17721
17988
|
console.log();
|
|
@@ -17942,11 +18209,11 @@ async function statsCommand() {
|
|
|
17942
18209
|
dotenv__default.default.config();
|
|
17943
18210
|
function warnIfBuildIsStale() {
|
|
17944
18211
|
try {
|
|
17945
|
-
const here =
|
|
18212
|
+
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))));
|
|
17946
18213
|
for (const rel of ["..", "../.."]) {
|
|
17947
|
-
const pkgPath =
|
|
17948
|
-
if (!
|
|
17949
|
-
const pkg = JSON.parse(
|
|
18214
|
+
const pkgPath = path26__default.default.join(here, rel, "package.json");
|
|
18215
|
+
if (!fs24__default.default.existsSync(pkgPath)) continue;
|
|
18216
|
+
const pkg = JSON.parse(fs24__default.default.readFileSync(pkgPath, "utf-8"));
|
|
17950
18217
|
if (pkg.name !== "cascade-ai") continue;
|
|
17951
18218
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
17952
18219
|
console.error(
|