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.cjs CHANGED
@@ -9,8 +9,8 @@ var https = require('https');
9
9
  var zlib = require('zlib');
10
10
  var stream = require('stream');
11
11
  var Database2 = require('better-sqlite3');
12
- var path32 = require('path');
13
- var fs29 = require('fs');
12
+ var path33 = require('path');
13
+ var fs30 = require('fs');
14
14
  var os10 = require('os');
15
15
  var crypto = require('crypto');
16
16
  var ink = require('ink');
@@ -19,7 +19,7 @@ var React2 = require('react');
19
19
  var chalk7 = require('chalk');
20
20
  var dotenv = require('dotenv');
21
21
  var url = require('url');
22
- var fs10 = require('fs/promises');
22
+ var fs11 = require('fs/promises');
23
23
  var _ignoreModule = require('ignore');
24
24
  var zod = require('zod');
25
25
  var child_process = require('child_process');
@@ -77,14 +77,14 @@ var http__default = /*#__PURE__*/_interopDefault(http);
77
77
  var https__default = /*#__PURE__*/_interopDefault(https);
78
78
  var zlib__default = /*#__PURE__*/_interopDefault(zlib);
79
79
  var Database2__default = /*#__PURE__*/_interopDefault(Database2);
80
- var path32__default = /*#__PURE__*/_interopDefault(path32);
81
- var fs29__default = /*#__PURE__*/_interopDefault(fs29);
80
+ var path33__default = /*#__PURE__*/_interopDefault(path33);
81
+ var fs30__default = /*#__PURE__*/_interopDefault(fs30);
82
82
  var os10__default = /*#__PURE__*/_interopDefault(os10);
83
83
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
84
84
  var React2__default = /*#__PURE__*/_interopDefault(React2);
85
85
  var chalk7__default = /*#__PURE__*/_interopDefault(chalk7);
86
86
  var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
87
- var fs10__default = /*#__PURE__*/_interopDefault(fs10);
87
+ var fs11__default = /*#__PURE__*/_interopDefault(fs11);
88
88
  var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
89
89
  var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
90
90
  var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
