cascade-ai 0.12.24 → 0.13.1

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
@@ -8,19 +8,19 @@ var http = require('http');
8
8
  var https = require('https');
9
9
  var zlib = require('zlib');
10
10
  var stream = require('stream');
11
+ var Database2 = require('better-sqlite3');
12
+ var path25 = require('path');
13
+ var fs23 = require('fs');
14
+ var os8 = require('os');
15
+ var crypto = require('crypto');
11
16
  var ink = require('ink');
12
17
  var commander = require('commander');
13
18
  var React2 = require('react');
14
19
  var chalk9 = require('chalk');
15
20
  var dotenv = require('dotenv');
16
- var fs21 = require('fs');
17
- var path23 = require('path');
18
21
  var url = require('url');
19
22
  var fs9 = require('fs/promises');
20
- var os6 = require('os');
21
- var crypto = require('crypto');
22
23
  var _ignoreModule = require('ignore');
23
- var Database = require('better-sqlite3');
24
24
  var zod = require('zod');
25
25
  var child_process = require('child_process');
26
26
  var jsxRuntime = require('react/jsx-runtime');
@@ -71,16 +71,16 @@ var dns__default = /*#__PURE__*/_interopDefault(dns);
71
71
  var http__default = /*#__PURE__*/_interopDefault(http);
72
72
  var https__default = /*#__PURE__*/_interopDefault(https);
73
73
  var zlib__default = /*#__PURE__*/_interopDefault(zlib);
74
+ var Database2__default = /*#__PURE__*/_interopDefault(Database2);
75
+ var path25__default = /*#__PURE__*/_interopDefault(path25);
76
+ var fs23__default = /*#__PURE__*/_interopDefault(fs23);
77
+ var os8__default = /*#__PURE__*/_interopDefault(os8);
78
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
74
79
  var React2__default = /*#__PURE__*/_interopDefault(React2);
75
80
  var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
76
81
  var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
77
- var fs21__default = /*#__PURE__*/_interopDefault(fs21);
78
- var path23__default = /*#__PURE__*/_interopDefault(path23);
79
82
  var fs9__default = /*#__PURE__*/_interopDefault(fs9);
80
- var os6__default = /*#__PURE__*/_interopDefault(os6);
81
- var crypto__default = /*#__PURE__*/_interopDefault(crypto);
82
83
  var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
83
- var Database__default = /*#__PURE__*/_interopDefault(Database);
84
84
  var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
85
85
  var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
86
86
  var dns2__default = /*#__PURE__*/_interopDefault(dns2);
