cascade-ai 0.66.2 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/cli.cjs +1769 -437
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1762 -430
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +70858 -1948
- package/dist/index.cjs +1746 -378
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +564 -12
- package/dist/index.d.ts +564 -12
- package/dist/index.js +1707 -372
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
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
|
|
13
|
-
var
|
|
12
|
+
var path34 = require('path');
|
|
13
|
+
var fs31 = 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
|
|
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
|
|
81
|
-
var
|
|
80
|
+
var path34__default = /*#__PURE__*/_interopDefault(path34);
|
|
81
|
+
var fs31__default = /*#__PURE__*/_interopDefault(fs31);
|
|
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
|
|
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.
|
|
1813
|
+
CASCADE_VERSION = "0.68.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 =
|
|
3936
|
-
if (!
|
|
3937
|
-
|
|
3935
|
+
const cascadeDir = path34__default.default.join(workspacePath, ".cascade");
|
|
3936
|
+
if (!fs31__default.default.existsSync(cascadeDir)) {
|
|
3937
|
+
fs31__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
3938
3938
|
}
|
|
3939
|
-
this.keyPath =
|
|
3940
|
-
this.dbPath =
|
|
3939
|
+
this.keyPath = path34__default.default.join(cascadeDir, "audit_log.key");
|
|
3940
|
+
this.dbPath = path34__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 (
|
|
3971
|
-
this.encryptionKey =
|
|
3970
|
+
if (fs31__default.default.existsSync(this.keyPath)) {
|
|
3971
|
+
this.encryptionKey = fs31__default.default.readFileSync(this.keyPath);
|
|
3972
3972
|
} else {
|
|
3973
3973
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
3974
|
-
|
|
3974
|
+
fs31__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 =
|
|
4052
|
+
const dumpPath = path34__default.default.join(os10__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
4053
4053
|
const entries = this.getAllLogs();
|
|
4054
|
-
|
|
4054
|
+
fs31__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 &&
|
|
4109
|
+
if (password && fs31__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 (!
|
|
4128
|
+
if (!fs31__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 (!
|
|
4142
|
+
if (!fs31__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 (!
|
|
4200
|
+
if (!fs31__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
|
-
|
|
4223
|
-
|
|
4222
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(this.storePath), { recursive: true });
|
|
4223
|
+
fs31__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
|
-
|
|
4234
|
-
|
|
4233
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(this.storePath), { recursive: true });
|
|
4234
|
+
fs31__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4235
4235
|
}
|
|
4236
4236
|
readRaw() {
|
|
4237
|
-
const buf =
|
|
4237
|
+
const buf = fs31__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 =
|
|
4270
|
+
const filePath = path34__default.default.join(workspacePath, ".cascadeignore");
|
|
4271
4271
|
try {
|
|
4272
|
-
const content = await
|
|
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 ?
|
|
4281
|
+
const relative = workspacePath ? path34__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 =
|
|
4292
|
+
const filePath = path34__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
|
|
4319
|
+
await fs11__default.default.writeFile(filePath, content, "utf-8");
|
|
4320
4320
|
}
|
|
4321
4321
|
async function loadCascadeMd(workspacePath) {
|
|
4322
|
-
const filePath =
|
|
4322
|
+
const filePath = path34__default.default.join(workspacePath, "CASCADE.md");
|
|
4323
4323
|
try {
|
|
4324
|
-
const raw = await
|
|
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 =
|
|
4351
|
+
const filePath = path34__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
|
|
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
|
-
|
|
4386
|
+
fs31__default.default.mkdirSync(path34__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
|
|
5638
|
+
return path34__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 =
|
|
5651
|
+
const raw = fs31__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
|
-
|
|
5663
|
+
fs31__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
|
-
|
|
5667
|
-
|
|
5666
|
+
fs31__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5667
|
+
fs31__default.default.renameSync(tmp, filePath);
|
|
5668
5668
|
try {
|
|
5669
|
-
|
|
5669
|
+
fs31__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 ??
|
|
5887
|
+
this.globalDir = globalDirOverride ?? path34__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(
|
|
5902
|
-
this.store = new MemoryStore(
|
|
5901
|
+
this.keystore = new Keystore(path34__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
5902
|
+
this.store = new MemoryStore(path34__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 =
|
|
5928
|
-
await
|
|
5929
|
-
await
|
|
5927
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5928
|
+
await fs11__default.default.mkdir(path34__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 =
|
|
5964
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5965
5965
|
try {
|
|
5966
|
-
const raw = await
|
|
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 (!
|
|
6967
|
-
const raw = JSON.parse(
|
|
6966
|
+
if (!fs31.existsSync(filePath)) return [];
|
|
6967
|
+
const raw = JSON.parse(fs31.readFileSync(filePath, "utf-8"));
|
|
6968
6968
|
return Array.isArray(raw) ? raw : [];
|
|
6969
6969
|
},
|
|
6970
6970
|
save(records) {
|
|
6971
|
-
|
|
6972
|
-
|
|
6971
|
+
fs31.mkdirSync(path34.dirname(filePath), { recursive: true });
|
|
6972
|
+
fs31.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 =
|
|
7131
|
+
var DEFAULT_CACHE_FILE = path34__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
|
|
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
|
|
7278
|
+
await fs11__default.default.mkdir(path34__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
|
|
7285
|
+
await fs11__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
7286
7286
|
} catch {
|
|
7287
7287
|
}
|
|
7288
7288
|
}
|
|
@@ -9068,6 +9068,79 @@ function describeProviderError(c, modelId) {
|
|
|
9068
9068
|
}
|
|
9069
9069
|
}
|
|
9070
9070
|
|
|
9071
|
+
// src/core/verification/acceptance.ts
|
|
9072
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
9073
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
9074
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
9075
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
9076
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
9077
|
+
function targetFile(criterion, ownedFiles) {
|
|
9078
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
9079
|
+
if (mentioned.length > 1) return null;
|
|
9080
|
+
if (mentioned.length === 1) {
|
|
9081
|
+
const candidate = mentioned[0];
|
|
9082
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
9083
|
+
}
|
|
9084
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
9085
|
+
}
|
|
9086
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
9087
|
+
const results = [];
|
|
9088
|
+
for (const criterion of criteria) {
|
|
9089
|
+
const text = criterion.trim();
|
|
9090
|
+
if (!text) {
|
|
9091
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9092
|
+
continue;
|
|
9093
|
+
}
|
|
9094
|
+
if (NEGATION.test(text)) {
|
|
9095
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9096
|
+
continue;
|
|
9097
|
+
}
|
|
9098
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
9099
|
+
const wantsExists = EXISTS.test(text);
|
|
9100
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
9101
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
9102
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9103
|
+
continue;
|
|
9104
|
+
}
|
|
9105
|
+
const file = targetFile(text, ownedFiles);
|
|
9106
|
+
if (!file) {
|
|
9107
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9108
|
+
continue;
|
|
9109
|
+
}
|
|
9110
|
+
const stat = await probe.stat(file);
|
|
9111
|
+
if (!stat) {
|
|
9112
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
9113
|
+
continue;
|
|
9114
|
+
}
|
|
9115
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
9116
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
9117
|
+
continue;
|
|
9118
|
+
}
|
|
9119
|
+
if (needle) {
|
|
9120
|
+
const content = await probe.read(file);
|
|
9121
|
+
if (content === null) {
|
|
9122
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
9123
|
+
continue;
|
|
9124
|
+
}
|
|
9125
|
+
const found = content.includes(needle);
|
|
9126
|
+
results.push({
|
|
9127
|
+
criterion,
|
|
9128
|
+
verdict: found ? "passed" : "failed",
|
|
9129
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
9130
|
+
});
|
|
9131
|
+
continue;
|
|
9132
|
+
}
|
|
9133
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
9134
|
+
}
|
|
9135
|
+
return results;
|
|
9136
|
+
}
|
|
9137
|
+
function undecided(results) {
|
|
9138
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
9139
|
+
}
|
|
9140
|
+
function failures(results) {
|
|
9141
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
9142
|
+
}
|
|
9143
|
+
|
|
9071
9144
|
// src/core/tiers/t3-worker.ts
|
|
9072
9145
|
var CriticalToolError = class extends Error {
|
|
9073
9146
|
constructor(message, toolName) {
|
|
@@ -9110,7 +9183,8 @@ var KNOWN_TOOLS = [
|
|
|
9110
9183
|
"generate_image",
|
|
9111
9184
|
"generate_video",
|
|
9112
9185
|
"generate_speech",
|
|
9113
|
-
"transcribe_audio"
|
|
9186
|
+
"transcribe_audio",
|
|
9187
|
+
"generate_document"
|
|
9114
9188
|
];
|
|
9115
9189
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
9116
9190
|
"generate_image",
|
|
@@ -9127,18 +9201,45 @@ function buildWorkerRules(has) {
|
|
|
9127
9201
|
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
9202
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
9129
9203
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
9204
|
+
// A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
|
|
9205
|
+
// file_write does exactly what it promises — writes the model's characters
|
|
9206
|
+
// verbatim — so without this rule the worker "wrote" a Word document that
|
|
9207
|
+
// Word correctly reported as corrupted. Stated as an explicit prohibition
|
|
9208
|
+
// on the tool it would otherwise reach for, because "use generate_document"
|
|
9209
|
+
// alone competes with a file_write habit the model already has.
|
|
9210
|
+
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.',
|
|
9211
|
+
// The alternative to this rule is not "a worse chart" — it is a paragraph
|
|
9212
|
+
// describing a chart that does not exist, which is what real decks came
|
|
9213
|
+
// back with. A `chart:` block is checkable, carries the exact numbers, and
|
|
9214
|
+
// becomes a genuine editable PowerPoint chart object.
|
|
9215
|
+
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
9216
|
// Without this the model "writes the image" as prose — a bracketed
|
|
9131
9217
|
// description sitting in the deck where the picture should be — even with a
|
|
9132
9218
|
// working image model registered. Naming the reference syntax matters as
|
|
9133
9219
|
// much as naming the tool: a generated image nobody embeds is still a
|
|
9134
|
-
// missing image.
|
|
9220
|
+
// missing image. It also has to say the reference must stand ALONE on its
|
|
9221
|
+
// line, because only a standalone reference is parsed as a picture (see
|
|
9222
|
+
// core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
|
|
9223
|
+
// silently degrades to caption text.
|
|
9135
9224
|
//
|
|
9136
|
-
// Scoped to PowerPoint/Word specifically: those are the only
|
|
9137
|
-
//
|
|
9138
|
-
//
|
|
9139
|
-
//
|
|
9140
|
-
//
|
|
9141
|
-
has("generate_image") &&
|
|
9225
|
+
// Scoped to PowerPoint/Word specifically: those are the only renderers that
|
|
9226
|
+
// actually embed a Markdown image reference. PDF and plain-text/Markdown
|
|
9227
|
+
// deliverables flatten a reference straight to caption text — telling the
|
|
9228
|
+
// model to call generate_image for THOSE just pays for an image nobody
|
|
9229
|
+
// ever sees.
|
|
9230
|
+
has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
|
|
9231
|
+
// standalone-line parser constraint, says to generate BEFORE writing the
|
|
9232
|
+
// document, and points data-driven visuals at a ```chart: block rather
|
|
9233
|
+
// than a Markdown table, which is only the right advice now that chart
|
|
9234
|
+
// blocks render as real chart objects.
|
|
9235
|
+
'- When a slide deck (PowerPoint) or Word document deliverable calls for a decorative image, photo or illustration, you MUST call the "generate_image" tool \u2014 once per image the deliverable needs, BEFORE you write the document \u2014 and then put each result on a line of its OWN using Markdown image syntax: , where "location" is exactly the string the tool reported back. A reference inside a sentence stays prose and no picture appears. NEVER write a text placeholder or a bracketed description such as [image: a cat] in its place, and never state that an image is included when you did not generate one. Do NOT use "generate_image" for a chart, graph or diagram that must show exact data (numbers, axes, labels) \u2014 an image model cannot guarantee those values are correct; emit a ```chart: block instead. For any OTHER deliverable format (PDF, plain text, code, etc.) do NOT call "generate_image" \u2014 those cannot embed the result, so describe the visual in words instead.',
|
|
9236
|
+
// The other half of image reliability, and the half no amount of
|
|
9237
|
+
// instruction-tightening could fix: generate_image only exists when an
|
|
9238
|
+
// OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
|
|
9239
|
+
// With neither, the model was never given a choice — and a model that
|
|
9240
|
+
// doesn't know that writes "[illustration of a cat]" and moves on. Said out
|
|
9241
|
+
// loud, up front, so the fallback is a real chart or honest prose.
|
|
9242
|
+
!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
9243
|
// The video counterpart of the image rule above, and the fix for the
|
|
9143
9244
|
// reported "it writes scripts forever and never makes the video" run. The
|
|
9144
9245
|
// failure mode is identical — the model narrates the deliverable instead of
|
|
@@ -9148,7 +9249,7 @@ function buildWorkerRules(has) {
|
|
|
9148
9249
|
// the deliverable rather than an illustration inside one, and the reference
|
|
9149
9250
|
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
9150
9251
|
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") &&
|
|
9252
|
+
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
9253
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
9153
9254
|
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
9255
|
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 +9260,34 @@ function buildWorkerRules(has) {
|
|
|
9159
9260
|
Rules:
|
|
9160
9261
|
${rules.filter((r) => r !== false).join("\n")}`;
|
|
9161
9262
|
}
|
|
9162
|
-
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
|
|
9263
|
+
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
|
|
9264
|
+
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
9265
|
function shouldRequireArtifact(assignment, toolNames) {
|
|
9164
9266
|
if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
|
|
9165
9267
|
if (assignment?.files?.length) return true;
|
|
9166
9268
|
const haystack = `${assignment?.description ?? ""}
|
|
9167
9269
|
${assignment?.expectedOutput ?? ""}`;
|
|
9168
|
-
return
|
|
9270
|
+
return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
9271
|
+
}
|
|
9272
|
+
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;
|
|
9273
|
+
var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
|
|
9274
|
+
var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
|
|
9275
|
+
function missingVisualEvidence(assignment, output, calledTools, toolNames) {
|
|
9276
|
+
const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
|
|
9277
|
+
if (!canVisualize) return null;
|
|
9278
|
+
const haystack = `${assignment?.description ?? ""}
|
|
9279
|
+
${assignment?.expectedOutput ?? ""}`;
|
|
9280
|
+
if (!VISUAL_REQUEST_RE.test(haystack)) return null;
|
|
9281
|
+
if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
|
|
9282
|
+
if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
|
|
9283
|
+
const options = [];
|
|
9284
|
+
if (toolNames.includes("generate_image")) {
|
|
9285
|
+
options.push('call "generate_image" and reference the result on its own line as ');
|
|
9286
|
+
}
|
|
9287
|
+
if (toolNames.includes("generate_document")) {
|
|
9288
|
+
options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
|
|
9289
|
+
}
|
|
9290
|
+
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
9291
|
}
|
|
9170
9292
|
var T3Worker = class extends BaseTier {
|
|
9171
9293
|
router;
|
|
@@ -9335,7 +9457,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
9335
9457
|
}
|
|
9336
9458
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
9337
9459
|
if (!artifactCheck.ok) {
|
|
9338
|
-
correctionAttempts
|
|
9460
|
+
correctionAttempts++;
|
|
9339
9461
|
issues.push(...artifactCheck.issues);
|
|
9340
9462
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
9341
9463
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -9346,13 +9468,47 @@ Now execute your subtask using this context where relevant.`
|
|
|
9346
9468
|
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
9347
9469
|
}
|
|
9348
9470
|
}
|
|
9471
|
+
const visualIssue = missingVisualEvidence(
|
|
9472
|
+
assignment,
|
|
9473
|
+
output,
|
|
9474
|
+
toolCalls.map((t) => t.name),
|
|
9475
|
+
this.tools.map((t) => t.name)
|
|
9476
|
+
);
|
|
9477
|
+
if (visualIssue) {
|
|
9478
|
+
correctionAttempts++;
|
|
9479
|
+
issues.push(visualIssue);
|
|
9480
|
+
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
9481
|
+
output = await this.correctOutput(output, [visualIssue]);
|
|
9482
|
+
}
|
|
9483
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
9484
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
9485
|
+
if (acceptanceFailures.length > 0) {
|
|
9486
|
+
correctionAttempts++;
|
|
9487
|
+
issues.push(...acceptanceFailures);
|
|
9488
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
9489
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
9490
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
9491
|
+
if (recheck.length > 0) {
|
|
9492
|
+
issues.push(...recheck);
|
|
9493
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
9494
|
+
failed.push(...recheck);
|
|
9495
|
+
this.setStatus("FAILED");
|
|
9496
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
9497
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
for (const result2 of acceptanceResults) {
|
|
9501
|
+
if (result2.verdict === "undecidable") continue;
|
|
9502
|
+
checksRun.push(result2.criterion);
|
|
9503
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
9504
|
+
}
|
|
9349
9505
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
9350
|
-
const testResult = await this.selfTest(assignment, output);
|
|
9506
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
9351
9507
|
checksRun.push(...testResult.checksRun);
|
|
9352
9508
|
passed.push(...testResult.passed);
|
|
9353
9509
|
failed.push(...testResult.failed);
|
|
9354
9510
|
if (testResult.failed.length > 0) {
|
|
9355
|
-
correctionAttempts
|
|
9511
|
+
correctionAttempts++;
|
|
9356
9512
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
9357
9513
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
9358
9514
|
output = corrected;
|
|
@@ -9660,8 +9816,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
9660
9816
|
// Media generation can run for a minute; without this a cancelled run
|
|
9661
9817
|
// still pays for an image nobody will see.
|
|
9662
9818
|
...this.signal ? { signal: this.signal } : {},
|
|
9663
|
-
saveSnapshot: async (
|
|
9664
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
9819
|
+
saveSnapshot: async (path39, content) => {
|
|
9820
|
+
this.store?.addFileSnapshot(this.taskId, path39, content);
|
|
9665
9821
|
},
|
|
9666
9822
|
sendPeerSync: (to, syncType, content) => {
|
|
9667
9823
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -9837,7 +9993,7 @@ ${assignment.expectedOutput}`;
|
|
|
9837
9993
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
9838
9994
|
const haystack = `${assignment.description}
|
|
9839
9995
|
${assignment.expectedOutput}`;
|
|
9840
|
-
const matches = haystack.match(
|
|
9996
|
+
const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
|
|
9841
9997
|
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
9842
9998
|
}
|
|
9843
9999
|
async verifyArtifacts(assignment) {
|
|
@@ -9848,9 +10004,9 @@ ${assignment.expectedOutput}`;
|
|
|
9848
10004
|
const { promisify: promisify4 } = await import('util');
|
|
9849
10005
|
const execAsync3 = promisify4(exec3);
|
|
9850
10006
|
for (const artifactPath of artifactPaths) {
|
|
9851
|
-
const absolutePath =
|
|
10007
|
+
const absolutePath = path34__default.default.resolve(process.cwd(), artifactPath);
|
|
9852
10008
|
try {
|
|
9853
|
-
const stat = await
|
|
10009
|
+
const stat = await fs11__default.default.stat(absolutePath);
|
|
9854
10010
|
if (!stat.isFile()) {
|
|
9855
10011
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
9856
10012
|
continue;
|
|
@@ -9859,8 +10015,22 @@ ${assignment.expectedOutput}`;
|
|
|
9859
10015
|
issues.push(`Artifact is empty: ${artifactPath}`);
|
|
9860
10016
|
continue;
|
|
9861
10017
|
}
|
|
9862
|
-
if (
|
|
9863
|
-
const
|
|
10018
|
+
if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
|
|
10019
|
+
const head = Buffer.alloc(4);
|
|
10020
|
+
const fh = await fs11__default.default.open(absolutePath, "r");
|
|
10021
|
+
try {
|
|
10022
|
+
await fh.read(head, 0, 4, 0);
|
|
10023
|
+
} finally {
|
|
10024
|
+
await fh.close();
|
|
10025
|
+
}
|
|
10026
|
+
if (head.toString("latin1") !== "PK") {
|
|
10027
|
+
issues.push(
|
|
10028
|
+
`${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.`
|
|
10029
|
+
);
|
|
10030
|
+
continue;
|
|
10031
|
+
}
|
|
10032
|
+
} else if (!/\.pdf$/i.test(artifactPath)) {
|
|
10033
|
+
const content = await fs11__default.default.readFile(absolutePath, "utf-8");
|
|
9864
10034
|
if (!content.trim()) {
|
|
9865
10035
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
9866
10036
|
continue;
|
|
@@ -9869,7 +10039,7 @@ ${assignment.expectedOutput}`;
|
|
|
9869
10039
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
9870
10040
|
continue;
|
|
9871
10041
|
}
|
|
9872
|
-
const ext =
|
|
10042
|
+
const ext = path34__default.default.extname(absolutePath).toLowerCase();
|
|
9873
10043
|
try {
|
|
9874
10044
|
if (ext === ".ts" || ext === ".tsx") {
|
|
9875
10045
|
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -9955,14 +10125,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
9955
10125
|
}
|
|
9956
10126
|
return current;
|
|
9957
10127
|
}
|
|
9958
|
-
|
|
10128
|
+
/**
|
|
10129
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
10130
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
10131
|
+
*/
|
|
10132
|
+
async checkAcceptance(assignment) {
|
|
10133
|
+
const criteria = assignment.acceptance ?? [];
|
|
10134
|
+
if (!criteria.length) return [];
|
|
10135
|
+
const resolve2 = (target) => path34__default.default.resolve(process.cwd(), target);
|
|
10136
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
10137
|
+
stat: async (target) => {
|
|
10138
|
+
try {
|
|
10139
|
+
const stat = await fs11__default.default.stat(resolve2(target));
|
|
10140
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
10141
|
+
} catch {
|
|
10142
|
+
return null;
|
|
10143
|
+
}
|
|
10144
|
+
},
|
|
10145
|
+
read: async (target) => {
|
|
10146
|
+
try {
|
|
10147
|
+
const content = await fs11__default.default.readFile(resolve2(target), "utf-8");
|
|
10148
|
+
return content.includes("\uFFFD") ? null : content;
|
|
10149
|
+
} catch {
|
|
10150
|
+
return null;
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
10153
|
+
});
|
|
10154
|
+
}
|
|
10155
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
10156
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
9959
10157
|
const prompt = `Self-test this output against the assignment requirements.
|
|
9960
10158
|
|
|
9961
10159
|
Assignment: ${assignment.description}
|
|
9962
10160
|
Expected output: ${assignment.expectedOutput}
|
|
9963
10161
|
Constraints: ${assignment.constraints.join("; ")}
|
|
9964
|
-
${
|
|
9965
|
-
${
|
|
10162
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
10163
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
9966
10164
|
` : ""}
|
|
9967
10165
|
Output to test:
|
|
9968
10166
|
${output}
|
|
@@ -10029,8 +10227,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
10029
10227
|
} catch {
|
|
10030
10228
|
}
|
|
10031
10229
|
}
|
|
10032
|
-
async correctOutput(originalOutput,
|
|
10033
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
10230
|
+
async correctOutput(originalOutput, failures2) {
|
|
10231
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
10034
10232
|
|
|
10035
10233
|
Original output:
|
|
10036
10234
|
${originalOutput}
|
|
@@ -10692,12 +10890,220 @@ function describeGenerationForPlanner(has) {
|
|
|
10692
10890
|
return lines.join("\n");
|
|
10693
10891
|
}
|
|
10694
10892
|
|
|
10893
|
+
// src/core/orchestration/task-graph.ts
|
|
10894
|
+
var TaskGraphValidationError = class extends Error {
|
|
10895
|
+
constructor(issues) {
|
|
10896
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
10897
|
+
this.issues = issues;
|
|
10898
|
+
this.name = "TaskGraphValidationError";
|
|
10899
|
+
}
|
|
10900
|
+
issues;
|
|
10901
|
+
};
|
|
10902
|
+
function normalizeDependencies(input) {
|
|
10903
|
+
if (!input?.length) return [];
|
|
10904
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
10905
|
+
}
|
|
10906
|
+
function cycleNodes(nodes) {
|
|
10907
|
+
const adjacency = new Map(
|
|
10908
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
10909
|
+
);
|
|
10910
|
+
const index = /* @__PURE__ */ new Map();
|
|
10911
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
10912
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
10913
|
+
const stack = [];
|
|
10914
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
10915
|
+
let counter = 0;
|
|
10916
|
+
const open = (id) => {
|
|
10917
|
+
index.set(id, counter);
|
|
10918
|
+
lowlink.set(id, counter);
|
|
10919
|
+
counter++;
|
|
10920
|
+
stack.push(id);
|
|
10921
|
+
onStack.add(id);
|
|
10922
|
+
};
|
|
10923
|
+
for (const root of nodes) {
|
|
10924
|
+
if (index.has(root.id)) continue;
|
|
10925
|
+
open(root.id);
|
|
10926
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
10927
|
+
while (frames.length > 0) {
|
|
10928
|
+
const frame = frames[frames.length - 1];
|
|
10929
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
10930
|
+
if (frame.edge < neighbours.length) {
|
|
10931
|
+
const next = neighbours[frame.edge];
|
|
10932
|
+
frame.edge++;
|
|
10933
|
+
if (!adjacency.has(next)) continue;
|
|
10934
|
+
if (!index.has(next)) {
|
|
10935
|
+
open(next);
|
|
10936
|
+
frames.push({ id: next, edge: 0 });
|
|
10937
|
+
} else if (onStack.has(next)) {
|
|
10938
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
10939
|
+
}
|
|
10940
|
+
continue;
|
|
10941
|
+
}
|
|
10942
|
+
frames.pop();
|
|
10943
|
+
const parent = frames[frames.length - 1];
|
|
10944
|
+
if (parent) {
|
|
10945
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
10946
|
+
}
|
|
10947
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
10948
|
+
const component = [];
|
|
10949
|
+
for (; ; ) {
|
|
10950
|
+
const member = stack.pop();
|
|
10951
|
+
onStack.delete(member);
|
|
10952
|
+
component.push(member);
|
|
10953
|
+
if (member === frame.id) break;
|
|
10954
|
+
}
|
|
10955
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
10956
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
return inCycle;
|
|
10961
|
+
}
|
|
10962
|
+
function throwIfStrict(mode, issues) {
|
|
10963
|
+
if (mode === "strict" && issues.length > 0) {
|
|
10964
|
+
throw new TaskGraphValidationError(issues);
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
10968
|
+
const mode = options.mode ?? "strict";
|
|
10969
|
+
const issues = [];
|
|
10970
|
+
if (inputNodes.length === 0) {
|
|
10971
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
10972
|
+
throwIfStrict(mode, issues);
|
|
10973
|
+
}
|
|
10974
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10975
|
+
const nodes = [];
|
|
10976
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
10977
|
+
const input = inputNodes[ordinal];
|
|
10978
|
+
const id = input.id?.trim();
|
|
10979
|
+
const title = input.title?.trim();
|
|
10980
|
+
if (!id || !title) {
|
|
10981
|
+
issues.push({
|
|
10982
|
+
code: "INVALID_NODE",
|
|
10983
|
+
nodeId: id || void 0,
|
|
10984
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
10985
|
+
});
|
|
10986
|
+
continue;
|
|
10987
|
+
}
|
|
10988
|
+
if (seen.has(id)) {
|
|
10989
|
+
issues.push({
|
|
10990
|
+
code: "DUPLICATE_NODE_ID",
|
|
10991
|
+
nodeId: id,
|
|
10992
|
+
message: `Duplicate task graph node id: ${id}`
|
|
10993
|
+
});
|
|
10994
|
+
continue;
|
|
10995
|
+
}
|
|
10996
|
+
seen.add(id);
|
|
10997
|
+
nodes.push({
|
|
10998
|
+
id,
|
|
10999
|
+
title,
|
|
11000
|
+
kind: input.kind,
|
|
11001
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
11002
|
+
payload: input.payload,
|
|
11003
|
+
ordinal
|
|
11004
|
+
});
|
|
11005
|
+
}
|
|
11006
|
+
throwIfStrict(mode, issues);
|
|
11007
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
11008
|
+
const sanitized = nodes.map((node) => {
|
|
11009
|
+
const dependsOn = [];
|
|
11010
|
+
for (const dependencyId of node.dependsOn) {
|
|
11011
|
+
if (dependencyId === node.id) {
|
|
11012
|
+
issues.push({
|
|
11013
|
+
code: "SELF_DEPENDENCY",
|
|
11014
|
+
nodeId: node.id,
|
|
11015
|
+
dependencyId,
|
|
11016
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
11017
|
+
});
|
|
11018
|
+
continue;
|
|
11019
|
+
}
|
|
11020
|
+
if (!ids.has(dependencyId)) {
|
|
11021
|
+
issues.push({
|
|
11022
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
11023
|
+
nodeId: node.id,
|
|
11024
|
+
dependencyId,
|
|
11025
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
11026
|
+
});
|
|
11027
|
+
continue;
|
|
11028
|
+
}
|
|
11029
|
+
dependsOn.push(dependencyId);
|
|
11030
|
+
}
|
|
11031
|
+
return { ...node, dependsOn };
|
|
11032
|
+
});
|
|
11033
|
+
throwIfStrict(mode, issues);
|
|
11034
|
+
const cyclic = cycleNodes(sanitized);
|
|
11035
|
+
let repaired = sanitized;
|
|
11036
|
+
if (cyclic.size > 0) {
|
|
11037
|
+
const cycleIssues = [];
|
|
11038
|
+
repaired = sanitized.map((node) => {
|
|
11039
|
+
if (!cyclic.has(node.id)) return node;
|
|
11040
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
11041
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
11042
|
+
cycleIssues.push({
|
|
11043
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
11044
|
+
nodeId: node.id,
|
|
11045
|
+
dependencyId,
|
|
11046
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
11047
|
+
});
|
|
11048
|
+
return false;
|
|
11049
|
+
});
|
|
11050
|
+
return { ...node, dependsOn };
|
|
11051
|
+
});
|
|
11052
|
+
issues.push(...cycleIssues);
|
|
11053
|
+
throwIfStrict(mode, cycleIssues);
|
|
11054
|
+
}
|
|
11055
|
+
const remainingCycle = cycleNodes(repaired);
|
|
11056
|
+
if (remainingCycle.size > 0) {
|
|
11057
|
+
throw new TaskGraphValidationError([
|
|
11058
|
+
{
|
|
11059
|
+
code: "INVALID_NODE",
|
|
11060
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
11061
|
+
}
|
|
11062
|
+
]);
|
|
11063
|
+
}
|
|
11064
|
+
return {
|
|
11065
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
11066
|
+
issues
|
|
11067
|
+
};
|
|
11068
|
+
}
|
|
11069
|
+
|
|
11070
|
+
// src/core/orchestration/adapters.ts
|
|
11071
|
+
function compileSectionGraph(sections) {
|
|
11072
|
+
return compileTaskGraph(
|
|
11073
|
+
sections.map((section) => ({
|
|
11074
|
+
id: section.sectionId,
|
|
11075
|
+
title: section.sectionTitle,
|
|
11076
|
+
kind: "section",
|
|
11077
|
+
dependsOn: section.dependsOn,
|
|
11078
|
+
payload: section
|
|
11079
|
+
})),
|
|
11080
|
+
{ mode: "repair" }
|
|
11081
|
+
);
|
|
11082
|
+
}
|
|
11083
|
+
function compileSubtaskGraph(subtasks) {
|
|
11084
|
+
return compileTaskGraph(
|
|
11085
|
+
subtasks.map((subtask) => ({
|
|
11086
|
+
id: subtask.subtaskId,
|
|
11087
|
+
title: subtask.subtaskTitle,
|
|
11088
|
+
kind: "subtask",
|
|
11089
|
+
dependsOn: subtask.dependsOn,
|
|
11090
|
+
payload: subtask
|
|
11091
|
+
})),
|
|
11092
|
+
{ mode: "repair" }
|
|
11093
|
+
);
|
|
11094
|
+
}
|
|
11095
|
+
|
|
10695
11096
|
// src/core/tiers/t2-manager.ts
|
|
10696
11097
|
function buildT2SystemPrompt(has) {
|
|
10697
11098
|
const generation = describeGenerationForPlanner(has);
|
|
10698
11099
|
return [
|
|
10699
11100
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
10700
|
-
|
|
11101
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
11102
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
11103
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
11104
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
11105
|
+
// real worker call on every small section.
|
|
11106
|
+
"Your role is to analyze a section of a task and decompose it into 1-4 discrete subtasks for T3 Workers \u2014 the fewest that fully cover it.",
|
|
10701
11107
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
10702
11108
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
10703
11109
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -11093,21 +11499,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11093
11499
|
* then reduce in-degrees of their dependents and repeat.
|
|
11094
11500
|
*/
|
|
11095
11501
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
11502
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
11503
|
+
for (const issue of compiled.issues) {
|
|
11504
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
11505
|
+
}
|
|
11506
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
11507
|
+
const assignment = node.payload;
|
|
11508
|
+
const original = assignment.dependsOn ?? [];
|
|
11509
|
+
const repaired = node.dependsOn;
|
|
11510
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
11511
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
11512
|
+
});
|
|
11096
11513
|
const adj = /* @__PURE__ */ new Map();
|
|
11097
11514
|
const inDegree = /* @__PURE__ */ new Map();
|
|
11098
11515
|
const resultMap = /* @__PURE__ */ new Map();
|
|
11099
|
-
for (const a of
|
|
11516
|
+
for (const a of sanitizedAssignments) {
|
|
11100
11517
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
11101
11518
|
inDegree.set(a.subtaskId, 0);
|
|
11102
11519
|
}
|
|
11103
|
-
for (const a of
|
|
11104
|
-
const
|
|
11105
|
-
for (const dep of deps) {
|
|
11520
|
+
for (const a of sanitizedAssignments) {
|
|
11521
|
+
for (const dep of a.dependsOn ?? []) {
|
|
11106
11522
|
adj.get(dep).add(a.subtaskId);
|
|
11107
11523
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
11108
11524
|
}
|
|
11109
11525
|
}
|
|
11110
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
11111
11526
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
11112
11527
|
let wave = 0;
|
|
11113
11528
|
let respawnBudget = 1;
|
|
@@ -11314,49 +11729,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11314
11729
|
}
|
|
11315
11730
|
return [...resultMap.values()];
|
|
11316
11731
|
}
|
|
11317
|
-
/**
|
|
11318
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
11319
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
11320
|
-
*
|
|
11321
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
11322
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
11323
|
-
*/
|
|
11324
|
-
breakCycles(assignments, adj, inDegree) {
|
|
11325
|
-
const degree = new Map(inDegree);
|
|
11326
|
-
const queue = [];
|
|
11327
|
-
const visited = /* @__PURE__ */ new Set();
|
|
11328
|
-
for (const [id, d] of degree) {
|
|
11329
|
-
if (d === 0) queue.push(id);
|
|
11330
|
-
}
|
|
11331
|
-
while (queue.length > 0) {
|
|
11332
|
-
const id = queue.shift();
|
|
11333
|
-
visited.add(id);
|
|
11334
|
-
for (const dep of adj.get(id) ?? []) {
|
|
11335
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
11336
|
-
degree.set(dep, newDeg);
|
|
11337
|
-
if (newDeg === 0) queue.push(dep);
|
|
11338
|
-
}
|
|
11339
|
-
}
|
|
11340
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
11341
|
-
if (cycleNodes.length === 0) return assignments;
|
|
11342
|
-
this.log(
|
|
11343
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
11344
|
-
);
|
|
11345
|
-
return assignments.map((a) => {
|
|
11346
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
11347
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
11348
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
11349
|
-
this.log(
|
|
11350
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
11351
|
-
);
|
|
11352
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
11353
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
11354
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
11355
|
-
}
|
|
11356
|
-
}
|
|
11357
|
-
return { ...a, dependsOn: safeDeps };
|
|
11358
|
-
});
|
|
11359
|
-
}
|
|
11360
11732
|
async retryT3(assignment, taskId) {
|
|
11361
11733
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
11362
11734
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -11495,6 +11867,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
11495
11867
|
// src/core/tiers/t1-administrator.ts
|
|
11496
11868
|
init_constants();
|
|
11497
11869
|
|
|
11870
|
+
// src/core/orchestration/scheduler.ts
|
|
11871
|
+
var DependencyScheduler = class {
|
|
11872
|
+
constructor(graph, hooks, options = {}) {
|
|
11873
|
+
this.graph = graph;
|
|
11874
|
+
this.hooks = hooks;
|
|
11875
|
+
this.options = options;
|
|
11876
|
+
}
|
|
11877
|
+
graph;
|
|
11878
|
+
hooks;
|
|
11879
|
+
options;
|
|
11880
|
+
async run() {
|
|
11881
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
11882
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
11883
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
11884
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
11885
|
+
for (const node of nodes) {
|
|
11886
|
+
for (const dependencyId of node.dependsOn) {
|
|
11887
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11890
|
+
const completed = /* @__PURE__ */ new Set();
|
|
11891
|
+
const results = /* @__PURE__ */ new Map();
|
|
11892
|
+
const waves = [];
|
|
11893
|
+
let wave = 0;
|
|
11894
|
+
while (completed.size < nodes.length) {
|
|
11895
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
11896
|
+
if (ready.length === 0) {
|
|
11897
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
11898
|
+
}
|
|
11899
|
+
waves.push(ready.map((node) => node.id));
|
|
11900
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
11901
|
+
const runOne = async (node) => {
|
|
11902
|
+
const result = await this.hooks.execute(node);
|
|
11903
|
+
results.set(node.id, result);
|
|
11904
|
+
completed.add(node.id);
|
|
11905
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
11906
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
11907
|
+
}
|
|
11908
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
11909
|
+
};
|
|
11910
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
11911
|
+
for (const node of ready) await runOne(node);
|
|
11912
|
+
} else {
|
|
11913
|
+
await Promise.all(ready.map(runOne));
|
|
11914
|
+
}
|
|
11915
|
+
wave++;
|
|
11916
|
+
}
|
|
11917
|
+
return { results, waves };
|
|
11918
|
+
}
|
|
11919
|
+
};
|
|
11920
|
+
|
|
11498
11921
|
// src/utils/json-extract.ts
|
|
11499
11922
|
function extractFirstJsonObject(input) {
|
|
11500
11923
|
if (!input) return null;
|
|
@@ -11566,7 +11989,7 @@ function buildT1SystemPrompt(has) {
|
|
|
11566
11989
|
Your responsibilities:
|
|
11567
11990
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
11568
11991
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
11569
|
-
3. For each section, define
|
|
11992
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
11570
11993
|
4. Return a structured plan as JSON
|
|
11571
11994
|
|
|
11572
11995
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -11981,7 +12404,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
11981
12404
|
}
|
|
11982
12405
|
}
|
|
11983
12406
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
11984
|
-
if (plan.sections.length < min)
|
|
12407
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
12408
|
+
this.log(
|
|
12409
|
+
`\u26A0 Plan has ${plan.sections.length} section(s) but complexity "${plan.complexity}" expects ${min}-${max}. Keeping the plan as written \u2014 a right-sized plan is better than one padded to hit a floor.`
|
|
12410
|
+
);
|
|
12411
|
+
}
|
|
11985
12412
|
}
|
|
11986
12413
|
async dispatchT2Managers(sections) {
|
|
11987
12414
|
for (const section of sections) {
|
|
@@ -12093,114 +12520,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
12093
12520
|
return t2Results;
|
|
12094
12521
|
}
|
|
12095
12522
|
/**
|
|
12096
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
12523
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
12524
|
+
*
|
|
12525
|
+
* Graph construction, validation and cycle repair now live in
|
|
12526
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
12527
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
12528
|
+
* ordered by one implementation rather than two that drifted.
|
|
12529
|
+
*
|
|
12530
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
12531
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
12532
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
12533
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
12534
|
+
* sections and started them immediately — before the sections they consume.
|
|
12535
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
12536
|
+
* members are touched.
|
|
12097
12537
|
*/
|
|
12098
12538
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
12099
|
-
const
|
|
12100
|
-
const
|
|
12101
|
-
|
|
12102
|
-
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
12103
|
-
for (const s of sections) {
|
|
12104
|
-
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
12105
|
-
inDegree.set(s.sectionId, 0);
|
|
12106
|
-
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
12107
|
-
}
|
|
12108
|
-
for (const s of sections) {
|
|
12109
|
-
for (const dep of s.dependsOn ?? []) {
|
|
12110
|
-
adj.get(dep).add(s.sectionId);
|
|
12111
|
-
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
12112
|
-
}
|
|
12113
|
-
}
|
|
12114
|
-
const queue = [];
|
|
12115
|
-
const degree = new Map(inDegree);
|
|
12116
|
-
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
12117
|
-
const visited = /* @__PURE__ */ new Set();
|
|
12118
|
-
while (queue.length > 0) {
|
|
12119
|
-
const u = queue.shift();
|
|
12120
|
-
visited.add(u);
|
|
12121
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
12122
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
12123
|
-
degree.set(v, newDeg);
|
|
12124
|
-
if (newDeg === 0) queue.push(v);
|
|
12125
|
-
}
|
|
12126
|
-
}
|
|
12127
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
12128
|
-
if (cycleNodes.length > 0) {
|
|
12129
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
12130
|
-
for (const s of sections) {
|
|
12131
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
12132
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
12133
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
12134
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
12135
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
12136
|
-
}
|
|
12137
|
-
s.dependsOn = safeDeps;
|
|
12138
|
-
}
|
|
12139
|
-
}
|
|
12539
|
+
const compiled = compileSectionGraph(sections);
|
|
12540
|
+
for (const issue of compiled.issues) {
|
|
12541
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
12140
12542
|
}
|
|
12141
|
-
const
|
|
12543
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
12544
|
+
for (const section of sections) {
|
|
12545
|
+
const node = byId.get(section.sectionId);
|
|
12546
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
12547
|
+
}
|
|
12548
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
12549
|
+
const totalSections = compiled.graph.nodes.length;
|
|
12142
12550
|
let completedSections = 0;
|
|
12143
|
-
const
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12551
|
+
const scheduler = new DependencyScheduler(
|
|
12552
|
+
compiled.graph,
|
|
12553
|
+
{
|
|
12554
|
+
execute: async (node) => {
|
|
12555
|
+
const section = node.payload;
|
|
12556
|
+
const manager = managers[node.ordinal];
|
|
12557
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
12558
|
+
this.sendStatusUpdate({
|
|
12559
|
+
progressPct,
|
|
12560
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
12561
|
+
status: "IN_PROGRESS"
|
|
12562
|
+
});
|
|
12563
|
+
this.throwIfCancelled();
|
|
12564
|
+
try {
|
|
12565
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
12566
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
12567
|
+
if (result.status === "ESCALATED") {
|
|
12568
|
+
this.escalations.push({
|
|
12569
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
12570
|
+
sectionId: section.sectionId,
|
|
12571
|
+
attempted: result.issues,
|
|
12572
|
+
blocker: result.issues.join("; "),
|
|
12573
|
+
needs: "Human review required"
|
|
12574
|
+
});
|
|
12575
|
+
}
|
|
12576
|
+
return result;
|
|
12577
|
+
} catch (err) {
|
|
12578
|
+
return {
|
|
12170
12579
|
sectionId: section.sectionId,
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12580
|
+
sectionTitle: section.sectionTitle,
|
|
12581
|
+
status: "FAILED",
|
|
12582
|
+
t3Results: [],
|
|
12583
|
+
sectionSummary: "",
|
|
12584
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
12585
|
+
};
|
|
12175
12586
|
}
|
|
12176
|
-
}
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
resultMap.set(id, result);
|
|
12187
|
-
completedSections++;
|
|
12188
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
12189
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
12190
|
-
}
|
|
12191
|
-
};
|
|
12192
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
12193
|
-
for (const id of readyIds) {
|
|
12194
|
-
await runOne(id);
|
|
12587
|
+
},
|
|
12588
|
+
onNodeComplete: (node, result) => {
|
|
12589
|
+
resultMap.set(node.id, result);
|
|
12590
|
+
completedSections++;
|
|
12591
|
+
this.emit("section:complete", {
|
|
12592
|
+
id: node.id,
|
|
12593
|
+
title: result.sectionTitle,
|
|
12594
|
+
status: result.status,
|
|
12595
|
+
output: result.sectionSummary
|
|
12596
|
+
});
|
|
12195
12597
|
}
|
|
12196
|
-
}
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12598
|
+
},
|
|
12599
|
+
{
|
|
12600
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
12601
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
12602
|
+
// serialized T3 workers within a single section while every section's
|
|
12603
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
12604
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
12201
12605
|
}
|
|
12202
|
-
|
|
12203
|
-
await
|
|
12606
|
+
);
|
|
12607
|
+
await scheduler.run();
|
|
12204
12608
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
12205
12609
|
}
|
|
12206
12610
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -12823,6 +13227,700 @@ function buildMediaTools(deps, readFile) {
|
|
|
12823
13227
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
12824
13228
|
return tools;
|
|
12825
13229
|
}
|
|
13230
|
+
var WorkspaceSandboxError = class extends Error {
|
|
13231
|
+
constructor(attempted, workspaceRoot) {
|
|
13232
|
+
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
13233
|
+
this.name = "WorkspaceSandboxError";
|
|
13234
|
+
}
|
|
13235
|
+
};
|
|
13236
|
+
function resolveInWorkspace(workspaceRoot, input) {
|
|
13237
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
13238
|
+
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
13239
|
+
}
|
|
13240
|
+
const root = path34__default.default.resolve(workspaceRoot);
|
|
13241
|
+
const abs = path34__default.default.isAbsolute(input) ? path34__default.default.resolve(input) : path34__default.default.resolve(root, input);
|
|
13242
|
+
const rel = path34__default.default.relative(root, abs);
|
|
13243
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path34__default.default.isAbsolute(rel)) {
|
|
13244
|
+
throw new WorkspaceSandboxError(input, root);
|
|
13245
|
+
}
|
|
13246
|
+
try {
|
|
13247
|
+
const real = fs31__default.default.realpathSync(abs);
|
|
13248
|
+
const realRel = path34__default.default.relative(root, real);
|
|
13249
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path34__default.default.isAbsolute(realRel))) {
|
|
13250
|
+
throw new WorkspaceSandboxError(input, root);
|
|
13251
|
+
}
|
|
13252
|
+
} catch (e) {
|
|
13253
|
+
if (e instanceof WorkspaceSandboxError) throw e;
|
|
13254
|
+
}
|
|
13255
|
+
return abs;
|
|
13256
|
+
}
|
|
13257
|
+
|
|
13258
|
+
// src/core/documents/blocks.ts
|
|
13259
|
+
function fileExt(name) {
|
|
13260
|
+
const base = name.split(/[\\/]/).pop() ?? name;
|
|
13261
|
+
const dot = base.lastIndexOf(".");
|
|
13262
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
13263
|
+
}
|
|
13264
|
+
function parseDelimited(text, name = "") {
|
|
13265
|
+
const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
|
|
13266
|
+
const rows = [];
|
|
13267
|
+
let row = [];
|
|
13268
|
+
let cell = "";
|
|
13269
|
+
let quoted = false;
|
|
13270
|
+
for (let i = 0; i < text.length; i++) {
|
|
13271
|
+
const ch = text[i];
|
|
13272
|
+
if (quoted) {
|
|
13273
|
+
if (ch === '"') {
|
|
13274
|
+
if (text[i + 1] === '"') {
|
|
13275
|
+
cell += '"';
|
|
13276
|
+
i++;
|
|
13277
|
+
} else quoted = false;
|
|
13278
|
+
} else cell += ch;
|
|
13279
|
+
} else if (ch === '"') {
|
|
13280
|
+
quoted = true;
|
|
13281
|
+
} else if (ch === delim) {
|
|
13282
|
+
row.push(cell);
|
|
13283
|
+
cell = "";
|
|
13284
|
+
} else if (ch === "\n") {
|
|
13285
|
+
row.push(cell);
|
|
13286
|
+
rows.push(row);
|
|
13287
|
+
row = [];
|
|
13288
|
+
cell = "";
|
|
13289
|
+
} else if (ch === "\r") ; else {
|
|
13290
|
+
cell += ch;
|
|
13291
|
+
}
|
|
13292
|
+
if (rows.length > 5e3) break;
|
|
13293
|
+
}
|
|
13294
|
+
if (cell !== "" || row.length) {
|
|
13295
|
+
row.push(cell);
|
|
13296
|
+
rows.push(row);
|
|
13297
|
+
}
|
|
13298
|
+
return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
|
|
13299
|
+
}
|
|
13300
|
+
var CHART_ALIASES = {
|
|
13301
|
+
bar: "bar",
|
|
13302
|
+
column: "bar",
|
|
13303
|
+
bars: "bar",
|
|
13304
|
+
barchart: "bar",
|
|
13305
|
+
horizontalbar: "bar",
|
|
13306
|
+
line: "line",
|
|
13307
|
+
lines: "line",
|
|
13308
|
+
linechart: "line",
|
|
13309
|
+
pie: "pie",
|
|
13310
|
+
piechart: "pie",
|
|
13311
|
+
doughnut: "doughnut",
|
|
13312
|
+
donut: "doughnut",
|
|
13313
|
+
area: "area",
|
|
13314
|
+
areachart: "area",
|
|
13315
|
+
scatter: "scatter",
|
|
13316
|
+
xy: "scatter"
|
|
13317
|
+
};
|
|
13318
|
+
function chartKind(raw) {
|
|
13319
|
+
return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
|
|
13320
|
+
}
|
|
13321
|
+
function toNumber(cell) {
|
|
13322
|
+
const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
|
|
13323
|
+
const n = Number.parseFloat(cleaned);
|
|
13324
|
+
return Number.isFinite(n) ? n : 0;
|
|
13325
|
+
}
|
|
13326
|
+
function parseChartSpec(kind, body) {
|
|
13327
|
+
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
|
13328
|
+
let title;
|
|
13329
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
13330
|
+
const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
|
|
13331
|
+
if (titleMatch) {
|
|
13332
|
+
title = titleMatch[1].trim();
|
|
13333
|
+
lines.shift();
|
|
13334
|
+
}
|
|
13335
|
+
const rows = parseDelimited(lines.join("\n").trim());
|
|
13336
|
+
if (rows.length < 2) return null;
|
|
13337
|
+
const header = rows[0].map((c) => c.trim());
|
|
13338
|
+
if (header.length < 2) return null;
|
|
13339
|
+
const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
13340
|
+
if (!dataRows.length) return null;
|
|
13341
|
+
const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
|
|
13342
|
+
const series = header.slice(1).map((name, col2) => ({
|
|
13343
|
+
name: name || `Series ${col2 + 1}`,
|
|
13344
|
+
values: dataRows.map((r) => toNumber((r[col2 + 1] ?? "").trim()))
|
|
13345
|
+
}));
|
|
13346
|
+
if (!series.length) return null;
|
|
13347
|
+
return {
|
|
13348
|
+
kind,
|
|
13349
|
+
...title ? { title } : {},
|
|
13350
|
+
categoryName: header[0] || "Category",
|
|
13351
|
+
categories,
|
|
13352
|
+
series
|
|
13353
|
+
};
|
|
13354
|
+
}
|
|
13355
|
+
function chartToTableRows(spec) {
|
|
13356
|
+
return [
|
|
13357
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
13358
|
+
...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
|
|
13359
|
+
];
|
|
13360
|
+
}
|
|
13361
|
+
function parseBlocks(md) {
|
|
13362
|
+
const out = [];
|
|
13363
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
13364
|
+
let i = 0;
|
|
13365
|
+
let ordinal = 0;
|
|
13366
|
+
const reset = () => {
|
|
13367
|
+
ordinal = 0;
|
|
13368
|
+
};
|
|
13369
|
+
while (i < lines.length) {
|
|
13370
|
+
const line = lines[i] ?? "";
|
|
13371
|
+
const fence = line.match(/^```+\s*(.*)$/);
|
|
13372
|
+
if (fence) {
|
|
13373
|
+
const info = (fence[1] ?? "").trim();
|
|
13374
|
+
i++;
|
|
13375
|
+
const code = [];
|
|
13376
|
+
while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
|
|
13377
|
+
code.push(lines[i] ?? "");
|
|
13378
|
+
i++;
|
|
13379
|
+
}
|
|
13380
|
+
i++;
|
|
13381
|
+
const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
|
|
13382
|
+
const kind = chartInfo ? chartKind(chartInfo[1]) : null;
|
|
13383
|
+
const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
|
|
13384
|
+
out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
|
|
13385
|
+
reset();
|
|
13386
|
+
continue;
|
|
13387
|
+
}
|
|
13388
|
+
if (!line.trim()) {
|
|
13389
|
+
i++;
|
|
13390
|
+
reset();
|
|
13391
|
+
continue;
|
|
13392
|
+
}
|
|
13393
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
13394
|
+
if (h) {
|
|
13395
|
+
out.push({ t: "heading", level: h[1].length, text: h[2] });
|
|
13396
|
+
i++;
|
|
13397
|
+
reset();
|
|
13398
|
+
continue;
|
|
13399
|
+
}
|
|
13400
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
13401
|
+
out.push({ t: "hr" });
|
|
13402
|
+
i++;
|
|
13403
|
+
reset();
|
|
13404
|
+
continue;
|
|
13405
|
+
}
|
|
13406
|
+
const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
|
|
13407
|
+
if (li) {
|
|
13408
|
+
const ordered = /\d/.test(li[2]);
|
|
13409
|
+
ordinal = ordered ? ordinal + 1 : 0;
|
|
13410
|
+
out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
|
|
13411
|
+
i++;
|
|
13412
|
+
continue;
|
|
13413
|
+
}
|
|
13414
|
+
if (/^>\s?/.test(line)) {
|
|
13415
|
+
out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
|
|
13416
|
+
i++;
|
|
13417
|
+
reset();
|
|
13418
|
+
continue;
|
|
13419
|
+
}
|
|
13420
|
+
if (/^\|.*\|/.test(line)) {
|
|
13421
|
+
const rows = [];
|
|
13422
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
|
|
13423
|
+
const r = lines[i];
|
|
13424
|
+
if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
|
|
13425
|
+
i++;
|
|
13426
|
+
}
|
|
13427
|
+
out.push({ t: "table", rows });
|
|
13428
|
+
reset();
|
|
13429
|
+
continue;
|
|
13430
|
+
}
|
|
13431
|
+
out.push({ t: "para", text: line });
|
|
13432
|
+
i++;
|
|
13433
|
+
reset();
|
|
13434
|
+
}
|
|
13435
|
+
return out;
|
|
13436
|
+
}
|
|
13437
|
+
var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
13438
|
+
function matchImageLine(s) {
|
|
13439
|
+
const m = s.trim().match(IMAGE_LINE);
|
|
13440
|
+
return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
|
|
13441
|
+
}
|
|
13442
|
+
function sniffImage(b) {
|
|
13443
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
13444
|
+
if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
13445
|
+
return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
13446
|
+
}
|
|
13447
|
+
if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
13448
|
+
return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
|
|
13449
|
+
}
|
|
13450
|
+
if (b.length > 26 && b[0] === 66 && b[1] === 77) {
|
|
13451
|
+
return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
|
|
13452
|
+
}
|
|
13453
|
+
if (b.length > 4 && b[0] === 255 && b[1] === 216) {
|
|
13454
|
+
let i = 2;
|
|
13455
|
+
while (i + 9 < b.length) {
|
|
13456
|
+
if (b[i] !== 255) {
|
|
13457
|
+
i++;
|
|
13458
|
+
continue;
|
|
13459
|
+
}
|
|
13460
|
+
const marker = b[i + 1];
|
|
13461
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
13462
|
+
return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
|
|
13463
|
+
}
|
|
13464
|
+
i += 2 + dv.getUint16(i + 2);
|
|
13465
|
+
}
|
|
13466
|
+
}
|
|
13467
|
+
return null;
|
|
13468
|
+
}
|
|
13469
|
+
function bytesToBase64(bytes) {
|
|
13470
|
+
const g = globalThis;
|
|
13471
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
13472
|
+
const CHUNK = 32768;
|
|
13473
|
+
let binary = "";
|
|
13474
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
13475
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
13476
|
+
}
|
|
13477
|
+
return g.btoa(binary);
|
|
13478
|
+
}
|
|
13479
|
+
function stripInline(s) {
|
|
13480
|
+
return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
|
|
13481
|
+
}
|
|
13482
|
+
function inlineRuns(text) {
|
|
13483
|
+
const acc = [];
|
|
13484
|
+
const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
|
|
13485
|
+
let last = 0;
|
|
13486
|
+
let m;
|
|
13487
|
+
while ((m = re.exec(text)) !== null) {
|
|
13488
|
+
if (m.index > last) acc.push({ text: text.slice(last, m.index) });
|
|
13489
|
+
const tok = m[0];
|
|
13490
|
+
if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
|
|
13491
|
+
else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
|
|
13492
|
+
else if (tok.startsWith("[")) {
|
|
13493
|
+
const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
|
|
13494
|
+
acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
|
|
13495
|
+
} else acc.push({ text: tok.slice(1, -1), italic: true });
|
|
13496
|
+
last = re.lastIndex;
|
|
13497
|
+
}
|
|
13498
|
+
if (last < text.length) acc.push({ text: text.slice(last) });
|
|
13499
|
+
return acc.length ? acc : [{ text }];
|
|
13500
|
+
}
|
|
13501
|
+
function splitSlides(md) {
|
|
13502
|
+
const norm = md.replace(/\r\n/g, "\n").trim();
|
|
13503
|
+
let chunks = norm.split(/\n\s*-{3,}\s*\n/);
|
|
13504
|
+
if (chunks.length === 1) {
|
|
13505
|
+
const lines = norm.split("\n");
|
|
13506
|
+
const sections = [];
|
|
13507
|
+
let cur = [];
|
|
13508
|
+
for (const ln of lines) {
|
|
13509
|
+
if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
|
|
13510
|
+
sections.push(cur.join("\n"));
|
|
13511
|
+
cur = [];
|
|
13512
|
+
}
|
|
13513
|
+
cur.push(ln);
|
|
13514
|
+
}
|
|
13515
|
+
if (cur.length) sections.push(cur.join("\n"));
|
|
13516
|
+
if (sections.length > 1) chunks = sections;
|
|
13517
|
+
}
|
|
13518
|
+
return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
|
|
13519
|
+
}
|
|
13520
|
+
function parseSlide(chunk) {
|
|
13521
|
+
let title = "";
|
|
13522
|
+
const body = [];
|
|
13523
|
+
const images = [];
|
|
13524
|
+
const charts = [];
|
|
13525
|
+
const lines = chunk.split("\n");
|
|
13526
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13527
|
+
const raw = lines[i] ?? "";
|
|
13528
|
+
const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
13529
|
+
if (fence) {
|
|
13530
|
+
const kind = chartKind(fence[1]);
|
|
13531
|
+
const bodyLines = [];
|
|
13532
|
+
i++;
|
|
13533
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
13534
|
+
bodyLines.push(lines[i] ?? "");
|
|
13535
|
+
i++;
|
|
13536
|
+
}
|
|
13537
|
+
const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
|
|
13538
|
+
if (spec) charts.push(spec);
|
|
13539
|
+
else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
|
|
13540
|
+
continue;
|
|
13541
|
+
}
|
|
13542
|
+
const line = raw.trim();
|
|
13543
|
+
if (!line) continue;
|
|
13544
|
+
const h = line.match(/^#{1,6}[ \t]+(.*)/);
|
|
13545
|
+
if (h) {
|
|
13546
|
+
if (!title) title = stripInline(h[1]);
|
|
13547
|
+
else body.push(stripInline(h[1]));
|
|
13548
|
+
continue;
|
|
13549
|
+
}
|
|
13550
|
+
const img = matchImageLine(line);
|
|
13551
|
+
if (img) {
|
|
13552
|
+
images.push(img);
|
|
13553
|
+
continue;
|
|
13554
|
+
}
|
|
13555
|
+
const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
|
|
13556
|
+
if (li) {
|
|
13557
|
+
const bulletImg = matchImageLine(li[2]);
|
|
13558
|
+
if (bulletImg) {
|
|
13559
|
+
images.push(bulletImg);
|
|
13560
|
+
continue;
|
|
13561
|
+
}
|
|
13562
|
+
body.push(stripInline(li[2]));
|
|
13563
|
+
continue;
|
|
13564
|
+
}
|
|
13565
|
+
if (/^>\s?/.test(line)) {
|
|
13566
|
+
body.push(stripInline(line.replace(/^>\s?/, "")));
|
|
13567
|
+
continue;
|
|
13568
|
+
}
|
|
13569
|
+
body.push(stripInline(line));
|
|
13570
|
+
}
|
|
13571
|
+
return { title, body, images, charts };
|
|
13572
|
+
}
|
|
13573
|
+
function extractCharts(source) {
|
|
13574
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
13575
|
+
const charts = [];
|
|
13576
|
+
const rest = [];
|
|
13577
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13578
|
+
const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
13579
|
+
if (!fence) {
|
|
13580
|
+
rest.push(lines[i]);
|
|
13581
|
+
continue;
|
|
13582
|
+
}
|
|
13583
|
+
const kind = chartKind(fence[1]);
|
|
13584
|
+
const body = [];
|
|
13585
|
+
i++;
|
|
13586
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
13587
|
+
body.push(lines[i] ?? "");
|
|
13588
|
+
i++;
|
|
13589
|
+
}
|
|
13590
|
+
const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
|
|
13591
|
+
if (spec) charts.push(spec);
|
|
13592
|
+
else rest.push(...body);
|
|
13593
|
+
}
|
|
13594
|
+
return { charts, rest: rest.join("\n") };
|
|
13595
|
+
}
|
|
13596
|
+
|
|
13597
|
+
// src/core/documents/render.ts
|
|
13598
|
+
function isDocumentFormat(ext) {
|
|
13599
|
+
return ext === "docx" || ext === "pptx" || ext === "xlsx";
|
|
13600
|
+
}
|
|
13601
|
+
async function loadImage(ref, load) {
|
|
13602
|
+
if (!load) return null;
|
|
13603
|
+
try {
|
|
13604
|
+
const bytes = await load(ref.url);
|
|
13605
|
+
if (!bytes || !bytes.length) return null;
|
|
13606
|
+
const info = sniffImage(bytes);
|
|
13607
|
+
if (!info || info.width <= 0 || info.height <= 0) return null;
|
|
13608
|
+
return { ...info, bytes, alt: ref.alt };
|
|
13609
|
+
} catch {
|
|
13610
|
+
return null;
|
|
13611
|
+
}
|
|
13612
|
+
}
|
|
13613
|
+
async function renderXlsx(source, opts = {}) {
|
|
13614
|
+
const XLSX = await import('xlsx');
|
|
13615
|
+
const { charts, rest } = extractCharts(source);
|
|
13616
|
+
const name = opts.name ?? "";
|
|
13617
|
+
const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
|
|
13618
|
+
const wb = XLSX.utils.book_new();
|
|
13619
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
|
|
13620
|
+
const used = /* @__PURE__ */ new Set(["Sheet1"]);
|
|
13621
|
+
charts.forEach((spec, i) => {
|
|
13622
|
+
const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
|
|
13623
|
+
let sheetName = base;
|
|
13624
|
+
let n = 2;
|
|
13625
|
+
while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
|
|
13626
|
+
used.add(sheetName);
|
|
13627
|
+
const aoa = [
|
|
13628
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
13629
|
+
...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
|
|
13630
|
+
];
|
|
13631
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
|
|
13632
|
+
});
|
|
13633
|
+
return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
|
|
13634
|
+
}
|
|
13635
|
+
var DOCX_MAX_W = 600;
|
|
13636
|
+
var DOCX_MAX_H = 864;
|
|
13637
|
+
async function renderDocx(md, opts = {}) {
|
|
13638
|
+
const d = await import('docx');
|
|
13639
|
+
const {
|
|
13640
|
+
Document,
|
|
13641
|
+
Packer,
|
|
13642
|
+
Paragraph,
|
|
13643
|
+
TextRun,
|
|
13644
|
+
ImageRun,
|
|
13645
|
+
HeadingLevel,
|
|
13646
|
+
Table,
|
|
13647
|
+
TableRow,
|
|
13648
|
+
TableCell,
|
|
13649
|
+
WidthType,
|
|
13650
|
+
BorderStyle,
|
|
13651
|
+
AlignmentType
|
|
13652
|
+
} = d;
|
|
13653
|
+
const HEADINGS = [
|
|
13654
|
+
HeadingLevel.HEADING_1,
|
|
13655
|
+
HeadingLevel.HEADING_2,
|
|
13656
|
+
HeadingLevel.HEADING_3,
|
|
13657
|
+
HeadingLevel.HEADING_4,
|
|
13658
|
+
HeadingLevel.HEADING_5,
|
|
13659
|
+
HeadingLevel.HEADING_6
|
|
13660
|
+
];
|
|
13661
|
+
const runs = (text) => inlineRuns(text).map((r) => new TextRun({
|
|
13662
|
+
text: r.text,
|
|
13663
|
+
...r.bold ? { bold: true } : {},
|
|
13664
|
+
...r.italic ? { italics: true } : {},
|
|
13665
|
+
...r.code ? { font: "Courier New" } : {}
|
|
13666
|
+
}));
|
|
13667
|
+
const tableOf = (rows) => new Table({
|
|
13668
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
13669
|
+
rows: rows.map((r) => new TableRow({
|
|
13670
|
+
children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
|
|
13671
|
+
}))
|
|
13672
|
+
});
|
|
13673
|
+
const imageParagraph = (img) => {
|
|
13674
|
+
const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
|
|
13675
|
+
return new Paragraph({
|
|
13676
|
+
children: [new ImageRun({
|
|
13677
|
+
type: img.type,
|
|
13678
|
+
data: img.bytes,
|
|
13679
|
+
transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
|
|
13680
|
+
...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
|
|
13681
|
+
})]
|
|
13682
|
+
});
|
|
13683
|
+
};
|
|
13684
|
+
const asImage = async (text) => {
|
|
13685
|
+
const ref = matchImageLine(text);
|
|
13686
|
+
return ref ? await loadImage(ref, opts.loadImageBytes) : null;
|
|
13687
|
+
};
|
|
13688
|
+
const children = [];
|
|
13689
|
+
for (const b of parseBlocks(md)) {
|
|
13690
|
+
switch (b.t) {
|
|
13691
|
+
case "heading":
|
|
13692
|
+
children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
|
|
13693
|
+
break;
|
|
13694
|
+
case "para": {
|
|
13695
|
+
const img = await asImage(b.text);
|
|
13696
|
+
children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
|
|
13697
|
+
break;
|
|
13698
|
+
}
|
|
13699
|
+
case "bullet": {
|
|
13700
|
+
const img = await asImage(b.text);
|
|
13701
|
+
children.push(img ? imageParagraph(img) : new Paragraph({
|
|
13702
|
+
children: runs(b.text),
|
|
13703
|
+
...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
|
|
13704
|
+
}));
|
|
13705
|
+
break;
|
|
13706
|
+
}
|
|
13707
|
+
case "code":
|
|
13708
|
+
for (const cl of b.lines) {
|
|
13709
|
+
children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
|
|
13710
|
+
}
|
|
13711
|
+
break;
|
|
13712
|
+
case "quote":
|
|
13713
|
+
children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
|
|
13714
|
+
break;
|
|
13715
|
+
case "table":
|
|
13716
|
+
children.push(tableOf(b.rows));
|
|
13717
|
+
break;
|
|
13718
|
+
case "chart":
|
|
13719
|
+
if (b.spec.title) {
|
|
13720
|
+
children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
|
|
13721
|
+
}
|
|
13722
|
+
children.push(tableOf(chartToTableRows(b.spec)));
|
|
13723
|
+
break;
|
|
13724
|
+
case "hr":
|
|
13725
|
+
children.push(new Paragraph({
|
|
13726
|
+
children: [],
|
|
13727
|
+
border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
|
|
13728
|
+
}));
|
|
13729
|
+
break;
|
|
13730
|
+
}
|
|
13731
|
+
}
|
|
13732
|
+
const doc = new Document({
|
|
13733
|
+
numbering: {
|
|
13734
|
+
config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
|
|
13735
|
+
},
|
|
13736
|
+
sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
|
|
13737
|
+
});
|
|
13738
|
+
return new Uint8Array(await Packer.toArrayBuffer(doc));
|
|
13739
|
+
}
|
|
13740
|
+
function pptxConstructor(mod) {
|
|
13741
|
+
let c = mod;
|
|
13742
|
+
for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
|
|
13743
|
+
if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
|
|
13744
|
+
return c;
|
|
13745
|
+
}
|
|
13746
|
+
var SLIDE_H = 7.5;
|
|
13747
|
+
var CONTENT_X = 0.6;
|
|
13748
|
+
var CONTENT_W = 12.1;
|
|
13749
|
+
var CONTENT_BOTTOM = 0.4;
|
|
13750
|
+
async function renderPptx(md, opts = {}) {
|
|
13751
|
+
const PptxGen = pptxConstructor(await import('pptxgenjs'));
|
|
13752
|
+
const pptx = new PptxGen();
|
|
13753
|
+
pptx.layout = "LAYOUT_WIDE";
|
|
13754
|
+
const CHART_TYPES = {
|
|
13755
|
+
bar: pptx.ChartType["bar"],
|
|
13756
|
+
line: pptx.ChartType["line"],
|
|
13757
|
+
pie: pptx.ChartType["pie"],
|
|
13758
|
+
doughnut: pptx.ChartType["doughnut"],
|
|
13759
|
+
area: pptx.ChartType["area"],
|
|
13760
|
+
scatter: pptx.ChartType["scatter"]
|
|
13761
|
+
};
|
|
13762
|
+
const slides = splitSlides(md);
|
|
13763
|
+
const blank = { title: "", body: [""], images: [], charts: [] };
|
|
13764
|
+
for (const s of slides.length ? slides : [blank]) {
|
|
13765
|
+
const slide = pptx.addSlide();
|
|
13766
|
+
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" });
|
|
13767
|
+
const top = s.title ? 1.5 : 0.5;
|
|
13768
|
+
const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
|
|
13769
|
+
const visuals = [
|
|
13770
|
+
...s.charts.map((spec) => ({ kind: "chart", spec })),
|
|
13771
|
+
...pics.map((img) => ({ kind: "image", img }))
|
|
13772
|
+
];
|
|
13773
|
+
const bodyH = visuals.length ? 2.2 : 5.4;
|
|
13774
|
+
if (s.body.length) {
|
|
13775
|
+
slide.addText(
|
|
13776
|
+
s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
|
|
13777
|
+
{ x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
|
|
13778
|
+
);
|
|
13779
|
+
}
|
|
13780
|
+
if (visuals.length) {
|
|
13781
|
+
const y = top + (s.body.length ? bodyH + 0.2 : 0);
|
|
13782
|
+
const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
|
|
13783
|
+
const gap = 0.3;
|
|
13784
|
+
const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
|
|
13785
|
+
visuals.forEach((v, i) => {
|
|
13786
|
+
const x = CONTENT_X + i * (w + gap);
|
|
13787
|
+
if (v.kind === "image") {
|
|
13788
|
+
slide.addImage({
|
|
13789
|
+
data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
|
|
13790
|
+
x,
|
|
13791
|
+
y,
|
|
13792
|
+
w,
|
|
13793
|
+
h,
|
|
13794
|
+
// 'contain' keeps the aspect ratio inside the box — a stretched
|
|
13795
|
+
// render looks worse than a smaller correct one.
|
|
13796
|
+
sizing: { type: "contain", w, h },
|
|
13797
|
+
...v.img.alt ? { altText: v.img.alt } : {}
|
|
13798
|
+
});
|
|
13799
|
+
} else {
|
|
13800
|
+
slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
|
|
13801
|
+
x,
|
|
13802
|
+
y,
|
|
13803
|
+
w,
|
|
13804
|
+
h,
|
|
13805
|
+
showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
|
|
13806
|
+
legendPos: "b",
|
|
13807
|
+
showTitle: !!v.spec.title,
|
|
13808
|
+
...v.spec.title ? { title: v.spec.title } : {},
|
|
13809
|
+
showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
|
|
13810
|
+
});
|
|
13811
|
+
}
|
|
13812
|
+
});
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
|
|
13816
|
+
}
|
|
13817
|
+
function chartData(spec) {
|
|
13818
|
+
const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
|
|
13819
|
+
return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
|
|
13820
|
+
}
|
|
13821
|
+
async function renderDocument(format, source, opts = {}) {
|
|
13822
|
+
if (format === "docx") return renderDocx(source, opts);
|
|
13823
|
+
if (format === "pptx") return renderPptx(source, opts);
|
|
13824
|
+
return renderXlsx(source, opts);
|
|
13825
|
+
}
|
|
13826
|
+
|
|
13827
|
+
// src/tools/generate-document.ts
|
|
13828
|
+
var SOURCE_HINT = {
|
|
13829
|
+
docx: "Markdown",
|
|
13830
|
+
pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
|
|
13831
|
+
xlsx: "CSV (one header row, then data rows)"
|
|
13832
|
+
};
|
|
13833
|
+
var GenerateDocumentTool = class extends BaseTool {
|
|
13834
|
+
/**
|
|
13835
|
+
* `readFile` is injected so the image loader is testable and so a host with a
|
|
13836
|
+
* different workspace (not plain disk) can supply its own reader.
|
|
13837
|
+
*/
|
|
13838
|
+
constructor(readFile = async (p) => new Uint8Array(await fs11__default.default.readFile(p))) {
|
|
13839
|
+
super();
|
|
13840
|
+
this.readFile = readFile;
|
|
13841
|
+
}
|
|
13842
|
+
readFile;
|
|
13843
|
+
name = "generate_document";
|
|
13844
|
+
description = "Create a REAL Microsoft Office file (.docx Word, .pptx PowerPoint, .xlsx Excel) in the workspace. You write the SOURCE and this tool renders the actual binary: Markdown for .docx, Markdown slides (separated by --- rules) for .pptx, CSV for .xlsx. ALWAYS use this instead of file_write for those three extensions \u2014 file_write stores your text verbatim, which Office reports as a corrupted file. Supports embedded images (reference a generated image as  on its own line) and real, data-accurate charts (a ```chart:bar / chart:line / chart:pie fenced block whose body is CSV). Use pdf_create for PDFs.";
|
|
13845
|
+
inputSchema = {
|
|
13846
|
+
type: "object",
|
|
13847
|
+
properties: {
|
|
13848
|
+
path: {
|
|
13849
|
+
type: "string",
|
|
13850
|
+
description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
|
|
13851
|
+
},
|
|
13852
|
+
content: {
|
|
13853
|
+
type: "string",
|
|
13854
|
+
description: 'The document source. Markdown for .docx; Markdown slides separated by --- rules for .pptx (each slide starts with a heading); CSV for .xlsx. A standalone  line embeds that image. A fenced ```chart:bar (or chart:line, chart:pie, chart:area, chart:doughnut, chart:scatter) block whose body is CSV \u2014 an optional "title: \u2026" line, then a header row of "<category>,<series1>,<series2>", then data rows \u2014 becomes a real chart.'
|
|
13855
|
+
},
|
|
13856
|
+
format: {
|
|
13857
|
+
type: "string",
|
|
13858
|
+
description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
|
|
13859
|
+
}
|
|
13860
|
+
},
|
|
13861
|
+
required: ["path", "content"]
|
|
13862
|
+
};
|
|
13863
|
+
isDangerous() {
|
|
13864
|
+
return true;
|
|
13865
|
+
}
|
|
13866
|
+
async execute(input, options) {
|
|
13867
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
13868
|
+
if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
|
|
13869
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
13870
|
+
if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
|
|
13871
|
+
const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
|
|
13872
|
+
const ext = requested || fileExt(filePath);
|
|
13873
|
+
if (!isDocumentFormat(ext)) {
|
|
13874
|
+
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.`;
|
|
13875
|
+
}
|
|
13876
|
+
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
13877
|
+
const embedded = [];
|
|
13878
|
+
const skipped = [];
|
|
13879
|
+
const loadImageBytes = async (url) => {
|
|
13880
|
+
if (/^(https?|ftp):\/\//i.test(url)) {
|
|
13881
|
+
skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
|
|
13882
|
+
return null;
|
|
13883
|
+
}
|
|
13884
|
+
const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
|
|
13885
|
+
try {
|
|
13886
|
+
const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
|
|
13887
|
+
embedded.push(local);
|
|
13888
|
+
return bytes2;
|
|
13889
|
+
} catch {
|
|
13890
|
+
skipped.push(`${local} (not found in the workspace)`);
|
|
13891
|
+
return null;
|
|
13892
|
+
}
|
|
13893
|
+
};
|
|
13894
|
+
if (options.saveSnapshot) {
|
|
13895
|
+
try {
|
|
13896
|
+
const old = await fs11__default.default.readFile(absPath, "utf-8");
|
|
13897
|
+
await options.saveSnapshot(absPath, old);
|
|
13898
|
+
} catch {
|
|
13899
|
+
}
|
|
13900
|
+
}
|
|
13901
|
+
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
13902
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(absPath), { recursive: true });
|
|
13903
|
+
await fs11__default.default.writeFile(absPath, bytes);
|
|
13904
|
+
const charts = countCharts(content);
|
|
13905
|
+
return [
|
|
13906
|
+
`Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
|
|
13907
|
+
embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
|
|
13908
|
+
// Named, not counted: a dropped image is the failure the user actually
|
|
13909
|
+
// notices, and the model can only fix it if it is told which reference died.
|
|
13910
|
+
skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
|
|
13911
|
+
charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
|
|
13912
|
+
charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
|
|
13913
|
+
charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
|
|
13914
|
+
].filter(Boolean).join("\n");
|
|
13915
|
+
}
|
|
13916
|
+
};
|
|
13917
|
+
var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
|
|
13918
|
+
function countCharts(source) {
|
|
13919
|
+
return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
|
|
13920
|
+
}
|
|
13921
|
+
function buildDocumentTools(readFile) {
|
|
13922
|
+
return readFile ? [new GenerateDocumentTool(readFile)] : [];
|
|
13923
|
+
}
|
|
12826
13924
|
|
|
12827
13925
|
// src/core/run-breaker.ts
|
|
12828
13926
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
@@ -12971,35 +14069,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
|
|
|
12971
14069
|
}
|
|
12972
14070
|
}
|
|
12973
14071
|
};
|
|
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
14072
|
var FileReadTool = class extends BaseTool {
|
|
13004
14073
|
name = "file_read";
|
|
13005
14074
|
description = "Read the contents of a file. Returns the file content as a string.";
|
|
@@ -13017,7 +14086,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
13017
14086
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
13018
14087
|
const offset = input["offset"] ?? 1;
|
|
13019
14088
|
const limit = input["limit"];
|
|
13020
|
-
const content = await
|
|
14089
|
+
const content = await fs11__default.default.readFile(absPath, "utf-8");
|
|
13021
14090
|
const lines = content.split("\n");
|
|
13022
14091
|
const start = Math.max(0, offset - 1);
|
|
13023
14092
|
const end = limit ? start + limit : lines.length;
|
|
@@ -13046,13 +14115,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
13046
14115
|
const content = input["content"];
|
|
13047
14116
|
if (options.saveSnapshot) {
|
|
13048
14117
|
try {
|
|
13049
|
-
const oldContent = await
|
|
14118
|
+
const oldContent = await fs11__default.default.readFile(absPath, "utf-8");
|
|
13050
14119
|
await options.saveSnapshot(absPath, oldContent);
|
|
13051
14120
|
} catch {
|
|
13052
14121
|
}
|
|
13053
14122
|
}
|
|
13054
|
-
await
|
|
13055
|
-
await
|
|
14123
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(absPath), { recursive: true });
|
|
14124
|
+
await fs11__default.default.writeFile(absPath, content, "utf-8");
|
|
13056
14125
|
return `Written ${content.length} characters to ${filePath}`;
|
|
13057
14126
|
}
|
|
13058
14127
|
};
|
|
@@ -13078,7 +14147,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
13078
14147
|
const oldString = input["old_string"];
|
|
13079
14148
|
const newString = input["new_string"];
|
|
13080
14149
|
const replaceAll = input["replace_all"] ?? false;
|
|
13081
|
-
const rawContent = await
|
|
14150
|
+
const rawContent = await fs11__default.default.readFile(absPath, "utf-8");
|
|
13082
14151
|
if (options.saveSnapshot) {
|
|
13083
14152
|
await options.saveSnapshot(absPath, rawContent);
|
|
13084
14153
|
}
|
|
@@ -13090,7 +14159,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
13090
14159
|
);
|
|
13091
14160
|
}
|
|
13092
14161
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
13093
|
-
await
|
|
14162
|
+
await fs11__default.default.writeFile(absPath, updated, "utf-8");
|
|
13094
14163
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
13095
14164
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
13096
14165
|
}
|
|
@@ -13113,12 +14182,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
13113
14182
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
13114
14183
|
if (options.saveSnapshot) {
|
|
13115
14184
|
try {
|
|
13116
|
-
const oldContent = await
|
|
14185
|
+
const oldContent = await fs11__default.default.readFile(absPath, "utf-8");
|
|
13117
14186
|
await options.saveSnapshot(absPath, oldContent);
|
|
13118
14187
|
} catch {
|
|
13119
14188
|
}
|
|
13120
14189
|
}
|
|
13121
|
-
await
|
|
14190
|
+
await fs11__default.default.rm(absPath, { recursive: false });
|
|
13122
14191
|
return `Deleted ${filePath}`;
|
|
13123
14192
|
}
|
|
13124
14193
|
};
|
|
@@ -13135,7 +14204,7 @@ var FileListTool = class extends BaseTool {
|
|
|
13135
14204
|
async execute(input, _options) {
|
|
13136
14205
|
const inputPath = input["path"] || ".";
|
|
13137
14206
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
13138
|
-
const entries = await
|
|
14207
|
+
const entries = await fs11__default.default.readdir(absPath, { withFileTypes: true });
|
|
13139
14208
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
13140
14209
|
}
|
|
13141
14210
|
};
|
|
@@ -13548,8 +14617,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
13548
14617
|
}
|
|
13549
14618
|
};
|
|
13550
14619
|
async function fileToImageAttachment(filePath) {
|
|
13551
|
-
const data = await
|
|
13552
|
-
const ext =
|
|
14620
|
+
const data = await fs11__default.default.readFile(filePath);
|
|
14621
|
+
const ext = path34__default.default.extname(filePath).toLowerCase();
|
|
13553
14622
|
const mimeMap = {
|
|
13554
14623
|
".jpg": "image/jpeg",
|
|
13555
14624
|
".jpeg": "image/jpeg",
|
|
@@ -13583,14 +14652,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
13583
14652
|
const filePath = input["path"];
|
|
13584
14653
|
const content = input["content"];
|
|
13585
14654
|
const title = input["title"];
|
|
13586
|
-
const dir =
|
|
13587
|
-
if (!
|
|
13588
|
-
|
|
14655
|
+
const dir = path34__default.default.dirname(filePath);
|
|
14656
|
+
if (!fs31__default.default.existsSync(dir)) {
|
|
14657
|
+
fs31__default.default.mkdirSync(dir, { recursive: true });
|
|
13589
14658
|
}
|
|
13590
14659
|
return new Promise((resolve2, reject) => {
|
|
13591
14660
|
try {
|
|
13592
14661
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
13593
|
-
const stream =
|
|
14662
|
+
const stream = fs31__default.default.createWriteStream(filePath);
|
|
13594
14663
|
doc.pipe(stream);
|
|
13595
14664
|
if (title) {
|
|
13596
14665
|
doc.info["Title"] = title;
|
|
@@ -13668,22 +14737,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
13668
14737
|
}
|
|
13669
14738
|
cmdPrefix = NODE_CMD;
|
|
13670
14739
|
}
|
|
13671
|
-
const tmpDir =
|
|
13672
|
-
if (!
|
|
13673
|
-
|
|
14740
|
+
const tmpDir = path34__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
14741
|
+
if (!fs31__default.default.existsSync(tmpDir)) {
|
|
14742
|
+
fs31__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
13674
14743
|
}
|
|
13675
14744
|
const extension = language === "python" ? "py" : "js";
|
|
13676
14745
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
13677
|
-
const filePath =
|
|
13678
|
-
|
|
14746
|
+
const filePath = path34__default.default.join(tmpDir, fileName);
|
|
14747
|
+
fs31__default.default.writeFileSync(filePath, code, "utf-8");
|
|
13679
14748
|
const execArgs = [filePath, ...args];
|
|
13680
14749
|
return new Promise((resolve2) => {
|
|
13681
14750
|
const startMs = Date.now();
|
|
13682
14751
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
13683
14752
|
const duration = Date.now() - startMs;
|
|
13684
14753
|
try {
|
|
13685
|
-
if (
|
|
13686
|
-
|
|
14754
|
+
if (fs31__default.default.existsSync(filePath)) {
|
|
14755
|
+
fs31__default.default.unlinkSync(filePath);
|
|
13687
14756
|
}
|
|
13688
14757
|
} catch (cleanupErr) {
|
|
13689
14758
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -13995,7 +15064,7 @@ var GlobTool = class extends BaseTool {
|
|
|
13995
15064
|
};
|
|
13996
15065
|
async execute(input, _options) {
|
|
13997
15066
|
const pattern = input["pattern"];
|
|
13998
|
-
const searchPath = input["path"] ?
|
|
15067
|
+
const searchPath = input["path"] ? path34__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
13999
15068
|
const matches = await glob.glob(pattern, {
|
|
14000
15069
|
cwd: searchPath,
|
|
14001
15070
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -14008,7 +15077,7 @@ var GlobTool = class extends BaseTool {
|
|
|
14008
15077
|
const withMtime = await Promise.all(
|
|
14009
15078
|
matches.map(async (rel) => {
|
|
14010
15079
|
try {
|
|
14011
|
-
const stat = await
|
|
15080
|
+
const stat = await fs11__default.default.stat(path34__default.default.join(searchPath, rel));
|
|
14012
15081
|
return { rel, mtime: stat.mtimeMs };
|
|
14013
15082
|
} catch {
|
|
14014
15083
|
return { rel, mtime: 0 };
|
|
@@ -14057,7 +15126,7 @@ var GrepTool = class extends BaseTool {
|
|
|
14057
15126
|
};
|
|
14058
15127
|
async execute(input, _options) {
|
|
14059
15128
|
const pattern = input["pattern"];
|
|
14060
|
-
const searchPath = input["path"] ?
|
|
15129
|
+
const searchPath = input["path"] ? path34__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
14061
15130
|
const globPattern = input["glob"];
|
|
14062
15131
|
const outputMode = input["output_mode"] ?? "content";
|
|
14063
15132
|
const context = input["context"] ?? 0;
|
|
@@ -14111,15 +15180,15 @@ var GrepTool = class extends BaseTool {
|
|
|
14111
15180
|
nodir: true
|
|
14112
15181
|
});
|
|
14113
15182
|
} catch {
|
|
14114
|
-
files = [
|
|
15183
|
+
files = [path34__default.default.relative(searchPath, searchPath) || "."];
|
|
14115
15184
|
}
|
|
14116
15185
|
const results = [];
|
|
14117
15186
|
let totalCount = 0;
|
|
14118
15187
|
for (const rel of files) {
|
|
14119
|
-
const abs =
|
|
15188
|
+
const abs = path34__default.default.join(searchPath, rel);
|
|
14120
15189
|
let content;
|
|
14121
15190
|
try {
|
|
14122
|
-
content = await
|
|
15191
|
+
content = await fs11__default.default.readFile(abs, "utf-8");
|
|
14123
15192
|
} catch {
|
|
14124
15193
|
continue;
|
|
14125
15194
|
}
|
|
@@ -14503,10 +15572,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
14503
15572
|
}
|
|
14504
15573
|
isIgnored(filePath) {
|
|
14505
15574
|
if (!filePath) return false;
|
|
14506
|
-
const abs =
|
|
14507
|
-
const rel =
|
|
14508
|
-
if (!rel || rel.startsWith("..") ||
|
|
14509
|
-
const posixRel = rel.split(
|
|
15575
|
+
const abs = path34__default.default.resolve(this.workspaceRoot, filePath);
|
|
15576
|
+
const rel = path34__default.default.relative(this.workspaceRoot, abs);
|
|
15577
|
+
if (!rel || rel.startsWith("..") || path34__default.default.isAbsolute(rel)) return true;
|
|
15578
|
+
const posixRel = rel.split(path34__default.default.sep).join("/");
|
|
14510
15579
|
return this.ignoreMatcher.ignores(posixRel);
|
|
14511
15580
|
}
|
|
14512
15581
|
};
|
|
@@ -14740,22 +15809,22 @@ var FileMcpOAuthStore = class {
|
|
|
14740
15809
|
filePath;
|
|
14741
15810
|
load() {
|
|
14742
15811
|
try {
|
|
14743
|
-
return JSON.parse(
|
|
15812
|
+
return JSON.parse(fs31__default.default.readFileSync(this.filePath, "utf-8"));
|
|
14744
15813
|
} catch {
|
|
14745
15814
|
return void 0;
|
|
14746
15815
|
}
|
|
14747
15816
|
}
|
|
14748
15817
|
save(state) {
|
|
14749
|
-
|
|
14750
|
-
|
|
15818
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
15819
|
+
fs31__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
14751
15820
|
try {
|
|
14752
|
-
|
|
15821
|
+
fs31__default.default.chmodSync(this.filePath, 384);
|
|
14753
15822
|
} catch {
|
|
14754
15823
|
}
|
|
14755
15824
|
}
|
|
14756
15825
|
clear() {
|
|
14757
15826
|
try {
|
|
14758
|
-
|
|
15827
|
+
fs31__default.default.rmSync(this.filePath);
|
|
14759
15828
|
} catch {
|
|
14760
15829
|
}
|
|
14761
15830
|
}
|
|
@@ -15218,6 +16287,14 @@ function heuristicAnalyze(prompt) {
|
|
|
15218
16287
|
const estimatedTokens = wordCount * 5;
|
|
15219
16288
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
15220
16289
|
}
|
|
16290
|
+
function hashPrompt(prompt) {
|
|
16291
|
+
let hash = 2166136261;
|
|
16292
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
16293
|
+
hash ^= prompt.charCodeAt(index);
|
|
16294
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
16295
|
+
}
|
|
16296
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
16297
|
+
}
|
|
15221
16298
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
15222
16299
|
var TASK_TYPE_TAGS = {
|
|
15223
16300
|
code: ["code", "instruction"],
|
|
@@ -15261,7 +16338,7 @@ var TaskAnalyzer = class {
|
|
|
15261
16338
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
15262
16339
|
*/
|
|
15263
16340
|
async analyze(prompt) {
|
|
15264
|
-
const cacheKey = prompt
|
|
16341
|
+
const cacheKey = hashPrompt(prompt);
|
|
15265
16342
|
const cached = analysisCache.get(cacheKey);
|
|
15266
16343
|
if (cached) {
|
|
15267
16344
|
this.lastProfile = cached;
|
|
@@ -15363,7 +16440,7 @@ var TaskAnalyzer = class {
|
|
|
15363
16440
|
|
|
15364
16441
|
// src/core/router/model-performance-tracker.ts
|
|
15365
16442
|
init_pricing();
|
|
15366
|
-
var DEFAULT_STATS_FILE =
|
|
16443
|
+
var DEFAULT_STATS_FILE = path34__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
|
|
15367
16444
|
var ModelPerformanceTracker = class {
|
|
15368
16445
|
stats = /* @__PURE__ */ new Map();
|
|
15369
16446
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -15383,7 +16460,7 @@ var ModelPerformanceTracker = class {
|
|
|
15383
16460
|
if (this.loaded) return;
|
|
15384
16461
|
this.loaded = true;
|
|
15385
16462
|
try {
|
|
15386
|
-
const raw = await
|
|
16463
|
+
const raw = await fs11__default.default.readFile(this.statsFile, "utf-8");
|
|
15387
16464
|
const parsed = JSON.parse(raw);
|
|
15388
16465
|
if (parsed.models) {
|
|
15389
16466
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -15403,15 +16480,15 @@ var ModelPerformanceTracker = class {
|
|
|
15403
16480
|
async save() {
|
|
15404
16481
|
if (this.readOnly) return;
|
|
15405
16482
|
try {
|
|
15406
|
-
await
|
|
16483
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(this.statsFile), { recursive: true });
|
|
15407
16484
|
const modelsObj = {};
|
|
15408
16485
|
const featuresObj = {};
|
|
15409
16486
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
15410
16487
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
15411
16488
|
const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
|
|
15412
16489
|
const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
|
|
15413
|
-
await
|
|
15414
|
-
await
|
|
16490
|
+
await fs11__default.default.writeFile(tmp, json, "utf-8");
|
|
16491
|
+
await fs11__default.default.rename(tmp, this.statsFile);
|
|
15415
16492
|
} catch {
|
|
15416
16493
|
}
|
|
15417
16494
|
}
|
|
@@ -15951,9 +17028,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
15951
17028
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
15952
17029
|
async loadPersistedTools() {
|
|
15953
17030
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
15954
|
-
const file =
|
|
17031
|
+
const file = path34__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
15955
17032
|
try {
|
|
15956
|
-
const raw = await
|
|
17033
|
+
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
15957
17034
|
const specs = JSON.parse(raw);
|
|
15958
17035
|
if (!Array.isArray(specs)) return;
|
|
15959
17036
|
let loaded = 0;
|
|
@@ -15975,11 +17052,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
15975
17052
|
}
|
|
15976
17053
|
async persist() {
|
|
15977
17054
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
15978
|
-
const dir =
|
|
15979
|
-
const file =
|
|
17055
|
+
const dir = path34__default.default.join(this.workspacePath, ".cascade");
|
|
17056
|
+
const file = path34__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
15980
17057
|
try {
|
|
15981
|
-
await
|
|
15982
|
-
await
|
|
17058
|
+
await fs11__default.default.mkdir(dir, { recursive: true });
|
|
17059
|
+
await fs11__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
15983
17060
|
} catch (err) {
|
|
15984
17061
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
15985
17062
|
}
|
|
@@ -15996,12 +17073,12 @@ var WorldStateDB = class {
|
|
|
15996
17073
|
constructor(workspacePath, debugMode = false) {
|
|
15997
17074
|
this.workspacePath = workspacePath;
|
|
15998
17075
|
this.debugMode = debugMode;
|
|
15999
|
-
const cascadeDir =
|
|
16000
|
-
if (!
|
|
16001
|
-
|
|
17076
|
+
const cascadeDir = path34__default.default.join(workspacePath, ".cascade");
|
|
17077
|
+
if (!fs31__default.default.existsSync(cascadeDir)) {
|
|
17078
|
+
fs31__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
16002
17079
|
}
|
|
16003
|
-
this.keyPath =
|
|
16004
|
-
this.dbPath =
|
|
17080
|
+
this.keyPath = path34__default.default.join(cascadeDir, "world_state.key");
|
|
17081
|
+
this.dbPath = path34__default.default.join(cascadeDir, "world_state.db");
|
|
16005
17082
|
this.initEncryptionKey();
|
|
16006
17083
|
this.db = new Database2__default.default(this.dbPath);
|
|
16007
17084
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -16058,11 +17135,11 @@ var WorldStateDB = class {
|
|
|
16058
17135
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
16059
17136
|
}
|
|
16060
17137
|
initEncryptionKey() {
|
|
16061
|
-
if (
|
|
16062
|
-
this.encryptionKey =
|
|
17138
|
+
if (fs31__default.default.existsSync(this.keyPath)) {
|
|
17139
|
+
this.encryptionKey = fs31__default.default.readFileSync(this.keyPath);
|
|
16063
17140
|
} else {
|
|
16064
17141
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
16065
|
-
|
|
17142
|
+
fs31__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
16066
17143
|
}
|
|
16067
17144
|
}
|
|
16068
17145
|
encrypt(text) {
|
|
@@ -16348,9 +17425,9 @@ var WorldStateDB = class {
|
|
|
16348
17425
|
dumpDebugIfNeeded() {
|
|
16349
17426
|
if (!this.debugMode) return;
|
|
16350
17427
|
try {
|
|
16351
|
-
const dumpPath =
|
|
17428
|
+
const dumpPath = path34__default.default.join(os10__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
16352
17429
|
const entries = this.getAllEntries();
|
|
16353
|
-
|
|
17430
|
+
fs31__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
16354
17431
|
} catch (err) {
|
|
16355
17432
|
console.error("Failed to dump debug world state", err);
|
|
16356
17433
|
}
|
|
@@ -16359,6 +17436,128 @@ var WorldStateDB = class {
|
|
|
16359
17436
|
this.db.close();
|
|
16360
17437
|
}
|
|
16361
17438
|
};
|
|
17439
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
17440
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
17441
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
17442
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
17443
|
+
function safeId(taskId) {
|
|
17444
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
17445
|
+
return cleaned.slice(0, 100) || "run";
|
|
17446
|
+
}
|
|
17447
|
+
function isCheckpoint(value) {
|
|
17448
|
+
if (typeof value !== "object" || value === null) return false;
|
|
17449
|
+
const c = value;
|
|
17450
|
+
return c.version === RESUME_SCHEMA_VERSION && typeof c.taskId === "string" && typeof c.prompt === "string" && typeof c.createdAt === "string" && typeof c.partialOutput === "string" && Array.isArray(c.completed);
|
|
17451
|
+
}
|
|
17452
|
+
var ResumeStore = class {
|
|
17453
|
+
dir;
|
|
17454
|
+
maxCheckpoints;
|
|
17455
|
+
maxAgeMs;
|
|
17456
|
+
now;
|
|
17457
|
+
constructor(options) {
|
|
17458
|
+
this.dir = options.dir;
|
|
17459
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
17460
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
17461
|
+
this.now = options.now ?? Date.now;
|
|
17462
|
+
}
|
|
17463
|
+
/**
|
|
17464
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
17465
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
17466
|
+
* worse outcome than the one this store exists to improve.
|
|
17467
|
+
*/
|
|
17468
|
+
async save(checkpoint) {
|
|
17469
|
+
try {
|
|
17470
|
+
const timestamp = this.now();
|
|
17471
|
+
const record = {
|
|
17472
|
+
...checkpoint,
|
|
17473
|
+
version: RESUME_SCHEMA_VERSION,
|
|
17474
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
17475
|
+
};
|
|
17476
|
+
await fs31.promises.mkdir(this.dir, { recursive: true });
|
|
17477
|
+
const finalPath = path34__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
17478
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
17479
|
+
await fs31.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
17480
|
+
await fs31.promises.rename(tempPath, finalPath);
|
|
17481
|
+
await this.prune();
|
|
17482
|
+
} catch {
|
|
17483
|
+
}
|
|
17484
|
+
}
|
|
17485
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
17486
|
+
async list() {
|
|
17487
|
+
let entries;
|
|
17488
|
+
try {
|
|
17489
|
+
entries = await fs31.promises.readdir(this.dir);
|
|
17490
|
+
} catch {
|
|
17491
|
+
return [];
|
|
17492
|
+
}
|
|
17493
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
17494
|
+
const candidates2 = entries.map((name) => ({ name, match: FILE_PATTERN.exec(name) })).filter((e) => e.match !== null).map((e) => ({ name: e.name, timestamp: Number(e.match[1]) })).filter((e) => e.timestamp >= cutoff).sort((a, b) => b.timestamp - a.timestamp);
|
|
17495
|
+
const checkpoints = [];
|
|
17496
|
+
for (const candidate of candidates2) {
|
|
17497
|
+
try {
|
|
17498
|
+
const raw = await fs31.promises.readFile(path34__default.default.join(this.dir, candidate.name), "utf-8");
|
|
17499
|
+
const parsed = JSON.parse(raw);
|
|
17500
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
17501
|
+
} catch {
|
|
17502
|
+
continue;
|
|
17503
|
+
}
|
|
17504
|
+
}
|
|
17505
|
+
return checkpoints;
|
|
17506
|
+
}
|
|
17507
|
+
/** The most recent resumable run, or null. */
|
|
17508
|
+
async latest() {
|
|
17509
|
+
return (await this.list())[0] ?? null;
|
|
17510
|
+
}
|
|
17511
|
+
/**
|
|
17512
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
17513
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
17514
|
+
*/
|
|
17515
|
+
async consume(taskId) {
|
|
17516
|
+
const all = await this.list();
|
|
17517
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
17518
|
+
if (!target) return null;
|
|
17519
|
+
await this.delete(target.taskId);
|
|
17520
|
+
return target;
|
|
17521
|
+
}
|
|
17522
|
+
/** Remove every checkpoint for a task id. */
|
|
17523
|
+
async delete(taskId) {
|
|
17524
|
+
try {
|
|
17525
|
+
const entries = await fs31.promises.readdir(this.dir);
|
|
17526
|
+
const wanted = safeId(taskId);
|
|
17527
|
+
await Promise.all(entries.map(async (name) => {
|
|
17528
|
+
const match = FILE_PATTERN.exec(name);
|
|
17529
|
+
if (match?.[2] === wanted) {
|
|
17530
|
+
await fs31.promises.rm(path34__default.default.join(this.dir, name), { force: true });
|
|
17531
|
+
}
|
|
17532
|
+
}));
|
|
17533
|
+
} catch {
|
|
17534
|
+
}
|
|
17535
|
+
}
|
|
17536
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
17537
|
+
async prune() {
|
|
17538
|
+
try {
|
|
17539
|
+
const entries = await fs31.promises.readdir(this.dir);
|
|
17540
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
17541
|
+
const files = entries.map((name) => ({ name, match: FILE_PATTERN.exec(name) })).filter((e) => e.match !== null).map((e) => ({ name: e.name, timestamp: Number(e.match[1]) })).sort((a, b) => b.timestamp - a.timestamp);
|
|
17542
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
17543
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
17544
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
17545
|
+
(name) => fs31.promises.rm(path34__default.default.join(this.dir, name), { force: true })
|
|
17546
|
+
));
|
|
17547
|
+
} catch {
|
|
17548
|
+
}
|
|
17549
|
+
}
|
|
17550
|
+
};
|
|
17551
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
17552
|
+
if (!completed.length) return "";
|
|
17553
|
+
const lines = completed.map((node) => {
|
|
17554
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
17555
|
+
return `- [${node.status}] ${node.title}
|
|
17556
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
17557
|
+
});
|
|
17558
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
17559
|
+
${lines.join("\n")}`;
|
|
17560
|
+
}
|
|
16362
17561
|
var ignore3 = _ignoreModule__namespace.default ?? _ignoreModule__namespace;
|
|
16363
17562
|
var PrivacyPaths = class {
|
|
16364
17563
|
localOnly;
|
|
@@ -16720,7 +17919,7 @@ var WorkspaceIndex = class {
|
|
|
16720
17919
|
isIgnored;
|
|
16721
17920
|
chunker;
|
|
16722
17921
|
constructor(opts) {
|
|
16723
|
-
this.root =
|
|
17922
|
+
this.root = path34__default.default.resolve(opts.root);
|
|
16724
17923
|
this.db = opts.db;
|
|
16725
17924
|
this.namespace = opts.namespace ?? "code";
|
|
16726
17925
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -16747,19 +17946,19 @@ var WorkspaceIndex = class {
|
|
|
16747
17946
|
const walk = async (dir) => {
|
|
16748
17947
|
let entries;
|
|
16749
17948
|
try {
|
|
16750
|
-
entries = await
|
|
17949
|
+
entries = await fs11__default.default.readdir(dir, { withFileTypes: true });
|
|
16751
17950
|
} catch {
|
|
16752
17951
|
return;
|
|
16753
17952
|
}
|
|
16754
17953
|
for (const entry of entries) {
|
|
16755
|
-
const abs =
|
|
17954
|
+
const abs = path34__default.default.join(dir, entry.name);
|
|
16756
17955
|
if (entry.isDirectory()) {
|
|
16757
17956
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
16758
17957
|
await walk(abs);
|
|
16759
17958
|
} else if (entry.isFile()) {
|
|
16760
|
-
const ext =
|
|
17959
|
+
const ext = path34__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
16761
17960
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
16762
|
-
out.push(
|
|
17961
|
+
out.push(path34__default.default.relative(this.root, abs));
|
|
16763
17962
|
}
|
|
16764
17963
|
}
|
|
16765
17964
|
};
|
|
@@ -16789,10 +17988,10 @@ var WorkspaceIndex = class {
|
|
|
16789
17988
|
const entries = [];
|
|
16790
17989
|
for (const rel of rels) {
|
|
16791
17990
|
try {
|
|
16792
|
-
const abs =
|
|
16793
|
-
const stat = await
|
|
17991
|
+
const abs = path34__default.default.join(this.root, rel);
|
|
17992
|
+
const stat = await fs11__default.default.stat(abs);
|
|
16794
17993
|
if (stat.size > this.maxFileBytes) continue;
|
|
16795
|
-
const buf = await
|
|
17994
|
+
const buf = await fs11__default.default.readFile(abs);
|
|
16796
17995
|
if (buf.includes(0)) continue;
|
|
16797
17996
|
entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
|
|
16798
17997
|
} catch {
|
|
@@ -17327,8 +18526,16 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
17327
18526
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
17328
18527
|
decisionLog = [];
|
|
17329
18528
|
initialized = false;
|
|
17330
|
-
/**
|
|
18529
|
+
/**
|
|
18530
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
18531
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
18532
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
18533
|
+
*/
|
|
17331
18534
|
lastInterruptedRun;
|
|
18535
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
18536
|
+
resumeStore;
|
|
18537
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
18538
|
+
currentRunCompleted = [];
|
|
17332
18539
|
initPromise;
|
|
17333
18540
|
store;
|
|
17334
18541
|
audit;
|
|
@@ -17361,14 +18568,21 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
17361
18568
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
17362
18569
|
try {
|
|
17363
18570
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
17364
|
-
fileDeadModelPersistence(
|
|
18571
|
+
fileDeadModelPersistence(path34__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
17365
18572
|
));
|
|
17366
18573
|
} catch {
|
|
17367
18574
|
}
|
|
18575
|
+
try {
|
|
18576
|
+
this.resumeStore = new ResumeStore({
|
|
18577
|
+
dir: path34__default.default.join(workspacePath, ".cascade", "resume")
|
|
18578
|
+
});
|
|
18579
|
+
} catch {
|
|
18580
|
+
}
|
|
17368
18581
|
this.multimodal = new MultimodalRegistry(
|
|
17369
18582
|
(this.config.providers ?? []).map((p) => p.type)
|
|
17370
18583
|
);
|
|
17371
18584
|
this.registerMediaTools(workspacePath);
|
|
18585
|
+
this.registerDocumentTools(workspacePath);
|
|
17372
18586
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
17373
18587
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
17374
18588
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -17448,8 +18662,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
17448
18662
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
17449
18663
|
return;
|
|
17450
18664
|
}
|
|
17451
|
-
const dbPath = ci.dbPath ||
|
|
17452
|
-
|
|
18665
|
+
const dbPath = ci.dbPath || path34__default.default.join(this.workspacePath, ".cascade", "code-index.db");
|
|
18666
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(dbPath), { recursive: true });
|
|
17453
18667
|
const db = new Database2__default.default(dbPath);
|
|
17454
18668
|
db.pragma("journal_mode = WAL");
|
|
17455
18669
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -17789,6 +19003,35 @@ ${input}` }],
|
|
|
17789
19003
|
this.toolRegistry.register(tool);
|
|
17790
19004
|
}
|
|
17791
19005
|
}
|
|
19006
|
+
/**
|
|
19007
|
+
* Register `generate_document` where there is a real workspace to write into.
|
|
19008
|
+
*
|
|
19009
|
+
* A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
|
|
19010
|
+
* does exactly what it says and writes the model's text verbatim — produced
|
|
19011
|
+
* files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
|
|
19012
|
+
* the conversion (see tools/generate-document.ts for why it isn't folded into
|
|
19013
|
+
* file_write).
|
|
19014
|
+
*
|
|
19015
|
+
* Registered OUTSIDE `enabledTools` for the same reason the media tools are:
|
|
19016
|
+
* that allowlist is a blast-radius control for tools that reach the machine
|
|
19017
|
+
* or the network, and this one only writes the artifact the user asked for,
|
|
19018
|
+
* into the workspace the run already owns. `disabledTools` still removes it.
|
|
19019
|
+
* A host with no filesystem (the cloud) has its own browser-side exporter and
|
|
19020
|
+
* simply never reaches this path — `buildDocumentTools` returns nothing
|
|
19021
|
+
* without a file reader, mirroring `transcribe_audio`.
|
|
19022
|
+
*/
|
|
19023
|
+
registerDocumentTools(workspacePath) {
|
|
19024
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
19025
|
+
const tools = buildDocumentTools(async (p) => {
|
|
19026
|
+
const fsp = await import('fs/promises');
|
|
19027
|
+
return new Uint8Array(await fsp.readFile(p));
|
|
19028
|
+
});
|
|
19029
|
+
for (const tool of tools) {
|
|
19030
|
+
if (denied.has(tool.name)) continue;
|
|
19031
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
19032
|
+
this.toolRegistry.register(tool);
|
|
19033
|
+
}
|
|
19034
|
+
}
|
|
17792
19035
|
/**
|
|
17793
19036
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
17794
19037
|
* have actually performed for this user, on top of public benchmarks.
|
|
@@ -17800,10 +19043,43 @@ ${input}` }],
|
|
|
17800
19043
|
setFeedbackSource(source) {
|
|
17801
19044
|
this.router.setFeedbackSource(source);
|
|
17802
19045
|
}
|
|
17803
|
-
/**
|
|
19046
|
+
/**
|
|
19047
|
+
* Write a durable checkpoint for an interrupted run.
|
|
19048
|
+
*
|
|
19049
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
19050
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
19051
|
+
* worse. Nothing here is allowed to throw.
|
|
19052
|
+
*/
|
|
19053
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
19054
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
19055
|
+
try {
|
|
19056
|
+
await this.resumeStore?.save({
|
|
19057
|
+
taskId,
|
|
19058
|
+
prompt,
|
|
19059
|
+
reason,
|
|
19060
|
+
...detail ? { detail } : {},
|
|
19061
|
+
partialOutput,
|
|
19062
|
+
completed: [...this.currentRunCompleted]
|
|
19063
|
+
});
|
|
19064
|
+
} catch {
|
|
19065
|
+
}
|
|
19066
|
+
}
|
|
19067
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
17804
19068
|
hasResumableRun() {
|
|
17805
19069
|
return this.lastInterruptedRun != null;
|
|
17806
19070
|
}
|
|
19071
|
+
/**
|
|
19072
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
19073
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
19074
|
+
* answer for callers already on a hot path.
|
|
19075
|
+
*/
|
|
19076
|
+
async hasDurableResume() {
|
|
19077
|
+
return await this.resumeStore?.latest() != null;
|
|
19078
|
+
}
|
|
19079
|
+
/** Interrupted runs available to resume, newest first. */
|
|
19080
|
+
async listResumableRuns() {
|
|
19081
|
+
return await this.resumeStore?.list() ?? [];
|
|
19082
|
+
}
|
|
17807
19083
|
/**
|
|
17808
19084
|
* Raise the per-run token budget for a resume and return the continuation
|
|
17809
19085
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -17814,22 +19090,54 @@ ${input}` }],
|
|
|
17814
19090
|
const last = this.lastInterruptedRun;
|
|
17815
19091
|
if (!last) return null;
|
|
17816
19092
|
this.lastInterruptedRun = void 0;
|
|
17817
|
-
|
|
19093
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
19094
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
19095
|
+
}
|
|
19096
|
+
/**
|
|
19097
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
19098
|
+
* process, including one that crashed before it could return. Consumes the
|
|
19099
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
19100
|
+
*/
|
|
19101
|
+
async prepareDurableResume(opts = {}) {
|
|
19102
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
19103
|
+
if (!checkpoint) return null;
|
|
19104
|
+
this.lastInterruptedRun = void 0;
|
|
19105
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
19106
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
19107
|
+
}
|
|
19108
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
19109
|
+
raiseBudgetForResume(maxTokens) {
|
|
19110
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
17818
19111
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
17819
19112
|
this.router.setMaxTokensPerRun(raised);
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17823
|
-
|
|
19113
|
+
}
|
|
19114
|
+
/**
|
|
19115
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
19116
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
19117
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
19118
|
+
* is something it can act on. That difference is the entire reason node
|
|
19119
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
19120
|
+
* finished work again, and the user pays for it a second time.
|
|
19121
|
+
*/
|
|
19122
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
19123
|
+
const done = summarizeCompleted(completed);
|
|
19124
|
+
return [
|
|
19125
|
+
"Continue and FINISH this task. A previous attempt was interrupted before completion; any files already created are on disk \u2014 build on them, do NOT recreate them. Plan ONLY the remaining work.",
|
|
19126
|
+
"",
|
|
19127
|
+
`Original task: ${prompt}`,
|
|
19128
|
+
done ? `
|
|
19129
|
+
${done}` : "",
|
|
19130
|
+
partialOutput ? `
|
|
17824
19131
|
Partial result so far:
|
|
17825
|
-
${
|
|
19132
|
+
${partialOutput}` : ""
|
|
19133
|
+
].filter(Boolean).join("\n");
|
|
17826
19134
|
}
|
|
17827
19135
|
/**
|
|
17828
19136
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
17829
19137
|
* Returns null when there is nothing to resume.
|
|
17830
19138
|
*/
|
|
17831
19139
|
async resumeRun(opts = {}) {
|
|
17832
|
-
const prompt = this.prepareResume(opts);
|
|
19140
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
17833
19141
|
if (!prompt) return null;
|
|
17834
19142
|
return this.run({ prompt });
|
|
17835
19143
|
}
|
|
@@ -18200,6 +19508,7 @@ ${prompt}` : prompt;
|
|
|
18200
19508
|
const startMs = Date.now();
|
|
18201
19509
|
const taskId = crypto.randomUUID();
|
|
18202
19510
|
this.decisionLog = [];
|
|
19511
|
+
this.currentRunCompleted = [];
|
|
18203
19512
|
if (options.fastAnswer) {
|
|
18204
19513
|
return this.runFastAnswer(options, startMs, taskId);
|
|
18205
19514
|
}
|
|
@@ -18314,6 +19623,11 @@ ${prompt}` : prompt;
|
|
|
18314
19623
|
});
|
|
18315
19624
|
tier.on("log", (e) => this.emit("log", e));
|
|
18316
19625
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
19626
|
+
tier.on("section:complete", (e) => {
|
|
19627
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
19628
|
+
this.currentRunCompleted.push(e);
|
|
19629
|
+
this.emit("section:complete", e);
|
|
19630
|
+
});
|
|
18317
19631
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
18318
19632
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
18319
19633
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -18460,6 +19774,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
18460
19774
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
18461
19775
|
const trip = runBreaker.reason();
|
|
18462
19776
|
if (trip) {
|
|
19777
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
18463
19778
|
finalOutput = [
|
|
18464
19779
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
18465
19780
|
"",
|
|
@@ -18479,6 +19794,13 @@ ${finalOutput}` : ""
|
|
|
18479
19794
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
18480
19795
|
partialOutput: finalOutput || ""
|
|
18481
19796
|
});
|
|
19797
|
+
await this.checkpointRun(
|
|
19798
|
+
taskId,
|
|
19799
|
+
options.prompt,
|
|
19800
|
+
finalOutput || "",
|
|
19801
|
+
"cancelled",
|
|
19802
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
19803
|
+
);
|
|
18482
19804
|
runError = null;
|
|
18483
19805
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
18484
19806
|
this.emit("run:budget-exceeded", {
|
|
@@ -18487,9 +19809,17 @@ ${finalOutput}` : ""
|
|
|
18487
19809
|
partialOutput: finalOutput || ""
|
|
18488
19810
|
});
|
|
18489
19811
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
19812
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
18490
19813
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
18491
19814
|
runError = null;
|
|
18492
19815
|
} else {
|
|
19816
|
+
await this.checkpointRun(
|
|
19817
|
+
taskId,
|
|
19818
|
+
options.prompt,
|
|
19819
|
+
finalOutput || "",
|
|
19820
|
+
"error",
|
|
19821
|
+
err instanceof Error ? err.message : String(err)
|
|
19822
|
+
);
|
|
18493
19823
|
runError = err;
|
|
18494
19824
|
throw err;
|
|
18495
19825
|
}
|
|
@@ -18701,14 +20031,14 @@ ${finalOutput}` : ""
|
|
|
18701
20031
|
init_constants();
|
|
18702
20032
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
18703
20033
|
function globalDir(homeDir = os10__default.default.homedir()) {
|
|
18704
|
-
return
|
|
20034
|
+
return path34__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
18705
20035
|
}
|
|
18706
20036
|
function cloudSessionPath(dir = globalDir()) {
|
|
18707
|
-
return
|
|
20037
|
+
return path34__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
18708
20038
|
}
|
|
18709
20039
|
function loadCloudSession(dir = globalDir()) {
|
|
18710
20040
|
try {
|
|
18711
|
-
const raw =
|
|
20041
|
+
const raw = fs31__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
18712
20042
|
const parsed = JSON.parse(raw);
|
|
18713
20043
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
18714
20044
|
return parsed;
|
|
@@ -18717,17 +20047,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
18717
20047
|
}
|
|
18718
20048
|
}
|
|
18719
20049
|
function saveCloudSession(session, dir = globalDir()) {
|
|
18720
|
-
|
|
20050
|
+
fs31__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
18721
20051
|
const file = cloudSessionPath(dir);
|
|
18722
|
-
|
|
20052
|
+
fs31__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
18723
20053
|
try {
|
|
18724
|
-
|
|
20054
|
+
fs31__default.default.chmodSync(file, 384);
|
|
18725
20055
|
} catch {
|
|
18726
20056
|
}
|
|
18727
20057
|
}
|
|
18728
20058
|
function clearCloudSession(dir = globalDir()) {
|
|
18729
20059
|
try {
|
|
18730
|
-
|
|
20060
|
+
fs31__default.default.rmSync(cloudSessionPath(dir));
|
|
18731
20061
|
} catch {
|
|
18732
20062
|
}
|
|
18733
20063
|
}
|
|
@@ -18899,16 +20229,16 @@ var CloudClient = class _CloudClient {
|
|
|
18899
20229
|
return (await this.refresh()).accessToken;
|
|
18900
20230
|
}
|
|
18901
20231
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
18902
|
-
async authed(
|
|
18903
|
-
const call = (token) => fetch(this.url(
|
|
20232
|
+
async authed(path39, init = {}) {
|
|
20233
|
+
const call = (token) => fetch(this.url(path39), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
18904
20234
|
let res = await call(await this.accessToken());
|
|
18905
20235
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
18906
20236
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
18907
20237
|
const text = await res.text();
|
|
18908
20238
|
return text ? JSON.parse(text) : {};
|
|
18909
20239
|
}
|
|
18910
|
-
authedGet(
|
|
18911
|
-
return this.authed(
|
|
20240
|
+
authedGet(path39) {
|
|
20241
|
+
return this.authed(path39);
|
|
18912
20242
|
}
|
|
18913
20243
|
async fetchMe(token) {
|
|
18914
20244
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -20683,10 +22013,10 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
20683
22013
|
}
|
|
20684
22014
|
} catch {
|
|
20685
22015
|
}
|
|
20686
|
-
const configPath =
|
|
22016
|
+
const configPath = path34__default.default.join(workspacePath, ".cascade", "config.json");
|
|
20687
22017
|
try {
|
|
20688
|
-
await
|
|
20689
|
-
await
|
|
22018
|
+
await fs11__default.default.mkdir(path34__default.default.dirname(configPath), { recursive: true });
|
|
22019
|
+
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
20690
22020
|
} catch (err) {
|
|
20691
22021
|
const msg = err instanceof Error ? err.message : String(err);
|
|
20692
22022
|
dispatch({
|
|
@@ -20737,9 +22067,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
20737
22067
|
if (msg.includes("non-text parts")) return;
|
|
20738
22068
|
originalLog(...args);
|
|
20739
22069
|
};
|
|
20740
|
-
const store = new MemoryStore(
|
|
22070
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
20741
22071
|
storeRef.current = store;
|
|
20742
|
-
globalStoreRef.current = new MemoryStore(
|
|
22072
|
+
globalStoreRef.current = new MemoryStore(path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
20743
22073
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
20744
22074
|
setIdentities(identityRows);
|
|
20745
22075
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -20807,14 +22137,14 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
20807
22137
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
20808
22138
|
onExport: async (fmt) => {
|
|
20809
22139
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
20810
|
-
const exportPath =
|
|
22140
|
+
const exportPath = path34__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
|
|
20811
22141
|
if (fmt === "json") {
|
|
20812
|
-
await
|
|
22142
|
+
await fs11__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
20813
22143
|
} else {
|
|
20814
22144
|
const markdown = state.messages.map((msg) => `## ${msg.role.toUpperCase()} \u2014 ${msg.timestamp}
|
|
20815
22145
|
|
|
20816
22146
|
${msg.content}`).join("\n\n");
|
|
20817
|
-
await
|
|
22147
|
+
await fs11__default.default.writeFile(exportPath, markdown, "utf-8");
|
|
20818
22148
|
}
|
|
20819
22149
|
},
|
|
20820
22150
|
onRollback: async () => {
|
|
@@ -20824,7 +22154,7 @@ ${msg.content}`).join("\n\n");
|
|
|
20824
22154
|
if (!snapshots.length) return "No file snapshots found for this session.";
|
|
20825
22155
|
for (const { filePath, content } of snapshots) {
|
|
20826
22156
|
try {
|
|
20827
|
-
await
|
|
22157
|
+
await fs11__default.default.writeFile(filePath, content, "utf-8");
|
|
20828
22158
|
} catch (err) {
|
|
20829
22159
|
console.error(`Restore failed: ${filePath}`, err);
|
|
20830
22160
|
}
|
|
@@ -21049,10 +22379,12 @@ ${lastUser.content}`;
|
|
|
21049
22379
|
onContinue: async (args) => {
|
|
21050
22380
|
const cascade = cascadeRef.current;
|
|
21051
22381
|
if (!cascade) return "Not ready yet.";
|
|
21052
|
-
if (!cascade.hasResumableRun()) return "Nothing to continue \u2014 no task was stopped by the budget cap.";
|
|
21053
22382
|
const n = args[0] ? parseInt(args[0].replace(/[^0-9]/g, ""), 10) : NaN;
|
|
21054
|
-
const
|
|
21055
|
-
|
|
22383
|
+
const opts = Number.isFinite(n) && n > 0 ? { maxTokens: n } : {};
|
|
22384
|
+
const prompt = await cascade.prepareDurableResume(opts) ?? cascade.prepareResume(opts);
|
|
22385
|
+
if (!prompt) {
|
|
22386
|
+
return "Nothing to continue \u2014 no interrupted task was found for this workspace.";
|
|
22387
|
+
}
|
|
21056
22388
|
await handleSubmit(prompt);
|
|
21057
22389
|
return "";
|
|
21058
22390
|
}
|
|
@@ -21705,9 +23037,9 @@ function stringifySlashOutput(val) {
|
|
|
21705
23037
|
}
|
|
21706
23038
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
21707
23039
|
if (!query) return "Usage: /search <query>";
|
|
21708
|
-
const dbPath =
|
|
23040
|
+
const dbPath = path34__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
21709
23041
|
try {
|
|
21710
|
-
await
|
|
23042
|
+
await fs11__default.default.access(dbPath);
|
|
21711
23043
|
} catch {
|
|
21712
23044
|
return "No database found. Start a conversation first.";
|
|
21713
23045
|
}
|
|
@@ -21739,7 +23071,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
21739
23071
|
async function diagnoseRuntime(config, workspacePath) {
|
|
21740
23072
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
21741
23073
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
21742
|
-
const store = new MemoryStore(
|
|
23074
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
21743
23075
|
try {
|
|
21744
23076
|
const sessions2 = store.listSessions(void 0, 3);
|
|
21745
23077
|
return [
|
|
@@ -21758,7 +23090,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
21758
23090
|
}
|
|
21759
23091
|
async function showRecentLogs(args, workspacePath) {
|
|
21760
23092
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
21761
|
-
const store = new MemoryStore(
|
|
23093
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
21762
23094
|
try {
|
|
21763
23095
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
21764
23096
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -21770,7 +23102,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
21770
23102
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
21771
23103
|
const sessionId = args[0];
|
|
21772
23104
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
21773
|
-
const store = new MemoryStore(
|
|
23105
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
21774
23106
|
try {
|
|
21775
23107
|
const session = store.getSession(sessionId);
|
|
21776
23108
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -22198,10 +23530,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
22198
23530
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
22199
23531
|
};
|
|
22200
23532
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
22201
|
-
const configDir =
|
|
22202
|
-
await
|
|
22203
|
-
const configPath =
|
|
22204
|
-
await
|
|
23533
|
+
const configDir = path34__default.default.join(workspacePath, ".cascade");
|
|
23534
|
+
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
23535
|
+
const configPath = path34__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
23536
|
+
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
22205
23537
|
savedConfigRef.current = config;
|
|
22206
23538
|
dispatchRef.current({ type: "GO_DONE" });
|
|
22207
23539
|
} catch (err) {
|
|
@@ -22542,14 +23874,14 @@ function printTelemetryBanner() {
|
|
|
22542
23874
|
async function initCommand(workspacePath = process.cwd()) {
|
|
22543
23875
|
const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
22544
23876
|
try {
|
|
22545
|
-
const configDir =
|
|
22546
|
-
await
|
|
22547
|
-
const mdPath =
|
|
23877
|
+
const configDir = path34__default.default.join(workspacePath, ".cascade");
|
|
23878
|
+
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
23879
|
+
const mdPath = path34__default.default.join(workspacePath, "CASCADE.md");
|
|
22548
23880
|
if (!await fileExists(mdPath)) {
|
|
22549
23881
|
await createDefaultCascadeMd(workspacePath);
|
|
22550
23882
|
spin.succeed(chalk7__default.default.green("Created CASCADE.md"));
|
|
22551
23883
|
}
|
|
22552
|
-
const ignorePath =
|
|
23884
|
+
const ignorePath = path34__default.default.join(workspacePath, ".cascadeignore");
|
|
22553
23885
|
if (!await fileExists(ignorePath)) {
|
|
22554
23886
|
await createDefaultIgnoreFile(workspacePath);
|
|
22555
23887
|
spin.succeed(chalk7__default.default.green("Created .cascadeignore"));
|
|
@@ -22558,7 +23890,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
22558
23890
|
console.log();
|
|
22559
23891
|
console.log(chalk7__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
22560
23892
|
console.log();
|
|
22561
|
-
const configPath =
|
|
23893
|
+
const configPath = path34__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
22562
23894
|
if (await fileExists(configPath)) {
|
|
22563
23895
|
console.log(chalk7__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
22564
23896
|
console.log();
|
|
@@ -22577,7 +23909,7 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
22577
23909
|
}
|
|
22578
23910
|
async function fileExists(p) {
|
|
22579
23911
|
try {
|
|
22580
|
-
await
|
|
23912
|
+
await fs11__default.default.access(p);
|
|
22581
23913
|
return true;
|
|
22582
23914
|
} catch {
|
|
22583
23915
|
return false;
|
|
@@ -22589,7 +23921,7 @@ init_constants();
|
|
|
22589
23921
|
var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
|
|
22590
23922
|
async function readJson(file) {
|
|
22591
23923
|
try {
|
|
22592
|
-
const raw = await
|
|
23924
|
+
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
22593
23925
|
return JSON.parse(raw);
|
|
22594
23926
|
} catch {
|
|
22595
23927
|
return null;
|
|
@@ -22616,7 +23948,7 @@ function fromEnv(env) {
|
|
|
22616
23948
|
return out;
|
|
22617
23949
|
}
|
|
22618
23950
|
async function fromClaudeCode(home) {
|
|
22619
|
-
const file =
|
|
23951
|
+
const file = path34__default.default.join(home, ".claude", ".credentials.json");
|
|
22620
23952
|
const data = await readJson(file);
|
|
22621
23953
|
if (!data) return [];
|
|
22622
23954
|
const oauth = data["claudeAiOauth"];
|
|
@@ -22639,7 +23971,7 @@ async function fromClaudeCode(home) {
|
|
|
22639
23971
|
return [];
|
|
22640
23972
|
}
|
|
22641
23973
|
async function fromCodex(home) {
|
|
22642
|
-
const file =
|
|
23974
|
+
const file = path34__default.default.join(home, ".codex", "auth.json");
|
|
22643
23975
|
const data = await readJson(file);
|
|
22644
23976
|
if (!data) return [];
|
|
22645
23977
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -22662,7 +23994,7 @@ async function fromCodex(home) {
|
|
|
22662
23994
|
return [];
|
|
22663
23995
|
}
|
|
22664
23996
|
async function fromGemini(home) {
|
|
22665
|
-
const file =
|
|
23997
|
+
const file = path34__default.default.join(home, ".gemini", "oauth_creds.json");
|
|
22666
23998
|
const data = await readJson(file);
|
|
22667
23999
|
const accessToken = str(data?.["access_token"]);
|
|
22668
24000
|
if (!accessToken) return [];
|
|
@@ -22678,7 +24010,7 @@ async function fromGemini(home) {
|
|
|
22678
24010
|
}
|
|
22679
24011
|
async function fromCopilot(home) {
|
|
22680
24012
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
22681
|
-
const file =
|
|
24013
|
+
const file = path34__default.default.join(home, ".config", "github-copilot", name);
|
|
22682
24014
|
const data = await readJson(file);
|
|
22683
24015
|
if (!data) continue;
|
|
22684
24016
|
for (const value of Object.values(data)) {
|
|
@@ -22732,7 +24064,7 @@ async function doctorCommand() {
|
|
|
22732
24064
|
checks.push({
|
|
22733
24065
|
label: "Cascade config",
|
|
22734
24066
|
ok: true,
|
|
22735
|
-
detail: `Loaded ${
|
|
24067
|
+
detail: `Loaded ${path34__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
22736
24068
|
});
|
|
22737
24069
|
const providers = [
|
|
22738
24070
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -22815,17 +24147,17 @@ async function doctorCommand() {
|
|
|
22815
24147
|
const detail = c.detail ? chalk7__default.default.gray(` \u2014 ${c.detail}`) : "";
|
|
22816
24148
|
console.log(`${icon} ${label}${detail}`);
|
|
22817
24149
|
}
|
|
22818
|
-
const
|
|
24150
|
+
const failures2 = checks.filter((c) => !c.ok);
|
|
22819
24151
|
console.log();
|
|
22820
|
-
if (
|
|
24152
|
+
if (failures2.length === 0) {
|
|
22821
24153
|
console.log(chalk7__default.default.green(" All checks passed!\n"));
|
|
22822
24154
|
} else {
|
|
22823
|
-
const critical =
|
|
24155
|
+
const critical = failures2.filter((c) => c.label.includes("Node") || c.label.includes("API key"));
|
|
22824
24156
|
if (critical.length) {
|
|
22825
24157
|
console.log(chalk7__default.default.yellow(` ${critical.length} issue(s) need attention.
|
|
22826
24158
|
`));
|
|
22827
24159
|
} else {
|
|
22828
|
-
console.log(chalk7__default.default.gray(` ${
|
|
24160
|
+
console.log(chalk7__default.default.gray(` ${failures2.length} optional item(s) not configured.
|
|
22829
24161
|
`));
|
|
22830
24162
|
}
|
|
22831
24163
|
}
|
|
@@ -22843,7 +24175,7 @@ async function checkEndpoint(url) {
|
|
|
22843
24175
|
}
|
|
22844
24176
|
}
|
|
22845
24177
|
async function indexCommand(dirPath) {
|
|
22846
|
-
const workspace =
|
|
24178
|
+
const workspace = path34__default.default.resolve(dirPath || process.cwd());
|
|
22847
24179
|
const cm = new ConfigManager(workspace);
|
|
22848
24180
|
await cm.load();
|
|
22849
24181
|
const config = cm.getConfig();
|
|
@@ -22854,8 +24186,8 @@ async function indexCommand(dirPath) {
|
|
|
22854
24186
|
process.exitCode = 1;
|
|
22855
24187
|
return;
|
|
22856
24188
|
}
|
|
22857
|
-
const dbPath = config.codeIndex?.dbPath ||
|
|
22858
|
-
|
|
24189
|
+
const dbPath = config.codeIndex?.dbPath || path34__default.default.join(workspace, ".cascade", "code-index.db");
|
|
24190
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(dbPath), { recursive: true });
|
|
22859
24191
|
const db = new Database2__default.default(dbPath);
|
|
22860
24192
|
db.pragma("journal_mode = WAL");
|
|
22861
24193
|
const complete = chatCompleterFromProviders(config.providers);
|
|
@@ -23344,7 +24676,7 @@ function openBrowser2(url) {
|
|
|
23344
24676
|
}
|
|
23345
24677
|
function storePathFor(name) {
|
|
23346
24678
|
const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
|
|
23347
|
-
return
|
|
24679
|
+
return path34__default.default.join(os10__default.default.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
|
|
23348
24680
|
}
|
|
23349
24681
|
function defaultName(url) {
|
|
23350
24682
|
try {
|
|
@@ -23846,7 +25178,7 @@ function aggregateCostStats(sessions2, opts = {}) {
|
|
|
23846
25178
|
}
|
|
23847
25179
|
|
|
23848
25180
|
// src/dashboard/server.ts
|
|
23849
|
-
var __dirname$1 =
|
|
25181
|
+
var __dirname$1 = path34__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
25182
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
23851
25183
|
var DashboardServer = class {
|
|
23852
25184
|
app;
|
|
@@ -24126,14 +25458,14 @@ var DashboardServer = class {
|
|
|
24126
25458
|
*/
|
|
24127
25459
|
persistConfig() {
|
|
24128
25460
|
try {
|
|
24129
|
-
const configPath =
|
|
24130
|
-
|
|
24131
|
-
|
|
25461
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
25462
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(configPath), { recursive: true });
|
|
25463
|
+
fs31__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
24132
25464
|
} catch (err) {
|
|
24133
25465
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
24134
25466
|
}
|
|
24135
25467
|
try {
|
|
24136
|
-
saveGlobalCredentials(
|
|
25468
|
+
saveGlobalCredentials(path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
24137
25469
|
} catch (err) {
|
|
24138
25470
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
24139
25471
|
}
|
|
@@ -24148,15 +25480,15 @@ var DashboardServer = class {
|
|
|
24148
25480
|
resolveDashboardSecret() {
|
|
24149
25481
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
24150
25482
|
if (fromConfig) return fromConfig;
|
|
24151
|
-
const secretPath =
|
|
25483
|
+
const secretPath = path34__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
24152
25484
|
try {
|
|
24153
|
-
if (
|
|
24154
|
-
const existing =
|
|
25485
|
+
if (fs31__default.default.existsSync(secretPath)) {
|
|
25486
|
+
const existing = fs31__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
24155
25487
|
if (existing.length >= 16) return existing;
|
|
24156
25488
|
}
|
|
24157
25489
|
const generated = crypto.randomUUID();
|
|
24158
|
-
|
|
24159
|
-
|
|
25490
|
+
fs31__default.default.mkdirSync(path34__default.default.dirname(secretPath), { recursive: true });
|
|
25491
|
+
fs31__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
24160
25492
|
if (this.config.dashboard.auth) {
|
|
24161
25493
|
console.warn(
|
|
24162
25494
|
`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 +25515,7 @@ var DashboardServer = class {
|
|
|
24183
25515
|
// ── Setup ─────────────────────────────────────
|
|
24184
25516
|
getGlobalStore() {
|
|
24185
25517
|
if (!this.globalStore) {
|
|
24186
|
-
const globalDbPath =
|
|
25518
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24187
25519
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
24188
25520
|
}
|
|
24189
25521
|
return this.globalStore;
|
|
@@ -24563,12 +25895,12 @@ ${prompt}`;
|
|
|
24563
25895
|
}
|
|
24564
25896
|
}
|
|
24565
25897
|
watchRuntimeChanges() {
|
|
24566
|
-
const workspaceDbPath =
|
|
24567
|
-
const globalDbPath =
|
|
25898
|
+
const workspaceDbPath = path34__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
25899
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24568
25900
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
24569
25901
|
for (const watchPath of watchPaths) {
|
|
24570
|
-
if (!
|
|
24571
|
-
|
|
25902
|
+
if (!fs31__default.default.existsSync(watchPath)) continue;
|
|
25903
|
+
fs31__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
24572
25904
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
24573
25905
|
});
|
|
24574
25906
|
}
|
|
@@ -24690,7 +26022,7 @@ ${prompt}`;
|
|
|
24690
26022
|
const sessionId = req.params.id;
|
|
24691
26023
|
this.store.deleteSession(sessionId);
|
|
24692
26024
|
this.store.deleteRuntimeSession(sessionId);
|
|
24693
|
-
const globalDbPath =
|
|
26025
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24694
26026
|
const globalStore = new MemoryStore(globalDbPath);
|
|
24695
26027
|
try {
|
|
24696
26028
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -24783,7 +26115,7 @@ ${prompt}`;
|
|
|
24783
26115
|
});
|
|
24784
26116
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
24785
26117
|
const body = req.body;
|
|
24786
|
-
const globalDbPath =
|
|
26118
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24787
26119
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
24788
26120
|
const globalStore = new MemoryStore(globalDbPath);
|
|
24789
26121
|
try {
|
|
@@ -24806,7 +26138,7 @@ ${prompt}`;
|
|
|
24806
26138
|
});
|
|
24807
26139
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
24808
26140
|
this.store.deleteAllRuntimeNodes();
|
|
24809
|
-
const globalDbPath =
|
|
26141
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24810
26142
|
const globalStore = new MemoryStore(globalDbPath);
|
|
24811
26143
|
try {
|
|
24812
26144
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -24892,12 +26224,12 @@ ${prompt}`;
|
|
|
24892
26224
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
24893
26225
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
24894
26226
|
try {
|
|
24895
|
-
const configPath =
|
|
24896
|
-
const existing =
|
|
26227
|
+
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
26228
|
+
const existing = fs31__default.default.existsSync(configPath) ? JSON.parse(fs31__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
24897
26229
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
24898
26230
|
const tmp = configPath + ".tmp";
|
|
24899
|
-
|
|
24900
|
-
|
|
26231
|
+
fs31__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
26232
|
+
fs31__default.default.renameSync(tmp, configPath);
|
|
24901
26233
|
res.json({ ok: true });
|
|
24902
26234
|
} catch (err) {
|
|
24903
26235
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -24925,7 +26257,7 @@ ${prompt}`;
|
|
|
24925
26257
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
24926
26258
|
const scope = req.query["scope"] ?? "workspace";
|
|
24927
26259
|
if (scope === "global") {
|
|
24928
|
-
const globalDbPath =
|
|
26260
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
24929
26261
|
const globalStore = new MemoryStore(globalDbPath);
|
|
24930
26262
|
try {
|
|
24931
26263
|
res.json({
|
|
@@ -25266,13 +26598,13 @@ ${prompt}`;
|
|
|
25266
26598
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
25267
26599
|
}
|
|
25268
26600
|
});
|
|
25269
|
-
const prodPath =
|
|
25270
|
-
const devPath =
|
|
25271
|
-
const webDistPath =
|
|
25272
|
-
if (
|
|
26601
|
+
const prodPath = path34__default.default.resolve(__dirname$1, "../web/dist");
|
|
26602
|
+
const devPath = path34__default.default.resolve(__dirname$1, "../../web/dist");
|
|
26603
|
+
const webDistPath = fs31__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
26604
|
+
if (fs31__default.default.existsSync(webDistPath)) {
|
|
25273
26605
|
this.app.use(express__default.default.static(webDistPath));
|
|
25274
26606
|
this.app.get("*", (_req, res) => {
|
|
25275
|
-
res.sendFile(
|
|
26607
|
+
res.sendFile(path34__default.default.join(webDistPath, "index.html"));
|
|
25276
26608
|
});
|
|
25277
26609
|
} else {
|
|
25278
26610
|
this.app.get("/", (_req, res) => {
|
|
@@ -25293,7 +26625,7 @@ init_constants();
|
|
|
25293
26625
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
25294
26626
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
25295
26627
|
const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
25296
|
-
const store = new MemoryStore(
|
|
26628
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
25297
26629
|
const server = new DashboardServer(config, store, workspacePath);
|
|
25298
26630
|
server.watchRuntimeChanges();
|
|
25299
26631
|
const gThis = globalThis;
|
|
@@ -25325,7 +26657,7 @@ init_constants();
|
|
|
25325
26657
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
25326
26658
|
const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
|
|
25327
26659
|
identity.command("list").description("List all available identities").action(() => {
|
|
25328
|
-
const store = new MemoryStore(
|
|
26660
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
25329
26661
|
try {
|
|
25330
26662
|
const identities = store.listIdentities();
|
|
25331
26663
|
console.log(chalk7__default.default.bold("\n Identities:"));
|
|
@@ -25345,7 +26677,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
25345
26677
|
}
|
|
25346
26678
|
});
|
|
25347
26679
|
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(
|
|
26680
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
25349
26681
|
try {
|
|
25350
26682
|
if (options.default) {
|
|
25351
26683
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -25371,7 +26703,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
25371
26703
|
}
|
|
25372
26704
|
});
|
|
25373
26705
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
25374
|
-
const store = new MemoryStore(
|
|
26706
|
+
const store = new MemoryStore(path34__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
25375
26707
|
try {
|
|
25376
26708
|
const identities = store.listIdentities();
|
|
25377
26709
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
@@ -25520,11 +26852,11 @@ async function exportCommand(options = {}) {
|
|
|
25520
26852
|
let store;
|
|
25521
26853
|
try {
|
|
25522
26854
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
25523
|
-
const workspaceDbPath =
|
|
25524
|
-
const globalDbPath =
|
|
26855
|
+
const workspaceDbPath = path34__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
26856
|
+
const globalDbPath = path34__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
25525
26857
|
let dbPath = globalDbPath;
|
|
25526
26858
|
try {
|
|
25527
|
-
await
|
|
26859
|
+
await fs11__default.default.access(workspaceDbPath);
|
|
25528
26860
|
dbPath = workspaceDbPath;
|
|
25529
26861
|
} catch {
|
|
25530
26862
|
}
|
|
@@ -25563,8 +26895,8 @@ async function exportCommand(options = {}) {
|
|
|
25563
26895
|
const ext = format === "json" ? ".json" : ".md";
|
|
25564
26896
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
25565
26897
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
25566
|
-
const outPath = options.output ?
|
|
25567
|
-
await
|
|
26898
|
+
const outPath = options.output ? path34__default.default.resolve(options.output) : path34__default.default.join(process.cwd(), defaultFile);
|
|
26899
|
+
await fs11__default.default.writeFile(outPath, content, "utf-8");
|
|
25568
26900
|
spin.succeed(chalk7__default.default.green(`Exported to ${chalk7__default.default.white(outPath)}`));
|
|
25569
26901
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
25570
26902
|
console.log();
|
|
@@ -25791,11 +27123,11 @@ async function statsCommand() {
|
|
|
25791
27123
|
dotenv__default.default.config();
|
|
25792
27124
|
function warnIfBuildIsStale() {
|
|
25793
27125
|
try {
|
|
25794
|
-
const here =
|
|
27126
|
+
const here = path34__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
27127
|
for (const rel of ["..", "../.."]) {
|
|
25796
|
-
const pkgPath =
|
|
25797
|
-
if (!
|
|
25798
|
-
const pkg = JSON.parse(
|
|
27128
|
+
const pkgPath = path34__default.default.join(here, rel, "package.json");
|
|
27129
|
+
if (!fs31__default.default.existsSync(pkgPath)) continue;
|
|
27130
|
+
const pkg = JSON.parse(fs31__default.default.readFileSync(pkgPath, "utf-8"));
|
|
25799
27131
|
if (pkg.name !== "cascade-ai") continue;
|
|
25800
27132
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
25801
27133
|
console.error(
|