cascade-ai 0.66.2 → 0.67.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/README.md +8 -0
- package/dist/cli.cjs +1031 -261
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1024 -254
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +70129 -1790
- package/dist/index.cjs +1007 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +270 -1
- package/dist/index.d.ts +270 -1
- package/dist/index.js +979 -204
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path25, { dirname } from 'path';
|
|
3
|
+
import fs24, { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import os6 from 'os';
|
|
5
5
|
import crypto4, { randomUUID, randomBytes, createHash, timingSafeEqual, webcrypto } from 'crypto';
|
|
6
6
|
import EventEmitter, { setMaxListeners } from 'events';
|
|
@@ -13,7 +13,7 @@ import http, { createServer } from 'http';
|
|
|
13
13
|
import https from 'https';
|
|
14
14
|
import zlib from 'zlib';
|
|
15
15
|
import { Readable } from 'stream';
|
|
16
|
-
import
|
|
16
|
+
import fs5 from 'fs/promises';
|
|
17
17
|
import * as ignoreFactory from 'ignore';
|
|
18
18
|
import ignoreFactory__default from 'ignore';
|
|
19
19
|
import { exec, execFile, execSync } from 'child_process';
|
|
@@ -61,12 +61,12 @@ var init_audit_logger = __esm({
|
|
|
61
61
|
constructor(workspacePath, debugMode = false) {
|
|
62
62
|
this.workspacePath = workspacePath;
|
|
63
63
|
this.debugMode = debugMode;
|
|
64
|
-
const cascadeDir =
|
|
65
|
-
if (!
|
|
66
|
-
|
|
64
|
+
const cascadeDir = path25.join(workspacePath, ".cascade");
|
|
65
|
+
if (!fs24.existsSync(cascadeDir)) {
|
|
66
|
+
fs24.mkdirSync(cascadeDir, { recursive: true });
|
|
67
67
|
}
|
|
68
|
-
this.keyPath =
|
|
69
|
-
this.dbPath =
|
|
68
|
+
this.keyPath = path25.join(cascadeDir, "audit_log.key");
|
|
69
|
+
this.dbPath = path25.join(cascadeDir, "audit_log.db");
|
|
70
70
|
this.initEncryptionKey();
|
|
71
71
|
this.db = new Database(this.dbPath);
|
|
72
72
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -96,11 +96,11 @@ var init_audit_logger = __esm({
|
|
|
96
96
|
return crypto4.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
97
97
|
}
|
|
98
98
|
initEncryptionKey() {
|
|
99
|
-
if (
|
|
100
|
-
this.encryptionKey =
|
|
99
|
+
if (fs24.existsSync(this.keyPath)) {
|
|
100
|
+
this.encryptionKey = fs24.readFileSync(this.keyPath);
|
|
101
101
|
} else {
|
|
102
102
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
103
|
-
|
|
103
|
+
fs24.writeFileSync(this.keyPath, this.encryptionKey);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
encrypt(text) {
|
|
@@ -178,9 +178,9 @@ var init_audit_logger = __esm({
|
|
|
178
178
|
dumpDebugIfNeeded() {
|
|
179
179
|
if (!this.debugMode) return;
|
|
180
180
|
try {
|
|
181
|
-
const dumpPath =
|
|
181
|
+
const dumpPath = path25.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
|
|
182
182
|
const entries = this.getAllLogs();
|
|
183
|
-
|
|
183
|
+
fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
184
184
|
} catch (err) {
|
|
185
185
|
console.error("Failed to dump debug audit logs", err);
|
|
186
186
|
}
|
|
@@ -1862,7 +1862,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
|
|
1864
1864
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1865
|
+
var CASCADE_VERSION = "0.67.0";
|
|
1866
1866
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1867
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1868
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4617,7 +4617,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4617
4617
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4618
4618
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4619
4619
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4620
|
-
var DEFAULT_CACHE_FILE =
|
|
4620
|
+
var DEFAULT_CACHE_FILE = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4621
4621
|
function normalizeModelId2(id) {
|
|
4622
4622
|
let s = id.toLowerCase();
|
|
4623
4623
|
const slash = s.lastIndexOf("/");
|
|
@@ -4652,7 +4652,7 @@ var LiveDataProvider = class {
|
|
|
4652
4652
|
if (this.loaded) return;
|
|
4653
4653
|
this.loaded = true;
|
|
4654
4654
|
try {
|
|
4655
|
-
const raw = await
|
|
4655
|
+
const raw = await fs5.readFile(this.opts.cacheFile, "utf-8");
|
|
4656
4656
|
const cache = JSON.parse(raw);
|
|
4657
4657
|
if (cache.snapshot?.families) {
|
|
4658
4658
|
this.snapshot = cache.snapshot;
|
|
@@ -4764,14 +4764,14 @@ var LiveDataProvider = class {
|
|
|
4764
4764
|
}
|
|
4765
4765
|
async saveCache() {
|
|
4766
4766
|
try {
|
|
4767
|
-
await
|
|
4767
|
+
await fs5.mkdir(path25.dirname(this.opts.cacheFile), { recursive: true });
|
|
4768
4768
|
const cache = {
|
|
4769
4769
|
fetchedAt: this.fetchedAt,
|
|
4770
4770
|
snapshot: this.snapshot ?? void 0,
|
|
4771
4771
|
prices: Object.fromEntries(this.prices),
|
|
4772
4772
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4773
4773
|
};
|
|
4774
|
-
await
|
|
4774
|
+
await fs5.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4775
4775
|
} catch {
|
|
4776
4776
|
}
|
|
4777
4777
|
}
|
|
@@ -6598,7 +6598,8 @@ var KNOWN_TOOLS = [
|
|
|
6598
6598
|
"generate_image",
|
|
6599
6599
|
"generate_video",
|
|
6600
6600
|
"generate_speech",
|
|
6601
|
-
"transcribe_audio"
|
|
6601
|
+
"transcribe_audio",
|
|
6602
|
+
"generate_document"
|
|
6602
6603
|
];
|
|
6603
6604
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
6604
6605
|
"generate_image",
|
|
@@ -6615,18 +6616,45 @@ function buildWorkerRules(has) {
|
|
|
6615
6616
|
canWriteFiles && "- If the task asks for a file or artifact, you must actually create it in the workspace, verify that it exists, and inspect it before claiming success.",
|
|
6616
6617
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
6617
6618
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
6619
|
+
// A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
|
|
6620
|
+
// file_write does exactly what it promises — writes the model's characters
|
|
6621
|
+
// verbatim — so without this rule the worker "wrote" a Word document that
|
|
6622
|
+
// Word correctly reported as corrupted. Stated as an explicit prohibition
|
|
6623
|
+
// on the tool it would otherwise reach for, because "use generate_document"
|
|
6624
|
+
// alone competes with a file_write habit the model already has.
|
|
6625
|
+
has("generate_document") && '- For a Word (.docx), PowerPoint (.pptx) or Excel (.xlsx) deliverable you MUST call the "generate_document" tool and NEVER "file_write" or "run_code" \u2014 those formats are ZIP archives of XML, so text written straight to the path opens as a corrupted file. Pass the target path plus the source: Markdown for .docx, Markdown slides separated by --- rules for .pptx (each slide starts with a heading), CSV for .xlsx.',
|
|
6626
|
+
// The alternative to this rule is not "a worse chart" — it is a paragraph
|
|
6627
|
+
// describing a chart that does not exist, which is what real decks came
|
|
6628
|
+
// back with. A `chart:` block is checkable, carries the exact numbers, and
|
|
6629
|
+
// becomes a genuine editable PowerPoint chart object.
|
|
6630
|
+
has("generate_document") && '- For any data-driven visualization (a chart, graph, trend, breakdown or comparison of numbers), emit a fenced ```chart:bar block \u2014 also chart:line, chart:pie, chart:doughnut, chart:area, chart:scatter \u2014 whose body is an optional "title: ..." line followed by CSV: a header row of "<category label>,<series name>,<series name>", then one row per category. In a PowerPoint deliverable that becomes a REAL, editable chart carrying your exact numbers; in Word and Excel the same block keeps every value as a table or worksheet. Never write prose describing a chart in place of emitting one.',
|
|
6618
6631
|
// Without this the model "writes the image" as prose — a bracketed
|
|
6619
6632
|
// description sitting in the deck where the picture should be — even with a
|
|
6620
6633
|
// working image model registered. Naming the reference syntax matters as
|
|
6621
6634
|
// much as naming the tool: a generated image nobody embeds is still a
|
|
6622
|
-
// missing image.
|
|
6635
|
+
// missing image. It also has to say the reference must stand ALONE on its
|
|
6636
|
+
// line, because only a standalone reference is parsed as a picture (see
|
|
6637
|
+
// core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
|
|
6638
|
+
// silently degrades to caption text.
|
|
6623
6639
|
//
|
|
6624
|
-
// Scoped to PowerPoint/Word specifically: those are the only
|
|
6625
|
-
//
|
|
6626
|
-
//
|
|
6627
|
-
//
|
|
6628
|
-
//
|
|
6629
|
-
has("generate_image") &&
|
|
6640
|
+
// Scoped to PowerPoint/Word specifically: those are the only renderers that
|
|
6641
|
+
// actually embed a Markdown image reference. PDF and plain-text/Markdown
|
|
6642
|
+
// deliverables flatten a reference straight to caption text — telling the
|
|
6643
|
+
// model to call generate_image for THOSE just pays for an image nobody
|
|
6644
|
+
// ever sees.
|
|
6645
|
+
has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
|
|
6646
|
+
// standalone-line parser constraint, says to generate BEFORE writing the
|
|
6647
|
+
// document, and points data-driven visuals at a ```chart: block rather
|
|
6648
|
+
// than a Markdown table, which is only the right advice now that chart
|
|
6649
|
+
// blocks render as real chart objects.
|
|
6650
|
+
'- When a slide deck (PowerPoint) or Word document deliverable calls for a decorative image, photo or illustration, you MUST call the "generate_image" tool \u2014 once per image the deliverable needs, BEFORE you write the document \u2014 and then put each result on a line of its OWN using Markdown image syntax: , where "location" is exactly the string the tool reported back. A reference inside a sentence stays prose and no picture appears. NEVER write a text placeholder or a bracketed description such as [image: a cat] in its place, and never state that an image is included when you did not generate one. Do NOT use "generate_image" for a chart, graph or diagram that must show exact data (numbers, axes, labels) \u2014 an image model cannot guarantee those values are correct; emit a ```chart: block instead. For any OTHER deliverable format (PDF, plain text, code, etc.) do NOT call "generate_image" \u2014 those cannot embed the result, so describe the visual in words instead.',
|
|
6651
|
+
// The other half of image reliability, and the half no amount of
|
|
6652
|
+
// instruction-tightening could fix: generate_image only exists when an
|
|
6653
|
+
// OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
|
|
6654
|
+
// With neither, the model was never given a choice — and a model that
|
|
6655
|
+
// doesn't know that writes "[illustration of a cat]" and moves on. Said out
|
|
6656
|
+
// loud, up front, so the fallback is a real chart or honest prose.
|
|
6657
|
+
!has("generate_image") && (has("generate_document") || canWriteFiles) && "- No image-generation model is available on this run, so there is NO tool that can draw a picture. Do not emit a Markdown image reference, a bracketed placeholder such as [image: a cat], or any claim that an illustration is included. If the request needs a data visualization use a ```chart: block, which needs no image model; otherwise describe the visual in words and say plainly that no image could be generated.",
|
|
6630
6658
|
// The video counterpart of the image rule above, and the fix for the
|
|
6631
6659
|
// reported "it writes scripts forever and never makes the video" run. The
|
|
6632
6660
|
// failure mode is identical — the model narrates the deliverable instead of
|
|
@@ -6636,7 +6664,7 @@ function buildWorkerRules(has) {
|
|
|
6636
6664
|
// the deliverable rather than an illustration inside one, and the reference
|
|
6637
6665
|
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
6638
6666
|
has("generate_video") && '- When the subtask deliverable is a video, you MUST call the "generate_video" tool \u2014 that call IS the deliverable, and the subtask is not done until it has returned. NEVER deliver the video as prose, a script, a storyboard, or a bracketed placeholder such as [video: a cat skating], and NEVER claim a video exists unless the tool reported a location for it. Call it exactly ONCE: it is billed per second of output and renders for minutes, so a second call charges the user again. Then report the location string the tool returned VERBATIM as your result, referencing it as [description](location) if it goes inside a document. If the tool reports a failure or a timeout, report that failure verbatim and stop \u2014 do NOT call it again, and do NOT substitute a written description of a video that was never made.',
|
|
6639
|
-
has("run_code") &&
|
|
6667
|
+
has("run_code") && `- Use the "run_code" tool for data processing, archives, and file formats not covered by a dedicated tool. ${has("generate_document") ? 'Do NOT use it to build a .docx, .pptx or .xlsx \u2014 "generate_document" already produces those correctly. ' : ""}${has("pdf_create") ? 'Do NOT use it to build a PDF \u2014 "pdf_create" does that. ' : ""}Always cleanup after code execution.`,
|
|
6640
6668
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
6641
6669
|
has("peer_message") && '- Use the "peer_message" tool to communicate with other T3 workers if your tasks have dependencies or shared state. You can send updates or wait for signals.',
|
|
6642
6670
|
hasAnyTool && "- Only use tools directly relevant to THIS subtask. Do not reach for an unrelated connected-service action (e.g. creating, deleting, or modifying a repository, issue, or PR; sending a message) unless the subtask explicitly calls for it.",
|
|
@@ -6647,13 +6675,34 @@ function buildWorkerRules(has) {
|
|
|
6647
6675
|
Rules:
|
|
6648
6676
|
${rules.filter((r) => r !== false).join("\n")}`;
|
|
6649
6677
|
}
|
|
6650
|
-
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
|
|
6678
|
+
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
|
|
6679
|
+
var ARTIFACT_FILE_RE = /\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|pptx?|xlsx?|png|jpg|jpeg|svg|gif)\b/i;
|
|
6651
6680
|
function shouldRequireArtifact(assignment, toolNames) {
|
|
6652
6681
|
if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
|
|
6653
6682
|
if (assignment?.files?.length) return true;
|
|
6654
6683
|
const haystack = `${assignment?.description ?? ""}
|
|
6655
6684
|
${assignment?.expectedOutput ?? ""}`;
|
|
6656
|
-
return
|
|
6685
|
+
return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
6686
|
+
}
|
|
6687
|
+
var VISUAL_REQUEST_RE = /\b(image|images|picture|pictures|photo|photos|illustration|illustrations|visual|visuals|visualisation|visualization|visualizations|chart|charts|graph|graphs|plot|plots|diagram|diagrams|infographic)\b/i;
|
|
6688
|
+
var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
|
|
6689
|
+
var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
|
|
6690
|
+
function missingVisualEvidence(assignment, output, calledTools, toolNames) {
|
|
6691
|
+
const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
|
|
6692
|
+
if (!canVisualize) return null;
|
|
6693
|
+
const haystack = `${assignment?.description ?? ""}
|
|
6694
|
+
${assignment?.expectedOutput ?? ""}`;
|
|
6695
|
+
if (!VISUAL_REQUEST_RE.test(haystack)) return null;
|
|
6696
|
+
if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
|
|
6697
|
+
if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
|
|
6698
|
+
const options = [];
|
|
6699
|
+
if (toolNames.includes("generate_image")) {
|
|
6700
|
+
options.push('call "generate_image" and reference the result on its own line as ');
|
|
6701
|
+
}
|
|
6702
|
+
if (toolNames.includes("generate_document")) {
|
|
6703
|
+
options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
|
|
6704
|
+
}
|
|
6705
|
+
return `The subtask asks for a visual, but the output contains no embedded image and no chart block \u2014 only prose. ${options.join(", or ")}. Describing a picture is not producing one.`;
|
|
6657
6706
|
}
|
|
6658
6707
|
var T3Worker = class extends BaseTier {
|
|
6659
6708
|
router;
|
|
@@ -6834,6 +6883,18 @@ Now execute your subtask using this context where relevant.`
|
|
|
6834
6883
|
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6835
6884
|
}
|
|
6836
6885
|
}
|
|
6886
|
+
const visualIssue = missingVisualEvidence(
|
|
6887
|
+
assignment,
|
|
6888
|
+
output,
|
|
6889
|
+
toolCalls.map((t) => t.name),
|
|
6890
|
+
this.tools.map((t) => t.name)
|
|
6891
|
+
);
|
|
6892
|
+
if (visualIssue) {
|
|
6893
|
+
correctionAttempts = 1;
|
|
6894
|
+
issues.push(visualIssue);
|
|
6895
|
+
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
6896
|
+
output = await this.correctOutput(output, [visualIssue]);
|
|
6897
|
+
}
|
|
6837
6898
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6838
6899
|
const testResult = await this.selfTest(assignment, output);
|
|
6839
6900
|
checksRun.push(...testResult.checksRun);
|
|
@@ -7148,8 +7209,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7148
7209
|
// Media generation can run for a minute; without this a cancelled run
|
|
7149
7210
|
// still pays for an image nobody will see.
|
|
7150
7211
|
...this.signal ? { signal: this.signal } : {},
|
|
7151
|
-
saveSnapshot: async (
|
|
7152
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7212
|
+
saveSnapshot: async (path27, content) => {
|
|
7213
|
+
this.store?.addFileSnapshot(this.taskId, path27, content);
|
|
7153
7214
|
},
|
|
7154
7215
|
sendPeerSync: (to, syncType, content) => {
|
|
7155
7216
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7325,7 +7386,7 @@ ${assignment.expectedOutput}`;
|
|
|
7325
7386
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7326
7387
|
const haystack = `${assignment.description}
|
|
7327
7388
|
${assignment.expectedOutput}`;
|
|
7328
|
-
const matches = haystack.match(
|
|
7389
|
+
const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
|
|
7329
7390
|
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
7330
7391
|
}
|
|
7331
7392
|
async verifyArtifacts(assignment) {
|
|
@@ -7336,9 +7397,9 @@ ${assignment.expectedOutput}`;
|
|
|
7336
7397
|
const { promisify: promisify4 } = await import('util');
|
|
7337
7398
|
const execAsync2 = promisify4(exec2);
|
|
7338
7399
|
for (const artifactPath of artifactPaths) {
|
|
7339
|
-
const absolutePath =
|
|
7400
|
+
const absolutePath = path25.resolve(process.cwd(), artifactPath);
|
|
7340
7401
|
try {
|
|
7341
|
-
const stat = await
|
|
7402
|
+
const stat = await fs5.stat(absolutePath);
|
|
7342
7403
|
if (!stat.isFile()) {
|
|
7343
7404
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
7344
7405
|
continue;
|
|
@@ -7347,8 +7408,22 @@ ${assignment.expectedOutput}`;
|
|
|
7347
7408
|
issues.push(`Artifact is empty: ${artifactPath}`);
|
|
7348
7409
|
continue;
|
|
7349
7410
|
}
|
|
7350
|
-
if (
|
|
7351
|
-
const
|
|
7411
|
+
if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
|
|
7412
|
+
const head = Buffer.alloc(4);
|
|
7413
|
+
const fh = await fs5.open(absolutePath, "r");
|
|
7414
|
+
try {
|
|
7415
|
+
await fh.read(head, 0, 4, 0);
|
|
7416
|
+
} finally {
|
|
7417
|
+
await fh.close();
|
|
7418
|
+
}
|
|
7419
|
+
if (head.toString("latin1") !== "PK") {
|
|
7420
|
+
issues.push(
|
|
7421
|
+
`${artifactPath} is not a valid Office file \u2014 it must be written with the generate_document tool, which renders the real OOXML archive. Plain text saved under that extension opens as corrupted.`
|
|
7422
|
+
);
|
|
7423
|
+
continue;
|
|
7424
|
+
}
|
|
7425
|
+
} else if (!/\.pdf$/i.test(artifactPath)) {
|
|
7426
|
+
const content = await fs5.readFile(absolutePath, "utf-8");
|
|
7352
7427
|
if (!content.trim()) {
|
|
7353
7428
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
7354
7429
|
continue;
|
|
@@ -7357,7 +7432,7 @@ ${assignment.expectedOutput}`;
|
|
|
7357
7432
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7358
7433
|
continue;
|
|
7359
7434
|
}
|
|
7360
|
-
const ext =
|
|
7435
|
+
const ext = path25.extname(absolutePath).toLowerCase();
|
|
7361
7436
|
try {
|
|
7362
7437
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7363
7438
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -10304,6 +10379,705 @@ function buildMediaTools(deps, readFile) {
|
|
|
10304
10379
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
10305
10380
|
return tools;
|
|
10306
10381
|
}
|
|
10382
|
+
var WorkspaceSandboxError = class extends Error {
|
|
10383
|
+
constructor(attempted, workspaceRoot) {
|
|
10384
|
+
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10385
|
+
this.name = "WorkspaceSandboxError";
|
|
10386
|
+
}
|
|
10387
|
+
};
|
|
10388
|
+
function resolveInWorkspace(workspaceRoot, input) {
|
|
10389
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
10390
|
+
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10391
|
+
}
|
|
10392
|
+
const root = path25.resolve(workspaceRoot);
|
|
10393
|
+
const abs = path25.isAbsolute(input) ? path25.resolve(input) : path25.resolve(root, input);
|
|
10394
|
+
const rel = path25.relative(root, abs);
|
|
10395
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path25.isAbsolute(rel)) {
|
|
10396
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10397
|
+
}
|
|
10398
|
+
try {
|
|
10399
|
+
const real = fs24.realpathSync(abs);
|
|
10400
|
+
const realRel = path25.relative(root, real);
|
|
10401
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path25.isAbsolute(realRel))) {
|
|
10402
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10403
|
+
}
|
|
10404
|
+
} catch (e) {
|
|
10405
|
+
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10406
|
+
}
|
|
10407
|
+
return abs;
|
|
10408
|
+
}
|
|
10409
|
+
|
|
10410
|
+
// src/core/documents/blocks.ts
|
|
10411
|
+
function fileExt(name) {
|
|
10412
|
+
const base = name.split(/[\\/]/).pop() ?? name;
|
|
10413
|
+
const dot = base.lastIndexOf(".");
|
|
10414
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
10415
|
+
}
|
|
10416
|
+
function parseDelimited(text, name = "") {
|
|
10417
|
+
const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
|
|
10418
|
+
const rows = [];
|
|
10419
|
+
let row = [];
|
|
10420
|
+
let cell = "";
|
|
10421
|
+
let quoted = false;
|
|
10422
|
+
for (let i = 0; i < text.length; i++) {
|
|
10423
|
+
const ch = text[i];
|
|
10424
|
+
if (quoted) {
|
|
10425
|
+
if (ch === '"') {
|
|
10426
|
+
if (text[i + 1] === '"') {
|
|
10427
|
+
cell += '"';
|
|
10428
|
+
i++;
|
|
10429
|
+
} else quoted = false;
|
|
10430
|
+
} else cell += ch;
|
|
10431
|
+
} else if (ch === '"') {
|
|
10432
|
+
quoted = true;
|
|
10433
|
+
} else if (ch === delim) {
|
|
10434
|
+
row.push(cell);
|
|
10435
|
+
cell = "";
|
|
10436
|
+
} else if (ch === "\n") {
|
|
10437
|
+
row.push(cell);
|
|
10438
|
+
rows.push(row);
|
|
10439
|
+
row = [];
|
|
10440
|
+
cell = "";
|
|
10441
|
+
} else if (ch === "\r") ; else {
|
|
10442
|
+
cell += ch;
|
|
10443
|
+
}
|
|
10444
|
+
if (rows.length > 5e3) break;
|
|
10445
|
+
}
|
|
10446
|
+
if (cell !== "" || row.length) {
|
|
10447
|
+
row.push(cell);
|
|
10448
|
+
rows.push(row);
|
|
10449
|
+
}
|
|
10450
|
+
return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
|
|
10451
|
+
}
|
|
10452
|
+
var CHART_ALIASES = {
|
|
10453
|
+
bar: "bar",
|
|
10454
|
+
column: "bar",
|
|
10455
|
+
bars: "bar",
|
|
10456
|
+
barchart: "bar",
|
|
10457
|
+
horizontalbar: "bar",
|
|
10458
|
+
line: "line",
|
|
10459
|
+
lines: "line",
|
|
10460
|
+
linechart: "line",
|
|
10461
|
+
pie: "pie",
|
|
10462
|
+
piechart: "pie",
|
|
10463
|
+
doughnut: "doughnut",
|
|
10464
|
+
donut: "doughnut",
|
|
10465
|
+
area: "area",
|
|
10466
|
+
areachart: "area",
|
|
10467
|
+
scatter: "scatter",
|
|
10468
|
+
xy: "scatter"
|
|
10469
|
+
};
|
|
10470
|
+
function chartKind(raw) {
|
|
10471
|
+
return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
|
|
10472
|
+
}
|
|
10473
|
+
function toNumber(cell) {
|
|
10474
|
+
const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
|
|
10475
|
+
const n = Number.parseFloat(cleaned);
|
|
10476
|
+
return Number.isFinite(n) ? n : 0;
|
|
10477
|
+
}
|
|
10478
|
+
function parseChartSpec(kind, body) {
|
|
10479
|
+
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
|
10480
|
+
let title;
|
|
10481
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
10482
|
+
const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
|
|
10483
|
+
if (titleMatch) {
|
|
10484
|
+
title = titleMatch[1].trim();
|
|
10485
|
+
lines.shift();
|
|
10486
|
+
}
|
|
10487
|
+
const rows = parseDelimited(lines.join("\n").trim());
|
|
10488
|
+
if (rows.length < 2) return null;
|
|
10489
|
+
const header = rows[0].map((c) => c.trim());
|
|
10490
|
+
if (header.length < 2) return null;
|
|
10491
|
+
const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
10492
|
+
if (!dataRows.length) return null;
|
|
10493
|
+
const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
|
|
10494
|
+
const series = header.slice(1).map((name, col) => ({
|
|
10495
|
+
name: name || `Series ${col + 1}`,
|
|
10496
|
+
values: dataRows.map((r) => toNumber((r[col + 1] ?? "").trim()))
|
|
10497
|
+
}));
|
|
10498
|
+
if (!series.length) return null;
|
|
10499
|
+
return {
|
|
10500
|
+
kind,
|
|
10501
|
+
...title ? { title } : {},
|
|
10502
|
+
categoryName: header[0] || "Category",
|
|
10503
|
+
categories,
|
|
10504
|
+
series
|
|
10505
|
+
};
|
|
10506
|
+
}
|
|
10507
|
+
function chartToTableRows(spec) {
|
|
10508
|
+
return [
|
|
10509
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10510
|
+
...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
|
|
10511
|
+
];
|
|
10512
|
+
}
|
|
10513
|
+
function parseBlocks(md) {
|
|
10514
|
+
const out = [];
|
|
10515
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
10516
|
+
let i = 0;
|
|
10517
|
+
let ordinal = 0;
|
|
10518
|
+
const reset = () => {
|
|
10519
|
+
ordinal = 0;
|
|
10520
|
+
};
|
|
10521
|
+
while (i < lines.length) {
|
|
10522
|
+
const line = lines[i] ?? "";
|
|
10523
|
+
const fence = line.match(/^```+\s*(.*)$/);
|
|
10524
|
+
if (fence) {
|
|
10525
|
+
const info = (fence[1] ?? "").trim();
|
|
10526
|
+
i++;
|
|
10527
|
+
const code = [];
|
|
10528
|
+
while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
|
|
10529
|
+
code.push(lines[i] ?? "");
|
|
10530
|
+
i++;
|
|
10531
|
+
}
|
|
10532
|
+
i++;
|
|
10533
|
+
const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
|
|
10534
|
+
const kind = chartInfo ? chartKind(chartInfo[1]) : null;
|
|
10535
|
+
const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
|
|
10536
|
+
out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
|
|
10537
|
+
reset();
|
|
10538
|
+
continue;
|
|
10539
|
+
}
|
|
10540
|
+
if (!line.trim()) {
|
|
10541
|
+
i++;
|
|
10542
|
+
reset();
|
|
10543
|
+
continue;
|
|
10544
|
+
}
|
|
10545
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
10546
|
+
if (h) {
|
|
10547
|
+
out.push({ t: "heading", level: h[1].length, text: h[2] });
|
|
10548
|
+
i++;
|
|
10549
|
+
reset();
|
|
10550
|
+
continue;
|
|
10551
|
+
}
|
|
10552
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
10553
|
+
out.push({ t: "hr" });
|
|
10554
|
+
i++;
|
|
10555
|
+
reset();
|
|
10556
|
+
continue;
|
|
10557
|
+
}
|
|
10558
|
+
const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
|
|
10559
|
+
if (li) {
|
|
10560
|
+
const ordered = /\d/.test(li[2]);
|
|
10561
|
+
ordinal = ordered ? ordinal + 1 : 0;
|
|
10562
|
+
out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
|
|
10563
|
+
i++;
|
|
10564
|
+
continue;
|
|
10565
|
+
}
|
|
10566
|
+
if (/^>\s?/.test(line)) {
|
|
10567
|
+
out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
|
|
10568
|
+
i++;
|
|
10569
|
+
reset();
|
|
10570
|
+
continue;
|
|
10571
|
+
}
|
|
10572
|
+
if (/^\|.*\|/.test(line)) {
|
|
10573
|
+
const rows = [];
|
|
10574
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
|
|
10575
|
+
const r = lines[i];
|
|
10576
|
+
if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
|
|
10577
|
+
i++;
|
|
10578
|
+
}
|
|
10579
|
+
out.push({ t: "table", rows });
|
|
10580
|
+
reset();
|
|
10581
|
+
continue;
|
|
10582
|
+
}
|
|
10583
|
+
out.push({ t: "para", text: line });
|
|
10584
|
+
i++;
|
|
10585
|
+
reset();
|
|
10586
|
+
}
|
|
10587
|
+
return out;
|
|
10588
|
+
}
|
|
10589
|
+
var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
10590
|
+
function matchImageLine(s) {
|
|
10591
|
+
const m = s.trim().match(IMAGE_LINE);
|
|
10592
|
+
return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
|
|
10593
|
+
}
|
|
10594
|
+
function sniffImage(b) {
|
|
10595
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
10596
|
+
if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
10597
|
+
return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
10598
|
+
}
|
|
10599
|
+
if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
10600
|
+
return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
|
|
10601
|
+
}
|
|
10602
|
+
if (b.length > 26 && b[0] === 66 && b[1] === 77) {
|
|
10603
|
+
return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
|
|
10604
|
+
}
|
|
10605
|
+
if (b.length > 4 && b[0] === 255 && b[1] === 216) {
|
|
10606
|
+
let i = 2;
|
|
10607
|
+
while (i + 9 < b.length) {
|
|
10608
|
+
if (b[i] !== 255) {
|
|
10609
|
+
i++;
|
|
10610
|
+
continue;
|
|
10611
|
+
}
|
|
10612
|
+
const marker = b[i + 1];
|
|
10613
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
10614
|
+
return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
|
|
10615
|
+
}
|
|
10616
|
+
i += 2 + dv.getUint16(i + 2);
|
|
10617
|
+
}
|
|
10618
|
+
}
|
|
10619
|
+
return null;
|
|
10620
|
+
}
|
|
10621
|
+
function bytesToBase64(bytes) {
|
|
10622
|
+
const g = globalThis;
|
|
10623
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
10624
|
+
const CHUNK = 32768;
|
|
10625
|
+
let binary = "";
|
|
10626
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
10627
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
10628
|
+
}
|
|
10629
|
+
return g.btoa(binary);
|
|
10630
|
+
}
|
|
10631
|
+
function stripInline(s) {
|
|
10632
|
+
return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
|
|
10633
|
+
}
|
|
10634
|
+
function inlineRuns(text) {
|
|
10635
|
+
const acc = [];
|
|
10636
|
+
const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
|
|
10637
|
+
let last = 0;
|
|
10638
|
+
let m;
|
|
10639
|
+
while ((m = re.exec(text)) !== null) {
|
|
10640
|
+
if (m.index > last) acc.push({ text: text.slice(last, m.index) });
|
|
10641
|
+
const tok = m[0];
|
|
10642
|
+
if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
|
|
10643
|
+
else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
|
|
10644
|
+
else if (tok.startsWith("[")) {
|
|
10645
|
+
const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
|
|
10646
|
+
acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
|
|
10647
|
+
} else acc.push({ text: tok.slice(1, -1), italic: true });
|
|
10648
|
+
last = re.lastIndex;
|
|
10649
|
+
}
|
|
10650
|
+
if (last < text.length) acc.push({ text: text.slice(last) });
|
|
10651
|
+
return acc.length ? acc : [{ text }];
|
|
10652
|
+
}
|
|
10653
|
+
function splitSlides(md) {
|
|
10654
|
+
const norm = md.replace(/\r\n/g, "\n").trim();
|
|
10655
|
+
let chunks = norm.split(/\n\s*-{3,}\s*\n/);
|
|
10656
|
+
if (chunks.length === 1) {
|
|
10657
|
+
const lines = norm.split("\n");
|
|
10658
|
+
const sections = [];
|
|
10659
|
+
let cur = [];
|
|
10660
|
+
for (const ln of lines) {
|
|
10661
|
+
if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
|
|
10662
|
+
sections.push(cur.join("\n"));
|
|
10663
|
+
cur = [];
|
|
10664
|
+
}
|
|
10665
|
+
cur.push(ln);
|
|
10666
|
+
}
|
|
10667
|
+
if (cur.length) sections.push(cur.join("\n"));
|
|
10668
|
+
if (sections.length > 1) chunks = sections;
|
|
10669
|
+
}
|
|
10670
|
+
return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
|
|
10671
|
+
}
|
|
10672
|
+
function parseSlide(chunk) {
|
|
10673
|
+
let title = "";
|
|
10674
|
+
const body = [];
|
|
10675
|
+
const images = [];
|
|
10676
|
+
const charts = [];
|
|
10677
|
+
const lines = chunk.split("\n");
|
|
10678
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10679
|
+
const raw = lines[i] ?? "";
|
|
10680
|
+
const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
10681
|
+
if (fence) {
|
|
10682
|
+
const kind = chartKind(fence[1]);
|
|
10683
|
+
const bodyLines = [];
|
|
10684
|
+
i++;
|
|
10685
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
10686
|
+
bodyLines.push(lines[i] ?? "");
|
|
10687
|
+
i++;
|
|
10688
|
+
}
|
|
10689
|
+
const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
|
|
10690
|
+
if (spec) charts.push(spec);
|
|
10691
|
+
else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
|
|
10692
|
+
continue;
|
|
10693
|
+
}
|
|
10694
|
+
const line = raw.trim();
|
|
10695
|
+
if (!line) continue;
|
|
10696
|
+
const h = line.match(/^#{1,6}[ \t]+(.*)/);
|
|
10697
|
+
if (h) {
|
|
10698
|
+
if (!title) title = stripInline(h[1]);
|
|
10699
|
+
else body.push(stripInline(h[1]));
|
|
10700
|
+
continue;
|
|
10701
|
+
}
|
|
10702
|
+
const img = matchImageLine(line);
|
|
10703
|
+
if (img) {
|
|
10704
|
+
images.push(img);
|
|
10705
|
+
continue;
|
|
10706
|
+
}
|
|
10707
|
+
const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
|
|
10708
|
+
if (li) {
|
|
10709
|
+
const bulletImg = matchImageLine(li[2]);
|
|
10710
|
+
if (bulletImg) {
|
|
10711
|
+
images.push(bulletImg);
|
|
10712
|
+
continue;
|
|
10713
|
+
}
|
|
10714
|
+
body.push(stripInline(li[2]));
|
|
10715
|
+
continue;
|
|
10716
|
+
}
|
|
10717
|
+
if (/^>\s?/.test(line)) {
|
|
10718
|
+
body.push(stripInline(line.replace(/^>\s?/, "")));
|
|
10719
|
+
continue;
|
|
10720
|
+
}
|
|
10721
|
+
body.push(stripInline(line));
|
|
10722
|
+
}
|
|
10723
|
+
return { title, body, images, charts };
|
|
10724
|
+
}
|
|
10725
|
+
function extractCharts(source) {
|
|
10726
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
10727
|
+
const charts = [];
|
|
10728
|
+
const rest = [];
|
|
10729
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10730
|
+
const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
10731
|
+
if (!fence) {
|
|
10732
|
+
rest.push(lines[i]);
|
|
10733
|
+
continue;
|
|
10734
|
+
}
|
|
10735
|
+
const kind = chartKind(fence[1]);
|
|
10736
|
+
const body = [];
|
|
10737
|
+
i++;
|
|
10738
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
10739
|
+
body.push(lines[i] ?? "");
|
|
10740
|
+
i++;
|
|
10741
|
+
}
|
|
10742
|
+
const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
|
|
10743
|
+
if (spec) charts.push(spec);
|
|
10744
|
+
else rest.push(...body);
|
|
10745
|
+
}
|
|
10746
|
+
return { charts, rest: rest.join("\n") };
|
|
10747
|
+
}
|
|
10748
|
+
|
|
10749
|
+
// src/core/documents/render.ts
|
|
10750
|
+
var DOCUMENT_MIME = {
|
|
10751
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
10752
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
10753
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
10754
|
+
};
|
|
10755
|
+
function isDocumentFormat(ext) {
|
|
10756
|
+
return ext === "docx" || ext === "pptx" || ext === "xlsx";
|
|
10757
|
+
}
|
|
10758
|
+
async function loadImage(ref, load) {
|
|
10759
|
+
if (!load) return null;
|
|
10760
|
+
try {
|
|
10761
|
+
const bytes = await load(ref.url);
|
|
10762
|
+
if (!bytes || !bytes.length) return null;
|
|
10763
|
+
const info = sniffImage(bytes);
|
|
10764
|
+
if (!info || info.width <= 0 || info.height <= 0) return null;
|
|
10765
|
+
return { ...info, bytes, alt: ref.alt };
|
|
10766
|
+
} catch {
|
|
10767
|
+
return null;
|
|
10768
|
+
}
|
|
10769
|
+
}
|
|
10770
|
+
async function renderXlsx(source, opts = {}) {
|
|
10771
|
+
const XLSX = await import('xlsx');
|
|
10772
|
+
const { charts, rest } = extractCharts(source);
|
|
10773
|
+
const name = opts.name ?? "";
|
|
10774
|
+
const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
|
|
10775
|
+
const wb = XLSX.utils.book_new();
|
|
10776
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
|
|
10777
|
+
const used = /* @__PURE__ */ new Set(["Sheet1"]);
|
|
10778
|
+
charts.forEach((spec, i) => {
|
|
10779
|
+
const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
|
|
10780
|
+
let sheetName = base;
|
|
10781
|
+
let n = 2;
|
|
10782
|
+
while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
|
|
10783
|
+
used.add(sheetName);
|
|
10784
|
+
const aoa = [
|
|
10785
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10786
|
+
...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
|
|
10787
|
+
];
|
|
10788
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
|
|
10789
|
+
});
|
|
10790
|
+
return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
|
|
10791
|
+
}
|
|
10792
|
+
var DOCX_MAX_W = 600;
|
|
10793
|
+
var DOCX_MAX_H = 864;
|
|
10794
|
+
async function renderDocx(md, opts = {}) {
|
|
10795
|
+
const d = await import('docx');
|
|
10796
|
+
const {
|
|
10797
|
+
Document,
|
|
10798
|
+
Packer,
|
|
10799
|
+
Paragraph,
|
|
10800
|
+
TextRun,
|
|
10801
|
+
ImageRun,
|
|
10802
|
+
HeadingLevel,
|
|
10803
|
+
Table,
|
|
10804
|
+
TableRow,
|
|
10805
|
+
TableCell,
|
|
10806
|
+
WidthType,
|
|
10807
|
+
BorderStyle,
|
|
10808
|
+
AlignmentType
|
|
10809
|
+
} = d;
|
|
10810
|
+
const HEADINGS = [
|
|
10811
|
+
HeadingLevel.HEADING_1,
|
|
10812
|
+
HeadingLevel.HEADING_2,
|
|
10813
|
+
HeadingLevel.HEADING_3,
|
|
10814
|
+
HeadingLevel.HEADING_4,
|
|
10815
|
+
HeadingLevel.HEADING_5,
|
|
10816
|
+
HeadingLevel.HEADING_6
|
|
10817
|
+
];
|
|
10818
|
+
const runs = (text) => inlineRuns(text).map((r) => new TextRun({
|
|
10819
|
+
text: r.text,
|
|
10820
|
+
...r.bold ? { bold: true } : {},
|
|
10821
|
+
...r.italic ? { italics: true } : {},
|
|
10822
|
+
...r.code ? { font: "Courier New" } : {}
|
|
10823
|
+
}));
|
|
10824
|
+
const tableOf = (rows) => new Table({
|
|
10825
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
10826
|
+
rows: rows.map((r) => new TableRow({
|
|
10827
|
+
children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
|
|
10828
|
+
}))
|
|
10829
|
+
});
|
|
10830
|
+
const imageParagraph = (img) => {
|
|
10831
|
+
const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
|
|
10832
|
+
return new Paragraph({
|
|
10833
|
+
children: [new ImageRun({
|
|
10834
|
+
type: img.type,
|
|
10835
|
+
data: img.bytes,
|
|
10836
|
+
transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
|
|
10837
|
+
...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
|
|
10838
|
+
})]
|
|
10839
|
+
});
|
|
10840
|
+
};
|
|
10841
|
+
const asImage = async (text) => {
|
|
10842
|
+
const ref = matchImageLine(text);
|
|
10843
|
+
return ref ? await loadImage(ref, opts.loadImageBytes) : null;
|
|
10844
|
+
};
|
|
10845
|
+
const children = [];
|
|
10846
|
+
for (const b of parseBlocks(md)) {
|
|
10847
|
+
switch (b.t) {
|
|
10848
|
+
case "heading":
|
|
10849
|
+
children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
|
|
10850
|
+
break;
|
|
10851
|
+
case "para": {
|
|
10852
|
+
const img = await asImage(b.text);
|
|
10853
|
+
children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
|
|
10854
|
+
break;
|
|
10855
|
+
}
|
|
10856
|
+
case "bullet": {
|
|
10857
|
+
const img = await asImage(b.text);
|
|
10858
|
+
children.push(img ? imageParagraph(img) : new Paragraph({
|
|
10859
|
+
children: runs(b.text),
|
|
10860
|
+
...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
|
|
10861
|
+
}));
|
|
10862
|
+
break;
|
|
10863
|
+
}
|
|
10864
|
+
case "code":
|
|
10865
|
+
for (const cl of b.lines) {
|
|
10866
|
+
children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
|
|
10867
|
+
}
|
|
10868
|
+
break;
|
|
10869
|
+
case "quote":
|
|
10870
|
+
children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
|
|
10871
|
+
break;
|
|
10872
|
+
case "table":
|
|
10873
|
+
children.push(tableOf(b.rows));
|
|
10874
|
+
break;
|
|
10875
|
+
case "chart":
|
|
10876
|
+
if (b.spec.title) {
|
|
10877
|
+
children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
|
|
10878
|
+
}
|
|
10879
|
+
children.push(tableOf(chartToTableRows(b.spec)));
|
|
10880
|
+
break;
|
|
10881
|
+
case "hr":
|
|
10882
|
+
children.push(new Paragraph({
|
|
10883
|
+
children: [],
|
|
10884
|
+
border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
|
|
10885
|
+
}));
|
|
10886
|
+
break;
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
const doc = new Document({
|
|
10890
|
+
numbering: {
|
|
10891
|
+
config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
|
|
10892
|
+
},
|
|
10893
|
+
sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
|
|
10894
|
+
});
|
|
10895
|
+
return new Uint8Array(await Packer.toArrayBuffer(doc));
|
|
10896
|
+
}
|
|
10897
|
+
function pptxConstructor(mod) {
|
|
10898
|
+
let c = mod;
|
|
10899
|
+
for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
|
|
10900
|
+
if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
|
|
10901
|
+
return c;
|
|
10902
|
+
}
|
|
10903
|
+
var SLIDE_H = 7.5;
|
|
10904
|
+
var CONTENT_X = 0.6;
|
|
10905
|
+
var CONTENT_W = 12.1;
|
|
10906
|
+
var CONTENT_BOTTOM = 0.4;
|
|
10907
|
+
async function renderPptx(md, opts = {}) {
|
|
10908
|
+
const PptxGen = pptxConstructor(await import('pptxgenjs'));
|
|
10909
|
+
const pptx = new PptxGen();
|
|
10910
|
+
pptx.layout = "LAYOUT_WIDE";
|
|
10911
|
+
const CHART_TYPES = {
|
|
10912
|
+
bar: pptx.ChartType["bar"],
|
|
10913
|
+
line: pptx.ChartType["line"],
|
|
10914
|
+
pie: pptx.ChartType["pie"],
|
|
10915
|
+
doughnut: pptx.ChartType["doughnut"],
|
|
10916
|
+
area: pptx.ChartType["area"],
|
|
10917
|
+
scatter: pptx.ChartType["scatter"]
|
|
10918
|
+
};
|
|
10919
|
+
const slides = splitSlides(md);
|
|
10920
|
+
const blank = { title: "", body: [""], images: [], charts: [] };
|
|
10921
|
+
for (const s of slides.length ? slides : [blank]) {
|
|
10922
|
+
const slide = pptx.addSlide();
|
|
10923
|
+
if (s.title) slide.addText(s.title, { x: 0.5, y: 0.35, w: 12.3, h: 0.9, fontSize: 28, bold: true, color: "1F2937" });
|
|
10924
|
+
const top = s.title ? 1.5 : 0.5;
|
|
10925
|
+
const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
|
|
10926
|
+
const visuals = [
|
|
10927
|
+
...s.charts.map((spec) => ({ kind: "chart", spec })),
|
|
10928
|
+
...pics.map((img) => ({ kind: "image", img }))
|
|
10929
|
+
];
|
|
10930
|
+
const bodyH = visuals.length ? 2.2 : 5.4;
|
|
10931
|
+
if (s.body.length) {
|
|
10932
|
+
slide.addText(
|
|
10933
|
+
s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
|
|
10934
|
+
{ x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
|
|
10935
|
+
);
|
|
10936
|
+
}
|
|
10937
|
+
if (visuals.length) {
|
|
10938
|
+
const y = top + (s.body.length ? bodyH + 0.2 : 0);
|
|
10939
|
+
const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
|
|
10940
|
+
const gap = 0.3;
|
|
10941
|
+
const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
|
|
10942
|
+
visuals.forEach((v, i) => {
|
|
10943
|
+
const x = CONTENT_X + i * (w + gap);
|
|
10944
|
+
if (v.kind === "image") {
|
|
10945
|
+
slide.addImage({
|
|
10946
|
+
data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
|
|
10947
|
+
x,
|
|
10948
|
+
y,
|
|
10949
|
+
w,
|
|
10950
|
+
h,
|
|
10951
|
+
// 'contain' keeps the aspect ratio inside the box — a stretched
|
|
10952
|
+
// render looks worse than a smaller correct one.
|
|
10953
|
+
sizing: { type: "contain", w, h },
|
|
10954
|
+
...v.img.alt ? { altText: v.img.alt } : {}
|
|
10955
|
+
});
|
|
10956
|
+
} else {
|
|
10957
|
+
slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
|
|
10958
|
+
x,
|
|
10959
|
+
y,
|
|
10960
|
+
w,
|
|
10961
|
+
h,
|
|
10962
|
+
showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
|
|
10963
|
+
legendPos: "b",
|
|
10964
|
+
showTitle: !!v.spec.title,
|
|
10965
|
+
...v.spec.title ? { title: v.spec.title } : {},
|
|
10966
|
+
showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
|
|
10967
|
+
});
|
|
10968
|
+
}
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
|
|
10973
|
+
}
|
|
10974
|
+
function chartData(spec) {
|
|
10975
|
+
const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
|
|
10976
|
+
return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
|
|
10977
|
+
}
|
|
10978
|
+
async function renderDocument(format, source, opts = {}) {
|
|
10979
|
+
if (format === "docx") return renderDocx(source, opts);
|
|
10980
|
+
if (format === "pptx") return renderPptx(source, opts);
|
|
10981
|
+
return renderXlsx(source, opts);
|
|
10982
|
+
}
|
|
10983
|
+
|
|
10984
|
+
// src/tools/generate-document.ts
|
|
10985
|
+
var SOURCE_HINT = {
|
|
10986
|
+
docx: "Markdown",
|
|
10987
|
+
pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
|
|
10988
|
+
xlsx: "CSV (one header row, then data rows)"
|
|
10989
|
+
};
|
|
10990
|
+
var GenerateDocumentTool = class extends BaseTool {
|
|
10991
|
+
/**
|
|
10992
|
+
* `readFile` is injected so the image loader is testable and so a host with a
|
|
10993
|
+
* different workspace (not plain disk) can supply its own reader.
|
|
10994
|
+
*/
|
|
10995
|
+
constructor(readFile = async (p) => new Uint8Array(await fs5.readFile(p))) {
|
|
10996
|
+
super();
|
|
10997
|
+
this.readFile = readFile;
|
|
10998
|
+
}
|
|
10999
|
+
readFile;
|
|
11000
|
+
name = "generate_document";
|
|
11001
|
+
description = "Create a REAL Microsoft Office file (.docx Word, .pptx PowerPoint, .xlsx Excel) in the workspace. You write the SOURCE and this tool renders the actual binary: Markdown for .docx, Markdown slides (separated by --- rules) for .pptx, CSV for .xlsx. ALWAYS use this instead of file_write for those three extensions \u2014 file_write stores your text verbatim, which Office reports as a corrupted file. Supports embedded images (reference a generated image as  on its own line) and real, data-accurate charts (a ```chart:bar / chart:line / chart:pie fenced block whose body is CSV). Use pdf_create for PDFs.";
|
|
11002
|
+
inputSchema = {
|
|
11003
|
+
type: "object",
|
|
11004
|
+
properties: {
|
|
11005
|
+
path: {
|
|
11006
|
+
type: "string",
|
|
11007
|
+
description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
|
|
11008
|
+
},
|
|
11009
|
+
content: {
|
|
11010
|
+
type: "string",
|
|
11011
|
+
description: 'The document source. Markdown for .docx; Markdown slides separated by --- rules for .pptx (each slide starts with a heading); CSV for .xlsx. A standalone  line embeds that image. A fenced ```chart:bar (or chart:line, chart:pie, chart:area, chart:doughnut, chart:scatter) block whose body is CSV \u2014 an optional "title: \u2026" line, then a header row of "<category>,<series1>,<series2>", then data rows \u2014 becomes a real chart.'
|
|
11012
|
+
},
|
|
11013
|
+
format: {
|
|
11014
|
+
type: "string",
|
|
11015
|
+
description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
|
|
11016
|
+
}
|
|
11017
|
+
},
|
|
11018
|
+
required: ["path", "content"]
|
|
11019
|
+
};
|
|
11020
|
+
isDangerous() {
|
|
11021
|
+
return true;
|
|
11022
|
+
}
|
|
11023
|
+
async execute(input, options) {
|
|
11024
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
11025
|
+
if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
|
|
11026
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
11027
|
+
if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
|
|
11028
|
+
const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
|
|
11029
|
+
const ext = requested || fileExt(filePath);
|
|
11030
|
+
if (!isDocumentFormat(ext)) {
|
|
11031
|
+
return `generate_document renders .docx, .pptx and .xlsx. "${filePath}" is not one of those \u2014 use pdf_create for a PDF, or file_write for a text file.`;
|
|
11032
|
+
}
|
|
11033
|
+
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
11034
|
+
const embedded = [];
|
|
11035
|
+
const skipped = [];
|
|
11036
|
+
const loadImageBytes = async (url) => {
|
|
11037
|
+
if (/^(https?|ftp):\/\//i.test(url)) {
|
|
11038
|
+
skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
|
|
11039
|
+
return null;
|
|
11040
|
+
}
|
|
11041
|
+
const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
|
|
11042
|
+
try {
|
|
11043
|
+
const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
|
|
11044
|
+
embedded.push(local);
|
|
11045
|
+
return bytes2;
|
|
11046
|
+
} catch {
|
|
11047
|
+
skipped.push(`${local} (not found in the workspace)`);
|
|
11048
|
+
return null;
|
|
11049
|
+
}
|
|
11050
|
+
};
|
|
11051
|
+
if (options.saveSnapshot) {
|
|
11052
|
+
try {
|
|
11053
|
+
const old = await fs5.readFile(absPath, "utf-8");
|
|
11054
|
+
await options.saveSnapshot(absPath, old);
|
|
11055
|
+
} catch {
|
|
11056
|
+
}
|
|
11057
|
+
}
|
|
11058
|
+
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11059
|
+
await fs5.mkdir(path25.dirname(absPath), { recursive: true });
|
|
11060
|
+
await fs5.writeFile(absPath, bytes);
|
|
11061
|
+
const charts = countCharts(content);
|
|
11062
|
+
return [
|
|
11063
|
+
`Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
|
|
11064
|
+
embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
|
|
11065
|
+
// Named, not counted: a dropped image is the failure the user actually
|
|
11066
|
+
// notices, and the model can only fix it if it is told which reference died.
|
|
11067
|
+
skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
|
|
11068
|
+
charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
|
|
11069
|
+
charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
|
|
11070
|
+
charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
|
|
11071
|
+
].filter(Boolean).join("\n");
|
|
11072
|
+
}
|
|
11073
|
+
};
|
|
11074
|
+
var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
|
|
11075
|
+
function countCharts(source) {
|
|
11076
|
+
return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
|
|
11077
|
+
}
|
|
11078
|
+
function buildDocumentTools(readFile) {
|
|
11079
|
+
return readFile ? [new GenerateDocumentTool(readFile)] : [];
|
|
11080
|
+
}
|
|
10307
11081
|
|
|
10308
11082
|
// src/core/run-breaker.ts
|
|
10309
11083
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
@@ -10626,35 +11400,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
|
|
|
10626
11400
|
}
|
|
10627
11401
|
}
|
|
10628
11402
|
};
|
|
10629
|
-
var WorkspaceSandboxError = class extends Error {
|
|
10630
|
-
constructor(attempted, workspaceRoot) {
|
|
10631
|
-
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10632
|
-
this.name = "WorkspaceSandboxError";
|
|
10633
|
-
}
|
|
10634
|
-
};
|
|
10635
|
-
function resolveInWorkspace(workspaceRoot, input) {
|
|
10636
|
-
if (typeof input !== "string" || input.length === 0) {
|
|
10637
|
-
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10638
|
-
}
|
|
10639
|
-
const root = path24.resolve(workspaceRoot);
|
|
10640
|
-
const abs = path24.isAbsolute(input) ? path24.resolve(input) : path24.resolve(root, input);
|
|
10641
|
-
const rel = path24.relative(root, abs);
|
|
10642
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path24.isAbsolute(rel)) {
|
|
10643
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10644
|
-
}
|
|
10645
|
-
try {
|
|
10646
|
-
const real = fs23.realpathSync(abs);
|
|
10647
|
-
const realRel = path24.relative(root, real);
|
|
10648
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path24.isAbsolute(realRel))) {
|
|
10649
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10650
|
-
}
|
|
10651
|
-
} catch (e) {
|
|
10652
|
-
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10653
|
-
}
|
|
10654
|
-
return abs;
|
|
10655
|
-
}
|
|
10656
|
-
|
|
10657
|
-
// src/tools/file.ts
|
|
10658
11403
|
var FileReadTool = class extends BaseTool {
|
|
10659
11404
|
name = "file_read";
|
|
10660
11405
|
description = "Read the contents of a file. Returns the file content as a string.";
|
|
@@ -10672,7 +11417,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
10672
11417
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10673
11418
|
const offset = input["offset"] ?? 1;
|
|
10674
11419
|
const limit = input["limit"];
|
|
10675
|
-
const content = await
|
|
11420
|
+
const content = await fs5.readFile(absPath, "utf-8");
|
|
10676
11421
|
const lines = content.split("\n");
|
|
10677
11422
|
const start = Math.max(0, offset - 1);
|
|
10678
11423
|
const end = limit ? start + limit : lines.length;
|
|
@@ -10701,13 +11446,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
10701
11446
|
const content = input["content"];
|
|
10702
11447
|
if (options.saveSnapshot) {
|
|
10703
11448
|
try {
|
|
10704
|
-
const oldContent = await
|
|
11449
|
+
const oldContent = await fs5.readFile(absPath, "utf-8");
|
|
10705
11450
|
await options.saveSnapshot(absPath, oldContent);
|
|
10706
11451
|
} catch {
|
|
10707
11452
|
}
|
|
10708
11453
|
}
|
|
10709
|
-
await
|
|
10710
|
-
await
|
|
11454
|
+
await fs5.mkdir(path25.dirname(absPath), { recursive: true });
|
|
11455
|
+
await fs5.writeFile(absPath, content, "utf-8");
|
|
10711
11456
|
return `Written ${content.length} characters to ${filePath}`;
|
|
10712
11457
|
}
|
|
10713
11458
|
};
|
|
@@ -10733,7 +11478,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10733
11478
|
const oldString = input["old_string"];
|
|
10734
11479
|
const newString = input["new_string"];
|
|
10735
11480
|
const replaceAll = input["replace_all"] ?? false;
|
|
10736
|
-
const rawContent = await
|
|
11481
|
+
const rawContent = await fs5.readFile(absPath, "utf-8");
|
|
10737
11482
|
if (options.saveSnapshot) {
|
|
10738
11483
|
await options.saveSnapshot(absPath, rawContent);
|
|
10739
11484
|
}
|
|
@@ -10745,7 +11490,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10745
11490
|
);
|
|
10746
11491
|
}
|
|
10747
11492
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
10748
|
-
await
|
|
11493
|
+
await fs5.writeFile(absPath, updated, "utf-8");
|
|
10749
11494
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
10750
11495
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
10751
11496
|
}
|
|
@@ -10768,12 +11513,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
10768
11513
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10769
11514
|
if (options.saveSnapshot) {
|
|
10770
11515
|
try {
|
|
10771
|
-
const oldContent = await
|
|
11516
|
+
const oldContent = await fs5.readFile(absPath, "utf-8");
|
|
10772
11517
|
await options.saveSnapshot(absPath, oldContent);
|
|
10773
11518
|
} catch {
|
|
10774
11519
|
}
|
|
10775
11520
|
}
|
|
10776
|
-
await
|
|
11521
|
+
await fs5.rm(absPath, { recursive: false });
|
|
10777
11522
|
return `Deleted ${filePath}`;
|
|
10778
11523
|
}
|
|
10779
11524
|
};
|
|
@@ -10790,7 +11535,7 @@ var FileListTool = class extends BaseTool {
|
|
|
10790
11535
|
async execute(input, _options) {
|
|
10791
11536
|
const inputPath = input["path"] || ".";
|
|
10792
11537
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
10793
|
-
const entries = await
|
|
11538
|
+
const entries = await fs5.readdir(absPath, { withFileTypes: true });
|
|
10794
11539
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
10795
11540
|
}
|
|
10796
11541
|
};
|
|
@@ -11203,8 +11948,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11203
11948
|
}
|
|
11204
11949
|
};
|
|
11205
11950
|
async function fileToImageAttachment(filePath) {
|
|
11206
|
-
const data = await
|
|
11207
|
-
const ext =
|
|
11951
|
+
const data = await fs5.readFile(filePath);
|
|
11952
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
11208
11953
|
const mimeMap = {
|
|
11209
11954
|
".jpg": "image/jpeg",
|
|
11210
11955
|
".jpeg": "image/jpeg",
|
|
@@ -11238,14 +11983,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11238
11983
|
const filePath = input["path"];
|
|
11239
11984
|
const content = input["content"];
|
|
11240
11985
|
const title = input["title"];
|
|
11241
|
-
const dir =
|
|
11242
|
-
if (!
|
|
11243
|
-
|
|
11986
|
+
const dir = path25.dirname(filePath);
|
|
11987
|
+
if (!fs24.existsSync(dir)) {
|
|
11988
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
11244
11989
|
}
|
|
11245
11990
|
return new Promise((resolve2, reject) => {
|
|
11246
11991
|
try {
|
|
11247
11992
|
const doc = new PDFDocument({ margin: 50 });
|
|
11248
|
-
const stream =
|
|
11993
|
+
const stream = fs24.createWriteStream(filePath);
|
|
11249
11994
|
doc.pipe(stream);
|
|
11250
11995
|
if (title) {
|
|
11251
11996
|
doc.info["Title"] = title;
|
|
@@ -11323,22 +12068,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11323
12068
|
}
|
|
11324
12069
|
cmdPrefix = NODE_CMD;
|
|
11325
12070
|
}
|
|
11326
|
-
const tmpDir =
|
|
11327
|
-
if (!
|
|
11328
|
-
|
|
12071
|
+
const tmpDir = path25.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12072
|
+
if (!fs24.existsSync(tmpDir)) {
|
|
12073
|
+
fs24.mkdirSync(tmpDir, { recursive: true });
|
|
11329
12074
|
}
|
|
11330
12075
|
const extension = language === "python" ? "py" : "js";
|
|
11331
12076
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
11332
|
-
const filePath =
|
|
11333
|
-
|
|
12077
|
+
const filePath = path25.join(tmpDir, fileName);
|
|
12078
|
+
fs24.writeFileSync(filePath, code, "utf-8");
|
|
11334
12079
|
const execArgs = [filePath, ...args];
|
|
11335
12080
|
return new Promise((resolve2) => {
|
|
11336
12081
|
const startMs = Date.now();
|
|
11337
12082
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
11338
12083
|
const duration = Date.now() - startMs;
|
|
11339
12084
|
try {
|
|
11340
|
-
if (
|
|
11341
|
-
|
|
12085
|
+
if (fs24.existsSync(filePath)) {
|
|
12086
|
+
fs24.unlinkSync(filePath);
|
|
11342
12087
|
}
|
|
11343
12088
|
} catch (cleanupErr) {
|
|
11344
12089
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -11650,7 +12395,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11650
12395
|
};
|
|
11651
12396
|
async execute(input, _options) {
|
|
11652
12397
|
const pattern = input["pattern"];
|
|
11653
|
-
const searchPath = input["path"] ?
|
|
12398
|
+
const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11654
12399
|
const matches = await glob(pattern, {
|
|
11655
12400
|
cwd: searchPath,
|
|
11656
12401
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -11663,7 +12408,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11663
12408
|
const withMtime = await Promise.all(
|
|
11664
12409
|
matches.map(async (rel) => {
|
|
11665
12410
|
try {
|
|
11666
|
-
const stat = await
|
|
12411
|
+
const stat = await fs5.stat(path25.join(searchPath, rel));
|
|
11667
12412
|
return { rel, mtime: stat.mtimeMs };
|
|
11668
12413
|
} catch {
|
|
11669
12414
|
return { rel, mtime: 0 };
|
|
@@ -11712,7 +12457,7 @@ var GrepTool = class extends BaseTool {
|
|
|
11712
12457
|
};
|
|
11713
12458
|
async execute(input, _options) {
|
|
11714
12459
|
const pattern = input["pattern"];
|
|
11715
|
-
const searchPath = input["path"] ?
|
|
12460
|
+
const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11716
12461
|
const globPattern = input["glob"];
|
|
11717
12462
|
const outputMode = input["output_mode"] ?? "content";
|
|
11718
12463
|
const context = input["context"] ?? 0;
|
|
@@ -11766,15 +12511,15 @@ var GrepTool = class extends BaseTool {
|
|
|
11766
12511
|
nodir: true
|
|
11767
12512
|
});
|
|
11768
12513
|
} catch {
|
|
11769
|
-
files = [
|
|
12514
|
+
files = [path25.relative(searchPath, searchPath) || "."];
|
|
11770
12515
|
}
|
|
11771
12516
|
const results = [];
|
|
11772
12517
|
let totalCount = 0;
|
|
11773
12518
|
for (const rel of files) {
|
|
11774
|
-
const abs =
|
|
12519
|
+
const abs = path25.join(searchPath, rel);
|
|
11775
12520
|
let content;
|
|
11776
12521
|
try {
|
|
11777
|
-
content = await
|
|
12522
|
+
content = await fs5.readFile(abs, "utf-8");
|
|
11778
12523
|
} catch {
|
|
11779
12524
|
continue;
|
|
11780
12525
|
}
|
|
@@ -12158,10 +12903,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
12158
12903
|
}
|
|
12159
12904
|
isIgnored(filePath) {
|
|
12160
12905
|
if (!filePath) return false;
|
|
12161
|
-
const abs =
|
|
12162
|
-
const rel =
|
|
12163
|
-
if (!rel || rel.startsWith("..") ||
|
|
12164
|
-
const posixRel = rel.split(
|
|
12906
|
+
const abs = path25.resolve(this.workspaceRoot, filePath);
|
|
12907
|
+
const rel = path25.relative(this.workspaceRoot, abs);
|
|
12908
|
+
if (!rel || rel.startsWith("..") || path25.isAbsolute(rel)) return true;
|
|
12909
|
+
const posixRel = rel.split(path25.sep).join("/");
|
|
12165
12910
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12166
12911
|
}
|
|
12167
12912
|
};
|
|
@@ -12395,22 +13140,22 @@ var FileMcpOAuthStore = class {
|
|
|
12395
13140
|
filePath;
|
|
12396
13141
|
load() {
|
|
12397
13142
|
try {
|
|
12398
|
-
return JSON.parse(
|
|
13143
|
+
return JSON.parse(fs24.readFileSync(this.filePath, "utf-8"));
|
|
12399
13144
|
} catch {
|
|
12400
13145
|
return void 0;
|
|
12401
13146
|
}
|
|
12402
13147
|
}
|
|
12403
13148
|
save(state) {
|
|
12404
|
-
|
|
12405
|
-
|
|
13149
|
+
fs24.mkdirSync(path25.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13150
|
+
fs24.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
12406
13151
|
try {
|
|
12407
|
-
|
|
13152
|
+
fs24.chmodSync(this.filePath, 384);
|
|
12408
13153
|
} catch {
|
|
12409
13154
|
}
|
|
12410
13155
|
}
|
|
12411
13156
|
clear() {
|
|
12412
13157
|
try {
|
|
12413
|
-
|
|
13158
|
+
fs24.rmSync(this.filePath);
|
|
12414
13159
|
} catch {
|
|
12415
13160
|
}
|
|
12416
13161
|
}
|
|
@@ -13449,7 +14194,7 @@ var TaskAnalyzer = class {
|
|
|
13449
14194
|
analysisCache.clear();
|
|
13450
14195
|
}
|
|
13451
14196
|
};
|
|
13452
|
-
var DEFAULT_STATS_FILE =
|
|
14197
|
+
var DEFAULT_STATS_FILE = path25.join(os6.homedir(), ".cascade", "model-perf.json");
|
|
13453
14198
|
var ModelPerformanceTracker = class {
|
|
13454
14199
|
stats = /* @__PURE__ */ new Map();
|
|
13455
14200
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -13469,7 +14214,7 @@ var ModelPerformanceTracker = class {
|
|
|
13469
14214
|
if (this.loaded) return;
|
|
13470
14215
|
this.loaded = true;
|
|
13471
14216
|
try {
|
|
13472
|
-
const raw = await
|
|
14217
|
+
const raw = await fs5.readFile(this.statsFile, "utf-8");
|
|
13473
14218
|
const parsed = JSON.parse(raw);
|
|
13474
14219
|
if (parsed.models) {
|
|
13475
14220
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -13489,15 +14234,15 @@ var ModelPerformanceTracker = class {
|
|
|
13489
14234
|
async save() {
|
|
13490
14235
|
if (this.readOnly) return;
|
|
13491
14236
|
try {
|
|
13492
|
-
await
|
|
14237
|
+
await fs5.mkdir(path25.dirname(this.statsFile), { recursive: true });
|
|
13493
14238
|
const modelsObj = {};
|
|
13494
14239
|
const featuresObj = {};
|
|
13495
14240
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
13496
14241
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
13497
14242
|
const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
|
|
13498
14243
|
const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
|
|
13499
|
-
await
|
|
13500
|
-
await
|
|
14244
|
+
await fs5.writeFile(tmp, json, "utf-8");
|
|
14245
|
+
await fs5.rename(tmp, this.statsFile);
|
|
13501
14246
|
} catch {
|
|
13502
14247
|
}
|
|
13503
14248
|
}
|
|
@@ -14037,9 +14782,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14037
14782
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
14038
14783
|
async loadPersistedTools() {
|
|
14039
14784
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14040
|
-
const file =
|
|
14785
|
+
const file = path25.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
14041
14786
|
try {
|
|
14042
|
-
const raw = await
|
|
14787
|
+
const raw = await fs5.readFile(file, "utf-8");
|
|
14043
14788
|
const specs = JSON.parse(raw);
|
|
14044
14789
|
if (!Array.isArray(specs)) return;
|
|
14045
14790
|
let loaded = 0;
|
|
@@ -14061,11 +14806,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14061
14806
|
}
|
|
14062
14807
|
async persist() {
|
|
14063
14808
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14064
|
-
const dir =
|
|
14065
|
-
const file =
|
|
14809
|
+
const dir = path25.join(this.workspacePath, ".cascade");
|
|
14810
|
+
const file = path25.join(dir, DYNAMIC_TOOLS_FILE);
|
|
14066
14811
|
try {
|
|
14067
|
-
await
|
|
14068
|
-
await
|
|
14812
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
14813
|
+
await fs5.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
14069
14814
|
} catch (err) {
|
|
14070
14815
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
14071
14816
|
}
|
|
@@ -14082,12 +14827,12 @@ var WorldStateDB = class {
|
|
|
14082
14827
|
constructor(workspacePath, debugMode = false) {
|
|
14083
14828
|
this.workspacePath = workspacePath;
|
|
14084
14829
|
this.debugMode = debugMode;
|
|
14085
|
-
const cascadeDir =
|
|
14086
|
-
if (!
|
|
14087
|
-
|
|
14830
|
+
const cascadeDir = path25.join(workspacePath, ".cascade");
|
|
14831
|
+
if (!fs24.existsSync(cascadeDir)) {
|
|
14832
|
+
fs24.mkdirSync(cascadeDir, { recursive: true });
|
|
14088
14833
|
}
|
|
14089
|
-
this.keyPath =
|
|
14090
|
-
this.dbPath =
|
|
14834
|
+
this.keyPath = path25.join(cascadeDir, "world_state.key");
|
|
14835
|
+
this.dbPath = path25.join(cascadeDir, "world_state.db");
|
|
14091
14836
|
this.initEncryptionKey();
|
|
14092
14837
|
this.db = new Database(this.dbPath);
|
|
14093
14838
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14144,11 +14889,11 @@ var WorldStateDB = class {
|
|
|
14144
14889
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14145
14890
|
}
|
|
14146
14891
|
initEncryptionKey() {
|
|
14147
|
-
if (
|
|
14148
|
-
this.encryptionKey =
|
|
14892
|
+
if (fs24.existsSync(this.keyPath)) {
|
|
14893
|
+
this.encryptionKey = fs24.readFileSync(this.keyPath);
|
|
14149
14894
|
} else {
|
|
14150
14895
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
14151
|
-
|
|
14896
|
+
fs24.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14152
14897
|
}
|
|
14153
14898
|
}
|
|
14154
14899
|
encrypt(text) {
|
|
@@ -14434,9 +15179,9 @@ var WorldStateDB = class {
|
|
|
14434
15179
|
dumpDebugIfNeeded() {
|
|
14435
15180
|
if (!this.debugMode) return;
|
|
14436
15181
|
try {
|
|
14437
|
-
const dumpPath =
|
|
15182
|
+
const dumpPath = path25.join(os6.tmpdir(), "cascade_world_state_debug.json");
|
|
14438
15183
|
const entries = this.getAllEntries();
|
|
14439
|
-
|
|
15184
|
+
fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
14440
15185
|
} catch (err) {
|
|
14441
15186
|
console.error("Failed to dump debug world state", err);
|
|
14442
15187
|
}
|
|
@@ -14491,9 +15236,9 @@ var CascadeIgnore = class {
|
|
|
14491
15236
|
]);
|
|
14492
15237
|
}
|
|
14493
15238
|
async load(workspacePath) {
|
|
14494
|
-
const filePath =
|
|
15239
|
+
const filePath = path25.join(workspacePath, ".cascadeignore");
|
|
14495
15240
|
try {
|
|
14496
|
-
const content = await
|
|
15241
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
14497
15242
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
14498
15243
|
this.ig.add(lines);
|
|
14499
15244
|
this.loaded = true;
|
|
@@ -14502,7 +15247,7 @@ var CascadeIgnore = class {
|
|
|
14502
15247
|
}
|
|
14503
15248
|
isIgnored(filePath, workspacePath) {
|
|
14504
15249
|
try {
|
|
14505
|
-
const relative = workspacePath ?
|
|
15250
|
+
const relative = workspacePath ? path25.relative(workspacePath, filePath) : filePath;
|
|
14506
15251
|
return this.ig.ignores(relative);
|
|
14507
15252
|
} catch {
|
|
14508
15253
|
return false;
|
|
@@ -14845,7 +15590,7 @@ var WorkspaceIndex = class {
|
|
|
14845
15590
|
isIgnored;
|
|
14846
15591
|
chunker;
|
|
14847
15592
|
constructor(opts) {
|
|
14848
|
-
this.root =
|
|
15593
|
+
this.root = path25.resolve(opts.root);
|
|
14849
15594
|
this.db = opts.db;
|
|
14850
15595
|
this.namespace = opts.namespace ?? "code";
|
|
14851
15596
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -14872,19 +15617,19 @@ var WorkspaceIndex = class {
|
|
|
14872
15617
|
const walk = async (dir) => {
|
|
14873
15618
|
let entries;
|
|
14874
15619
|
try {
|
|
14875
|
-
entries = await
|
|
15620
|
+
entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
14876
15621
|
} catch {
|
|
14877
15622
|
return;
|
|
14878
15623
|
}
|
|
14879
15624
|
for (const entry of entries) {
|
|
14880
|
-
const abs =
|
|
15625
|
+
const abs = path25.join(dir, entry.name);
|
|
14881
15626
|
if (entry.isDirectory()) {
|
|
14882
15627
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
14883
15628
|
await walk(abs);
|
|
14884
15629
|
} else if (entry.isFile()) {
|
|
14885
|
-
const ext =
|
|
15630
|
+
const ext = path25.extname(entry.name).slice(1).toLowerCase();
|
|
14886
15631
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
14887
|
-
out.push(
|
|
15632
|
+
out.push(path25.relative(this.root, abs));
|
|
14888
15633
|
}
|
|
14889
15634
|
}
|
|
14890
15635
|
};
|
|
@@ -14914,10 +15659,10 @@ var WorkspaceIndex = class {
|
|
|
14914
15659
|
const entries = [];
|
|
14915
15660
|
for (const rel of rels) {
|
|
14916
15661
|
try {
|
|
14917
|
-
const abs =
|
|
14918
|
-
const stat = await
|
|
15662
|
+
const abs = path25.join(this.root, rel);
|
|
15663
|
+
const stat = await fs5.stat(abs);
|
|
14919
15664
|
if (stat.size > this.maxFileBytes) continue;
|
|
14920
|
-
const buf = await
|
|
15665
|
+
const buf = await fs5.readFile(abs);
|
|
14921
15666
|
if (buf.includes(0)) continue;
|
|
14922
15667
|
entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
|
|
14923
15668
|
} catch {
|
|
@@ -15486,7 +16231,7 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15486
16231
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
15487
16232
|
try {
|
|
15488
16233
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
15489
|
-
fileDeadModelPersistence(
|
|
16234
|
+
fileDeadModelPersistence(path25.join(workspacePath, ".cascade", "dead-models.json"))
|
|
15490
16235
|
));
|
|
15491
16236
|
} catch {
|
|
15492
16237
|
}
|
|
@@ -15494,6 +16239,7 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15494
16239
|
(this.config.providers ?? []).map((p) => p.type)
|
|
15495
16240
|
);
|
|
15496
16241
|
this.registerMediaTools(workspacePath);
|
|
16242
|
+
this.registerDocumentTools(workspacePath);
|
|
15497
16243
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
15498
16244
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
15499
16245
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -15573,8 +16319,8 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15573
16319
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
15574
16320
|
return;
|
|
15575
16321
|
}
|
|
15576
|
-
const dbPath = ci.dbPath ||
|
|
15577
|
-
|
|
16322
|
+
const dbPath = ci.dbPath || path25.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16323
|
+
fs24.mkdirSync(path25.dirname(dbPath), { recursive: true });
|
|
15578
16324
|
const db = new Database(dbPath);
|
|
15579
16325
|
db.pragma("journal_mode = WAL");
|
|
15580
16326
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -15914,6 +16660,35 @@ ${input}` }],
|
|
|
15914
16660
|
this.toolRegistry.register(tool);
|
|
15915
16661
|
}
|
|
15916
16662
|
}
|
|
16663
|
+
/**
|
|
16664
|
+
* Register `generate_document` where there is a real workspace to write into.
|
|
16665
|
+
*
|
|
16666
|
+
* A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
|
|
16667
|
+
* does exactly what it says and writes the model's text verbatim — produced
|
|
16668
|
+
* files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
|
|
16669
|
+
* the conversion (see tools/generate-document.ts for why it isn't folded into
|
|
16670
|
+
* file_write).
|
|
16671
|
+
*
|
|
16672
|
+
* Registered OUTSIDE `enabledTools` for the same reason the media tools are:
|
|
16673
|
+
* that allowlist is a blast-radius control for tools that reach the machine
|
|
16674
|
+
* or the network, and this one only writes the artifact the user asked for,
|
|
16675
|
+
* into the workspace the run already owns. `disabledTools` still removes it.
|
|
16676
|
+
* A host with no filesystem (the cloud) has its own browser-side exporter and
|
|
16677
|
+
* simply never reaches this path — `buildDocumentTools` returns nothing
|
|
16678
|
+
* without a file reader, mirroring `transcribe_audio`.
|
|
16679
|
+
*/
|
|
16680
|
+
registerDocumentTools(workspacePath) {
|
|
16681
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
16682
|
+
const tools = buildDocumentTools(async (p) => {
|
|
16683
|
+
const fsp = await import('fs/promises');
|
|
16684
|
+
return new Uint8Array(await fsp.readFile(p));
|
|
16685
|
+
});
|
|
16686
|
+
for (const tool of tools) {
|
|
16687
|
+
if (denied.has(tool.name)) continue;
|
|
16688
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
16689
|
+
this.toolRegistry.register(tool);
|
|
16690
|
+
}
|
|
16691
|
+
}
|
|
15917
16692
|
/**
|
|
15918
16693
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
15919
16694
|
* have actually performed for this user, on top of public benchmarks.
|
|
@@ -16862,7 +17637,7 @@ var Keystore = class {
|
|
|
16862
17637
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
16863
17638
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
16864
17639
|
this.backend = "keytar";
|
|
16865
|
-
if (password &&
|
|
17640
|
+
if (password && fs24.existsSync(this.storePath)) {
|
|
16866
17641
|
try {
|
|
16867
17642
|
const fileEntries = this.decryptFile(password);
|
|
16868
17643
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -16881,7 +17656,7 @@ var Keystore = class {
|
|
|
16881
17656
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
16882
17657
|
);
|
|
16883
17658
|
}
|
|
16884
|
-
if (!
|
|
17659
|
+
if (!fs24.existsSync(this.storePath)) {
|
|
16885
17660
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
16886
17661
|
this.masterKey = this.deriveKey(password, salt);
|
|
16887
17662
|
this.writeWithSalt({}, salt);
|
|
@@ -16895,7 +17670,7 @@ var Keystore = class {
|
|
|
16895
17670
|
}
|
|
16896
17671
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
16897
17672
|
unlockSync(password) {
|
|
16898
|
-
if (!
|
|
17673
|
+
if (!fs24.existsSync(this.storePath)) {
|
|
16899
17674
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
16900
17675
|
this.masterKey = this.deriveKey(password, salt);
|
|
16901
17676
|
this.writeWithSalt({}, salt);
|
|
@@ -16953,7 +17728,7 @@ var Keystore = class {
|
|
|
16953
17728
|
}
|
|
16954
17729
|
}
|
|
16955
17730
|
decryptFile(password, knownSalt) {
|
|
16956
|
-
if (!
|
|
17731
|
+
if (!fs24.existsSync(this.storePath)) return {};
|
|
16957
17732
|
try {
|
|
16958
17733
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
16959
17734
|
const useSalt = knownSalt ?? salt;
|
|
@@ -16975,8 +17750,8 @@ var Keystore = class {
|
|
|
16975
17750
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16976
17751
|
const tag = cipher.getAuthTag();
|
|
16977
17752
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
16978
|
-
|
|
16979
|
-
|
|
17753
|
+
fs24.mkdirSync(path25.dirname(this.storePath), { recursive: true });
|
|
17754
|
+
fs24.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16980
17755
|
}
|
|
16981
17756
|
writeWithSalt(data, salt) {
|
|
16982
17757
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -16986,11 +17761,11 @@ var Keystore = class {
|
|
|
16986
17761
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16987
17762
|
const tag = cipher.getAuthTag();
|
|
16988
17763
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
16989
|
-
|
|
16990
|
-
|
|
17764
|
+
fs24.mkdirSync(path25.dirname(this.storePath), { recursive: true });
|
|
17765
|
+
fs24.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16991
17766
|
}
|
|
16992
17767
|
readRaw() {
|
|
16993
|
-
const buf =
|
|
17768
|
+
const buf = fs24.readFileSync(this.storePath);
|
|
16994
17769
|
let offset = 0;
|
|
16995
17770
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
16996
17771
|
offset += SALT_LEN;
|
|
@@ -17006,9 +17781,9 @@ var Keystore = class {
|
|
|
17006
17781
|
}
|
|
17007
17782
|
};
|
|
17008
17783
|
async function loadCascadeMd(workspacePath) {
|
|
17009
|
-
const filePath =
|
|
17784
|
+
const filePath = path25.join(workspacePath, "CASCADE.md");
|
|
17010
17785
|
try {
|
|
17011
|
-
const raw = await
|
|
17786
|
+
const raw = await fs5.readFile(filePath, "utf-8");
|
|
17012
17787
|
return parseCascadeMd(raw);
|
|
17013
17788
|
} catch {
|
|
17014
17789
|
return null;
|
|
@@ -17037,7 +17812,7 @@ ${raw.trim()}`;
|
|
|
17037
17812
|
var MemoryStore = class _MemoryStore {
|
|
17038
17813
|
db;
|
|
17039
17814
|
constructor(dbPath) {
|
|
17040
|
-
|
|
17815
|
+
fs24.mkdirSync(path25.dirname(dbPath), { recursive: true });
|
|
17041
17816
|
try {
|
|
17042
17817
|
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
17043
17818
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17873,7 +18648,7 @@ Original error: ${err.message}`
|
|
|
17873
18648
|
}
|
|
17874
18649
|
};
|
|
17875
18650
|
function credentialsPath(globalDir2) {
|
|
17876
|
-
return
|
|
18651
|
+
return path25.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
17877
18652
|
}
|
|
17878
18653
|
function providerKey(p) {
|
|
17879
18654
|
if (p.type === "azure") {
|
|
@@ -17886,7 +18661,7 @@ function isPersistable(p) {
|
|
|
17886
18661
|
}
|
|
17887
18662
|
function loadGlobalCredentials(globalDir2) {
|
|
17888
18663
|
try {
|
|
17889
|
-
const raw =
|
|
18664
|
+
const raw = fs24.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
17890
18665
|
const parsed = JSON.parse(raw);
|
|
17891
18666
|
if (!Array.isArray(parsed.providers)) return [];
|
|
17892
18667
|
return parsed.providers.filter(
|
|
@@ -17898,13 +18673,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
17898
18673
|
}
|
|
17899
18674
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
17900
18675
|
const filePath = credentialsPath(globalDir2);
|
|
17901
|
-
|
|
18676
|
+
fs24.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
17902
18677
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
17903
18678
|
const tmp = `${filePath}.tmp`;
|
|
17904
|
-
|
|
17905
|
-
|
|
18679
|
+
fs24.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
18680
|
+
fs24.renameSync(tmp, filePath);
|
|
17906
18681
|
try {
|
|
17907
|
-
|
|
18682
|
+
fs24.chmodSync(filePath, 384);
|
|
17908
18683
|
} catch {
|
|
17909
18684
|
}
|
|
17910
18685
|
}
|
|
@@ -17939,7 +18714,7 @@ var ConfigManager = class {
|
|
|
17939
18714
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
17940
18715
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
17941
18716
|
this.workspacePath = workspacePath;
|
|
17942
|
-
this.globalDir = globalDirOverride ??
|
|
18717
|
+
this.globalDir = globalDirOverride ?? path25.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
17943
18718
|
}
|
|
17944
18719
|
async load() {
|
|
17945
18720
|
this.config = await this.loadConfig();
|
|
@@ -17953,8 +18728,8 @@ var ConfigManager = class {
|
|
|
17953
18728
|
this.ignore = new CascadeIgnore();
|
|
17954
18729
|
await this.ignore.load(this.workspacePath);
|
|
17955
18730
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
17956
|
-
this.keystore = new Keystore(
|
|
17957
|
-
this.store = new MemoryStore(
|
|
18731
|
+
this.keystore = new Keystore(path25.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
18732
|
+
this.store = new MemoryStore(path25.join(this.workspacePath, CASCADE_DB_FILE));
|
|
17958
18733
|
await this.injectEnvKeys();
|
|
17959
18734
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
17960
18735
|
await this.ensureDefaultIdentity();
|
|
@@ -17979,9 +18754,9 @@ var ConfigManager = class {
|
|
|
17979
18754
|
return this.workspacePath;
|
|
17980
18755
|
}
|
|
17981
18756
|
async save() {
|
|
17982
|
-
const configPath =
|
|
17983
|
-
await
|
|
17984
|
-
await
|
|
18757
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18758
|
+
await fs5.mkdir(path25.dirname(configPath), { recursive: true });
|
|
18759
|
+
await fs5.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
17985
18760
|
try {
|
|
17986
18761
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
17987
18762
|
} catch (err) {
|
|
@@ -18016,9 +18791,9 @@ var ConfigManager = class {
|
|
|
18016
18791
|
return configProvider?.apiKey;
|
|
18017
18792
|
}
|
|
18018
18793
|
async loadConfig() {
|
|
18019
|
-
const configPath =
|
|
18794
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18020
18795
|
try {
|
|
18021
|
-
const raw = await
|
|
18796
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
18022
18797
|
return validateConfig(JSON.parse(raw));
|
|
18023
18798
|
} catch (err) {
|
|
18024
18799
|
if (err.code === "ENOENT") {
|
|
@@ -18548,7 +19323,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
18548
19323
|
}
|
|
18549
19324
|
|
|
18550
19325
|
// src/dashboard/server.ts
|
|
18551
|
-
var __dirname$1 =
|
|
19326
|
+
var __dirname$1 = path25.dirname(fileURLToPath(import.meta.url));
|
|
18552
19327
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
18553
19328
|
var DashboardServer = class {
|
|
18554
19329
|
app;
|
|
@@ -18828,14 +19603,14 @@ var DashboardServer = class {
|
|
|
18828
19603
|
*/
|
|
18829
19604
|
persistConfig() {
|
|
18830
19605
|
try {
|
|
18831
|
-
const configPath =
|
|
18832
|
-
|
|
18833
|
-
|
|
19606
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19607
|
+
fs24.mkdirSync(path25.dirname(configPath), { recursive: true });
|
|
19608
|
+
fs24.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18834
19609
|
} catch (err) {
|
|
18835
19610
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
18836
19611
|
}
|
|
18837
19612
|
try {
|
|
18838
|
-
saveGlobalCredentials(
|
|
19613
|
+
saveGlobalCredentials(path25.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
18839
19614
|
} catch (err) {
|
|
18840
19615
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
18841
19616
|
}
|
|
@@ -18850,15 +19625,15 @@ var DashboardServer = class {
|
|
|
18850
19625
|
resolveDashboardSecret() {
|
|
18851
19626
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
18852
19627
|
if (fromConfig) return fromConfig;
|
|
18853
|
-
const secretPath =
|
|
19628
|
+
const secretPath = path25.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
18854
19629
|
try {
|
|
18855
|
-
if (
|
|
18856
|
-
const existing =
|
|
19630
|
+
if (fs24.existsSync(secretPath)) {
|
|
19631
|
+
const existing = fs24.readFileSync(secretPath, "utf-8").trim();
|
|
18857
19632
|
if (existing.length >= 16) return existing;
|
|
18858
19633
|
}
|
|
18859
19634
|
const generated = randomUUID();
|
|
18860
|
-
|
|
18861
|
-
|
|
19635
|
+
fs24.mkdirSync(path25.dirname(secretPath), { recursive: true });
|
|
19636
|
+
fs24.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
18862
19637
|
if (this.config.dashboard.auth) {
|
|
18863
19638
|
console.warn(
|
|
18864
19639
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -18885,7 +19660,7 @@ var DashboardServer = class {
|
|
|
18885
19660
|
// ── Setup ─────────────────────────────────────
|
|
18886
19661
|
getGlobalStore() {
|
|
18887
19662
|
if (!this.globalStore) {
|
|
18888
|
-
const globalDbPath =
|
|
19663
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
18889
19664
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
18890
19665
|
}
|
|
18891
19666
|
return this.globalStore;
|
|
@@ -19265,12 +20040,12 @@ ${prompt}`;
|
|
|
19265
20040
|
}
|
|
19266
20041
|
}
|
|
19267
20042
|
watchRuntimeChanges() {
|
|
19268
|
-
const workspaceDbPath =
|
|
19269
|
-
const globalDbPath =
|
|
20043
|
+
const workspaceDbPath = path25.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20044
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19270
20045
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
19271
20046
|
for (const watchPath of watchPaths) {
|
|
19272
|
-
if (!
|
|
19273
|
-
|
|
20047
|
+
if (!fs24.existsSync(watchPath)) continue;
|
|
20048
|
+
fs24.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
19274
20049
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
19275
20050
|
});
|
|
19276
20051
|
}
|
|
@@ -19392,7 +20167,7 @@ ${prompt}`;
|
|
|
19392
20167
|
const sessionId = req.params.id;
|
|
19393
20168
|
this.store.deleteSession(sessionId);
|
|
19394
20169
|
this.store.deleteRuntimeSession(sessionId);
|
|
19395
|
-
const globalDbPath =
|
|
20170
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19396
20171
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19397
20172
|
try {
|
|
19398
20173
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -19485,7 +20260,7 @@ ${prompt}`;
|
|
|
19485
20260
|
});
|
|
19486
20261
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
19487
20262
|
const body = req.body;
|
|
19488
|
-
const globalDbPath =
|
|
20263
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19489
20264
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
19490
20265
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19491
20266
|
try {
|
|
@@ -19508,7 +20283,7 @@ ${prompt}`;
|
|
|
19508
20283
|
});
|
|
19509
20284
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
19510
20285
|
this.store.deleteAllRuntimeNodes();
|
|
19511
|
-
const globalDbPath =
|
|
20286
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19512
20287
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19513
20288
|
try {
|
|
19514
20289
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -19594,12 +20369,12 @@ ${prompt}`;
|
|
|
19594
20369
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
19595
20370
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
19596
20371
|
try {
|
|
19597
|
-
const configPath =
|
|
19598
|
-
const existing =
|
|
20372
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20373
|
+
const existing = fs24.existsSync(configPath) ? JSON.parse(fs24.readFileSync(configPath, "utf-8")) : {};
|
|
19599
20374
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
19600
20375
|
const tmp = configPath + ".tmp";
|
|
19601
|
-
|
|
19602
|
-
|
|
20376
|
+
fs24.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20377
|
+
fs24.renameSync(tmp, configPath);
|
|
19603
20378
|
res.json({ ok: true });
|
|
19604
20379
|
} catch (err) {
|
|
19605
20380
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -19627,7 +20402,7 @@ ${prompt}`;
|
|
|
19627
20402
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
19628
20403
|
const scope = req.query["scope"] ?? "workspace";
|
|
19629
20404
|
if (scope === "global") {
|
|
19630
|
-
const globalDbPath =
|
|
20405
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19631
20406
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19632
20407
|
try {
|
|
19633
20408
|
res.json({
|
|
@@ -19968,13 +20743,13 @@ ${prompt}`;
|
|
|
19968
20743
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
19969
20744
|
}
|
|
19970
20745
|
});
|
|
19971
|
-
const prodPath =
|
|
19972
|
-
const devPath =
|
|
19973
|
-
const webDistPath =
|
|
19974
|
-
if (
|
|
20746
|
+
const prodPath = path25.resolve(__dirname$1, "../web/dist");
|
|
20747
|
+
const devPath = path25.resolve(__dirname$1, "../../web/dist");
|
|
20748
|
+
const webDistPath = fs24.existsSync(prodPath) ? prodPath : devPath;
|
|
20749
|
+
if (fs24.existsSync(webDistPath)) {
|
|
19975
20750
|
this.app.use(express.static(webDistPath));
|
|
19976
20751
|
this.app.get("*", (_req, res) => {
|
|
19977
|
-
res.sendFile(
|
|
20752
|
+
res.sendFile(path25.join(webDistPath, "index.html"));
|
|
19978
20753
|
});
|
|
19979
20754
|
} else {
|
|
19980
20755
|
this.app.get("/", (_req, res) => {
|
|
@@ -20161,14 +20936,14 @@ function planRetrieval(ctx) {
|
|
|
20161
20936
|
}
|
|
20162
20937
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20163
20938
|
function globalDir(homeDir = os6.homedir()) {
|
|
20164
|
-
return
|
|
20939
|
+
return path25.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20165
20940
|
}
|
|
20166
20941
|
function cloudSessionPath(dir = globalDir()) {
|
|
20167
|
-
return
|
|
20942
|
+
return path25.join(dir, CLOUD_SESSION_FILE);
|
|
20168
20943
|
}
|
|
20169
20944
|
function loadCloudSession(dir = globalDir()) {
|
|
20170
20945
|
try {
|
|
20171
|
-
const raw =
|
|
20946
|
+
const raw = fs24.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20172
20947
|
const parsed = JSON.parse(raw);
|
|
20173
20948
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20174
20949
|
return parsed;
|
|
@@ -20177,17 +20952,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20177
20952
|
}
|
|
20178
20953
|
}
|
|
20179
20954
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20180
|
-
|
|
20955
|
+
fs24.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20181
20956
|
const file = cloudSessionPath(dir);
|
|
20182
|
-
|
|
20957
|
+
fs24.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20183
20958
|
try {
|
|
20184
|
-
|
|
20959
|
+
fs24.chmodSync(file, 384);
|
|
20185
20960
|
} catch {
|
|
20186
20961
|
}
|
|
20187
20962
|
}
|
|
20188
20963
|
function clearCloudSession(dir = globalDir()) {
|
|
20189
20964
|
try {
|
|
20190
|
-
|
|
20965
|
+
fs24.rmSync(cloudSessionPath(dir));
|
|
20191
20966
|
} catch {
|
|
20192
20967
|
}
|
|
20193
20968
|
}
|
|
@@ -20359,16 +21134,16 @@ var CloudClient = class _CloudClient {
|
|
|
20359
21134
|
return (await this.refresh()).accessToken;
|
|
20360
21135
|
}
|
|
20361
21136
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
20362
|
-
async authed(
|
|
20363
|
-
const call = (token) => fetch(this.url(
|
|
21137
|
+
async authed(path27, init = {}) {
|
|
21138
|
+
const call = (token) => fetch(this.url(path27), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
20364
21139
|
let res = await call(await this.accessToken());
|
|
20365
21140
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
20366
21141
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
20367
21142
|
const text = await res.text();
|
|
20368
21143
|
return text ? JSON.parse(text) : {};
|
|
20369
21144
|
}
|
|
20370
|
-
authedGet(
|
|
20371
|
-
return this.authed(
|
|
21145
|
+
authedGet(path27) {
|
|
21146
|
+
return this.authed(path27);
|
|
20372
21147
|
}
|
|
20373
21148
|
async fetchMe(token) {
|
|
20374
21149
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -20705,6 +21480,6 @@ function applySyncBundle(bundle, config) {
|
|
|
20705
21480
|
return next;
|
|
20706
21481
|
}
|
|
20707
21482
|
|
|
20708
|
-
export { AZURE_BASE_MODELS, 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, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildManifest, buildSessionTranscript, cagCharBudget, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, isMcpToolName, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseRankOrder, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, runCascade, sessionWorthRemembering, streamCascade, uniqueMcpServerName };
|
|
21483
|
+
export { AZURE_BASE_MODELS, 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, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, extractCharts, fileExt, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, uniqueMcpServerName };
|
|
20709
21484
|
//# sourceMappingURL=index.js.map
|
|
20710
21485
|
//# sourceMappingURL=index.js.map
|