cascade-ai 0.12.23 → 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/README.md +13 -2
- package/dist/cli.cjs +828 -177
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +823 -172
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +1504 -893
- package/dist/index.cjs +779 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +756 -140
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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.
|
|
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 &&
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
1736
|
-
|
|
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
|
-
|
|
1747
|
-
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2042
|
+
fs23__default.default.mkdirSync(path25__default.default.dirname(dbPath), { recursive: true });
|
|
1900
2043
|
try {
|
|
1901
|
-
this.db = new
|
|
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 =
|
|
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(
|
|
2948
|
-
this.store = new MemoryStore(
|
|
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 =
|
|
2972
|
-
await fs9__default.default.mkdir(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
5695
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5920
|
-
|
|
5921
|
-
const
|
|
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: `
|
|
6155
|
+
content: `You are an independent critic reviewing another worker's output against its assignment.
|
|
5925
6156
|
|
|
5926
|
-
Goal
|
|
6157
|
+
Goal: ${assignment.expectedOutput}
|
|
5927
6158
|
Subtask: ${assignment.description}
|
|
5928
|
-
|
|
5929
|
-
Output:
|
|
6159
|
+
Current Output:
|
|
5930
6160
|
${current}
|
|
5931
6161
|
|
|
5932
|
-
|
|
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:
|
|
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
|
|
5938
|
-
|
|
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:
|
|
5951
|
-
|
|
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
|
-
|
|
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 =
|
|
7847
|
-
const abs =
|
|
7848
|
-
const rel =
|
|
7849
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
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 =
|
|
7854
|
-
const realRel =
|
|
7855
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
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(
|
|
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 =
|
|
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 =
|
|
8449
|
-
if (!
|
|
8450
|
-
|
|
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 =
|
|
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 =
|
|
8534
|
-
if (!
|
|
8535
|
-
|
|
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 =
|
|
8540
|
-
|
|
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 (
|
|
8548
|
-
|
|
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"] ?
|
|
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(
|
|
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"] ?
|
|
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 = [
|
|
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 =
|
|
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 =
|
|
9311
|
-
const rel =
|
|
9312
|
-
if (!rel || rel.startsWith("..") ||
|
|
9313
|
-
const posixRel = rel.split(
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
9836
|
-
const
|
|
9837
|
-
|
|
9838
|
-
|
|
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 =
|
|
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 =
|
|
10262
|
-
const 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 =
|
|
13078
|
+
const configPath = path25__default.default.join(workspacePath, ".cascade", "config.json");
|
|
12529
13079
|
try {
|
|
12530
|
-
await fs9__default.default.mkdir(
|
|
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(
|
|
13132
|
+
const store = new MemoryStore(path25__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
12583
13133
|
storeRef.current = store;
|
|
12584
|
-
globalStoreRef.current = new MemoryStore(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
14578
|
+
const configDir = path25__default.default.join(workspacePath, ".cascade");
|
|
14021
14579
|
await fs9__default.default.mkdir(configDir, { recursive: true });
|
|
14022
|
-
const configPath =
|
|
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 =
|
|
14902
|
+
const configDir = path25__default.default.join(workspacePath, ".cascade");
|
|
14345
14903
|
await fs9__default.default.mkdir(configDir, { recursive: true });
|
|
14346
|
-
const mdPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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 ${
|
|
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 =
|
|
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 =
|
|
15001
|
-
|
|
15002
|
-
|
|
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 =
|
|
15613
|
+
const secretPath = path25__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
15018
15614
|
try {
|
|
15019
|
-
if (
|
|
15020
|
-
const existing =
|
|
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
|
-
|
|
15025
|
-
|
|
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 =
|
|
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 =
|
|
15194
|
-
const globalDbPath =
|
|
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 (!
|
|
15198
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
15439
|
-
const existing =
|
|
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
|
-
|
|
15443
|
-
|
|
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 =
|
|
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 =
|
|
15552
|
-
const devPath =
|
|
15553
|
-
const webDistPath =
|
|
15554
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
15785
|
-
const globalDbPath =
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
16058
|
-
if (!
|
|
16059
|
-
const pkg = JSON.parse(
|
|
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(
|