cascade-ai 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,8 +7,8 @@ import https from 'https';
7
7
  import zlib from 'zlib';
8
8
  import { Readable } from 'stream';
9
9
  import Database2 from 'better-sqlite3';
10
- import path25 from 'path';
11
- import fs23 from 'fs';
10
+ import path26 from 'path';
11
+ import fs24 from 'fs';
12
12
  import os8 from 'os';
13
13
  import crypto, { randomUUID, timingSafeEqual } from 'crypto';
14
14
  import { render, Box, Text, useStdout, useApp, useInput, Static } from 'ink';
@@ -17,7 +17,7 @@ import React2, { useState, useReducer, useRef, useCallback, useEffect, useMemo }
17
17
  import chalk9 from 'chalk';
18
18
  import dotenv from 'dotenv';
19
19
  import { fileURLToPath } from 'url';
20
- import fs9 from 'fs/promises';
20
+ import fs10 from 'fs/promises';
21
21
  import * as _ignoreModule from 'ignore';
22
22
  import _ignoreModule__default from 'ignore';
23
23
  import { z } from 'zod';
@@ -42,6 +42,7 @@ import bcrypt from 'bcryptjs';
42
42
  import { Server } from 'socket.io';
43
43
  import parser from 'socket.io-msgpack-parser';
44
44
  import jwt from 'jsonwebtoken';
45
+ import cron from 'node-cron';
45
46
 
46
47
  // Cascade AI — Multi-tier AI Orchestration System
47
48
  var __defProp = Object.defineProperty;
@@ -55,16 +56,17 @@ var __export = (target, all) => {
55
56
  };
56
57
 
57
58
  // src/constants.ts
