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.js
CHANGED
|
@@ -7,8 +7,8 @@ import https from 'https';
|
|
|
7
7
|
import zlib from 'zlib';
|
|
8
8
|
import { Readable } from 'stream';
|
|
9
9
|
import Database2 from 'better-sqlite3';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import path26 from 'path';
|
|
11
|
+
import fs24 from 'fs';
|
|
12
12
|
import os8 from 'os';
|
|
13
13
|
import crypto, { randomUUID, timingSafeEqual } from 'crypto';
|
|
14
14
|
import { render, Box, Text, useStdout, useApp, useInput, Static } from 'ink';
|
|
@@ -17,7 +17,7 @@ import React2, { useState, useReducer, useRef, useCallback, useEffect, useMemo }
|
|
|
17
17
|
import chalk9 from 'chalk';
|
|
18
18
|
import dotenv from 'dotenv';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
|
-
import
|
|
20
|
+
import fs10 from 'fs/promises';
|
|
21
21
|
import * as _ignoreModule from 'ignore';
|
|
22
22
|
import _ignoreModule__default from 'ignore';
|
|
23
23
|
import { z } from 'zod';
|
|
@@ -56,16 +56,17 @@ var __export = (target, all) => {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
// src/constants.ts
|
|
59
|
-
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;
|
|
59
|
+
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;
|
|
60
60
|
var init_constants = __esm({
|
|
61
61
|
"src/constants.ts"() {
|
|
62
|
-
CASCADE_VERSION = "0.
|
|
62
|
+
CASCADE_VERSION = "0.19.0";
|
|
63
63
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
64
64
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
65
65
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
66
66
|
GLOBAL_CONFIG_DIR = ".cascade-ai";
|
|
67
67
|
GLOBAL_DB_FILE = "memory.db";
|
|
68
68
|
GLOBAL_KEYSTORE_FILE = "keystore.enc";
|
|
69
|
+
GLOBAL_CREDENTIALS_FILE = "credentials.json";
|
|
69
70
|
GLOBAL_RUNTIME_DB_FILE = "runtime.db";
|
|
70
71
|
DEFAULT_DASHBOARD_PORT = 4891;
|
|
71
72
|
DEFAULT_CONTEXT_LIMIT = 2e5;
|
|
@@ -884,8 +885,26 @@ var init_openai = __esm({
|
|
|
884
885
|
// src/providers/azure.ts
|
|
885
886
|
var azure_exports = {};
|
|
886
887
|
__export(azure_exports, {
|
|
887
|
-
AzureOpenAIProvider: () => AzureOpenAIProvider
|
|
888
|
+
AzureOpenAIProvider: () => AzureOpenAIProvider,
|
|
889
|
+
azureModelForDeployment: () => azureModelForDeployment
|
|
888
890
|
});
|
|
891
|
+
function azureModelForDeployment(cfg) {
|
|
892
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
893
|
+
const id = cfg.deploymentName.trim();
|
|
894
|
+
return {
|
|
895
|
+
id,
|
|
896
|
+
name: cfg.label?.trim() || id,
|
|
897
|
+
provider: "azure",
|
|
898
|
+
contextWindow: 128e3,
|
|
899
|
+
isVisionCapable: false,
|
|
900
|
+
inputCostPer1kTokens: 25e-4,
|
|
901
|
+
outputCostPer1kTokens: 0.01,
|
|
902
|
+
maxOutputTokens: 16e3,
|
|
903
|
+
supportsStreaming: true,
|
|
904
|
+
isLocal: false,
|
|
905
|
+
supportsToolUse: true
|
|
906
|
+
};
|
|
907
|
+
}
|
|
889
908
|
var AzureOpenAIProvider;
|
|
890
909
|
var init_azure = __esm({
|
|
891
910
|
"src/providers/azure.ts"() {
|
|
@@ -911,7 +930,8 @@ var init_azure = __esm({
|
|
|
911
930
|
});
|
|
912
931
|
}
|
|
913
932
|
async listModels() {
|
|
914
|
-
|
|
933
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
934
|
+
return [fromDeployment ?? this.model];
|
|
915
935
|
}
|
|
916
936
|
async isAvailable() {
|
|
917
937
|
try {
|
|
@@ -1580,12 +1600,12 @@ var init_audit_logger = __esm({
|
|
|
1580
1600
|
constructor(workspacePath, debugMode = false) {
|
|
1581
1601
|
this.workspacePath = workspacePath;
|
|
1582
1602
|
this.debugMode = debugMode;
|
|
1583
|
-
const cascadeDir =
|
|
1584
|
-
if (!
|
|
1585
|
-
|
|
1603
|
+
const cascadeDir = path26.join(workspacePath, ".cascade");
|
|
1604
|
+
if (!fs24.existsSync(cascadeDir)) {
|
|
1605
|
+
fs24.mkdirSync(cascadeDir, { recursive: true });
|
|
1586
1606
|
}
|
|
1587
|
-
this.keyPath =
|
|
1588
|
-
this.dbPath =
|
|
1607
|
+
this.keyPath = path26.join(cascadeDir, "audit_log.key");
|
|
1608
|
+
this.dbPath = path26.join(cascadeDir, "audit_log.db");
|
|
1589
1609
|
this.initEncryptionKey();
|
|
1590
1610
|
this.db = new Database2(this.dbPath);
|
|
1591
1611
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -1615,11 +1635,11 @@ var init_audit_logger = __esm({
|
|
|
1615
1635
|
return crypto.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
1616
1636
|
}
|
|
1617
1637
|
initEncryptionKey() {
|
|
1618
|
-
if (
|
|
1619
|
-
this.encryptionKey =
|
|
1638
|
+
if (fs24.existsSync(this.keyPath)) {
|
|
1639
|
+
this.encryptionKey = fs24.readFileSync(this.keyPath);
|
|
1620
1640
|
} else {
|
|
1621
1641
|
this.encryptionKey = crypto.randomBytes(32);
|
|
1622
|
-
|
|
1642
|
+
fs24.writeFileSync(this.keyPath, this.encryptionKey);
|
|
1623
1643
|
}
|
|
1624
1644
|
}
|
|
1625
1645
|
encrypt(text) {
|
|
@@ -1697,9 +1717,9 @@ var init_audit_logger = __esm({
|
|
|
1697
1717
|
dumpDebugIfNeeded() {
|
|
1698
1718
|
if (!this.debugMode) return;
|
|
1699
1719
|
try {
|
|
1700
|
-
const dumpPath =
|
|
1720
|
+
const dumpPath = path26.join(os8.tmpdir(), "cascade_audit_logs_debug.json");
|
|
1701
1721
|
const entries = this.getAllLogs();
|
|
1702
|
-
|
|
1722
|
+
fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
1703
1723
|
} catch (err) {
|
|
1704
1724
|
console.error("Failed to dump debug audit logs", err);
|
|
1705
1725
|
}
|
|
@@ -1754,7 +1774,7 @@ var Keystore = class {
|
|
|
1754
1774
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
1755
1775
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
1756
1776
|
this.backend = "keytar";
|
|
1757
|
-
if (password &&
|
|
1777
|
+
if (password && fs24.existsSync(this.storePath)) {
|
|
1758
1778
|
try {
|
|
1759
1779
|
const fileEntries = this.decryptFile(password);
|
|
1760
1780
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -1773,7 +1793,7 @@ var Keystore = class {
|
|
|
1773
1793
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
1774
1794
|
);
|
|
1775
1795
|
}
|
|
1776
|
-
if (!
|
|
1796
|
+
if (!fs24.existsSync(this.storePath)) {
|
|
1777
1797
|
const salt = crypto.randomBytes(SALT_LEN);
|
|
1778
1798
|
this.masterKey = this.deriveKey(password, salt);
|
|
1779
1799
|
this.writeWithSalt({}, salt);
|
|
@@ -1787,7 +1807,7 @@ var Keystore = class {
|
|
|
1787
1807
|
}
|
|
1788
1808
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
1789
1809
|
unlockSync(password) {
|
|
1790
|
-
if (!
|
|
1810
|
+
if (!fs24.existsSync(this.storePath)) {
|
|
1791
1811
|
const salt = crypto.randomBytes(SALT_LEN);
|
|
1792
1812
|
this.masterKey = this.deriveKey(password, salt);
|
|
1793
1813
|
this.writeWithSalt({}, salt);
|
|
@@ -1845,7 +1865,7 @@ var Keystore = class {
|
|
|
1845
1865
|
}
|
|
1846
1866
|
}
|
|
1847
1867
|
decryptFile(password, knownSalt) {
|
|
1848
|
-
if (!
|
|
1868
|
+
if (!fs24.existsSync(this.storePath)) return {};
|
|
1849
1869
|
try {
|
|
1850
1870
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
1851
1871
|
const useSalt = knownSalt ?? salt;
|
|
@@ -1867,8 +1887,8 @@ var Keystore = class {
|
|
|
1867
1887
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1868
1888
|
const tag = cipher.getAuthTag();
|
|
1869
1889
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
1870
|
-
|
|
1871
|
-
|
|
1890
|
+
fs24.mkdirSync(path26.dirname(this.storePath), { recursive: true });
|
|
1891
|
+
fs24.writeFileSync(this.storePath, out, { mode: 384 });
|
|
1872
1892
|
}
|
|
1873
1893
|
writeWithSalt(data, salt) {
|
|
1874
1894
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -1878,11 +1898,11 @@ var Keystore = class {
|
|
|
1878
1898
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1879
1899
|
const tag = cipher.getAuthTag();
|
|
1880
1900
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
1881
|
-
|
|
1882
|
-
|
|
1901
|
+
fs24.mkdirSync(path26.dirname(this.storePath), { recursive: true });
|
|
1902
|
+
fs24.writeFileSync(this.storePath, out, { mode: 384 });
|
|
1883
1903
|
}
|
|
1884
1904
|
readRaw() {
|
|
1885
|
-
const buf =
|
|
1905
|
+
const buf = fs24.readFileSync(this.storePath);
|
|
1886
1906
|
let offset = 0;
|
|
1887
1907
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
1888
1908
|
offset += SALT_LEN;
|
|
@@ -1915,9 +1935,9 @@ var CascadeIgnore = class {
|
|
|
1915
1935
|
]);
|
|
1916
1936
|
}
|
|
1917
1937
|
async load(workspacePath) {
|
|
1918
|
-
const filePath =
|
|
1938
|
+
const filePath = path26.join(workspacePath, ".cascadeignore");
|
|
1919
1939
|
try {
|
|
1920
|
-
const content = await
|
|
1940
|
+
const content = await fs10.readFile(filePath, "utf-8");
|
|
1921
1941
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
1922
1942
|
this.ig.add(lines);
|
|
1923
1943
|
this.loaded = true;
|
|
@@ -1926,7 +1946,7 @@ var CascadeIgnore = class {
|
|
|
1926
1946
|
}
|
|
1927
1947
|
isIgnored(filePath, workspacePath) {
|
|
1928
1948
|
try {
|
|
1929
|
-
const relative = workspacePath ?
|
|
1949
|
+
const relative = workspacePath ? path26.relative(workspacePath, filePath) : filePath;
|
|
1930
1950
|
return this.ig.ignores(relative);
|
|
1931
1951
|
} catch {
|
|
1932
1952
|
return false;
|
|
@@ -1937,7 +1957,7 @@ var CascadeIgnore = class {
|
|
|
1937
1957
|
}
|
|
1938
1958
|
};
|
|
1939
1959
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
1940
|
-
const filePath =
|
|
1960
|
+
const filePath = path26.join(workspacePath, ".cascadeignore");
|
|
1941
1961
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
1942
1962
|
# Syntax identical to .gitignore
|
|
1943
1963
|
|
|
@@ -1964,12 +1984,12 @@ build/
|
|
|
1964
1984
|
.DS_Store
|
|
1965
1985
|
Thumbs.db
|
|
1966
1986
|
`;
|
|
1967
|
-
await
|
|
1987
|
+
await fs10.writeFile(filePath, content, "utf-8");
|
|
1968
1988
|
}
|
|
1969
1989
|
async function loadCascadeMd(workspacePath) {
|
|
1970
|
-
const filePath =
|
|
1990
|
+
const filePath = path26.join(workspacePath, "CASCADE.md");
|
|
1971
1991
|
try {
|
|
1972
|
-
const raw = await
|
|
1992
|
+
const raw = await fs10.readFile(filePath, "utf-8");
|
|
1973
1993
|
return parseCascadeMd(raw);
|
|
1974
1994
|
} catch {
|
|
1975
1995
|
return null;
|
|
@@ -1996,7 +2016,7 @@ ${raw.trim()}`;
|
|
|
1996
2016
|
return { raw, sections, systemPrompt };
|
|
1997
2017
|
}
|
|
1998
2018
|
async function createDefaultCascadeMd(workspacePath) {
|
|
1999
|
-
const filePath =
|
|
2019
|
+
const filePath = path26.join(workspacePath, "CASCADE.md");
|
|
2000
2020
|
const content = `# Cascade Project Instructions
|
|
2001
2021
|
|
|
2002
2022
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -2026,12 +2046,12 @@ All tools are allowed unless specified otherwise.
|
|
|
2026
2046
|
|
|
2027
2047
|
List any areas the agent should not touch.
|
|
2028
2048
|
`;
|
|
2029
|
-
await
|
|
2049
|
+
await fs10.writeFile(filePath, content, "utf-8");
|
|
2030
2050
|
}
|
|
2031
2051
|
var MemoryStore = class _MemoryStore {
|
|
2032
2052
|
db;
|
|
2033
2053
|
constructor(dbPath) {
|
|
2034
|
-
|
|
2054
|
+
fs24.mkdirSync(path26.dirname(dbPath), { recursive: true });
|
|
2035
2055
|
try {
|
|
2036
2056
|
this.db = new Database2(dbPath, { timeout: 5e3 });
|
|
2037
2057
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -2996,8 +3016,11 @@ var CascadeConfigSchema = z.object({
|
|
|
2996
3016
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
2997
3017
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
2998
3018
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
3019
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
3020
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
3021
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
2999
3022
|
*/
|
|
3000
|
-
cascadeAuto: z.boolean().default(
|
|
3023
|
+
cascadeAuto: z.boolean().default(true),
|
|
3001
3024
|
/**
|
|
3002
3025
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
3003
3026
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -3178,6 +3201,63 @@ function validateConfig(raw) {
|
|
|
3178
3201
|
return result.data;
|
|
3179
3202
|
}
|
|
3180
3203
|
|
|
3204
|
+
// src/config/global-credentials.ts
|
|
3205
|
+
init_constants();
|
|
3206
|
+
function credentialsPath(globalDir) {
|
|
3207
|
+
return path26.join(globalDir, GLOBAL_CREDENTIALS_FILE);
|
|
3208
|
+
}
|
|
3209
|
+
function providerKey(p) {
|
|
3210
|
+
if (p.type === "azure") {
|
|
3211
|
+
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
3212
|
+
}
|
|
3213
|
+
return p.type;
|
|
3214
|
+
}
|
|
3215
|
+
function isPersistable(p) {
|
|
3216
|
+
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
3217
|
+
}
|
|
3218
|
+
function loadGlobalCredentials(globalDir) {
|
|
3219
|
+
try {
|
|
3220
|
+
const raw = fs24.readFileSync(credentialsPath(globalDir), "utf-8");
|
|
3221
|
+
const parsed = JSON.parse(raw);
|
|
3222
|
+
if (!Array.isArray(parsed.providers)) return [];
|
|
3223
|
+
return parsed.providers.filter(
|
|
3224
|
+
(p) => Boolean(p) && typeof p.type === "string"
|
|
3225
|
+
);
|
|
3226
|
+
} catch {
|
|
3227
|
+
return [];
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
function saveGlobalCredentials(globalDir, providers) {
|
|
3231
|
+
const filePath = credentialsPath(globalDir);
|
|
3232
|
+
fs24.mkdirSync(globalDir, { recursive: true, mode: 448 });
|
|
3233
|
+
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
3234
|
+
const tmp = `${filePath}.tmp`;
|
|
3235
|
+
fs24.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3236
|
+
fs24.renameSync(tmp, filePath);
|
|
3237
|
+
try {
|
|
3238
|
+
fs24.chmodSync(filePath, 384);
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
3243
|
+
const merged = [...workspaceProviders];
|
|
3244
|
+
const byKey = new Map(merged.map((p) => [providerKey(p), p]));
|
|
3245
|
+
for (const g of globalProviders) {
|
|
3246
|
+
const existing = byKey.get(providerKey(g));
|
|
3247
|
+
if (!existing) {
|
|
3248
|
+
merged.push({ ...g });
|
|
3249
|
+
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
if (!existing.apiKey && g.apiKey) existing.apiKey = g.apiKey;
|
|
3253
|
+
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
3254
|
+
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
3255
|
+
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
3256
|
+
if (!existing.label && g.label) existing.label = g.label;
|
|
3257
|
+
}
|
|
3258
|
+
return merged;
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3181
3261
|
// src/config/index.ts
|
|
3182
3262
|
init_constants();
|
|
3183
3263
|
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
@@ -3193,18 +3273,20 @@ var ConfigManager = class {
|
|
|
3193
3273
|
cascadeMd = null;
|
|
3194
3274
|
workspacePath;
|
|
3195
3275
|
globalDir;
|
|
3196
|
-
|
|
3276
|
+
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
3277
|
+
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
3197
3278
|
this.workspacePath = workspacePath;
|
|
3198
|
-
this.globalDir =
|
|
3279
|
+
this.globalDir = globalDirOverride ?? path26.join(os8.homedir(), GLOBAL_CONFIG_DIR);
|
|
3199
3280
|
}
|
|
3200
3281
|
async load() {
|
|
3201
3282
|
this.config = await this.loadConfig();
|
|
3202
3283
|
this.ignore = new CascadeIgnore();
|
|
3203
3284
|
await this.ignore.load(this.workspacePath);
|
|
3204
3285
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
3205
|
-
this.keystore = new Keystore(
|
|
3206
|
-
this.store = new MemoryStore(
|
|
3286
|
+
this.keystore = new Keystore(path26.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
3287
|
+
this.store = new MemoryStore(path26.join(this.workspacePath, CASCADE_DB_FILE));
|
|
3207
3288
|
await this.injectEnvKeys();
|
|
3289
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
3208
3290
|
await this.ensureDefaultIdentity();
|
|
3209
3291
|
}
|
|
3210
3292
|
getConfig() {
|
|
@@ -3226,9 +3308,14 @@ var ConfigManager = class {
|
|
|
3226
3308
|
return this.workspacePath;
|
|
3227
3309
|
}
|
|
3228
3310
|
async save() {
|
|
3229
|
-
const configPath =
|
|
3230
|
-
await
|
|
3231
|
-
await
|
|
3311
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
3312
|
+
await fs10.mkdir(path26.dirname(configPath), { recursive: true });
|
|
3313
|
+
await fs10.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
3314
|
+
try {
|
|
3315
|
+
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
3316
|
+
} catch (err) {
|
|
3317
|
+
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
3318
|
+
}
|
|
3232
3319
|
}
|
|
3233
3320
|
async updateConfig(updates) {
|
|
3234
3321
|
this.config = validateConfig({ ...this.config, ...updates });
|
|
@@ -3251,9 +3338,9 @@ var ConfigManager = class {
|
|
|
3251
3338
|
return configProvider?.apiKey;
|
|
3252
3339
|
}
|
|
3253
3340
|
async loadConfig() {
|
|
3254
|
-
const configPath =
|
|
3341
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
3255
3342
|
try {
|
|
3256
|
-
const raw = await
|
|
3343
|
+
const raw = await fs10.readFile(configPath, "utf-8");
|
|
3257
3344
|
return validateConfig(JSON.parse(raw));
|
|
3258
3345
|
} catch (err) {
|
|
3259
3346
|
if (err.code === "ENOENT") {
|
|
@@ -4130,7 +4217,7 @@ init_constants();
|
|
|
4130
4217
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4131
4218
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4132
4219
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4133
|
-
var DEFAULT_CACHE_FILE =
|
|
4220
|
+
var DEFAULT_CACHE_FILE = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4134
4221
|
function normalizeModelId(id) {
|
|
4135
4222
|
let s = id.toLowerCase();
|
|
4136
4223
|
const slash = s.lastIndexOf("/");
|
|
@@ -4163,7 +4250,7 @@ var LiveDataProvider = class {
|
|
|
4163
4250
|
if (this.loaded) return;
|
|
4164
4251
|
this.loaded = true;
|
|
4165
4252
|
try {
|
|
4166
|
-
const raw = await
|
|
4253
|
+
const raw = await fs10.readFile(this.opts.cacheFile, "utf-8");
|
|
4167
4254
|
const cache = JSON.parse(raw);
|
|
4168
4255
|
if (cache.snapshot?.families) {
|
|
4169
4256
|
this.snapshot = cache.snapshot;
|
|
@@ -4275,14 +4362,14 @@ var LiveDataProvider = class {
|
|
|
4275
4362
|
}
|
|
4276
4363
|
async saveCache() {
|
|
4277
4364
|
try {
|
|
4278
|
-
await
|
|
4365
|
+
await fs10.mkdir(path26.dirname(this.opts.cacheFile), { recursive: true });
|
|
4279
4366
|
const cache = {
|
|
4280
4367
|
fetchedAt: this.fetchedAt,
|
|
4281
4368
|
snapshot: this.snapshot ?? void 0,
|
|
4282
4369
|
prices: Object.fromEntries(this.prices),
|
|
4283
4370
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4284
4371
|
};
|
|
4285
|
-
await
|
|
4372
|
+
await fs10.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4286
4373
|
} catch {
|
|
4287
4374
|
}
|
|
4288
4375
|
}
|
|
@@ -4487,6 +4574,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
4487
4574
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
4488
4575
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
4489
4576
|
}
|
|
4577
|
+
if (availableProviders.has("azure")) {
|
|
4578
|
+
for (const cfg of config.providers) {
|
|
4579
|
+
const model = azureModelForDeployment(cfg);
|
|
4580
|
+
if (model) this.selector.addDynamicModel(model);
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4490
4583
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
4491
4584
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
4492
4585
|
if (!override || override === "auto") continue;
|
|
@@ -5093,7 +5186,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5093
5186
|
ensureProvider(model, configs) {
|
|
5094
5187
|
const key = `${model.provider}:${model.id}`;
|
|
5095
5188
|
if (this.providers.has(key)) return;
|
|
5096
|
-
const cfg = configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
5189
|
+
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 };
|
|
5097
5190
|
const provider = this.createProvider(cfg, model);
|
|
5098
5191
|
this.providers.set(key, provider);
|
|
5099
5192
|
}
|
|
@@ -5253,6 +5346,12 @@ var BaseTier = class extends EventEmitter {
|
|
|
5253
5346
|
* which would interleave, are not tagged.
|
|
5254
5347
|
*/
|
|
5255
5348
|
isPresenter = false;
|
|
5349
|
+
/**
|
|
5350
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
5351
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
5352
|
+
* which node (Cockpit node panel / Why panel).
|
|
5353
|
+
*/
|
|
5354
|
+
servingModel;
|
|
5256
5355
|
constructor(role, id, parentId) {
|
|
5257
5356
|
super();
|
|
5258
5357
|
this.role = role;
|
|
@@ -5277,11 +5376,16 @@ var BaseTier = class extends EventEmitter {
|
|
|
5277
5376
|
label: this.label,
|
|
5278
5377
|
status,
|
|
5279
5378
|
timestamp,
|
|
5280
|
-
output
|
|
5379
|
+
output,
|
|
5380
|
+
model: this.servingModel
|
|
5281
5381
|
};
|
|
5282
5382
|
this.emit("status", event);
|
|
5283
5383
|
this.emit("tier:status", event);
|
|
5284
5384
|
}
|
|
5385
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
5386
|
+
setServingModel(model) {
|
|
5387
|
+
this.servingModel = model || void 0;
|
|
5388
|
+
}
|
|
5285
5389
|
setLabel(label) {
|
|
5286
5390
|
this.label = label;
|
|
5287
5391
|
}
|
|
@@ -5304,7 +5408,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
5304
5408
|
currentAction: update.currentAction,
|
|
5305
5409
|
progressPct: update.progressPct,
|
|
5306
5410
|
timestamp,
|
|
5307
|
-
output: update.output
|
|
5411
|
+
output: update.output,
|
|
5412
|
+
model: this.servingModel
|
|
5308
5413
|
});
|
|
5309
5414
|
}
|
|
5310
5415
|
buildMessage(type, to, payload) {
|
|
@@ -5671,6 +5776,21 @@ Available tools: ${tools.map((t) => t.name).join(", ")}.
|
|
|
5671
5776
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
5672
5777
|
}
|
|
5673
5778
|
|
|
5779
|
+
// src/utils/truncate.ts
|
|
5780
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
5781
|
+
if (text.length <= maxChars) return text;
|
|
5782
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
5783
|
+
const tailLen = maxChars - headLen;
|
|
5784
|
+
const head = text.slice(0, headLen);
|
|
5785
|
+
const tail = text.slice(-tailLen);
|
|
5786
|
+
const elided = text.length - headLen - tailLen;
|
|
5787
|
+
return `${head}
|
|
5788
|
+
|
|
5789
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
5790
|
+
|
|
5791
|
+
${tail}`;
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5674
5794
|
// src/core/tiers/t3-worker.ts
|
|
5675
5795
|
var CriticalToolError = class extends Error {
|
|
5676
5796
|
constructor(message, toolName) {
|
|
@@ -5978,6 +6098,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
5978
6098
|
} catch {
|
|
5979
6099
|
}
|
|
5980
6100
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
6101
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
5981
6102
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
5982
6103
|
let sentFullTextContract = false;
|
|
5983
6104
|
let textContractSignature = "";
|
|
@@ -6075,7 +6196,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6075
6196
|
const toolResult = await this.executeTool(tc);
|
|
6076
6197
|
await this.context.addMessage({
|
|
6077
6198
|
role: "tool",
|
|
6078
|
-
content: toolResult,
|
|
6199
|
+
content: truncateForContext(toolResult),
|
|
6079
6200
|
toolCallId: tc.id
|
|
6080
6201
|
});
|
|
6081
6202
|
}
|
|
@@ -6179,8 +6300,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
6179
6300
|
tierId: this.id,
|
|
6180
6301
|
sessionId: this.taskId,
|
|
6181
6302
|
requireApproval: false,
|
|
6182
|
-
saveSnapshot: async (
|
|
6183
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
6303
|
+
saveSnapshot: async (path31, content) => {
|
|
6304
|
+
this.store?.addFileSnapshot(this.taskId, path31, content);
|
|
6184
6305
|
},
|
|
6185
6306
|
sendPeerSync: (to, syncType, content) => {
|
|
6186
6307
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -6332,15 +6453,17 @@ ${assignment.expectedOutput}`;
|
|
|
6332
6453
|
};
|
|
6333
6454
|
}
|
|
6334
6455
|
requiresArtifact() {
|
|
6456
|
+
if (this.assignment?.files?.length) return true;
|
|
6335
6457
|
const haystack = `${this.assignment?.description ?? ""}
|
|
6336
6458
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
6337
6459
|
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);
|
|
6338
6460
|
}
|
|
6339
6461
|
extractArtifactPaths(assignment) {
|
|
6462
|
+
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
6340
6463
|
const haystack = `${assignment.description}
|
|
6341
6464
|
${assignment.expectedOutput}`;
|
|
6342
6465
|
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) ?? [];
|
|
6343
|
-
return [
|
|
6466
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
6344
6467
|
}
|
|
6345
6468
|
async verifyArtifacts(assignment) {
|
|
6346
6469
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -6350,9 +6473,9 @@ ${assignment.expectedOutput}`;
|
|
|
6350
6473
|
const { promisify: promisify4 } = await import('util');
|
|
6351
6474
|
const execAsync3 = promisify4(exec3);
|
|
6352
6475
|
for (const artifactPath of artifactPaths) {
|
|
6353
|
-
const absolutePath =
|
|
6476
|
+
const absolutePath = path26.resolve(process.cwd(), artifactPath);
|
|
6354
6477
|
try {
|
|
6355
|
-
const stat = await
|
|
6478
|
+
const stat = await fs10.stat(absolutePath);
|
|
6356
6479
|
if (!stat.isFile()) {
|
|
6357
6480
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
6358
6481
|
continue;
|
|
@@ -6362,7 +6485,7 @@ ${assignment.expectedOutput}`;
|
|
|
6362
6485
|
continue;
|
|
6363
6486
|
}
|
|
6364
6487
|
if (!/\.pdf$/i.test(artifactPath)) {
|
|
6365
|
-
const content = await
|
|
6488
|
+
const content = await fs10.readFile(absolutePath, "utf-8");
|
|
6366
6489
|
if (!content.trim()) {
|
|
6367
6490
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
6368
6491
|
continue;
|
|
@@ -6371,7 +6494,7 @@ ${assignment.expectedOutput}`;
|
|
|
6371
6494
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
6372
6495
|
continue;
|
|
6373
6496
|
}
|
|
6374
|
-
const ext =
|
|
6497
|
+
const ext = path26.extname(absolutePath).toLowerCase();
|
|
6375
6498
|
try {
|
|
6376
6499
|
if (ext === ".ts" || ext === ".tsx") {
|
|
6377
6500
|
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -6463,7 +6586,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
6463
6586
|
Assignment: ${assignment.description}
|
|
6464
6587
|
Expected output: ${assignment.expectedOutput}
|
|
6465
6588
|
Constraints: ${assignment.constraints.join("; ")}
|
|
6466
|
-
|
|
6589
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
6590
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
6591
|
+
` : ""}
|
|
6467
6592
|
Output to test:
|
|
6468
6593
|
${output}
|
|
6469
6594
|
|
|
@@ -6552,17 +6677,27 @@ Your subtask:
|
|
|
6552
6677
|
- Title: ${assignment.subtaskTitle}
|
|
6553
6678
|
- Description: ${assignment.description}
|
|
6554
6679
|
- Expected output: ${assignment.expectedOutput}
|
|
6555
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
6680
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
6681
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
6682
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
6556
6683
|
}
|
|
6557
6684
|
buildInitialPrompt(assignment) {
|
|
6558
6685
|
return `Execute the following subtask completely:
|
|
6559
6686
|
|
|
6560
6687
|
**${assignment.subtaskTitle}**
|
|
6561
|
-
|
|
6688
|
+
${assignment.contextBrief ? `
|
|
6689
|
+
Context: ${assignment.contextBrief}
|
|
6690
|
+
` : ""}
|
|
6562
6691
|
${assignment.description}
|
|
6563
6692
|
|
|
6564
6693
|
Expected output: ${assignment.expectedOutput}
|
|
6565
|
-
|
|
6694
|
+
${assignment.files?.length ? `
|
|
6695
|
+
Files you own (create or edit exactly these paths):
|
|
6696
|
+
${assignment.files.map((f) => `- ${f}`).join("\n")}
|
|
6697
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
6698
|
+
Definition of done (your output must satisfy ALL of these):
|
|
6699
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
6700
|
+
` : ""}
|
|
6566
6701
|
Constraints:
|
|
6567
6702
|
${assignment.constraints.map((c) => `- ${c}`).join("\n")}
|
|
6568
6703
|
|
|
@@ -7043,6 +7178,8 @@ var T2Manager = class extends BaseTier {
|
|
|
7043
7178
|
this.assignment = assignment;
|
|
7044
7179
|
this.taskId = taskId;
|
|
7045
7180
|
this.setLabel(assignment.sectionTitle);
|
|
7181
|
+
const m = this.router.getModelForTier("T2");
|
|
7182
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
7046
7183
|
this.setStatus("ACTIVE");
|
|
7047
7184
|
this.sendStatusUpdate({
|
|
7048
7185
|
progressPct: 0,
|
|
@@ -7129,7 +7266,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
7129
7266
|
// ── Private ──────────────────────────────────
|
|
7130
7267
|
async decomposeSection(assignment) {
|
|
7131
7268
|
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");
|
|
7132
|
-
const prompt = `Decompose this section into
|
|
7269
|
+
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).
|
|
7133
7270
|
|
|
7134
7271
|
Section: ${assignment.sectionTitle}
|
|
7135
7272
|
Description: ${assignment.description}
|
|
@@ -7148,6 +7285,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
7148
7285
|
- peerT3Ids: string[] (empty for now)
|
|
7149
7286
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
7150
7287
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
7288
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
7289
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
7290
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
7151
7291
|
|
|
7152
7292
|
Return ONLY the JSON array.`;
|
|
7153
7293
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -7748,6 +7888,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
7748
7888
|
this.signal = signal;
|
|
7749
7889
|
this.taskId = randomUUID();
|
|
7750
7890
|
this.setLabel("Administrator");
|
|
7891
|
+
const m = this.router.getModelForTier("T1");
|
|
7892
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
7751
7893
|
this.setStatus("ACTIVE");
|
|
7752
7894
|
this.taskGoal = userPrompt;
|
|
7753
7895
|
this.sendStatusUpdate({
|
|
@@ -7994,10 +8136,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
7994
8136
|
"description": "Run npm init",
|
|
7995
8137
|
"expectedOutput": "package.json created",
|
|
7996
8138
|
"constraints": [],
|
|
7997
|
-
"dependsOn": []
|
|
8139
|
+
"dependsOn": [],
|
|
8140
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
8141
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
8142
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
7998
8143
|
}]
|
|
7999
8144
|
}, {
|
|
8000
|
-
"sectionId": "s2",
|
|
8145
|
+
"sectionId": "s2",
|
|
8001
8146
|
"sectionTitle": "Write Tests",
|
|
8002
8147
|
"description": "Write tests for the project",
|
|
8003
8148
|
"expectedOutput": "Tests passing",
|
|
@@ -8007,7 +8152,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
8007
8152
|
}]
|
|
8008
8153
|
}
|
|
8009
8154
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
8010
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
8155
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
8156
|
+
|
|
8157
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
8158
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
8159
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
8160
|
+
- "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.
|
|
8161
|
+
- 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.`;
|
|
8011
8162
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
8012
8163
|
const result = await this.router.generate("T1", {
|
|
8013
8164
|
messages,
|
|
@@ -8462,16 +8613,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
8462
8613
|
if (typeof input !== "string" || input.length === 0) {
|
|
8463
8614
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
8464
8615
|
}
|
|
8465
|
-
const root =
|
|
8466
|
-
const abs =
|
|
8467
|
-
const rel =
|
|
8468
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
8616
|
+
const root = path26.resolve(workspaceRoot);
|
|
8617
|
+
const abs = path26.isAbsolute(input) ? path26.resolve(input) : path26.resolve(root, input);
|
|
8618
|
+
const rel = path26.relative(root, abs);
|
|
8619
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26.isAbsolute(rel)) {
|
|
8469
8620
|
throw new WorkspaceSandboxError(input, root);
|
|
8470
8621
|
}
|
|
8471
8622
|
try {
|
|
8472
|
-
const real =
|
|
8473
|
-
const realRel =
|
|
8474
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
8623
|
+
const real = fs24.realpathSync(abs);
|
|
8624
|
+
const realRel = path26.relative(root, real);
|
|
8625
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26.isAbsolute(realRel))) {
|
|
8475
8626
|
throw new WorkspaceSandboxError(input, root);
|
|
8476
8627
|
}
|
|
8477
8628
|
} catch (e) {
|
|
@@ -8498,7 +8649,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
8498
8649
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
8499
8650
|
const offset = input["offset"] ?? 1;
|
|
8500
8651
|
const limit = input["limit"];
|
|
8501
|
-
const content = await
|
|
8652
|
+
const content = await fs10.readFile(absPath, "utf-8");
|
|
8502
8653
|
const lines = content.split("\n");
|
|
8503
8654
|
const start = Math.max(0, offset - 1);
|
|
8504
8655
|
const end = limit ? start + limit : lines.length;
|
|
@@ -8527,13 +8678,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
8527
8678
|
const content = input["content"];
|
|
8528
8679
|
if (options.saveSnapshot) {
|
|
8529
8680
|
try {
|
|
8530
|
-
const oldContent = await
|
|
8681
|
+
const oldContent = await fs10.readFile(absPath, "utf-8");
|
|
8531
8682
|
await options.saveSnapshot(absPath, oldContent);
|
|
8532
8683
|
} catch {
|
|
8533
8684
|
}
|
|
8534
8685
|
}
|
|
8535
|
-
await
|
|
8536
|
-
await
|
|
8686
|
+
await fs10.mkdir(path26.dirname(absPath), { recursive: true });
|
|
8687
|
+
await fs10.writeFile(absPath, content, "utf-8");
|
|
8537
8688
|
return `Written ${content.length} characters to ${filePath}`;
|
|
8538
8689
|
}
|
|
8539
8690
|
};
|
|
@@ -8559,7 +8710,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
8559
8710
|
const oldString = input["old_string"];
|
|
8560
8711
|
const newString = input["new_string"];
|
|
8561
8712
|
const replaceAll = input["replace_all"] ?? false;
|
|
8562
|
-
const rawContent = await
|
|
8713
|
+
const rawContent = await fs10.readFile(absPath, "utf-8");
|
|
8563
8714
|
if (options.saveSnapshot) {
|
|
8564
8715
|
await options.saveSnapshot(absPath, rawContent);
|
|
8565
8716
|
}
|
|
@@ -8571,7 +8722,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
8571
8722
|
);
|
|
8572
8723
|
}
|
|
8573
8724
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
8574
|
-
await
|
|
8725
|
+
await fs10.writeFile(absPath, updated, "utf-8");
|
|
8575
8726
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
8576
8727
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
8577
8728
|
}
|
|
@@ -8594,12 +8745,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
8594
8745
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
8595
8746
|
if (options.saveSnapshot) {
|
|
8596
8747
|
try {
|
|
8597
|
-
const oldContent = await
|
|
8748
|
+
const oldContent = await fs10.readFile(absPath, "utf-8");
|
|
8598
8749
|
await options.saveSnapshot(absPath, oldContent);
|
|
8599
8750
|
} catch {
|
|
8600
8751
|
}
|
|
8601
8752
|
}
|
|
8602
|
-
await
|
|
8753
|
+
await fs10.rm(absPath, { recursive: false });
|
|
8603
8754
|
return `Deleted ${filePath}`;
|
|
8604
8755
|
}
|
|
8605
8756
|
};
|
|
@@ -8616,7 +8767,7 @@ var FileListTool = class extends BaseTool {
|
|
|
8616
8767
|
async execute(input, _options) {
|
|
8617
8768
|
const inputPath = input["path"] || ".";
|
|
8618
8769
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
8619
|
-
const entries = await
|
|
8770
|
+
const entries = await fs10.readdir(absPath, { withFileTypes: true });
|
|
8620
8771
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
8621
8772
|
}
|
|
8622
8773
|
};
|
|
@@ -9029,8 +9180,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
9029
9180
|
}
|
|
9030
9181
|
};
|
|
9031
9182
|
async function fileToImageAttachment(filePath) {
|
|
9032
|
-
const data = await
|
|
9033
|
-
const ext =
|
|
9183
|
+
const data = await fs10.readFile(filePath);
|
|
9184
|
+
const ext = path26.extname(filePath).toLowerCase();
|
|
9034
9185
|
const mimeMap = {
|
|
9035
9186
|
".jpg": "image/jpeg",
|
|
9036
9187
|
".jpeg": "image/jpeg",
|
|
@@ -9064,14 +9215,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
9064
9215
|
const filePath = input["path"];
|
|
9065
9216
|
const content = input["content"];
|
|
9066
9217
|
const title = input["title"];
|
|
9067
|
-
const dir =
|
|
9068
|
-
if (!
|
|
9069
|
-
|
|
9218
|
+
const dir = path26.dirname(filePath);
|
|
9219
|
+
if (!fs24.existsSync(dir)) {
|
|
9220
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
9070
9221
|
}
|
|
9071
9222
|
return new Promise((resolve, reject) => {
|
|
9072
9223
|
try {
|
|
9073
9224
|
const doc = new PDFDocument({ margin: 50 });
|
|
9074
|
-
const stream =
|
|
9225
|
+
const stream = fs24.createWriteStream(filePath);
|
|
9075
9226
|
doc.pipe(stream);
|
|
9076
9227
|
if (title) {
|
|
9077
9228
|
doc.info["Title"] = title;
|
|
@@ -9149,22 +9300,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
9149
9300
|
}
|
|
9150
9301
|
cmdPrefix = NODE_CMD;
|
|
9151
9302
|
}
|
|
9152
|
-
const tmpDir =
|
|
9153
|
-
if (!
|
|
9154
|
-
|
|
9303
|
+
const tmpDir = path26.join(this.workspaceRoot, ".cascade", "tmp");
|
|
9304
|
+
if (!fs24.existsSync(tmpDir)) {
|
|
9305
|
+
fs24.mkdirSync(tmpDir, { recursive: true });
|
|
9155
9306
|
}
|
|
9156
9307
|
const extension = language === "python" ? "py" : "js";
|
|
9157
9308
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
9158
|
-
const filePath =
|
|
9159
|
-
|
|
9309
|
+
const filePath = path26.join(tmpDir, fileName);
|
|
9310
|
+
fs24.writeFileSync(filePath, code, "utf-8");
|
|
9160
9311
|
const execArgs = [filePath, ...args];
|
|
9161
9312
|
return new Promise((resolve) => {
|
|
9162
9313
|
const startMs = Date.now();
|
|
9163
9314
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
9164
9315
|
const duration = Date.now() - startMs;
|
|
9165
9316
|
try {
|
|
9166
|
-
if (
|
|
9167
|
-
|
|
9317
|
+
if (fs24.existsSync(filePath)) {
|
|
9318
|
+
fs24.unlinkSync(filePath);
|
|
9168
9319
|
}
|
|
9169
9320
|
} catch (cleanupErr) {
|
|
9170
9321
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -9315,30 +9466,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
9315
9466
|
engine: "tavily"
|
|
9316
9467
|
}));
|
|
9317
9468
|
}
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9469
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
9470
|
+
function unwrapDdgRedirect(href) {
|
|
9471
|
+
try {
|
|
9472
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
9473
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
9474
|
+
const target = url.searchParams.get("uddg");
|
|
9475
|
+
if (target) return decodeURIComponent(target);
|
|
9476
|
+
}
|
|
9477
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
9478
|
+
} catch {
|
|
9479
|
+
return href;
|
|
9480
|
+
}
|
|
9481
|
+
}
|
|
9482
|
+
function stripTags(html) {
|
|
9483
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
9484
|
+
}
|
|
9485
|
+
function decodeEntities(text) {
|
|
9486
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
9487
|
+
}
|
|
9488
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
9489
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
9490
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
9491
|
+
const results = [];
|
|
9329
9492
|
let m;
|
|
9330
|
-
while ((m =
|
|
9331
|
-
|
|
9493
|
+
while ((m = anchorRe.exec(html)) !== null) {
|
|
9494
|
+
const tag = m[0];
|
|
9495
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
9496
|
+
const title = decodeEntities(stripTags(m[1] ?? ""));
|
|
9497
|
+
if (!href || !title) continue;
|
|
9498
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
9332
9499
|
}
|
|
9333
|
-
|
|
9334
|
-
|
|
9500
|
+
const snippets = [];
|
|
9501
|
+
while ((m = snippetRe.exec(html)) !== null) {
|
|
9502
|
+
snippets.push(decodeEntities(stripTags(m[1] ?? "")));
|
|
9335
9503
|
}
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9504
|
+
for (let i = 0; i < results.length; i++) {
|
|
9505
|
+
if (snippets[i]) results[i].snippet = snippets[i];
|
|
9506
|
+
}
|
|
9507
|
+
return results;
|
|
9508
|
+
}
|
|
9509
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
9510
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
9511
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
9512
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
9513
|
+
signal: AbortSignal.timeout(1e4)
|
|
9514
|
+
});
|
|
9515
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
9516
|
+
const html = await resp.text();
|
|
9517
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
9518
|
+
return parsed.slice(0, maxResults).map((r) => ({ ...r, engine: `duckduckgo-${variant}` }));
|
|
9342
9519
|
}
|
|
9343
9520
|
var WebSearchTool = class extends BaseTool {
|
|
9344
9521
|
name = "web_search";
|
|
@@ -9397,12 +9574,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
9397
9574
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
9398
9575
|
}
|
|
9399
9576
|
}
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9577
|
+
for (const variant of ["html", "lite"]) {
|
|
9578
|
+
try {
|
|
9579
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
9580
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
9581
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
9582
|
+
} catch (err) {
|
|
9583
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9584
|
+
}
|
|
9406
9585
|
}
|
|
9407
9586
|
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" : "";
|
|
9408
9587
|
return [
|
|
@@ -9443,7 +9622,7 @@ var GlobTool = class extends BaseTool {
|
|
|
9443
9622
|
};
|
|
9444
9623
|
async execute(input, _options) {
|
|
9445
9624
|
const pattern = input["pattern"];
|
|
9446
|
-
const searchPath = input["path"] ?
|
|
9625
|
+
const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
9447
9626
|
const matches = await glob(pattern, {
|
|
9448
9627
|
cwd: searchPath,
|
|
9449
9628
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -9456,7 +9635,7 @@ var GlobTool = class extends BaseTool {
|
|
|
9456
9635
|
const withMtime = await Promise.all(
|
|
9457
9636
|
matches.map(async (rel) => {
|
|
9458
9637
|
try {
|
|
9459
|
-
const stat = await
|
|
9638
|
+
const stat = await fs10.stat(path26.join(searchPath, rel));
|
|
9460
9639
|
return { rel, mtime: stat.mtimeMs };
|
|
9461
9640
|
} catch {
|
|
9462
9641
|
return { rel, mtime: 0 };
|
|
@@ -9505,7 +9684,7 @@ var GrepTool = class extends BaseTool {
|
|
|
9505
9684
|
};
|
|
9506
9685
|
async execute(input, _options) {
|
|
9507
9686
|
const pattern = input["pattern"];
|
|
9508
|
-
const searchPath = input["path"] ?
|
|
9687
|
+
const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
9509
9688
|
const globPattern = input["glob"];
|
|
9510
9689
|
const outputMode = input["output_mode"] ?? "content";
|
|
9511
9690
|
const context = input["context"] ?? 0;
|
|
@@ -9559,15 +9738,15 @@ var GrepTool = class extends BaseTool {
|
|
|
9559
9738
|
nodir: true
|
|
9560
9739
|
});
|
|
9561
9740
|
} catch {
|
|
9562
|
-
files = [
|
|
9741
|
+
files = [path26.relative(searchPath, searchPath) || "."];
|
|
9563
9742
|
}
|
|
9564
9743
|
const results = [];
|
|
9565
9744
|
let totalCount = 0;
|
|
9566
9745
|
for (const rel of files) {
|
|
9567
|
-
const abs =
|
|
9746
|
+
const abs = path26.join(searchPath, rel);
|
|
9568
9747
|
let content;
|
|
9569
9748
|
try {
|
|
9570
|
-
content = await
|
|
9749
|
+
content = await fs10.readFile(abs, "utf-8");
|
|
9571
9750
|
} catch {
|
|
9572
9751
|
continue;
|
|
9573
9752
|
}
|
|
@@ -9926,10 +10105,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
9926
10105
|
}
|
|
9927
10106
|
isIgnored(filePath) {
|
|
9928
10107
|
if (!filePath) return false;
|
|
9929
|
-
const abs =
|
|
9930
|
-
const rel =
|
|
9931
|
-
if (!rel || rel.startsWith("..") ||
|
|
9932
|
-
const posixRel = rel.split(
|
|
10108
|
+
const abs = path26.resolve(this.workspaceRoot, filePath);
|
|
10109
|
+
const rel = path26.relative(this.workspaceRoot, abs);
|
|
10110
|
+
if (!rel || rel.startsWith("..") || path26.isAbsolute(rel)) return true;
|
|
10111
|
+
const posixRel = rel.split(path26.sep).join("/");
|
|
9933
10112
|
return this.ignoreMatcher.ignores(posixRel);
|
|
9934
10113
|
}
|
|
9935
10114
|
};
|
|
@@ -10452,7 +10631,7 @@ var TaskAnalyzer = class {
|
|
|
10452
10631
|
analysisCache.clear();
|
|
10453
10632
|
}
|
|
10454
10633
|
};
|
|
10455
|
-
var DEFAULT_STATS_FILE =
|
|
10634
|
+
var DEFAULT_STATS_FILE = path26.join(os8.homedir(), ".cascade", "model-perf.json");
|
|
10456
10635
|
var ModelPerformanceTracker = class {
|
|
10457
10636
|
stats = /* @__PURE__ */ new Map();
|
|
10458
10637
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -10465,7 +10644,7 @@ var ModelPerformanceTracker = class {
|
|
|
10465
10644
|
if (this.loaded) return;
|
|
10466
10645
|
this.loaded = true;
|
|
10467
10646
|
try {
|
|
10468
|
-
const raw = await
|
|
10647
|
+
const raw = await fs10.readFile(this.statsFile, "utf-8");
|
|
10469
10648
|
const parsed = JSON.parse(raw);
|
|
10470
10649
|
if (parsed.models) {
|
|
10471
10650
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -10484,12 +10663,12 @@ var ModelPerformanceTracker = class {
|
|
|
10484
10663
|
}
|
|
10485
10664
|
async save() {
|
|
10486
10665
|
try {
|
|
10487
|
-
await
|
|
10666
|
+
await fs10.mkdir(path26.dirname(this.statsFile), { recursive: true });
|
|
10488
10667
|
const modelsObj = {};
|
|
10489
10668
|
const featuresObj = {};
|
|
10490
10669
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
10491
10670
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
10492
|
-
await
|
|
10671
|
+
await fs10.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
|
|
10493
10672
|
} catch {
|
|
10494
10673
|
}
|
|
10495
10674
|
}
|
|
@@ -11023,9 +11202,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
11023
11202
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
11024
11203
|
async loadPersistedTools() {
|
|
11025
11204
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
11026
|
-
const file =
|
|
11205
|
+
const file = path26.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
11027
11206
|
try {
|
|
11028
|
-
const raw = await
|
|
11207
|
+
const raw = await fs10.readFile(file, "utf-8");
|
|
11029
11208
|
const specs = JSON.parse(raw);
|
|
11030
11209
|
if (!Array.isArray(specs)) return;
|
|
11031
11210
|
let loaded = 0;
|
|
@@ -11047,11 +11226,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
11047
11226
|
}
|
|
11048
11227
|
async persist() {
|
|
11049
11228
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
11050
|
-
const dir =
|
|
11051
|
-
const file =
|
|
11229
|
+
const dir = path26.join(this.workspacePath, ".cascade");
|
|
11230
|
+
const file = path26.join(dir, DYNAMIC_TOOLS_FILE);
|
|
11052
11231
|
try {
|
|
11053
|
-
await
|
|
11054
|
-
await
|
|
11232
|
+
await fs10.mkdir(dir, { recursive: true });
|
|
11233
|
+
await fs10.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
11055
11234
|
} catch (err) {
|
|
11056
11235
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
11057
11236
|
}
|
|
@@ -11068,12 +11247,12 @@ var WorldStateDB = class {
|
|
|
11068
11247
|
constructor(workspacePath, debugMode = false) {
|
|
11069
11248
|
this.workspacePath = workspacePath;
|
|
11070
11249
|
this.debugMode = debugMode;
|
|
11071
|
-
const cascadeDir =
|
|
11072
|
-
if (!
|
|
11073
|
-
|
|
11250
|
+
const cascadeDir = path26.join(workspacePath, ".cascade");
|
|
11251
|
+
if (!fs24.existsSync(cascadeDir)) {
|
|
11252
|
+
fs24.mkdirSync(cascadeDir, { recursive: true });
|
|
11074
11253
|
}
|
|
11075
|
-
this.keyPath =
|
|
11076
|
-
this.dbPath =
|
|
11254
|
+
this.keyPath = path26.join(cascadeDir, "world_state.key");
|
|
11255
|
+
this.dbPath = path26.join(cascadeDir, "world_state.db");
|
|
11077
11256
|
this.initEncryptionKey();
|
|
11078
11257
|
this.db = new Database2(this.dbPath);
|
|
11079
11258
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -11103,11 +11282,11 @@ var WorldStateDB = class {
|
|
|
11103
11282
|
dbPath;
|
|
11104
11283
|
encryptionKey;
|
|
11105
11284
|
initEncryptionKey() {
|
|
11106
|
-
if (
|
|
11107
|
-
this.encryptionKey =
|
|
11285
|
+
if (fs24.existsSync(this.keyPath)) {
|
|
11286
|
+
this.encryptionKey = fs24.readFileSync(this.keyPath);
|
|
11108
11287
|
} else {
|
|
11109
11288
|
this.encryptionKey = crypto.randomBytes(32);
|
|
11110
|
-
|
|
11289
|
+
fs24.writeFileSync(this.keyPath, this.encryptionKey);
|
|
11111
11290
|
}
|
|
11112
11291
|
}
|
|
11113
11292
|
encrypt(text) {
|
|
@@ -11248,6 +11427,22 @@ var WorldStateDB = class {
|
|
|
11248
11427
|
}
|
|
11249
11428
|
return selected.slice(0, limit).map((f) => `- ${f.entity} ${f.relation} ${f.value}`).join("\n");
|
|
11250
11429
|
}
|
|
11430
|
+
/**
|
|
11431
|
+
* Delete one fact by its (normalized) entity + relation key. Returns whether
|
|
11432
|
+
* a row was actually removed. Powers the desktop Knowledge tab's per-fact
|
|
11433
|
+
* delete — users can prune what the planner remembers about their project.
|
|
11434
|
+
*/
|
|
11435
|
+
deleteFact(entity, relation) {
|
|
11436
|
+
const e = normalizeKey(entity);
|
|
11437
|
+
const r = normalizeKey(relation);
|
|
11438
|
+
if (!e || !r) return false;
|
|
11439
|
+
const result = this.db.prepare("DELETE FROM facts WHERE entity = ? AND relation = ?").run(e, r);
|
|
11440
|
+
return result.changes > 0;
|
|
11441
|
+
}
|
|
11442
|
+
/** Delete every fact. Returns how many were removed. */
|
|
11443
|
+
clearFacts() {
|
|
11444
|
+
return this.db.prepare("DELETE FROM facts").run().changes;
|
|
11445
|
+
}
|
|
11251
11446
|
// ── Export / Import ──────────────────────────
|
|
11252
11447
|
//
|
|
11253
11448
|
// Knowledge travels DECRYPTED in the export bundle (a portable plaintext
|
|
@@ -11296,9 +11491,9 @@ var WorldStateDB = class {
|
|
|
11296
11491
|
dumpDebugIfNeeded() {
|
|
11297
11492
|
if (!this.debugMode) return;
|
|
11298
11493
|
try {
|
|
11299
|
-
const dumpPath =
|
|
11494
|
+
const dumpPath = path26.join(os8.tmpdir(), "cascade_world_state_debug.json");
|
|
11300
11495
|
const entries = this.getAllEntries();
|
|
11301
|
-
|
|
11496
|
+
fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
11302
11497
|
} catch (err) {
|
|
11303
11498
|
console.error("Failed to dump debug world state", err);
|
|
11304
11499
|
}
|
|
@@ -11733,13 +11928,30 @@ ${last.partialOutput}` : "");
|
|
|
11733
11928
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
11734
11929
|
* Simple/Moderate) don't get over-escalated.
|
|
11735
11930
|
*/
|
|
11736
|
-
|
|
11931
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
11932
|
+
buildSignals(prompt) {
|
|
11737
11933
|
const p = prompt.trim();
|
|
11738
|
-
if (p.length < 24) return false;
|
|
11934
|
+
if (p.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
11739
11935
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
11740
|
-
const
|
|
11936
|
+
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;
|
|
11741
11937
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
11742
|
-
return buildVerb
|
|
11938
|
+
return { buildVerb, scaleCount, multiPart };
|
|
11939
|
+
}
|
|
11940
|
+
/**
|
|
11941
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
11942
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
11943
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
11944
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
11945
|
+
* × workers for a task one worker handles).
|
|
11946
|
+
*/
|
|
11947
|
+
looksClearlyComplex(prompt) {
|
|
11948
|
+
const s = this.buildSignals(prompt);
|
|
11949
|
+
return s.buildVerb && (s.scaleCount >= 2 || s.scaleCount >= 1 && s.multiPart);
|
|
11950
|
+
}
|
|
11951
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
11952
|
+
looksLikeModerateBuild(prompt) {
|
|
11953
|
+
const s = this.buildSignals(prompt);
|
|
11954
|
+
return s.buildVerb && (s.scaleCount >= 1 || s.multiPart);
|
|
11743
11955
|
}
|
|
11744
11956
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
11745
11957
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -11827,6 +12039,9 @@ ${prompt}` : prompt;
|
|
|
11827
12039
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
11828
12040
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
11829
12041
|
verdict = "Complex";
|
|
12042
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
12043
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
12044
|
+
verdict = "Moderate";
|
|
11830
12045
|
} else {
|
|
11831
12046
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
11832
12047
|
}
|
|
@@ -13716,10 +13931,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13716
13931
|
}
|
|
13717
13932
|
} catch {
|
|
13718
13933
|
}
|
|
13719
|
-
const configPath =
|
|
13934
|
+
const configPath = path26.join(workspacePath, ".cascade", "config.json");
|
|
13720
13935
|
try {
|
|
13721
|
-
await
|
|
13722
|
-
await
|
|
13936
|
+
await fs10.mkdir(path26.dirname(configPath), { recursive: true });
|
|
13937
|
+
await fs10.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
13723
13938
|
} catch (err) {
|
|
13724
13939
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13725
13940
|
dispatch({
|
|
@@ -13770,9 +13985,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13770
13985
|
if (msg.includes("non-text parts")) return;
|
|
13771
13986
|
originalLog(...args);
|
|
13772
13987
|
};
|
|
13773
|
-
const store = new MemoryStore(
|
|
13988
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
13774
13989
|
storeRef.current = store;
|
|
13775
|
-
globalStoreRef.current = new MemoryStore(
|
|
13990
|
+
globalStoreRef.current = new MemoryStore(path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
13776
13991
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
13777
13992
|
setIdentities(identityRows);
|
|
13778
13993
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -13835,14 +14050,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
13835
14050
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
13836
14051
|
onExport: async (fmt) => {
|
|
13837
14052
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13838
|
-
const exportPath =
|
|
14053
|
+
const exportPath = path26.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
|
|
13839
14054
|
if (fmt === "json") {
|
|
13840
|
-
await
|
|
14055
|
+
await fs10.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
13841
14056
|
} else {
|
|
13842
14057
|
const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
|
|
13843
14058
|
|
|
13844
14059
|
${msg.content}`).join("\n\n");
|
|
13845
|
-
await
|
|
14060
|
+
await fs10.writeFile(exportPath, markdown, "utf-8");
|
|
13846
14061
|
}
|
|
13847
14062
|
},
|
|
13848
14063
|
onRollback: async () => {
|
|
@@ -13852,7 +14067,7 @@ ${msg.content}`).join("\n\n");
|
|
|
13852
14067
|
if (!snapshots.length) return "No file snapshots found for this session.";
|
|
13853
14068
|
for (const { filePath, content } of snapshots) {
|
|
13854
14069
|
try {
|
|
13855
|
-
await
|
|
14070
|
+
await fs10.writeFile(filePath, content, "utf-8");
|
|
13856
14071
|
} catch (err) {
|
|
13857
14072
|
console.error(`Restore failed: ${filePath}`, err);
|
|
13858
14073
|
}
|
|
@@ -14731,9 +14946,9 @@ function stringifySlashOutput(val) {
|
|
|
14731
14946
|
}
|
|
14732
14947
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
14733
14948
|
if (!query) return "Usage: /search <query>";
|
|
14734
|
-
const dbPath =
|
|
14949
|
+
const dbPath = path26.join(workspacePath, CASCADE_DB_FILE);
|
|
14735
14950
|
try {
|
|
14736
|
-
await
|
|
14951
|
+
await fs10.access(dbPath);
|
|
14737
14952
|
} catch {
|
|
14738
14953
|
return "No database found. Start a conversation first.";
|
|
14739
14954
|
}
|
|
@@ -14765,7 +14980,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
14765
14980
|
async function diagnoseRuntime(config, workspacePath) {
|
|
14766
14981
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
14767
14982
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
14768
|
-
const store = new MemoryStore(
|
|
14983
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
14769
14984
|
try {
|
|
14770
14985
|
const sessions = store.listSessions(void 0, 3);
|
|
14771
14986
|
return [
|
|
@@ -14784,7 +14999,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
14784
14999
|
}
|
|
14785
15000
|
async function showRecentLogs(args, workspacePath) {
|
|
14786
15001
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
14787
|
-
const store = new MemoryStore(
|
|
15002
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
14788
15003
|
try {
|
|
14789
15004
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
14790
15005
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -14796,7 +15011,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
14796
15011
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
14797
15012
|
const sessionId = args[0];
|
|
14798
15013
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
14799
|
-
const store = new MemoryStore(
|
|
15014
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
14800
15015
|
try {
|
|
14801
15016
|
const session = store.getSession(sessionId);
|
|
14802
15017
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -15216,10 +15431,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
15216
15431
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
15217
15432
|
};
|
|
15218
15433
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
15219
|
-
const configDir =
|
|
15220
|
-
await
|
|
15221
|
-
const configPath =
|
|
15222
|
-
await
|
|
15434
|
+
const configDir = path26.join(workspacePath, ".cascade");
|
|
15435
|
+
await fs10.mkdir(configDir, { recursive: true });
|
|
15436
|
+
const configPath = path26.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
15437
|
+
await fs10.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
15223
15438
|
savedConfigRef.current = config;
|
|
15224
15439
|
dispatchRef.current({ type: "GO_DONE" });
|
|
15225
15440
|
} catch (err) {
|
|
@@ -15559,14 +15774,14 @@ function printTelemetryBanner() {
|
|
|
15559
15774
|
async function initCommand(workspacePath = process.cwd()) {
|
|
15560
15775
|
const spin = ora({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
15561
15776
|
try {
|
|
15562
|
-
const configDir =
|
|
15563
|
-
await
|
|
15564
|
-
const mdPath =
|
|
15777
|
+
const configDir = path26.join(workspacePath, ".cascade");
|
|
15778
|
+
await fs10.mkdir(configDir, { recursive: true });
|
|
15779
|
+
const mdPath = path26.join(workspacePath, "CASCADE.md");
|
|
15565
15780
|
if (!await fileExists(mdPath)) {
|
|
15566
15781
|
await createDefaultCascadeMd(workspacePath);
|
|
15567
15782
|
spin.succeed(chalk9.green("Created CASCADE.md"));
|
|
15568
15783
|
}
|
|
15569
|
-
const ignorePath =
|
|
15784
|
+
const ignorePath = path26.join(workspacePath, ".cascadeignore");
|
|
15570
15785
|
if (!await fileExists(ignorePath)) {
|
|
15571
15786
|
await createDefaultIgnoreFile(workspacePath);
|
|
15572
15787
|
spin.succeed(chalk9.green("Created .cascadeignore"));
|
|
@@ -15575,7 +15790,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
15575
15790
|
console.log();
|
|
15576
15791
|
console.log(chalk9.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
15577
15792
|
console.log();
|
|
15578
|
-
const configPath =
|
|
15793
|
+
const configPath = path26.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
15579
15794
|
if (await fileExists(configPath)) {
|
|
15580
15795
|
console.log(chalk9.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
15581
15796
|
console.log();
|
|
@@ -15594,7 +15809,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
15594
15809
|
}
|
|
15595
15810
|
async function fileExists(p) {
|
|
15596
15811
|
try {
|
|
15597
|
-
await
|
|
15812
|
+
await fs10.access(p);
|
|
15598
15813
|
return true;
|
|
15599
15814
|
} catch {
|
|
15600
15815
|
return false;
|
|
@@ -15606,7 +15821,7 @@ init_constants();
|
|
|
15606
15821
|
var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
|
|
15607
15822
|
async function readJson(file) {
|
|
15608
15823
|
try {
|
|
15609
|
-
const raw = await
|
|
15824
|
+
const raw = await fs10.readFile(file, "utf-8");
|
|
15610
15825
|
return JSON.parse(raw);
|
|
15611
15826
|
} catch {
|
|
15612
15827
|
return null;
|
|
@@ -15633,7 +15848,7 @@ function fromEnv(env) {
|
|
|
15633
15848
|
return out;
|
|
15634
15849
|
}
|
|
15635
15850
|
async function fromClaudeCode(home) {
|
|
15636
|
-
const file =
|
|
15851
|
+
const file = path26.join(home, ".claude", ".credentials.json");
|
|
15637
15852
|
const data = await readJson(file);
|
|
15638
15853
|
if (!data) return [];
|
|
15639
15854
|
const oauth = data["claudeAiOauth"];
|
|
@@ -15656,7 +15871,7 @@ async function fromClaudeCode(home) {
|
|
|
15656
15871
|
return [];
|
|
15657
15872
|
}
|
|
15658
15873
|
async function fromCodex(home) {
|
|
15659
|
-
const file =
|
|
15874
|
+
const file = path26.join(home, ".codex", "auth.json");
|
|
15660
15875
|
const data = await readJson(file);
|
|
15661
15876
|
if (!data) return [];
|
|
15662
15877
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -15679,7 +15894,7 @@ async function fromCodex(home) {
|
|
|
15679
15894
|
return [];
|
|
15680
15895
|
}
|
|
15681
15896
|
async function fromGemini(home) {
|
|
15682
|
-
const file =
|
|
15897
|
+
const file = path26.join(home, ".gemini", "oauth_creds.json");
|
|
15683
15898
|
const data = await readJson(file);
|
|
15684
15899
|
const accessToken = str(data?.["access_token"]);
|
|
15685
15900
|
if (!accessToken) return [];
|
|
@@ -15695,7 +15910,7 @@ async function fromGemini(home) {
|
|
|
15695
15910
|
}
|
|
15696
15911
|
async function fromCopilot(home) {
|
|
15697
15912
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
15698
|
-
const file =
|
|
15913
|
+
const file = path26.join(home, ".config", "github-copilot", name);
|
|
15699
15914
|
const data = await readJson(file);
|
|
15700
15915
|
if (!data) continue;
|
|
15701
15916
|
for (const value of Object.values(data)) {
|
|
@@ -15749,7 +15964,7 @@ async function doctorCommand() {
|
|
|
15749
15964
|
checks.push({
|
|
15750
15965
|
label: "Cascade config",
|
|
15751
15966
|
ok: true,
|
|
15752
|
-
detail: `Loaded ${
|
|
15967
|
+
detail: `Loaded ${path26.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
15753
15968
|
});
|
|
15754
15969
|
const providers = [
|
|
15755
15970
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -15905,6 +16120,11 @@ function authMiddleware(secret, required = true) {
|
|
|
15905
16120
|
}
|
|
15906
16121
|
const user = verifyToken(token, secret);
|
|
15907
16122
|
if (!user) {
|
|
16123
|
+
if (!required) {
|
|
16124
|
+
req.user = void 0;
|
|
16125
|
+
next();
|
|
16126
|
+
return;
|
|
16127
|
+
}
|
|
15908
16128
|
res.status(401).json({ error: "Invalid or expired token" });
|
|
15909
16129
|
return;
|
|
15910
16130
|
}
|
|
@@ -16216,7 +16436,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
16216
16436
|
}
|
|
16217
16437
|
|
|
16218
16438
|
// src/dashboard/server.ts
|
|
16219
|
-
var __dirname$1 =
|
|
16439
|
+
var __dirname$1 = path26.dirname(fileURLToPath(import.meta.url));
|
|
16220
16440
|
var DashboardServer = class {
|
|
16221
16441
|
app;
|
|
16222
16442
|
httpServer;
|
|
@@ -16450,12 +16670,17 @@ var DashboardServer = class {
|
|
|
16450
16670
|
*/
|
|
16451
16671
|
persistConfig() {
|
|
16452
16672
|
try {
|
|
16453
|
-
const configPath =
|
|
16454
|
-
|
|
16455
|
-
|
|
16673
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
16674
|
+
fs24.mkdirSync(path26.dirname(configPath), { recursive: true });
|
|
16675
|
+
fs24.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
16456
16676
|
} catch (err) {
|
|
16457
16677
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
16458
16678
|
}
|
|
16679
|
+
try {
|
|
16680
|
+
saveGlobalCredentials(path26.join(os8.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
16681
|
+
} catch (err) {
|
|
16682
|
+
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
16683
|
+
}
|
|
16459
16684
|
}
|
|
16460
16685
|
/**
|
|
16461
16686
|
* Produce a stable dashboard JWT signing secret.
|
|
@@ -16467,15 +16692,15 @@ var DashboardServer = class {
|
|
|
16467
16692
|
resolveDashboardSecret() {
|
|
16468
16693
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
16469
16694
|
if (fromConfig) return fromConfig;
|
|
16470
|
-
const secretPath =
|
|
16695
|
+
const secretPath = path26.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
16471
16696
|
try {
|
|
16472
|
-
if (
|
|
16473
|
-
const existing =
|
|
16697
|
+
if (fs24.existsSync(secretPath)) {
|
|
16698
|
+
const existing = fs24.readFileSync(secretPath, "utf-8").trim();
|
|
16474
16699
|
if (existing.length >= 16) return existing;
|
|
16475
16700
|
}
|
|
16476
16701
|
const generated = randomUUID();
|
|
16477
|
-
|
|
16478
|
-
|
|
16702
|
+
fs24.mkdirSync(path26.dirname(secretPath), { recursive: true });
|
|
16703
|
+
fs24.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
16479
16704
|
if (this.config.dashboard.auth) {
|
|
16480
16705
|
console.warn(
|
|
16481
16706
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -16502,7 +16727,7 @@ var DashboardServer = class {
|
|
|
16502
16727
|
// ── Setup ─────────────────────────────────────
|
|
16503
16728
|
getGlobalStore() {
|
|
16504
16729
|
if (!this.globalStore) {
|
|
16505
|
-
const globalDbPath =
|
|
16730
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16506
16731
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
16507
16732
|
}
|
|
16508
16733
|
return this.globalStore;
|
|
@@ -16771,12 +16996,12 @@ ${prompt}`;
|
|
|
16771
16996
|
}
|
|
16772
16997
|
}
|
|
16773
16998
|
watchRuntimeChanges() {
|
|
16774
|
-
const workspaceDbPath =
|
|
16775
|
-
const globalDbPath =
|
|
16999
|
+
const workspaceDbPath = path26.join(this.workspacePath, CASCADE_DB_FILE);
|
|
17000
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16776
17001
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
16777
17002
|
for (const watchPath of watchPaths) {
|
|
16778
|
-
if (!
|
|
16779
|
-
|
|
17003
|
+
if (!fs24.existsSync(watchPath)) continue;
|
|
17004
|
+
fs24.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
16780
17005
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
16781
17006
|
});
|
|
16782
17007
|
}
|
|
@@ -16898,7 +17123,7 @@ ${prompt}`;
|
|
|
16898
17123
|
const sessionId = req.params.id;
|
|
16899
17124
|
this.store.deleteSession(sessionId);
|
|
16900
17125
|
this.store.deleteRuntimeSession(sessionId);
|
|
16901
|
-
const globalDbPath =
|
|
17126
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16902
17127
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16903
17128
|
try {
|
|
16904
17129
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -16991,7 +17216,7 @@ ${prompt}`;
|
|
|
16991
17216
|
});
|
|
16992
17217
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
16993
17218
|
const body = req.body;
|
|
16994
|
-
const globalDbPath =
|
|
17219
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
16995
17220
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
16996
17221
|
const globalStore = new MemoryStore(globalDbPath);
|
|
16997
17222
|
try {
|
|
@@ -17014,7 +17239,7 @@ ${prompt}`;
|
|
|
17014
17239
|
});
|
|
17015
17240
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
17016
17241
|
this.store.deleteAllRuntimeNodes();
|
|
17017
|
-
const globalDbPath =
|
|
17242
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
17018
17243
|
const globalStore = new MemoryStore(globalDbPath);
|
|
17019
17244
|
try {
|
|
17020
17245
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -17100,12 +17325,12 @@ ${prompt}`;
|
|
|
17100
17325
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
17101
17326
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
17102
17327
|
try {
|
|
17103
|
-
const configPath =
|
|
17104
|
-
const existing =
|
|
17328
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
17329
|
+
const existing = fs24.existsSync(configPath) ? JSON.parse(fs24.readFileSync(configPath, "utf-8")) : {};
|
|
17105
17330
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
17106
17331
|
const tmp = configPath + ".tmp";
|
|
17107
|
-
|
|
17108
|
-
|
|
17332
|
+
fs24.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
17333
|
+
fs24.renameSync(tmp, configPath);
|
|
17109
17334
|
res.json({ ok: true });
|
|
17110
17335
|
} catch (err) {
|
|
17111
17336
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -17133,7 +17358,7 @@ ${prompt}`;
|
|
|
17133
17358
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
17134
17359
|
const scope = req.query["scope"] ?? "workspace";
|
|
17135
17360
|
if (scope === "global") {
|
|
17136
|
-
const globalDbPath =
|
|
17361
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
17137
17362
|
const globalStore = new MemoryStore(globalDbPath);
|
|
17138
17363
|
try {
|
|
17139
17364
|
res.json({
|
|
@@ -17368,6 +17593,48 @@ ${prompt}`;
|
|
|
17368
17593
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17369
17594
|
}
|
|
17370
17595
|
});
|
|
17596
|
+
this.app.get("/api/knowledge", auth, (_req, res) => {
|
|
17597
|
+
try {
|
|
17598
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17599
|
+
try {
|
|
17600
|
+
const facts = ws.getAllFacts();
|
|
17601
|
+
res.json({ total: facts.length, facts });
|
|
17602
|
+
} finally {
|
|
17603
|
+
ws.close();
|
|
17604
|
+
}
|
|
17605
|
+
} catch (err) {
|
|
17606
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17607
|
+
}
|
|
17608
|
+
});
|
|
17609
|
+
this.app.delete("/api/knowledge/fact", auth, mutationLimiter, (req, res) => {
|
|
17610
|
+
const body = req.body;
|
|
17611
|
+
if (!body.entity || !body.relation) {
|
|
17612
|
+
res.status(400).json({ error: "entity and relation are required" });
|
|
17613
|
+
return;
|
|
17614
|
+
}
|
|
17615
|
+
try {
|
|
17616
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17617
|
+
try {
|
|
17618
|
+
res.json({ ok: true, deleted: ws.deleteFact(body.entity, body.relation) });
|
|
17619
|
+
} finally {
|
|
17620
|
+
ws.close();
|
|
17621
|
+
}
|
|
17622
|
+
} catch (err) {
|
|
17623
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17624
|
+
}
|
|
17625
|
+
});
|
|
17626
|
+
this.app.delete("/api/knowledge", auth, mutationLimiter, (_req, res) => {
|
|
17627
|
+
try {
|
|
17628
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
17629
|
+
try {
|
|
17630
|
+
res.json({ ok: true, deleted: ws.clearFacts() });
|
|
17631
|
+
} finally {
|
|
17632
|
+
ws.close();
|
|
17633
|
+
}
|
|
17634
|
+
} catch (err) {
|
|
17635
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17636
|
+
}
|
|
17637
|
+
});
|
|
17371
17638
|
this.app.get("/api/audit-chain", auth, async (req, res) => {
|
|
17372
17639
|
try {
|
|
17373
17640
|
const limit = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
|
|
@@ -17386,13 +17653,13 @@ ${prompt}`;
|
|
|
17386
17653
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
17387
17654
|
}
|
|
17388
17655
|
});
|
|
17389
|
-
const prodPath =
|
|
17390
|
-
const devPath =
|
|
17391
|
-
const webDistPath =
|
|
17392
|
-
if (
|
|
17656
|
+
const prodPath = path26.resolve(__dirname$1, "../web/dist");
|
|
17657
|
+
const devPath = path26.resolve(__dirname$1, "../../web/dist");
|
|
17658
|
+
const webDistPath = fs24.existsSync(prodPath) ? prodPath : devPath;
|
|
17659
|
+
if (fs24.existsSync(webDistPath)) {
|
|
17393
17660
|
this.app.use(express.static(webDistPath));
|
|
17394
17661
|
this.app.get("*", (_req, res) => {
|
|
17395
|
-
res.sendFile(
|
|
17662
|
+
res.sendFile(path26.join(webDistPath, "index.html"));
|
|
17396
17663
|
});
|
|
17397
17664
|
} else {
|
|
17398
17665
|
this.app.get("/", (_req, res) => {
|
|
@@ -17413,7 +17680,7 @@ init_constants();
|
|
|
17413
17680
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
17414
17681
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
17415
17682
|
const spin = ora({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
17416
|
-
const store = new MemoryStore(
|
|
17683
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
17417
17684
|
const server = new DashboardServer(config, store, workspacePath);
|
|
17418
17685
|
server.watchRuntimeChanges();
|
|
17419
17686
|
const gThis = globalThis;
|
|
@@ -17445,7 +17712,7 @@ init_constants();
|
|
|
17445
17712
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
17446
17713
|
const identity = new Command("identity").alias("id").description("Manage Cascade identities");
|
|
17447
17714
|
identity.command("list").description("List all available identities").action(() => {
|
|
17448
|
-
const store = new MemoryStore(
|
|
17715
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
17449
17716
|
try {
|
|
17450
17717
|
const identities = store.listIdentities();
|
|
17451
17718
|
console.log(chalk9.bold("\n Identities:"));
|
|
@@ -17465,7 +17732,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
17465
17732
|
}
|
|
17466
17733
|
});
|
|
17467
17734
|
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) => {
|
|
17468
|
-
const store = new MemoryStore(
|
|
17735
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
17469
17736
|
try {
|
|
17470
17737
|
if (options.default) {
|
|
17471
17738
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -17491,7 +17758,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
17491
17758
|
}
|
|
17492
17759
|
});
|
|
17493
17760
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
17494
|
-
const store = new MemoryStore(
|
|
17761
|
+
const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
|
|
17495
17762
|
try {
|
|
17496
17763
|
const identities = store.listIdentities();
|
|
17497
17764
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
@@ -17619,11 +17886,11 @@ async function exportCommand(options = {}) {
|
|
|
17619
17886
|
let store;
|
|
17620
17887
|
try {
|
|
17621
17888
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
17622
|
-
const workspaceDbPath =
|
|
17623
|
-
const globalDbPath =
|
|
17889
|
+
const workspaceDbPath = path26.join(workspacePath, CASCADE_DB_FILE);
|
|
17890
|
+
const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
17624
17891
|
let dbPath = globalDbPath;
|
|
17625
17892
|
try {
|
|
17626
|
-
await
|
|
17893
|
+
await fs10.access(workspaceDbPath);
|
|
17627
17894
|
dbPath = workspaceDbPath;
|
|
17628
17895
|
} catch {
|
|
17629
17896
|
}
|
|
@@ -17662,8 +17929,8 @@ async function exportCommand(options = {}) {
|
|
|
17662
17929
|
const ext = format === "json" ? ".json" : ".md";
|
|
17663
17930
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
17664
17931
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
17665
|
-
const outPath = options.output ?
|
|
17666
|
-
await
|
|
17932
|
+
const outPath = options.output ? path26.resolve(options.output) : path26.join(process.cwd(), defaultFile);
|
|
17933
|
+
await fs10.writeFile(outPath, content, "utf-8");
|
|
17667
17934
|
spin.succeed(chalk9.green(`Exported to ${chalk9.white(outPath)}`));
|
|
17668
17935
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
17669
17936
|
console.log();
|
|
@@ -17890,11 +18157,11 @@ async function statsCommand() {
|
|
|
17890
18157
|
dotenv.config();
|
|
17891
18158
|
function warnIfBuildIsStale() {
|
|
17892
18159
|
try {
|
|
17893
|
-
const here =
|
|
18160
|
+
const here = path26.dirname(fileURLToPath(import.meta.url));
|
|
17894
18161
|
for (const rel of ["..", "../.."]) {
|
|
17895
|
-
const pkgPath =
|
|
17896
|
-
if (!
|
|
17897
|
-
const pkg = JSON.parse(
|
|
18162
|
+
const pkgPath = path26.join(here, rel, "package.json");
|
|
18163
|
+
if (!fs24.existsSync(pkgPath)) continue;
|
|
18164
|
+
const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
|
|
17898
18165
|
if (pkg.name !== "cascade-ai") continue;
|
|
17899
18166
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
17900
18167
|
console.error(
|