@@ -1810,7 +1810,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
1810
1810
  var init_constants = __esm({
1811
1811
  "src/constants.ts"() {
1812
1812
  init_pricing();
1813
- CASCADE_VERSION = "0.66.2";
1813
+ CASCADE_VERSION = "0.67.0";
1814
1814
  CASCADE_CONFIG_FILE = ".cascade/config.json";
1815
1815
  CASCADE_DB_FILE = ".cascade/memory.db";
1816
1816
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -3932,12 +3932,12 @@ var init_audit_logger = __esm({
3932
3932
  constructor(workspacePath, debugMode = false) {
3933
3933
  this.workspacePath = workspacePath;
3934
3934
  this.debugMode = debugMode;
3935
- const cascadeDir = path32__default.default.join(workspacePath, ".cascade");
3936
- if (!fs29__default.default.existsSync(cascadeDir)) {
3937
- fs29__default.default.mkdirSync(cascadeDir, { recursive: true });
3935
+ const cascadeDir = path33__default.default.join(workspacePath, ".cascade");
3936
+ if (!fs30__default.default.existsSync(cascadeDir)) {
3937
+ fs30__default.default.mkdirSync(cascadeDir, { recursive: true });
3938
3938
  }
3939
- this.keyPath = path32__default.default.join(cascadeDir, "audit_log.key");
3940
- this.dbPath = path32__default.default.join(cascadeDir, "audit_log.db");
3939
+ this.keyPath = path33__default.default.join(cascadeDir, "audit_log.key");
3940
+ this.dbPath = path33__default.default.join(cascadeDir, "audit_log.db");
3941
3941
  this.initEncryptionKey();
3942
3942
  this.db = new Database2__default.default(this.dbPath);
3943
3943
  this.db.pragma("journal_mode = WAL");
@@ -3967,11 +3967,11 @@ var init_audit_logger = __esm({
3967
3967
  return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
3968
3968
  }
3969
3969
  initEncryptionKey() {
3970
- if (fs29__default.default.existsSync(this.keyPath)) {
3971
- this.encryptionKey = fs29__default.default.readFileSync(this.keyPath);
3970
+ if (fs30__default.default.existsSync(this.keyPath)) {
3971
+ this.encryptionKey = fs30__default.default.readFileSync(this.keyPath);
3972
3972
  } else {
3973
3973
  this.encryptionKey = crypto__default.default.randomBytes(32);
3974
- fs29__default.default.writeFileSync(this.keyPath, this.encryptionKey);
3974
+ fs30__default.default.writeFileSync(this.keyPath, this.encryptionKey);
3975
3975
  }
3976
3976
  }
3977
3977
  encrypt(text) {
@@ -4049,9 +4049,9 @@ var init_audit_logger = __esm({
4049
4049
  dumpDebugIfNeeded() {
4050
4050
  if (!this.debugMode) return;
4051
4051
  try {
4052
- const dumpPath = path32__default.default.join(os10__default.default.tmpdir(), "cascade_audit_logs_debug.json");
4052
+ const dumpPath = path33__default.default.join(os10__default.default.tmpdir(), "cascade_audit_logs_debug.json");
4053
4053
  const entries = this.getAllLogs();
4054
- fs29__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
4054
+ fs30__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
4055
4055
  } catch (err) {
4056
4056
  console.error("Failed to dump debug audit logs", err);
4057
4057
  }
@@ -4106,7 +4106,7 @@ var Keystore = class {
4106
4106
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
4107
4107
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
4108
4108
  this.backend = "keytar";
4109
- if (password && fs29__default.default.existsSync(this.storePath)) {
4109
+ if (password && fs30__default.default.existsSync(this.storePath)) {
4110
4110
  try {
4111
4111
  const fileEntries = this.decryptFile(password);
4112
4112
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -4125,7 +4125,7 @@ var Keystore = class {
4125
4125
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
4126
4126
  );
4127
4127
  }
4128
- if (!fs29__default.default.existsSync(this.storePath)) {
4128
+ if (!fs30__default.default.existsSync(this.storePath)) {
4129
4129
  const salt = crypto__default.default.randomBytes(SALT_LEN);
4130
4130
  this.masterKey = this.deriveKey(password, salt);
4131
4131
  this.writeWithSalt({}, salt);
@@ -4139,7 +4139,7 @@ var Keystore = class {
4139
4139
  }
4140
4140
  /** Synchronous legacy unlock kept for AES-only environments. */
4141
4141
  unlockSync(password) {
4142
- if (!fs29__default.default.existsSync(this.storePath)) {
4142
+ if (!fs30__default.default.existsSync(this.storePath)) {
4143
4143
  const salt = crypto__default.default.randomBytes(SALT_LEN);
4144
4144
  this.masterKey = this.deriveKey(password, salt);
4145
4145
  this.writeWithSalt({}, salt);
@@ -4197,7 +4197,7 @@ var Keystore = class {
4197
4197
  }
4198
4198
  }
4199
4199
  decryptFile(password, knownSalt) {
4200
- if (!fs29__default.default.existsSync(this.storePath)) return {};
4200
+ if (!fs30__default.default.existsSync(this.storePath)) return {};
4201
4201
  try {
4202
4202
  const { salt, ciphertext, iv, tag } = this.readRaw();
4203
4203
  const useSalt = knownSalt ?? salt;
@@ -4219,8 +4219,8 @@ var Keystore = class {
4219
4219
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
4220
4220
  const tag = cipher.getAuthTag();
4221
4221
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
4222
- fs29__default.default.mkdirSync(path32__default.default.dirname(this.storePath), { recursive: true });
4223
- fs29__default.default.writeFileSync(this.storePath, out, { mode: 384 });
4222
+ fs30__default.default.mkdirSync(path33__default.default.dirname(this.storePath), { recursive: true });
4223
+ fs30__default.default.writeFileSync(this.storePath, out, { mode: 384 });
4224
4224
  }
4225
4225
  writeWithSalt(data, salt) {
4226
4226
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -4230,11 +4230,11 @@ var Keystore = class {
4230
4230
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
4231
4231
  const tag = cipher.getAuthTag();
4232
4232
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
4233
- fs29__default.default.mkdirSync(path32__default.default.dirname(this.storePath), { recursive: true });
4234
- fs29__default.default.writeFileSync(this.storePath, out, { mode: 384 });
4233
+ fs30__default.default.mkdirSync(path33__default.default.dirname(this.storePath), { recursive: true });
4234
+ fs30__default.default.writeFileSync(this.storePath, out, { mode: 384 });
4235
4235
  }
4236
4236
  readRaw() {
4237
- const buf = fs29__default.default.readFileSync(this.storePath);
4237
+ const buf = fs30__default.default.readFileSync(this.storePath);
4238
4238
  let offset = 0;
4239
4239
  const salt = buf.subarray(offset, offset + SALT_LEN);
4240
4240
  offset += SALT_LEN;
@@ -4267,9 +4267,9 @@ var CascadeIgnore = class {
4267
4267
  ]);
4268
4268
  }
4269
4269
  async load(workspacePath) {
4270
- const filePath = path32__default.default.join(workspacePath, ".cascadeignore");
4270
+ const filePath = path33__default.default.join(workspacePath, ".cascadeignore");
4271
4271
  try {
4272
- const content = await fs10__default.default.readFile(filePath, "utf-8");
4272
+ const content = await fs11__default.default.readFile(filePath, "utf-8");
4273
4273
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
4274
4274
  this.ig.add(lines);
4275
4275
  this.loaded = true;
@@ -4278,7 +4278,7 @@ var CascadeIgnore = class {
4278
4278
  }
4279
4279
  isIgnored(filePath, workspacePath) {
4280
4280
  try {
4281
- const relative = workspacePath ? path32__default.default.relative(workspacePath, filePath) : filePath;
4281
+ const relative = workspacePath ? path33__default.default.relative(workspacePath, filePath) : filePath;
4282
4282
  return this.ig.ignores(relative);
4283
4283
  } catch {
4284
4284
  return false;
@@ -4289,7 +4289,7 @@ var CascadeIgnore = class {
4289
4289
  }
4290
4290
  };
4291
4291
  async function createDefaultIgnoreFile(workspacePath) {
4292
- const filePath = path32__default.default.join(workspacePath, ".cascadeignore");
4292
+ const filePath = path33__default.default.join(workspacePath, ".cascadeignore");
4293
4293
  const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
4294
4294
  # Syntax identical to .gitignore
4295
4295
 
@@ -4316,12 +4316,12 @@ build/
4316
4316
  .DS_Store
4317
4317
  Thumbs.db
4318
4318
  `;
4319
- await fs10__default.default.writeFile(filePath, content, "utf-8");
4319
+ await fs11__default.default.writeFile(filePath, content, "utf-8");
4320
4320
  }
4321
4321
  async function loadCascadeMd(workspacePath) {
4322
- const filePath = path32__default.default.join(workspacePath, "CASCADE.md");
4322
+ const filePath = path33__default.default.join(workspacePath, "CASCADE.md");
4323
4323
  try {
4324
- const raw = await fs10__default.default.readFile(filePath, "utf-8");
4324
+ const raw = await fs11__default.default.readFile(filePath, "utf-8");
4325
4325
  return parseCascadeMd(raw);
4326
4326
  } catch {
4327
4327
  return null;
@@ -4348,7 +4348,7 @@ ${raw.trim()}`;
4348
4348
  return { raw, sections, systemPrompt };
4349
4349
  }
4350
4350
  async function createDefaultCascadeMd(workspacePath) {
4351
- const filePath = path32__default.default.join(workspacePath, "CASCADE.md");
4351
+ const filePath = path33__default.default.join(workspacePath, "CASCADE.md");
4352
4352
  const content = `# Cascade Project Instructions
4353
4353
 
4354
4354
  This file contains project-specific instructions for the Cascade AI agent.
@@ -4378,12 +4378,12 @@ All tools are allowed unless specified otherwise.
4378
4378
 
4379
4379
  List any areas the agent should not touch.
4380
4380
  `;
4381
- await fs10__default.default.writeFile(filePath, content, "utf-8");
4381
+ await fs11__default.default.writeFile(filePath, content, "utf-8");
4382
4382
  }
4383
4383
  var MemoryStore = class _MemoryStore {
4384
4384
  db;
4385
4385
  constructor(dbPath) {
4386
- fs29__default.default.mkdirSync(path32__default.default.dirname(dbPath), { recursive: true });
4386
+ fs30__default.default.mkdirSync(path33__default.default.dirname(dbPath), { recursive: true });
4387
4387
  try {
4388
4388
  this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
4389
4389
  this.db.pragma("journal_mode = WAL");
@@ -5635,7 +5635,7 @@ function validateConfig(raw) {
5635
5635
  // src/config/global-credentials.ts
5636
5636
  init_constants();
5637
5637
  function credentialsPath(globalDir2) {
5638
- return path32__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
5638
+ return path33__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
5639
5639
  }
5640
5640
  function providerKey(p) {
5641
5641
  if (p.type === "azure") {
@@ -5648,7 +5648,7 @@ function isPersistable(p) {
5648
5648
  }
5649
5649
  function loadGlobalCredentials(globalDir2) {
5650
5650
  try {
5651
- const raw = fs29__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
5651
+ const raw = fs30__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
5652
5652
  const parsed = JSON.parse(raw);
5653
5653
  if (!Array.isArray(parsed.providers)) return [];
5654
5654
  return parsed.providers.filter(
@@ -5660,13 +5660,13 @@ function loadGlobalCredentials(globalDir2) {
5660
5660
  }
5661
5661
  function saveGlobalCredentials(globalDir2, providers) {
5662
5662
  const filePath = credentialsPath(globalDir2);
5663
- fs29__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
5663
+ fs30__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
5664
5664
  const body = { version: 1, providers: providers.filter(isPersistable) };
5665
5665
  const tmp = `${filePath}.tmp`;
5666
- fs29__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
5667
- fs29__default.default.renameSync(tmp, filePath);
5666
+ fs30__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
5667
+ fs30__default.default.renameSync(tmp, filePath);
5668
5668
  try {
5669
- fs29__default.default.chmodSync(filePath, 384);
5669
+ fs30__default.default.chmodSync(filePath, 384);
5670
5670
  } catch {
5671
5671
  }
5672
5672
  }
@@ -5884,7 +5884,7 @@ var ConfigManager = class {
5884
5884
  /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
5885
5885
  constructor(workspacePath = process.cwd(), globalDirOverride) {
5886
5886
  this.workspacePath = workspacePath;
5887
- this.globalDir = globalDirOverride ?? path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR);
5887
+ this.globalDir = globalDirOverride ?? path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR);
5888
5888
  }
5889
5889
  async load() {
5890
5890
  this.config = await this.loadConfig();
@@ -5898,8 +5898,8 @@ var ConfigManager = class {
5898
5898
  this.ignore = new CascadeIgnore();
5899
5899
  await this.ignore.load(this.workspacePath);
5900
5900
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
5901
- this.keystore = new Keystore(path32__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
5902
- this.store = new MemoryStore(path32__default.default.join(this.workspacePath, CASCADE_DB_FILE));
5901
+ this.keystore = new Keystore(path33__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
5902
+ this.store = new MemoryStore(path33__default.default.join(this.workspacePath, CASCADE_DB_FILE));
5903
5903
  await this.injectEnvKeys();
5904
5904
  this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
5905
5905
  await this.ensureDefaultIdentity();
@@ -5924,9 +5924,9 @@ var ConfigManager = class {
5924
5924
  return this.workspacePath;
5925
5925
  }
5926
5926
  async save() {
5927
- const configPath = path32__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
5928
- await fs10__default.default.mkdir(path32__default.default.dirname(configPath), { recursive: true });
5929
- await fs10__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
5927
+ const configPath = path33__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
5928
+ await fs11__default.default.mkdir(path33__default.default.dirname(configPath), { recursive: true });
5929
+ await fs11__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
5930
5930
  try {
5931
5931
  saveGlobalCredentials(this.globalDir, this.config.providers);
5932
5932
  } catch (err) {
@@ -5961,9 +5961,9 @@ var ConfigManager = class {
5961
5961
  return configProvider?.apiKey;
5962
5962
  }
5963
5963
  async loadConfig() {
5964
- const configPath = path32__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
5964
+ const configPath = path33__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
5965
5965
  try {
5966
- const raw = await fs10__default.default.readFile(configPath, "utf-8");
5966
+ const raw = await fs11__default.default.readFile(configPath, "utf-8");
5967
5967
  return validateConfig(JSON.parse(raw));
5968
5968
  } catch (err) {
5969
5969
  if (err.code === "ENOENT") {
@@ -6963,13 +6963,13 @@ var DeadModelStore = class {
6963
6963
  function fileDeadModelPersistence(filePath) {
6964
6964
  return {
6965
6965
  load() {
6966
- if (!fs29.existsSync(filePath)) return [];
6967
- const raw = JSON.parse(fs29.readFileSync(filePath, "utf-8"));
6966
+ if (!fs30.existsSync(filePath)) return [];
6967
+ const raw = JSON.parse(fs30.readFileSync(filePath, "utf-8"));
6968
6968
  return Array.isArray(raw) ? raw : [];
6969
6969
  },
6970
6970
  save(records) {
6971
- fs29.mkdirSync(path32.dirname(filePath), { recursive: true });
6972
- fs29.writeFileSync(filePath, JSON.stringify(records, null, 2));
6971
+ fs30.mkdirSync(path33.dirname(filePath), { recursive: true });
6972
+ fs30.writeFileSync(filePath, JSON.stringify(records, null, 2));
6973
6973
  }
6974
6974
  };
6975
6975
  }
@@ -7128,7 +7128,7 @@ init_pricing();
7128
7128
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
7129
7129
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
7130
7130
  var FETCH_TIMEOUT_MS = 8e3;
7131
- var DEFAULT_CACHE_FILE = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
7131
+ var DEFAULT_CACHE_FILE = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
7132
7132
  function normalizeModelId2(id) {
7133
7133
  let s = id.toLowerCase();
7134
7134
  const slash = s.lastIndexOf("/");
@@ -7163,7 +7163,7 @@ var LiveDataProvider = class {
7163
7163
  if (this.loaded) return;
7164
7164
  this.loaded = true;
7165
7165
  try {
7166
- const raw = await fs10__default.default.readFile(this.opts.cacheFile, "utf-8");
7166
+ const raw = await fs11__default.default.readFile(this.opts.cacheFile, "utf-8");
7167
7167
  const cache = JSON.parse(raw);
7168
7168
  if (cache.snapshot?.families) {
7169
7169
  this.snapshot = cache.snapshot;
@@ -7275,14 +7275,14 @@ var LiveDataProvider = class {
7275
7275
  }
7276
7276
  async saveCache() {
7277
7277
  try {
7278
- await fs10__default.default.mkdir(path32__default.default.dirname(this.opts.cacheFile), { recursive: true });
7278
+ await fs11__default.default.mkdir(path33__default.default.dirname(this.opts.cacheFile), { recursive: true });
7279
7279
  const cache = {
7280
7280
  fetchedAt: this.fetchedAt,
7281
7281
  snapshot: this.snapshot ?? void 0,
7282
7282
  prices: Object.fromEntries(this.prices),
7283
7283
  capabilities: Object.fromEntries(this.capabilities)
7284
7284
  };
7285
- await fs10__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
7285
+ await fs11__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
7286
7286
  } catch {
7287
7287
  }
7288
7288
  }
@@ -9110,7 +9110,8 @@ var KNOWN_TOOLS = [
9110
9110
  "generate_image",
9111
9111
  "generate_video",
9112
9112
  "generate_speech",
9113
- "transcribe_audio"
9113
+ "transcribe_audio",
9114
+ "generate_document"
9114
9115
  ];
9115
9116
  var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
9116
9117
  "generate_image",
@@ -9127,18 +9128,45 @@ function buildWorkerRules(has) {
9127
9128
  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.",
9128
9129
  has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
9129
9130
  has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
9131
+ // A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
9132
+ // file_write does exactly what it promises — writes the model's characters
9133
+ // verbatim — so without this rule the worker "wrote" a Word document that
9134
+ // Word correctly reported as corrupted. Stated as an explicit prohibition
9135
+ // on the tool it would otherwise reach for, because "use generate_document"
9136
+ // alone competes with a file_write habit the model already has.
9137
+ 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.',
9138
+ // The alternative to this rule is not "a worse chart" — it is a paragraph
9139
+ // describing a chart that does not exist, which is what real decks came
9140
+ // back with. A `chart:` block is checkable, carries the exact numbers, and
9141
+ // becomes a genuine editable PowerPoint chart object.
9142
+ 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.',
9130
9143
  // Without this the model "writes the image" as prose — a bracketed
9131
9144
  // description sitting in the deck where the picture should be — even with a
9132
9145
  // working image model registered. Naming the reference syntax matters as
9133
9146
  // much as naming the tool: a generated image nobody embeds is still a
9134
- // missing image.
9147
+ // missing image. It also has to say the reference must stand ALONE on its
9148
+ // line, because only a standalone reference is parsed as a picture (see
9149
+ // core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
9150
+ // silently degrades to caption text.
9135
9151
  //
9136
- // Scoped to PowerPoint/Word specifically: those are the only exporters
9137
- // that actually embed a Markdown image reference (cloud/web/exporters.ts
9138
- // toPptx/toDocx). PDF and plain-text/Markdown deliverables flatten a
9139
- // reference straight to caption text — telling the model to call
9140
- // generate_image for THOSE just pays for an image nobody ever sees.
9141
- 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.',
9152
+ // Scoped to PowerPoint/Word specifically: those are the only renderers that
9153
+ // actually embed a Markdown image reference. PDF and plain-text/Markdown
9154
+ // deliverables flatten a reference straight to caption text — telling the
9155
+ // model to call generate_image for THOSE just pays for an image nobody
9156
+ // ever sees.
9157
+ has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
9158
+ // standalone-line parser constraint, says to generate BEFORE writing the
9159
+ // document, and points data-driven visuals at a ```chart: block rather
9160
+ // than a Markdown table, which is only the right advice now that chart
9161
+ // blocks render as real chart objects.
9162
+ '- 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.',
9163
+ // The other half of image reliability, and the half no amount of
9164
+ // instruction-tightening could fix: generate_image only exists when an
9165
+ // OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
9166
+ // With neither, the model was never given a choice — and a model that
9167
+ // doesn't know that writes "[illustration of a cat]" and moves on. Said out
9168
+ // loud, up front, so the fallback is a real chart or honest prose.
9169
+ !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.",
9142
9170
  // The video counterpart of the image rule above, and the fix for the
9143
9171
  // reported "it writes scripts forever and never makes the video" run. The
9144
9172
  // failure mode is identical — the model narrates the deliverable instead of
@@ -9148,7 +9176,7 @@ function buildWorkerRules(has) {
9148
9176
  // the deliverable rather than an illustration inside one, and the reference
9149
9177
  // syntax is a plain link: Markdown image syntax does not embed a video.
9150
9178
  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.',
9151
- 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.',
9179
+ 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.`,
9152
9180
  "- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
9153
9181
  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.',
9154
9182
  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.",
@@ -9159,13 +9187,34 @@ function buildWorkerRules(has) {
9159
9187
  Rules:
9160
9188
  ${rules.filter((r) => r !== false).join("\n")}`;
9161
9189
  }
9162
- var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
9190
+ var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
9191
+ 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;
9163
9192
  function shouldRequireArtifact(assignment, toolNames) {
9164
9193
  if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
9165
9194
  if (assignment?.files?.length) return true;
9166
9195
  const haystack = `${assignment?.description ?? ""}
9167
9196
  ${assignment?.expectedOutput ?? ""}`;
9168
- 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);
9197
+ return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
9198
+ }
9199
+ 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;
9200
+ var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
9201
+ var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
9202
+ function missingVisualEvidence(assignment, output, calledTools, toolNames) {
9203
+ const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
9204
+ if (!canVisualize) return null;
9205
+ const haystack = `${assignment?.description ?? ""}
9206
+ ${assignment?.expectedOutput ?? ""}`;
9207
+ if (!VISUAL_REQUEST_RE.test(haystack)) return null;
9208
+ if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
9209
+ if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
9210
+ const options = [];
9211
+ if (toolNames.includes("generate_image")) {
9212
+ options.push('call "generate_image" and reference the result on its own line as ![description](location)');
9213
+ }
9214
+ if (toolNames.includes("generate_document")) {
9215
+ options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
9216
+ }
9217
+ 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.`;
9169
9218
  }
9170
9219
  var T3Worker = class extends BaseTier {
9171
9220
  router;
@@ -9346,6 +9395,18 @@ Now execute your subtask using this context where relevant.`
9346
9395
  return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
9347
9396
  }
9348
9397
  }
9398
+ const visualIssue = missingVisualEvidence(
9399
+ assignment,
9400
+ output,
9401
+ toolCalls.map((t) => t.name),
9402
+ this.tools.map((t) => t.name)
9403
+ );
9404
+ if (visualIssue) {
9405
+ correctionAttempts = 1;
9406
+ issues.push(visualIssue);
9407
+ this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
9408
+ output = await this.correctOutput(output, [visualIssue]);
9409
+ }
9349
9410
  this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
9350
9411
  const testResult = await this.selfTest(assignment, output);
9351
9412
  checksRun.push(...testResult.checksRun);
@@ -9660,8 +9721,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
9660
9721
  // Media generation can run for a minute; without this a cancelled run
9661
9722
  // still pays for an image nobody will see.
9662
9723
  ...this.signal ? { signal: this.signal } : {},
9663
- saveSnapshot: async (path37, content) => {
9664
- this.store?.addFileSnapshot(this.taskId, path37, content);
9724
+ saveSnapshot: async (path38, content) => {
9725
+ this.store?.addFileSnapshot(this.taskId, path38, content);
9665
9726
  },
9666
9727
  sendPeerSync: (to, syncType, content) => {
9667
9728
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -9837,7 +9898,7 @@ ${assignment.expectedOutput}`;
9837
9898
  const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
9838
9899
  const haystack = `${assignment.description}
9839
9900
  ${assignment.expectedOutput}`;
9840
- 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) ?? [];
9901
+ const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
9841
9902
  return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
9842
9903
  }
9843
9904
  async verifyArtifacts(assignment) {
@@ -9848,9 +9909,9 @@ ${assignment.expectedOutput}`;
9848
9909
  const { promisify: promisify4 } = await import('util');
9849
9910
  const execAsync3 = promisify4(exec3);
9850
9911
  for (const artifactPath of artifactPaths) {
9851
- const absolutePath = path32__default.default.resolve(process.cwd(), artifactPath);
9912
+ const absolutePath = path33__default.default.resolve(process.cwd(), artifactPath);
9852
9913
  try {
9853
- const stat = await fs10__default.default.stat(absolutePath);
9914
+ const stat = await fs11__default.default.stat(absolutePath);
9854
9915
  if (!stat.isFile()) {
9855
9916
  issues.push(`Expected artifact is not a file: ${artifactPath}`);
9856
9917
  continue;
@@ -9859,8 +9920,22 @@ ${assignment.expectedOutput}`;
9859
9920
  issues.push(`Artifact is empty: ${artifactPath}`);
9860
9921
  continue;
9861
9922
  }
9862
- if (!/\.pdf$/i.test(artifactPath)) {
9863
- const content = await fs10__default.default.readFile(absolutePath, "utf-8");
9923
+ if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
9924
+ const head = Buffer.alloc(4);
9925
+ const fh = await fs11__default.default.open(absolutePath, "r");
9926
+ try {
9927
+ await fh.read(head, 0, 4, 0);
9928
+ } finally {
9929
+ await fh.close();
9930
+ }
9931
+ if (head.toString("latin1") !== "PK") {
9932
+ issues.push(
9933
+ `${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.`
9934
+ );
9935
+ continue;
9936
+ }
9937
+ } else if (!/\.pdf$/i.test(artifactPath)) {
9938
+ const content = await fs11__default.default.readFile(absolutePath, "utf-8");
9864
9939
  if (!content.trim()) {
9865
9940
  issues.push(`Artifact content is empty: ${artifactPath}`);
9866
9941
  continue;
@@ -9869,7 +9944,7 @@ ${assignment.expectedOutput}`;
9869
9944
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
9870
9945
  continue;
9871
9946
  }
9872
- const ext = path32__default.default.extname(absolutePath).toLowerCase();
9947
+ const ext = path33__default.default.extname(absolutePath).toLowerCase();
9873
9948
  try {
9874
9949
  if (ext === ".ts" || ext === ".tsx") {
9875
9950
  await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
@@ -12823,6 +12898,700 @@ function buildMediaTools(deps, readFile) {
12823
12898
  if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
12824
12899
  return tools;
12825
12900
  }
12901
+ var WorkspaceSandboxError = class extends Error {
12902
+ constructor(attempted, workspaceRoot) {
12903
+ super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
12904
+ this.name = "WorkspaceSandboxError";
12905
+ }
12906
+ };
12907
+ function resolveInWorkspace(workspaceRoot, input) {
12908
+ if (typeof input !== "string" || input.length === 0) {
12909
+ throw new WorkspaceSandboxError(String(input), workspaceRoot);
12910
+ }
12911
+ const root = path33__default.default.resolve(workspaceRoot);
12912
+ const abs = path33__default.default.isAbsolute(input) ? path33__default.default.resolve(input) : path33__default.default.resolve(root, input);
12913
+ const rel = path33__default.default.relative(root, abs);
12914
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path33__default.default.isAbsolute(rel)) {
12915
+ throw new WorkspaceSandboxError(input, root);
12916
+ }
12917
+ try {
12918
+ const real = fs30__default.default.realpathSync(abs);
12919
+ const realRel = path33__default.default.relative(root, real);
12920
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path33__default.default.isAbsolute(realRel))) {
12921
+ throw new WorkspaceSandboxError(input, root);
12922
+ }
12923
+ } catch (e) {
12924
+ if (e instanceof WorkspaceSandboxError) throw e;
12925
+ }
12926
+ return abs;
12927
+ }
12928
+
12929
+ // src/core/documents/blocks.ts
12930
+ function fileExt(name) {
12931
+ const base = name.split(/[\\/]/).pop() ?? name;
12932
+ const dot = base.lastIndexOf(".");
12933
+ return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
12934
+ }
12935
+ function parseDelimited(text, name = "") {
12936
+ const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
12937
+ const rows = [];
12938
+ let row = [];
12939
+ let cell = "";
12940
+ let quoted = false;
12941
+ for (let i = 0; i < text.length; i++) {
12942
+ const ch = text[i];
12943
+ if (quoted) {
12944
+ if (ch === '"') {
12945
+ if (text[i + 1] === '"') {
12946
+ cell += '"';
12947
+ i++;
12948
+ } else quoted = false;
12949
+ } else cell += ch;
12950
+ } else if (ch === '"') {
12951
+ quoted = true;
12952
+ } else if (ch === delim) {
12953
+ row.push(cell);
12954
+ cell = "";
12955
+ } else if (ch === "\n") {
12956
+ row.push(cell);
12957
+ rows.push(row);
12958
+ row = [];
12959
+ cell = "";
12960
+ } else if (ch === "\r") ; else {
12961
+ cell += ch;
12962
+ }
12963
+ if (rows.length > 5e3) break;
12964
+ }
12965
+ if (cell !== "" || row.length) {
12966
+ row.push(cell);
12967
+ rows.push(row);
12968
+ }
12969
+ return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
12970
+ }
12971
+ var CHART_ALIASES = {
12972
+ bar: "bar",
12973
+ column: "bar",
12974
+ bars: "bar",
12975
+ barchart: "bar",
12976
+ horizontalbar: "bar",
12977
+ line: "line",
12978
+ lines: "line",
12979
+ linechart: "line",
12980
+ pie: "pie",
12981
+ piechart: "pie",
12982
+ doughnut: "doughnut",
12983
+ donut: "doughnut",
12984
+ area: "area",
12985
+ areachart: "area",
12986
+ scatter: "scatter",
12987
+ xy: "scatter"
12988
+ };
12989
+ function chartKind(raw) {
12990
+ return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
12991
+ }
12992
+ function toNumber(cell) {
12993
+ const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
12994
+ const n = Number.parseFloat(cleaned);
12995
+ return Number.isFinite(n) ? n : 0;
12996
+ }
12997
+ function parseChartSpec(kind, body) {
12998
+ const lines = body.replace(/\r\n/g, "\n").split("\n");
12999
+ let title;
13000
+ while (lines.length && !lines[0].trim()) lines.shift();
13001
+ const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
13002
+ if (titleMatch) {
13003
+ title = titleMatch[1].trim();
13004
+ lines.shift();
13005
+ }
13006
+ const rows = parseDelimited(lines.join("\n").trim());
13007
+ if (rows.length < 2) return null;
13008
+ const header = rows[0].map((c) => c.trim());
13009
+ if (header.length < 2) return null;
13010
+ const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
13011
+ if (!dataRows.length) return null;
13012
+ const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
13013
+ const series = header.slice(1).map((name, col2) => ({
13014
+ name: name || `Series ${col2 + 1}`,
13015
+ values: dataRows.map((r) => toNumber((r[col2 + 1] ?? "").trim()))
13016
+ }));
13017
+ if (!series.length) return null;
13018
+ return {
13019
+ kind,
13020
+ ...title ? { title } : {},
13021
+ categoryName: header[0] || "Category",
13022
+ categories,
13023
+ series
13024
+ };
13025
+ }
13026
+ function chartToTableRows(spec) {
13027
+ return [
13028
+ [spec.categoryName, ...spec.series.map((s) => s.name)],
13029
+ ...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
13030
+ ];
13031
+ }
13032
+ function parseBlocks(md) {
13033
+ const out = [];
13034
+ const lines = md.replace(/\r\n/g, "\n").split("\n");
13035
+ let i = 0;
13036
+ let ordinal = 0;
13037
+ const reset = () => {
13038
+ ordinal = 0;
13039
+ };
13040
+ while (i < lines.length) {
13041
+ const line = lines[i] ?? "";
13042
+ const fence = line.match(/^```+\s*(.*)$/);
13043
+ if (fence) {
13044
+ const info = (fence[1] ?? "").trim();
13045
+ i++;
13046
+ const code = [];
13047
+ while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
13048
+ code.push(lines[i] ?? "");
13049
+ i++;
13050
+ }
13051
+ i++;
13052
+ const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
13053
+ const kind = chartInfo ? chartKind(chartInfo[1]) : null;
13054
+ const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
13055
+ out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
13056
+ reset();
13057
+ continue;
13058
+ }
13059
+ if (!line.trim()) {
13060
+ i++;
13061
+ reset();
13062
+ continue;
13063
+ }
13064
+ const h = line.match(/^(#{1,6})\s+(.*)$/);
13065
+ if (h) {
13066
+ out.push({ t: "heading", level: h[1].length, text: h[2] });
13067
+ i++;
13068
+ reset();
13069
+ continue;
13070
+ }
13071
+ if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
13072
+ out.push({ t: "hr" });
13073
+ i++;
13074
+ reset();
13075
+ continue;
13076
+ }
13077
+ const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
13078
+ if (li) {
13079
+ const ordered = /\d/.test(li[2]);
13080
+ ordinal = ordered ? ordinal + 1 : 0;
13081
+ out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
13082
+ i++;
13083
+ continue;
13084
+ }
13085
+ if (/^>\s?/.test(line)) {
13086
+ out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
13087
+ i++;
13088
+ reset();
13089
+ continue;
13090
+ }
13091
+ if (/^\|.*\|/.test(line)) {
13092
+ const rows = [];
13093
+ while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
13094
+ const r = lines[i];
13095
+ if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
13096
+ i++;
13097
+ }
13098
+ out.push({ t: "table", rows });
13099
+ reset();
13100
+ continue;
13101
+ }
13102
+ out.push({ t: "para", text: line });
13103
+ i++;
13104
+ reset();
13105
+ }
13106
+ return out;
13107
+ }
13108
+ var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
13109
+ function matchImageLine(s) {
13110
+ const m = s.trim().match(IMAGE_LINE);
13111
+ return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
13112
+ }
13113
+ function sniffImage(b) {
13114
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
13115
+ if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
13116
+ return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
13117
+ }
13118
+ if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
13119
+ return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
13120
+ }
13121
+ if (b.length > 26 && b[0] === 66 && b[1] === 77) {
13122
+ return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
13123
+ }
13124
+ if (b.length > 4 && b[0] === 255 && b[1] === 216) {
13125
+ let i = 2;
13126
+ while (i + 9 < b.length) {
13127
+ if (b[i] !== 255) {
13128
+ i++;
13129
+ continue;
13130
+ }
13131
+ const marker = b[i + 1];
13132
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
13133
+ return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
13134
+ }
13135
+ i += 2 + dv.getUint16(i + 2);
13136
+ }
13137
+ }
13138
+ return null;
13139
+ }
13140
+ function bytesToBase64(bytes) {
13141
+ const g = globalThis;
13142
+ if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
13143
+ const CHUNK = 32768;
13144
+ let binary = "";
13145
+ for (let i = 0; i < bytes.length; i += CHUNK) {
13146
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
13147
+ }
13148
+ return g.btoa(binary);
13149
+ }
13150
+ function stripInline(s) {
13151
+ return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
13152
+ }
13153
+ function inlineRuns(text) {
13154
+ const acc = [];
13155
+ const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
13156
+ let last = 0;
13157
+ let m;
13158
+ while ((m = re.exec(text)) !== null) {
13159
+ if (m.index > last) acc.push({ text: text.slice(last, m.index) });
13160
+ const tok = m[0];
13161
+ if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
13162
+ else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
13163
+ else if (tok.startsWith("[")) {
13164
+ const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
13165
+ acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
13166
+ } else acc.push({ text: tok.slice(1, -1), italic: true });
13167
+ last = re.lastIndex;
13168
+ }
13169
+ if (last < text.length) acc.push({ text: text.slice(last) });
13170
+ return acc.length ? acc : [{ text }];
13171
+ }
13172
+ function splitSlides(md) {
13173
+ const norm = md.replace(/\r\n/g, "\n").trim();
13174
+ let chunks = norm.split(/\n\s*-{3,}\s*\n/);
13175
+ if (chunks.length === 1) {
13176
+ const lines = norm.split("\n");
13177
+ const sections = [];
13178
+ let cur = [];
13179
+ for (const ln of lines) {
13180
+ if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
13181
+ sections.push(cur.join("\n"));
13182
+ cur = [];
13183
+ }
13184
+ cur.push(ln);
13185
+ }
13186
+ if (cur.length) sections.push(cur.join("\n"));
13187
+ if (sections.length > 1) chunks = sections;
13188
+ }
13189
+ return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
13190
+ }
13191
+ function parseSlide(chunk) {
13192
+ let title = "";
13193
+ const body = [];
13194
+ const images = [];
13195
+ const charts = [];
13196
+ const lines = chunk.split("\n");
13197
+ for (let i = 0; i < lines.length; i++) {
13198
+ const raw = lines[i] ?? "";
13199
+ const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
13200
+ if (fence) {
13201
+ const kind = chartKind(fence[1]);
13202
+ const bodyLines = [];
13203
+ i++;
13204
+ while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
13205
+ bodyLines.push(lines[i] ?? "");
13206
+ i++;
13207
+ }
13208
+ const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
13209
+ if (spec) charts.push(spec);
13210
+ else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
13211
+ continue;
13212
+ }
13213
+ const line = raw.trim();
13214
+ if (!line) continue;
13215
+ const h = line.match(/^#{1,6}[ \t]+(.*)/);
13216
+ if (h) {
13217
+ if (!title) title = stripInline(h[1]);
13218
+ else body.push(stripInline(h[1]));
13219
+ continue;
13220
+ }
13221
+ const img = matchImageLine(line);
13222
+ if (img) {
13223
+ images.push(img);
13224
+ continue;
13225
+ }
13226
+ const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
13227
+ if (li) {
13228
+ const bulletImg = matchImageLine(li[2]);
13229
+ if (bulletImg) {
13230
+ images.push(bulletImg);
13231
+ continue;
13232
+ }
13233
+ body.push(stripInline(li[2]));
13234
+ continue;
13235
+ }
13236
+ if (/^>\s?/.test(line)) {
13237
+ body.push(stripInline(line.replace(/^>\s?/, "")));
13238
+ continue;
13239
+ }
13240
+ body.push(stripInline(line));
13241
+ }
13242
+ return { title, body, images, charts };
13243
+ }
13244
+ function extractCharts(source) {
13245
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
13246
+ const charts = [];
13247
+ const rest = [];
13248
+ for (let i = 0; i < lines.length; i++) {
13249
+ const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
13250
+ if (!fence) {
13251
+ rest.push(lines[i]);
13252
+ continue;
13253
+ }
13254
+ const kind = chartKind(fence[1]);
13255
+ const body = [];
13256
+ i++;
13257
+ while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
13258
+ body.push(lines[i] ?? "");
13259
+ i++;
13260
+ }
13261
+ const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
13262
+ if (spec) charts.push(spec);
13263
+ else rest.push(...body);
13264
+ }
13265
+ return { charts, rest: rest.join("\n") };
13266
+ }
13267
+
13268
+ // src/core/documents/render.ts
13269
+ function isDocumentFormat(ext) {
13270
+ return ext === "docx" || ext === "pptx" || ext === "xlsx";
13271
+ }
13272
+ async function loadImage(ref, load) {
13273
+ if (!load) return null;
13274
+ try {
13275
+ const bytes = await load(ref.url);
13276
+ if (!bytes || !bytes.length) return null;
13277
+ const info = sniffImage(bytes);
13278
+ if (!info || info.width <= 0 || info.height <= 0) return null;
13279
+ return { ...info, bytes, alt: ref.alt };
13280
+ } catch {
13281
+ return null;
13282
+ }
13283
+ }
13284
+ async function renderXlsx(source, opts = {}) {
13285
+ const XLSX = await import('xlsx');
13286
+ const { charts, rest } = extractCharts(source);
13287
+ const name = opts.name ?? "";
13288
+ const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
13289
+ const wb = XLSX.utils.book_new();
13290
+ XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
13291
+ const used = /* @__PURE__ */ new Set(["Sheet1"]);
13292
+ charts.forEach((spec, i) => {
13293
+ const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
13294
+ let sheetName = base;
13295
+ let n = 2;
13296
+ while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
13297
+ used.add(sheetName);
13298
+ const aoa = [
13299
+ [spec.categoryName, ...spec.series.map((s) => s.name)],
13300
+ ...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
13301
+ ];
13302
+ XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
13303
+ });
13304
+ return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
13305
+ }
13306
+ var DOCX_MAX_W = 600;
13307
+ var DOCX_MAX_H = 864;
13308
+ async function renderDocx(md, opts = {}) {
13309
+ const d = await import('docx');
13310
+ const {
13311
+ Document,
13312
+ Packer,
13313
+ Paragraph,
13314
+ TextRun,
13315
+ ImageRun,
13316
+ HeadingLevel,
13317
+ Table,
13318
+ TableRow,
13319
+ TableCell,
13320
+ WidthType,
13321
+ BorderStyle,
13322
+ AlignmentType
13323
+ } = d;
13324
+ const HEADINGS = [
13325
+ HeadingLevel.HEADING_1,
13326
+ HeadingLevel.HEADING_2,
13327
+ HeadingLevel.HEADING_3,
13328
+ HeadingLevel.HEADING_4,
13329
+ HeadingLevel.HEADING_5,
13330
+ HeadingLevel.HEADING_6
13331
+ ];
13332
+ const runs = (text) => inlineRuns(text).map((r) => new TextRun({
13333
+ text: r.text,
13334
+ ...r.bold ? { bold: true } : {},
13335
+ ...r.italic ? { italics: true } : {},
13336
+ ...r.code ? { font: "Courier New" } : {}
13337
+ }));
13338
+ const tableOf = (rows) => new Table({
13339
+ width: { size: 100, type: WidthType.PERCENTAGE },
13340
+ rows: rows.map((r) => new TableRow({
13341
+ children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
13342
+ }))
13343
+ });
13344
+ const imageParagraph = (img) => {
13345
+ const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
13346
+ return new Paragraph({
13347
+ children: [new ImageRun({
13348
+ type: img.type,
13349
+ data: img.bytes,
13350
+ transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
13351
+ ...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
13352
+ })]
13353
+ });
13354
+ };
13355
+ const asImage = async (text) => {
13356
+ const ref = matchImageLine(text);
13357
+ return ref ? await loadImage(ref, opts.loadImageBytes) : null;
13358
+ };
13359
+ const children = [];
13360
+ for (const b of parseBlocks(md)) {
13361
+ switch (b.t) {
13362
+ case "heading":
13363
+ children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
13364
+ break;
13365
+ case "para": {
13366
+ const img = await asImage(b.text);
13367
+ children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
13368
+ break;
13369
+ }
13370
+ case "bullet": {
13371
+ const img = await asImage(b.text);
13372
+ children.push(img ? imageParagraph(img) : new Paragraph({
13373
+ children: runs(b.text),
13374
+ ...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
13375
+ }));
13376
+ break;
13377
+ }
13378
+ case "code":
13379
+ for (const cl of b.lines) {
13380
+ children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
13381
+ }
13382
+ break;
13383
+ case "quote":
13384
+ children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
13385
+ break;
13386
+ case "table":
13387
+ children.push(tableOf(b.rows));
13388
+ break;
13389
+ case "chart":
13390
+ if (b.spec.title) {
13391
+ children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
13392
+ }
13393
+ children.push(tableOf(chartToTableRows(b.spec)));
13394
+ break;
13395
+ case "hr":
13396
+ children.push(new Paragraph({
13397
+ children: [],
13398
+ border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
13399
+ }));
13400
+ break;
13401
+ }
13402
+ }
13403
+ const doc = new Document({
13404
+ numbering: {
13405
+ config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
13406
+ },
13407
+ sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
13408
+ });
13409
+ return new Uint8Array(await Packer.toArrayBuffer(doc));
13410
+ }
13411
+ function pptxConstructor(mod) {
13412
+ let c = mod;
13413
+ for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
13414
+ if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
13415
+ return c;
13416
+ }
13417
+ var SLIDE_H = 7.5;
13418
+ var CONTENT_X = 0.6;
13419
+ var CONTENT_W = 12.1;
13420
+ var CONTENT_BOTTOM = 0.4;
13421
+ async function renderPptx(md, opts = {}) {
13422
+ const PptxGen = pptxConstructor(await import('pptxgenjs'));
13423
+ const pptx = new PptxGen();
13424
+ pptx.layout = "LAYOUT_WIDE";
13425
+ const CHART_TYPES = {
13426
+ bar: pptx.ChartType["bar"],
13427
+ line: pptx.ChartType["line"],
13428
+ pie: pptx.ChartType["pie"],
13429
+ doughnut: pptx.ChartType["doughnut"],
13430
+ area: pptx.ChartType["area"],
13431
+ scatter: pptx.ChartType["scatter"]
13432
+ };
13433
+ const slides = splitSlides(md);
13434
+ const blank = { title: "", body: [""], images: [], charts: [] };
13435
+ for (const s of slides.length ? slides : [blank]) {
13436
+ const slide = pptx.addSlide();
13437
+ 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" });
13438
+ const top = s.title ? 1.5 : 0.5;
13439
+ const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
13440
+ const visuals = [
13441
+ ...s.charts.map((spec) => ({ kind: "chart", spec })),
13442
+ ...pics.map((img) => ({ kind: "image", img }))
13443
+ ];
13444
+ const bodyH = visuals.length ? 2.2 : 5.4;
13445
+ if (s.body.length) {
13446
+ slide.addText(
13447
+ s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
13448
+ { x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
13449
+ );
13450
+ }
13451
+ if (visuals.length) {
13452
+ const y = top + (s.body.length ? bodyH + 0.2 : 0);
13453
+ const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
13454
+ const gap = 0.3;
13455
+ const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
13456
+ visuals.forEach((v, i) => {
13457
+ const x = CONTENT_X + i * (w + gap);
13458
+ if (v.kind === "image") {
13459
+ slide.addImage({
13460
+ data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
13461
+ x,
13462
+ y,
13463
+ w,
13464
+ h,
13465
+ // 'contain' keeps the aspect ratio inside the box — a stretched
13466
+ // render looks worse than a smaller correct one.
13467
+ sizing: { type: "contain", w, h },
13468
+ ...v.img.alt ? { altText: v.img.alt } : {}
13469
+ });
13470
+ } else {
13471
+ slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
13472
+ x,
13473
+ y,
13474
+ w,
13475
+ h,
13476
+ showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
13477
+ legendPos: "b",
13478
+ showTitle: !!v.spec.title,
13479
+ ...v.spec.title ? { title: v.spec.title } : {},
13480
+ showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
13481
+ });
13482
+ }
13483
+ });
13484
+ }
13485
+ }
13486
+ return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
13487
+ }
13488
+ function chartData(spec) {
13489
+ const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
13490
+ return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
13491
+ }
13492
+ async function renderDocument(format, source, opts = {}) {
13493
+ if (format === "docx") return renderDocx(source, opts);
13494
+ if (format === "pptx") return renderPptx(source, opts);
13495
+ return renderXlsx(source, opts);
13496
+ }
13497
+
13498
+ // src/tools/generate-document.ts
13499
+ var SOURCE_HINT = {
13500
+ docx: "Markdown",
13501
+ pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
13502
+ xlsx: "CSV (one header row, then data rows)"
13503
+ };
13504
+ var GenerateDocumentTool = class extends BaseTool {
13505
+ /**
13506
+ * `readFile` is injected so the image loader is testable and so a host with a
13507
+ * different workspace (not plain disk) can supply its own reader.
13508
+ */
13509
+ constructor(readFile = async (p) => new Uint8Array(await fs11__default.default.readFile(p))) {
13510
+ super();
13511
+ this.readFile = readFile;
13512
+ }
13513
+ readFile;
13514
+ name = "generate_document";
13515
+ 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.";
13516
+ inputSchema = {
13517
+ type: "object",
13518
+ properties: {
13519
+ path: {
13520
+ type: "string",
13521
+ description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
13522
+ },
13523
+ content: {
13524
+ type: "string",
13525
+ 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.'
13526
+ },
13527
+ format: {
13528
+ type: "string",
13529
+ description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
13530
+ }
13531
+ },
13532
+ required: ["path", "content"]
13533
+ };
13534
+ isDangerous() {
13535
+ return true;
13536
+ }
13537
+ async execute(input, options) {
13538
+ const filePath = String(input["path"] ?? "").trim();
13539
+ if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
13540
+ const content = typeof input["content"] === "string" ? input["content"] : "";
13541
+ if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
13542
+ const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
13543
+ const ext = requested || fileExt(filePath);
13544
+ if (!isDocumentFormat(ext)) {
13545
+ 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.`;
13546
+ }
13547
+ const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
13548
+ const embedded = [];
13549
+ const skipped = [];
13550
+ const loadImageBytes = async (url) => {
13551
+ if (/^(https?|ftp):\/\//i.test(url)) {
13552
+ skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
13553
+ return null;
13554
+ }
13555
+ const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
13556
+ try {
13557
+ const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
13558
+ embedded.push(local);
13559
+ return bytes2;
13560
+ } catch {
13561
+ skipped.push(`${local} (not found in the workspace)`);
13562
+ return null;
13563
+ }
13564
+ };
13565
+ if (options.saveSnapshot) {
13566
+ try {
13567
+ const old = await fs11__default.default.readFile(absPath, "utf-8");
13568
+ await options.saveSnapshot(absPath, old);
13569
+ } catch {
13570
+ }
13571
+ }
13572
+ const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
13573
+ await fs11__default.default.mkdir(path33__default.default.dirname(absPath), { recursive: true });
13574
+ await fs11__default.default.writeFile(absPath, bytes);
13575
+ const charts = countCharts(content);
13576
+ return [
13577
+ `Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
13578
+ embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
13579
+ // Named, not counted: a dropped image is the failure the user actually
13580
+ // notices, and the model can only fix it if it is told which reference died.
13581
+ skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
13582
+ charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
13583
+ charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
13584
+ charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
13585
+ ].filter(Boolean).join("\n");
13586
+ }
13587
+ };
13588
+ var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
13589
+ function countCharts(source) {
13590
+ return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
13591
+ }
13592
+ function buildDocumentTools(readFile) {
13593
+ return readFile ? [new GenerateDocumentTool(readFile)] : [];
13594
+ }
12826
13595
 
12827
13596
  // src/core/run-breaker.ts
12828
13597
  var DEFAULT_FAILURE_THRESHOLD = 3;
@@ -12971,35 +13740,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
12971
13740
  }
12972
13741
  }
12973
13742
  };
12974
- var WorkspaceSandboxError = class extends Error {
12975
- constructor(attempted, workspaceRoot) {
12976
- super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
12977
- this.name = "WorkspaceSandboxError";
12978
- }
12979
- };
12980
- function resolveInWorkspace(workspaceRoot, input) {
12981
- if (typeof input !== "string" || input.length === 0) {
12982
- throw new WorkspaceSandboxError(String(input), workspaceRoot);
12983
- }
12984
- const root = path32__default.default.resolve(workspaceRoot);
12985
- const abs = path32__default.default.isAbsolute(input) ? path32__default.default.resolve(input) : path32__default.default.resolve(root, input);
12986
- const rel = path32__default.default.relative(root, abs);
12987
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path32__default.default.isAbsolute(rel)) {
12988
- throw new WorkspaceSandboxError(input, root);
12989
- }
12990
- try {
12991
- const real = fs29__default.default.realpathSync(abs);
12992
- const realRel = path32__default.default.relative(root, real);
12993
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path32__default.default.isAbsolute(realRel))) {
12994
- throw new WorkspaceSandboxError(input, root);
12995
- }
12996
- } catch (e) {
12997
- if (e instanceof WorkspaceSandboxError) throw e;
12998
- }
12999
- return abs;
13000
- }
13001
-
13002
- // src/tools/file.ts
13003
13743
  var FileReadTool = class extends BaseTool {
13004
13744
  name = "file_read";
13005
13745
  description = "Read the contents of a file. Returns the file content as a string.";
@@ -13017,7 +13757,7 @@ var FileReadTool = class extends BaseTool {
13017
13757
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
13018
13758
  const offset = input["offset"] ?? 1;
13019
13759
  const limit = input["limit"];
13020
- const content = await fs10__default.default.readFile(absPath, "utf-8");
13760
+ const content = await fs11__default.default.readFile(absPath, "utf-8");
13021
13761
  const lines = content.split("\n");
13022
13762
  const start = Math.max(0, offset - 1);
13023
13763
  const end = limit ? start + limit : lines.length;
@@ -13046,13 +13786,13 @@ var FileWriteTool = class extends BaseTool {
13046
13786
  const content = input["content"];
13047
13787
  if (options.saveSnapshot) {
13048
13788
  try {
13049
- const oldContent = await fs10__default.default.readFile(absPath, "utf-8");
13789
+ const oldContent = await fs11__default.default.readFile(absPath, "utf-8");
13050
13790
  await options.saveSnapshot(absPath, oldContent);
13051
13791
  } catch {
13052
13792
  }
13053
13793
  }
13054
- await fs10__default.default.mkdir(path32__default.default.dirname(absPath), { recursive: true });
13055
- await fs10__default.default.writeFile(absPath, content, "utf-8");
13794
+ await fs11__default.default.mkdir(path33__default.default.dirname(absPath), { recursive: true });
13795
+ await fs11__default.default.writeFile(absPath, content, "utf-8");
13056
13796
  return `Written ${content.length} characters to ${filePath}`;
13057
13797
  }
13058
13798
  };
@@ -13078,7 +13818,7 @@ var FileEditTool = class extends BaseTool {
13078
13818
  const oldString = input["old_string"];
13079
13819
  const newString = input["new_string"];
13080
13820
  const replaceAll = input["replace_all"] ?? false;
13081
- const rawContent = await fs10__default.default.readFile(absPath, "utf-8");
13821
+ const rawContent = await fs11__default.default.readFile(absPath, "utf-8");
13082
13822
  if (options.saveSnapshot) {
13083
13823
  await options.saveSnapshot(absPath, rawContent);
13084
13824
  }
@@ -13090,7 +13830,7 @@ var FileEditTool = class extends BaseTool {
13090
13830
  );
13091
13831
  }
13092
13832
  const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
13093
- await fs10__default.default.writeFile(absPath, updated, "utf-8");
13833
+ await fs11__default.default.writeFile(absPath, updated, "utf-8");
13094
13834
  const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
13095
13835
  return `Replaced ${count} occurrence(s) in ${filePath}`;
13096
13836
  }
@@ -13113,12 +13853,12 @@ var FileDeleteTool = class extends BaseTool {
13113
13853
  const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
13114
13854
  if (options.saveSnapshot) {
13115
13855
  try {
13116
- const oldContent = await fs10__default.default.readFile(absPath, "utf-8");
13856
+ const oldContent = await fs11__default.default.readFile(absPath, "utf-8");
13117
13857
  await options.saveSnapshot(absPath, oldContent);
13118
13858
  } catch {
13119
13859
  }
13120
13860
  }
13121
- await fs10__default.default.rm(absPath, { recursive: false });
13861
+ await fs11__default.default.rm(absPath, { recursive: false });
13122
13862
  return `Deleted ${filePath}`;
13123
13863
  }
13124
13864
  };
@@ -13135,7 +13875,7 @@ var FileListTool = class extends BaseTool {
13135
13875
  async execute(input, _options) {
13136
13876
  const inputPath = input["path"] || ".";
13137
13877
  const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
13138
- const entries = await fs10__default.default.readdir(absPath, { withFileTypes: true });
13878
+ const entries = await fs11__default.default.readdir(absPath, { withFileTypes: true });
13139
13879
  return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
13140
13880
  }
13141
13881
  };
@@ -13548,8 +14288,8 @@ var ImageAnalyzeTool = class extends BaseTool {
13548
14288
  }
13549
14289
  };
13550
14290
  async function fileToImageAttachment(filePath) {
13551
- const data = await fs10__default.default.readFile(filePath);
13552
- const ext = path32__default.default.extname(filePath).toLowerCase();
14291
+ const data = await fs11__default.default.readFile(filePath);
14292
+ const ext = path33__default.default.extname(filePath).toLowerCase();
13553
14293
  const mimeMap = {
13554
14294
  ".jpg": "image/jpeg",
13555
14295
  ".jpeg": "image/jpeg",
@@ -13583,14 +14323,14 @@ var PDFCreateTool = class extends BaseTool {
13583
14323
  const filePath = input["path"];
13584
14324
  const content = input["content"];
13585
14325
  const title = input["title"];
13586
- const dir = path32__default.default.dirname(filePath);
13587
- if (!fs29__default.default.existsSync(dir)) {
13588
- fs29__default.default.mkdirSync(dir, { recursive: true });
14326
+ const dir = path33__default.default.dirname(filePath);
14327
+ if (!fs30__default.default.existsSync(dir)) {
14328
+ fs30__default.default.mkdirSync(dir, { recursive: true });
13589
14329
  }
13590
14330
  return new Promise((resolve2, reject) => {
13591
14331
  try {
13592
14332
  const doc = new PDFDocument__default.default({ margin: 50 });
13593
- const stream = fs29__default.default.createWriteStream(filePath);
14333
+ const stream = fs30__default.default.createWriteStream(filePath);
13594
14334
  doc.pipe(stream);
13595
14335
  if (title) {
13596
14336
  doc.info["Title"] = title;
@@ -13668,22 +14408,22 @@ var CodeInterpreterTool = class extends BaseTool {
13668
14408
  }
13669
14409
  cmdPrefix = NODE_CMD;
13670
14410
  }
13671
- const tmpDir = path32__default.default.join(this.workspaceRoot, ".cascade", "tmp");
13672
- if (!fs29__default.default.existsSync(tmpDir)) {
13673
- fs29__default.default.mkdirSync(tmpDir, { recursive: true });
14411
+ const tmpDir = path33__default.default.join(this.workspaceRoot, ".cascade", "tmp");
14412
+ if (!fs30__default.default.existsSync(tmpDir)) {
14413
+ fs30__default.default.mkdirSync(tmpDir, { recursive: true });
13674
14414
  }
13675
14415
  const extension = language === "python" ? "py" : "js";
13676
14416
  const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
13677
- const filePath = path32__default.default.join(tmpDir, fileName);
13678
- fs29__default.default.writeFileSync(filePath, code, "utf-8");
14417
+ const filePath = path33__default.default.join(tmpDir, fileName);
14418
+ fs30__default.default.writeFileSync(filePath, code, "utf-8");
13679
14419
  const execArgs = [filePath, ...args];
13680
14420
  return new Promise((resolve2) => {
13681
14421
  const startMs = Date.now();
13682
14422
  child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
13683
14423
  const duration = Date.now() - startMs;
13684
14424
  try {
13685
- if (fs29__default.default.existsSync(filePath)) {
13686
- fs29__default.default.unlinkSync(filePath);
14425
+ if (fs30__default.default.existsSync(filePath)) {
14426
+ fs30__default.default.unlinkSync(filePath);
13687
14427
  }
13688
14428
  } catch (cleanupErr) {
13689
14429
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -13995,7 +14735,7 @@ var GlobTool = class extends BaseTool {
13995
14735
  };
13996
14736
  async execute(input, _options) {
13997
14737
  const pattern = input["pattern"];
13998
- const searchPath = input["path"] ? path32__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
14738
+ const searchPath = input["path"] ? path33__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
13999
14739
  const matches = await glob.glob(pattern, {
14000
14740
  cwd: searchPath,
14001
14741
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -14008,7 +14748,7 @@ var GlobTool = class extends BaseTool {
14008
14748
  const withMtime = await Promise.all(
14009
14749
  matches.map(async (rel) => {
14010
14750
  try {
14011
- const stat = await fs10__default.default.stat(path32__default.default.join(searchPath, rel));
14751
+ const stat = await fs11__default.default.stat(path33__default.default.join(searchPath, rel));
14012
14752
  return { rel, mtime: stat.mtimeMs };
14013
14753
  } catch {
14014
14754
  return { rel, mtime: 0 };
@@ -14057,7 +14797,7 @@ var GrepTool = class extends BaseTool {
14057
14797
  };
14058
14798
  async execute(input, _options) {
14059
14799
  const pattern = input["pattern"];
14060
- const searchPath = input["path"] ? path32__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
14800
+ const searchPath = input["path"] ? path33__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
14061
14801
  const globPattern = input["glob"];
14062
14802
  const outputMode = input["output_mode"] ?? "content";
14063
14803
  const context = input["context"] ?? 0;
@@ -14111,15 +14851,15 @@ var GrepTool = class extends BaseTool {
14111
14851
  nodir: true
14112
14852
  });
14113
14853
  } catch {
14114
- files = [path32__default.default.relative(searchPath, searchPath) || "."];
14854
+ files = [path33__default.default.relative(searchPath, searchPath) || "."];
14115
14855
  }
14116
14856
  const results = [];
14117
14857
  let totalCount = 0;
14118
14858
  for (const rel of files) {
14119
- const abs = path32__default.default.join(searchPath, rel);
14859
+ const abs = path33__default.default.join(searchPath, rel);
14120
14860
  let content;
14121
14861
  try {
14122
- content = await fs10__default.default.readFile(abs, "utf-8");
14862
+ content = await fs11__default.default.readFile(abs, "utf-8");
14123
14863
  } catch {
14124
14864
  continue;
14125
14865
  }
@@ -14503,10 +15243,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
14503
15243
  }
14504
15244
  isIgnored(filePath) {
14505
15245
  if (!filePath) return false;
14506
- const abs = path32__default.default.resolve(this.workspaceRoot, filePath);
14507
- const rel = path32__default.default.relative(this.workspaceRoot, abs);
14508
- if (!rel || rel.startsWith("..") || path32__default.default.isAbsolute(rel)) return true;
14509
- const posixRel = rel.split(path32__default.default.sep).join("/");
15246
+ const abs = path33__default.default.resolve(this.workspaceRoot, filePath);
15247
+ const rel = path33__default.default.relative(this.workspaceRoot, abs);
15248
+ if (!rel || rel.startsWith("..") || path33__default.default.isAbsolute(rel)) return true;
15249
+ const posixRel = rel.split(path33__default.default.sep).join("/");
14510
15250
  return this.ignoreMatcher.ignores(posixRel);
14511
15251
  }
14512
15252
  };
@@ -14740,22 +15480,22 @@ var FileMcpOAuthStore = class {
14740
15480
  filePath;
14741
15481
  load() {
14742
15482
  try {
14743
- return JSON.parse(fs29__default.default.readFileSync(this.filePath, "utf-8"));
15483
+ return JSON.parse(fs30__default.default.readFileSync(this.filePath, "utf-8"));
14744
15484
  } catch {
14745
15485
  return void 0;
14746
15486
  }
14747
15487
  }
14748
15488
  save(state) {
14749
- fs29__default.default.mkdirSync(path32__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
14750
- fs29__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
15489
+ fs30__default.default.mkdirSync(path33__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
15490
+ fs30__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
14751
15491
  try {
14752
- fs29__default.default.chmodSync(this.filePath, 384);
15492
+ fs30__default.default.chmodSync(this.filePath, 384);
14753
15493
  } catch {
14754
15494
  }
14755
15495
  }
14756
15496
  clear() {
14757
15497
  try {
14758
- fs29__default.default.rmSync(this.filePath);
15498
+ fs30__default.default.rmSync(this.filePath);
14759
15499
  } catch {
14760
15500
  }
14761
15501
  }
@@ -15363,7 +16103,7 @@ var TaskAnalyzer = class {
15363
16103
 
15364
16104
  // src/core/router/model-performance-tracker.ts
15365
16105
  init_pricing();
15366
- var DEFAULT_STATS_FILE = path32__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
16106
+ var DEFAULT_STATS_FILE = path33__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
15367
16107
  var ModelPerformanceTracker = class {
15368
16108
  stats = /* @__PURE__ */ new Map();
15369
16109
  featureStats = /* @__PURE__ */ new Map();
@@ -15383,7 +16123,7 @@ var ModelPerformanceTracker = class {
15383
16123
  if (this.loaded) return;
15384
16124
  this.loaded = true;
15385
16125
  try {
15386
- const raw = await fs10__default.default.readFile(this.statsFile, "utf-8");
16126
+ const raw = await fs11__default.default.readFile(this.statsFile, "utf-8");
15387
16127
  const parsed = JSON.parse(raw);
15388
16128
  if (parsed.models) {
15389
16129
  for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
@@ -15403,15 +16143,15 @@ var ModelPerformanceTracker = class {
15403
16143
  async save() {
15404
16144
  if (this.readOnly) return;
15405
16145
  try {
15406
- await fs10__default.default.mkdir(path32__default.default.dirname(this.statsFile), { recursive: true });
16146
+ await fs11__default.default.mkdir(path33__default.default.dirname(this.statsFile), { recursive: true });
15407
16147
  const modelsObj = {};
15408
16148
  const featuresObj = {};
15409
16149
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
15410
16150
  for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
15411
16151
  const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
15412
16152
  const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
15413
- await fs10__default.default.writeFile(tmp, json, "utf-8");
15414
- await fs10__default.default.rename(tmp, this.statsFile);
16153
+ await fs11__default.default.writeFile(tmp, json, "utf-8");
16154
+ await fs11__default.default.rename(tmp, this.statsFile);
15415
16155
  } catch {
15416
16156
  }
15417
16157
  }
@@ -15951,9 +16691,9 @@ Required capability: ${description.slice(0, 300)}`;
15951
16691
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
15952
16692
  async loadPersistedTools() {
15953
16693
  if (!this.workspacePath || !this.persistEnabled) return;
15954
- const file = path32__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
16694
+ const file = path33__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
15955
16695
  try {
15956
- const raw = await fs10__default.default.readFile(file, "utf-8");
16696
+ const raw = await fs11__default.default.readFile(file, "utf-8");
15957
16697
  const specs = JSON.parse(raw);
15958
16698
  if (!Array.isArray(specs)) return;
15959
16699
  let loaded = 0;
@@ -15975,11 +16715,11 @@ Required capability: ${description.slice(0, 300)}`;
15975
16715
  }
15976
16716
  async persist() {
15977
16717
  if (!this.workspacePath || !this.persistEnabled) return;
15978
- const dir = path32__default.default.join(this.workspacePath, ".cascade");
15979
- const file = path32__default.default.join(dir, DYNAMIC_TOOLS_FILE);
16718
+ const dir = path33__default.default.join(this.workspacePath, ".cascade");
16719
+ const file = path33__default.default.join(dir, DYNAMIC_TOOLS_FILE);
15980
16720
  try {
15981
- await fs10__default.default.mkdir(dir, { recursive: true });
15982
- await fs10__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
16721
+ await fs11__default.default.mkdir(dir, { recursive: true });
16722
+ await fs11__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
15983
16723
  } catch (err) {
15984
16724
  this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
15985
16725
  }
@@ -15996,12 +16736,12 @@ var WorldStateDB = class {
15996
16736
  constructor(workspacePath, debugMode = false) {
15997
16737
  this.workspacePath = workspacePath;
15998
16738
  this.debugMode = debugMode;
15999
- const cascadeDir = path32__default.default.join(workspacePath, ".cascade");
16000
- if (!fs29__default.default.existsSync(cascadeDir)) {
16001
- fs29__default.default.mkdirSync(cascadeDir, { recursive: true });
16739
+ const cascadeDir = path33__default.default.join(workspacePath, ".cascade");
16740
+ if (!fs30__default.default.existsSync(cascadeDir)) {
16741
+ fs30__default.default.mkdirSync(cascadeDir, { recursive: true });
16002
16742
  }
16003
- this.keyPath = path32__default.default.join(cascadeDir, "world_state.key");
16004
- this.dbPath = path32__default.default.join(cascadeDir, "world_state.db");
16743
+ this.keyPath = path33__default.default.join(cascadeDir, "world_state.key");
16744
+ this.dbPath = path33__default.default.join(cascadeDir, "world_state.db");
16005
16745
  this.initEncryptionKey();
16006
16746
  this.db = new Database2__default.default(this.dbPath);
16007
16747
  this.db.pragma("journal_mode = WAL");
@@ -16058,11 +16798,11 @@ var WorldStateDB = class {
16058
16798
  ).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
16059
16799
  }
16060
16800
  initEncryptionKey() {
16061
- if (fs29__default.default.existsSync(this.keyPath)) {
16062
- this.encryptionKey = fs29__default.default.readFileSync(this.keyPath);
16801
+ if (fs30__default.default.existsSync(this.keyPath)) {
16802
+ this.encryptionKey = fs30__default.default.readFileSync(this.keyPath);
16063
16803
  } else {
16064
16804
  this.encryptionKey = crypto__default.default.randomBytes(32);
16065
- fs29__default.default.writeFileSync(this.keyPath, this.encryptionKey);
16805
+ fs30__default.default.writeFileSync(this.keyPath, this.encryptionKey);
16066
16806
  }
16067
16807
  }
16068
16808
  encrypt(text) {
@@ -16348,9 +17088,9 @@ var WorldStateDB = class {
16348
17088
  dumpDebugIfNeeded() {
16349
17089
  if (!this.debugMode) return;
16350
17090
  try {
16351
- const dumpPath = path32__default.default.join(os10__default.default.tmpdir(), "cascade_world_state_debug.json");
17091
+ const dumpPath = path33__default.default.join(os10__default.default.tmpdir(), "cascade_world_state_debug.json");
16352
17092
  const entries = this.getAllEntries();
16353
- fs29__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
17093
+ fs30__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
16354
17094
  } catch (err) {
16355
17095
  console.error("Failed to dump debug world state", err);
16356
17096
  }
@@ -16720,7 +17460,7 @@ var WorkspaceIndex = class {
16720
17460
  isIgnored;
16721
17461
  chunker;
16722
17462
  constructor(opts) {
16723
- this.root = path32__default.default.resolve(opts.root);
17463
+ this.root = path33__default.default.resolve(opts.root);
16724
17464
  this.db = opts.db;
16725
17465
  this.namespace = opts.namespace ?? "code";
16726
17466
  this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
@@ -16747,19 +17487,19 @@ var WorkspaceIndex = class {
16747
17487
  const walk = async (dir) => {
16748
17488
  let entries;
16749
17489
  try {
16750
- entries = await fs10__default.default.readdir(dir, { withFileTypes: true });
17490
+ entries = await fs11__default.default.readdir(dir, { withFileTypes: true });
16751
17491
  } catch {
16752
17492
  return;
16753
17493
  }
16754
17494
  for (const entry of entries) {
16755
- const abs = path32__default.default.join(dir, entry.name);
17495
+ const abs = path33__default.default.join(dir, entry.name);
16756
17496
  if (entry.isDirectory()) {
16757
17497
  if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
16758
17498
  await walk(abs);
16759
17499
  } else if (entry.isFile()) {
16760
- const ext = path32__default.default.extname(entry.name).slice(1).toLowerCase();
17500
+ const ext = path33__default.default.extname(entry.name).slice(1).toLowerCase();
16761
17501
  if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
16762
- out.push(path32__default.default.relative(this.root, abs));
17502
+ out.push(path33__default.default.relative(this.root, abs));
16763
17503
  }
16764
17504
  }
16765
17505
  };
@@ -16789,10 +17529,10 @@ var WorkspaceIndex = class {
16789
17529
  const entries = [];
16790
17530
  for (const rel of rels) {
16791
17531
  try {
16792
- const abs = path32__default.default.join(this.root, rel);
16793
- const stat = await fs10__default.default.stat(abs);
17532
+ const abs = path33__default.default.join(this.root, rel);
17533
+ const stat = await fs11__default.default.stat(abs);
16794
17534
  if (stat.size > this.maxFileBytes) continue;
16795
- const buf = await fs10__default.default.readFile(abs);
17535
+ const buf = await fs11__default.default.readFile(abs);
16796
17536
  if (buf.includes(0)) continue;
16797
17537
  entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
16798
17538
  } catch {
@@ -17361,7 +18101,7 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
17361
18101
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
17362
18102
  try {
17363
18103
  this.router.setDeadModelStore(new DeadModelStore(
17364
- fileDeadModelPersistence(path32__default.default.join(workspacePath, ".cascade", "dead-models.json"))
18104
+ fileDeadModelPersistence(path33__default.default.join(workspacePath, ".cascade", "dead-models.json"))
17365
18105
  ));
17366
18106
  } catch {
17367
18107
  }
@@ -17369,6 +18109,7 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
17369
18109
  (this.config.providers ?? []).map((p) => p.type)
17370
18110
  );
17371
18111
  this.registerMediaTools(workspacePath);
18112
+ this.registerDocumentTools(workspacePath);
17372
18113
  this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
17373
18114
  this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
17374
18115
  this.router.setWorldStateDB(this.worldStateDB);
@@ -17448,8 +18189,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
17448
18189
  this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
17449
18190
  return;
17450
18191
  }
17451
- const dbPath = ci.dbPath || path32__default.default.join(this.workspacePath, ".cascade", "code-index.db");
17452
- fs29__default.default.mkdirSync(path32__default.default.dirname(dbPath), { recursive: true });
18192
+ const dbPath = ci.dbPath || path33__default.default.join(this.workspacePath, ".cascade", "code-index.db");
18193
+ fs30__default.default.mkdirSync(path33__default.default.dirname(dbPath), { recursive: true });
17453
18194
  const db = new Database2__default.default(dbPath);
17454
18195
  db.pragma("journal_mode = WAL");
17455
18196
  const complete = chatCompleterFromProviders(this.config.providers);
@@ -17789,6 +18530,35 @@ ${input}` }],
17789
18530
  this.toolRegistry.register(tool);
17790
18531
  }
17791
18532
  }
18533
+ /**
18534
+ * Register `generate_document` where there is a real workspace to write into.
18535
+ *
18536
+ * A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
18537
+ * does exactly what it says and writes the model's text verbatim — produced
18538
+ * files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
18539
+ * the conversion (see tools/generate-document.ts for why it isn't folded into
18540
+ * file_write).
18541
+ *
18542
+ * Registered OUTSIDE `enabledTools` for the same reason the media tools are:
18543
+ * that allowlist is a blast-radius control for tools that reach the machine
18544
+ * or the network, and this one only writes the artifact the user asked for,
18545
+ * into the workspace the run already owns. `disabledTools` still removes it.
18546
+ * A host with no filesystem (the cloud) has its own browser-side exporter and
18547
+ * simply never reaches this path — `buildDocumentTools` returns nothing
18548
+ * without a file reader, mirroring `transcribe_audio`.
18549
+ */
18550
+ registerDocumentTools(workspacePath) {
18551
+ const denied = new Set(this.config.tools?.disabledTools ?? []);
18552
+ const tools = buildDocumentTools(async (p) => {
18553
+ const fsp = await import('fs/promises');
18554
+ return new Uint8Array(await fsp.readFile(p));
18555
+ });
18556
+ for (const tool of tools) {
18557
+ if (denied.has(tool.name)) continue;
18558
+ tool.setWorkspaceRoot(workspacePath);
18559
+ this.toolRegistry.register(tool);
18560
+ }
18561
+ }
17792
18562
  /**
17793
18563
  * Supply per-model thumbs-up/down counts so Auto routing can weigh how models
17794
18564
  * have actually performed for this user, on top of public benchmarks.
@@ -18701,14 +19471,14 @@ ${finalOutput}` : ""
18701
19471
  init_constants();
18702
19472
  var CLOUD_SESSION_FILE = "cloud-session.json";
18703
19473
  function globalDir(homeDir = os10__default.default.homedir()) {
18704
- return path32__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
19474
+ return path33__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
18705
19475
  }
18706
19476
  function cloudSessionPath(dir = globalDir()) {
18707
- return path32__default.default.join(dir, CLOUD_SESSION_FILE);
19477
+ return path33__default.default.join(dir, CLOUD_SESSION_FILE);
18708
19478
  }
18709
19479
  function loadCloudSession(dir = globalDir()) {
18710
19480
  try {
18711
- const raw = fs29__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
19481
+ const raw = fs30__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
18712
19482
  const parsed = JSON.parse(raw);
18713
19483
  if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
18714
19484
  return parsed;
@@ -18717,17 +19487,17 @@ function loadCloudSession(dir = globalDir()) {
18717
19487
  }
18718
19488
  }
18719
19489
  function saveCloudSession(session, dir = globalDir()) {
18720
- fs29__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
19490
+ fs30__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
18721
19491
  const file = cloudSessionPath(dir);
18722
- fs29__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
19492
+ fs30__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
18723
19493
  try {
18724
- fs29__default.default.chmodSync(file, 384);
19494
+ fs30__default.default.chmodSync(file, 384);
18725
19495
  } catch {
18726
19496
  }
18727
19497
  }
18728
19498
  function clearCloudSession(dir = globalDir()) {
18729
19499
  try {
18730
- fs29__default.default.rmSync(cloudSessionPath(dir));
19500
+ fs30__default.default.rmSync(cloudSessionPath(dir));
18731
19501
  } catch {
18732
19502
  }
18733
19503
  }
@@ -18899,16 +19669,16 @@ var CloudClient = class _CloudClient {
18899
19669
  return (await this.refresh()).accessToken;
18900
19670
  }
18901
19671
  /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
18902
- async authed(path37, init = {}) {
18903
- const call = (token) => fetch(this.url(path37), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
19672
+ async authed(path38, init = {}) {
19673
+ const call = (token) => fetch(this.url(path38), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
18904
19674
  let res = await call(await this.accessToken());
18905
19675
  if (res.status === 401) res = await call((await this.refresh()).accessToken);
18906
19676
  if (!res.ok) throw new Error(`Request failed (${res.status}).`);
18907
19677
  const text = await res.text();
18908
19678
  return text ? JSON.parse(text) : {};
18909
19679
  }
18910
- authedGet(path37) {
18911
- return this.authed(path37);
19680
+ authedGet(path38) {
19681
+ return this.authed(path38);
18912
19682
  }
18913
19683
  async fetchMe(token) {
18914
19684
  const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
@@ -20683,10 +21453,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
20683
21453
  }
20684
21454
  } catch {
20685
21455
  }
20686
- const configPath = path32__default.default.join(workspacePath, ".cascade", "config.json");
21456
+ const configPath = path33__default.default.join(workspacePath, ".cascade", "config.json");
20687
21457
  try {
20688
- await fs10__default.default.mkdir(path32__default.default.dirname(configPath), { recursive: true });
20689
- await fs10__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
21458
+ await fs11__default.default.mkdir(path33__default.default.dirname(configPath), { recursive: true });
21459
+ await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
20690
21460
  } catch (err) {
20691
21461
  const msg = err instanceof Error ? err.message : String(err);
20692
21462
  dispatch({
@@ -20737,9 +21507,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
20737
21507
  if (msg.includes("non-text parts")) return;
20738
21508
  originalLog(...args);
20739
21509
  };
20740
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
21510
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
20741
21511
  storeRef.current = store;
20742
- globalStoreRef.current = new MemoryStore(path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
21512
+ globalStoreRef.current = new MemoryStore(path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
20743
21513
  const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
20744
21514
  setIdentities(identityRows);
20745
21515
  let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
@@ -20807,14 +21577,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
20807
21577
  onThemeChange: (name) => setTheme(getTheme(name)),
20808
21578
  onExport: async (fmt) => {
20809
21579
  const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
20810
- const exportPath = path32__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
21580
+ const exportPath = path33__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
20811
21581
  if (fmt === "json") {
20812
- await fs10__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
21582
+ await fs11__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
20813
21583
  } else {
20814
21584
  const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
20815
21585
 
20816
21586
  ${msg.content}`).join("\n\n");
20817
- await fs10__default.default.writeFile(exportPath, markdown, "utf-8");
21587
+ await fs11__default.default.writeFile(exportPath, markdown, "utf-8");
20818
21588
  }
20819
21589
  },
20820
21590
  onRollback: async () => {
@@ -20824,7 +21594,7 @@ ${msg.content}`).join("\n\n");
20824
21594
  if (!snapshots.length) return "No file snapshots found for this session.";
20825
21595
  for (const { filePath, content } of snapshots) {
20826
21596
  try {
20827
- await fs10__default.default.writeFile(filePath, content, "utf-8");
21597
+ await fs11__default.default.writeFile(filePath, content, "utf-8");
20828
21598
  } catch (err) {
20829
21599
  console.error(`Restore failed: ${filePath}`, err);
20830
21600
  }
@@ -21705,9 +22475,9 @@ function stringifySlashOutput(val) {
21705
22475
  }
21706
22476
  async function searchSessionsAndMessages(query, workspacePath) {
21707
22477
  if (!query) return "Usage: /search <query>";
21708
- const dbPath = path32__default.default.join(workspacePath, CASCADE_DB_FILE);
22478
+ const dbPath = path33__default.default.join(workspacePath, CASCADE_DB_FILE);
21709
22479
  try {
21710
- await fs10__default.default.access(dbPath);
22480
+ await fs11__default.default.access(dbPath);
21711
22481
  } catch {
21712
22482
  return "No database found. Start a conversation first.";
21713
22483
  }
@@ -21739,7 +22509,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
21739
22509
  async function diagnoseRuntime(config, workspacePath) {
21740
22510
  const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
21741
22511
  const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
21742
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
22512
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
21743
22513
  try {
21744
22514
  const sessions2 = store.listSessions(void 0, 3);
21745
22515
  return [
@@ -21758,7 +22528,7 @@ async function diagnoseRuntime(config, workspacePath) {
21758
22528
  }
21759
22529
  async function showRecentLogs(args, workspacePath) {
21760
22530
  const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
21761
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
22531
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
21762
22532
  try {
21763
22533
  const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
21764
22534
  if (!logs.length) return "No recent runtime logs.";
@@ -21770,7 +22540,7 @@ async function showRecentLogs(args, workspacePath) {
21770
22540
  async function loadSessionSnapshot(args, workspacePath) {
21771
22541
  const sessionId = args[0];
21772
22542
  if (!sessionId) return "Usage: /resume <sessionId>";
21773
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
22543
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
21774
22544
  try {
21775
22545
  const session = store.getSession(sessionId);
21776
22546
  if (!session) return `Session not found: ${sessionId}`;
@@ -22198,10 +22968,10 @@ function SetupWizard({ workspacePath, onComplete }) {
22198
22968
  ...anyAuto ? { cascadeAuto: true } : {}
22199
22969
  };
22200
22970
  const config = CascadeConfigSchema.parse(rawConfig);
22201
- const configDir = path32__default.default.join(workspacePath, ".cascade");
22202
- await fs10__default.default.mkdir(configDir, { recursive: true });
22203
- const configPath = path32__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
22204
- await fs10__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
22971
+ const configDir = path33__default.default.join(workspacePath, ".cascade");
22972
+ await fs11__default.default.mkdir(configDir, { recursive: true });
22973
+ const configPath = path33__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
22974
+ await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
22205
22975
  savedConfigRef.current = config;
22206
22976
  dispatchRef.current({ type: "GO_DONE" });
22207
22977
  } catch (err) {
@@ -22542,14 +23312,14 @@ function printTelemetryBanner() {
22542
23312
  async function initCommand(workspacePath = process.cwd()) {
22543
23313
  const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
22544
23314
  try {
22545
- const configDir = path32__default.default.join(workspacePath, ".cascade");
22546
- await fs10__default.default.mkdir(configDir, { recursive: true });
22547
- const mdPath = path32__default.default.join(workspacePath, "CASCADE.md");
23315
+ const configDir = path33__default.default.join(workspacePath, ".cascade");
23316
+ await fs11__default.default.mkdir(configDir, { recursive: true });
23317
+ const mdPath = path33__default.default.join(workspacePath, "CASCADE.md");
22548
23318
  if (!await fileExists(mdPath)) {
22549
23319
  await createDefaultCascadeMd(workspacePath);
22550
23320
  spin.succeed(chalk7__default.default.green("Created CASCADE.md"));
22551
23321
  }
22552
- const ignorePath = path32__default.default.join(workspacePath, ".cascadeignore");
23322
+ const ignorePath = path33__default.default.join(workspacePath, ".cascadeignore");
22553
23323
  if (!await fileExists(ignorePath)) {
22554
23324
  await createDefaultIgnoreFile(workspacePath);
22555
23325
  spin.succeed(chalk7__default.default.green("Created .cascadeignore"));
@@ -22558,7 +23328,7 @@ async function initCommand(workspacePath = process.cwd()) {
22558
23328
  console.log();
22559
23329
  console.log(chalk7__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
22560
23330
  console.log();
22561
- const configPath = path32__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
23331
+ const configPath = path33__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
22562
23332
  if (await fileExists(configPath)) {
22563
23333
  console.log(chalk7__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
22564
23334
  console.log();
@@ -22577,7 +23347,7 @@ async function initCommand(workspacePath = process.cwd()) {
22577
23347
  }
22578
23348
  async function fileExists(p) {
22579
23349
  try {
22580
- await fs10__default.default.access(p);
23350
+ await fs11__default.default.access(p);
22581
23351
  return true;
22582
23352
  } catch {
22583
23353
  return false;
@@ -22589,7 +23359,7 @@ init_constants();
22589
23359
  var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
22590
23360
  async function readJson(file) {
22591
23361
  try {
22592
- const raw = await fs10__default.default.readFile(file, "utf-8");
23362
+ const raw = await fs11__default.default.readFile(file, "utf-8");
22593
23363
  return JSON.parse(raw);
22594
23364
  } catch {
22595
23365
  return null;
@@ -22616,7 +23386,7 @@ function fromEnv(env) {
22616
23386
  return out;
22617
23387
  }
22618
23388
  async function fromClaudeCode(home) {
22619
- const file = path32__default.default.join(home, ".claude", ".credentials.json");
23389
+ const file = path33__default.default.join(home, ".claude", ".credentials.json");
22620
23390
  const data = await readJson(file);
22621
23391
  if (!data) return [];
22622
23392
  const oauth = data["claudeAiOauth"];
@@ -22639,7 +23409,7 @@ async function fromClaudeCode(home) {
22639
23409
  return [];
22640
23410
  }
22641
23411
  async function fromCodex(home) {
22642
- const file = path32__default.default.join(home, ".codex", "auth.json");
23412
+ const file = path33__default.default.join(home, ".codex", "auth.json");
22643
23413
  const data = await readJson(file);
22644
23414
  if (!data) return [];
22645
23415
  const apiKey = str(data["OPENAI_API_KEY"]);
@@ -22662,7 +23432,7 @@ async function fromCodex(home) {
22662
23432
  return [];
22663
23433
  }
22664
23434
  async function fromGemini(home) {
22665
- const file = path32__default.default.join(home, ".gemini", "oauth_creds.json");
23435
+ const file = path33__default.default.join(home, ".gemini", "oauth_creds.json");
22666
23436
  const data = await readJson(file);
22667
23437
  const accessToken = str(data?.["access_token"]);
22668
23438
  if (!accessToken) return [];
@@ -22678,7 +23448,7 @@ async function fromGemini(home) {
22678
23448
  }
22679
23449
  async function fromCopilot(home) {
22680
23450
  for (const name of ["apps.json", "hosts.json"]) {
22681
- const file = path32__default.default.join(home, ".config", "github-copilot", name);
23451
+ const file = path33__default.default.join(home, ".config", "github-copilot", name);
22682
23452
  const data = await readJson(file);
22683
23453
  if (!data) continue;
22684
23454
  for (const value of Object.values(data)) {
@@ -22732,7 +23502,7 @@ async function doctorCommand() {
22732
23502
  checks.push({
22733
23503
  label: "Cascade config",
22734
23504
  ok: true,
22735
- detail: `Loaded ${path32__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
23505
+ detail: `Loaded ${path33__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
22736
23506
  });
22737
23507
  const providers = [
22738
23508
  { type: "anthropic", name: "Anthropic" },
@@ -22843,7 +23613,7 @@ async function checkEndpoint(url) {
22843
23613
  }
22844
23614
  }
22845
23615
  async function indexCommand(dirPath) {
22846
- const workspace = path32__default.default.resolve(dirPath || process.cwd());
23616
+ const workspace = path33__default.default.resolve(dirPath || process.cwd());
22847
23617
  const cm = new ConfigManager(workspace);
22848
23618
  await cm.load();
22849
23619
  const config = cm.getConfig();
@@ -22854,8 +23624,8 @@ async function indexCommand(dirPath) {
22854
23624
  process.exitCode = 1;
22855
23625
  return;
22856
23626
  }
22857
- const dbPath = config.codeIndex?.dbPath || path32__default.default.join(workspace, ".cascade", "code-index.db");
22858
- fs29__default.default.mkdirSync(path32__default.default.dirname(dbPath), { recursive: true });
23627
+ const dbPath = config.codeIndex?.dbPath || path33__default.default.join(workspace, ".cascade", "code-index.db");
23628
+ fs30__default.default.mkdirSync(path33__default.default.dirname(dbPath), { recursive: true });
22859
23629
  const db = new Database2__default.default(dbPath);
22860
23630
  db.pragma("journal_mode = WAL");
22861
23631
  const complete = chatCompleterFromProviders(config.providers);
@@ -23344,7 +24114,7 @@ function openBrowser2(url) {
23344
24114
  }
23345
24115
  function storePathFor(name) {
23346
24116
  const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
23347
- return path32__default.default.join(os10__default.default.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
24117
+ return path33__default.default.join(os10__default.default.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
23348
24118
  }
23349
24119
  function defaultName(url) {
23350
24120
  try {
@@ -23846,7 +24616,7 @@ function aggregateCostStats(sessions2, opts = {}) {
23846
24616
  }
23847
24617
 
23848
24618
  // src/dashboard/server.ts
23849
- var __dirname$1 = path32__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
24619
+ var __dirname$1 = path33__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
23850
24620
  var ORPHANED_ESCALATION_GRACE_MS = 3e4;
23851
24621
  var DashboardServer = class {
23852
24622
  app;
@@ -24126,14 +24896,14 @@ var DashboardServer = class {
24126
24896
  */
24127
24897
  persistConfig() {
24128
24898
  try {
24129
- const configPath = path32__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
24130
- fs29__default.default.mkdirSync(path32__default.default.dirname(configPath), { recursive: true });
24131
- fs29__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
24899
+ const configPath = path33__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
24900
+ fs30__default.default.mkdirSync(path33__default.default.dirname(configPath), { recursive: true });
24901
+ fs30__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
24132
24902
  } catch (err) {
24133
24903
  console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
24134
24904
  }
24135
24905
  try {
24136
- saveGlobalCredentials(path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
24906
+ saveGlobalCredentials(path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
24137
24907
  } catch (err) {
24138
24908
  console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
24139
24909
  }
@@ -24148,15 +24918,15 @@ var DashboardServer = class {
24148
24918
  resolveDashboardSecret() {
24149
24919
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
24150
24920
  if (fromConfig) return fromConfig;
24151
- const secretPath = path32__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
24921
+ const secretPath = path33__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
24152
24922
  try {
24153
- if (fs29__default.default.existsSync(secretPath)) {
24154
- const existing = fs29__default.default.readFileSync(secretPath, "utf-8").trim();
24923
+ if (fs30__default.default.existsSync(secretPath)) {
24924
+ const existing = fs30__default.default.readFileSync(secretPath, "utf-8").trim();
24155
24925
  if (existing.length >= 16) return existing;
24156
24926
  }
24157
24927
  const generated = crypto.randomUUID();
24158
- fs29__default.default.mkdirSync(path32__default.default.dirname(secretPath), { recursive: true });
24159
- fs29__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
24928
+ fs30__default.default.mkdirSync(path33__default.default.dirname(secretPath), { recursive: true });
24929
+ fs30__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
24160
24930
  if (this.config.dashboard.auth) {
24161
24931
  console.warn(
24162
24932
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -24183,7 +24953,7 @@ var DashboardServer = class {
24183
24953
  // ── Setup ─────────────────────────────────────
24184
24954
  getGlobalStore() {
24185
24955
  if (!this.globalStore) {
24186
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24956
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24187
24957
  this.globalStore = new MemoryStore(globalDbPath);
24188
24958
  }
24189
24959
  return this.globalStore;
@@ -24563,12 +25333,12 @@ ${prompt}`;
24563
25333
  }
24564
25334
  }
24565
25335
  watchRuntimeChanges() {
24566
- const workspaceDbPath = path32__default.default.join(this.workspacePath, CASCADE_DB_FILE);
24567
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25336
+ const workspaceDbPath = path33__default.default.join(this.workspacePath, CASCADE_DB_FILE);
25337
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24568
25338
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
24569
25339
  for (const watchPath of watchPaths) {
24570
- if (!fs29__default.default.existsSync(watchPath)) continue;
24571
- fs29__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
25340
+ if (!fs30__default.default.existsSync(watchPath)) continue;
25341
+ fs30__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
24572
25342
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
24573
25343
  });
24574
25344
  }
@@ -24690,7 +25460,7 @@ ${prompt}`;
24690
25460
  const sessionId = req.params.id;
24691
25461
  this.store.deleteSession(sessionId);
24692
25462
  this.store.deleteRuntimeSession(sessionId);
24693
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25463
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24694
25464
  const globalStore = new MemoryStore(globalDbPath);
24695
25465
  try {
24696
25466
  globalStore.deleteRuntimeSession(sessionId);
@@ -24783,7 +25553,7 @@ ${prompt}`;
24783
25553
  });
24784
25554
  this.app.delete("/api/sessions", auth2, (req, res) => {
24785
25555
  const body = req.body;
24786
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25556
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24787
25557
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
24788
25558
  const globalStore = new MemoryStore(globalDbPath);
24789
25559
  try {
@@ -24806,7 +25576,7 @@ ${prompt}`;
24806
25576
  });
24807
25577
  this.app.delete("/api/runtime", auth2, (_req, res) => {
24808
25578
  this.store.deleteAllRuntimeNodes();
24809
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25579
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24810
25580
  const globalStore = new MemoryStore(globalDbPath);
24811
25581
  try {
24812
25582
  globalStore.deleteAllRuntimeNodes();
@@ -24892,12 +25662,12 @@ ${prompt}`;
24892
25662
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
24893
25663
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
24894
25664
  try {
24895
- const configPath = path32__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
24896
- const existing = fs29__default.default.existsSync(configPath) ? JSON.parse(fs29__default.default.readFileSync(configPath, "utf-8")) : {};
25665
+ const configPath = path33__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
25666
+ const existing = fs30__default.default.existsSync(configPath) ? JSON.parse(fs30__default.default.readFileSync(configPath, "utf-8")) : {};
24897
25667
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
24898
25668
  const tmp = configPath + ".tmp";
24899
- fs29__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
24900
- fs29__default.default.renameSync(tmp, configPath);
25669
+ fs30__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
25670
+ fs30__default.default.renameSync(tmp, configPath);
24901
25671
  res.json({ ok: true });
24902
25672
  } catch (err) {
24903
25673
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -24925,7 +25695,7 @@ ${prompt}`;
24925
25695
  this.app.get("/api/runtime", auth2, (req, res) => {
24926
25696
  const scope = req.query["scope"] ?? "workspace";
24927
25697
  if (scope === "global") {
24928
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
25698
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
24929
25699
  const globalStore = new MemoryStore(globalDbPath);
24930
25700
  try {
24931
25701
  res.json({
@@ -25266,13 +26036,13 @@ ${prompt}`;
25266
26036
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
25267
26037
  }
25268
26038
  });
25269
- const prodPath = path32__default.default.resolve(__dirname$1, "../web/dist");
25270
- const devPath = path32__default.default.resolve(__dirname$1, "../../web/dist");
25271
- const webDistPath = fs29__default.default.existsSync(prodPath) ? prodPath : devPath;
25272
- if (fs29__default.default.existsSync(webDistPath)) {
26039
+ const prodPath = path33__default.default.resolve(__dirname$1, "../web/dist");
26040
+ const devPath = path33__default.default.resolve(__dirname$1, "../../web/dist");
26041
+ const webDistPath = fs30__default.default.existsSync(prodPath) ? prodPath : devPath;
26042
+ if (fs30__default.default.existsSync(webDistPath)) {
25273
26043
  this.app.use(express__default.default.static(webDistPath));
25274
26044
  this.app.get("*", (_req, res) => {
25275
- res.sendFile(path32__default.default.join(webDistPath, "index.html"));
26045
+ res.sendFile(path33__default.default.join(webDistPath, "index.html"));
25276
26046
  });
25277
26047
  } else {
25278
26048
  this.app.get("/", (_req, res) => {
@@ -25293,7 +26063,7 @@ init_constants();
25293
26063
  async function dashboardCommand(config, workspacePath = process.cwd()) {
25294
26064
  const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
25295
26065
  const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
25296
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
26066
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
25297
26067
  const server = new DashboardServer(config, store, workspacePath);
25298
26068
  server.watchRuntimeChanges();
25299
26069
  const gThis = globalThis;
@@ -25325,7 +26095,7 @@ init_constants();
25325
26095
  function makeIdentityCommand(workspacePath = process.cwd()) {
25326
26096
  const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
25327
26097
  identity.command("list").description("List all available identities").action(() => {
25328
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
26098
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
25329
26099
  try {
25330
26100
  const identities = store.listIdentities();
25331
26101
  console.log(chalk7__default.default.bold("\n Identities:"));
@@ -25345,7 +26115,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
25345
26115
  }
25346
26116
  });
25347
26117
  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) => {
25348
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
26118
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
25349
26119
  try {
25350
26120
  if (options.default) {
25351
26121
  const existingDefault = store.getDefaultIdentity();
@@ -25371,7 +26141,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
25371
26141
  }
25372
26142
  });
25373
26143
  identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
25374
- const store = new MemoryStore(path32__default.default.join(workspacePath, CASCADE_DB_FILE));
26144
+ const store = new MemoryStore(path33__default.default.join(workspacePath, CASCADE_DB_FILE));
25375
26145
  try {
25376
26146
  const identities = store.listIdentities();
25377
26147
  const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
@@ -25520,11 +26290,11 @@ async function exportCommand(options = {}) {
25520
26290
  let store;
25521
26291
  try {
25522
26292
  const workspacePath = options.workspacePath ?? process.cwd();
25523
- const workspaceDbPath = path32__default.default.join(workspacePath, CASCADE_DB_FILE);
25524
- const globalDbPath = path32__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
26293
+ const workspaceDbPath = path33__default.default.join(workspacePath, CASCADE_DB_FILE);
26294
+ const globalDbPath = path33__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
25525
26295
  let dbPath = globalDbPath;
25526
26296
  try {
25527
- await fs10__default.default.access(workspaceDbPath);
26297
+ await fs11__default.default.access(workspaceDbPath);
25528
26298
  dbPath = workspaceDbPath;
25529
26299
  } catch {
25530
26300
  }
@@ -25563,8 +26333,8 @@ async function exportCommand(options = {}) {
25563
26333
  const ext = format === "json" ? ".json" : ".md";
25564
26334
  const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
25565
26335
  const defaultFile = `cascade-export-${safeName}${ext}`;
25566
- const outPath = options.output ? path32__default.default.resolve(options.output) : path32__default.default.join(process.cwd(), defaultFile);
25567
- await fs10__default.default.writeFile(outPath, content, "utf-8");
26336
+ const outPath = options.output ? path33__default.default.resolve(options.output) : path33__default.default.join(process.cwd(), defaultFile);
26337
+ await fs11__default.default.writeFile(outPath, content, "utf-8");
25568
26338
  spin.succeed(chalk7__default.default.green(`Exported to ${chalk7__default.default.white(outPath)}`));
25569
26339
  const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
25570
26340
  console.log();
@@ -25791,11 +26561,11 @@ async function statsCommand() {
25791
26561
  dotenv__default.default.config();
25792
26562
  function warnIfBuildIsStale() {
25793
26563
  try {
25794
- const here = path32__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
26564
+ const here = path33__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
25795
26565
  for (const rel of ["..", "../.."]) {
25796
- const pkgPath = path32__default.default.join(here, rel, "package.json");
25797
- if (!fs29__default.default.existsSync(pkgPath)) continue;
25798
- const pkg = JSON.parse(fs29__default.default.readFileSync(pkgPath, "utf-8"));
26566
+ const pkgPath = path33__default.default.join(here, rel, "package.json");
26567
+ if (!fs30__default.default.existsSync(pkgPath)) continue;
26568
+ const pkg = JSON.parse(fs30__default.default.readFileSync(pkgPath, "utf-8"));
25799
26569
  if (pkg.name !== "cascade-ai") continue;
25800
26570
  if (pkg.version && pkg.version !== CASCADE_VERSION) {
25801
26571
  console.error(