cascade-ai 0.66.2 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,8 +7,8 @@ import https from 'https';
7
7
  import zlib from 'zlib';
8
8
  import { Readable } from 'stream';
9
9
  import Database2 from 'better-sqlite3';
10
- import path32, { dirname } from 'path';
11
- import fs29, { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
10
+ import path33, { dirname } from 'path';
11
+ import fs30, { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
12
12
  import os10 from 'os';
13
13
  import crypto, { randomUUID, randomBytes, webcrypto, timingSafeEqual, createHash } from 'crypto';
14
14
  import { render, Box, Text, useStdout, useApp, useInput, Static } from 'ink';
@@ -17,7 +17,7 @@ import React2, { useState, useReducer, useRef, useCallback, useEffect, useMemo }
17
17
  import chalk7 from 'chalk';
18
18
  import dotenv from 'dotenv';
19
19
  import { fileURLToPath } from 'url';
20
- import fs10 from 'fs/promises';
20
+ import fs11 from 'fs/promises';
21
21
  import * as _ignoreModule from 'ignore';
22
22
  import _ignoreModule__default from 'ignore';
23
23
  import { z } from 'zod';
@@ -1757,7 +1757,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
1757
1757
  var init_constants = __esm({
1758
1758
  "src/constants.ts"() {
1759
1759
  init_pricing();
1760
- CASCADE_VERSION = "0.66.2";
1760
+ CASCADE_VERSION = "0.67.0";
1761
1761
  CASCADE_CONFIG_FILE = ".cascade/config.json";
1762
1762
  CASCADE_DB_FILE = ".cascade/memory.db";
1763
1763
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -3879,12 +3879,12 @@ var init_audit_logger = __esm({
3879
3879
  constructor(workspacePath, debugMode = false) {
3880
3880
  this.workspacePath = workspacePath;
3881
3881
  this.debugMode = debugMode;
3882
- const cascadeDir = path32.join(workspacePath, ".cascade");
3883
- if (!fs29.existsSync(cascadeDir)) {
3884
- fs29.mkdirSync(cascadeDir, { recursive: true });
3882
+ const cascadeDir = path33.join(workspacePath, ".cascade");
3883
+ if (!fs30.existsSync(cascadeDir)) {
3884
+ fs30.mkdirSync(cascadeDir, { recursive: true });
3885
3885
  }
3886
- this.keyPath = path32.join(cascadeDir, "audit_log.key");
3887
- this.dbPath = path32.join(cascadeDir, "audit_log.db");
3886
+ this.keyPath = path33.join(cascadeDir, "audit_log.key");
3887
+ this.dbPath = path33.join(cascadeDir, "audit_log.db");
3888
3888
  this.initEncryptionKey();
3889
3889
  this.db = new Database2(this.dbPath);
3890
3890
  this.db.pragma("journal_mode = WAL");
@@ -3914,11 +3914,11 @@ var init_audit_logger = __esm({
3914
3914
  return crypto.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
3915
3915
  }
3916
3916
  initEncryptionKey() {
3917
- if (fs29.existsSync(this.keyPath)) {
3918
- this.encryptionKey = fs29.readFileSync(this.keyPath);
3917
+ if (fs30.existsSync(this.keyPath)) {
3918
+ this.encryptionKey = fs30.readFileSync(this.keyPath);
3919
3919
  } else {
3920
3920
  this.encryptionKey = crypto.randomBytes(32);
3921
- fs29.writeFileSync(this.keyPath, this.encryptionKey);
3921
+ fs30.writeFileSync(this.keyPath, this.encryptionKey);
3922
3922
  }
3923
3923
  }
3924
3924
  encrypt(text) {
@@ -3996,9 +3996,9 @@ var init_audit_logger = __esm({
3996
3996
  dumpDebugIfNeeded() {
3997
3997
  if (!this.debugMode) return;
3998
3998
  try {
3999
- const dumpPath = path32.join(os10.tmpdir(), "cascade_audit_logs_debug.json");
3999
+ const dumpPath = path33.join(os10.tmpdir(), "cascade_audit_logs_debug.json");
4000
4000
  const entries = this.getAllLogs();
4001
- fs29.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
4001
+ fs30.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
4002
4002
  } catch (err) {
4003
4003
  console.error("Failed to dump debug audit logs", err);
4004
4004
  }
@@ -4053,7 +4053,7 @@ var Keystore = class {
4053
4053
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
4054
4054
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
4055
4055
  this.backend = "keytar";
4056
- if (password && fs29.existsSync(this.storePath)) {
4056
+ if (password && fs30.existsSync(this.storePath)) {
4057
4057
  try {
4058
4058
  const fileEntries = this.decryptFile(password);
4059
4059
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -4072,7 +4072,7 @@ var Keystore = class {
4072
4072
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
4073
4073
  );
4074
4074
  }
4075
- if (!fs29.existsSync(this.storePath)) {
4075
+ if (!fs30.existsSync(this.storePath)) {
4076
4076
  const salt = crypto.randomBytes(SALT_LEN);
4077
4077
  this.masterKey = this.deriveKey(password, salt);
4078
4078
  this.writeWithSalt({}, salt);
@@ -4086,7 +4086,7 @@ var Keystore = class {
4086
4086
  }
4087
4087
  /** Synchronous legacy unlock kept for AES-only environments. */
4088
4088
  unlockSync(password) {
4089
- if (!fs29.existsSync(this.storePath)) {
4089
+ if (!fs30.existsSync(this.storePath)) {
4090
4090
  const salt = crypto.randomBytes(SALT_LEN);
4091
4091
  this.masterKey = this.deriveKey(password, salt);
4092
4092
  this.writeWithSalt({}, salt);
@@ -4144,7 +4144,7 @@ var Keystore = class {
4144
4144
  }
4145
4145
  }
4146
4146
  decryptFile(password, knownSalt) {
4147
- if (!fs29.existsSync(this.storePath)) return {};
4147
+ if (!fs30.existsSync(this.storePath)) return {};
4148
4148
  try {
4149
4149
  const { salt, ciphertext, iv, tag } = this.readRaw();
4150
4150
  const useSalt = knownSalt ?? salt;
@@ -4166,8 +4166,8 @@ var Keystore = class {
4166
4166
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
4167
4167
  const tag = cipher.getAuthTag();
4168
4168
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
4169
- fs29.mkdirSync(path32.dirname(this.storePath), { recursive: true });
4170
- fs29.writeFileSync(this.storePath, out, { mode: 384 });
4169
+ fs30.mkdirSync(path33.dirname(this.storePath), { recursive: true });
4170
+ fs30.writeFileSync(this.storePath, out, { mode: 384 });
4171
4171
  }
4172
4172
  writeWithSalt(data, salt) {
4173
4173
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -4177,11 +4177,11 @@ var Keystore = class {
4177
4177
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
4178
4178
  const tag = cipher.getAuthTag();
4179
4179
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
4180
- fs29.mkdirSync(path32.dirname(this.storePath), { recursive: true });
4181
- fs29.writeFileSync(this.storePath, out, { mode: 384 });
4180
+ fs30.mkdirSync(path33.dirname(this.storePath), { recursive: true });
4181
+ fs30.writeFileSync(this.storePath, out, { mode: 384 });
4182
4182
  }
4183
4183
  readRaw() {
4184
- const buf = fs29.readFileSync(this.storePath);
4184
+ const buf = fs30.readFileSync(this.storePath);
4185
4185
  let offset = 0;
4186
4186
  const salt = buf.subarray(offset, offset + SALT_LEN);
4187
4187
  offset += SALT_LEN;
@@ -4214,9 +4214,9 @@ var CascadeIgnore = class {
4214
4214
  ]);
4215
4215
  }
4216
4216
  async load(workspacePath) {
4217
- const filePath = path32.join(workspacePath, ".cascadeignore");
4217
+ const filePath = path33.join(workspacePath, ".cascadeignore");
4218
4218
  try {
4219
- const content = await fs10.readFile(filePath, "utf-8");
4219
+ const content = await fs11.readFile(filePath, "utf-8");
4220
4220
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
4221
4221
  this.ig.add(lines);
4222
4222
  this.loaded = true;
@@ -4225,7 +4225,7 @@ var CascadeIgnore = class {
4225
4225
  }
4226
4226
  isIgnored(filePath, workspacePath) {
4227
4227
  try {
4228
- const relative = workspacePath ? path32.relative(workspacePath, filePath) : filePath;
4228
+ const relative = workspacePath ? path33.relative(workspacePath, filePath) : filePath;
4229
4229
  return this.ig.ignores(relative);
4230
4230
  } catch {
4231
4231
  return false;
@@ -4236,7 +4236,7 @@ var CascadeIgnore = class {
4236
4236
  }
4237
4237
  };
4238
4238
  async function createDefaultIgnoreFile(workspacePath) {
4239
- const filePath = path32.join(workspacePath, ".cascadeignore");
4239
+ const filePath = path33.join(workspacePath, ".cascadeignore");
4240
4240
  const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
4241
4241
  # Syntax identical to .gitignore
4242
4242
 
@@ -4263,12 +4263,12 @@ build/
4263
4263
  .DS_Store
4264
4264
  Thumbs.db
4265
4265
  `;
4266
- await fs10.writeFile(filePath, content, "utf-8");
4266
+ await fs11.writeFile(filePath, content, "utf-8");
4267
4267
  }
4268
4268
  async function loadCascadeMd(workspacePath) {
4269
- const filePath = path32.join(workspacePath, "CASCADE.md");
4269
+ const filePath = path33.join(workspacePath, "CASCADE.md");
4270
4270
  try {
4271
- const raw = await fs10.readFile(filePath, "utf-8");
4271
+ const raw = await fs11.readFile(filePath, "utf-8");
4272
4272
  return parseCascadeMd(raw);
4273
4273
  } catch {
4274
4274
  return null;
@@ -4295,7 +4295,7 @@ ${raw.trim()}`;
4295
4295
  return { raw, sections, systemPrompt };
4296
4296
  }
4297
4297
  async function createDefaultCascadeMd(workspacePath) {
4298
- const filePath = path32.join(workspacePath, "CASCADE.md");
4298
+ const filePath = path33.join(workspacePath, "CASCADE.md");
4299
4299
  const content = `# Cascade Project Instructions
4300
4300
 
4301
4301
  This file contains project-specific instructions for the Cascade AI agent.
@@ -4325,12 +4325,12 @@ All tools are allowed unless specified otherwise.
4325
4325
 
4326
4326
  List any areas the agent should not touch.
4327
4327
  `;
4328
- await fs10.writeFile(filePath, content, "utf-8");
4328
+ await fs11.writeFile(filePath, content, "utf-8");
4329
4329
  }
4330
4330
  var MemoryStore = class _MemoryStore {
4331
4331
  db;
4332
4332
  constructor(dbPath) {
4333
- fs29.mkdirSync(path32.dirname(dbPath), { recursive: true });
4333
+ fs30.mkdirSync(path33.dirname(dbPath), { recursive: true });
4334
4334
  try {
4335
4335
  this.db = new Database2(dbPath, { timeout: 5e3 });
4336
4336
  this.db.pragma("journal_mode = WAL");
@@ -5582,7 +5582,7 @@ function validateConfig(raw) {
5582
5582
  // src/config/global-credentials.ts
5583
5583
  init_constants();
5584
5584
  function credentialsPath(globalDir2) {
5585
- return path32.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
5585
+ return path33.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
5586
5586
  }
5587
5587
  function providerKey(p) {
5588
5588
  if (p.type === "azure") {
@@ -5595,7 +5595,7 @@ function isPersistable(p) {
5595
5595
  }
5596
5596
  function loadGlobalCredentials(globalDir2) {
5597
5597
  try {
5598
- const raw = fs29.readFileSync(credentialsPath(globalDir2), "utf-8");
5598
+ const raw = fs30.readFileSync(credentialsPath(globalDir2), "utf-8");
5599
5599
  const parsed = JSON.parse(raw);
5600
5600
  if (!Array.isArray(parsed.providers)) return [];
5601
5601
  return parsed.providers.filter(
@@ -5607,13 +5607,13 @@ function loadGlobalCredentials(globalDir2) {
5607
5607
  }
5608
5608
  function saveGlobalCredentials(globalDir2, providers) {
5609
5609
  const filePath = credentialsPath(globalDir2);
5610
- fs29.mkdirSync(globalDir2, { recursive: true, mode: 448 });
5610
+ fs30.mkdirSync(globalDir2, { recursive: true, mode: 448 });
5611
5611
  const body = { version: 1, providers: providers.filter(isPersistable) };
5612
5612
  const tmp = `${filePath}.tmp`;
5613
- fs29.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
5614
- fs29.renameSync(tmp, filePath);
5613
+ fs30.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
5614
+ fs30.renameSync(tmp, filePath);
5615
5615
  try {
5616
- fs29.chmodSync(filePath, 384);
5616
+ fs30.chmodSync(filePath, 384);
5617
5617
  } catch {
5618
5618
  }
5619
5619
  }
@@ -5831,7 +5831,7 @@ var ConfigManager = class {
5831
5831
  /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
5832
5832
  constructor(workspacePath = process.cwd(), globalDirOverride) {
5833
5833
  this.workspacePath = workspacePath;
5834
- this.globalDir = globalDirOverride ?? path32.join(os10.homedir(), GLOBAL_CONFIG_DIR);
5834
+ this.globalDir = globalDirOverride ?? path33.join(os10.homedir(), GLOBAL_CONFIG_DIR);
5835
5835
  }
5836
5836
  async load() {
5837
5837
  this.config = await this.loadConfig();
@@ -5845,8 +5845,8 @@ var ConfigManager = class {
5845
5845
  this.ignore = new CascadeIgnore();
5846
5846
  await this.ignore.load(this.workspacePath);
5847
5847
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
5848
- this.keystore = new Keystore(path32.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
5849
- this.store = new MemoryStore(path32.join(this.workspacePath, CASCADE_DB_FILE));
5848
+ this.keystore = new Keystore(path33.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
5849
+ this.store = new MemoryStore(path33.join(this.workspacePath, CASCADE_DB_FILE));
5850
5850
  await this.injectEnvKeys();
5851
5851
  this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
5852
5852
  await this.ensureDefaultIdentity();
@@ -5871,9 +5871,9 @@ var ConfigManager = class {
5871
5871
  return this.workspacePath;
5872
5872
  }
5873
5873
  async save() {
5874
- const configPath = path32.join(this.workspacePath, CASCADE_CONFIG_FILE);
5875
- await fs10.mkdir(path32.dirname(configPath), { recursive: true });
5876
- await fs10.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
5874
+ const configPath = path33.join(this.workspacePath, CASCADE_CONFIG_FILE);
5875
+ await fs11.mkdir(path33.dirname(configPath), { recursive: true });
5876
+ await fs11.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
5877
5877
  try {
5878
5878
  saveGlobalCredentials(this.globalDir, this.config.providers);
5879
5879
  } catch (err) {
@@ -5908,9 +5908,9 @@ var ConfigManager = class {
5908
5908
  return configProvider?.apiKey;
5909
5909
  }
5910
5910
  async loadConfig() {
5911
- const configPath = path32.join(this.workspacePath, CASCADE_CONFIG_FILE);
5911
+ const configPath = path33.join(this.workspacePath, CASCADE_CONFIG_FILE);
5912
5912
  try {
5913
- const raw = await fs10.readFile(configPath, "utf-8");
5913
+ const raw = await fs11.readFile(configPath, "utf-8");
5914
5914
  return validateConfig(JSON.parse(raw));
5915
5915
  } catch (err) {
5916
5916
  if (err.code === "ENOENT") {
@@ -7075,7 +7075,7 @@ init_pricing();
7075
7075
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
7076
7076
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
7077
7077
  var FETCH_TIMEOUT_MS = 8e3;
7078
- var DEFAULT_CACHE_FILE = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
7078
+ var DEFAULT_CACHE_FILE = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
7079
7079
  function normalizeModelId2(id) {
7080
7080
  let s = id.toLowerCase();
7081
7081
  const slash = s.lastIndexOf("/");
@@ -7110,7 +7110,7 @@ var LiveDataProvider = class {
7110
7110
  if (this.loaded) return;
7111
7111
  this.loaded = true;
7112
7112
  try {
7113
- const raw = await fs10.readFile(this.opts.cacheFile, "utf-8");
7113
+ const raw = await fs11.readFile(this.opts.cacheFile, "utf-8");
7114
7114
  const cache = JSON.parse(raw);
7115
7115
  if (cache.snapshot?.families) {
7116
7116
  this.snapshot = cache.snapshot;
@@ -7222,14 +7222,14 @@ var LiveDataProvider = class {
7222
7222
  }
7223
7223
  async saveCache() {
7224
7224
  try {
7225
- await fs10.mkdir(path32.dirname(this.opts.cacheFile), { recursive: true });
7225
+ await fs11.mkdir(path33.dirname(this.opts.cacheFile), { recursive: true });
7226
7226
  const cache = {
7227
7227
  fetchedAt: this.fetchedAt,
7228
7228
  snapshot: this.snapshot ?? void 0,
7229
7229
  prices: Object.fromEntries(this.prices),
7230
7230
  capabilities: Object.fromEntries(this.capabilities)
7231
7231
  };
7232
- await fs10.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
7232
+ await fs11.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
7233
7233
  } catch {
7234
7234
  }
7235
7235
  }
@@ -9057,7 +9057,8 @@ var KNOWN_TOOLS = [
9057
9057
  "generate_image",
9058
9058
  "generate_video",
9059
9059
  "generate_speech",
9060
- "transcribe_audio"
9060
+ "transcribe_audio",
9061
+ "generate_document"
9061
9062
  ];
9062
9063
  var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
9063
9064
  "generate_image",
@@ -9074,18 +9075,45 @@ function buildWorkerRules(has) {
9074
9075
  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.",
9075
9076
  has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
9076
9077
  has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
9078
+ // A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
9079
+ // file_write does exactly what it promises — writes the model's characters
9080
+ // verbatim — so without this rule the worker "wrote" a Word document that
9081
+ // Word correctly reported as corrupted. Stated as an explicit prohibition
9082
+ // on the tool it would otherwise reach for, because "use generate_document"
9083
+ // alone competes with a file_write habit the model already has.
9084
+ 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.',
9085
+ // The alternative to this rule is not "a worse chart" — it is a paragraph
9086
+ // describing a chart that does not exist, which is what real decks came
9087
+ // back with. A `chart:` block is checkable, carries the exact numbers, and
9088
+ // becomes a genuine editable PowerPoint chart object.
9089
+ 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.',
9077
9090
  // Without this the model "writes the image" as prose — a bracketed
9078
9091
  // description sitting in the deck where the picture should be — even with a
9079
9092
  // working image model registered. Naming the reference syntax matters as
9080
9093
  // much as naming the tool: a generated image nobody embeds is still a
9081
- // missing image.
9094
+ // missing image. It also has to say the reference must stand ALONE on its
9095
+ // line, because only a standalone reference is parsed as a picture (see
9096
+ // core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
9097
+ // silently degrades to caption text.
9082
9098
  //
9083
- // Scoped to PowerPoint/Word specifically: those are the only exporters
9084
- // that actually embed a Markdown image reference (cloud/web/exporters.ts
9085
- // toPptx/toDocx). PDF and plain-text/Markdown deliverables flatten a
9086
- // reference straight to caption text — telling the model to call
9087
- // generate_image for THOSE just pays for an image nobody ever sees.
9088
- 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.',
9099
+ // Scoped to PowerPoint/Word specifically: those are the only renderers that
9100
+ // actually embed a Markdown image reference. PDF and plain-text/Markdown
9101
+ // deliverables flatten a reference straight to caption text — telling the
9102
+ // model to call generate_image for THOSE just pays for an image nobody
9103
+ // ever sees.
9104
+ has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
9105
+ // standalone-line parser constraint, says to generate BEFORE writing the
9106
+ // document, and points data-driven visuals at a ```chart: block rather
9107
+ // than a Markdown table, which is only the right advice now that chart
9108
+ // blocks render as real chart objects.
9109
+ '- 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.',
9110
+ // The other half of image reliability, and the half no amount of
9111
+ // instruction-tightening could fix: generate_image only exists when an
9112
+ // OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
9113
+ // With neither, the model was never given a choice — and a model that
9114
+ // doesn't know that writes "[illustration of a cat]" and moves on. Said out
9115
+ // loud, up front, so the fallback is a real chart or honest prose.
9116
+ !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.",
9089
9117
  // The video counterpart of the image rule above, and the fix for the
9090
9118
  // reported "it writes scripts forever and never makes the video" run. The
9091
9119
  // failure mode is identical — the model narrates the deliverable instead of
@@ -9095,7 +9123,7 @@ function buildWorkerRules(has) {
9095
9123
  // the deliverable rather than an illustration inside one, and the reference
9096
9124
  // syntax is a plain link: Markdown image syntax does not embed a video.
9097
9125
  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.',
9098
- 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.',
9126
+ 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.`,
9099
9127
  "- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
9100
9128
  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.',
9101
9129
  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.",
@@ -9106,13 +9134,34 @@ function buildWorkerRules(has) {
9106
9134
  Rules:
9107
9135
  ${rules.filter((r) => r !== false).join("\n")}`;
9108
9136
  }
9109
- var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
9137
+ var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
9138
+ 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;
9110
9139
  function shouldRequireArtifact(assignment, toolNames) {
9111
9140
  if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
9112
9141
  if (assignment?.files?.length) return true;
9113
9142
  const haystack = `${assignment?.description ?? ""}
9114
9143
  ${assignment?.expectedOutput ?? ""}`;
9115
- 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);
9144
+ return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
9145
+ }
9146
+ 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;
9147
+ var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
9148
+ var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
9149
+ function missingVisualEvidence(assignment, output, calledTools, toolNames) {
9150
+ const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
9151
+ if (!canVisualize) return null;
9152
+ const haystack = `${assignment?.description ?? ""}
9153
+ ${assignment?.expectedOutput ?? ""}`;
9154
+ if (!VISUAL_REQUEST_RE.test(haystack)) return null;
9155
+ if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
9156
+ if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
9157
+ const options = [];
9158
+ if (toolNames.includes("generate_image")) {
9159
+ options.push('call "generate_image" and reference the result on its own line as ![description](location)');
9160
+ }
9161
+ if (toolNames.includes("generate_document")) {
9162
+ options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
9163
+ }
9164
+ 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.`;
9116
9165
  }
9117
9166
  var T3Worker = class extends BaseTier {
9118
9167
  router;
@@ -9293,6 +9342,18 @@ Now execute your subtask using this context where relevant.`
9293
9342
  return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
9294
9343
  }
9295
9344
  }
9345
+ const visualIssue = missingVisualEvidence(
9346
+ assignment,
9347
+ output,
9348
+ toolCalls.map((t) => t.name),
9349
+ this.tools.map((t) => t.name)
9350
+ );
9351
+ if (visualIssue) {
9352
+ correctionAttempts = 1;
9353
+ issues.push(visualIssue);
9354
+ this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
9355
+ output = await this.correctOutput(output, [visualIssue]);
9356
+ }
9296
9357
  this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
9297
9358
  const testResult = await this.selfTest(assignment, output);
9298
9359
  checksRun.push(...testResult.checksRun);
@@ -9607,8 +9668,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
9607
9668
  // Media generation can run for a minute; without this a cancelled run
9608
9669
  // still pays for an image nobody will see.
9609
9670
  ...this.signal ? { signal: this.signal } : {},
9610
- saveSnapshot: async (path37, content) => {
9611
- this.store?.addFileSnapshot(this.taskId, path37, content);
9671
+ saveSnapshot: async (path38, content) => {
9672
+ this.store?.addFileSnapshot(this.taskId, path38, content);
9612
9673
  },
9613
9674
  sendPeerSync: (to, syncType, content) => {
9614
9675
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -9784,7 +9845,7 @@ ${assignment.expectedOutput}`;
9784
9845
  const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
9785
9846
  const haystack = `${assignment.description}
9786
9847
  ${assignment.expectedOutput}`;
9787
- 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) ?? [];
9848
+ const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
9788
9849
  return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
9789
9850
  }
9790
9851
  async verifyArtifacts(assignment) {
@@ -9795,9 +9856,9 @@ ${assignment.expectedOutput}`;
9795
9856
  const { promisify: promisify4 } = await import('util');
9796
9857
  const execAsync3 = promisify4(exec3);
9797
9858
  for (const artifactPath of artifactPaths) {
9798
- const absolutePath = path32.resolve(process.cwd(), artifactPath);
9859
+ const absolutePath = path33.resolve(process.cwd(), artifactPath);
9799
9860
  try {
9800
- const stat = await fs10.stat(absolutePath);
9861
+ const stat = await fs11.stat(absolutePath);
9801
9862
  if (!stat.isFile()) {
9802
9863
  issues.push(`Expected artifact is not a file: ${artifactPath}`);
9803
9864
  continue;
@@ -9806,8 +9867,22 @@ ${assignment.expectedOutput}`;
9806
9867
  issues.push(`Artifact is empty: ${artifactPath}`);
9807
9868
  continue;
9808
9869
  }
9809
- if (!/\.pdf$/i.test(artifactPath)) {
9810
- const content = await fs10.readFile(absolutePath, "utf-8");
9870
+ if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
9871
+ const head = Buffer.alloc(4);
9872
+ const fh = await fs11.open(absolutePath, "r");
9873
+ try {
9874
+ await fh.read(head, 0, 4, 0);
9875
+ } finally {
9876
+ await fh.close();
9877
+ }
9878
+ if (head.toString("latin1") !== "PK") {
9879
+ issues.push(
9880
+ `${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.`
9881
+ );
9882
+ continue;
9883
+ }
9884
+ } else if (!/\.pdf$/i.test(artifactPath)) {
9885
+ const content = await fs11.readFile(absolutePath, "utf-8");
9811
9886
  if (!content.trim()) {
9812
9887
  issues.push(`Artifact content is empty: ${artifactPath}`);
9813
9888
  continue;
@@ -9816,7 +9891,7 @@ ${assignment.expectedOutput}`;
9816
9891
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
9817
9892
  continue;
9818
9893
  }
9819
- const ext = path32.extname(absolutePath).toLowerCase();
9894
+ const ext = path33.extname(absolutePath).toLowerCase();
9820
9895
  try {
9821
9896
  if (ext === ".ts" || ext === ".tsx") {
9822
9897
  await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
@@ -12770,6 +12845,700 @@ function buildMediaTools(deps, readFile) {
12770
12845
  if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
12771
12846
  return tools;
12772
12847
  }
12848
+ var WorkspaceSandboxError = class extends Error {
12849
+ constructor(attempted, workspaceRoot) {
12850
+ super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
12851
+ this.name = "WorkspaceSandboxError";
12852
+ }
12853
+ };
12854
+ function resolveInWorkspace(workspaceRoot, input) {
12855
+ if (typeof input !== "string" || input.length === 0) {
12856
+ throw new WorkspaceSandboxError(String(input), workspaceRoot);
12857
+ }
12858
+ const root = path33.resolve(workspaceRoot);
12859
+ const abs = path33.isAbsolute(input) ? path33.resolve(input) : path33.resolve(root, input);
12860
+ const rel = path33.relative(root, abs);
12861
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path33.isAbsolute(rel)) {
12862
+ throw new WorkspaceSandboxError(input, root);
12863
+ }
12864
+ try {
12865
+ const real = fs30.realpathSync(abs);
12866
+ const realRel = path33.relative(root, real);
12867
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path33.isAbsolute(realRel))) {
12868
+ throw new WorkspaceSandboxError(input, root);
12869
+ }
12870
+ } catch (e) {
12871
+ if (e instanceof WorkspaceSandboxError) throw e;
12872
+ }
12873
+ return abs;
12874
+ }
12875
+
12876
+ // src/core/documents/blocks.ts
12877
+ function fileExt(name) {
12878
+ const base = name.split(/[\\/]/).pop() ?? name;
12879
+ const dot = base.lastIndexOf(".");
12880
+ return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
12881
+ }
12882
+ function parseDelimited(text, name = "") {
12883
+ const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
12884
+ const rows = [];
12885
+ let row = [];
12886
+ let cell = "";
12887
+ let quoted = false;
12888
+ for (let i = 0; i < text.length; i++) {
12889
+ const ch = text[i];
12890
+ if (quoted) {
12891
+ if (ch === '"') {
12892
+ if (text[i + 1] === '"') {
12893
+ cell += '"';
12894
+ i++;
12895
+ } else quoted = false;
12896
+ } else cell += ch;
12897
+ } else if (ch === '"') {
12898
+ quoted = true;
12899
+ } else if (ch === delim) {
12900
+ row.push(cell);
12901
+ cell = "";
12902
+ } else if (ch === "\n") {
12903
+ row.push(cell);
12904
+ rows.push(row);
12905
+ row = [];
12906
+ cell = "";
12907
+ } else if (ch === "\r") ; else {
12908
+ cell += ch;
12909
+ }
12910
+ if (rows.length > 5e3) break;
12911
+ }
12912
+ if (cell !== "" || row.length) {
12913
+ row.push(cell);
12914
+ rows.push(row);
12915
+ }
12916
+ return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
12917
+ }
12918
+ var CHART_ALIASES = {
12919
+ bar: "bar",
12920
+ column: "bar",
12921
+ bars: "bar",
12922
+ barchart: "bar",
12923
+ horizontalbar: "bar",
12924
+ line: "line",
12925
+ lines: "line",
12926
+ linechart: "line",
12927
+ pie: "pie",
12928
+ piechart: "pie",
12929
+ doughnut: "doughnut",
12930
+ donut: "doughnut",
12931
+ area: "area",
12932
+ areachart: "area",
12933
+ scatter: "scatter",
12934
+ xy: "scatter"
12935
+ };
12936
+ function chartKind(raw) {
12937
+ return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
12938
+ }
12939
+ function toNumber(cell) {
12940
+ const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
12941
+ const n = Number.parseFloat(cleaned);
12942
+ return Number.isFinite(n) ? n : 0;
12943
+ }
12944
+ function parseChartSpec(kind, body) {
12945
+ const lines = body.replace(/\r\n/g, "\n").split("\n");
12946
+ let title;
12947
+ while (lines.length && !lines[0].trim()) lines.shift();
12948
+ const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
12949
+ if (titleMatch) {
12950
+ title = titleMatch[1].trim();
12951
+ lines.shift();
12952
+ }
12953
+ const rows = parseDelimited(lines.join("\n").trim());
12954
+ if (rows.length < 2) return null;
12955
+ const header = rows[0].map((c) => c.trim());
12956
+ if (header.length < 2) return null;
12957
+ const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
12958
+ if (!dataRows.length) return null;
12959
+ const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
12960
+ const series = header.slice(1).map((name, col2) => ({
12961
+ name: name || `Series ${col2 + 1}`,
12962
+ values: dataRows.map((r) => toNumber((r[col2 + 1] ?? "").trim()))
12963
+ }));
12964
+ if (!series.length) return null;
12965
+ return {
12966
+ kind,
12967
+ ...title ? { title } : {},
12968
+ categoryName: header[0] || "Category",
12969
+ categories,
12970
+ series
12971
+ };
12972
+ }
12973
+ function chartToTableRows(spec) {
12974
+ return [
12975
+ [spec.categoryName, ...spec.series.map((s) => s.name)],
12976
+ ...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
12977
+ ];
12978
+ }
12979
+ function parseBlocks(md) {
12980
+ const out = [];
12981
+ const lines = md.replace(/\r\n/g, "\n").split("\n");
12982
+ let i = 0;
12983
+ let ordinal = 0;
12984
+ const reset = () => {
12985
+ ordinal = 0;
12986
+ };
12987
+ while (i < lines.length) {
12988
+ const line = lines[i] ?? "";
12989
+ const fence = line.match(/^```+\s*(.*)$/);
12990
+ if (fence) {
12991
+ const info = (fence[1] ?? "").trim();
12992
+ i++;
12993
+ const code = [];
12994
+ while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
12995
+ code.push(lines[i] ?? "");
12996
+ i++;
12997
+ }
12998
+ i++;
12999
+ const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
13000
+ const kind = chartInfo ? chartKind(chartInfo[1]) : null;
13001
+ const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
13002
+ out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
13003
+ reset();
13004
+ continue;
13005
+ }
13006
+ if (!line.trim()) {
13007
+ i++;
13008
+ reset();
13009
+ continue;
13010
+ }
13011
+ const h = line.match(/^(#{1,6})\s+(.*)$/);
13012
+ if (h) {
13013
+ out.push({ t: "heading", level: h[1].length, text: h[2] });
13014
+ i++;
13015
+ reset();
13016
+ continue;
13017
+ }
13018
+ if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
13019
+ out.push({ t: "hr" });
13020
+ i++;
13021
+ reset();
13022
+ continue;
13023
+ }
13024
+ const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
13025
+ if (li) {
13026
+ const ordered = /\d/.test(li[2]);
13027
+ ordinal = ordered ? ordinal + 1 : 0;
13028
+ out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
13029
+ i++;
13030
+ continue;
13031
+ }
13032
+ if (/^>\s?/.test(line)) {
13033
+ out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
13034
+ i++;
13035
+ reset();
13036
+ continue;
13037
+ }
13038
+ if (/^\|.*\|/.test(line)) {
13039
+ const rows = [];
13040
+ while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
13041
+ const r = lines[i];
13042
+ if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
13043
+ i++;
13044
+ }
13045
+ out.push({ t: "table", rows });
13046
+ reset();
13047
+ continue;
13048
+ }
13049
+ out.push({ t: "para", text: line });
13050
+ i++;
13051
+ reset();
13052
+ }
13053
+ return out;
13054
+ }
13055
+ var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
13056
+ function matchImageLine(s) {
13057
+ const m = s.trim().match(IMAGE_LINE);
13058
+ return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
13059
+ }
13060
+ function sniffImage(b) {
13061
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
13062
+ if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
13063
+ return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
13064
+ }
13065
+ if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
13066
+ return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
13067
+ }
13068
+ if (b.length > 26 && b[0] === 66 && b[1] === 77) {
13069
+ return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
13070
+ }
13071
+ if (b.length > 4 && b[0] === 255 && b[1] === 216) {
13072
+ let i = 2;
13073
+ while (i + 9 < b.length) {
13074
+ if (b[i] !== 255) {
13075
+ i++;
13076
+ continue;
13077
+ }
13078
+ const marker = b[i + 1];
13079
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
13080
+ return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
13081
+ }
13082
+ i += 2 + dv.getUint16(i + 2);
13083
+ }
13084
+ }
13085
+ return null;
13086
+ }
13087
+ function bytesToBase64(bytes) {
13088
+ const g = globalThis;
13089
+ if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
13090
+ const CHUNK = 32768;
13091
+ let binary = "";
13092
+ for (let i = 0; i < bytes.length; i += CHUNK) {
13093
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
13094
+ }
13095
+ return g.btoa(binary);
13096
+ }
13097
+ function stripInline(s) {
13098
+ return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
13099
+ }
13100
+ function inlineRuns(text) {
13101
+ const acc = [];
13102
+ const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
13103
+ let last = 0;
13104
+ let m;
13105
+ while ((m = re.exec(text)) !== null) {
13106
+ if (m.index > last) acc.push({ text: text.slice(last, m.index) });
13107
+ const tok = m[0];
13108
+ if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
13109
+ else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
13110
+ else if (tok.startsWith("[")) {
13111
+ const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
13112
+ acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
13113
+ } else acc.push({ text: tok.slice(1, -1), italic: true });
13114
+ last = re.lastIndex;
13115
+ }
13116
+ if (last < text.length) acc.push({ text: text.slice(last) });
13117
+ return acc.length ? acc : [{ text }];
13118
+ }
13119
+ function splitSlides(md) {
13120
+ const norm = md.replace(/\r\n/g, "\n").trim();
13121
+ let chunks = norm.split(/\n\s*-{3,}\s*\n/);
13122
+ if (chunks.length === 1) {
13123
+ const lines = norm.split("\n");
13124
+ const sections = [];
13125
+ let cur = [];
13126
+ for (const ln of lines) {
13127
+ if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
13128
+ sections.push(cur.join("\n"));
13129
+ cur = [];
13130
+ }
13131
+ cur.push(ln);
13132
+ }
13133
+ if (cur.length) sections.push(cur.join("\n"));
13134
+ if (sections.length > 1) chunks = sections;
13135
+ }
13136
+ return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
13137
+ }
13138
+ function parseSlide(chunk) {
13139
+ let title = "";
13140
+ const body = [];
13141
+ const images = [];
13142
+ const charts = [];
13143
+ const lines = chunk.split("\n");
13144
+ for (let i = 0; i < lines.length; i++) {
13145
+ const raw = lines[i] ?? "";
13146
+ const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
13147
+ if (fence) {
13148
+ const kind = chartKind(fence[1]);
13149
+ const bodyLines = [];
13150
+ i++;
13151
+ while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
13152
+ bodyLines.push(lines[i] ?? "");
13153
+ i++;
13154
+ }
13155
+ const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
13156
+ if (spec) charts.push(spec);
13157
+ else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
13158
+ continue;
13159
+ }
13160
+ const line = raw.trim();
13161
+ if (!line) continue;
13162
+ const h = line.match(/^#{1,6}[ \t]+(.*)/);
13163
+ if (h) {
13164
+ if (!title) title = stripInline(h[1]);
13165
+ else body.push(stripInline(h[1]));
13166
+ continue;
13167
+ }
13168
+ const img = matchImageLine(line);
13169
+ if (img) {
13170
+ images.push(img);
13171
+ continue;
13172
+ }
13173
+ const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
13174
+ if (li) {
13175
+ const bulletImg = matchImageLine(li[2]);
13176
+ if (bulletImg) {
13177
+ images.push(bulletImg);
13178
+ continue;
13179
+ }
13180
+ body.push(stripInline(li[2]));
13181
+ continue;
13182
+ }
13183
+ if (/^>\s?/.test(line)) {
13184
+ body.push(stripInline(line.replace(/^>\s?/, "")));
13185
+ continue;
13186
+ }
13187
+ body.push(stripInline(line));
13188
+ }
13189
+ return { title, body, images, charts };
13190
+ }
13191
+ function extractCharts(source) {
13192
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
13193
+ const charts = [];
13194
+ const rest = [];
13195
+ for (let i = 0; i < lines.length; i++) {
13196
+ const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
13197
+ if (!fence) {
13198
+ rest.push(lines[i]);
13199
+ continue;
13200
+ }
13201
+ const kind = chartKind(fence[1]);
13202
+ const body = [];
13203
+ i++;
13204
+ while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
13205
+ body.push(lines[i] ?? "");
13206
+ i++;
13207
+ }
13208
+ const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
13209
+ if (spec) charts.push(spec);
13210
+ else rest.push(...body);
13211
+ }
13212
+ return { charts, rest: rest.join("\n") };
13213
+ }
13214
+
13215
+ // src/core/documents/render.ts
13216
+ function isDocumentFormat(ext) {
13217
+ return ext === "docx" || ext === "pptx" || ext === "xlsx";
13218
+ }
13219
+ async function loadImage(ref, load) {
13220
+ if (!load) return null;
13221
+ try {
13222
+ const bytes = await load(ref.url);
13223
+ if (!bytes || !bytes.length) return null;
13224
+ const info = sniffImage(bytes);
13225
+ if (!info || info.width <= 0 || info.height <= 0) return null;
13226
+ return { ...info, bytes, alt: ref.alt };
13227
+ } catch {
13228
+ return null;
13229
+ }
13230
+ }
13231
+ async function renderXlsx(source, opts = {}) {
13232
+ const XLSX = await import('xlsx');
13233
+ const { charts, rest } = extractCharts(source);
13234
+ const name = opts.name ?? "";
13235
+ const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
13236
+ const wb = XLSX.utils.book_new();
13237
+ XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
13238
+ const used = /* @__PURE__ */ new Set(["Sheet1"]);
13239
+ charts.forEach((spec, i) => {
13240
+ const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
13241
+ let sheetName = base;
13242
+ let n = 2;
13243
+ while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
13244
+ used.add(sheetName);
13245
+ const aoa = [
13246
+ [spec.categoryName, ...spec.series.map((s) => s.name)],
13247
+ ...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
13248
+ ];
13249
+ XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
13250
+ });
13251
+ return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
13252
+ }
13253
+ var DOCX_MAX_W = 600;
13254
+ var DOCX_MAX_H = 864;
13255
+ async function renderDocx(md, opts = {}) {
13256
+ const d = await import('docx');
13257
+ const {
13258
+ Document,
13259
+ Packer,
13260
+ Paragraph,
13261
+ TextRun,
13262
+ ImageRun,
13263
+ HeadingLevel,
13264
+ Table,
13265
+ TableRow,
13266
+ TableCell,
13267
+ WidthType,
13268
+ BorderStyle,
13269
+ AlignmentType
13270
+ } = d;
13271
+ const HEADINGS = [
13272
+ HeadingLevel.HEADING_1,
13273
+ HeadingLevel.HEADING_2,
13274
+ HeadingLevel.HEADING_3,
13275
+ HeadingLevel.HEADING_4,
13276
+ HeadingLevel.HEADING_5,
13277
+ HeadingLevel.HEADING_6
13278
+ ];
13279
+ const runs = (text) => inlineRuns(text).map((r) => new TextRun({
13280
+ text: r.text,
13281
+ ...r.bold ? { bold: true } : {},
13282
+ ...r.italic ? { italics: true } : {},
13283
+ ...r.code ? { font: "Courier New" } : {}
13284
+ }));
13285
+ const tableOf = (rows) => new Table({
13286
+ width: { size: 100, type: WidthType.PERCENTAGE },
13287
+ rows: rows.map((r) => new TableRow({
13288
+ children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
13289
+ }))
13290
+ });
13291
+ const imageParagraph = (img) => {
13292
+ const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
13293
+ return new Paragraph({
13294
+ children: [new ImageRun({
13295
+ type: img.type,
13296
+ data: img.bytes,
13297
+ transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
13298
+ ...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
13299
+ })]
13300
+ });
13301
+ };
13302
+ const asImage = async (text) => {
13303
+ const ref = matchImageLine(text);
13304
+ return ref ? await loadImage(ref, opts.loadImageBytes) : null;
13305
+ };
13306
+ const children = [];
13307
+ for (const b of parseBlocks(md)) {
13308
+ switch (b.t) {
13309
+ case "heading":
13310
+ children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
13311
+ break;
13312
+ case "para": {
13313
+ const img = await asImage(b.text);
13314
+ children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
13315
+ break;
13316
+ }
13317
+ case "bullet": {
13318
+ const img = await asImage(b.text);
13319
+ children.push(img ? imageParagraph(img) : new Paragraph({
13320
+ children: runs(b.text),
13321
+ ...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
13322
+ }));
13323
+ break;
13324
+ }
13325
+ case "code":
13326
+ for (const cl of b.lines) {
13327
+ children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
13328
+ }
13329
+ break;
13330
+ case "quote":
13331
+ children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
13332
+ break;
13333
+ case "table":
13334
+ children.push(tableOf(b.rows));
13335
+ break;
13336
+ case "chart":
13337
+ if (b.spec.title) {
13338
+ children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
13339
+ }
13340
+ children.push(tableOf(chartToTableRows(b.spec)));
13341
+ break;
13342
+ case "hr":
13343
+ children.push(new Paragraph({
13344
+ children: [],
13345
+ border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
13346
+ }));
13347
+ break;
13348
+ }
13349
+ }
13350
+ const doc = new Document({
13351
+ numbering: {
13352
+ config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
13353
+ },
13354
+ sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
13355
+ });
13356
+ return new Uint8Array(await Packer.toArrayBuffer(doc));
13357
+ }
13358
+ function pptxConstructor(mod) {
13359
+ let c = mod;
13360
+ for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
13361
+ if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
13362
+ return c;
13363
+ }
13364
+ var SLIDE_H = 7.5;
13365
+ var CONTENT_X = 0.6;
13366
+ var CONTENT_W = 12.1;
13367
+ var CONTENT_BOTTOM = 0.4;
13368
+ async function renderPptx(md, opts = {}) {
13369
+ const PptxGen = pptxConstructor(await import('pptxgenjs'));
13370
+ const pptx = new PptxGen();
13371
+ pptx.layout = "LAYOUT_WIDE";
13372
+ const CHART_TYPES = {
13373
+ bar: pptx.ChartType["bar"],
13374
+ line: pptx.ChartType["line"],
13375
+ pie: pptx.ChartType["pie"],
13376
+ doughnut: pptx.ChartType["doughnut"],
13377
+ area: pptx.ChartType["area"],
13378
+ scatter: pptx.ChartType["scatter"]
13379
+ };
13380
+ const slides = splitSlides(md);
13381
+ const blank = { title: "", body: [""], images: [], charts: [] };
13382
+ for (const s of slides.length ? slides : [blank]) {
13383
+ const slide = pptx.addSlide();
13384
+ 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" });
13385
+ const top = s.title ? 1.5 : 0.5;
13386
+ const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
13387
+ const visuals = [
13388
+ ...s.charts.map((spec) => ({ kind: "chart", spec })),
13389
+ ...pics.map((img) => ({ kind: "image", img }))
13390
+ ];
13391
+ const bodyH = visuals.length ? 2.2 : 5.4;
13392
+ if (s.body.length) {
13393
+ slide.addText(
13394
+ s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
13395
+ { x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
13396
+ );
13397
+ }
13398
+ if (visuals.length) {
13399
+ const y = top + (s.body.length ? bodyH + 0.2 : 0);
13400
+ const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
13401
+ const gap = 0.3;
13402
+ const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
13403
+ visuals.forEach((v, i) => {
13404
+ const x = CONTENT_X + i * (w + gap);
13405
+ if (v.kind === "image") {
13406
+ slide.addImage({
13407
+ data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
13408
+ x,
13409
+ y,
13410
+ w,
13411
+ h,
13412
+ // 'contain' keeps the aspect ratio inside the box — a stretched
13413
+ // render looks worse than a smaller correct one.
13414
+ sizing: { type: "contain", w, h },
13415
+ ...v.img.alt ? { altText: v.img.alt } : {}
13416
+ });
13417
+ } else {
13418
+ slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
13419
+ x,
13420
+ y,
13421
+ w,
13422
+ h,
13423
+ showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
13424
+ legendPos: "b",
13425
+ showTitle: !!v.spec.title,
13426
+ ...v.spec.title ? { title: v.spec.title } : {},
13427
+ showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
13428
+ });
13429
+ }
13430
+ });
13431
+ }
13432
+ }
13433
+ return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
13434
+ }
13435
+ function chartData(spec) {
13436
+ const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
13437
+ return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
13438
+ }
13439
+ async function renderDocument(format, source, opts = {}) {
13440
+ if (format === "docx") return renderDocx(source, opts);
13441
+ if (format === "pptx") return renderPptx(source, opts);
13442
+ return renderXlsx(source, opts);
13443
+ }
13444
+
13445
+ // src/tools/generate-document.ts
13446
+ var SOURCE_HINT = {
13447
+ docx: "Markdown",
13448
+ pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
13449
+ xlsx: "CSV (one header row, then data rows)"
13450
+ };
13451
+ var GenerateDocumentTool = class extends BaseTool {
13452
+ /**
13453
+ * `readFile` is injected so the image loader is testable and so a host with a
13454
+ * different workspace (not plain disk) can supply its own reader.
13455
+ */
13456
+ constructor(readFile = async (p) => new Uint8Array(await fs11.readFile(p))) {
13457
+ super();
13458
+ this.readFile = readFile;
13459
+ }
13460
+ readFile;
13461
+ name = "generate_document";
13462
+ 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.";
13463
+ inputSchema = {
13464
+ type: "object",
13465
+ properties: {
13466
+ path: {
13467
+ type: "string",
13468
+ description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
13469
+ },
13470
+ content: {
13471
+ type: "string",
13472
+ 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.'
13473
+ },
13474
+ format: {
13475
+ type: "string",
13476
+ description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
13477
+ }
13478
+ },
13479
+ required: ["path", "content"]
13480
+ };
13481
+ isDangerous() {
13482
+ return true;
13483
+ }
13484
+ async execute(input, options) {
13485
+ const filePath = String(input["path"] ?? "").trim();
13486
+ if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
13487
+ const content = typeof input["content"] === "string" ? input["content"] : "";
13488
+ if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
13489
+ const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
13490
+ const ext = requested || fileExt(filePath);
13491
+ if (!isDocumentFormat(ext)) {
13492
+ 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.`;
13493
+ }
13494
+ const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
13495
+ const embedded = [];
13496
+ const skipped = [];
13497
+ const loadImageBytes = async (url) => {
13498
+ if (/^(https?|ftp):\/\//i.test(url)) {
13499
+ skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
13500
+ return null;
13501
+ }
13502
+ const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
13503
+ try {
13504
+ const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
13505
+ embedded.push(local);
13506
+ return bytes2;
13507
+ } catch {
13508
+ skipped.push(`${local} (not found in the workspace)`);
13509
+ return null;
13510
+ }
13511
+ };
13512
+ if (options.saveSnapshot) {
13513
+ try {
13514
+ const old = await fs11.readFile(absPath, "utf-8");
13515
+ await options.saveSnapshot(absPath, old);
13516
+ } catch {
13517
+ }
13518
+ }
13519
+ const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
13520
+ await fs11.mkdir(path33.dirname(absPath), { recursive: true });
13521
+ await fs11.writeFile(absPath, bytes);
13522
+ const charts = countCharts(content);
13523
+ return [
13524
+ `Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
13525
+ embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
13526
+ // Named, not counted: a dropped image is the failure the user actually
13527
+ // notices, and the model can only fix it if it is told which reference died.
13528
+ skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
13529
+ charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
13530
+ charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
13531
+ charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
13532
+ ].filter(Boolean).join("\n");
13533
+ }
13534
+ };
13535
+ var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
13536
+ function countCharts(source) {
13537
+ return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
13538
+ }
13539
+ function buildDocumentTools(readFile) {
13540
+ return readFile ? [new GenerateDocumentTool(readFile)] : [];
13541
+ }
12773
13542
 
12774
13543
  // src/core/run-breaker.ts
12775
13544
  var DEFAULT_FAILURE_THRESHOLD = 3;
@@ -12918,35 +13687,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
12918
13687
  }
12919
13688
  }
12920
13689
  };
12921
- var WorkspaceSandboxError = class extends Error {
12922
- constructor(attempted, workspaceRoot) {
12923
- super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
12924
- this.name = "WorkspaceSandboxError";
12925
- }
12926
- };
12927
- function resolveInWorkspace(workspaceRoot, input) {
12928
- if (typeof input !== "string" || input.length === 0) {
12929
- throw new WorkspaceSandboxError(String(input), workspaceRoot);
12930
- }
12931
- const root = path32.resolve(workspaceRoot);
12932
- const abs = path32.isAbsolute(input) ? path32.resolve(input) : path32.resolve(root, input);
12933
- const rel = path32.relative(root, abs);
12934
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path32.isAbsolute(rel)) {
12935
- throw new WorkspaceSandboxError(input, root);
12936
- }
12937
- try {
12938
- const real = fs29.realpathSync(abs);
12939
- const realRel = path32.relative(root, real);
12940
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path32.isAbsolute(realRel))) {
12941
- throw new WorkspaceSandboxError(input, root);
12942
- }
12943
- } catch (e) {
12944
- if (e instanceof WorkspaceSandboxError) throw e;
12945
- }
12946
- return abs;
12947
- }
12948
-
12949
- // src/tools/file.ts
12950
13690
  var FileReadTool = class extends BaseTool {
12951
13691
  name = "file_read";
12952
13692
  description = "Read the contents of a file. Returns the file content as a string.";
@@ -12964,7 +13704,7 @@ var FileReadTool = class extends BaseTool {
12964
13704
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
12965
13705
  const offset = input["offset"] ?? 1;
12966
13706
  const limit = input["limit"];
12967
- const content = await fs10.readFile(absPath, "utf-8");
13707
+ const content = await fs11.readFile(absPath, "utf-8");
12968
13708
  const lines = content.split("\n");
12969
13709
  const start = Math.max(0, offset - 1);
12970
13710
  const end = limit ? start + limit : lines.length;
@@ -12993,13 +13733,13 @@ var FileWriteTool = class extends BaseTool {
12993
13733
  const content = input["content"];
12994
13734
  if (options.saveSnapshot) {
12995
13735
  try {
12996
- const oldContent = await fs10.readFile(absPath, "utf-8");
13736
+ const oldContent = await fs11.readFile(absPath, "utf-8");
12997
13737
  await options.saveSnapshot(absPath, oldContent);
12998
13738
  } catch {
12999
13739
  }
13000
13740
  }
13001
- await fs10.mkdir(path32.dirname(absPath), { recursive: true });
13002
- await fs10.writeFile(absPath, content, "utf-8");
13741
+ await fs11.mkdir(path33.dirname(absPath), { recursive: true });
13742
+ await fs11.writeFile(absPath, content, "utf-8");
13003
13743
  return `Written ${content.length} characters to ${filePath}`;
13004
13744
  }
13005
13745
  };
@@ -13025,7 +13765,7 @@ var FileEditTool = class extends BaseTool {
13025
13765
  const oldString = input["old_string"];
13026
13766
  const newString = input["new_string"];
13027
13767
  const replaceAll = input["replace_all"] ?? false;
13028
- const rawContent = await fs10.readFile(absPath, "utf-8");
13768
+ const rawContent = await fs11.readFile(absPath, "utf-8");
13029
13769
  if (options.saveSnapshot) {
13030
13770
  await options.saveSnapshot(absPath, rawContent);
13031
13771
  }
@@ -13037,7 +13777,7 @@ var FileEditTool = class extends BaseTool {
13037
13777
  );
13038
13778
  }
13039
13779
  const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
13040
- await fs10.writeFile(absPath, updated, "utf-8");
13780
+ await fs11.writeFile(absPath, updated, "utf-8");
13041
13781
  const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
13042
13782
  return `Replaced ${count} occurrence(s) in ${filePath}`;
13043
13783
  }
@@ -13060,12 +13800,12 @@ var FileDeleteTool = class extends BaseTool {
13060
13800
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
13061
13801
  if (options.saveSnapshot) {
13062
13802
  try {
13063
- const oldContent = await fs10.readFile(absPath, "utf-8");
13803
+ const oldContent = await fs11.readFile(absPath, "utf-8");
13064
13804
  await options.saveSnapshot(absPath, oldContent);
13065
13805
  } catch {
13066
13806
  }
13067
13807
  }
13068
- await fs10.rm(absPath, { recursive: false });
13808
+ await fs11.rm(absPath, { recursive: false });
13069
13809
  return `Deleted ${filePath}`;
13070
13810
  }
13071
13811
  };
@@ -13082,7 +13822,7 @@ var FileListTool = class extends BaseTool {
13082
13822
  async execute(input, _options) {
13083
13823
  const inputPath = input["path"] || ".";
13084
13824
  const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
13085
- const entries = await fs10.readdir(absPath, { withFileTypes: true });
13825
+ const entries = await fs11.readdir(absPath, { withFileTypes: true });
13086
13826
  return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
13087
13827
  }
13088
13828
  };
@@ -13495,8 +14235,8 @@ var ImageAnalyzeTool = class extends BaseTool {
13495
14235
  }
13496
14236
  };
13497
14237
  async function fileToImageAttachment(filePath) {
13498
- const data = await fs10.readFile(filePath);
13499
- const ext = path32.extname(filePath).toLowerCase();
14238
+ const data = await fs11.readFile(filePath);
14239
+ const ext = path33.extname(filePath).toLowerCase();
13500
14240
  const mimeMap = {
13501
14241
  ".jpg": "image/jpeg",
13502
14242
  ".jpeg": "image/jpeg",
@@ -13530,14 +14270,14 @@ var PDFCreateTool = class extends BaseTool {
13530
14270
  const filePath = input["path"];
13531
14271
  const content = input["content"];
13532
14272
  const title = input["title"];
13533
- const dir = path32.dirname(filePath);
13534
- if (!fs29.existsSync(dir)) {
13535
- fs29.mkdirSync(dir, { recursive: true });
14273
+ const dir = path33.dirname(filePath);
14274
+ if (!fs30.existsSync(dir)) {
14275
+ fs30.mkdirSync(dir, { recursive: true });
13536
14276
  }
13537
14277
  return new Promise((resolve2, reject) => {
13538
14278
  try {
13539
14279
  const doc = new PDFDocument({ margin: 50 });
13540
- const stream = fs29.createWriteStream(filePath);
14280
+ const stream = fs30.createWriteStream(filePath);
13541
14281
  doc.pipe(stream);
13542
14282
  if (title) {
13543
14283
  doc.info["Title"] = title;
@@ -13615,22 +14355,22 @@ var CodeInterpreterTool = class extends BaseTool {
13615
14355
  }
13616
14356
  cmdPrefix = NODE_CMD;
13617
14357
  }
13618
- const tmpDir = path32.join(this.workspaceRoot, ".cascade", "tmp");
13619
- if (!fs29.existsSync(tmpDir)) {
13620
- fs29.mkdirSync(tmpDir, { recursive: true });
14358
+ const tmpDir = path33.join(this.workspaceRoot, ".cascade", "tmp");
14359
+ if (!fs30.existsSync(tmpDir)) {
14360
+ fs30.mkdirSync(tmpDir, { recursive: true });
13621
14361
  }
13622
14362
  const extension = language === "python" ? "py" : "js";
13623
14363
  const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
13624
- const filePath = path32.join(tmpDir, fileName);
13625
- fs29.writeFileSync(filePath, code, "utf-8");
14364
+ const filePath = path33.join(tmpDir, fileName);
14365
+ fs30.writeFileSync(filePath, code, "utf-8");
13626
14366
  const execArgs = [filePath, ...args];
13627
14367
  return new Promise((resolve2) => {
13628
14368
  const startMs = Date.now();
13629
14369
  execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
13630
14370
  const duration = Date.now() - startMs;
13631
14371
  try {
13632
- if (fs29.existsSync(filePath)) {
13633
- fs29.unlinkSync(filePath);
14372
+ if (fs30.existsSync(filePath)) {
14373
+ fs30.unlinkSync(filePath);
13634
14374
  }
13635
14375
  } catch (cleanupErr) {
13636
14376
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -13942,7 +14682,7 @@ var GlobTool = class extends BaseTool {
13942
14682
  };
13943
14683
  async execute(input, _options) {
13944
14684
  const pattern = input["pattern"];
13945
- const searchPath = input["path"] ? path32.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
14685
+ const searchPath = input["path"] ? path33.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
13946
14686
  const matches = await glob(pattern, {
13947
14687
  cwd: searchPath,
13948
14688
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -13955,7 +14695,7 @@ var GlobTool = class extends BaseTool {
13955
14695
  const withMtime = await Promise.all(
13956
14696
  matches.map(async (rel) => {
13957
14697
  try {
13958
- const stat = await fs10.stat(path32.join(searchPath, rel));
14698
+ const stat = await fs11.stat(path33.join(searchPath, rel));
13959
14699
  return { rel, mtime: stat.mtimeMs };
13960
14700
  } catch {
13961
14701
  return { rel, mtime: 0 };
@@ -14004,7 +14744,7 @@ var GrepTool = class extends BaseTool {
14004
14744
  };
14005
14745
  async execute(input, _options) {
14006
14746
  const pattern = input["pattern"];
14007
- const searchPath = input["path"] ? path32.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
14747
+ const searchPath = input["path"] ? path33.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
14008
14748
  const globPattern = input["glob"];
14009
14749
  const outputMode = input["output_mode"] ?? "content";
14010
14750
  const context = input["context"] ?? 0;
@@ -14058,15 +14798,15 @@ var GrepTool = class extends BaseTool {
14058
14798
  nodir: true
14059
14799
  });
14060
14800
  } catch {
14061
- files = [path32.relative(searchPath, searchPath) || "."];
14801
+ files = [path33.relative(searchPath, searchPath) || "."];
14062
14802
  }
14063
14803
  const results = [];
14064
14804
  let totalCount = 0;
14065
14805
  for (const rel of files) {
14066
- const abs = path32.join(searchPath, rel);
14806
+ const abs = path33.join(searchPath, rel);
14067
14807
  let content;
14068
14808
  try {
14069
- content = await fs10.readFile(abs, "utf-8");
14809
+ content = await fs11.readFile(abs, "utf-8");
14070
14810
  } catch {
14071
14811
  continue;
14072
14812
  }
@@ -14450,10 +15190,10 @@ var ToolRegistry = class extends EventEmitter {
14450
15190
  }
14451
15191
  isIgnored(filePath) {
14452
15192
  if (!filePath) return false;
14453
- const abs = path32.resolve(this.workspaceRoot, filePath);
14454
- const rel = path32.relative(this.workspaceRoot, abs);
14455
- if (!rel || rel.startsWith("..") || path32.isAbsolute(rel)) return true;
14456
- const posixRel = rel.split(path32.sep).join("/");
15193
+ const abs = path33.resolve(this.workspaceRoot, filePath);
15194
+ const rel = path33.relative(this.workspaceRoot, abs);
15195
+ if (!rel || rel.startsWith("..") || path33.isAbsolute(rel)) return true;
15196
+ const posixRel = rel.split(path33.sep).join("/");
14457
15197
  return this.ignoreMatcher.ignores(posixRel);
14458
15198
  }
14459
15199
  };
@@ -14687,22 +15427,22 @@ var FileMcpOAuthStore = class {
14687
15427
  filePath;
14688
15428
  load() {
14689
15429
  try {
14690
- return JSON.parse(fs29.readFileSync(this.filePath, "utf-8"));
15430
+ return JSON.parse(fs30.readFileSync(this.filePath, "utf-8"));
14691
15431
  } catch {
14692
15432
  return void 0;
14693
15433
  }
14694
15434
  }
14695
15435
  save(state) {
14696
- fs29.mkdirSync(path32.dirname(this.filePath), { recursive: true, mode: 448 });
14697
- fs29.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
15436
+ fs30.mkdirSync(path33.dirname(this.filePath), { recursive: true, mode: 448 });
15437
+ fs30.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
14698
15438
  try {
14699
- fs29.chmodSync(this.filePath, 384);
15439
+ fs30.chmodSync(this.filePath, 384);
14700
15440
  } catch {
14701
15441
  }
14702
15442
  }
14703
15443
  clear() {
14704
15444
  try {
14705
- fs29.rmSync(this.filePath);
15445
+ fs30.rmSync(this.filePath);
14706
15446
  } catch {
14707
15447
  }
14708
15448
  }
@@ -15310,7 +16050,7 @@ var TaskAnalyzer = class {
15310
16050
 
15311
16051
  // src/core/router/model-performance-tracker.ts
15312
16052
  init_pricing();
15313
- var DEFAULT_STATS_FILE = path32.join(os10.homedir(), ".cascade", "model-perf.json");
16053
+ var DEFAULT_STATS_FILE = path33.join(os10.homedir(), ".cascade", "model-perf.json");
15314
16054
  var ModelPerformanceTracker = class {
15315
16055
  stats = /* @__PURE__ */ new Map();
15316
16056
  featureStats = /* @__PURE__ */ new Map();
@@ -15330,7 +16070,7 @@ var ModelPerformanceTracker = class {
15330
16070
  if (this.loaded) return;
15331
16071
  this.loaded = true;
15332
16072
  try {
15333
- const raw = await fs10.readFile(this.statsFile, "utf-8");
16073
+ const raw = await fs11.readFile(this.statsFile, "utf-8");
15334
16074
  const parsed = JSON.parse(raw);
15335
16075
  if (parsed.models) {
15336
16076
  for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
@@ -15350,15 +16090,15 @@ var ModelPerformanceTracker = class {
15350
16090
  async save() {
15351
16091
  if (this.readOnly) return;
15352
16092
  try {
15353
- await fs10.mkdir(path32.dirname(this.statsFile), { recursive: true });
16093
+ await fs11.mkdir(path33.dirname(this.statsFile), { recursive: true });
15354
16094
  const modelsObj = {};
15355
16095
  const featuresObj = {};
15356
16096
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
15357
16097
  for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
15358
16098
  const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
15359
16099
  const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
15360
- await fs10.writeFile(tmp, json, "utf-8");
15361
- await fs10.rename(tmp, this.statsFile);
16100
+ await fs11.writeFile(tmp, json, "utf-8");
16101
+ await fs11.rename(tmp, this.statsFile);
15362
16102
  } catch {
15363
16103
  }
15364
16104
  }
@@ -15898,9 +16638,9 @@ Required capability: ${description.slice(0, 300)}`;
15898
16638
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
15899
16639
  async loadPersistedTools() {
15900
16640
  if (!this.workspacePath || !this.persistEnabled) return;
15901
- const file = path32.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
16641
+ const file = path33.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
15902
16642
  try {
15903
- const raw = await fs10.readFile(file, "utf-8");
16643
+ const raw = await fs11.readFile(file, "utf-8");
15904
16644
  const specs = JSON.parse(raw);
15905
16645
  if (!Array.isArray(specs)) return;
15906
16646
  let loaded = 0;
@@ -15922,11 +16662,11 @@ Required capability: ${description.slice(0, 300)}`;
15922
16662
  }
15923
16663
  async persist() {
15924
16664
  if (!this.workspacePath || !this.persistEnabled) return;
15925
- const dir = path32.join(this.workspacePath, ".cascade");
15926
- const file = path32.join(dir, DYNAMIC_TOOLS_FILE);
16665
+ const dir = path33.join(this.workspacePath, ".cascade");
16666
+ const file = path33.join(dir, DYNAMIC_TOOLS_FILE);
15927
16667
  try {
15928
- await fs10.mkdir(dir, { recursive: true });
15929
- await fs10.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
16668
+ await fs11.mkdir(dir, { recursive: true });
16669
+ await fs11.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
15930
16670
  } catch (err) {
15931
16671
  this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
15932
16672
  }
@@ -15943,12 +16683,12 @@ var WorldStateDB = class {
15943
16683
  constructor(workspacePath, debugMode = false) {
15944
16684
  this.workspacePath = workspacePath;
15945
16685
  this.debugMode = debugMode;
15946
- const cascadeDir = path32.join(workspacePath, ".cascade");
15947
- if (!fs29.existsSync(cascadeDir)) {
15948
- fs29.mkdirSync(cascadeDir, { recursive: true });
16686
+ const cascadeDir = path33.join(workspacePath, ".cascade");
16687
+ if (!fs30.existsSync(cascadeDir)) {
16688
+ fs30.mkdirSync(cascadeDir, { recursive: true });
15949
16689
  }
15950
- this.keyPath = path32.join(cascadeDir, "world_state.key");
15951
- this.dbPath = path32.join(cascadeDir, "world_state.db");
16690
+ this.keyPath = path33.join(cascadeDir, "world_state.key");
16691
+ this.dbPath = path33.join(cascadeDir, "world_state.db");
15952
16692
  this.initEncryptionKey();
15953
16693
  this.db = new Database2(this.dbPath);
15954
16694
  this.db.pragma("journal_mode = WAL");
@@ -16005,11 +16745,11 @@ var WorldStateDB = class {
16005
16745
  ).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
16006
16746
  }
16007
16747
  initEncryptionKey() {
16008
- if (fs29.existsSync(this.keyPath)) {
16009
- this.encryptionKey = fs29.readFileSync(this.keyPath);
16748
+ if (fs30.existsSync(this.keyPath)) {
16749
+ this.encryptionKey = fs30.readFileSync(this.keyPath);
16010
16750
  } else {
16011
16751
  this.encryptionKey = crypto.randomBytes(32);
16012
- fs29.writeFileSync(this.keyPath, this.encryptionKey);
16752
+ fs30.writeFileSync(this.keyPath, this.encryptionKey);
16013
16753
  }
16014
16754
  }
16015
16755
  encrypt(text) {
@@ -16295,9 +17035,9 @@ var WorldStateDB = class {
16295
17035
  dumpDebugIfNeeded() {
16296
17036
  if (!this.debugMode) return;
16297
17037
  try {
16298
- const dumpPath = path32.join(os10.tmpdir(), "cascade_world_state_debug.json");
17038
+ const dumpPath = path33.join(os10.tmpdir(), "cascade_world_state_debug.json");
16299
17039
  const entries = this.getAllEntries();
16300
- fs29.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
17040
+ fs30.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
16301
17041
  } catch (err) {
16302
17042
  console.error("Failed to dump debug world state", err);
16303
17043
  }
@@ -16667,7 +17407,7 @@ var WorkspaceIndex = class {
16667
17407
  isIgnored;
16668
17408
  chunker;
16669
17409
  constructor(opts) {
16670
- this.root = path32.resolve(opts.root);
17410
+ this.root = path33.resolve(opts.root);
16671
17411
  this.db = opts.db;
16672
17412
  this.namespace = opts.namespace ?? "code";
16673
17413
  this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
@@ -16694,19 +17434,19 @@ var WorkspaceIndex = class {
16694
17434
  const walk = async (dir) => {
16695
17435
  let entries;
16696
17436
  try {
16697
- entries = await fs10.readdir(dir, { withFileTypes: true });
17437
+ entries = await fs11.readdir(dir, { withFileTypes: true });
16698
17438
  } catch {
16699
17439
  return;
16700
17440
  }
16701
17441
  for (const entry of entries) {
16702
- const abs = path32.join(dir, entry.name);
17442
+ const abs = path33.join(dir, entry.name);
16703
17443
  if (entry.isDirectory()) {
16704
17444
  if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
16705
17445
  await walk(abs);
16706
17446
  } else if (entry.isFile()) {
16707
- const ext = path32.extname(entry.name).slice(1).toLowerCase();
17447
+ const ext = path33.extname(entry.name).slice(1).toLowerCase();
16708
17448
  if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
16709
- out.push(path32.relative(this.root, abs));
17449
+ out.push(path33.relative(this.root, abs));
16710
17450
  }
16711
17451
  }
16712
17452
  };
@@ -16736,10 +17476,10 @@ var WorkspaceIndex = class {
16736
17476
  const entries = [];
16737
17477
  for (const rel of rels) {
16738
17478
  try {
16739
- const abs = path32.join(this.root, rel);
16740
- const stat = await fs10.stat(abs);
17479
+ const abs = path33.join(this.root, rel);
17480
+ const stat = await fs11.stat(abs);
16741
17481
  if (stat.size > this.maxFileBytes) continue;
16742
- const buf = await fs10.readFile(abs);
17482
+ const buf = await fs11.readFile(abs);
16743
17483
  if (buf.includes(0)) continue;
16744
17484
  entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
16745
17485
  } catch {
@@ -17308,7 +18048,7 @@ var Cascade = class _Cascade extends EventEmitter {
17308
18048
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
17309
18049
  try {
17310
18050
  this.router.setDeadModelStore(new DeadModelStore(
17311
- fileDeadModelPersistence(path32.join(workspacePath, ".cascade", "dead-models.json"))
18051
+ fileDeadModelPersistence(path33.join(workspacePath, ".cascade", "dead-models.json"))
17312
18052
  ));
17313
18053
  } catch {
17314
18054
  }
@@ -17316,6 +18056,7 @@ var Cascade = class _Cascade extends EventEmitter {
17316
18056
  (this.config.providers ?? []).map((p) => p.type)
17317
18057
  );
17318
18058
  this.registerMediaTools(workspacePath);
18059
+ this.registerDocumentTools(workspacePath);
17319
18060
  this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
17320
18061
  this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
17321
18062
  this.router.setWorldStateDB(this.worldStateDB);
@@ -17395,8 +18136,8 @@ var Cascade = class _Cascade extends EventEmitter {
17395
18136
  this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
17396
18137
  return;
17397
18138
  }
17398
- const dbPath = ci.dbPath || path32.join(this.workspacePath, ".cascade", "code-index.db");
17399
- fs29.mkdirSync(path32.dirname(dbPath), { recursive: true });
18139
+ const dbPath = ci.dbPath || path33.join(this.workspacePath, ".cascade", "code-index.db");
18140
+ fs30.mkdirSync(path33.dirname(dbPath), { recursive: true });
17400
18141
  const db = new Database2(dbPath);
17401
18142
  db.pragma("journal_mode = WAL");
17402
18143
  const complete = chatCompleterFromProviders(this.config.providers);
@@ -17736,6 +18477,35 @@ ${input}` }],
17736
18477
  this.toolRegistry.register(tool);
17737
18478
  }
17738
18479
  }
18480
+ /**
18481
+ * Register `generate_document` where there is a real workspace to write into.
18482
+ *
18483
+ * A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
18484
+ * does exactly what it says and writes the model's text verbatim — produced
18485
+ * files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
18486
+ * the conversion (see tools/generate-document.ts for why it isn't folded into
18487
+ * file_write).
18488
+ *
18489
+ * Registered OUTSIDE `enabledTools` for the same reason the media tools are:
18490
+ * that allowlist is a blast-radius control for tools that reach the machine
18491
+ * or the network, and this one only writes the artifact the user asked for,
18492
+ * into the workspace the run already owns. `disabledTools` still removes it.
18493
+ * A host with no filesystem (the cloud) has its own browser-side exporter and
18494
+ * simply never reaches this path — `buildDocumentTools` returns nothing
18495
+ * without a file reader, mirroring `transcribe_audio`.
18496
+ */
18497
+ registerDocumentTools(workspacePath) {
18498
+ const denied = new Set(this.config.tools?.disabledTools ?? []);
18499
+ const tools = buildDocumentTools(async (p) => {
18500
+ const fsp = await import('fs/promises');
18501
+ return new Uint8Array(await fsp.readFile(p));
18502
+ });
18503
+ for (const tool of tools) {
18504
+ if (denied.has(tool.name)) continue;
18505
+ tool.setWorkspaceRoot(workspacePath);
18506
+ this.toolRegistry.register(tool);
18507
+ }
18508
+ }
17739
18509
  /**
17740
18510
  * Supply per-model thumbs-up/down counts so Auto routing can weigh how models
17741
18511
  * have actually performed for this user, on top of public benchmarks.
@@ -18648,14 +19418,14 @@ ${finalOutput}` : ""
18648
19418
  init_constants();
18649
19419
  var CLOUD_SESSION_FILE = "cloud-session.json";
18650
19420
  function globalDir(homeDir = os10.homedir()) {
18651
- return path32.join(homeDir, GLOBAL_CONFIG_DIR);
19421
+ return path33.join(homeDir, GLOBAL_CONFIG_DIR);
18652
19422
  }
18653
19423
  function cloudSessionPath(dir = globalDir()) {
18654
- return path32.join(dir, CLOUD_SESSION_FILE);
19424
+ return path33.join(dir, CLOUD_SESSION_FILE);
18655
19425
  }
18656
19426
  function loadCloudSession(dir = globalDir()) {
18657
19427
  try {
18658
- const raw = fs29.readFileSync(cloudSessionPath(dir), "utf-8");
19428
+ const raw = fs30.readFileSync(cloudSessionPath(dir), "utf-8");
18659
19429
  const parsed = JSON.parse(raw);
18660
19430
  if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
18661
19431
  return parsed;
@@ -18664,17 +19434,17 @@ function loadCloudSession(dir = globalDir()) {
18664
19434
  }
18665
19435
  }
18666
19436
  function saveCloudSession(session, dir = globalDir()) {
18667
- fs29.mkdirSync(dir, { recursive: true, mode: 448 });
19437
+ fs30.mkdirSync(dir, { recursive: true, mode: 448 });
18668
19438
  const file = cloudSessionPath(dir);
18669
- fs29.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
19439
+ fs30.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
18670
19440
  try {
18671
- fs29.chmodSync(file, 384);
19441
+ fs30.chmodSync(file, 384);
18672
19442
  } catch {
18673
19443
  }
18674
19444
  }
18675
19445
  function clearCloudSession(dir = globalDir()) {
18676
19446
  try {
18677
- fs29.rmSync(cloudSessionPath(dir));
19447
+ fs30.rmSync(cloudSessionPath(dir));
18678
19448
  } catch {
18679
19449
  }
18680
19450
  }
@@ -18846,16 +19616,16 @@ var CloudClient = class _CloudClient {
18846
19616
  return (await this.refresh()).accessToken;
18847
19617
  }
18848
19618
  /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
18849
- async authed(path37, init = {}) {
18850
- const call = (token) => fetch(this.url(path37), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
19619
+ async authed(path38, init = {}) {
19620
+ const call = (token) => fetch(this.url(path38), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
18851
19621
  let res = await call(await this.accessToken());
18852
19622
  if (res.status === 401) res = await call((await this.refresh()).accessToken);
18853
19623
  if (!res.ok) throw new Error(`Request failed (${res.status}).`);
18854
19624
  const text = await res.text();
18855
19625
  return text ? JSON.parse(text) : {};
18856
19626
  }
18857
- authedGet(path37) {
18858
- return this.authed(path37);
19627
+ authedGet(path38) {
19628
+ return this.authed(path38);
18859
19629
  }
18860
19630
  async fetchMe(token) {
18861
19631
  const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
@@ -20630,10 +21400,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
20630
21400
  }
20631
21401
  } catch {
20632
21402
  }
20633
- const configPath = path32.join(workspacePath, ".cascade", "config.json");
21403
+ const configPath = path33.join(workspacePath, ".cascade", "config.json");
20634
21404
  try {
20635
- await fs10.mkdir(path32.dirname(configPath), { recursive: true });
20636
- await fs10.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
21405
+ await fs11.mkdir(path33.dirname(configPath), { recursive: true });
21406
+ await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
20637
21407
  } catch (err) {
20638
21408
  const msg = err instanceof Error ? err.message : String(err);
20639
21409
  dispatch({
@@ -20684,9 +21454,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
20684
21454
  if (msg.includes("non-text parts")) return;
20685
21455
  originalLog(...args);
20686
21456
  };
20687
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
21457
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
20688
21458
  storeRef.current = store;
20689
- globalStoreRef.current = new MemoryStore(path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
21459
+ globalStoreRef.current = new MemoryStore(path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
20690
21460
  const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
20691
21461
  setIdentities(identityRows);
20692
21462
  let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
@@ -20754,14 +21524,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
20754
21524
  onThemeChange: (name) => setTheme(getTheme(name)),
20755
21525
  onExport: async (fmt) => {
20756
21526
  const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
20757
- const exportPath = path32.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
21527
+ const exportPath = path33.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
20758
21528
  if (fmt === "json") {
20759
- await fs10.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
21529
+ await fs11.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
20760
21530
  } else {
20761
21531
  const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
20762
21532
 
20763
21533
  ${msg.content}`).join("\n\n");
20764
- await fs10.writeFile(exportPath, markdown, "utf-8");
21534
+ await fs11.writeFile(exportPath, markdown, "utf-8");
20765
21535
  }
20766
21536
  },
20767
21537
  onRollback: async () => {
@@ -20771,7 +21541,7 @@ ${msg.content}`).join("\n\n");
20771
21541
  if (!snapshots.length) return "No file snapshots found for this session.";
20772
21542
  for (const { filePath, content } of snapshots) {
20773
21543
  try {
20774
- await fs10.writeFile(filePath, content, "utf-8");
21544
+ await fs11.writeFile(filePath, content, "utf-8");
20775
21545
  } catch (err) {
20776
21546
  console.error(`Restore failed: ${filePath}`, err);
20777
21547
  }
@@ -21652,9 +22422,9 @@ function stringifySlashOutput(val) {
21652
22422
  }
21653
22423
  async function searchSessionsAndMessages(query, workspacePath) {
21654
22424
  if (!query) return "Usage: /search <query>";
21655
- const dbPath = path32.join(workspacePath, CASCADE_DB_FILE);
22425
+ const dbPath = path33.join(workspacePath, CASCADE_DB_FILE);
21656
22426
  try {
21657
- await fs10.access(dbPath);
22427
+ await fs11.access(dbPath);
21658
22428
  } catch {
21659
22429
  return "No database found. Start a conversation first.";
21660
22430
  }
@@ -21686,7 +22456,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
21686
22456
  async function diagnoseRuntime(config, workspacePath) {
21687
22457
  const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
21688
22458
  const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
21689
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
22459
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
21690
22460
  try {
21691
22461
  const sessions2 = store.listSessions(void 0, 3);
21692
22462
  return [
@@ -21705,7 +22475,7 @@ async function diagnoseRuntime(config, workspacePath) {
21705
22475
  }
21706
22476
  async function showRecentLogs(args, workspacePath) {
21707
22477
  const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
21708
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
22478
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
21709
22479
  try {
21710
22480
  const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
21711
22481
  if (!logs.length) return "No recent runtime logs.";
@@ -21717,7 +22487,7 @@ async function showRecentLogs(args, workspacePath) {
21717
22487
  async function loadSessionSnapshot(args, workspacePath) {
21718
22488
  const sessionId = args[0];
21719
22489
  if (!sessionId) return "Usage: /resume <sessionId>";
21720
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
22490
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
21721
22491
  try {
21722
22492
  const session = store.getSession(sessionId);
21723
22493
  if (!session) return `Session not found: ${sessionId}`;
@@ -22145,10 +22915,10 @@ function SetupWizard({ workspacePath, onComplete }) {
22145
22915
  ...anyAuto ? { cascadeAuto: true } : {}
22146
22916
  };
22147
22917
  const config = CascadeConfigSchema.parse(rawConfig);
22148
- const configDir = path32.join(workspacePath, ".cascade");
22149
- await fs10.mkdir(configDir, { recursive: true });
22150
- const configPath = path32.join(workspacePath, CASCADE_CONFIG_FILE);
22151
- await fs10.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
22918
+ const configDir = path33.join(workspacePath, ".cascade");
22919
+ await fs11.mkdir(configDir, { recursive: true });
22920
+ const configPath = path33.join(workspacePath, CASCADE_CONFIG_FILE);
22921
+ await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
22152
22922
  savedConfigRef.current = config;
22153
22923
  dispatchRef.current({ type: "GO_DONE" });
22154
22924
  } catch (err) {
@@ -22489,14 +23259,14 @@ function printTelemetryBanner() {
22489
23259
  async function initCommand(workspacePath = process.cwd()) {
22490
23260
  const spin = ora({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
22491
23261
  try {
22492
- const configDir = path32.join(workspacePath, ".cascade");
22493
- await fs10.mkdir(configDir, { recursive: true });
22494
- const mdPath = path32.join(workspacePath, "CASCADE.md");
23262
+ const configDir = path33.join(workspacePath, ".cascade");
23263
+ await fs11.mkdir(configDir, { recursive: true });
23264
+ const mdPath = path33.join(workspacePath, "CASCADE.md");
22495
23265
  if (!await fileExists(mdPath)) {
22496
23266
  await createDefaultCascadeMd(workspacePath);
22497
23267
  spin.succeed(chalk7.green("Created CASCADE.md"));
22498
23268
  }
22499
- const ignorePath = path32.join(workspacePath, ".cascadeignore");
23269
+ const ignorePath = path33.join(workspacePath, ".cascadeignore");
22500
23270
  if (!await fileExists(ignorePath)) {
22501
23271
  await createDefaultIgnoreFile(workspacePath);
22502
23272
  spin.succeed(chalk7.green("Created .cascadeignore"));
@@ -22505,7 +23275,7 @@ async function initCommand(workspacePath = process.cwd()) {
22505
23275
  console.log();
22506
23276
  console.log(chalk7.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
22507
23277
  console.log();
22508
- const configPath = path32.join(workspacePath, CASCADE_CONFIG_FILE);
23278
+ const configPath = path33.join(workspacePath, CASCADE_CONFIG_FILE);
22509
23279
  if (await fileExists(configPath)) {
22510
23280
  console.log(chalk7.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
22511
23281
  console.log();
@@ -22524,7 +23294,7 @@ async function initCommand(workspacePath = process.cwd()) {
22524
23294
  }
22525
23295
  async function fileExists(p) {
22526
23296
  try {
22527
- await fs10.access(p);
23297
+ await fs11.access(p);
22528
23298
  return true;
22529
23299
  } catch {
22530
23300
  return false;
@@ -22536,7 +23306,7 @@ init_constants();
22536
23306
  var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
22537
23307
  async function readJson(file) {
22538
23308
  try {
22539
- const raw = await fs10.readFile(file, "utf-8");
23309
+ const raw = await fs11.readFile(file, "utf-8");
22540
23310
  return JSON.parse(raw);
22541
23311
  } catch {
22542
23312
  return null;
@@ -22563,7 +23333,7 @@ function fromEnv(env) {
22563
23333
  return out;
22564
23334
  }
22565
23335
  async function fromClaudeCode(home) {
22566
- const file = path32.join(home, ".claude", ".credentials.json");
23336
+ const file = path33.join(home, ".claude", ".credentials.json");
22567
23337
  const data = await readJson(file);
22568
23338
  if (!data) return [];
22569
23339
  const oauth = data["claudeAiOauth"];
@@ -22586,7 +23356,7 @@ async function fromClaudeCode(home) {
22586
23356
  return [];
22587
23357
  }
22588
23358
  async function fromCodex(home) {
22589
- const file = path32.join(home, ".codex", "auth.json");
23359
+ const file = path33.join(home, ".codex", "auth.json");
22590
23360
  const data = await readJson(file);
22591
23361
  if (!data) return [];
22592
23362
  const apiKey = str(data["OPENAI_API_KEY"]);
@@ -22609,7 +23379,7 @@ async function fromCodex(home) {
22609
23379
  return [];
22610
23380
  }
22611
23381
  async function fromGemini(home) {
22612
- const file = path32.join(home, ".gemini", "oauth_creds.json");
23382
+ const file = path33.join(home, ".gemini", "oauth_creds.json");
22613
23383
  const data = await readJson(file);
22614
23384
  const accessToken = str(data?.["access_token"]);
22615
23385
  if (!accessToken) return [];
@@ -22625,7 +23395,7 @@ async function fromGemini(home) {
22625
23395
  }
22626
23396
  async function fromCopilot(home) {
22627
23397
  for (const name of ["apps.json", "hosts.json"]) {
22628
- const file = path32.join(home, ".config", "github-copilot", name);
23398
+ const file = path33.join(home, ".config", "github-copilot", name);
22629
23399
  const data = await readJson(file);
22630
23400
  if (!data) continue;
22631
23401
  for (const value of Object.values(data)) {
@@ -22679,7 +23449,7 @@ async function doctorCommand() {
22679
23449
  checks.push({
22680
23450
  label: "Cascade config",
22681
23451
  ok: true,
22682
- detail: `Loaded ${path32.join(process.cwd(), CASCADE_CONFIG_FILE)}`
23452
+ detail: `Loaded ${path33.join(process.cwd(), CASCADE_CONFIG_FILE)}`
22683
23453
  });
22684
23454
  const providers = [
22685
23455
  { type: "anthropic", name: "Anthropic" },
@@ -22790,7 +23560,7 @@ async function checkEndpoint(url) {
22790
23560
  }
22791
23561
  }
22792
23562
  async function indexCommand(dirPath) {
22793
- const workspace = path32.resolve(dirPath || process.cwd());
23563
+ const workspace = path33.resolve(dirPath || process.cwd());
22794
23564
  const cm = new ConfigManager(workspace);
22795
23565
  await cm.load();
22796
23566
  const config = cm.getConfig();
@@ -22801,8 +23571,8 @@ async function indexCommand(dirPath) {
22801
23571
  process.exitCode = 1;
22802
23572
  return;
22803
23573
  }
22804
- const dbPath = config.codeIndex?.dbPath || path32.join(workspace, ".cascade", "code-index.db");
22805
- fs29.mkdirSync(path32.dirname(dbPath), { recursive: true });
23574
+ const dbPath = config.codeIndex?.dbPath || path33.join(workspace, ".cascade", "code-index.db");
23575
+ fs30.mkdirSync(path33.dirname(dbPath), { recursive: true });
22806
23576
  const db = new Database2(dbPath);
22807
23577
  db.pragma("journal_mode = WAL");
22808
23578
  const complete = chatCompleterFromProviders(config.providers);
@@ -23291,7 +24061,7 @@ function openBrowser2(url) {
23291
24061
  }
23292
24062
  function storePathFor(name) {
23293
24063
  const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
23294
- return path32.join(os10.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
24064
+ return path33.join(os10.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
23295
24065
  }
23296
24066
  function defaultName(url) {
23297
24067
  try {
@@ -23793,7 +24563,7 @@ function aggregateCostStats(sessions2, opts = {}) {
23793
24563
  }
23794
24564
 
23795
24565
  // src/dashboard/server.ts
23796
- var __dirname$1 = path32.dirname(fileURLToPath(import.meta.url));
24566
+ var __dirname$1 = path33.dirname(fileURLToPath(import.meta.url));
23797
24567
  var ORPHANED_ESCALATION_GRACE_MS = 3e4;
23798
24568
  var DashboardServer = class {
23799
24569
  app;
@@ -24073,14 +24843,14 @@ var DashboardServer = class {
24073
24843
  */
24074
24844
  persistConfig() {
24075
24845
  try {
24076
- const configPath = path32.join(this.workspacePath, CASCADE_CONFIG_FILE);
24077
- fs29.mkdirSync(path32.dirname(configPath), { recursive: true });
24078
- fs29.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
24846
+ const configPath = path33.join(this.workspacePath, CASCADE_CONFIG_FILE);
24847
+ fs30.mkdirSync(path33.dirname(configPath), { recursive: true });
24848
+ fs30.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
24079
24849
  } catch (err) {
24080
24850
  console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
24081
24851
  }
24082
24852
  try {
24083
- saveGlobalCredentials(path32.join(os10.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
24853
+ saveGlobalCredentials(path33.join(os10.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
24084
24854
  } catch (err) {
24085
24855
  console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
24086
24856
  }
@@ -24095,15 +24865,15 @@ var DashboardServer = class {
24095
24865
  resolveDashboardSecret() {
24096
24866
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
24097
24867
  if (fromConfig) return fromConfig;
24098
- const secretPath = path32.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
24868
+ const secretPath = path33.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
24099
24869
  try {
24100
- if (fs29.existsSync(secretPath)) {
24101
- const existing = fs29.readFileSync(secretPath, "utf-8").trim();
24870
+ if (fs30.existsSync(secretPath)) {
24871
+ const existing = fs30.readFileSync(secretPath, "utf-8").trim();
24102
24872
  if (existing.length >= 16) return existing;
24103
24873
  }
24104
24874
  const generated = randomUUID();
24105
- fs29.mkdirSync(path32.dirname(secretPath), { recursive: true });
24106
- fs29.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
24875
+ fs30.mkdirSync(path33.dirname(secretPath), { recursive: true });
24876
+ fs30.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
24107
24877
  if (this.config.dashboard.auth) {
24108
24878
  console.warn(
24109
24879
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -24130,7 +24900,7 @@ var DashboardServer = class {
24130
24900
  // ── Setup ─────────────────────────────────────
24131
24901
  getGlobalStore() {
24132
24902
  if (!this.globalStore) {
24133
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24903
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24134
24904
  this.globalStore = new MemoryStore(globalDbPath);
24135
24905
  }
24136
24906
  return this.globalStore;
@@ -24510,12 +25280,12 @@ ${prompt}`;
24510
25280
  }
24511
25281
  }
24512
25282
  watchRuntimeChanges() {
24513
- const workspaceDbPath = path32.join(this.workspacePath, CASCADE_DB_FILE);
24514
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25283
+ const workspaceDbPath = path33.join(this.workspacePath, CASCADE_DB_FILE);
25284
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24515
25285
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
24516
25286
  for (const watchPath of watchPaths) {
24517
- if (!fs29.existsSync(watchPath)) continue;
24518
- fs29.watchFile(watchPath, { interval: 3e3 }, () => {
25287
+ if (!fs30.existsSync(watchPath)) continue;
25288
+ fs30.watchFile(watchPath, { interval: 3e3 }, () => {
24519
25289
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
24520
25290
  });
24521
25291
  }
@@ -24637,7 +25407,7 @@ ${prompt}`;
24637
25407
  const sessionId = req.params.id;
24638
25408
  this.store.deleteSession(sessionId);
24639
25409
  this.store.deleteRuntimeSession(sessionId);
24640
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25410
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24641
25411
  const globalStore = new MemoryStore(globalDbPath);
24642
25412
  try {
24643
25413
  globalStore.deleteRuntimeSession(sessionId);
@@ -24730,7 +25500,7 @@ ${prompt}`;
24730
25500
  });
24731
25501
  this.app.delete("/api/sessions", auth2, (req, res) => {
24732
25502
  const body = req.body;
24733
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25503
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24734
25504
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
24735
25505
  const globalStore = new MemoryStore(globalDbPath);
24736
25506
  try {
@@ -24753,7 +25523,7 @@ ${prompt}`;
24753
25523
  });
24754
25524
  this.app.delete("/api/runtime", auth2, (_req, res) => {
24755
25525
  this.store.deleteAllRuntimeNodes();
24756
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25526
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24757
25527
  const globalStore = new MemoryStore(globalDbPath);
24758
25528
  try {
24759
25529
  globalStore.deleteAllRuntimeNodes();
@@ -24839,12 +25609,12 @@ ${prompt}`;
24839
25609
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
24840
25610
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
24841
25611
  try {
24842
- const configPath = path32.join(this.workspacePath, CASCADE_CONFIG_FILE);
24843
- const existing = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf-8")) : {};
25612
+ const configPath = path33.join(this.workspacePath, CASCADE_CONFIG_FILE);
25613
+ const existing = fs30.existsSync(configPath) ? JSON.parse(fs30.readFileSync(configPath, "utf-8")) : {};
24844
25614
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
24845
25615
  const tmp = configPath + ".tmp";
24846
- fs29.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
24847
- fs29.renameSync(tmp, configPath);
25616
+ fs30.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
25617
+ fs30.renameSync(tmp, configPath);
24848
25618
  res.json({ ok: true });
24849
25619
  } catch (err) {
24850
25620
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -24872,7 +25642,7 @@ ${prompt}`;
24872
25642
  this.app.get("/api/runtime", auth2, (req, res) => {
24873
25643
  const scope = req.query["scope"] ?? "workspace";
24874
25644
  if (scope === "global") {
24875
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25645
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24876
25646
  const globalStore = new MemoryStore(globalDbPath);
24877
25647
  try {
24878
25648
  res.json({
@@ -25213,13 +25983,13 @@ ${prompt}`;
25213
25983
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
25214
25984
  }
25215
25985
  });
25216
- const prodPath = path32.resolve(__dirname$1, "../web/dist");
25217
- const devPath = path32.resolve(__dirname$1, "../../web/dist");
25218
- const webDistPath = fs29.existsSync(prodPath) ? prodPath : devPath;
25219
- if (fs29.existsSync(webDistPath)) {
25986
+ const prodPath = path33.resolve(__dirname$1, "../web/dist");
25987
+ const devPath = path33.resolve(__dirname$1, "../../web/dist");
25988
+ const webDistPath = fs30.existsSync(prodPath) ? prodPath : devPath;
25989
+ if (fs30.existsSync(webDistPath)) {
25220
25990
  this.app.use(express.static(webDistPath));
25221
25991
  this.app.get("*", (_req, res) => {
25222
- res.sendFile(path32.join(webDistPath, "index.html"));
25992
+ res.sendFile(path33.join(webDistPath, "index.html"));
25223
25993
  });
25224
25994
  } else {
25225
25995
  this.app.get("/", (_req, res) => {
@@ -25240,7 +26010,7 @@ init_constants();
25240
26010
  async function dashboardCommand(config, workspacePath = process.cwd()) {
25241
26011
  const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
25242
26012
  const spin = ora({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
25243
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
26013
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
25244
26014
  const server = new DashboardServer(config, store, workspacePath);
25245
26015
  server.watchRuntimeChanges();
25246
26016
  const gThis = globalThis;
@@ -25272,7 +26042,7 @@ init_constants();
25272
26042
  function makeIdentityCommand(workspacePath = process.cwd()) {
25273
26043
  const identity = new Command("identity").alias("id").description("Manage Cascade identities");
25274
26044
  identity.command("list").description("List all available identities").action(() => {
25275
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
26045
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
25276
26046
  try {
25277
26047
  const identities = store.listIdentities();
25278
26048
  console.log(chalk7.bold("\n Identities:"));
@@ -25292,7 +26062,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
25292
26062
  }
25293
26063
  });
25294
26064
  identity.command("create <name>").description("Create a new identity").option("-d, --desc <text>", "Description of the identity").option("-s, --system <text>", "System prompt").option("--default", "Set as default identity").action((name, options) => {
25295
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
26065
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
25296
26066
  try {
25297
26067
  if (options.default) {
25298
26068
  const existingDefault = store.getDefaultIdentity();
@@ -25318,7 +26088,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
25318
26088
  }
25319
26089
  });
25320
26090
  identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
25321
- const store = new MemoryStore(path32.join(workspacePath, CASCADE_DB_FILE));
26091
+ const store = new MemoryStore(path33.join(workspacePath, CASCADE_DB_FILE));
25322
26092
  try {
25323
26093
  const identities = store.listIdentities();
25324
26094
  const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
@@ -25467,11 +26237,11 @@ async function exportCommand(options = {}) {
25467
26237
  let store;
25468
26238
  try {
25469
26239
  const workspacePath = options.workspacePath ?? process.cwd();
25470
- const workspaceDbPath = path32.join(workspacePath, CASCADE_DB_FILE);
25471
- const globalDbPath = path32.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
26240
+ const workspaceDbPath = path33.join(workspacePath, CASCADE_DB_FILE);
26241
+ const globalDbPath = path33.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
25472
26242
  let dbPath = globalDbPath;
25473
26243
  try {
25474
- await fs10.access(workspaceDbPath);
26244
+ await fs11.access(workspaceDbPath);
25475
26245
  dbPath = workspaceDbPath;
25476
26246
  } catch {
25477
26247
  }
@@ -25510,8 +26280,8 @@ async function exportCommand(options = {}) {
25510
26280
  const ext = format === "json" ? ".json" : ".md";
25511
26281
  const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
25512
26282
  const defaultFile = `cascade-export-${safeName}${ext}`;
25513
- const outPath = options.output ? path32.resolve(options.output) : path32.join(process.cwd(), defaultFile);
25514
- await fs10.writeFile(outPath, content, "utf-8");
26283
+ const outPath = options.output ? path33.resolve(options.output) : path33.join(process.cwd(), defaultFile);
26284
+ await fs11.writeFile(outPath, content, "utf-8");
25515
26285
  spin.succeed(chalk7.green(`Exported to ${chalk7.white(outPath)}`));
25516
26286
  const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
25517
26287
  console.log();
@@ -25738,11 +26508,11 @@ async function statsCommand() {
25738
26508
  dotenv.config();
25739
26509
  function warnIfBuildIsStale() {
25740
26510
  try {
25741
- const here = path32.dirname(fileURLToPath(import.meta.url));
26511
+ const here = path33.dirname(fileURLToPath(import.meta.url));
25742
26512
  for (const rel of ["..", "../.."]) {
25743
- const pkgPath = path32.join(here, rel, "package.json");
25744
- if (!fs29.existsSync(pkgPath)) continue;
25745
- const pkg = JSON.parse(fs29.readFileSync(pkgPath, "utf-8"));
26513
+ const pkgPath = path33.join(here, rel, "package.json");
26514
+ if (!fs30.existsSync(pkgPath)) continue;
26515
+ const pkg = JSON.parse(fs30.readFileSync(pkgPath, "utf-8"));
25746
26516
  if (pkg.name !== "cascade-ai") continue;
25747
26517
  if (pkg.version && pkg.version !== CASCADE_VERSION) {
25748
26518
  console.error(