cascade-ai 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +499 -232
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +496 -229
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +1066 -802
- package/dist/index.cjs +422 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +420 -157
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var Database = require('better-sqlite3');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var path21 = require('path');
|
|
5
|
+
var fs20 = require('fs');
|
|
6
6
|
var os6 = require('os');
|
|
7
7
|
var crypto3 = require('crypto');
|
|
8
8
|
var EventEmitter = require('events');
|
|
@@ -58,8 +58,8 @@ function _interopNamespace(e) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
61
|
-
var
|
|
62
|
-
var
|
|
61
|
+
var path21__default = /*#__PURE__*/_interopDefault(path21);
|
|
62
|
+
var fs20__default = /*#__PURE__*/_interopDefault(fs20);
|
|
63
63
|
var os6__default = /*#__PURE__*/_interopDefault(os6);
|
|
64
64
|
var crypto3__default = /*#__PURE__*/_interopDefault(crypto3);
|
|
65
65
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
@@ -104,12 +104,12 @@ var init_audit_logger = __esm({
|
|
|
104
104
|
constructor(workspacePath, debugMode = false) {
|
|
105
105
|
this.workspacePath = workspacePath;
|
|
106
106
|
this.debugMode = debugMode;
|
|
107
|
-
const cascadeDir =
|
|
108
|
-
if (!
|
|
109
|
-
|
|
107
|
+
const cascadeDir = path21__default.default.join(workspacePath, ".cascade");
|
|
108
|
+
if (!fs20__default.default.existsSync(cascadeDir)) {
|
|
109
|
+
fs20__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
110
110
|
}
|
|
111
|
-
this.keyPath =
|
|
112
|
-
this.dbPath =
|
|
111
|
+
this.keyPath = path21__default.default.join(cascadeDir, "audit_log.key");
|
|
112
|
+
this.dbPath = path21__default.default.join(cascadeDir, "audit_log.db");
|
|
113
113
|
this.initEncryptionKey();
|
|
114
114
|
this.db = new Database__default.default(this.dbPath);
|
|
115
115
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -139,11 +139,11 @@ var init_audit_logger = __esm({
|
|
|
139
139
|
return crypto3__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
140
140
|
}
|
|
141
141
|
initEncryptionKey() {
|
|
142
|
-
if (
|
|
143
|
-
this.encryptionKey =
|
|
142
|
+
if (fs20__default.default.existsSync(this.keyPath)) {
|
|
143
|
+
this.encryptionKey = fs20__default.default.readFileSync(this.keyPath);
|
|
144
144
|
} else {
|
|
145
145
|
this.encryptionKey = crypto3__default.default.randomBytes(32);
|
|
146
|
-
|
|
146
|
+
fs20__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
encrypt(text) {
|
|
@@ -221,9 +221,9 @@ var init_audit_logger = __esm({
|
|
|
221
221
|
dumpDebugIfNeeded() {
|
|
222
222
|
if (!this.debugMode) return;
|
|
223
223
|
try {
|
|
224
|
-
const dumpPath =
|
|
224
|
+
const dumpPath = path21__default.default.join(os6__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
225
225
|
const entries = this.getAllLogs();
|
|
226
|
-
|
|
226
|
+
fs20__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
227
227
|
} catch (err) {
|
|
228
228
|
console.error("Failed to dump debug audit logs", err);
|
|
229
229
|
}
|
|
@@ -236,7 +236,7 @@ var init_audit_logger = __esm({
|
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
// src/constants.ts
|
|
239
|
-
var CASCADE_VERSION = "0.
|
|
239
|
+
var CASCADE_VERSION = "0.19.0";
|
|
240
240
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
241
241
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
242
242
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -248,6 +248,7 @@ var CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
|
248
248
|
var GLOBAL_CONFIG_DIR = ".cascade-ai";
|
|
249
249
|
var GLOBAL_DB_FILE = "memory.db";
|
|
250
250
|
var GLOBAL_KEYSTORE_FILE = "keystore.enc";
|
|
251
|
+
var GLOBAL_CREDENTIALS_FILE = "credentials.json";
|
|
251
252
|
var GLOBAL_RUNTIME_DB_FILE = "runtime.db";
|
|
252
253
|
var DEFAULT_DASHBOARD_PORT = 4891;
|
|
253
254
|
var DEFAULT_API_PORT = 4892;
|
|
@@ -1038,6 +1039,23 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
1038
1039
|
};
|
|
1039
1040
|
|
|
1040
1041
|
// src/providers/azure.ts
|
|
1042
|
+
function azureModelForDeployment(cfg) {
|
|
1043
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
1044
|
+
const id = cfg.deploymentName.trim();
|
|
1045
|
+
return {
|
|
1046
|
+
id,
|
|
1047
|
+
name: cfg.label?.trim() || id,
|
|
1048
|
+
provider: "azure",
|
|
1049
|
+
contextWindow: 128e3,
|
|
1050
|
+
isVisionCapable: false,
|
|
1051
|
+
inputCostPer1kTokens: 25e-4,
|
|
1052
|
+
outputCostPer1kTokens: 0.01,
|
|
1053
|
+
maxOutputTokens: 16e3,
|
|
1054
|
+
supportsStreaming: true,
|
|
1055
|
+
isLocal: false,
|
|
1056
|
+
supportsToolUse: true
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1041
1059
|
var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
1042
1060
|
constructor(config, model) {
|
|
1043
1061
|
const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -1058,7 +1076,8 @@ var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
|
1058
1076
|
});
|
|
1059
1077
|
}
|
|
1060
1078
|
async listModels() {
|
|
1061
|
-
|
|
1079
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
1080
|
+
return [fromDeployment ?? this.model];
|
|
1062
1081
|
}
|
|
1063
1082
|
async isAvailable() {
|
|
1064
1083
|
try {
|
|
@@ -2210,7 +2229,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
2210
2229
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
2211
2230
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
2212
2231
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
2213
|
-
var DEFAULT_CACHE_FILE =
|
|
2232
|
+
var DEFAULT_CACHE_FILE = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
2214
2233
|
function normalizeModelId(id) {
|
|
2215
2234
|
let s = id.toLowerCase();
|
|
2216
2235
|
const slash = s.lastIndexOf("/");
|
|
@@ -2355,7 +2374,7 @@ var LiveDataProvider = class {
|
|
|
2355
2374
|
}
|
|
2356
2375
|
async saveCache() {
|
|
2357
2376
|
try {
|
|
2358
|
-
await fs4__default.default.mkdir(
|
|
2377
|
+
await fs4__default.default.mkdir(path21__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
2359
2378
|
const cache = {
|
|
2360
2379
|
fetchedAt: this.fetchedAt,
|
|
2361
2380
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -2567,6 +2586,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
2567
2586
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
2568
2587
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
2569
2588
|
}
|
|
2589
|
+
if (availableProviders.has("azure")) {
|
|
2590
|
+
for (const cfg of config.providers) {
|
|
2591
|
+
const model = azureModelForDeployment(cfg);
|
|
2592
|
+
if (model) this.selector.addDynamicModel(model);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2570
2595
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
2571
2596
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
2572
2597
|
if (!override || override === "auto") continue;
|
|
@@ -3173,7 +3198,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
3173
3198
|
ensureProvider(model, configs) {
|
|
3174
3199
|
const key = `${model.provider}:${model.id}`;
|
|
3175
3200
|
if (this.providers.has(key)) return;
|
|
3176
|
-
const cfg = configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
3201
|
+
const cfg = (model.provider === "azure" ? configs.find((c) => c.type === "azure" && c.deploymentName === model.id) : void 0) ?? configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
3177
3202
|
const provider = this.createProvider(cfg, model);
|
|
3178
3203
|
this.providers.set(key, provider);
|
|
3179
3204
|
}
|
|
@@ -3333,6 +3358,12 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3333
3358
|
* which would interleave, are not tagged.
|
|
3334
3359
|
*/
|
|
3335
3360
|
isPresenter = false;
|
|
3361
|
+
/**
|
|
3362
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
3363
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
3364
|
+
* which node (Cockpit node panel / Why panel).
|
|
3365
|
+
*/
|
|
3366
|
+
servingModel;
|
|
3336
3367
|
constructor(role, id, parentId) {
|
|
3337
3368
|
super();
|
|
3338
3369
|
this.role = role;
|
|
@@ -3357,11 +3388,16 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3357
3388
|
label: this.label,
|
|
3358
3389
|
status,
|
|
3359
3390
|
timestamp,
|
|
3360
|
-
output
|
|
3391
|
+
output,
|
|
3392
|
+
model: this.servingModel
|
|
3361
3393
|
};
|
|
3362
3394
|
this.emit("status", event);
|
|
3363
3395
|
this.emit("tier:status", event);
|
|
3364
3396
|
}
|
|
3397
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
3398
|
+
setServingModel(model) {
|
|
3399
|
+
this.servingModel = model || void 0;
|
|
3400
|
+
}
|
|
3365
3401
|
setLabel(label) {
|
|
3366
3402
|
this.label = label;
|
|
3367
3403
|
}
|
|
@@ -3384,7 +3420,8 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
3384
3420
|
currentAction: update.currentAction,
|
|
3385
3421
|
progressPct: update.progressPct,
|
|
3386
3422
|
timestamp,
|
|
3387
|
-
output: update.output
|
|
3423
|
+
output: update.output,
|
|
3424
|
+
model: this.servingModel
|
|
3388
3425
|
});
|
|
3389
3426
|
}
|
|
3390
3427
|
buildMessage(type, to, payload) {
|
|
@@ -3750,6 +3787,21 @@ Available tools: ${tools.map((t) => t.name).join(", ")}.
|
|
|
3750
3787
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
3751
3788
|
}
|
|
3752
3789
|
|
|
3790
|
+
// src/utils/truncate.ts
|
|
3791
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
3792
|
+
if (text.length <= maxChars) return text;
|
|
3793
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
3794
|
+
const tailLen = maxChars - headLen;
|
|
3795
|
+
const head = text.slice(0, headLen);
|
|
3796
|
+
const tail = text.slice(-tailLen);
|
|
3797
|
+
const elided = text.length - headLen - tailLen;
|
|
3798
|
+
return `${head}
|
|
3799
|
+
|
|
3800
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
3801
|
+
|
|
3802
|
+
${tail}`;
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3753
3805
|
// src/core/tiers/t3-worker.ts
|
|
3754
3806
|
var CriticalToolError = class extends Error {
|
|
3755
3807
|
constructor(message, toolName) {
|
|
@@ -4057,6 +4109,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
4057
4109
|
} catch {
|
|
4058
4110
|
}
|
|
4059
4111
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
4112
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
4060
4113
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
4061
4114
|
let sentFullTextContract = false;
|
|
4062
4115
|
let textContractSignature = "";
|
|
@@ -4154,7 +4207,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
4154
4207
|
const toolResult = await this.executeTool(tc);
|
|
4155
4208
|
await this.context.addMessage({
|
|
4156
4209
|
role: "tool",
|
|
4157
|
-
content: toolResult,
|
|
4210
|
+
content: truncateForContext(toolResult),
|
|
4158
4211
|
toolCallId: tc.id
|
|
4159
4212
|
});
|
|
4160
4213
|
}
|
|
@@ -4258,8 +4311,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
4258
4311
|
tierId: this.id,
|
|
4259
4312
|
sessionId: this.taskId,
|
|
4260
4313
|
requireApproval: false,
|
|
4261
|
-
saveSnapshot: async (
|
|
4262
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
4314
|
+
saveSnapshot: async (path22, content) => {
|
|
4315
|
+
this.store?.addFileSnapshot(this.taskId, path22, content);
|
|
4263
4316
|
},
|
|
4264
4317
|
sendPeerSync: (to, syncType, content) => {
|
|
4265
4318
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -4411,15 +4464,17 @@ ${assignment.expectedOutput}`;
|
|
|
4411
4464
|
};
|
|
4412
4465
|
}
|
|
4413
4466
|
requiresArtifact() {
|
|
4467
|
+
if (this.assignment?.files?.length) return true;
|
|
4414
4468
|
const haystack = `${this.assignment?.description ?? ""}
|
|
4415
4469
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
4416
4470
|
return /\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/i.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
4417
4471
|
}
|
|
4418
4472
|
extractArtifactPaths(assignment) {
|
|
4473
|
+
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
4419
4474
|
const haystack = `${assignment.description}
|
|
4420
4475
|
${assignment.expectedOutput}`;
|
|
4421
4476
|
const matches = haystack.match(/\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/gi) ?? [];
|
|
4422
|
-
return [
|
|
4477
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
4423
4478
|
}
|
|
4424
4479
|
async verifyArtifacts(assignment) {
|
|
4425
4480
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -4429,7 +4484,7 @@ ${assignment.expectedOutput}`;
|
|
|
4429
4484
|
const { promisify: promisify4 } = await import('util');
|
|
4430
4485
|
const execAsync2 = promisify4(exec2);
|
|
4431
4486
|
for (const artifactPath of artifactPaths) {
|
|
4432
|
-
const absolutePath =
|
|
4487
|
+
const absolutePath = path21__default.default.resolve(process.cwd(), artifactPath);
|
|
4433
4488
|
try {
|
|
4434
4489
|
const stat = await fs4__default.default.stat(absolutePath);
|
|
4435
4490
|
if (!stat.isFile()) {
|
|
@@ -4450,7 +4505,7 @@ ${assignment.expectedOutput}`;
|
|
|
4450
4505
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
4451
4506
|
continue;
|
|
4452
4507
|
}
|
|
4453
|
-
const ext =
|
|
4508
|
+
const ext = path21__default.default.extname(absolutePath).toLowerCase();
|
|
4454
4509
|
try {
|
|
4455
4510
|
if (ext === ".ts" || ext === ".tsx") {
|
|
4456
4511
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -4542,7 +4597,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4542
4597
|
Assignment: ${assignment.description}
|
|
4543
4598
|
Expected output: ${assignment.expectedOutput}
|
|
4544
4599
|
Constraints: ${assignment.constraints.join("; ")}
|
|
4545
|
-
|
|
4600
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
4601
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
4602
|
+
` : ""}
|
|
4546
4603
|
Output to test:
|
|
4547
4604
|
${output}
|
|
4548
4605
|
|
|
@@ -4631,17 +4688,27 @@ Your subtask:
|
|
|
4631
4688
|
- Title: ${assignment.subtaskTitle}
|
|
4632
4689
|
- Description: ${assignment.description}
|
|
4633
4690
|
- Expected output: ${assignment.expectedOutput}
|
|
4634
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
4691
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
4692
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
4693
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
4635
4694
|
}
|
|
4636
4695
|
buildInitialPrompt(assignment) {
|
|
4637
4696
|
return `Execute the following subtask completely:
|
|
4638
4697
|
|
|
4639
4698
|
**${assignment.subtaskTitle}**
|
|
4640
|
-
|
|
4699
|
+
${assignment.contextBrief ? `
|
|
4700
|
+
Context: ${assignment.contextBrief}
|
|
4701
|
+
` : ""}
|
|
4641
4702
|
${assignment.description}
|
|
4642
4703
|
|
|
4643
4704
|
Expected output: ${assignment.expectedOutput}
|
|
4644
|
-
|
|
4705
|
+
${assignment.files?.length ? `
|
|
4706
|
+
Files you own (create or edit exactly these paths):
|
|
4707
|
+
${assignment.files.map((f) => `- ${f}`).join("\n")}
|
|
4708
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
4709
|
+
Definition of done (your output must satisfy ALL of these):
|
|
4710
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
4711
|
+
` : ""}
|
|
4645
4712
|
Constraints:
|
|
4646
4713
|
${assignment.constraints.map((c) => `- ${c}`).join("\n")}
|
|
4647
4714
|
|
|
@@ -5122,6 +5189,8 @@ var T2Manager = class extends BaseTier {
|
|
|
5122
5189
|
this.assignment = assignment;
|
|
5123
5190
|
this.taskId = taskId;
|
|
5124
5191
|
this.setLabel(assignment.sectionTitle);
|
|
5192
|
+
const m = this.router.getModelForTier("T2");
|
|
5193
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
5125
5194
|
this.setStatus("ACTIVE");
|
|
5126
5195
|
this.sendStatusUpdate({
|
|
5127
5196
|
progressPct: 0,
|
|
@@ -5208,7 +5277,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
5208
5277
|
// ── Private ──────────────────────────────────
|
|
5209
5278
|
async decomposeSection(assignment) {
|
|
5210
5279
|
const peerPlans = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p) => `[Peer ${p.fromId} Plan]: ${p.content.sectionTitle} - ${p.content.subtaskTitles?.join(", ")}`).join("\n");
|
|
5211
|
-
const prompt = `Decompose this section into
|
|
5280
|
+
const prompt = `Decompose this section into 1-4 concrete subtasks for T3 workers \u2014 the FEWEST that fully cover it (one subtask is the correct answer for a small section).
|
|
5212
5281
|
|
|
5213
5282
|
Section: ${assignment.sectionTitle}
|
|
5214
5283
|
Description: ${assignment.description}
|
|
@@ -5227,6 +5296,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
5227
5296
|
- peerT3Ids: string[] (empty for now)
|
|
5228
5297
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
5229
5298
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
5299
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
5300
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
5301
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
5230
5302
|
|
|
5231
5303
|
Return ONLY the JSON array.`;
|
|
5232
5304
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -5824,6 +5896,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
5824
5896
|
this.signal = signal;
|
|
5825
5897
|
this.taskId = crypto3.randomUUID();
|
|
5826
5898
|
this.setLabel("Administrator");
|
|
5899
|
+
const m = this.router.getModelForTier("T1");
|
|
5900
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
5827
5901
|
this.setStatus("ACTIVE");
|
|
5828
5902
|
this.taskGoal = userPrompt;
|
|
5829
5903
|
this.sendStatusUpdate({
|
|
@@ -6070,10 +6144,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
6070
6144
|
"description": "Run npm init",
|
|
6071
6145
|
"expectedOutput": "package.json created",
|
|
6072
6146
|
"constraints": [],
|
|
6073
|
-
"dependsOn": []
|
|
6147
|
+
"dependsOn": [],
|
|
6148
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
6149
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
6150
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
6074
6151
|
}]
|
|
6075
6152
|
}, {
|
|
6076
|
-
"sectionId": "s2",
|
|
6153
|
+
"sectionId": "s2",
|
|
6077
6154
|
"sectionTitle": "Write Tests",
|
|
6078
6155
|
"description": "Write tests for the project",
|
|
6079
6156
|
"expectedOutput": "Tests passing",
|
|
@@ -6083,7 +6160,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
6083
6160
|
}]
|
|
6084
6161
|
}
|
|
6085
6162
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
6086
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
6163
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
6164
|
+
|
|
6165
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
6166
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
6167
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
6168
|
+
- "contextBrief": 1-3 short sentences with the ONLY background the worker needs. It sees nothing else about the task, so make the brief self-sufficient \u2014 but never pad it.
|
|
6169
|
+
- RIGHT-SIZE the plan: use the FEWEST sections and workers that fully cover the task. One section with 1-2 subtasks is the CORRECT plan for a small task; padding a plan with filler sections wastes the user's money.`;
|
|
6087
6170
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
6088
6171
|
const result = await this.router.generate("T1", {
|
|
6089
6172
|
messages,
|
|
@@ -6532,16 +6615,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
6532
6615
|
if (typeof input !== "string" || input.length === 0) {
|
|
6533
6616
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
6534
6617
|
}
|
|
6535
|
-
const root =
|
|
6536
|
-
const abs =
|
|
6537
|
-
const rel =
|
|
6538
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
6618
|
+
const root = path21__default.default.resolve(workspaceRoot);
|
|
6619
|
+
const abs = path21__default.default.isAbsolute(input) ? path21__default.default.resolve(input) : path21__default.default.resolve(root, input);
|
|
6620
|
+
const rel = path21__default.default.relative(root, abs);
|
|
6621
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path21__default.default.isAbsolute(rel)) {
|
|
6539
6622
|
throw new WorkspaceSandboxError(input, root);
|
|
6540
6623
|
}
|
|
6541
6624
|
try {
|
|
6542
|
-
const real =
|
|
6543
|
-
const realRel =
|
|
6544
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
6625
|
+
const real = fs20__default.default.realpathSync(abs);
|
|
6626
|
+
const realRel = path21__default.default.relative(root, real);
|
|
6627
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path21__default.default.isAbsolute(realRel))) {
|
|
6545
6628
|
throw new WorkspaceSandboxError(input, root);
|
|
6546
6629
|
}
|
|
6547
6630
|
} catch (e) {
|
|
@@ -6602,7 +6685,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
6602
6685
|
} catch {
|
|
6603
6686
|
}
|
|
6604
6687
|
}
|
|
6605
|
-
await fs4__default.default.mkdir(
|
|
6688
|
+
await fs4__default.default.mkdir(path21__default.default.dirname(absPath), { recursive: true });
|
|
6606
6689
|
await fs4__default.default.writeFile(absPath, content, "utf-8");
|
|
6607
6690
|
return `Written ${content.length} characters to ${filePath}`;
|
|
6608
6691
|
}
|
|
@@ -7100,7 +7183,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
7100
7183
|
};
|
|
7101
7184
|
async function fileToImageAttachment(filePath) {
|
|
7102
7185
|
const data = await fs4__default.default.readFile(filePath);
|
|
7103
|
-
const ext =
|
|
7186
|
+
const ext = path21__default.default.extname(filePath).toLowerCase();
|
|
7104
7187
|
const mimeMap = {
|
|
7105
7188
|
".jpg": "image/jpeg",
|
|
7106
7189
|
".jpeg": "image/jpeg",
|
|
@@ -7134,14 +7217,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
7134
7217
|
const filePath = input["path"];
|
|
7135
7218
|
const content = input["content"];
|
|
7136
7219
|
const title = input["title"];
|
|
7137
|
-
const dir =
|
|
7138
|
-
if (!
|
|
7139
|
-
|
|
7220
|
+
const dir = path21__default.default.dirname(filePath);
|
|
7221
|
+
if (!fs20__default.default.existsSync(dir)) {
|
|
7222
|
+
fs20__default.default.mkdirSync(dir, { recursive: true });
|
|
7140
7223
|
}
|
|
7141
7224
|
return new Promise((resolve, reject) => {
|
|
7142
7225
|
try {
|
|
7143
7226
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
7144
|
-
const stream =
|
|
7227
|
+
const stream = fs20__default.default.createWriteStream(filePath);
|
|
7145
7228
|
doc.pipe(stream);
|
|
7146
7229
|
if (title) {
|
|
7147
7230
|
doc.info["Title"] = title;
|
|
@@ -7219,22 +7302,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
7219
7302
|
}
|
|
7220
7303
|
cmdPrefix = NODE_CMD;
|
|
7221
7304
|
}
|
|
7222
|
-
const tmpDir =
|
|
7223
|
-
if (!
|
|
7224
|
-
|
|
7305
|
+
const tmpDir = path21__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
7306
|
+
if (!fs20__default.default.existsSync(tmpDir)) {
|
|
7307
|
+
fs20__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
7225
7308
|
}
|
|
7226
7309
|
const extension = language === "python" ? "py" : "js";
|
|
7227
7310
|
const fileName = `intp_${crypto3.randomUUID().slice(0, 8)}.${extension}`;
|
|
7228
|
-
const filePath =
|
|
7229
|
-
|
|
7311
|
+
const filePath = path21__default.default.join(tmpDir, fileName);
|
|
7312
|
+
fs20__default.default.writeFileSync(filePath, code, "utf-8");
|
|
7230
7313
|
const execArgs = [filePath, ...args];
|
|
7231
7314
|
return new Promise((resolve) => {
|
|
7232
7315
|
const startMs = Date.now();
|
|
7233
7316
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
7234
7317
|
const duration = Date.now() - startMs;
|
|
7235
7318
|
try {
|
|
7236
|
-
if (
|
|
7237
|
-
|
|
7319
|
+
if (fs20__default.default.existsSync(filePath)) {
|
|
7320
|
+
fs20__default.default.unlinkSync(filePath);
|
|
7238
7321
|
}
|
|
7239
7322
|
} catch (cleanupErr) {
|
|
7240
7323
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -7385,30 +7468,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
7385
7468
|
engine: "tavily"
|
|
7386
7469
|
}));
|
|
7387
7470
|
}
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7471
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
7472
|
+
function unwrapDdgRedirect(href) {
|
|
7473
|
+
try {
|
|
7474
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
7475
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
7476
|
+
const target = url.searchParams.get("uddg");
|
|
7477
|
+
if (target) return decodeURIComponent(target);
|
|
7478
|
+
}
|
|
7479
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
7480
|
+
} catch {
|
|
7481
|
+
return href;
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
function stripTags(html) {
|
|
7485
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
7486
|
+
}
|
|
7487
|
+
function decodeEntities(text) {
|
|
7488
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
7489
|
+
}
|
|
7490
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
7491
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
7492
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
7493
|
+
const results = [];
|
|
7399
7494
|
let m;
|
|
7400
|
-
while ((m =
|
|
7401
|
-
|
|
7495
|
+
while ((m = anchorRe.exec(html)) !== null) {
|
|
7496
|
+
const tag = m[0];
|
|
7497
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7498
|
+
const title = decodeEntities(stripTags(m[1] ?? ""));
|
|
7499
|
+
if (!href || !title) continue;
|
|
7500
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
7402
7501
|
}
|
|
7403
|
-
|
|
7404
|
-
|
|
7502
|
+
const snippets = [];
|
|
7503
|
+
while ((m = snippetRe.exec(html)) !== null) {
|
|
7504
|
+
snippets.push(decodeEntities(stripTags(m[1] ?? "")));
|
|
7405
7505
|
}
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7506
|
+
for (let i = 0; i < results.length; i++) {
|
|
7507
|
+
if (snippets[i]) results[i].snippet = snippets[i];
|
|
7508
|
+
}
|
|
7509
|
+
return results;
|
|
7510
|
+
}
|
|
7511
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
7512
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
7513
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
7514
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
7515
|
+
signal: AbortSignal.timeout(1e4)
|
|
7516
|
+
});
|
|
7517
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
7518
|
+
const html = await resp.text();
|
|
7519
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
7520
|
+
return parsed.slice(0, maxResults).map((r) => ({ ...r, engine: `duckduckgo-${variant}` }));
|
|
7412
7521
|
}
|
|
7413
7522
|
var WebSearchTool = class extends BaseTool {
|
|
7414
7523
|
name = "web_search";
|
|
@@ -7467,12 +7576,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
7467
7576
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
7468
7577
|
}
|
|
7469
7578
|
}
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7579
|
+
for (const variant of ["html", "lite"]) {
|
|
7580
|
+
try {
|
|
7581
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
7582
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
7583
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
7584
|
+
} catch (err) {
|
|
7585
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7586
|
+
}
|
|
7476
7587
|
}
|
|
7477
7588
|
const configHint = !this.config.searxngUrl && !this.config.braveApiKey && !this.config.tavilyApiKey ? "\nTip: Configure a search backend for better results:\n \u2022 Self-hosted: set SEARXNG_URL in your environment\n \u2022 Brave Search API: set BRAVE_SEARCH_API_KEY\n \u2022 Tavily API: set TAVILY_API_KEY" : "";
|
|
7478
7589
|
return [
|
|
@@ -7513,7 +7624,7 @@ var GlobTool = class extends BaseTool {
|
|
|
7513
7624
|
};
|
|
7514
7625
|
async execute(input, _options) {
|
|
7515
7626
|
const pattern = input["pattern"];
|
|
7516
|
-
const searchPath = input["path"] ?
|
|
7627
|
+
const searchPath = input["path"] ? path21__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
7517
7628
|
const matches = await glob.glob(pattern, {
|
|
7518
7629
|
cwd: searchPath,
|
|
7519
7630
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -7526,7 +7637,7 @@ var GlobTool = class extends BaseTool {
|
|
|
7526
7637
|
const withMtime = await Promise.all(
|
|
7527
7638
|
matches.map(async (rel) => {
|
|
7528
7639
|
try {
|
|
7529
|
-
const stat = await fs4__default.default.stat(
|
|
7640
|
+
const stat = await fs4__default.default.stat(path21__default.default.join(searchPath, rel));
|
|
7530
7641
|
return { rel, mtime: stat.mtimeMs };
|
|
7531
7642
|
} catch {
|
|
7532
7643
|
return { rel, mtime: 0 };
|
|
@@ -7575,7 +7686,7 @@ var GrepTool = class extends BaseTool {
|
|
|
7575
7686
|
};
|
|
7576
7687
|
async execute(input, _options) {
|
|
7577
7688
|
const pattern = input["pattern"];
|
|
7578
|
-
const searchPath = input["path"] ?
|
|
7689
|
+
const searchPath = input["path"] ? path21__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
7579
7690
|
const globPattern = input["glob"];
|
|
7580
7691
|
const outputMode = input["output_mode"] ?? "content";
|
|
7581
7692
|
const context = input["context"] ?? 0;
|
|
@@ -7629,12 +7740,12 @@ var GrepTool = class extends BaseTool {
|
|
|
7629
7740
|
nodir: true
|
|
7630
7741
|
});
|
|
7631
7742
|
} catch {
|
|
7632
|
-
files = [
|
|
7743
|
+
files = [path21__default.default.relative(searchPath, searchPath) || "."];
|
|
7633
7744
|
}
|
|
7634
7745
|
const results = [];
|
|
7635
7746
|
let totalCount = 0;
|
|
7636
7747
|
for (const rel of files) {
|
|
7637
|
-
const abs =
|
|
7748
|
+
const abs = path21__default.default.join(searchPath, rel);
|
|
7638
7749
|
let content;
|
|
7639
7750
|
try {
|
|
7640
7751
|
content = await fs4__default.default.readFile(abs, "utf-8");
|
|
@@ -7996,10 +8107,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
7996
8107
|
}
|
|
7997
8108
|
isIgnored(filePath) {
|
|
7998
8109
|
if (!filePath) return false;
|
|
7999
|
-
const abs =
|
|
8000
|
-
const rel =
|
|
8001
|
-
if (!rel || rel.startsWith("..") ||
|
|
8002
|
-
const posixRel = rel.split(
|
|
8110
|
+
const abs = path21__default.default.resolve(this.workspaceRoot, filePath);
|
|
8111
|
+
const rel = path21__default.default.relative(this.workspaceRoot, abs);
|
|
8112
|
+
if (!rel || rel.startsWith("..") || path21__default.default.isAbsolute(rel)) return true;
|
|
8113
|
+
const posixRel = rel.split(path21__default.default.sep).join("/");
|
|
8003
8114
|
return this.ignoreMatcher.ignores(posixRel);
|
|
8004
8115
|
}
|
|
8005
8116
|
};
|
|
@@ -8416,8 +8527,11 @@ var CascadeConfigSchema = zod.z.object({
|
|
|
8416
8527
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
8417
8528
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
8418
8529
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
8530
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
8531
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
8532
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
8419
8533
|
*/
|
|
8420
|
-
cascadeAuto: zod.z.boolean().default(
|
|
8534
|
+
cascadeAuto: zod.z.boolean().default(true),
|
|
8421
8535
|
/**
|
|
8422
8536
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
8423
8537
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -8833,7 +8947,7 @@ var TaskAnalyzer = class {
|
|
|
8833
8947
|
analysisCache.clear();
|
|
8834
8948
|
}
|
|
8835
8949
|
};
|
|
8836
|
-
var DEFAULT_STATS_FILE =
|
|
8950
|
+
var DEFAULT_STATS_FILE = path21__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
|
|
8837
8951
|
var ModelPerformanceTracker = class {
|
|
8838
8952
|
stats = /* @__PURE__ */ new Map();
|
|
8839
8953
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -8865,7 +8979,7 @@ var ModelPerformanceTracker = class {
|
|
|
8865
8979
|
}
|
|
8866
8980
|
async save() {
|
|
8867
8981
|
try {
|
|
8868
|
-
await fs4__default.default.mkdir(
|
|
8982
|
+
await fs4__default.default.mkdir(path21__default.default.dirname(this.statsFile), { recursive: true });
|
|
8869
8983
|
const modelsObj = {};
|
|
8870
8984
|
const featuresObj = {};
|
|
8871
8985
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -9404,7 +9518,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
9404
9518
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
9405
9519
|
async loadPersistedTools() {
|
|
9406
9520
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
9407
|
-
const file =
|
|
9521
|
+
const file = path21__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
9408
9522
|
try {
|
|
9409
9523
|
const raw = await fs4__default.default.readFile(file, "utf-8");
|
|
9410
9524
|
const specs = JSON.parse(raw);
|
|
@@ -9428,8 +9542,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
9428
9542
|
}
|
|
9429
9543
|
async persist() {
|
|
9430
9544
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
9431
|
-
const dir =
|
|
9432
|
-
const file =
|
|
9545
|
+
const dir = path21__default.default.join(this.workspacePath, ".cascade");
|
|
9546
|
+
const file = path21__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
9433
9547
|
try {
|
|
9434
9548
|
await fs4__default.default.mkdir(dir, { recursive: true });
|
|
9435
9549
|
await fs4__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -9449,12 +9563,12 @@ var WorldStateDB = class {
|
|
|
9449
9563
|
constructor(workspacePath, debugMode = false) {
|
|
9450
9564
|
this.workspacePath = workspacePath;
|
|
9451
9565
|
this.debugMode = debugMode;
|
|
9452
|
-
const cascadeDir =
|
|
9453
|
-
if (!
|
|
9454
|
-
|
|
9566
|
+
const cascadeDir = path21__default.default.join(workspacePath, ".cascade");
|
|
9567
|
+
if (!fs20__default.default.existsSync(cascadeDir)) {
|
|
9568
|
+
fs20__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
9455
9569
|
}
|
|
9456
|
-
this.keyPath =
|
|
9457
|
-
this.dbPath =
|
|
9570
|
+
this.keyPath = path21__default.default.join(cascadeDir, "world_state.key");
|
|
9571
|
+
this.dbPath = path21__default.default.join(cascadeDir, "world_state.db");
|
|
9458
9572
|
this.initEncryptionKey();
|
|
9459
9573
|
this.db = new Database__default.default(this.dbPath);
|
|
9460
9574
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -9484,11 +9598,11 @@ var WorldStateDB = class {
|
|
|
9484
9598
|
dbPath;
|
|
9485
9599
|
encryptionKey;
|
|
9486
9600
|
initEncryptionKey() {
|
|
9487
|
-
if (
|
|
9488
|
-
this.encryptionKey =
|
|
9601
|
+
if (fs20__default.default.existsSync(this.keyPath)) {
|
|
9602
|
+
this.encryptionKey = fs20__default.default.readFileSync(this.keyPath);
|
|
9489
9603
|
} else {
|
|
9490
9604
|
this.encryptionKey = crypto3__default.default.randomBytes(32);
|
|
9491
|
-
|
|
9605
|
+
fs20__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
9492
9606
|
}
|
|
9493
9607
|
}
|
|
9494
9608
|
encrypt(text) {
|
|
@@ -9629,6 +9743,22 @@ var WorldStateDB = class {
|
|
|
9629
9743
|
}
|
|
9630
9744
|
return selected.slice(0, limit).map((f) => `- ${f.entity} ${f.relation} ${f.value}`).join("\n");
|
|
9631
9745
|
}
|
|
9746
|
+
/**
|
|
9747
|
+
* Delete one fact by its (normalized) entity + relation key. Returns whether
|
|
9748
|
+
* a row was actually removed. Powers the desktop Knowledge tab's per-fact
|
|
9749
|
+
* delete — users can prune what the planner remembers about their project.
|
|
9750
|
+
*/
|
|
9751
|
+
deleteFact(entity, relation) {
|
|
9752
|
+
const e = normalizeKey(entity);
|
|
9753
|
+
const r = normalizeKey(relation);
|
|
9754
|
+
if (!e || !r) return false;
|
|
9755
|
+
const result = this.db.prepare("DELETE FROM facts WHERE entity = ? AND relation = ?").run(e, r);
|
|
9756
|
+
return result.changes > 0;
|
|
9757
|
+
}
|
|
9758
|
+
/** Delete every fact. Returns how many were removed. */
|
|
9759
|
+
clearFacts() {
|
|
9760
|
+
return this.db.prepare("DELETE FROM facts").run().changes;
|
|
9761
|
+
}
|
|
9632
9762
|
// ── Export / Import ──────────────────────────
|
|
9633
9763
|
//
|
|
9634
9764
|
// Knowledge travels DECRYPTED in the export bundle (a portable plaintext
|
|
@@ -9677,9 +9807,9 @@ var WorldStateDB = class {
|
|
|
9677
9807
|
dumpDebugIfNeeded() {
|
|
9678
9808
|
if (!this.debugMode) return;
|
|
9679
9809
|
try {
|
|
9680
|
-
const dumpPath =
|
|
9810
|
+
const dumpPath = path21__default.default.join(os6__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
9681
9811
|
const entries = this.getAllEntries();
|
|
9682
|
-
|
|
9812
|
+
fs20__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
9683
9813
|
} catch (err) {
|
|
9684
9814
|
console.error("Failed to dump debug world state", err);
|
|
9685
9815
|
}
|
|
@@ -10114,13 +10244,30 @@ ${last.partialOutput}` : "");
|
|
|
10114
10244
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
10115
10245
|
* Simple/Moderate) don't get over-escalated.
|
|
10116
10246
|
*/
|
|
10117
|
-
|
|
10247
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
10248
|
+
buildSignals(prompt) {
|
|
10118
10249
|
const p = prompt.trim();
|
|
10119
|
-
if (p.length < 24) return false;
|
|
10250
|
+
if (p.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
10120
10251
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
10121
|
-
const
|
|
10252
|
+
const scaleCount = (p.match(/\b(?:app(?:lication)?|system|platform|service|api|backend|frontend|full[- ]?stack|website|dashboard|pipeline|microservices?|database schema|authentication|end[- ]to[- ]end|codebase|project|multiple files|several (?:files|modules|components)|test suite)\b/gi) ?? []).length;
|
|
10122
10253
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
10123
|
-
return buildVerb
|
|
10254
|
+
return { buildVerb, scaleCount, multiPart };
|
|
10255
|
+
}
|
|
10256
|
+
/**
|
|
10257
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
10258
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
10259
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
10260
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
10261
|
+
* × workers for a task one worker handles).
|
|
10262
|
+
*/
|
|
10263
|
+
looksClearlyComplex(prompt) {
|
|
10264
|
+
const s = this.buildSignals(prompt);
|
|
10265
|
+
return s.buildVerb && (s.scaleCount >= 2 || s.scaleCount >= 1 && s.multiPart);
|
|
10266
|
+
}
|
|
10267
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
10268
|
+
looksLikeModerateBuild(prompt) {
|
|
10269
|
+
const s = this.buildSignals(prompt);
|
|
10270
|
+
return s.buildVerb && (s.scaleCount >= 1 || s.multiPart);
|
|
10124
10271
|
}
|
|
10125
10272
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
10126
10273
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -10208,6 +10355,9 @@ ${prompt}` : prompt;
|
|
|
10208
10355
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
10209
10356
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
10210
10357
|
verdict = "Complex";
|
|
10358
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
10359
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
10360
|
+
verdict = "Moderate";
|
|
10211
10361
|
} else {
|
|
10212
10362
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
10213
10363
|
}
|
|
@@ -10589,7 +10739,7 @@ var Keystore = class {
|
|
|
10589
10739
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
10590
10740
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
10591
10741
|
this.backend = "keytar";
|
|
10592
|
-
if (password &&
|
|
10742
|
+
if (password && fs20__default.default.existsSync(this.storePath)) {
|
|
10593
10743
|
try {
|
|
10594
10744
|
const fileEntries = this.decryptFile(password);
|
|
10595
10745
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -10608,7 +10758,7 @@ var Keystore = class {
|
|
|
10608
10758
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
10609
10759
|
);
|
|
10610
10760
|
}
|
|
10611
|
-
if (!
|
|
10761
|
+
if (!fs20__default.default.existsSync(this.storePath)) {
|
|
10612
10762
|
const salt = crypto3__default.default.randomBytes(SALT_LEN);
|
|
10613
10763
|
this.masterKey = this.deriveKey(password, salt);
|
|
10614
10764
|
this.writeWithSalt({}, salt);
|
|
@@ -10622,7 +10772,7 @@ var Keystore = class {
|
|
|
10622
10772
|
}
|
|
10623
10773
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
10624
10774
|
unlockSync(password) {
|
|
10625
|
-
if (!
|
|
10775
|
+
if (!fs20__default.default.existsSync(this.storePath)) {
|
|
10626
10776
|
const salt = crypto3__default.default.randomBytes(SALT_LEN);
|
|
10627
10777
|
this.masterKey = this.deriveKey(password, salt);
|
|
10628
10778
|
this.writeWithSalt({}, salt);
|
|
@@ -10680,7 +10830,7 @@ var Keystore = class {
|
|
|
10680
10830
|
}
|
|
10681
10831
|
}
|
|
10682
10832
|
decryptFile(password, knownSalt) {
|
|
10683
|
-
if (!
|
|
10833
|
+
if (!fs20__default.default.existsSync(this.storePath)) return {};
|
|
10684
10834
|
try {
|
|
10685
10835
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
10686
10836
|
const useSalt = knownSalt ?? salt;
|
|
@@ -10702,8 +10852,8 @@ var Keystore = class {
|
|
|
10702
10852
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
10703
10853
|
const tag = cipher.getAuthTag();
|
|
10704
10854
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
10705
|
-
|
|
10706
|
-
|
|
10855
|
+
fs20__default.default.mkdirSync(path21__default.default.dirname(this.storePath), { recursive: true });
|
|
10856
|
+
fs20__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
10707
10857
|
}
|
|
10708
10858
|
writeWithSalt(data, salt) {
|
|
10709
10859
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -10713,11 +10863,11 @@ var Keystore = class {
|
|
|
10713
10863
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
10714
10864
|
const tag = cipher.getAuthTag();
|
|
10715
10865
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
10716
|
-
|
|
10717
|
-
|
|
10866
|
+
fs20__default.default.mkdirSync(path21__default.default.dirname(this.storePath), { recursive: true });
|
|
10867
|
+
fs20__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
10718
10868
|
}
|
|
10719
10869
|
readRaw() {
|
|
10720
|
-
const buf =
|
|
10870
|
+
const buf = fs20__default.default.readFileSync(this.storePath);
|
|
10721
10871
|
let offset = 0;
|
|
10722
10872
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
10723
10873
|
offset += SALT_LEN;
|
|
@@ -10750,7 +10900,7 @@ var CascadeIgnore = class {
|
|
|
10750
10900
|
]);
|
|
10751
10901
|
}
|
|
10752
10902
|
async load(workspacePath) {
|
|
10753
|
-
const filePath =
|
|
10903
|
+
const filePath = path21__default.default.join(workspacePath, ".cascadeignore");
|
|
10754
10904
|
try {
|
|
10755
10905
|
const content = await fs4__default.default.readFile(filePath, "utf-8");
|
|
10756
10906
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -10761,7 +10911,7 @@ var CascadeIgnore = class {
|
|
|
10761
10911
|
}
|
|
10762
10912
|
isIgnored(filePath, workspacePath) {
|
|
10763
10913
|
try {
|
|
10764
|
-
const relative = workspacePath ?
|
|
10914
|
+
const relative = workspacePath ? path21__default.default.relative(workspacePath, filePath) : filePath;
|
|
10765
10915
|
return this.ig.ignores(relative);
|
|
10766
10916
|
} catch {
|
|
10767
10917
|
return false;
|
|
@@ -10772,7 +10922,7 @@ var CascadeIgnore = class {
|
|
|
10772
10922
|
}
|
|
10773
10923
|
};
|
|
10774
10924
|
async function loadCascadeMd(workspacePath) {
|
|
10775
|
-
const filePath =
|
|
10925
|
+
const filePath = path21__default.default.join(workspacePath, "CASCADE.md");
|
|
10776
10926
|
try {
|
|
10777
10927
|
const raw = await fs4__default.default.readFile(filePath, "utf-8");
|
|
10778
10928
|
return parseCascadeMd(raw);
|
|
@@ -10803,7 +10953,7 @@ ${raw.trim()}`;
|
|
|
10803
10953
|
var MemoryStore = class _MemoryStore {
|
|
10804
10954
|
db;
|
|
10805
10955
|
constructor(dbPath) {
|
|
10806
|
-
|
|
10956
|
+
fs20__default.default.mkdirSync(path21__default.default.dirname(dbPath), { recursive: true });
|
|
10807
10957
|
try {
|
|
10808
10958
|
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
10809
10959
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -11638,6 +11788,60 @@ Original error: ${err.message}`
|
|
|
11638
11788
|
};
|
|
11639
11789
|
}
|
|
11640
11790
|
};
|
|
11791
|
+
function credentialsPath(globalDir) {
|
|
11792
|
+
return path21__default.default.join(globalDir, GLOBAL_CREDENTIALS_FILE);
|
|
11793
|
+
}
|
|
11794
|
+
function providerKey(p) {
|
|
11795
|
+
if (p.type === "azure") {
|
|
11796
|
+
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
11797
|
+
}
|
|
11798
|
+
return p.type;
|
|
11799
|
+
}
|
|
11800
|
+
function isPersistable(p) {
|
|
11801
|
+
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
11802
|
+
}
|
|
11803
|
+
function loadGlobalCredentials(globalDir) {
|
|
11804
|
+
try {
|
|
11805
|
+
const raw = fs20__default.default.readFileSync(credentialsPath(globalDir), "utf-8");
|
|
11806
|
+
const parsed = JSON.parse(raw);
|
|
11807
|
+
if (!Array.isArray(parsed.providers)) return [];
|
|
11808
|
+
return parsed.providers.filter(
|
|
11809
|
+
(p) => Boolean(p) && typeof p.type === "string"
|
|
11810
|
+
);
|
|
11811
|
+
} catch {
|
|
11812
|
+
return [];
|
|
11813
|
+
}
|
|
11814
|
+
}
|
|
11815
|
+
function saveGlobalCredentials(globalDir, providers) {
|
|
11816
|
+
const filePath = credentialsPath(globalDir);
|
|
11817
|
+
fs20__default.default.mkdirSync(globalDir, { recursive: true, mode: 448 });
|
|
11818
|
+
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
11819
|
+
const tmp = `${filePath}.tmp`;
|
|
11820
|
+
fs20__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
11821
|
+
fs20__default.default.renameSync(tmp, filePath);
|
|
11822
|
+
try {
|
|
11823
|
+
fs20__default.default.chmodSync(filePath, 384);
|
|
11824
|
+
} catch {
|
|
11825
|
+
}
|
|
11826
|
+
}
|
|
11827
|
+
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
11828
|
+
const merged = [...workspaceProviders];
|
|
11829
|
+
const byKey = new Map(merged.map((p) => [providerKey(p), p]));
|
|
11830
|
+
for (const g of globalProviders) {
|
|
11831
|
+
const existing = byKey.get(providerKey(g));
|
|
11832
|
+
if (!existing) {
|
|
11833
|
+
merged.push({ ...g });
|
|
11834
|
+
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
11835
|
+
continue;
|
|
11836
|
+
}
|
|
11837
|
+
if (!existing.apiKey && g.apiKey) existing.apiKey = g.apiKey;
|
|
11838
|
+
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
11839
|
+
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
11840
|
+
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
11841
|
+
if (!existing.label && g.label) existing.label = g.label;
|
|
11842
|
+
}
|
|
11843
|
+
return merged;
|
|
11844
|
+
}
|
|
11641
11845
|
|
|
11642
11846
|
// src/config/index.ts
|
|
11643
11847
|
var ConfigManager = class {
|
|
@@ -11648,18 +11852,20 @@ var ConfigManager = class {
|
|
|
11648
11852
|
cascadeMd = null;
|
|
11649
11853
|
workspacePath;
|
|
11650
11854
|
globalDir;
|
|
11651
|
-
|
|
11855
|
+
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
11856
|
+
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
11652
11857
|
this.workspacePath = workspacePath;
|
|
11653
|
-
this.globalDir =
|
|
11858
|
+
this.globalDir = globalDirOverride ?? path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
11654
11859
|
}
|
|
11655
11860
|
async load() {
|
|
11656
11861
|
this.config = await this.loadConfig();
|
|
11657
11862
|
this.ignore = new CascadeIgnore();
|
|
11658
11863
|
await this.ignore.load(this.workspacePath);
|
|
11659
11864
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
11660
|
-
this.keystore = new Keystore(
|
|
11661
|
-
this.store = new MemoryStore(
|
|
11865
|
+
this.keystore = new Keystore(path21__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
11866
|
+
this.store = new MemoryStore(path21__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
11662
11867
|
await this.injectEnvKeys();
|
|
11868
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
11663
11869
|
await this.ensureDefaultIdentity();
|
|
11664
11870
|
}
|
|
11665
11871
|
getConfig() {
|
|
@@ -11681,9 +11887,14 @@ var ConfigManager = class {
|
|
|
11681
11887
|
return this.workspacePath;
|
|
11682
11888
|
}
|
|
11683
11889
|
async save() {
|
|
11684
|
-
const configPath =
|
|
11685
|
-
await fs4__default.default.mkdir(
|
|
11890
|
+
const configPath = path21__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
11891
|
+
await fs4__default.default.mkdir(path21__default.default.dirname(configPath), { recursive: true });
|
|
11686
11892
|
await fs4__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
11893
|
+
try {
|
|
11894
|
+
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
11895
|
+
} catch (err) {
|
|
11896
|
+
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
11897
|
+
}
|
|
11687
11898
|
}
|
|
11688
11899
|
async updateConfig(updates) {
|
|
11689
11900
|
this.config = validateConfig({ ...this.config, ...updates });
|
|
@@ -11706,7 +11917,7 @@ var ConfigManager = class {
|
|
|
11706
11917
|
return configProvider?.apiKey;
|
|
11707
11918
|
}
|
|
11708
11919
|
async loadConfig() {
|
|
11709
|
-
const configPath =
|
|
11920
|
+
const configPath = path21__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
11710
11921
|
try {
|
|
11711
11922
|
const raw = await fs4__default.default.readFile(configPath, "utf-8");
|
|
11712
11923
|
return validateConfig(JSON.parse(raw));
|
|
@@ -11813,6 +12024,11 @@ function authMiddleware(secret, required = true) {
|
|
|
11813
12024
|
}
|
|
11814
12025
|
const user = verifyToken(token, secret);
|
|
11815
12026
|
if (!user) {
|
|
12027
|
+
if (!required) {
|
|
12028
|
+
req.user = void 0;
|
|
12029
|
+
next();
|
|
12030
|
+
return;
|
|
12031
|
+
}
|
|
11816
12032
|
res.status(401).json({ error: "Invalid or expired token" });
|
|
11817
12033
|
return;
|
|
11818
12034
|
}
|
|
@@ -12121,7 +12337,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
12121
12337
|
}
|
|
12122
12338
|
|
|
12123
12339
|
// src/dashboard/server.ts
|
|
12124
|
-
var __dirname$1 =
|
|
12340
|
+
var __dirname$1 = path21__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
12125
12341
|
var DashboardServer = class {
|
|
12126
12342
|
app;
|
|
12127
12343
|
httpServer;
|
|
@@ -12355,12 +12571,17 @@ var DashboardServer = class {
|
|
|
12355
12571
|
*/
|
|
12356
12572
|
persistConfig() {
|
|
12357
12573
|
try {
|
|
12358
|
-
const configPath =
|
|
12359
|
-
|
|
12360
|
-
|
|
12574
|
+
const configPath = path21__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
12575
|
+
fs20__default.default.mkdirSync(path21__default.default.dirname(configPath), { recursive: true });
|
|
12576
|
+
fs20__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
12361
12577
|
} catch (err) {
|
|
12362
12578
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
12363
12579
|
}
|
|
12580
|
+
try {
|
|
12581
|
+
saveGlobalCredentials(path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
12582
|
+
} catch (err) {
|
|
12583
|
+
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
12584
|
+
}
|
|
12364
12585
|
}
|
|
12365
12586
|
/**
|
|
12366
12587
|
* Produce a stable dashboard JWT signing secret.
|
|
@@ -12372,15 +12593,15 @@ var DashboardServer = class {
|
|
|
12372
12593
|
resolveDashboardSecret() {
|
|
12373
12594
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
12374
12595
|
if (fromConfig) return fromConfig;
|
|
12375
|
-
const secretPath =
|
|
12596
|
+
const secretPath = path21__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
12376
12597
|
try {
|
|
12377
|
-
if (
|
|
12378
|
-
const existing =
|
|
12598
|
+
if (fs20__default.default.existsSync(secretPath)) {
|
|
12599
|
+
const existing = fs20__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
12379
12600
|
if (existing.length >= 16) return existing;
|
|
12380
12601
|
}
|
|
12381
12602
|
const generated = crypto3.randomUUID();
|
|
12382
|
-
|
|
12383
|
-
|
|
12603
|
+
fs20__default.default.mkdirSync(path21__default.default.dirname(secretPath), { recursive: true });
|
|
12604
|
+
fs20__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
12384
12605
|
if (this.config.dashboard.auth) {
|
|
12385
12606
|
console.warn(
|
|
12386
12607
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -12407,7 +12628,7 @@ var DashboardServer = class {
|
|
|
12407
12628
|
// ── Setup ─────────────────────────────────────
|
|
12408
12629
|
getGlobalStore() {
|
|
12409
12630
|
if (!this.globalStore) {
|
|
12410
|
-
const globalDbPath =
|
|
12631
|
+
const globalDbPath = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12411
12632
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
12412
12633
|
}
|
|
12413
12634
|
return this.globalStore;
|
|
@@ -12676,12 +12897,12 @@ ${prompt}`;
|
|
|
12676
12897
|
}
|
|
12677
12898
|
}
|
|
12678
12899
|
watchRuntimeChanges() {
|
|
12679
|
-
const workspaceDbPath =
|
|
12680
|
-
const globalDbPath =
|
|
12900
|
+
const workspaceDbPath = path21__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
12901
|
+
const globalDbPath = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12681
12902
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
12682
12903
|
for (const watchPath of watchPaths) {
|
|
12683
|
-
if (!
|
|
12684
|
-
|
|
12904
|
+
if (!fs20__default.default.existsSync(watchPath)) continue;
|
|
12905
|
+
fs20__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
12685
12906
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
12686
12907
|
});
|
|
12687
12908
|
}
|
|
@@ -12803,7 +13024,7 @@ ${prompt}`;
|
|
|
12803
13024
|
const sessionId = req.params.id;
|
|
12804
13025
|
this.store.deleteSession(sessionId);
|
|
12805
13026
|
this.store.deleteRuntimeSession(sessionId);
|
|
12806
|
-
const globalDbPath =
|
|
13027
|
+
const globalDbPath = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12807
13028
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12808
13029
|
try {
|
|
12809
13030
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -12896,7 +13117,7 @@ ${prompt}`;
|
|
|
12896
13117
|
});
|
|
12897
13118
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
12898
13119
|
const body = req.body;
|
|
12899
|
-
const globalDbPath =
|
|
13120
|
+
const globalDbPath = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12900
13121
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
12901
13122
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12902
13123
|
try {
|
|
@@ -12919,7 +13140,7 @@ ${prompt}`;
|
|
|
12919
13140
|
});
|
|
12920
13141
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
12921
13142
|
this.store.deleteAllRuntimeNodes();
|
|
12922
|
-
const globalDbPath =
|
|
13143
|
+
const globalDbPath = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12923
13144
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12924
13145
|
try {
|
|
12925
13146
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -13005,12 +13226,12 @@ ${prompt}`;
|
|
|
13005
13226
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
13006
13227
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
13007
13228
|
try {
|
|
13008
|
-
const configPath =
|
|
13009
|
-
const existing =
|
|
13229
|
+
const configPath = path21__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
13230
|
+
const existing = fs20__default.default.existsSync(configPath) ? JSON.parse(fs20__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
13010
13231
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
13011
13232
|
const tmp = configPath + ".tmp";
|
|
13012
|
-
|
|
13013
|
-
|
|
13233
|
+
fs20__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
13234
|
+
fs20__default.default.renameSync(tmp, configPath);
|
|
13014
13235
|
res.json({ ok: true });
|
|
13015
13236
|
} catch (err) {
|
|
13016
13237
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -13038,7 +13259,7 @@ ${prompt}`;
|
|
|
13038
13259
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
13039
13260
|
const scope = req.query["scope"] ?? "workspace";
|
|
13040
13261
|
if (scope === "global") {
|
|
13041
|
-
const globalDbPath =
|
|
13262
|
+
const globalDbPath = path21__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
13042
13263
|
const globalStore = new MemoryStore(globalDbPath);
|
|
13043
13264
|
try {
|
|
13044
13265
|
res.json({
|
|
@@ -13273,6 +13494,48 @@ ${prompt}`;
|
|
|
13273
13494
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13274
13495
|
}
|
|
13275
13496
|
});
|
|
13497
|
+
this.app.get("/api/knowledge", auth, (_req, res) => {
|
|
13498
|
+
try {
|
|
13499
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
13500
|
+
try {
|
|
13501
|
+
const facts = ws.getAllFacts();
|
|
13502
|
+
res.json({ total: facts.length, facts });
|
|
13503
|
+
} finally {
|
|
13504
|
+
ws.close();
|
|
13505
|
+
}
|
|
13506
|
+
} catch (err) {
|
|
13507
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13508
|
+
}
|
|
13509
|
+
});
|
|
13510
|
+
this.app.delete("/api/knowledge/fact", auth, mutationLimiter, (req, res) => {
|
|
13511
|
+
const body = req.body;
|
|
13512
|
+
if (!body.entity || !body.relation) {
|
|
13513
|
+
res.status(400).json({ error: "entity and relation are required" });
|
|
13514
|
+
return;
|
|
13515
|
+
}
|
|
13516
|
+
try {
|
|
13517
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
13518
|
+
try {
|
|
13519
|
+
res.json({ ok: true, deleted: ws.deleteFact(body.entity, body.relation) });
|
|
13520
|
+
} finally {
|
|
13521
|
+
ws.close();
|
|
13522
|
+
}
|
|
13523
|
+
} catch (err) {
|
|
13524
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13525
|
+
}
|
|
13526
|
+
});
|
|
13527
|
+
this.app.delete("/api/knowledge", auth, mutationLimiter, (_req, res) => {
|
|
13528
|
+
try {
|
|
13529
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
13530
|
+
try {
|
|
13531
|
+
res.json({ ok: true, deleted: ws.clearFacts() });
|
|
13532
|
+
} finally {
|
|
13533
|
+
ws.close();
|
|
13534
|
+
}
|
|
13535
|
+
} catch (err) {
|
|
13536
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13537
|
+
}
|
|
13538
|
+
});
|
|
13276
13539
|
this.app.get("/api/audit-chain", auth, async (req, res) => {
|
|
13277
13540
|
try {
|
|
13278
13541
|
const limit = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
|
|
@@ -13291,13 +13554,13 @@ ${prompt}`;
|
|
|
13291
13554
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13292
13555
|
}
|
|
13293
13556
|
});
|
|
13294
|
-
const prodPath =
|
|
13295
|
-
const devPath =
|
|
13296
|
-
const webDistPath =
|
|
13297
|
-
if (
|
|
13557
|
+
const prodPath = path21__default.default.resolve(__dirname$1, "../web/dist");
|
|
13558
|
+
const devPath = path21__default.default.resolve(__dirname$1, "../../web/dist");
|
|
13559
|
+
const webDistPath = fs20__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
13560
|
+
if (fs20__default.default.existsSync(webDistPath)) {
|
|
13298
13561
|
this.app.use(express__default.default.static(webDistPath));
|
|
13299
13562
|
this.app.get("*", (_req, res) => {
|
|
13300
|
-
res.sendFile(
|
|
13563
|
+
res.sendFile(path21__default.default.join(webDistPath, "index.html"));
|
|
13301
13564
|
});
|
|
13302
13565
|
} else {
|
|
13303
13566
|
this.app.get("/", (_req, res) => {
|
|
@@ -13398,6 +13661,7 @@ exports.DEFAULT_RETENTION_DAYS = DEFAULT_RETENTION_DAYS;
|
|
|
13398
13661
|
exports.DEFAULT_THEME = DEFAULT_THEME;
|
|
13399
13662
|
exports.DashboardServer = DashboardServer;
|
|
13400
13663
|
exports.GLOBAL_CONFIG_DIR = GLOBAL_CONFIG_DIR;
|
|
13664
|
+
exports.GLOBAL_CREDENTIALS_FILE = GLOBAL_CREDENTIALS_FILE;
|
|
13401
13665
|
exports.GLOBAL_DB_FILE = GLOBAL_DB_FILE;
|
|
13402
13666
|
exports.GLOBAL_KEYSTORE_FILE = GLOBAL_KEYSTORE_FILE;
|
|
13403
13667
|
exports.GLOBAL_RUNTIME_DB_FILE = GLOBAL_RUNTIME_DB_FILE;
|