@@ -109,7 +109,7 @@ var __export = (target, all) => {
109
109
  var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
110
110
  var init_constants = __esm({
111
111
  "src/constants.ts"() {
112
- CASCADE_VERSION = "0.12.24";
112
+ CASCADE_VERSION = "0.13.1";
113
113
  CASCADE_CONFIG_FILE = ".cascade/config.json";
114
114
  CASCADE_DB_FILE = ".cascade/memory.db";
115
115
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -1576,6 +1576,149 @@ var init_openai_compatible = __esm({
1576
1576
  }
1577
1577
  });
1578
1578
 
1579
+ // src/core/audit/audit-logger.ts
1580
+ var audit_logger_exports = {};
1581
+ __export(audit_logger_exports, {
1582
+ AuditLogger: () => AuditLogger2
1583
+ });
1584
+ var AuditLogger2;
1585
+ var init_audit_logger = __esm({
1586
+ "src/core/audit/audit-logger.ts"() {
1587
+ AuditLogger2 = class {
1588
+ constructor(workspacePath, debugMode = false) {
1589
+ this.workspacePath = workspacePath;
1590
+ this.debugMode = debugMode;
1591
+ const cascadeDir = path25__default.default.join(workspacePath, ".cascade");
1592
+ if (!fs23__default.default.existsSync(cascadeDir)) {
1593
+ fs23__default.default.mkdirSync(cascadeDir, { recursive: true });
1594
+ }
1595
+ this.keyPath = path25__default.default.join(cascadeDir, "audit_log.key");
1596
+ this.dbPath = path25__default.default.join(cascadeDir, "audit_log.db");
1597
+ this.initEncryptionKey();
1598
+ this.db = new Database2__default.default(this.dbPath);
1599
+ this.db.pragma("journal_mode = WAL");
1600
+ this.db.exec(`
1601
+ CREATE TABLE IF NOT EXISTS audit_logs (
1602
+ id TEXT PRIMARY KEY,
1603
+ timestamp TEXT NOT NULL,
1604
+ event_type TEXT NOT NULL,
1605
+ tier_id TEXT NOT NULL,
1606
+ encrypted_payload TEXT NOT NULL,
1607
+ prev_hash TEXT NOT NULL DEFAULT '',
1608
+ hash TEXT NOT NULL DEFAULT ''
1609
+ )
1610
+ `);
1611
+ const cols = this.db.pragma("table_info(audit_logs)").map((c) => c.name);
1612
+ if (!cols.includes("prev_hash")) this.db.exec(`ALTER TABLE audit_logs ADD COLUMN prev_hash TEXT NOT NULL DEFAULT ''`);
1613
+ if (!cols.includes("hash")) this.db.exec(`ALTER TABLE audit_logs ADD COLUMN hash TEXT NOT NULL DEFAULT ''`);
1614
+ }
1615
+ workspacePath;
1616
+ debugMode;
1617
+ db;
1618
+ keyPath;
1619
+ dbPath;
1620
+ encryptionKey;
1621
+ /** SHA-256 link over everything that identifies an entry, chained to the previous entry's hash. */
1622
+ chainHash(prevHash, timestamp, eventType, tierId, encryptedPayload) {
1623
+ return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
1624
+ }
1625
+ initEncryptionKey() {
1626
+ if (fs23__default.default.existsSync(this.keyPath)) {
1627
+ this.encryptionKey = fs23__default.default.readFileSync(this.keyPath);
1628
+ } else {
1629
+ this.encryptionKey = crypto__default.default.randomBytes(32);
1630
+ fs23__default.default.writeFileSync(this.keyPath, this.encryptionKey);
1631
+ }
1632
+ }
1633
+ encrypt(text) {
1634
+ const iv = crypto__default.default.randomBytes(16);
1635
+ const cipher = crypto__default.default.createCipheriv("aes-256-gcm", this.encryptionKey, iv);
1636
+ let encrypted = cipher.update(text, "utf8", "hex");
1637
+ encrypted += cipher.final("hex");
1638
+ const authTag = cipher.getAuthTag().toString("hex");
1639
+ return `${iv.toString("hex")}:${authTag}:${encrypted}`;
1640
+ }
1641
+ decrypt(text) {
1642
+ const parts = text.split(":");
1643
+ if (parts.length !== 3) throw new Error("Invalid encrypted payload format");
1644
+ const [ivHex, authTagHex, encryptedHex] = parts;
1645
+ const iv = Buffer.from(ivHex, "hex");
1646
+ const authTag = Buffer.from(authTagHex, "hex");
1647
+ const decipher = crypto__default.default.createDecipheriv("aes-256-gcm", this.encryptionKey, iv);
1648
+ decipher.setAuthTag(authTag);
1649
+ let decrypted = decipher.update(encryptedHex, "hex", "utf8");
1650
+ decrypted += decipher.final("utf8");
1651
+ return decrypted;
1652
+ }
1653
+ logEvent(eventType, tierId, payloadObj) {
1654
+ const id = crypto__default.default.randomUUID();
1655
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1656
+ const payload = JSON.stringify(payloadObj);
1657
+ const encryptedPayload = this.encrypt(payload);
1658
+ const last = this.db.prepare("SELECT hash FROM audit_logs ORDER BY rowid DESC LIMIT 1").get();
1659
+ const prevHash = last?.hash ?? "";
1660
+ const hash = this.chainHash(prevHash, timestamp, eventType, tierId, encryptedPayload);
1661
+ const stmt = this.db.prepare("INSERT INTO audit_logs (id, timestamp, event_type, tier_id, encrypted_payload, prev_hash, hash) VALUES (?, ?, ?, ?, ?, ?, ?)");
1662
+ stmt.run(id, timestamp, eventType, tierId, encryptedPayload, prevHash, hash);
1663
+ this.dumpDebugIfNeeded();
1664
+ return id;
1665
+ }
1666
+ /**
1667
+ * Recompute the whole hash chain in insertion (rowid) order. Any edited,
1668
+ * removed, or reordered row makes every later hash mismatch. Rows written
1669
+ * before the chain existed (empty hash) fail verification too — integrity
1670
+ * can only be claimed for chained history.
1671
+ */
1672
+ verifyChain() {
1673
+ const rows = this.db.prepare(
1674
+ "SELECT rowid, timestamp, event_type, tier_id, encrypted_payload, prev_hash, hash FROM audit_logs ORDER BY rowid ASC"
1675
+ ).all();
1676
+ let prevHash = "";
1677
+ for (const row of rows) {
1678
+ const expected = this.chainHash(prevHash, row.timestamp, row.event_type, row.tier_id, row.encrypted_payload);
1679
+ if (row.prev_hash !== prevHash || row.hash !== expected) {
1680
+ return { ok: false, entries: rows.length, firstBadRow: row.rowid };
1681
+ }
1682
+ prevHash = row.hash;
1683
+ }
1684
+ return { ok: true, entries: rows.length };
1685
+ }
1686
+ getAllLogs() {
1687
+ const stmt = this.db.prepare("SELECT id, timestamp, event_type, tier_id, encrypted_payload FROM audit_logs ORDER BY timestamp ASC");
1688
+ const rows = stmt.all();
1689
+ return rows.map((row) => {
1690
+ let payload = "";
1691
+ try {
1692
+ payload = this.decrypt(row.encrypted_payload);
1693
+ } catch (err) {
1694
+ payload = '{"error":"[Decryption Failed - Payload Corrupted]"}';
1695
+ }
1696
+ return {
1697
+ id: row.id,
1698
+ timestamp: row.timestamp,
1699
+ eventType: row.event_type,
1700
+ tierId: row.tier_id,
1701
+ payload
1702
+ };
1703
+ });
1704
+ }
1705
+ dumpDebugIfNeeded() {
1706
+ if (!this.debugMode) return;
1707
+ try {
1708
+ const dumpPath = path25__default.default.join(os8__default.default.tmpdir(), "cascade_audit_logs_debug.json");
1709
+ const entries = this.getAllLogs();
1710
+ fs23__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
1711
+ } catch (err) {
1712
+ console.error("Failed to dump debug audit logs", err);
1713
+ }
1714
+ }
1715
+ close() {
1716
+ this.db.close();
1717
+ }
1718
+ };
1719
+ }
1720
+ });
1721
+
1579
1722
  // src/cli/index.ts
1580
1723
  init_constants();
1581
1724
  var ALGORITHM = "aes-256-gcm";
@@ -1619,7 +1762,7 @@ var Keystore = class {
1619
1762
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
1620
1763
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
1621
1764
  this.backend = "keytar";
1622
- if (password && fs21__default.default.existsSync(this.storePath)) {
1765
+ if (password && fs23__default.default.existsSync(this.storePath)) {
1623
1766
  try {
1624
1767
  const fileEntries = this.decryptFile(password);
1625
1768
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -1638,7 +1781,7 @@ var Keystore = class {
1638
1781
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
1639
1782
  );
1640
1783
  }
1641
- if (!fs21__default.default.existsSync(this.storePath)) {
1784
+ if (!fs23__default.default.existsSync(this.storePath)) {
1642
1785
  const salt = crypto__default.default.randomBytes(SALT_LEN);
1643
1786
  this.masterKey = this.deriveKey(password, salt);
1644
1787
  this.writeWithSalt({}, salt);
@@ -1652,7 +1795,7 @@ var Keystore = class {
1652
1795
  }
1653
1796
  /** Synchronous legacy unlock kept for AES-only environments. */
1654
1797
  unlockSync(password) {
1655
- if (!fs21__default.default.existsSync(this.storePath)) {
1798
+ if (!fs23__default.default.existsSync(this.storePath)) {
1656
1799
  const salt = crypto__default.default.randomBytes(SALT_LEN);
1657
1800
  this.masterKey = this.deriveKey(password, salt);
1658
1801
  this.writeWithSalt({}, salt);
@@ -1710,7 +1853,7 @@ var Keystore = class {
1710
1853
  }
1711
1854
  }
1712
1855
  decryptFile(password, knownSalt) {
1713
- if (!fs21__default.default.existsSync(this.storePath)) return {};
1856
+ if (!fs23__default.default.existsSync(this.storePath)) return {};
1714
1857
  try {
1715
1858
  const { salt, ciphertext, iv, tag } = this.readRaw();
1716
1859
  const useSalt = knownSalt ?? salt;
@@ -1732,8 +1875,8 @@ var Keystore = class {
1732
1875
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1733
1876
  const tag = cipher.getAuthTag();
1734
1877
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
1735
- fs21__default.default.mkdirSync(path23__default.default.dirname(this.storePath), { recursive: true });
1736
- fs21__default.default.writeFileSync(this.storePath, out, { mode: 384 });
1878
+ fs23__default.default.mkdirSync(path25__default.default.dirname(this.storePath), { recursive: true });
1879
+ fs23__default.default.writeFileSync(this.storePath, out, { mode: 384 });
1737
1880
  }
1738
1881
  writeWithSalt(data, salt) {
1739
1882
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -1743,11 +1886,11 @@ var Keystore = class {
1743
1886
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1744
1887
  const tag = cipher.getAuthTag();
1745
1888
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
1746
- fs21__default.default.mkdirSync(path23__default.default.dirname(this.storePath), { recursive: true });
1747
- fs21__default.default.writeFileSync(this.storePath, out, { mode: 384 });
1889
+ fs23__default.default.mkdirSync(path25__default.default.dirname(this.storePath), { recursive: true });
1890
+ fs23__default.default.writeFileSync(this.storePath, out, { mode: 384 });
1748
1891
  }
1749
1892
  readRaw() {
1750
- const buf = fs21__default.default.readFileSync(this.storePath);
1893
+ const buf = fs23__default.default.readFileSync(this.storePath);
1751
1894
  let offset = 0;
1752
1895
  const salt = buf.subarray(offset, offset + SALT_LEN);
1753
1896
  offset += SALT_LEN;
@@ -1780,7 +1923,7 @@ var CascadeIgnore = class {
1780
1923
  ]);
1781
1924
  }
1782
1925
  async load(workspacePath) {
1783
- const filePath = path23__default.default.join(workspacePath, ".cascadeignore");
1926
+ const filePath = path25__default.default.join(workspacePath, ".cascadeignore");
1784
1927
  try {
1785
1928
  const content = await fs9__default.default.readFile(filePath, "utf-8");
1786
1929
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
@@ -1791,7 +1934,7 @@ var CascadeIgnore = class {
1791
1934
  }
1792
1935
  isIgnored(filePath, workspacePath) {
1793
1936
  try {
1794
- const relative = workspacePath ? path23__default.default.relative(workspacePath, filePath) : filePath;
1937
+ const relative = workspacePath ? path25__default.default.relative(workspacePath, filePath) : filePath;
1795
1938
  return this.ig.ignores(relative);
1796
1939
  } catch {
1797
1940
  return false;
@@ -1802,7 +1945,7 @@ var CascadeIgnore = class {
1802
1945
  }
1803
1946
  };
1804
1947
  async function createDefaultIgnoreFile(workspacePath) {
1805
- const filePath = path23__default.default.join(workspacePath, ".cascadeignore");
1948
+ const filePath = path25__default.default.join(workspacePath, ".cascadeignore");
1806
1949
  const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
1807
1950
  # Syntax identical to .gitignore
1808
1951
 
@@ -1832,7 +1975,7 @@ Thumbs.db
1832
1975
  await fs9__default.default.writeFile(filePath, content, "utf-8");
1833
1976
  }
1834
1977
  async function loadCascadeMd(workspacePath) {
1835
- const filePath = path23__default.default.join(workspacePath, "CASCADE.md");
1978
+ const filePath = path25__default.default.join(workspacePath, "CASCADE.md");
1836
1979
  try {
1837
1980
  const raw = await fs9__default.default.readFile(filePath, "utf-8");
1838
1981
  return parseCascadeMd(raw);
@@ -1861,7 +2004,7 @@ ${raw.trim()}`;
1861
2004
  return { raw, sections, systemPrompt };
1862
2005
  }
1863
2006
  async function createDefaultCascadeMd(workspacePath) {
1864
- const filePath = path23__default.default.join(workspacePath, "CASCADE.md");
2007
+ const filePath = path25__default.default.join(workspacePath, "CASCADE.md");
1865
2008
  const content = `# Cascade Project Instructions
1866
2009
 
1867
2010
  This file contains project-specific instructions for the Cascade AI agent.
@@ -1896,9 +2039,9 @@ List any areas the agent should not touch.
1896
2039
  var MemoryStore = class _MemoryStore {
1897
2040
  db;
1898
2041
  constructor(dbPath) {
1899
- fs21__default.default.mkdirSync(path23__default.default.dirname(dbPath), { recursive: true });
2042
+ fs23__default.default.mkdirSync(path25__default.default.dirname(dbPath), { recursive: true });
1900
2043
  try {
1901
- this.db = new Database__default.default(dbPath, { timeout: 5e3 });
2044
+ this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
1902
2045
  this.db.pragma("journal_mode = WAL");
1903
2046
  this.db.pragma("foreign_keys = ON");
1904
2047
  this.db.pragma("synchronous = NORMAL");
@@ -2743,7 +2886,8 @@ var WorkspaceConfigSchema = zod.z.object({
2743
2886
  cascadeMdPath: zod.z.string().default("CASCADE.md"),
2744
2887
  configPath: zod.z.string().default(".cascade/config.json"),
2745
2888
  keystorePath: zod.z.string().default(".cascade/keystore.enc"),
2746
- auditLogPath: zod.z.string().default(".cascade/audit.log")
2889
+ auditLogPath: zod.z.string().default(".cascade/audit.log"),
2890
+ debugWorldState: zod.z.boolean().default(false)
2747
2891
  });
2748
2892
  var CascadeConfigSchema = zod.z.object({
2749
2893
  version: zod.z.literal("1.0").default("1.0"),
@@ -2888,6 +3032,14 @@ var CascadeConfigSchema = zod.z.object({
2888
3032
  * 'parallel' / 'sequential' — force it.
2889
3033
  */
2890
3034
  t3Execution: zod.z.enum(["auto", "parallel", "sequential"]).default("auto"),
3035
+ /**
3036
+ * Per-path privacy tiers. A subtask touching a `local-only` path is forced
3037
+ * onto LOCAL models (never cloud) and its raw output is withheld from the
3038
+ * tiers above. Patterns use .gitignore syntax, like .cascadeignore.
3039
+ */
3040
+ privacy: zod.z.object({
3041
+ paths: zod.z.array(zod.z.object({ pattern: zod.z.string().min(1), policy: zod.z.enum(["local-only"]) })).default([])
3042
+ }).optional(),
2891
3043
  /**
2892
3044
  * T3→T2 reinforcement: when enabled, a worker that discovers its subtask should
2893
3045
  * fan out can call the `request_workers` tool to have its T2 manager spawn
@@ -2937,15 +3089,15 @@ var ConfigManager = class {
2937
3089
  globalDir;
2938
3090
  constructor(workspacePath = process.cwd()) {
2939
3091
  this.workspacePath = workspacePath;
2940
- this.globalDir = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
3092
+ this.globalDir = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR);
2941
3093
  }
2942
3094
  async load() {
2943
3095
  this.config = await this.loadConfig();
2944
3096
  this.ignore = new CascadeIgnore();
2945
3097
  await this.ignore.load(this.workspacePath);
2946
3098
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
2947
- this.keystore = new Keystore(path23__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
2948
- this.store = new MemoryStore(path23__default.default.join(this.workspacePath, CASCADE_DB_FILE));
3099
+ this.keystore = new Keystore(path25__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
3100
+ this.store = new MemoryStore(path25__default.default.join(this.workspacePath, CASCADE_DB_FILE));
2949
3101
  await this.injectEnvKeys();
2950
3102
  await this.ensureDefaultIdentity();
2951
3103
  }
@@ -2968,8 +3120,8 @@ var ConfigManager = class {
2968
3120
  return this.workspacePath;
2969
3121
  }
2970
3122
  async save() {
2971
- const configPath = path23__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
2972
- await fs9__default.default.mkdir(path23__default.default.dirname(configPath), { recursive: true });
3123
+ const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
3124
+ await fs9__default.default.mkdir(path25__default.default.dirname(configPath), { recursive: true });
2973
3125
  await fs9__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
2974
3126
  }
2975
3127
  async updateConfig(updates) {
@@ -2993,7 +3145,7 @@ var ConfigManager = class {
2993
3145
  return configProvider?.apiKey;
2994
3146
  }
2995
3147
  async loadConfig() {
2996
- const configPath = path23__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
3148
+ const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
2997
3149
  try {
2998
3150
  const raw = await fs9__default.default.readFile(configPath, "utf-8");
2999
3151
  return validateConfig(JSON.parse(raw));
@@ -3872,7 +4024,7 @@ init_constants();
3872
4024
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
3873
4025
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
3874
4026
  var FETCH_TIMEOUT_MS = 8e3;
3875
- var DEFAULT_CACHE_FILE = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
4027
+ var DEFAULT_CACHE_FILE = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
3876
4028
  function normalizeModelId(id) {
3877
4029
  let s = id.toLowerCase();
3878
4030
  const slash = s.lastIndexOf("/");
@@ -3987,7 +4139,7 @@ var LiveDataProvider = class {
3987
4139
  }
3988
4140
  async saveCache() {
3989
4141
  try {
3990
- await fs9__default.default.mkdir(path23__default.default.dirname(this.opts.cacheFile), { recursive: true });
4142
+ await fs9__default.default.mkdir(path25__default.default.dirname(this.opts.cacheFile), { recursive: true });
3991
4143
  const cache = {
3992
4144
  fetchedAt: this.fetchedAt,
3993
4145
  snapshot: this.snapshot ?? void 0,
@@ -4117,7 +4269,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4117
4269
  costByTier: {},
4118
4270
  tokensByTier: {},
4119
4271
  inputTokensByTier: {},
4120
- outputTokensByTier: {}
4272
+ outputTokensByTier: {},
4273
+ costByFeature: {}
4121
4274
  };
4122
4275
  tierModels = /* @__PURE__ */ new Map();
4123
4276
  config;
@@ -4139,6 +4292,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4139
4292
  tpmLimiter;
4140
4293
  localQueue;
4141
4294
  taskAnalyzer;
4295
+ worldStateDB;
4296
+ privacyPaths;
4297
+ guidanceQueue;
4142
4298
  liveData;
4143
4299
  /** Snapshot of configured/default tier models, taken before Cascade Auto overrides them. */
4144
4300
  originalTierModels;
@@ -4296,7 +4452,17 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4296
4452
  if (options.model && !requireVision) {
4297
4453
  this.ensureProvider(options.model, this.config.providers);
4298
4454
  }
4299
- const model = requireVision ? this.selector.selectVisionModel() : options.model ?? this.tierModels.get(tier);
4455
+ let model = requireVision ? this.selector.selectVisionModel() : options.model ?? this.tierModels.get(tier);
4456
+ if (options.forceLocal && model && !this.isPrivateModel(model)) {
4457
+ const localModel = this.selector.getAllAvailableModels().find((m) => this.isPrivateModel(m));
4458
+ if (!localModel) {
4459
+ throw new Error(
4460
+ "privacy.paths: this subtask touches a local-only path but no LOCAL model is available. Configure Ollama or an OpenAI-compatible endpoint on a loopback/private host, or remove the privacy policy."
4461
+ );
4462
+ }
4463
+ this.ensureProvider(localModel, this.config.providers);
4464
+ model = localModel;
4465
+ }
4300
4466
  if (!model) throw new Error(`No model available for tier ${tier}`);
4301
4467
  const provider = this.getProvider(model);
4302
4468
  if (!provider) throw new Error(`No provider for model ${model.id}`);
@@ -4368,7 +4534,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4368
4534
  if (!result || typeof result.content !== "string" || !result.usage) {
4369
4535
  throw new Error(`Provider ${model.provider}:${model.id} returned an invalid generation result.`);
4370
4536
  }
4371
- this.recordStats(tier, model, result.usage);
4537
+ this.recordStats(tier, model, result.usage, options.featureTag);
4372
4538
  this.failover.recordSuccess(model.provider);
4373
4539
  return result;
4374
4540
  } catch (err) {
@@ -4473,6 +4639,42 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4473
4639
  setTaskAnalyzer(analyzer) {
4474
4640
  this.taskAnalyzer = analyzer;
4475
4641
  }
4642
+ setWorldStateDB(db) {
4643
+ this.worldStateDB = db;
4644
+ }
4645
+ getWorldStateDB() {
4646
+ return this.worldStateDB;
4647
+ }
4648
+ setPrivacyPaths(paths) {
4649
+ this.privacyPaths = paths;
4650
+ }
4651
+ getPrivacyPaths() {
4652
+ return this.privacyPaths;
4653
+ }
4654
+ setGuidanceQueue(queue) {
4655
+ this.guidanceQueue = queue;
4656
+ }
4657
+ getGuidanceQueue() {
4658
+ return this.guidanceQueue;
4659
+ }
4660
+ /**
4661
+ * "Private" = inference never leaves the user's machine/network: Ollama
4662
+ * models (isLocal), or an OpenAI-compatible endpoint (e.g. llama.cpp, vLLM,
4663
+ * LM Studio) whose configured host is loopback or a private range. A cloud
4664
+ * OpenAI-compatible endpoint (public host) does NOT qualify.
4665
+ */
4666
+ isPrivateModel(model) {
4667
+ if (model.isLocal) return true;
4668
+ if (model.provider !== "openai-compatible") return false;
4669
+ const baseUrl = this.config?.providers?.find((p) => p.type === "openai-compatible")?.baseUrl;
4670
+ if (!baseUrl) return false;
4671
+ try {
4672
+ const host = new URL(baseUrl).hostname.toLowerCase();
4673
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]" || /^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host) || host.endsWith(".local");
4674
+ } catch {
4675
+ return false;
4676
+ }
4677
+ }
4476
4678
  /**
4477
4679
  * Cascade Auto per-subtask routing: pick the benchmark-best model for a
4478
4680
  * specific subtask's text, scoped to the tier's eligible candidates. Returns
@@ -4496,7 +4698,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4496
4698
  costByTier: { ...this.stats.costByTier },
4497
4699
  tokensByTier: { ...this.stats.tokensByTier },
4498
4700
  inputTokensByTier: { ...this.stats.inputTokensByTier },
4499
- outputTokensByTier: { ...this.stats.outputTokensByTier }
4701
+ outputTokensByTier: { ...this.stats.outputTokensByTier },
4702
+ costByFeature: { ...this.stats.costByFeature }
4500
4703
  };
4501
4704
  }
4502
4705
  /**
@@ -4546,7 +4749,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4546
4749
  costByTier: {},
4547
4750
  tokensByTier: {},
4548
4751
  inputTokensByTier: {},
4549
- outputTokensByTier: {}
4752
+ outputTokensByTier: {},
4753
+ costByFeature: {}
4550
4754
  };
4551
4755
  this.sessionCostUsd = 0;
4552
4756
  this.budgetState = "ok";
@@ -4712,7 +4916,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4712
4916
  }
4713
4917
  return void 0;
4714
4918
  }
4715
- recordStats(tier, model, usage) {
4919
+ recordStats(tier, model, usage, featureTag) {
4716
4920
  this.stats.totalTokens += usage.totalTokens;
4717
4921
  this.stats.totalCostUsd += usage.estimatedCostUsd;
4718
4922
  this.sessionCostUsd += usage.estimatedCostUsd;
@@ -4722,6 +4926,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
4722
4926
  this.stats.tokensByTier[tier] = (this.stats.tokensByTier[tier] ?? 0) + usage.totalTokens;
4723
4927
  this.stats.inputTokensByTier[tier] = (this.stats.inputTokensByTier[tier] ?? 0) + usage.inputTokens;
4724
4928
  this.stats.outputTokensByTier[tier] = (this.stats.outputTokensByTier[tier] ?? 0) + usage.outputTokens;
4929
+ if (featureTag) {
4930
+ this.stats.costByFeature[featureTag] = (this.stats.costByFeature[featureTag] ?? 0) + usage.estimatedCostUsd;
4931
+ }
4725
4932
  this.runTokens += usage.totalTokens;
4726
4933
  this.runCostUsd += usage.estimatedCostUsd;
4727
4934
  this.updateBudgetState();
@@ -5259,6 +5466,8 @@ var T3Worker = class extends BaseTier {
5259
5466
  toolRegistry;
5260
5467
  context;
5261
5468
  assignment;
5469
+ /** True when this subtask matched a privacy.paths local-only pattern. */
5470
+ localOnlyMatch = false;
5262
5471
  peerSyncBuffer = [];
5263
5472
  store;
5264
5473
  audit;
@@ -5314,6 +5523,11 @@ var T3Worker = class extends BaseTier {
5314
5523
  this.taskId = taskId;
5315
5524
  this.setLabel(assignment.subtaskTitle);
5316
5525
  this.setStatus("ACTIVE");
5526
+ const privacy = this.router.getPrivacyPaths?.();
5527
+ this.localOnlyMatch = !!privacy?.hasPolicies() && privacy.anyLocalOnly(this.extractArtifactPaths(assignment));
5528
+ if (this.localOnlyMatch) {
5529
+ this.log("Privacy: subtask touches a local-only path \u2014 forcing a private model; raw output will be withheld upstream.");
5530
+ }
5317
5531
  this.tools = this.toolRegistry.getToolDefinitions();
5318
5532
  if (this.reinforcementDepth === 0 && this.router.getReinforcementsConfig?.()?.enabled) {
5319
5533
  this.tools = [...this.tools, {
@@ -5444,9 +5658,17 @@ Now execute your subtask using this context where relevant.`
5444
5658
  }
5445
5659
  const reflectCfg = this.router.getReflectionConfig?.() ?? { enabled: false, maxRounds: 1 };
5446
5660
  if (reflectCfg.enabled) {
5447
- this.sendStatusUpdate({ progressPct: 85, currentAction: "Reflecting on output", status: "IN_PROGRESS" });
5661
+ this.sendStatusUpdate({ progressPct: 85, currentAction: "Reflecting on output via T2-Critic", status: "IN_PROGRESS" });
5448
5662
  output = await this.reflectAndImprove(assignment, output, reflectCfg.maxRounds);
5449
5663
  }
5664
+ const db = this.router.getWorldStateDB?.();
5665
+ if (db) {
5666
+ try {
5667
+ db.addEntry(this.id, `Completed: ${assignment.subtaskTitle}. Output length: ${output.length} chars.`);
5668
+ } catch (e) {
5669
+ this.log("Failed to write to World State DB");
5670
+ }
5671
+ }
5450
5672
  this.setStatus("COMPLETED", output);
5451
5673
  this.sendStatusUpdate({ progressPct: 100, currentAction: "Subtask complete", status: "IN_PROGRESS", output });
5452
5674
  this.peerBus?.publish(this.id, assignment.subtaskId, output, "COMPLETED");
@@ -5519,6 +5741,16 @@ Now execute your subtask using this context where relevant.`
5519
5741
  while (iterations < MAX_ITERATIONS) {
5520
5742
  iterations++;
5521
5743
  this.throwIfCancelled();
5744
+ const guidance = this.router.getGuidanceQueue?.()?.drain(this.id) ?? [];
5745
+ for (const g of guidance) {
5746
+ this.log(`User intervention received: ${g.text}`);
5747
+ this.sendStatusUpdate({ progressPct: 50, currentAction: "Applying user intervention", status: "IN_PROGRESS" });
5748
+ await this.context.addMessage({
5749
+ role: "user",
5750
+ content: `USER INTERVENTION (mid-run steering \u2014 follow this over prior instructions where they conflict):
5751
+ ${g.text}`
5752
+ });
5753
+ }
5522
5754
  const options = {
5523
5755
  messages: this.context.getMessages(),
5524
5756
  systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
@@ -5527,7 +5759,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
5527
5759
  // Don't pass tools array when model can't use them natively
5528
5760
  tools: useTextTools ? void 0 : tools.length ? tools : void 0,
5529
5761
  maxTokens: 4096,
5530
- ...subtaskModel ? { model: subtaskModel } : {}
5762
+ ...subtaskModel ? { model: subtaskModel } : {},
5763
+ featureTag: this.assignment?.sectionTitle,
5764
+ ...this.localOnlyMatch ? { forceLocal: true } : {}
5531
5765
  };
5532
5766
  const result = await this.router.generate(
5533
5767
  "T3",
@@ -5691,8 +5925,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
5691
5925
  tierId: this.id,
5692
5926
  sessionId: this.taskId,
5693
5927
  requireApproval: false,
5694
- saveSnapshot: async (path28, content) => {
5695
- this.store?.addFileSnapshot(this.taskId, path28, content);
5928
+ saveSnapshot: async (path30, content) => {
5929
+ this.store?.addFileSnapshot(this.taskId, path30, content);
5696
5930
  },
5697
5931
  sendPeerSync: (to, syncType, content) => {
5698
5932
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -5862,7 +6096,7 @@ ${assignment.expectedOutput}`;
5862
6096
  const { promisify: promisify4 } = await import('util');
5863
6097
  const execAsync3 = promisify4(exec3);
5864
6098
  for (const artifactPath of artifactPaths) {
5865
- const absolutePath = path23__default.default.resolve(process.cwd(), artifactPath);
6099
+ const absolutePath = path25__default.default.resolve(process.cwd(), artifactPath);
5866
6100
  try {
5867
6101
  const stat = await fs9__default.default.stat(absolutePath);
5868
6102
  if (!stat.isFile()) {
@@ -5883,7 +6117,7 @@ ${assignment.expectedOutput}`;
5883
6117
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
5884
6118
  continue;
5885
6119
  }
5886
- const ext = path23__default.default.extname(absolutePath).toLowerCase();
6120
+ const ext = path25__default.default.extname(absolutePath).toLowerCase();
5887
6121
  try {
5888
6122
  if (ext === ".ts" || ext === ".tsx") {
5889
6123
  await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
@@ -5912,30 +6146,35 @@ ${stdout}`);
5912
6146
  * needed. Best-effort: any parse/error just keeps the current output.
5913
6147
  */
5914
6148
  async reflectAndImprove(assignment, output, maxRounds) {
5915
- const sys = this.systemPromptOverride + (this.hierarchyContext ? `
5916
-
5917
- HIERARCHY CONTEXT: ${this.hierarchyContext}` : "");
5918
6149
  let current = output;
5919
- for (let round = 0; round < Math.max(1, maxRounds); round++) {
5920
- try {
5921
- const verdict = await this.router.generate("T3", {
6150
+ try {
6151
+ for (let round = 0; round < Math.max(1, maxRounds); round++) {
6152
+ const verdictResult = await this.router.generate("T2", {
5922
6153
  messages: [{
5923
6154
  role: "user",
5924
- content: `Does this output FULLY achieve the goal \u2014 not just the literal task, but the intent behind it?
6155
+ content: `You are an independent critic reviewing another worker's output against its assignment.
5925
6156
 
5926
- Goal / expected: ${assignment.expectedOutput}
6157
+ Goal: ${assignment.expectedOutput}
5927
6158
  Subtask: ${assignment.description}
5928
-
5929
- Output:
6159
+ Current Output:
5930
6160
  ${current}
5931
6161
 
5932
- Reply with ONLY JSON: {"sufficient": true|false, "notes": "what is weak or missing if not sufficient"}`
6162
+ Is this output sufficient and correct? Respond with ONLY a JSON object:
6163
+ {"sufficient": true|false, "notes": "what is wrong or missing if false"}`
5933
6164
  }],
5934
- systemPrompt: sys,
5935
- maxTokens: 400
6165
+ systemPrompt: "You are a T2-Critic reviewing a T3 Worker's output. Judge strictly against the stated goal.",
6166
+ maxTokens: 400,
6167
+ signal: this.signal,
6168
+ featureTag: assignment.sectionTitle,
6169
+ ...this.localOnlyMatch ? { forceLocal: true } : {}
5936
6170
  });
5937
- const parsed = JSON.parse(/\{[\s\S]*\}/.exec(verdict.content)?.[0] ?? "{}");
5938
- if (parsed.sufficient !== false) break;
6171
+ const match = /\{[\s\S]*\}/.exec(verdictResult.content);
6172
+ const parsed = match ? JSON.parse(match[0]) : { sufficient: true };
6173
+ if (parsed.sufficient !== false) {
6174
+ this.log("T2-Critic approved output.");
6175
+ break;
6176
+ }
6177
+ this.log(`T2-Critic rejected output: ${parsed.notes}`);
5939
6178
  const improved = await this.router.generate("T3", {
5940
6179
  messages: [{
5941
6180
  role: "user",
@@ -5947,16 +6186,20 @@ Goal / expected: ${assignment.expectedOutput}
5947
6186
  Current output:
5948
6187
  ${current}`
5949
6188
  }],
5950
- systemPrompt: sys,
5951
- maxTokens: 4096
6189
+ systemPrompt: this.systemPromptOverride + (this.hierarchyContext ? `
6190
+
6191
+ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
6192
+ maxTokens: 4096,
6193
+ featureTag: assignment.sectionTitle,
6194
+ ...this.localOnlyMatch ? { forceLocal: true } : {}
5952
6195
  });
5953
6196
  const next = (improved.content ?? "").trim();
5954
6197
  if (!next) break;
5955
6198
  current = next;
5956
6199
  this.log("Reflection: revised output for better goal alignment.");
5957
- } catch {
5958
- break;
5959
6200
  }
6201
+ } catch (e) {
6202
+ this.log(`T2-Critic reflection failed: ${e}`);
5960
6203
  }
5961
6204
  return current;
5962
6205
  }
@@ -5977,7 +6220,9 @@ Reply with JSON: { "completeness": "pass"|"fail", "correctness": "pass"|"fail",
5977
6220
  maxTokens: 500,
5978
6221
  systemPrompt: this.systemPromptOverride + (this.hierarchyContext ? `
5979
6222
 
5980
- HIERARCHY CONTEXT: ${this.hierarchyContext}` : "")
6223
+ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
6224
+ featureTag: assignment.sectionTitle,
6225
+ ...this.localOnlyMatch ? { forceLocal: true } : {}
5981
6226
  });
5982
6227
  try {
5983
6228
  const jsonMatch = /\{[\s\S]*\}/.exec(testResult.content);
@@ -6072,6 +6317,7 @@ Begin execution now.`;
6072
6317
  issues,
6073
6318
  peerSyncsUsed: this.peerSyncBuffer.map((m) => m.fromId),
6074
6319
  correctionAttempts,
6320
+ localOnly: this.localOnlyMatch || void 0,
6075
6321
  reinforcements: this.pendingReinforcements.length ? this.pendingReinforcements : void 0
6076
6322
  };
6077
6323
  }
@@ -6363,6 +6609,42 @@ var PeerBus = class extends EventEmitter__default.default {
6363
6609
  }
6364
6610
  };
6365
6611
 
6612
+ // src/core/audit/redaction.ts
6613
+ var RedactionLayer = class {
6614
+ // Regexes for common secrets/PII
6615
+ static RULES = [
6616
+ // IPv4 addresses (basic approximation)
6617
+ { pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, replacement: "[REDACTED_IP]" },
6618
+ // Generic API keys/Secrets (looks like a long random hex or b64 string preceded by key/secret/token)
6619
+ { pattern: /(?:\b(?:api_key|apikey|secret|token|password|bearer|auth|authorization)\b[^a-zA-Z0-9_]{1,4})([a-zA-Z0-9_\-\.]{16,})/gi, replacement: "$1[REDACTED_SECRET]" },
6620
+ // Email addresses
6621
+ { pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g, replacement: "[REDACTED_EMAIL]" },
6622
+ // Phone numbers (simplistic)
6623
+ { pattern: /\b(?:\+\d{1,3}[- ]?)?\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}\b/g, replacement: "[REDACTED_PHONE]" },
6624
+ // AWS Access Key ID
6625
+ { pattern: /\b(AKIA[0-9A-Z]{16})\b/g, replacement: "[REDACTED_AWS_AK]" }
6626
+ ];
6627
+ /**
6628
+ * Applies all redaction rules to the input string.
6629
+ */
6630
+ static redact(text) {
6631
+ if (!text) return text;
6632
+ let redacted = text;
6633
+ for (const rule of this.RULES) {
6634
+ if (rule.pattern.test(redacted)) {
6635
+ rule.pattern.lastIndex = 0;
6636
+ redacted = redacted.replace(rule.pattern, (match, p1, offset, str2) => {
6637
+ if (p1 && match.includes(p1) && p1 !== match) {
6638
+ return match.replace(p1, rule.replacement.replace("$1", ""));
6639
+ }
6640
+ return rule.replacement;
6641
+ });
6642
+ }
6643
+ }
6644
+ return redacted;
6645
+ }
6646
+ };
6647
+
6366
6648
  // src/core/tiers/t2-manager.ts
6367
6649
  var T2_SYSTEM_PROMPT = `You are a T2 Manager agent in the Cascade AI system.
6368
6650
  Your role is to analyze a section of a task and decompose it into 2-5 discrete subtasks for T3 Workers.
@@ -6591,7 +6873,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
6591
6873
  try {
6592
6874
  const jsonMatch = /\[[\s\S]*\]/.exec(result.content);
6593
6875
  if (!jsonMatch) throw new Error("No JSON array found");
6594
- return JSON.parse(jsonMatch[0]);
6876
+ const parsed = JSON.parse(jsonMatch[0]);
6877
+ return parsed.map((a) => ({ ...a, sectionTitle: assignment.sectionTitle }));
6595
6878
  } catch {
6596
6879
  return [{
6597
6880
  subtaskId: crypto.randomUUID(),
@@ -6601,6 +6884,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
6601
6884
  constraints: assignment.constraints,
6602
6885
  peerT3Ids: [],
6603
6886
  parentT2: this.id,
6887
+ sectionTitle: assignment.sectionTitle,
6888
+ dependsOn: [],
6604
6889
  executionMode: "parallel"
6605
6890
  }];
6606
6891
  }
@@ -6720,6 +7005,14 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
6720
7005
  const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
6721
7006
  const worker = workerMap.get(id);
6722
7007
  const result = await worker.execute(assignment, taskId, waveSignal);
7008
+ if (result.localOnly) {
7009
+ result.output = `[local-only path \u2014 output withheld by privacy policy; status: ${result.status}; checks passed: ${result.testResults.passed.length}/${result.testResults.checksRun.length || 0}]`;
7010
+ } else {
7011
+ if (typeof result.output === "string" && result.output) {
7012
+ result.output = RedactionLayer.redact(result.output);
7013
+ }
7014
+ }
7015
+ if (result.issues) result.issues = result.issues.map((i) => RedactionLayer.redact(i));
6723
7016
  resultMap.set(id, result);
6724
7017
  return result;
6725
7018
  };
@@ -7352,10 +7645,15 @@ In 3-5 terse bullets, flag the most important RISKS, GAPS, or over-/under-decomp
7352
7645
  }
7353
7646
  }
7354
7647
  async decomposeTask(prompt, systemContext) {
7648
+ const db = this.router.getWorldStateDB?.();
7649
+ const worldStateContext = db ? `
7650
+
7651
+ PROJECT WORLD STATE (Current architecture and recent changes):
7652
+ ${db.getFormattedState()}` : "";
7355
7653
  const contextSection = systemContext ? `
7356
7654
  Project context:
7357
7655
  ${systemContext}` : "";
7358
- const decompositionPrompt = `Analyze this task and create an execution plan.${contextSection}
7656
+ const decompositionPrompt = `Analyze this task and create an execution plan.${contextSection}${worldStateContext}
7359
7657
 
7360
7658
  Task: ${prompt}
7361
7659
 
@@ -7843,16 +8141,16 @@ function resolveInWorkspace(workspaceRoot, input) {
7843
8141
  if (typeof input !== "string" || input.length === 0) {
7844
8142
  throw new WorkspaceSandboxError(String(input), workspaceRoot);
7845
8143
  }
7846
- const root = path23__default.default.resolve(workspaceRoot);
7847
- const abs = path23__default.default.isAbsolute(input) ? path23__default.default.resolve(input) : path23__default.default.resolve(root, input);
7848
- const rel = path23__default.default.relative(root, abs);
7849
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path23__default.default.isAbsolute(rel)) {
8144
+ const root = path25__default.default.resolve(workspaceRoot);
8145
+ const abs = path25__default.default.isAbsolute(input) ? path25__default.default.resolve(input) : path25__default.default.resolve(root, input);
8146
+ const rel = path25__default.default.relative(root, abs);
8147
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path25__default.default.isAbsolute(rel)) {
7850
8148
  throw new WorkspaceSandboxError(input, root);
7851
8149
  }
7852
8150
  try {
7853
- const real = fs21__default.default.realpathSync(abs);
7854
- const realRel = path23__default.default.relative(root, real);
7855
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path23__default.default.isAbsolute(realRel))) {
8151
+ const real = fs23__default.default.realpathSync(abs);
8152
+ const realRel = path25__default.default.relative(root, real);
8153
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path25__default.default.isAbsolute(realRel))) {
7856
8154
  throw new WorkspaceSandboxError(input, root);
7857
8155
  }
7858
8156
  } catch (e) {
@@ -7913,7 +8211,7 @@ var FileWriteTool = class extends BaseTool {
7913
8211
  } catch {
7914
8212
  }
7915
8213
  }
7916
- await fs9__default.default.mkdir(path23__default.default.dirname(absPath), { recursive: true });
8214
+ await fs9__default.default.mkdir(path25__default.default.dirname(absPath), { recursive: true });
7917
8215
  await fs9__default.default.writeFile(absPath, content, "utf-8");
7918
8216
  return `Written ${content.length} characters to ${filePath}`;
7919
8217
  }
@@ -8411,7 +8709,7 @@ var ImageAnalyzeTool = class extends BaseTool {
8411
8709
  };
8412
8710
  async function fileToImageAttachment(filePath) {
8413
8711
  const data = await fs9__default.default.readFile(filePath);
8414
- const ext = path23__default.default.extname(filePath).toLowerCase();
8712
+ const ext = path25__default.default.extname(filePath).toLowerCase();
8415
8713
  const mimeMap = {
8416
8714
  ".jpg": "image/jpeg",
8417
8715
  ".jpeg": "image/jpeg",
@@ -8445,14 +8743,14 @@ var PDFCreateTool = class extends BaseTool {
8445
8743
  const filePath = input["path"];
8446
8744
  const content = input["content"];
8447
8745
  const title = input["title"];
8448
- const dir = path23__default.default.dirname(filePath);
8449
- if (!fs21__default.default.existsSync(dir)) {
8450
- fs21__default.default.mkdirSync(dir, { recursive: true });
8746
+ const dir = path25__default.default.dirname(filePath);
8747
+ if (!fs23__default.default.existsSync(dir)) {
8748
+ fs23__default.default.mkdirSync(dir, { recursive: true });
8451
8749
  }
8452
8750
  return new Promise((resolve, reject) => {
8453
8751
  try {
8454
8752
  const doc = new PDFDocument__default.default({ margin: 50 });
8455
- const stream = fs21__default.default.createWriteStream(filePath);
8753
+ const stream = fs23__default.default.createWriteStream(filePath);
8456
8754
  doc.pipe(stream);
8457
8755
  if (title) {
8458
8756
  doc.info["Title"] = title;
@@ -8530,22 +8828,22 @@ var CodeInterpreterTool = class extends BaseTool {
8530
8828
  }
8531
8829
  cmdPrefix = NODE_CMD;
8532
8830
  }
8533
- const tmpDir = path23__default.default.join(this.workspaceRoot, ".cascade", "tmp");
8534
- if (!fs21__default.default.existsSync(tmpDir)) {
8535
- fs21__default.default.mkdirSync(tmpDir, { recursive: true });
8831
+ const tmpDir = path25__default.default.join(this.workspaceRoot, ".cascade", "tmp");
8832
+ if (!fs23__default.default.existsSync(tmpDir)) {
8833
+ fs23__default.default.mkdirSync(tmpDir, { recursive: true });
8536
8834
  }
8537
8835
  const extension = language === "python" ? "py" : "js";
8538
8836
  const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
8539
- const filePath = path23__default.default.join(tmpDir, fileName);
8540
- fs21__default.default.writeFileSync(filePath, code, "utf-8");
8837
+ const filePath = path25__default.default.join(tmpDir, fileName);
8838
+ fs23__default.default.writeFileSync(filePath, code, "utf-8");
8541
8839
  const execArgs = [filePath, ...args];
8542
8840
  return new Promise((resolve) => {
8543
8841
  const startMs = Date.now();
8544
8842
  child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
8545
8843
  const duration = Date.now() - startMs;
8546
8844
  try {
8547
- if (fs21__default.default.existsSync(filePath)) {
8548
- fs21__default.default.unlinkSync(filePath);
8845
+ if (fs23__default.default.existsSync(filePath)) {
8846
+ fs23__default.default.unlinkSync(filePath);
8549
8847
  }
8550
8848
  } catch (cleanupErr) {
8551
8849
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -8824,7 +9122,7 @@ var GlobTool = class extends BaseTool {
8824
9122
  };
8825
9123
  async execute(input, _options) {
8826
9124
  const pattern = input["pattern"];
8827
- const searchPath = input["path"] ? path23__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
9125
+ const searchPath = input["path"] ? path25__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
8828
9126
  const matches = await glob.glob(pattern, {
8829
9127
  cwd: searchPath,
8830
9128
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -8837,7 +9135,7 @@ var GlobTool = class extends BaseTool {
8837
9135
  const withMtime = await Promise.all(
8838
9136
  matches.map(async (rel) => {
8839
9137
  try {
8840
- const stat = await fs9__default.default.stat(path23__default.default.join(searchPath, rel));
9138
+ const stat = await fs9__default.default.stat(path25__default.default.join(searchPath, rel));
8841
9139
  return { rel, mtime: stat.mtimeMs };
8842
9140
  } catch {
8843
9141
  return { rel, mtime: 0 };
@@ -8886,7 +9184,7 @@ var GrepTool = class extends BaseTool {
8886
9184
  };
8887
9185
  async execute(input, _options) {
8888
9186
  const pattern = input["pattern"];
8889
- const searchPath = input["path"] ? path23__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
9187
+ const searchPath = input["path"] ? path25__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
8890
9188
  const globPattern = input["glob"];
8891
9189
  const outputMode = input["output_mode"] ?? "content";
8892
9190
  const context = input["context"] ?? 0;
@@ -8940,12 +9238,12 @@ var GrepTool = class extends BaseTool {
8940
9238
  nodir: true
8941
9239
  });
8942
9240
  } catch {
8943
- files = [path23__default.default.relative(searchPath, searchPath) || "."];
9241
+ files = [path25__default.default.relative(searchPath, searchPath) || "."];
8944
9242
  }
8945
9243
  const results = [];
8946
9244
  let totalCount = 0;
8947
9245
  for (const rel of files) {
8948
- const abs = path23__default.default.join(searchPath, rel);
9246
+ const abs = path25__default.default.join(searchPath, rel);
8949
9247
  let content;
8950
9248
  try {
8951
9249
  content = await fs9__default.default.readFile(abs, "utf-8");
@@ -9307,10 +9605,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
9307
9605
  }
9308
9606
  isIgnored(filePath) {
9309
9607
  if (!filePath) return false;
9310
- const abs = path23__default.default.resolve(this.workspaceRoot, filePath);
9311
- const rel = path23__default.default.relative(this.workspaceRoot, abs);
9312
- if (!rel || rel.startsWith("..") || path23__default.default.isAbsolute(rel)) return true;
9313
- const posixRel = rel.split(path23__default.default.sep).join("/");
9608
+ const abs = path25__default.default.resolve(this.workspaceRoot, filePath);
9609
+ const rel = path25__default.default.relative(this.workspaceRoot, abs);
9610
+ if (!rel || rel.startsWith("..") || path25__default.default.isAbsolute(rel)) return true;
9611
+ const posixRel = rel.split(path25__default.default.sep).join("/");
9314
9612
  return this.ignoreMatcher.ignores(posixRel);
9315
9613
  }
9316
9614
  };
@@ -9427,6 +9725,9 @@ var McpClient = class _McpClient {
9427
9725
  return this.clients.has(serverName);
9428
9726
  }
9429
9727
  };
9728
+
9729
+ // src/core/cascade.ts
9730
+ init_audit_logger();
9430
9731
  var SAFE_TOOLS = /* @__PURE__ */ new Set([
9431
9732
  "file_read",
9432
9733
  "file_list",
@@ -9810,9 +10111,10 @@ var TaskAnalyzer = class {
9810
10111
  analysisCache.clear();
9811
10112
  }
9812
10113
  };
9813
- var DEFAULT_STATS_FILE = path23__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
10114
+ var DEFAULT_STATS_FILE = path25__default.default.join(os8__default.default.homedir(), ".cascade", "model-perf.json");
9814
10115
  var ModelPerformanceTracker = class {
9815
10116
  stats = /* @__PURE__ */ new Map();
10117
+ featureStats = /* @__PURE__ */ new Map();
9816
10118
  statsFile;
9817
10119
  loaded = false;
9818
10120
  constructor(statsFile = DEFAULT_STATS_FILE) {
@@ -9824,18 +10126,29 @@ var ModelPerformanceTracker = class {
9824
10126
  try {
9825
10127
  const raw = await fs9__default.default.readFile(this.statsFile, "utf-8");
9826
10128
  const parsed = JSON.parse(raw);
9827
- for (const [key, stat] of Object.entries(parsed)) {
9828
- this.stats.set(key, stat);
10129
+ if (parsed.models) {
10130
+ for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
10131
+ } else {
10132
+ for (const [key, stat] of Object.entries(parsed)) {
10133
+ if (stat && typeof stat === "object" && typeof stat.successCount === "number") {
10134
+ this.stats.set(key, stat);
10135
+ }
10136
+ }
10137
+ }
10138
+ if (parsed.features) {
10139
+ for (const [key, stat] of Object.entries(parsed.features)) this.featureStats.set(key, stat);
9829
10140
  }
9830
10141
  } catch {
9831
10142
  }
9832
10143
  }
9833
10144
  async save() {
9834
10145
  try {
9835
- await fs9__default.default.mkdir(path23__default.default.dirname(this.statsFile), { recursive: true });
9836
- const obj = {};
9837
- for (const [key, stat] of this.stats) obj[key] = stat;
9838
- await fs9__default.default.writeFile(this.statsFile, JSON.stringify(obj, null, 2), "utf-8");
10146
+ await fs9__default.default.mkdir(path25__default.default.dirname(this.statsFile), { recursive: true });
10147
+ const modelsObj = {};
10148
+ const featuresObj = {};
10149
+ for (const [key, stat] of this.stats) modelsObj[key] = stat;
10150
+ for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
10151
+ await fs9__default.default.writeFile(this.statsFile, JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2), "utf-8");
9839
10152
  } catch {
9840
10153
  }
9841
10154
  }
@@ -9856,6 +10169,13 @@ var ModelPerformanceTracker = class {
9856
10169
  sampleCount: s.sampleCount + 1
9857
10170
  });
9858
10171
  }
10172
+ recordFeatureCost(featureTag, costUsd) {
10173
+ const s = this.featureStats.get(featureTag) ?? { totalCostUsd: 0, runCount: 0 };
10174
+ this.featureStats.set(featureTag, {
10175
+ totalCostUsd: s.totalCostUsd + costUsd,
10176
+ runCount: s.runCount + 1
10177
+ });
10178
+ }
9859
10179
  /**
9860
10180
  * Record an explicit user rating (good/bad). Counts as 3 automatic samples
9861
10181
  * so user feedback carries significantly more weight than auto-detected outcomes.
@@ -9870,6 +10190,9 @@ var ModelPerformanceTracker = class {
9870
10190
  getAll() {
9871
10191
  return new Map(this.stats);
9872
10192
  }
10193
+ getAllFeatures() {
10194
+ return new Map(this.featureStats);
10195
+ }
9873
10196
  /**
9874
10197
  * Returns 0.05–1.0; defaults to 0.5 (neutral prior) when no history exists.
9875
10198
  * High retry counts penalise the score.
@@ -10234,7 +10557,7 @@ Required capability: ${description.slice(0, 300)}`;
10234
10557
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
10235
10558
  async loadPersistedTools() {
10236
10559
  if (!this.workspacePath || !this.persistEnabled) return;
10237
- const file = path23__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
10560
+ const file = path25__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
10238
10561
  try {
10239
10562
  const raw = await fs9__default.default.readFile(file, "utf-8");
10240
10563
  const specs = JSON.parse(raw);
@@ -10258,8 +10581,8 @@ Required capability: ${description.slice(0, 300)}`;
10258
10581
  }
10259
10582
  async persist() {
10260
10583
  if (!this.workspacePath || !this.persistEnabled) return;
10261
- const dir = path23__default.default.join(this.workspacePath, ".cascade");
10262
- const file = path23__default.default.join(dir, DYNAMIC_TOOLS_FILE);
10584
+ const dir = path25__default.default.join(this.workspacePath, ".cascade");
10585
+ const file = path25__default.default.join(dir, DYNAMIC_TOOLS_FILE);
10263
10586
  try {
10264
10587
  await fs9__default.default.mkdir(dir, { recursive: true });
10265
10588
  await fs9__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
@@ -10272,6 +10595,166 @@ Required capability: ${description.slice(0, 300)}`;
10272
10595
  return Array.from(this.specs.keys());
10273
10596
  }
10274
10597
  };
10598
+ var WorldStateDB = class {
10599
+ constructor(workspacePath, debugMode = false) {
10600
+ this.workspacePath = workspacePath;
10601
+ this.debugMode = debugMode;
10602
+ const cascadeDir = path25__default.default.join(workspacePath, ".cascade");
10603
+ if (!fs23__default.default.existsSync(cascadeDir)) {
10604
+ fs23__default.default.mkdirSync(cascadeDir, { recursive: true });
10605
+ }
10606
+ this.keyPath = path25__default.default.join(cascadeDir, "world_state.key");
10607
+ this.dbPath = path25__default.default.join(cascadeDir, "world_state.db");
10608
+ this.initEncryptionKey();
10609
+ this.db = new Database2__default.default(this.dbPath);
10610
+ this.db.pragma("journal_mode = WAL");
10611
+ this.db.exec(`
10612
+ CREATE TABLE IF NOT EXISTS world_state (
10613
+ id TEXT PRIMARY KEY,
10614
+ timestamp TEXT NOT NULL,
10615
+ worker_id TEXT NOT NULL,
10616
+ encrypted_payload TEXT NOT NULL
10617
+ )
10618
+ `);
10619
+ }
10620
+ workspacePath;
10621
+ debugMode;
10622
+ db;
10623
+ keyPath;
10624
+ dbPath;
10625
+ encryptionKey;
10626
+ initEncryptionKey() {
10627
+ if (fs23__default.default.existsSync(this.keyPath)) {
10628
+ this.encryptionKey = fs23__default.default.readFileSync(this.keyPath);
10629
+ } else {
10630
+ this.encryptionKey = crypto__default.default.randomBytes(32);
10631
+ fs23__default.default.writeFileSync(this.keyPath, this.encryptionKey);
10632
+ }
10633
+ }
10634
+ encrypt(text) {
10635
+ const iv = crypto__default.default.randomBytes(16);
10636
+ const cipher = crypto__default.default.createCipheriv("aes-256-gcm", this.encryptionKey, iv);
10637
+ let encrypted = cipher.update(text, "utf8", "hex");
10638
+ encrypted += cipher.final("hex");
10639
+ const authTag = cipher.getAuthTag().toString("hex");
10640
+ return `${iv.toString("hex")}:${authTag}:${encrypted}`;
10641
+ }
10642
+ decrypt(text) {
10643
+ const parts = text.split(":");
10644
+ if (parts.length !== 3) throw new Error("Invalid encrypted payload format");
10645
+ const [ivHex, authTagHex, encryptedHex] = parts;
10646
+ const iv = Buffer.from(ivHex, "hex");
10647
+ const authTag = Buffer.from(authTagHex, "hex");
10648
+ const decipher = crypto__default.default.createDecipheriv("aes-256-gcm", this.encryptionKey, iv);
10649
+ decipher.setAuthTag(authTag);
10650
+ let decrypted = decipher.update(encryptedHex, "hex", "utf8");
10651
+ decrypted += decipher.final("utf8");
10652
+ return decrypted;
10653
+ }
10654
+ addEntry(workerId, summary) {
10655
+ const id = crypto__default.default.randomUUID();
10656
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
10657
+ const payload = JSON.stringify({ summary });
10658
+ const encryptedPayload = this.encrypt(payload);
10659
+ const stmt = this.db.prepare("INSERT INTO world_state (id, timestamp, worker_id, encrypted_payload) VALUES (?, ?, ?, ?)");
10660
+ stmt.run(id, timestamp, workerId, encryptedPayload);
10661
+ this.dumpDebugIfNeeded();
10662
+ return id;
10663
+ }
10664
+ getAllEntries() {
10665
+ const stmt = this.db.prepare("SELECT id, timestamp, worker_id, encrypted_payload FROM world_state ORDER BY timestamp ASC");
10666
+ const rows = stmt.all();
10667
+ return rows.map((row) => {
10668
+ try {
10669
+ const decrypted = this.decrypt(row.encrypted_payload);
10670
+ const parsed = JSON.parse(decrypted);
10671
+ return {
10672
+ id: row.id,
10673
+ timestamp: row.timestamp,
10674
+ workerId: row.worker_id,
10675
+ summary: parsed.summary
10676
+ };
10677
+ } catch (err) {
10678
+ return {
10679
+ id: row.id,
10680
+ timestamp: row.timestamp,
10681
+ workerId: row.worker_id,
10682
+ summary: "[Decryption Failed - Payload Corrupted]"
10683
+ };
10684
+ }
10685
+ });
10686
+ }
10687
+ getFormattedState() {
10688
+ const entries = this.getAllEntries();
10689
+ if (entries.length === 0) return "World State is currently empty.";
10690
+ return entries.map((e, idx) => `[${e.timestamp}] Step ${idx + 1} (${e.workerId}): ${e.summary}`).join("\n");
10691
+ }
10692
+ dumpDebugIfNeeded() {
10693
+ if (!this.debugMode) return;
10694
+ try {
10695
+ const dumpPath = path25__default.default.join(os8__default.default.tmpdir(), "cascade_world_state_debug.json");
10696
+ const entries = this.getAllEntries();
10697
+ fs23__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
10698
+ } catch (err) {
10699
+ console.error("Failed to dump debug world state", err);
10700
+ }
10701
+ }
10702
+ close() {
10703
+ this.db.close();
10704
+ }
10705
+ };
10706
+ var ignore3 = _ignoreModule__namespace.default ?? _ignoreModule__namespace;
10707
+ var PrivacyPaths = class {
10708
+ localOnly;
10709
+ hasRules;
10710
+ constructor(policies = []) {
10711
+ this.localOnly = ignore3();
10712
+ const patterns = policies.filter((p) => p.policy === "local-only").map((p) => p.pattern);
10713
+ if (patterns.length) this.localOnly.add(patterns);
10714
+ this.hasRules = patterns.length > 0;
10715
+ }
10716
+ /** True when any privacy rules are configured at all (cheap short-circuit). */
10717
+ hasPolicies() {
10718
+ return this.hasRules;
10719
+ }
10720
+ /** True when the given workspace-relative path falls under a local-only policy. */
10721
+ isLocalOnly(relativePath) {
10722
+ if (!this.hasRules || !relativePath) return false;
10723
+ try {
10724
+ return this.localOnly.ignores(relativePath.replace(/^\.?\//, ""));
10725
+ } catch {
10726
+ return false;
10727
+ }
10728
+ }
10729
+ /** True when ANY of the given paths falls under a local-only policy. */
10730
+ anyLocalOnly(relativePaths) {
10731
+ return relativePaths.some((p) => this.isLocalOnly(p));
10732
+ }
10733
+ };
10734
+
10735
+ // src/core/steering/guidance.ts
10736
+ var GuidanceQueue = class {
10737
+ entries = [];
10738
+ /** Per-consumer read cursor so a broadcast entry reaches each worker once. */
10739
+ cursors = /* @__PURE__ */ new Map();
10740
+ push(text, nodeId) {
10741
+ const entry = { text, nodeId, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
10742
+ this.entries.push(entry);
10743
+ return entry;
10744
+ }
10745
+ /**
10746
+ * New entries for this consumer since its last drain, filtered to entries
10747
+ * that target it (or target everyone). Advances the consumer's cursor.
10748
+ */
10749
+ drain(consumerId) {
10750
+ const from = this.cursors.get(consumerId) ?? 0;
10751
+ this.cursors.set(consumerId, this.entries.length);
10752
+ return this.entries.slice(from).filter((e) => !e.nodeId || consumerId === e.nodeId || consumerId.startsWith(e.nodeId));
10753
+ }
10754
+ get size() {
10755
+ return this.entries.length;
10756
+ }
10757
+ };
10275
10758
 
10276
10759
  // src/core/cascade.ts
10277
10760
  var Cascade = class _Cascade extends EventEmitter__default.default {
@@ -10291,6 +10774,9 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
10291
10774
  taskAnalyzer;
10292
10775
  perfTracker;
10293
10776
  toolCreator;
10777
+ worldStateDB;
10778
+ encryptedAuditLogger;
10779
+ guidanceQueue;
10294
10780
  workspacePath;
10295
10781
  constructor(config, workspacePath, store) {
10296
10782
  super();
@@ -10312,6 +10798,23 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
10312
10798
  });
10313
10799
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
10314
10800
  this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
10801
+ this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
10802
+ this.router.setWorldStateDB(this.worldStateDB);
10803
+ this.encryptedAuditLogger = new AuditLogger2(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
10804
+ const privacyPolicies = this.config.privacy?.paths ?? [];
10805
+ if (privacyPolicies.length) this.router.setPrivacyPaths(new PrivacyPaths(privacyPolicies));
10806
+ this.guidanceQueue = new GuidanceQueue();
10807
+ this.router.setGuidanceQueue(this.guidanceQueue);
10808
+ }
10809
+ /**
10810
+ * Live intervention: inject a user correction into the running hierarchy.
10811
+ * Every active T3 worker (or only the one matching `nodeId`) picks it up at
10812
+ * the top of its next agent-loop iteration as a USER INTERVENTION message.
10813
+ */
10814
+ injectGuidance(text, nodeId) {
10815
+ const entry = this.guidanceQueue.push(text, nodeId);
10816
+ this.encryptedAuditLogger?.logEvent("user_guidance", nodeId ?? "*", { text });
10817
+ this.emit("guidance:injected", entry);
10315
10818
  }
10316
10819
  initOptionalFeatures() {
10317
10820
  if (this.config.cascadeAuto === true) {
@@ -10474,6 +10977,12 @@ ${last.partialOutput}` : "");
10474
10977
  if (!prompt) return null;
10475
10978
  return this.run({ prompt });
10476
10979
  }
10980
+ getWorkspacePath() {
10981
+ return this.workspacePath;
10982
+ }
10983
+ getWorldStateDB() {
10984
+ return this.worldStateDB;
10985
+ }
10477
10986
  /**
10478
10987
  * Record an explicit user rating for the last completed run.
10479
10988
  * Explicit ratings carry 3× the weight of auto-detected outcomes so user
@@ -10560,6 +11069,11 @@ ${last.partialOutput}` : "");
10560
11069
  }
10561
11070
  this.initOptionalFeatures();
10562
11071
  if (this.toolCreator) await this.toolCreator.loadPersistedTools();
11072
+ if (this.encryptedAuditLogger) {
11073
+ this.on("tool:call", (e) => this.encryptedAuditLogger.logEvent("tool_call", e.tierId || "unknown", e));
11074
+ this.on("tool:result", (e) => this.encryptedAuditLogger.logEvent("tool_result", e.tierId || "unknown", e));
11075
+ this.on("tier:status", (e) => this.encryptedAuditLogger.logEvent("tier_status", e.tierId || "unknown", e));
11076
+ }
10563
11077
  this.initialized = true;
10564
11078
  })();
10565
11079
  try {
@@ -10985,6 +11499,7 @@ ${prompt}` : prompt;
10985
11499
  durationMs,
10986
11500
  costByTier: stats.costByTier,
10987
11501
  tokensByTier: stats.tokensByTier,
11502
+ costByFeature: stats.costByFeature,
10988
11503
  costPercentByTier: this.router.getTierCostPercentages()
10989
11504
  };
10990
11505
  }
@@ -11234,6 +11749,30 @@ var SlashCommandRegistry = class {
11234
11749
  return { output: out || "File changes rolled back.", handled: true };
11235
11750
  }
11236
11751
  });
11752
+ this.register({
11753
+ command: "/steer",
11754
+ description: "Steer a running task: /steer <correction for the active workers>",
11755
+ args: ["<guidance>"],
11756
+ handler: async (args, ctx) => {
11757
+ const output = await ctx.onSteer(args);
11758
+ return { output, handled: true };
11759
+ }
11760
+ });
11761
+ this.register({
11762
+ command: "/audit",
11763
+ description: "Verify the tamper-evident audit log (hash chain integrity)",
11764
+ handler: async (_args, ctx) => {
11765
+ const { AuditLogger: AuditLogger3 } = await Promise.resolve().then(() => (init_audit_logger(), audit_logger_exports));
11766
+ const logger = new AuditLogger3(ctx.workspacePath);
11767
+ try {
11768
+ const v = logger.verifyChain();
11769
+ const output = v.ok ? `\u2714 Audit log intact \u2014 ${v.entries} entr${v.entries === 1 ? "y" : "ies"}, hash chain verified.` : `\u2718 Audit log FAILED verification at row ${v.firstBadRow} of ${v.entries} \u2014 entries at or after that row were modified, removed, or predate the hash chain.`;
11770
+ return { output, handled: true };
11771
+ } finally {
11772
+ logger.close();
11773
+ }
11774
+ }
11775
+ });
11237
11776
  this.register({
11238
11777
  command: "/branch",
11239
11778
  description: "Fork current session into parallel branches",
@@ -11881,7 +12420,8 @@ function CostTracker({
11881
12420
  tokensByTier,
11882
12421
  compact = false,
11883
12422
  savedUsd = 0,
11884
- savedPct = 0
12423
+ savedPct = 0,
12424
+ costByFeature
11885
12425
  }) {
11886
12426
  const hasTierCost = costByTier && Object.keys(costByTier).length > 0;
11887
12427
  const savingsLine = savedUsd > 0 ? `Saved $${savedUsd.toFixed(4)} (${savedPct}%) vs. running every call on T1` : null;
@@ -11980,6 +12520,16 @@ function CostTracker({
11980
12520
  ] }, tier);
11981
12521
  })
11982
12522
  ] })
12523
+ ] }),
12524
+ costByFeature && Object.keys(costByFeature).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginTop: 1, children: [
12525
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: "By feature/section:" }),
12526
+ Object.entries(costByFeature).map(([feature, cost]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginLeft: 2, children: [
12527
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { width: 30, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.secondary, wrap: "truncate-end", children: feature }) }),
12528
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.success, children: [
12529
+ "$",
12530
+ cost.toFixed(6)
12531
+ ] })
12532
+ ] }, feature))
11983
12533
  ] })
11984
12534
  ] });
11985
12535
  }
@@ -12393,7 +12943,7 @@ function replReducer(state, action) {
12393
12943
  case "SET_TREE":
12394
12944
  return { ...state, agentTree: action.tree };
12395
12945
  case "UPDATE_COST":
12396
- return { ...state, totalTokens: action.tokens, totalCostUsd: action.costUsd, callsByProvider: action.byProvider, callsByTier: action.byTier, costByTier: action.costByTier, tokensByTier: action.tokensByTier, savedUsd: action.savedUsd, savedPct: action.savedPct };
12946
+ return { ...state, totalTokens: action.tokens, totalCostUsd: action.costUsd, callsByProvider: action.byProvider, callsByTier: action.byTier, costByTier: action.costByTier, tokensByTier: action.tokensByTier, costByFeature: action.costByFeature, savedUsd: action.savedUsd, savedPct: action.savedPct };
12397
12947
  case "SET_APPROVAL":
12398
12948
  return { ...state, approvalRequest: action.request };
12399
12949
  case "SET_EXECUTING":
@@ -12407,7 +12957,7 @@ function replReducer(state, action) {
12407
12957
  case "TOGGLE_COMMS":
12408
12958
  return { ...state, showComms: !state.showComms };
12409
12959
  case "CLEAR":
12410
- return { ...state, messages: [], agentTree: null, streamBuffer: "", totalTokens: 0, totalCostUsd: 0, callsByProvider: {}, callsByTier: {}, costByTier: {}, tokensByTier: {}, savedUsd: 0, savedPct: 0, peerEvents: [], activeTool: null };
12960
+ return { ...state, messages: [], agentTree: null, streamBuffer: "", totalTokens: 0, totalCostUsd: 0, callsByProvider: {}, callsByTier: {}, costByTier: {}, tokensByTier: {}, costByFeature: {}, savedUsd: 0, savedPct: 0, peerEvents: [], activeTool: null };
12411
12961
  case "CLEAR_TREE":
12412
12962
  return { ...state, agentTree: null };
12413
12963
  case "TOGGLE_COST":
@@ -12456,7 +13006,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
12456
13006
  const [slashIndex, setSlashIndex] = React2.useState(0);
12457
13007
  const [identities, setIdentities] = React2.useState([]);
12458
13008
  const [currentIdentityId, setCurrentIdentityId] = React2.useState(config.defaultIdentityId);
12459
- const [state, dispatch] = React2.useReducer(replReducer, { messages: [], agentTree: null, isStreaming: false, isExecuting: false, streamBuffer: "", totalTokens: 0, totalCostUsd: 0, callsByProvider: {}, callsByTier: {}, costByTier: {}, tokensByTier: {}, savedUsd: 0, savedPct: 0, peerEvents: [], showComms: true, approvalRequest: null, showCost: false, showDetails: false, error: null, activeTool: null });
13009
+ const [state, dispatch] = React2.useReducer(replReducer, { messages: [], agentTree: null, isStreaming: false, isExecuting: false, streamBuffer: "", totalTokens: 0, totalCostUsd: 0, callsByProvider: {}, callsByTier: {}, costByTier: {}, tokensByTier: {}, costByFeature: {}, savedUsd: 0, savedPct: 0, peerEvents: [], showComms: true, approvalRequest: null, showCost: false, showDetails: false, error: null, activeTool: null });
12460
13010
  const [isShowingModels, setIsShowingModels] = React2.useState(false);
12461
13011
  const [planApprovalRequest, setPlanApprovalRequest] = React2.useState(null);
12462
13012
  const [historyOffset, setHistoryOffset] = React2.useState(0);
@@ -12525,9 +13075,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
12525
13075
  }
12526
13076
  } catch {
12527
13077
  }
12528
- const configPath = path23__default.default.join(workspacePath, ".cascade", "config.json");
13078
+ const configPath = path25__default.default.join(workspacePath, ".cascade", "config.json");
12529
13079
  try {
12530
- await fs9__default.default.mkdir(path23__default.default.dirname(configPath), { recursive: true });
13080
+ await fs9__default.default.mkdir(path25__default.default.dirname(configPath), { recursive: true });
12531
13081
  await fs9__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
12532
13082
  } catch (err) {
12533
13083
  const msg = err instanceof Error ? err.message : String(err);
@@ -12579,9 +13129,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
12579
13129
  if (msg.includes("non-text parts")) return;
12580
13130
  originalLog(...args);
12581
13131
  };
12582
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
13132
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
12583
13133
  storeRef.current = store;
12584
- globalStoreRef.current = new MemoryStore(path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
13134
+ globalStoreRef.current = new MemoryStore(path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
12585
13135
  const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
12586
13136
  setIdentities(identityRows);
12587
13137
  let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
@@ -12644,7 +13194,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
12644
13194
  onThemeChange: (name) => setTheme(getTheme(name)),
12645
13195
  onExport: async (fmt) => {
12646
13196
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
12647
- const exportPath = path23__default.default.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
13197
+ const exportPath = path25__default.default.join(workspacePath, `cascade-export-${stamp}.${fmt === "json" ? "json" : "md"}`);
12648
13198
  if (fmt === "json") {
12649
13199
  await fs9__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
12650
13200
  } else {
@@ -12668,6 +13218,14 @@ ${msg.content}`).join("\n\n");
12668
13218
  }
12669
13219
  return `Restored ${snapshots.length} files to their initial session state.`;
12670
13220
  },
13221
+ onSteer: (args) => {
13222
+ const text = args.join(" ").trim();
13223
+ if (!text) return "Usage: /steer <correction for the active workers>";
13224
+ const cascade = cascadeRef.current;
13225
+ if (!cascade) return "No active Cascade instance.";
13226
+ cascade.injectGuidance(text);
13227
+ return "Guidance queued \u2014 active workers apply it on their next step. (No task running? It applies to the next run's workers.)";
13228
+ },
12671
13229
  onBranch: async () => {
12672
13230
  const store = storeRef.current;
12673
13231
  if (!store) return;
@@ -12986,7 +13544,7 @@ ${lastUser.content}`;
12986
13544
  if (lastTool !== null) dispatch({ type: "SET_ACTIVE_TOOL", toolName: lastTool });
12987
13545
  const stats = cascade.getRouter().getStats();
12988
13546
  const savings = cascade.getRouter().getDelegationSavings();
12989
- dispatch({ type: "UPDATE_COST", tokens: stats.totalTokens, costUsd: stats.totalCostUsd, byProvider: stats.callsByProvider, byTier: stats.callsByTier, costByTier: stats.costByTier, tokensByTier: stats.tokensByTier, savedUsd: savings.savedUsd, savedPct: savings.savedPct });
13547
+ dispatch({ type: "UPDATE_COST", tokens: stats.totalTokens, costUsd: stats.totalCostUsd, byProvider: stats.callsByProvider, byTier: stats.callsByTier, costByTier: stats.costByTier, tokensByTier: stats.tokensByTier, costByFeature: stats.costByFeature, savedUsd: savings.savedUsd, savedPct: savings.savedPct });
12990
13548
  statusThrottleTimeout = null;
12991
13549
  };
12992
13550
  cascade.on("tier:root", onRoot);
@@ -13078,7 +13636,7 @@ ${lastUser.content}`;
13078
13636
  flushPeerEvents();
13079
13637
  const stats = cascade.getRouter().getStats();
13080
13638
  const savings = cascade.getRouter().getDelegationSavings();
13081
- dispatch({ type: "UPDATE_COST", tokens: stats.totalTokens, costUsd: stats.totalCostUsd, byProvider: stats.callsByProvider, byTier: stats.callsByTier, costByTier: stats.costByTier, tokensByTier: stats.tokensByTier, savedUsd: savings.savedUsd, savedPct: savings.savedPct });
13639
+ dispatch({ type: "UPDATE_COST", tokens: stats.totalTokens, costUsd: stats.totalCostUsd, byProvider: stats.callsByProvider, byTier: stats.callsByTier, costByTier: stats.costByTier, tokensByTier: stats.tokensByTier, costByFeature: stats.costByFeature, savedUsd: savings.savedUsd, savedPct: savings.savedPct });
13082
13640
  dispatch({ type: "COMMIT_STREAM", finalText: result.output, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
13083
13641
  persistMessage("assistant", result.output, (/* @__PURE__ */ new Date()).toISOString());
13084
13642
  const receipt = formatRunReceipt(result, stats.totalCostUsd, savings);
@@ -13363,7 +13921,7 @@ ${lastUser.content}`;
13363
13921
  ),
13364
13922
  adaptiveMode === "wide" && state.showComms && liveBudget.commsMaxEvents > 0 && /* @__PURE__ */ jsxRuntime.jsx(PeerFeed, { events: state.peerEvents, theme, maxRows: liveBudget.commsMaxEvents }),
13365
13923
  adaptiveMode === "wide" && state.showDetails && liveBudget.showTimeline && /* @__PURE__ */ jsxRuntime.jsx(TimelinePanel, { nodes: [...treeNodesRef.current.values()], theme, currentIndex: timelineIndex, onChangeIndex: setTimelineIndex }),
13366
- adaptiveMode !== "narrow" && state.showCost && /* @__PURE__ */ jsxRuntime.jsx(CostTracker, { theme, totalTokens: state.totalTokens, totalCostUsd: state.totalCostUsd, callsByProvider: state.callsByProvider, callsByTier: state.callsByTier, costByTier: state.costByTier, tokensByTier: state.tokensByTier, compact: liveBudget.costCompact, savedUsd: state.savedUsd, savedPct: state.savedPct }),
13924
+ adaptiveMode !== "narrow" && state.showCost && /* @__PURE__ */ jsxRuntime.jsx(CostTracker, { theme, totalTokens: state.totalTokens, totalCostUsd: state.totalCostUsd, callsByProvider: state.callsByProvider, callsByTier: state.callsByTier, costByTier: state.costByTier, tokensByTier: state.tokensByTier, costByFeature: state.costByFeature, compact: liveBudget.costCompact, savedUsd: state.savedUsd, savedPct: state.savedPct }),
13367
13925
  liveBudget.collapsed && (state.showCost || state.showDetails || state.agentTree != null) && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u25B8 panels collapsed (small terminal)" }),
13368
13926
  state.approvalRequest && /* @__PURE__ */ jsxRuntime.jsx(ApprovalPrompt, { request: state.approvalRequest, theme, onDecision: (decision) => {
13369
13927
  dispatch({ type: "SET_APPROVAL", request: null });
@@ -13532,7 +14090,7 @@ function stringifySlashOutput(val) {
13532
14090
  }
13533
14091
  async function searchSessionsAndMessages(query, workspacePath) {
13534
14092
  if (!query) return "Usage: /search <query>";
13535
- const dbPath = path23__default.default.join(workspacePath, CASCADE_DB_FILE);
14093
+ const dbPath = path25__default.default.join(workspacePath, CASCADE_DB_FILE);
13536
14094
  try {
13537
14095
  await fs9__default.default.access(dbPath);
13538
14096
  } catch {
@@ -13566,7 +14124,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
13566
14124
  async function diagnoseRuntime(config, workspacePath) {
13567
14125
  const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
13568
14126
  const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
13569
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
14127
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
13570
14128
  try {
13571
14129
  const sessions = store.listSessions(void 0, 3);
13572
14130
  return [
@@ -13585,7 +14143,7 @@ async function diagnoseRuntime(config, workspacePath) {
13585
14143
  }
13586
14144
  async function showRecentLogs(args, workspacePath) {
13587
14145
  const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
13588
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
14146
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
13589
14147
  try {
13590
14148
  const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
13591
14149
  if (!logs.length) return "No recent runtime logs.";
@@ -13597,7 +14155,7 @@ async function showRecentLogs(args, workspacePath) {
13597
14155
  async function loadSessionSnapshot(args, workspacePath) {
13598
14156
  const sessionId = args[0];
13599
14157
  if (!sessionId) return "Usage: /resume <sessionId>";
13600
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
14158
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
13601
14159
  try {
13602
14160
  const session = store.getSession(sessionId);
13603
14161
  if (!session) return `Session not found: ${sessionId}`;
@@ -14017,9 +14575,9 @@ function SetupWizard({ workspacePath, onComplete }) {
14017
14575
  ...anyAuto ? { cascadeAuto: true } : {}
14018
14576
  };
14019
14577
  const config = CascadeConfigSchema.parse(rawConfig);
14020
- const configDir = path23__default.default.join(workspacePath, ".cascade");
14578
+ const configDir = path25__default.default.join(workspacePath, ".cascade");
14021
14579
  await fs9__default.default.mkdir(configDir, { recursive: true });
14022
- const configPath = path23__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
14580
+ const configPath = path25__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
14023
14581
  await fs9__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
14024
14582
  savedConfigRef.current = config;
14025
14583
  dispatchRef.current({ type: "GO_DONE" });
@@ -14341,14 +14899,14 @@ function printTelemetryBanner() {
14341
14899
  async function initCommand(workspacePath = process.cwd()) {
14342
14900
  const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
14343
14901
  try {
14344
- const configDir = path23__default.default.join(workspacePath, ".cascade");
14902
+ const configDir = path25__default.default.join(workspacePath, ".cascade");
14345
14903
  await fs9__default.default.mkdir(configDir, { recursive: true });
14346
- const mdPath = path23__default.default.join(workspacePath, "CASCADE.md");
14904
+ const mdPath = path25__default.default.join(workspacePath, "CASCADE.md");
14347
14905
  if (!await fileExists(mdPath)) {
14348
14906
  await createDefaultCascadeMd(workspacePath);
14349
14907
  spin.succeed(chalk9__default.default.green("Created CASCADE.md"));
14350
14908
  }
14351
- const ignorePath = path23__default.default.join(workspacePath, ".cascadeignore");
14909
+ const ignorePath = path25__default.default.join(workspacePath, ".cascadeignore");
14352
14910
  if (!await fileExists(ignorePath)) {
14353
14911
  await createDefaultIgnoreFile(workspacePath);
14354
14912
  spin.succeed(chalk9__default.default.green("Created .cascadeignore"));
@@ -14357,7 +14915,7 @@ async function initCommand(workspacePath = process.cwd()) {
14357
14915
  console.log();
14358
14916
  console.log(chalk9__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
14359
14917
  console.log();
14360
- const configPath = path23__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
14918
+ const configPath = path25__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
14361
14919
  if (await fileExists(configPath)) {
14362
14920
  console.log(chalk9__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
14363
14921
  console.log();
@@ -14415,7 +14973,7 @@ function fromEnv(env) {
14415
14973
  return out;
14416
14974
  }
14417
14975
  async function fromClaudeCode(home) {
14418
- const file = path23__default.default.join(home, ".claude", ".credentials.json");
14976
+ const file = path25__default.default.join(home, ".claude", ".credentials.json");
14419
14977
  const data = await readJson(file);
14420
14978
  if (!data) return [];
14421
14979
  const oauth = data["claudeAiOauth"];
@@ -14438,7 +14996,7 @@ async function fromClaudeCode(home) {
14438
14996
  return [];
14439
14997
  }
14440
14998
  async function fromCodex(home) {
14441
- const file = path23__default.default.join(home, ".codex", "auth.json");
14999
+ const file = path25__default.default.join(home, ".codex", "auth.json");
14442
15000
  const data = await readJson(file);
14443
15001
  if (!data) return [];
14444
15002
  const apiKey = str(data["OPENAI_API_KEY"]);
@@ -14461,7 +15019,7 @@ async function fromCodex(home) {
14461
15019
  return [];
14462
15020
  }
14463
15021
  async function fromGemini(home) {
14464
- const file = path23__default.default.join(home, ".gemini", "oauth_creds.json");
15022
+ const file = path25__default.default.join(home, ".gemini", "oauth_creds.json");
14465
15023
  const data = await readJson(file);
14466
15024
  const accessToken = str(data?.["access_token"]);
14467
15025
  if (!accessToken) return [];
@@ -14477,7 +15035,7 @@ async function fromGemini(home) {
14477
15035
  }
14478
15036
  async function fromCopilot(home) {
14479
15037
  for (const name of ["apps.json", "hosts.json"]) {
14480
- const file = path23__default.default.join(home, ".config", "github-copilot", name);
15038
+ const file = path25__default.default.join(home, ".config", "github-copilot", name);
14481
15039
  const data = await readJson(file);
14482
15040
  if (!data) continue;
14483
15041
  for (const value of Object.values(data)) {
@@ -14498,7 +15056,7 @@ async function fromCopilot(home) {
14498
15056
  return [];
14499
15057
  }
14500
15058
  async function discoverCredentials(opts = {}) {
14501
- const home = opts.homeDir ?? os6__default.default.homedir();
15059
+ const home = opts.homeDir ?? os8__default.default.homedir();
14502
15060
  const env = opts.env ?? process.env;
14503
15061
  const groups = await Promise.all([
14504
15062
  Promise.resolve(fromEnv(env)),
@@ -14531,7 +15089,7 @@ async function doctorCommand() {
14531
15089
  checks.push({
14532
15090
  label: "Cascade config",
14533
15091
  ok: true,
14534
- detail: `Loaded ${path23__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
15092
+ detail: `Loaded ${path25__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
14535
15093
  });
14536
15094
  const providers = [
14537
15095
  { type: "anthropic", name: "Anthropic" },
@@ -14810,6 +15368,24 @@ var DashboardSocket = class {
14810
15368
  });
14811
15369
  });
14812
15370
  }
15371
+ onSessionHalt(callback) {
15372
+ this.io.on("connection", (socket) => {
15373
+ socket.on("session:halt", (payload) => {
15374
+ if (typeof payload?.sessionId === "string") {
15375
+ callback(payload.sessionId);
15376
+ }
15377
+ });
15378
+ });
15379
+ }
15380
+ onSessionSteer(callback) {
15381
+ this.io.on("connection", (socket) => {
15382
+ socket.on("session:steer", (payload) => {
15383
+ if (typeof payload?.message === "string" && payload.message.trim()) {
15384
+ callback(payload.message.trim(), payload.sessionId, payload.nodeId);
15385
+ }
15386
+ });
15387
+ });
15388
+ }
14813
15389
  onConfigUpdate(callback) {
14814
15390
  this.io.on("connection", (socket) => {
14815
15391
  socket.on("config:update", (payload) => {
@@ -14844,7 +15420,7 @@ var DashboardSocket = class {
14844
15420
 
14845
15421
  // src/dashboard/server.ts
14846
15422
  init_constants();
14847
- var __dirname$1 = path23__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))));
15423
+ 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))));
14848
15424
  var DashboardServer = class {
14849
15425
  app;
14850
15426
  httpServer;
@@ -14855,6 +15431,13 @@ var DashboardServer = class {
14855
15431
  globalStore = null;
14856
15432
  broadcastTimer = null;
14857
15433
  activeSessions = /* @__PURE__ */ new Map();
15434
+ activeControllers = /* @__PURE__ */ new Map();
15435
+ /**
15436
+ * Run taskIds per chat session — file snapshots are keyed by the run's
15437
+ * taskId (see t3-worker saveSnapshot), so session rollback needs the list
15438
+ * of runs the session performed in this server's lifetime.
15439
+ */
15440
+ sessionTaskIds = /* @__PURE__ */ new Map();
14858
15441
  port;
14859
15442
  host;
14860
15443
  workspacePath;
@@ -14915,6 +15498,8 @@ var DashboardServer = class {
14915
15498
  });
14916
15499
  this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId) => {
14917
15500
  const sessionId = requestedSessionId ?? crypto.randomUUID();
15501
+ const abortController = new AbortController();
15502
+ this.activeControllers.set(sessionId, abortController);
14918
15503
  const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
14919
15504
  const title = this.persistRunStart(sessionId, prompt);
14920
15505
  const cfg = model !== "auto" ? { ...this.config, models: { ...this.config.models, t1: model } } : this.config;
@@ -14933,7 +15518,8 @@ var DashboardServer = class {
14933
15518
  this.socket.emitPeerMessage(e);
14934
15519
  });
14935
15520
  try {
14936
- const result = await cascade.run({ prompt: runPrompt });
15521
+ const result = await cascade.run({ prompt: runPrompt, signal: abortController.signal });
15522
+ this.recordSessionTask(sessionId, result.taskId);
14937
15523
  this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
14938
15524
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
14939
15525
  this.socket.broadcast("cost:update", {
@@ -14950,6 +15536,16 @@ var DashboardServer = class {
14950
15536
  });
14951
15537
  } finally {
14952
15538
  this.activeSessions.delete(sessionId);
15539
+ this.activeControllers.delete(sessionId);
15540
+ }
15541
+ });
15542
+ this.socket.onSessionHalt((sessionId) => {
15543
+ this.activeControllers.get(sessionId)?.abort();
15544
+ });
15545
+ this.socket.onSessionSteer((message, sessionId, nodeId) => {
15546
+ const steered = this.steerSessions(message, sessionId, nodeId);
15547
+ if (steered > 0) {
15548
+ this.socket.broadcast("session:message-injected", { sessionId, nodeId, message, steered, requestedAt: (/* @__PURE__ */ new Date()).toISOString() });
14953
15549
  }
14954
15550
  });
14955
15551
  }
@@ -14997,9 +15593,9 @@ var DashboardServer = class {
14997
15593
  */
14998
15594
  persistConfig() {
14999
15595
  try {
15000
- const configPath = path23__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
15001
- fs21__default.default.mkdirSync(path23__default.default.dirname(configPath), { recursive: true });
15002
- fs21__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
15596
+ const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
15597
+ fs23__default.default.mkdirSync(path25__default.default.dirname(configPath), { recursive: true });
15598
+ fs23__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
15003
15599
  } catch (err) {
15004
15600
  console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
15005
15601
  }
@@ -15014,15 +15610,15 @@ var DashboardServer = class {
15014
15610
  resolveDashboardSecret() {
15015
15611
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
15016
15612
  if (fromConfig) return fromConfig;
15017
- const secretPath = path23__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
15613
+ const secretPath = path25__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
15018
15614
  try {
15019
- if (fs21__default.default.existsSync(secretPath)) {
15020
- const existing = fs21__default.default.readFileSync(secretPath, "utf-8").trim();
15615
+ if (fs23__default.default.existsSync(secretPath)) {
15616
+ const existing = fs23__default.default.readFileSync(secretPath, "utf-8").trim();
15021
15617
  if (existing.length >= 16) return existing;
15022
15618
  }
15023
15619
  const generated = crypto.randomUUID();
15024
- fs21__default.default.mkdirSync(path23__default.default.dirname(secretPath), { recursive: true });
15025
- fs21__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
15620
+ fs23__default.default.mkdirSync(path25__default.default.dirname(secretPath), { recursive: true });
15621
+ fs23__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
15026
15622
  if (this.config.dashboard.auth) {
15027
15623
  console.warn(
15028
15624
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -15049,7 +15645,7 @@ var DashboardServer = class {
15049
15645
  // ── Setup ─────────────────────────────────────
15050
15646
  getGlobalStore() {
15051
15647
  if (!this.globalStore) {
15052
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15648
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15053
15649
  this.globalStore = new MemoryStore(globalDbPath);
15054
15650
  }
15055
15651
  return this.globalStore;
@@ -15097,6 +15693,21 @@ var DashboardServer = class {
15097
15693
  }
15098
15694
  this.persistRuntimeRow(sessionId, title, status, latestPrompt);
15099
15695
  }
15696
+ /**
15697
+ * Route steering text into running Cascade instances. Targets the given
15698
+ * session, or every active session when none is specified (the desktop
15699
+ * usually has exactly one run in flight). Returns how many were reached.
15700
+ */
15701
+ steerSessions(message, sessionId, nodeId) {
15702
+ const targets = sessionId ? [this.activeSessions.get(sessionId)].filter((c) => !!c) : [...this.activeSessions.values()];
15703
+ for (const cascade of targets) cascade.injectGuidance(message, nodeId);
15704
+ return targets.length;
15705
+ }
15706
+ recordSessionTask(sessionId, taskId) {
15707
+ const list = this.sessionTaskIds.get(sessionId) ?? [];
15708
+ list.push(taskId);
15709
+ this.sessionTaskIds.set(sessionId, list);
15710
+ }
15100
15711
  persistRuntimeRow(sessionId, title, status, latestPrompt) {
15101
15712
  const now = (/* @__PURE__ */ new Date()).toISOString();
15102
15713
  const row = { sessionId, title, workspacePath: this.workspacePath, status, startedAt: now, updatedAt: now, latestPrompt, isGlobal: false };
@@ -15190,12 +15801,12 @@ ${prompt}`;
15190
15801
  }
15191
15802
  }
15192
15803
  watchRuntimeChanges() {
15193
- const workspaceDbPath = path23__default.default.join(this.workspacePath, CASCADE_DB_FILE);
15194
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15804
+ const workspaceDbPath = path25__default.default.join(this.workspacePath, CASCADE_DB_FILE);
15805
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15195
15806
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
15196
15807
  for (const watchPath of watchPaths) {
15197
- if (!fs21__default.default.existsSync(watchPath)) continue;
15198
- fs21__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
15808
+ if (!fs23__default.default.existsSync(watchPath)) continue;
15809
+ fs23__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
15199
15810
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
15200
15811
  });
15201
15812
  }
@@ -15303,7 +15914,8 @@ ${prompt}`;
15303
15914
  res.status(400).json({ error: "message is required and must be a string" });
15304
15915
  return;
15305
15916
  }
15306
- const payload = { sessionId, nodeId, message, requestedAt: (/* @__PURE__ */ new Date()).toISOString() };
15917
+ const steered = this.steerSessions(message, sessionId, nodeId);
15918
+ const payload = { sessionId, nodeId, message, steered, requestedAt: (/* @__PURE__ */ new Date()).toISOString() };
15307
15919
  this.socket.broadcast("session:message-injected", payload);
15308
15920
  if (sessionId) this.socket.broadcastToRoom(`session:${sessionId}`, "session:message-injected", payload);
15309
15921
  res.json({ success: true, ...payload });
@@ -15325,7 +15937,7 @@ ${prompt}`;
15325
15937
  const sessionId = req.params.id;
15326
15938
  this.store.deleteSession(sessionId);
15327
15939
  this.store.deleteRuntimeSession(sessionId);
15328
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15940
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15329
15941
  const globalStore = new MemoryStore(globalDbPath);
15330
15942
  try {
15331
15943
  globalStore.deleteRuntimeSession(sessionId);
@@ -15337,9 +15949,34 @@ ${prompt}`;
15337
15949
  this.socket.broadcast("runtime:refresh", { scope: "global" });
15338
15950
  res.json({ ok: true });
15339
15951
  });
15952
+ this.app.post("/api/sessions/:id/rollback", auth, mutationLimiter, async (req, res) => {
15953
+ const sessionId = req.params.id;
15954
+ const taskIds = this.sessionTaskIds.get(sessionId) ?? [];
15955
+ if (!taskIds.length) {
15956
+ res.json({ ok: true, restored: 0, message: "No file snapshots recorded for this session in the current app run." });
15957
+ return;
15958
+ }
15959
+ const toRestore = /* @__PURE__ */ new Map();
15960
+ for (const taskId of taskIds) {
15961
+ for (const { filePath, content } of this.store.getLatestFileSnapshots(taskId)) {
15962
+ if (!toRestore.has(filePath)) toRestore.set(filePath, content);
15963
+ }
15964
+ }
15965
+ const { writeFile } = await import('fs/promises');
15966
+ let restored = 0;
15967
+ for (const [filePath, content] of toRestore) {
15968
+ try {
15969
+ await writeFile(filePath, content, "utf-8");
15970
+ restored++;
15971
+ } catch (err) {
15972
+ console.warn(`[dashboard] rollback restore failed: ${filePath}`, err);
15973
+ }
15974
+ }
15975
+ res.json({ ok: true, restored });
15976
+ });
15340
15977
  this.app.delete("/api/sessions", auth, (req, res) => {
15341
15978
  const body = req.body;
15342
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15979
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15343
15980
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
15344
15981
  const globalStore = new MemoryStore(globalDbPath);
15345
15982
  try {
@@ -15362,7 +15999,7 @@ ${prompt}`;
15362
15999
  });
15363
16000
  this.app.delete("/api/runtime", auth, (_req, res) => {
15364
16001
  this.store.deleteAllRuntimeNodes();
15365
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16002
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15366
16003
  const globalStore = new MemoryStore(globalDbPath);
15367
16004
  try {
15368
16005
  globalStore.deleteAllRuntimeNodes();
@@ -15413,6 +16050,19 @@ ${prompt}`;
15413
16050
  this.store.deleteIdentity(req.params.id);
15414
16051
  res.json({ ok: true });
15415
16052
  });
16053
+ this.app.get("/api/audit/verify", auth, async (_req, res) => {
16054
+ try {
16055
+ const { AuditLogger: AuditLogger3 } = await Promise.resolve().then(() => (init_audit_logger(), audit_logger_exports));
16056
+ const logger = new AuditLogger3(this.workspacePath);
16057
+ try {
16058
+ res.json(logger.verifyChain());
16059
+ } finally {
16060
+ logger.close();
16061
+ }
16062
+ } catch (err) {
16063
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
16064
+ }
16065
+ });
15416
16066
  this.app.get("/api/audit/:sessionId", auth, (req, res) => {
15417
16067
  const log = this.store.getAuditLog(req.params.sessionId);
15418
16068
  res.json(log);
@@ -15435,12 +16085,12 @@ ${prompt}`;
15435
16085
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
15436
16086
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
15437
16087
  try {
15438
- const configPath = path23__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
15439
- const existing = fs21__default.default.existsSync(configPath) ? JSON.parse(fs21__default.default.readFileSync(configPath, "utf-8")) : {};
16088
+ const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
16089
+ const existing = fs23__default.default.existsSync(configPath) ? JSON.parse(fs23__default.default.readFileSync(configPath, "utf-8")) : {};
15440
16090
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
15441
16091
  const tmp = configPath + ".tmp";
15442
- fs21__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
15443
- fs21__default.default.renameSync(tmp, configPath);
16092
+ fs23__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
16093
+ fs23__default.default.renameSync(tmp, configPath);
15444
16094
  res.json({ ok: true });
15445
16095
  } catch (err) {
15446
16096
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -15468,7 +16118,7 @@ ${prompt}`;
15468
16118
  this.app.get("/api/runtime", auth, (req, res) => {
15469
16119
  const scope = req.query["scope"] ?? "workspace";
15470
16120
  if (scope === "global") {
15471
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
16121
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
15472
16122
  const globalStore = new MemoryStore(globalDbPath);
15473
16123
  try {
15474
16124
  res.json({
@@ -15518,6 +16168,7 @@ ${prompt}`;
15518
16168
  });
15519
16169
  try {
15520
16170
  const result = await cascade.run({ prompt: runPrompt, identityId: body.identityId });
16171
+ this.recordSessionTask(sessionId, result.taskId);
15521
16172
  this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
15522
16173
  this.socket.broadcast("cost:update", {
15523
16174
  sessionId,
@@ -15548,13 +16199,13 @@ ${prompt}`;
15548
16199
  }))
15549
16200
  });
15550
16201
  });
15551
- const prodPath = path23__default.default.resolve(__dirname$1, "../web/dist");
15552
- const devPath = path23__default.default.resolve(__dirname$1, "../../web/dist");
15553
- const webDistPath = fs21__default.default.existsSync(prodPath) ? prodPath : devPath;
15554
- if (fs21__default.default.existsSync(webDistPath)) {
16202
+ const prodPath = path25__default.default.resolve(__dirname$1, "../web/dist");
16203
+ const devPath = path25__default.default.resolve(__dirname$1, "../../web/dist");
16204
+ const webDistPath = fs23__default.default.existsSync(prodPath) ? prodPath : devPath;
16205
+ if (fs23__default.default.existsSync(webDistPath)) {
15555
16206
  this.app.use(express__default.default.static(webDistPath));
15556
16207
  this.app.get("*", (_req, res) => {
15557
- res.sendFile(path23__default.default.join(webDistPath, "index.html"));
16208
+ res.sendFile(path25__default.default.join(webDistPath, "index.html"));
15558
16209
  });
15559
16210
  } else {
15560
16211
  this.app.get("/", (_req, res) => {
@@ -15575,7 +16226,7 @@ init_constants();
15575
16226
  async function dashboardCommand(config, workspacePath = process.cwd()) {
15576
16227
  const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
15577
16228
  const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
15578
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
16229
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
15579
16230
  const server = new DashboardServer(config, store, workspacePath);
15580
16231
  server.watchRuntimeChanges();
15581
16232
  const gThis = globalThis;
@@ -15607,7 +16258,7 @@ init_constants();
15607
16258
  function makeIdentityCommand(workspacePath = process.cwd()) {
15608
16259
  const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
15609
16260
  identity.command("list").description("List all available identities").action(() => {
15610
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
16261
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
15611
16262
  try {
15612
16263
  const identities = store.listIdentities();
15613
16264
  console.log(chalk9__default.default.bold("\n Identities:"));
@@ -15627,7 +16278,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
15627
16278
  }
15628
16279
  });
15629
16280
  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) => {
15630
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
16281
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
15631
16282
  try {
15632
16283
  if (options.default) {
15633
16284
  const existingDefault = store.getDefaultIdentity();
@@ -15653,7 +16304,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
15653
16304
  }
15654
16305
  });
15655
16306
  identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
15656
- const store = new MemoryStore(path23__default.default.join(workspacePath, CASCADE_DB_FILE));
16307
+ const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
15657
16308
  try {
15658
16309
  const identities = store.listIdentities();
15659
16310
  const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
@@ -15781,8 +16432,8 @@ async function exportCommand(options = {}) {
15781
16432
  let store;
15782
16433
  try {
15783
16434
  const workspacePath = options.workspacePath ?? process.cwd();
15784
- const workspaceDbPath = path23__default.default.join(workspacePath, CASCADE_DB_FILE);
15785
- const globalDbPath = path23__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
16435
+ const workspaceDbPath = path25__default.default.join(workspacePath, CASCADE_DB_FILE);
16436
+ const globalDbPath = path25__default.default.join(os8__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
15786
16437
  let dbPath = globalDbPath;
15787
16438
  try {
15788
16439
  await fs9__default.default.access(workspaceDbPath);
@@ -15824,7 +16475,7 @@ async function exportCommand(options = {}) {
15824
16475
  const ext = format === "json" ? ".json" : ".md";
15825
16476
  const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
15826
16477
  const defaultFile = `cascade-export-${safeName}${ext}`;
15827
- const outPath = options.output ? path23__default.default.resolve(options.output) : path23__default.default.join(process.cwd(), defaultFile);
16478
+ const outPath = options.output ? path25__default.default.resolve(options.output) : path25__default.default.join(process.cwd(), defaultFile);
15828
16479
  await fs9__default.default.writeFile(outPath, content, "utf-8");
15829
16480
  spin.succeed(chalk9__default.default.green(`Exported to ${chalk9__default.default.white(outPath)}`));
15830
16481
  const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
@@ -16052,11 +16703,11 @@ async function statsCommand() {
16052
16703
  dotenv__default.default.config();
16053
16704
  function warnIfBuildIsStale() {
16054
16705
  try {
16055
- const here = path23__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))));
16706
+ 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))));
16056
16707
  for (const rel of ["..", "../.."]) {
16057
- const pkgPath = path23__default.default.join(here, rel, "package.json");
16058
- if (!fs21__default.default.existsSync(pkgPath)) continue;
16059
- const pkg = JSON.parse(fs21__default.default.readFileSync(pkgPath, "utf-8"));
16708
+ const pkgPath = path25__default.default.join(here, rel, "package.json");
16709
+ if (!fs23__default.default.existsSync(pkgPath)) continue;
16710
+ const pkg = JSON.parse(fs23__default.default.readFileSync(pkgPath, "utf-8"));
16060
16711
  if (pkg.name !== "cascade-ai") continue;
16061
16712
  if (pkg.version && pkg.version !== CASCADE_VERSION) {
16062
16713
  console.error(