58
- var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
59
+ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
59
60
  var init_constants = __esm({
60
61
  "src/constants.ts"() {
61
- CASCADE_VERSION = "0.16.0";
62
+ CASCADE_VERSION = "0.18.0";
62
63
  CASCADE_CONFIG_FILE = ".cascade/config.json";
63
64
  CASCADE_DB_FILE = ".cascade/memory.db";
64
65
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
65
66
  GLOBAL_CONFIG_DIR = ".cascade-ai";
66
67
  GLOBAL_DB_FILE = "memory.db";
67
68
  GLOBAL_KEYSTORE_FILE = "keystore.enc";
69
+ GLOBAL_CREDENTIALS_FILE = "credentials.json";
68
70
  GLOBAL_RUNTIME_DB_FILE = "runtime.db";
69
71
  DEFAULT_DASHBOARD_PORT = 4891;
70
72
  DEFAULT_CONTEXT_LIMIT = 2e5;
@@ -1579,12 +1581,12 @@ var init_audit_logger = __esm({
1579
1581
  constructor(workspacePath, debugMode = false) {
1580
1582
  this.workspacePath = workspacePath;
1581
1583
  this.debugMode = debugMode;
1582
- const cascadeDir = path25.join(workspacePath, ".cascade");
1583
- if (!fs23.existsSync(cascadeDir)) {
1584
- fs23.mkdirSync(cascadeDir, { recursive: true });
1584
+ const cascadeDir = path26.join(workspacePath, ".cascade");
1585
+ if (!fs24.existsSync(cascadeDir)) {
1586
+ fs24.mkdirSync(cascadeDir, { recursive: true });
1585
1587
  }
1586
- this.keyPath = path25.join(cascadeDir, "audit_log.key");
1587
- this.dbPath = path25.join(cascadeDir, "audit_log.db");
1588
+ this.keyPath = path26.join(cascadeDir, "audit_log.key");
1589
+ this.dbPath = path26.join(cascadeDir, "audit_log.db");
1588
1590
  this.initEncryptionKey();
1589
1591
  this.db = new Database2(this.dbPath);
1590
1592
  this.db.pragma("journal_mode = WAL");
@@ -1614,11 +1616,11 @@ var init_audit_logger = __esm({
1614
1616
  return crypto.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
1615
1617
  }
1616
1618
  initEncryptionKey() {
1617
- if (fs23.existsSync(this.keyPath)) {
1618
- this.encryptionKey = fs23.readFileSync(this.keyPath);
1619
+ if (fs24.existsSync(this.keyPath)) {
1620
+ this.encryptionKey = fs24.readFileSync(this.keyPath);
1619
1621
  } else {
1620
1622
  this.encryptionKey = crypto.randomBytes(32);
1621
- fs23.writeFileSync(this.keyPath, this.encryptionKey);
1623
+ fs24.writeFileSync(this.keyPath, this.encryptionKey);
1622
1624
  }
1623
1625
  }
1624
1626
  encrypt(text) {
@@ -1696,9 +1698,9 @@ var init_audit_logger = __esm({
1696
1698
  dumpDebugIfNeeded() {
1697
1699
  if (!this.debugMode) return;
1698
1700
  try {
1699
- const dumpPath = path25.join(os8.tmpdir(), "cascade_audit_logs_debug.json");
1701
+ const dumpPath = path26.join(os8.tmpdir(), "cascade_audit_logs_debug.json");
1700
1702
  const entries = this.getAllLogs();
1701
- fs23.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
1703
+ fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
1702
1704
  } catch (err) {
1703
1705
  console.error("Failed to dump debug audit logs", err);
1704
1706
  }
@@ -1753,7 +1755,7 @@ var Keystore = class {
1753
1755
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
1754
1756
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
1755
1757
  this.backend = "keytar";
1756
- if (password && fs23.existsSync(this.storePath)) {
1758
+ if (password && fs24.existsSync(this.storePath)) {
1757
1759
  try {
1758
1760
  const fileEntries = this.decryptFile(password);
1759
1761
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -1772,7 +1774,7 @@ var Keystore = class {
1772
1774
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
1773
1775
  );
1774
1776
  }
1775
- if (!fs23.existsSync(this.storePath)) {
1777
+ if (!fs24.existsSync(this.storePath)) {
1776
1778
  const salt = crypto.randomBytes(SALT_LEN);
1777
1779
  this.masterKey = this.deriveKey(password, salt);
1778
1780
  this.writeWithSalt({}, salt);
@@ -1786,7 +1788,7 @@ var Keystore = class {
1786
1788
  }
1787
1789
  /** Synchronous legacy unlock kept for AES-only environments. */
1788
1790
  unlockSync(password) {
1789
- if (!fs23.existsSync(this.storePath)) {
1791
+ if (!fs24.existsSync(this.storePath)) {
1790
1792
  const salt = crypto.randomBytes(SALT_LEN);
1791
1793
  this.masterKey = this.deriveKey(password, salt);
1792
1794
  this.writeWithSalt({}, salt);
@@ -1844,7 +1846,7 @@ var Keystore = class {
1844
1846
  }
1845
1847
  }
1846
1848
  decryptFile(password, knownSalt) {
1847
- if (!fs23.existsSync(this.storePath)) return {};
1849
+ if (!fs24.existsSync(this.storePath)) return {};
1848
1850
  try {
1849
1851
  const { salt, ciphertext, iv, tag } = this.readRaw();
1850
1852
  const useSalt = knownSalt ?? salt;
@@ -1866,8 +1868,8 @@ var Keystore = class {
1866
1868
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1867
1869
  const tag = cipher.getAuthTag();
1868
1870
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
1869
- fs23.mkdirSync(path25.dirname(this.storePath), { recursive: true });
1870
- fs23.writeFileSync(this.storePath, out, { mode: 384 });
1871
+ fs24.mkdirSync(path26.dirname(this.storePath), { recursive: true });
1872
+ fs24.writeFileSync(this.storePath, out, { mode: 384 });
1871
1873
  }
1872
1874
  writeWithSalt(data, salt) {
1873
1875
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -1877,11 +1879,11 @@ var Keystore = class {
1877
1879
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1878
1880
  const tag = cipher.getAuthTag();
1879
1881
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
1880
- fs23.mkdirSync(path25.dirname(this.storePath), { recursive: true });
1881
- fs23.writeFileSync(this.storePath, out, { mode: 384 });
1882
+ fs24.mkdirSync(path26.dirname(this.storePath), { recursive: true });
1883
+ fs24.writeFileSync(this.storePath, out, { mode: 384 });
1882
1884
  }
1883
1885
  readRaw() {
1884
- const buf = fs23.readFileSync(this.storePath);
1886
+ const buf = fs24.readFileSync(this.storePath);
1885
1887
  let offset = 0;
1886
1888
  const salt = buf.subarray(offset, offset + SALT_LEN);
1887
1889
  offset += SALT_LEN;
@@ -1914,9 +1916,9 @@ var CascadeIgnore = class {
1914
1916
  ]);
1915
1917
  }
1916
1918
  async load(workspacePath) {
1917
- const filePath = path25.join(workspacePath, ".cascadeignore");
1919
+ const filePath = path26.join(workspacePath, ".cascadeignore");
1918
1920
  try {
1919
- const content = await fs9.readFile(filePath, "utf-8");
1921
+ const content = await fs10.readFile(filePath, "utf-8");
1920
1922
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
1921
1923
  this.ig.add(lines);
1922
1924
  this.loaded = true;
@@ -1925,7 +1927,7 @@ var CascadeIgnore = class {
1925
1927
  }
1926
1928
  isIgnored(filePath, workspacePath) {
1927
1929
  try {
1928
- const relative = workspacePath ? path25.relative(workspacePath, filePath) : filePath;
1930
+ const relative = workspacePath ? path26.relative(workspacePath, filePath) : filePath;
1929
1931
  return this.ig.ignores(relative);
1930
1932
  } catch {
1931
1933
  return false;
@@ -1936,7 +1938,7 @@ var CascadeIgnore = class {
1936
1938
  }
1937
1939
  };
1938
1940
  async function createDefaultIgnoreFile(workspacePath) {
1939
- const filePath = path25.join(workspacePath, ".cascadeignore");
1941
+ const filePath = path26.join(workspacePath, ".cascadeignore");
1940
1942
  const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
1941
1943
  # Syntax identical to .gitignore
1942
1944
 
@@ -1963,12 +1965,12 @@ build/
1963
1965
  .DS_Store
1964
1966
  Thumbs.db
1965
1967
  `;
1966
- await fs9.writeFile(filePath, content, "utf-8");
1968
+ await fs10.writeFile(filePath, content, "utf-8");
1967
1969
  }
1968
1970
  async function loadCascadeMd(workspacePath) {
1969
- const filePath = path25.join(workspacePath, "CASCADE.md");
1971
+ const filePath = path26.join(workspacePath, "CASCADE.md");
1970
1972
  try {
1971
- const raw = await fs9.readFile(filePath, "utf-8");
1973
+ const raw = await fs10.readFile(filePath, "utf-8");
1972
1974
  return parseCascadeMd(raw);
1973
1975
  } catch {
1974
1976
  return null;
@@ -1995,7 +1997,7 @@ ${raw.trim()}`;
1995
1997
  return { raw, sections, systemPrompt };
1996
1998
  }
1997
1999
  async function createDefaultCascadeMd(workspacePath) {
1998
- const filePath = path25.join(workspacePath, "CASCADE.md");
2000
+ const filePath = path26.join(workspacePath, "CASCADE.md");
1999
2001
  const content = `# Cascade Project Instructions
2000
2002
 
2001
2003
  This file contains project-specific instructions for the Cascade AI agent.
@@ -2025,12 +2027,12 @@ All tools are allowed unless specified otherwise.
2025
2027
 
2026
2028
  List any areas the agent should not touch.
2027
2029
  `;
2028
- await fs9.writeFile(filePath, content, "utf-8");
2030
+ await fs10.writeFile(filePath, content, "utf-8");
2029
2031
  }
2030
2032
  var MemoryStore = class _MemoryStore {
2031
2033
  db;
2032
2034
  constructor(dbPath) {
2033
- fs23.mkdirSync(path25.dirname(dbPath), { recursive: true });
2035
+ fs24.mkdirSync(path26.dirname(dbPath), { recursive: true });
2034
2036
  try {
2035
2037
  this.db = new Database2(dbPath, { timeout: 5e3 });
2036
2038
  this.db.pragma("journal_mode = WAL");
@@ -3177,6 +3179,63 @@ function validateConfig(raw) {
3177
3179
  return result.data;
3178
3180
  }
3179
3181
 
3182
+ // src/config/global-credentials.ts
3183
+ init_constants();
3184
+ function credentialsPath(globalDir) {
3185
+ return path26.join(globalDir, GLOBAL_CREDENTIALS_FILE);
3186
+ }
3187
+ function providerKey(p) {
3188
+ if (p.type === "azure") {
3189
+ return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
3190
+ }
3191
+ return p.type;
3192
+ }
3193
+ function isPersistable(p) {
3194
+ return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
3195
+ }
3196
+ function loadGlobalCredentials(globalDir) {
3197
+ try {
3198
+ const raw = fs24.readFileSync(credentialsPath(globalDir), "utf-8");
3199
+ const parsed = JSON.parse(raw);
3200
+ if (!Array.isArray(parsed.providers)) return [];
3201
+ return parsed.providers.filter(
3202
+ (p) => Boolean(p) && typeof p.type === "string"
3203
+ );
3204
+ } catch {
3205
+ return [];
3206
+ }
3207
+ }
3208
+ function saveGlobalCredentials(globalDir, providers) {
3209
+ const filePath = credentialsPath(globalDir);
3210
+ fs24.mkdirSync(globalDir, { recursive: true, mode: 448 });
3211
+ const body = { version: 1, providers: providers.filter(isPersistable) };
3212
+ const tmp = `${filePath}.tmp`;
3213
+ fs24.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
3214
+ fs24.renameSync(tmp, filePath);
3215
+ try {
3216
+ fs24.chmodSync(filePath, 384);
3217
+ } catch {
3218
+ }
3219
+ }
3220
+ function mergeGlobalCredentials(workspaceProviders, globalProviders) {
3221
+ const merged = [...workspaceProviders];
3222
+ const byKey = new Map(merged.map((p) => [providerKey(p), p]));
3223
+ for (const g of globalProviders) {
3224
+ const existing = byKey.get(providerKey(g));
3225
+ if (!existing) {
3226
+ merged.push({ ...g });
3227
+ byKey.set(providerKey(g), merged[merged.length - 1]);
3228
+ continue;
3229
+ }
3230
+ if (!existing.apiKey && g.apiKey) existing.apiKey = g.apiKey;
3231
+ if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
3232
+ if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
3233
+ if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
3234
+ if (!existing.label && g.label) existing.label = g.label;
3235
+ }
3236
+ return merged;
3237
+ }
3238
+
3180
3239
  // src/config/index.ts
3181
3240
  init_constants();
3182
3241
  var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
@@ -3192,18 +3251,20 @@ var ConfigManager = class {
3192
3251
  cascadeMd = null;
3193
3252
  workspacePath;
3194
3253
  globalDir;
3195
- constructor(workspacePath = process.cwd()) {
3254
+ /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
3255
+ constructor(workspacePath = process.cwd(), globalDirOverride) {
3196
3256
  this.workspacePath = workspacePath;
3197
- this.globalDir = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR);
3257
+ this.globalDir = globalDirOverride ?? path26.join(os8.homedir(), GLOBAL_CONFIG_DIR);
3198
3258
  }
3199
3259
  async load() {
3200
3260
  this.config = await this.loadConfig();
3201
3261
  this.ignore = new CascadeIgnore();
3202
3262
  await this.ignore.load(this.workspacePath);
3203
3263
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
3204
- this.keystore = new Keystore(path25.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
3205
- this.store = new MemoryStore(path25.join(this.workspacePath, CASCADE_DB_FILE));
3264
+ this.keystore = new Keystore(path26.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
3265
+ this.store = new MemoryStore(path26.join(this.workspacePath, CASCADE_DB_FILE));
3206
3266
  await this.injectEnvKeys();
3267
+ this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
3207
3268
  await this.ensureDefaultIdentity();
3208
3269
  }
3209
3270
  getConfig() {
@@ -3225,9 +3286,14 @@ var ConfigManager = class {
3225
3286
  return this.workspacePath;
3226
3287
  }
3227
3288
  async save() {
3228
- const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
3229
- await fs9.mkdir(path25.dirname(configPath), { recursive: true });
3230
- await fs9.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
3289
+ const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
3290
+ await fs10.mkdir(path26.dirname(configPath), { recursive: true });
3291
+ await fs10.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
3292
+ try {
3293
+ saveGlobalCredentials(this.globalDir, this.config.providers);
3294
+ } catch (err) {
3295
+ console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
3296
+ }
3231
3297
  }
3232
3298
  async updateConfig(updates) {
3233
3299
  this.config = validateConfig({ ...this.config, ...updates });
@@ -3250,9 +3316,9 @@ var ConfigManager = class {
3250
3316
  return configProvider?.apiKey;
3251
3317
  }
3252
3318
  async loadConfig() {
3253
- const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
3319
+ const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
3254
3320
  try {
3255
- const raw = await fs9.readFile(configPath, "utf-8");
3321
+ const raw = await fs10.readFile(configPath, "utf-8");
3256
3322
  return validateConfig(JSON.parse(raw));
3257
3323
  } catch (err) {
3258
3324
  if (err.code === "ENOENT") {
@@ -4129,7 +4195,7 @@ init_constants();
4129
4195
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
4130
4196
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
4131
4197
  var FETCH_TIMEOUT_MS = 8e3;
4132
- var DEFAULT_CACHE_FILE = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
4198
+ var DEFAULT_CACHE_FILE = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
4133
4199
  function normalizeModelId(id) {
4134
4200
  let s = id.toLowerCase();
4135
4201
  const slash = s.lastIndexOf("/");
@@ -4162,7 +4228,7 @@ var LiveDataProvider = class {
4162
4228
  if (this.loaded) return;
4163
4229
  this.loaded = true;
4164
4230
  try {
4165
- const raw = await fs9.readFile(this.opts.cacheFile, "utf-8");
4231
+ const raw = await fs10.readFile(this.opts.cacheFile, "utf-8");
4166
4232
  const cache = JSON.parse(raw);
4167
4233
  if (cache.snapshot?.families) {
4168
4234
  this.snapshot = cache.snapshot;
@@ -4274,14 +4340,14 @@ var LiveDataProvider = class {
4274
4340
  }
4275
4341
  async saveCache() {
4276
4342
  try {
4277
- await fs9.mkdir(path25.dirname(this.opts.cacheFile), { recursive: true });
4343
+ await fs10.mkdir(path26.dirname(this.opts.cacheFile), { recursive: true });
4278
4344
  const cache = {
4279
4345
  fetchedAt: this.fetchedAt,
4280
4346
  snapshot: this.snapshot ?? void 0,
4281
4347
  prices: Object.fromEntries(this.prices),
4282
4348
  capabilities: Object.fromEntries(this.capabilities)
4283
4349
  };
4284
- await fs9.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
4350
+ await fs10.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
4285
4351
  } catch {
4286
4352
  }
4287
4353
  }
@@ -6178,8 +6244,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
6178
6244
  tierId: this.id,
6179
6245
  sessionId: this.taskId,
6180
6246
  requireApproval: false,
6181
- saveSnapshot: async (path30, content) => {
6182
- this.store?.addFileSnapshot(this.taskId, path30, content);
6247
+ saveSnapshot: async (path31, content) => {
6248
+ this.store?.addFileSnapshot(this.taskId, path31, content);
6183
6249
  },
6184
6250
  sendPeerSync: (to, syncType, content) => {
6185
6251
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -6349,9 +6415,9 @@ ${assignment.expectedOutput}`;
6349
6415
  const { promisify: promisify4 } = await import('util');
6350
6416
  const execAsync3 = promisify4(exec3);
6351
6417
  for (const artifactPath of artifactPaths) {
6352
- const absolutePath = path25.resolve(process.cwd(), artifactPath);
6418
+ const absolutePath = path26.resolve(process.cwd(), artifactPath);
6353
6419
  try {
6354
- const stat = await fs9.stat(absolutePath);
6420
+ const stat = await fs10.stat(absolutePath);
6355
6421
  if (!stat.isFile()) {
6356
6422
  issues.push(`Expected artifact is not a file: ${artifactPath}`);
6357
6423
  continue;
@@ -6361,7 +6427,7 @@ ${assignment.expectedOutput}`;
6361
6427
  continue;
6362
6428
  }
6363
6429
  if (!/\.pdf$/i.test(artifactPath)) {
6364
- const content = await fs9.readFile(absolutePath, "utf-8");
6430
+ const content = await fs10.readFile(absolutePath, "utf-8");
6365
6431
  if (!content.trim()) {
6366
6432
  issues.push(`Artifact content is empty: ${artifactPath}`);
6367
6433
  continue;
@@ -6370,7 +6436,7 @@ ${assignment.expectedOutput}`;
6370
6436
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
6371
6437
  continue;
6372
6438
  }
6373
- const ext = path25.extname(absolutePath).toLowerCase();
6439
+ const ext = path26.extname(absolutePath).toLowerCase();
6374
6440
  try {
6375
6441
  if (ext === ".ts" || ext === ".tsx") {
6376
6442
  await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
@@ -8461,16 +8527,16 @@ function resolveInWorkspace(workspaceRoot, input) {
8461
8527
  if (typeof input !== "string" || input.length === 0) {
8462
8528
  throw new WorkspaceSandboxError(String(input), workspaceRoot);
8463
8529
  }
8464
- const root = path25.resolve(workspaceRoot);
8465
- const abs = path25.isAbsolute(input) ? path25.resolve(input) : path25.resolve(root, input);
8466
- const rel = path25.relative(root, abs);
8467
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path25.isAbsolute(rel)) {
8530
+ const root = path26.resolve(workspaceRoot);
8531
+ const abs = path26.isAbsolute(input) ? path26.resolve(input) : path26.resolve(root, input);
8532
+ const rel = path26.relative(root, abs);
8533
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26.isAbsolute(rel)) {
8468
8534
  throw new WorkspaceSandboxError(input, root);
8469
8535
  }
8470
8536
  try {
8471
- const real = fs23.realpathSync(abs);
8472
- const realRel = path25.relative(root, real);
8473
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path25.isAbsolute(realRel))) {
8537
+ const real = fs24.realpathSync(abs);
8538
+ const realRel = path26.relative(root, real);
8539
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26.isAbsolute(realRel))) {
8474
8540
  throw new WorkspaceSandboxError(input, root);
8475
8541
  }
8476
8542
  } catch (e) {
@@ -8497,7 +8563,7 @@ var FileReadTool = class extends BaseTool {
8497
8563
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
8498
8564
  const offset = input["offset"] ?? 1;
8499
8565
  const limit = input["limit"];
8500
- const content = await fs9.readFile(absPath, "utf-8");
8566
+ const content = await fs10.readFile(absPath, "utf-8");
8501
8567
  const lines = content.split("\n");
8502
8568
  const start = Math.max(0, offset - 1);
8503
8569
  const end = limit ? start + limit : lines.length;
@@ -8526,13 +8592,13 @@ var FileWriteTool = class extends BaseTool {
8526
8592
  const content = input["content"];
8527
8593
  if (options.saveSnapshot) {
8528
8594
  try {
8529
- const oldContent = await fs9.readFile(absPath, "utf-8");
8595
+ const oldContent = await fs10.readFile(absPath, "utf-8");
8530
8596
  await options.saveSnapshot(absPath, oldContent);
8531
8597
  } catch {
8532
8598
  }
8533
8599
  }
8534
- await fs9.mkdir(path25.dirname(absPath), { recursive: true });
8535
- await fs9.writeFile(absPath, content, "utf-8");
8600
+ await fs10.mkdir(path26.dirname(absPath), { recursive: true });
8601
+ await fs10.writeFile(absPath, content, "utf-8");
8536
8602
  return `Written ${content.length} characters to ${filePath}`;
8537
8603
  }
8538
8604
  };
@@ -8558,7 +8624,7 @@ var FileEditTool = class extends BaseTool {
8558
8624
  const oldString = input["old_string"];
8559
8625
  const newString = input["new_string"];
8560
8626
  const replaceAll = input["replace_all"] ?? false;
8561
- const rawContent = await fs9.readFile(absPath, "utf-8");
8627
+ const rawContent = await fs10.readFile(absPath, "utf-8");
8562
8628
  if (options.saveSnapshot) {
8563
8629
  await options.saveSnapshot(absPath, rawContent);
8564
8630
  }
@@ -8570,7 +8636,7 @@ var FileEditTool = class extends BaseTool {
8570
8636
  );
8571
8637
  }
8572
8638
  const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
8573
- await fs9.writeFile(absPath, updated, "utf-8");
8639
+ await fs10.writeFile(absPath, updated, "utf-8");
8574
8640
  const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
8575
8641
  return `Replaced ${count} occurrence(s) in ${filePath}`;
8576
8642
  }
@@ -8593,12 +8659,12 @@ var FileDeleteTool = class extends BaseTool {
8593
8659
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
8594
8660
  if (options.saveSnapshot) {
8595
8661
  try {
8596
- const oldContent = await fs9.readFile(absPath, "utf-8");
8662
+ const oldContent = await fs10.readFile(absPath, "utf-8");
8597
8663
  await options.saveSnapshot(absPath, oldContent);
8598
8664
  } catch {
8599
8665
  }
8600
8666
  }
8601
- await fs9.rm(absPath, { recursive: false });
8667
+ await fs10.rm(absPath, { recursive: false });
8602
8668
  return `Deleted ${filePath}`;
8603
8669
  }
8604
8670
  };
@@ -8615,7 +8681,7 @@ var FileListTool = class extends BaseTool {
8615
8681
  async execute(input, _options) {
8616
8682
  const inputPath = input["path"] || ".";
8617
8683
  const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
8618
- const entries = await fs9.readdir(absPath, { withFileTypes: true });
8684
+ const entries = await fs10.readdir(absPath, { withFileTypes: true });
8619
8685
  return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
8620
8686
  }
8621
8687
  };
@@ -9028,8 +9094,8 @@ var ImageAnalyzeTool = class extends BaseTool {
9028
9094
  }
9029
9095
  };
9030
9096
  async function fileToImageAttachment(filePath) {
9031
- const data = await fs9.readFile(filePath);
9032
- const ext = path25.extname(filePath).toLowerCase();
9097
+ const data = await fs10.readFile(filePath);
9098
+ const ext = path26.extname(filePath).toLowerCase();
9033
9099
  const mimeMap = {
9034
9100
  ".jpg": "image/jpeg",
9035
9101
  ".jpeg": "image/jpeg",
@@ -9063,14 +9129,14 @@ var PDFCreateTool = class extends BaseTool {
9063
9129
  const filePath = input["path"];
9064
9130
  const content = input["content"];
9065
9131
  const title = input["title"];
9066
- const dir = path25.dirname(filePath);
9067
- if (!fs23.existsSync(dir)) {
9068
- fs23.mkdirSync(dir, { recursive: true });
9132
+ const dir = path26.dirname(filePath);
9133
+ if (!fs24.existsSync(dir)) {
9134
+ fs24.mkdirSync(dir, { recursive: true });
9069
9135
  }
9070
9136
  return new Promise((resolve, reject) => {
9071
9137
  try {
9072
9138
  const doc = new PDFDocument({ margin: 50 });
9073
- const stream = fs23.createWriteStream(filePath);
9139
+ const stream = fs24.createWriteStream(filePath);
9074
9140
  doc.pipe(stream);
9075
9141
  if (title) {
9076
9142
  doc.info["Title"] = title;
@@ -9148,22 +9214,22 @@ var CodeInterpreterTool = class extends BaseTool {
9148
9214
  }
9149
9215
  cmdPrefix = NODE_CMD;
9150
9216
  }
9151
- const tmpDir = path25.join(this.workspaceRoot, ".cascade", "tmp");
9152
- if (!fs23.existsSync(tmpDir)) {
9153
- fs23.mkdirSync(tmpDir, { recursive: true });
9217
+ const tmpDir = path26.join(this.workspaceRoot, ".cascade", "tmp");
9218
+ if (!fs24.existsSync(tmpDir)) {
9219
+ fs24.mkdirSync(tmpDir, { recursive: true });
9154
9220
  }
9155
9221
  const extension = language === "python" ? "py" : "js";
9156
9222
  const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
9157
- const filePath = path25.join(tmpDir, fileName);
9158
- fs23.writeFileSync(filePath, code, "utf-8");
9223
+ const filePath = path26.join(tmpDir, fileName);
9224
+ fs24.writeFileSync(filePath, code, "utf-8");
9159
9225
  const execArgs = [filePath, ...args];
9160
9226
  return new Promise((resolve) => {
9161
9227
  const startMs = Date.now();
9162
9228
  execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
9163
9229
  const duration = Date.now() - startMs;
9164
9230
  try {
9165
- if (fs23.existsSync(filePath)) {
9166
- fs23.unlinkSync(filePath);
9231
+ if (fs24.existsSync(filePath)) {
9232
+ fs24.unlinkSync(filePath);
9167
9233
  }
9168
9234
  } catch (cleanupErr) {
9169
9235
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -9442,7 +9508,7 @@ var GlobTool = class extends BaseTool {
9442
9508
  };
9443
9509
  async execute(input, _options) {
9444
9510
  const pattern = input["pattern"];
9445
- const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
9511
+ const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
9446
9512
  const matches = await glob(pattern, {
9447
9513
  cwd: searchPath,
9448
9514
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -9455,7 +9521,7 @@ var GlobTool = class extends BaseTool {
9455
9521
  const withMtime = await Promise.all(
9456
9522
  matches.map(async (rel) => {
9457
9523
  try {
9458
- const stat = await fs9.stat(path25.join(searchPath, rel));
9524
+ const stat = await fs10.stat(path26.join(searchPath, rel));
9459
9525
  return { rel, mtime: stat.mtimeMs };
9460
9526
  } catch {
9461
9527
  return { rel, mtime: 0 };
@@ -9504,7 +9570,7 @@ var GrepTool = class extends BaseTool {
9504
9570
  };
9505
9571
  async execute(input, _options) {
9506
9572
  const pattern = input["pattern"];
9507
- const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
9573
+ const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
9508
9574
  const globPattern = input["glob"];
9509
9575
  const outputMode = input["output_mode"] ?? "content";
9510
9576
  const context = input["context"] ?? 0;
@@ -9558,15 +9624,15 @@ var GrepTool = class extends BaseTool {
9558
9624
  nodir: true
9559
9625
  });
9560
9626
  } catch {
9561
- files = [path25.relative(searchPath, searchPath) || "."];
9627
+ files = [path26.relative(searchPath, searchPath) || "."];
9562
9628
  }
9563
9629
  const results = [];
9564
9630
  let totalCount = 0;
9565
9631
  for (const rel of files) {
9566
- const abs = path25.join(searchPath, rel);
9632
+ const abs = path26.join(searchPath, rel);
9567
9633
  let content;
9568
9634
  try {
9569
- content = await fs9.readFile(abs, "utf-8");
9635
+ content = await fs10.readFile(abs, "utf-8");
9570
9636
  } catch {
9571
9637
  continue;
9572
9638
  }
@@ -9925,10 +9991,10 @@ var ToolRegistry = class extends EventEmitter {
9925
9991
  }
9926
9992
  isIgnored(filePath) {
9927
9993
  if (!filePath) return false;
9928
- const abs = path25.resolve(this.workspaceRoot, filePath);
9929
- const rel = path25.relative(this.workspaceRoot, abs);
9930
- if (!rel || rel.startsWith("..") || path25.isAbsolute(rel)) return true;
9931
- const posixRel = rel.split(path25.sep).join("/");
9994
+ const abs = path26.resolve(this.workspaceRoot, filePath);
9995
+ const rel = path26.relative(this.workspaceRoot, abs);
9996
+ if (!rel || rel.startsWith("..") || path26.isAbsolute(rel)) return true;
9997
+ const posixRel = rel.split(path26.sep).join("/");
9932
9998
  return this.ignoreMatcher.ignores(posixRel);
9933
9999
  }
9934
10000
  };
@@ -10451,7 +10517,7 @@ var TaskAnalyzer = class {
10451
10517
  analysisCache.clear();
10452
10518
  }
10453
10519
  };
10454
- var DEFAULT_STATS_FILE = path25.join(os8.homedir(), ".cascade", "model-perf.json");
10520
+ var DEFAULT_STATS_FILE = path26.join(os8.homedir(), ".cascade", "model-perf.json");
10455
10521
  var ModelPerformanceTracker = class {
10456
10522
  stats = /* @__PURE__ */ new Map();
10457
10523
  featureStats = /* @__PURE__ */ new Map();
@@ -10464,7 +10530,7 @@ var ModelPerformanceTracker = class {
10464
10530
  if (this.loaded) return;
10465
10531
  this.loaded = true;
10466
10532
  try {
10467
- const raw = await fs9.readFile(this.statsFile, "utf-8");
10533
+ const raw = await fs10.readFile(this.statsFile, "utf-8");
10468
10534
  const parsed = JSON.parse(raw);
10469
10535
  if (parsed.models) {
10470
10536
  for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
@@ -10483,12 +10549,12 @@ var ModelPerformanceTracker = class {
10483
10549
  }
10484
10550
  async save() {
10485
10551
  try {
10486
- await fs9.mkdir(path25.dirname(this.statsFile), { recursive: true });
10552
+ await fs10.mkdir(path26.dirname(this.statsFile), { recursive: true });
10487
10553
  const modelsObj = {};
10488
10554
  const featuresObj = {};
10489
10555
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
10490
10556
  for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
10491
- await fs9.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
10557
+ await fs10.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
10492
10558
  } catch {
10493
10559
  }
10494
10560
  }
@@ -11022,9 +11088,9 @@ Required capability: ${description.slice(0, 300)}`;
11022
11088
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
11023
11089
  async loadPersistedTools() {
11024
11090
  if (!this.workspacePath || !this.persistEnabled) return;
11025
- const file = path25.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
11091
+ const file = path26.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
11026
11092
  try {
11027
- const raw = await fs9.readFile(file, "utf-8");
11093
+ const raw = await fs10.readFile(file, "utf-8");
11028
11094
  const specs = JSON.parse(raw);
11029
11095
  if (!Array.isArray(specs)) return;
11030
11096
  let loaded = 0;
@@ -11046,11 +11112,11 @@ Required capability: ${description.slice(0, 300)}`;
11046
11112
  }
11047
11113
  async persist() {
11048
11114
  if (!this.workspacePath || !this.persistEnabled) return;
11049
- const dir = path25.join(this.workspacePath, ".cascade");
11050
- const file = path25.join(dir, DYNAMIC_TOOLS_FILE);
11115
+ const dir = path26.join(this.workspacePath, ".cascade");
11116
+ const file = path26.join(dir, DYNAMIC_TOOLS_FILE);
11051
11117
  try {
11052
- await fs9.mkdir(dir, { recursive: true });
11053
- await fs9.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
11118
+ await fs10.mkdir(dir, { recursive: true });
11119
+ await fs10.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
11054
11120
  } catch (err) {
11055
11121
  this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
11056
11122
  }
@@ -11067,12 +11133,12 @@ var WorldStateDB = class {
11067
11133
  constructor(workspacePath, debugMode = false) {
11068
11134
  this.workspacePath = workspacePath;
11069
11135
  this.debugMode = debugMode;
11070
- const cascadeDir = path25.join(workspacePath, ".cascade");
11071
- if (!fs23.existsSync(cascadeDir)) {
11072
- fs23.mkdirSync(cascadeDir, { recursive: true });
11136
+ const cascadeDir = path26.join(workspacePath, ".cascade");
11137
+ if (!fs24.existsSync(cascadeDir)) {
11138
+ fs24.mkdirSync(cascadeDir, { recursive: true });
11073
11139
  }
11074
- this.keyPath = path25.join(cascadeDir, "world_state.key");
11075
- this.dbPath = path25.join(cascadeDir, "world_state.db");
11140
+ this.keyPath = path26.join(cascadeDir, "world_state.key");
11141
+ this.dbPath = path26.join(cascadeDir, "world_state.db");
11076
11142
  this.initEncryptionKey();
11077
11143
  this.db = new Database2(this.dbPath);
11078
11144
  this.db.pragma("journal_mode = WAL");
@@ -11102,11 +11168,11 @@ var WorldStateDB = class {
11102
11168
  dbPath;
11103
11169
  encryptionKey;
11104
11170
  initEncryptionKey() {
11105
- if (fs23.existsSync(this.keyPath)) {
11106
- this.encryptionKey = fs23.readFileSync(this.keyPath);
11171
+ if (fs24.existsSync(this.keyPath)) {
11172
+ this.encryptionKey = fs24.readFileSync(this.keyPath);
11107
11173
  } else {
11108
11174
  this.encryptionKey = crypto.randomBytes(32);
11109
- fs23.writeFileSync(this.keyPath, this.encryptionKey);
11175
+ fs24.writeFileSync(this.keyPath, this.encryptionKey);
11110
11176
  }
11111
11177
  }
11112
11178
  encrypt(text) {
@@ -11247,6 +11313,22 @@ var WorldStateDB = class {
11247
11313
  }
11248
11314
  return selected.slice(0, limit).map((f) => `- ${f.entity} ${f.relation} ${f.value}`).join("\n");
11249
11315
  }
11316
+ /**
11317
+ * Delete one fact by its (normalized) entity + relation key. Returns whether
11318
+ * a row was actually removed. Powers the desktop Knowledge tab's per-fact
11319
+ * delete — users can prune what the planner remembers about their project.
11320
+ */
11321
+ deleteFact(entity, relation) {
11322
+ const e = normalizeKey(entity);
11323
+ const r = normalizeKey(relation);
11324
+ if (!e || !r) return false;
11325
+ const result = this.db.prepare("DELETE FROM facts WHERE entity = ? AND relation = ?").run(e, r);
11326
+ return result.changes > 0;
11327
+ }
11328
+ /** Delete every fact. Returns how many were removed. */
11329
+ clearFacts() {
11330
+ return this.db.prepare("DELETE FROM facts").run().changes;
11331
+ }
11250
11332
  // ── Export / Import ──────────────────────────
11251
11333
  //
11252
11334
  // Knowledge travels DECRYPTED in the export bundle (a portable plaintext
@@ -11295,9 +11377,9 @@ var WorldStateDB = class {
11295
11377
  dumpDebugIfNeeded() {
11296
11378
  if (!this.debugMode) return;
11297
11379
  try {
11298
- const dumpPath = path25.join(os8.tmpdir(), "cascade_world_state_debug.json");
11380
+ const dumpPath = path26.join(os8.tmpdir(), "cascade_world_state_debug.json");
11299
11381
  const entries = this.getAllEntries();
11300
- fs23.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
11382
+ fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
11301
11383
  } catch (err) {
11302
11384
  console.error("Failed to dump debug world state", err);
11303
11385
  }
@@ -13715,10 +13797,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
13715
13797
  }
13716
13798
  } catch {
13717
13799
  }
13718
- const configPath = path25.join(workspacePath, ".cascade", "config.json");
13800
+ const configPath = path26.join(workspacePath, ".cascade", "config.json");
13719
13801
  try {
13720
- await fs9.mkdir(path25.dirname(configPath), { recursive: true });
13721
- await fs9.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
13802
+ await fs10.mkdir(path26.dirname(configPath), { recursive: true });
13803
+ await fs10.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
13722
13804
  } catch (err) {
13723
13805
  const msg = err instanceof Error ? err.message : String(err);
13724
13806
  dispatch({
@@ -13769,9 +13851,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
13769
13851
  if (msg.includes("non-text parts")) return;
13770
13852
  originalLog(...args);
13771
13853
  };
13772
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
13854
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
13773
13855
  storeRef.current = store;
13774
- globalStoreRef.current = new MemoryStore(path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
13856
+ globalStoreRef.current = new MemoryStore(path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
13775
13857
  const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
13776
13858
  setIdentities(identityRows);
13777
13859
  let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
@@ -13834,14 +13916,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
13834
13916
  onThemeChange: (name) => setTheme(getTheme(name)),
13835
13917
  onExport: async (fmt) => {
13836
13918
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13837
- const exportPath = path25.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
13919
+ const exportPath = path26.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
13838
13920
  if (fmt === "json") {
13839
- await fs9.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
13921
+ await fs10.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
13840
13922
  } else {
13841
13923
  const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
13842
13924
 
13843
13925
  ${msg.content}`).join("\n\n");
13844
- await fs9.writeFile(exportPath, markdown, "utf-8");
13926
+ await fs10.writeFile(exportPath, markdown, "utf-8");
13845
13927
  }
13846
13928
  },
13847
13929
  onRollback: async () => {
@@ -13851,7 +13933,7 @@ ${msg.content}`).join("\n\n");
13851
13933
  if (!snapshots.length) return "No file snapshots found for this session.";
13852
13934
  for (const { filePath, content } of snapshots) {
13853
13935
  try {
13854
- await fs9.writeFile(filePath, content, "utf-8");
13936
+ await fs10.writeFile(filePath, content, "utf-8");
13855
13937
  } catch (err) {
13856
13938
  console.error(`Restore failed: ${filePath}`, err);
13857
13939
  }
@@ -14730,9 +14812,9 @@ function stringifySlashOutput(val) {
14730
14812
  }
14731
14813
  async function searchSessionsAndMessages(query, workspacePath) {
14732
14814
  if (!query) return "Usage: /search <query>";
14733
- const dbPath = path25.join(workspacePath, CASCADE_DB_FILE);
14815
+ const dbPath = path26.join(workspacePath, CASCADE_DB_FILE);
14734
14816
  try {
14735
- await fs9.access(dbPath);
14817
+ await fs10.access(dbPath);
14736
14818
  } catch {
14737
14819
  return "No database found. Start a conversation first.";
14738
14820
  }
@@ -14764,7 +14846,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
14764
14846
  async function diagnoseRuntime(config, workspacePath) {
14765
14847
  const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
14766
14848
  const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
14767
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
14849
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
14768
14850
  try {
14769
14851
  const sessions = store.listSessions(void 0, 3);
14770
14852
  return [
@@ -14783,7 +14865,7 @@ async function diagnoseRuntime(config, workspacePath) {
14783
14865
  }
14784
14866
  async function showRecentLogs(args, workspacePath) {
14785
14867
  const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
14786
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
14868
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
14787
14869
  try {
14788
14870
  const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
14789
14871
  if (!logs.length) return "No recent runtime logs.";
@@ -14795,7 +14877,7 @@ async function showRecentLogs(args, workspacePath) {
14795
14877
  async function loadSessionSnapshot(args, workspacePath) {
14796
14878
  const sessionId = args[0];
14797
14879
  if (!sessionId) return "Usage: /resume <sessionId>";
14798
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
14880
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
14799
14881
  try {
14800
14882
  const session = store.getSession(sessionId);
14801
14883
  if (!session) return `Session not found: ${sessionId}`;
@@ -15215,10 +15297,10 @@ function SetupWizard({ workspacePath, onComplete }) {
15215
15297
  ...anyAuto ? { cascadeAuto: true } : {}
15216
15298
  };
15217
15299
  const config = CascadeConfigSchema.parse(rawConfig);
15218
- const configDir = path25.join(workspacePath, ".cascade");
15219
- await fs9.mkdir(configDir, { recursive: true });
15220
- const configPath = path25.join(workspacePath, CASCADE_CONFIG_FILE);
15221
- await fs9.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
15300
+ const configDir = path26.join(workspacePath, ".cascade");
15301
+ await fs10.mkdir(configDir, { recursive: true });
15302
+ const configPath = path26.join(workspacePath, CASCADE_CONFIG_FILE);
15303
+ await fs10.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
15222
15304
  savedConfigRef.current = config;
15223
15305
  dispatchRef.current({ type: "GO_DONE" });
15224
15306
  } catch (err) {
@@ -15558,14 +15640,14 @@ function printTelemetryBanner() {
15558
15640
  async function initCommand(workspacePath = process.cwd()) {
15559
15641
  const spin = ora({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
15560
15642
  try {
15561
- const configDir = path25.join(workspacePath, ".cascade");
15562
- await fs9.mkdir(configDir, { recursive: true });
15563
- const mdPath = path25.join(workspacePath, "CASCADE.md");
15643
+ const configDir = path26.join(workspacePath, ".cascade");
15644
+ await fs10.mkdir(configDir, { recursive: true });
15645
+ const mdPath = path26.join(workspacePath, "CASCADE.md");
15564
15646
  if (!await fileExists(mdPath)) {
15565
15647
  await createDefaultCascadeMd(workspacePath);
15566
15648
  spin.succeed(chalk9.green("Created CASCADE.md"));
15567
15649
  }
15568
- const ignorePath = path25.join(workspacePath, ".cascadeignore");
15650
+ const ignorePath = path26.join(workspacePath, ".cascadeignore");
15569
15651
  if (!await fileExists(ignorePath)) {
15570
15652
  await createDefaultIgnoreFile(workspacePath);
15571
15653
  spin.succeed(chalk9.green("Created .cascadeignore"));
@@ -15574,7 +15656,7 @@ async function initCommand(workspacePath = process.cwd()) {
15574
15656
  console.log();
15575
15657
  console.log(chalk9.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
15576
15658
  console.log();
15577
- const configPath = path25.join(workspacePath, CASCADE_CONFIG_FILE);
15659
+ const configPath = path26.join(workspacePath, CASCADE_CONFIG_FILE);
15578
15660
  if (await fileExists(configPath)) {
15579
15661
  console.log(chalk9.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
15580
15662
  console.log();
@@ -15593,7 +15675,7 @@ async function initCommand(workspacePath = process.cwd()) {
15593
15675
  }
15594
15676
  async function fileExists(p) {
15595
15677
  try {
15596
- await fs9.access(p);
15678
+ await fs10.access(p);
15597
15679
  return true;
15598
15680
  } catch {
15599
15681
  return false;
@@ -15605,7 +15687,7 @@ init_constants();
15605
15687
  var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
15606
15688
  async function readJson(file) {
15607
15689
  try {
15608
- const raw = await fs9.readFile(file, "utf-8");
15690
+ const raw = await fs10.readFile(file, "utf-8");
15609
15691
  return JSON.parse(raw);
15610
15692
  } catch {
15611
15693
  return null;
@@ -15632,7 +15714,7 @@ function fromEnv(env) {
15632
15714
  return out;
15633
15715
  }
15634
15716
  async function fromClaudeCode(home) {
15635
- const file = path25.join(home, ".claude", ".credentials.json");
15717
+ const file = path26.join(home, ".claude", ".credentials.json");
15636
15718
  const data = await readJson(file);
15637
15719
  if (!data) return [];
15638
15720
  const oauth = data["claudeAiOauth"];
@@ -15655,7 +15737,7 @@ async function fromClaudeCode(home) {
15655
15737
  return [];
15656
15738
  }
15657
15739
  async function fromCodex(home) {
15658
- const file = path25.join(home, ".codex", "auth.json");
15740
+ const file = path26.join(home, ".codex", "auth.json");
15659
15741
  const data = await readJson(file);
15660
15742
  if (!data) return [];
15661
15743
  const apiKey = str(data["OPENAI_API_KEY"]);
@@ -15678,7 +15760,7 @@ async function fromCodex(home) {
15678
15760
  return [];
15679
15761
  }
15680
15762
  async function fromGemini(home) {
15681
- const file = path25.join(home, ".gemini", "oauth_creds.json");
15763
+ const file = path26.join(home, ".gemini", "oauth_creds.json");
15682
15764
  const data = await readJson(file);
15683
15765
  const accessToken = str(data?.["access_token"]);
15684
15766
  if (!accessToken) return [];
@@ -15694,7 +15776,7 @@ async function fromGemini(home) {
15694
15776
  }
15695
15777
  async function fromCopilot(home) {
15696
15778
  for (const name of ["apps.json", "hosts.json"]) {
15697
- const file = path25.join(home, ".config", "github-copilot", name);
15779
+ const file = path26.join(home, ".config", "github-copilot", name);
15698
15780
  const data = await readJson(file);
15699
15781
  if (!data) continue;
15700
15782
  for (const value of Object.values(data)) {
@@ -15748,7 +15830,7 @@ async function doctorCommand() {
15748
15830
  checks.push({
15749
15831
  label: "Cascade config",
15750
15832
  ok: true,
15751
- detail: `Loaded ${path25.join(process.cwd(), CASCADE_CONFIG_FILE)}`
15833
+ detail: `Loaded ${path26.join(process.cwd(), CASCADE_CONFIG_FILE)}`
15752
15834
  });
15753
15835
  const providers = [
15754
15836
  { type: "anthropic", name: "Anthropic" },
@@ -15904,6 +15986,11 @@ function authMiddleware(secret, required = true) {
15904
15986
  }
15905
15987
  const user = verifyToken(token, secret);
15906
15988
  if (!user) {
15989
+ if (!required) {
15990
+ req.user = void 0;
15991
+ next();
15992
+ return;
15993
+ }
15907
15994
  res.status(401).json({ error: "Invalid or expired token" });
15908
15995
  return;
15909
15996
  }
@@ -16036,6 +16123,25 @@ var DashboardSocket = class {
16036
16123
  });
16037
16124
  });
16038
16125
  }
16126
+ /**
16127
+ * Boardroom plan decisions from a connected client. The desktop shows a
16128
+ * plan-review modal on `plan:approval-required` and answers here; the
16129
+ * server routes the decision into the paused run via resolvePlanApproval.
16130
+ */
16131
+ onPlanDecision(callback) {
16132
+ this.io.on("connection", (socket) => {
16133
+ socket.on("plan:decision", (payload) => {
16134
+ if (typeof payload?.sessionId === "string" && typeof payload?.approved === "boolean") {
16135
+ callback({
16136
+ sessionId: payload.sessionId,
16137
+ approved: payload.approved,
16138
+ note: typeof payload.note === "string" && payload.note.trim() ? payload.note.trim() : void 0,
16139
+ editedPlan: payload.editedPlan
16140
+ });
16141
+ }
16142
+ });
16143
+ });
16144
+ }
16039
16145
  onSessionSteer(callback) {
16040
16146
  this.io.on("connection", (socket) => {
16041
16147
  socket.on("session:steer", (payload) => {
@@ -16080,7 +16186,123 @@ var DashboardSocket = class {
16080
16186
 
16081
16187
  // src/dashboard/server.ts
16082
16188
  init_constants();
16083
- var __dirname$1 = path25.dirname(fileURLToPath(import.meta.url));
16189
+ var TaskScheduler = class {
16190
+ cronJobs = /* @__PURE__ */ new Map();
16191
+ store;
16192
+ runner;
16193
+ constructor(store, runner) {
16194
+ this.store = store;
16195
+ this.runner = runner;
16196
+ }
16197
+ start() {
16198
+ const tasks = this.store.listScheduledTasks();
16199
+ for (const task of tasks) {
16200
+ if (task.enabled) this.schedule(task);
16201
+ }
16202
+ }
16203
+ stop() {
16204
+ for (const job of this.cronJobs.values()) job.stop();
16205
+ this.cronJobs.clear();
16206
+ }
16207
+ schedule(task) {
16208
+ if (!cron.validate(task.cronExpression)) {
16209
+ throw new Error(`Invalid cron expression: ${task.cronExpression}`);
16210
+ }
16211
+ const existing = this.cronJobs.get(task.id);
16212
+ if (existing) {
16213
+ try {
16214
+ existing.stop();
16215
+ } catch {
16216
+ }
16217
+ }
16218
+ const job = cron.schedule(task.cronExpression, async () => {
16219
+ try {
16220
+ task.lastRun = (/* @__PURE__ */ new Date()).toISOString();
16221
+ this.store.saveScheduledTask(task);
16222
+ await this.runner(task);
16223
+ } catch (err) {
16224
+ console.error(`[scheduler] Task "${task.id}" (${task.cronExpression}) failed:`, err);
16225
+ }
16226
+ }, { timezone: "UTC" });
16227
+ this.cronJobs.set(task.id, job);
16228
+ }
16229
+ unschedule(taskId) {
16230
+ this.cronJobs.get(taskId)?.stop();
16231
+ this.cronJobs.delete(taskId);
16232
+ }
16233
+ add(task) {
16234
+ this.store.saveScheduledTask(task);
16235
+ if (task.enabled) this.schedule(task);
16236
+ }
16237
+ remove(taskId) {
16238
+ this.unschedule(taskId);
16239
+ this.store.deleteScheduledTask(taskId);
16240
+ }
16241
+ list() {
16242
+ return this.store.listScheduledTasks();
16243
+ }
16244
+ isRunning(taskId) {
16245
+ return this.cronJobs.has(taskId);
16246
+ }
16247
+ static validateCron(expression) {
16248
+ return cron.validate(expression);
16249
+ }
16250
+ };
16251
+
16252
+ // src/dashboard/cost-stats.ts
16253
+ var DAY_MS = 24 * 60 * 60 * 1e3;
16254
+ function utcDateKey(d) {
16255
+ return d.toISOString().slice(0, 10);
16256
+ }
16257
+ function aggregateCostStats(sessions, opts = {}) {
16258
+ const days = Math.max(1, opts.days ?? 30);
16259
+ const topN = Math.max(1, opts.topN ?? 8);
16260
+ const now = opts.now ?? /* @__PURE__ */ new Date();
16261
+ const buckets = /* @__PURE__ */ new Map();
16262
+ for (let i = days - 1; i >= 0; i--) {
16263
+ const key = utcDateKey(new Date(now.getTime() - i * DAY_MS));
16264
+ buckets.set(key, { date: key, costUsd: 0, tokens: 0, runs: 0 });
16265
+ }
16266
+ let totalCostUsd = 0;
16267
+ let totalTokens = 0;
16268
+ let totalRuns = 0;
16269
+ for (const s of sessions) {
16270
+ const cost = s.metadata?.totalCostUsd ?? 0;
16271
+ const tokens = s.metadata?.totalTokens ?? 0;
16272
+ const runs = s.metadata?.taskCount ?? 0;
16273
+ totalCostUsd += cost;
16274
+ totalTokens += tokens;
16275
+ totalRuns += runs;
16276
+ const when = new Date(s.updatedAt || s.createdAt);
16277
+ if (!Number.isNaN(when.getTime())) {
16278
+ const bucket = buckets.get(utcDateKey(when));
16279
+ if (bucket) {
16280
+ bucket.costUsd += cost;
16281
+ bucket.tokens += tokens;
16282
+ bucket.runs += runs;
16283
+ }
16284
+ }
16285
+ }
16286
+ const topSessions = [...sessions].filter((s) => (s.metadata?.totalCostUsd ?? 0) > 0).sort((a, b) => (b.metadata?.totalCostUsd ?? 0) - (a.metadata?.totalCostUsd ?? 0)).slice(0, topN).map((s) => ({
16287
+ sessionId: s.id,
16288
+ title: s.title,
16289
+ costUsd: s.metadata?.totalCostUsd ?? 0,
16290
+ tokens: s.metadata?.totalTokens ?? 0,
16291
+ runs: s.metadata?.taskCount ?? 0,
16292
+ updatedAt: s.updatedAt
16293
+ }));
16294
+ return {
16295
+ totalCostUsd,
16296
+ totalTokens,
16297
+ totalSessions: sessions.length,
16298
+ totalRuns,
16299
+ perDay: [...buckets.values()],
16300
+ topSessions
16301
+ };
16302
+ }
16303
+
16304
+ // src/dashboard/server.ts
16305
+ var __dirname$1 = path26.dirname(fileURLToPath(import.meta.url));
16084
16306
  var DashboardServer = class {
16085
16307
  app;
16086
16308
  httpServer;
@@ -16105,6 +16327,14 @@ var DashboardServer = class {
16105
16327
  * map is how that answer reaches the run that's blocked on it.
16106
16328
  */
16107
16329
  pendingApprovals = /* @__PURE__ */ new Map();
16330
+ /**
16331
+ * The orchestration decision trail ("why") of each session's most recent
16332
+ * run — captured when the run ends so the desktop's Why panel can show it
16333
+ * after the fact. Bounded: oldest entry evicted past 50 sessions.
16334
+ */
16335
+ whyBySession = /* @__PURE__ */ new Map();
16336
+ /** Cron scheduler for the Schedules UI — runs tasks while this server is up. */
16337
+ scheduler;
16108
16338
  port;
16109
16339
  host;
16110
16340
  workspacePath;
@@ -16122,6 +16352,7 @@ var DashboardServer = class {
16122
16352
  secret: this.dashboardSecret,
16123
16353
  corsOrigin: config.dashboard.auth ? [`http://localhost:${this.port}`, `http://127.0.0.1:${this.port}`] : "*"
16124
16354
  });
16355
+ this.scheduler = new TaskScheduler(store, (task) => this.runScheduledTask(task));
16125
16356
  this.setupMiddleware();
16126
16357
  this.setupRoutes();
16127
16358
  this.socket.onSessionRate((sessionId, rating) => {
@@ -16185,6 +16416,9 @@ var DashboardServer = class {
16185
16416
  cascade.on("peer:message", (e) => {
16186
16417
  this.socket.emitPeerMessage(e);
16187
16418
  });
16419
+ cascade.on("plan:approval-required", (e) => {
16420
+ this.socket.emitToSocket(socketId, "plan:approval-required", { sessionId, ...e });
16421
+ });
16188
16422
  try {
16189
16423
  const result = await cascade.run({
16190
16424
  prompt: runPrompt,
@@ -16192,7 +16426,8 @@ var DashboardServer = class {
16192
16426
  approvalCallback: this.makeApprovalCallback(sessionId)
16193
16427
  });
16194
16428
  this.recordSessionTask(sessionId, result.taskId);
16195
- this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
16429
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
16430
+ this.captureWhy(sessionId, cascade, result);
16196
16431
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
16197
16432
  this.socket.broadcast("cost:update", {
16198
16433
  sessionId,
@@ -16202,6 +16437,7 @@ var DashboardServer = class {
16202
16437
  this.throttledBroadcast("workspace");
16203
16438
  } catch (err) {
16204
16439
  this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
16440
+ this.captureWhy(sessionId, cascade);
16205
16441
  this.socket.emitToSocket(socketId, "session:error", {
16206
16442
  sessionId,
16207
16443
  error: err instanceof Error ? err.message : String(err)
@@ -16212,9 +16448,17 @@ var DashboardServer = class {
16212
16448
  this.denyPendingApprovals(sessionId);
16213
16449
  }
16214
16450
  });
16451
+ this.socket.onPlanDecision(({ sessionId, approved, note, editedPlan }) => {
16452
+ this.activeSessions.get(sessionId)?.resolvePlanApproval(
16453
+ approved,
16454
+ note,
16455
+ editedPlan
16456
+ );
16457
+ });
16215
16458
  this.socket.onSessionHalt((sessionId) => {
16216
16459
  this.activeControllers.get(sessionId)?.abort();
16217
16460
  this.denyPendingApprovals(sessionId);
16461
+ this.activeSessions.get(sessionId)?.resolvePlanApproval(false);
16218
16462
  });
16219
16463
  this.socket.onApprovalResponse(({ requestId, approved, always }) => {
16220
16464
  const pending = this.pendingApprovals.get(requestId);
@@ -16247,12 +16491,21 @@ var DashboardServer = class {
16247
16491
  resolve();
16248
16492
  });
16249
16493
  });
16494
+ try {
16495
+ this.scheduler.start();
16496
+ } catch (err) {
16497
+ console.warn("[dashboard] failed to start task scheduler:", err);
16498
+ }
16250
16499
  }
16251
16500
  async stop() {
16252
16501
  if (this.broadcastTimer) {
16253
16502
  clearTimeout(this.broadcastTimer);
16254
16503
  this.broadcastTimer = null;
16255
16504
  }
16505
+ try {
16506
+ this.scheduler.stop();
16507
+ } catch {
16508
+ }
16256
16509
  this.socket.close();
16257
16510
  try {
16258
16511
  this.globalStore?.close();
@@ -16283,12 +16536,17 @@ var DashboardServer = class {
16283
16536
  */
16284
16537
  persistConfig() {
16285
16538
  try {
16286
- const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
16287
- fs23.mkdirSync(path25.dirname(configPath), { recursive: true });
16288
- fs23.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
16539
+ const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
16540
+ fs24.mkdirSync(path26.dirname(configPath), { recursive: true });
16541
+ fs24.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
16289
16542
  } catch (err) {
16290
16543
  console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
16291
16544
  }
16545
+ try {
16546
+ saveGlobalCredentials(path26.join(os8.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
16547
+ } catch (err) {
16548
+ console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
16549
+ }
16292
16550
  }
16293
16551
  /**
16294
16552
  * Produce a stable dashboard JWT signing secret.
@@ -16300,15 +16558,15 @@ var DashboardServer = class {
16300
16558
  resolveDashboardSecret() {
16301
16559
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
16302
16560
  if (fromConfig) return fromConfig;
16303
- const secretPath = path25.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
16561
+ const secretPath = path26.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
16304
16562
  try {
16305
- if (fs23.existsSync(secretPath)) {
16306
- const existing = fs23.readFileSync(secretPath, "utf-8").trim();
16563
+ if (fs24.existsSync(secretPath)) {
16564
+ const existing = fs24.readFileSync(secretPath, "utf-8").trim();
16307
16565
  if (existing.length >= 16) return existing;
16308
16566
  }
16309
16567
  const generated = randomUUID();
16310
- fs23.mkdirSync(path25.dirname(secretPath), { recursive: true });
16311
- fs23.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
16568
+ fs24.mkdirSync(path26.dirname(secretPath), { recursive: true });
16569
+ fs24.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
16312
16570
  if (this.config.dashboard.auth) {
16313
16571
  console.warn(
16314
16572
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -16335,7 +16593,7 @@ var DashboardServer = class {
16335
16593
  // ── Setup ─────────────────────────────────────
16336
16594
  getGlobalStore() {
16337
16595
  if (!this.globalStore) {
16338
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16596
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16339
16597
  this.globalStore = new MemoryStore(globalDbPath);
16340
16598
  }
16341
16599
  return this.globalStore;
@@ -16373,16 +16631,60 @@ var DashboardServer = class {
16373
16631
  return title;
16374
16632
  }
16375
16633
  /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
16376
- persistRunEnd(sessionId, title, latestPrompt, reply, status) {
16634
+ persistRunEnd(sessionId, title, latestPrompt, reply, status, result) {
16377
16635
  try {
16378
16636
  if (reply && reply.trim()) {
16379
16637
  this.store.addMessage({ id: randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
16380
16638
  }
16639
+ if (result) {
16640
+ const session = this.store.getSession(sessionId);
16641
+ if (session) {
16642
+ this.store.updateSession(sessionId, {
16643
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16644
+ metadata: {
16645
+ ...session.metadata,
16646
+ totalTokens: session.metadata.totalTokens + result.usage.totalTokens,
16647
+ totalCostUsd: session.metadata.totalCostUsd + result.usage.estimatedCostUsd,
16648
+ taskCount: session.metadata.taskCount + 1
16649
+ }
16650
+ });
16651
+ }
16652
+ }
16381
16653
  } catch (err) {
16382
16654
  console.warn("[dashboard] failed to persist run end:", err);
16383
16655
  }
16384
16656
  this.persistRuntimeRow(sessionId, title, status, latestPrompt);
16385
16657
  }
16658
+ /**
16659
+ * Capture the run's decision trail + router economics ("why") and broadcast
16660
+ * it so the desktop's Why panel updates live; kept per-session for the
16661
+ * GET /api/sessions/:id/why fallback (panel opened after the run).
16662
+ */
16663
+ captureWhy(sessionId, cascade, result) {
16664
+ try {
16665
+ const stats = cascade.getRouter().getStats();
16666
+ const savings = cascade.getRouter().getDelegationSavings();
16667
+ const report = {
16668
+ sessionId,
16669
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
16670
+ decisions: cascade.getDecisionLog(),
16671
+ savedUsd: savings.savedUsd,
16672
+ savedPct: savings.savedPct,
16673
+ totalCostUsd: stats.totalCostUsd,
16674
+ totalTokens: stats.totalTokens,
16675
+ costByTier: stats.costByTier,
16676
+ durationMs: result?.durationMs
16677
+ };
16678
+ this.whyBySession.set(sessionId, report);
16679
+ if (this.whyBySession.size > 50) {
16680
+ const oldest = this.whyBySession.keys().next().value;
16681
+ if (oldest) this.whyBySession.delete(oldest);
16682
+ }
16683
+ this.socket.broadcast("run:why", report);
16684
+ } catch (err) {
16685
+ console.warn("[dashboard] failed to capture decision trail:", err);
16686
+ }
16687
+ }
16386
16688
  /**
16387
16689
  * Route steering text into running Cascade instances. Targets the given
16388
16690
  * session, or every active session when none is specified (the desktop
@@ -16412,6 +16714,52 @@ var DashboardServer = class {
16412
16714
  this.pendingApprovals.set(request.id, { resolve, sessionId });
16413
16715
  });
16414
16716
  }
16717
+ /**
16718
+ * Execute one scheduled task firing. Runs headless (like `cascade run -p`):
16719
+ * tool approvals are auto-granted since nobody may be watching when the cron
16720
+ * fires — the Schedules UI states this. Events broadcast to every connected
16721
+ * client so an open desktop sees the run appear live.
16722
+ */
16723
+ async runScheduledTask(task) {
16724
+ const sessionId = randomUUID();
16725
+ const prompt = task.prompt;
16726
+ const title = this.persistRunStart(sessionId, `[${task.name}] ${prompt}`);
16727
+ const cascade = new Cascade(this.config, task.workspacePath ?? this.workspacePath, this.store);
16728
+ this.activeSessions.set(sessionId, cascade);
16729
+ cascade.on("tier:status", (e) => {
16730
+ this.socket.broadcast("tier:status", { sessionId, ...e });
16731
+ });
16732
+ cascade.on("peer:message", (e) => {
16733
+ this.socket.emitPeerMessage(e);
16734
+ });
16735
+ try {
16736
+ const result = await cascade.run({
16737
+ prompt,
16738
+ identityId: task.identityId,
16739
+ approvalCallback: async () => ({ approved: true, always: false })
16740
+ });
16741
+ this.recordSessionTask(sessionId, result.taskId);
16742
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
16743
+ this.captureWhy(sessionId, cascade, result);
16744
+ this.socket.broadcast("session:complete", { sessionId, result });
16745
+ this.socket.broadcast("cost:update", {
16746
+ sessionId,
16747
+ totalTokens: result.usage.totalTokens,
16748
+ totalCostUsd: result.usage.estimatedCostUsd
16749
+ });
16750
+ this.throttledBroadcast("workspace");
16751
+ } catch (err) {
16752
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
16753
+ this.captureWhy(sessionId, cascade);
16754
+ this.socket.broadcast("session:error", {
16755
+ sessionId,
16756
+ error: err instanceof Error ? err.message : String(err)
16757
+ });
16758
+ throw err;
16759
+ } finally {
16760
+ this.activeSessions.delete(sessionId);
16761
+ }
16762
+ }
16415
16763
  /** Deny + clear any approvals still pending for a session (run end / abort). */
16416
16764
  denyPendingApprovals(sessionId) {
16417
16765
  for (const [id, pending] of this.pendingApprovals) {
@@ -16514,12 +16862,12 @@ ${prompt}`;
16514
16862
  }
16515
16863
  }
16516
16864
  watchRuntimeChanges() {
16517
- const workspaceDbPath = path25.join(this.workspacePath, CASCADE_DB_FILE);
16518
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16865
+ const workspaceDbPath = path26.join(this.workspacePath, CASCADE_DB_FILE);
16866
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16519
16867
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
16520
16868
  for (const watchPath of watchPaths) {
16521
- if (!fs23.existsSync(watchPath)) continue;
16522
- fs23.watchFile(watchPath, { interval: 3e3 }, () => {
16869
+ if (!fs24.existsSync(watchPath)) continue;
16870
+ fs24.watchFile(watchPath, { interval: 3e3 }, () => {
16523
16871
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
16524
16872
  });
16525
16873
  }
@@ -16641,7 +16989,7 @@ ${prompt}`;
16641
16989
  const sessionId = req.params.id;
16642
16990
  this.store.deleteSession(sessionId);
16643
16991
  this.store.deleteRuntimeSession(sessionId);
16644
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16992
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16645
16993
  const globalStore = new MemoryStore(globalDbPath);
16646
16994
  try {
16647
16995
  globalStore.deleteRuntimeSession(sessionId);
@@ -16734,7 +17082,7 @@ ${prompt}`;
16734
17082
  });
16735
17083
  this.app.delete("/api/sessions", auth, (req, res) => {
16736
17084
  const body = req.body;
16737
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
17085
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16738
17086
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
16739
17087
  const globalStore = new MemoryStore(globalDbPath);
16740
17088
  try {
@@ -16757,7 +17105,7 @@ ${prompt}`;
16757
17105
  });
16758
17106
  this.app.delete("/api/runtime", auth, (_req, res) => {
16759
17107
  this.store.deleteAllRuntimeNodes();
16760
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
17108
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16761
17109
  const globalStore = new MemoryStore(globalDbPath);
16762
17110
  try {
16763
17111
  globalStore.deleteAllRuntimeNodes();
@@ -16843,12 +17191,12 @@ ${prompt}`;
16843
17191
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
16844
17192
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
16845
17193
  try {
16846
- const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
16847
- const existing = fs23.existsSync(configPath) ? JSON.parse(fs23.readFileSync(configPath, "utf-8")) : {};
17194
+ const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
17195
+ const existing = fs24.existsSync(configPath) ? JSON.parse(fs24.readFileSync(configPath, "utf-8")) : {};
16848
17196
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
16849
17197
  const tmp = configPath + ".tmp";
16850
- fs23.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
16851
- fs23.renameSync(tmp, configPath);
17198
+ fs24.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
17199
+ fs24.renameSync(tmp, configPath);
16852
17200
  res.json({ ok: true });
16853
17201
  } catch (err) {
16854
17202
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -16876,7 +17224,7 @@ ${prompt}`;
16876
17224
  this.app.get("/api/runtime", auth, (req, res) => {
16877
17225
  const scope = req.query["scope"] ?? "workspace";
16878
17226
  if (scope === "global") {
16879
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
17227
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16880
17228
  const globalStore = new MemoryStore(globalDbPath);
16881
17229
  try {
16882
17230
  res.json({
@@ -16924,6 +17272,9 @@ ${prompt}`;
16924
17272
  cascade.on("peer:message", (e) => {
16925
17273
  this.socket.emitPeerMessage(e);
16926
17274
  });
17275
+ cascade.on("plan:approval-required", (e) => {
17276
+ this.socket.broadcastToRoom(`session:${sessionId}`, "plan:approval-required", { sessionId, ...e });
17277
+ });
16927
17278
  try {
16928
17279
  const result = await cascade.run({
16929
17280
  prompt: runPrompt,
@@ -16931,7 +17282,8 @@ ${prompt}`;
16931
17282
  approvalCallback: this.makeApprovalCallback(sessionId)
16932
17283
  });
16933
17284
  this.recordSessionTask(sessionId, result.taskId);
16934
- this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
17285
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED", result);
17286
+ this.captureWhy(sessionId, cascade, result);
16935
17287
  this.socket.broadcast("cost:update", {
16936
17288
  sessionId,
16937
17289
  totalTokens: result.usage.totalTokens,
@@ -16941,6 +17293,7 @@ ${prompt}`;
16941
17293
  this.throttledBroadcast("workspace");
16942
17294
  } catch (err) {
16943
17295
  this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
17296
+ this.captureWhy(sessionId, cascade);
16944
17297
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
16945
17298
  sessionId,
16946
17299
  error: err instanceof Error ? err.message : String(err)
@@ -16962,13 +17315,217 @@ ${prompt}`;
16962
17315
  }))
16963
17316
  });
16964
17317
  });
16965
- const prodPath = path25.resolve(__dirname$1, "../web/dist");
16966
- const devPath = path25.resolve(__dirname$1, "../../web/dist");
16967
- const webDistPath = fs23.existsSync(prodPath) ? prodPath : devPath;
16968
- if (fs23.existsSync(webDistPath)) {
17318
+ this.app.get("/api/sessions/:id/why", auth, (req, res) => {
17319
+ const report = this.whyBySession.get(req.params.id);
17320
+ if (!report) {
17321
+ res.status(404).json({ error: "No decision trail recorded for this session in the current app run." });
17322
+ return;
17323
+ }
17324
+ res.json(report);
17325
+ });
17326
+ this.app.get("/api/sessions/:id/changes", auth, async (req, res) => {
17327
+ const sessionId = req.params.id;
17328
+ const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
17329
+ const before = /* @__PURE__ */ new Map();
17330
+ for (const taskId of taskIds) {
17331
+ for (const { filePath, content } of this.store.getLatestFileSnapshots(taskId)) {
17332
+ if (!before.has(filePath)) before.set(filePath, content);
17333
+ }
17334
+ }
17335
+ const { readFile } = await import('fs/promises');
17336
+ const MAX_DIFF_BYTES = 2 * 1024 * 1024;
17337
+ const changes = await Promise.all([...before.entries()].map(async ([filePath, beforeContent]) => {
17338
+ let after = "";
17339
+ let missing = false;
17340
+ try {
17341
+ const buf = await readFile(filePath);
17342
+ after = buf.length > MAX_DIFF_BYTES ? `[file too large to diff \u2014 ${buf.length} bytes]` : buf.toString("utf-8");
17343
+ } catch {
17344
+ missing = true;
17345
+ }
17346
+ return { filePath, before: beforeContent, after, missing, changed: missing || after !== beforeContent };
17347
+ }));
17348
+ res.json({ sessionId, changes: changes.filter((c) => c.changed) });
17349
+ });
17350
+ this.app.post("/api/sessions/:id/revert-file", auth, mutationLimiter, async (req, res) => {
17351
+ const sessionId = req.params.id;
17352
+ const body = req.body;
17353
+ if (!body.filePath || typeof body.filePath !== "string") {
17354
+ res.status(400).json({ error: "filePath is required" });
17355
+ return;
17356
+ }
17357
+ const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
17358
+ let content;
17359
+ for (const taskId of taskIds) {
17360
+ const snap = this.store.getLatestFileSnapshots(taskId).find((s) => s.filePath === body.filePath);
17361
+ if (snap) {
17362
+ content = snap.content;
17363
+ break;
17364
+ }
17365
+ }
17366
+ if (content === void 0) {
17367
+ res.status(404).json({ error: "No snapshot recorded for that file in this session." });
17368
+ return;
17369
+ }
17370
+ try {
17371
+ const { writeFile } = await import('fs/promises');
17372
+ await writeFile(body.filePath, content, "utf-8");
17373
+ res.json({ ok: true, filePath: body.filePath });
17374
+ } catch (err) {
17375
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17376
+ }
17377
+ });
17378
+ this.app.get("/api/costs", auth, (_req, res) => {
17379
+ try {
17380
+ const sessions = this.store.listSessions(void 0, 1e3);
17381
+ const budget = this.config.budget ?? { warnAtPct: 80 };
17382
+ res.json({
17383
+ ...aggregateCostStats(sessions, { days: 30, topN: 8 }),
17384
+ budget: {
17385
+ dailyBudgetUsd: budget.dailyBudgetUsd,
17386
+ sessionBudgetUsd: budget.sessionBudgetUsd,
17387
+ maxCostPerRunUsd: budget.maxCostPerRunUsd
17388
+ }
17389
+ });
17390
+ } catch (err) {
17391
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17392
+ }
17393
+ });
17394
+ this.app.get("/api/schedules", auth, (_req, res) => {
17395
+ try {
17396
+ res.json(this.scheduler.list().map((t) => ({ ...t, armed: this.scheduler.isRunning(t.id) })));
17397
+ } catch (err) {
17398
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17399
+ }
17400
+ });
17401
+ this.app.post("/api/schedules", auth, mutationLimiter, (req, res) => {
17402
+ const body = req.body;
17403
+ if (!body.name?.trim() || !body.cronExpression?.trim() || !body.prompt?.trim()) {
17404
+ res.status(400).json({ error: "name, cronExpression, and prompt are required" });
17405
+ return;
17406
+ }
17407
+ if (!TaskScheduler.validateCron(body.cronExpression.trim())) {
17408
+ res.status(400).json({ error: `Invalid cron expression: ${body.cronExpression}` });
17409
+ return;
17410
+ }
17411
+ const task = {
17412
+ id: randomUUID(),
17413
+ name: body.name.trim(),
17414
+ cronExpression: body.cronExpression.trim(),
17415
+ prompt: body.prompt.trim(),
17416
+ workspacePath: this.workspacePath,
17417
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17418
+ enabled: body.enabled !== false
17419
+ };
17420
+ try {
17421
+ this.scheduler.add(task);
17422
+ res.json(task);
17423
+ } catch (err) {
17424
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17425
+ }
17426
+ });
17427
+ this.app.put("/api/schedules/:id", auth, mutationLimiter, (req, res) => {
17428
+ const id = req.params.id;
17429
+ const existing = this.scheduler.list().find((t) => t.id === id);
17430
+ if (!existing) {
17431
+ res.status(404).json({ error: "Not found" });
17432
+ return;
17433
+ }
17434
+ const body = req.body;
17435
+ if (body.cronExpression !== void 0 && !TaskScheduler.validateCron(body.cronExpression)) {
17436
+ res.status(400).json({ error: `Invalid cron expression: ${body.cronExpression}` });
17437
+ return;
17438
+ }
17439
+ const updated = {
17440
+ ...existing,
17441
+ name: body.name?.trim() || existing.name,
17442
+ cronExpression: body.cronExpression?.trim() || existing.cronExpression,
17443
+ prompt: body.prompt?.trim() || existing.prompt,
17444
+ enabled: body.enabled ?? existing.enabled
17445
+ };
17446
+ try {
17447
+ this.scheduler.add(updated);
17448
+ if (!updated.enabled) this.scheduler.unschedule(id);
17449
+ res.json(updated);
17450
+ } catch (err) {
17451
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17452
+ }
17453
+ });
17454
+ this.app.delete("/api/schedules/:id", auth, mutationLimiter, (req, res) => {
17455
+ try {
17456
+ this.scheduler.remove(req.params.id);
17457
+ res.json({ ok: true });
17458
+ } catch (err) {
17459
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17460
+ }
17461
+ });
17462
+ this.app.get("/api/knowledge", auth, (_req, res) => {
17463
+ try {
17464
+ const ws = new WorldStateDB(this.workspacePath);
17465
+ try {
17466
+ const facts = ws.getAllFacts();
17467
+ res.json({ total: facts.length, facts });
17468
+ } finally {
17469
+ ws.close();
17470
+ }
17471
+ } catch (err) {
17472
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17473
+ }
17474
+ });
17475
+ this.app.delete("/api/knowledge/fact", auth, mutationLimiter, (req, res) => {
17476
+ const body = req.body;
17477
+ if (!body.entity || !body.relation) {
17478
+ res.status(400).json({ error: "entity and relation are required" });
17479
+ return;
17480
+ }
17481
+ try {
17482
+ const ws = new WorldStateDB(this.workspacePath);
17483
+ try {
17484
+ res.json({ ok: true, deleted: ws.deleteFact(body.entity, body.relation) });
17485
+ } finally {
17486
+ ws.close();
17487
+ }
17488
+ } catch (err) {
17489
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17490
+ }
17491
+ });
17492
+ this.app.delete("/api/knowledge", auth, mutationLimiter, (_req, res) => {
17493
+ try {
17494
+ const ws = new WorldStateDB(this.workspacePath);
17495
+ try {
17496
+ res.json({ ok: true, deleted: ws.clearFacts() });
17497
+ } finally {
17498
+ ws.close();
17499
+ }
17500
+ } catch (err) {
17501
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17502
+ }
17503
+ });
17504
+ this.app.get("/api/audit-chain", auth, async (req, res) => {
17505
+ try {
17506
+ const limit = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
17507
+ const offset = Math.max(0, parseInt(req.query["offset"] || "0", 10) || 0);
17508
+ const { AuditLogger: AuditLogger3 } = await Promise.resolve().then(() => (init_audit_logger(), audit_logger_exports));
17509
+ const logger = new AuditLogger3(this.workspacePath);
17510
+ try {
17511
+ const all = logger.getAllLogs();
17512
+ const total = all.length;
17513
+ const entries = all.reverse().slice(offset, offset + limit);
17514
+ res.json({ total, offset, entries });
17515
+ } finally {
17516
+ logger.close();
17517
+ }
17518
+ } catch (err) {
17519
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
17520
+ }
17521
+ });
17522
+ const prodPath = path26.resolve(__dirname$1, "../web/dist");
17523
+ const devPath = path26.resolve(__dirname$1, "../../web/dist");
17524
+ const webDistPath = fs24.existsSync(prodPath) ? prodPath : devPath;
17525
+ if (fs24.existsSync(webDistPath)) {
16969
17526
  this.app.use(express.static(webDistPath));
16970
17527
  this.app.get("*", (_req, res) => {
16971
- res.sendFile(path25.join(webDistPath, "index.html"));
17528
+ res.sendFile(path26.join(webDistPath, "index.html"));
16972
17529
  });
16973
17530
  } else {
16974
17531
  this.app.get("/", (_req, res) => {
@@ -16989,7 +17546,7 @@ init_constants();
16989
17546
  async function dashboardCommand(config, workspacePath = process.cwd()) {
16990
17547
  const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
16991
17548
  const spin = ora({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
16992
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
17549
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
16993
17550
  const server = new DashboardServer(config, store, workspacePath);
16994
17551
  server.watchRuntimeChanges();
16995
17552
  const gThis = globalThis;
@@ -17021,7 +17578,7 @@ init_constants();
17021
17578
  function makeIdentityCommand(workspacePath = process.cwd()) {
17022
17579
  const identity = new Command("identity").alias("id").description("Manage Cascade identities");
17023
17580
  identity.command("list").description("List all available identities").action(() => {
17024
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
17581
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
17025
17582
  try {
17026
17583
  const identities = store.listIdentities();
17027
17584
  console.log(chalk9.bold("\n Identities:"));
@@ -17041,7 +17598,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
17041
17598
  }
17042
17599
  });
17043
17600
  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) => {
17044
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
17601
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
17045
17602
  try {
17046
17603
  if (options.default) {
17047
17604
  const existingDefault = store.getDefaultIdentity();
@@ -17067,7 +17624,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
17067
17624
  }
17068
17625
  });
17069
17626
  identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
17070
- const store = new MemoryStore(path25.join(workspacePath, CASCADE_DB_FILE));
17627
+ const store = new MemoryStore(path26.join(workspacePath, CASCADE_DB_FILE));
17071
17628
  try {
17072
17629
  const identities = store.listIdentities();
17073
17630
  const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
@@ -17195,11 +17752,11 @@ async function exportCommand(options = {}) {
17195
17752
  let store;
17196
17753
  try {
17197
17754
  const workspacePath = options.workspacePath ?? process.cwd();
17198
- const workspaceDbPath = path25.join(workspacePath, CASCADE_DB_FILE);
17199
- const globalDbPath = path25.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
17755
+ const workspaceDbPath = path26.join(workspacePath, CASCADE_DB_FILE);
17756
+ const globalDbPath = path26.join(os8.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
17200
17757
  let dbPath = globalDbPath;
17201
17758
  try {
17202
- await fs9.access(workspaceDbPath);
17759
+ await fs10.access(workspaceDbPath);
17203
17760
  dbPath = workspaceDbPath;
17204
17761
  } catch {
17205
17762
  }
@@ -17238,8 +17795,8 @@ async function exportCommand(options = {}) {
17238
17795
  const ext = format === "json" ? ".json" : ".md";
17239
17796
  const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
17240
17797
  const defaultFile = `cascade-export-${safeName}${ext}`;
17241
- const outPath = options.output ? path25.resolve(options.output) : path25.join(process.cwd(), defaultFile);
17242
- await fs9.writeFile(outPath, content, "utf-8");
17798
+ const outPath = options.output ? path26.resolve(options.output) : path26.join(process.cwd(), defaultFile);
17799
+ await fs10.writeFile(outPath, content, "utf-8");
17243
17800
  spin.succeed(chalk9.green(`Exported to ${chalk9.white(outPath)}`));
17244
17801
  const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
17245
17802
  console.log();
@@ -17466,11 +18023,11 @@ async function statsCommand() {
17466
18023
  dotenv.config();
17467
18024
  function warnIfBuildIsStale() {
17468
18025
  try {
17469
- const here = path25.dirname(fileURLToPath(import.meta.url));
18026
+ const here = path26.dirname(fileURLToPath(import.meta.url));
17470
18027
  for (const rel of ["..", "../.."]) {
17471
- const pkgPath = path25.join(here, rel, "package.json");
17472
- if (!fs23.existsSync(pkgPath)) continue;
17473
- const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf-8"));
18028
+ const pkgPath = path26.join(here, rel, "package.json");
18029
+ if (!fs24.existsSync(pkgPath)) continue;
18030
+ const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
17474
18031
  if (pkg.name !== "cascade-ai") continue;
17475
18032
  if (pkg.version && pkg.version !== CASCADE_VERSION) {
17476
18033
  console.error(