cascade-ai 0.66.1 → 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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
- import path24, { dirname } from 'path';
3
- import fs23, { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
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 fs4 from 'fs/promises';
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 = path24.join(workspacePath, ".cascade");
65
- if (!fs23.existsSync(cascadeDir)) {
66
- fs23.mkdirSync(cascadeDir, { recursive: true });
64
+ const cascadeDir = path25.join(workspacePath, ".cascade");
65
+ if (!fs24.existsSync(cascadeDir)) {
66
+ fs24.mkdirSync(cascadeDir, { recursive: true });
67
67
  }
68
- this.keyPath = path24.join(cascadeDir, "audit_log.key");
69
- this.dbPath = path24.join(cascadeDir, "audit_log.db");
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 (fs23.existsSync(this.keyPath)) {
100
- this.encryptionKey = fs23.readFileSync(this.keyPath);
99
+ if (fs24.existsSync(this.keyPath)) {
100
+ this.encryptionKey = fs24.readFileSync(this.keyPath);
101
101
  } else {
102
102
  this.encryptionKey = crypto4.randomBytes(32);
103
- fs23.writeFileSync(this.keyPath, this.encryptionKey);
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 = path24.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
181
+ const dumpPath = path25.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
182
182
  const entries = this.getAllLogs();
183
- fs23.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
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.66.1";
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 = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
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 fs4.readFile(this.opts.cacheFile, "utf-8");
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 fs4.mkdir(path24.dirname(this.opts.cacheFile), { recursive: true });
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 fs4.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
4774
+ await fs5.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
4775
4775
  } catch {
4776
4776
  }
4777
4777
  }
@@ -6590,7 +6590,16 @@ var KNOWN_TOOLS = [
6590
6590
  "glob",
6591
6591
  "grep",
6592
6592
  "web_fetch",
6593
- "generate_image"
6593
+ // Every media tool buildMediaTools can register, not just the image one.
6594
+ // Omitting three of the four meant a run whose ONLY tools were media ones
6595
+ // (a video-generation subtask on an account with no file/web tools) counted
6596
+ // as "no tools registered" and dropped all tool guidance — the worker was
6597
+ // then told nothing about using tools at all, and wrote the video as prose.
6598
+ "generate_image",
6599
+ "generate_video",
6600
+ "generate_speech",
6601
+ "transcribe_audio",
6602
+ "generate_document"
6594
6603
  ];
6595
6604
  var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
6596
6605
  "generate_image",
@@ -6607,19 +6616,55 @@ function buildWorkerRules(has) {
6607
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.",
6608
6617
  has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
6609
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.',
6610
6631
  // Without this the model "writes the image" as prose — a bracketed
6611
6632
  // description sitting in the deck where the picture should be — even with a
6612
6633
  // working image model registered. Naming the reference syntax matters as
6613
6634
  // much as naming the tool: a generated image nobody embeds is still a
6614
- // 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.
6615
6639
  //
6616
- // Scoped to PowerPoint/Word specifically: those are the only exporters
6617
- // that actually embed a Markdown image reference (cloud/web/exporters.ts
6618
- // toPptx/toDocx). PDF and plain-text/Markdown deliverables flatten a
6619
- // reference straight to caption text — telling the model to call
6620
- // generate_image for THOSE just pays for an image nobody ever sees.
6621
- has("generate_image") && '- When a slide deck (PowerPoint) or Word document deliverable calls for a decorative image or illustration, you MUST call the "generate_image" tool and then reference its result using Markdown image syntax: ![description](location), where "location" is exactly the string the tool reported back. NEVER write a text placeholder or a bracketed description such as [image: a cat] in its place. 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, so render that data as a Markdown table 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.',
6622
- has("run_code") && '- Use the "run_code" tool for any file types (Excel, Zip, csv, etc.) or complex processing not covered by other tools. Always cleanup after code execution.',
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: ![description](location), 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.",
6658
+ // The video counterpart of the image rule above, and the fix for the
6659
+ // reported "it writes scripts forever and never makes the video" run. The
6660
+ // failure mode is identical — the model narrates the deliverable instead of
6661
+ // producing it — but the stakes are not: a video subtask that ends in prose
6662
+ // has burned the whole pre-production plan for nothing. Unlike the image
6663
+ // rule this is NOT scoped to a document format, because the clip itself is
6664
+ // the deliverable rather than an illustration inside one, and the reference
6665
+ // syntax is a plain link: Markdown image syntax does not embed a video.
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.',
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.`,
6623
6668
  "- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
6624
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.',
6625
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.",
@@ -6630,13 +6675,34 @@ function buildWorkerRules(has) {
6630
6675
  Rules:
6631
6676
  ${rules.filter((r) => r !== false).join("\n")}`;
6632
6677
  }
6633
- 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;
6634
6680
  function shouldRequireArtifact(assignment, toolNames) {
6635
6681
  if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
6636
6682
  if (assignment?.files?.length) return true;
6637
6683
  const haystack = `${assignment?.description ?? ""}
6638
6684
  ${assignment?.expectedOutput ?? ""}`;
6639
- return /\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/i.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
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 ![description](location)');
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.`;
6640
6706
  }
6641
6707
  var T3Worker = class extends BaseTier {
6642
6708
  router;
@@ -6817,6 +6883,18 @@ Now execute your subtask using this context where relevant.`
6817
6883
  return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
6818
6884
  }
6819
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
+ }
6820
6898
  this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
6821
6899
  const testResult = await this.selfTest(assignment, output);
6822
6900
  checksRun.push(...testResult.checksRun);
@@ -7131,8 +7209,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
7131
7209
  // Media generation can run for a minute; without this a cancelled run
7132
7210
  // still pays for an image nobody will see.
7133
7211
  ...this.signal ? { signal: this.signal } : {},
7134
- saveSnapshot: async (path26, content) => {
7135
- this.store?.addFileSnapshot(this.taskId, path26, content);
7212
+ saveSnapshot: async (path27, content) => {
7213
+ this.store?.addFileSnapshot(this.taskId, path27, content);
7136
7214
  },
7137
7215
  sendPeerSync: (to, syncType, content) => {
7138
7216
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -7161,6 +7239,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
7161
7239
  if (isTaggedSystemic || isProviderBacked && classifyProviderError(err).systemic) {
7162
7240
  throw new CriticalToolError(errMsg, tc.name);
7163
7241
  }
7242
+ if (isProviderBacked) {
7243
+ throw new CriticalToolError(
7244
+ `${tc.name} failed and was not retried \u2014 media generation is billed per attempt, so Cascade stops on the first failure instead of paying for a second. Reason: ${errMsg}`,
7245
+ tc.name
7246
+ );
7247
+ }
7164
7248
  return await this.adaptiveFallback(tc, `Tool error: ${errMsg}`);
7165
7249
  }
7166
7250
  }
@@ -7170,6 +7254,14 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
7170
7254
  * 1. Find a semantically similar registered tool and retry with same input
7171
7255
  * 2. Synthesize a new tool via ToolCreator (if available) and run it
7172
7256
  * 3. Return the original error so the agent loop can decide what to do next
7257
+ *
7258
+ * Scoped to CHEAP, task-scoped failures — the class of problem it was built
7259
+ * for is a wrong or missing tool name, where a name-similar sibling plausibly
7260
+ * does the same job for the same (near-zero) cost. PROVIDER_BACKED_TOOLS never
7261
+ * reach it: their siblings are keyword-similar but semantically different
7262
+ * (generate_image is not a stand-in for generate_video), and every attempt —
7263
+ * theirs, or a synthesized replacement's — is separately billed. See the
7264
+ * fast-fail branch in executeTool().
7173
7265
  */
7174
7266
  async adaptiveFallback(tc, originalError) {
7175
7267
  const altTool = this.findAlternativeTool(tc.name);
@@ -7294,7 +7386,7 @@ ${assignment.expectedOutput}`;
7294
7386
  const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
7295
7387
  const haystack = `${assignment.description}
7296
7388
  ${assignment.expectedOutput}`;
7297
- const matches = haystack.match(/\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|png|jpg|jpeg|svg|gif)\b/gi) ?? [];
7389
+ const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
7298
7390
  return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
7299
7391
  }
7300
7392
  async verifyArtifacts(assignment) {
@@ -7305,9 +7397,9 @@ ${assignment.expectedOutput}`;
7305
7397
  const { promisify: promisify4 } = await import('util');
7306
7398
  const execAsync2 = promisify4(exec2);
7307
7399
  for (const artifactPath of artifactPaths) {
7308
- const absolutePath = path24.resolve(process.cwd(), artifactPath);
7400
+ const absolutePath = path25.resolve(process.cwd(), artifactPath);
7309
7401
  try {
7310
- const stat = await fs4.stat(absolutePath);
7402
+ const stat = await fs5.stat(absolutePath);
7311
7403
  if (!stat.isFile()) {
7312
7404
  issues.push(`Expected artifact is not a file: ${artifactPath}`);
7313
7405
  continue;
@@ -7316,8 +7408,22 @@ ${assignment.expectedOutput}`;
7316
7408
  issues.push(`Artifact is empty: ${artifactPath}`);
7317
7409
  continue;
7318
7410
  }
7319
- if (!/\.pdf$/i.test(artifactPath)) {
7320
- const content = await fs4.readFile(absolutePath, "utf-8");
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");
7321
7427
  if (!content.trim()) {
7322
7428
  issues.push(`Artifact content is empty: ${artifactPath}`);
7323
7429
  continue;
@@ -7326,7 +7432,7 @@ ${assignment.expectedOutput}`;
7326
7432
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
7327
7433
  continue;
7328
7434
  }
7329
- const ext = path24.extname(absolutePath).toLowerCase();
7435
+ const ext = path25.extname(absolutePath).toLowerCase();
7330
7436
  try {
7331
7437
  if (ext === ".ts" || ext === ".tsx") {
7332
7438
  await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
@@ -7922,15 +8028,244 @@ _(incomplete: ${t2Result.issues.join("; ")})_`;
7922
8028
  return output;
7923
8029
  }
7924
8030
 
8031
+ // src/core/multimodal/registry.ts
8032
+ var CAPABILITIES = [
8033
+ // ── Image ──
8034
+ {
8035
+ modality: "image",
8036
+ provider: "openai",
8037
+ modelId: "dall-e-3",
8038
+ api: "openai-images"
8039
+ },
8040
+ {
8041
+ // Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
8042
+ // ("no longer available to new users") — Imagen's :predict API is deprecated
8043
+ // and shuts down 2026-08-17. Google's own migration path is this model on
8044
+ // the ordinary generateContent endpoint, so the fix was never another Imagen
8045
+ // id: the whole request/response SHAPE changes, which is why `api` moved to
8046
+ // 'gemini-generate-content' rather than just the modelId moving.
8047
+ // https://ai.google.dev/gemini-api/docs/imagen
8048
+ modality: "image",
8049
+ provider: "gemini",
8050
+ modelId: "gemini-2.5-flash-image",
8051
+ api: "gemini-generate-content"
8052
+ },
8053
+ // ── Speech (text → audio) ──
8054
+ {
8055
+ modality: "speech",
8056
+ provider: "openai",
8057
+ modelId: "tts-1",
8058
+ api: "openai-speech"
8059
+ },
8060
+ {
8061
+ // The only quality claim in this table that needs no leaderboard: OpenAI
8062
+ // ships these as the same voice at two fidelities and charges double for
8063
+ // this one. That is the vendor's own ordering, not an inference.
8064
+ modality: "speech",
8065
+ provider: "openai",
8066
+ modelId: "tts-1-hd",
8067
+ api: "openai-speech",
8068
+ quality: 70,
8069
+ qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
8070
+ },
8071
+ // ── Transcription (audio → text) ──
8072
+ {
8073
+ modality: "transcription",
8074
+ provider: "openai",
8075
+ modelId: "whisper-1",
8076
+ api: "openai-transcriptions"
8077
+ },
8078
+ // ── Video ──
8079
+ {
8080
+ // Long-running: submit returns an operation name, the video lands minutes
8081
+ // later, and the bytes come from a short-lived signed URL. generate.ts owns
8082
+ // that polling loop; the registry only records the shape via `api`.
8083
+ //
8084
+ // `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
8085
+ // not the Gemini Developer API's — this file calls
8086
+ // generativelanguage.googleapis.com (see generate.ts), which 404s on it:
8087
+ // "models/veo-3.1-generate-001 is not found for API version v1beta". On the
8088
+ // Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
8089
+ // there is `veo-3.1-generate-preview`.
8090
+ // https://ai.google.dev/gemini-api/docs/veo
8091
+ modality: "video",
8092
+ provider: "gemini",
8093
+ modelId: "veo-3.1-generate-preview",
8094
+ api: "gemini-predict-lro"
8095
+ }
8096
+ ];
8097
+ function capabilityCost(cap) {
8098
+ const match = findPricing(cap.modelId, cap.provider, {
8099
+ fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
8100
+ });
8101
+ const rate = match?.entry.rates?.[0];
8102
+ if (!rate) return null;
8103
+ const unitLabel = {
8104
+ per_image: "per image",
8105
+ per_second: "per second of video",
8106
+ per_character: "per character",
8107
+ per_audio_minute: "per audio minute"
8108
+ };
8109
+ const unit = unitLabel[rate.unit] ?? rate.unit;
8110
+ const amount = rate.amount;
8111
+ const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
8112
+ return { label: `$${shown} ${unit}`, amount, unit };
8113
+ }
8114
+ function explainChoice(c, cost, modality, isBest) {
8115
+ const priced = cost ? ` ${cost.label}.` : "";
8116
+ if (c.quality !== void 0) {
8117
+ return isBest ? `${c.modelId} (${c.provider}) \u2014 ranked highest for ${modality}: ${c.qualityBasis}.${priced}` : `${c.modelId} (${c.provider}) \u2014 ranked alternative for ${modality}: ${c.qualityBasis}.${priced}`;
8118
+ }
8119
+ const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
8120
+ return isBest ? `${c.modelId} (${c.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c.modelId} (${c.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
8121
+ }
8122
+ var MultimodalRegistry = class {
8123
+ /** Provider types the user has actually configured a key for. */
8124
+ available;
8125
+ constructor(availableProviders) {
8126
+ this.available = new Set(availableProviders);
8127
+ }
8128
+ /** Every capability whose provider is configured AND which Cascade can call. */
8129
+ usable(modality) {
8130
+ return CAPABILITIES.filter(
8131
+ (c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
8132
+ );
8133
+ }
8134
+ /** Modalities this account can actually generate right now. */
8135
+ availableModalities() {
8136
+ return [...new Set(this.usable().map((c) => c.modality))];
8137
+ }
8138
+ /**
8139
+ * Every usable capability for a modality, best first.
8140
+ *
8141
+ * Ranked models beat unranked ones, then higher quality, then lower unit
8142
+ * price. An explicit `preferModel` is moved to the head when it is usable —
8143
+ * the user naming a model is a stronger signal than any prior.
8144
+ *
8145
+ * `select()` is just the head of this list. The TAIL is the point: a systemic
8146
+ * provider failure (dead key, 404, exhausted quota) is scoped to one
8147
+ * provider+model, so "this model cannot draw" is not the same fact as "this
8148
+ * account cannot draw". A caller holding the ordered alternatives can try the
8149
+ * next provider instead of aborting the whole run — see generate-media.ts.
8150
+ */
8151
+ rank(modality, opts = {}) {
8152
+ const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
8153
+ const qa = a.c.quality ?? -1;
8154
+ const qb = b.c.quality ?? -1;
8155
+ if (qa !== qb) return qb - qa;
8156
+ const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
8157
+ const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
8158
+ return pa - pb;
8159
+ });
8160
+ const results = scored.map(({ c, cost }, i) => ({
8161
+ capability: c,
8162
+ cost,
8163
+ reason: explainChoice(c, cost, modality, i === 0)
8164
+ }));
8165
+ if (opts.preferModel) {
8166
+ const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
8167
+ if (i > -1) {
8168
+ const [wanted] = results.splice(i, 1);
8169
+ results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
8170
+ }
8171
+ }
8172
+ return results;
8173
+ }
8174
+ /**
8175
+ * Pick a model for a modality — the best usable one, or null if there is none.
8176
+ */
8177
+ select(modality, opts = {}) {
8178
+ return this.rank(modality, opts)[0] ?? null;
8179
+ }
8180
+ /**
8181
+ * A plain-language account of what this account can and cannot generate —
8182
+ * including the things it cannot, and why.
8183
+ *
8184
+ * This is the USER-facing inventory (model ids and unit prices), keyed on
8185
+ * which providers are configured. It is deliberately NOT what the planner
8186
+ * reads: see `describeGenerationForPlanner()` below for that, and the note
8187
+ * there for why the two cannot be the same string.
8188
+ */
8189
+ describe() {
8190
+ const lines = [];
8191
+ const usable = this.usable();
8192
+ if (usable.length) {
8193
+ lines.push("Generation models available on your configured providers:");
8194
+ for (const modality of ["image", "speech", "transcription", "video"]) {
8195
+ const forModality = usable.filter((c) => c.modality === modality);
8196
+ if (!forModality.length) continue;
8197
+ const shown = forModality.map((c) => {
8198
+ const cost = capabilityCost(c);
8199
+ return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
8200
+ });
8201
+ lines.push(` ${modality}: ${shown.join(", ")}`);
8202
+ }
8203
+ } else {
8204
+ lines.push("No generation models are available \u2014 none of your configured providers offers one.");
8205
+ }
8206
+ const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
8207
+ for (const c of blocked) {
8208
+ lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
8209
+ }
8210
+ lines.push(
8211
+ " music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
8212
+ );
8213
+ return lines.join("\n");
8214
+ }
8215
+ };
8216
+ var PLANNER_FACTS = [
8217
+ { tool: "generate_image", modality: "image", shape: "one call returns one finished image" },
8218
+ { tool: "generate_speech", modality: "speech", shape: "one call returns one finished audio file" },
8219
+ {
8220
+ tool: "generate_video",
8221
+ modality: "video",
8222
+ shape: "one call returns one finished clip, after rendering for 1-3 minutes (Cascade stops waiting at 8)"
8223
+ },
8224
+ {
8225
+ tool: "transcribe_audio",
8226
+ modality: "transcription",
8227
+ shape: "one call returns the transcript of one audio file"
8228
+ }
8229
+ ];
8230
+ function plannerPriceNote(modality) {
8231
+ const costs = CAPABILITIES.filter((c) => c.modality === modality && !c.unsupported).map((c) => capabilityCost(c)).filter((c) => c !== null);
8232
+ if (!costs.length) return "";
8233
+ const labels = new Set(costs.map((c) => c.label));
8234
+ if (labels.size === 1) return ` Billed ${costs[0].label}.`;
8235
+ const units = new Set(costs.map((c) => c.unit));
8236
+ return units.size === 1 ? ` Billed ${[...units][0]}.` : "";
8237
+ }
8238
+ function describeGenerationForPlanner(has) {
8239
+ const present = PLANNER_FACTS.filter((f) => has(f.tool));
8240
+ if (!present.length) return "";
8241
+ const lines = ["MEDIA GENERATION \u2014 the only generation tools this run can call:"];
8242
+ for (const f of present) {
8243
+ lines.push(`- "${f.tool}" (${f.modality}): ${f.shape}.${plannerPriceNote(f.modality)}`);
8244
+ }
8245
+ lines.push(
8246
+ "Each one is a single ATOMIC tool call, made by ONE T3 worker, that produces the finished file. It is never a multi-step pipeline, and no worker can satisfy it by describing the result in words.",
8247
+ "No other modality has a tool here \u2014 never plan a step that generates music, 3D or anything not listed above."
8248
+ );
8249
+ if (has("generate_video")) {
8250
+ lines.push(
8251
+ 'VIDEO PLANS MUST END IN THE TOOL CALL. Creative pre-production \u2014 a script, a shot list, direction notes \u2014 is expected and worth planning, but it is NOT the video. Any plan that involves video MUST contain exactly ONE subtask whose deliverable is the "generate_video" call itself; it MUST be the last subtask on that path (dependsOn the pre-production ones), and nothing after it may be required to produce the clip. Never end a video plan on a script, a storyboard or a review step. Never split one clip across several "generate_video" subtasks, and never plan a render \u2192 critique \u2192 re-render loop: every render is billed again and takes minutes.'
8252
+ );
8253
+ }
8254
+ return lines.join("\n");
8255
+ }
8256
+
7925
8257
  // src/core/tiers/t2-manager.ts
7926
- function buildT2SystemPrompt(hasPeerMessage) {
8258
+ function buildT2SystemPrompt(has) {
8259
+ const generation = describeGenerationForPlanner(has);
7927
8260
  return [
7928
8261
  "You are a T2 Manager agent in the Cascade AI system.",
7929
8262
  "Your role is to analyze a section of a task and decompose it into 2-5 discrete subtasks for T3 Workers.",
7930
8263
  'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
7931
- hasPeerMessage && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
7932
- "Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text."
7933
- ].filter((l) => l !== false).join("\n");
8264
+ has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
8265
+ "Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
8266
+ generation && `
8267
+ ${generation}`
8268
+ ].filter((l) => l !== false && l !== "").join("\n");
7934
8269
  }
7935
8270
  var T2Manager = class extends BaseTier {
7936
8271
  router;
@@ -8227,7 +8562,7 @@ Return ONLY the JSON array.`;
8227
8562
  const messages = [{ role: "user", content: prompt }];
8228
8563
  const result = await this.router.generate("T2", {
8229
8564
  messages,
8230
- systemPrompt: this.systemPromptOverride + buildT2SystemPrompt(this.toolRegistry.hasTool("peer_message")) + (this.hierarchyContext ? `
8565
+ systemPrompt: this.systemPromptOverride + buildT2SystemPrompt((name) => this.toolRegistry.hasTool(name)) + (this.hierarchyContext ? `
8231
8566
 
8232
8567
  HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
8233
8568
  maxTokens: 2e3,
@@ -8777,6 +9112,7 @@ function isStrongKeywordOverlap(a = [], b = []) {
8777
9112
  }
8778
9113
  function buildT1SystemPrompt(has) {
8779
9114
  const canWriteArtifacts = has("file_write") || has("file_edit") || has("run_code") || has("pdf_create");
9115
+ const generation = describeGenerationForPlanner(has);
8780
9116
  const rules = [
8781
9117
  "- Simple \u2192 1 T3, Moderate \u2192 2-3 T2s, Complex \u2192 3-5 T2s, Highly Complex \u2192 5+ T2s",
8782
9118
  "- Return ONLY valid JSON \u2014 no other text",
@@ -8811,7 +9147,9 @@ DEPENDENCY GUIDANCE:
8811
9147
  QUALITY RULES:
8812
9148
  - Each section must have a clear, testable "expectedOutput" so T2 knows when it is done.
8813
9149
  - Do NOT create trivial sections that only move files or print summaries \u2014 fold those into adjacent sections.
8814
- - If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2).`;
9150
+ - If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2).${generation ? `
9151
+
9152
+ ${generation}` : ""}`;
8815
9153
  }
8816
9154
  var T1Administrator = class extends BaseTier {
8817
9155
  router;
@@ -9472,219 +9810,37 @@ Instructions:
9472
9810
  return result.content;
9473
9811
  }
9474
9812
  /**
9475
- * T1-level permission evaluator.
9476
- * Uses T1's model with full task context.
9477
- * Returns null only when the model explicitly says UNSURE (triggers user prompt).
9478
- */
9479
- async evaluatePermissionAtT1(req) {
9480
- const prompt = `You are T1 Administrator. Overall task goal:
9481
- ${this.taskGoal}
9482
-
9483
- A T3 Worker (inside section "${req.sectionContext}") wants to:
9484
- Tool: ${req.toolName}
9485
- Target: ${JSON.stringify(req.input)}
9486
- Reason: ${req.subtaskContext}
9487
-
9488
- T2 Manager was uncertain about this. Given the overall task goal, should this be allowed?
9489
- Reply with exactly one word: YES, NO, or UNSURE.
9490
- (UNSURE = escalate to the human user for a final decision.)`;
9491
- try {
9492
- const result = await this.router.generate("T1", {
9493
- messages: [{ role: "user", content: prompt }],
9494
- systemPrompt: this.systemPromptOverride + "You are a T1 Administrator evaluating permissions.",
9495
- maxTokens: 10,
9496
- temperature: 0
9497
- });
9498
- const answer = result.content.trim().toUpperCase();
9499
- const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
9500
- (req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
9501
- return null;
9502
- } catch {
9503
- (req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
9504
- return null;
9505
- }
9506
- }
9507
- };
9508
-
9509
- // src/core/multimodal/registry.ts
9510
- var CAPABILITIES = [
9511
- // ── Image ──
9512
- {
9513
- modality: "image",
9514
- provider: "openai",
9515
- modelId: "dall-e-3",
9516
- api: "openai-images"
9517
- },
9518
- {
9519
- // Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
9520
- // ("no longer available to new users") — Imagen's :predict API is deprecated
9521
- // and shuts down 2026-08-17. Google's own migration path is this model on
9522
- // the ordinary generateContent endpoint, so the fix was never another Imagen
9523
- // id: the whole request/response SHAPE changes, which is why `api` moved to
9524
- // 'gemini-generate-content' rather than just the modelId moving.
9525
- // https://ai.google.dev/gemini-api/docs/imagen
9526
- modality: "image",
9527
- provider: "gemini",
9528
- modelId: "gemini-2.5-flash-image",
9529
- api: "gemini-generate-content"
9530
- },
9531
- // ── Speech (text → audio) ──
9532
- {
9533
- modality: "speech",
9534
- provider: "openai",
9535
- modelId: "tts-1",
9536
- api: "openai-speech"
9537
- },
9538
- {
9539
- // The only quality claim in this table that needs no leaderboard: OpenAI
9540
- // ships these as the same voice at two fidelities and charges double for
9541
- // this one. That is the vendor's own ordering, not an inference.
9542
- modality: "speech",
9543
- provider: "openai",
9544
- modelId: "tts-1-hd",
9545
- api: "openai-speech",
9546
- quality: 70,
9547
- qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
9548
- },
9549
- // ── Transcription (audio → text) ──
9550
- {
9551
- modality: "transcription",
9552
- provider: "openai",
9553
- modelId: "whisper-1",
9554
- api: "openai-transcriptions"
9555
- },
9556
- // ── Video ──
9557
- {
9558
- // Long-running: submit returns an operation name, the video lands minutes
9559
- // later, and the bytes come from a short-lived signed URL. generate.ts owns
9560
- // that polling loop; the registry only records the shape via `api`.
9561
- //
9562
- // `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
9563
- // not the Gemini Developer API's — this file calls
9564
- // generativelanguage.googleapis.com (see generate.ts), which 404s on it:
9565
- // "models/veo-3.1-generate-001 is not found for API version v1beta". On the
9566
- // Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
9567
- // there is `veo-3.1-generate-preview`.
9568
- // https://ai.google.dev/gemini-api/docs/veo
9569
- modality: "video",
9570
- provider: "gemini",
9571
- modelId: "veo-3.1-generate-preview",
9572
- api: "gemini-predict-lro"
9573
- }
9574
- ];
9575
- function capabilityCost(cap) {
9576
- const match = findPricing(cap.modelId, cap.provider, {
9577
- fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
9578
- });
9579
- const rate = match?.entry.rates?.[0];
9580
- if (!rate) return null;
9581
- const unitLabel = {
9582
- per_image: "per image",
9583
- per_second: "per second of video",
9584
- per_character: "per character",
9585
- per_audio_minute: "per audio minute"
9586
- };
9587
- const unit = unitLabel[rate.unit] ?? rate.unit;
9588
- const amount = rate.amount;
9589
- const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
9590
- return { label: `$${shown} ${unit}`, amount, unit };
9591
- }
9592
- function explainChoice(c, cost, modality, isBest) {
9593
- const priced = cost ? ` ${cost.label}.` : "";
9594
- if (c.quality !== void 0) {
9595
- return isBest ? `${c.modelId} (${c.provider}) \u2014 ranked highest for ${modality}: ${c.qualityBasis}.${priced}` : `${c.modelId} (${c.provider}) \u2014 ranked alternative for ${modality}: ${c.qualityBasis}.${priced}`;
9596
- }
9597
- const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
9598
- return isBest ? `${c.modelId} (${c.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c.modelId} (${c.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
9599
- }
9600
- var MultimodalRegistry = class {
9601
- /** Provider types the user has actually configured a key for. */
9602
- available;
9603
- constructor(availableProviders) {
9604
- this.available = new Set(availableProviders);
9605
- }
9606
- /** Every capability whose provider is configured AND which Cascade can call. */
9607
- usable(modality) {
9608
- return CAPABILITIES.filter(
9609
- (c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
9610
- );
9611
- }
9612
- /** Modalities this account can actually generate right now. */
9613
- availableModalities() {
9614
- return [...new Set(this.usable().map((c) => c.modality))];
9615
- }
9616
- /**
9617
- * Every usable capability for a modality, best first.
9618
- *
9619
- * Ranked models beat unranked ones, then higher quality, then lower unit
9620
- * price. An explicit `preferModel` is moved to the head when it is usable —
9621
- * the user naming a model is a stronger signal than any prior.
9622
- *
9623
- * `select()` is just the head of this list. The TAIL is the point: a systemic
9624
- * provider failure (dead key, 404, exhausted quota) is scoped to one
9625
- * provider+model, so "this model cannot draw" is not the same fact as "this
9626
- * account cannot draw". A caller holding the ordered alternatives can try the
9627
- * next provider instead of aborting the whole run — see generate-media.ts.
9628
- */
9629
- rank(modality, opts = {}) {
9630
- const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
9631
- const qa = a.c.quality ?? -1;
9632
- const qb = b.c.quality ?? -1;
9633
- if (qa !== qb) return qb - qa;
9634
- const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
9635
- const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
9636
- return pa - pb;
9637
- });
9638
- const results = scored.map(({ c, cost }, i) => ({
9639
- capability: c,
9640
- cost,
9641
- reason: explainChoice(c, cost, modality, i === 0)
9642
- }));
9643
- if (opts.preferModel) {
9644
- const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
9645
- if (i > -1) {
9646
- const [wanted] = results.splice(i, 1);
9647
- results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
9648
- }
9649
- }
9650
- return results;
9651
- }
9652
- /**
9653
- * Pick a model for a modality — the best usable one, or null if there is none.
9654
- */
9655
- select(modality, opts = {}) {
9656
- return this.rank(modality, opts)[0] ?? null;
9657
- }
9658
- /**
9659
- * A plain-language account of what this account can and cannot generate —
9660
- * including the things it cannot, and why. The planner sees this, so being
9661
- * candid here is what stops it inventing a `generate_music` call.
9662
- */
9663
- describe() {
9664
- const lines = [];
9665
- const usable = this.usable();
9666
- if (usable.length) {
9667
- lines.push("Generation models available on your configured providers:");
9668
- for (const modality of ["image", "speech", "transcription", "video"]) {
9669
- const forModality = usable.filter((c) => c.modality === modality);
9670
- if (!forModality.length) continue;
9671
- const shown = forModality.map((c) => {
9672
- const cost = capabilityCost(c);
9673
- return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
9674
- });
9675
- lines.push(` ${modality}: ${shown.join(", ")}`);
9676
- }
9677
- } else {
9678
- lines.push("No generation models are available \u2014 none of your configured providers offers one.");
9679
- }
9680
- const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
9681
- for (const c of blocked) {
9682
- lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
9813
+ * T1-level permission evaluator.
9814
+ * Uses T1's model with full task context.
9815
+ * Returns null only when the model explicitly says UNSURE (triggers user prompt).
9816
+ */
9817
+ async evaluatePermissionAtT1(req) {
9818
+ const prompt = `You are T1 Administrator. Overall task goal:
9819
+ ${this.taskGoal}
9820
+
9821
+ A T3 Worker (inside section "${req.sectionContext}") wants to:
9822
+ Tool: ${req.toolName}
9823
+ Target: ${JSON.stringify(req.input)}
9824
+ Reason: ${req.subtaskContext}
9825
+
9826
+ T2 Manager was uncertain about this. Given the overall task goal, should this be allowed?
9827
+ Reply with exactly one word: YES, NO, or UNSURE.
9828
+ (UNSURE = escalate to the human user for a final decision.)`;
9829
+ try {
9830
+ const result = await this.router.generate("T1", {
9831
+ messages: [{ role: "user", content: prompt }],
9832
+ systemPrompt: this.systemPromptOverride + "You are a T1 Administrator evaluating permissions.",
9833
+ maxTokens: 10,
9834
+ temperature: 0
9835
+ });
9836
+ const answer = result.content.trim().toUpperCase();
9837
+ const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
9838
+ (req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
9839
+ return null;
9840
+ } catch {
9841
+ (req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
9842
+ return null;
9683
9843
  }
9684
- lines.push(
9685
- " music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
9686
- );
9687
- return lines.join("\n");
9688
9844
  }
9689
9845
  };
9690
9846
 
@@ -9708,10 +9864,17 @@ var BaseTool = class {
9708
9864
 
9709
9865
  // src/core/multimodal/generate.ts
9710
9866
  var DEFAULT_TIMEOUT_MS = 12e4;
9867
+ var GenerationGaveUpError = class extends Error {
9868
+ constructor(message) {
9869
+ super(message);
9870
+ this.name = "GenerationGaveUpError";
9871
+ }
9872
+ };
9711
9873
  async function callProvider(fn, modelId) {
9712
9874
  try {
9713
9875
  return await fn();
9714
9876
  } catch (err) {
9877
+ if (err instanceof GenerationGaveUpError) throw err;
9715
9878
  const classified = classifyProviderError(err);
9716
9879
  throw new Error(describeProviderError(classified, modelId));
9717
9880
  }
@@ -9909,8 +10072,8 @@ async function generateVideo(cap, cfg, req, signal, onProgress) {
9909
10072
  onProgress?.("Still rendering\u2026");
9910
10073
  }
9911
10074
  if (!operation.done) {
9912
- throw new Error(
9913
- `Video was still rendering after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes. It may still complete on the provider side, but Cascade stopped waiting.`
10075
+ throw new GenerationGaveUpError(
10076
+ `${cap.modelId} timed out after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes \u2014 no video was produced. Cascade stopped waiting; the render may still finish on the provider side, but nothing was saved. Try again with a shorter clip or a simpler prompt.`
9914
10077
  );
9915
10078
  }
9916
10079
  if (operation.error?.message) throw new Error(operation.error.message);
@@ -10216,6 +10379,705 @@ function buildMediaTools(deps, readFile) {
10216
10379
  if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
10217
10380
  return tools;
10218
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 ![alt](path-the-image-tool-returned) 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 ![alt](path) 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
+ }
10219
11081
 
10220
11082
  // src/core/run-breaker.ts
10221
11083
  var DEFAULT_FAILURE_THRESHOLD = 3;
@@ -10538,35 +11400,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
10538
11400
  }
10539
11401
  }
10540
11402
  };
10541
- var WorkspaceSandboxError = class extends Error {
10542
- constructor(attempted, workspaceRoot) {
10543
- super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
10544
- this.name = "WorkspaceSandboxError";
10545
- }
10546
- };
10547
- function resolveInWorkspace(workspaceRoot, input) {
10548
- if (typeof input !== "string" || input.length === 0) {
10549
- throw new WorkspaceSandboxError(String(input), workspaceRoot);
10550
- }
10551
- const root = path24.resolve(workspaceRoot);
10552
- const abs = path24.isAbsolute(input) ? path24.resolve(input) : path24.resolve(root, input);
10553
- const rel = path24.relative(root, abs);
10554
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path24.isAbsolute(rel)) {
10555
- throw new WorkspaceSandboxError(input, root);
10556
- }
10557
- try {
10558
- const real = fs23.realpathSync(abs);
10559
- const realRel = path24.relative(root, real);
10560
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path24.isAbsolute(realRel))) {
10561
- throw new WorkspaceSandboxError(input, root);
10562
- }
10563
- } catch (e) {
10564
- if (e instanceof WorkspaceSandboxError) throw e;
10565
- }
10566
- return abs;
10567
- }
10568
-
10569
- // src/tools/file.ts
10570
11403
  var FileReadTool = class extends BaseTool {
10571
11404
  name = "file_read";
10572
11405
  description = "Read the contents of a file. Returns the file content as a string.";
@@ -10584,7 +11417,7 @@ var FileReadTool = class extends BaseTool {
10584
11417
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
10585
11418
  const offset = input["offset"] ?? 1;
10586
11419
  const limit = input["limit"];
10587
- const content = await fs4.readFile(absPath, "utf-8");
11420
+ const content = await fs5.readFile(absPath, "utf-8");
10588
11421
  const lines = content.split("\n");
10589
11422
  const start = Math.max(0, offset - 1);
10590
11423
  const end = limit ? start + limit : lines.length;
@@ -10613,13 +11446,13 @@ var FileWriteTool = class extends BaseTool {
10613
11446
  const content = input["content"];
10614
11447
  if (options.saveSnapshot) {
10615
11448
  try {
10616
- const oldContent = await fs4.readFile(absPath, "utf-8");
11449
+ const oldContent = await fs5.readFile(absPath, "utf-8");
10617
11450
  await options.saveSnapshot(absPath, oldContent);
10618
11451
  } catch {
10619
11452
  }
10620
11453
  }
10621
- await fs4.mkdir(path24.dirname(absPath), { recursive: true });
10622
- await fs4.writeFile(absPath, content, "utf-8");
11454
+ await fs5.mkdir(path25.dirname(absPath), { recursive: true });
11455
+ await fs5.writeFile(absPath, content, "utf-8");
10623
11456
  return `Written ${content.length} characters to ${filePath}`;
10624
11457
  }
10625
11458
  };
@@ -10645,7 +11478,7 @@ var FileEditTool = class extends BaseTool {
10645
11478
  const oldString = input["old_string"];
10646
11479
  const newString = input["new_string"];
10647
11480
  const replaceAll = input["replace_all"] ?? false;
10648
- const rawContent = await fs4.readFile(absPath, "utf-8");
11481
+ const rawContent = await fs5.readFile(absPath, "utf-8");
10649
11482
  if (options.saveSnapshot) {
10650
11483
  await options.saveSnapshot(absPath, rawContent);
10651
11484
  }
@@ -10657,7 +11490,7 @@ var FileEditTool = class extends BaseTool {
10657
11490
  );
10658
11491
  }
10659
11492
  const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
10660
- await fs4.writeFile(absPath, updated, "utf-8");
11493
+ await fs5.writeFile(absPath, updated, "utf-8");
10661
11494
  const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
10662
11495
  return `Replaced ${count} occurrence(s) in ${filePath}`;
10663
11496
  }
@@ -10680,12 +11513,12 @@ var FileDeleteTool = class extends BaseTool {
10680
11513
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
10681
11514
  if (options.saveSnapshot) {
10682
11515
  try {
10683
- const oldContent = await fs4.readFile(absPath, "utf-8");
11516
+ const oldContent = await fs5.readFile(absPath, "utf-8");
10684
11517
  await options.saveSnapshot(absPath, oldContent);
10685
11518
  } catch {
10686
11519
  }
10687
11520
  }
10688
- await fs4.rm(absPath, { recursive: false });
11521
+ await fs5.rm(absPath, { recursive: false });
10689
11522
  return `Deleted ${filePath}`;
10690
11523
  }
10691
11524
  };
@@ -10702,7 +11535,7 @@ var FileListTool = class extends BaseTool {
10702
11535
  async execute(input, _options) {
10703
11536
  const inputPath = input["path"] || ".";
10704
11537
  const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
10705
- const entries = await fs4.readdir(absPath, { withFileTypes: true });
11538
+ const entries = await fs5.readdir(absPath, { withFileTypes: true });
10706
11539
  return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
10707
11540
  }
10708
11541
  };
@@ -11115,8 +11948,8 @@ var ImageAnalyzeTool = class extends BaseTool {
11115
11948
  }
11116
11949
  };
11117
11950
  async function fileToImageAttachment(filePath) {
11118
- const data = await fs4.readFile(filePath);
11119
- const ext = path24.extname(filePath).toLowerCase();
11951
+ const data = await fs5.readFile(filePath);
11952
+ const ext = path25.extname(filePath).toLowerCase();
11120
11953
  const mimeMap = {
11121
11954
  ".jpg": "image/jpeg",
11122
11955
  ".jpeg": "image/jpeg",
@@ -11150,14 +11983,14 @@ var PDFCreateTool = class extends BaseTool {
11150
11983
  const filePath = input["path"];
11151
11984
  const content = input["content"];
11152
11985
  const title = input["title"];
11153
- const dir = path24.dirname(filePath);
11154
- if (!fs23.existsSync(dir)) {
11155
- fs23.mkdirSync(dir, { recursive: true });
11986
+ const dir = path25.dirname(filePath);
11987
+ if (!fs24.existsSync(dir)) {
11988
+ fs24.mkdirSync(dir, { recursive: true });
11156
11989
  }
11157
11990
  return new Promise((resolve2, reject) => {
11158
11991
  try {
11159
11992
  const doc = new PDFDocument({ margin: 50 });
11160
- const stream = fs23.createWriteStream(filePath);
11993
+ const stream = fs24.createWriteStream(filePath);
11161
11994
  doc.pipe(stream);
11162
11995
  if (title) {
11163
11996
  doc.info["Title"] = title;
@@ -11235,22 +12068,22 @@ var CodeInterpreterTool = class extends BaseTool {
11235
12068
  }
11236
12069
  cmdPrefix = NODE_CMD;
11237
12070
  }
11238
- const tmpDir = path24.join(this.workspaceRoot, ".cascade", "tmp");
11239
- if (!fs23.existsSync(tmpDir)) {
11240
- fs23.mkdirSync(tmpDir, { recursive: true });
12071
+ const tmpDir = path25.join(this.workspaceRoot, ".cascade", "tmp");
12072
+ if (!fs24.existsSync(tmpDir)) {
12073
+ fs24.mkdirSync(tmpDir, { recursive: true });
11241
12074
  }
11242
12075
  const extension = language === "python" ? "py" : "js";
11243
12076
  const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
11244
- const filePath = path24.join(tmpDir, fileName);
11245
- fs23.writeFileSync(filePath, code, "utf-8");
12077
+ const filePath = path25.join(tmpDir, fileName);
12078
+ fs24.writeFileSync(filePath, code, "utf-8");
11246
12079
  const execArgs = [filePath, ...args];
11247
12080
  return new Promise((resolve2) => {
11248
12081
  const startMs = Date.now();
11249
12082
  execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
11250
12083
  const duration = Date.now() - startMs;
11251
12084
  try {
11252
- if (fs23.existsSync(filePath)) {
11253
- fs23.unlinkSync(filePath);
12085
+ if (fs24.existsSync(filePath)) {
12086
+ fs24.unlinkSync(filePath);
11254
12087
  }
11255
12088
  } catch (cleanupErr) {
11256
12089
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -11562,7 +12395,7 @@ var GlobTool = class extends BaseTool {
11562
12395
  };
11563
12396
  async execute(input, _options) {
11564
12397
  const pattern = input["pattern"];
11565
- const searchPath = input["path"] ? path24.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
12398
+ const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
11566
12399
  const matches = await glob(pattern, {
11567
12400
  cwd: searchPath,
11568
12401
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -11575,7 +12408,7 @@ var GlobTool = class extends BaseTool {
11575
12408
  const withMtime = await Promise.all(
11576
12409
  matches.map(async (rel) => {
11577
12410
  try {
11578
- const stat = await fs4.stat(path24.join(searchPath, rel));
12411
+ const stat = await fs5.stat(path25.join(searchPath, rel));
11579
12412
  return { rel, mtime: stat.mtimeMs };
11580
12413
  } catch {
11581
12414
  return { rel, mtime: 0 };
@@ -11624,7 +12457,7 @@ var GrepTool = class extends BaseTool {
11624
12457
  };
11625
12458
  async execute(input, _options) {
11626
12459
  const pattern = input["pattern"];
11627
- const searchPath = input["path"] ? path24.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
12460
+ const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
11628
12461
  const globPattern = input["glob"];
11629
12462
  const outputMode = input["output_mode"] ?? "content";
11630
12463
  const context = input["context"] ?? 0;
@@ -11678,15 +12511,15 @@ var GrepTool = class extends BaseTool {
11678
12511
  nodir: true
11679
12512
  });
11680
12513
  } catch {
11681
- files = [path24.relative(searchPath, searchPath) || "."];
12514
+ files = [path25.relative(searchPath, searchPath) || "."];
11682
12515
  }
11683
12516
  const results = [];
11684
12517
  let totalCount = 0;
11685
12518
  for (const rel of files) {
11686
- const abs = path24.join(searchPath, rel);
12519
+ const abs = path25.join(searchPath, rel);
11687
12520
  let content;
11688
12521
  try {
11689
- content = await fs4.readFile(abs, "utf-8");
12522
+ content = await fs5.readFile(abs, "utf-8");
11690
12523
  } catch {
11691
12524
  continue;
11692
12525
  }
@@ -12070,10 +12903,10 @@ var ToolRegistry = class extends EventEmitter {
12070
12903
  }
12071
12904
  isIgnored(filePath) {
12072
12905
  if (!filePath) return false;
12073
- const abs = path24.resolve(this.workspaceRoot, filePath);
12074
- const rel = path24.relative(this.workspaceRoot, abs);
12075
- if (!rel || rel.startsWith("..") || path24.isAbsolute(rel)) return true;
12076
- const posixRel = rel.split(path24.sep).join("/");
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("/");
12077
12910
  return this.ignoreMatcher.ignores(posixRel);
12078
12911
  }
12079
12912
  };
@@ -12307,22 +13140,22 @@ var FileMcpOAuthStore = class {
12307
13140
  filePath;
12308
13141
  load() {
12309
13142
  try {
12310
- return JSON.parse(fs23.readFileSync(this.filePath, "utf-8"));
13143
+ return JSON.parse(fs24.readFileSync(this.filePath, "utf-8"));
12311
13144
  } catch {
12312
13145
  return void 0;
12313
13146
  }
12314
13147
  }
12315
13148
  save(state) {
12316
- fs23.mkdirSync(path24.dirname(this.filePath), { recursive: true, mode: 448 });
12317
- fs23.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
13149
+ fs24.mkdirSync(path25.dirname(this.filePath), { recursive: true, mode: 448 });
13150
+ fs24.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
12318
13151
  try {
12319
- fs23.chmodSync(this.filePath, 384);
13152
+ fs24.chmodSync(this.filePath, 384);
12320
13153
  } catch {
12321
13154
  }
12322
13155
  }
12323
13156
  clear() {
12324
13157
  try {
12325
- fs23.rmSync(this.filePath);
13158
+ fs24.rmSync(this.filePath);
12326
13159
  } catch {
12327
13160
  }
12328
13161
  }
@@ -13361,7 +14194,7 @@ var TaskAnalyzer = class {
13361
14194
  analysisCache.clear();
13362
14195
  }
13363
14196
  };
13364
- var DEFAULT_STATS_FILE = path24.join(os6.homedir(), ".cascade", "model-perf.json");
14197
+ var DEFAULT_STATS_FILE = path25.join(os6.homedir(), ".cascade", "model-perf.json");
13365
14198
  var ModelPerformanceTracker = class {
13366
14199
  stats = /* @__PURE__ */ new Map();
13367
14200
  featureStats = /* @__PURE__ */ new Map();
@@ -13381,7 +14214,7 @@ var ModelPerformanceTracker = class {
13381
14214
  if (this.loaded) return;
13382
14215
  this.loaded = true;
13383
14216
  try {
13384
- const raw = await fs4.readFile(this.statsFile, "utf-8");
14217
+ const raw = await fs5.readFile(this.statsFile, "utf-8");
13385
14218
  const parsed = JSON.parse(raw);
13386
14219
  if (parsed.models) {
13387
14220
  for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
@@ -13401,15 +14234,15 @@ var ModelPerformanceTracker = class {
13401
14234
  async save() {
13402
14235
  if (this.readOnly) return;
13403
14236
  try {
13404
- await fs4.mkdir(path24.dirname(this.statsFile), { recursive: true });
14237
+ await fs5.mkdir(path25.dirname(this.statsFile), { recursive: true });
13405
14238
  const modelsObj = {};
13406
14239
  const featuresObj = {};
13407
14240
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
13408
14241
  for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
13409
14242
  const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
13410
14243
  const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
13411
- await fs4.writeFile(tmp, json, "utf-8");
13412
- await fs4.rename(tmp, this.statsFile);
14244
+ await fs5.writeFile(tmp, json, "utf-8");
14245
+ await fs5.rename(tmp, this.statsFile);
13413
14246
  } catch {
13414
14247
  }
13415
14248
  }
@@ -13949,9 +14782,9 @@ Required capability: ${description.slice(0, 300)}`;
13949
14782
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
13950
14783
  async loadPersistedTools() {
13951
14784
  if (!this.workspacePath || !this.persistEnabled) return;
13952
- const file = path24.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
14785
+ const file = path25.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
13953
14786
  try {
13954
- const raw = await fs4.readFile(file, "utf-8");
14787
+ const raw = await fs5.readFile(file, "utf-8");
13955
14788
  const specs = JSON.parse(raw);
13956
14789
  if (!Array.isArray(specs)) return;
13957
14790
  let loaded = 0;
@@ -13973,11 +14806,11 @@ Required capability: ${description.slice(0, 300)}`;
13973
14806
  }
13974
14807
  async persist() {
13975
14808
  if (!this.workspacePath || !this.persistEnabled) return;
13976
- const dir = path24.join(this.workspacePath, ".cascade");
13977
- const file = path24.join(dir, DYNAMIC_TOOLS_FILE);
14809
+ const dir = path25.join(this.workspacePath, ".cascade");
14810
+ const file = path25.join(dir, DYNAMIC_TOOLS_FILE);
13978
14811
  try {
13979
- await fs4.mkdir(dir, { recursive: true });
13980
- await fs4.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
14812
+ await fs5.mkdir(dir, { recursive: true });
14813
+ await fs5.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
13981
14814
  } catch (err) {
13982
14815
  this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
13983
14816
  }
@@ -13994,12 +14827,12 @@ var WorldStateDB = class {
13994
14827
  constructor(workspacePath, debugMode = false) {
13995
14828
  this.workspacePath = workspacePath;
13996
14829
  this.debugMode = debugMode;
13997
- const cascadeDir = path24.join(workspacePath, ".cascade");
13998
- if (!fs23.existsSync(cascadeDir)) {
13999
- fs23.mkdirSync(cascadeDir, { recursive: true });
14830
+ const cascadeDir = path25.join(workspacePath, ".cascade");
14831
+ if (!fs24.existsSync(cascadeDir)) {
14832
+ fs24.mkdirSync(cascadeDir, { recursive: true });
14000
14833
  }
14001
- this.keyPath = path24.join(cascadeDir, "world_state.key");
14002
- this.dbPath = path24.join(cascadeDir, "world_state.db");
14834
+ this.keyPath = path25.join(cascadeDir, "world_state.key");
14835
+ this.dbPath = path25.join(cascadeDir, "world_state.db");
14003
14836
  this.initEncryptionKey();
14004
14837
  this.db = new Database(this.dbPath);
14005
14838
  this.db.pragma("journal_mode = WAL");
@@ -14056,11 +14889,11 @@ var WorldStateDB = class {
14056
14889
  ).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
14057
14890
  }
14058
14891
  initEncryptionKey() {
14059
- if (fs23.existsSync(this.keyPath)) {
14060
- this.encryptionKey = fs23.readFileSync(this.keyPath);
14892
+ if (fs24.existsSync(this.keyPath)) {
14893
+ this.encryptionKey = fs24.readFileSync(this.keyPath);
14061
14894
  } else {
14062
14895
  this.encryptionKey = crypto4.randomBytes(32);
14063
- fs23.writeFileSync(this.keyPath, this.encryptionKey);
14896
+ fs24.writeFileSync(this.keyPath, this.encryptionKey);
14064
14897
  }
14065
14898
  }
14066
14899
  encrypt(text) {
@@ -14346,9 +15179,9 @@ var WorldStateDB = class {
14346
15179
  dumpDebugIfNeeded() {
14347
15180
  if (!this.debugMode) return;
14348
15181
  try {
14349
- const dumpPath = path24.join(os6.tmpdir(), "cascade_world_state_debug.json");
15182
+ const dumpPath = path25.join(os6.tmpdir(), "cascade_world_state_debug.json");
14350
15183
  const entries = this.getAllEntries();
14351
- fs23.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
15184
+ fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
14352
15185
  } catch (err) {
14353
15186
  console.error("Failed to dump debug world state", err);
14354
15187
  }
@@ -14403,9 +15236,9 @@ var CascadeIgnore = class {
14403
15236
  ]);
14404
15237
  }
14405
15238
  async load(workspacePath) {
14406
- const filePath = path24.join(workspacePath, ".cascadeignore");
15239
+ const filePath = path25.join(workspacePath, ".cascadeignore");
14407
15240
  try {
14408
- const content = await fs4.readFile(filePath, "utf-8");
15241
+ const content = await fs5.readFile(filePath, "utf-8");
14409
15242
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
14410
15243
  this.ig.add(lines);
14411
15244
  this.loaded = true;
@@ -14414,7 +15247,7 @@ var CascadeIgnore = class {
14414
15247
  }
14415
15248
  isIgnored(filePath, workspacePath) {
14416
15249
  try {
14417
- const relative = workspacePath ? path24.relative(workspacePath, filePath) : filePath;
15250
+ const relative = workspacePath ? path25.relative(workspacePath, filePath) : filePath;
14418
15251
  return this.ig.ignores(relative);
14419
15252
  } catch {
14420
15253
  return false;
@@ -14757,7 +15590,7 @@ var WorkspaceIndex = class {
14757
15590
  isIgnored;
14758
15591
  chunker;
14759
15592
  constructor(opts) {
14760
- this.root = path24.resolve(opts.root);
15593
+ this.root = path25.resolve(opts.root);
14761
15594
  this.db = opts.db;
14762
15595
  this.namespace = opts.namespace ?? "code";
14763
15596
  this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
@@ -14784,19 +15617,19 @@ var WorkspaceIndex = class {
14784
15617
  const walk = async (dir) => {
14785
15618
  let entries;
14786
15619
  try {
14787
- entries = await fs4.readdir(dir, { withFileTypes: true });
15620
+ entries = await fs5.readdir(dir, { withFileTypes: true });
14788
15621
  } catch {
14789
15622
  return;
14790
15623
  }
14791
15624
  for (const entry of entries) {
14792
- const abs = path24.join(dir, entry.name);
15625
+ const abs = path25.join(dir, entry.name);
14793
15626
  if (entry.isDirectory()) {
14794
15627
  if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
14795
15628
  await walk(abs);
14796
15629
  } else if (entry.isFile()) {
14797
- const ext = path24.extname(entry.name).slice(1).toLowerCase();
15630
+ const ext = path25.extname(entry.name).slice(1).toLowerCase();
14798
15631
  if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
14799
- out.push(path24.relative(this.root, abs));
15632
+ out.push(path25.relative(this.root, abs));
14800
15633
  }
14801
15634
  }
14802
15635
  };
@@ -14826,10 +15659,10 @@ var WorkspaceIndex = class {
14826
15659
  const entries = [];
14827
15660
  for (const rel of rels) {
14828
15661
  try {
14829
- const abs = path24.join(this.root, rel);
14830
- const stat = await fs4.stat(abs);
15662
+ const abs = path25.join(this.root, rel);
15663
+ const stat = await fs5.stat(abs);
14831
15664
  if (stat.size > this.maxFileBytes) continue;
14832
- const buf = await fs4.readFile(abs);
15665
+ const buf = await fs5.readFile(abs);
14833
15666
  if (buf.includes(0)) continue;
14834
15667
  entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
14835
15668
  } catch {
@@ -15398,7 +16231,7 @@ var Cascade = class _Cascade extends EventEmitter {
15398
16231
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
15399
16232
  try {
15400
16233
  this.router.setDeadModelStore(new DeadModelStore(
15401
- fileDeadModelPersistence(path24.join(workspacePath, ".cascade", "dead-models.json"))
16234
+ fileDeadModelPersistence(path25.join(workspacePath, ".cascade", "dead-models.json"))
15402
16235
  ));
15403
16236
  } catch {
15404
16237
  }
@@ -15406,6 +16239,7 @@ var Cascade = class _Cascade extends EventEmitter {
15406
16239
  (this.config.providers ?? []).map((p) => p.type)
15407
16240
  );
15408
16241
  this.registerMediaTools(workspacePath);
16242
+ this.registerDocumentTools(workspacePath);
15409
16243
  this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
15410
16244
  this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
15411
16245
  this.router.setWorldStateDB(this.worldStateDB);
@@ -15485,8 +16319,8 @@ var Cascade = class _Cascade extends EventEmitter {
15485
16319
  this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
15486
16320
  return;
15487
16321
  }
15488
- const dbPath = ci.dbPath || path24.join(this.workspacePath, ".cascade", "code-index.db");
15489
- fs23.mkdirSync(path24.dirname(dbPath), { recursive: true });
16322
+ const dbPath = ci.dbPath || path25.join(this.workspacePath, ".cascade", "code-index.db");
16323
+ fs24.mkdirSync(path25.dirname(dbPath), { recursive: true });
15490
16324
  const db = new Database(dbPath);
15491
16325
  db.pragma("journal_mode = WAL");
15492
16326
  const complete = chatCompleterFromProviders(this.config.providers);
@@ -15826,6 +16660,35 @@ ${input}` }],
15826
16660
  this.toolRegistry.register(tool);
15827
16661
  }
15828
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
+ }
15829
16692
  /**
15830
16693
  * Supply per-model thumbs-up/down counts so Auto routing can weigh how models
15831
16694
  * have actually performed for this user, on top of public benchmarks.
@@ -16774,7 +17637,7 @@ var Keystore = class {
16774
17637
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
16775
17638
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
16776
17639
  this.backend = "keytar";
16777
- if (password && fs23.existsSync(this.storePath)) {
17640
+ if (password && fs24.existsSync(this.storePath)) {
16778
17641
  try {
16779
17642
  const fileEntries = this.decryptFile(password);
16780
17643
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -16793,7 +17656,7 @@ var Keystore = class {
16793
17656
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
16794
17657
  );
16795
17658
  }
16796
- if (!fs23.existsSync(this.storePath)) {
17659
+ if (!fs24.existsSync(this.storePath)) {
16797
17660
  const salt = crypto4.randomBytes(SALT_LEN);
16798
17661
  this.masterKey = this.deriveKey(password, salt);
16799
17662
  this.writeWithSalt({}, salt);
@@ -16807,7 +17670,7 @@ var Keystore = class {
16807
17670
  }
16808
17671
  /** Synchronous legacy unlock kept for AES-only environments. */
16809
17672
  unlockSync(password) {
16810
- if (!fs23.existsSync(this.storePath)) {
17673
+ if (!fs24.existsSync(this.storePath)) {
16811
17674
  const salt = crypto4.randomBytes(SALT_LEN);
16812
17675
  this.masterKey = this.deriveKey(password, salt);
16813
17676
  this.writeWithSalt({}, salt);
@@ -16865,7 +17728,7 @@ var Keystore = class {
16865
17728
  }
16866
17729
  }
16867
17730
  decryptFile(password, knownSalt) {
16868
- if (!fs23.existsSync(this.storePath)) return {};
17731
+ if (!fs24.existsSync(this.storePath)) return {};
16869
17732
  try {
16870
17733
  const { salt, ciphertext, iv, tag } = this.readRaw();
16871
17734
  const useSalt = knownSalt ?? salt;
@@ -16887,8 +17750,8 @@ var Keystore = class {
16887
17750
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
16888
17751
  const tag = cipher.getAuthTag();
16889
17752
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
16890
- fs23.mkdirSync(path24.dirname(this.storePath), { recursive: true });
16891
- fs23.writeFileSync(this.storePath, out, { mode: 384 });
17753
+ fs24.mkdirSync(path25.dirname(this.storePath), { recursive: true });
17754
+ fs24.writeFileSync(this.storePath, out, { mode: 384 });
16892
17755
  }
16893
17756
  writeWithSalt(data, salt) {
16894
17757
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -16898,11 +17761,11 @@ var Keystore = class {
16898
17761
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
16899
17762
  const tag = cipher.getAuthTag();
16900
17763
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
16901
- fs23.mkdirSync(path24.dirname(this.storePath), { recursive: true });
16902
- fs23.writeFileSync(this.storePath, out, { mode: 384 });
17764
+ fs24.mkdirSync(path25.dirname(this.storePath), { recursive: true });
17765
+ fs24.writeFileSync(this.storePath, out, { mode: 384 });
16903
17766
  }
16904
17767
  readRaw() {
16905
- const buf = fs23.readFileSync(this.storePath);
17768
+ const buf = fs24.readFileSync(this.storePath);
16906
17769
  let offset = 0;
16907
17770
  const salt = buf.subarray(offset, offset + SALT_LEN);
16908
17771
  offset += SALT_LEN;
@@ -16918,9 +17781,9 @@ var Keystore = class {
16918
17781
  }
16919
17782
  };
16920
17783
  async function loadCascadeMd(workspacePath) {
16921
- const filePath = path24.join(workspacePath, "CASCADE.md");
17784
+ const filePath = path25.join(workspacePath, "CASCADE.md");
16922
17785
  try {
16923
- const raw = await fs4.readFile(filePath, "utf-8");
17786
+ const raw = await fs5.readFile(filePath, "utf-8");
16924
17787
  return parseCascadeMd(raw);
16925
17788
  } catch {
16926
17789
  return null;
@@ -16949,7 +17812,7 @@ ${raw.trim()}`;
16949
17812
  var MemoryStore = class _MemoryStore {
16950
17813
  db;
16951
17814
  constructor(dbPath) {
16952
- fs23.mkdirSync(path24.dirname(dbPath), { recursive: true });
17815
+ fs24.mkdirSync(path25.dirname(dbPath), { recursive: true });
16953
17816
  try {
16954
17817
  this.db = new Database(dbPath, { timeout: 5e3 });
16955
17818
  this.db.pragma("journal_mode = WAL");
@@ -17785,7 +18648,7 @@ Original error: ${err.message}`
17785
18648
  }
17786
18649
  };
17787
18650
  function credentialsPath(globalDir2) {
17788
- return path24.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
18651
+ return path25.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
17789
18652
  }
17790
18653
  function providerKey(p) {
17791
18654
  if (p.type === "azure") {
@@ -17798,7 +18661,7 @@ function isPersistable(p) {
17798
18661
  }
17799
18662
  function loadGlobalCredentials(globalDir2) {
17800
18663
  try {
17801
- const raw = fs23.readFileSync(credentialsPath(globalDir2), "utf-8");
18664
+ const raw = fs24.readFileSync(credentialsPath(globalDir2), "utf-8");
17802
18665
  const parsed = JSON.parse(raw);
17803
18666
  if (!Array.isArray(parsed.providers)) return [];
17804
18667
  return parsed.providers.filter(
@@ -17810,13 +18673,13 @@ function loadGlobalCredentials(globalDir2) {
17810
18673
  }
17811
18674
  function saveGlobalCredentials(globalDir2, providers) {
17812
18675
  const filePath = credentialsPath(globalDir2);
17813
- fs23.mkdirSync(globalDir2, { recursive: true, mode: 448 });
18676
+ fs24.mkdirSync(globalDir2, { recursive: true, mode: 448 });
17814
18677
  const body = { version: 1, providers: providers.filter(isPersistable) };
17815
18678
  const tmp = `${filePath}.tmp`;
17816
- fs23.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
17817
- fs23.renameSync(tmp, filePath);
18679
+ fs24.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
18680
+ fs24.renameSync(tmp, filePath);
17818
18681
  try {
17819
- fs23.chmodSync(filePath, 384);
18682
+ fs24.chmodSync(filePath, 384);
17820
18683
  } catch {
17821
18684
  }
17822
18685
  }
@@ -17851,7 +18714,7 @@ var ConfigManager = class {
17851
18714
  /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
17852
18715
  constructor(workspacePath = process.cwd(), globalDirOverride) {
17853
18716
  this.workspacePath = workspacePath;
17854
- this.globalDir = globalDirOverride ?? path24.join(os6.homedir(), GLOBAL_CONFIG_DIR);
18717
+ this.globalDir = globalDirOverride ?? path25.join(os6.homedir(), GLOBAL_CONFIG_DIR);
17855
18718
  }
17856
18719
  async load() {
17857
18720
  this.config = await this.loadConfig();
@@ -17865,8 +18728,8 @@ var ConfigManager = class {
17865
18728
  this.ignore = new CascadeIgnore();
17866
18729
  await this.ignore.load(this.workspacePath);
17867
18730
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
17868
- this.keystore = new Keystore(path24.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
17869
- this.store = new MemoryStore(path24.join(this.workspacePath, CASCADE_DB_FILE));
18731
+ this.keystore = new Keystore(path25.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
18732
+ this.store = new MemoryStore(path25.join(this.workspacePath, CASCADE_DB_FILE));
17870
18733
  await this.injectEnvKeys();
17871
18734
  this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
17872
18735
  await this.ensureDefaultIdentity();
@@ -17891,9 +18754,9 @@ var ConfigManager = class {
17891
18754
  return this.workspacePath;
17892
18755
  }
17893
18756
  async save() {
17894
- const configPath = path24.join(this.workspacePath, CASCADE_CONFIG_FILE);
17895
- await fs4.mkdir(path24.dirname(configPath), { recursive: true });
17896
- await fs4.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
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");
17897
18760
  try {
17898
18761
  saveGlobalCredentials(this.globalDir, this.config.providers);
17899
18762
  } catch (err) {
@@ -17928,9 +18791,9 @@ var ConfigManager = class {
17928
18791
  return configProvider?.apiKey;
17929
18792
  }
17930
18793
  async loadConfig() {
17931
- const configPath = path24.join(this.workspacePath, CASCADE_CONFIG_FILE);
18794
+ const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
17932
18795
  try {
17933
- const raw = await fs4.readFile(configPath, "utf-8");
18796
+ const raw = await fs5.readFile(configPath, "utf-8");
17934
18797
  return validateConfig(JSON.parse(raw));
17935
18798
  } catch (err) {
17936
18799
  if (err.code === "ENOENT") {
@@ -18460,7 +19323,7 @@ function aggregateCostStats(sessions, opts = {}) {
18460
19323
  }
18461
19324
 
18462
19325
  // src/dashboard/server.ts
18463
- var __dirname$1 = path24.dirname(fileURLToPath(import.meta.url));
19326
+ var __dirname$1 = path25.dirname(fileURLToPath(import.meta.url));
18464
19327
  var ORPHANED_ESCALATION_GRACE_MS = 3e4;
18465
19328
  var DashboardServer = class {
18466
19329
  app;
@@ -18740,14 +19603,14 @@ var DashboardServer = class {
18740
19603
  */
18741
19604
  persistConfig() {
18742
19605
  try {
18743
- const configPath = path24.join(this.workspacePath, CASCADE_CONFIG_FILE);
18744
- fs23.mkdirSync(path24.dirname(configPath), { recursive: true });
18745
- fs23.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
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");
18746
19609
  } catch (err) {
18747
19610
  console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
18748
19611
  }
18749
19612
  try {
18750
- saveGlobalCredentials(path24.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
19613
+ saveGlobalCredentials(path25.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
18751
19614
  } catch (err) {
18752
19615
  console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
18753
19616
  }
@@ -18762,15 +19625,15 @@ var DashboardServer = class {
18762
19625
  resolveDashboardSecret() {
18763
19626
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
18764
19627
  if (fromConfig) return fromConfig;
18765
- const secretPath = path24.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
19628
+ const secretPath = path25.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
18766
19629
  try {
18767
- if (fs23.existsSync(secretPath)) {
18768
- const existing = fs23.readFileSync(secretPath, "utf-8").trim();
19630
+ if (fs24.existsSync(secretPath)) {
19631
+ const existing = fs24.readFileSync(secretPath, "utf-8").trim();
18769
19632
  if (existing.length >= 16) return existing;
18770
19633
  }
18771
19634
  const generated = randomUUID();
18772
- fs23.mkdirSync(path24.dirname(secretPath), { recursive: true });
18773
- fs23.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
19635
+ fs24.mkdirSync(path25.dirname(secretPath), { recursive: true });
19636
+ fs24.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
18774
19637
  if (this.config.dashboard.auth) {
18775
19638
  console.warn(
18776
19639
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -18797,7 +19660,7 @@ var DashboardServer = class {
18797
19660
  // ── Setup ─────────────────────────────────────
18798
19661
  getGlobalStore() {
18799
19662
  if (!this.globalStore) {
18800
- const globalDbPath = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
19663
+ const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
18801
19664
  this.globalStore = new MemoryStore(globalDbPath);
18802
19665
  }
18803
19666
  return this.globalStore;
@@ -19177,12 +20040,12 @@ ${prompt}`;
19177
20040
  }
19178
20041
  }
19179
20042
  watchRuntimeChanges() {
19180
- const workspaceDbPath = path24.join(this.workspacePath, CASCADE_DB_FILE);
19181
- const globalDbPath = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
20043
+ const workspaceDbPath = path25.join(this.workspacePath, CASCADE_DB_FILE);
20044
+ const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
19182
20045
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
19183
20046
  for (const watchPath of watchPaths) {
19184
- if (!fs23.existsSync(watchPath)) continue;
19185
- fs23.watchFile(watchPath, { interval: 3e3 }, () => {
20047
+ if (!fs24.existsSync(watchPath)) continue;
20048
+ fs24.watchFile(watchPath, { interval: 3e3 }, () => {
19186
20049
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
19187
20050
  });
19188
20051
  }
@@ -19304,7 +20167,7 @@ ${prompt}`;
19304
20167
  const sessionId = req.params.id;
19305
20168
  this.store.deleteSession(sessionId);
19306
20169
  this.store.deleteRuntimeSession(sessionId);
19307
- const globalDbPath = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
20170
+ const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
19308
20171
  const globalStore = new MemoryStore(globalDbPath);
19309
20172
  try {
19310
20173
  globalStore.deleteRuntimeSession(sessionId);
@@ -19397,7 +20260,7 @@ ${prompt}`;
19397
20260
  });
19398
20261
  this.app.delete("/api/sessions", auth2, (req, res) => {
19399
20262
  const body = req.body;
19400
- const globalDbPath = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
20263
+ const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
19401
20264
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
19402
20265
  const globalStore = new MemoryStore(globalDbPath);
19403
20266
  try {
@@ -19420,7 +20283,7 @@ ${prompt}`;
19420
20283
  });
19421
20284
  this.app.delete("/api/runtime", auth2, (_req, res) => {
19422
20285
  this.store.deleteAllRuntimeNodes();
19423
- const globalDbPath = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
20286
+ const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
19424
20287
  const globalStore = new MemoryStore(globalDbPath);
19425
20288
  try {
19426
20289
  globalStore.deleteAllRuntimeNodes();
@@ -19506,12 +20369,12 @@ ${prompt}`;
19506
20369
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
19507
20370
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
19508
20371
  try {
19509
- const configPath = path24.join(this.workspacePath, CASCADE_CONFIG_FILE);
19510
- const existing = fs23.existsSync(configPath) ? JSON.parse(fs23.readFileSync(configPath, "utf-8")) : {};
20372
+ const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
20373
+ const existing = fs24.existsSync(configPath) ? JSON.parse(fs24.readFileSync(configPath, "utf-8")) : {};
19511
20374
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
19512
20375
  const tmp = configPath + ".tmp";
19513
- fs23.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
19514
- fs23.renameSync(tmp, configPath);
20376
+ fs24.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
20377
+ fs24.renameSync(tmp, configPath);
19515
20378
  res.json({ ok: true });
19516
20379
  } catch (err) {
19517
20380
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -19539,7 +20402,7 @@ ${prompt}`;
19539
20402
  this.app.get("/api/runtime", auth2, (req, res) => {
19540
20403
  const scope = req.query["scope"] ?? "workspace";
19541
20404
  if (scope === "global") {
19542
- const globalDbPath = path24.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
20405
+ const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
19543
20406
  const globalStore = new MemoryStore(globalDbPath);
19544
20407
  try {
19545
20408
  res.json({
@@ -19880,13 +20743,13 @@ ${prompt}`;
19880
20743
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
19881
20744
  }
19882
20745
  });
19883
- const prodPath = path24.resolve(__dirname$1, "../web/dist");
19884
- const devPath = path24.resolve(__dirname$1, "../../web/dist");
19885
- const webDistPath = fs23.existsSync(prodPath) ? prodPath : devPath;
19886
- if (fs23.existsSync(webDistPath)) {
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)) {
19887
20750
  this.app.use(express.static(webDistPath));
19888
20751
  this.app.get("*", (_req, res) => {
19889
- res.sendFile(path24.join(webDistPath, "index.html"));
20752
+ res.sendFile(path25.join(webDistPath, "index.html"));
19890
20753
  });
19891
20754
  } else {
19892
20755
  this.app.get("/", (_req, res) => {
@@ -20073,14 +20936,14 @@ function planRetrieval(ctx) {
20073
20936
  }
20074
20937
  var CLOUD_SESSION_FILE = "cloud-session.json";
20075
20938
  function globalDir(homeDir = os6.homedir()) {
20076
- return path24.join(homeDir, GLOBAL_CONFIG_DIR);
20939
+ return path25.join(homeDir, GLOBAL_CONFIG_DIR);
20077
20940
  }
20078
20941
  function cloudSessionPath(dir = globalDir()) {
20079
- return path24.join(dir, CLOUD_SESSION_FILE);
20942
+ return path25.join(dir, CLOUD_SESSION_FILE);
20080
20943
  }
20081
20944
  function loadCloudSession(dir = globalDir()) {
20082
20945
  try {
20083
- const raw = fs23.readFileSync(cloudSessionPath(dir), "utf-8");
20946
+ const raw = fs24.readFileSync(cloudSessionPath(dir), "utf-8");
20084
20947
  const parsed = JSON.parse(raw);
20085
20948
  if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
20086
20949
  return parsed;
@@ -20089,17 +20952,17 @@ function loadCloudSession(dir = globalDir()) {
20089
20952
  }
20090
20953
  }
20091
20954
  function saveCloudSession(session, dir = globalDir()) {
20092
- fs23.mkdirSync(dir, { recursive: true, mode: 448 });
20955
+ fs24.mkdirSync(dir, { recursive: true, mode: 448 });
20093
20956
  const file = cloudSessionPath(dir);
20094
- fs23.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
20957
+ fs24.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
20095
20958
  try {
20096
- fs23.chmodSync(file, 384);
20959
+ fs24.chmodSync(file, 384);
20097
20960
  } catch {
20098
20961
  }
20099
20962
  }
20100
20963
  function clearCloudSession(dir = globalDir()) {
20101
20964
  try {
20102
- fs23.rmSync(cloudSessionPath(dir));
20965
+ fs24.rmSync(cloudSessionPath(dir));
20103
20966
  } catch {
20104
20967
  }
20105
20968
  }
@@ -20271,16 +21134,16 @@ var CloudClient = class _CloudClient {
20271
21134
  return (await this.refresh()).accessToken;
20272
21135
  }
20273
21136
  /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
20274
- async authed(path26, init = {}) {
20275
- const call = (token) => fetch(this.url(path26), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
21137
+ async authed(path27, init = {}) {
21138
+ const call = (token) => fetch(this.url(path27), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
20276
21139
  let res = await call(await this.accessToken());
20277
21140
  if (res.status === 401) res = await call((await this.refresh()).accessToken);
20278
21141
  if (!res.ok) throw new Error(`Request failed (${res.status}).`);
20279
21142
  const text = await res.text();
20280
21143
  return text ? JSON.parse(text) : {};
20281
21144
  }
20282
- authedGet(path26) {
20283
- return this.authed(path26);
21145
+ authedGet(path27) {
21146
+ return this.authed(path27);
20284
21147
  }
20285
21148
  async fetchMe(token) {
20286
21149
  const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
@@ -20617,6 +21480,6 @@ function applySyncBundle(bundle, config) {
20617
21480
  return next;
20618
21481
  }
20619
21482
 
20620
- 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 };
20621
21484
  //# sourceMappingURL=index.js.map
20622
21485
  //# sourceMappingURL=index.js.map