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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path21 from 'path';
|
|
3
|
+
import fs20 from 'fs';
|
|
4
4
|
import os6 from 'os';
|
|
5
5
|
import crypto3, { randomUUID, timingSafeEqual } from 'crypto';
|
|
6
6
|
import EventEmitter from 'events';
|
|
@@ -58,12 +58,12 @@ var init_audit_logger = __esm({
|
|
|
58
58
|
constructor(workspacePath, debugMode = false) {
|
|
59
59
|
this.workspacePath = workspacePath;
|
|
60
60
|
this.debugMode = debugMode;
|
|
61
|
-
const cascadeDir =
|
|
62
|
-
if (!
|
|
63
|
-
|
|
61
|
+
const cascadeDir = path21.join(workspacePath, ".cascade");
|
|
62
|
+
if (!fs20.existsSync(cascadeDir)) {
|
|
63
|
+
fs20.mkdirSync(cascadeDir, { recursive: true });
|
|
64
64
|
}
|
|
65
|
-
this.keyPath =
|
|
66
|
-
this.dbPath =
|
|
65
|
+
this.keyPath = path21.join(cascadeDir, "audit_log.key");
|
|
66
|
+
this.dbPath = path21.join(cascadeDir, "audit_log.db");
|
|
67
67
|
this.initEncryptionKey();
|
|
68
68
|
this.db = new Database(this.dbPath);
|
|
69
69
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -93,11 +93,11 @@ var init_audit_logger = __esm({
|
|
|
93
93
|
return crypto3.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
94
94
|
}
|
|
95
95
|
initEncryptionKey() {
|
|
96
|
-
if (
|
|
97
|
-
this.encryptionKey =
|
|
96
|
+
if (fs20.existsSync(this.keyPath)) {
|
|
97
|
+
this.encryptionKey = fs20.readFileSync(this.keyPath);
|
|
98
98
|
} else {
|
|
99
99
|
this.encryptionKey = crypto3.randomBytes(32);
|
|
100
|
-
|
|
100
|
+
fs20.writeFileSync(this.keyPath, this.encryptionKey);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
encrypt(text) {
|
|
@@ -175,9 +175,9 @@ var init_audit_logger = __esm({
|
|
|
175
175
|
dumpDebugIfNeeded() {
|
|
176
176
|
if (!this.debugMode) return;
|
|
177
177
|
try {
|
|
178
|
-
const dumpPath =
|
|
178
|
+
const dumpPath = path21.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
|
|
179
179
|
const entries = this.getAllLogs();
|
|
180
|
-
|
|
180
|
+
fs20.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
181
181
|
} catch (err) {
|
|
182
182
|
console.error("Failed to dump debug audit logs", err);
|
|
183
183
|
}
|
|
@@ -190,7 +190,7 @@ var init_audit_logger = __esm({
|
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// src/constants.ts
|
|
193
|
-
var CASCADE_VERSION = "0.
|
|
193
|
+
var CASCADE_VERSION = "0.19.0";
|
|
194
194
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
195
195
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
196
196
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -202,6 +202,7 @@ var CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
|
202
202
|
var GLOBAL_CONFIG_DIR = ".cascade-ai";
|
|
203
203
|
var GLOBAL_DB_FILE = "memory.db";
|
|
204
204
|
var GLOBAL_KEYSTORE_FILE = "keystore.enc";
|
|
205
|
+
var GLOBAL_CREDENTIALS_FILE = "credentials.json";
|
|
205
206
|
var GLOBAL_RUNTIME_DB_FILE = "runtime.db";
|
|
206
207
|
var DEFAULT_DASHBOARD_PORT = 4891;
|
|
207
208
|
var DEFAULT_API_PORT = 4892;
|
|
@@ -992,6 +993,23 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
992
993
|
};
|
|
993
994
|
|
|
994
995
|
// src/providers/azure.ts
|
|
996
|
+
function azureModelForDeployment(cfg) {
|
|
997
|
+
if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
|
|
998
|
+
const id = cfg.deploymentName.trim();
|
|
999
|
+
return {
|
|
1000
|
+
id,
|
|
1001
|
+
name: cfg.label?.trim() || id,
|
|
1002
|
+
provider: "azure",
|
|
1003
|
+
contextWindow: 128e3,
|
|
1004
|
+
isVisionCapable: false,
|
|
1005
|
+
inputCostPer1kTokens: 25e-4,
|
|
1006
|
+
outputCostPer1kTokens: 0.01,
|
|
1007
|
+
maxOutputTokens: 16e3,
|
|
1008
|
+
supportsStreaming: true,
|
|
1009
|
+
isLocal: false,
|
|
1010
|
+
supportsToolUse: true
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
995
1013
|
var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
996
1014
|
constructor(config, model) {
|
|
997
1015
|
const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -1012,7 +1030,8 @@ var AzureOpenAIProvider = class extends OpenAIProvider {
|
|
|
1012
1030
|
});
|
|
1013
1031
|
}
|
|
1014
1032
|
async listModels() {
|
|
1015
|
-
|
|
1033
|
+
const fromDeployment = azureModelForDeployment(this.config);
|
|
1034
|
+
return [fromDeployment ?? this.model];
|
|
1016
1035
|
}
|
|
1017
1036
|
async isAvailable() {
|
|
1018
1037
|
try {
|
|
@@ -2164,7 +2183,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
2164
2183
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
2165
2184
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
2166
2185
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
2167
|
-
var DEFAULT_CACHE_FILE =
|
|
2186
|
+
var DEFAULT_CACHE_FILE = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
2168
2187
|
function normalizeModelId(id) {
|
|
2169
2188
|
let s = id.toLowerCase();
|
|
2170
2189
|
const slash = s.lastIndexOf("/");
|
|
@@ -2309,7 +2328,7 @@ var LiveDataProvider = class {
|
|
|
2309
2328
|
}
|
|
2310
2329
|
async saveCache() {
|
|
2311
2330
|
try {
|
|
2312
|
-
await fs4.mkdir(
|
|
2331
|
+
await fs4.mkdir(path21.dirname(this.opts.cacheFile), { recursive: true });
|
|
2313
2332
|
const cache = {
|
|
2314
2333
|
fetchedAt: this.fetchedAt,
|
|
2315
2334
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -2521,6 +2540,12 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
2521
2540
|
const results = await Promise.all(ocConfigs.map((cfg) => this.discoverOpenAICompatibleModels(cfg)));
|
|
2522
2541
|
if (results.some(Boolean)) this.selector.markProviderAvailable("openai-compatible");
|
|
2523
2542
|
}
|
|
2543
|
+
if (availableProviders.has("azure")) {
|
|
2544
|
+
for (const cfg of config.providers) {
|
|
2545
|
+
const model = azureModelForDeployment(cfg);
|
|
2546
|
+
if (model) this.selector.addDynamicModel(model);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2524
2549
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
2525
2550
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
2526
2551
|
if (!override || override === "auto") continue;
|
|
@@ -3127,7 +3152,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
3127
3152
|
ensureProvider(model, configs) {
|
|
3128
3153
|
const key = `${model.provider}:${model.id}`;
|
|
3129
3154
|
if (this.providers.has(key)) return;
|
|
3130
|
-
const cfg = configs.find((c) => c.type === model.provider) ?? { type: model.provider };
|
|
3155
|
+
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 };
|
|
3131
3156
|
const provider = this.createProvider(cfg, model);
|
|
3132
3157
|
this.providers.set(key, provider);
|
|
3133
3158
|
}
|
|
@@ -3287,6 +3312,12 @@ var BaseTier = class extends EventEmitter {
|
|
|
3287
3312
|
* which would interleave, are not tagged.
|
|
3288
3313
|
*/
|
|
3289
3314
|
isPresenter = false;
|
|
3315
|
+
/**
|
|
3316
|
+
* The model actually serving this tier (`provider:id`), once resolved —
|
|
3317
|
+
* rides on every tier:status event so the desktop can show which model ran
|
|
3318
|
+
* which node (Cockpit node panel / Why panel).
|
|
3319
|
+
*/
|
|
3320
|
+
servingModel;
|
|
3290
3321
|
constructor(role, id, parentId) {
|
|
3291
3322
|
super();
|
|
3292
3323
|
this.role = role;
|
|
@@ -3311,11 +3342,16 @@ var BaseTier = class extends EventEmitter {
|
|
|
3311
3342
|
label: this.label,
|
|
3312
3343
|
status,
|
|
3313
3344
|
timestamp,
|
|
3314
|
-
output
|
|
3345
|
+
output,
|
|
3346
|
+
model: this.servingModel
|
|
3315
3347
|
};
|
|
3316
3348
|
this.emit("status", event);
|
|
3317
3349
|
this.emit("tier:status", event);
|
|
3318
3350
|
}
|
|
3351
|
+
/** Record the model serving this tier; future status events carry it. */
|
|
3352
|
+
setServingModel(model) {
|
|
3353
|
+
this.servingModel = model || void 0;
|
|
3354
|
+
}
|
|
3319
3355
|
setLabel(label) {
|
|
3320
3356
|
this.label = label;
|
|
3321
3357
|
}
|
|
@@ -3338,7 +3374,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
3338
3374
|
currentAction: update.currentAction,
|
|
3339
3375
|
progressPct: update.progressPct,
|
|
3340
3376
|
timestamp,
|
|
3341
|
-
output: update.output
|
|
3377
|
+
output: update.output,
|
|
3378
|
+
model: this.servingModel
|
|
3342
3379
|
});
|
|
3343
3380
|
}
|
|
3344
3381
|
buildMessage(type, to, payload) {
|
|
@@ -3704,6 +3741,21 @@ Available tools: ${tools.map((t) => t.name).join(", ")}.
|
|
|
3704
3741
|
When you have enough information, stop calling tools and write your final answer.`;
|
|
3705
3742
|
}
|
|
3706
3743
|
|
|
3744
|
+
// src/utils/truncate.ts
|
|
3745
|
+
function truncateForContext(text, maxChars = 12e3) {
|
|
3746
|
+
if (text.length <= maxChars) return text;
|
|
3747
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
3748
|
+
const tailLen = maxChars - headLen;
|
|
3749
|
+
const head = text.slice(0, headLen);
|
|
3750
|
+
const tail = text.slice(-tailLen);
|
|
3751
|
+
const elided = text.length - headLen - tailLen;
|
|
3752
|
+
return `${head}
|
|
3753
|
+
|
|
3754
|
+
[... ${elided.toLocaleString()} characters elided to keep context small \u2014 re-read the file with a line range if you need the middle ...]
|
|
3755
|
+
|
|
3756
|
+
${tail}`;
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3707
3759
|
// src/core/tiers/t3-worker.ts
|
|
3708
3760
|
var CriticalToolError = class extends Error {
|
|
3709
3761
|
constructor(message, toolName) {
|
|
@@ -4011,6 +4063,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
4011
4063
|
} catch {
|
|
4012
4064
|
}
|
|
4013
4065
|
const effectiveModel = subtaskModel ?? this.router.getModelForTier("T3");
|
|
4066
|
+
if (effectiveModel) this.setServingModel(`${effectiveModel.provider}:${effectiveModel.id}`);
|
|
4014
4067
|
const useTextTools = effectiveModel?.supportsToolUse === false && tools.length > 0;
|
|
4015
4068
|
let sentFullTextContract = false;
|
|
4016
4069
|
let textContractSignature = "";
|
|
@@ -4108,7 +4161,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
4108
4161
|
const toolResult = await this.executeTool(tc);
|
|
4109
4162
|
await this.context.addMessage({
|
|
4110
4163
|
role: "tool",
|
|
4111
|
-
content: toolResult,
|
|
4164
|
+
content: truncateForContext(toolResult),
|
|
4112
4165
|
toolCallId: tc.id
|
|
4113
4166
|
});
|
|
4114
4167
|
}
|
|
@@ -4212,8 +4265,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
4212
4265
|
tierId: this.id,
|
|
4213
4266
|
sessionId: this.taskId,
|
|
4214
4267
|
requireApproval: false,
|
|
4215
|
-
saveSnapshot: async (
|
|
4216
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
4268
|
+
saveSnapshot: async (path22, content) => {
|
|
4269
|
+
this.store?.addFileSnapshot(this.taskId, path22, content);
|
|
4217
4270
|
},
|
|
4218
4271
|
sendPeerSync: (to, syncType, content) => {
|
|
4219
4272
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -4365,15 +4418,17 @@ ${assignment.expectedOutput}`;
|
|
|
4365
4418
|
};
|
|
4366
4419
|
}
|
|
4367
4420
|
requiresArtifact() {
|
|
4421
|
+
if (this.assignment?.files?.length) return true;
|
|
4368
4422
|
const haystack = `${this.assignment?.description ?? ""}
|
|
4369
4423
|
${this.assignment?.expectedOutput ?? ""}`;
|
|
4370
4424
|
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);
|
|
4371
4425
|
}
|
|
4372
4426
|
extractArtifactPaths(assignment) {
|
|
4427
|
+
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
4373
4428
|
const haystack = `${assignment.description}
|
|
4374
4429
|
${assignment.expectedOutput}`;
|
|
4375
4430
|
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) ?? [];
|
|
4376
|
-
return [
|
|
4431
|
+
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
4377
4432
|
}
|
|
4378
4433
|
async verifyArtifacts(assignment) {
|
|
4379
4434
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
@@ -4383,7 +4438,7 @@ ${assignment.expectedOutput}`;
|
|
|
4383
4438
|
const { promisify: promisify4 } = await import('util');
|
|
4384
4439
|
const execAsync2 = promisify4(exec2);
|
|
4385
4440
|
for (const artifactPath of artifactPaths) {
|
|
4386
|
-
const absolutePath =
|
|
4441
|
+
const absolutePath = path21.resolve(process.cwd(), artifactPath);
|
|
4387
4442
|
try {
|
|
4388
4443
|
const stat = await fs4.stat(absolutePath);
|
|
4389
4444
|
if (!stat.isFile()) {
|
|
@@ -4404,7 +4459,7 @@ ${assignment.expectedOutput}`;
|
|
|
4404
4459
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
4405
4460
|
continue;
|
|
4406
4461
|
}
|
|
4407
|
-
const ext =
|
|
4462
|
+
const ext = path21.extname(absolutePath).toLowerCase();
|
|
4408
4463
|
try {
|
|
4409
4464
|
if (ext === ".ts" || ext === ".tsx") {
|
|
4410
4465
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -4496,7 +4551,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4496
4551
|
Assignment: ${assignment.description}
|
|
4497
4552
|
Expected output: ${assignment.expectedOutput}
|
|
4498
4553
|
Constraints: ${assignment.constraints.join("; ")}
|
|
4499
|
-
|
|
4554
|
+
${assignment.acceptance?.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
4555
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
4556
|
+
` : ""}
|
|
4500
4557
|
Output to test:
|
|
4501
4558
|
${output}
|
|
4502
4559
|
|
|
@@ -4585,17 +4642,27 @@ Your subtask:
|
|
|
4585
4642
|
- Title: ${assignment.subtaskTitle}
|
|
4586
4643
|
- Description: ${assignment.description}
|
|
4587
4644
|
- Expected output: ${assignment.expectedOutput}
|
|
4588
|
-
- Constraints: ${assignment.constraints.join("; ")}
|
|
4645
|
+
- Constraints: ${assignment.constraints.join("; ")}${assignment.files?.length ? `
|
|
4646
|
+
- Files you own (create/edit ONLY these): ${assignment.files.join(", ")}` : ""}${assignment.acceptance?.length ? `
|
|
4647
|
+
- Definition of done: ${assignment.acceptance.join("; ")}` : ""}`;
|
|
4589
4648
|
}
|
|
4590
4649
|
buildInitialPrompt(assignment) {
|
|
4591
4650
|
return `Execute the following subtask completely:
|
|
4592
4651
|
|
|
4593
4652
|
**${assignment.subtaskTitle}**
|
|
4594
|
-
|
|
4653
|
+
${assignment.contextBrief ? `
|
|
4654
|
+
Context: ${assignment.contextBrief}
|
|
4655
|
+
` : ""}
|
|
4595
4656
|
${assignment.description}
|
|
4596
4657
|
|
|
4597
4658
|
Expected output: ${assignment.expectedOutput}
|
|
4598
|
-
|
|
4659
|
+
${assignment.files?.length ? `
|
|
4660
|
+
Files you own (create or edit exactly these paths):
|
|
4661
|
+
${assignment.files.map((f) => `- ${f}`).join("\n")}
|
|
4662
|
+
` : ""}${assignment.acceptance?.length ? `
|
|
4663
|
+
Definition of done (your output must satisfy ALL of these):
|
|
4664
|
+
${assignment.acceptance.map((a) => `- ${a}`).join("\n")}
|
|
4665
|
+
` : ""}
|
|
4599
4666
|
Constraints:
|
|
4600
4667
|
${assignment.constraints.map((c) => `- ${c}`).join("\n")}
|
|
4601
4668
|
|
|
@@ -5076,6 +5143,8 @@ var T2Manager = class extends BaseTier {
|
|
|
5076
5143
|
this.assignment = assignment;
|
|
5077
5144
|
this.taskId = taskId;
|
|
5078
5145
|
this.setLabel(assignment.sectionTitle);
|
|
5146
|
+
const m = this.router.getModelForTier("T2");
|
|
5147
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
5079
5148
|
this.setStatus("ACTIVE");
|
|
5080
5149
|
this.sendStatusUpdate({
|
|
5081
5150
|
progressPct: 0,
|
|
@@ -5162,7 +5231,7 @@ Guidance (must be followed): ${decision.note}`
|
|
|
5162
5231
|
// ── Private ──────────────────────────────────
|
|
5163
5232
|
async decomposeSection(assignment) {
|
|
5164
5233
|
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");
|
|
5165
|
-
const prompt = `Decompose this section into
|
|
5234
|
+
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).
|
|
5166
5235
|
|
|
5167
5236
|
Section: ${assignment.sectionTitle}
|
|
5168
5237
|
Description: ${assignment.description}
|
|
@@ -5181,6 +5250,9 @@ Return a JSON array of subtask objects, each with:
|
|
|
5181
5250
|
- peerT3Ids: string[] (empty for now)
|
|
5182
5251
|
- dependsOn: string[] (array of subtaskIds this task depends on to start)
|
|
5183
5252
|
- executionMode: "parallel|sequential" (default is parallel)
|
|
5253
|
+
- files: string[] (the EXACT relative paths this subtask creates or edits)
|
|
5254
|
+
- acceptance: string[] (1-3 mechanically checkable done-criteria: file exists / contains X / command exits 0)
|
|
5255
|
+
- contextBrief: string (1-3 short sentences with ALL the background the worker needs \u2014 it sees nothing else)
|
|
5184
5256
|
|
|
5185
5257
|
Return ONLY the JSON array.`;
|
|
5186
5258
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -5778,6 +5850,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
5778
5850
|
this.signal = signal;
|
|
5779
5851
|
this.taskId = randomUUID();
|
|
5780
5852
|
this.setLabel("Administrator");
|
|
5853
|
+
const m = this.router.getModelForTier("T1");
|
|
5854
|
+
if (m) this.setServingModel(`${m.provider}:${m.id}`);
|
|
5781
5855
|
this.setStatus("ACTIVE");
|
|
5782
5856
|
this.taskGoal = userPrompt;
|
|
5783
5857
|
this.sendStatusUpdate({
|
|
@@ -6024,10 +6098,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
6024
6098
|
"description": "Run npm init",
|
|
6025
6099
|
"expectedOutput": "package.json created",
|
|
6026
6100
|
"constraints": [],
|
|
6027
|
-
"dependsOn": []
|
|
6101
|
+
"dependsOn": [],
|
|
6102
|
+
"files": ["package.json"], // \u2190 exact paths this subtask owns
|
|
6103
|
+
"acceptance": ["package.json exists and parses as JSON"], // \u2190 objectively checkable
|
|
6104
|
+
"contextBrief": "Fresh Node 20 project; npm available." // \u2190 ALL the background the worker gets
|
|
6028
6105
|
}]
|
|
6029
6106
|
}, {
|
|
6030
|
-
"sectionId": "s2",
|
|
6107
|
+
"sectionId": "s2",
|
|
6031
6108
|
"sectionTitle": "Write Tests",
|
|
6032
6109
|
"description": "Write tests for the project",
|
|
6033
6110
|
"expectedOutput": "Tests passing",
|
|
@@ -6037,7 +6114,13 @@ Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
|
6037
6114
|
}]
|
|
6038
6115
|
}
|
|
6039
6116
|
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
6040
|
-
Leave dependsOn empty for sections that can run immediately in parallel
|
|
6117
|
+
Leave dependsOn empty for sections that can run immediately in parallel.
|
|
6118
|
+
|
|
6119
|
+
SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute from their slice ALONE):
|
|
6120
|
+
- "files": the exact relative paths the subtask creates or edits. Never vague ("some files"); always concrete.
|
|
6121
|
+
- "acceptance": 1-3 checks a reviewer could verify mechanically (file exists / contains X / command exits 0). These define done.
|
|
6122
|
+
- "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.
|
|
6123
|
+
- 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.`;
|
|
6041
6124
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
6042
6125
|
const result = await this.router.generate("T1", {
|
|
6043
6126
|
messages,
|
|
@@ -6486,16 +6569,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
6486
6569
|
if (typeof input !== "string" || input.length === 0) {
|
|
6487
6570
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
6488
6571
|
}
|
|
6489
|
-
const root =
|
|
6490
|
-
const abs =
|
|
6491
|
-
const rel =
|
|
6492
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
6572
|
+
const root = path21.resolve(workspaceRoot);
|
|
6573
|
+
const abs = path21.isAbsolute(input) ? path21.resolve(input) : path21.resolve(root, input);
|
|
6574
|
+
const rel = path21.relative(root, abs);
|
|
6575
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
6493
6576
|
throw new WorkspaceSandboxError(input, root);
|
|
6494
6577
|
}
|
|
6495
6578
|
try {
|
|
6496
|
-
const real =
|
|
6497
|
-
const realRel =
|
|
6498
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
6579
|
+
const real = fs20.realpathSync(abs);
|
|
6580
|
+
const realRel = path21.relative(root, real);
|
|
6581
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path21.isAbsolute(realRel))) {
|
|
6499
6582
|
throw new WorkspaceSandboxError(input, root);
|
|
6500
6583
|
}
|
|
6501
6584
|
} catch (e) {
|
|
@@ -6556,7 +6639,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
6556
6639
|
} catch {
|
|
6557
6640
|
}
|
|
6558
6641
|
}
|
|
6559
|
-
await fs4.mkdir(
|
|
6642
|
+
await fs4.mkdir(path21.dirname(absPath), { recursive: true });
|
|
6560
6643
|
await fs4.writeFile(absPath, content, "utf-8");
|
|
6561
6644
|
return `Written ${content.length} characters to ${filePath}`;
|
|
6562
6645
|
}
|
|
@@ -7054,7 +7137,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
7054
7137
|
};
|
|
7055
7138
|
async function fileToImageAttachment(filePath) {
|
|
7056
7139
|
const data = await fs4.readFile(filePath);
|
|
7057
|
-
const ext =
|
|
7140
|
+
const ext = path21.extname(filePath).toLowerCase();
|
|
7058
7141
|
const mimeMap = {
|
|
7059
7142
|
".jpg": "image/jpeg",
|
|
7060
7143
|
".jpeg": "image/jpeg",
|
|
@@ -7088,14 +7171,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
7088
7171
|
const filePath = input["path"];
|
|
7089
7172
|
const content = input["content"];
|
|
7090
7173
|
const title = input["title"];
|
|
7091
|
-
const dir =
|
|
7092
|
-
if (!
|
|
7093
|
-
|
|
7174
|
+
const dir = path21.dirname(filePath);
|
|
7175
|
+
if (!fs20.existsSync(dir)) {
|
|
7176
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
7094
7177
|
}
|
|
7095
7178
|
return new Promise((resolve, reject) => {
|
|
7096
7179
|
try {
|
|
7097
7180
|
const doc = new PDFDocument({ margin: 50 });
|
|
7098
|
-
const stream =
|
|
7181
|
+
const stream = fs20.createWriteStream(filePath);
|
|
7099
7182
|
doc.pipe(stream);
|
|
7100
7183
|
if (title) {
|
|
7101
7184
|
doc.info["Title"] = title;
|
|
@@ -7173,22 +7256,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
7173
7256
|
}
|
|
7174
7257
|
cmdPrefix = NODE_CMD;
|
|
7175
7258
|
}
|
|
7176
|
-
const tmpDir =
|
|
7177
|
-
if (!
|
|
7178
|
-
|
|
7259
|
+
const tmpDir = path21.join(this.workspaceRoot, ".cascade", "tmp");
|
|
7260
|
+
if (!fs20.existsSync(tmpDir)) {
|
|
7261
|
+
fs20.mkdirSync(tmpDir, { recursive: true });
|
|
7179
7262
|
}
|
|
7180
7263
|
const extension = language === "python" ? "py" : "js";
|
|
7181
7264
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
7182
|
-
const filePath =
|
|
7183
|
-
|
|
7265
|
+
const filePath = path21.join(tmpDir, fileName);
|
|
7266
|
+
fs20.writeFileSync(filePath, code, "utf-8");
|
|
7184
7267
|
const execArgs = [filePath, ...args];
|
|
7185
7268
|
return new Promise((resolve) => {
|
|
7186
7269
|
const startMs = Date.now();
|
|
7187
7270
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
7188
7271
|
const duration = Date.now() - startMs;
|
|
7189
7272
|
try {
|
|
7190
|
-
if (
|
|
7191
|
-
|
|
7273
|
+
if (fs20.existsSync(filePath)) {
|
|
7274
|
+
fs20.unlinkSync(filePath);
|
|
7192
7275
|
}
|
|
7193
7276
|
} catch (cleanupErr) {
|
|
7194
7277
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -7339,30 +7422,56 @@ async function searchTavily(query, apiKey, maxResults) {
|
|
|
7339
7422
|
engine: "tavily"
|
|
7340
7423
|
}));
|
|
7341
7424
|
}
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7425
|
+
var BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
7426
|
+
function unwrapDdgRedirect(href) {
|
|
7427
|
+
try {
|
|
7428
|
+
const url = new URL(href.startsWith("//") ? `https:${href}` : href, "https://duckduckgo.com");
|
|
7429
|
+
if (/(^|\.)duckduckgo\.com$/i.test(url.hostname) && url.pathname.startsWith("/l/")) {
|
|
7430
|
+
const target = url.searchParams.get("uddg");
|
|
7431
|
+
if (target) return decodeURIComponent(target);
|
|
7432
|
+
}
|
|
7433
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : href;
|
|
7434
|
+
} catch {
|
|
7435
|
+
return href;
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
function stripTags(html) {
|
|
7439
|
+
return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
7440
|
+
}
|
|
7441
|
+
function decodeEntities(text) {
|
|
7442
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'").replace(/ /g, " ");
|
|
7443
|
+
}
|
|
7444
|
+
function parseDdgAnchors(html, anchorClass, snippetClass) {
|
|
7445
|
+
const anchorRe = new RegExp(`<a\\b[^>]*class=["']?[^"'>]*\\b${anchorClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/a>`, "gi");
|
|
7446
|
+
const snippetRe = new RegExp(`class=["']?[^"'>]*\\b${snippetClass}\\b[^"'>]*["']?[^>]*>([\\s\\S]*?)<\\/(?:td|a|div|span)>`, "gi");
|
|
7447
|
+
const results = [];
|
|
7353
7448
|
let m;
|
|
7354
|
-
while ((m =
|
|
7355
|
-
|
|
7449
|
+
while ((m = anchorRe.exec(html)) !== null) {
|
|
7450
|
+
const tag = m[0];
|
|
7451
|
+
const href = /href=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7452
|
+
const title = decodeEntities(stripTags(m[1] ?? ""));
|
|
7453
|
+
if (!href || !title) continue;
|
|
7454
|
+
results.push({ title, url: unwrapDdgRedirect(decodeEntities(href)), snippet: "" });
|
|
7356
7455
|
}
|
|
7357
|
-
|
|
7358
|
-
|
|
7456
|
+
const snippets = [];
|
|
7457
|
+
while ((m = snippetRe.exec(html)) !== null) {
|
|
7458
|
+
snippets.push(decodeEntities(stripTags(m[1] ?? "")));
|
|
7359
7459
|
}
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7460
|
+
for (let i = 0; i < results.length; i++) {
|
|
7461
|
+
if (snippets[i]) results[i].snippet = snippets[i];
|
|
7462
|
+
}
|
|
7463
|
+
return results;
|
|
7464
|
+
}
|
|
7465
|
+
async function searchDuckDuckGo(query, maxResults, variant) {
|
|
7466
|
+
const base = variant === "html" ? "https://html.duckduckgo.com/html/?q=" : "https://lite.duckduckgo.com/lite/?q=";
|
|
7467
|
+
const resp = await fetch(`${base}${encodeURIComponent(query)}`, {
|
|
7468
|
+
headers: { "User-Agent": BROWSER_UA, Accept: "text/html" },
|
|
7469
|
+
signal: AbortSignal.timeout(1e4)
|
|
7470
|
+
});
|
|
7471
|
+
if (!resp.ok) throw new Error(`DuckDuckGo ${variant} returned HTTP ${resp.status}`);
|
|
7472
|
+
const html = await resp.text();
|
|
7473
|
+
const parsed = variant === "html" ? parseDdgAnchors(html, "result__a", "result__snippet") : parseDdgAnchors(html, "result-link", "result-snippet");
|
|
7474
|
+
return parsed.slice(0, maxResults).map((r) => ({ ...r, engine: `duckduckgo-${variant}` }));
|
|
7366
7475
|
}
|
|
7367
7476
|
var WebSearchTool = class extends BaseTool {
|
|
7368
7477
|
name = "web_search";
|
|
@@ -7421,12 +7530,14 @@ var WebSearchTool = class extends BaseTool {
|
|
|
7421
7530
|
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
7422
7531
|
}
|
|
7423
7532
|
}
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7533
|
+
for (const variant of ["html", "lite"]) {
|
|
7534
|
+
try {
|
|
7535
|
+
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
7536
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
7537
|
+
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
7538
|
+
} catch (err) {
|
|
7539
|
+
errors.push(`DuckDuckGo ${variant}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7540
|
+
}
|
|
7430
7541
|
}
|
|
7431
7542
|
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" : "";
|
|
7432
7543
|
return [
|
|
@@ -7467,7 +7578,7 @@ var GlobTool = class extends BaseTool {
|
|
|
7467
7578
|
};
|
|
7468
7579
|
async execute(input, _options) {
|
|
7469
7580
|
const pattern = input["pattern"];
|
|
7470
|
-
const searchPath = input["path"] ?
|
|
7581
|
+
const searchPath = input["path"] ? path21.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
7471
7582
|
const matches = await glob(pattern, {
|
|
7472
7583
|
cwd: searchPath,
|
|
7473
7584
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -7480,7 +7591,7 @@ var GlobTool = class extends BaseTool {
|
|
|
7480
7591
|
const withMtime = await Promise.all(
|
|
7481
7592
|
matches.map(async (rel) => {
|
|
7482
7593
|
try {
|
|
7483
|
-
const stat = await fs4.stat(
|
|
7594
|
+
const stat = await fs4.stat(path21.join(searchPath, rel));
|
|
7484
7595
|
return { rel, mtime: stat.mtimeMs };
|
|
7485
7596
|
} catch {
|
|
7486
7597
|
return { rel, mtime: 0 };
|
|
@@ -7529,7 +7640,7 @@ var GrepTool = class extends BaseTool {
|
|
|
7529
7640
|
};
|
|
7530
7641
|
async execute(input, _options) {
|
|
7531
7642
|
const pattern = input["pattern"];
|
|
7532
|
-
const searchPath = input["path"] ?
|
|
7643
|
+
const searchPath = input["path"] ? path21.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
7533
7644
|
const globPattern = input["glob"];
|
|
7534
7645
|
const outputMode = input["output_mode"] ?? "content";
|
|
7535
7646
|
const context = input["context"] ?? 0;
|
|
@@ -7583,12 +7694,12 @@ var GrepTool = class extends BaseTool {
|
|
|
7583
7694
|
nodir: true
|
|
7584
7695
|
});
|
|
7585
7696
|
} catch {
|
|
7586
|
-
files = [
|
|
7697
|
+
files = [path21.relative(searchPath, searchPath) || "."];
|
|
7587
7698
|
}
|
|
7588
7699
|
const results = [];
|
|
7589
7700
|
let totalCount = 0;
|
|
7590
7701
|
for (const rel of files) {
|
|
7591
|
-
const abs =
|
|
7702
|
+
const abs = path21.join(searchPath, rel);
|
|
7592
7703
|
let content;
|
|
7593
7704
|
try {
|
|
7594
7705
|
content = await fs4.readFile(abs, "utf-8");
|
|
@@ -7950,10 +8061,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
7950
8061
|
}
|
|
7951
8062
|
isIgnored(filePath) {
|
|
7952
8063
|
if (!filePath) return false;
|
|
7953
|
-
const abs =
|
|
7954
|
-
const rel =
|
|
7955
|
-
if (!rel || rel.startsWith("..") ||
|
|
7956
|
-
const posixRel = rel.split(
|
|
8064
|
+
const abs = path21.resolve(this.workspaceRoot, filePath);
|
|
8065
|
+
const rel = path21.relative(this.workspaceRoot, abs);
|
|
8066
|
+
if (!rel || rel.startsWith("..") || path21.isAbsolute(rel)) return true;
|
|
8067
|
+
const posixRel = rel.split(path21.sep).join("/");
|
|
7957
8068
|
return this.ignoreMatcher.ignores(posixRel);
|
|
7958
8069
|
}
|
|
7959
8070
|
};
|
|
@@ -8370,8 +8481,11 @@ var CascadeConfigSchema = z.object({
|
|
|
8370
8481
|
* Cascade Auto: when true, the TaskAnalyzer selects the optimal model for each
|
|
8371
8482
|
* tier based on task type and complexity, overriding the static priority lists.
|
|
8372
8483
|
* Heuristic-first with AI inference fallback (adds ~0–500ms per task).
|
|
8484
|
+
* ON by default since v0.19.0 — "Auto" without it was just a static priority
|
|
8485
|
+
* list, not the benchmark-value routing the docs describe. Explicit per-tier
|
|
8486
|
+
* model pins are unaffected; disable via config/Settings → Advanced.
|
|
8373
8487
|
*/
|
|
8374
|
-
cascadeAuto: z.boolean().default(
|
|
8488
|
+
cascadeAuto: z.boolean().default(true),
|
|
8375
8489
|
/**
|
|
8376
8490
|
* Cascade Auto trade-off bias when picking a model for a task:
|
|
8377
8491
|
* - 'balanced' (default): quality × cost-efficiency — cheap models win
|
|
@@ -8787,7 +8901,7 @@ var TaskAnalyzer = class {
|
|
|
8787
8901
|
analysisCache.clear();
|
|
8788
8902
|
}
|
|
8789
8903
|
};
|
|
8790
|
-
var DEFAULT_STATS_FILE =
|
|
8904
|
+
var DEFAULT_STATS_FILE = path21.join(os6.homedir(), ".cascade", "model-perf.json");
|
|
8791
8905
|
var ModelPerformanceTracker = class {
|
|
8792
8906
|
stats = /* @__PURE__ */ new Map();
|
|
8793
8907
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -8819,7 +8933,7 @@ var ModelPerformanceTracker = class {
|
|
|
8819
8933
|
}
|
|
8820
8934
|
async save() {
|
|
8821
8935
|
try {
|
|
8822
|
-
await fs4.mkdir(
|
|
8936
|
+
await fs4.mkdir(path21.dirname(this.statsFile), { recursive: true });
|
|
8823
8937
|
const modelsObj = {};
|
|
8824
8938
|
const featuresObj = {};
|
|
8825
8939
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -9358,7 +9472,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
9358
9472
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
9359
9473
|
async loadPersistedTools() {
|
|
9360
9474
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
9361
|
-
const file =
|
|
9475
|
+
const file = path21.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
9362
9476
|
try {
|
|
9363
9477
|
const raw = await fs4.readFile(file, "utf-8");
|
|
9364
9478
|
const specs = JSON.parse(raw);
|
|
@@ -9382,8 +9496,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
9382
9496
|
}
|
|
9383
9497
|
async persist() {
|
|
9384
9498
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
9385
|
-
const dir =
|
|
9386
|
-
const file =
|
|
9499
|
+
const dir = path21.join(this.workspacePath, ".cascade");
|
|
9500
|
+
const file = path21.join(dir, DYNAMIC_TOOLS_FILE);
|
|
9387
9501
|
try {
|
|
9388
9502
|
await fs4.mkdir(dir, { recursive: true });
|
|
9389
9503
|
await fs4.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -9403,12 +9517,12 @@ var WorldStateDB = class {
|
|
|
9403
9517
|
constructor(workspacePath, debugMode = false) {
|
|
9404
9518
|
this.workspacePath = workspacePath;
|
|
9405
9519
|
this.debugMode = debugMode;
|
|
9406
|
-
const cascadeDir =
|
|
9407
|
-
if (!
|
|
9408
|
-
|
|
9520
|
+
const cascadeDir = path21.join(workspacePath, ".cascade");
|
|
9521
|
+
if (!fs20.existsSync(cascadeDir)) {
|
|
9522
|
+
fs20.mkdirSync(cascadeDir, { recursive: true });
|
|
9409
9523
|
}
|
|
9410
|
-
this.keyPath =
|
|
9411
|
-
this.dbPath =
|
|
9524
|
+
this.keyPath = path21.join(cascadeDir, "world_state.key");
|
|
9525
|
+
this.dbPath = path21.join(cascadeDir, "world_state.db");
|
|
9412
9526
|
this.initEncryptionKey();
|
|
9413
9527
|
this.db = new Database(this.dbPath);
|
|
9414
9528
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -9438,11 +9552,11 @@ var WorldStateDB = class {
|
|
|
9438
9552
|
dbPath;
|
|
9439
9553
|
encryptionKey;
|
|
9440
9554
|
initEncryptionKey() {
|
|
9441
|
-
if (
|
|
9442
|
-
this.encryptionKey =
|
|
9555
|
+
if (fs20.existsSync(this.keyPath)) {
|
|
9556
|
+
this.encryptionKey = fs20.readFileSync(this.keyPath);
|
|
9443
9557
|
} else {
|
|
9444
9558
|
this.encryptionKey = crypto3.randomBytes(32);
|
|
9445
|
-
|
|
9559
|
+
fs20.writeFileSync(this.keyPath, this.encryptionKey);
|
|
9446
9560
|
}
|
|
9447
9561
|
}
|
|
9448
9562
|
encrypt(text) {
|
|
@@ -9583,6 +9697,22 @@ var WorldStateDB = class {
|
|
|
9583
9697
|
}
|
|
9584
9698
|
return selected.slice(0, limit).map((f) => `- ${f.entity} ${f.relation} ${f.value}`).join("\n");
|
|
9585
9699
|
}
|
|
9700
|
+
/**
|
|
9701
|
+
* Delete one fact by its (normalized) entity + relation key. Returns whether
|
|
9702
|
+
* a row was actually removed. Powers the desktop Knowledge tab's per-fact
|
|
9703
|
+
* delete — users can prune what the planner remembers about their project.
|
|
9704
|
+
*/
|
|
9705
|
+
deleteFact(entity, relation) {
|
|
9706
|
+
const e = normalizeKey(entity);
|
|
9707
|
+
const r = normalizeKey(relation);
|
|
9708
|
+
if (!e || !r) return false;
|
|
9709
|
+
const result = this.db.prepare("DELETE FROM facts WHERE entity = ? AND relation = ?").run(e, r);
|
|
9710
|
+
return result.changes > 0;
|
|
9711
|
+
}
|
|
9712
|
+
/** Delete every fact. Returns how many were removed. */
|
|
9713
|
+
clearFacts() {
|
|
9714
|
+
return this.db.prepare("DELETE FROM facts").run().changes;
|
|
9715
|
+
}
|
|
9586
9716
|
// ── Export / Import ──────────────────────────
|
|
9587
9717
|
//
|
|
9588
9718
|
// Knowledge travels DECRYPTED in the export bundle (a portable plaintext
|
|
@@ -9631,9 +9761,9 @@ var WorldStateDB = class {
|
|
|
9631
9761
|
dumpDebugIfNeeded() {
|
|
9632
9762
|
if (!this.debugMode) return;
|
|
9633
9763
|
try {
|
|
9634
|
-
const dumpPath =
|
|
9764
|
+
const dumpPath = path21.join(os6.tmpdir(), "cascade_world_state_debug.json");
|
|
9635
9765
|
const entries = this.getAllEntries();
|
|
9636
|
-
|
|
9766
|
+
fs20.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
9637
9767
|
} catch (err) {
|
|
9638
9768
|
console.error("Failed to dump debug world state", err);
|
|
9639
9769
|
}
|
|
@@ -10068,13 +10198,30 @@ ${last.partialOutput}` : "");
|
|
|
10068
10198
|
* explicit multi-part structure, so ordinary single-file asks (handled as
|
|
10069
10199
|
* Simple/Moderate) don't get over-escalated.
|
|
10070
10200
|
*/
|
|
10071
|
-
|
|
10201
|
+
/** Shared build/scale signals for the complexity floors below. */
|
|
10202
|
+
buildSignals(prompt) {
|
|
10072
10203
|
const p = prompt.trim();
|
|
10073
|
-
if (p.length < 24) return false;
|
|
10204
|
+
if (p.length < 24) return { buildVerb: false, scaleCount: 0, multiPart: false };
|
|
10074
10205
|
const buildVerb = /\b(?:build|implement|create|develop|design|scaffold|refactor|migrate|architect|set up|integrate)\b/i.test(p);
|
|
10075
|
-
const
|
|
10206
|
+
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;
|
|
10076
10207
|
const multiPart = /(?:\b(?:and|then|also|plus|as well as)\b.*\b(?:and|then|also)\b)|(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/i.test(p);
|
|
10077
|
-
return buildVerb
|
|
10208
|
+
return { buildVerb, scaleCount, multiPart };
|
|
10209
|
+
}
|
|
10210
|
+
/**
|
|
10211
|
+
* A build prompt with REAL scale: multiple system-level deliverables, or a
|
|
10212
|
+
* deliverable plus explicitly multi-part phrasing. Only these floor to the
|
|
10213
|
+
* full T1→T2→T3 hierarchy — "create a todo app" is a build prompt too, but
|
|
10214
|
+
* flooring every small build to Complex was the #1 token bomb (3-5 managers
|
|
10215
|
+
* × workers for a task one worker handles).
|
|
10216
|
+
*/
|
|
10217
|
+
looksClearlyComplex(prompt) {
|
|
10218
|
+
const s = this.buildSignals(prompt);
|
|
10219
|
+
return s.buildVerb && (s.scaleCount >= 2 || s.scaleCount >= 1 && s.multiPart);
|
|
10220
|
+
}
|
|
10221
|
+
/** A small single-deliverable build — real work, but one manager's worth. */
|
|
10222
|
+
looksLikeModerateBuild(prompt) {
|
|
10223
|
+
const s = this.buildSignals(prompt);
|
|
10224
|
+
return s.buildVerb && (s.scaleCount >= 1 || s.multiPart);
|
|
10078
10225
|
}
|
|
10079
10226
|
// Cache glob scan results per workspace path to avoid repeated I/O.
|
|
10080
10227
|
static globCache = /* @__PURE__ */ new Map();
|
|
@@ -10162,6 +10309,9 @@ ${prompt}` : prompt;
|
|
|
10162
10309
|
if (verdict !== "Complex" && this.looksClearlyComplex(prompt)) {
|
|
10163
10310
|
this.recordDecision("complexity", `Complex \u2014 heuristic floor over classifier "${verdict}": explicit multi-step build/implementation signals (T1 engaged)`);
|
|
10164
10311
|
verdict = "Complex";
|
|
10312
|
+
} else if (verdict === "Simple" && this.looksLikeModerateBuild(prompt)) {
|
|
10313
|
+
this.recordDecision("complexity", 'Moderate \u2014 heuristic floor over classifier "Simple": build signals without multi-system scale (single manager)');
|
|
10314
|
+
verdict = "Moderate";
|
|
10165
10315
|
} else {
|
|
10166
10316
|
this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
|
|
10167
10317
|
}
|
|
@@ -10543,7 +10693,7 @@ var Keystore = class {
|
|
|
10543
10693
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
10544
10694
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
10545
10695
|
this.backend = "keytar";
|
|
10546
|
-
if (password &&
|
|
10696
|
+
if (password && fs20.existsSync(this.storePath)) {
|
|
10547
10697
|
try {
|
|
10548
10698
|
const fileEntries = this.decryptFile(password);
|
|
10549
10699
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -10562,7 +10712,7 @@ var Keystore = class {
|
|
|
10562
10712
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
10563
10713
|
);
|
|
10564
10714
|
}
|
|
10565
|
-
if (!
|
|
10715
|
+
if (!fs20.existsSync(this.storePath)) {
|
|
10566
10716
|
const salt = crypto3.randomBytes(SALT_LEN);
|
|
10567
10717
|
this.masterKey = this.deriveKey(password, salt);
|
|
10568
10718
|
this.writeWithSalt({}, salt);
|
|
@@ -10576,7 +10726,7 @@ var Keystore = class {
|
|
|
10576
10726
|
}
|
|
10577
10727
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
10578
10728
|
unlockSync(password) {
|
|
10579
|
-
if (!
|
|
10729
|
+
if (!fs20.existsSync(this.storePath)) {
|
|
10580
10730
|
const salt = crypto3.randomBytes(SALT_LEN);
|
|
10581
10731
|
this.masterKey = this.deriveKey(password, salt);
|
|
10582
10732
|
this.writeWithSalt({}, salt);
|
|
@@ -10634,7 +10784,7 @@ var Keystore = class {
|
|
|
10634
10784
|
}
|
|
10635
10785
|
}
|
|
10636
10786
|
decryptFile(password, knownSalt) {
|
|
10637
|
-
if (!
|
|
10787
|
+
if (!fs20.existsSync(this.storePath)) return {};
|
|
10638
10788
|
try {
|
|
10639
10789
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
10640
10790
|
const useSalt = knownSalt ?? salt;
|
|
@@ -10656,8 +10806,8 @@ var Keystore = class {
|
|
|
10656
10806
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
10657
10807
|
const tag = cipher.getAuthTag();
|
|
10658
10808
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
10659
|
-
|
|
10660
|
-
|
|
10809
|
+
fs20.mkdirSync(path21.dirname(this.storePath), { recursive: true });
|
|
10810
|
+
fs20.writeFileSync(this.storePath, out, { mode: 384 });
|
|
10661
10811
|
}
|
|
10662
10812
|
writeWithSalt(data, salt) {
|
|
10663
10813
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -10667,11 +10817,11 @@ var Keystore = class {
|
|
|
10667
10817
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
10668
10818
|
const tag = cipher.getAuthTag();
|
|
10669
10819
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
10670
|
-
|
|
10671
|
-
|
|
10820
|
+
fs20.mkdirSync(path21.dirname(this.storePath), { recursive: true });
|
|
10821
|
+
fs20.writeFileSync(this.storePath, out, { mode: 384 });
|
|
10672
10822
|
}
|
|
10673
10823
|
readRaw() {
|
|
10674
|
-
const buf =
|
|
10824
|
+
const buf = fs20.readFileSync(this.storePath);
|
|
10675
10825
|
let offset = 0;
|
|
10676
10826
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
10677
10827
|
offset += SALT_LEN;
|
|
@@ -10704,7 +10854,7 @@ var CascadeIgnore = class {
|
|
|
10704
10854
|
]);
|
|
10705
10855
|
}
|
|
10706
10856
|
async load(workspacePath) {
|
|
10707
|
-
const filePath =
|
|
10857
|
+
const filePath = path21.join(workspacePath, ".cascadeignore");
|
|
10708
10858
|
try {
|
|
10709
10859
|
const content = await fs4.readFile(filePath, "utf-8");
|
|
10710
10860
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -10715,7 +10865,7 @@ var CascadeIgnore = class {
|
|
|
10715
10865
|
}
|
|
10716
10866
|
isIgnored(filePath, workspacePath) {
|
|
10717
10867
|
try {
|
|
10718
|
-
const relative = workspacePath ?
|
|
10868
|
+
const relative = workspacePath ? path21.relative(workspacePath, filePath) : filePath;
|
|
10719
10869
|
return this.ig.ignores(relative);
|
|
10720
10870
|
} catch {
|
|
10721
10871
|
return false;
|
|
@@ -10726,7 +10876,7 @@ var CascadeIgnore = class {
|
|
|
10726
10876
|
}
|
|
10727
10877
|
};
|
|
10728
10878
|
async function loadCascadeMd(workspacePath) {
|
|
10729
|
-
const filePath =
|
|
10879
|
+
const filePath = path21.join(workspacePath, "CASCADE.md");
|
|
10730
10880
|
try {
|
|
10731
10881
|
const raw = await fs4.readFile(filePath, "utf-8");
|
|
10732
10882
|
return parseCascadeMd(raw);
|
|
@@ -10757,7 +10907,7 @@ ${raw.trim()}`;
|
|
|
10757
10907
|
var MemoryStore = class _MemoryStore {
|
|
10758
10908
|
db;
|
|
10759
10909
|
constructor(dbPath) {
|
|
10760
|
-
|
|
10910
|
+
fs20.mkdirSync(path21.dirname(dbPath), { recursive: true });
|
|
10761
10911
|
try {
|
|
10762
10912
|
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
10763
10913
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -11592,6 +11742,60 @@ Original error: ${err.message}`
|
|
|
11592
11742
|
};
|
|
11593
11743
|
}
|
|
11594
11744
|
};
|
|
11745
|
+
function credentialsPath(globalDir) {
|
|
11746
|
+
return path21.join(globalDir, GLOBAL_CREDENTIALS_FILE);
|
|
11747
|
+
}
|
|
11748
|
+
function providerKey(p) {
|
|
11749
|
+
if (p.type === "azure") {
|
|
11750
|
+
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
11751
|
+
}
|
|
11752
|
+
return p.type;
|
|
11753
|
+
}
|
|
11754
|
+
function isPersistable(p) {
|
|
11755
|
+
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
11756
|
+
}
|
|
11757
|
+
function loadGlobalCredentials(globalDir) {
|
|
11758
|
+
try {
|
|
11759
|
+
const raw = fs20.readFileSync(credentialsPath(globalDir), "utf-8");
|
|
11760
|
+
const parsed = JSON.parse(raw);
|
|
11761
|
+
if (!Array.isArray(parsed.providers)) return [];
|
|
11762
|
+
return parsed.providers.filter(
|
|
11763
|
+
(p) => Boolean(p) && typeof p.type === "string"
|
|
11764
|
+
);
|
|
11765
|
+
} catch {
|
|
11766
|
+
return [];
|
|
11767
|
+
}
|
|
11768
|
+
}
|
|
11769
|
+
function saveGlobalCredentials(globalDir, providers) {
|
|
11770
|
+
const filePath = credentialsPath(globalDir);
|
|
11771
|
+
fs20.mkdirSync(globalDir, { recursive: true, mode: 448 });
|
|
11772
|
+
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
11773
|
+
const tmp = `${filePath}.tmp`;
|
|
11774
|
+
fs20.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
11775
|
+
fs20.renameSync(tmp, filePath);
|
|
11776
|
+
try {
|
|
11777
|
+
fs20.chmodSync(filePath, 384);
|
|
11778
|
+
} catch {
|
|
11779
|
+
}
|
|
11780
|
+
}
|
|
11781
|
+
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
11782
|
+
const merged = [...workspaceProviders];
|
|
11783
|
+
const byKey = new Map(merged.map((p) => [providerKey(p), p]));
|
|
11784
|
+
for (const g of globalProviders) {
|
|
11785
|
+
const existing = byKey.get(providerKey(g));
|
|
11786
|
+
if (!existing) {
|
|
11787
|
+
merged.push({ ...g });
|
|
11788
|
+
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
11789
|
+
continue;
|
|
11790
|
+
}
|
|
11791
|
+
if (!existing.apiKey && g.apiKey) existing.apiKey = g.apiKey;
|
|
11792
|
+
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
11793
|
+
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
11794
|
+
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
11795
|
+
if (!existing.label && g.label) existing.label = g.label;
|
|
11796
|
+
}
|
|
11797
|
+
return merged;
|
|
11798
|
+
}
|
|
11595
11799
|
|
|
11596
11800
|
// src/config/index.ts
|
|
11597
11801
|
var ConfigManager = class {
|
|
@@ -11602,18 +11806,20 @@ var ConfigManager = class {
|
|
|
11602
11806
|
cascadeMd = null;
|
|
11603
11807
|
workspacePath;
|
|
11604
11808
|
globalDir;
|
|
11605
|
-
|
|
11809
|
+
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
11810
|
+
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
11606
11811
|
this.workspacePath = workspacePath;
|
|
11607
|
-
this.globalDir =
|
|
11812
|
+
this.globalDir = globalDirOverride ?? path21.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
11608
11813
|
}
|
|
11609
11814
|
async load() {
|
|
11610
11815
|
this.config = await this.loadConfig();
|
|
11611
11816
|
this.ignore = new CascadeIgnore();
|
|
11612
11817
|
await this.ignore.load(this.workspacePath);
|
|
11613
11818
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
11614
|
-
this.keystore = new Keystore(
|
|
11615
|
-
this.store = new MemoryStore(
|
|
11819
|
+
this.keystore = new Keystore(path21.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
11820
|
+
this.store = new MemoryStore(path21.join(this.workspacePath, CASCADE_DB_FILE));
|
|
11616
11821
|
await this.injectEnvKeys();
|
|
11822
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
11617
11823
|
await this.ensureDefaultIdentity();
|
|
11618
11824
|
}
|
|
11619
11825
|
getConfig() {
|
|
@@ -11635,9 +11841,14 @@ var ConfigManager = class {
|
|
|
11635
11841
|
return this.workspacePath;
|
|
11636
11842
|
}
|
|
11637
11843
|
async save() {
|
|
11638
|
-
const configPath =
|
|
11639
|
-
await fs4.mkdir(
|
|
11844
|
+
const configPath = path21.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
11845
|
+
await fs4.mkdir(path21.dirname(configPath), { recursive: true });
|
|
11640
11846
|
await fs4.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
11847
|
+
try {
|
|
11848
|
+
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
11849
|
+
} catch (err) {
|
|
11850
|
+
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
11851
|
+
}
|
|
11641
11852
|
}
|
|
11642
11853
|
async updateConfig(updates) {
|
|
11643
11854
|
this.config = validateConfig({ ...this.config, ...updates });
|
|
@@ -11660,7 +11871,7 @@ var ConfigManager = class {
|
|
|
11660
11871
|
return configProvider?.apiKey;
|
|
11661
11872
|
}
|
|
11662
11873
|
async loadConfig() {
|
|
11663
|
-
const configPath =
|
|
11874
|
+
const configPath = path21.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
11664
11875
|
try {
|
|
11665
11876
|
const raw = await fs4.readFile(configPath, "utf-8");
|
|
11666
11877
|
return validateConfig(JSON.parse(raw));
|
|
@@ -11767,6 +11978,11 @@ function authMiddleware(secret, required = true) {
|
|
|
11767
11978
|
}
|
|
11768
11979
|
const user = verifyToken(token, secret);
|
|
11769
11980
|
if (!user) {
|
|
11981
|
+
if (!required) {
|
|
11982
|
+
req.user = void 0;
|
|
11983
|
+
next();
|
|
11984
|
+
return;
|
|
11985
|
+
}
|
|
11770
11986
|
res.status(401).json({ error: "Invalid or expired token" });
|
|
11771
11987
|
return;
|
|
11772
11988
|
}
|
|
@@ -12075,7 +12291,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
12075
12291
|
}
|
|
12076
12292
|
|
|
12077
12293
|
// src/dashboard/server.ts
|
|
12078
|
-
var __dirname$1 =
|
|
12294
|
+
var __dirname$1 = path21.dirname(fileURLToPath(import.meta.url));
|
|
12079
12295
|
var DashboardServer = class {
|
|
12080
12296
|
app;
|
|
12081
12297
|
httpServer;
|
|
@@ -12309,12 +12525,17 @@ var DashboardServer = class {
|
|
|
12309
12525
|
*/
|
|
12310
12526
|
persistConfig() {
|
|
12311
12527
|
try {
|
|
12312
|
-
const configPath =
|
|
12313
|
-
|
|
12314
|
-
|
|
12528
|
+
const configPath = path21.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
12529
|
+
fs20.mkdirSync(path21.dirname(configPath), { recursive: true });
|
|
12530
|
+
fs20.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
12315
12531
|
} catch (err) {
|
|
12316
12532
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
12317
12533
|
}
|
|
12534
|
+
try {
|
|
12535
|
+
saveGlobalCredentials(path21.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
12536
|
+
} catch (err) {
|
|
12537
|
+
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
12538
|
+
}
|
|
12318
12539
|
}
|
|
12319
12540
|
/**
|
|
12320
12541
|
* Produce a stable dashboard JWT signing secret.
|
|
@@ -12326,15 +12547,15 @@ var DashboardServer = class {
|
|
|
12326
12547
|
resolveDashboardSecret() {
|
|
12327
12548
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
12328
12549
|
if (fromConfig) return fromConfig;
|
|
12329
|
-
const secretPath =
|
|
12550
|
+
const secretPath = path21.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
12330
12551
|
try {
|
|
12331
|
-
if (
|
|
12332
|
-
const existing =
|
|
12552
|
+
if (fs20.existsSync(secretPath)) {
|
|
12553
|
+
const existing = fs20.readFileSync(secretPath, "utf-8").trim();
|
|
12333
12554
|
if (existing.length >= 16) return existing;
|
|
12334
12555
|
}
|
|
12335
12556
|
const generated = randomUUID();
|
|
12336
|
-
|
|
12337
|
-
|
|
12557
|
+
fs20.mkdirSync(path21.dirname(secretPath), { recursive: true });
|
|
12558
|
+
fs20.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
12338
12559
|
if (this.config.dashboard.auth) {
|
|
12339
12560
|
console.warn(
|
|
12340
12561
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -12361,7 +12582,7 @@ var DashboardServer = class {
|
|
|
12361
12582
|
// ── Setup ─────────────────────────────────────
|
|
12362
12583
|
getGlobalStore() {
|
|
12363
12584
|
if (!this.globalStore) {
|
|
12364
|
-
const globalDbPath =
|
|
12585
|
+
const globalDbPath = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12365
12586
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
12366
12587
|
}
|
|
12367
12588
|
return this.globalStore;
|
|
@@ -12630,12 +12851,12 @@ ${prompt}`;
|
|
|
12630
12851
|
}
|
|
12631
12852
|
}
|
|
12632
12853
|
watchRuntimeChanges() {
|
|
12633
|
-
const workspaceDbPath =
|
|
12634
|
-
const globalDbPath =
|
|
12854
|
+
const workspaceDbPath = path21.join(this.workspacePath, CASCADE_DB_FILE);
|
|
12855
|
+
const globalDbPath = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12635
12856
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
12636
12857
|
for (const watchPath of watchPaths) {
|
|
12637
|
-
if (!
|
|
12638
|
-
|
|
12858
|
+
if (!fs20.existsSync(watchPath)) continue;
|
|
12859
|
+
fs20.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
12639
12860
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
12640
12861
|
});
|
|
12641
12862
|
}
|
|
@@ -12757,7 +12978,7 @@ ${prompt}`;
|
|
|
12757
12978
|
const sessionId = req.params.id;
|
|
12758
12979
|
this.store.deleteSession(sessionId);
|
|
12759
12980
|
this.store.deleteRuntimeSession(sessionId);
|
|
12760
|
-
const globalDbPath =
|
|
12981
|
+
const globalDbPath = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12761
12982
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12762
12983
|
try {
|
|
12763
12984
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -12850,7 +13071,7 @@ ${prompt}`;
|
|
|
12850
13071
|
});
|
|
12851
13072
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
12852
13073
|
const body = req.body;
|
|
12853
|
-
const globalDbPath =
|
|
13074
|
+
const globalDbPath = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12854
13075
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
12855
13076
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12856
13077
|
try {
|
|
@@ -12873,7 +13094,7 @@ ${prompt}`;
|
|
|
12873
13094
|
});
|
|
12874
13095
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
12875
13096
|
this.store.deleteAllRuntimeNodes();
|
|
12876
|
-
const globalDbPath =
|
|
13097
|
+
const globalDbPath = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12877
13098
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12878
13099
|
try {
|
|
12879
13100
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -12959,12 +13180,12 @@ ${prompt}`;
|
|
|
12959
13180
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
12960
13181
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
12961
13182
|
try {
|
|
12962
|
-
const configPath =
|
|
12963
|
-
const existing =
|
|
13183
|
+
const configPath = path21.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
13184
|
+
const existing = fs20.existsSync(configPath) ? JSON.parse(fs20.readFileSync(configPath, "utf-8")) : {};
|
|
12964
13185
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
12965
13186
|
const tmp = configPath + ".tmp";
|
|
12966
|
-
|
|
12967
|
-
|
|
13187
|
+
fs20.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
13188
|
+
fs20.renameSync(tmp, configPath);
|
|
12968
13189
|
res.json({ ok: true });
|
|
12969
13190
|
} catch (err) {
|
|
12970
13191
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -12992,7 +13213,7 @@ ${prompt}`;
|
|
|
12992
13213
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
12993
13214
|
const scope = req.query["scope"] ?? "workspace";
|
|
12994
13215
|
if (scope === "global") {
|
|
12995
|
-
const globalDbPath =
|
|
13216
|
+
const globalDbPath = path21.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
12996
13217
|
const globalStore = new MemoryStore(globalDbPath);
|
|
12997
13218
|
try {
|
|
12998
13219
|
res.json({
|
|
@@ -13227,6 +13448,48 @@ ${prompt}`;
|
|
|
13227
13448
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13228
13449
|
}
|
|
13229
13450
|
});
|
|
13451
|
+
this.app.get("/api/knowledge", auth, (_req, res) => {
|
|
13452
|
+
try {
|
|
13453
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
13454
|
+
try {
|
|
13455
|
+
const facts = ws.getAllFacts();
|
|
13456
|
+
res.json({ total: facts.length, facts });
|
|
13457
|
+
} finally {
|
|
13458
|
+
ws.close();
|
|
13459
|
+
}
|
|
13460
|
+
} catch (err) {
|
|
13461
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13462
|
+
}
|
|
13463
|
+
});
|
|
13464
|
+
this.app.delete("/api/knowledge/fact", auth, mutationLimiter, (req, res) => {
|
|
13465
|
+
const body = req.body;
|
|
13466
|
+
if (!body.entity || !body.relation) {
|
|
13467
|
+
res.status(400).json({ error: "entity and relation are required" });
|
|
13468
|
+
return;
|
|
13469
|
+
}
|
|
13470
|
+
try {
|
|
13471
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
13472
|
+
try {
|
|
13473
|
+
res.json({ ok: true, deleted: ws.deleteFact(body.entity, body.relation) });
|
|
13474
|
+
} finally {
|
|
13475
|
+
ws.close();
|
|
13476
|
+
}
|
|
13477
|
+
} catch (err) {
|
|
13478
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13479
|
+
}
|
|
13480
|
+
});
|
|
13481
|
+
this.app.delete("/api/knowledge", auth, mutationLimiter, (_req, res) => {
|
|
13482
|
+
try {
|
|
13483
|
+
const ws = new WorldStateDB(this.workspacePath);
|
|
13484
|
+
try {
|
|
13485
|
+
res.json({ ok: true, deleted: ws.clearFacts() });
|
|
13486
|
+
} finally {
|
|
13487
|
+
ws.close();
|
|
13488
|
+
}
|
|
13489
|
+
} catch (err) {
|
|
13490
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13491
|
+
}
|
|
13492
|
+
});
|
|
13230
13493
|
this.app.get("/api/audit-chain", auth, async (req, res) => {
|
|
13231
13494
|
try {
|
|
13232
13495
|
const limit = Math.min(500, Math.max(1, parseInt(req.query["limit"] || "200", 10) || 200));
|
|
@@ -13245,13 +13508,13 @@ ${prompt}`;
|
|
|
13245
13508
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
13246
13509
|
}
|
|
13247
13510
|
});
|
|
13248
|
-
const prodPath =
|
|
13249
|
-
const devPath =
|
|
13250
|
-
const webDistPath =
|
|
13251
|
-
if (
|
|
13511
|
+
const prodPath = path21.resolve(__dirname$1, "../web/dist");
|
|
13512
|
+
const devPath = path21.resolve(__dirname$1, "../../web/dist");
|
|
13513
|
+
const webDistPath = fs20.existsSync(prodPath) ? prodPath : devPath;
|
|
13514
|
+
if (fs20.existsSync(webDistPath)) {
|
|
13252
13515
|
this.app.use(express.static(webDistPath));
|
|
13253
13516
|
this.app.get("*", (_req, res) => {
|
|
13254
|
-
res.sendFile(
|
|
13517
|
+
res.sendFile(path21.join(webDistPath, "index.html"));
|
|
13255
13518
|
});
|
|
13256
13519
|
} else {
|
|
13257
13520
|
this.app.get("/", (_req, res) => {
|
|
@@ -13324,6 +13587,6 @@ var HooksRunner = class {
|
|
|
13324
13587
|
}
|
|
13325
13588
|
};
|
|
13326
13589
|
|
|
13327
|
-
export { AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, ConfigManager, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, HooksRunner, Keystore, LM_STUDIO_BASE_URL, MODELS, McpClient, MemoryStore, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
|
|
13590
|
+
export { AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, ConfigManager, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, HooksRunner, Keystore, LM_STUDIO_BASE_URL, MODELS, McpClient, MemoryStore, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
|
|
13328
13591
|
//# sourceMappingURL=index.js.map
|
|
13329
13592
|
//# sourceMappingURL=index.js.map
|