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 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 path25 = require('path');
13
- var fs23 = require('fs');
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 fs9 = require('fs/promises');
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 path25__default = /*#__PURE__*/_interopDefault(path25);
77
- var fs23__default = /*#__PURE__*/_interopDefault(fs23);
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 fs9__default = /*#__PURE__*/_interopDefault(fs9);
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.17.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
- return [this.model];
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 = path25__default.default.join(workspacePath, ".cascade");
1636
- if (!fs23__default.default.existsSync(cascadeDir)) {
1637
- fs23__default.default.mkdirSync(cascadeDir, { recursive: true });
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 = path25__default.default.join(cascadeDir, "audit_log.key");
1640
- this.dbPath = path25__default.default.join(cascadeDir, "audit_log.db");
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 (fs23__default.default.existsSync(this.keyPath)) {
1671
- this.encryptionKey = fs23__default.default.readFileSync(this.keyPath);
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
- fs23__default.default.writeFileSync(this.keyPath, this.encryptionKey);
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 = path25__default.default.join(os8__default.default.tmpdir(), "cascade_audit_logs_debug.json");
1772
+ const dumpPath = path26__default.default.join(os8__default.default.tmpdir(), "cascade_audit_logs_debug.json");
1753
1773
  const entries = this.getAllLogs();
1754
- fs23__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
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 && fs23__default.default.existsSync(this.storePath)) {
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 (!fs23__default.default.existsSync(this.storePath)) {
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 (!fs23__default.default.existsSync(this.storePath)) {
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 (!fs23__default.default.existsSync(this.storePath)) return {};
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
- fs23__default.default.mkdirSync(path25__default.default.dirname(this.storePath), { recursive: true });
1923
- fs23__default.default.writeFileSync(this.storePath, out, { mode: 384 });
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
- fs23__default.default.mkdirSync(path25__default.default.dirname(this.storePath), { recursive: true });
1934
- fs23__default.default.writeFileSync(this.storePath, out, { mode: 384 });
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 = fs23__default.default.readFileSync(this.storePath);
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 = path25__default.default.join(workspacePath, ".cascadeignore");
1990
+ const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
1971
1991
  try {
1972
- const content = await fs9__default.default.readFile(filePath, "utf-8");
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 ? path25__default.default.relative(workspacePath, filePath) : filePath;
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 = path25__default.default.join(workspacePath, ".cascadeignore");
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 fs9__default.default.writeFile(filePath, content, "utf-8");
2039
+ await fs10__default.default.writeFile(filePath, content, "utf-8");
2020
2040
  }
2021
2041
  async function loadCascadeMd(workspacePath) {
2022
- const filePath = path25__default.default.join(workspacePath, "CASCADE.md");
2042
+ const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
2023
2043
  try {
2024
- const raw = await fs9__default.default.readFile(filePath, "utf-8");
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 = path25__default.default.join(workspacePath, "CASCADE.md");
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 fs9__default.default.writeFile(filePath, content, "utf-8");
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
- fs23__default.default.mkdirSync(path25__default.default.dirname(dbPath), { recursive: true });
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(false),
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
- constructor(workspacePath = process.cwd()) {
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR);
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(path25__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
3258
- this.store = new MemoryStore(path25__default.default.join(this.workspacePath, CASCADE_DB_FILE));
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 = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
3282
- await fs9__default.default.mkdir(path25__default.default.dirname(configPath), { recursive: true });
3283
- await fs9__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
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 = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
3393
+ const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
3307
3394
  try {
3308
- const raw = await fs9__default.default.readFile(configPath, "utf-8");
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
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 fs9__default.default.readFile(this.opts.cacheFile, "utf-8");
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 fs9__default.default.mkdir(path25__default.default.dirname(this.opts.cacheFile), { recursive: true });
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 fs9__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
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 (path30, content) => {
6235
- this.store?.addFileSnapshot(this.taskId, path30, content);
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 [...new Set(matches.map((m) => m.trim()))];
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 = path25__default.default.resolve(process.cwd(), artifactPath);
6528
+ const absolutePath = path26__default.default.resolve(process.cwd(), artifactPath);
6406
6529
  try {
6407
- const stat = await fs9__default.default.stat(absolutePath);
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 fs9__default.default.readFile(absolutePath, "utf-8");
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 = path25__default.default.extname(absolutePath).toLowerCase();
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 2-5 concrete subtasks for T3 workers.
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 = path25__default.default.resolve(workspaceRoot);
8518
- const abs = path25__default.default.isAbsolute(input) ? path25__default.default.resolve(input) : path25__default.default.resolve(root, input);
8519
- const rel = path25__default.default.relative(root, abs);
8520
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path25__default.default.isAbsolute(rel)) {
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 = fs23__default.default.realpathSync(abs);
8525
- const realRel = path25__default.default.relative(root, real);
8526
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path25__default.default.isAbsolute(realRel))) {
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 fs9__default.default.readFile(absPath, "utf-8");
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 fs9__default.default.readFile(absPath, "utf-8");
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 fs9__default.default.mkdir(path25__default.default.dirname(absPath), { recursive: true });
8588
- await fs9__default.default.writeFile(absPath, content, "utf-8");
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 fs9__default.default.readFile(absPath, "utf-8");
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 fs9__default.default.writeFile(absPath, updated, "utf-8");
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 fs9__default.default.readFile(absPath, "utf-8");
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 fs9__default.default.rm(absPath, { recursive: false });
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 fs9__default.default.readdir(absPath, { withFileTypes: true });
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 fs9__default.default.readFile(filePath);
9085
- const ext = path25__default.default.extname(filePath).toLowerCase();
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 = path25__default.default.dirname(filePath);
9120
- if (!fs23__default.default.existsSync(dir)) {
9121
- fs23__default.default.mkdirSync(dir, { recursive: true });
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 = fs23__default.default.createWriteStream(filePath);
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 = path25__default.default.join(this.workspaceRoot, ".cascade", "tmp");
9205
- if (!fs23__default.default.existsSync(tmpDir)) {
9206
- fs23__default.default.mkdirSync(tmpDir, { recursive: true });
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 = path25__default.default.join(tmpDir, fileName);
9211
- fs23__default.default.writeFileSync(filePath, code, "utf-8");
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 (fs23__default.default.existsSync(filePath)) {
9219
- fs23__default.default.unlinkSync(filePath);
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
- async function searchDuckDuckGoLite(query, maxResults) {
9371
- const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
9372
- headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
9373
- signal: AbortSignal.timeout(1e4)
9374
- });
9375
- if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
9376
- const html = await resp.text();
9377
- const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
9378
- const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
9379
- const links = [];
9380
- const snippets = [];
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(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;|&#39;/g, "'").replace(/&nbsp;/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 = linkPattern.exec(html)) !== null) {
9383
- links.push({ url: m[1], title: m[2].trim() });
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
- while ((m = snippetPattern.exec(html)) !== null) {
9386
- snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
9552
+ const snippets = [];
9553
+ while ((m = snippetRe.exec(html)) !== null) {
9554
+ snippets.push(decodeEntities(stripTags(m[1] ?? "")));
9387
9555
  }
9388
- return links.slice(0, maxResults).map((link, i) => ({
9389
- title: link.title,
9390
- url: link.url,
9391
- snippet: snippets[i] ?? "",
9392
- engine: "duckduckgo-lite"
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
- try {
9453
- results = await searchDuckDuckGoLite(query, maxResults);
9454
- if (results.length > 0) return this.formatResults(query, results);
9455
- errors.push("DuckDuckGo Lite: returned 0 results");
9456
- } catch (err) {
9457
- errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
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"] ? path25__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
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 fs9__default.default.stat(path25__default.default.join(searchPath, rel));
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"] ? path25__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
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 = [path25__default.default.relative(searchPath, searchPath) || "."];
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 = path25__default.default.join(searchPath, rel);
9798
+ const abs = path26__default.default.join(searchPath, rel);
9620
9799
  let content;
9621
9800
  try {
9622
- content = await fs9__default.default.readFile(abs, "utf-8");
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 = path25__default.default.resolve(this.workspaceRoot, filePath);
9982
- const rel = path25__default.default.relative(this.workspaceRoot, abs);
9983
- if (!rel || rel.startsWith("..") || path25__default.default.isAbsolute(rel)) return true;
9984
- const posixRel = rel.split(path25__default.default.sep).join("/");
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 = path25__default.default.join(os8__default.default.homedir(), ".cascade", "model-perf.json");
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 fs9__default.default.readFile(this.statsFile, "utf-8");
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 fs9__default.default.mkdir(path25__default.default.dirname(this.statsFile), { recursive: true });
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 fs9__default.default.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
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 = path25__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
11257
+ const file = path26__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
11079
11258
  try {
11080
- const raw = await fs9__default.default.readFile(file, "utf-8");
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 = path25__default.default.join(this.workspacePath, ".cascade");
11103
- const file = path25__default.default.join(dir, DYNAMIC_TOOLS_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 fs9__default.default.mkdir(dir, { recursive: true });
11106
- await fs9__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
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 = path25__default.default.join(workspacePath, ".cascade");
11124
- if (!fs23__default.default.existsSync(cascadeDir)) {
11125
- fs23__default.default.mkdirSync(cascadeDir, { recursive: true });
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 = path25__default.default.join(cascadeDir, "world_state.key");
11128
- this.dbPath = path25__default.default.join(cascadeDir, "world_state.db");
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 (fs23__default.default.existsSync(this.keyPath)) {
11159
- this.encryptionKey = fs23__default.default.readFileSync(this.keyPath);
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
- fs23__default.default.writeFileSync(this.keyPath, this.encryptionKey);
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 = path25__default.default.join(os8__default.default.tmpdir(), "cascade_world_state_debug.json");
11546
+ const dumpPath = path26__default.default.join(os8__default.default.tmpdir(), "cascade_world_state_debug.json");
11352
11547
  const entries = this.getAllEntries();
11353
- fs23__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
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
- looksClearlyComplex(prompt) {
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 scaleNoun = /\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/i.test(p);
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 && (scaleNoun || multiPart);
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 = path25__default.default.join(workspacePath, ".cascade", "config.json");
13986
+ const configPath = path26__default.default.join(workspacePath, ".cascade", "config.json");
13772
13987
  try {
13773
- await fs9__default.default.mkdir(path25__default.default.dirname(configPath), { recursive: true });
13774
- await fs9__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
14040
+ const store = new MemoryStore(path26__default.default.join(workspacePath, CASCADE_DB_FILE));
13826
14041
  storeRef.current = store;
13827
- globalStoreRef.current = new MemoryStore(path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
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 = path25__default.default.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
14105
+ const exportPath = path26__default.default.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
13891
14106
  if (fmt === "json") {
13892
- await fs9__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
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 fs9__default.default.writeFile(exportPath, markdown, "utf-8");
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 fs9__default.default.writeFile(filePath, content, "utf-8");
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 = path25__default.default.join(workspacePath, CASCADE_DB_FILE);
15001
+ const dbPath = path26__default.default.join(workspacePath, CASCADE_DB_FILE);
14787
15002
  try {
14788
- await fs9__default.default.access(dbPath);
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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 = path25__default.default.join(workspacePath, ".cascade");
15272
- await fs9__default.default.mkdir(configDir, { recursive: true });
15273
- const configPath = path25__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
15274
- await fs9__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
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 = path25__default.default.join(workspacePath, ".cascade");
15615
- await fs9__default.default.mkdir(configDir, { recursive: true });
15616
- const mdPath = path25__default.default.join(workspacePath, "CASCADE.md");
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 = path25__default.default.join(workspacePath, ".cascadeignore");
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 = path25__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
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 fs9__default.default.access(p);
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 fs9__default.default.readFile(file, "utf-8");
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 = path25__default.default.join(home, ".claude", ".credentials.json");
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 = path25__default.default.join(home, ".codex", "auth.json");
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 = path25__default.default.join(home, ".gemini", "oauth_creds.json");
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 = path25__default.default.join(home, ".config", "github-copilot", name);
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 ${path25__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
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 = path25__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))));
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 = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
16506
- fs23__default.default.mkdirSync(path25__default.default.dirname(configPath), { recursive: true });
16507
- fs23__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
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 = path25__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
16747
+ const secretPath = path26__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
16523
16748
  try {
16524
- if (fs23__default.default.existsSync(secretPath)) {
16525
- const existing = fs23__default.default.readFileSync(secretPath, "utf-8").trim();
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
- fs23__default.default.mkdirSync(path25__default.default.dirname(secretPath), { recursive: true });
16530
- fs23__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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 = path25__default.default.join(this.workspacePath, CASCADE_DB_FILE);
16827
- const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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 (!fs23__default.default.existsSync(watchPath)) continue;
16831
- fs23__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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 = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
17156
- const existing = fs23__default.default.existsSync(configPath) ? JSON.parse(fs23__default.default.readFileSync(configPath, "utf-8")) : {};
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
- fs23__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
17160
- fs23__default.default.renameSync(tmp, configPath);
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 = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
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 = path25__default.default.resolve(__dirname$1, "../web/dist");
17442
- const devPath = path25__default.default.resolve(__dirname$1, "../../web/dist");
17443
- const webDistPath = fs23__default.default.existsSync(prodPath) ? prodPath : devPath;
17444
- if (fs23__default.default.existsSync(webDistPath)) {
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(path25__default.default.join(webDistPath, "index.html"));
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
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 = path25__default.default.join(workspacePath, CASCADE_DB_FILE);
17675
- const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
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 fs9__default.default.access(workspaceDbPath);
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 ? path25__default.default.resolve(options.output) : path25__default.default.join(process.cwd(), defaultFile);
17718
- await fs9__default.default.writeFile(outPath, content, "utf-8");
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 = path25__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))));
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 = path25__default.default.join(here, rel, "package.json");
17948
- if (!fs23__default.default.existsSync(pkgPath)) continue;
17949
- const pkg = JSON.parse(fs23__default.default.readFileSync(pkgPath, "utf-8"));
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(